Bug 1242874 - part2 : window's suspend attribute. draft
authorAlastor Wu <alwu@mozilla.com>
Thu, 21 Apr 2016 17:14:27 +0800
changeset 354581 0f755dbf0c68ca1df2a6c43f0cbfee464bb16412
parent 354558 420428b89b8be4f423c878906349e27a6e8b6402
child 354582 9493cfcfb3fcf593bc88023e6a0bc0b9dc50d9cd
push id16130
push useralwu@mozilla.com
push dateThu, 21 Apr 2016 10:15:27 +0000
bugs1242874
milestone48.0a1
Bug 1242874 - part2 : window's suspend attribute. MozReview-Commit-ID: KaUI6oDMgMN
dom/base/nsDOMWindowUtils.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsPIDOMWindow.h
dom/interfaces/base/nsIDOMWindowUtils.idl
mobile/android/installer/package-manifest.in
toolkit/content/browser-content.js
toolkit/content/widgets/browser.xml
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3669,27 +3669,27 @@ nsDOMWindowUtils::PostRestyleSelfEvent(n
     return NS_ERROR_INVALID_ARG;
   }
 
   nsLayoutUtils::PostRestyleEvent(element, eRestyle_Self, nsChangeHint(0));
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::GetMediaSuspended(bool* aSuspended)
+nsDOMWindowUtils::GetMediaSuspended(uint32_t* aSuspended)
 {
   nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
   NS_ENSURE_STATE(window);
 
   *aSuspended = window->GetMediaSuspended();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::SetMediaSuspended(bool aSuspended)
+nsDOMWindowUtils::SetMediaSuspended(uint32_t aSuspended)
 {
   nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
   NS_ENSURE_STATE(window);
 
   window->SetMediaSuspended(aSuspended);
   return NS_OK;
 }
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -609,17 +609,18 @@ nsPIDOMWindow<T>::nsPIDOMWindow(nsPIDOMW
 : mFrameElement(nullptr), mDocShell(nullptr), mModalStateDepth(0),
   mRunningTimeout(nullptr), mMutationBits(0), mIsDocumentLoaded(false),
   mIsHandlingResizeEvent(false), mIsInnerWindow(aOuterWindow != nullptr),
   mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false),
   mMayHaveMouseEnterLeaveEventListener(false),
   mMayHavePointerEnterLeaveEventListener(false),
   mInnerObjectsFreed(false),
   mIsModalContentWindow(false),
-  mIsActive(false), mIsBackground(false), mMediaSuspended(false),
+  mIsActive(false), mIsBackground(false),
+  mMediaSuspended(nsISuspendedTypes::NONE_SUSPENDED),
   mAudioMuted(false), mAudioVolume(1.0), mAudioCaptured(false),
   mDesktopModeViewport(false), mInnerWindow(nullptr),
   mOuterWindow(aOuterWindow),
   // Make sure no actual window ends up with mWindowID == 0
   mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
   mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false)
  {}
 
@@ -3668,40 +3669,39 @@ nsPIDOMWindowInner::CreatePerformanceObj
         parentPerformance = parentInnerWindow->GetPerformance();
       }
     }
     mPerformance =
       new nsPerformance(this, timing, timedChannel, parentPerformance);
   }
 }
 
-bool
+nsSuspendedTypes
 nsPIDOMWindowOuter::GetMediaSuspended() const
 {
   if (IsInnerWindow()) {
     return mOuterWindow->GetMediaSuspended();
   }
 
   return mMediaSuspended;
 }
 
 void
