Bug 870290 - TEST-UNEXPECTED-FAIL test_hang_submit.xul | Test timed out. (Broken plugin crash reporter submit link), r=Ratty, a=Ratty for comm-aurora, a=Callek for CLOSED TREE
authorFrank Wein <mcsmurf@mcsmurf.de>
Mon, 01 Jul 2013 12:48:37 +0200
changeset 15817 73fe0c8b8409ac5b5c4f798382651980ad65f96f
parent 15816 93ab132967c610fd03fc369e73693125e61bab0d
child 15818 a7b8008b37c7410c84f22ec51ecf03e0f017ce4f
push id942
push userbugzilla@standard8.plus.com
push dateMon, 05 Aug 2013 19:15:38 +0000
treeherdercomm-beta@0e1a1c4a9f0c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersRatty, Ratty, Callek
bugs870290
Bug 870290 - TEST-UNEXPECTED-FAIL test_hang_submit.xul | Test timed out. (Broken plugin crash reporter submit link), r=Ratty, a=Ratty for comm-aurora, a=Callek for CLOSED TREE
suite/browser/test/Makefile.in
suite/browser/test/browser/browser_pluginCrashCommentAndURL.js
suite/browser/test/browser/pluginCrashCommentAndURL.html
suite/common/bindings/notification.xml
suite/themes/modern/mozapps/plugins/pluginProblem.css
--- a/suite/browser/test/Makefile.in
+++ b/suite/browser/test/Makefile.in
@@ -45,21 +45,23 @@ endif
                  browser_bug561636.js \
                  browser_bug562649.js \
                  browser_bug581947.js \
                  browser_bug585511.js \
                  browser_bug595507.js \
                  browser_bug623155.js \
                  browser_fayt.js \
                  browser_page_style_menu.js \
+                 browser_pluginCrashCommentAndURL.js \
                  page_style_sample.html \
                  browser_pageInfo.js \
                  feed_tab.html \
                  browser_pluginnotification.js \
                  browser_pluginplaypreview.js \
+                 pluginCrashCommentAndURL.html \
                  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 \
