Bug 967969 - Handle click-to-play plugin events in Metro [r=rsilveira]
authorMatt Brubeck <mbrubeck@mozilla.com>
Tue, 25 Feb 2014 11:49:30 -0800
changeset 170952 71cfb5244ca044b67c0851b5f74e4c7fd9ad15d6
parent 170951 01fe5f5afa0f6670d92606c8e1f952b9a34253ce
child 170953 9be1d1bc11fc9107a675e78ef48d6cad106d1aae
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersrsilveira
bugs967969
milestone30.0a1
Bug 967969 - Handle click-to-play plugin events in Metro [r=rsilveira]
browser/metro/base/content/browser.js
browser/metro/base/content/contenthandlers/PluginHelper.js
browser/metro/base/jar.mn
browser/metro/profile/metro.js
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -59,16 +59,17 @@ var Browser = {
     try {
       messageManager.loadFrameScript("chrome://browser/content/Util.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/Content.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/FormHelper.js", true);
       messageManager.loadFrameScript("chrome://browser/content/library/SelectionPrototype.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/SelectionHandler.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ContextMenuHandler.js", true);
       messageManager.loadFrameScript("chrome://browser/content/contenthandlers/ConsoleAPIObserver.js", true);
+      messageManager.loadFrameScript("chrome://browser/content/contenthandlers/PluginHelper.js", true);
     } catch (e) {
       // XXX whatever is calling startup needs to dump errors!
       dump("###########" + e + "\n");
     }
 
     if (!Services.metro) {
       // Services.metro is only available on Windows Metro. We want to be able
       // to test metro on other platforms, too, so we provide a minimal shim.
copy from mobile/android/chrome/content/PluginHelper.js
copy to browser/metro/base/content/contenthandlers/PluginHelper.js
--- a/mobile/android/chrome/content/PluginHelper.js
+++ b/browser/metro/base/content/contenthandlers/PluginHelper.js
@@ -1,298 +1,101 @@
 /* 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/. */
 "use strict";
 
-var PluginHelper = {
-  showDoorHanger: function(aTab) {
-    if (!aTab.browser)
-      return;
-
-    // Even though we may not end up showing a doorhanger, this flag
-    // lets us know that we've tried to show a doorhanger.
-    aTab.shouldShowPluginDoorhanger = false;
-
-    let uri = aTab.browser.currentURI;
-
-    // If the user has previously set a plugins permission for this website,
-    // either play or don't play the plugins instead of showing a doorhanger.
-    let permValue = Services.perms.testPermission(uri, "plugins");
-    if (permValue != Services.perms.UNKNOWN_ACTION) {
-      if (permValue == Services.perms.ALLOW_ACTION)
-        PluginHelper.playAllPlugins(aTab.browser.contentWindow);
-
-      return;
-    }
+dump("### PluginHelper.js loaded\n");
 
-    let message = Strings.browser.formatStringFromName("clickToPlayPlugins.message2",
-                                                       [uri.host], 1);
-    let buttons = [
-      {
-        label: Strings.browser.GetStringFromName("clickToPlayPlugins.activate"),
-        callback: function(aChecked) {
-          // If the user checked "Don't ask again", make a permanent exception
-          if (aChecked)
-            Services.perms.add(uri, "plugins", Ci.nsIPermissionManager.ALLOW_ACTION);
-
-          PluginHelper.playAllPlugins(aTab.browser.contentWindow);
-        }
-      },
-      {
-        label: Strings.browser.GetStringFromName("clickToPlayPlugins.dontActivate"),
-        callback: function(aChecked) {
-          // If the user checked "Don't ask again", make a permanent exception
-          if (aChecked)
-            Services.perms.add(uri, "plugins", Ci.nsIPermissionManager.DENY_ACTION);
-
-          // Other than that, do nothing
-        }
-      }
-    ];
-
-    // 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);
+/**
+ * Handle events generated by plugin click-to-play code.
+ *
+ * This "PluginBindingAttached" fires when a "pluginProblem" XBL binding is
+ * created.  This binding overlays plugin content when the plugin is missing,
+ * blocked, click-to-play, or replaced by a "preview" extension plugin like
+ * Shumway or PDF.js.
+ */
+var PluginHelper = {
+  init: function () {
+    addEventListener("PluginBindingAttached", this, true, true);
   },
 
-  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.playPlugin();
-  },
-
-  stopPlayPreview: function(plugin, playPlugin) {
-    let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-    if (objLoadingContent.activated)
-      return;
-
-    if (playPlugin)
-      objLoadingContent.playPlugin();
-    else
-      objLoadingContent.cancelPlayPreview();
-  },
-
-  getPluginPreference: function getPluginPreference() {
-    let pluginDisable = Services.prefs.getBoolPref("plugin.disable");
-    if (pluginDisable)
-      return "0";
-
-    let state = Services.prefs.getIntPref("plugin.default.state");
-    return state == Ci.nsIPluginTag.STATE_CLICKTOPLAY ? "2" : "1";
-  },
-
-  setPluginPreference: function setPluginPreference(aValue) {
-    switch (aValue) {
-      case "0": // Enable Plugins = No
-        Services.prefs.setBoolPref("plugin.disable", true);
-        Services.prefs.clearUserPref("plugin.default.state");
-        break;
-      case "1": // Enable Plugins = Yes
-        Services.prefs.clearUserPref("plugin.disable");
-        Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_ENABLED);
-        break;
-      case "2": // Enable Plugins = Tap to Play (default)
-        Services.prefs.clearUserPref("plugin.disable");
-        Services.prefs.clearUserPref("plugin.default.state");
+  handleEvent: function handleEvent(aEvent) {
+    switch (aEvent.type) {
+      case "PluginBindingAttached":
+        this.handlePluginBindingAttached(aEvent);
         break;
     }
   },
 
-  // Copied from /browser/base/content/browser.js
-  isTooSmall : function (plugin, overlay) {
-    // Is the <object>'s size too small to hold what we want to show?
-    let pluginRect = plugin.getBoundingClientRect();
-    // XXX bug 446693. The text-shadow on the submitted-report text at
-    //     the bottom causes scrollHeight to be larger than it should be.
-    let overflows = (overlay.scrollWidth > pluginRect.width) ||
-                    (overlay.scrollHeight - 5 > pluginRect.height);
-
-    return overflows;
-  },
-
   getPluginMimeType: function (plugin) {
     var tagMimetype;
-    if (plugin instanceof HTMLAppletElement) {
+    if (plugin instanceof plugin.ownerDocument.defaultView.HTMLAppletElement) {
       tagMimetype = "application/x-java-vm";
     } else {
       tagMimetype = plugin.QueryInterface(Components.interfaces.nsIObjectLoadingContent)
                           .actualType;
 
       if (tagMimetype == "") {
         tagMimetype = plugin.type;
       }
     }
-
     return tagMimetype;
   },
 
-  handlePluginBindingAttached: function (aTab, aEvent) {
+  handlePluginBindingAttached: function (aEvent) {
     let plugin = aEvent.target;
     let doc = plugin.ownerDocument;
     let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
     if (!overlay || overlay._bindingHandled) {
       return;
     }
     overlay._bindingHandled = true;
 
     let eventType = PluginHelper._getBindingType(plugin);
     if (!eventType) {
-      // Not all bindings have handlers
       return;
     }
 
     switch  (eventType) {
-      case "PluginClickToPlay": {
-        // 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;
-        }
-
-        // If the plugin is hidden, or if the overlay is too small, show a 
-        // doorhanger notification
-        if (PluginHelper.isTooSmall(plugin, overlay)) {
-          PluginHelper.delayAndShowDoorHanger(aTab);
-        } else {
-          // There's a large enough visible overlay that we don't need to show
-          // the doorhanger.
-          aTab.shouldShowPluginDoorhanger = false;
-          overlay.classList.add("visible");
-        }
-
-        // Add click to play listener to the overlay
-        overlay.addEventListener("click", function(e) {
-          if (!e.isTrusted)
-            return;
-          e.preventDefault();
-          let win = e.target.ownerDocument.defaultView.top;
-          let tab = BrowserApp.getTabForWindow(win);
-          tab.clickToPlayPluginsActivated = true;
-          PluginHelper.playAllPlugins(win);
-
-          NativeWindow.doorhanger.hide("ask-to-play-plugins", tab.id);
-        }, true);
-
-        // Add handlers for over- and underflow in case the plugin gets resized
-        plugin.addEventListener("overflow", function(event) {
-          overlay.classList.remove("visible");
-          PluginHelper.delayAndShowDoorHanger(aTab);
-        });
-        plugin.addEventListener("underflow", function(event) {
-          // This is also triggered if only one dimension underflows,
-          // the other dimension might still overflow
-          if (!PluginHelper.isTooSmall(plugin, overlay)) {
-            overlay.classList.add("visible");
-          }
-        });
-
-        break;
-      }
-
       case "PluginPlayPreview": {
+        // Load the "preview" handler (an extension plugin like Shumway or PDF.js).
         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);
         }
         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);
-
-          let playPlugin = !aEvent.detail;
-          PluginHelper.stopPlayPreview(plugin, playPlugin);
-
-          // cleaning up: removes overlay iframe from the DOM
-          let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
-          if (iframe)
-            previewContent.removeChild(iframe);
-        }, true);
         break;
       }
 
       case "PluginNotFound": {
-        // On devices where we don't support Flash, there will be a
-        // "Learn More..." link in the missing plugin error message.
-        let learnMoreLink = doc.getAnonymousElementByAttribute(plugin, "class", "unsupportedLearnMoreLink");
-        let learnMoreUrl = Services.urlFormatter.formatURLPref("app.support.baseURL");
-        learnMoreUrl += "mobile-flash-unsupported";
-        learnMoreLink.href = learnMoreUrl;
-        overlay.classList.add("visible");
+        // TODO: Display a message about missing plugins (bug 936907)
         break;
       }
     }
   },
 
   // Helper to get the binding handler type from a plugin object
   _getBindingType: function(plugin) {
     if (!(plugin instanceof Ci.nsIObjectLoadingContent))
       return null;
 
     switch (plugin.pluginFallbackType) {
       case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
         return "PluginNotFound";
-      case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
-        return "PluginClickToPlay";
       case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW:
         return "PluginPlayPreview";
       default:
-        // Not all states map to a handler
+        // Metro Firefox does not yet support other fallback types.
         return null;
     }
-  }
+  },
 };
