/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "BrowserElementAudioChannel.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/dom/BrowserElementAudioChannelBinding.h" #include "mozilla/dom/DOMRequest.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/TabParent.h" #include "mozilla/dom/ToJSValue.h" #include "AudioChannelService.h" #include "nsContentUtils.h" #include "nsIBrowserElementAPI.h" #include "nsIDocShell.h" #include "nsIDOMDOMRequest.h" #include "nsIObserverService.h" #include "nsISupportsPrimitives.h" #include "nsITabParent.h" #include "nsPIDOMWindow.h" namespace mozilla { namespace dom { NS_IMPL_ADDREF_INHERITED(BrowserElementAudioChannel, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(BrowserElementAudioChannel, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BrowserElementAudioChannel) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_INHERITED(BrowserElementAudioChannel, DOMEventTargetHelper, mFrameLoader, mFrameWindow, mTabParent, mBrowserElementAPI) /* static */ already_AddRefed BrowserElementAudioChannel::Create(nsPIDOMWindowInner* aWindow, nsIFrameLoader* aFrameLoader, nsIBrowserElementAPI* aAPI, AudioChannel aAudioChannel, ErrorResult& aRv) { RefPtr ac = new BrowserElementAudioChannel(aWindow, aFrameLoader, aAPI, aAudioChannel); aRv = ac->Initialize(); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("BrowserElementAudioChannel, Create, channel = %p, type = %d\n", ac.get(), aAudioChannel)); return ac.forget(); } BrowserElementAudioChannel::BrowserElementAudioChannel( nsPIDOMWindowInner* aWindow, nsIFrameLoader* aFrameLoader, nsIBrowserElementAPI* aAPI, AudioChannel aAudioChannel) : DOMEventTargetHelper(aWindow) , mFrameLoader(aFrameLoader) , mBrowserElementAPI(aAPI) , mAudioChannel(aAudioChannel) , mState(eStateUnknown) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { nsAutoString name; AudioChannelService::GetAudioChannelString(aAudioChannel, name); nsAutoCString topic; topic.Assign("audiochannel-activity-"); topic.Append(NS_ConvertUTF16toUTF8(name)); obs->AddObserver(this, topic.get(), true); } } BrowserElementAudioChannel::~BrowserElementAudioChannel() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { nsAutoString name; AudioChannelService::GetAudioChannelString(mAudioChannel, name); nsAutoCString topic; topic.Assign("audiochannel-activity-"); topic.Append(NS_ConvertUTF16toUTF8(name)); obs->RemoveObserver(this, topic.get()); } } nsresult BrowserElementAudioChannel::Initialize() { if (!mFrameLoader) { nsCOMPtr window = GetOwner(); if (!window) { return NS_ERROR_FAILURE; } mFrameWindow = window->GetScriptableTop(); mFrameWindow = mFrameWindow->GetOuterWindow(); return NS_OK; } nsCOMPtr docShell; nsresult rv = mFrameLoader->GetDocShell(getter_AddRefs(docShell)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (docShell) { nsCOMPtr window = docShell->GetWindow(); if (!window) { return NS_ERROR_FAILURE; } mFrameWindow = window->GetScriptableTop(); mFrameWindow = mFrameWindow->GetOuterWindow(); return NS_OK; } rv = mFrameLoader->GetTabParent(getter_AddRefs(mTabParent)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(mTabParent); return NS_OK; } JSObject* BrowserElementAudioChannel::WrapObject(JSContext *aCx, JS::Handle aGivenProto) { return BrowserElementAudioChannelBinding::Wrap(aCx, this, aGivenProto); } AudioChannel BrowserElementAudioChannel::Name() const { MOZ_ASSERT(NS_IsMainThread()); return mAudioChannel; } namespace { class BaseRunnable : public Runnable { protected: nsCOMPtr mParentWindow; nsCOMPtr mFrameWindow; RefPtr mRequest; AudioChannel mAudioChannel; virtual void DoWork(AudioChannelService* aService, JSContext* aCx) = 0; public: BaseRunnable(nsPIDOMWindowInner* aParentWindow, nsPIDOMWindowOuter* aFrameWindow, DOMRequest* aRequest, AudioChannel aAudioChannel) : mParentWindow(aParentWindow) , mFrameWindow(aFrameWindow) , mRequest(aRequest) , mAudioChannel(aAudioChannel) {} NS_IMETHOD Run() override { RefPtr service = AudioChannelService::GetOrCreate(); if (!service) { return NS_OK; } AutoJSAPI jsapi; if (!jsapi.Init(mParentWindow)) { mRequest->FireError(NS_ERROR_FAILURE); return NS_OK; } DoWork(service, jsapi.cx()); return NS_OK; } }; class GetVolumeRunnable final : public BaseRunnable { public: GetVolumeRunnable(nsPIDOMWindowInner* aParentWindow, nsPIDOMWindowOuter* aFrameWindow, DOMRequest* aRequest, AudioChannel aAudioChannel) : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel) {} protected: virtual void DoWork(AudioChannelService* aService, JSContext* aCx) override { float volume = aService->GetAudioChannelVolume(mFrameWindow, mAudioChannel); JS::Rooted value(aCx); if (!ToJSValue(aCx, volume, &value)) { mRequest->FireError(NS_ERROR_FAILURE); return; } mRequest->FireSuccess(value); } }; class GetMutedRunnable final : public BaseRunnable { public: GetMutedRunnable(nsPIDOMWindowInner* aParentWindow, nsPIDOMWindowOuter* aFrameWindow, DOMRequest* aRequest, AudioChannel aAudioChannel) : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel) {} protected: virtual void DoWork(AudioChannelService* aService, JSContext* aCx) override { bool muted = aService->GetAudioChannelMuted(mFrameWindow, mAudioChannel); JS::Rooted value(aCx); if (!ToJSValue(aCx, muted, &value)) { mRequest->FireError(NS_ERROR_FAILURE); return; } mRequest->FireSuccess(value); } }; class IsActiveRunnable final : public BaseRunnable { bool mActive; bool mValueKnown; public: IsActiveRunnable(nsPIDOMWindowInner* aParentWindow, nsPIDOMWindowOuter* aFrameWindow, DOMRequest* aRequest, AudioChannel aAudioChannel, bool aActive) : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel) , mActive(aActive) , mValueKnown(true) {} IsActiveRunnable(nsPIDOMWindowInner* aParentWindow, nsPIDOMWindowOuter* aFrameWindow, DOMRequest* aRequest, AudioChannel aAudioChannel) : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel) , mActive(true) , mValueKnown(false) {} protected: virtual void DoWork(AudioChannelService* aService, JSContext* aCx) override { if (!mValueKnown) { mActive = aService->IsAudioChannelActive(mFrameWindow, mAudioChannel); } JS::Rooted value(aCx); if (!ToJSValue(aCx, mActive, &value)) { mRequest->FireError(NS_ERROR_FAILURE); return; } mRequest->FireSuccess(value); } }; class FireSuccessRunnable final : public BaseRunnable { public: FireSuccessRunnable(nsPIDOMWindowInner* aParentWindow, nsPIDOMWindowOuter* aFrameWindow, DOMRequest* aRequest, AudioChannel aAudioChannel) : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel) {} protected: virtual void DoWork(AudioChannelService* aService, JSContext* aCx) override { JS::Rooted value(aCx); mRequest->FireSuccess(value); } }; } // anonymous namespace already_AddRefed BrowserElementAudioChannel::GetVolume(ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); if (!mFrameWindow) { nsCOMPtr request; aRv = mBrowserElementAPI->GetAudioChannelVolume((uint32_t)mAudioChannel, getter_AddRefs(request)); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return request.forget().downcast(); } RefPtr domRequest = new DOMRequest(GetOwner()); nsCOMPtr runnable = new GetVolumeRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel); NS_DispatchToMainThread(runnable); return domRequest.forget(); } already_AddRefed BrowserElementAudioChannel::SetVolume(float aVolume, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); if (!mFrameWindow) { nsCOMPtr request; aRv = mBrowserElementAPI->SetAudioChannelVolume((uint32_t)mAudioChannel, aVolume, getter_AddRefs(request)); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return request.forget().downcast(); } RefPtr service = AudioChannelService::GetOrCreate(); if (service) { service->SetAudioChannelVolume(mFrameWindow, mAudioChannel, aVolume); } RefPtr domRequest = new DOMRequest(GetOwner()); nsCOMPtr runnable = new FireSuccessRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel); NS_DispatchToMainThread(runnable); return domRequest.forget(); } already_AddRefed BrowserElementAudioChannel::GetMuted(ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); if (!mFrameWindow) { nsCOMPtr request; aRv = mBrowserElementAPI->GetAudioChannelMuted((uint32_t)mAudioChannel, getter_AddRefs(request)); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return request.forget().downcast(); } RefPtr domRequest = new DOMRequest(GetOwner()); nsCOMPtr runnable = new GetMutedRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel); NS_DispatchToMainThread(runnable); return domRequest.forget(); } already_AddRefed BrowserElementAudioChannel::SetMuted(bool aMuted, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); if (!mFrameWindow) { nsCOMPtr request; aRv = mBrowserElementAPI->SetAudioChannelMuted((uint32_t)mAudioChannel, aMuted, getter_AddRefs(request)); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return request.forget().downcast(); } RefPtr service = AudioChannelService::GetOrCreate(); if (service) { service->SetAudioChannelMuted(mFrameWindow, mAudioChannel, aMuted); } RefPtr domRequest = new DOMRequest(GetOwner()); nsCOMPtr runnable = new FireSuccessRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel); NS_DispatchToMainThread(runnable); return domRequest.forget(); } already_AddRefed BrowserElementAudioChannel::IsActive(ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); if (mState != eStateUnknown) { RefPtr domRequest = new DOMRequest(GetOwner()); nsCOMPtr runnable = new IsActiveRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel, mState == eStateActive); NS_DispatchToMainThread(runnable); return domRequest.forget(); } if (!mFrameWindow) { nsCOMPtr request; aRv = mBrowserElementAPI->IsAudioChannelActive((uint32_t)mAudioChannel, getter_AddRefs(request)); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return request.forget().downcast(); } RefPtr domRequest = new DOMRequest(GetOwner()); nsCOMPtr runnable = new IsActiveRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel); NS_DispatchToMainThread(runnable); return domRequest.forget(); } NS_IMETHODIMP BrowserElementAudioChannel::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { nsAutoString name; AudioChannelService::GetAudioChannelString(mAudioChannel, name); nsAutoCString topic; topic.Assign("audiochannel-activity-"); topic.Append(NS_ConvertUTF16toUTF8(name)); if (strcmp(topic.get(), aTopic)) { return NS_OK; } // Message received from the child. if (!mFrameWindow) { if (mTabParent == aSubject) { ProcessStateChanged(aData); } return NS_OK; } nsCOMPtr wrapper = do_QueryInterface(aSubject); if (!wrapper) { bool isNested = false; nsresult rv = IsFromNestedFrame(aSubject, isNested); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (isNested) { ProcessStateChanged(aData); } return NS_OK; } uint64_t windowID; nsresult rv = wrapper->GetData(&windowID); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (windowID != mFrameWindow->WindowID()) { return NS_OK; } ProcessStateChanged(aData); return NS_OK; } void BrowserElementAudioChannel::ProcessStateChanged(const char16_t* aData) { MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("BrowserElementAudioChannel, ProcessStateChanged, this = %p, " "type = %d\n", this, mAudioChannel)); nsAutoString value(aData); mState = value.EqualsASCII("active") ? eStateActive : eStateInactive; DispatchTrustedEvent(NS_LITERAL_STRING("activestatechanged")); } bool BrowserElementAudioChannel::IsSystemAppWindow(nsPIDOMWindowOuter* aWindow) const { nsCOMPtr doc = aWindow->GetExtantDoc(); if (!doc) { return false; } if (nsContentUtils::IsChromeDoc(doc)) { return true; } nsAdoptingCString systemAppUrl = mozilla::Preferences::GetCString("b2g.system_startup_url"); if (!systemAppUrl) { return false; } nsCOMPtr principal = doc->NodePrincipal(); nsCOMPtr uri; principal->GetURI(getter_AddRefs(uri)); if (uri) { nsAutoCString spec; uri->GetSpec(spec); if (spec.Equals(systemAppUrl)) { return true; } } return false; } nsresult BrowserElementAudioChannel::IsFromNestedFrame(nsISupports* aSubject, bool& aIsNested) const { aIsNested = false; nsCOMPtr iTabParent = do_QueryInterface(aSubject); if (!iTabParent) { return NS_ERROR_FAILURE; } RefPtr tabParent = TabParent::GetFrom(iTabParent); if (!tabParent) { return NS_ERROR_FAILURE; } Element* element = tabParent->GetOwnerElement(); if (!element) { return NS_ERROR_FAILURE; } // Since the normal OOP processes are opened out from b2g process, the owner // of their tabParent are the same - system app window. Therefore, in order // to find the case of nested MozFrame, we need to exclude this situation. nsCOMPtr window = element->OwnerDoc()->GetWindow(); if (window == mFrameWindow && !IsSystemAppWindow(window)) { aIsNested = true; return NS_OK; } return NS_OK; } } // dom namespace } // mozilla namespace