new file mode 100644
--- /dev/null
+++ b/suite/browser/test/browser/browser_pluginCrashCommentAndURL.js
@@ -0,0 +1,154 @@
+/* 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/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const CRASH_URL = "http://example.com/browser/suite/browser/test/pluginCrashCommentAndURL.html";
+
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+
+function test() {
+  // Crashing the plugin takes up a lot of time, so extend the test timeout.
+  requestLongerTimeout(runs.length);
+  waitForExplicitFinish();
+
+  // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin
+  // crash reports.  This test needs them enabled.  The test also needs a mock
+  // report server, and fortunately one is already set up by toolkit/
+  // crashreporter/test/Makefile.in.  Assign its URL to MOZ_CRASHREPORTER_URL,
+  // which CrashSubmit.jsm uses as a server override.
+  let env = Cc["@mozilla.org/process/environment;1"].
+            getService(Ci.nsIEnvironment);
+  let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+  let serverURL = env.get("MOZ_CRASHREPORTER_URL");
+  env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+  env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+  let tab = gBrowser.loadOneTab("about:blank", { inBackground: false });
+  let browser = tab.linkedBrowser;
+  browser.addEventListener("PluginCrashed", onCrash, false);
+  Services.obs.addObserver(onSubmitStatus, "crash-report-status", false);
+
+  registerCleanupFunction(function cleanUp() {
+    env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+    env.set("MOZ_CRASHREPORTER_URL", serverURL);
+    gBrowser.selectedBrowser.removeEventListener("PluginCrashed", onCrash,
+                                                 false);
+    Services.obs.removeObserver(onSubmitStatus, "crash-report-status");
+    gBrowser.removeCurrentTab();
+  });
+
+  doNextRun();
+}
+
+let runs = [
+  {
+    shouldSubmissionUIBeVisible: true,
+    comment: "",
+    urlOptIn: false,
+  },
+  {
+    shouldSubmissionUIBeVisible: true,
+    comment: "a test comment",
+    urlOptIn: true,
+  },
+  {
+    width: 300,
+    height: 300,
+    shouldSubmissionUIBeVisible: false,
+  },
+];
+
+let currentRun = null;
+
+function doNextRun() {
+  try {
+    if (!runs.length) {
+      finish();
+      return;
+    }
+    currentRun = runs.shift();
+    let args = ["width", "height"].reduce(function (memo, arg) {
+      if (arg in currentRun)
+        memo[arg] = currentRun[arg];
+      return memo;
+    }, {});
+    gBrowser.loadURI(CRASH_URL + "?" +
+                     encodeURIComponent(JSON.stringify(args)));
+    // And now wait for the crash.
+  }
+  catch (err) {
+    failWithException(err);
+    finish();
+  }
+}
+
+function onCrash() {
+  try {
+    let plugin = getBrowser().contentDocument.getElementById("plugin");
+    let elt = plugin.ownerDocument.getAnonymousElementByAttribute.bind(plugin.ownerDocument, plugin, "class");
+    let style =
+      gBrowser.contentWindow.getComputedStyle(elt("msg msgPleaseSubmit"));
+    is(style.display,
+       currentRun.shouldSubmissionUIBeVisible ? "block" : "none",
+       "Submission UI visibility should be correct");
+    if (!currentRun.shouldSubmissionUIBeVisible) {
+      // Done with this run.
+      doNextRun();
+      return;
+    }
+    elt("submitComment").value = currentRun.comment;
+    elt("submitURLOptIn").checked = currentRun.urlOptIn;
+    elt("submitButton").click();
+    // And now wait for the submission status notification.
+  }
+  catch (err) {
+    failWithException(err);
+    doNextRun();
+  }
+}
+
+function onSubmitStatus(subj, topic, data) {
+  try {
+    // Wait for success or failed, doesn't matter which.
+    if (data != "success" && data != "failed")
+      return;
+
+    let extra = getPropertyBagValue(subj.QueryInterface(Ci.nsIPropertyBag),
+                                    "extra");
+    ok(extra instanceof Ci.nsIPropertyBag, "Extra data should be property bag");
+
+    let val = getPropertyBagValue(extra, "PluginUserComment");
+    if (currentRun.comment)
+      is(val, currentRun.comment,
+         "Comment in extra data should match comment in textbox");
+    else
+      ok(val === undefined,
+         "Comment should be absent from extra data when textbox is empty");
+
+    val = getPropertyBagValue(extra, "PluginContentURL");
+    if (currentRun.urlOptIn)
+      is(val, gBrowser.currentURI.spec,
+         "URL in extra data should match browser URL when opt-in checked");
+    else
+      ok(val === undefined,
+         "URL should be absent from extra data when opt-in not checked");
+  }
+  catch (err) {
+    failWithException(err);
+  }
+  doNextRun();
+}
+
+function getPropertyBagValue(bag, key) {
+  try {
+    var val = bag.getProperty(key);
+  }
+  catch (e if e.result == Components.results.NS_ERROR_FAILURE) {}
+  return val;
+}
+
+function failWithException(err) {
+  ok(false, "Uncaught exception: " + err + "\n" + err.stack);
+}
new file mode 100644
--- /dev/null
+++ b/suite/browser/test/browser/pluginCrashCommentAndURL.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <script type="text/javascript">
+      function crash() {
+        var plugin = document.getElementById("plugin");
+        var argStr = decodeURIComponent(window.location.search.substr(1));
+        if (argStr) {
+          var args = JSON.parse(argStr);
+          for (var key in args)
+            plugin.setAttribute(key, args[key]);
+        }
+        try {
+          plugin.crash();
+        }
+        catch (err) {}
+      }
+    </script>
+  </head>
+  <body onload="crash();">
+    <embed id="plugin" type="application/x-test"
+           width="400" height="400"
+           drawmode="solid" color="FF00FFFF">
+    </embed>
+  </body>
+</html>
--- a/suite/common/bindings/notification.xml
+++ b/suite/common/bindings/notification.xml
@@ -764,16 +764,26 @@
             // XXX bug 446693. The text-shadow on the submitted-report text at
             //     the bottom causes scrollHeight to be larger than it should be.
             return (aOverlay.scrollWidth > pluginRect.width) ||
                    (aOverlay.scrollHeight - 5 > pluginRect.height);
           ]]>
         </body>
       </method>
 
+      <method name="getPluginUI">
+        <parameter name="aPlugin"/>
+        <parameter name="aClassName"/>
+        <body>
+          <![CDATA[
+            return aPlugin.ownerDocument.getAnonymousElementByAttribute(aPlugin, "class", aClassName);
+          ]]>
+        </body>
+      </method>
+
       <method name="addLinkClickCallback">
         <parameter name="linkNode"/>
         <parameter name="callback"/>
         <body>
           <![CDATA[
             // XXX just doing (callback)(arg) was giving a same-origin error. bug?
 
             /* We use event bubbling for the event listeners so that inside a
@@ -812,19 +822,28 @@
           ]]>
         </body>
       </method>
 
       <!-- Callback for user clicking "submit a report" link -->
       <method name="submitReport">
         <parameter name="pluginDumpID"/>
         <parameter name="browserDumpID"/>
+        <parameter name="plugin"/>
         <body>
           <![CDATA[
-            this.CrashSubmit.submit(pluginDumpID);
+            var keyVals = {};
+            if (plugin) {
+              let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
+              if (userComment)
+                keyVals.PluginUserComment = userComment;
+              if (this.getPluginUI(plugin, "submitURLOptIn").checked)
+                keyVals.PluginContentURL = plugin.ownerDocument.URL;
+            }
+            this.CrashSubmit.submit(pluginDumpID, { extraExtraKeyVals: keyVals });
             if (browserDumpID)
               this.CrashSubmit.submit(browserDumpID);
           ]]>
         </body>
       </method>
 
       <!-- Callback for user clicking a "reload page" link -->
       <method name="reloadPage">
@@ -1304,29 +1323,29 @@
         </body>
       </method>
 
       <method name="hideClickToPlayOverlay">
         <parameter name="pluginElement"/>
         <body>
           <![CDATA[
             var doc = pluginElement.ownerDocument;
-            var overlay = doc.getAnonymousElementByAttribute(pluginElement, "class", "mainBox");
+            var overlay = this.getPluginUI(pluginElement, "mainBox");
             if (overlay) // no overlay if plugin has been activated
               overlay.style.visibility = "hidden";
           ]]>
         </body>
       </method>
 
       <method name="setupPluginClickToPlay">
         <parameter name="pluginElement"/>
         <body>
           <![CDATA[
             var doc = pluginElement.ownerDocument;
-            var overlay = doc.getAnonymousElementByAttribute(pluginElement, "class", "mainBox");
+            var overlay = this.getPluginUI(pluginElement, "mainBox");
 
             var objLoadingContent = pluginElement.QueryInterface(Components.interfaces.nsIObjectLoadingContent);
             if (!this.canActivatePlugin(objLoadingContent)) {
               overlay.style.visibility = "hidden";
               return;
             }
 
             if (this.clickToPlayPluginsActivated) {
@@ -1339,28 +1358,28 @@
 
             if (this.isTooSmall(pluginElement, overlay)) {
               overlay.style.visibility = "hidden";
               return;
             }
 
             this.addLinkClickCallback(overlay, this.activateSinglePlugin, pluginElement);
 
-            var closeIcon = doc.getAnonymousElementByAttribute(pluginElement, "anonid", "closeIcon");
+            var closeIcon = this.getPluginUI(pluginElement, "closeIcon");
             this.addLinkClickCallback(closeIcon, this.hideClickToPlayOverlay, pluginElement);
           ]]>
         </body>
       </method>
 
       <method name="handlePlayPreviewEvent">
         <parameter name="pluginElement"/>
         <body>
           <![CDATA[
             var doc = pluginElement.ownerDocument;
-            var previewContent = doc.getAnonymousElementByAttribute(pluginElement, "class", "previewPluginContent");
+            var previewContent = this.getPluginUI(pluginElement, "previewPluginContent");
 
             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);
             }
@@ -2059,17 +2078,17 @@
           switch (plugin.pluginFallbackType) {
             case nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
               // For broken non-object plugin tags, register a click handler so
               // that the user can click the plugin replacement to get the new
               // plugin. Object tags can, and often do, deal with that
               // themselves, so don't stomp on the page developer's toes.
               if (!(plugin instanceof HTMLObjectElement)) {
                 // We don't yet check to see if there's actually an installer available.
-                var installStatus = doc.getAnonymousElementByAttribute(plugin, "class", "installStatus");
+                var installStatus = this.getPluginUI(plugin, "installStatus");
                 installStatus.setAttribute("status", "ready");
                 var iconStatus = doc.getAnonymousElementByAttribute(plugin, "class", "icon");
                 iconStatus.setAttribute("status", "ready");
                 var installLink = doc.getAnonymousElementByAttribute(plugin, "class", "installPluginLink");
                 this.addLinkClickCallback(installLink, installMissingPlugins);
               }
 
               notification = "missing-plugins";
@@ -2168,21 +2187,25 @@
             if (submittedReport) { // submitReports && !doPrompt, handled in observer
               status = "submitted";
             }
             else if (!submitReports && !doPrompt) {
               status = "noSubmit";
             }
             else { // doPrompt
               status = "please";
-              // XXX can we make the link target actually be blank?
-              let pleaseLink = doc.getAnonymousElementByAttribute(
-                                    plugin, "class", "pleaseSubmitLink");
-              this.addLinkClickCallback(pleaseLink, this.submitReport,
-                                        pluginDumpID, browserDumpID);
+              this.getPluginUI(plugin, "submitButton").addEventListener("click",
+                function (event) {
+                  if (event.button != 0 || !event.isTrusted)
+                    return;
+                  this.submitReport(pluginDumpID, browserDumpID, plugin);
+                  this._prefs.setBoolPref("dom.ipc.plugins.reportCrashURL", optInCB.checked);
+                }.bind(this));
+                let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
+                optInCB.checked = this._prefs.getBoolPref("dom.ipc.plugins.reportCrashURL");
             }
 
             // 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([Components.interfaces.nsIObserver,
@@ -2218,46 +2241,54 @@
           // 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);
 
-          var bottomLinks = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgBottomLinks");
-          bottomLinks.style.display = "block";
           var helpIcon = doc.getAnonymousElementByAttribute(plugin, "class", "helpIcon");
           this.addLinkClickCallback(helpIcon, this.openHelpPage);
 
           var messageString = this._stringBundle.formatStringFromName("crashedpluginsMessage.title", [pluginName], 1);
-          var crashText = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgCrashed");
+          var crashText = this.getPluginUI(plugin, "msgCrashedText");
           crashText.textContent = messageString;
 
           var link = doc.getAnonymousElementByAttribute(plugin, "class", "reloadLink");
           this.addLinkClickCallback(link, this.reloadPage);
 
+          var isShowing = true;
+
           // Is the <object>'s size too small to hold what we want to show?
           if (this.isTooSmall(plugin, overlay)) {
-            // Hide the overlay's contents. Use visibility style, so that it
-            // doesn't collapse down to 0x0.
-            overlay.style.visibility = "hidden";
-            // If another plugin on the page was large enough to show our UI, we
-            // don't want to show a notification bar.
-            if (!this.crashNotified)
-              this.showPluginCrashedNotification(pluginDumpID, browserDumpID, messageString);
+            // First try hiding the comment box and related report submission UI.
+            statusDiv.removeAttribute("status");
+
+            if (this.isTooSmall(plugin, overlay)) {
+              // Hide the overlay's contents. Use visibility style, so that it doesn't
+              // collapse down to 0x0.
+              overlay.style.visibility = "hidden";
+              isShowing = false;
+            }
           }
-          else {
+
+          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.
             var notification = this.getNotificationWithValue("plugin-crashed");
             if (notification)
               this.removeNotification(notification, true);
             this.crashNotified = true;
+          } else {
+            // If another plugin on the page was large enough to show our UI, we don't
+            // want to show a notification bar.
+            if (!this.crashNotified)
+              this.showPluginCrashedNotification(pluginDumpID, browserDumpID, messageString);
           }
         ]]>
       </handler>
 
       <handler event="npapi-carbon-event-model-failure" phase="capturing">
         <![CDATA[
           var plugin = event.target;
           // Force a style flush, so that we ensure our binding is attached.
--- a/suite/themes/modern/mozapps/plugins/pluginProblem.css
+++ b/suite/themes/modern/mozapps/plugins/pluginProblem.css
@@ -94,43 +94,80 @@ html|a {
 :-moz-handler-clicktoplay .msgTapToPlay {
   display: none;
 }
 
 .submitStatus div {
   min-height: 19px; /* height of biggest line (with throbber) */
 }
 
