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 291234 c8728788af45309519fa8f54fd982f504f06296d
parent 291233 d0704310fc2fc7cc7f0754df7b2468f9af15b91f
child 291235 e17ded471c3ff88d278ac451f42fe62b00fc6509
push id74528
push userolivier@olivieryiptong.com
push dateFri, 01 Apr 2016 17:54:49 +0000
treeherdermozilla-inbound@14ebae70ac37 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersursula
bugs1239119
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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");
+  }
+});