diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 52ea0892981..42710fc079c 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -888,8 +888,6 @@ bin/libfreebl_32int64_3.so @RESPATH@/components/DataStoreImpl.js @RESPATH@/components/dom_datastore.xpt -@RESPATH@/components/dom_audiochannel.xpt - ; Shutdown Terminator @RESPATH@/components/nsTerminatorTelemetry.js @RESPATH@/components/terminator.manifest diff --git a/dom/audiochannel/AudioChannelAgent.cpp b/dom/audiochannel/AudioChannelAgent.cpp index 31b3146de12..aaef0c336e6 100644 --- a/dom/audiochannel/AudioChannelAgent.cpp +++ b/dom/audiochannel/AudioChannelAgent.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AudioChannelAgent.h" +#include "AudioChannelCommon.h" #include "AudioChannelService.h" #include "nsIDOMWindow.h" #include "nsPIDOMWindow.h" @@ -25,6 +26,8 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioChannelAgent) AudioChannelAgent::AudioChannelAgent() : mAudioChannelType(AUDIO_AGENT_CHANNEL_ERROR) , mIsRegToService(false) + , mVisible(true) + , mWithVideo(false) { } @@ -63,10 +66,21 @@ AudioChannelAgent::InitWithWeakCallback(nsIDOMWindow* aWindow, /* useWeakRef = */ true); } +/* void initWithVideo(in nsIDOMWindow window, in long channelType, + * in nsIAudioChannelAgentCallback callback, in boolean weak); */ +NS_IMETHODIMP +AudioChannelAgent::InitWithVideo(nsIDOMWindow* aWindow, int32_t aChannelType, + nsIAudioChannelAgentCallback *aCallback, + bool aUseWeakRef) +{ + return InitInternal(aWindow, aChannelType, aCallback, aUseWeakRef, + /* withVideo = */ true); +} + nsresult AudioChannelAgent::InitInternal(nsIDOMWindow* aWindow, int32_t aChannelType, nsIAudioChannelAgentCallback *aCallback, - bool aUseWeakRef) + bool aUseWeakRef, bool aWithVideo) { // We syncd the enum of channel type between nsIAudioChannelAgent.idl and // AudioChannelBinding.h the same. @@ -86,12 +100,12 @@ AudioChannelAgent::InitInternal(nsIDOMWindow* aWindow, int32_t aChannelType, } if (aWindow) { - nsCOMPtr topWindow; - aWindow->GetScriptableTop(getter_AddRefs(topWindow)); - MOZ_ASSERT(topWindow); + nsCOMPtr pWindow = do_QueryInterface(aWindow); + if (!pWindow->IsInnerWindow()) { + pWindow = pWindow->GetCurrentInnerWindow(); + } - mWindow = do_QueryInterface(topWindow); - mWindow = mWindow->GetOuterWindow(); + mWindow = pWindow.forget(); } mAudioChannelType = aChannelType; @@ -102,26 +116,23 @@ AudioChannelAgent::InitInternal(nsIDOMWindow* aWindow, int32_t aChannelType, mCallback = aCallback; } + mWithVideo = aWithVideo; + return NS_OK; } /* boolean startPlaying (); */ -NS_IMETHODIMP AudioChannelAgent::StartPlaying(float *aVolume, bool* aMuted) +NS_IMETHODIMP AudioChannelAgent::StartPlaying(int32_t *_retval) { - MOZ_ASSERT(aVolume); - MOZ_ASSERT(aMuted); - - nsRefPtr service = AudioChannelService::GetOrCreate(); + AudioChannelService *service = AudioChannelService::GetOrCreateAudioChannelService(); if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR || service == nullptr || mIsRegToService) { return NS_ERROR_FAILURE; } service->RegisterAudioChannelAgent(this, - static_cast(mAudioChannelType)); - - service->GetState(mWindow, mAudioChannelType, aVolume, aMuted); - + static_cast(mAudioChannelType), mWithVideo); + *_retval = service->GetState(this, !mVisible); mIsRegToService = true; return NS_OK; } @@ -134,12 +145,36 @@ NS_IMETHODIMP AudioChannelAgent::StopPlaying(void) return NS_ERROR_FAILURE; } - nsRefPtr service = AudioChannelService::GetOrCreate(); + AudioChannelService *service = AudioChannelService::GetOrCreateAudioChannelService(); service->UnregisterAudioChannelAgent(this); mIsRegToService = false; return NS_OK; } +/* void setVisibilityState (in boolean visible); */ +NS_IMETHODIMP AudioChannelAgent::SetVisibilityState(bool visible) +{ + bool oldVisibility = mVisible; + + nsCOMPtr callback = GetCallback(); + + mVisible = visible; + if (mIsRegToService && oldVisibility != mVisible && callback) { + AudioChannelService *service = AudioChannelService::GetOrCreateAudioChannelService(); + callback->CanPlayChanged(service->GetState(this, !mVisible)); + } + return NS_OK; +} + +void AudioChannelAgent::NotifyAudioChannelStateChanged() +{ + nsCOMPtr callback = GetCallback(); + if (callback) { + AudioChannelService *service = AudioChannelService::GetOrCreateAudioChannelService(); + callback->CanPlayChanged(service->GetState(this, !mVisible)); + } +} + already_AddRefed AudioChannelAgent::GetCallback() { @@ -158,17 +193,20 @@ AudioChannelAgent::WindowVolumeChanged() return; } - float volume = 1.0; - bool muted = false; - - nsRefPtr service = AudioChannelService::GetOrCreate(); - service->GetState(mWindow, mAudioChannelType, &volume, &muted); - - callback->WindowVolumeChanged(volume, muted); + callback->WindowVolumeChanged(); } -uint64_t -AudioChannelAgent::WindowID() const +NS_IMETHODIMP +AudioChannelAgent::GetWindowVolume(float* aVolume) { - return mWindow ? mWindow->WindowID() : 0; + NS_ENSURE_ARG_POINTER(aVolume); + + nsCOMPtr win = do_QueryInterface(mWindow); + if (!win) { + *aVolume = 1.0f; + return NS_OK; + } + + *aVolume = win->GetAudioGlobalVolume(); + return NS_OK; } diff --git a/dom/audiochannel/AudioChannelAgent.h b/dom/audiochannel/AudioChannelAgent.h index ed70c7e3b22..f2259386f6d 100644 --- a/dom/audiochannel/AudioChannelAgent.h +++ b/dom/audiochannel/AudioChannelAgent.h @@ -17,7 +17,7 @@ #define NS_AUDIOCHANNELAGENT_CID {0xf27688e2, 0x3dd7, 0x11e2, \ {0x90, 0x4e, 0x10, 0xbf, 0x48, 0xd6, 0x4b, 0xd4}} -class nsPIDOMWindow; +class nsIDOMWindow; namespace mozilla { namespace dom { @@ -32,16 +32,15 @@ public: NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgent) AudioChannelAgent(); + virtual void NotifyAudioChannelStateChanged(); void WindowVolumeChanged(); - nsPIDOMWindow* Window() const + nsIDOMWindow* Window() const { return mWindow; } - uint64_t WindowID() const; - private: virtual ~AudioChannelAgent(); @@ -51,15 +50,15 @@ private: nsresult InitInternal(nsIDOMWindow* aWindow, int32_t aAudioAgentType, nsIAudioChannelAgentCallback* aCallback, - bool aUseWeakRef); + bool aUseWeakRef, bool aWithVideo=false); - nsCOMPtr mWindow; + nsCOMPtr mWindow; nsCOMPtr mCallback; - nsWeakPtr mWeakCallback; - int32_t mAudioChannelType; bool mIsRegToService; + bool mVisible; + bool mWithVideo; }; } // namespace dom diff --git a/dom/audiochannel/AudioChannelCommon.h b/dom/audiochannel/AudioChannelCommon.h new file mode 100644 index 00000000000..b0de2904140 --- /dev/null +++ b/dom/audiochannel/AudioChannelCommon.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_audiochannelcommon_h__ +#define mozilla_dom_audiochannelcommon_h__ + +namespace mozilla { +namespace dom { + +enum AudioChannelState { + AUDIO_CHANNEL_STATE_NORMAL = 0, + AUDIO_CHANNEL_STATE_MUTED, + AUDIO_CHANNEL_STATE_FADED, + AUDIO_CHANNEL_STATE_LAST +}; + +} // namespace dom +} // namespace mozilla + +#endif + diff --git a/dom/audiochannel/AudioChannelService.cpp b/dom/audiochannel/AudioChannelService.cpp index 6ce82c4315e..87eceb2a8ad 100644 --- a/dom/audiochannel/AudioChannelService.cpp +++ b/dom/audiochannel/AudioChannelService.cpp @@ -5,6 +5,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AudioChannelService.h" +#include "AudioChannelServiceChild.h" #include "base/basictypes.h" @@ -12,11 +13,9 @@ #include "mozilla/StaticPtr.h" #include "mozilla/unused.h" -#include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentParent.h" #include "nsContentUtils.h" -#include "nsIScriptSecurityManager.h" #include "nsISupportsPrimitives.h" #include "nsThreadUtils.h" #include "nsHashPropertyBag.h" @@ -38,54 +37,18 @@ using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::hal; -namespace { - -void -NotifyChannelActive(uint64_t aWindowID, AudioChannel aAudioChannel, - bool aActive) +// When a inner-window is destroyed we have to mute all the related +// AudioChannelAgents. In order to do this we have to notify them after purging +// AudioChannelService::mAgents. +struct MOZ_STACK_CLASS WindowDestroyedEnumeratorData { - nsCOMPtr observerService = - services::GetObserverService(); - if (NS_WARN_IF(!observerService)) { - return; - } + explicit WindowDestroyedEnumeratorData(uint64_t aInnerID) + : mInnerID(aInnerID) + {} - nsCOMPtr wrapper = - do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID); - if (!wrapper) { - return; - } - - wrapper->SetData(aWindowID); - - nsAutoString name; - AudioChannelService::GetAudioChannelString(aAudioChannel, name); - - nsAutoCString topic; - topic.Assign("audiochannel-activity-"); - topic.Append(NS_ConvertUTF16toUTF8(name)); - - observerService->NotifyObservers(wrapper, topic.get(), - aActive - ? MOZ_UTF16("active") : MOZ_UTF16("inactive")); -} - -already_AddRefed -GetTopWindow(nsIDOMWindow* aWindow) -{ - MOZ_ASSERT(aWindow); - - nsCOMPtr topWindow; - aWindow->GetScriptableTop(getter_AddRefs(topWindow)); - MOZ_ASSERT(topWindow); - - nsCOMPtr window = do_QueryInterface(topWindow); - window = window->GetOuterWindow(); - - return window.forget(); -} - -} // anonymous namespace + nsTArray> mAgents; + uint64_t mInnerID; +}; StaticRefPtr gAudioChannelService; @@ -101,52 +64,63 @@ static const nsAttrValue::EnumTable kMozAudioChannelAttributeTable[] = { { nullptr } }; -/* static */ already_AddRefed -AudioChannelService::GetOrCreate() +// static +AudioChannelService* +AudioChannelService::GetAudioChannelService() { MOZ_ASSERT(NS_IsMainThread()); - if (!gAudioChannelService) { - gAudioChannelService = new AudioChannelService(); + if (!XRE_IsParentProcess()) { + return AudioChannelServiceChild::GetAudioChannelService(); } - nsRefPtr service = gAudioChannelService.get(); - return service.forget(); + return gAudioChannelService; + +} + +// static +AudioChannelService* +AudioChannelService::GetOrCreateAudioChannelService() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!XRE_IsParentProcess()) { + return AudioChannelServiceChild::GetOrCreateAudioChannelService(); + } + + // If we already exist, exit early + if (gAudioChannelService) { + return gAudioChannelService; + } + + // Create new instance, register, return + nsRefPtr service = new AudioChannelService(); + MOZ_ASSERT(service); + + gAudioChannelService = service; + return gAudioChannelService; } void AudioChannelService::Shutdown() { - if (gAudioChannelService) { - if (XRE_GetProcessType() == GeckoProcessType_Default) { - nsCOMPtr obs = mozilla::services::GetObserverService(); - if (obs) { - obs->RemoveObserver(gAudioChannelService, "ipc:content-shutdown"); - obs->RemoveObserver(gAudioChannelService, "xpcom-shutdown"); - obs->RemoveObserver(gAudioChannelService, "inner-window-destroyed"); -#ifdef MOZ_WIDGET_GONK - // To monitor the volume settings based on audio channel. - obs->RemoveObserver(gAudioChannelService, "mozsettings-changed"); -#endif - } - } + if (!XRE_IsParentProcess()) { + return AudioChannelServiceChild::Shutdown(); + } + if (gAudioChannelService) { gAudioChannelService = nullptr; } } -NS_INTERFACE_MAP_BEGIN(AudioChannelService) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAudioChannelService) - NS_INTERFACE_MAP_ENTRY(nsIAudioChannelService) - NS_INTERFACE_MAP_ENTRY(nsIObserver) -NS_INTERFACE_MAP_END - -NS_IMPL_ADDREF(AudioChannelService) -NS_IMPL_RELEASE(AudioChannelService) +NS_IMPL_ISUPPORTS(AudioChannelService, nsIObserver, nsITimerCallback) AudioChannelService::AudioChannelService() - : mDisabled(false) - , mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN) +: mCurrentHigherChannel(-1) +, mCurrentVisibleHigherChannel(-1) +, mPlayableHiddenContentChildID(CONTENT_PROCESS_ID_UNKNOWN) +, mDisabled(false) +, mDefChannelChildID(CONTENT_PROCESS_ID_UNKNOWN) { if (XRE_IsParentProcess()) { nsCOMPtr obs = mozilla::services::GetObserverService(); @@ -168,29 +142,23 @@ AudioChannelService::~AudioChannelService() void AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent, - AudioChannel aChannel) + AudioChannel aChannel, + bool aWithVideo) { if (mDisabled) { return; } - uint64_t windowID = aAgent->WindowID(); - AudioChannelWindow* winData = mWindows.LookupOrAdd(windowID); - - MOZ_ASSERT(!winData->mAgents.Get(aAgent)); - - AudioChannel* audioChannel = new AudioChannel(aChannel); - winData->mAgents.Put(aAgent, audioChannel); - - ++winData->mChannels[(uint32_t)aChannel].mNumberOfAgents; - - // The first one, we must inform the BrowserElementAudioChannel. - if (winData->mChannels[(uint32_t)aChannel].mNumberOfAgents == 1) { - NotifyChannelActive(aAgent->WindowID(), aChannel, true); - } + AudioChannelAgentData* data = new AudioChannelAgentData(aChannel, + true /* aElementHidden */, + AUDIO_CHANNEL_STATE_MUTED /* aState */, + aWithVideo); + mAgents.Put(aAgent, data); + RegisterType(aChannel, CONTENT_PROCESS_ID_MAIN, aWithVideo); // If this is the first agent for this window, we must notify the observers. - if (winData->mAgents.Count() == 1) { + uint32_t count = CountWindow(aAgent->Window()); + if (count == 1) { nsCOMPtr observerService = services::GetObserverService(); if (observerService) { @@ -201,6 +169,61 @@ AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent, } } +void +AudioChannelService::RegisterType(AudioChannel aChannel, uint64_t aChildID, + bool aWithVideo) +{ + if (mDisabled) { + return; + } + + AudioChannelInternalType type = GetInternalType(aChannel, true); + mChannelCounters[type].AppendElement(aChildID); + + if (XRE_IsParentProcess()) { + + // We must keep the childIds in order to decide which app is allowed to play + // with then telephony channel. + if (aChannel == AudioChannel::Telephony) { + RegisterTelephonyChild(aChildID); + } + + // Since there is another telephony registered, we can unregister old one + // immediately. + if (mDeferTelChannelTimer && aChannel == AudioChannel::Telephony) { + mDeferTelChannelTimer->Cancel(); + mDeferTelChannelTimer = nullptr; + UnregisterTypeInternal(aChannel, mTimerElementHidden, mTimerChildID, + false); + } + + if (aWithVideo) { + mWithVideoChildIDs.AppendElement(aChildID); + } + + // No hidden content channel can be playable if there is a content channel + // in foreground (bug 855208), nor if there is a normal channel with video + // in foreground (bug 894249). + if (type == AUDIO_CHANNEL_INT_CONTENT || + (type == AUDIO_CHANNEL_INT_NORMAL && + mWithVideoChildIDs.Contains(aChildID))) { + mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; + } + // One hidden content channel can be playable only when there is no any + // content channel in the foreground, and no normal channel with video in + // foreground. + else if (type == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && + mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { + mPlayableHiddenContentChildID = aChildID; + } + + // In order to avoid race conditions, it's safer to notify any existing + // agent any time a new one is registered. + SendAudioChannelChangedNotification(aChildID); + SendNotification(); + } +} + void AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent) { @@ -208,23 +231,12 @@ AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent) return; } - uint64_t windowID = aAgent->WindowID(); - AudioChannelWindow* winData = nullptr; - if (!mWindows.Get(windowID, &winData)) { - return; - } + nsAutoPtr data; + mAgents.RemoveAndForget(aAgent, data); - nsAutoPtr audioChannel; - winData->mAgents.RemoveAndForget(aAgent, audioChannel); - if (audioChannel) { - MOZ_ASSERT(winData->mChannels[(uint32_t)*audioChannel].mNumberOfAgents > 0); - - --winData->mChannels[(uint32_t)*audioChannel].mNumberOfAgents; - - // The last one, we must inform the BrowserElementAudioChannel. - if (winData->mChannels[(uint32_t)*audioChannel].mNumberOfAgents == 0) { - NotifyChannelActive(aAgent->WindowID(), *audioChannel, false); - } + if (data) { + UnregisterType(data->mChannel, data->mElementHidden, + CONTENT_PROCESS_ID_MAIN, data->mWithVideo); } #ifdef MOZ_WIDGET_GONK @@ -235,7 +247,8 @@ AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent) #endif // If this is the last agent for this window, we must notify the observers. - if (winData->mAgents.Count() == 0) { + uint32_t count = CountWindow(aAgent->Window()); + if (count == 0) { nsCOMPtr observerService = services::GetObserverService(); if (observerService) { @@ -247,118 +260,616 @@ AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent) } void -AudioChannelService::GetState(nsPIDOMWindow* aWindow, uint32_t aAudioChannel, - float* aVolume, bool* aMuted) +AudioChannelService::UnregisterType(AudioChannel aChannel, + bool aElementHidden, + uint64_t aChildID, + bool aWithVideo) { - MOZ_ASSERT(!aWindow || aWindow->IsOuterWindow()); - MOZ_ASSERT(aVolume && aMuted); - MOZ_ASSERT(aAudioChannel < NUMBER_OF_AUDIO_CHANNELS); - - *aVolume = 1.0; - *aMuted = false; - - if (!aWindow || !aWindow->IsOuterWindow()) { + if (mDisabled) { return; } - AudioChannelWindow* winData = nullptr; - nsCOMPtr window = aWindow; + // There are two reasons to defer the decrease of telephony channel. + // 1. User can have time to remove device from his ear before music resuming. + // 2. Give BT SCO to be disconnected before starting to connect A2DP. + if (XRE_IsParentProcess()) { - // The volume must be calculated based on the window hierarchy. Here we go up - // to the top window and we calculate the volume and the muted flag. - do { - if (mWindows.Get(window->WindowID(), &winData)) { - *aVolume *= winData->mChannels[aAudioChannel].mVolume; - *aMuted = *aMuted || winData->mChannels[aAudioChannel].mMuted; + if (aChannel == AudioChannel::Telephony) { + UnregisterTelephonyChild(aChildID); } - *aVolume *= window->GetAudioVolume(); - *aMuted = *aMuted || window->GetAudioMuted(); - - nsCOMPtr win; - window->GetScriptableParent(getter_AddRefs(win)); - if (window == win) { - break; + if (aChannel == AudioChannel::Telephony && + (mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].Length() + + mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].Length()) == 1) { + mTimerElementHidden = aElementHidden; + mTimerChildID = aChildID; + mDeferTelChannelTimer = do_CreateInstance("@mozilla.org/timer;1"); + mDeferTelChannelTimer->InitWithCallback(this, 1500, nsITimer::TYPE_ONE_SHOT); + return; } + } - window = do_QueryInterface(win); - - // If there is no parent, or we are the toplevel we don't continue. - } while (window && window != aWindow); + UnregisterTypeInternal(aChannel, aElementHidden, aChildID, aWithVideo); } -PLDHashOperator -AudioChannelService::TelephonyChannelIsActiveEnumerator( - const uint64_t& aWindowID, - nsAutoPtr& aWinData, - void* aPtr) +void +AudioChannelService::UnregisterTypeInternal(AudioChannel aChannel, + bool aElementHidden, + uint64_t aChildID, + bool aWithVideo) { - bool* isActive = static_cast(aPtr); - *isActive = - aWinData->mChannels[(uint32_t)AudioChannel::Telephony].mNumberOfAgents != 0 && - !aWinData->mChannels[(uint32_t)AudioChannel::Telephony].mMuted; - return *isActive ? PL_DHASH_STOP : PL_DHASH_NEXT; + // The array may contain multiple occurrence of this appId but + // this should remove only the first one. + AudioChannelInternalType type = GetInternalType(aChannel, aElementHidden); + MOZ_ASSERT(mChannelCounters[type].Contains(aChildID)); + mChannelCounters[type].RemoveElement(aChildID); + + // In order to avoid race conditions, it's safer to notify any existing + // agent any time a new one is registered. + if (XRE_IsParentProcess()) { + // No hidden content channel is playable if the original playable hidden + // process does not need to play audio from background anymore. + if (aChannel == AudioChannel::Content && + mPlayableHiddenContentChildID == aChildID && + !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID)) { + mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; + } + + if (aWithVideo) { + MOZ_ASSERT(mWithVideoChildIDs.Contains(aChildID)); + mWithVideoChildIDs.RemoveElement(aChildID); + } + + SendAudioChannelChangedNotification(aChildID); + SendNotification(); + } +} + +void +AudioChannelService::UpdateChannelType(AudioChannel aChannel, + uint64_t aChildID, + bool aElementHidden, + bool aElementWasHidden) +{ + // Calculate the new and old internal type and update the hashtable if needed. + AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden); + AudioChannelInternalType oldType = GetInternalType(aChannel, aElementWasHidden); + + if (newType != oldType) { + mChannelCounters[newType].AppendElement(aChildID); + MOZ_ASSERT(mChannelCounters[oldType].Contains(aChildID)); + mChannelCounters[oldType].RemoveElement(aChildID); + } + + // No hidden content channel can be playable if there is a content channel + // in foreground (bug 855208), nor if there is a normal channel with video + // in foreground (bug 894249). + if (newType == AUDIO_CHANNEL_INT_CONTENT || + (newType == AUDIO_CHANNEL_INT_NORMAL && + mWithVideoChildIDs.Contains(aChildID))) { + mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; + } + // If there is no content channel in foreground and no normal channel with + // video in foreground, the last content channel which goes from foreground + // to background can be playable. + else if (oldType == AUDIO_CHANNEL_INT_CONTENT && + newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && + mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { + mPlayableHiddenContentChildID = aChildID; + } +} + +AudioChannelState +AudioChannelService::GetState(AudioChannelAgent* aAgent, bool aElementHidden) +{ + AudioChannelAgentData* data; + if (!mAgents.Get(aAgent, &data)) { + return AUDIO_CHANNEL_STATE_MUTED; + } + + bool oldElementHidden = data->mElementHidden; + // Update visibility. + data->mElementHidden = aElementHidden; + + data->mState = GetStateInternal(data->mChannel, CONTENT_PROCESS_ID_MAIN, + aElementHidden, oldElementHidden); + #ifdef MOZ_WIDGET_GONK + /** Only modify the speaker status when + * (1) apps in the foreground. + * (2) apps in the backgrund and inactive. + * Notice : check the state when the visible status is stable, because there + * has lantency in passing the visibility events. + **/ + bool active = AnyAudioChannelIsActive(); + if (aElementHidden == oldElementHidden && + (!aElementHidden || (aElementHidden && !active))) { + for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) { + mSpeakerManager[i]->SetAudioChannelActive(active); + } + } + #endif + + return data->mState; +} + +AudioChannelState +AudioChannelService::GetStateInternal(AudioChannel aChannel, uint64_t aChildID, + bool aElementHidden, + bool aElementWasHidden) +{ + UpdateChannelType(aChannel, aChildID, aElementHidden, aElementWasHidden); + + // Calculating the new and old type and update the hashtable if needed. + AudioChannelInternalType newType = GetInternalType(aChannel, aElementHidden); + AudioChannelInternalType oldType = GetInternalType(aChannel, + aElementWasHidden); + + if (newType != oldType && + (aChannel == AudioChannel::Content || + (aChannel == AudioChannel::Normal && + mWithVideoChildIDs.Contains(aChildID)))) { + SendNotification(); + } + + SendAudioChannelChangedNotification(aChildID); + + // Let play any visible audio channel. + if (!aElementHidden) { + if (CheckVolumeFadedCondition(newType, aElementHidden)) { + return AUDIO_CHANNEL_STATE_FADED; + } + return CheckTelephonyPolicy(aChannel, aChildID); + } + + // We are not visible, maybe we have to mute. + if (newType == AUDIO_CHANNEL_INT_NORMAL_HIDDEN || + (newType == AUDIO_CHANNEL_INT_CONTENT_HIDDEN && + // One process can have multiple content channels; and during the + // transition from foreground to background, its content channels will be + // updated with correct visibility status one by one. All its content + // channels should remain playable until all of their visibility statuses + // have been updated as hidden. After all its content channels have been + // updated properly as hidden, mPlayableHiddenContentChildID is used to + // check whether this background process is playable or not. + !(mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) || + (mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() && + mPlayableHiddenContentChildID == aChildID)))) { + return AUDIO_CHANNEL_STATE_MUTED; + } + + // After checking the condition on normal & content channel, if the state + // is not on muted then checking other higher channels type here. + if (ChannelsActiveWithHigherPriorityThan(newType)) { + MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN); + if (CheckVolumeFadedCondition(newType, aElementHidden)) { + return AUDIO_CHANNEL_STATE_FADED; + } + return AUDIO_CHANNEL_STATE_MUTED; + } + + return CheckTelephonyPolicy(aChannel, aChildID); +} + +AudioChannelState +AudioChannelService::CheckTelephonyPolicy(AudioChannel aChannel, + uint64_t aChildID) +{ + // Only the latest childID is allowed to play with telephony channel. + if (aChannel != AudioChannel::Telephony) { + return AUDIO_CHANNEL_STATE_NORMAL; + } + + MOZ_ASSERT(!mTelephonyChildren.IsEmpty()); + +#if DEBUG + bool found = false; + for (uint32_t i = 0, len = mTelephonyChildren.Length(); i < len; ++i) { + if (mTelephonyChildren[i].mChildID == aChildID) { + found = true; + break; + } + } + + MOZ_ASSERT(found); +#endif + + return mTelephonyChildren.LastElement().mChildID == aChildID + ? AUDIO_CHANNEL_STATE_NORMAL : AUDIO_CHANNEL_STATE_MUTED; +} + +bool +AudioChannelService::CheckVolumeFadedCondition(AudioChannelInternalType aType, + bool aElementHidden) +{ + // Only normal & content channels are considered + if (aType > AUDIO_CHANNEL_INT_CONTENT_HIDDEN) { + return false; + } + + // Consider that audio from notification is with short duration + // so just fade the volume not pause it + if (mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty() && + mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN].IsEmpty()) { + return false; + } + + // Since this element is on the foreground, it can be allowed to play always. + // So return true directly when there is any notification channel alive. + if (aElementHidden == false) { + return true; + } + + // If element is on the background, it is possible paused by channels higher + // then notification. + for (int i = AUDIO_CHANNEL_INT_LAST - 1; + i != AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN; --i) { + if (!mChannelCounters[i].IsEmpty()) { + return false; + } + } + + return true; +} + +bool +AudioChannelService::ContentOrNormalChannelIsActive() +{ + return !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() || + !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty() || + !mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty(); } bool AudioChannelService::TelephonyChannelIsActive() { - // TODO: no child process check. - - bool active = false; - mWindows.Enumerate(TelephonyChannelIsActiveEnumerator, &active); - return active; + return !mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].IsEmpty() || + !mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].IsEmpty(); } bool AudioChannelService::ProcessContentOrNormalChannelIsActive(uint64_t aChildID) { -/* TODO - AudioChannelChildData* data; - if (!mData.Get(aChildID, &data)) { - return false; - } - - return data->mChannels[(uint32_t)AudioChannel::Content].mNumberOfAgents != 0 || - data->mChannels[(uint32_t)AudioChannel::Normal].mNumberOfAgents != 0; -*/ - return true; + return mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].Contains(aChildID) || + mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].Contains(aChildID) || + mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].Contains(aChildID); } -PLDHashOperator -AudioChannelService::AnyAudioChannelIsActiveEnumerator( - const uint64_t& aWindowID, - nsAutoPtr& aWinData, - void* aPtr) +void +AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel, + bool aVisible) { - bool* isActive = static_cast(aPtr); - for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { - if (aWinData->mChannels[kMozAudioChannelAttributeTable[i].value].mNumberOfAgents - != 0) { - *isActive = true; + SetDefaultVolumeControlChannelInternal(aChannel, aVisible, + CONTENT_PROCESS_ID_MAIN); +} + +void +AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel, + bool aVisible, + uint64_t aChildID) +{ + if (!XRE_IsParentProcess()) { + return; + } + + // If this child is in the background and mDefChannelChildID is set to + // others then it means other child in the foreground already set it's + // own default channel already. + if (!aVisible && mDefChannelChildID != aChildID) { + return; + } + // Workaround for the call screen app. The call screen app is running on the + // main process, that will results in wrong visible state. Because we use the + // docshell's active state as visible state, the main process is always + // active. Therefore, we will see the strange situation that the visible + // state of the call screen is always true. If the mDefChannelChildID is set + // to others then it means other child in the foreground already set it's + // own default channel already. + // Summary : + // Child process : foreground app always can set type. + // Parent process : check the mDefChannelChildID. + else if (aChildID == CONTENT_PROCESS_ID_MAIN && + mDefChannelChildID != CONTENT_PROCESS_ID_UNKNOWN) { + return; + } + + mDefChannelChildID = aVisible ? aChildID : CONTENT_PROCESS_ID_UNKNOWN; + nsAutoString channelName; + if (aChannel == -1) { + channelName.AssignASCII("unknown"); + } else { + GetAudioChannelString(static_cast(aChannel), channelName); + } + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "default-volume-channel-changed", + channelName.get()); + } +} + +void +AudioChannelService::SendAudioChannelChangedNotification(uint64_t aChildID) +{ + if (!XRE_IsParentProcess()) { + return; + } + + nsRefPtr props = new nsHashPropertyBag(); + props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), aChildID); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(static_cast(props), + "audio-channel-process-changed", nullptr); + } + + // Calculating the most important active channel. + int32_t higher = -1; + + // Top-Down in the hierarchy for visible elements + if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION].IsEmpty()) { + higher = static_cast(AudioChannel::Publicnotification); + } + + else if (!mChannelCounters[AUDIO_CHANNEL_INT_RINGER].IsEmpty()) { + higher = static_cast(AudioChannel::Ringer); + } + + else if (!mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY].IsEmpty()) { + higher = static_cast(AudioChannel::Telephony); + } + + else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM].IsEmpty()) { + higher = static_cast(AudioChannel::Alarm); + } + + else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION].IsEmpty()) { + higher = static_cast(AudioChannel::Notification); + } + + else if (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) { + higher = static_cast(AudioChannel::Content); + } + + else if (!mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty()) { + higher = static_cast(AudioChannel::Normal); + } + + int32_t visibleHigher = higher; + + // Top-Down in the hierarchy for non-visible elements + // And we can ignore normal channel because it can't play in the background. + int32_t index; + for (index = 0; kMozAudioChannelAttributeTable[index].tag; ++index); + + for (--index; + kMozAudioChannelAttributeTable[index].value > higher && + kMozAudioChannelAttributeTable[index].value > (int16_t)AudioChannel::Normal; + --index) { + // Each channel type will be split to fg and bg for recording the state, + // so here need to do a translation. + if (mChannelCounters[index * 2 + 1].IsEmpty()) { + continue; + } + + if (kMozAudioChannelAttributeTable[index].value == (int16_t)AudioChannel::Content) { + if (mPlayableHiddenContentChildID != CONTENT_PROCESS_ID_UNKNOWN) { + higher = kMozAudioChannelAttributeTable[index].value; + break; + } + } else { + higher = kMozAudioChannelAttributeTable[index].value; break; } } - return *isActive ? PL_DHASH_STOP : PL_DHASH_NEXT; + if (higher != mCurrentHigherChannel) { + mCurrentHigherChannel = higher; + + nsString channelName; + if (mCurrentHigherChannel != -1) { + GetAudioChannelString(static_cast(mCurrentHigherChannel), + channelName); + } else { + channelName.AssignLiteral("none"); + } + + if (obs) { + obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get()); + } + } + + if (visibleHigher != mCurrentVisibleHigherChannel) { + mCurrentVisibleHigherChannel = visibleHigher; + + nsString channelName; + if (mCurrentVisibleHigherChannel != -1) { + GetAudioChannelString(static_cast(mCurrentVisibleHigherChannel), + channelName); + } else { + channelName.AssignLiteral("none"); + } + + if (obs) { + obs->NotifyObservers(nullptr, "visible-audio-channel-changed", channelName.get()); + } + } +} + +PLDHashOperator +AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent, + AudioChannelAgentData* aData, void* aUnused) +{ + MOZ_ASSERT(aAgent); + aAgent->NotifyAudioChannelStateChanged(); + return PL_DHASH_NEXT; +} + +class NotifyRunnable : public nsRunnable +{ +public: + explicit NotifyRunnable(AudioChannelService* aService) + : mService(aService) + {} + + NS_IMETHOD Run() + { + mService->Notify(); + return NS_OK; + } + +private: + nsRefPtr mService; +}; + +void +AudioChannelService::SendNotification() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mRunnable) { + return; + } + + mRunnable = new NotifyRunnable(this); + NS_DispatchToCurrentThread(mRunnable); +} + +void +AudioChannelService::Notify() +{ + MOZ_ASSERT(NS_IsMainThread()); + mRunnable = nullptr; + + // Notify any agent for the main process. + mAgents.EnumerateRead(NotifyEnumerator, nullptr); + + // Notify for the child processes. + nsTArray children; + ContentParent::GetAll(children); + for (uint32_t i = 0; i < children.Length(); i++) { + unused << children[i]->SendAudioChannelNotify(); + } +} + +NS_IMETHODIMP +AudioChannelService::Notify(nsITimer* aTimer) +{ + UnregisterTypeInternal(AudioChannel::Telephony, mTimerElementHidden, + mTimerChildID, false); + mDeferTelChannelTimer = nullptr; + return NS_OK; } bool AudioChannelService::AnyAudioChannelIsActive() { - // TODO: no child process check. - bool active = false; - mWindows.Enumerate(AnyAudioChannelIsActiveEnumerator, &active); - return active; + for (int i = AUDIO_CHANNEL_INT_LAST - 1; + i >= AUDIO_CHANNEL_INT_NORMAL; --i) { + if (!mChannelCounters[i].IsEmpty()) { + return true; + } + } + + return false; +} + +bool +AudioChannelService::ChannelsActiveWithHigherPriorityThan( + AudioChannelInternalType aType) +{ + for (int i = AUDIO_CHANNEL_INT_LAST - 1; + i != AUDIO_CHANNEL_INT_CONTENT_HIDDEN; --i) { + if (i == aType) { + return false; + } + + if (!mChannelCounters[i].IsEmpty()) { + return true; + } + } + + return false; +} + +PLDHashOperator +AudioChannelService::WindowDestroyedEnumerator(AudioChannelAgent* aAgent, + nsAutoPtr& aData, + void* aPtr) +{ + auto* data = static_cast(aPtr); + MOZ_ASSERT(data); + + nsCOMPtr window = do_QueryInterface(aAgent->Window()); + if (window && !window->IsInnerWindow()) { + window = window->GetCurrentInnerWindow(); + } + + if (!window || window->WindowID() != data->mInnerID) { + return PL_DHASH_NEXT; + } + + AudioChannelService* service = AudioChannelService::GetAudioChannelService(); + MOZ_ASSERT(service); + + service->UnregisterType(aData->mChannel, aData->mElementHidden, + CONTENT_PROCESS_ID_MAIN, aData->mWithVideo); + data->mAgents.AppendElement(aAgent); + + return PL_DHASH_REMOVE; } NS_IMETHODIMP -AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, - const char16_t* aData) +AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (!strcmp(aTopic, "xpcom-shutdown")) { mDisabled = true; - mWindows.Clear(); + } + + if (!strcmp(aTopic, "ipc:content-shutdown")) { + nsCOMPtr props = do_QueryInterface(aSubject); + if (!props) { + NS_WARNING("ipc:content-shutdown message without property bag as subject"); + return NS_OK; + } + + int32_t index; + uint64_t childID = 0; + nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), + &childID); + if (NS_SUCCEEDED(rv)) { + for (int32_t type = AUDIO_CHANNEL_INT_NORMAL; + type < AUDIO_CHANNEL_INT_LAST; + ++type) { + + while ((index = mChannelCounters[type].IndexOf(childID)) != -1) { + mChannelCounters[type].RemoveElementAt(index); + } + } + + // No hidden content channel is playable if the original playable hidden + // process shuts down. + if (mPlayableHiddenContentChildID == childID) { + mPlayableHiddenContentChildID = CONTENT_PROCESS_ID_UNKNOWN; + } + + while ((index = mWithVideoChildIDs.IndexOf(childID)) != -1) { + mWithVideoChildIDs.RemoveElementAt(index); + } + + // We don't have to remove the agents from the mAgents hashtable because if + // that table contains only agents running on the same process. + + SendAudioChannelChangedNotification(childID); + SendNotification(); + + if (mDefChannelChildID == childID) { + SetDefaultVolumeControlChannelInternal(-1, false, childID); + mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN; + } + } else { + NS_WARNING("ipc:content-shutdown message without childID property"); + } } #ifdef MOZ_WIDGET_GONK @@ -409,10 +920,10 @@ AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, return rv; } - nsAutoPtr window; - mWindows.RemoveAndForget(innerID, window); - if (window) { - window->mAgents.EnumerateRead(NotifyEnumerator, nullptr); + WindowDestroyedEnumeratorData data(innerID); + mAgents.Enumerate(WindowDestroyedEnumerator, &data); + for (uint32_t i = 0, len = data.mAgents.Length(); i < len; ++i) { + data.mAgents[i]->NotifyAudioChannelStateChanged(); } #ifdef MOZ_WIDGET_GONK @@ -423,27 +934,54 @@ AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, #endif } - else if (!strcmp(aTopic, "ipc:content-shutdown")) { - nsCOMPtr props = do_QueryInterface(aSubject); - if (!props) { - NS_WARNING("ipc:content-shutdown message without property bag as subject"); - return NS_OK; - } + return NS_OK; +} - uint64_t childID = 0; - nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), - &childID); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } +AudioChannelService::AudioChannelInternalType +AudioChannelService::GetInternalType(AudioChannel aChannel, + bool aElementHidden) +{ + switch (aChannel) { + case AudioChannel::Normal: + return aElementHidden + ? AUDIO_CHANNEL_INT_NORMAL_HIDDEN + : AUDIO_CHANNEL_INT_NORMAL; - if (mDefChannelChildID == childID) { - SetDefaultVolumeControlChannelInternal(-1, false, childID); - mDefChannelChildID = CONTENT_PROCESS_ID_UNKNOWN; - } + case AudioChannel::Content: + return aElementHidden + ? AUDIO_CHANNEL_INT_CONTENT_HIDDEN + : AUDIO_CHANNEL_INT_CONTENT; + + case AudioChannel::Notification: + return aElementHidden + ? AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN + : AUDIO_CHANNEL_INT_NOTIFICATION; + + case AudioChannel::Alarm: + return aElementHidden + ? AUDIO_CHANNEL_INT_ALARM_HIDDEN + : AUDIO_CHANNEL_INT_ALARM; + + case AudioChannel::Telephony: + return aElementHidden + ? AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN + : AUDIO_CHANNEL_INT_TELEPHONY; + + case AudioChannel::Ringer: + return aElementHidden + ? AUDIO_CHANNEL_INT_RINGER_HIDDEN + : AUDIO_CHANNEL_INT_RINGER; + + case AudioChannel::Publicnotification: + return aElementHidden + ? AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN + : AUDIO_CHANNEL_INT_PUBLICNOTIFICATION; + + default: + break; } - return NS_OK; + MOZ_CRASH("unexpected audio channel"); } struct RefreshAgentsVolumeData @@ -457,24 +995,68 @@ struct RefreshAgentsVolumeData }; PLDHashOperator -AudioChannelService::RefreshAgentsVolumeEnumerator( - AudioChannelAgent* aAgent, - AudioChannel* aUnused, - void* aPtr) +AudioChannelService::RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent, + AudioChannelAgentData* aUnused, + void* aPtr) { MOZ_ASSERT(aAgent); - aAgent->WindowVolumeChanged(); + RefreshAgentsVolumeData* data = static_cast(aPtr); + MOZ_ASSERT(data); + + nsCOMPtr window = do_QueryInterface(aAgent->Window()); + if (window && !window->IsInnerWindow()) { + window = window->GetCurrentInnerWindow(); + } + + if (window == data->mWindow) { + data->mAgents.AppendElement(aAgent); + } + return PL_DHASH_NEXT; } void AudioChannelService::RefreshAgentsVolume(nsPIDOMWindow* aWindow) { - AudioChannelWindow* winData = mWindows.Get(aWindow->WindowID()); - if (!winData) { - return; + RefreshAgentsVolumeData data(aWindow); + mAgents.EnumerateRead(RefreshAgentsVolumeEnumerator, &data); + + for (uint32_t i = 0; i < data.mAgents.Length(); ++i) { + data.mAgents[i]->WindowVolumeChanged(); + } +} + +struct CountWindowData +{ + explicit CountWindowData(nsIDOMWindow* aWindow) + : mWindow(aWindow) + , mCount(0) + {} + + nsIDOMWindow* mWindow; + uint32_t mCount; +}; + +PLDHashOperator +AudioChannelService::CountWindowEnumerator(AudioChannelAgent* aAgent, + AudioChannelAgentData* aUnused, + void* aPtr) +{ + CountWindowData* data = static_cast(aPtr); + MOZ_ASSERT(aAgent); + + if (aAgent->Window() == data->mWindow) { + ++data->mCount; } - winData->mAgents.EnumerateRead(RefreshAgentsVolumeEnumerator, nullptr); + return PL_DHASH_NEXT; +} + +uint32_t +AudioChannelService::CountWindow(nsIDOMWindow* aWindow) +{ + CountWindowData data(aWindow); + mAgents.EnumerateRead(CountWindowEnumerator, &data); + return data.mCount; } /* static */ const nsAttrValue::EnumTable* @@ -498,7 +1080,7 @@ AudioChannelService::GetAudioChannel(const nsAString& aChannel) /* static */ AudioChannel AudioChannelService::GetDefaultAudioChannel() { - nsAutoString audioChannel(Preferences::GetString("media.defaultAudioChannel")); + nsString audioChannel = Preferences::GetString("media.defaultAudioChannel"); if (audioChannel.IsEmpty()) { return AudioChannel::Normal; } @@ -532,7 +1114,7 @@ AudioChannelService::GetDefaultAudioChannelString(nsAString& aString) { aString.AssignASCII("normal"); - nsAutoString audioChannel(Preferences::GetString("media.defaultAudioChannel")); + nsString audioChannel = Preferences::GetString("media.defaultAudioChannel"); if (!audioChannel.IsEmpty()) { for (uint32_t i = 0; kMozAudioChannelAttributeTable[i].tag; ++i) { if (audioChannel.EqualsASCII(kMozAudioChannelAttributeTable[i].tag)) { @@ -543,196 +1125,38 @@ AudioChannelService::GetDefaultAudioChannelString(nsAString& aString) } } -AudioChannelService::AudioChannelWindow& -AudioChannelService::GetOrCreateWindowData(nsPIDOMWindow* aWindow) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aWindow); - MOZ_ASSERT(aWindow->IsOuterWindow()); - - AudioChannelWindow* winData = mWindows.LookupOrAdd(aWindow->WindowID()); - return *winData; -} - -float -AudioChannelService::GetAudioChannelVolume(nsPIDOMWindow* aWindow, - AudioChannel aAudioChannel) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aWindow); - MOZ_ASSERT(aWindow->IsOuterWindow()); - - AudioChannelWindow& winData = GetOrCreateWindowData(aWindow); - return winData.mChannels[(uint32_t)aAudioChannel].mVolume; -} - -NS_IMETHODIMP -AudioChannelService::GetAudioChannelVolume(nsIDOMWindow* aWindow, - unsigned short aAudioChannel, - float* aVolume) -{ - nsCOMPtr window = GetTopWindow(aWindow); - *aVolume = GetAudioChannelVolume(window, (AudioChannel)aAudioChannel); - return NS_OK; -} - void -AudioChannelService::SetAudioChannelVolume(nsPIDOMWindow* aWindow, - AudioChannel aAudioChannel, - float aVolume) +AudioChannelService::RegisterTelephonyChild(uint64_t aChildID) { - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aWindow); - MOZ_ASSERT(aWindow->IsOuterWindow()); + for (uint32_t i = 0, len = mTelephonyChildren.Length(); i < len; ++i) { + if (mTelephonyChildren[i].mChildID == aChildID) { + ++mTelephonyChildren[i].mInstances; - AudioChannelWindow& winData = GetOrCreateWindowData(aWindow); - winData.mChannels[(uint32_t)aAudioChannel].mVolume = aVolume; - RefreshAgentsVolume(aWindow); -} + if (i != len - 1) { + TelephonyChild child = mTelephonyChildren[i]; + mTelephonyChildren.RemoveElementAt(i); + mTelephonyChildren.AppendElement(child); + } -NS_IMETHODIMP -AudioChannelService::SetAudioChannelVolume(nsIDOMWindow* aWindow, - unsigned short aAudioChannel, - float aVolume) -{ - nsCOMPtr window = GetTopWindow(aWindow); - SetAudioChannelVolume(window, (AudioChannel)aAudioChannel, aVolume); - return NS_OK; -} - -bool -AudioChannelService::GetAudioChannelMuted(nsPIDOMWindow* aWindow, - AudioChannel aAudioChannel) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aWindow); - MOZ_ASSERT(aWindow->IsOuterWindow()); - - AudioChannelWindow& winData = GetOrCreateWindowData(aWindow); - return winData.mChannels[(uint32_t)aAudioChannel].mMuted; -} - -NS_IMETHODIMP -AudioChannelService::GetAudioChannelMuted(nsIDOMWindow* aWindow, - unsigned short aAudioChannel, - bool* aMuted) -{ - nsCOMPtr window = GetTopWindow(aWindow); - *aMuted = GetAudioChannelMuted(window, (AudioChannel)aAudioChannel); - return NS_OK; -} - -void -AudioChannelService::SetAudioChannelMuted(nsPIDOMWindow* aWindow, - AudioChannel aAudioChannel, - bool aMuted) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aWindow); - MOZ_ASSERT(aWindow->IsOuterWindow()); - - AudioChannelWindow& winData = GetOrCreateWindowData(aWindow); - winData.mChannels[(uint32_t)aAudioChannel].mMuted = aMuted; - RefreshAgentsVolume(aWindow); -} - -NS_IMETHODIMP -AudioChannelService::SetAudioChannelMuted(nsIDOMWindow* aWindow, - unsigned short aAudioChannel, - bool aMuted) -{ - nsCOMPtr window = GetTopWindow(aWindow); - SetAudioChannelMuted(window, (AudioChannel)aAudioChannel, aMuted); - return NS_OK; -} - -bool -AudioChannelService::IsAudioChannelActive(nsPIDOMWindow* aWindow, - AudioChannel aAudioChannel) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aWindow); - MOZ_ASSERT(aWindow->IsOuterWindow()); - - AudioChannelWindow& winData = GetOrCreateWindowData(aWindow); - return !!winData.mChannels[(uint32_t)aAudioChannel].mNumberOfAgents; -} - -NS_IMETHODIMP -AudioChannelService::IsAudioChannelActive(nsIDOMWindow* aWindow, - unsigned short aAudioChannel, - bool* aActive) -{ - nsCOMPtr window = GetTopWindow(aWindow); - *aActive = IsAudioChannelActive(window, (AudioChannel)aAudioChannel); - return NS_OK; -} -void -AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel, - bool aVisible) -{ - SetDefaultVolumeControlChannelInternal(aChannel, aVisible, - CONTENT_PROCESS_ID_MAIN); -} - -void -AudioChannelService::SetDefaultVolumeControlChannelInternal(int32_t aChannel, - bool aVisible, - uint64_t aChildID) -{ - if (XRE_GetProcessType() != GeckoProcessType_Default) { - ContentChild* cc = ContentChild::GetSingleton(); - if (cc) { - cc->SendAudioChannelChangeDefVolChannel(aChannel, aVisible); + return; } - - return; } - // If this child is in the background and mDefChannelChildID is set to - // others then it means other child in the foreground already set it's - // own default channel. - if (!aVisible && mDefChannelChildID != aChildID) { - return; - } - - // Workaround for the call screen app. The call screen app is running on the - // main process, that will results in wrong visible state. Because we use the - // docshell's active state as visible state, the main process is always - // active. Therefore, we will see the strange situation that the visible - // state of the call screen is always true. If the mDefChannelChildID is set - // to others then it means other child in the foreground already set it's - // own default channel already. - // Summary : - // Child process : foreground app always can set type. - // Parent process : check the mDefChannelChildID. - else if (aChildID == CONTENT_PROCESS_ID_MAIN && - mDefChannelChildID != CONTENT_PROCESS_ID_UNKNOWN) { - return; - } - - mDefChannelChildID = aVisible ? aChildID : CONTENT_PROCESS_ID_UNKNOWN; - nsAutoString channelName; - - if (aChannel == -1) { - channelName.AssignASCII("unknown"); - } else { - GetAudioChannelString(static_cast(aChannel), channelName); - } - - nsCOMPtr obs = mozilla::services::GetObserverService(); - if (obs) { - obs->NotifyObservers(nullptr, "default-volume-channel-changed", - channelName.get()); - } + mTelephonyChildren.AppendElement(TelephonyChild(aChildID)); } -/* static */ PLDHashOperator -AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent, - AudioChannel* aAudioChannel, - void* aUnused) +void +AudioChannelService::UnregisterTelephonyChild(uint64_t aChildID) { - aAgent->WindowVolumeChanged(); - return PL_DHASH_NEXT; -} + for (uint32_t i = 0, len = mTelephonyChildren.Length(); i < len; ++i) { + if (mTelephonyChildren[i].mChildID == aChildID) { + if (!--mTelephonyChildren[i].mInstances) { + mTelephonyChildren.RemoveElementAt(i); + } + return; + } + } + + MOZ_ASSERT(false, "This should not happen."); +} diff --git a/dom/audiochannel/AudioChannelService.h b/dom/audiochannel/AudioChannelService.h index 2ff391a9365..8e20c92b53e 100644 --- a/dom/audiochannel/AudioChannelService.h +++ b/dom/audiochannel/AudioChannelService.h @@ -7,11 +7,12 @@ #ifndef mozilla_dom_audiochannelservice_h__ #define mozilla_dom_audiochannelservice_h__ -#include "nsIAudioChannelService.h" #include "nsAutoPtr.h" #include "nsIObserver.h" #include "nsTArray.h" +#include "nsITimer.h" +#include "AudioChannelCommon.h" #include "AudioChannelAgent.h" #include "nsAttrValue.h" #include "nsClassHashtable.h" @@ -25,23 +26,27 @@ namespace dom { #ifdef MOZ_WIDGET_GONK class SpeakerManagerService; #endif - -#define NUMBER_OF_AUDIO_CHANNELS (uint32_t)AudioChannel::Publicnotification + 1 - -class AudioChannelService final : public nsIAudioChannelService - , public nsIObserver +class AudioChannelService +: public nsIObserver +, public nsITimerCallback { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER - NS_DECL_NSIAUDIOCHANNELSERVICE + NS_DECL_NSITIMERCALLBACK + + /** + * Returns the AudioChannelServce singleton or null if the process havn't create it before. + * Only to be called from main thread. + */ + static AudioChannelService* GetAudioChannelService(); /** * Returns the AudioChannelServce singleton. * If AudioChannelServce is not exist, create and return new one. * Only to be called from main thread. */ - static already_AddRefed GetOrCreate(); + static AudioChannelService* GetOrCreateAudioChannelService(); /** * Shutdown the singleton. @@ -52,45 +57,40 @@ public: * Any audio channel agent that starts playing should register itself to * this service, sharing the AudioChannel. */ - void RegisterAudioChannelAgent(AudioChannelAgent* aAgent, AudioChannel aChannel); + virtual void RegisterAudioChannelAgent(AudioChannelAgent* aAgent, + AudioChannel aChannel, + bool aWithVideo); /** * Any audio channel agent that stops playing should unregister itself to * this service. */ - void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent); + virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent); /** - * Return the state to indicate this audioChannel for his window should keep - * playing/muted. + * Return the state to indicate this agent should keep playing/ + * fading volume/muted. */ - void GetState(nsPIDOMWindow* aWindow, uint32_t aChannel, - float* aVolume, bool* aMuted); + virtual AudioChannelState GetState(AudioChannelAgent* aAgent, + bool aElementHidden); - /* Methods for the BrowserElementAudioChannel */ - float GetAudioChannelVolume(nsPIDOMWindow* aWindow, AudioChannel aChannel); - - void SetAudioChannelVolume(nsPIDOMWindow* aWindow, AudioChannel aChannel, - float aVolume); - - bool GetAudioChannelMuted(nsPIDOMWindow* aWindow, AudioChannel aChannel); - - void SetAudioChannelMuted(nsPIDOMWindow* aWindow, AudioChannel aChannel, - bool aMuted); - - bool IsAudioChannelActive(nsPIDOMWindow* aWindow, AudioChannel aChannel); + /** + * Return true if there is a content channel active in this process + * or one of its subprocesses. + */ + virtual bool ContentOrNormalChannelIsActive(); /** * Return true if there is a telephony channel active in this process * or one of its subprocesses. */ - bool TelephonyChannelIsActive(); + virtual bool TelephonyChannelIsActive(); /** * Return true if a normal or content channel is active for the given * process ID. */ - bool ProcessContentOrNormalChannelIsActive(uint64_t aChildID); + virtual bool ProcessContentOrNormalChannelIsActive(uint64_t aChildID); /*** * AudioChannelManager calls this function to notify the default channel used @@ -125,69 +125,161 @@ public: static void GetAudioChannelString(AudioChannel aChannel, nsAString& aString); static void GetDefaultAudioChannelString(nsAString& aString); - void Notify(uint64_t aWindowID); + void Notify(); -private: - AudioChannelService(); - ~AudioChannelService(); +protected: + void SendNotification(); + + /** + * Send the audio-channel-changed notification for the given process ID if + * needed. + */ + void SendAudioChannelChangedNotification(uint64_t aChildID); + + /* Register/Unregister IPC types: */ + void RegisterType(AudioChannel aChannel, uint64_t aChildID, bool aWithVideo); + void UnregisterType(AudioChannel aChannel, bool aElementHidden, + uint64_t aChildID, bool aWithVideo); + void UnregisterTypeInternal(AudioChannel aChannel, bool aElementHidden, + uint64_t aChildID, bool aWithVideo); + + AudioChannelState GetStateInternal(AudioChannel aChannel, uint64_t aChildID, + bool aElementHidden, + bool aElementWasHidden); + + /* Update the internal type value following the visibility changes */ + void UpdateChannelType(AudioChannel aChannel, uint64_t aChildID, + bool aElementHidden, bool aElementWasHidden); /* Send the default-volume-channel-changed notification */ void SetDefaultVolumeControlChannelInternal(int32_t aChannel, bool aVisible, uint64_t aChildID); - struct AudioChannelConfig final - { - AudioChannelConfig() - : mVolume(1.0) - , mMuted(false) - , mNumberOfAgents(0) + AudioChannelState CheckTelephonyPolicy(AudioChannel aChannel, + uint64_t aChildID); + void RegisterTelephonyChild(uint64_t aChildID); + void UnregisterTelephonyChild(uint64_t aChildID); + + AudioChannelService(); + virtual ~AudioChannelService(); + + enum AudioChannelInternalType { + AUDIO_CHANNEL_INT_NORMAL = 0, + AUDIO_CHANNEL_INT_NORMAL_HIDDEN, + AUDIO_CHANNEL_INT_CONTENT, + AUDIO_CHANNEL_INT_CONTENT_HIDDEN, + AUDIO_CHANNEL_INT_NOTIFICATION, + AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN, + AUDIO_CHANNEL_INT_ALARM, + AUDIO_CHANNEL_INT_ALARM_HIDDEN, + AUDIO_CHANNEL_INT_TELEPHONY, + AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN, + AUDIO_CHANNEL_INT_RINGER, + AUDIO_CHANNEL_INT_RINGER_HIDDEN, + AUDIO_CHANNEL_INT_PUBLICNOTIFICATION, + AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN, + AUDIO_CHANNEL_INT_LAST + }; + + bool ChannelsActiveWithHigherPriorityThan(AudioChannelInternalType aType); + + bool CheckVolumeFadedCondition(AudioChannelInternalType aType, + bool aElementHidden); + + AudioChannelInternalType GetInternalType(AudioChannel aChannel, + bool aElementHidden); + + class AudioChannelAgentData { + public: + AudioChannelAgentData(AudioChannel aChannel, + bool aElementHidden, + AudioChannelState aState, + bool aWithVideo) + : mChannel(aChannel) + , mElementHidden(aElementHidden) + , mState(aState) + , mWithVideo(aWithVideo) {} - float mVolume; - bool mMuted; - - uint32_t mNumberOfAgents; + AudioChannel mChannel; + bool mElementHidden; + AudioChannelState mState; + const bool mWithVideo; }; - struct AudioChannelWindow final - { - AudioChannelConfig mChannels[NUMBER_OF_AUDIO_CHANNELS]; - nsClassHashtable, AudioChannel> mAgents; - }; - - AudioChannelWindow& - GetOrCreateWindowData(nsPIDOMWindow* aWindow); - - static PLDHashOperator - TelephonyChannelIsActiveEnumerator(const uint64_t& aWindowID, - nsAutoPtr& aWinData, - void *aPtr); - - static PLDHashOperator - AnyAudioChannelIsActiveEnumerator(const uint64_t& aWindowID, - nsAutoPtr& aWinData, - void *aPtr); - - static PLDHashOperator - RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent, - AudioChannel* aUnused, - void *aPtr); - static PLDHashOperator NotifyEnumerator(AudioChannelAgent* aAgent, - AudioChannel* aAudioChannel, - void* aUnused); + AudioChannelAgentData* aData, void *aUnused); - nsClassHashtable mWindows; + static PLDHashOperator + RefreshAgentsVolumeEnumerator(AudioChannelAgent* aAgent, + AudioChannelAgentData* aUnused, + void *aPtr); + static PLDHashOperator + CountWindowEnumerator(AudioChannelAgent* aAgent, + AudioChannelAgentData* aUnused, + void *aPtr); + + static PLDHashOperator + WindowDestroyedEnumerator(AudioChannelAgent* aAgent, + nsAutoPtr& aData, + void *aPtr); + + // This returns the number of agents from this aWindow. + uint32_t CountWindow(nsIDOMWindow* aWindow); + + nsClassHashtable< nsPtrHashKey, AudioChannelAgentData > mAgents; #ifdef MOZ_WIDGET_GONK nsTArray mSpeakerManager; #endif + nsTArray mChannelCounters[AUDIO_CHANNEL_INT_LAST]; + + int32_t mCurrentHigherChannel; + int32_t mCurrentVisibleHigherChannel; + + nsTArray mWithVideoChildIDs; + + // Telephony Channel policy is "LIFO", the last app to require the resource is + // allowed to play. The others are muted. + struct TelephonyChild { + uint64_t mChildID; + uint32_t mInstances; + + explicit TelephonyChild(uint64_t aChildID) + : mChildID(aChildID) + , mInstances(1) + {} + }; + nsTArray mTelephonyChildren; + + // mPlayableHiddenContentChildID stores the ChildID of the process which can + // play content channel(s) in the background. + // A background process contained content channel(s) will become playable: + // 1. When this background process registers its content channel(s) in + // AudioChannelService and there is no foreground process with registered + // content channel(s). + // 2. When this process goes from foreground into background and there is + // no foreground process with registered content channel(s). + // A background process contained content channel(s) will become non-playable: + // 1. When there is a foreground process registering its content channel(s) + // in AudioChannelService. + // ps. Currently this condition is never satisfied because the default value + // of visibility status of each channel during registering is hidden = true. + // 2. When there is a process with registered content channel(s) goes from + // background into foreground. + // 3. When this process unregisters all hidden content channels. + // 4. When this process shuts down. + uint64_t mPlayableHiddenContentChildID; bool mDisabled; nsCOMPtr mRunnable; + nsCOMPtr mDeferTelChannelTimer; + bool mTimerElementHidden; + uint64_t mTimerChildID; + uint64_t mDefChannelChildID; // This is needed for IPC comunication between diff --git a/dom/audiochannel/AudioChannelServiceChild.cpp b/dom/audiochannel/AudioChannelServiceChild.cpp new file mode 100644 index 00000000000..ba23feb8ba7 --- /dev/null +++ b/dom/audiochannel/AudioChannelServiceChild.cpp @@ -0,0 +1,168 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "AudioChannelServiceChild.h" + +#include "base/basictypes.h" + +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/unused.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" + +#ifdef MOZ_WIDGET_GONK +#include "SpeakerManagerService.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::hal; + +StaticRefPtr gAudioChannelServiceChild; + +// static +AudioChannelService* +AudioChannelServiceChild::GetAudioChannelService() +{ + MOZ_ASSERT(NS_IsMainThread()); + + return gAudioChannelServiceChild; + +} + +// static +AudioChannelService* +AudioChannelServiceChild::GetOrCreateAudioChannelService() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // If we already exist, exit early + if (gAudioChannelServiceChild) { + return gAudioChannelServiceChild; + } + + // Create new instance, register, return + nsRefPtr service = new AudioChannelServiceChild(); + MOZ_ASSERT(service); + + gAudioChannelServiceChild = service; + return gAudioChannelServiceChild; +} + +void +AudioChannelServiceChild::Shutdown() +{ + if (gAudioChannelServiceChild) { + gAudioChannelServiceChild = nullptr; + } +} + +AudioChannelServiceChild::AudioChannelServiceChild() +{ +} + +AudioChannelServiceChild::~AudioChannelServiceChild() +{ +} + +AudioChannelState +AudioChannelServiceChild::GetState(AudioChannelAgent* aAgent, bool aElementHidden) +{ + AudioChannelAgentData* data; + if (!mAgents.Get(aAgent, &data)) { + return AUDIO_CHANNEL_STATE_MUTED; + } + + AudioChannelState state = AUDIO_CHANNEL_STATE_MUTED; + bool oldElementHidden = data->mElementHidden; + + UpdateChannelType(data->mChannel, CONTENT_PROCESS_ID_MAIN, aElementHidden, + oldElementHidden); + + // Update visibility. + data->mElementHidden = aElementHidden; + + ContentChild* cc = ContentChild::GetSingleton(); + cc->SendAudioChannelGetState(data->mChannel, aElementHidden, oldElementHidden, + &state); + data->mState = state; + cc->SendAudioChannelChangedNotification(); + + #ifdef MOZ_WIDGET_GONK + /** Only modify the speaker status when + * (1) apps in the foreground. + * (2) apps in the backgrund and inactive. + * Notice : modify only when the visible status is stable, because there + * has lantency in passing the visibility events. + **/ + bool active = AnyAudioChannelIsActive(); + if (aElementHidden == oldElementHidden && + (!aElementHidden || (aElementHidden && !active))) { + for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) { + mSpeakerManager[i]->SetAudioChannelActive(active); + } + } + #endif + + return state; +} + +void +AudioChannelServiceChild::RegisterAudioChannelAgent(AudioChannelAgent* aAgent, + AudioChannel aChannel, + bool aWithVideo) +{ + AudioChannelService::RegisterAudioChannelAgent(aAgent, aChannel, aWithVideo); + + ContentChild::GetSingleton()->SendAudioChannelRegisterType(aChannel, aWithVideo); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "audio-channel-agent-changed", nullptr); + } +} + +void +AudioChannelServiceChild::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent) +{ + AudioChannelAgentData *pData; + if (!mAgents.Get(aAgent, &pData)) { + return; + } + + // We need to keep a copy because unregister will remove the + // AudioChannelAgentData object from the hashtable. + AudioChannelAgentData data(*pData); + + AudioChannelService::UnregisterAudioChannelAgent(aAgent); + + ContentChild::GetSingleton()->SendAudioChannelUnregisterType( + data.mChannel, data.mElementHidden, data.mWithVideo); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "audio-channel-agent-changed", nullptr); + } +#ifdef MOZ_WIDGET_GONK + bool active = AnyAudioChannelIsActive(); + for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) { + mSpeakerManager[i]->SetAudioChannelActive(active); + } +#endif +} + +void +AudioChannelServiceChild::SetDefaultVolumeControlChannel(int32_t aChannel, + bool aHidden) +{ + ContentChild *cc = ContentChild::GetSingleton(); + if (cc) { + cc->SendAudioChannelChangeDefVolChannel(aChannel, aHidden); + } +} diff --git a/dom/audiochannel/AudioChannelServiceChild.h b/dom/audiochannel/AudioChannelServiceChild.h new file mode 100644 index 00000000000..503033495c5 --- /dev/null +++ b/dom/audiochannel/AudioChannelServiceChild.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_audiochannelservicechild_h__ +#define mozilla_dom_audiochannelservicechild_h__ + +#include "nsAutoPtr.h" +#include "nsISupports.h" + +#include "AudioChannelService.h" +#include "AudioChannelCommon.h" + +namespace mozilla { +namespace dom { + +class AudioChannelServiceChild : public AudioChannelService +{ +public: + + /** + * Returns the AudioChannelServce singleton or null if the process havn't create it before. + * Only to be called from main thread. + */ + static AudioChannelService* GetAudioChannelService(); + + /** + * Returns the AudioChannelServce singleton. + * If AudioChannelServce is not exist, create and return new one. + * Only to be called from main thread. + */ + static AudioChannelService* GetOrCreateAudioChannelService(); + + static void Shutdown(); + + virtual void RegisterAudioChannelAgent(AudioChannelAgent* aAgent, + AudioChannel aChannel, + bool aWithVideo); + virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent); + + /** + * Return the state to indicate this agent should keep playing/ + * fading volume/muted. + */ + virtual AudioChannelState GetState(AudioChannelAgent* aAgent, + bool aElementHidden); + + virtual void SetDefaultVolumeControlChannel(int32_t aChannel, + bool aHidden); + +protected: + AudioChannelServiceChild(); + virtual ~AudioChannelServiceChild(); +}; + +} // namespace dom +} // namespace mozilla + +#endif + diff --git a/dom/audiochannel/moz.build b/dom/audiochannel/moz.build index cc8b71ffd0c..00803501bc4 100644 --- a/dom/audiochannel/moz.build +++ b/dom/audiochannel/moz.build @@ -4,21 +4,25 @@ # 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/. +TEST_DIRS += ['tests'] + XPIDL_SOURCES += [ 'nsIAudioChannelAgent.idl', - 'nsIAudioChannelService.idl', ] XPIDL_MODULE = 'dom_audiochannel' EXPORTS += [ 'AudioChannelAgent.h', + 'AudioChannelCommon.h', 'AudioChannelService.h', + 'AudioChannelServiceChild.h', ] UNIFIED_SOURCES += [ 'AudioChannelAgent.cpp', 'AudioChannelService.cpp', + 'AudioChannelServiceChild.cpp', ] FAIL_ON_WARNINGS = True diff --git a/dom/audiochannel/nsIAudioChannelAgent.idl b/dom/audiochannel/nsIAudioChannelAgent.idl index 8febf962fbd..e990d3959ab 100644 --- a/dom/audiochannel/nsIAudioChannelAgent.idl +++ b/dom/audiochannel/nsIAudioChannelAgent.idl @@ -6,13 +6,24 @@ interface nsIDOMWindow; -[uuid(4f537c88-3722-4946-9a09-ce559fa0591d)] +[uuid(194b55d9-39c0-45c6-b8ef-b8049f978ea5)] interface nsIAudioChannelAgentCallback : nsISupports { + /** + * Notified when the playable status of channel is changed. + * + * @param canPlay + * Callback from agent to notify component of the playable status + * of the channel. If canPlay is muted state, component SHOULD stop + * playing media associated with this channel as soon as possible. if + * it is faded state then the volume of media should be reduced. + */ + void canPlayChanged(in long canPlay); + /** * Notified when the window volume/mute is changed */ - void windowVolumeChanged(in float aVolume, in bool aMuted); + void windowVolumeChanged(); }; /** @@ -29,7 +40,7 @@ interface nsIAudioChannelAgentCallback : nsISupports * 1. Changes to the playable status of this channel. */ -[uuid(363ff8d3-5bd2-485a-84ac-125062cbdc19)] +[uuid(2b0222a5-8f7b-49d2-9ab8-cd01b744b23e)] interface nsIAudioChannelAgent : nsISupports { const long AUDIO_AGENT_CHANNEL_NORMAL = 0; @@ -80,6 +91,16 @@ interface nsIAudioChannelAgent : nsISupports void initWithWeakCallback(in nsIDOMWindow window, in long channelType, in nsIAudioChannelAgentCallback callback); + /** + * This method is just like init(), and specify the channel is associated + * with video. + * + * @param weak + * true if weak reference should be hold. + */ + void initWithVideo(in nsIDOMWindow window, in long channelType, + in nsIAudioChannelAgentCallback callback, in boolean weak); + /** * Notify the agent that we want to start playing. * Note: Gecko component SHOULD call this function first then start to @@ -94,7 +115,7 @@ interface nsIAudioChannelAgent : nsISupports * faded state: the agent has registered with audio channel service the * component should start playback as well as reducing the volume. */ - void startPlaying(out float volume, out bool muted); + long startPlaying(); /** * Notify the agent we no longer want to play. @@ -105,4 +126,17 @@ interface nsIAudioChannelAgent : nsISupports * channel service. */ void stopPlaying(); + + /** + * Notify the agent of the visibility state of the window using this agent. + * @param visible + * True if the window associated with the agent is visible. + */ + void setVisibilityState(in boolean visible); + + /** + * Retrieve the volume from the window. + */ + readonly attribute float windowVolume; }; + diff --git a/dom/audiochannel/nsIAudioChannelService.idl b/dom/audiochannel/nsIAudioChannelService.idl deleted file mode 100644 index 8cdc0379d26..00000000000 --- a/dom/audiochannel/nsIAudioChannelService.idl +++ /dev/null @@ -1,29 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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 "nsISupports.idl" - -interface nsIDOMWindow; - -[scriptable, builtinclass, uuid(323e5472-b8f4-4288-b1b9-53c7c54bbbe8)] -interface nsIAudioChannelService : nsISupports -{ - float getAudioChannelVolume(in nsIDOMWindow window, - in unsigned short audioChannel); - - void setAudioChannelVolume(in nsIDOMWindow window, - in unsigned short audioChannel, - in float volume); - - boolean getAudioChannelMuted(in nsIDOMWindow window, - in unsigned short audioChannel); - - void setAudioChannelMuted(in nsIDOMWindow window, - in unsigned short audioChannel, - in boolean muted); - - boolean isAudioChannelActive(in nsIDOMWindow window, - in unsigned short audioChannel); -}; diff --git a/dom/audiochannel/tests/AudioChannelChromeScript.js b/dom/audiochannel/tests/AudioChannelChromeScript.js new file mode 100644 index 00000000000..46bd7d30791 --- /dev/null +++ b/dom/audiochannel/tests/AudioChannelChromeScript.js @@ -0,0 +1,18 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; +const { Services } = Cu.import('resource://gre/modules/Services.jsm'); +const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm'); + +addMessageListener('init-chrome-event', function(message) { + // listen mozChromeEvent and forward to content process. + let type = message.type; + + SystemAppProxy.addEventListener('mozChromeEvent', function(event) { + let details = event.detail; + if (details.type === type) { + sendAsyncMessage('chrome-event', details); + } + }, true); +}); diff --git a/dom/audiochannel/tests/TestAudioChannelService.cpp b/dom/audiochannel/tests/TestAudioChannelService.cpp new file mode 100644 index 00000000000..9d6a6d1a40d --- /dev/null +++ b/dom/audiochannel/tests/TestAudioChannelService.cpp @@ -0,0 +1,669 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ +#ifdef XP_WIN +#include +#else +#include +#endif + +#include "TestHarness.h" + +#include "nsWeakReference.h" +#include "AudioChannelService.h" +#include "AudioChannelAgent.h" + +#include "nsThreadUtils.h" + +#define TEST_ENSURE_BASE(_test, _msg) \ + PR_BEGIN_MACRO \ + if (!(_test)) { \ + fail(_msg); \ + return NS_ERROR_FAILURE; \ + } else { \ + passed(_msg); \ + } \ + PR_END_MACRO + +using namespace mozilla::dom; + +void +spin_events_loop_until_false(const bool* const aCondition) +{ + nsCOMPtr thread(::do_GetCurrentThread()); + nsresult rv = NS_OK; + bool processed = true; + while (*aCondition && NS_SUCCEEDED(rv)) { + rv = thread->ProcessNextEvent(true, &processed); + } +} + +class Agent : public nsIAudioChannelAgentCallback, + public nsSupportsWeakReference +{ +protected: + virtual ~Agent() + { + if (mRegistered) { + StopPlaying(); + } + } + +public: + NS_DECL_ISUPPORTS + + explicit Agent(AudioChannel aChannel) + : mChannel(aChannel) + , mWaitCallback(false) + , mRegistered(false) + , mCanPlay(AUDIO_CHANNEL_STATE_MUTED) + { + mAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1"); + } + + nsresult Init(bool video=false) + { + nsresult rv = NS_OK; + if (video) { + rv = mAgent->InitWithVideo(nullptr, static_cast(mChannel), + this, true); + } + else { + rv = mAgent->InitWithWeakCallback(nullptr, static_cast(mChannel), + this); + } + NS_ENSURE_SUCCESS(rv, rv); + + return mAgent->SetVisibilityState(false); + } + + nsresult StartPlaying(AudioChannelState *_ret) + { + if (mRegistered) { + StopPlaying(); + } + + nsresult rv = mAgent->StartPlaying((int32_t *)_ret); + mRegistered = true; + return rv; + } + + nsresult StopPlaying() + { + mRegistered = false; + spin_events_loop_until_false(&mWaitCallback); + return mAgent->StopPlaying(); + } + + nsresult SetVisibilityState(bool visible) + { + if (mRegistered) { + mWaitCallback = true; + } + return mAgent->SetVisibilityState(visible); + } + + NS_IMETHODIMP CanPlayChanged(int32_t canPlay) override + { + mCanPlay = static_cast(canPlay); + mWaitCallback = false; + return NS_OK; + } + + NS_IMETHODIMP WindowVolumeChanged() override + { + return NS_OK; + } + + nsresult GetCanPlay(AudioChannelState *_ret, bool aWaitCallback = false) + { + if (aWaitCallback) { + mWaitCallback = true; + } + + spin_events_loop_until_false(&mWaitCallback); + *_ret = mCanPlay; + return NS_OK; + } + + nsCOMPtr mAgent; + AudioChannel mChannel; + bool mWaitCallback; + bool mRegistered; + AudioChannelState mCanPlay; +}; + +NS_IMPL_ISUPPORTS(Agent, nsIAudioChannelAgentCallback, + nsISupportsWeakReference) + +nsresult +TestDoubleStartPlaying() +{ + nsRefPtr agent = new Agent(AudioChannel::Normal); + + nsresult rv = agent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + AudioChannelState playable; + rv = agent->mAgent->StartPlaying((int32_t *)&playable); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent->mAgent->StartPlaying((int32_t *)&playable); + TEST_ENSURE_BASE(NS_FAILED(rv), + "Test0: StartPlaying calling twice must return error"); + + return NS_OK; +} + +nsresult +TestOneNormalChannel() +{ + nsRefPtr agent = new Agent(AudioChannel::Normal); + nsresult rv = agent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + AudioChannelState playable; + rv = agent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test1: A normal channel unvisible agent must be muted"); + + rv = agent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test1: A normal channel visible agent must be playable"); + + return rv; +} + +nsresult +TestTwoNormalChannels() +{ + nsRefPtr agent1 = new Agent(AudioChannel::Normal); + nsresult rv = agent1->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr agent2 = new Agent(AudioChannel::Normal); + rv = agent2->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + AudioChannelState playable; + rv = agent1->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test2: A normal channel unvisible agent1 must be muted"); + + rv = agent2->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test2: A normal channel unvisible agent2 must be muted"); + + rv = agent1->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent2->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent1->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test2: A normal channel visible agent1 must be playable"); + + rv = agent2->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test2: A normal channel visible agent2 must be playable"); + + return rv; +} + +nsresult +TestContentChannels() +{ + nsRefPtr agent1 = new Agent(AudioChannel::Content); + nsresult rv = agent1->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr agent2 = new Agent(AudioChannel::Content); + rv = agent2->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + // All content channels in the foreground can be allowed to play + rv = agent1->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent2->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + AudioChannelState playable; + rv = agent1->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test3: A content channel visible agent1 must be playable"); + + rv = agent2->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test3: A content channel visible agent2 must be playable"); + + // Test the transition state of one content channel tried to set non-visible + // state first when app is going to background. + rv = agent1->SetVisibilityState(false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent1->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test3: A content channel unvisible agent1 must be playable from " + "foreground to background"); + + // Test all content channels set non-visible already + rv = agent2->SetVisibilityState(false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent2->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test3: A content channel unvisible agent2 must be playable from " + "foreground to background"); + + // Clear the content channels & mActiveContentChildIDs in AudioChannelService. + // If agent stop playable in the background, we will reserve it's childID in + // mActiveContentChildIDs, then it can allow to play next song. So we set agents + // to foreground first then stopping to play + rv = agent1->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + rv = agent2->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + rv = agent1->StopPlaying(); + NS_ENSURE_SUCCESS(rv, rv); + rv = agent2->StopPlaying(); + NS_ENSURE_SUCCESS(rv, rv); + + // Test that content channels can be allow to play when they starts from + // the background state + rv = agent1->SetVisibilityState(false); + NS_ENSURE_SUCCESS(rv, rv); + rv = agent2->SetVisibilityState(false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent1->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test3: A content channel unvisible agent1 must be playable " + "from background state"); + + rv = agent2->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test3: A content channel unvisible agent2 must be playable " + "from background state"); + + return rv; +} + +nsresult +TestFadedState() +{ + nsRefPtr normalAgent = new Agent(AudioChannel::Normal); + nsresult rv = normalAgent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr contentAgent = new Agent(AudioChannel::Content); + rv = contentAgent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr notificationAgent = new Agent(AudioChannel::Notification); + rv = notificationAgent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = normalAgent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = contentAgent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = notificationAgent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + AudioChannelState playable; + rv = normalAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test4: A normal channel visible agent must be playable"); + + rv = contentAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test4: A content channel visible agent must be playable"); + + rv = notificationAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test4: A notification channel visible agent must be playable"); + + rv = contentAgent->GetCanPlay(&playable, true); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_FADED, + "Test4: A content channel unvisible agent must be faded because of " + "notification channel is playing"); + + rv = contentAgent->SetVisibilityState(false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = contentAgent->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_FADED, + "Test4: A content channel unvisible agent must be faded because of " + "notification channel is playing"); + + rv = notificationAgent->SetVisibilityState(false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = notificationAgent->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test4: A notification channel unvisible agent must be playable from " + "foreground to background"); + + rv = notificationAgent->StopPlaying(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = contentAgent->GetCanPlay(&playable, true); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test4: A content channel unvisible agent must be playable " + "because of notification channel is stopped"); + + rv = contentAgent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +nsresult +TestPriorities() +{ + nsRefPtr normalAgent = new Agent(AudioChannel::Normal); + nsresult rv = normalAgent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr contentAgent = new Agent(AudioChannel::Content); + rv = contentAgent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr notificationAgent = new Agent(AudioChannel::Notification); + rv = notificationAgent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr alarmAgent = new Agent(AudioChannel::Alarm); + rv = alarmAgent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr telephonyAgent = new Agent(AudioChannel::Telephony); + rv = telephonyAgent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr ringerAgent = new Agent(AudioChannel::Ringer); + rv = ringerAgent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr pNotificationAgent = + new Agent(AudioChannel::Publicnotification); + rv = pNotificationAgent->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + AudioChannelState playable; + + rv = normalAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test5: A normal channel unvisible agent must be muted"); + + rv = contentAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: A content channel unvisible agent must be playable while " + "playing from background state"); + + rv = notificationAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: A notification channel unvisible agent must be playable"); + + rv = alarmAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: An alarm channel unvisible agent must be playable"); + + rv = notificationAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test5: A notification channel unvisible agent must be muted when an " + "alarm is playing"); + + rv = telephonyAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: A telephony channel unvisible agent must be playable"); + + rv = alarmAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test5: An alarm channel unvisible agent must be muted when a telephony " + "is playing"); + + rv = ringerAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: A ringer channel unvisible agent must be playable"); + + rv = telephonyAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test5: A telephony channel unvisible agent must be muted when a ringer " + "is playing"); + + rv = pNotificationAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: A pNotification channel unvisible agent must be playable"); + + rv = ringerAgent->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test5: A ringer channel unvisible agent must be muted when a public " + "notification is playing"); + + // Stop to play notification channel or normal/content will be faded. + // Which already be tested on Test 4. + rv = notificationAgent->StopPlaying(); + NS_ENSURE_SUCCESS(rv, rv); + + // Settings visible the normal channel. + rv = normalAgent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = normalAgent->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: A normal channel visible agent must be playable"); + + // Set the content channel as visible . + rv = contentAgent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + // Content must be playable because visible. + rv = contentAgent->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: A content channel visible agent must be playable"); + + // Set the alarm channel as visible. + rv = alarmAgent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = alarmAgent->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: An alarm channel visible agent must be playable"); + + // Set the telephony channel as visible. + rv = telephonyAgent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = telephonyAgent->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: A telephony channel visible agent must be playable"); + + // Set the ringer channel as visible. + rv = ringerAgent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ringerAgent->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: A ringer channel visible agent must be playable"); + + // Set the public notification channel as visible. + rv = pNotificationAgent->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pNotificationAgent->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test5: A pNotification channel visible agent must be playable"); + + return rv; +} + +nsresult +TestOneVideoNormalChannel() +{ + nsRefPtr agent1 = new Agent(AudioChannel::Normal); + nsresult rv = agent1->Init(true); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr agent2 = new Agent(AudioChannel::Content); + rv = agent2->Init(false); + NS_ENSURE_SUCCESS(rv, rv); + + AudioChannelState playable; + rv = agent1->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test6: A video normal channel invisible agent1 must be muted"); + + rv = agent2->StartPlaying(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test6: A content channel invisible agent2 must be playable"); + + // one video normal channel in foreground and one content channel in background + rv = agent1->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent1->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test6: A video normal channel visible agent1 must be playable"); + + rv = agent2->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test6: A content channel invisible agent2 must be muted"); + + // both one video normal channel and one content channel in foreground + rv = agent2->SetVisibilityState(true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent1->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test6: A video normal channel visible agent1 must be playable"); + + rv = agent2->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test6: A content channel visible agent2 must be playable"); + + // one video normal channel in background and one content channel in foreground + rv = agent1->SetVisibilityState(false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent1->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test6: A video normal channel invisible agent1 must be muted"); + + rv = agent2->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test6: A content channel visible agent2 must be playable"); + + // both one video normal channel and one content channel in background + rv = agent2->SetVisibilityState(false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = agent1->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_MUTED, + "Test6: A video normal channel invisible agent1 must be muted"); + + rv = agent2->GetCanPlay(&playable); + NS_ENSURE_SUCCESS(rv, rv); + TEST_ENSURE_BASE(playable == AUDIO_CHANNEL_STATE_NORMAL, + "Test6: A content channel invisible agent2 must be playable"); + + return rv; +} + +int main(int argc, char** argv) +{ + ScopedXPCOM xpcom("AudioChannelService"); + if (xpcom.failed()) { + return 1; + } + + if (NS_FAILED(TestDoubleStartPlaying())) { + return 1; + } + + if (NS_FAILED(TestOneNormalChannel())) { + return 1; + } + + if (NS_FAILED(TestTwoNormalChannels())) { + return 1; + } + + if (NS_FAILED(TestContentChannels())) { + return 1; + } + + if (NS_FAILED(TestFadedState())) { + return 1; + } + + // Channel type with AudioChannel::Telephony cannot be unregistered until the + // main thread has chances to process 1500 millisecond timer. In order to + // skip ambiguous return value of ChannelsActiveWithHigherPriorityThan(), new + // test cases are added before any test case that registers the channel type + // with AudioChannel::Telephony channel. + if (NS_FAILED(TestOneVideoNormalChannel())) { + return 1; + } + + if (NS_FAILED(TestPriorities())) { + return 1; + } + + return 0; +} + diff --git a/dom/audiochannel/tests/audio.ogg b/dom/audiochannel/tests/audio.ogg new file mode 100644 index 00000000000..d7f6a0ccf47 Binary files /dev/null and b/dom/audiochannel/tests/audio.ogg differ diff --git a/dom/audiochannel/tests/file_audio.html b/dom/audiochannel/tests/file_audio.html new file mode 100644 index 00000000000..11058a1ff11 --- /dev/null +++ b/dom/audiochannel/tests/file_audio.html @@ -0,0 +1,99 @@ + + + + Test audio-channel-changed & visible-audio-channel-changed mozChromeEvent + + +
+ + + diff --git a/dom/audiochannel/tests/file_telephonyPolicy.html b/dom/audiochannel/tests/file_telephonyPolicy.html new file mode 100644 index 00000000000..f95b7d40ff8 --- /dev/null +++ b/dom/audiochannel/tests/file_telephonyPolicy.html @@ -0,0 +1,18 @@ + + + + + Test Telephony Channel Policy + + +
+ + + diff --git a/dom/audiochannel/tests/mochitest.ini b/dom/audiochannel/tests/mochitest.ini new file mode 100644 index 00000000000..45564d6dd46 --- /dev/null +++ b/dom/audiochannel/tests/mochitest.ini @@ -0,0 +1,11 @@ +[DEFAULT] +support-files = + audio.ogg + file_audio.html + file_telephonyPolicy.html + AudioChannelChromeScript.js + +[test_telephonyPolicy.html] +skip-if = buildapp == 'mulet' || (toolkit == 'gonk' || e10s) || os == "android" +[test_audioChannelChange.html] +skip-if = (toolkit != 'gonk') diff --git a/dom/audiochannel/tests/moz.build b/dom/audiochannel/tests/moz.build new file mode 100644 index 00000000000..b1bf003ce18 --- /dev/null +++ b/dom/audiochannel/tests/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +GeckoCppUnitTests([ + 'TestAudioChannelService', +]) + +if CONFIG['OS_ARCH'] == 'WINNT': + DEFINES['NOMINMAX'] = True + +MOCHITEST_MANIFESTS += ['mochitest.ini'] + +FAIL_ON_WARNINGS = True diff --git a/dom/audiochannel/tests/test_audioChannelChange.html b/dom/audiochannel/tests/test_audioChannelChange.html new file mode 100644 index 00000000000..1036018047f --- /dev/null +++ b/dom/audiochannel/tests/test_audioChannelChange.html @@ -0,0 +1,209 @@ + + + + + Test audio-channel-changed & visible-audio-channel-changed mozChromeEvent + + + + +
+ + + diff --git a/dom/audiochannel/tests/test_telephonyPolicy.html b/dom/audiochannel/tests/test_telephonyPolicy.html new file mode 100644 index 00000000000..f0294ac3cf8 --- /dev/null +++ b/dom/audiochannel/tests/test_telephonyPolicy.html @@ -0,0 +1,87 @@ + + + + + Test the Telephony Channel Policy + + + + +
+ + + diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index 30de73cac69..816a4cfadf0 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -2055,7 +2055,6 @@ GK_ATOM(onwarning, "onwarning") GK_ATOM(onstart, "onstart") GK_ATOM(onstop, "onstop") GK_ATOM(onphoto, "onphoto") -GK_ATOM(onactivestatechanged, "onactivestatechanged") #ifdef MOZ_GAMEPAD GK_ATOM(ongamepadbuttondown, "ongamepadbuttondown") GK_ATOM(ongamepadbuttonup, "ongamepadbuttonup") diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 25679f0e796..3a76b314b7c 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -3735,10 +3735,39 @@ nsPIDOMWindow::SetAudioVolume(float aVolume) return NS_OK; } +float +nsPIDOMWindow::GetAudioGlobalVolume() +{ + float globalVolume = 1.0; + nsCOMPtr window = this; + + do { + if (window->GetAudioMuted()) { + return 0; + } + + globalVolume *= window->GetAudioVolume(); + + nsCOMPtr win; + window->GetParent(getter_AddRefs(win)); + if (window == win) { + break; + } + + window = do_QueryInterface(win); + + // If there is not parent, or we are the toplevel or the volume is + // already 0.0, we don't continue. + } while (window && window != this && globalVolume); + + return globalVolume; +} + void nsPIDOMWindow::RefreshMediaElements() { - nsRefPtr service = AudioChannelService::GetOrCreate(); + nsRefPtr service = + AudioChannelService::GetOrCreateAudioChannelService(); service->RefreshAgentsVolume(GetCurrentInnerWindow()); } diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h index 3f7b1b220fd..fd4593d580e 100644 --- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -185,6 +185,8 @@ public: float GetAudioVolume() const; nsresult SetAudioVolume(float aVolume); + float GetAudioGlobalVolume(); + virtual void SetServiceWorkersTestingEnabled(bool aEnabled) { MOZ_ASSERT(IsOuterWindow()); diff --git a/dom/browser-element/BrowserElementAudioChannel.cpp b/dom/browser-element/BrowserElementAudioChannel.cpp deleted file mode 100644 index bb540ba1111..00000000000 --- a/dom/browser-element/BrowserElementAudioChannel.cpp +++ /dev/null @@ -1,489 +0,0 @@ -/* 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/Services.h" -#include "mozilla/dom/BrowserElementAudioChannelBinding.h" -#include "mozilla/dom/DOMRequest.h" -#include "mozilla/dom/ToJSValue.h" -#include "AudioChannelService.h" -#include "nsIBrowserElementAPI.h" -#include "nsIDocShell.h" -#include "nsIDOMDOMRequest.h" -#include "nsIObserverService.h" -#include "nsISupportsPrimitives.h" -#include "nsITabParent.h" -#include "nsPIDOMWindow.h" - -namespace { - -void -AssertIsInMainProcess() -{ - MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); -} - -} // anonymous namespace - -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) - -BrowserElementAudioChannel::BrowserElementAudioChannel( - nsPIDOMWindow* aWindow, - nsIFrameLoader* aFrameLoader, - nsIBrowserElementAPI* aAPI, - AudioChannel aAudioChannel) - : DOMEventTargetHelper(aWindow) - , mFrameLoader(aFrameLoader) - , mBrowserElementAPI(aAPI) - , mAudioChannel(aAudioChannel) - , mState(eStateUnknown) -{ - MOZ_ASSERT(NS_IsMainThread()); - AssertIsInMainProcess(); - MOZ_ASSERT(mFrameLoader); - - 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()); - AssertIsInMainProcess(); - - 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() -{ - 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; - } - - nsCOMPtr topWindow; - window->GetScriptableTop(getter_AddRefs(topWindow)); - - mFrameWindow = do_QueryInterface(topWindow); - 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()); - AssertIsInMainProcess(); - - return mAudioChannel; -} - -namespace { - -class BaseRunnable : public nsRunnable -{ -protected: - nsCOMPtr mParentWindow; - nsCOMPtr mFrameWindow; - nsRefPtr mRequest; - AudioChannel mAudioChannel; - - virtual void DoWork(AudioChannelService* aService, - JSContext* aCx) = 0; - -public: - BaseRunnable(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* aFrameWindow, - DOMRequest* aRequest, AudioChannel aAudioChannel) - : mParentWindow(aParentWindow) - , mFrameWindow(aFrameWindow) - , mRequest(aRequest) - , mAudioChannel(aAudioChannel) - {} - - NS_IMETHODIMP Run() override - { - nsRefPtr service = AudioChannelService::GetOrCreate(); - MOZ_ASSERT(service); - - 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(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* 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(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* 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(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* aFrameWindow, - DOMRequest* aRequest, AudioChannel aAudioChannel, - bool aActive) - : BaseRunnable(aParentWindow, aFrameWindow, aRequest, aAudioChannel) - , mActive(aActive) - , mValueKnown(true) - {} - - IsActiveRunnable(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* 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(nsPIDOMWindow* aParentWindow, nsPIDOMWindow* 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()); - AssertIsInMainProcess(); - - 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(); - } - - nsRefPtr 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()); - AssertIsInMainProcess(); - - 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(); - } - - nsRefPtr service = AudioChannelService::GetOrCreate(); - MOZ_ASSERT(service); - - service->SetAudioChannelVolume(mFrameWindow, mAudioChannel, aVolume); - - nsRefPtr 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()); - AssertIsInMainProcess(); - - 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(); - } - - nsRefPtr 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()); - AssertIsInMainProcess(); - - 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(); - } - - nsRefPtr service = AudioChannelService::GetOrCreate(); - MOZ_ASSERT(service); - - service->SetAudioChannelMuted(mFrameWindow, mAudioChannel, aMuted); - - nsRefPtr 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()); - AssertIsInMainProcess(); - - if (mState != eStateUnknown) { - nsRefPtr 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(); - } - - nsRefPtr 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 (NS_WARN_IF(!wrapper)) { - return NS_ERROR_FAILURE; - } - - 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) -{ - nsAutoString value(aData); - mState = value.EqualsASCII("active") ? eStateActive : eStateInactive; - DispatchTrustedEvent(NS_LITERAL_STRING("activestatechanged")); -} - -} // dom namespace -} // mozilla namespace diff --git a/dom/browser-element/BrowserElementAudioChannel.h b/dom/browser-element/BrowserElementAudioChannel.h deleted file mode 100644 index af990caa8a5..00000000000 --- a/dom/browser-element/BrowserElementAudioChannel.h +++ /dev/null @@ -1,83 +0,0 @@ -/* 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/. */ - -#ifndef mozilla_dom_BrowserElementAudioChannels_h -#define mozilla_dom_BrowserElementAudioChannels_h - -#include "mozilla/dom/AudioChannelBinding.h" -#include "mozilla/dom/BindingDeclarations.h" -#include "mozilla/DOMEventTargetHelper.h" -#include "mozilla/ErrorResult.h" -#include "nsCycleCollectionParticipant.h" -#include "nsIObserver.h" -#include "nsIFrameLoader.h" -#include "nsWeakReference.h" -#include "nsWrapperCache.h" - -class nsIBrowserElementAPI; -class nsITabParent; -class nsPIDOMWindow; - -namespace mozilla { -namespace dom { - -class DOMRequest; - -class BrowserElementAudioChannel final : public DOMEventTargetHelper - , public nsSupportsWeakReference - , public nsIObserver -{ -public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_NSIOBSERVER - - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BrowserElementAudioChannel, - DOMEventTargetHelper) - - BrowserElementAudioChannel(nsPIDOMWindow* aWindow, - nsIFrameLoader* aFrameLoader, - nsIBrowserElementAPI* aAPI, - AudioChannel aAudioChannel); - - nsresult Initialize(); - - // WebIDL methods - - virtual JSObject* WrapObject(JSContext *aCx, - JS::Handle aGivenProto) override; - - AudioChannel Name() const; - - already_AddRefed GetVolume(ErrorResult& aRv); - already_AddRefed SetVolume(float aVolume, ErrorResult& aRv); - - already_AddRefed GetMuted(ErrorResult& aRv); - already_AddRefed SetMuted(bool aMuted, ErrorResult& aRv); - - already_AddRefed IsActive(ErrorResult& aRv); - - IMPL_EVENT_HANDLER(activestatechanged); - -private: - ~BrowserElementAudioChannel(); - - void ProcessStateChanged(const char16_t* aData); - - nsCOMPtr mFrameLoader; - nsCOMPtr mBrowserElementAPI; - nsCOMPtr mTabParent; - nsCOMPtr mFrameWindow; - AudioChannel mAudioChannel; - - enum { - eStateActive, - eStateInactive, - eStateUnknown - } mState; -}; - -} // dom namespace -} // mozilla namespace - -#endif // mozilla_dom_BrowserElementAudioChannels_h diff --git a/dom/browser-element/BrowserElementChildPreload.js b/dom/browser-element/BrowserElementChildPreload.js index 457f882496a..147a4af91e7 100644 --- a/dom/browser-element/BrowserElementChildPreload.js +++ b/dom/browser-element/BrowserElementChildPreload.js @@ -13,10 +13,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/BrowserElementPromptService.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "acs", - "@mozilla.org/audiochannel/service;1", - "nsIAudioChannelService"); - let kLongestReturnedString = 128; function debug(msg) { @@ -238,11 +234,6 @@ BrowserElementChild.prototype = { "find-all": this._recvFindAll.bind(this), "find-next": this._recvFindNext.bind(this), "clear-match": this._recvClearMatch.bind(this), - "get-audio-channel-volume": this._recvGetAudioChannelVolume, - "set-audio-channel-volume": this._recvSetAudioChannelVolume, - "get-audio-channel-muted": this._recvGetAudioChannelMuted, - "set-audio-channel-muted": this._recvSetAudioChannelMuted, - "get-is-audio-channel-active": this._recvIsAudioChannelActive } addMessageListener("browser-element-api:call", function(aMessage) { @@ -1255,55 +1246,6 @@ BrowserElementChild.prototype = { } }, - _recvGetAudioChannelVolume: function(data) { - debug("Received getAudioChannelVolume message: (" + data.json.id + ")"); - - let volume = acs.getAudioChannelVolume(content, - data.json.args.audioChannel); - sendAsyncMsg('got-audio-channel-volume', { - id: data.json.id, successRv: volume - }); - }, - - _recvSetAudioChannelVolume: function(data) { - debug("Received setAudioChannelVolume message: (" + data.json.id + ")"); - - acs.setAudioChannelVolume(content, - data.json.args.audioChannel, - data.json.args.volume); - sendAsyncMsg('got-set-audio-channel-volume', { - id: data.json.id, successRv: true - }); - }, - - _recvGetAudioChannelMuted: function(data) { - debug("Received getAudioChannelMuted message: (" + data.json.id + ")"); - - let muted = acs.getAudioChannelMuted(content, data.json.args.audioChannel); - sendAsyncMsg('got-audio-channel-muted', { - id: data.json.id, successRv: muted - }); - }, - - _recvSetAudioChannelMuted: function(data) { - debug("Received setAudioChannelMuted message: (" + data.json.id + ")"); - - acs.setAudioChannelMuted(content, data.json.args.audioChannel, - data.json.args.muted); - sendAsyncMsg('got-set-audio-channel-muted', { - id: data.json.id, successRv: true - }); - }, - - _recvIsAudioChannelActive: function(data) { - debug("Received isAudioChannelActive message: (" + data.json.id + ")"); - - let active = acs.isAudioChannelActive(content, data.json.args.audioChannel); - sendAsyncMsg('got-is-audio-channel-active', { - id: data.json.id, successRv: active - }); - }, - _initFinder: function() { if (!this._finder) { try { diff --git a/dom/browser-element/BrowserElementParent.cpp b/dom/browser-element/BrowserElementParent.cpp index d1094c12198..8c77ba46bc2 100644 --- a/dom/browser-element/BrowserElementParent.cpp +++ b/dom/browser-element/BrowserElementParent.cpp @@ -15,7 +15,6 @@ #endif #include "BrowserElementParent.h" -#include "BrowserElementAudioChannel.h" #include "mozilla/EventDispatcher.h" #include "mozilla/dom/HTMLIFrameElement.h" #include "mozilla/dom/ToJSValue.h" diff --git a/dom/browser-element/BrowserElementParent.js b/dom/browser-element/BrowserElementParent.js index 44a0c3e5a0e..4dfa4a9ab92 100644 --- a/dom/browser-element/BrowserElementParent.js +++ b/dom/browser-element/BrowserElementParent.js @@ -206,12 +206,7 @@ BrowserElementParent.prototype = { "selectionstatechanged": this._handleSelectionStateChanged, "scrollviewchange": this._handleScrollViewChange, "caretstatechanged": this._handleCaretStateChanged, - "findchange": this._handleFindChange, - "got-audio-channel-volume": this._gotDOMRequestResult, - "got-set-audio-channel-volume": this._gotDOMRequestResult, - "got-audio-channel-muted": this._gotDOMRequestResult, - "got-set-audio-channel-muted": this._gotDOMRequestResult, - "got-is-audio-channel-active": this._gotDOMRequestResult + "findchange": this._handleFindChange }; let mmSecuritySensitiveCalls = { @@ -978,33 +973,6 @@ BrowserElementParent.prototype = { } }, - getAudioChannelVolume: function(aAudioChannel) { - return this._sendDOMRequest('get-audio-channel-volume', - {audioChannel: aAudioChannel}); - }, - - setAudioChannelVolume: function(aAudioChannel, aVolume) { - return this._sendDOMRequest('set-audio-channel-volume', - {audioChannel: aAudioChannel, - volume: aVolume}); - }, - - getAudioChannelMuted: function(aAudioChannel) { - return this._sendDOMRequest('get-audio-channel-muted', - {audioChannel: aAudioChannel}); - }, - - setAudioChannelMuted: function(aAudioChannel, aMuted) { - return this._sendDOMRequest('set-audio-channel-muted', - {audioChannel: aAudioChannel, - muted: aMuted}); - }, - - isAudioChannelActive: function(aAudioChannel) { - return this._sendDOMRequest('get-is-audio-channel-active', - {audioChannel: aAudioChannel}); - }, - /** * Called when the visibility of the window which owns this iframe changes. */ diff --git a/dom/browser-element/mochitest/browserElement_AudioChannel.js b/dom/browser-element/mochitest/browserElement_AudioChannel.js deleted file mode 100644 index f61ff70bf81..00000000000 --- a/dom/browser-element/mochitest/browserElement_AudioChannel.js +++ /dev/null @@ -1,153 +0,0 @@ -/* Any copyright is dedicated to the public domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -// Bug 1113086 - tests for AudioChannel API into BrowserElement - -"use strict"; - -SimpleTest.waitForExplicitFinish(); -browserElementTestHelpers.setEnabledPref(true); -browserElementTestHelpers.addPermission(); - -SpecialPowers.setBoolPref("dom.testing.browserElementAudioChannel.noapp", true); -SpecialPowers.setBoolPref("media.useAudioChannelService", true); - -function noaudio() { - var iframe = document.createElement('iframe'); - iframe.setAttribute('mozbrowser', 'true'); - iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp'); - iframe.src = 'http://example.org/tests/dom/browser-element/mochitest/file_empty.html'; - - function noaudio_loadend() { - ok("allowedAudioChannels" in iframe, "allowedAudioChannels exist"); - var channels = iframe.allowedAudioChannels; - is(channels.length, 1, "1 audio channel by default"); - - var ac = channels[0]; - - ok(ac instanceof BrowserElementAudioChannel, "Correct class"); - ok("getVolume" in ac, "ac.getVolume exists"); - ok("setVolume" in ac, "ac.setVolume exists"); - ok("getMuted" in ac, "ac.getMuted exists"); - ok("setMuted" in ac, "ac.setMuted exists"); - ok("isActive" in ac, "ac.isActive exists"); - - new Promise(function(r, rr) { - var req = ac.getVolume(); - ok(req instanceof DOMRequest, "This is a domRequest."); - req.onsuccess = function(e) { - is(e.target.result, 1.0, "The default volume should be 1.0"); - r(); - } - }) - - .then(function() { - return new Promise(function(r, rr) { - ac.getMuted().onsuccess = function(e) { - is(e.target.result, false, "The default muted value should be false"); - r(); - } - }); - }) - - .then(function() { - return new Promise(function(r, rr) { - ac.setVolume(0.8).onsuccess = function() { r(); } - }); - }) - - .then(function() { - return new Promise(function(r, rr) { - ac.getVolume().onsuccess = function(e) { - // the actual value is 0.800000011920929.. - ok(Math.abs(0.8 - e.target.result) < 0.01, "The new volume should be 0.8: " + e.target.result); - r(); - } - }); - }) - - .then(function() { - return new Promise(function(r, rr) { - ac.setVolume(1.0).onsuccess = function() { r(); } - }); - }) - - .then(function() { - return new Promise(function(r, rr) { - ac.setMuted(true).onsuccess = function() { r(); } - }); - }) - - .then(function() { - return new Promise(function(r, rr) { - ac.getMuted().onsuccess = function(e) { - is(e.target.result, true, "The new muted value should be true"); - r(); - } - }); - }) - - .then(function() { - return new Promise(function(r, rr) { - ac.isActive().onsuccess = function(e) { - is(e.target.result, false, "ac.isActive is false: no audio element active."); - r(); - } - }); - }) - - .then(runTests); - } - - iframe.addEventListener('mozbrowserloadend', noaudio_loadend); - document.body.appendChild(iframe); -} - -function audio() { - var iframe = document.createElement('iframe'); - iframe.setAttribute('mozbrowser', 'true'); - iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp'); - iframe.src = 'http://example.org/tests/dom/browser-element/mochitest/iframe_file_audio.html'; - - function audio_loadend() { - ok("allowedAudioChannels" in iframe, "allowedAudioChannels exist"); - var channels = iframe.allowedAudioChannels; - is(channels.length, 1, "1 audio channel by default"); - - var ac = channels[0]; - - ok(ac instanceof BrowserElementAudioChannel, "Correct class"); - ok("getVolume" in ac, "ac.getVolume exists"); - ok("setVolume" in ac, "ac.setVolume exists"); - ok("getMuted" in ac, "ac.getMuted exists"); - ok("setMuted" in ac, "ac.setMuted exists"); - ok("isActive" in ac, "ac.isActive exists"); - - ac.onactivestatechanged = function() { - ok("activestatechanged event received."); - ac.onactivestatechanged = null; - runTests(); - } - } - - iframe.addEventListener('mozbrowserloadend', audio_loadend); - document.body.appendChild(iframe); -} - -var tests = [ noaudio, audio ]; - -function runTests() { - if (tests.length == 0) { - SimpleTest.finish(); - return; - } - - var test = tests.shift(); - test(); -} - - -addEventListener('load', function() { - SimpleTest.executeSoon(runTests); -}); - diff --git a/dom/browser-element/mochitest/file_audio.html b/dom/browser-element/mochitest/file_audio.html deleted file mode 100644 index 38ea1c41f3d..00000000000 --- a/dom/browser-element/mochitest/file_audio.html +++ /dev/null @@ -1,15 +0,0 @@ - - -