Bug 1373258 - Convert PageThumbsProtocol.js to PageThumbsProtocol.cpp draft
authorUrsula Sarracini
Mon, 05 Jun 2017 10:04:06 -0400
changeset 689576 eaff3b648b35eabbbbf66124b171a83b1f81ee16
parent 687660 ae49d4a5762264ded3aae4006baddc2203b79b94
child 738342 73f1abf072da193ee066b19b1699aedcacb40651
push id87051
push userusarracini@mozilla.com
push dateTue, 31 Oct 2017 18:38:51 +0000
bugs1373258
milestone58.0a1
Bug 1373258 - Convert PageThumbsProtocol.js to PageThumbsProtocol.cpp MozReview-Commit-ID: tdNee2EPdV
browser/base/content/newtab/grid.js
browser/base/content/newtab/sites.js
browser/base/content/test/newtab/browser_newtab_background_captures.js
browser/installer/package-manifest.in
image/imgLoader.cpp
toolkit/components/thumbnails/BrowserPageThumbs.manifest
toolkit/components/thumbnails/PageThumbs.jsm
toolkit/components/thumbnails/PageThumbsComponents.manifest
toolkit/components/thumbnails/PageThumbsProtocol.cpp
toolkit/components/thumbnails/PageThumbsProtocol.h
toolkit/components/thumbnails/PageThumbsProtocol.js
toolkit/components/thumbnails/PageThumbsStorageService.js
toolkit/components/thumbnails/moz.build
toolkit/components/thumbnails/nsIPageThumbsStorageService.idl
toolkit/components/thumbnails/nsPageThumbsModule.cpp
toolkit/components/thumbnails/test/browser_thumbnails_bug818225.js
toolkit/components/thumbnails/test/browser_thumbnails_storage.js
toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js
toolkit/components/thumbnails/test/browser_thumbnails_update.js
toolkit/components/thumbnails/test/head.js
toolkit/components/thumbnails/test/test_thumbnails_interfaces.js
--- a/browser/base/content/newtab/grid.js
+++ b/browser/base/content/newtab/grid.js
@@ -178,18 +178,18 @@ var gGrid = {
     let site = document.createElementNS(HTML_NAMESPACE, "div");
     site.classList.add("newtab-site");
     site.setAttribute("draggable", "true");
 
     // Create the site's inner HTML code.
     site.innerHTML =
       '<a class="newtab-link">' +
       '  <span class="newtab-thumbnail placeholder"/>' +
-      '  <span class="newtab-thumbnail thumbnail"/>' +
-      '  <span class="newtab-thumbnail enhanced-content"/>' +
+      '  <img src="" alt="" class="newtab-thumbnail thumbnail"/>' +
+      '  <img src="" alt="" class="newtab-thumbnail enhanced-content"/>' +
       '  <span class="newtab-title"/>' +
       '</a>' +
       '<input type="button" title="' + newTabString("pin") + '"' +
       '       class="newtab-control newtab-control-pin"/>' +
       '<input type="button" title="' + newTabString("block") + '"' +
       '       class="newtab-control newtab-control-block"/>';
 
     this._siteFragment = document.createDocumentFragment();
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -181,17 +181,17 @@ Site.prototype = {
     let link = gAllPages.enhanced && DirectoryLinksProvider.getEnhancedLink(this.link) ||
                this.link;
 
     let thumbnail = this._querySelector(".newtab-thumbnail.thumbnail");
     if (link.bgColor) {
       thumbnail.style.backgroundColor = link.bgColor;
     }
     let uri = link.imageURI || PageThumbs.getThumbnailURL(this.url);
-    thumbnail.style.backgroundImage = 'url("' + uri + '")';
+    thumbnail.src = uri;
 
     if (THUMBNAIL_PLACEHOLDER_ENABLED &&
         link.type == "history" &&
         link.baseDomain) {
       let placeholder = this._querySelector(".newtab-thumbnail.placeholder");
       let charCodeSum = 0;
       for (let c of link.baseDomain) {
         charCodeSum += c.charCodeAt(0);
@@ -199,17 +199,17 @@ Site.prototype = {
       const COLORS = 16;
       let hue = Math.round((charCodeSum % COLORS) / COLORS * 360);
       placeholder.style.backgroundColor = "hsl(" + hue + ",80%,40%)";
       placeholder.textContent = link.baseDomain.substr(0,1).toUpperCase();
     }
 
     if (link.enhancedImageURI) {
       let enhanced = this._querySelector(".enhanced-content");
-      enhanced.style.backgroundImage = 'url("' + link.enhancedImageURI + '")';
+      enhanced.src = link.enhancedImageURI;
     }
   },
 
   /**
    * Adds event handlers for the site and its buttons.
    */
   _addEventHandlers: function Site_addEventHandlers() {
     // Register drag-and-drop event handlers.
--- a/browser/base/content/test/newtab/browser_newtab_background_captures.js
+++ b/browser/base/content/test/newtab/browser_newtab_background_captures.js
@@ -3,26 +3,27 @@
 
 /**
  * Verifies that hidden, pre-loaded newtabs don't allow background captures, and
  * when unhidden, do allow background captures.
  */
 
 const CAPTURE_PREF = "browser.pagethumbnails.capturing_disabled";
 
+XPCOMUtils.defineLazyServiceGetter(this, "PageThumbsStorageService",
+  "@mozilla.org/thumbnails/pagethumbs-service;1",
+  "nsIPageThumbsStorageService");
+
 add_task(async function() {
-  let imports = {};
-  Cu.import("resource://gre/modules/PageThumbs.jsm", imports);
-
   // Disable captures.
   await pushPrefs([CAPTURE_PREF, false]);
 
   // Make sure the thumbnail doesn't exist yet.
   let url = "http://example.com/";
-  let path = imports.PageThumbsStorage.getFilePathForURL(url);
+  let path = PageThumbsStorageService.getFilePathForURL(url);
   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   file.initWithPath(path);
   try {
     file.remove(false);
   } catch (err) {}
 
   // Add a top site.
   await setLinks("-1");
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -381,17 +381,18 @@
 @RESPATH@/browser/components/aboutdevtools.manifest
 @RESPATH@/browser/components/Experiments.manifest
 @RESPATH@/browser/components/ExperimentsService.js
 @RESPATH@/browser/components/browser-newtab.xpt
 @RESPATH@/browser/components/aboutNewTabService.js
 @RESPATH@/browser/components/NewTabComponents.manifest
 @RESPATH@/components/Downloads.manifest
 @RESPATH@/components/DownloadLegacy.js
-@RESPATH@/components/BrowserPageThumbs.manifest
+@RESPATH@/components/thumbnails.xpt
+@RESPATH@/components/PageThumbsComponents.manifest
 @RESPATH@/components/crashmonitor.manifest
 @RESPATH@/components/nsCrashMonitor.js
 @RESPATH@/components/toolkitsearch.manifest
 @RESPATH@/components/nsSearchService.js
 @RESPATH@/components/nsSearchSuggestions.js
 @RESPATH@/components/nsSidebar.js
 #ifdef NIGHTLY_BUILD
 @RESPATH@/components/payments.manifest
@@ -451,17 +452,17 @@
 @RESPATH@/components/toolkitplaces.manifest
 @RESPATH@/components/nsLivemarkService.js
 @RESPATH@/components/nsTaggingService.js
 @RESPATH@/components/UnifiedComplete.js
 @RESPATH@/components/nsPlacesExpiration.js
 @RESPATH@/components/PageIconProtocolHandler.js
 @RESPATH@/components/PlacesCategoriesStarter.js
 @RESPATH@/components/ColorAnalyzer.js
-@RESPATH@/components/PageThumbsProtocol.js
+@RESPATH@/components/PageThumbsStorageService.js
 @RESPATH@/components/mozProtocolHandler.js
 @RESPATH@/components/mozProtocolHandler.manifest
 @RESPATH@/components/nsDefaultCLH.manifest
 @RESPATH@/components/nsDefaultCLH.js
 @RESPATH@/components/ContentPrefService2.manifest
 @RESPATH@/components/ContentPrefService2.js
 @RESPATH@/components/nsContentDispatchChooser.manifest
 @RESPATH@/components/nsContentDispatchChooser.js
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -2191,16 +2191,24 @@ imgLoader::LoadImage(nsIURI* aURI,
     isPrivate = nsContentUtils::IsInPrivateBrowsing(aLoadGroup);
   }
   MOZ_ASSERT(isPrivate == mRespectPrivacy);
 #endif
 
   // Get the default load flags from the loadgroup (if possible)...
   if (aLoadGroup) {
     aLoadGroup->GetLoadFlags(&requestFlags);
+
+    // If we are trying to load a thumbnail via the moz-page-thumb:// protocol, load
+    // it directly from the cache to prevent re-decoding the image. See Bug 1373258
+    // TODO: Bug 1406134
+    bool isThumbnailScheme = false;
+    if (NS_SUCCEEDED(aURI->SchemeIs("moz-page-thumb", &isThumbnailScheme)) && isThumbnailScheme) {
+      requestFlags |= nsIRequest::LOAD_FROM_CACHE;
+    }
   }
   //
   // Merge the default load flags with those passed in via aLoadFlags.
   // Currently, *only* the caching, validation and background load flags
   // are merged...
   //
   // The flags in aLoadFlags take precedence over the default flags!
   //
@@ -2468,16 +2476,24 @@ imgLoader::LoadImageWithChannel(nsIChann
 
   nsresult rv;
   ImageCacheKey key(uri, attrs, doc, rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
   channel->GetLoadFlags(&requestFlags);
 
+  // If we are trying to load a thumbnail via the moz-page-thumb:// protocol, load
+  // it directly from the cache to prevent re-decoding the image. See Bug 1373258
+  // TODO: Bug 1406134
+  bool isThumbnailScheme = false;
+  if (NS_SUCCEEDED(uri->SchemeIs("moz-page-thumb", &isThumbnailScheme)) && isThumbnailScheme) {
+    requestFlags |= nsIRequest::LOAD_FROM_CACHE;
+  }
+
   RefPtr<imgCacheEntry> entry;
 
   if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
     RemoveFromCache(key);
   } else {
     // Look in the cache for our URI, and then validate it.
     // XXX For now ignore aCacheKey. We will need it in the future
     // for correctly dealing with image load requests that are a result
deleted file mode 100644
--- a/toolkit/components/thumbnails/BrowserPageThumbs.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-component {5a4ae9b5-f475-48ae-9dce-0b4c1d347884} PageThumbsProtocol.js
-contract @mozilla.org/network/protocol;1?name=moz-page-thumb {5a4ae9b5-f475-48ae-9dce-0b4c1d347884}
--- a/toolkit/components/thumbnails/PageThumbs.jsm
+++ b/toolkit/components/thumbnails/PageThumbs.jsm
@@ -43,26 +43,18 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
   PageThumbUtils: "resource://gre/modules/PageThumbUtils.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "gUpdateTimerManager",
   "@mozilla.org/updates/timer-manager;1", "nsIUpdateTimerManager");
 
-XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function() {
-  return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
-});
-
-XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function() {
-  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
-                    .createInstance(Ci.nsIScriptableUnicodeConverter);
-  converter.charset = "utf8";
-  return converter;
-});
+XPCOMUtils.defineLazyServiceGetter(this, "PageThumbsStorageService",
+  "@mozilla.org/thumbnails/pagethumbs-service;1", "nsIPageThumbsStorageService");
 
 /**
  * Utilities for dealing with promises and Task.jsm
  */
 const TaskUtils = {
   /**
    * Read the bytes from a blob, asynchronously.
    *
@@ -154,17 +146,17 @@ this.PageThumbs = {
     * Gets the path of the thumbnail file for a given web page's
     * url. This file may or may not exist depending on whether the
     * thumbnail has been captured or not.
     *
     * @param aUrl The web page's url.
     * @return The path of the thumbnail file.
     */
    getThumbnailPath: function PageThumbs_getThumbnailPath(aUrl) {
-     return PageThumbsStorage.getFilePathForURL(aUrl);
+     return PageThumbsStorageService.getFilePathForURL(aUrl);
    },
 
   /**
    * Asynchronously returns a thumbnail as a blob for the given
    * window.
    *
    * @param aBrowser The <browser> to capture a thumbnail from.
    * @return {Promise}
@@ -502,49 +494,29 @@ this.PageThumbs = {
       return !Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
     } catch (e) {
       return true;
     }
   },
 };
 
 this.PageThumbsStorage = {
-  // The path for the storage
-  _path: null,
-  get path() {
-    if (!this._path) {
-      this._path = OS.Path.join(OS.Constants.Path.localProfileDir, THUMBNAIL_DIRECTORY);
-    }
-    return this._path;
-  },
 
   ensurePath: function Storage_ensurePath() {
     // Create the directory (ignore any error if the directory
     // already exists). As all writes are done from the PageThumbsWorker
     // thread, which serializes its operations, this ensures that
     // future operations can proceed without having to check whether
     // the directory exists.
     return PageThumbsWorker.post("makeDir",
-      [this.path, {ignoreExisting: true}]).catch(function onError(aReason) {
+      [PageThumbsStorageService.path, {ignoreExisting: true}]).catch(function onError(aReason) {
           Components.utils.reportError("Could not create thumbnails directory" + aReason);
         });
   },
 
-  getLeafNameForURL: function Storage_getLeafNameForURL(aURL) {
-    if (typeof aURL != "string") {
-      throw new TypeError("Expecting a string");
-    }
-    let hash = this._calculateMD5Hash(aURL);
-    return hash + ".png";
-  },
-
-  getFilePathForURL: function Storage_getFilePathForURL(aURL) {
-    return OS.Path.join(this.path, this.getLeafNameForURL(aURL));
-  },
-
   _revisionTable: {},
 
   // Generate an arbitrary revision tag, i.e. one that can't be used to
   // infer URL frecency.
   _updateRevision(aURL) {
     // Initialize with a random value and increment on each update. Wrap around
     // modulo _revisionRange, so that even small values carry no meaning.
     let rev = this._revisionTable[aURL];
@@ -586,17 +558,17 @@ this.PageThumbsStorage = {
    * an ArrayBuffer. This array buffer will be detached and cannot be
    * reused after the copy.
    * @param {boolean} aNoOverwrite If true and the thumbnail's file already
    * exists, the file will not be overwritten.
    *
    * @return {Promise}
    */
   writeData: function Storage_writeData(aURL, aData, aNoOverwrite) {
-    let path = this.getFilePathForURL(aURL);
+    let path = PageThumbsStorageService.getFilePathForURL(aURL);
     this.ensurePath();
     aData = new Uint8Array(aData);
     let msg = [
       path,
       aData,
       {
         tmpPath: path + ".tmp",
         bytes: aData.byteLength,
@@ -617,30 +589,30 @@ this.PageThumbsStorage = {
    * @param {string} aTargetURL The url of the target thumbnail.
    * @param {boolean} aNoOverwrite If true and the target file already exists,
    * the file will not be overwritten.
    *
    * @return {Promise}
    */
   copy: function Storage_copy(aSourceURL, aTargetURL, aNoOverwrite) {
     this.ensurePath();
-    let sourceFile = this.getFilePathForURL(aSourceURL);
-    let targetFile = this.getFilePathForURL(aTargetURL);
+    let sourceFile = PageThumbsStorageService.getFilePathForURL(aSourceURL);
+    let targetFile = PageThumbsStorageService.getFilePathForURL(aTargetURL);
     let options = { noOverwrite: aNoOverwrite };
     return PageThumbsWorker.post("copy", [sourceFile, targetFile, options]).
       then(() => this._updateRevision(aTargetURL), this._eatNoOverwriteError(aNoOverwrite));
   },
 
   /**
    * Remove a single thumbnail, off the main thread.
    *
    * @return {Promise}
    */
   remove: function Storage_remove(aURL) {
-    return PageThumbsWorker.post("remove", [this.getFilePathForURL(aURL)]);
+    return PageThumbsWorker.post("remove", [PageThumbsStorageService.getFilePathForURL(aURL)]);
   },
 
   /**
    * Remove all thumbnails, off the main thread.
    *
    * @return {Promise}
    */
   wipe: async function Storage_wipe() {
@@ -662,57 +634,41 @@ this.PageThumbsStorage = {
     // to clear the thumbnail wipe.
     AsyncShutdown.profileBeforeChange.addBlocker(
       "PageThumbs: removing all thumbnails",
       blocker);
 
     // Start the work only now that `profileBeforeChange` has had
     // a chance to throw an error.
 
-    let promise = PageThumbsWorker.post("wipe", [this.path]);
+    let promise = PageThumbsWorker.post("wipe", [PageThumbsStorageService.path]);
     try {
       await promise;
     } finally {
        // Generally, we will be done much before profileBeforeChange,
        // so let's not hoard blockers.
        if ("removeBlocker" in AsyncShutdown.profileBeforeChange) {
          // `removeBlocker` was added with bug 985655. In the interest
          // of backporting, let's degrade gracefully if `removeBlocker`
          // doesn't exist.
          AsyncShutdown.profileBeforeChange.removeBlocker(blocker);
        }
     }
   },
 
   fileExistsForURL: function Storage_fileExistsForURL(aURL) {
-    return PageThumbsWorker.post("exists", [this.getFilePathForURL(aURL)]);
+    return PageThumbsWorker.post("exists", [PageThumbsStorageService.getFilePathForURL(aURL)]);
   },
 
   isFileRecentForURL: function Storage_isFileRecentForURL(aURL) {
     return PageThumbsWorker.post("isFileRecent",
-                                 [this.getFilePathForURL(aURL),
+                                 [PageThumbsStorageService.getFilePathForURL(aURL),
                                   MAX_THUMBNAIL_AGE_SECS]);
   },
 
-  _calculateMD5Hash: function Storage_calculateMD5Hash(aValue) {
-    let hash = gCryptoHash;
-    let value = gUnicodeConverter.convertToByteArray(aValue);
-
-    hash.init(hash.MD5);
-    hash.update(value, value.length);
-    return this._convertToHexString(hash.finish(false));
-  },
-
-  _convertToHexString: function Storage_convertToHexString(aData) {
-    let hex = "";
-    for (let i = 0; i < aData.length; i++)
-      hex += ("0" + aData.charCodeAt(i).toString(16)).slice(-2);
-    return hex;
-  },
-
   /**
    * For functions that take a noOverwrite option, OS.File throws an error if
    * the target file exists and noOverwrite is true.  We don't consider that an
    * error, and we don't want such errors propagated.
    *
    * @param {aNoOverwrite} The noOverwrite option used in the OS.File operation.
    *
    * @return {function} A function that should be passed as the second argument
@@ -728,17 +684,17 @@ this.PageThumbsStorage = {
     };
   },
 
   // Deprecated, please do not use
   getFileForURL: function Storage_getFileForURL_DEPRECATED(aURL) {
     Deprecated.warning("PageThumbs.getFileForURL is deprecated. Please use PageThumbs.getFilePathForURL and OS.File",
                        "https://developer.mozilla.org/docs/JavaScript_OS.File");
     // Note: Once this method has been removed, we can get rid of the dependency towards FileUtils
-    return new FileUtils.File(PageThumbsStorage.getFilePathForURL(aURL));
+    return new FileUtils.File(PageThumbsStorageService.getFilePathForURL(aURL));
   }
 };
 
 var PageThumbsStorageMigrator = {
   get currentVersion() {
     try {
       return Services.prefs.getIntPref(PREF_STORAGE_VERSION);
     } catch (e) {
@@ -837,19 +793,19 @@ var PageThumbsExpiration = {
       if (typeof filter == "function")
         filter(filterCallback);
       else
         filter.filterForThumbnailExpiration(filterCallback);
     }
   },
 
   expireThumbnails: function Expiration_expireThumbnails(aURLsToKeep) {
-    let keep = aURLsToKeep.map(url => PageThumbsStorage.getLeafNameForURL(url));
+    let keep = aURLsToKeep.map(url => PageThumbsStorageService.getLeafNameForURL(url));
     let msg = [
-      PageThumbsStorage.path,
+      PageThumbsStorageService.path,
       keep,
       EXPIRATION_MIN_CHUNK_SIZE
     ];
 
     return PageThumbsWorker.post(
       "expireFilesInDirectory",
       msg
     );
new file mode 100644
--- /dev/null
+++ b/toolkit/components/thumbnails/PageThumbsComponents.manifest
@@ -0,0 +1,2 @@
+component {97943eec-0e48-49ef-b7b7-cf4aa0109bb6} PageThumbsStorageService.js
+contract @mozilla.org/thumbnails/pagethumbs-service;1 {97943eec-0e48-49ef-b7b7-cf4aa0109bb6}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/thumbnails/PageThumbsProtocol.cpp
@@ -0,0 +1,189 @@
+//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/**
+ * Implementation of moz-page-thumb protocol. This accesses and displays
+ * screenshots for URLs that are stored in the profile directory.
+ */
+
+#include "nsIPageThumbsStorageService.h"
+#include "PageThumbsProtocol.h"
+#include "nsIURI.h"
+#include "nsIFileURL.h"
+#include "nsIFile.h"
+#include "nsIChannel.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNetUtil.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "nsStandardURL.h"
+
+using mozilla::dom::URLParams;
+using mozilla::net::nsStandardURL;
+
+NS_IMPL_ISUPPORTS(PageThumbsProtocol, nsIProtocolHandler);
+
+// PageThumbsProtocol::GetScheme
+
+NS_IMETHODIMP
+PageThumbsProtocol::GetScheme(nsACString& aScheme)
+{
+  aScheme.AssignLiteral("moz-page-thumb");
+  return NS_OK;
+}
+
+// PageThumbsProtocol::GetDefaultPort
+
+NS_IMETHODIMP
+PageThumbsProtocol::GetDefaultPort(int32_t *aDefaultPort)
+{
+  *aDefaultPort = -1;
+  return NS_OK;
+}
+
+// PageThumbsProtocol::GetProtocolFlags
+
+NS_IMETHODIMP
+PageThumbsProtocol::GetProtocolFlags(uint32_t *aProtocolFlags)
+{
+  *aProtocolFlags = (URI_DANGEROUS_TO_LOAD | URI_IS_LOCAL_RESOURCE |
+                     URI_NORELATIVE | URI_NOAUTH);
+  return NS_OK;
+}
+
+// PageThumbsProtocol::NewURI
+
+NS_IMETHODIMP
+PageThumbsProtocol::NewURI(const nsACString& aSpec,
+                           const char *aOriginCharset,
+                           nsIURI *aBaseURI, nsIURI **_retval)
+{
+  nsCOMPtr <nsIURI> uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID);
+  nsresult rv = uri->SetSpec(aSpec);
+  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+  uri.forget(_retval);
+  return rv;
+}
+
+// PageThumbsProtocol::NewChannel
+
+NS_IMETHODIMP
+PageThumbsProtocol::NewChannel2(nsIURI* aURI,
+                                nsILoadInfo *aLoadInfo,
+                                nsIChannel** _retval)
+{
+  // Get the file path for the URL
+  nsCOMPtr <nsIFile> filePath;
+  nsresult rv = GetFilePathForURL(aURI, getter_AddRefs(filePath));
+  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+  // Get a file URI from the local file path
+  nsCOMPtr <nsIURI> fileURI;
+  rv = NS_NewFileURI(getter_AddRefs(fileURI), filePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+  // Create a new channel with the file URI created
+  nsCOMPtr <nsIChannel> channel;
+  nsCOMPtr <nsIIOService> ios = do_GetIOService();
+  rv = ios->NewChannelFromURIWithLoadInfo(fileURI, aLoadInfo, getter_AddRefs(channel));
+  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+  channel->SetOriginalURI(aURI);
+  channel.forget(_retval);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PageThumbsProtocol::NewChannel(nsIURI* aURI, nsIChannel** _retval)
+{
+  return NewChannel2(aURI, nullptr, _retval);
+}
+
+// PageThumbsProtocol::AllowPort
+
+NS_IMETHODIMP
+PageThumbsProtocol::AllowPort(int32_t aPort, const char *aScheme, bool *_retval)
+{
+  *_retval = false;
+  return NS_OK;
+}
+
+// PageThumbsProtocol::ParseProtocolURL
+//
+//    Extracts the URL from the query parameter. The URI is passed in in the form:
+//    'moz-page-thumb://thumbnail/?url=http%3A%2F%2Fwww.mozilla.org%2F'.
+
+nsresult
+PageThumbsProtocol::ParseProtocolURL(nsIURI* aURI, nsString& aParsedURL)
+{
+  nsAutoCString spec;
+  aURI->GetSpec(spec);
+
+  // Check that we have the correct host
+  nsAutoCString host;
+  host = Substring(spec, spec.FindChar(':') + 3, 9);
+  if (!host.EqualsLiteral("thumbnail")) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // Get the path out of the URI
+  nsAutoCString path;
+  nsresult rv = aURI->GetPathQueryRef(path);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+  // Since this is a protocol URI and it doesn't parse nicely, we split on where
+  // the start of the query is and parse it from there
+  int32_t queryBegins = path.FindChar('?');
+  if (queryBegins <= 0) {
+    return NS_ERROR_MALFORMED_URI;
+  }
+
+  URLParams params;
+  params.ParseInput(Substring(path, queryBegins + 1));
+
+  params.Get(NS_LITERAL_STRING("url"), aParsedURL);
+
+  // If there's no URL as part of the query params, there will be no thumbnail
+  if (aParsedURL.IsVoid()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return NS_OK;
+}
+
+// PageThumbsProtocol::GetFilePathForURL
+//
+//    Returns the thumbnail's file path for a given URL
+
+nsresult
+PageThumbsProtocol::GetFilePathForURL(nsIURI* aURI, nsIFile **_retval)
+{
+  nsresult rv;
+
+  // Use PageThumbsStorageService to get the local file path of the screenshot
+  // for the given URL
+  nsAutoString filePathForURL;
+  nsCOMPtr <nsIPageThumbsStorageService> pageThumbsStorage =
+            do_GetService("@mozilla.org/thumbnails/pagethumbs-service;1", &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+  // Parse the protocol URL to extract the thumbnail's URL
+  nsAutoString parsedURL;
+  rv = ParseProtocolURL(aURI, parsedURL);
+  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+  rv = pageThumbsStorage->GetFilePathForURL(parsedURL, filePathForURL);
+  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+  // Find the local file containing the screenshot
+  nsCOMPtr <nsIFile> filePath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+  rv = filePath->InitWithPath(filePathForURL);
+  if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+  filePath.forget(_retval);
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/thumbnails/PageThumbsProtocol.h
@@ -0,0 +1,28 @@
+//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef PageThumbsProtocol_h__
+#define PageThumbsProtocol_h__
+
+#include "nsIProtocolHandler.h"
+#include "nsString.h"
+
+// {5a4ae9b5-f475-48ae-9dce-0b4c1d347884}
+#define PAGETHUMBSPROTOCOL_CID \
+{ 0x5a4ae9b5, 0xf475, 0x48ae, { 0x9d, 0xce, 0x0b, 0x4c, 0x1d, 0x34, 0x78, 0x84 } }
+
+class PageThumbsProtocol final : public nsIProtocolHandler
+{
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIPROTOCOLHANDLER
+
+  private:
+    ~PageThumbsProtocol() {}
+    nsresult GetFilePathForURL(nsIURI* aURI, nsIFile **_retval);
+    nsresult ParseProtocolURL(nsIURI* aURI, nsString& aParsedURL);
+};
+
+#endif /* PageThumbsProtocol_h__ */
deleted file mode 100644
--- a/toolkit/components/thumbnails/PageThumbsProtocol.js
+++ /dev/null
@@ -1,154 +0,0 @@
-/* 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/. */
-
-/**
- * PageThumbsProtocol.js
- *
- * This file implements the moz-page-thumb:// protocol and the corresponding
- * channel delivering cached thumbnails.
- *
- * URL structure:
- *
- * moz-page-thumb://thumbnail/?url=http%3A%2F%2Fwww.mozilla.org%2F&revision=XX
- *
- * This URL requests an image for 'http://www.mozilla.org/'.
- * The value of the revision key may change when the stored thumbnail changes.
- */
-
-"use strict";
-
-const Cu = Components.utils;
-const Cc = Components.classes;
-const Cr = Components.results;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/PageThumbs.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/osfile.jsm", this);
-
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
-  "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
-  "resource://gre/modules/FileUtils.jsm");
-
-const SUBSTITUTING_URL_CID = "{dea9657c-18cf-4984-bde9-ccef5d8ab473}";
-
-/**
- * Implements the thumbnail protocol handler responsible for moz-page-thumb: URLs.
- */
-function Protocol() {
-}
-
-Protocol.prototype = {
-  /**
-   * The scheme used by this protocol.
-   */
-  get scheme() {
-    return PageThumbs.scheme;
-  },
-
-  /**
-   * The default port for this protocol (we don't support ports).
-   */
-  get defaultPort() {
-    return -1;
-  },
-
-  /**
-   * The flags specific to this protocol implementation.
-   */
-  get protocolFlags() {
-    return Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
-           Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
-           Ci.nsIProtocolHandler.URI_NORELATIVE |
-           Ci.nsIProtocolHandler.URI_NOAUTH;
-  },
-
-  /**
-   * Creates a new URI object that is suitable for loading by this protocol.
-   * @param aSpec The URI string in UTF8 encoding.
-   * @param aOriginCharset The charset of the document from which the URI originated.
-   * @return The newly created URI.
-   */
-  newURI: function Proto_newURI(aSpec, aOriginCharset) {
-    let uri = Components.classesByID[SUBSTITUTING_URL_CID].createInstance(Ci.nsIURL);
-    uri.spec = aSpec;
-    return uri;
-  },
-
-  /**
-   * Constructs a new channel from the given URI for this protocol handler.
-   * @param aURI The URI for which to construct a channel.
-   * @param aLoadInfo The Loadinfo which to use on the channel.
-   * @return The newly created channel.
-   */
-  newChannel2: function Proto_newChannel2(aURI, aLoadInfo) {
-    let {file} = aURI.QueryInterface(Ci.nsIFileURL);
-    let fileuri = Services.io.newFileURI(file);
-    let channel = Services.io.newChannelFromURIWithLoadInfo(fileuri, aLoadInfo);
-    channel.originalURI = aURI;
-    return channel;
-  },
-
-  newChannel: function Proto_newChannel(aURI) {
-    return this.newChannel2(aURI, null);
-  },
-
-  /**
-   * Decides whether to allow a blacklisted port.
-   * @return Always false, we'll never allow ports.
-   */
-  allowPort: () => false,
-
-  // nsISubstitutingProtocolHandler methods
-
-  /*
-   * Substituting the scheme and host isn't enough, we also transform the path.
-   * So declare no-op implementations for (get|set|has)Substitution methods and
-   * do all the work in resolveURI.
-   */
-
-  setSubstitution(root, baseURI) {},
-
-  getSubstitution(root) {
-    throw Cr.NS_ERROR_NOT_AVAILABLE;
-  },
-
-  hasSubstitution(root) {
-    return false;
-  },
-
-  resolveURI(resURI) {
-    let {url} = parseURI(resURI);
-    let path = PageThumbsStorage.getFilePathForURL(url);
-    return OS.Path.toFileURI(path);
-  },
-
-  // xpcom machinery
-  classID: Components.ID("{5a4ae9b5-f475-48ae-9dce-0b4c1d347884}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler,
-                                         Ci.nsISubstitutingProtocolHandler])
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Protocol]);
-
-/**
- * Parses a given URI and extracts all parameters relevant to this protocol.
- * @param aURI The URI to parse.
- * @return The parsed parameters.
- */
-function parseURI(aURI) {
-  if (aURI.host != PageThumbs.staticHost)
-    throw Cr.NS_ERROR_NOT_AVAILABLE;
-
-  let {query} = aURI.QueryInterface(Ci.nsIURL);
-  let params = {};
-
-  query.split("&").forEach(function(aParam) {
-    let [key, value] = aParam.split("=").map(decodeURIComponent);
-    params[key.toLowerCase()] = value;
-  });
-
-  return params;
-}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/thumbnails/PageThumbsStorageService.js
@@ -0,0 +1,65 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const THUMBNAIL_DIRECTORY = "thumbnails";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+
+XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function() {
+  return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function() {
+  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                    .createInstance(Ci.nsIScriptableUnicodeConverter);
+  converter.charset = "utf8";
+  return converter;
+});
+function PageThumbsStorageService() {}
+
+PageThumbsStorageService.prototype = {
+    classID: Components.ID("{97943eec-0e48-49ef-b7b7-cf4aa0109bb6}"),
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPageThumbsStorageService]),
+    _xpcom_categories: [{
+      service: true
+    }],
+    // The path for the storage
+    _path: null,
+    get path() {
+      if (!this._path) {
+        this._path = OS.Path.join(OS.Constants.Path.localProfileDir, THUMBNAIL_DIRECTORY);
+      }
+      return this._path;
+    },
+
+    getLeafNameForURL(aURL) {
+      if (typeof aURL != "string") {
+        throw new TypeError("Expecting a string");
+      }
+      let hash = this._calculateMD5Hash(aURL);
+      return hash + ".png";
+    },
+
+    getFilePathForURL(aURL) {
+      return OS.Path.join(this.path, this.getLeafNameForURL(aURL));
+    },
+
+    _calculateMD5Hash(aValue) {
+      let hash = gCryptoHash;
+      let value = gUnicodeConverter.convertToByteArray(aValue);
+
+      hash.init(hash.MD5);
+      hash.update(value, value.length);
+      return this._convertToHexString(hash.finish(false));
+    },
+
+    _convertToHexString(aData) {
+      let hex = "";
+      for (let i = 0; i < aData.length; i++)
+        hex += ("0" + aData.charCodeAt(i).toString(16)).slice(-2);
+      return hex;
+    },
+
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PageThumbsStorageService]);
--- a/toolkit/components/thumbnails/moz.build
+++ b/toolkit/components/thumbnails/moz.build
@@ -6,20 +6,37 @@
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'New Tab Page')
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
 
 EXTRA_COMPONENTS += [
-    'BrowserPageThumbs.manifest',
-    'PageThumbsProtocol.js',
+    'PageThumbsComponents.manifest',
+    'PageThumbsStorageService.js',
 ]
 
 EXTRA_JS_MODULES += [
     'BackgroundPageThumbs.jsm',
     'PageThumbs.jsm',
     'PageThumbsWorker.js',
     'PageThumbUtils.jsm',
 ]
 
+UNIFIED_SOURCES += [
+    'nsPageThumbsModule.cpp',
+    'PageThumbsProtocol.cpp'
+]
+
+XPIDL_SOURCES += [
+    'nsIPageThumbsStorageService.idl',
+]
+
+LOCAL_INCLUDES += [
+    '/netwerk/base'
+]
+
+XPIDL_MODULE = 'thumbnails'
+
+FINAL_LIBRARY = 'xul'
+
 JAR_MANIFESTS += ['jar.mn']
new file mode 100644
--- /dev/null
+++ b/toolkit/components/thumbnails/nsIPageThumbsStorageService.idl
@@ -0,0 +1,30 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * A service which returns information about file paths where the
+ * screenshots for URLs are stored. These screenshots are used by the
+ * moz-page-thumb protocol
+ */
+
+[scriptable, uuid(97943eec-0e48-49ef-b7b7-cf4aa0109bb6)]
+interface nsIPageThumbsStorageService : nsISupports
+{
+  /**
+   * Returns the leaf name of the file containing the screenshot for a given URL
+   */
+  AString getLeafNameForURL(in AString aURL);
+
+  /**
+   * Returns the path where the thumbnails are stored
+   */
+  readonly attribute ACString path;
+
+  /**
+   * Returns the full file path containing the screenshot for a given URL
+   */
+  AString getFilePathForURL(in AString aURL);
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/thumbnails/nsPageThumbsModule.cpp
@@ -0,0 +1,25 @@
+#include "mozilla/ModuleUtils.h"
+#include "nsIClassInfoImpl.h"
+
+#include "PageThumbsProtocol.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(PageThumbsProtocol)
+NS_DEFINE_NAMED_CID(PAGETHUMBSPROTOCOL_CID);
+
+const mozilla::Module::CIDEntry kPageThumbsCIDs[] = {
+    { &kPAGETHUMBSPROTOCOL_CID, false, nullptr, PageThumbsProtocolConstructor },
+    { nullptr }
+};
+
+const mozilla::Module::ContractIDEntry kPageThumbsContracts[] = {
+    { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-page-thumb", &kPAGETHUMBSPROTOCOL_CID },
+    { nullptr }
+};
+
+const mozilla::Module kPageThumbsModule = {
+    mozilla::Module::kVersion,
+    kPageThumbsCIDs,
+    kPageThumbsContracts
+};
+
+NSMODULE_DEFN(nsPageThumbsModule) = &kPageThumbsModule;
--- a/toolkit/components/thumbnails/test/browser_thumbnails_bug818225.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_bug818225.js
@@ -9,17 +9,17 @@ function* runTests() {
 
   let path = PageThumbs.getThumbnailPath(URL);
   yield testIfExists(path, false, "Thumbnail file does not exist");
 
   yield addVisitsAndRepopulateNewTabLinks(URL, next);
   yield createThumbnail(URL);
 
   path = PageThumbs.getThumbnailPath(URL);
-  let expectedPath = PageThumbsStorage.getFilePathForURL(URL);
+  let expectedPath = PageThumbsStorageService.getFilePathForURL(URL);
   is(path, expectedPath, "Thumbnail file has correct path");
 
   yield testIfExists(path, true, "Thumbnail file exists");
 
 }
 
 function createThumbnail(aURL) {
   addTab(aURL, function() {
--- a/toolkit/components/thumbnails/test/browser_thumbnails_storage.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage.js
@@ -22,25 +22,25 @@ function* runTests() {
     dontExpireThumbnailURLs([URL, URL_COPY]);
 
     await promiseClearHistory();
     await promiseAddVisitsAndRepopulateNewTabLinks(URL);
     await promiseCreateThumbnail();
 
     // Make sure Storage.copy() updates an existing file.
     await PageThumbsStorage.copy(URL, URL_COPY);
-    let copy = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY));
+    let copy = new FileUtils.File(PageThumbsStorageService.getFilePathForURL(URL_COPY));
     let mtime = copy.lastModifiedTime -= 60;
 
     await PageThumbsStorage.copy(URL, URL_COPY);
-    isnot(new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY)).lastModifiedTime, mtime,
+    isnot(new FileUtils.File(PageThumbsStorageService.getFilePathForURL(URL_COPY)).lastModifiedTime, mtime,
           "thumbnail file was updated");
 
-    let file = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL));
-    let fileCopy = new FileUtils.File(PageThumbsStorage.getFilePathForURL(URL_COPY));
+    let file = new FileUtils.File(PageThumbsStorageService.getFilePathForURL(URL));
+    let fileCopy = new FileUtils.File(PageThumbsStorageService.getFilePathForURL(URL_COPY));
 
     // Clear the browser history. Retry until the files are gone because Windows
     // locks them sometimes.
     info("Clearing history");
     while (file.exists() || fileCopy.exists()) {
       await promiseClearHistory();
     }
     info("History is clear");
--- a/toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage_migrate3.js
@@ -24,31 +24,31 @@ XPCOMUtils.defineLazyServiceGetter(this,
 function* runTests() {
   // Prepare a local profile directory.
   let localProfile = FileUtils.getDir("ProfD", ["local-test"], true);
   changeLocation("ProfLD", localProfile);
 
   let roaming = FileUtils.getDir("ProfD", [THUMBNAIL_DIRECTORY], true);
 
   // Set up some data in the roaming profile.
-  let name = PageThumbsStorage.getLeafNameForURL(URL);
+  let name = PageThumbsStorageService.getLeafNameForURL(URL);
   let file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
   writeDummyFile(file);
 
-  name = PageThumbsStorage.getLeafNameForURL(URL2);
+  name = PageThumbsStorageService.getLeafNameForURL(URL2);
   file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
   writeDummyFile(file);
 
-  name = PageThumbsStorage.getLeafNameForURL(URL3);
+  name = PageThumbsStorageService.getLeafNameForURL(URL3);
   file = FileUtils.getFile("ProfD", [THUMBNAIL_DIRECTORY, name]);
   writeDummyFile(file);
 
   // Pretend to have one of the thumbnails
   // already in place at the new storage site.
-  name = PageThumbsStorage.getLeafNameForURL(URL3);
+  name = PageThumbsStorageService.getLeafNameForURL(URL3);
   file = FileUtils.getFile("ProfLD", [THUMBNAIL_DIRECTORY, name]);
   writeDummyFile(file, "no-overwrite-plz");
 
   // Kick off thumbnail storage migration.
   PageThumbsStorageMigrator.migrateToVersion3(localProfile.path);
   ok(true, "migration finished");
 
   // Wait until the first thumbnail was moved to its new location.
@@ -66,17 +66,17 @@ function* runTests() {
   is(getFileContents(file), "no-overwrite-plz",
     "existing thumbnail was not overwritten");
 
   // Sanity check: ensure that, until it is removed, deprecated
   // function |getFileForURL| points to the same path as
   // |getFilePathForURL|.
   if ("getFileForURL" in PageThumbsStorage) {
     file = PageThumbsStorage.getFileForURL(URL);
-    is(file.path, PageThumbsStorage.getFilePathForURL(URL),
+    is(file.path, PageThumbsStorageService.getFilePathForURL(URL),
        "Deprecated getFileForURL and getFilePathForURL return the same path");
   }
 }
 
 function changeLocation(aLocation, aNewDir) {
   let oldDir = gDirSvc.get(aLocation, Ci.nsIFile);
   gDirSvc.undefine(aLocation);
   gDirSvc.set(aLocation, aNewDir);
--- a/toolkit/components/thumbnails/test/browser_thumbnails_update.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_update.js
@@ -19,26 +19,26 @@ function* runTests() {
     for (let iterator of test())
       yield iterator;
   }
 }
 
 function ensureThumbnailStale(url) {
   // We go behind the back of the thumbnail service and change the
   // mtime of the file to be in the past.
-  let fname = PageThumbsStorage.getFilePathForURL(url);
+  let fname = PageThumbsStorageService.getFilePathForURL(url);
   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   file.initWithPath(fname);
   ok(file.exists(), fname + " should exist");
   // Set it as very stale...
   file.lastModifiedTime = Date.now() - 1000000000;
 }
 
 function getThumbnailModifiedTime(url) {
-  let fname = PageThumbsStorage.getFilePathForURL(url);
+  let fname = PageThumbsStorageService.getFilePathForURL(url);
   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   file.initWithPath(fname);
   return file.lastModifiedTime;
 }
 
 // The tests!
 /* Check functionality of a normal captureAndStoreIfStale request */
 function* simpleCaptureTest() {
--- a/toolkit/components/thumbnails/test/head.js
+++ b/toolkit/components/thumbnails/test/head.js
@@ -12,16 +12,20 @@ Cu.import("resource://gre/modules/NewTab
 Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp);
 Cu.import("resource://gre/modules/FileUtils.jsm", tmp);
 Cu.import("resource://gre/modules/osfile.jsm", tmp);
 var {PageThumbs, BackgroundPageThumbs, NewTabUtils, PageThumbsStorage, SessionStore, FileUtils, OS} = tmp;
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
   "resource://testing-common/PlacesTestUtils.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "PageThumbsStorageService",
+  "@mozilla.org/thumbnails/pagethumbs-service;1",
+  "nsIPageThumbsStorageService");
+
 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);
 });
@@ -168,17 +172,17 @@ function retrieveImageDataForURL(aURL, a
   });
 }
 
 /**
  * Returns the file of the thumbnail with the given URL.
  * @param aURL The URL of the thumbnail.
  */
 function thumbnailFile(aURL) {
-  return new FileUtils.File(PageThumbsStorage.getFilePathForURL(aURL));
+  return new FileUtils.File(PageThumbsStorageService.getFilePathForURL(aURL));
 }
 
 /**
  * Checks if a thumbnail for the given URL exists.
  * @param aURL The url associated to the thumbnail.
  */
 function thumbnailExists(aURL) {
   let file = thumbnailFile(aURL);
--- a/toolkit/components/thumbnails/test/test_thumbnails_interfaces.js
+++ b/toolkit/components/thumbnails/test/test_thumbnails_interfaces.js
@@ -1,29 +1,29 @@
-// tests to check that moz-page-thumb URLs correctly resolve as file:// URLS
 "use strict";
 
 const Cu = Components.utils;
-const Cc = Components.classes;
-const Cr = Components.results;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 
-// need profile so that PageThumbsStorage can resolve the path to the underlying file
+// need profile so that PageThumbsStorageService can resolve the path to the underlying file
 do_get_profile();
 
 function run_test() {
-  // first check the protocol handler implements the correct interface
+  // check the protocol handler implements the correct interface
   let handler = Services.io.getProtocolHandler("moz-page-thumb");
-  ok(handler instanceof Ci.nsISubstitutingProtocolHandler,
-     "moz-page-thumb handler provides substituting interface");
+  ok(handler instanceof Ci.nsIProtocolHandler,
+     "moz-page-thumb handler provides a protocol handler interface");
 
-  // then check that the file URL resolution works
-  let uri = Services.io.newURI("moz-page-thumb://thumbnail/?url=http%3A%2F%2Fwww.mozilla.org%2F");
-  ok(uri instanceof Ci.nsIFileURL, "moz-page-thumb:// is a FileURL");
-  ok(uri.file, "This moz-page-thumb:// object is backed by a file");
+  // and check that the error cases work as specified
+  let badhost = Services.io.newURI("moz-page-thumb://wronghost/?url=http%3A%2F%2Fwww.mozilla.org%2F");
+  Assert.throws(() => handler.newChannel(badhost), /NS_ERROR_NOT_AVAILABLE/i,
+      "moz-page-thumb object with wrong host must not resolve to a file path");
 
-  // and check that the error case works as specified
-  let bad = Services.io.newURI("moz-page-thumb://wronghost/?url=http%3A%2F%2Fwww.mozilla.org%2F");
-  Assert.throws(() => handler.resolveURI(bad), /NS_ERROR_NOT_AVAILABLE/i,
-                "moz-page-thumb object with wrong host must not resolve to a file path");
+  let badQuery = Services.io.newURI("moz-page-thumb://thumbnail/http%3A%2F%2Fwww.mozilla.org%2F");
+  Assert.throws(() => handler.newChannel(badQuery), /NS_ERROR_MALFORMED_URI/i,
+      "moz-page-thumb object with malformed query parameters must not resolve to a file path");
+
+  let noURL = Services.io.newURI("moz-page-thumb://thumbnail/?badStuff");
+  Assert.throws(() => handler.newChannel(noURL), /NS_ERROR_NOT_AVAILABLE/i,
+      "moz-page-thumb object without a URL parameter must not resolve to a file path");
 }