Bug 1373258 - Convert PageThumbsProtocol.js to PageThumbsProtocol.cpp r=adw
authorUrsula Sarracini
Mon, 05 Jun 2017 10:04:06 -0400
changeset 390156 d05449eb1e035689552164417468a6e9f6b9e5cd
parent 390155 31b3ba0e8491869687c99a6b10e4aafdba7dc852
child 390157 5e0723d7f6ed03695225f7a71298784a5f41d808
push id96988
push userarchaeopteryx@coole-files.de
push dateSat, 04 Nov 2017 10:03:29 +0000
treeherdermozilla-inbound@39d71149faea [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw
bugs1373258
milestone58.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 1373258 - Convert PageThumbsProtocol.js to PageThumbsProtocol.cpp r=adw 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
@@ -376,17 +376,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
@@ -446,17 +447,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
@@ -2200,16 +2200,24 @@ imgLoader::LoadImage(nsIURI* aURI,
       aLoadingDocument->GetDocumentLoadGroup();
     MOZ_ASSERT(docLoadGroup == aLoadGroup);
   }
 #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!
   //
@@ -2478,16 +2486,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");
 }