Bug 839714 - Extend PlayPreview API. r=jschoenick, r=jwein
authorYury Delendik <ydelendik@mozilla.com>
Thu, 14 Feb 2013 15:38:41 -0600
changeset 121977 45b37a31ac9cc96cc829a2cd8dff86096a062310
parent 121976 960f1346d63d0d90de7435ed596a7c6b86cebc58
child 121978 9f1436e907830159ecd77c444ae3f48d31f3059c
push id24314
push userryanvm@gmail.com
push dateFri, 15 Feb 2013 14:39:46 +0000
treeherdermozilla-central@326c5e4868fe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjschoenick, jwein
bugs839714
milestone21.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 839714 - Extend PlayPreview API. r=jschoenick, r=jwein
browser/base/content/browser-plugins.js
browser/base/content/test/Makefile.in
browser/base/content/test/browser_pluginplaypreview.js
browser/base/content/test/browser_pluginplaypreview2.js
content/base/src/nsObjectLoadingContent.cpp
content/base/src/nsObjectLoadingContent.h
dom/plugins/base/Makefile.in
dom/plugins/base/nsIPluginHost.idl
dom/plugins/base/nsPluginHost.cpp
dom/plugins/base/nsPluginHost.h
dom/plugins/base/nsPluginPlayPreviewInfo.cpp
dom/plugins/base/nsPluginPlayPreviewInfo.h
mobile/android/chrome/content/browser.js
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -299,20 +299,29 @@ var gPluginHandler = {
     if (!gPluginHandler.isKnownPlugin(objLoadingContent))
       return false;
 
     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
     let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
     let browser = gBrowser.getBrowserForDocument(objLoadingContent.ownerDocument.defaultView.top.document);
     let pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
 
+    let isFallbackTypeValid =
+      objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
+      objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
+
+    if (objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW) {
+      // checking if play preview is subject to CTP rules
+      let playPreviewInfo = pluginHost.getPlayPreviewInfo(objLoadingContent.actualType);
+      isFallbackTypeValid = !playPreviewInfo.ignoreCTP;
+    }
+
     return !objLoadingContent.activated &&
            pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
-           objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
-           objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
+           isFallbackTypeValid;
   },
 
   activatePlugins: function PH_activatePlugins(aContentWindow) {
     let browser = gBrowser.getBrowserForDocument(aContentWindow.document);
     browser._clickToPlayAllPluginsActivated = true;
     let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils);
     let plugins = cwu.plugins;
@@ -467,30 +476,43 @@ var gPluginHandler = {
       }, true);
     }
 
     gPluginHandler._showClickToPlayNotification(browser);
   },
 
   _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) {
     let doc = aPlugin.ownerDocument;
+    let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+    let pluginInfo = this._getPluginInfo(aPlugin);
+    let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype);
+
+    if (!playPreviewInfo.ignoreCTP) {
+      // if click-to-play rules used, play plugin at once if plugins were
+      // activated for this window
+      if (browser._clickToPlayAllPluginsActivated ||
+          browser._clickToPlayPluginsActivated.get(pluginInfo.pluginName)) {
+        objLoadingContent.playPlugin();
+        return;
+      }
+    }
+
     let previewContent = doc.getAnonymousElementByAttribute(aPlugin, "class", "previewPluginContent");
     let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
     if (!iframe) {
       // lazy initialization of the iframe
       iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
       iframe.className = "previewPluginContentFrame";
       previewContent.appendChild(iframe);
 
       // Force a style flush, so that we ensure our binding is attached.
       aPlugin.clientTop;
     }
-    let pluginInfo = this._getPluginInfo(aPlugin);
-    let playPreviewUri = "data:application/x-moz-playpreview;," + pluginInfo.mimetype;
-    iframe.src = playPreviewUri;
+    iframe.src = playPreviewInfo.redirectURL;
 
     // MozPlayPlugin event can be dispatched from the extension chrome
     // code to replace the preview content with the native plugin
     previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) {
       if (!aEvent.isTrusted)
         return;
 
       previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true);
@@ -498,16 +520,20 @@ var gPluginHandler = {
       let playPlugin = !aEvent.detail;
       gPluginHandler.stopPlayPreview(aPlugin, playPlugin);
 
       // cleaning up: removes overlay iframe from the DOM
       let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
       if (iframe)
         previewContent.removeChild(iframe);
     }, true);
