Bug 1493766 - part2 : save the status of temporary autoplay permission in outer window. r=johannh,smaug
authoralwu <alwu@mozilla.com>
Fri, 16 Nov 2018 18:27:00 +0000
changeset 503242 41fe6af27564c37af484fdad90833a065e30c314
parent 503241 0e0a171941b2169accb5f5af19b9094623666954
child 503243 7a9c6dea709ee75cef4d10beb79aeece3cf64d1f
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh, smaug
bugs1493766
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1493766 - part2 : save the status of temporary autoplay permission in outer window. r=johannh,smaug In order to know whether we have temporary autoplay permission without creating a request, we need to cache its state in the outer window so that we can get the correct returned value for AutoplayPolicy::IsAllowedToPlay(). Differential Revision: https://phabricator.services.mozilla.com/D7013
dom/base/nsDOMWindowUtils.cpp
dom/base/nsGlobalWindowOuter.cpp
dom/base/nsPIDOMWindow.h
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/media/AutoplayPolicy.cpp
toolkit/actors/AudioPlaybackChild.jsm
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -4367,16 +4367,32 @@ nsDOMWindowUtils::ResetPrefersReducedMot
   nsIWidget* widget = GetWidget();
   if (!widget) {
     return NS_OK;
   }
 
   return widget->ResetPrefersReducedMotionOverrideForTest();
 }
 
+NS_IMETHODIMP
+nsDOMWindowUtils::NotifyTemporaryAutoplayPermissionChanged(int32_t aState,
+                                                           const nsAString& aPrePath)
+{
+  nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryReferent(mWindow);
+  NS_ENSURE_STATE(window);
+
+  nsCOMPtr<nsPIDOMWindowOuter> topWindow = window->GetScriptableTop();
+  if (!topWindow) {
+    return NS_OK;
+  }
+
+  topWindow->NotifyTemporaryAutoplayPermissionChanged(aState, aPrePath);
+  return NS_OK;
+}
+
 NS_INTERFACE_MAP_BEGIN(nsTranslationNodeList)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsITranslationNodeList)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(nsTranslationNodeList)
 NS_IMPL_RELEASE(nsTranslationNodeList)
 
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -248,16 +248,18 @@
 #ifdef HAVE_SIDEBAR
 #include "mozilla/dom/ExternalBinding.h"
 #endif
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
 #endif
 
+#include "mozilla/HashFunctions.h"
+
 // Apple system headers seem to have a check() macro.  <sigh>
 #ifdef check
 class nsIScriptTimeoutHandler;
 #undef check
 #endif // check
 #include "AccessCheck.h"
 
 #ifdef ANDROID
@@ -315,16 +317,21 @@ using mozilla::TimeStamp;
   PR_END_MACRO
 
 static LazyLogModule gDOMLeakPRLogOuter("DOMLeakOuter");
 
 static int32_t              gOpenPopupSpamCount               = 0;
 
 nsGlobalWindowOuter::OuterWindowByIdTable *nsGlobalWindowOuter::sOuterWindowsById = nullptr;
 
