Bug 1350947 - start loading plugin content after the tab is resumed. draft
authorAlastor Wu <alwu@mozilla.com>
Mon, 08 May 2017 14:04:27 +0800 (2017-05-08)
changeset 573935 1d5d43e0e146868eda5c9ab5cd7c0f296de9bdf7
parent 573930 9797e0df780e41b5ccfe81fb1768d5978af329a1
child 627442 bf0574328b030a9d114cedaa8a99bc54e55c0ecb
push id57544
push useralwu@mozilla.com
push dateMon, 08 May 2017 06:04:41 +0000 (2017-05-08)
bugs1350947
milestone55.0a1
Bug 1350947 - start loading plugin content after the tab is resumed. For the feature, blocking autoplay media, it would prevent media automatically starts playing even the tab is not visted before. For the consistentcy, we want all media won't start playing until the tab is resumed. (tab is visited by user) For media element, web audio, we have methods to acheive that goal, but can't do the same thing for Flash media content, because it doesn't support for pausing. Another alternative way is to to postpone loading the Flash content when the tab is not visited before, and resume loading when the tab is resumed. Therefore, we use the wrapper to register an audio channel agent in order to know whether the tab is blocked or not, and then the wrapper can decide when to start loading the plugin's content. In addition, we would also postpone document's onload event until the tab is resumed. MozReview-Commit-ID: Evx3sZvK1ow
dom/base/nsObjectLoadingContent.cpp
dom/base/nsObjectLoadingContent.h
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -604,33 +604,35 @@ nsObjectLoadingContent::nsObjectLoadingC
   , mNetworkCreated(true)
   , mActivated(false)
   , mContentBlockingEnabled(false)
   , mIsStopping(false)
   , mIsLoading(false)
   , mScriptRequested(false)
   , mRewrittenYoutubeEmbed(false)
   , mPreferFallback(false)
