Bug 914920 - Fix bug whereby about:newtab displays thumbnails from unrelated websites for some links. r=markh
authorDrew Willcoxon <adw@mozilla.com>
Wed, 04 Dec 2013 17:38:00 -0800
changeset 168021 4727303dd8a0c29681632ecb387c37ba4d758165
parent 168020 71fcb255a8cf1ed38f530a421d70bf989a824b24
child 168022 b3096c58816b3f36508025d042bb2d7a061c34f5
push id4703
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 20:24:19 +0000
treeherdermozilla-aurora@20af7fbd96c1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh
bugs914920
milestone28.0a1
Bug 914920 - Fix bug whereby about:newtab displays thumbnails from unrelated websites for some links. r=markh
toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
--- a/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
+++ b/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
@@ -5,16 +5,20 @@
 (function () { // bug 673569 workaround :(
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/PageThumbs.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+const STATE_LOADING = 1;
+const STATE_CAPTURING = 2;
+const STATE_CANCELED = 3;
+
 const backgroundPageThumbsContent = {
 
   init: function () {
     Services.obs.addObserver(this, "document-element-inserted", true);
 
     // We want a low network priority for this service - lower than b/g tabs
     // etc - so set it to the lowest priority available.
     this._webNav.QueryInterface(Ci.nsIDocumentLoader).
@@ -51,65 +55,113 @@ const backgroundPageThumbsContent = {
     }
   },
 
   get _webNav() {
     return docShell.QueryInterface(Ci.nsIWebNavigation);
   },
 
   _onCapture: function (msg) {
-    this._webNav.loadURI(msg.json.url,
+    this._nextCapture = {
+      id: msg.data.id,
+      url: msg.data.url,
+    };
+    if (this._currentCapture) {
+      if (this._state == STATE_LOADING) {
+        // Cancel the current capture.
+        this._state = STATE_CANCELED;
+        this._loadAboutBlank();
+      }
+      // Let the current capture finish capturing, or if it was just canceled,
+      // wait for onStateChange due to the about:blank load.
+      return;
+    }
+    this._startNextCapture();
+  },
+
+  _startNextCapture: function () {
+    if (!this._nextCapture)
+      return;
+    this._currentCapture = this._nextCapture;
+    delete this._nextCapture;
+    this._state = STATE_LOADING;
+    this._currentCapture.pageLoadStartDate = new Date();
+    this._webNav.loadURI(this._currentCapture.url,
                          Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
                          null, null, null);
-    // If a page was already loading, onStateChange is synchronously called at
-    // this point by loadURI.
-    this._requestID = msg.json.id;
-    this._requestDate = new Date();
   },
 
   onStateChange: function (webProgress, req, flags, status) {
-    if (!webProgress.isTopLevel ||
-        !(flags & Ci.nsIWebProgressListener.STATE_STOP) ||
-        req.name == "about:blank")
-      return;
+    if (webProgress.isTopLevel &&
+        (flags & Ci.nsIWebProgressListener.STATE_STOP) &&
+        this._currentCapture) {
+      if (req.name == "about:blank") {
+        if (this._state == STATE_CAPTURING) {
+          // about:blank has loaded, ending the current capture.
+          this._finishCurrentCapture();
+          delete this._currentCapture;
+          this._startNextCapture();
+        }
+        else if (this._state == STATE_CANCELED) {
+          // A capture request was received while the current capture's page
+          // was still loading.
+          delete this._currentCapture;
+          this._startNextCapture();
+        }
+      }
+      else if (this._state == STATE_LOADING) {
+        // The requested page has loaded.  Capture it.
+        this._state = STATE_CAPTURING;
+        this._captureCurrentPage();
+      }
+    }
+  },
 
-    let requestID = this._requestID;
-    let pageLoadTime = new Date() - this._requestDate;
-    delete this._requestID;
+  _captureCurrentPage: function () {
+    let capture = this._currentCapture;
+    capture.finalURL = this._webNav.currentURI.spec;
+    capture.pageLoadTime = new Date() - capture.pageLoadStartDate;
 
     let canvas = PageThumbs._createCanvas(content);
-    let captureDate = new Date();
+    let canvasDrawDate = new Date();
     PageThumbs._captureToCanvas(content, canvas);
-    let captureTime = new Date() - captureDate;
+    capture.canvasDrawTime = new Date() - canvasDrawDate;
 
-    let finalURL = this._webNav.currentURI.spec;
+    canvas.toBlob(blob => {
+      capture.imageBlob = blob;
+      // Load about:blank to finish the capture and wait for onStateChange.
+      this._loadAboutBlank();
+    });
+  },
+
+  _finishCurrentCapture: function () {
+    let capture = this._currentCapture;
     let fileReader = Cc["@mozilla.org/files/filereader;1"].
                      createInstance(Ci.nsIDOMFileReader);
     fileReader.onloadend = () => {
       sendAsyncMessage("BackgroundPageThumbs:didCapture", {
-        id: requestID,
+        id: capture.id,
         imageData: fileReader.result,
-        finalURL: finalURL,
+        finalURL: capture.finalURL,
         telemetry: {
-          CAPTURE_PAGE_LOAD_TIME_MS: pageLoadTime,
-          CAPTURE_CANVAS_DRAW_TIME_MS: captureTime,
+          CAPTURE_PAGE_LOAD_TIME_MS: capture.pageLoadTime,
+          CAPTURE_CANVAS_DRAW_TIME_MS: capture.canvasDrawTime,
         },
       });
     };
-    canvas.toBlob(blob => fileReader.readAsArrayBuffer(blob));
+    fileReader.readAsArrayBuffer(capture.imageBlob);
+  },
 
-    // If no other pages are loading, load about:blank to cause the captured
-    // window to be collected... eventually.  Calling loadURI at this point
-    // trips an assertion in nsLoadGroup::Cancel, so do it on another stack.
-    Services.tm.mainThread.dispatch(() => {
-      if (!("_requestID" in this))
-        this._webNav.loadURI("about:blank",
-                             Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
-                             null, null, null);
-    }, Ci.nsIEventTarget.DISPATCH_NORMAL);
+  // We load about:blank to finish all captures, even canceled captures.  Two
+  // reasons: GC the captured page, and ensure it can't possibly load any more
+  // resources.
+  _loadAboutBlank: function _loadAboutBlank() {
+    this._webNav.loadURI("about:blank",
+                         Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
+                         null, null, null);
   },
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIWebProgressListener,
     Ci.nsISupportsWeakReference,
     Ci.nsIObserver,
   ]),
 };