Bug 1239119 - PreviewProvider module produces thumbnail data URIs given a url r=ursula
authorOlivier Yiptong <olivier@olivieryiptong.com>
Tue, 08 Mar 2016 14:20:56 -0500
changeset 291404 c8728788af45309519fa8f54fd982f504f06296d
parent 291403 d0704310fc2fc7cc7f0754df7b2468f9af15b91f
child 291405 e17ded471c3ff88d278ac451f42fe62b00fc6509
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersursula
bugs1239119
milestone48.0a1
Bug 1239119 - PreviewProvider module produces thumbnail data URIs given a url r=ursula MozReview-Commit-ID: KD6taVtzJCy
browser/components/newtab/PreviewProvider.jsm
browser/components/newtab/moz.build
browser/components/newtab/tests/browser/blue_page.html
browser/components/newtab/tests/browser/browser.ini
browser/components/newtab/tests/browser/browser_PreviewProvider.js
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/PreviewProvider.jsm
@@ -0,0 +1,49 @@
+/* global XPCOMUtils, BackgroundPageThumbs, FileUtils, PageThumbsStorage, Task, MIMEService */
+/* exported PreviewProvider */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["PreviewProvider"];
+
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+const {OS} = Cu.import("resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BackgroundPageThumbs",
+  "resource://gre/modules/BackgroundPageThumbs.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "MIMEService",
+  "@mozilla.org/mime;1", "nsIMIMEService");
+
+let PreviewProvider = {
+  /**
+   * Returns a thumbnail as a data URI for a url, creating it if necessary
+   *
+   * @param {String} url
+   *        a url to obtain a thumbnail for
+   * @return {Promise} A Promise that resolves with a base64 encoded thumbnail
+   */
+  getThumbnail: Task.async(function* PreviewProvider_getThumbnail(url) {
+    try {
+      yield BackgroundPageThumbs.captureIfMissing(url);
+      let imgPath = PageThumbsStorage.getFilePathForURL(url);
+
+      // OS.File object used to easily read off-thread
+      let file = yield OS.File.open(imgPath, {read: true, existing: true});
+
+      // nsIFile object needed for MIMEService
+      let nsFile = FileUtils.File(imgPath);
+
+      let contentType = MIMEService.getTypeFromFile(nsFile);
+      let bytes = yield file.read();
+      let encodedData = btoa(String.fromCharCode.apply(null, bytes));
+      file.close();
+      return `data:${contentType};base64,${encodedData}`;
+    } catch (err) {
+      Cu.reportError(`PreviewProvider_getThumbnail error: ${err}`);
+      throw err;
+    }
+  })
+};
--- a/browser/components/newtab/moz.build
+++ b/browser/components/newtab/moz.build
@@ -11,17 +11,18 @@ XPCSHELL_TESTS_MANIFESTS += [
 ]
 
 EXTRA_JS_MODULES += [
     'NewTabMessages.jsm',
     'NewTabPrefsProvider.jsm',
     'NewTabRemoteResources.jsm',
     'NewTabURL.jsm',
     'NewTabWebChannel.jsm',
-    'PlacesProvider.jsm'
+    'PlacesProvider.jsm',
+    'PreviewProvider.jsm'
 ]
 
 XPIDL_SOURCES += [
     'nsIAboutNewTabService.idl',
 ]
 
 XPIDL_MODULE = 'browser-newtab'
 
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/tests/browser/blue_page.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+  <meta charset="utf-8">
+</head>
+<body style="background-color: blue">
+</body>
+</html>
--- a/browser/components/newtab/tests/browser/browser.ini
+++ b/browser/components/newtab/tests/browser/browser.ini
@@ -1,10 +1,12 @@
 [DEFAULT]
 support-files =
   dummy_page.html
   newtabwebchannel_basic.html
   newtabmessages_prefs.html
+  blue_page.html
 
+[browser_PreviewProvider.js]
 [browser_remotenewtab_pageloads.js]
 [browser_newtab_overrides.js]
 [browser_newtabmessages.js]
 [browser_newtabwebchannel.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/tests/browser/browser_PreviewProvider.js
@@ -0,0 +1,90 @@
+/* globals XPCOMUtils, Services, PreviewProvider, registerCleanupFunction */
+"use strict";
+
+let Cu = Components.utils;
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PreviewProvider",
+                                  "resource:///modules/PreviewProvider.jsm");
+
+var oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
+Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
+
+registerCleanupFunction(function() {
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeTab(gBrowser.tabs[1]);
+  }
+  Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref);
+});
+
+const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/blue_page.html";
+
+function pixelsForDataURI(dataURI, options) {
+  return new Promise(resolve => {
+    if (!options) {
+      options = {};
+    }
+    let {width, height} = options;
+    if (!width) {
+      width = 100;
+    }
+    if (!height) {
+      height = 100;
+    }
+
+    let htmlns = "http://www.w3.org/1999/xhtml";
+    let img = document.createElementNS(htmlns, "img");
+    img.setAttribute("src", dataURI);
+
+    img.addEventListener("load", function onLoad() {
+      img.removeEventListener("load", onLoad, true);
+      let canvas = document.createElementNS(htmlns, "canvas");
+      canvas.setAttribute("width", width);
+      canvas.setAttribute("height", height);
+      let ctx = canvas.getContext("2d");
+      ctx.drawImage(img, 0, 0, width, height);
+      let result = ctx.getImageData(0, 0, width, height).data;
+      resolve(result);
+    });
+  });
+}
+
+function* chunk_four(listData) {
+  let index = 0;
+  while (index < listData.length) {
+    yield listData.slice(index, index + 5);
+    index += 4;
+  }
+}
+
+add_task(function* open_page() {
+  let dataURI = yield PreviewProvider.getThumbnail(TEST_URL);
+  let pixels = yield pixelsForDataURI(dataURI, {width: 10, height: 10});
+  let rgbCount = {r: 0, g: 0, b: 0, a: 0};
+  for (let [r, g, b, a] of chunk_four(pixels)) {
+    if (r === 255) {
+      rgbCount.r += 1;
+    }
+    if (g === 255) {
+      rgbCount.g += 1;
+    }
+    if (b === 255) {
+      rgbCount.b += 1;
+    }
+    if (a === 255) {
+      rgbCount.a += 1;
+    }
+  }
+  Assert.equal(`${rgbCount.r},${rgbCount.g},${rgbCount.b},${rgbCount.a}`,
+      "0,0,100,100", "there should be 100 blue-only pixels at full opacity");
+});
+
+add_task(function* invalid_url() {
+  try {
+    yield PreviewProvider.getThumbnail("invalid:URL");
+  } catch (err) {
+    Assert.ok(true, "URL Failed");
+  }
+});