Bug 812898 Implement plugin preview overlay from Bug 776208 (combined patch for comm-beta) r=Neil a=Callek. SEAMONKEY_2_15b3_BUILD1 SEAMONKEY_2_15b3_RELEASE
authorPhilip Chee <philip.chee@gmail.com>
Tue, 20 Nov 2012 00:05:01 +0800
changeset 13981 3363831600dfd7f8c099ba68e62c08f554835dcb
parent 13978 7b0ff9c645ed782cb7e4d0cfee72a920a84fb6b7
child 13982 7398241a19fbf0d62376e3f0148084956a86ce10
child 13984 ef261d601f6df4dadcae4909309a7c2909bf4e01
push idunknown
push userunknown
push dateunknown
reviewersNeil, Callek
bugs812898, 776208
Bug 812898 Implement plugin preview overlay from Bug 776208 (combined patch for comm-beta) r=Neil a=Callek.
suite/browser/test/Makefile.in
suite/browser/test/browser/browser_pluginnotification.js
suite/browser/test/browser/browser_pluginplaypreview.js
suite/browser/test/browser/head.js
suite/common/bindings/notification.xml
--- a/suite/browser/test/Makefile.in
+++ b/suite/browser/test/Makefile.in
@@ -30,16 +30,17 @@ ifneq (gtk2,$(MOZ_WIDGET_TOOLKIT))
 		subtst_contextmenu.html \
 		ctxmenu-image.png \
 		audio.ogg \
 		video.ogg \
 		$(NULL)
 endif
 
 _BROWSER_FILES = \
+                 head.js \
                  browser_bug329212.js \
                  browser_bug413915.js \
                  browser_bug427559.js \
                  browser_bug435325.js \
                  browser_bug519216.js \
                  browser_bug561636.js \
                  browser_bug562649.js \
                  browser_bug581947.js \
@@ -47,16 +48,17 @@ endif
                  browser_bug595507.js \
                  browser_bug623155.js \
                  browser_fayt.js \
                  browser_page_style_menu.js \
                  page_style_sample.html \
                  browser_pageInfo.js \
                  feed_tab.html \
                  browser_pluginnotification.js \
+                 browser_pluginplaypreview.js \
                  plugin_alternate_content.html \
                  plugin_bug743421.html \
                  plugin_bug749455.html \
                  plugin_clickToPlayAllow.html \
                  plugin_hidden_to_visible.html \
                  plugin_unknown.html \
                  plugin_test.html \
                  plugin_test2.html \
--- a/suite/browser/test/browser/browser_pluginnotification.js
+++ b/suite/browser/test/browser/browser_pluginnotification.js
@@ -11,47 +11,16 @@ var gClickToPlayPluginExpectedEvents = 5
 function count(o)
 {
   var n = 0;
   for (var p in o)
     n += Object.prototype.hasOwnProperty.call(o, p);
   return n;
 }
 