-  , mPreferFallbackKnown(false) {}
+  , mPreferFallbackKnown(false)
+  , mLoader(new PluginContentLoader(this)) {}
 
 nsObjectLoadingContent::~nsObjectLoadingContent()
 {
   // Should have been unbound from the tree at this point, and
   // CheckPluginStopEvent keeps us alive
   if (mFrameLoader) {
     NS_NOTREACHED("Should not be tearing down frame loaders at this point");
     mFrameLoader->Destroy();
   }
   if (mInstanceOwner || mInstantiating) {
     // This is especially bad as delayed stop will try to hold on to this
     // object...
     NS_NOTREACHED("Should not be tearing down a plugin at this point!");
     StopPluginInstance();
   }
   DestroyImageLoadingContent();
+  mLoader = nullptr;
 }
 
 nsresult
 nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
 {
   if (mInstanceOwner || mType != eType_Plugin || (mIsLoading != aIsLoading) ||
       mInstantiating) {
     // If we hit this assertion it's probably because LoadObject re-entered :(
@@ -2493,16 +2495,21 @@ nsObjectLoadingContent::OpenChannel()
   nsresult rv;
   mChannel = nullptr;
 
   // E.g. mms://
   if (!mURI || !CanHandleURI(mURI)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  if (mLoader->ShouldBlockLoadingContent()) {
+    mLoader->BlockLoadingPluginContent();
+    return NS_OK;
+  }
+
   nsCOMPtr<nsILoadGroup> group = doc->GetDocumentLoadGroup();
   nsCOMPtr<nsIChannel> chan;
   RefPtr<ObjectInterfaceRequestorShim> shim =
     new ObjectInterfaceRequestorShim(this);
 
   bool isSandBoxed = doc->GetSandboxFlags() & SANDBOXED_ORIGIN;
   bool inherit = nsContentUtils::ChannelShouldInheritPrincipal(thisContent->NodePrincipal(),
                                                                mURI,
@@ -3806,8 +3813,132 @@ nsObjectLoadingContent::SetupProtoChainR
   }
   nsObjectLoadingContent* objectLoadingContent =
     static_cast<nsObjectLoadingContent*>(mContent.get());
   objectLoadingContent->SetupProtoChain(cx, obj);
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(nsObjectLoadingContent::SetupProtoChainRunner, nsIRunnable)
+
+nsObjectLoadingContent::PluginContentLoader::PluginContentLoader(nsObjectLoadingContent* aLoader)
+  : mLoader(aLoader)
+  , mIsBlockedLoading(false)
+  , mHasEverBeenBlocked(false)
+{}
+
+NS_IMETHODIMP
+nsObjectLoadingContent::PluginContentLoader::WindowSuspendChanged(SuspendTypes aSuspend)
+{
+  MOZ_ASSERT(mAudioChannelAgent);
+
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("PluginContentLoader, WindowSuspendChanged, "
+          "this = %p, aSuspend = %s\n", this, SuspendTypeToStr(aSuspend)));
+
+  nsresult rv {NS_OK};
+  if (aSuspend == nsISuspendedTypes::NONE_SUSPENDED && mIsBlockedLoading) {
+    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+           ("PluginContentLoader, start loading content, this = %p\n", this));
+    mIsBlockedLoading = false;
+    mHasEverBeenBlocked = true;
+    NotifyAudioChannelAgent(false);
+    mLoader->OpenChannel();
+    BlockDocumentOnLoad(false);
+  }
+  return rv;
+}
+
+void
+nsObjectLoadingContent::PluginContentLoader::BlockLoadingPluginContent()
+{
+  MOZ_ASSERT(mAudioChannelAgent);
+  MOZ_ASSERT(!mHasEverBeenBlocked);
+
+  if (mIsBlockedLoading) {
+    return;
+  }
+
+  MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+         ("PluginContentLoader, block loading content, this = %p\n", this));
+  mIsBlockedLoading = true;
+  NotifyAudioChannelAgent(true);
+  BlockDocumentOnLoad(true);
+}
+
+bool
+nsObjectLoadingContent::PluginContentLoader::ShouldBlockLoadingContent()
+{
+  // Block loading would only happen once.
+  if (mHasEverBeenBlocked) {
+    return false;
+  }
+
+  if (!mAudioChannelAgent) {
+    nsCOMPtr<nsIContent> thisContent =
+      do_QueryInterface(static_cast<nsIObjectLoadingContent*>(mLoader));
+    MOZ_ASSERT(thisContent, "Must be an instance of content");
+    CreateAudioChannelAgent(thisContent->OwnerDoc()->GetInnerWindow());
+  }
+
+  return (mAudioChannelAgent && mAudioChannelAgent->ShouldBlockMedia());
+}
+
+void
+nsObjectLoadingContent::PluginContentLoader::BlockDocumentOnLoad(bool aBlock)
+{
+  nsCOMPtr<nsIContent> thisContent =
+    do_QueryInterface(static_cast<nsIObjectLoadingContent*>(mLoader));
+  MOZ_ASSERT(thisContent, "Must be an instance of content");
+  nsIDocument* doc = thisContent->OwnerDoc();
+
+  if (!doc) {
+    return;
+  }
+
+  if (aBlock) {
+    doc->BlockOnload();
+  } else {
+    doc->UnblockOnload(false);
+  }
+}
+
+void
+nsObjectLoadingContent::PluginContentLoader::NotifyAudioChannelAgent(bool aPlaying)
+{
+  MOZ_ASSERT(mAudioChannelAgent);
+
+  if (aPlaying) {
+    AudioPlaybackConfig config;
+    mAudioChannelAgent->NotifyStartedPlaying(&config,
+                                             AudioChannelService::AudibleState::eAudible);
+  } else {
+    mAudioChannelAgent->NotifyStoppedPlaying();
+    mAudioChannelAgent = nullptr;
+  }
+}
+
+bool
+nsObjectLoadingContent::PluginContentLoader::CreateAudioChannelAgent(mozIDOMWindow* aWindow)
+{
+  mAudioChannelAgent = new AudioChannelAgent();
+  nsresult rv = mAudioChannelAgent->Init(aWindow,
+                                         static_cast<int32_t>(AudioChannel::Normal),
+                                         this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mAudioChannelAgent = nullptr;
+    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+           ("PluginContentLoader, Fail to initialize the audio channel agent, "
+            "this = %p\n", this));
+    return false;
+  }
+
+  return true;
+}
+
+NS_IMPL_CYCLE_COLLECTION(nsObjectLoadingContent::PluginContentLoader, mAudioChannelAgent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsObjectLoadingContent::PluginContentLoader)
+  NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsObjectLoadingContent::PluginContentLoader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsObjectLoadingContent::PluginContentLoader)
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -19,16 +19,17 @@
 #include "nsIStreamListener.h"
 #include "nsIChannelEventSink.h"
 #include "nsIContentPolicy.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsIRunnable.h"
 #include "nsIThreadInternal.h"
 #include "nsIFrame.h"
 #include "nsIFrameLoader.h"
