Bug 497543 - Part 1 - JavaScript Module; r=dietrich
authorTim Taubert <tim.taubert@gmx.de>
Thu, 19 Jan 2012 16:01:42 +0100
changeset 86601 066e52b8906b96ba662317bece0056c248c7b7cb
parent 86600 85fbc84de81850ee41b12d28288bd78cbebfe524
child 86602 fecd16860a0264b69fefd934951038b254d60de9
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdietrich
bugs497543
milestone12.0a1
Bug 497543 - Part 1 - JavaScript Module; r=dietrich
browser/components/thumbnails/PageThumbs.jsm
toolkit/content/Services.jsm
new file mode 100644
--- /dev/null
+++ b/browser/components/thumbnails/PageThumbs.jsm
@@ -0,0 +1,266 @@
+/* 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/. */
+
+"use strict";
+
+let EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsCache"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
+
+/**
+ * The default width for page thumbnails.
+ *
+ * Hint: This is the default value because the 'New Tab Page' is the only
+ *       client for now.
+ */
+const THUMBNAIL_WIDTH = 201;
+
+/**
+ * The default height for page thumbnails.
+ *
+ * Hint: This is the default value because the 'New Tab Page' is the only
+ *       client for now.
+ */
+const THUMBNAIL_HEIGHT = 127;
+
+/**
+ * The default background color for page thumbnails.
+ */
+const THUMBNAIL_BG_COLOR = "#fff";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+  "resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+  "resource://gre/modules/Services.jsm");
+
+/**
+ * Singleton providing functionality for capturing web page thumbnails and for
+ * accessing them if already cached.
+ */
+let PageThumbs = {
+  /**
+   * The scheme to use for thumbnail urls.
+   */
+  get scheme() "moz-page-thumb",
+
+  /**
+   * The static host to use for thumbnail urls.
+   */
+  get staticHost() "thumbnail",
+
+  /**
+   * The thumbnails' image type.
+   */
+  get contentType() "image/png",
+
+  /**
+   * Gets the thumbnail image's url for a given web page's url.
+   * @param aUrl The web page's url that is depicted in the thumbnail.
+   * @return The thumbnail image's url.
+   */
+  getThumbnailURL: function PageThumbs_getThumbnailURL(aUrl) {
+    return this.scheme + "://" + this.staticHost +
+           "?url=" + encodeURIComponent(aUrl);
+  },
+
+  /**
+   * Creates a canvas containing a thumbnail depicting the given window.
+   * @param aWindow The DOM window to capture a thumbnail from.
+   * @return The newly created canvas containing the image data.
+   */
+  capture: function PageThumbs_capture(aWindow) {
+    let [sx, sy, sw, sh, scale] = this._determineCropRectangle(aWindow);
+
+    let canvas = this._createCanvas();
+    let ctx = canvas.getContext("2d");
+
+    // Scale the canvas accordingly.
+    ctx.scale(scale, scale);
+
+    try {
+      // Draw the window contents to the canvas.
+      ctx.drawWindow(aWindow, sx, sy, sw, sh, THUMBNAIL_BG_COLOR,
+                     ctx.DRAWWINDOW_DO_NOT_FLUSH);
+    } catch (e) {
+      // We couldn't draw to the canvas for some reason.
+    }
+
+    return canvas;
+  },
+
+  /**
+   * Stores the image data contained in the given canvas to the underlying
+   * storage.
+   * @param aKey The key to use for the storage.
+   * @param aCanvas The canvas containing the thumbnail's image data.
+   * @param aCallback The function to be called when the canvas data has been
+   *                  stored (optional).
+   */
+  store: function PageThumbs_store(aKey, aCanvas, aCallback) {
+    let self = this;
+
+    function finish(aSuccessful) {
+      if (aCallback)
+        aCallback(aSuccessful);
+    }
+
+    // Get a writeable cache entry.
+    PageThumbsCache.getWriteEntry(aKey, function (aEntry) {
+      if (!aEntry) {
+        finish(false);
+        return;
+      }
+
+      // Extract image data from the canvas.
+      self._readImageData(aCanvas, function (aData) {
+        let outputStream = aEntry.openOutputStream(0);
+
+        // Write the image data to the cache entry.
+        NetUtil.asyncCopy(aData, outputStream, function (aResult) {
+          let success = Components.isSuccessCode(aResult);
+          if (success)
+            aEntry.markValid();
+
+          aEntry.close();
+          finish(success);
+        });
+      });
+    });
+  },
+
+  /**
+   * Reads the image data from a given canvas and passes it to the callback.
+   * @param aCanvas The canvas to read the image data from.
+   * @param aCallback The function that the image data is passed to.
+   */
+  _readImageData: function PageThumbs_readImageData(aCanvas, aCallback) {
+    let dataUri = aCanvas.toDataURL(PageThumbs.contentType, "");
+    let uri = Services.io.newURI(dataUri, "UTF8", null);
+
+    NetUtil.asyncFetch(uri, function (aData, aResult) {
+      if (Components.isSuccessCode(aResult) && aData && aData.available())
+        aCallback(aData);
+    });
+  },
+
+  /**
+   * Determines the crop rectangle for a given content window.
+   * @param aWindow The content window.
+   * @return An array containing x, y, width, heigh and the scale of the crop
+   *         rectangle.
+   */
+  _determineCropRectangle: function PageThumbs_determineCropRectangle(aWindow) {
+    let sx = 0;
+    let sy = 0;
+    let sw = aWindow.innerWidth;
+    let sh = aWindow.innerHeight;
+
+    let scale = Math.max(THUMBNAIL_WIDTH / sw, THUMBNAIL_HEIGHT / sh);
+    let scaledWidth = sw * scale;
+    let scaledHeight = sh * scale;
+
+    if (scaledHeight > THUMBNAIL_HEIGHT) {
+      sy = Math.floor(Math.abs((scaledHeight - THUMBNAIL_HEIGHT) / 2) / scale);
+      sh -= 2 * sy;
+    }
+
+    if (scaledWidth > THUMBNAIL_WIDTH) {
+      sx = Math.floor(Math.abs((scaledWidth - THUMBNAIL_WIDTH) / 2) / scale);
+      sw -= 2 * sx;
+    }
+
+    return [sx, sy, sw, sh, scale];
+  },
+
+  /**
+   * Creates a new hidden canvas element.
+   * @return The newly created canvas.
+   */
+  _createCanvas: function PageThumbs_createCanvas() {
+    let doc = Services.appShell.hiddenDOMWindow.document;
+    let canvas = doc.createElementNS(HTML_NAMESPACE, "canvas");
+    canvas.mozOpaque = true;
+    canvas.mozImageSmoothingEnabled = true;
+    canvas.width = THUMBNAIL_WIDTH;
+    canvas.height = THUMBNAIL_HEIGHT;
+    return canvas;
+  }
+};
+
+/**
+ * A singleton handling the storage of page thumbnails.
+ */
+let PageThumbsCache = {
+  /**
+   * Calls the given callback with a cache entry opened for reading.
+   * @param aKey The key identifying the desired cache entry.
+   * @param aCallback The callback that is called when the cache entry is ready.
+   */
+  getReadEntry: function Cache_getReadEntry(aKey, aCallback) {
+    // Try to open the desired cache entry.
+    this._openCacheEntry(aKey, Ci.nsICache.ACCESS_READ, aCallback);
+  },
+
+  /**
+   * Calls the given callback with a cache entry opened for writing.
+   * @param aKey The key identifying the desired cache entry.
+   * @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);
+  },
+
+  /**
+   * 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;
+      let validStatus = Components.isSuccessCode(aStatus);
+
+      // Check if a valid entry was passed and if the
+      // access we requested was actually granted.
+      if (aEntry && !(validAccess && validStatus)) {
+        aEntry.close();
+        aEntry = null;
+      }
+
+      aCallback(aEntry);
+    }
+
+    let listener = this._createCacheListener(onCacheEntryAvailable);
+    this._cacheSession.asyncOpenCacheEntry(aKey, aAccess, listener);
+  },
+
+  /**
+   * Returns a cache listener implementing the nsICacheListener interface.
+   * @param aCallback The callback to be called when the cache entry is available.
+   * @return The new cache listener.
+   */
+  _createCacheListener: function Cache_createCacheListener(aCallback) {
+    return {
+      onCacheEntryAvailable: aCallback,
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsICacheListener])
+    };
+  }
+};
+
+/**
+ * Define a lazy getter for the cache session.
+ */
+XPCOMUtils.defineLazyGetter(PageThumbsCache, "_cacheSession", function () {
+  return Services.cache.createSession(PageThumbs.scheme,
+                                     Ci.nsICache.STORE_ON_DISK, true);
+});
--- a/toolkit/content/Services.jsm
+++ b/toolkit/content/Services.jsm
@@ -58,16 +58,18 @@ XPCOMUtils.defineLazyGetter(Services, "a
 
 XPCOMUtils.defineLazyGetter(Services, "dirsvc", function () {
   return Cc["@mozilla.org/file/directory_service;1"]
            .getService(Ci.nsIDirectoryService)
            .QueryInterface(Ci.nsIProperties);
 });
 
 let initTable = [
+  ["appShell", "@mozilla.org/appshell/appShellService;1", "nsIAppShellService"],
+  ["cache", "@mozilla.org/network/cache-service;1", "nsICacheService"],
   ["console", "@mozilla.org/consoleservice;1", "nsIConsoleService"],
   ["contentPrefs", "@mozilla.org/content-pref/service;1", "nsIContentPrefService"],
   ["cookies", "@mozilla.org/cookiemanager;1", "nsICookieManager2"],
   ["droppedLinkHandler", "@mozilla.org/content/dropped-link-handler;1", "nsIDroppedLinkHandler"],
   ["eTLD", "@mozilla.org/network/effective-tld-service;1", "nsIEffectiveTLDService"],
   ["io", "@mozilla.org/network/io-service;1", "nsIIOService2"],
   ["locale", "@mozilla.org/intl/nslocaleservice;1", "nsILocaleService"],
   ["logins", "@mozilla.org/login-manager;1", "nsILoginManager"],