Bug 910566 - handle remote frameworker process crashes. r=felipe
authorMark Hammond <mhammond@skippinet.com.au>
Thu, 24 Oct 2013 15:22:16 +1100
changeset 166829 e095e2f442f847d77fe563623d0f58f87b7ae643
parent 166580 684d1c36eace12b366735d36d174f0787b0e5c97
child 166830 d6018de7fe82b2e584d17d237b2500aada5c202d
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe
bugs910566
milestone27.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 910566 - handle remote frameworker process crashes. r=felipe
browser/base/content/aboutSocialError.xhtml
browser/base/content/test/social/browser.ini
browser/base/content/test/social/browser_social_workercrash.js
browser/base/content/test/social/social_crash_content_helper.js
toolkit/components/social/FrameWorker.jsm
--- a/browser/base/content/aboutSocialError.xhtml
+++ b/browser/base/content/aboutSocialError.xhtml
@@ -107,15 +107,27 @@
       config.tryAgainCallback();
     }
 
     function loadQueryURL() {
       window.location.href = config.queryURL;
     }
 
     function reloadProvider() {
+      // Just incase the current provider *isn't* in a frameworker-error
+      // state, reload the current one.
       Social.provider.reload();
+      // If the problem is a frameworker-error, it may be that the child
+      // process crashed - and if that happened, then *all* providers in that
+      // process will have crashed.  However, only the current provider is
+      // likely to have the error surfaced in the UI - so we reload *all*
+      // providers that are in a frameworker-error state.
+      for (let provider of Social.providers) {
+        if (provider.enabled && provider.errorState == "frameworker-error") {
+          provider.reload();
+        }
+      }
     }
 
     parseQueryString();
     setUpStrings();
   ]]></script>
 </html>
--- a/browser/base/content/test/social/browser.ini
+++ b/browser/base/content/test/social/browser.ini
@@ -7,16 +7,17 @@ support-files =
   opengraph/opengraph.html
   opengraph/shortlink_linkrel.html
   opengraph/shorturl_link.html
   opengraph/shorturl_linkrel.html
   share.html
   social_activate.html
   social_activate_iframe.html
   social_chat.html
+  social_crash_content_helper.js
   social_flyout.html
   social_mark.html
   social_panel.html
   social_sidebar.html
   social_sidebar_empty.html
   social_window.html
   social_worker.js
   unchecked.jpg
@@ -37,8 +38,9 @@ support-files =
 [browser_social_mozSocial_API.js]
 [browser_social_multiprovider.js]
 [browser_social_multiworker.js]
 [browser_social_perwindowPB.js]
 [browser_social_sidebar.js]
 [browser_social_status.js]
 [browser_social_toolbar.js]
 [browser_social_window.js]