-
-function getTestPlugin() {
-  var ph = Components.classes["@mozilla.org/plugin/host;1"]
-                     .getService(Components.interfaces.nsIPluginHost);
-  var tags = ph.getPluginTags();
-
-  // Find the test plugin
-  for (var i = 0; i < tags.length; i++) {
-    if (tags[i].name == "Test Plug-in")
-      return tags[i];
-  }
-  ok(false, "Unable to find plugin");
-  return null;
-}
-
-
-function waitForCondition(condition, nextTest, errorMsg) {
-  var tries = 0;
-  var interval = setInterval(function() {
-    if (tries >= 30) {
-      ok(false, errorMsg);
-      moveOn();
-    }
-    if (condition()) {
-      moveOn();
-   }
-    tries++;
-  }, 100);
-  var moveOn = function() { clearInterval(interval); nextTest(); };
-}
-
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 // This listens for the next opened tab and checks it is of the right url.
 // opencallback is called when the new tab is fully loaded
 // closecallback is called when the tab is closed
 function TabOpenListener(url, opencallback, closecallback) {
   this.url = url;
   this.opencallback = opencallback;
new file mode 100644
--- /dev/null
+++ b/suite/browser/test/browser/browser_pluginplaypreview.js
@@ -0,0 +1,311 @@
+/* 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) {
+
+  function StreamConverterFactory() {}
+  StreamConverterFactory.prototype = {
+    QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIFactory]),
+    _targetConstructor: null,
+
+    register: function register(targetConstructor) {
+      this._targetConstructor = targetConstructor;
+      var proto = targetConstructor.prototype;
+      var registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+      registrar.registerFactory(proto.classID, proto.classDescription,
+                                proto.contractID, this);
+    },
+
+    unregister: function unregister() {
+      var proto = this._targetConstructor.prototype;
+      var registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+      registrar.unregisterFactory(proto.classID, this);
+      this._targetConstructor = null;
+    },
+
+    // nsIFactory
+    createInstance: function createInstance(aOuter, iid) {
+      if (aOuter !== null)
+        throw Components.results.NS_ERROR_NO_AGGREGATION;
+      return (new (this._targetConstructor)).QueryInterface(iid);
+    },
+
+    // nsIFactory
+    lockFactory: function lockFactory(lock) {
+      // No longer used as of gecko 1.7.
+      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+    }
+  };
+
+  function OverlayStreamConverter() {}
+  OverlayStreamConverter.prototype = {
+    QueryInterface: XPCOMUtils.generateQI([
+        Components.interfaces.nsISupports,
+        Components.interfaces.nsIStreamConverter,
+        Components.interfaces.nsIStreamListener,
+        Components.interfaces.nsIRequestObserver
+    ]),
+
+    classID: Components.ID('{4c6030f7-e20a-264f-0f9b-ada3a9e97384}'),
+    classDescription: 'overlay-test-data Component',
+    contractID: '@mozilla.org/streamconv;1?from=application/x-moz-playpreview&to=*/*',
+
+    // nsIStreamConverter::convert
+    convert: function(aFromStream, aFromType, aToType, aCtxt) {
+      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+    },
+
+    // nsIStreamConverter::asyncConvertData
+    asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
+      var isValidRequest = false;
+      try {
+        var request = aCtxt;
+        request.QueryInterface(Components.interfaces.nsIChannel);
+        var spec = request.URI.spec;
+        var expectedSpec = 'data:application/x-moz-playpreview;,' + mimeType;
+        isValidRequest = (spec == expectedSpec);
+      } catch (e) { }
+      if (!isValidRequest)
+        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+
+      // Store the listener passed to us
+      this.listener = aListener;
+    },
+
+    // nsIStreamListener::onDataAvailable
+    onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+      // Do nothing since all the data loading is handled by the viewer.
+      ok(false, "onDataAvailable should not be called");
+    },
+
+    // nsIRequestObserver::onStartRequest
+    onStartRequest: function(aRequest, aContext) {
+
+      // Setup the request so we can use it below.
+      aRequest.QueryInterface(Components.interfaces.nsIChannel);
+      // Cancel the request so the viewer can handle it.
+      aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+
+      // Create a new channel that is viewer loaded as a resource.
+      var ioService = Services.io;
+      var channel = ioService.newChannel(targetUrl, null, null);
+      channel.asyncOpen(this.listener, aContext);
+    },
+
+    // nsIRequestObserver::onStopRequest
+    onStopRequest: function(aRequest, aContext, aStatusCode) {
+      // Do nothing.
+    }
+  };
+
+  var ph = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+  ph.registerPlayPreviewMimeType(mimeType);
+
+  var factory = new StreamConverterFactory();
+  factory.register(OverlayStreamConverter);
+
+  return (gPlayPreviewRegistration = {
+    unregister: function() {
+      ph.unregisterPlayPreviewMimeType(mimeType);
+      factory.unregister();
+      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("PluginPlayPreview", handlePluginPlayPreview, true);
+
+  registerPlayPreview('application/x-test', 'about:');
+  prepareTest(test1a, gTestRoot + "plugin_test.html", 1);
+}
+
+function finishTest() {
+  gTestBrowser.removeEventListener("load", pageLoad, true);
+  gTestBrowser.removeEventListener("PluginPlayPreview", handlePluginPlayPreview, true);
+  gBrowser.removeCurrentTab();
+  window.focus();
+  finish();
+}
+
+function handlePluginPlayPreview() {
+  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(Components.interfaces.nsIObjectLoadingContent);
+  is(objLoadingContent.pluginFallbackType, Components.interfaces.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(Components.interfaces.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 plugin = gTestBrowser.contentDocument.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 2, Plugin should 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 plugin = gTestBrowser.contentDocument.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 3, Plugin should be activated");
+
+  unregisterPlayPreview();
+
+  registerPlayPreview('application/x-test', 'about:');
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  prepareTest(test4a, gTestRoot + "plugin_test.html", 1);
+}
+
+// Test a fallback to the click-to-play
+function test4a() {
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
+  is(objLoadingContent.pluginFallbackType, Components.interfaces.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 4a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+  ok(!objLoadingContent.activated, "Test 4a, Plugin should not be activated");
+
+  var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+  ok(overlay, "Test 4a, the overlay div is expected");
+
+  var e = overlay.ownerDocument.createEvent("CustomEvent");
+  e.initCustomEvent("MozPlayPlugin", true, true, true);
+  overlay.dispatchEvent(e);
+  var condition = function() objLoadingContent.pluginFallbackType == Components.interfaces.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
+  waitForCondition(condition, test4b, "Test 4a, Waited too long for plugin to stop play preview");
+}
+
+function test4b() {
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
+  ok(objLoadingContent.pluginFallbackType != Components.interfaces.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 4b, plugin fallback type should not be PLUGIN_PLAY_PREVIEW");
+  ok(!objLoadingContent.activated, "Test 4b, Plugin should not be activated");
+
+  prepareTest(test5a, gTestRoot + "plugin_test.html", 1);
+}
+
+// Test a bypass of the click-to-play
+function test5a() {
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
+  is(objLoadingContent.pluginFallbackType, Components.interfaces.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 5a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+  ok(!objLoadingContent.activated, "Test 5a, Plugin should not be activated");
+
+  var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+  ok(overlay, "Test 5a, the overlay div is expected");
+
+  var e = overlay.ownerDocument.createEvent("CustomEvent");
+  e.initCustomEvent("MozPlayPlugin", true, true, false);
+  overlay.dispatchEvent(e);
+  var condition = function() objLoadingContent.activated;
+  waitForCondition(condition, test5b, "Test 5a, Waited too long for plugin to stop play preview");
+}
+
+function test5b() {
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 5b, Plugin should be activated");
+
+  finishTest();
+}
+
new file mode 100644
--- /dev/null
+++ b/suite/browser/test/browser/head.js
@@ -0,0 +1,33 @@
+/* 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/. */
+
+function getTestPlugin() {
+  var ph = Components.classes["@mozilla.org/plugin/host;1"]
+                     .getService(Components.interfaces.nsIPluginHost);
+  var tags = ph.getPluginTags();
+
+  // Find the test plugin
+  for (var i = 0; i < tags.length; i++) {
+    if (tags[i].name == "Test Plug-in")
+      return tags[i];
+  }
+
+  ok(false, "Unable to find plugin");
+  return null;
+}
+
+function waitForCondition(condition, nextTest, errorMsg) {
+  var tries = 0;
+  var interval = setInterval(function() {
+    if (tries >= 30) {
+      ok(false, errorMsg);
+      moveOn();
+    }
+    if (condition()) {
+      moveOn();
+    }
+    tries++;
+  }, 100);
+  var moveOn = function() { clearInterval(interval); nextTest(); };
+}
--- a/suite/common/bindings/notification.xml
+++ b/suite/common/bindings/notification.xml
@@ -435,51 +435,79 @@
             // Now we can display our notification.
             const priority = this.PRIORITY_WARNING_MEDIUM;
             this.appendNotification(aMessage, aNotification,
                                     null, priority, aButtons);
           ]]>
         </body>
       </method>
 
+      <method name="canActivatePlugin">
+        <parameter name="objLoadingContent"/>
+        <body>
+          <![CDATA[
+            var nsIObjectLoadingContent = Components.interfaces.nsIObjectLoadingContent;
+            return !objLoadingContent.activated &&
+                   objLoadingContent.pluginFallbackType !== nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW;
+          ]]>
+        </body>
+      </method>
+
       <method name="activatePlugins">
         <body>
           <![CDATA[
             this.clickToPlayPluginsActivated = true;
             var plugins = this.contentWindowUtils.plugins;
             for (let plugin of plugins) {
               let objLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
-              if (!objLoadingContent.activated)
+              if (this.canActivatePlugin(objLoadingContent))
                 objLoadingContent.playPlugin();
             }
             this.removePluginClickToPlayPrompt();
           ]]>
         </body>
       </method>
       
       <method name="activateSinglePlugin">
         <parameter name="aPlugin"/>
         <body>
           <![CDATA[
             var objLoadingContent = aPlugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
-            if (!objLoadingContent.activated)
+            if (this.canActivatePlugin(objLoadingContent))
               objLoadingContent.playPlugin();
         
             var haveUnplayedPlugins = this.contentWindowUtils.plugins.some(function(plugin) {
               var hupObjLoadingContent = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
-              return plugin != aPlugin && !hupObjLoadingContent.activated;
-            });
+              return plugin != aPlugin && this.canActivatePlugin(hupObjLoadingContent);
+            }, this);
             if (!haveUnplayedPlugins) {
               this.removePluginClickToPlayPrompt();
               this.clickToPlayPromptShown = false;
             }
           ]]>
         </body>
       </method>
 
+      <method name="stopPlayPreview">
+        <parameter name="aPlugin"/>
+        <parameter name="aPlayPlugin"/>
+        <body>
+          <![CDATA[
+            var objLoadingContent = aPlugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
+            if (objLoadingContent.activated)
+              return;
+
+            if (aPlayPlugin)
+              objLoadingContent.playPlugin();
+            else
+              objLoadingContent.cancelPlayPreview();
+          ]]>
+        </body>
+      </method>
+
       <method name="openURLPref">
         <parameter name="aPref"/>
         <body>
           <![CDATA[
             var url = this._urlFormatter.formatURLPref(aPref);
             var nsIBrowserDOMWindow = Components.interfaces.nsIBrowserDOMWindow;
 
             var browserWin;
@@ -664,16 +692,41 @@
             link.setAttribute("value", this._stringBundle.GetStringFromName("crashedpluginsMessage.learnMore"));
             this.addLinkClickCallback(link, this.openHelpPage);
             var description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
             description.appendChild(link);
           ]]>
         </body>
       </method>
 
+      <method name="handleEvent">
+        <parameter name="aEvent"/>
+        <body>
+          <![CDATA[
+          if (!aEvent.isTrusted)
+            return;
+
+          if (aEvent.type != "MozPlayPlugin")
+            return;
+
+          var previewContent = aEvent.currentTarget;
+          previewContent.removeEventListener("MozPlayPlugin", this, true);
+
+          var pluginElement = previewContent.ownerDocument.getBindingParent(previewContent);
+          var playPlugin = !aEvent.detail;
+          this.stopPlayPreview(pluginElement, playPlugin);
+
+          // cleaning up: removes overlay iframe from the DOM
+          var iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
+          if (iframe)
+            previewContent.removeChild(iframe);
+          ]]>
+        </body>
+      </method>
+
       <method name="installMissingPlugins">
         <body>
           <![CDATA[
             window.openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
                               "", "chrome,centerscreen,resizable=yes",
                               {plugins: this.missingPlugins, browser: this.activeBrowser});
           ]]>
         </body>
@@ -1083,16 +1136,51 @@
 
             let overlay = doc.getAnonymousElementByAttribute(pluginElement, "class", "mainBox");
             if (this.isTooSmall(pluginElement, overlay))
               overlay.style.visibility = "hidden";
           ]]>
         </body>
       </method>
 