-.msgBottomLinks {
-  padding-left: 2px;
-  padding-right: 2px;
+.submitComment {
+  width: 340px;
+  height: 70px;
+  padding: 5px;
+  border: none;
+  border-radius: 5px;
+  resize: none;
+  font-family: inherit;
+  font-size: inherit;
+}
+
+.submitURLOptInBox {
+  text-align: start;
+}
+
+.submitURLOptIn {
+  margin-left: -1px;
 }
 
+.mainBox[chromedir="rtl"] .submitURLOptIn {
+  margin-left: 0;
+  margin-right: -1px;
+}
+
+.submitButtonBox {
+  margin-top: 7px;
+}
+
+.submitButton {
+  float: right;
+}
+
+.mainBox[chromedir="rtl"] .submitButton {
+  float: left;
+}
+
+
 .helpIcon {
   float: left;
   display: inline-block;
   min-width: 16px;
   min-height: 16px;
   background: url(chrome://mozapps/skin/plugins/pluginHelp-16.png) no-repeat;
+  cursor: pointer;
+}
+
+.mainBox[chromedir="rtl"] .helpIcon {
+  float: right;
 }
 
 .closeIcon {
   display: block;
   position: absolute;
   width: 15px;
   height: 16px;
   top: 4px;
   right: 4px;
   border-style: none;
   background-color: transparent;
   background-image: url(chrome://global/skin/icons/close.gif);
   background-repeat: no-repeat;
 }
 
-.closeIcon:-moz-locale-dir(rtl) {
+.mainBox[chromedir="rtl"] .closeIcon {
   right: auto;
   left: 4px;
 }
 
 .closeIcon:focus,
 .closeIcon:hover {
   background-image: url(chrome://global/skin/icons/close-hov.gif);
 }