Bug 785920 - reload for workers. r=gavin.sharp
authorShane Caraveo <scaraveo@mozilla.com>
Fri, 12 Oct 2012 16:58:50 -0700
changeset 110298 7a75ede979bf04a07ed28b71b67ad7f4e20ed4e8
parent 110297 5a5563b58d5b6cd059f4a77f8395b9dc6795ee1a
child 110299 637ab1786ee16c9a22986b0f574fb8d4023fb54d
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersgavin
bugs785920
milestone19.0a1
Bug 785920 - reload for workers. r=gavin.sharp
toolkit/components/social/FrameWorker.jsm
toolkit/components/social/WorkerAPI.jsm
toolkit/components/social/test/browser/browser_workerAPI.js
toolkit/components/social/test/browser/worker_social.js
--- a/toolkit/components/social/FrameWorker.jsm
+++ b/toolkit/components/social/FrameWorker.jsm
@@ -66,34 +66,49 @@ function getFrameWorkerHandle(url, clien
 function FrameWorker(url, name) {
   this.url = url;
   this.name = name || url;
   this.ports = {};
   this.pendingPorts = [];
   this.loaded = false;
 
   this.frame = makeHiddenFrame();
-
-  var self = this;
-  Services.obs.addObserver(function injectController(doc, topic, data) {
-    if (!doc.defaultView || doc.defaultView != self.frame.contentWindow) {
-      return;
-    }
-    Services.obs.removeObserver(injectController, "document-element-inserted", false);
-    try {
-      self.createSandbox();
-    } catch (e) {
-      Cu.reportError("FrameWorker: failed to create sandbox for " + url + ". " + e);
-    }
-  }, "document-element-inserted", false);
-
-  this.frame.setAttribute("src", url);
+  this.load();
 }
 
 FrameWorker.prototype = {
+  load: function FrameWorker_loadWorker() {
+    var self = this;
+    Services.obs.addObserver(function injectController(doc, topic, data) {
+      if (!doc.defaultView || doc.defaultView != self.frame.contentWindow) {
+        return;
+      }
+      Services.obs.removeObserver(injectController, "document-element-inserted", false);
+      try {
+        self.createSandbox();
+      } catch (e) {
+        Cu.reportError("FrameWorker: failed to create sandbox for " + url + ". " + e);
+      }
+    }, "document-element-inserted", false);
+
+    this.frame.setAttribute("src", this.url);
+  },
+
+  reload: function FrameWorker_reloadWorker() {
+    // push all the ports into pending ports, they will be re-entangled
+    // during the call to createSandbox after the document is reloaded
+    for (let [portid, port] in Iterator(this.ports)) {
+      port._window = null;
+      this.pendingPorts.push(port);
+    }
+    this.ports = {};
+    this.loaded = false;
+    this.load();
+  },
+
   createSandbox: function createSandbox() {
     let workerWindow = this.frame.contentWindow;
     let sandbox = new Cu.Sandbox(workerWindow);
 
     // copy the window apis onto the sandbox namespace only functions or
     // objects that are naturally a part of an iframe, I'm assuming they are
     // safe to import this way
     let workerAPI = ['WebSocket', 'localStorage', 'atob', 'btoa',
@@ -134,20 +149,20 @@ FrameWorker.prototype = {
     // a function, but using eval() directly means functions in the script
     // don't end up in the global scope.
     sandbox._evalInSandbox = function(s) {
       Cu.evalInSandbox(s, sandbox);
     };
 
     // and we delegate ononline and onoffline events to the worker.
     // See http://www.whatwg.org/specs/web-apps/current-work/multipage/workers.html#workerglobalscope
-    this.frame.addEventListener('offline', function fw_onoffline(event) {
+    workerWindow.addEventListener('offline', function fw_onoffline(event) {
       Cu.evalInSandbox("onoffline();", sandbox);
     }, false);
-    this.frame.addEventListener('online', function fw_ononline(event) {
+    workerWindow.addEventListener('online', function fw_ononline(event) {
       Cu.evalInSandbox("ononline();", sandbox);
     }, false);
 
     sandbox._postMessage = function fw_postMessage(d, o) {
       workerWindow.postMessage(d, o)
     };
     sandbox._addEventListener = function fw_addEventListener(t, l, c) {
       workerWindow.addEventListener(t, l, c)
@@ -243,25 +258,22 @@ function makeHiddenFrame() {
   docShell.allowAuth = false;
   docShell.allowPlugins = false;
   docShell.allowImages = false;
   docShell.allowWindowControl = false;
   // TODO: disable media (bug 759964)
   return iframe;
 }
 
+// public methods on WorkerHandle should conform to the SharedWorker api
 function WorkerHandle(port, worker) {
   this.port = port;
   this._worker = worker;
 }
 WorkerHandle.prototype = {
-  get document() {
-    return this._worker.frame.contentDocument;
-  },
-
   // XXX - workers have no .close() method, but *do* have a .terminate()
   // method which we should implement. However, the worker spec doesn't define
   // a callback to be made in the worker when this happens - it all just dies.
   // TODO: work out a sane impl for 'terminate'.
   terminate: function terminate() {
     this._worker.terminate();
   }
 };
--- a/toolkit/components/social/WorkerAPI.jsm
+++ b/toolkit/components/social/WorkerAPI.jsm
@@ -22,19 +22,16 @@ function WorkerAPI(provider, port) {
   this._provider = provider;
   this._port = port;
   this._port.onmessage = this._handleMessage.bind(this);
 
   // Send an "intro" message so the worker knows this is the port
   // used for the api.
   // later we might even include an API version - version 0 for now!
   this._port.postMessage({topic: "social.initialize"});
-  
-  // backwards compat, remove after Aug 1.
-  this._port.postMessage({topic: "social.cookie-changed"});
 }
 
 WorkerAPI.prototype = {
   terminate: function terminate() {
     this._port.close();
   },
 
   _handleMessage: function _handleMessage(event) {
@@ -47,24 +44,32 @@ WorkerAPI.prototype = {
     try {
       handler.call(this, data);
     } catch (ex) {
       Cu.reportError("WorkerAPI: failed to handle message '" + topic + "': " + ex);
     }
   },
 
   handlers: {
+    "social.reload-worker": function(data) {
+      getFrameWorkerHandle(this._provider.workerURL, null)._worker.reload();
+      // the frameworker is going to be reloaded, send the initialization
+      // so it can have the same startup sequence as if it were loaded
+      // the first time.  This will be queued until the frameworker is ready.
+      this._port.postMessage({topic: "social.initialize"});
+    },
     "social.user-profile": function (data) {
       this._provider.updateUserProfile(data);
     },
     "social.ambient-notification": function (data) {
       this._provider.setAmbientNotification(data);
     },
     "social.cookies-get": function(data) {
-      let document = getFrameWorkerHandle(this._provider.workerURL, null).document;
+      let document = getFrameWorkerHandle(this._provider.workerURL, null).
+                        _worker.frame.contentDocument;
       let cookies = document.cookie.split(";");
       let results = [];
       cookies.forEach(function(aCookie) {
         let [name, value] = aCookie.split("=");
         results.push({name: unescape(name.trim()),
                       value: unescape(value.trim())});
       });
       this._port.postMessage({topic: "social.cookies-get-response",
--- a/toolkit/components/social/test/browser/browser_workerAPI.js
+++ b/toolkit/components/social/test/browser/browser_workerAPI.js
@@ -97,11 +97,48 @@ let tests = {
         port.close();
         next();
       }
     }
     var MAX_EXPIRY = Math.pow(2, 62);
     Services.cookies.add('.example.com', '/', 'cheez', 'burger', false, false, true, MAX_EXPIRY);
     port.postMessage({topic: "test-initialization"});
     port.postMessage({topic: "test.cookies-get"});
-  }
+  },
+
+  testWorkerReload: function(next) {
+    let fw = {};
+    Cu.import("resource://gre/modules/FrameWorker.jsm", fw);
 
+    // get a real handle to the worker so we can watch the unload event
+    // we watch for the unload of the worker to know it is infact being
+    // unloaded, after that if we get worker.connected we know that
+    // the worker was loaded again and ports reconnected
+    let reloading = false;
+    let worker = fw.getFrameWorkerHandle(provider.workerURL, undefined, "testWorkerReload");
+    let win = worker._worker.frame.contentWindow;
+    win.addEventListener("unload", function workerUnload(e) {
+      win.removeEventListener("unload", workerUnload);
+      ok(true, "worker unload event has fired");
+      reloading = true;
+    });
+    let port = provider.getWorkerPort();
+    ok(port, "provider has a port");
+    port.onmessage = function (e) {
+      let topic = e.data.topic;
+      switch (topic) {
+        case "test-initialization-complete":
+          // tell the worker to send the reload msg
+          port.postMessage({topic: "test-reload-init"});
+          break;
+        case "worker.connected":
+          // we'll get this message from the worker on every load of the worker,
+          // so we need to ignore it unless we have requested the reload.
+          if (reloading) {
+            ok(true, "worker reloaded and testPort was reconnected");
+            next();
+          }
+          break;
+      }
+    }
+    port.postMessage({topic: "test-initialization"});
+  }
 };
--- a/toolkit/components/social/test/browser/worker_social.js
+++ b/toolkit/components/social/test/browser/worker_social.js
@@ -28,11 +28,22 @@ onconnect = function(e) {
       case "test-ambient":
         apiPort.postMessage({topic: "social.ambient-notification", data: data});
         break;
       case "test.cookies-get":
         apiPort.postMessage({topic: "social.cookies-get"});
         break;
       case "social.cookies-get-response":
         testerPort.postMessage({topic: "test.cookies-get-response", data: data});
+        break;
+      case "test-reload-init":
+        // browser_social_sidebar.js started test, tell the sidebar to
+        // start
+        apiPort.postMessage({topic: 'social.reload-worker'});
+        break;
     }
   }
+  // used for "test-reload-worker"
+  if (apiPort && apiPort != port) {
+    port.postMessage({topic: "worker.connected"})
+  }
+
 }