summaryrefslogtreecommitdiffstats
path: root/dom/browser-element/BrowserElementAudioChannel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/browser-element/BrowserElementAudioChannel.cpp')
-rw-r--r--dom/browser-element/BrowserElementAudioChannel.cpp591
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