Bug 1009760 - Support displaying of crash notification for GMP plugins. r=gfritzsche
authorFelipe Gomes <felipc@gmail.com>
Fri, 18 Jul 2014 15:11:53 -0300
changeset 216835 41df67208b1ba1c32fa9919d844db577e2813c2c
parent 216834 f1ab07827b04a1c18d3e708e853e7dde164e4fe0
child 216836 5e5eb00a12e9ada9c727bc93c69d15862bba0d67
child 216918 e66b5696189ee373b8d5a60f709cb77325b0923f
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgfritzsche
bugs1009760
milestone33.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 1009760 - Support displaying of crash notification for GMP plugins. r=gfritzsche
browser/base/content/browser-plugins.js
browser/base/content/test/plugins/browser.ini
browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -271,16 +271,24 @@ var gPluginHandler = {
     if (eventType == "PluginRemoved") {
       let doc = event.target;
       let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
       if (browser)
         this._setPluginNotificationIcon(browser);
       return;
     }
 
+    if (eventType == "PluginCrashed" &&
+        !(event.target instanceof Ci.nsIObjectLoadingContent)) {
+      // If the event target is not a plugin object (i.e., an <object> or
+      // <embed> element), this call is for a window-global plugin.
+      this.pluginInstanceCrashed(event.target, event);
+      return;
+    }
+
     let plugin = event.target;
     let doc = plugin.ownerDocument;
 
     if (!(plugin instanceof Ci.nsIObjectLoadingContent))
       return;
 
     if (eventType == "PluginBindingAttached") {
       // The plugin binding fires this event when it is created.
@@ -1138,17 +1146,17 @@ var gPluginHandler = {
       // Submission is async, so we can't easily show failure UI.
       propertyBag.setPropertyAsBool("submittedCrashReport", true);
     }
 #endif
   },
 
   // Crashed-plugin event listener. Called for every instance of a
   // plugin in content.
-  pluginInstanceCrashed: function (plugin, aEvent) {
+  pluginInstanceCrashed: function (target, aEvent) {
     // Ensure the plugin and event are of the right type.
     if (!(aEvent instanceof Ci.nsIDOMCustomEvent))
       return;
 
     let propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2);
     let submittedReport = propBag.getPropertyAsBool("submittedCrashReport");
     let doPrompt        = true; // XXX followup for .getPropertyAsBool("doPrompt");
     let submitReports   = true; // XXX followup for .getPropertyAsBool("submitReports");
@@ -1156,60 +1164,48 @@ var gPluginHandler = {
     let pluginDumpID    = propBag.getPropertyAsAString("pluginDumpID");
     let browserDumpID   = propBag.getPropertyAsAString("browserDumpID");
 
     // Remap the plugin name to a more user-presentable form.
     pluginName = this.makeNicePluginName(pluginName);
 
     let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]);
 
-    //
-    // Configure the crashed-plugin placeholder.
-    //
+    let plugin = null, doc;
+    if (target instanceof Ci.nsIObjectLoadingContent) {
+      plugin = target;
+      doc = plugin.ownerDocument;
+    } else {
+      doc = target.document;
+      if (!doc) {
+        return;
+      }
+      // doPrompt is specific to the crashed plugin overlay, and
+      // therefore is not applicable for window-global plugins.
+      doPrompt = false;
+    }
 
-    // Force a layout flush so the binding is attached.
-    plugin.clientTop;
-    let overlay = this.getPluginUI(plugin, "main");
-    let statusDiv = this.getPluginUI(plugin, "submitStatus");
-    let doc = plugin.ownerDocument;
+    let status;
 #ifdef MOZ_CRASHREPORTER
-    let status;
-
     // Determine which message to show regarding crash reports.
     if (submittedReport) { // submitReports && !doPrompt, handled in observer
       status = "submitted";
     }
     else if (!submitReports && !doPrompt) {
       status = "noSubmit";
     }
-    else { // doPrompt
+    else if (!pluginDumpID) {
+      // If we don't have a minidumpID, we can't (or didn't) submit anything.
+      // This can happen if the plugin is killed from the task manager.
+      status = "noReport";
+    }
+    else {
       status = "please";
-      this.getPluginUI(plugin, "submitButton").addEventListener("click",
-        function (event) {
-          if (event.button != 0 || !event.isTrusted)
-            return;
-          this.submitReport(pluginDumpID, browserDumpID, plugin);
-          pref.setBoolPref("", optInCB.checked);
-        }.bind(this));
-      let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
-      let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
-      optInCB.checked = pref.getBoolPref("");
     }
 
-    // If we don't have a minidumpID, we can't (or didn't) submit anything.
-    // This can happen if the plugin is killed from the task manager.
-    if (!pluginDumpID) {
-        status = "noReport";
-    }
-
-    statusDiv.setAttribute("status", status);
-
-    let helpIcon = this.getPluginUI(plugin, "helpIcon");
-    this.addLinkClickCallback(helpIcon, "openHelpPage");
-
     // If we're showing the link to manually trigger report submission, we'll
     // want to be able to update all the instances of the UI for this crash to
     // show an updated message when a report is submitted.
     if (doPrompt) {
       let observer = {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                                Ci.nsISupportsWeakReference]),
         observe : function(subject, topic, data) {
@@ -1233,36 +1229,25 @@ var gPluginHandler = {
       // it from being GC. But I don't want to manually manage the reference's
       // lifetime (which should be no greater than the page).
       // Clever solution? Use a closue with an event listener on the document.
       // When the doc goes away, so do the listener references and the closure.
       doc.addEventListener("mozCleverClosureHack", observer, false);
     }
 #endif
 
-    let crashText = this.getPluginUI(plugin, "crashedText");
-    crashText.textContent = messageString;
-
     let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
-
-    let link = this.getPluginUI(plugin, "reloadLink");
-    this.addLinkClickCallback(link, "reloadPage", browser);
-
     let notificationBox = gBrowser.getNotificationBox(browser);
+    let isShowing = false;
 
-    let isShowing = this.shouldShowOverlay(plugin, overlay);
-
-    // Is the <object>'s size too small to hold what we want to show?
-    if (!isShowing) {
-      // First try hiding the crash report submission UI.
-      statusDiv.removeAttribute("status");
-
-      isShowing = this.shouldShowOverlay(plugin, overlay);
+    if (plugin) {
+      // If there's no plugin (an <object> or <embed> element), this call is
+      // for a window-global plugin. In this case, there's no overlay to show.
+      isShowing = _setUpPluginOverlay.call(this, plugin, doPrompt, browser);
     }
-    this.setVisibility(plugin, overlay, isShowing);
 
     if (isShowing) {
       // If a previous plugin on the page was too small and resulted in adding a
       // notification bar, then remove it because this plugin instance it big
       // enough to serve as in-content notification.
       hideNotificationBar();
       doc.mozNoPluginCrashedNotification = true;
     } else {
@@ -1325,10 +1310,59 @@ var gPluginHandler = {
       description.appendChild(link);
 
       // Remove the notfication when the page is reloaded.
       doc.defaultView.top.addEventListener("unload", function() {
         notificationBox.removeNotification(notification);
       }, false);
     }
 
+    // Configure the crashed-plugin placeholder.
+    // Returns true if the plugin overlay is visible.
+    function _setUpPluginOverlay(plugin, doPromptSubmit, browser) {
+      if (!plugin) {
+        return false;
+      }
+
+      // Force a layout flush so the binding is attached.
+      plugin.clientTop;
+      let overlay = this.getPluginUI(plugin, "main");
+      let statusDiv = this.getPluginUI(plugin, "submitStatus");
+
+      if (doPromptSubmit) {
+        this.getPluginUI(plugin, "submitButton").addEventListener("click",
+        function (event) {
+          if (event.button != 0 || !event.isTrusted)
+            return;
+          this.submitReport(pluginDumpID, browserDumpID, plugin);
+          pref.setBoolPref("", optInCB.checked);
+        }.bind(this));
+        let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
+        let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
+        optInCB.checked = pref.getBoolPref("");
+      }
+
+      statusDiv.setAttribute("status", status);
+
+      let helpIcon = this.getPluginUI(plugin, "helpIcon");
+      this.addLinkClickCallback(helpIcon, "openHelpPage");
+
+      let crashText = this.getPluginUI(plugin, "crashedText");
+      crashText.textContent = messageString;
+
+      let link = this.getPluginUI(plugin, "reloadLink");
+      this.addLinkClickCallback(link, "reloadPage", browser);
+
+      let isShowing = this.shouldShowOverlay(plugin, overlay);
+
+      // Is the <object>'s size too small to hold what we want to show?
+      if (!isShowing) {
+        // First try hiding the crash report submission UI.
+        statusDiv.removeAttribute("status");
+
+        isShowing = this.shouldShowOverlay(plugin, overlay);
+      }
+      this.setVisibility(plugin, overlay, isShowing);
+
+      return isShowing;
+    }
   }
 };
--- a/browser/base/content/test/plugins/browser.ini
+++ b/browser/base/content/test/plugins/browser.ini
@@ -57,15 +57,16 @@ run-if = crashreporter
 [browser_CTP_drag_drop.js]
 [browser_CTP_hide_overlay.js]
 [browser_CTP_iframe.js]
 [browser_CTP_multi_allow.js]
 [browser_CTP_nonplugins.js]
 [browser_CTP_notificationBar.js]
 [browser_CTP_outsideScrollArea.js]
 [browser_CTP_resize.js]
+[browser_globalplugin_crashinfobar.js]
 [browser_pageInfo_plugins.js]
 [browser_pluginnotification.js]
 [browser_pluginplaypreview.js]
 [browser_pluginplaypreview2.js]
 [browser_pluginCrashCommentAndURL.js]
 run-if = crashreporter
 [browser_plugins_added_dynamically.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
@@ -0,0 +1,69 @@
+/* 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/. */
+
+let gTestBrowser = null;
+
+let propBagProperties = {
+  pluginName: "GlobalTestPlugin",
+  pluginDumpID: "1234",
+  browserDumpID: "5678",
+  submittedCrashReport: false
+}
+
+// Test that plugin crash submissions still work properly after
+// click-to-play activation.
+
+function test() {
+  waitForExplicitFinish();
+  let tab = gBrowser.loadOneTab("about:blank", { inBackground: false });
+  gTestBrowser = gBrowser.getBrowserForTab(tab);
+  gTestBrowser.addEventListener("PluginCrashed", onCrash, false);
+  gTestBrowser.addEventListener("load", onPageLoad, true);
+
+  registerCleanupFunction(function cleanUp() {
+    gTestBrowser.removeEventListener("PluginCrashed", onCrash, false);
+    gTestBrowser.removeEventListener("load", onPageLoad, true);
+    gBrowser.removeTab(tab);
+  });
+}
+
+function onPageLoad() {
+  executeSoon(generateCrashEvent);
+}
+
+function generateCrashEvent() {
+  let window = gTestBrowser.contentWindow;
+  let propBag = Cc["@mozilla.org/hash-property-bag;1"]
+                  .createInstance(Ci.nsIWritablePropertyBag);
+  for (let [name, val] of Iterator(propBagProperties)) {
+    propBag.setProperty(name, val);
+  }
+
+  let event = window.document.createEvent("CustomEvent");
+  event.initCustomEvent("PluginCrashed", true, true, propBag);
+  window.dispatchEvent(event);
+}
+
+
+function onCrash(event) {
+  let target = event.target;
+  is (target, gTestBrowser.contentWindow, "Event target is the window.");
+
+  let propBag = event.detail.QueryInterface(Ci.nsIPropertyBag2);
+  for (let [name, val] of Iterator(propBagProperties)) {
+    let type = typeof val;
+    let propVal = type == "string"
+                  ? propBag.getPropertyAsAString(name)
+                  : propBag.getPropertyAsBool(name);
+    is (propVal, val, "Correct property in detail propBag: " + name + ".");
+  }
+
+  let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+  let notification = notificationBox.getNotificationWithValue("plugin-crashed");
+
+  ok(notification, "Infobar was shown.");
+  is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM, "Correct priority.");
+  is(notification.getAttribute("label"), "The GlobalTestPlugin plugin has crashed.", "Correct message.");
+  finish();
+}