+
+    if (!playPreviewInfo.ignoreCTP) {
+      gPluginHandler._showClickToPlayNotification(browser);
+    }
   },
 
   reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
     let browser = gBrowser.selectedBrowser;
     if (gPluginHandler._pluginNeedsActivationExceptThese([]))
       gPluginHandler._showClickToPlayNotification(browser);
   },
 
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -181,16 +181,17 @@ endif
                  browser_locationBarExternalLoad.js \
                  browser_page_style_menu.js \
                  browser_pinnedTabs.js \
                  browser_plainTextLinks.js \
                  browser_pluginnotification.js \
                  browser_plugins_added_dynamically.js \
                  browser_CTPScriptPlugin.js \
                  browser_pluginplaypreview.js \
+                 browser_pluginplaypreview2.js \
                  browser_private_browsing_window.js \
                  browser_relatedTabs.js \
                  browser_sanitize-passwordDisabledHosts.js \
                  browser_sanitize-sitepermissions.js \
                  browser_sanitize-timespans.js \
                  browser_clearplugindata.js \
                  browser_clearplugindata.html \
                  browser_clearplugindata_noage.html \
--- a/browser/base/content/test/browser_pluginplaypreview.js
+++ b/browser/base/content/test/browser_pluginplaypreview.js
@@ -106,17 +106,17 @@ function registerPlayPreview(mimeType, t
 
     // nsIRequestObserver::onStopRequest
     onStopRequest: function(aRequest, aContext, aStatusCode) {
       // Do nothing.
     }
   };
 
   var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-  ph.registerPlayPreviewMimeType(mimeType);