+
+PluginHelper.init();
--- a/browser/metro/base/jar.mn
+++ b/browser/metro/base/jar.mn
@@ -54,16 +54,17 @@ chrome.jar:
   content/helperui/FindHelperUI.js             (content/helperui/FindHelperUI.js)
   content/helperui/ItemPinHelper.js            (content/helperui/ItemPinHelper.js)
 
   content/contenthandlers/ContextMenuHandler.js (content/contenthandlers/ContextMenuHandler.js)
   content/contenthandlers/SelectionHandler.js  (content/contenthandlers/SelectionHandler.js)
   content/contenthandlers/FormHelper.js        (content/contenthandlers/FormHelper.js)
   content/contenthandlers/ConsoleAPIObserver.js (content/contenthandlers/ConsoleAPIObserver.js)
   content/contenthandlers/Content.js           (content/contenthandlers/Content.js)
+  content/contenthandlers/PluginHelper.js      (content/contenthandlers/PluginHelper.js)
 
   content/library/SelectionPrototype.js        (content/library/SelectionPrototype.js)
 
   content/ContentAreaObserver.js               (content/ContentAreaObserver.js)
   content/BrowserTouchHandler.js               (content/BrowserTouchHandler.js)
 * content/WebProgress.js                       (content/WebProgress.js)
   content/pages/config.js                      (content/pages/config.js)
 * content/browser.xul                          (content/browser.xul)
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -568,17 +568,22 @@ pref("pdfjs.disabled", true);
 pref("pdfjs.firstRun", true);
 // The values of preferredAction and alwaysAskBeforeHandling before pdf.js
 // became the default.
 pref("pdfjs.previousHandler.preferredAction", 0);
 pref("pdfjs.previousHandler.alwaysAskBeforeHandling", false);
 #endif
 
 #ifdef NIGHTLY_BUILD
+// Shumay is currently experimental.  Toggle this pref to enable Shumway for
+// testing and development.
 pref("shumway.disabled", true);
+// When Shumway is enabled, use it all the time, not only when Flash is set to
+// click-to-play (because Metro doesn't even load the native Flash plugin).
+pref("shumway.ignoreCTP", true);
 #endif
 
 // The maximum amount of decoded image data we'll willingly keep around (we
 // might keep around more than this, but we'll try to get down to this value).
 // (This is intentionally on the high side; see bug 746055.)
 pref("image.mem.max_decoded_image_kb", 256000);
 
 // enable touch events interfaces