Bug 1283825 - Add a page-icon protocol to fetch the best icon for a url. r=adw
authorMarco Bonardo <mbonardo@mozilla.com>
Thu, 30 Jun 2016 18:17:44 +0200
changeset 303591 a46d234975d9455d70a8b0db69c46c7a0141b2c5
parent 303590 7eef107d8d310cc5c0419d3fd3bf7f74590da3db
child 303592 0842107a80e7b434b964922bfb5b4b3f88e8eec2
child 303687 fdffe351484e4abbd32f686424ff840ae05f0992
push id79124
push userphilringnalda@gmail.com
push dateMon, 04 Jul 2016 22:08:33 +0000
treeherdermozilla-inbound@4b7053b141ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw
bugs1283825
milestone50.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 1283825 - Add a page-icon protocol to fetch the best icon for a url. r=adw MozReview-Commit-ID: 3exDniH8Hkm
browser/installer/package-manifest.in
toolkit/components/places/FaviconHelpers.cpp
toolkit/components/places/PageIconProtocolHandler.js
toolkit/components/places/moz.build
toolkit/components/places/nsAnnoProtocolHandler.cpp
toolkit/components/places/nsFaviconService.cpp
toolkit/components/places/nsFaviconService.h
toolkit/components/places/nsIFaviconService.idl
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/tests/favicons/test_page-icon_protocol.js
toolkit/components/places/tests/favicons/xpcshell.ini
toolkit/components/places/toolkitplaces.manifest
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -437,16 +437,17 @@
 @RESPATH@/browser/components/@DLL_PREFIX@browsercomps@DLL_SUFFIX@
 @RESPATH@/components/txEXSLTRegExFunctions.manifest
 @RESPATH@/components/txEXSLTRegExFunctions.js
 @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/nsDefaultCLH.manifest
 @RESPATH@/components/nsDefaultCLH.js
 @RESPATH@/components/nsContentPrefService.manifest
 @RESPATH@/components/nsContentPrefService.js
 @RESPATH@/components/nsContentDispatchChooser.manifest
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -608,17 +608,17 @@ AsyncFetchAndSetIconForPage::OnStopReque
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::PLACES_FAVICON_OTHER_SIZES, mIcon.data.Length());
   }
 
   rv = OptimizeIconSize(mIcon, favicons);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If over the maximum size allowed, don't save data to the database to
   // avoid bloating it.