+#include "AudioChannelService.h"
 
 class nsAsyncInstantiateEvent;
 class nsStopPluginRunnable;
 class AutoSetInstantiatingToFalse;
 class nsIPrincipal;
 class nsFrameLoader;
 class nsPluginFrame;
 class nsXULElement;
@@ -344,16 +345,65 @@ class nsObjectLoadingContent : public ns
      * - If the node is the child of an <object> node that already has
      *   content being loaded.
      *
      * In these cases, this function will return false, which will cause
      * us to skip calling LoadObject.
      */
     bool BlockEmbedOrObjectContentLoading();
 
+    /**
+     * Used to prevent early loading the plugin's content before the tab is
+     * allowed to play any autoplay media.
+     */
+    class PluginContentLoader : public nsIAudioChannelAgentCallback
+    {
+    public:
+      NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+      NS_DECL_CYCLE_COLLECTION_CLASS(PluginContentLoader)
+
+      explicit PluginContentLoader(nsObjectLoadingContent* aLoader);
+
+      bool
+      ShouldBlockLoadingContent();
+
+      void
+      BlockLoadingPluginContent();
+
+      NS_IMETHODIMP
+      WindowSuspendChanged(SuspendTypes aSuspend) override;
+
+      NS_IMETHODIMP
+      WindowVolumeChanged(float aVolume, bool aMuted) override {return NS_OK;}
+
+      NS_IMETHODIMP
+      WindowAudioCaptureChanged(bool aCapture) override {return NS_OK;}
+
+    private:
+      virtual ~PluginContentLoader() {
+        mAudioChannelAgent = nullptr;
+      };
+
+      bool
+      CreateAudioChannelAgent(mozIDOMWindow* aWindow);
+
+      void
+      NotifyAudioChannelAgent(bool aPlaying);
+
+      void
+      BlockDocumentOnLoad(bool aBlock);
+
+      RefPtr<mozilla::dom::AudioChannelAgent> mAudioChannelAgent;
+      // Use raw pointer because the loadingContent's life would be longer than
+      // loader, and nsObjectLoadingContent doesn't support ref counting.
+      nsObjectLoadingContent* mLoader;
+      bool mIsBlockedLoading;
+      bool mHasEverBeenBlocked;
+    };
+
   private:
 
     // Object parameter changes returned by UpdateObjectParameters
     enum ParameterUpdateFlags {
       eParamNoChange           = 0,
       // Parameters that potentially affect the channel changed
       // - mOriginalURI, mOriginalContentType
       eParamChannelChanged     = 1u << 0,
@@ -708,11 +758,12 @@ class nsObjectLoadingContent : public ns
     bool                        mPreferFallback : 1;
     bool                        mPreferFallbackKnown : 1;
 
     WeakFrame                   mPrintFrame;
 
     RefPtr<nsPluginInstanceOwner> mInstanceOwner;
     nsTArray<mozilla::dom::MozPluginParameter> mCachedAttributes;
     nsTArray<mozilla::dom::MozPluginParameter> mCachedParameters;
+    RefPtr<PluginContentLoader> mLoader;
 };
 
 #endif