+[browser_social_workercrash.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_workercrash.js
@@ -0,0 +1,157 @@
+/* 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/. */
+
+// This tests our recovery if a child content process hosting providers
+// crashes.
+
+// A content script we inject into one of our browsers
+const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/browser/base/content/test/social/social_crash_content_helper.js";
+
+let {getFrameWorkerHandle} = Cu.import("resource://gre/modules/FrameWorker.jsm", {});
+
+function test() {
+  waitForExplicitFinish();
+
+  Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
+  // We need to ensure all our workers are in the same content process.
+  Services.prefs.setIntPref("dom.ipc.processCount", 1);
+
+  runSocialTestWithProvider(gProviders, function (finishcb) {
+    Social.enabled = true;
+    runSocialTests(tests, undefined, undefined, function() {
+      Services.prefs.clearUserPref("dom.ipc.processCount");
+      Services.prefs.clearUserPref("social.sidebar.open");
+      Services.prefs.clearUserPref("social.allowMultipleWorkers");
+      finishcb();
+    });
+  });
+}
+
+let gProviders = [
+  {
+    name: "provider 1",
+    origin: "https://example.com",
+    sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html?provider1",
+    workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+    iconURL: "chrome://branding/content/icon48.png"
+  },
+  {
+    name: "provider 2",
+    origin: "https://test1.example.com",
+    sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
+    workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
+    iconURL: "chrome://branding/content/icon48.png"
+  }
+];
+
+var tests = {
+  testCrash: function(next) {
+    // open the sidebar, then crash the child.
+    let sbrowser = document.getElementById("social-sidebar-browser");
+    onSidebarLoad(function() {
+      // get the browser element for our provider.
+      let fw = getFrameWorkerHandle(gProviders[0].workerURL);
+      fw.port.close();
+      fw._worker.browserPromise.then(browser => {
+        let mm = browser.messageManager;
+        mm.loadFrameScript(TEST_CONTENT_HELPER, false);
+        // add an observer for the crash - after it sees the crash we attempt
+        // a reload.
+        let observer = new crashObserver(function() {
+          info("Saw the process crash.")
+          Services.obs.removeObserver(observer, 'ipc:content-shutdown');
+          // Add another sidebar load listener - it should be the error page.
+          onSidebarLoad(function() {
+            ok(sbrowser.contentDocument.location.href.indexOf("about:socialerror?")==0, "is on social error page");
+            // after reloading, the sidebar should reload
+            onSidebarLoad(function() {
+              // now ping both workers - they should both be alive.
+              ensureWorkerLoaded(gProviders[0], function() {
+                ensureWorkerLoaded(gProviders[1], function() {
+                  // and we are done!
+                  next();
+                });
+              });
+            });
+            // click the try-again button.
+            sbrowser.contentDocument.getElementById("btnTryAgain").click();
+          });
+        });
+        Services.obs.addObserver(observer, 'ipc:content-shutdown', false);
+        // and cause the crash.
+        mm.sendAsyncMessage("social-test:crash");
+      });
+    })
+    Services.prefs.setBoolPref("social.sidebar.open", true);
+  },
+}
+
+function onSidebarLoad(callback) {
+  let sbrowser = document.getElementById("social-sidebar-browser");
+  sbrowser.addEventListener("load", function load() {
+    sbrowser.removeEventListener("load", load, true);
+    callback();
+  }, true);
+}
+
+function ensureWorkerLoaded(manifest, callback) {
+  let fw = getFrameWorkerHandle(manifest.workerURL);
+  // once the worker responds to a ping we know it must be up.
+  let port = fw.port;
+  port.onmessage = function(msg) {
+    if (msg.data.topic == "pong") {
+      port.close();
+      callback();
+    }
+  }
+  port.postMessage({topic: "ping"})
+}
+
+// More duplicated code from browser_thumbnails_brackground_crash.
+// Bug 915518 exists to unify these.
+
+// This observer is needed so we can clean up all evidence of the crash so
+// the testrunner thinks things are peachy.
+let crashObserver = function(callback) {
+  this.callback = callback;
+}
+crashObserver.prototype = {
+  observe: function(subject, topic, data) {
+    is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
+    ok(subject instanceof Components.interfaces.nsIPropertyBag2,
+       'Subject implements nsIPropertyBag2.');
+    // we might see this called as the process terminates due to previous tests.
+    // We are only looking for "abnormal" exits...
+    if (!subject.hasKey("abnormal")) {
+      info("This is a normal termination and isn't the one we are looking for...");
+      return;
+    }
+
+    var dumpID;
+    if ('nsICrashReporter' in Components.interfaces) {
+      dumpID = subject.getPropertyAsAString('dumpID');
+      ok(dumpID, "dumpID is present and not an empty string");
+    }
+
+    if (dumpID) {
+      var minidumpDirectory = getMinidumpDirectory();
+      removeFile(minidumpDirectory, dumpID + '.dmp');
+      removeFile(minidumpDirectory, dumpID + '.extra');
+    }
+    this.callback();
+  }
+}
+
+function getMinidumpDirectory() {
+  var dir = Services.dirsvc.get('ProfD', Components.interfaces.nsIFile);
+  dir.append("minidumps");
+  return dir;
+}
+function removeFile(directory, filename) {
+  var file = directory.clone();
+  file.append(filename);
+  if (file.exists()) {
+    file.remove(false);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/social/social_crash_content_helper.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Ideally we would use CrashTestUtils.jsm, but that's only available for
+// xpcshell tests - so we just copy a ctypes crasher from it.
+Cu.import("resource://gre/modules/ctypes.jsm");
+let crash = function() { // this will crash when called.
+  let zero = new ctypes.intptr_t(8);
+  let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+  badptr.contents
+};
+
+
+TestHelper = {
+  init: function() {
+    addMessageListener("social-test:crash", this);
+  },
+
+  receiveMessage: function(msg) {
+    switch (msg.name) {
+      case "social-test:crash":
+        privateNoteIntentionalCrash();
+        crash();
+      break;
+    }
+  },
+}
+
+TestHelper.init();
--- a/toolkit/components/social/FrameWorker.jsm
+++ b/toolkit/components/social/FrameWorker.jsm
@@ -66,16 +66,21 @@ this.getFrameWorkerHandle =
 
 // A "_Worker" is an internal representation of a worker.  It's never returned
 // directly to consumers.
 function _Worker(browserPromise, options) {
   this.browserPromise = browserPromise;
   this.options = options;
   this.ports = new Map();
   browserPromise.then(browser => {
+    browser.addEventListener("oop-browser-crashed", () => {
+      Cu.reportError("FrameWorker remote process crashed");
+      notifyWorkerError(options.origin);
+    });
+
     let mm = browser.messageManager;
     // execute the content script and send the message to bootstrap the content
     // side of the world.
     mm.loadFrameScript("resource://gre/modules/FrameWorkerContent.js", true);
     mm.sendAsyncMessage("frameworker:init", this.options);
     mm.addMessageListener("frameworker:port-message", this);
     mm.addMessageListener("frameworker:notify-worker-error", this);
   });