-nsPIDOMWindowOuter::SetMediaSuspended(bool aSuspended)
+nsPIDOMWindowOuter::SetMediaSuspended(nsSuspendedTypes aSuspended)
 {
   if (IsInnerWindow()) {
     mOuterWindow->SetMediaSuspended(aSuspended);
     return;
   }
 
-  if (mMediaSuspended == aSuspended) {
-    return;
-  }
-
-  mMediaSuspended = aSuspended;
-  RefreshMediaElements();
+  if (!IsDisposableSuspended(aSuspended)) {
+    mMediaSuspended = aSuspended;
+  }
+
+  RefreshMediaElementsSuspended(aSuspended);
 }
 
 bool
 nsPIDOMWindowOuter::GetAudioMuted() const
 {
   if (IsInnerWindow()) {
     return mOuterWindow->GetAudioMuted();
   }
@@ -3717,17 +3717,17 @@ nsPIDOMWindowOuter::SetAudioMuted(bool a
     return;
   }
 
   if (mAudioMuted == aMuted) {
     return;
   }
 
   mAudioMuted = aMuted;
-  RefreshMediaElements();
+  RefreshMediaElementsVolume();
 }
 
 float
 nsPIDOMWindowOuter::GetAudioVolume() const
 {
   if (IsInnerWindow()) {
     return mOuterWindow->GetAudioVolume();
   }
@@ -3746,29 +3746,45 @@ nsPIDOMWindowOuter::SetAudioVolume(float
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
   if (mAudioVolume == aVolume) {
     return NS_OK;
   }
 
   mAudioVolume = aVolume;
-  RefreshMediaElements();
+  RefreshMediaElementsVolume();
   return NS_OK;
 }
 
 void
-nsPIDOMWindowOuter::RefreshMediaElements()
+nsPIDOMWindowOuter::RefreshMediaElementsVolume()
 {
   RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
   if (service) {
     service->RefreshAgentsVolume(GetOuterWindow());
   }
 }
 
+void
+nsPIDOMWindowOuter::RefreshMediaElementsSuspended(nsSuspendedTypes aSuspended)
+{
+  RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  if (service) {
+    // TODO : Impelement in next patch.
+  }
+}
+
+bool
+nsPIDOMWindowOuter::IsDisposableSuspended(nsSuspendedTypes aSuspended) const
+{
+  return (aSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
+          aSuspended == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE);
+}
+
 bool
 nsPIDOMWindowInner::GetAudioCaptured() const
 {
   MOZ_ASSERT(IsInnerWindow());
   return mAudioCaptured;
 }
 
 nsresult
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -33,16 +33,18 @@ class nsIScriptTimeoutHandler;
 class nsIURI;
 class nsPerformance;
 class nsPIDOMWindowInner;
 class nsPIDOMWindowOuter;
 class nsPIWindowRoot;
 class nsXBLPrototypeHandler;
 struct nsTimeout;
 
+typedef uint32_t nsSuspendedTypes;
+
 namespace mozilla {
 namespace dom {
 class AudioContext;
 class Element;
 class ServiceWorkerRegistrationMainThread;
 } // namespace dom
 namespace gfx {
 class VRDeviceProxy;
@@ -308,17 +310,17 @@ public:
   /**
    * Call this to check whether some node (this window, its document,
    * or content in that document) has a paint event listener.
    */
   bool HasPaintEventListeners()
   {
     return mMayHavePaintEventListener;
   }
-  
+
   /**
    * Call this to indicate that some node (this window, its document,
    * or content in that document) has a touch event listener.
    */
   void SetHasTouchEventListeners()
   {
     if (!mMayHaveTouchEventListener) {
       mMayHaveTouchEventListener = true;
@@ -644,17 +646,29 @@ protected:
   // Only used on outer windows.
   bool                   mIsActive;
 
   // Tracks whether our docshell is active.  If it is, mIsBackground
   // is false.  Too bad we have so many different concepts of
   // "active".  Only used on outer windows.
   bool                   mIsBackground;
 
-  bool                   mMediaSuspended;
+  /**
+   * The suspended types can be "disposable" or "permanent". This varable only
+   * stores the value about permanent suspend.
+   * - disposable
+   * To pause all playing media in that window, but doesn't affect the media
+   * which starts after that.
+   *
+   * - permanent
+   * To pause all media in that window, and also affect the media which starts
+   * after that.
+   */
+  nsSuspendedTypes       mMediaSuspended;
+
   bool                   mAudioMuted;
   float                  mAudioVolume;
 
   bool                   mAudioCaptured;
 
   // current desktop mode flag.
   bool                   mDesktopModeViewport;
 
@@ -800,17 +814,19 @@ protected:
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
 
 // NB: It's very very important that these two classes have identical vtables
 // and memory layout!
 class nsPIDOMWindowOuter : public nsPIDOMWindow<mozIDOMWindowProxy>
 {
 protected:
-  void RefreshMediaElements();
+  void RefreshMediaElementsVolume();
+  void RefreshMediaElementsSuspended(nsSuspendedTypes aSuspended);
+  bool IsDisposableSuspended(nsSuspendedTypes aSuspended) const;
 
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_PIDOMWINDOWOUTER_IID)
 
   static nsPIDOMWindowOuter* From(mozIDOMWindowProxy* aFrom) {
     return static_cast<nsPIDOMWindowOuter*>(aFrom);
   }
 
@@ -851,18 +867,18 @@ public:
     return mDesktopModeViewport;
   }
   bool IsBackground()
   {
     return mIsBackground;
   }
 
   // Audio API
-  bool GetMediaSuspended() const;
-  void SetMediaSuspended(bool aSuspended);
+  nsSuspendedTypes GetMediaSuspended() const;
+  void SetMediaSuspended(nsSuspendedTypes aSuspended);
 
   bool GetAudioMuted() const;
   void SetAudioMuted(bool aMuted);
 
   float GetAudioVolume() const;
   nsresult SetAudioVolume(float aVolume);
 
   void SetServiceWorkersTestingEnabled(bool aEnabled)
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -44,17 +44,17 @@ interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 interface nsIObserver;
 
-[scriptable, uuid(46b44e33-13c2-4eb3-bf80-76a4e0857ccc)]
+[scriptable, uuid(81f63a84-93cf-4510-b346-32926024ecf8)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -872,17 +872,17 @@ interface nsIDOMWindowUtils : nsISupport
    * @param aLeftSize How much to expand left the rectangle
    * @param aIgnoreRootScrollFrame whether or not to ignore the root scroll
    *        frame when retrieving the element. If false, this method returns
    *        null for coordinates outside of the viewport.
    * @param aFlushLayout flushes layout if true. Otherwise, no flush occurs.
    */
   nsIDOMNodeList nodesFromRect(in float aX,
                                in float aY,
-                               in float aTopSize, 
+                               in float aTopSize,
                                in float aRightSize,
                                in float aBottomSize,
                                in float aLeftSize,
                                in boolean aIgnoreRootScrollFrame,
                                in boolean aFlushLayout);
 
 
   /**
@@ -1325,17 +1325,17 @@ interface nsIDOMWindowUtils : nsISupport
   void resumeTimeouts();
 
   /**
    * What type of layer manager the widget associated with this window is
    * using. "Basic" is unaccelerated; other types are accelerated. Throws an
    * error if there is no widget associated with this window.
    */
   readonly attribute AString layerManagerType;
-  
+
   /**
    * True if the layer manager for the widget associated with this window is
    * forwarding layers to a remote compositor, false otherwise. Throws an
    * error if there is no widget associated with this window.
    */
   readonly attribute boolean layerManagerRemote;
 
   /**
@@ -1500,17 +1500,17 @@ interface nsIDOMWindowUtils : nsISupport
 
   /**
    * Checks the layer tree for this window and returns true
    * if all layers have transforms that are translations by integers,
    * no leaf layers overlap, and the union of the leaf layers is exactly
    * the bounds of the window. Always returns true in non-DEBUG builds.
    */
   boolean leafLayersPartitionWindow();
- 
+
   /**
    * Check if any PaintedLayer painting has been done for this element,
    * clears the painted flags if they have.
    */
   boolean checkAndClearPaintedState(in nsIDOMElement aElement);
 
   /**
    * Check whether all display items of the primary frame of aElement have been
@@ -1635,17 +1635,17 @@ interface nsIDOMWindowUtils : nsISupport
 
   const unsigned long AGENT_SHEET = 0;
   const unsigned long USER_SHEET = 1;
   const unsigned long AUTHOR_SHEET = 2;
   /**
    * Synchronously loads a style sheet from |sheetURI| and adds it to the list
    * of additional style sheets of the document.
    *
-   * These additional style sheets are very much like user/agent sheets loaded 
+   * These additional style sheets are very much like user/agent sheets loaded
    * with loadAndRegisterSheet. The only difference is that they are applied only
    * on the document owned by this window.
    *
    * Sheets added via this API take effect immediately on the document.
    */
   void loadSheet(in nsIURI sheetURI, in unsigned long type);
 
   /**
@@ -1658,17 +1658,17 @@ interface nsIDOMWindowUtils : nsISupport
    *
    * Style sheets can be preloaded with nsIStyleSheetService.preloadSheet.
    *
    * Sheets added via this API take effect immediately on the document.
    */
   void addSheet(in nsIDOMStyleSheet sheet, in unsigned long type);
 
   /**
-   * Remove the document style sheet at |sheetURI| from the list of additional 
+   * Remove the document style sheet at |sheetURI| from the list of additional
    * style sheets of the document.  The removal takes effect immediately.
    */
   void removeSheet(in nsIURI sheetURI, in unsigned long type);
 
   /**
    * Same as the above method but allows passing the URI as a string.
    */
   void removeSheetUsingURIString(in ACString sheetURI, in unsigned long type);
@@ -1746,20 +1746,20 @@ interface nsIDOMWindowUtils : nsISupport
   [implicit_jscontext] jsval getCompositorAPZTestData();
 
   /**
    * Posts an eRestyle_Self restyle event for the given element.
    */
   void postRestyleSelfEvent(in nsIDOMElement aElement);
 
   /**
-   * Used to pause or resume all MediaElements in this window. Use-cases are
-   * audio competing and remote media control.
+   * Used to pause or resume all media in this window. Use-cases are audio
+   * competing, remote media control and to prevent auto-playing media.
    */
-  attribute boolean mediaSuspended;
+  attribute uint32_t mediaSuspended;
 
   /**
    * With this it's possible to mute all the MediaElements in this window.
    * We have audioMuted and audioVolume to preserve the volume across
    * mute/umute.
    */
   attribute boolean audioMuted;
 
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -557,8 +557,10 @@
 
 @BINPATH@/components/DataStore.manifest
 @BINPATH@/components/DataStoreImpl.js
 @BINPATH@/components/dom_datastore.xpt
 
 #ifdef PKG_LOCALE_MANIFEST
 #include @PKG_LOCALE_MANIFEST@
 #endif
+
+@BINPATH@/components/dom_audiochannel.xpt
\ No newline at end of file
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -716,59 +716,87 @@ addMessageListener("WebChannelMessageToC
 
 var AudioPlaybackListener = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   init() {
     Services.obs.addObserver(this, "audio-playback", false);
     Services.obs.addObserver(this, "AudioFocusChanged", false);
 
-    addMessageListener("AudioPlaybackMute", this);
+    addMessageListener("AudioPlayback", this);
     addEventListener("unload", () => {
       AudioPlaybackListener.uninit();
     });
   },
 
   uninit() {
     Services.obs.removeObserver(this, "audio-playback");
     Services.obs.removeObserver(this, "AudioFocusChanged");
 
-    removeMessageListener("AudioPlaybackMute", this);
+    removeMessageListener("AudioPlayback", this);
   },
 
   observe(subject, topic, data) {
     if (topic === "audio-playback") {
       if (subject && subject.top == global.content) {
         let name = "AudioPlayback:";
         name += (data === "active") ? "Start" : "Stop";
         sendAsyncMessage(name);
       }
     } else if (topic == "AudioFocusChanged") {
       let utils = global.content.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIDOMWindowUtils);
+      var suspendTypes = Ci.nsISuspendedTypes;
       switch (data) {
         // The AudioFocus:LossTransient means the media would be resumed after
         // the interruption ended, but AudioFocus:Loss doesn't.
-        // TODO : distinguish these types, it would be done in bug1242874.
         case "Loss":
+          utils.mediaSuspended = suspendTypes.SUSPENDED_STOP_DISPOSABLE;
+          break;
         case "LossTransient":
-          utils.mediaSuspended = true;
+          utils.mediaSuspended = suspendTypes.SUSPENDED_PAUSE;
           break;
         case "Gain":
-          utils.mediaSuspended = false;
+          utils.mediaSuspended = suspendTypes.NONE_SUSPENDED;
           break;
       }
     }
   },
 
   receiveMessage(msg) {
-    if (msg.name == "AudioPlaybackMute") {
+    if (msg.name == "AudioPlayback") {
       let utils = global.content.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIDOMWindowUtils);
-      utils.audioMuted = msg.data.type === "mute";
+      var suspendTypes = Ci.nsISuspendedTypes;
+      switch (msg.data.type) {
+        case "mute":
+          utils.audioMuted = true;
+          break;
+        case "unmute":
+          utils.audioMuted = false;
+          break;
+        case "lostAudioFocusTransiently":
+          utils.mediaSuspended = suspendTypes.SUSPENDED_PAUSE;
+          break;
+        case "mediaControlPaused":
+          utils.mediaSuspended = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE;
+          break;
+        case "mediaControlStoppedOrLostAudioFocusPermanently":
+          utils.mediaSuspended = suspendTypes.SUSPENDED_STOP_DISPOSABLE;
+          break;
+        case "blockInactivePageMedia":
+          utils.mediaSuspended = suspendTypes.SUSPENDED_BLOCK;
+          break;
+        case "resumeMedia":
+          utils.mediaSuspended = suspendTypes.NONE_SUSPENDED;
+          break;
+        default:
+          dump("Error : AudioPlaybackListener, wrong suspended type!\n");
+          break;
+      }
     }
   },
 };
 AudioPlaybackListener.init();
 
 addMessageListener("Browser:PurgeSessionHistory", function BrowserPurgeHistory() {
   let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
   if (!sessionHistory) {
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -709,32 +709,76 @@
       <property name="audioMuted"
                 onget="return this._audioMuted;"
                 readonly="true"/>
 
       <method name="mute">
         <body>
           <![CDATA[
             this._audioMuted = true;
-            this.messageManager.sendAsyncMessage("AudioPlaybackMute",
+            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                  {type: "mute"});
           ]]>
         </body>
       </method>
 
       <method name="unmute">
         <body>
           <![CDATA[
             this._audioMuted = false;
-            this.messageManager.sendAsyncMessage("AudioPlaybackMute",
+            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                  {type: "unmute"});
           ]]>
         </body>
       </method>
 
+      <method name="pauseMedia">
+        <parameter name="disposable"/>
+        <body>
+          <![CDATA[
+            var suspendedReason;
+            if (disposable) {
+              suspendedReason = "mediaControlPaused";
+            } else {
+              suspendedReason = "lostAudioFocusTransiently";
+            }
+
+            this.messageManager.sendAsyncMessage("AudioPlayback",
+                                                 {type: suspendedReason});
+          ]]>
+        </body>
+      </method>
+
+      <method name="stopMedia">
+        <body>
+          <![CDATA[
+            this.messageManager.sendAsyncMessage("AudioPlayback",
+                                                 {type: "mediaControlStoppedOrLostAudioFocusPermanently"});
+          ]]>
+        </body>
+      </method>
+
+      <method name="blockMedia">
+        <body>
+          <![CDATA[
+            this.messageManager.sendAsyncMessage("AudioPlayback",
+                                                 {type: "blockInactivePageMedia"});
+          ]]>
+        </body>
+      </method>
+
+      <method name="resumeMedia">
+        <body>
+          <![CDATA[
+            this.messageManager.sendAsyncMessage("AudioPlayback",
+                                                 {type: "resumeMedia"});
+          ]]>
+        </body>
+      </method>
+
       <property name="securityUI">
         <getter>
           <![CDATA[
             if (!this.docShell.securityUI) {
               const SECUREBROWSERUI_CONTRACTID = "@mozilla.org/secure_browser_ui;1";
               if (!this.hasAttribute("disablesecurity") &&
                   SECUREBROWSERUI_CONTRACTID in Components.classes) {
                 var securityUI = Components.classes[SECUREBROWSERUI_CONTRACTID]