Bug 804910 - Part 1. Check for worker load failures and send a notification in case of errors. r=felipe
authorShane Caraveo <scaraveo@mozilla.com>
Mon, 29 Oct 2012 21:57:07 -0700
changeset 111940 9104ce0e7396c5843b0f6d1e45a980506a9528fd
parent 111939 4eba57a286d7e8bd3423f81eb5628dc381f0cd18
child 111941 08f97e0fa9daef428a8f4109adf905a4c9675160
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersfelipe
bugs804910
milestone19.0a1
Bug 804910 - Part 1. Check for worker load failures and send a notification in case of errors. r=felipe
toolkit/components/social/FrameWorker.jsm
toolkit/components/social/MessagePortWorker.js
toolkit/components/social/test/browser/browser_frameworker.js
--- a/toolkit/components/social/FrameWorker.jsm
+++ b/toolkit/components/social/FrameWorker.jsm
@@ -178,44 +178,57 @@ FrameWorker.prototype = {
       workerWindow.addEventListener(t, l, c)
     };
 
     // Note we don't need to stash |sandbox| in |this| as the unload handler
     // has a reference in its closure, so it can't die until that handler is
     // removed - at which time we've explicitly killed it anyway.
     let worker = this;
 
-    workerWindow.addEventListener("load", function loadListener() {
-      workerWindow.removeEventListener("load", loadListener);
+    workerWindow.addEventListener("DOMContentLoaded", function loadListener() {
+      workerWindow.removeEventListener("DOMContentLoaded", loadListener);
+
+      // no script, error out now rather than creating ports, etc
+      let scriptText = workerWindow.document.body.textContent.trim();
+      if (!scriptText) {
+        Cu.reportError("FrameWorker: Empty worker script received");
+        Services.obs.notifyObservers(null, "social:frameworker-error", worker.url);
+        return;
+      }
+
       // the iframe has loaded the js file as text - first inject the magic
       // port-handling code into the sandbox.
       try {
         Services.scriptloader.loadSubScript("resource://gre/modules/MessagePortBase.jsm", sandbox);
         Services.scriptloader.loadSubScript("resource://gre/modules/MessagePortWorker.js", sandbox);
       }
       catch (e) {
         Cu.reportError("FrameWorker: Error injecting port code into content side of the worker: " + e + "\n" + e.stack);
+        Services.obs.notifyObservers(null, "social:frameworker-error", worker.url);
+        return;
       }
 
       // and wire up the client message handling.
       try {
         initClientMessageHandler(worker, workerWindow);
       }
       catch (e) {
         Cu.reportError("FrameWorker: Error setting up event listener for chrome side of the worker: " + e + "\n" + e.stack);
+        Services.obs.notifyObservers(null, "social:frameworker-error", worker.url);
+        return;
       }
 
       // Now get the worker js code and eval it into the sandbox
       try {
-        let scriptText = workerWindow.document.body.textContent;
         Cu.evalInSandbox(scriptText, sandbox, "1.8", workerWindow.location.href, 1);
       } catch (e) {
         Cu.reportError("FrameWorker: Error evaluating worker script for " + worker.name + ": " + e + "; " +
             (e.lineNumber ? ("Line #" + e.lineNumber) : "") +
             (e.stack ? ("\n" + e.stack) : ""));
+        Services.obs.notifyObservers(null, "social:frameworker-error", worker.url);
         return;
       }
 
       // so finally we are ready to roll - dequeue all the pending connects
       worker.loaded = true;
       for (let port of worker.pendingPorts) {
         if (port._portid) { // may have already been closed!
           try {
@@ -336,17 +349,21 @@ function initClientMessageHandler(worker
     let portid = data.portId;
     let port;
     if (!data.portFromType || data.portFromType === "client") {
       // this is a message posted by ourself so ignore it.
       return;
     }
     switch (data.portTopic) {
       // No "port-create" here - client ports are created explicitly.
-
+      case "port-connection-error":
+        // onconnect failed, we cannot connect the port, the worker has
+        // become invalid
+        Services.obs.notifyObservers(null, "social:frameworker-error", worker.url);
+        break;
       case "port-close":
         // the worker side of the port was closed, so close this side too.
         port = worker.ports[portid];
         if (!port) {
           // port already closed (which will happen when we call port.close()
           // below - the worker side will send us this message but we've
           // already closed it.)
           return;
--- a/toolkit/components/social/MessagePortWorker.js
+++ b/toolkit/components/social/MessagePortWorker.js
@@ -60,17 +60,24 @@ function __initWorkerMessageHandler() {
     }
     switch (data.portTopic) {
       case "port-create":
         // a new port was created on the "client" side - create a new worker
         // port and store it in the map
         port = new WorkerPort(portid);
         ports[portid] = port;
         // and call the "onconnect" handler.
-        onconnect({ports: [port]});
+        try {
+          onconnect({ports: [port]});
+        } catch(e) {
+          // we have a bad worker and cannot continue, we need to signal
+          // an error
+          port._postControlMessage("port-connection-error", JSON.stringify(e.toString()));
+          throw e;
+        }
         break;
 
       case "port-close":
         // the client side of the port was closed, so close this side too.
         port = ports[portid];
         if (!port) {
           // port already closed (which will happen when we call port.close()
           // below - the client side will send us this message but we've
--- a/toolkit/components/social/test/browser/browser_frameworker.js
+++ b/toolkit/components/social/test/browser/browser_frameworker.js
@@ -458,10 +458,72 @@ let tests = {
           ioService.offline = oldOffline;
           worker.terminate();
           cbnext();
         }
         ioService.offline = false;
       }
       ioService.offline = true;
     }
-  }
+  },
+  
+  testMissingWorker: function(cbnext) {
+    Services.obs.addObserver(function handleError() {
+      Services.obs.removeObserver(handleError, "social:frameworker-error");
+        ok(true, "social:frameworker-error was handled");
+        cbnext();
+    }, 'social:frameworker-error', false);
+    // don't ever create this file!  We want a 404.
+    let url = "https://example.com/browser/toolkit/components/social/test/browser/worker_is_missing.js";
+    let worker = getFrameWorkerHandle(url, undefined, "testMissingWorker");
+    worker.port.onmessage = function(e) {
+      ok(false, "social:frameworker-error was handled");
+      cbnext();
+    }
+  },
+
+  testNoConnectWorker: function(cbnext) {
+    Services.obs.addObserver(function handleError() {
+      Services.obs.removeObserver(handleError, "social:frameworker-error");
+        ok(true, "social:frameworker-error was handled");
+        cbnext();
+    }, 'social:frameworker-error', false);
+    let worker = getFrameWorkerHandle(makeWorkerUrl(function () {}),
+                                      undefined, "testNoConnectWorker");
+    worker.port.onmessage = function(e) {
+      ok(false, "social:frameworker-error was handled");
+      cbnext();
+    }
+  },
+
+  testEmptyWorker: function(cbnext) {
+    Services.obs.addObserver(function handleError() {
+      Services.obs.removeObserver(handleError, "social:frameworker-error");
+        ok(true, "social:frameworker-error was handled");
+        cbnext();
+    }, 'social:frameworker-error', false);
+    let worker = getFrameWorkerHandle("data:application/javascript;charset=utf-8,",
+                                      undefined, "testEmptyWorker");
+    worker.port.onmessage = function(e) {
+      ok(false, "social:frameworker-error was handled");
+      cbnext();
+    }
+  },
+
+  testWorkerConnectError: function(cbnext) {
+    Services.obs.addObserver(function handleError() {
+      Services.obs.removeObserver(handleError, "social:frameworker-error");
+        ok(true, "social:frameworker-error was handled");
+        cbnext();
+    }, 'social:frameworker-error', false);
+    let run = function () {
+      onconnect = function(e) {
+        throw new Error("worker failure");
+      }
+    }
+    let worker = getFrameWorkerHandle(makeWorkerUrl(run),
+                                      undefined, "testWorkerConnectError");
+    worker.port.onmessage = function(e) {
+      ok(false, "social:frameworker-error was handled");
+      cbnext();
+    }
+  },
 }