diff options
Diffstat (limited to 'dom/browser-element/BrowserElementAudioChannel.cpp')
-rw-r--r-- | dom/browser-element/BrowserElementAudioChannel.cpp | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/dom/browser-element/BrowserElementAudioChannel.cpp b/dom/browser-element/BrowserElementAudioChannel.cpp new file mode 100644 index 000000000..c9c29e2ea --- /dev/null +++ b/dom/browser-element/BrowserElementAudioChannel.cpp @@ -0,0 +1,591 @@ +/* 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> +BrowserElementAudioChannel::Create(nsPIDOMWindowInner* aWindow, + nsIFrameLoader* aFrameLoader, + nsIBrowserElementAPI* aAPI, + AudioChannel aAudioChannel, + ErrorResult& aRv) +{ + RefPtr<BrowserElementAudioChannel> 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<nsIObserverService> 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<nsIObserverService> 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<nsPIDOMWindowInner> window = GetOwner(); + if (!window) { + return NS_ERROR_FAILURE; + } + + mFrameWindow = window->GetScriptableTop(); + mFrameWindow = mFrameWindow->GetOuterWindow(); + return NS_OK; + } + + nsCOMPtr<nsIDocShell> docShell; + nsresult rv = mFrameLoader->GetDocShell(getter_AddRefs(docShell)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (docShell) { + nsCOMPtr<nsPIDOMWindowOuter> 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<JSObject*> aGivenProto) +{ + return BrowserElementAudioChannelBinding::Wrap(aCx, this, aGivenProto); +} + +AudioChannel +BrowserElementAudioChannel::Name() const +{ + MOZ_ASSERT(NS_IsMainThread()); + return mAudioChannel; +} + +namespace { + +class BaseRunnable : public Runnable +{ +protected: + nsCOMPtr<nsPIDOMWindowInner> mParentWindow; + nsCOMPtr<nsPIDOMWindowOuter> mFrameWindow; + RefPtr<DOMRequest> 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<AudioChannelService> 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<JS::Value> 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<JS::Value> 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<JS::Value> 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<JS::Value> value(aCx); + mRequest->FireSuccess(value); + } +}; + +} // anonymous namespace + +already_AddRefed<dom::DOMRequest> +BrowserElementAudioChannel::GetVolume(ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mFrameWindow) { + nsCOMPtr<nsIDOMDOMRequest> request; + aRv = mBrowserElementAPI->GetAudioChannelVolume((uint32_t)mAudioChannel, + getter_AddRefs(request)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return request.forget().downcast<DOMRequest>(); + } + + RefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner()); + + nsCOMPtr<nsIRunnable> runnable = + new GetVolumeRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel); + NS_DispatchToMainThread(runnable); + + return domRequest.forget(); +} + +already_AddRefed<dom::DOMRequest> +BrowserElementAudioChannel::SetVolume(float aVolume, ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mFrameWindow) { + nsCOMPtr<nsIDOMDOMRequest> request; + aRv = mBrowserElementAPI->SetAudioChannelVolume((uint32_t)mAudioChannel, + aVolume, + getter_AddRefs(request)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return request.forget().downcast<DOMRequest>(); + } + + RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate(); + if (service) { + service->SetAudioChannelVolume(mFrameWindow, mAudioChannel, aVolume); + } + + RefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner()); + nsCOMPtr<nsIRunnable> runnable = new FireSuccessRunnable(GetOwner(), + mFrameWindow, + domRequest, + mAudioChannel); + NS_DispatchToMainThread(runnable); + + return domRequest.forget(); +} + +already_AddRefed<dom::DOMRequest> +BrowserElementAudioChannel::GetMuted(ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mFrameWindow) { + nsCOMPtr<nsIDOMDOMRequest> request; + aRv = mBrowserElementAPI->GetAudioChannelMuted((uint32_t)mAudioChannel, + getter_AddRefs(request)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return request.forget().downcast<DOMRequest>(); + } + + RefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner()); + + nsCOMPtr<nsIRunnable> runnable = + new GetMutedRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel); + NS_DispatchToMainThread(runnable); + + return domRequest.forget(); +} + +already_AddRefed<dom::DOMRequest> +BrowserElementAudioChannel::SetMuted(bool aMuted, ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mFrameWindow) { + nsCOMPtr<nsIDOMDOMRequest> request; + aRv = mBrowserElementAPI->SetAudioChannelMuted((uint32_t)mAudioChannel, + aMuted, + getter_AddRefs(request)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return request.forget().downcast<DOMRequest>(); + } + + RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate(); + if (service) { + service->SetAudioChannelMuted(mFrameWindow, mAudioChannel, aMuted); + } + + RefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner()); + nsCOMPtr<nsIRunnable> runnable = new FireSuccessRunnable(GetOwner(), + mFrameWindow, + domRequest, + mAudioChannel); + NS_DispatchToMainThread(runnable); + + return domRequest.forget(); +} + +already_AddRefed<dom::DOMRequest> +BrowserElementAudioChannel::IsActive(ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mState != eStateUnknown) { + RefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner()); + + nsCOMPtr<nsIRunnable> runnable = + new IsActiveRunnable(GetOwner(), mFrameWindow, domRequest, mAudioChannel, + mState == eStateActive); + NS_DispatchToMainThread(runnable); + + return domRequest.forget(); + } + + if (!mFrameWindow) { + nsCOMPtr<nsIDOMDOMRequest> request; + aRv = mBrowserElementAPI->IsAudioChannelActive((uint32_t)mAudioChannel, + getter_AddRefs(request)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return request.forget().downcast<DOMRequest>(); + } + + RefPtr<DOMRequest> domRequest = new DOMRequest(GetOwner()); + + nsCOMPtr<nsIRunnable> 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<nsISupportsPRUint64> 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<nsIDocument> 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<nsIPrincipal> principal = doc->NodePrincipal(); + nsCOMPtr<nsIURI> 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<nsITabParent> iTabParent = do_QueryInterface(aSubject); + if (!iTabParent) { + return NS_ERROR_FAILURE; + } + + RefPtr<TabParent> 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<nsPIDOMWindowOuter> window = element->OwnerDoc()->GetWindow(); + if (window == mFrameWindow && !IsSystemAppWindow(window)) { + aIsNested = true; + return NS_OK; + } + + return NS_OK; +} + +} // dom namespace +} // mozilla namespace |