+      <method name="handlePlayPreviewEvent">
+        <parameter name="pluginElement"/>
+        <body>
+          <![CDATA[
+            var doc = pluginElement.ownerDocument;
+            var previewContent = doc.getAnonymousElementByAttribute(pluginElement, "class", "previewPluginContent");
+            if (!previewContent) {
+            // If the XBL binding is not attached (element is display:none),
+            // fallback to click-to-play logic.
+              this.stopPlayPreview(pluginElement, false);
+              return;
+            }
+
+            var 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.
+              pluginElement.clientTop;
+            }
+
+            var pluginInfo = this.getPluginInfo(pluginElement);
+            var playPreviewUri = "data:application/x-moz-playpreview;," + pluginInfo.mimetype;
+            iframe.src = playPreviewUri;
+
+            // The MozPlayPlugin event can be dispatched from the extension chrome
+            // code to replace the preview content with the native plugin.
+            previewContent.addEventListener("MozPlayPlugin", this, true);
+          ]]>
+        </body>
+      </method>
+
       <method name="showGeolocationPrompt">
         <parameter name="file"/>
         <parameter name="site"/>
         <parameter name="allowCallback"/>
         <parameter name="cancelCallback"/>
         <body>
           <![CDATA[
             var type = "geolocation";
@@ -1905,16 +1993,22 @@
       </handler>
 
       <handler event="PluginClickToPlay" phase="capturing">
         <![CDATA[
           this.setupPluginClickToPlay(event.target);
         ]]>
       </handler>
 
+      <handler event="PluginPlayPreview" phase="capturing">
+        <![CDATA[
+          this.handlePlayPreviewEvent(event.target);
+        ]]>
+      </handler>
+
       <handler event="NewPluginInstalled" phase="capturing">
         <![CDATA[
           this.missingPlugins = {};
 
           // clean up the UI after a new plugin has been installed.
           var notification = this.getNotificationWithValue("missing-plugins");
           if (notification)
             this.removeNotification(notification);
@@ -1951,18 +2045,18 @@
           var pm = Components.classes["@mozilla.org/permissionmanager;1"]
                              .getService(nsIPermissionManager);
           var pluginPermission = pm.testPermission(this.activeBrowser.currentURI, "plugins");
           if (pluginPermission == nsIPermissionManager.DENY_ACTION)
             return;
 
           var pluginNeedsActivation = this.contentWindowUtils.plugins.some(function(aPlugin) {
             var objLoadingContent = aPlugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
-            return !objLoadingContent.activated;
-          });
+            return this.canActivatePlugin(objLoadingContent);
+          }, this);
           if (pluginNeedsActivation)
             this.showPluginClickToPlayPrompt();
         ]]>
       </handler>
     </handlers>
   </binding>
 
   <binding id="popup-notification"