Bug 705911 - [Page Thumbnails] Save screenshots of redirecting sites; r=dao a=akeybl
authorTim Taubert <tim.taubert@gmx.de>
Fri, 23 Mar 2012 19:12:43 +0100
changeset 90611 85b2776948f6833b7ed40dfdef0ecbb884605edb
parent 90610 c6f1210231c1ffbde39bd822156ea6183edb4c16
child 90612 e09f1ba7b42e115d5fd8b99fbf17ae00217514a6
push id1032
push usertim.taubert@gmx.de
push dateThu, 29 Mar 2012 09:27:55 +0000
treeherdermozilla-aurora@85b2776948f6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao, akeybl
bugs705911
milestone13.0a2
Bug 705911 - [Page Thumbnails] Save screenshots of redirecting sites; r=dao a=akeybl
browser/components/thumbnails/PageThumbs.jsm
browser/components/thumbnails/test/Makefile.in
browser/components/thumbnails/test/background_red.html
browser/components/thumbnails/test/background_red_redirect.sjs
browser/components/thumbnails/test/browser_thumbnails_redirect.js
browser/components/thumbnails/test/head.js
--- a/browser/components/thumbnails/PageThumbs.jsm
+++ b/browser/components/thumbnails/PageThumbs.jsm
@@ -105,23 +105,40 @@ let PageThumbs = {
 
   /**
    * Captures a thumbnail for the given browser and stores it to the cache.
    * @param aBrowser The browser to capture a thumbnail for.
    * @param aCallback The function to be called when finished (optional).
    */
   captureAndStore: function PageThumbs_captureAndStore(aBrowser, aCallback) {
     let url = aBrowser.currentURI.spec;
+    let channel = aBrowser.docShell.currentDocumentChannel;
+    let originalURL = channel.originalURI.spec;
+
     this.capture(aBrowser.contentWindow, function (aInputStream) {
       let telemetryStoreTime = new Date();
 
       function finish(aSuccessful) {
         if (aSuccessful) {
           Services.telemetry.getHistogramById("FX_THUMBNAILS_STORE_TIME_MS")
             .add(new Date() - telemetryStoreTime);
+
+          // We've been redirected. Create a copy of the current thumbnail for
+          // the redirect source. We need to do this because:
+          //
+          // 1) Users can drag any kind of links onto the newtab page. If those
+          //    links redirect to a different URL then we want to be able to
+          //    provide thumbnails for both of them.
+          //
+          // 2) The newtab page should actually display redirect targets, only.
+          //    Because of bug 559175 this information can get lost when using
+          //    Sync and therefore also redirect sources appear on the newtab
+          //    page. We also want thumbnails for those.
+          if (url != originalURL)
+            PageThumbsCache._copy(url, originalURL);
         }
 
         if (aCallback)
           aCallback(aSuccessful);
       }
 
       // Get a writeable cache entry.
       PageThumbsCache.getWriteEntry(url, function (aEntry) {
@@ -202,16 +219,64 @@ let PageThumbsCache = {
    * @param aCallback The callback that is called when the cache entry is ready.
    */
   getWriteEntry: function Cache_getWriteEntry(aKey, aCallback) {
     // Try to open the desired cache entry.
     this._openCacheEntry(aKey, Ci.nsICache.ACCESS_WRITE, aCallback);
   },
 
   /**
+   * Copies an existing cache entry's data to a new cache entry.
+   * @param aSourceKey The key that contains the data to copy.
+   * @param aTargetKey The key that will be the copy of aSourceKey's data.
+   */
+  _copy: function Cache_copy(aSourceKey, aTargetKey) {
+    let sourceEntry, targetEntry, waitingCount = 2;
+
+    function finish() {
+      if (sourceEntry)
+        sourceEntry.close();
+
+      if (targetEntry)
+        targetEntry.close();
+    }
+
+    function copyDataWhenReady() {
+      if (--waitingCount > 0)
+        return;
+
+      if (!sourceEntry || !targetEntry) {
+        finish();
+        return;
+      }
+
+      let inputStream = sourceEntry.openInputStream(0);
+      let outputStream = targetEntry.openOutputStream(0);
+
+      // Copy the image data to a new entry.
+      NetUtil.asyncCopy(inputStream, outputStream, function (aResult) {
+        if (Components.isSuccessCode(aResult))
+          targetEntry.markValid();
+
+        finish();
+      });
+    }
+
+    this.getReadEntry(aSourceKey, function (aSourceEntry) {
+      sourceEntry = aSourceEntry;
+      copyDataWhenReady();
+    });
+
+    this.getWriteEntry(aTargetKey, function (aTargetEntry) {
+      targetEntry = aTargetEntry;
+      copyDataWhenReady();
+    });
+  },
+
+  /**
    * Opens the cache entry identified by the given key.
    * @param aKey The key identifying the desired cache entry.
    * @param aAccess The desired access mode (see nsICache.ACCESS_* constants).
    * @param aCallback The function to be called when the cache entry was opened.
    */
   _openCacheEntry: function Cache_openCacheEntry(aKey, aAccess, aCallback) {
     function onCacheEntryAvailable(aEntry, aAccessGranted, aStatus) {
       let validAccess = aAccess == aAccessGranted;
--- a/browser/components/thumbnails/test/Makefile.in
+++ b/browser/components/thumbnails/test/Makefile.in
@@ -8,14 +8,17 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = browser/components/thumbnails/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
 	browser_thumbnails_capture.js \
+	browser_thumbnails_redirect.js \
 	browser_thumbnails_bug726727.js \
 	head.js \
+	background_red.html \
+	background_red_redirect.sjs \
 	$(NULL)
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/components/thumbnails/test/background_red.html
@@ -0,0 +1,3 @@
+<html>
+  <body bgcolor=ff0000></body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/thumbnails/test/background_red_redirect.sjs
@@ -0,0 +1,10 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(aRequest, aResponse) {
+ // Set HTTP Status.
+ aResponse.setStatusLine(aRequest.httpVersion, 301, "Moved Permanently");
+
+ // Set redirect URI.
+ aResponse.setHeader("Location", "http://mochi.test:8888/browser/browser/components/thumbnails/test/background_red.html");
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/thumbnails/test/browser_thumbnails_redirect.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL = "http://mochi.test:8888/browser/browser/components/thumbnails/" +
+            "test/background_red_redirect.sjs";
+
+let cacheService = Cc["@mozilla.org/network/cache-service;1"]
+                   .getService(Ci.nsICacheService);
+
+/**
+ * These tests ensure that we save and provide thumbnails for redirecting sites.
+ */
+function runTests() {
+  // Kick off history by loading a tab first or the test fails in single mode.
+  yield addTab(URL);
+  gBrowser.removeTab(gBrowser.selectedTab);
+
+  // Create a tab, redirecting to a page with a red background.
+  yield addTab(URL);
+  yield captureAndCheckColor(255, 0, 0, "we have a red thumbnail");
+
+  // Wait until the referrer's thumbnail's cache entry has been written.
+  yield whenCacheEntryExists(URL);
+  yield checkThumbnailColor(URL, 255, 0, 0, "referrer has a red thumbnail");
+}
+
+function whenCacheEntryExists(aKey) {
+  let callback = next;
+
+  checkCacheEntryExists(aKey, function (aExists) {
+    if (!aExists)
+      callback = function () whenCacheEntryExists(aKey);
+
+    executeSoon(callback);
+  });
+}
+
+function checkCacheEntryExists(aKey, aCallback) {
+  PageThumbsCache.getReadEntry(aKey, function (aEntry) {
+    let inputStream = aEntry && aEntry.openInputStream(0);
+    let exists = inputStream && inputStream.available();
+
+    if (inputStream)
+      inputStream.close();
+
+    if (aEntry)
+      aEntry.close();
+
+    aCallback(exists);
+  });
+}
--- a/browser/components/thumbnails/test/head.js
+++ b/browser/components/thumbnails/test/head.js
@@ -93,36 +93,48 @@ function whenLoaded(aElement, aCallback)
  * @param aBlue The blue component's intensity.
  * @param aMessage The info message to print when comparing the pixel color.
  */
 function captureAndCheckColor(aRed, aGreen, aBlue, aMessage) {
   let browser = gBrowser.selectedBrowser;
 
   // Capture the screenshot.
   PageThumbs.captureAndStore(browser, function () {
-    let width = 100, height = 100;
-    let thumb = PageThumbs.getThumbnailURL(browser.currentURI.spec, width, height);
+    checkThumbnailColor(browser.currentURI.spec, aRed, aGreen, aBlue, aMessage);
+  });
+}
 
-    getXULDocument(function (aDocument) {
-      let htmlns = "http://www.w3.org/1999/xhtml";
-      let img = aDocument.createElementNS(htmlns, "img");
-      img.setAttribute("src", thumb);
+/**
+ * Retrieve a thumbnail from the cache and compare its pixel color values.
+ * @param aURL The URL of the thumbnail's page.
+ * @param aRed The red component's intensity.
+ * @param aGreen The green component's intensity.
+ * @param aBlue The blue component's intensity.
+ * @param aMessage The info message to print when comparing the pixel color.
+ */
+function checkThumbnailColor(aURL, aRed, aGreen, aBlue, aMessage) {
+  let width = 100, height = 100;
+  let thumb = PageThumbs.getThumbnailURL(aURL, width, height);
 
-      whenLoaded(img, function () {
-        let canvas = aDocument.createElementNS(htmlns, "canvas");
-        canvas.setAttribute("width", width);
-        canvas.setAttribute("height", height);
+  getXULDocument(function (aDocument) {
+    let htmlns = "http://www.w3.org/1999/xhtml";
+    let img = aDocument.createElementNS(htmlns, "img");
+    img.setAttribute("src", thumb);
 
-        // Draw the image to a canvas and compare the pixel color values.
-        let ctx = canvas.getContext("2d");
-        ctx.drawImage(img, 0, 0, width, height);
-        checkCanvasColor(ctx, aRed, aGreen, aBlue, aMessage);
+    whenLoaded(img, function () {
+      let canvas = aDocument.createElementNS(htmlns, "canvas");
+      canvas.setAttribute("width", width);
+      canvas.setAttribute("height", height);
 
-        next();
-      });
+      // Draw the image to a canvas and compare the pixel color values.
+      let ctx = canvas.getContext("2d");
+      ctx.drawImage(img, 0, 0, width, height);
+      checkCanvasColor(ctx, aRed, aGreen, aBlue, aMessage);
+
+      next();
     });
   });
 }
 
 /**
  * Passes a XUL document (created if necessary) to the given callback.
  * @param aCallback The function to be called when the XUL document has been
  *                  created. The first argument will be the document.