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
--- 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