+  ph.registerPlayPreviewMimeType(mimeType, true); // ignoring CTP rules
 
   var factory = new StreamConverterFactory();
   factory.register(OverlayStreamConverter);
 
   return (gPlayPreviewRegistration = {
     unregister: function() {
       ph.unregisterPlayPreviewMimeType(mimeType);
       factory.unregister();
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_pluginplaypreview2.js
@@ -0,0 +1,173 @@
+/* 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/. */
+
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gNextTestSkip = 0;
+var gPlayPreviewPluginActualEvents = 0;
+var gPlayPreviewPluginExpectedEvents = 1;
+
+var gPlayPreviewRegistration = null;
+
+function registerPlayPreview(mimeType, targetUrl) {
+  var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+  ph.registerPlayPreviewMimeType(mimeType, false, targetUrl);
+
+  return (gPlayPreviewRegistration = {
+    unregister: function() {
+      ph.unregisterPlayPreviewMimeType(mimeType);
+      gPlayPreviewRegistration = null;
+    }
+  });
+}
+
+function unregisterPlayPreview() {
+  gPlayPreviewRegistration.unregister();
+}
+
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+
+function test() {
+  waitForExplicitFinish();
+  registerCleanupFunction(function() {
+    if (gPlayPreviewRegistration)
+      gPlayPreviewRegistration.unregister();
+    Services.prefs.clearUserPref("plugins.click_to_play");
+  });
+
+  var newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  gTestBrowser = gBrowser.selectedBrowser;
+  gTestBrowser.addEventListener("load", pageLoad, true);
+  gTestBrowser.addEventListener("PluginBindingAttached", handleBindingAttached, true, true);
+
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+  registerPlayPreview('application/x-test', 'about:');
+  prepareTest(test1a, gTestRoot + "plugin_test.html", 1);
+}
+
+function finishTest() {
+  gTestBrowser.removeEventListener("load", pageLoad, true);
+  gTestBrowser.removeEventListener("PluginBindingAttached", handleBindingAttached, true, true);
+  gBrowser.removeCurrentTab();
+  window.focus();
+  finish();
+}
+
+function handleBindingAttached(evt) {
+  if (evt.target instanceof Ci.nsIObjectLoadingContent &&
+      evt.target.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW)
+    gPlayPreviewPluginActualEvents++;
+}
+
+function pageLoad() {
+  // The plugin events are async dispatched and can come after the load event
+  // This just allows the events to fire before we then go on to test the states
+
+  // iframe might triggers load event as well, making sure we skip some to let
+  // all iframes on the page be loaded as well
+  if (gNextTestSkip) {
+    gNextTestSkip--;
+    return;
+  }
+  executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url, skip) {
+  gNextTest = nextTest;
+  gNextTestSkip = skip;
+  gTestBrowser.contentWindow.location = url;
+}
+
+// Tests a page with normal play preview registration (1/2)
+function test1a() {
+  var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+  ok(!notificationBox.getNotificationWithValue("missing-plugins"), "Test 1a, Should not have displayed the missing plugin notification");
+  ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 1a, Should not have displayed the blocked plugin notification");
+
+  var pluginInfo = getTestPlugin();
+  ok(pluginInfo, "Should have a test plugin");
+
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 1a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+  ok(!objLoadingContent.activated, "Test 1a, Plugin should not be activated");
+
+  var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+  ok(overlay, "Test 1a, the overlay div is expected");
+
+  var iframe = overlay.getElementsByClassName("previewPluginContentFrame")[0];
+  ok(iframe && iframe.localName == "iframe", "Test 1a, the overlay iframe is expected");
+  var iframeHref = iframe.contentWindow.location.href;
+  ok(iframeHref == "about:", "Test 1a, the overlay about: content is expected");
+
+  var rect = iframe.getBoundingClientRect();
+  ok(rect.width == 200, "Test 1a, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being replaced by actual plugin");
+  ok(rect.height == 200, "Test 1a, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being replaced by actual plugin");
+
+  var e = overlay.ownerDocument.createEvent("CustomEvent");
+  e.initCustomEvent("MozPlayPlugin", true, true, null);
+  overlay.dispatchEvent(e);
+  var condition = function() objLoadingContent.activated;
+  waitForCondition(condition, test1b, "Test 1a, Waited too long for plugin to stop play preview");
+}
+
+// Tests that activating via MozPlayPlugin through the notification works (part 2/2)
+function test1b() {
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 1b, Plugin should be activated");
+
+  is(gPlayPreviewPluginActualEvents, gPlayPreviewPluginExpectedEvents,
+     "There should be exactly one PluginPlayPreview event");
+
+  unregisterPlayPreview();
+
+  prepareTest(test2, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- the mime type was just unregistered.
+function test2() {
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.pluginFallbackType != Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 2, plugin fallback type should not be PLUGIN_PLAY_PREVIEW");
+  ok(!objLoadingContent.activated, "Test 2, Plugin should not be activated");
+
+  registerPlayPreview('application/x-unknown', 'about:');
+
+  prepareTest(test3, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- diffent play preview type is reserved.
+function test3() {
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.pluginFallbackType != Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 3, plugin fallback type should not be PLUGIN_PLAY_PREVIEW");
+  ok(!objLoadingContent.activated, "Test 3, Plugin should not be activated");
+
+  unregisterPlayPreview();
+
+  registerPlayPreview('application/x-test', 'about:');
+  Services.prefs.setBoolPref("plugins.click_to_play", false);
+  prepareTest(test4, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- click-to-play is off
+function test4() {
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 4, Plugin should be activated");
+
+  finishTest();
+}
+
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -1753,36 +1753,30 @@ nsObjectLoadingContent::LoadObject(bool 
         //            reject plugins
         fallbackType = eFallbackUserDisabled;
       } else {
         fallbackType = eFallbackSuppressed;
       }
     }
   }
 
-  // Items resolved as Image/Document will not be checked for previews, as well
-  // as invalid plugins (they will not have the mContentType set).
-  if ((mType == eType_Null || mType == eType_Plugin) && ShouldPreview()) {
-    // If plugin preview exists, we shall use it
-    LOG(("OBJLC [%p]: Using plugin preview", this));
-    mType = eType_Null;
-    fallbackType = eFallbackPlayPreview;
-  }
-
   // If we're a plugin but shouldn't start yet, load fallback with
-  // reason click-to-play instead
+  // reason click-to-play instead. Items resolved as Image/Document
+  // will not be checked for previews, as well as invalid plugins
+  // (they will not have the mContentType set).
   FallbackType clickToPlayReason;
-  if (mType == eType_Plugin && !ShouldPlay(clickToPlayReason)) {
+  if ((mType == eType_Null || mType == eType_Plugin) &&
+      !ShouldPlay(clickToPlayReason)) {
     LOG(("OBJLC [%p]: Marking plugin as click-to-play", this));
     mType = eType_Null;
     fallbackType = clickToPlayReason;
   }
 
   if (!mActivated && mType == eType_Plugin) {
-    // Object passed ShouldPlay and !ShouldPreview, so it should be considered
+    // Object passed ShouldPlay, so it should be considered
     // activated until it changes content type
     LOG(("OBJLC [%p]: Object implicitly activated", this));
     mActivated = true;
   }
 
   // Sanity check: We shouldn't have any loaded resources, pending events, or
   // a final listener at this point
   if (mFrameLoader || mPendingInstantiateEvent || mInstanceOwner ||
@@ -2645,37 +2639,44 @@ nsObjectLoadingContent::CancelPlayPrevie
   if (mType == eType_Null && mFallbackType == eFallbackPlayPreview) {
     return LoadObject(true, true);
   }
 
   return NS_OK;
 }
 
 bool
-nsObjectLoadingContent::ShouldPreview()
-{
-  if (mPlayPreviewCanceled || mActivated)
-    return false;
-
-  nsRefPtr<nsPluginHost> pluginHost =
-    already_AddRefed<nsPluginHost>(nsPluginHost::GetInst());
-
-  return pluginHost->IsPluginPlayPreviewForType(mContentType.get());
-}
-
-bool
 nsObjectLoadingContent::ShouldPlay(FallbackType &aReason)
 {
   // mActivated is true if we've been activated via PlayPlugin() (e.g. user has
   // clicked through). Otherwise, only play if click-to-play is off or if page
   // is whitelisted
 
   nsRefPtr<nsPluginHost> pluginHost =
     already_AddRefed<nsPluginHost>(nsPluginHost::GetInst());
 
+  nsCOMPtr<nsIPluginPlayPreviewInfo> playPreviewInfo;
+  bool isPlayPreviewSpecified = NS_SUCCEEDED(pluginHost->GetPlayPreviewInfo(
+    mContentType, getter_AddRefs(playPreviewInfo)));
+  bool ignoreCTP = false;
+  if (isPlayPreviewSpecified) {
+    playPreviewInfo->GetIgnoreCTP(&ignoreCTP);
+  }
+  if (isPlayPreviewSpecified && !mPlayPreviewCanceled && !mActivated &&
+      ignoreCTP) {
+    // play preview in ignoreCTP mode is shown even if the native plugin
+    // is not present/installed
+    aReason = eFallbackPlayPreview;
+    return false;
+  }
+  // at this point if it's not a plugin, we let it play/fallback
+  if (mType != eType_Plugin) {
+    return true;
+  }
+
   bool isCTP;
   nsresult rv = pluginHost->IsPluginClickToPlayForType(mContentType, &isCTP);
   if (NS_FAILED(rv)) {
     return false;
   }
 
   if (!isCTP || mActivated) {
     return true;
@@ -2730,11 +2731,17 @@ nsObjectLoadingContent::ShouldPlay(Fallb
     uint32_t permission;
     rv = permissionManager->TestPermissionFromPrincipal(topDoc->NodePrincipal(),
                                                         permissionString.Data(),
                                                         &permission);
     NS_ENSURE_SUCCESS(rv, false);
     allowPerm = permission == nsIPermissionManager::ALLOW_ACTION;
   }
 
+  if (aReason == eFallbackClickToPlay && isPlayPreviewSpecified &&
+      !mPlayPreviewCanceled && !ignoreCTP) {
+    // play preview in click-to-play mode is shown instead of standard CTP UI
+    aReason = eFallbackPlayPreview;
+  }
+
   return allowPerm;
 }
 
--- a/content/base/src/nsObjectLoadingContent.h
+++ b/content/base/src/nsObjectLoadingContent.h
@@ -297,21 +297,16 @@ class nsObjectLoadingContent : public ns
     /**
      * If this object is allowed to play plugin content, or if it would display
      * click-to-play instead.
      * NOTE that this does not actually check if the object is a loadable plugin
      */
     bool ShouldPlay(FallbackType &aReason);
 
     /**
-     * If the object should display preview content for the current mContentType
-     */
-    bool ShouldPreview();
-
-    /**
      * Helper to check if our current URI passes policy
      *
      * @param aContentPolicy [out] The result of the content policy decision
      *
      * @return true if call succeeded and NS_CP_ACCEPTED(*aContentPolicy)
      */
     bool CheckLoadPolicy(int16_t *aContentPolicy);
 
--- a/dom/plugins/base/Makefile.in
+++ b/dom/plugins/base/Makefile.in
@@ -40,16 +40,17 @@ EXPORTS = \
   nsPluginNativeWindow.h \
   nsPluginsCID.h \
   nsNPAPIPluginInstance.h \
   nsPluginsDir.h \
   nsPluginTags.h \
   nsPluginDirServiceProvider.h \
   nsPluginHost.h \
   nsPluginInstanceOwner.h \
+  nsPluginPlayPreviewInfo.h \
   $(NULL)
 
 EXPORTS_mozilla = \
   PluginPRLibrary.h \
   $(NULL)
 
 CPPSRCS		= \
 		nsNPAPIPlugin.cpp \
@@ -57,16 +58,17 @@ CPPSRCS		= \
 		nsNPAPIPluginStreamListener.cpp \
 		nsPluginStreamListenerPeer.cpp \
 		nsPluginHost.cpp \
 		nsPluginModule.cpp \
 		nsJSNPRuntime.cpp \
 		nsPluginTags.cpp \
 		PluginPRLibrary.cpp \
 		nsPluginInstanceOwner.cpp \
+		nsPluginPlayPreviewInfo.cpp \
 		$(NULL)
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),android)
 DIRS += android
 LOCAL_INCLUDES += -I$(topsrcdir)/dom/plugins/base/android
 else
 # android_npapi.h extends the NPNVariable and NPPVariable enums
 # using #defines, which results in Wswitch warnings in gcc-4.6.
--- a/dom/plugins/base/nsIPluginHost.idl
+++ b/dom/plugins/base/nsIPluginHost.idl
@@ -7,17 +7,25 @@
 #include "nsISupports.idl"
 #include "nsIPluginTag.idl"
 
 %{C++
 #define MOZ_PLUGIN_HOST_CONTRACTID \
   "@mozilla.org/plugin/host;1"
 %}
 
-[scriptable, uuid(3ac8fe33-c38c-4123-b2f0-0e8a2824b9c5)]
+[scriptable, uuid(f89e7679-0adf-4a30-bda9-1afe1ee270d6)]
+interface nsIPluginPlayPreviewInfo : nsISupports
+{
+  readonly attribute AUTF8String mimeType;
+  readonly attribute boolean     ignoreCTP;
+  readonly attribute AUTF8String redirectURL;
+};
+
+[scriptable, uuid(67ebff01-0dce-48f7-b6a5-6235fc78382b)]
 interface nsIPluginHost : nsISupports
 {
   /**
    * Causes the plugins directory to be searched again for new plugin 
    * libraries.
    *
    * @param reloadPages - indicates whether currently visible pages should 
    * also be reloaded
@@ -71,19 +79,27 @@ interface nsIPluginHost : nsISupports
    *                will be determined; if any data for the base domain or its
    *                subdomains is found, return true.
    */
   boolean siteHasData(in nsIPluginTag plugin, in AUTF8String domain);
 
   /**
    * Registers the play preview plugin mode for specific mime type
    *
-   * @param mimeType - specified mime type
+   * @param mimeType: specifies plugin mime type.
+   * @param ignoreCTP: if true, the play preview ignores CTP rules, e.g.
+                       whitelisted websites, will not notify about plugin
+                       presence in the address bar.
+   * @param redirectURL: specifies url for the overlay iframe
    */
-  void registerPlayPreviewMimeType(in AUTF8String mimeType);
+  void registerPlayPreviewMimeType(in AUTF8String mimeType,
+                                   [optional] in boolean ignoreCTP,
+                                   [optional] in AUTF8String redirectURL);
 
   void unregisterPlayPreviewMimeType(in AUTF8String mimeType);
 
+  nsIPluginPlayPreviewInfo getPlayPreviewInfo(in AUTF8String mimeType);
+
   ACString getPermissionStringForType(in AUTF8String mimeType);
 
   bool isPluginClickToPlayForType(in AUTF8String mimeType);
 };
 
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1149,27 +1149,16 @@ nsPluginHost::IsPluginClickToPlayForType
   }
   else {
     *aResult = false;
   }
 
   return NS_OK;
 }
 
-bool
-nsPluginHost::IsPluginPlayPreviewForType(const char* aMimeType)
-{
-  for (uint32_t i = 0; i < mPlayPreviewMimeTypes.Length(); i++) {
-    nsCString mt = mPlayPreviewMimeTypes[i];
-    if (PL_strcasecmp(mt.get(), aMimeType) == 0)
-      return true;
-  }
-  return false;
-}
-
 nsresult
 nsPluginHost::GetBlocklistStateForType(const char *aMimeType, uint32_t *aState)
 {
   nsPluginTag *plugin = FindPluginForType(aMimeType, true);
   if (plugin) {
     nsCOMPtr<nsIBlocklistService> blocklist = do_GetService("@mozilla.org/extensions/blocklist;1");
     if (blocklist) {
       // The EmptyString()s are so we use the currently running application
@@ -1643,37 +1632,66 @@ nsPluginHost::EnumerateSiteData(const ns
       break;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsPluginHost::RegisterPlayPreviewMimeType(const nsACString& mimeType)
+nsPluginHost::RegisterPlayPreviewMimeType(const nsACString& mimeType,
+                                          bool ignoreCTP,
+                                          const nsACString& redirectURL)
 {
-  mPlayPreviewMimeTypes.AppendElement(mimeType);
+  nsAutoCString mt(mimeType);
+  nsAutoCString url(redirectURL);
+  if (url.Length() == 0) {
+    // using default play preview iframe URL, if redirectURL is not specified
+    url.Assign("data:application/x-moz-playpreview;,");
+    url.Append(mimeType);
+  }
+
+  nsRefPtr<nsPluginPlayPreviewInfo> playPreview =
+    new nsPluginPlayPreviewInfo(mt.get(), ignoreCTP, url.get());
+  mPlayPreviewMimeTypes.AppendElement(playPreview);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPluginHost::UnregisterPlayPreviewMimeType(const nsACString& mimeType)
 {
   nsAutoCString mimeTypeToRemove(mimeType);
-  for (uint32_t i = mPlayPreviewMimeTypes.Length(); i > 0;) {
-    nsCString mt = mPlayPreviewMimeTypes[--i];
-    if (PL_strcasecmp(mt.get(), mimeTypeToRemove.get()) == 0) {
-      mPlayPreviewMimeTypes.RemoveElementAt(i);
+  for (uint32_t i = mPlayPreviewMimeTypes.Length(); i > 0; i--) {
+    nsRefPtr<nsPluginPlayPreviewInfo> pp = mPlayPreviewMimeTypes[i - 1];
+    if (PL_strcasecmp(pp.get()->mMimeType.get(), mimeTypeToRemove.get()) == 0) {
+      mPlayPreviewMimeTypes.RemoveElementAt(i - 1);
       break;
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsPluginHost::GetPlayPreviewInfo(const nsACString& mimeType,
+                                 nsIPluginPlayPreviewInfo** aResult)
+{
+  nsAutoCString mimeTypeToFind(mimeType);
+  for (uint32_t i = 0; i < mPlayPreviewMimeTypes.Length(); i++) {
+    nsRefPtr<nsPluginPlayPreviewInfo> pp = mPlayPreviewMimeTypes[i];
+    if (PL_strcasecmp(pp.get()->mMimeType.get(), mimeTypeToFind.get()) == 0) {
+      *aResult = new nsPluginPlayPreviewInfo(pp.get());
+      NS_ADDREF(*aResult);
+      return NS_OK;
+    }
+  }
+  *aResult = nullptr;
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
 nsPluginHost::ClearSiteData(nsIPluginTag* plugin, const nsACString& domain,
                             uint64_t flags, int64_t maxAge)
 {
   // maxAge must be either a nonnegative integer or -1.
   NS_ENSURE_ARG(maxAge >= 0 || maxAge == -1);
 
   // Caller may give us a tag object that is no longer live.
   if (!IsLiveTag(plugin)) {
--- a/dom/plugins/base/nsPluginHost.h
+++ b/dom/plugins/base/nsPluginHost.h
@@ -20,16 +20,17 @@
 #include "nsWeakPtr.h"
 #include "nsIPrompt.h"
 #include "nsWeakReference.h"
 #include "nsThreadUtils.h"
 #include "nsTArray.h"
 #include "nsTObserverArray.h"
 #include "nsITimer.h"
 #include "nsPluginTags.h"
+#include "nsPluginPlayPreviewInfo.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIIDNService.h"
 #include "nsCRT.h"
 
 class nsNPAPIPlugin;
 class nsIComponentManager;
 class nsIFile;
 class nsIChannel;
@@ -75,17 +76,16 @@ public:
   nsresult LoadPlugins();
   nsresult UnloadPlugins();
 
   nsresult SetUpPluginInstance(const char *aMimeType,
                                nsIURI *aURL,
                                nsPluginInstanceOwner *aOwner);
   nsresult IsPluginEnabledForType(const char* aMimeType);
   nsresult IsPluginEnabledForExtension(const char* aExtension, const char* &aMimeType);
-  bool     IsPluginPlayPreviewForType(const char *aMimeType);
   nsresult GetBlocklistStateForType(const char *aMimeType, uint32_t *state);
 
   nsresult GetPluginCount(uint32_t* aPluginCount);
   nsresult GetPlugins(uint32_t aPluginCount, nsIDOMPlugin** aPluginArray);
 
   nsresult GetURL(nsISupports* pluginInst,
                   const char* url,
                   const char* target,
@@ -257,17 +257,17 @@ private:
 
   nsresult EnsurePrivateDirServiceProvider();
 
   void OnPluginInstanceDestroyed(nsPluginTag* aPluginTag);
 
   nsRefPtr<nsPluginTag> mPlugins;
   nsRefPtr<nsPluginTag> mCachedPlugins;
   nsRefPtr<nsInvalidPluginTag> mInvalidPlugins;
-  nsTArray<nsCString> mPlayPreviewMimeTypes;
+  nsTArray< nsRefPtr<nsPluginPlayPreviewInfo> > mPlayPreviewMimeTypes;
   bool mPluginsLoaded;
 
   // set by pref plugin.override_internal_types
   bool mOverrideInternalTypes;
 
   // set by pref plugin.disable
   bool mPluginsDisabled;
   // set by pref plugins.click_to_play
new file mode 100644
--- /dev/null
+++ b/dom/plugins/base/nsPluginPlayPreviewInfo.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; 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 "nsPluginPlayPreviewInfo.h"
+
+using namespace mozilla;
+
+nsPluginPlayPreviewInfo::nsPluginPlayPreviewInfo(const char* aMimeType,
+                                                 bool aIgnoreCTP,
+                                                 const char* aRedirectURL)
+  : mMimeType(aMimeType), mIgnoreCTP(aIgnoreCTP), mRedirectURL(aRedirectURL) {}
+
+nsPluginPlayPreviewInfo::nsPluginPlayPreviewInfo(
+  const nsPluginPlayPreviewInfo* aSource)
+{
+  MOZ_ASSERT(aSource);
+
+  mMimeType = aSource->mMimeType;
+  mIgnoreCTP = aSource->mIgnoreCTP;
+  mRedirectURL = aSource->mRedirectURL;
+}
+
+nsPluginPlayPreviewInfo::~nsPluginPlayPreviewInfo()
+{
+}
+
+NS_IMPL_ISUPPORTS1(nsPluginPlayPreviewInfo, nsIPluginPlayPreviewInfo)
+
+NS_IMETHODIMP
+nsPluginPlayPreviewInfo::GetMimeType(nsACString& aMimeType)
+{
+  aMimeType = mMimeType;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginPlayPreviewInfo::GetIgnoreCTP(bool* aIgnoreCTP)
+{
+  *aIgnoreCTP = mIgnoreCTP;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginPlayPreviewInfo::GetRedirectURL(nsACString& aRedirectURL)
+{
+  aRedirectURL = mRedirectURL;
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/plugins/base/nsPluginPlayPreviewInfo.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef nsPluginPlayPreviewInfo_h_
+#define nsPluginPlayPreviewInfo_h_
+
+#include "nsString.h"
+#include "nsIPluginHost.h"
+
+class nsPluginPlayPreviewInfo : public nsIPluginPlayPreviewInfo
+{
+public:
+   NS_DECL_ISUPPORTS
+   NS_DECL_NSIPLUGINPLAYPREVIEWINFO
+
+  nsPluginPlayPreviewInfo(const char* aMimeType,
+                          bool aIgnoreCTP,
+                          const char* aRedirectURL);
+  nsPluginPlayPreviewInfo(const nsPluginPlayPreviewInfo* aSource);
+  virtual ~nsPluginPlayPreviewInfo();
+
+  nsCString mMimeType;
+  bool      mIgnoreCTP;
+  nsCString mRedirectURL;
+};
+
+
+#endif // nsPluginPlayPreviewInfo_h_
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -6162,31 +6162,43 @@ var PluginHelper = {
 
     // Add a checkbox with a "Don't ask again" message if the uri contains a
     // host. Adding a permanent exception will fail if host is not present.
     let options = uri.host ? { checkbox: Strings.browser.GetStringFromName("clickToPlayPlugins.dontAskAgain") } : {};
 
     NativeWindow.doorhanger.show(message, "ask-to-play-plugins", buttons, aTab.id, options);
   },
 
+  delayAndShowDoorHanger: function(aTab) {
+    // To avoid showing the doorhanger if there are also visible plugin
+    // overlays on the page, delay showing the doorhanger to check if
+    // visible plugins get added in the near future.
+    if (!aTab.pluginDoorhangerTimeout) {
+      aTab.pluginDoorhangerTimeout = setTimeout(function() {
+        if (this.shouldShowPluginDoorhanger) {
+          PluginHelper.showDoorHanger(this);
+        }
+      }.bind(aTab), 500);
+    }
+  },
+
   playAllPlugins: function(aContentWindow) {
     let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils);
     // XXX not sure if we should enable plugins for the parent documents...
     let plugins = cwu.plugins;
     if (!plugins || !plugins.length)
       return;
 
     plugins.forEach(this.playPlugin);
   },
 
   playPlugin: function(plugin) {
     let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-    if (!objLoadingContent.activated &&
-        objLoadingContent.pluginFallbackType !== Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW)
+    if (!objLoadingContent.activated)
       objLoadingContent.playPlugin();
   },
 
   stopPlayPreview: function(plugin, playPlugin) {
     let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
     if (objLoadingContent.activated)
       return;
 
@@ -6274,27 +6286,17 @@ var PluginHelper = {
             Services.perms.ALLOW_ACTION) {
           PluginHelper.playPlugin(plugin);
           return;
         }
 
         // If the plugin is hidden, or if the overlay is too small, show a 
         // doorhanger notification
         if (PluginHelper.isTooSmall(plugin, overlay)) {
-          // To avoid showing the doorhanger if there are also visible plugin
-          // overlays on the page, delay showing the doorhanger to check if
-          // visible plugins get added in the near future.
-          if (!aTab.pluginDoorhangerTimeout) {
-            aTab.pluginDoorhangerTimeout = setTimeout(function() {
-              if (this.shouldShowPluginDoorhanger) {
-                PluginHelper.showDoorHanger(this);
-              }
-            }.bind(aTab), 500);
-          }
-
+          PluginHelper.delayAndShowDoorHanger(aTab);
         } else {
           // There's a large enough visible overlay that we don't need to show
           // the doorhanger.
           aTab.shouldShowPluginDoorhanger = false;
         }
 
         // Add click to play listener to the overlay
         overlay.addEventListener("click", function(e) {
@@ -6308,26 +6310,42 @@ var PluginHelper = {
 
           NativeWindow.doorhanger.hide("ask-to-play-plugins", tab.id);
         }, true);
         break;
       }
 
       case "PluginPlayPreview": {
         let previewContent = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+        let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+        let mimeType = PluginHelper.getPluginMimeType(plugin);
+        let playPreviewInfo = pluginHost.getPlayPreviewInfo(mimeType);
+
+        if (!playPreviewInfo.ignoreCTP) {
+          // Check if plugins have already been activated for this page, or if
+          // the user has set a permission to always play plugins on the site
+          if (aTab.clickToPlayPluginsActivated ||
+              Services.perms.testPermission(aTab.browser.currentURI, "plugins") ==
+              Services.perms.ALLOW_ACTION) {
+            PluginHelper.playPlugin(plugin);
+            return;
+          }
+
+          // Always show door hanger for play preview plugins
+          PluginHelper.delayAndShowDoorHanger(aTab);
+        }
+
         let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
         if (!iframe) {
           // lazy initialization of the iframe
           iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
           iframe.className = "previewPluginContentFrame";
           previewContent.appendChild(iframe);
         }
-        let mimeType = PluginHelper.getPluginMimeType(plugin);
-        let playPreviewUri = "data:application/x-moz-playpreview;," + mimeType;
-        iframe.src = playPreviewUri;
+        iframe.src = playPreviewInfo.redirectURL;
 
         // MozPlayPlugin event can be dispatched from the extension chrome
         // code to replace the preview content with the native plugin
         previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(e) {
           if (!e.isTrusted)
             return;
 
           previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true);