+extern mozilla::LazyLogModule gAutoplayPermissionLog;
+
+#define AUTOPLAY_LOG(msg, ...)                                             \
+  MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
 /* static */
 nsPIDOMWindowOuter*
 nsPIDOMWindowOuter::GetFromCurrentInner(nsPIDOMWindowInner* aInner)
 {
   if (!aInner) {
     return nullptr;
   }
 
@@ -7803,16 +7810,76 @@ nsGlobalWindowOuter::Create(nsIDocShell*
 }
 
 nsIURI*
 nsPIDOMWindowOuter::GetDocumentURI() const
 {
   return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get();
 }
 
+void
+nsPIDOMWindowOuter::NotifyTemporaryAutoplayPermissionChanged(int32_t aState,
+                                                             const nsAString& aPrePath)
+{
+  MOZ_ASSERT(int32_t(nsIPermissionManager::ALLOW_ACTION) == int32_t(nsIDOMWindowUtils::PERMISSION_ALLOW) &&
+             int32_t(nsIPermissionManager::DENY_ACTION) == int32_t(nsIDOMWindowUtils::PERMISSION_DENY) &&
+             int32_t(nsIPermissionManager::UNKNOWN_ACTION) == int32_t(nsIDOMWindowUtils::PERMISSION_UNKNOWN));
+
+  bool isAllowed = aState == nsIDOMWindowUtils::PERMISSION_ALLOW;
+  switch (aState) {
+    case nsIDOMWindowUtils::PERMISSION_ALLOW:
+    case nsIDOMWindowUtils::PERMISSION_DENY:
+      AUTOPLAY_LOG("update temporary autoplay permission");
+      mAutoplayTemporaryPermission.Put(aPrePath, PermissionInfo(isAllowed, TimeStamp::Now()));
+      break;
+    case nsIDOMWindowUtils::PERMISSION_UNKNOWN:
+      if (!aPrePath.Length()) {
+        AUTOPLAY_LOG("remove temporary autoplay permission for all domains");
+        mAutoplayTemporaryPermission.Clear();
+      } else {
+        AUTOPLAY_LOG("remove temporary autoplay permission");
+        mAutoplayTemporaryPermission.Remove(aPrePath);
+      }
+      break;
+    default:
+      AUTOPLAY_LOG("ERROR! non-defined permission state!");
+  }
+}
+
+bool
+nsPIDOMWindowOuter::HasTemporaryAutoplayPermission()
+{
+  if (!mDoc) {
+    return false;
+  }
+
+  nsIPrincipal* principal = mDoc->NodePrincipal();
+  if (!principal) {
+    return false;
+  }
+
+  nsCOMPtr<nsIURI> URI;
+  nsresult rv = principal->GetURI(getter_AddRefs(URI));
+  if (NS_FAILED(rv) || !URI) {
+    return false;
+  }
+
+  nsAutoCString prePath;
+  URI->GetPrePath(prePath);
+  NS_ConvertUTF8toUTF16 key(prePath);
+  PermissionInfo info = mAutoplayTemporaryPermission.Get(key);
+  int32_t expireTime = Preferences::GetInt("privacy.temporary_permission_expire_time_ms", 3600 * 1000);
+  if (info.first &&
+      TimeStamp::Now() - info.second >= TimeDuration::FromMilliseconds(expireTime)) {
+    AUTOPLAY_LOG("remove expired temporary autoplay permission");
+    mAutoplayTemporaryPermission.Remove(key);
+    return false;
+  }
+  return info.first;
+}
 
 void
 nsPIDOMWindowOuter::MaybeCreateDoc()
 {
   MOZ_ASSERT(!mDoc);
   if (nsIDocShell* docShell = GetDocShell()) {
     // Note that |document| here is the same thing as our mDoc, but we
     // don't have to explicitly set the member variable because the docshell
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -6,19 +6,21 @@
 
 #ifndef nsPIDOMWindow_h__
 #define nsPIDOMWindow_h__
 
 #include "nsIDOMWindow.h"
 #include "mozIDOMWindow.h"
 
 #include "nsCOMPtr.h"
+#include "nsDataHashtable.h"
 #include "nsTArray.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/TaskCategory.h"
+#include "mozilla/TimeStamp.h"
 #include "js/TypeDecls.h"
 #include "nsRefPtrHashtable.h"
 
 // Only fired for inner windows.
 #define DOM_WINDOW_DESTROYED_TOPIC "dom-window-destroyed"
 #define DOM_WINDOW_FROZEN_TOPIC "dom-window-frozen"
 #define DOM_WINDOW_THAWED_TOPIC "dom-window-thawed"
 
@@ -855,16 +857,23 @@ public:
 
   mozilla::dom::TabGroup* TabGroup();
 
   virtual nsPIDOMWindowOuter* GetPrivateRoot() = 0;
 
   virtual void ActivateOrDeactivate(bool aActivate) = 0;
 
   /**
+   * These functions are used to modify and check temporary autoplay permission.
+   */
+  void NotifyTemporaryAutoplayPermissionChanged(int32_t aState,
+                                                const nsAString& aPrePath);
+  bool HasTemporaryAutoplayPermission();
+
+  /**
    * |top| gets the root of the window hierarchy.
    *
    * This function does not cross chrome-content boundaries, so if this
    * window's parent is of a different type, |top| will return this window.
    *
    * When script reads the top property, we run GetScriptableTop, which
    * will not cross an <iframe mozbrowser> boundary.
    *
@@ -1273,16 +1282,19 @@ protected:
 
   // Let the service workers plumbing know that some feature are enabled while
   // testing.
   bool mServiceWorkersTestingEnabled;
 
   mozilla::dom::LargeAllocStatus mLargeAllocStatus;
 
   nsCOMPtr<nsPIDOMWindowOuter> mOpenerForInitialContentBrowser;
+
+  using PermissionInfo = std::pair<bool, mozilla::TimeStamp>;
+  nsDataHashtable<nsStringHashKey, PermissionInfo> mAutoplayTemporaryPermission;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowOuter, NS_PIDOMWINDOWOUTER_IID)
 
 #include "nsPIDOMWindowInlines.h"
 
 #ifdef MOZILLA_INTERNAL_API
 #define NS_AUTO_POPUP_STATE_PUSHER nsAutoPopupStatePusherInternal
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1944,16 +1944,27 @@ interface nsIDOMWindowUtils : nsISupport
    * Currently this function is available only on MacOSX.
    */
   void setPrefersReducedMotionOverrideForTest(in boolean aValue);
   /**
    * Reset the internal state to be used for above setPrefersReducedMotion.
    */
   void resetPrefersReducedMotionOverrideForTest();
 
+  /**
+   * Used to notify temporary autoplay permission change and cache it on the dom
+   * window. These states are equal to nsIPermissionManager's action states.
+   */
+  const long PERMISSION_UNKNOWN = 0;
+  const long PERMISSION_ALLOW = 1;
+  const long PERMISSION_DENY = 2;
+
+  void notifyTemporaryAutoplayPermissionChanged(in long aState,
+                                                in AString aPrePath);
+
   // These consts are only for testing purposes.
   const long DEFAULT_MOUSE_POINTER_ID = 0;
   const long DEFAULT_PEN_POINTER_ID   = 1;
   const long DEFAULT_TOUCH_POINTER_ID = 2;
 
   // Match WidgetMouseEventBase::buttonType.
   const long MOUSE_BUTTON_LEFT_BUTTON   = 0;
   const long MOUSE_BUTTON_MIDDLE_BUTTON = 1;
--- a/dom/media/AutoplayPolicy.cpp
+++ b/dom/media/AutoplayPolicy.cpp
@@ -85,24 +85,30 @@ IsWindowAllowedToPlay(nsPIDOMWindowInner
   // content document and check permissions etc on the top level content
   // document. FeaturePolicy propagates the permission to any sub-documents if
   // they don't have special directives.
   if (!FeaturePolicyUtils::IsFeatureAllowed(aWindow->GetExtantDoc(),
                                             NS_LITERAL_STRING("autoplay"))) {
     return false;
   }
 
+  nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
+  if (topWindow && topWindow->HasTemporaryAutoplayPermission()) {
+    AUTOPLAY_LOG("Allow autoplay as document has temporary autoplay permission.");
+    return true;
+  }
+
   nsIDocument* approver = ApproverDocOf(*aWindow->GetExtantDoc());
   if (!approver) {
     return false;
   }
 
   if (nsContentUtils::IsExactSitePermAllow(approver->NodePrincipal(),
                                            "autoplay-media")) {
-    AUTOPLAY_LOG("Allow autoplay as document has autoplay permission.");
+    AUTOPLAY_LOG("Allow autoplay as document has permanent autoplay permission.");
     return true;
   }
 
   if (approver->HasBeenUserGestureActivated()) {
     AUTOPLAY_LOG("Allow autoplay as document activated by user gesture.");
     return true;
   }
 
@@ -145,17 +151,17 @@ IsMediaElementAllowedToPlay(const HTMLMe
 {
   if ((aElement.Volume() == 0.0 || aElement.Muted()) &&
        Preferences::GetBool("media.autoplay.allow-muted", true)) {
     AUTOPLAY_LOG("Allow muted media %p to autoplay.", &aElement);
     return true;
   }
 
   if (IsWindowAllowedToPlay(aElement.OwnerDoc()->GetInnerWindow())) {
-    AUTOPLAY_LOG("Autoplay allowed as activated/whitelisted window, media %p.", &aElement);
+    AUTOPLAY_LOG("Autoplay allowed as window is allowed to play, media %p.", &aElement);
     return true;
   }
 
   nsIDocument* topDocument = ApproverDocOf(*aElement.OwnerDoc());
   if (topDocument &&
       topDocument->MediaDocumentKind() == nsIDocument::MediaDocumentKind::Video) {
     AUTOPLAY_LOG("Allow video document %p to autoplay", &aElement);
     return true;
--- a/toolkit/actors/AudioPlaybackChild.jsm
+++ b/toolkit/actors/AudioPlaybackChild.jsm
@@ -59,23 +59,24 @@ class AudioPlaybackChild extends ActorCh
         } else {
           name += (data === "active") ? "Start" : "Stop";
         }
         this.mm.sendAsyncMessage(name);
       }
     }
   }
 
-  receiveMessage(msg) {
-    switch (msg.name) {
+  receiveMessage({name, data}) {
+    switch (name) {
       case "AudioPlayback":
-        this.handleMediaControlMessage(msg.data.type);
+        this.handleMediaControlMessage(data.type);
         break;
       case "TemporaryPermissionChanged":
-        if (msg.data.permission !== "autoplay-media") {
+        if (data.permission !== "autoplay-media") {
           return;
         }
-
-        // TODO : update permission in content side.
+        let utils = this.content.windowUtils;
+        utils.notifyTemporaryAutoplayPermissionChanged(data.state,
+                                                       data.prePath);
         break;
     }
   }
 }