-  if (mIcon.data.Length() > MAX_FAVICON_SIZE) {
+  if (mIcon.data.Length() > nsIFaviconService::MAX_FAVICON_SIZE) {
     return NS_OK;
   }
 
   mIcon.status = ICON_STATUS_CHANGED;
 
   RefPtr<Database> DB = Database::GetDatabase();
   NS_ENSURE_STATE(DB);
   RefPtr<AsyncAssociateIconToPage> event =
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/PageIconProtocolHandler.js
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
+
+function makeDefaultFaviconChannel(uri, loadInfo) {
+  let channel = Services.io.newChannelFromURIWithLoadInfo(
+    PlacesUtils.favicons.defaultFavicon, loadInfo);
+  channel.originalURI = uri;
+  return channel;
+}
+
+function streamDefaultFavicon(uri, loadInfo, outputStream) {
+  try {
+    // Open up a new channel to get that data, and push it to our output stream.
+    // Create a listener to hand data to the pipe's output stream.
+    let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
+                     .createInstance(Ci.nsISimpleStreamListener);
+    listener.init(outputStream, {
+      onStartRequest(request, context) {},
+      onStopRequest(request, context, statusCode) {
+        // We must close the outputStream regardless.
+        outputStream.close();
+      }
+    });
+    let defaultIconChannel = makeDefaultFaviconChannel(uri, loadInfo);
+    defaultIconChannel.asyncOpen2(listener);
+  } catch (ex) {
+    Cu.reportError(ex);
+    outputStream.close();
+  }
+}
+
+function PageIconProtocolHandler() {
+}
+
+PageIconProtocolHandler.prototype = {
+  get scheme() {
+    return "page-icon";
+  },
+
+  get defaultPort() {
+    return -1;
+  },
+
+  get protocolFlags() {
+    return Ci.nsIProtocolHandler.URI_NORELATIVE |
+           Ci.nsIProtocolHandler.URI_NOAUTH |
+           Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+           Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE;
+  },
+
+  newURI(spec, originCharset, baseURI) {
+    let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+    uri.spec = spec;
+    return uri;
+  },
+
+  newChannel2(uri, loadInfo) {
+    try {
+      // Create a pipe that will give us an output stream that we can use once
+      // we got all the favicon data.
+      let pipe = Cc["@mozilla.org/pipe;1"]
+                   .createInstance(Ci.nsIPipe);
+      pipe.init(true, true, 0, Ci.nsIFaviconService.MAX_FAVICON_SIZE);
+
+      // Create our channel.
+      let channel = Cc['@mozilla.org/network/input-stream-channel;1']
+                      .createInstance(Ci.nsIInputStreamChannel);
+      channel.QueryInterface(Ci.nsIChannel);
+      channel.setURI(uri);
+      channel.contentStream = pipe.inputStream;
+      channel.loadInfo = loadInfo;
+
+      let pageURI = NetUtil.newURI(uri.path);
+      PlacesUtils.favicons.getFaviconDataForPage(pageURI, (iconuri, len, data, mime) => {
+        if (len == 0) {
+          channel.contentType = "image/png";
+          streamDefaultFavicon(uri, loadInfo, pipe.outputStream);
+          return;
+        }
+
+        try {
+          channel.contentType = mime;
+          // Pass the icon data to the output stream.
+          let stream = Cc["@mozilla.org/binaryoutputstream;1"]
+                         .createInstance(Ci.nsIBinaryOutputStream);
+          stream.setOutputStream(pipe.outputStream);
+          stream.writeByteArray(data, len);
+          stream.close();
+          pipe.outputStream.close();
+        } catch (ex) {
+          channel.contentType = "image/png";
+          streamDefaultFavicon(uri, loadInfo, pipe.outputStream);
+        }
+      });
+
+      return channel;
+    } catch (ex) {
+      return makeDefaultFaviconChannel(uri, loadInfo);
+    }
+  },
+
+  newChannel(uri) {
+    return this.newChannel2(uri, null);
+  },
+
+  allowPort(port, scheme) {
+    return false;
+  },
+
+  classID: Components.ID("{60a1f7c6-4ff9-4a42-84d3-5a185faa6f32}"),
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIProtocolHandler
+  ])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PageIconProtocolHandler]);
--- a/toolkit/components/places/moz.build
+++ b/toolkit/components/places/moz.build
@@ -73,16 +73,17 @@ if CONFIG['MOZ_PLACES']:
         'PlacesUtils.jsm',
     ]
 
     EXTRA_COMPONENTS += [
         'ColorAnalyzer.js',
         'nsLivemarkService.js',
         'nsPlacesExpiration.js',
         'nsTaggingService.js',
+        'PageIconProtocolHandler.js',
         'PlacesCategoriesStarter.js',
         'toolkitplaces.manifest',
         'UnifiedComplete.js',
     ]
 
     if CONFIG['MOZ_SUITE']:
         EXTRA_COMPONENTS += [
             'nsPlacesAutoComplete.js',
--- a/toolkit/components/places/nsAnnoProtocolHandler.cpp
+++ b/toolkit/components/places/nsAnnoProtocolHandler.cpp
@@ -331,18 +331,17 @@ nsAnnoProtocolHandler::NewFaviconChannel
                                          nsILoadInfo* aLoadInfo, nsIChannel **_channel)
 {
   // Create our pipe.  This will give us our input stream and output stream
   // that will be written to when we get data from the database.
   nsCOMPtr<nsIInputStream> inputStream;
   nsCOMPtr<nsIOutputStream> outputStream;
   nsresult rv = NS_NewPipe(getter_AddRefs(inputStream),
                            getter_AddRefs(outputStream),
-                           MAX_FAVICON_SIZE, MAX_FAVICON_SIZE, true,
-                           true);
+                           0, nsIFaviconService::MAX_FAVICON_SIZE, true, true);
   NS_ENSURE_SUCCESS(rv, GetDefaultIcon(aLoadInfo, _channel));
 
   // Create our channel.  We'll call SetContentType with the right type when
   // we know what it actually is.
   nsCOMPtr<nsIChannel> channel;
   rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
                                         aURI,
                                         inputStream,
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -342,17 +342,17 @@ nsFaviconService::ReplaceFaviconData(nsI
 
   // If the page provided a large image for the favicon (eg, a highres image
   // or a multiresolution .ico file), we don't want to store more data than
   // needed.
   if (aDataLen > MAX_ICON_FILESIZE(mOptimizedIconDimension)) {
     rv = OptimizeFaviconImage(aData, aDataLen, aMimeType, iconData->data, iconData->mimeType);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (iconData->data.Length() > MAX_FAVICON_SIZE) {
+    if (iconData->data.Length() > nsIFaviconService::MAX_FAVICON_SIZE) {
       // We cannot optimize this favicon size and we are over the maximum size
       // allowed, so we will not save data to the db to avoid bloating it.
       mUnassociatedIcons.RemoveEntry(aFaviconURI);
       return NS_ERROR_FAILURE;
     }
   } else {
     iconData->mimeType.Assign(aMimeType);
     iconData->data.Assign(TO_CHARBUFFER(aData), aDataLen);
--- a/toolkit/components/places/nsFaviconService.h
+++ b/toolkit/components/places/nsFaviconService.h
@@ -18,21 +18,16 @@
 #include "nsURIHashKey.h"
 #include "nsITimer.h"
 #include "Database.h"
 #include "mozilla/storage.h"
 #include "mozilla/Attributes.h"
 
 #include "FaviconHelpers.h"
 
-// Favicons bigger than this size should not be saved to the db to avoid
-// bloating it with large image blobs.
-// This still allows us to accept a favicon even if we cannot optimize it.
-#define MAX_FAVICON_SIZE 10240
-
 // Most icons will be smaller than this rough estimate of the size of an
 // uncompressed 16x16 RGBA image of the same dimensions.
 #define MAX_ICON_FILESIZE(s) ((uint32_t) s*s*4)
 
 // forward class definitions
 class mozIStorageStatementCallback;
 
 class UnassociatedIconHashKey : public nsURIHashKey
--- a/toolkit/components/places/nsIFaviconService.idl
+++ b/toolkit/components/places/nsIFaviconService.idl
@@ -11,16 +11,22 @@ interface nsIURI;
 interface nsIFaviconService : nsISupports
 {
   // The favicon is being loaded from a private browsing window
   const unsigned long FAVICON_LOAD_PRIVATE = 1;
   // The favicon is being loaded from a non-private browsing window
   const unsigned long FAVICON_LOAD_NON_PRIVATE = 2;
 
   /**
+   * Favicons bigger than this size in bytes, won't be saved to the database to
+   * avoid bloating it with large image blobs.
+   */
+  const unsigned long MAX_FAVICON_SIZE = 10240;
+
+  /**
    * For a given icon URI, this will return a URI that will result in the image.
    * In most cases, this is an annotation URI.  For chrome URIs, this will do
    * nothing but returning the input URI.
    *
    * No validity checking is done. If you pass an icon URI that we've never
    * seen, you'll get back a URI that references an invalid icon. The moz-anno
    * protocol handler's special case for "favicon" annotations will resolve
    * invalid icons to the default icon, although without caching.
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -1140,27 +1140,28 @@ nsNavHistory::CanAddURI(nsIURI* aURI, bo
   }
   if (scheme.EqualsLiteral("https")) {
     *canAdd = true;
     return NS_OK;
   }
 
   // now check for all bad things
   if (scheme.EqualsLiteral("about") ||
+      scheme.EqualsLiteral("blob") ||
+      scheme.EqualsLiteral("chrome") ||
+      scheme.EqualsLiteral("data") ||
       scheme.EqualsLiteral("imap") ||
-      scheme.EqualsLiteral("news") ||
+      scheme.EqualsLiteral("javascript") ||
       scheme.EqualsLiteral("mailbox") ||
       scheme.EqualsLiteral("moz-anno") ||
-      scheme.EqualsLiteral("view-source") ||
-      scheme.EqualsLiteral("chrome") ||
+      scheme.EqualsLiteral("news") ||
+      scheme.EqualsLiteral("page-icon") ||
       scheme.EqualsLiteral("resource") ||
-      scheme.EqualsLiteral("data") ||
-      scheme.EqualsLiteral("wyciwyg") ||
-      scheme.EqualsLiteral("javascript") ||
-      scheme.EqualsLiteral("blob")) {
+      scheme.EqualsLiteral("view-source") ||
+      scheme.EqualsLiteral("wyciwyg")) {
     return NS_OK;
   }
   *canAdd = true;
   return NS_OK;
 }
 
 // nsNavHistory::GetNewQuery
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
@@ -0,0 +1,66 @@
+const ICON_DATA = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
+const TEST_URI = NetUtil.newURI("http://mozilla.org/");
+const ICON_URI = NetUtil.newURI("http://mozilla.org/favicon.ico");
+
+function fetchIconForSpec(spec) {
+ return new Promise((resolve, reject) => {
+    NetUtil.asyncFetch({
+      uri: NetUtil.newURI("page-icon:" + TEST_URI.spec),
+      loadUsingSystemPrincipal: true,
+      contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
+    }, (input, status, request) => {
+       if (!Components.isSuccessCode(status)) {
+        reject(new Error("unable to load icon"));
+        return;
+      }
+
+      try {
+        let data = NetUtil.readInputStreamToString(input, input.available());
+        let contentType = request.QueryInterface(Ci.nsIChannel).contentType;
+        input.close();
+        resolve({ data, contentType });
+      } catch (ex) {
+        reject(ex);
+      }
+    });
+  });
+}
+
+var gDefaultFavicon;
+var gFavicon;
+
+add_task(function* setup() {
+  PlacesTestUtils.addVisits({ uri: TEST_URI });
+
+  PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+    ICON_URI, ICON_DATA, (Date.now() + 8640000) * 1000,
+    Services.scriptSecurityManager.getSystemPrincipal());
+
+  yield new Promise(resolve => {
+    PlacesUtils.favicons.setAndFetchFaviconForPage(
+      TEST_URI, ICON_URI, false,
+      PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+      resolve, Services.scriptSecurityManager.getSystemPrincipal());
+  });
+
+  gDefaultFavicon = yield fetchIconForSpec(PlacesUtils.favicons.defaultFavicon);
+  gFavicon = yield fetchIconForSpec(ICON_DATA);
+});
+
+add_task(function* known_url() {
+  let {data, contentType} = yield fetchIconForSpec(TEST_URI.spec);
+  Assert.equal(contentType, gFavicon.contentType);
+  Assert.ok(data == gFavicon.data, "Got the favicon data");
+});
+
+add_task(function* unknown_url() {
+  let {data, contentType} = yield fetchIconForSpec("http://www.moz.org/");
+  Assert.equal(contentType, gDefaultFavicon.contentType);
+  Assert.ok(data == gDefaultFavicon.data, "Got the default favicon data");
+});
+
+add_task(function* invalid_url() {
+  let {data, contentType} = yield fetchIconForSpec("test");
+  Assert.equal(contentType, gDefaultFavicon.contentType);
+  Assert.ok(data == gDefaultFavicon.data, "Got the default favicon data");
+});
--- a/toolkit/components/places/tests/favicons/xpcshell.ini
+++ b/toolkit/components/places/tests/favicons/xpcshell.ini
@@ -22,11 +22,12 @@ support-files =
 
 [test_expireAllFavicons.js]
 [test_favicons_conversions.js]
 # Bug 676989: test fails consistently on Android
 fail-if = os == "android"
 [test_getFaviconDataForPage.js]
 [test_getFaviconURLForPage.js]
 [test_moz-anno_favicon_mime_type.js]
+[test_page-icon_protocol.js]
 [test_query_result_favicon_changed_on_child.js]
 [test_replaceFaviconData.js]
 [test_replaceFaviconDataFromDataURL.js]
--- a/toolkit/components/places/toolkitplaces.manifest
+++ b/toolkit/components/places/toolkitplaces.manifest
@@ -21,8 +21,12 @@ category bookmark-observers PlacesCatego
 
 # ColorAnalyzer.js
 component {d056186c-28a0-494e-aacc-9e433772b143} ColorAnalyzer.js
 contract @mozilla.org/places/colorAnalyzer;1 {d056186c-28a0-494e-aacc-9e433772b143}
 
 # UnifiedComplete.js
 component {f964a319-397a-4d21-8be6-5cdd1ee3e3ae} UnifiedComplete.js
 contract @mozilla.org/autocomplete/search;1?name=unifiedcomplete {f964a319-397a-4d21-8be6-5cdd1ee3e3ae}
+
+# PageIconProtocolHandler.js
+component {60a1f7c6-4ff9-4a42-84d3-5a185faa6f32} PageIconProtocolHandler.js
+contract @mozilla.org/network/protocol;1?name=page-icon {60a1f7c6-4ff9-4a42-84d3-5a185faa6f32}