Backed out 5 changesets (bug 1552815) for causing bp-nu bustages in nsFaviconService.cpp. CLOSED TREE

Backed out changeset 4be83e948d32 (bug 1552815)
Backed out changeset 0b2d40719586 (bug 1552815)
Backed out changeset c17e4da85514 (bug 1552815)
Backed out changeset c66812747d09 (bug 1552815)
Backed out changeset 62b3d85f4ca3 (bug 1552815)
This commit is contained in:
Stanca Serban 2024-05-01 09:58:57 +03:00
parent a290e8b405
commit a5a1a182a7
41 changed files with 1691 additions and 933 deletions

View file

@ -21,16 +21,17 @@ add_task(async function test_notificationClose() {
Services.prefs.setBoolPref("alerts.showFavicons", true); Services.prefs.setBoolPref("alerts.showFavicons", true);
await PlacesTestUtils.addVisits(notificationURI); await PlacesTestUtils.addVisits(notificationURI);
let dataURL = makeURI( let faviconURI = await new Promise(resolve => {
let uri = makeURI(
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC" "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC"
); );
await new Promise(resolve => { PlacesUtils.favicons.setAndFetchFaviconForPage(
PlacesUtils.favicons.setFaviconForPage(
notificationURI, notificationURI,
dataURL, uri,
dataURL, true,
null, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
resolve uriResult => resolve(uriResult),
Services.scriptSecurityManager.getSystemPrincipal()
); );
}); });
@ -66,7 +67,11 @@ add_task(async function test_notificationClose() {
"Body text of notification should be present" "Body text of notification should be present"
); );
let alertIcon = alertWindow.document.getElementById("alertIcon"); let alertIcon = alertWindow.document.getElementById("alertIcon");
is(alertIcon.src, dataURL.spec, "Icon of notification should be present"); is(
alertIcon.src,
faviconURI.spec,
"Icon of notification should be present"
);
let alertCloseButton = alertWindow.document.querySelector(".close-icon"); let alertCloseButton = alertWindow.document.querySelector(".close-icon");
is(alertCloseButton.localName, "toolbarbutton", "close button found"); is(alertCloseButton.localName, "toolbarbutton", "close button found");

View file

@ -247,10 +247,21 @@ DistributionCustomizer.prototype = {
if (item.icon && item.iconData) { if (item.icon && item.iconData) {
try { try {
lazy.PlacesUtils.favicons.setFaviconForPage( let faviconURI = Services.io.newURI(item.icon);
lazy.PlacesUtils.favicons.replaceFaviconDataFromDataURL(
faviconURI,
item.iconData,
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
lazy.PlacesUtils.favicons.setAndFetchFaviconForPage(
Services.io.newURI(item.link), Services.io.newURI(item.link),
Services.io.newURI(item.icon), faviconURI,
Services.io.newURI(item.iconData) false,
lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null,
Services.scriptSecurityManager.getSystemPrincipal()
); );
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View file

@ -204,30 +204,44 @@ async function insertBookmark(bookmark) {
} }
function setFaviconForBookmark(bookmark) { function setFaviconForBookmark(bookmark) {
let faviconURI;
let nullPrincipal = Services.scriptSecurityManager.createNullPrincipal({});
switch (bookmark.Favicon.protocol) { switch (bookmark.Favicon.protocol) {
case "data:": { case "data:":
lazy.PlacesUtils.favicons.setFaviconForPage( // data urls must first call replaceFaviconDataFromDataURL, using a
bookmark.URL.URI, // fake URL. Later, it's needed to call setAndFetchFaviconForPage
Services.io.newURI("fake-favicon-uri:" + bookmark.URL.href), // with the same URL.
bookmark.Favicon.URI faviconURI = Services.io.newURI("fake-favicon-uri:" + bookmark.URL.href);
lazy.PlacesUtils.favicons.replaceFaviconDataFromDataURL(
faviconURI,
bookmark.Favicon.href,
0 /* max expiration length */,
nullPrincipal
);
break;
case "http:":
case "https:":
faviconURI = Services.io.newURI(bookmark.Favicon.href);
break;
default:
lazy.log.error(
`Bad URL given for favicon on bookmark "${bookmark.Title}"`
); );
return; return;
} }
case "http:":
case "https:": {
lazy.PlacesUtils.favicons.setAndFetchFaviconForPage( lazy.PlacesUtils.favicons.setAndFetchFaviconForPage(
bookmark.URL.URI, Services.io.newURI(bookmark.URL.href),
bookmark.Favicon.URI, faviconURI,
false /* forceReload */, false /* forceReload */,
lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null, null,
Services.scriptSecurityManager.createNullPrincipal({}) nullPrincipal
); );
return;
}
}
lazy.log.error(`Bad URL given for favicon on bookmark "${bookmark.Title}"`);
} }
// Cache of folder names to guids to be used by the getParentGuid // Cache of folder names to guids to be used by the getParentGuid

View file

@ -785,8 +785,7 @@ async function GetBookmarksResource(aProfileFolder, aBrowserKey) {
} }
// Import Bookmark Favicons // Import Bookmark Favicons
MigrationUtils.insertManyFavicons(favicons).catch(console.error); MigrationUtils.insertManyFavicons(favicons);
if (gotErrors) { if (gotErrors) {
throw new Error("The migration included errors."); throw new Error("The migration included errors.");
} }

View file

@ -381,7 +381,7 @@ Bookmarks.prototype = {
} }
await MigrationUtils.insertManyBookmarksWrapper(bookmarks, aDestFolderGuid); await MigrationUtils.insertManyBookmarksWrapper(bookmarks, aDestFolderGuid);
MigrationUtils.insertManyFavicons(favicons).catch(console.error); MigrationUtils.insertManyFavicons(favicons);
}, },
/** /**

View file

@ -870,53 +870,36 @@ class MigrationUtils {
* Iterates through the favicons, sniffs for a mime type, * Iterates through the favicons, sniffs for a mime type,
* and uses the mime type to properly import the favicon. * and uses the mime type to properly import the favicon.
* *
* Note: You may not want to await on the returned promise, especially if by
* doing so there's risk of interrupting the migration of more critical
* data (e.g. bookmarks).
*
* @param {object[]} favicons * @param {object[]} favicons
* An array of Objects with these properties: * An array of Objects with these properties:
* {Uint8Array} faviconData: The binary data of a favicon * {Uint8Array} faviconData: The binary data of a favicon
* {nsIURI} uri: The URI of the associated page * {nsIURI} uri: The URI of the associated page
*/ */
async insertManyFavicons(favicons) { insertManyFavicons(favicons) {
let sniffer = Cc["@mozilla.org/image/loader;1"].createInstance( let sniffer = Cc["@mozilla.org/image/loader;1"].createInstance(
Ci.nsIContentSniffer Ci.nsIContentSniffer
); );
for (let faviconDataItem of favicons) { for (let faviconDataItem of favicons) {
let dataURL;
try {
// getMIMETypeFromContent throws error if could not get the mime type
// from the data.
let mimeType = sniffer.getMIMETypeFromContent( let mimeType = sniffer.getMIMETypeFromContent(
null, null,
faviconDataItem.faviconData, faviconDataItem.faviconData,
faviconDataItem.faviconData.length faviconDataItem.faviconData.length
); );
dataURL = await new Promise((resolve, reject) => {
let buffer = new Uint8ClampedArray(faviconDataItem.faviconData);
let blob = new Blob([buffer], { type: mimeType });
let reader = new FileReader();
reader.addEventListener("load", () => resolve(reader.result));
reader.addEventListener("error", reject);
reader.readAsDataURL(blob);
});
} catch (e) {
// Even if error happens for favicon, continue the process.
console.warn(e);
continue;
}
let fakeFaviconURI = Services.io.newURI( let fakeFaviconURI = Services.io.newURI(
"fake-favicon-uri:" + faviconDataItem.uri.spec "fake-favicon-uri:" + faviconDataItem.uri.spec
); );
lazy.PlacesUtils.favicons.setFaviconForPage( lazy.PlacesUtils.favicons.replaceFaviconData(
fakeFaviconURI,
faviconDataItem.faviconData,
mimeType
);
lazy.PlacesUtils.favicons.setAndFetchFaviconForPage(
faviconDataItem.uri, faviconDataItem.uri,
fakeFaviconURI, fakeFaviconURI,
Services.io.newURI(dataURL) true,
lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null,
Services.scriptSecurityManager.getSystemPrincipal()
); );
} }
} }

View file

@ -253,7 +253,7 @@ Bookmarks.prototype = {
parentGuid parentGuid
); );
MigrationUtils.insertManyFavicons(favicons).catch(console.error); MigrationUtils.insertManyFavicons(favicons);
}, },
/** /**

View file

@ -117,34 +117,6 @@ async function assertFavicons(pageURIs) {
} }
} }
/**
* Check the image data for favicon of given page uri.
*
* @param {string} pageURI
* The page URI to which the favicon belongs.
* @param {Array} expectedImageData
* Expected image data of the favicon.
* @param {string} expectedMimeType
* Expected mime type of the favicon.
*/
async function assertFavicon(pageURI, expectedImageData, expectedMimeType) {
let result = await new Promise(resolve => {
PlacesUtils.favicons.getFaviconDataForPage(
Services.io.newURI(pageURI),
(faviconURI, dataLen, imageData, mimeType) => {
resolve({ faviconURI, dataLen, imageData, mimeType });
}
);
});
Assert.ok(!!result, `Got favicon for ${pageURI}`);
Assert.equal(
result.imageData.join(","),
expectedImageData.join(","),
"Image data is correct"
);
Assert.equal(result.mimeType, expectedMimeType, "Mime type is correct");
}
/** /**
* Replaces a directory service entry with a given nsIFile. * Replaces a directory service entry with a given nsIFile.
* *

View file

@ -71,13 +71,11 @@ async function testBookmarks(migratorKey, subDirs) {
).path; ).path;
await IOUtils.copy(sourcePath, target.path); await IOUtils.copy(sourcePath, target.path);
// Get page url and the image data for each favicon // Get page url for each favicon
let favicons = await MigrationUtils.getRowsFromDBWithoutLocks( let faviconURIs = await MigrationUtils.getRowsFromDBWithoutLocks(
sourcePath, sourcePath,
"Chrome Bookmark Favicons", "Chrome Bookmark Favicons",
`SELECT page_url, image_data FROM icon_mapping `select page_url from icon_mapping`
INNER JOIN favicon_bitmaps ON (favicon_bitmaps.icon_id = icon_mapping.icon_id)
`
); );
target.append("Bookmarks"); target.append("Bookmarks");
@ -173,14 +171,10 @@ async function testBookmarks(migratorKey, subDirs) {
"Telemetry reporting correct." "Telemetry reporting correct."
); );
Assert.ok(observerNotified, "The observer should be notified upon migration"); Assert.ok(observerNotified, "The observer should be notified upon migration");
let pageUrls = Array.from(faviconURIs, f =>
for (const favicon of favicons) { Services.io.newURI(f.getResultByName("page_url"))
await assertFavicon(
favicon.getResultByName("page_url"),
favicon.getResultByName("image_data"),
"image/png"
); );
} await assertFavicons(pageUrls);
} }
add_task(async function test_Chrome() { add_task(async function test_Chrome() {

View file

@ -199,24 +199,21 @@ let InternalFaviconLoader = {
win.addEventListener("unload", unloadHandler, true); win.addEventListener("unload", unloadHandler, true);
} }
let callback = this._makeCompletionCallback(win, innerWindowID);
if (iconURI?.schemeIs("data")) {
lazy.PlacesUtils.favicons.setFaviconForPage(
pageURI,
uri,
iconURI,
lazy.PlacesUtils.toPRTime(expiration),
() => {
callback.onComplete(uri);
}
);
return;
}
// First we do the actual setAndFetch call: // First we do the actual setAndFetch call:
let loadType = lazy.PrivateBrowsingUtils.isWindowPrivate(win) let loadType = lazy.PrivateBrowsingUtils.isWindowPrivate(win)
? lazy.PlacesUtils.favicons.FAVICON_LOAD_PRIVATE ? lazy.PlacesUtils.favicons.FAVICON_LOAD_PRIVATE
: lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE; : lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE;
let callback = this._makeCompletionCallback(win, innerWindowID);
if (iconURI && iconURI.schemeIs("data")) {
expiration = lazy.PlacesUtils.toPRTime(expiration);
lazy.PlacesUtils.favicons.replaceFaviconDataFromDataURL(
uri,
iconURI.spec,
expiration,
principal
);
}
let request = lazy.PlacesUtils.favicons.setAndFetchFaviconForPage( let request = lazy.PlacesUtils.favicons.setAndFetchFaviconForPage(
pageURI, pageURI,

View file

@ -764,7 +764,26 @@ add_task(async function test_onFaviconChanged() {
let iconURL = let iconURL =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
"AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=="; "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
await PlacesTestUtils.setFaviconForPage(pageURI, iconURI, iconURL);
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
iconURI,
iconURL,
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
await new Promise(resolve => {
PlacesUtils.favicons.setAndFetchFaviconForPage(
pageURI,
iconURI,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
() => {
resolve();
},
Services.scriptSecurityManager.getSystemPrincipal()
);
});
await verifyTrackedItems([]); await verifyTrackedItems([]);
Assert.equal(tracker.score, 0); Assert.equal(tracker.score, 0);
} finally { } finally {

View file

@ -1082,11 +1082,21 @@ BookmarkExporter.prototype = {
function insertFaviconForNode(node) { function insertFaviconForNode(node) {
if (node.icon) { if (node.icon) {
try { try {
PlacesUtils.favicons.setFaviconForPage( // Create a fake faviconURI to use (FIXME: bug 523932)
let faviconURI = Services.io.newURI("fake-favicon-uri:" + node.url);
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
faviconURI,
node.icon,
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
PlacesUtils.favicons.setAndFetchFaviconForPage(
Services.io.newURI(node.url), Services.io.newURI(node.url),
// Create a fake favicon URI to use (FIXME: bug 523932) faviconURI,
Services.io.newURI("fake-favicon-uri:" + node.url), false,
Services.io.newURI(node.icon) PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null,
Services.scriptSecurityManager.getSystemPrincipal()
); );
} catch (ex) { } catch (ex) {
console.error("Failed to import favicon data:", ex); console.error("Failed to import favicon data:", ex);

View file

@ -503,11 +503,21 @@ function translateTreeTypes(node) {
function insertFaviconForNode(node) { function insertFaviconForNode(node) {
if (node.icon) { if (node.icon) {
try { try {
PlacesUtils.favicons.setFaviconForPage(
Services.io.newURI(node.url),
// Create a fake faviconURI to use (FIXME: bug 523932) // Create a fake faviconURI to use (FIXME: bug 523932)
Services.io.newURI("fake-favicon-uri:" + node.url), let faviconURI = Services.io.newURI("fake-favicon-uri:" + node.url);
Services.io.newURI(node.icon) PlacesUtils.favicons.replaceFaviconDataFromDataURL(
faviconURI,
node.icon,
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
PlacesUtils.favicons.setAndFetchFaviconForPage(
Services.io.newURI(node.url),
faviconURI,
false,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null,
Services.scriptSecurityManager.getSystemPrincipal()
); );
} catch (ex) { } catch (ex) {
console.error("Failed to import favicon data:", ex); console.error("Failed to import favicon data:", ex);

View file

@ -959,49 +959,6 @@ AsyncAssociateIconToPage::Run() {
return NS_OK; return NS_OK;
} }
////////////////////////////////////////////////////////////////////////////////
//// AsyncSetIconForPage
AsyncSetIconForPage::AsyncSetIconForPage(const IconData& aIcon,
const PageData& aPage,
PlacesCompletionCallback* aCallback)
: Runnable("places::AsyncSetIconForPage"),
mCallback(new nsMainThreadPtrHolder<PlacesCompletionCallback>(
"AsyncSetIconForPage::mCallback", aCallback, false)),
mIcon(aIcon),
mPage(aPage) {}
NS_IMETHODIMP
AsyncSetIconForPage::Run() {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(mIcon.payloads.Length(), "The icon should have valid data");
MOZ_ASSERT(mPage.spec.Length(), "The page should have spec");
MOZ_ASSERT(mPage.guid.IsEmpty(), "The page should not have guid");
nsresult rv = NS_OK;
auto guard = MakeScopeExit([&]() {
if (mCallback) {
NS_DispatchToMainThread(
NS_NewRunnableFunction("AsyncSetIconForPage::Callback",
[rv, callback = std::move(mCallback)]() {
(void)callback->Complete(rv);
}));
}
});
// Fetch the page data.
RefPtr<Database> DB = Database::GetDatabase();
if (MOZ_UNLIKELY(!DB)) {
return (rv = NS_ERROR_UNEXPECTED);
}
rv = FetchPageInfo(DB, mPage);
NS_ENSURE_SUCCESS(rv, rv);
nsMainThreadPtrHandle<nsIFaviconDataCallback> nullCallback;
AsyncAssociateIconToPage event(mIcon, mPage, nullCallback);
return (rv = event.Run());
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
//// AsyncGetFaviconURLForPage //// AsyncGetFaviconURLForPage
@ -1083,6 +1040,61 @@ AsyncGetFaviconDataForPage::Run() {
return NS_OK; return NS_OK;
} }
////////////////////////////////////////////////////////////////////////////////
//// AsyncReplaceFaviconData
AsyncReplaceFaviconData::AsyncReplaceFaviconData(const IconData& aIcon)
: Runnable("places::AsyncReplaceFaviconData"), mIcon(aIcon) {
MOZ_ASSERT(NS_IsMainThread());
}
NS_IMETHODIMP
AsyncReplaceFaviconData::Run() {
MOZ_ASSERT(!NS_IsMainThread());
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
mozStorageTransaction transaction(
DB->MainConn(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
// XXX Handle the error, bug 1696133.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
nsresult rv = SetIconInfo(DB, mIcon, true);
if (rv == NS_ERROR_NOT_AVAILABLE) {
// There's no previous icon to replace, we don't need to do anything.
(void)transaction.Commit();
return NS_OK;
}
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
// We can invalidate the cache version since we now persist the icon.
nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
"places::AsyncReplaceFaviconData::RemoveIconDataCacheEntry", this,
&AsyncReplaceFaviconData::RemoveIconDataCacheEntry);
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult AsyncReplaceFaviconData::RemoveIconDataCacheEntry() {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIURI> iconURI;
nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
NS_ENSURE_SUCCESS(rv, rv);
nsFaviconService* favicons = nsFaviconService::GetFaviconService();
NS_ENSURE_STATE(favicons);
favicons->mUnassociatedIcons.RemoveEntry(iconURI);
return NS_OK;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
//// NotifyIconObservers //// NotifyIconObservers
@ -1195,8 +1207,7 @@ AsyncCopyFavicons::Run() {
} }
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// Get just one icon, to check whether the page has any, and to notify // Get just one icon, to check whether the page has any, and to notify later.
// later.
rv = FetchIconPerSpec(DB, mFromPage.spec, ""_ns, icon, UINT16_MAX); rv = FetchIconPerSpec(DB, mFromPage.spec, ""_ns, icon, UINT16_MAX);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);

View file

@ -14,7 +14,6 @@
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "nsProxyRelease.h" #include "nsProxyRelease.h"
#include "imgLoader.h" #include "imgLoader.h"
#include "PlacesCompletionCallback.h"
class nsIPrincipal; class nsIPrincipal;
@ -192,33 +191,6 @@ class AsyncAssociateIconToPage final : public Runnable {
PageData mPage; PageData mPage;
}; };
/**
* Set favicon for the page, finally dispatches an event to the
* main thread to notify the change to observers.
*/
class AsyncSetIconForPage final : public Runnable {
public:
NS_DECL_NSIRUNNABLE
/**
* Constructor.
*
* @param aIcon
* Icon to be associated.
* @param aPage
* Page to which associate the icon.
* @param aCallback
* Function to be called when the associate process finishes.
*/
AsyncSetIconForPage(const IconData& aIcon, const PageData& aPage,
PlacesCompletionCallback* aCallback);
private:
nsMainThreadPtrHandle<PlacesCompletionCallback> mCallback;
IconData mIcon;
PageData mPage;
};
/** /**
* Asynchronously tries to get the URL of a page's favicon, then notifies the * Asynchronously tries to get the URL of a page's favicon, then notifies the
* given observer. * given observer.
@ -284,6 +256,18 @@ class AsyncGetFaviconDataForPage final : public Runnable {
nsCString mPageHost; nsCString mPageHost;
}; };
class AsyncReplaceFaviconData final : public Runnable {
public:
NS_DECL_NSIRUNNABLE
explicit AsyncReplaceFaviconData(const IconData& aIcon);
private:
nsresult RemoveIconDataCacheEntry();
IconData mIcon;
};
/** /**
* Notifies the icon change to favicon observers. * Notifies the icon change to favicon observers.
*/ */
@ -292,7 +276,7 @@ class NotifyIconObservers final : public Runnable {
NS_DECL_NSIRUNNABLE NS_DECL_NSIRUNNABLE
/** /**
* Constructor for nsIFaviconDataCallback. * Constructor.
* *
* @param aIcon * @param aIcon
* Icon information. Can be empty if no icon is associated to the page. * Icon information. Can be empty if no icon is associated to the page.

View file

@ -1,19 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
* 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"
[scriptable, function, uuid(ea26627b-d21a-4f97-b2d0-f8df7be24808)]
interface PlacesCompletionCallback : nsISupports {
/**
* Indicates that the event this callback was passed in for has completed.
*
* @param status
* The status of the call. Generally NS_OK if the operation
* completed successfully.
*/
void complete(in nsresult status);
};

View file

@ -23,7 +23,6 @@ if CONFIG["MOZ_PLACES"]:
"nsINavBookmarksService.idl", "nsINavBookmarksService.idl",
"nsIPlacesPreviewsHelperService.idl", "nsIPlacesPreviewsHelperService.idl",
"nsITaggingService.idl", "nsITaggingService.idl",
"PlacesCompletionCallback.idl",
] ]
EXPORTS.mozilla.places = [ EXPORTS.mozilla.places = [

View file

@ -14,7 +14,6 @@
*/ */
#include "nsFaviconService.h" #include "nsFaviconService.h"
#include "PlacesCompletionCallback.h"
#include "nsNavHistory.h" #include "nsNavHistory.h"
#include "nsPlacesMacros.h" #include "nsPlacesMacros.h"
@ -36,6 +35,13 @@
#include "nsContentUtils.h" #include "nsContentUtils.h"
#include "imgICache.h" #include "imgICache.h"
#define UNASSOCIATED_FAVICONS_LENGTH 32
// When replaceFaviconData is called, we store the icons in an in-memory cache
// instead of in storage. Icons in the cache are expired according to this
// interval.
#define UNASSOCIATED_ICON_EXPIRY_INTERVAL 60000
using namespace mozilla; using namespace mozilla;
using namespace mozilla::places; using namespace mozilla::places;
@ -117,10 +123,12 @@ nsresult GetFramesInfoForContainer(imgIContainer* aContainer,
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService) PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService)
NS_IMPL_CLASSINFO(nsFaviconService, nullptr, 0, NS_FAVICONSERVICE_CID) NS_IMPL_CLASSINFO(nsFaviconService, nullptr, 0, NS_FAVICONSERVICE_CID)
NS_IMPL_ISUPPORTS_CI(nsFaviconService, nsIFaviconService) NS_IMPL_ISUPPORTS_CI(nsFaviconService, nsIFaviconService, nsITimerCallback,
nsINamed)
nsFaviconService::nsFaviconService() nsFaviconService::nsFaviconService()
: mDefaultIconURIPreferredSize(UINT16_MAX) { : mUnassociatedIcons(UNASSOCIATED_FAVICONS_LENGTH),
mDefaultIconURIPreferredSize(UINT16_MAX) {
NS_ASSERTION(!gFaviconService, NS_ASSERTION(!gFaviconService,
"Attempting to create two instances of the service!"); "Attempting to create two instances of the service!");
gFaviconService = this; gFaviconService = this;
@ -144,6 +152,10 @@ nsFaviconService::StoreLastInsertedId(const nsACString& aTable,
nsresult nsFaviconService::Init() { nsresult nsFaviconService::Init() {
mDB = Database::GetDatabase(); mDB = Database::GetDatabase();
NS_ENSURE_STATE(mDB); NS_ENSURE_STATE(mDB);
mExpireUnassociatedIconsTimer = NS_NewTimer();
NS_ENSURE_STATE(mExpireUnassociatedIconsTimer);
return NS_OK; return NS_OK;
} }
@ -175,6 +187,41 @@ nsFaviconService::ExpireAllFavicons() {
return conn->ExecuteAsync(stmts, callback, getter_AddRefs(ps)); return conn->ExecuteAsync(stmts, callback, getter_AddRefs(ps));
} }
////////////////////////////////////////////////////////////////////////////////
//// nsITimerCallback
NS_IMETHODIMP
nsFaviconService::Notify(nsITimer* timer) {
if (timer != mExpireUnassociatedIconsTimer.get()) {
return NS_ERROR_INVALID_ARG;
}
PRTime now = PR_Now();
for (auto iter = mUnassociatedIcons.Iter(); !iter.Done(); iter.Next()) {
UnassociatedIconHashKey* iconKey = iter.Get();
if (now - iconKey->created >= UNASSOCIATED_ICON_EXPIRY_INTERVAL) {
iter.Remove();
}
}
// Re-init the expiry timer if the cache isn't empty.
if (mUnassociatedIcons.Count() > 0) {
mExpireUnassociatedIconsTimer->InitWithCallback(
this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////
//// nsINamed
NS_IMETHODIMP
nsFaviconService::GetName(nsACString& aName) {
aName.AssignLiteral("nsFaviconService");
return NS_OK;
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
//// nsIFaviconService //// nsIFaviconService
@ -211,161 +258,6 @@ void nsFaviconService::ClearImageCache(nsIURI* aImageURI) {
} }
} }
NS_IMETHODIMP
nsFaviconService::SetFaviconForPage(
nsIURI* aPageURI, nsIURI* aFaviconURI, nsIURI* aDataURL,
PRTime aExpiration = 0, PlacesCompletionCallback* aCallback = nullptr) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aPageURI);
NS_ENSURE_ARG(aFaviconURI);
NS_ENSURE_ARG(aDataURL);
MOZ_DIAGNOSTIC_ASSERT(aDataURL->SchemeIs("data"));
if (!aDataURL->SchemeIs("data")) {
return NS_ERROR_INVALID_ARG;
}
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
return NS_OK;
}
PRTime now = PR_Now();
if (aExpiration < now + MIN_FAVICON_EXPIRATION) {
// Invalid input, just use the default.
aExpiration = now + MAX_FAVICON_EXPIRATION;
}
// Use the data: protocol handler to convert the data.
nsresult rv = NS_OK;
auto guard = MakeScopeExit([&]() {
if (aCallback) {
aCallback->Complete(rv);
}
});
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIProtocolHandler> protocolHandler;
rv = ioService->GetProtocolHandler("data", getter_AddRefs(protocolHandler));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrincipal> loadingPrincipal =
NullPrincipal::CreateWithoutOriginAttributes();
if (MOZ_UNLIKELY(!(loadingPrincipal))) {
return (rv = NS_ERROR_NULL_POINTER);
}
nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
loadingPrincipal,
nullptr, // aTriggeringPrincipal
nullptr, // aLoadingNode
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
nsILoadInfo::SEC_ALLOW_CHROME | nsILoadInfo::SEC_DISALLOW_SCRIPT,
nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON);
nsCOMPtr<nsIChannel> channel;
rv = protocolHandler->NewChannel(aDataURL, loadInfo, getter_AddRefs(channel));
NS_ENSURE_SUCCESS(rv, rv);
// Blocking stream is OK for data URIs.
nsCOMPtr<nsIInputStream> stream;
rv = channel->Open(getter_AddRefs(stream));
NS_ENSURE_SUCCESS(rv, rv);
uint64_t available64;
rv = stream->Available(&available64);
NS_ENSURE_SUCCESS(rv, rv);
if (available64 == 0 || available64 > UINT32_MAX / sizeof(uint8_t)) {
return (rv = NS_ERROR_FILE_TOO_BIG);
}
uint32_t available = (uint32_t)available64;
// Read all the decoded data.
nsTArray<uint8_t> buffer;
buffer.SetLength(available);
uint32_t numRead;
rv = stream->Read(TO_CHARBUFFER(buffer.Elements()), available, &numRead);
NS_ENSURE_SUCCESS(rv, rv);
if (numRead != available) {
return (rv = NS_ERROR_UNEXPECTED);
}
nsAutoCString mimeType;
rv = channel->GetContentType(mimeType);
NS_ENSURE_SUCCESS(rv, rv);
if (!imgLoader::SupportImageWithMimeType(
mimeType, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)) {
return (rv = NS_ERROR_UNEXPECTED);
}
// Favicon should be handled without userpass.
nsCOMPtr<nsIURI> faviconURI = GetExposableURI(aFaviconURI);
nsCOMPtr<nsIURI> pageURI = GetExposableURI(aPageURI);
IconData icon;
icon.expiration = aExpiration;
icon.status = ICON_STATUS_CACHED;
icon.fetchMode = FETCH_NEVER;
rv = faviconURI->GetSpec(icon.spec);
NS_ENSURE_SUCCESS(rv, rv);
// URIs can arguably lack a host.
(void)faviconURI->GetHost(icon.host);
if (StringBeginsWith(icon.host, "www."_ns)) {
icon.host.Cut(0, 4);
}
IconPayload payload;
payload.mimeType = mimeType;
payload.data.Assign(TO_CHARBUFFER(buffer.Elements()), buffer.Length());
if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
payload.width = UINT16_MAX;
}
icon.payloads.AppendElement(payload);
rv = OptimizeIconSizes(icon);
NS_ENSURE_SUCCESS(rv, rv);
PageData page;
rv = pageURI->GetSpec(page.spec);
NS_ENSURE_SUCCESS(rv, rv);
// URIs can arguably lack a host.
(void)pageURI->GetHost(page.host);
if (StringBeginsWith(page.host, "www."_ns)) {
page.host.Cut(0, 4);
}
// A root icon is when the icon and page have the same host and the path
// is just /favicon.ico. These icons are considered valid for the whole
// origin and expired with the origin through a trigger.
nsAutoCString path;
if (NS_SUCCEEDED(faviconURI->GetPathQueryRef(path)) && !icon.host.IsEmpty() &&
icon.host.Equals(page.host) && path.EqualsLiteral("/favicon.ico")) {
icon.rootIcon = 1;
}
// If the page url points to an image, the icon's url will be the same.
// TODO (Bug 403651): store a resample of the image. For now avoid that
// for database size and UX concerns.
// Don't store favicons for error pages either.
if (icon.spec.Equals(page.spec) ||
icon.spec.EqualsLiteral(FAVICON_CERTERRORPAGE_URL) ||
icon.spec.EqualsLiteral(FAVICON_ERRORPAGE_URL)) {
return NS_OK;
}
RefPtr<AsyncSetIconForPage> event =
new AsyncSetIconForPage(icon, page, aCallback);
RefPtr<Database> DB = Database::GetDatabase();
if (MOZ_UNLIKELY(!DB)) {
return (rv = NS_ERROR_UNEXPECTED);
}
DB->DispatchToAsyncThread(event);
guard.release();
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
nsFaviconService::SetAndFetchFaviconForPage( nsFaviconService::SetAndFetchFaviconForPage(
nsIURI* aPageURI, nsIURI* aFaviconURI, bool aForceReload, nsIURI* aPageURI, nsIURI* aFaviconURI, bool aForceReload,
@ -418,6 +310,12 @@ nsFaviconService::SetAndFetchFaviconForPage(
// Build icon data. // Build icon data.
IconData icon; IconData icon;
// If we have an in-memory icon payload, it overwrites the actual request.
UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(faviconURI);
if (iconKey) {
icon = iconKey->iconData;
mUnassociatedIcons.RemoveEntry(iconKey);
} else {
icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING; icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING;
rv = faviconURI->GetSpec(icon.spec); rv = faviconURI->GetSpec(icon.spec);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
@ -426,6 +324,7 @@ nsFaviconService::SetAndFetchFaviconForPage(
if (StringBeginsWith(icon.host, "www."_ns)) { if (StringBeginsWith(icon.host, "www."_ns)) {
icon.host.Cut(0, 4); icon.host.Cut(0, 4);
} }
}
// A root icon is when the icon and page have the same host and the path // A root icon is when the icon and page have the same host and the path
// is just /favicon.ico. These icons are considered valid for the whole // is just /favicon.ico. These icons are considered valid for the whole
@ -461,6 +360,182 @@ nsFaviconService::SetAndFetchFaviconForPage(
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
nsFaviconService::ReplaceFaviconData(nsIURI* aFaviconURI,
const nsTArray<uint8_t>& aData,
const nsACString& aMimeType,
PRTime aExpiration) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aFaviconURI);
NS_ENSURE_ARG(aData.Length() > 0);
NS_ENSURE_ARG(aMimeType.Length() > 0);
NS_ENSURE_ARG(imgLoader::SupportImageWithMimeType(
aMimeType, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS));
nsCOMPtr<nsIURI> faviconURI = GetExposableURI(aFaviconURI);
PRTime now = PR_Now();
if (aExpiration < now + MIN_FAVICON_EXPIRATION) {
// Invalid input, just use the default.
aExpiration = now + MAX_FAVICON_EXPIRATION;
}
UnassociatedIconHashKey* iconKey = mUnassociatedIcons.PutEntry(faviconURI);
if (!iconKey) {
return NS_ERROR_OUT_OF_MEMORY;
}
iconKey->created = now;
// If the cache contains unassociated icons, an expiry timer should already
// exist, otherwise there may be a timer left hanging around, so make sure we
// fire a new one.
uint32_t unassociatedCount = mUnassociatedIcons.Count();
if (unassociatedCount == 1) {
mExpireUnassociatedIconsTimer->Cancel();
mExpireUnassociatedIconsTimer->InitWithCallback(
this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
}
IconData* iconData = &(iconKey->iconData);
iconData->expiration = aExpiration;
iconData->status = ICON_STATUS_CACHED;
iconData->fetchMode = FETCH_NEVER;
nsresult rv = faviconURI->GetSpec(iconData->spec);
NS_ENSURE_SUCCESS(rv, rv);
// URIs can arguably lack a host.
Unused << faviconURI->GetHost(iconData->host);
if (StringBeginsWith(iconData->host, "www."_ns)) {
iconData->host.Cut(0, 4);
}
// Note we can't set rootIcon here, because don't know the page it will be
// associated with. We'll do that later in SetAndFetchFaviconForPage if the
// icon doesn't exist; otherwise, if AsyncReplaceFaviconData updates an
// existing icon, it will take care of not overwriting an existing
// root = 1 value.
IconPayload payload;
payload.mimeType = aMimeType;
payload.data.Assign(TO_CHARBUFFER(aData.Elements()), aData.Length());
if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
payload.width = UINT16_MAX;
}
// There may already be a previous payload, so ensure to only have one.
iconData->payloads.Clear();
iconData->payloads.AppendElement(payload);
rv = OptimizeIconSizes(*iconData);
NS_ENSURE_SUCCESS(rv, rv);
// If there's not valid payload, don't store the icon into to the database.
if ((*iconData).payloads.Length() == 0) {
// 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(faviconURI);
return NS_ERROR_FAILURE;
}
// If the database contains an icon at the given url, we will update the
// database immediately so that the associated pages are kept in sync.
// Otherwise, do nothing and let the icon be picked up from the memory hash.
RefPtr<AsyncReplaceFaviconData> event =
new AsyncReplaceFaviconData(*iconData);
RefPtr<Database> DB = Database::GetDatabase();
NS_ENSURE_STATE(DB);
DB->DispatchToAsyncThread(event);
return NS_OK;
}
NS_IMETHODIMP
nsFaviconService::ReplaceFaviconDataFromDataURL(
nsIURI* aFaviconURI, const nsAString& aDataURL, PRTime aExpiration,
nsIPrincipal* aLoadingPrincipal) {
NS_ENSURE_ARG(aFaviconURI);
NS_ENSURE_TRUE(aDataURL.Length() > 0, NS_ERROR_INVALID_ARG);
PRTime now = PR_Now();
if (aExpiration < now + MIN_FAVICON_EXPIRATION) {
// Invalid input, just use the default.
aExpiration = now + MAX_FAVICON_EXPIRATION;
}
nsCOMPtr<nsIURI> dataURI;
nsresult rv = NS_NewURI(getter_AddRefs(dataURI), aDataURL);
NS_ENSURE_SUCCESS(rv, rv);
// Use the data: protocol handler to convert the data.
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIProtocolHandler> protocolHandler;
rv = ioService->GetProtocolHandler("data", getter_AddRefs(protocolHandler));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
MOZ_ASSERT(loadingPrincipal,
"please provide aLoadingPrincipal for this favicon");
if (!loadingPrincipal) {
// Let's default to the nullPrincipal if no loadingPrincipal is provided.
AutoTArray<nsString, 2> params = {
u"nsFaviconService::ReplaceFaviconDataFromDataURL()"_ns,
u"nsFaviconService::ReplaceFaviconDataFromDataURL(...,"
" [optional aLoadingPrincipal])"_ns};
nsContentUtils::ReportToConsole(
nsIScriptError::warningFlag, "Security by Default"_ns,
nullptr, // aDocument
nsContentUtils::eNECKO_PROPERTIES, "APIDeprecationWarning", params);
loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
}
NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
loadingPrincipal,
nullptr, // aTriggeringPrincipal
nullptr, // aLoadingNode
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
nsILoadInfo::SEC_ALLOW_CHROME | nsILoadInfo::SEC_DISALLOW_SCRIPT,
nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON);
nsCOMPtr<nsIChannel> channel;
rv = protocolHandler->NewChannel(dataURI, loadInfo, getter_AddRefs(channel));
NS_ENSURE_SUCCESS(rv, rv);
// Blocking stream is OK for data URIs.
nsCOMPtr<nsIInputStream> stream;
rv = channel->Open(getter_AddRefs(stream));
NS_ENSURE_SUCCESS(rv, rv);
uint64_t available64;
rv = stream->Available(&available64);
NS_ENSURE_SUCCESS(rv, rv);
if (available64 == 0 || available64 > UINT32_MAX / sizeof(uint8_t)) {
return NS_ERROR_FILE_TOO_BIG;
}
uint32_t available = (uint32_t)available64;
// Read all the decoded data.
nsTArray<uint8_t> buffer;
buffer.SetLength(available);
uint32_t numRead;
rv = stream->Read(TO_CHARBUFFER(buffer.Elements()), available, &numRead);
if (NS_FAILED(rv) || numRead != available) {
return rv;
}
nsAutoCString mimeType;
rv = channel->GetContentType(mimeType);
if (NS_FAILED(rv)) {
return rv;
}
// ReplaceFaviconData can now do the dirty work.
rv = ReplaceFaviconData(aFaviconURI, buffer, mimeType, aExpiration);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
nsFaviconService::GetFaviconURLForPage(nsIURI* aPageURI, nsFaviconService::GetFaviconURLForPage(nsIURI* aPageURI,
nsIFaviconDataCallback* aCallback, nsIFaviconDataCallback* aCallback,

View file

@ -16,6 +16,8 @@
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h" #include "nsComponentManagerUtils.h"
#include "nsIFaviconService.h" #include "nsIFaviconService.h"
#include "nsINamed.h"
#include "nsITimer.h"
#include "nsServiceManagerUtils.h" #include "nsServiceManagerUtils.h"
#include "nsString.h" #include "nsString.h"
#include "nsTHashtable.h" #include "nsTHashtable.h"
@ -30,7 +32,21 @@ extern const uint16_t gFaviconSizes[7];
// forward class definitions // forward class definitions
class mozIStorageStatementCallback; class mozIStorageStatementCallback;
class nsFaviconService final : public nsIFaviconService { class UnassociatedIconHashKey : public nsURIHashKey {
public:
explicit UnassociatedIconHashKey(const nsIURI* aURI)
: nsURIHashKey(aURI), created(PR_Now()) {}
UnassociatedIconHashKey(UnassociatedIconHashKey&& aOther) noexcept
: nsURIHashKey(std::move(aOther)),
iconData(std::move(aOther.iconData)),
created(std::move(aOther.created)) {}
mozilla::places::IconData iconData;
PRTime created;
};
class nsFaviconService final : public nsIFaviconService,
public nsITimerCallback,
public nsINamed {
public: public:
nsFaviconService(); nsFaviconService();
@ -91,6 +107,8 @@ class nsFaviconService final : public nsIFaviconService {
NS_DECL_ISUPPORTS NS_DECL_ISUPPORTS
NS_DECL_NSIFAVICONSERVICE NS_DECL_NSIFAVICONSERVICE
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSINAMED
private: private:
imgITools* GetImgTools() { imgITools* GetImgTools() {
@ -104,6 +122,7 @@ class nsFaviconService final : public nsIFaviconService {
RefPtr<mozilla::places::Database> mDB; RefPtr<mozilla::places::Database> mDB;
nsCOMPtr<nsITimer> mExpireUnassociatedIconsTimer;
nsCOMPtr<imgITools> mImgTools; nsCOMPtr<imgITools> mImgTools;
static nsFaviconService* gFaviconService; static nsFaviconService* gFaviconService;
@ -115,6 +134,11 @@ class nsFaviconService final : public nsIFaviconService {
* they get back. May be null, in which case it needs initialization. * they get back. May be null, in which case it needs initialization.
*/ */
nsCOMPtr<nsIURI> mDefaultIcon; nsCOMPtr<nsIURI> mDefaultIcon;
// This class needs access to the icons cache.
friend class mozilla::places::AsyncReplaceFaviconData;
nsTHashtable<UnassociatedIconHashKey> mUnassociatedIcons;
uint16_t mDefaultIconURIPreferredSize; uint16_t mDefaultIconURIPreferredSize;
}; };

View file

@ -9,7 +9,6 @@ interface nsIURI;
interface nsIPrincipal; interface nsIPrincipal;
interface mozIPlacesPendingOperation; interface mozIPlacesPendingOperation;
interface nsIFaviconDataCallback; interface nsIFaviconDataCallback;
interface PlacesCompletionCallback;
[scriptable, uuid(e81e0b0c-b9f1-4c2e-8f3c-b809933cf73c)] [scriptable, uuid(e81e0b0c-b9f1-4c2e-8f3c-b809933cf73c)]
interface nsIFaviconService : nsISupports interface nsIFaviconService : nsISupports
@ -142,35 +141,72 @@ interface nsIFaviconService : nsISupports
[optional] in unsigned long long aRequestContextID); [optional] in unsigned long long aRequestContextID);
/** /**
* Stores the relation between a page URI and a favicon URI, whose icon data * Sets the data for a given favicon URI either by replacing existing data in
* is provided through a data URL. * the database or taking the place of otherwise fetched icon data when
* The process is asynchronous and a callback with the status of the operation * calling setAndFetchFaviconForPage later.
* will be invoked at the end of it. *
* Favicon data for favicon URIs that are not associated with a page URI via
* setAndFetchFaviconForPage will be stored in memory, but may be expired at
* any time, so you should make an effort to associate favicon URIs with page
* URIs as soon as possible.
*
* It's better to not use this function for chrome: icon URIs since you can
* reference the chrome image yourself. getFaviconLinkForIcon/Page will ignore
* any associated data if the favicon URI is "chrome:" and just return the
* same chrome URI.
*
* This function does NOT send out notifications that the data has changed.
* Pages using this favicons that are visible in history or bookmarks views
* will keep the old icon until they have been refreshed by other means.
*
* This function tries to optimize the favicon size, if it is bigger
* than a defined limit we will try to convert it to a 16x16 png image.
* If the conversion fails and favicon is still bigger than our max accepted
* size it won't be saved.
* *
* @param aPageURI
* URI of the page whose favicon is being set.
* @param aFaviconURI * @param aFaviconURI
* URI of the favicon to associate with the page. * URI of the favicon whose data is being set.
* @param aDataURL * @param aData
* String that represents a data URL to replace as the favicon content. * Binary contents of the favicon to save
* @param aMimeType
* MIME type of the data to store. This is important so that we know
* what to report when the favicon is used. You should always set this
* param unless you are clearing an icon.
* @param [optional] aExpiration * @param [optional] aExpiration
* Time in microseconds since the epoch when this favicon expires. * Time in microseconds since the epoch when this favicon expires.
* Until this time, we won't try to load it again. * Until this time, we won't try to load it again.
* If this argument is omitted, the expiration defaults to * @throws NS_ERROR_FAILURE
* 7 days (FaviconHelpers::MAX_FAVICON_EXPIRATION) from now. * Thrown if the favicon is overbloated and won't be saved to the db.
* @param [optional] aCallback
* Once we're done setting and/or fetching the favicon, we invoke this
* callback.
*
* @see PlacesCompletionCallback in PlacesCompletionCallback.idl.
*/ */
void setFaviconForPage( void replaceFaviconData(in nsIURI aFaviconURI,
in nsIURI aPageURI, in Array<octet> aData,
in nsIURI aFaviconURI, in AUTF8String aMimeType,
in nsIURI aDataURL, [optional] in PRTime aExpiration);
/**
* Same as replaceFaviconData but the data is provided by a string
* containing a data URL.
*
* @see replaceFaviconData
*
* @param aFaviconURI
* URI of the favicon whose data is being set.
* @param aDataURL
* string containing a data URL that represents the contents of
* the favicon to save
* @param [optional] aExpiration
* Time in microseconds since the epoch when this favicon expires.
* Until this time, we won't try to load it again.
* @param [optional] aLoadingPrincipal
* Principal of the page whose favicon is being set. If this argument
* is omitted, the loadingPrincipal defaults to the nullPrincipal.
* @throws NS_ERROR_FAILURE
* Thrown if the favicon is overbloated and won't be saved to the db.
*/
void replaceFaviconDataFromDataURL(in nsIURI aFaviconURI,
in AString aDataURL,
[optional] in PRTime aExpiration, [optional] in PRTime aExpiration,
[optional] in PlacesCompletionCallback aCallback [optional] in nsIPrincipal aLoadingPrincipal);
);
/** /**
* Retrieves the favicon URI associated to the given page, if any. * Retrieves the favicon URI associated to the given page, if any.

View file

@ -156,43 +156,6 @@ export var PlacesTestUtils = Object.freeze({
await Promise.all(faviconPromises); await Promise.all(faviconPromises);
}, },
/*
* Helper function to call PlacesUtils.favicons.setFaviconForPage() and waits
* finishing setting. This function throws an error if the status of
* PlacesUtils.favicons.setFaviconForPage() is not success.
*
* @param {string or nsIURI} pageURI
* @param {string or nsIURI} faviconURI
* @param {string or nsIURI} faviconDataURL
* @param {Number} [optional] expiration
* @return {Promise} waits for finishing setting
*/
setFaviconForPage(pageURI, faviconURI, faviconDataURL, expiration = 0) {
return new Promise((resolve, reject) => {
lazy.PlacesUtils.favicons.setFaviconForPage(
pageURI instanceof Ci.nsIURI ? pageURI : Services.io.newURI(pageURI),
faviconURI instanceof Ci.nsIURI
? faviconURI
: Services.io.newURI(faviconURI),
faviconDataURL instanceof Ci.nsIURI
? faviconDataURL
: Services.io.newURI(faviconDataURL),
expiration,
status => {
if (Components.isSuccessCode(status)) {
resolve(status);
} else {
reject(
new Error(
`Failed to process setFaviconForPage(): status code = ${status}`
)
);
}
}
);
});
},
/** /**
* Clears any favicons stored in the database. * Clears any favicons stored in the database.
*/ */

View file

@ -104,12 +104,19 @@ function test()
// icon with a page explicitly in order for it to be visible through // icon with a page explicitly in order for it to be visible through
// the protocol. // the protocol.
info("Replace favicon data"); info("Replace favicon data");
PlacesUtils.favicons.setFaviconForPage( var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
.createInstance(Ci.nsIPrincipal);
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
Services.io.newURI(tests[1].url),
tests[1].expectedIcon,
(Date.now() + 86400) * 1000,
systemPrincipal);
info("Set favicon data");
PlacesUtils.favicons.setAndFetchFaviconForPage(
Services.io.newURI("https://example.com/favicon_annotations"), Services.io.newURI("https://example.com/favicon_annotations"),
Services.io.newURI(tests[1].url), Services.io.newURI(tests[1].url),
Services.io.newURI(tests[1].expectedIcon), true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
(Date.now() + 86400) * 1000, systemPrincipal);
);
// And start our test process. // And start our test process.
loadNextTest(); loadNextTest();

View file

@ -356,7 +356,13 @@ add_task(async function test_expire_icons() {
} }
if (entry.icon) { if (entry.icon) {
await PlacesTestUtils.setFaviconForPage(entry.page, entry.icon, dataUrl); PlacesUtils.favicons.replaceFaviconDataFromDataURL(
Services.io.newURI(entry.icon),
dataUrl,
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
await PlacesTestUtils.addFavicons(new Map([[entry.page, entry.icon]]));
Assert.equal( Assert.equal(
await getFaviconUrlForPage(entry.page), await getFaviconUrlForPage(entry.page),
entry.icon, entry.icon,
@ -374,7 +380,13 @@ add_task(async function test_expire_icons() {
} }
if (entry.root) { if (entry.root) {
await PlacesTestUtils.setFaviconForPage(entry.page, entry.root, dataUrl); PlacesUtils.favicons.replaceFaviconDataFromDataURL(
Services.io.newURI(entry.root),
dataUrl,
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
await PlacesTestUtils.addFavicons(new Map([[entry.page, entry.root]]));
} }
if (entry.iconExpired) { if (entry.iconExpired) {

View file

@ -15,9 +15,6 @@
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
// Used in createFavicon().
let uniqueFaviconId = 0;
/** /**
* Checks that the favicon for the given page matches the provided data. * Checks that the favicon for the given page matches the provided data.
* *
@ -27,7 +24,6 @@ let uniqueFaviconId = 0;
* Expected MIME type of the icon, for example "image/png". * Expected MIME type of the icon, for example "image/png".
* @param aExpectedData * @param aExpectedData
* Expected icon data, expressed as an array of byte values. * Expected icon data, expressed as an array of byte values.
* If set null, skip the test for the favicon data.
* @param aCallback * @param aCallback
* This function is called after the check finished. * This function is called after the check finished.
*/ */
@ -41,9 +37,7 @@ function checkFaviconDataForPage(
aPageURI, aPageURI,
async function (aURI, aDataLen, aData, aMimeType) { async function (aURI, aDataLen, aData, aMimeType) {
Assert.equal(aExpectedMimeType, aMimeType); Assert.equal(aExpectedMimeType, aMimeType);
if (aExpectedData) {
Assert.ok(compareArrays(aExpectedData, aData)); Assert.ok(compareArrays(aExpectedData, aData));
}
await check_guid_for_uri(aPageURI); await check_guid_for_uri(aPageURI);
aCallback(); aCallback();
} }
@ -82,82 +76,3 @@ function promiseFaviconChanged(aExpectedPageURI, aExpectedFaviconURI) {
}); });
}); });
} }
/**
* Create favicon file to temp directory.
*
* @param {string} aFileName
* File name that will be created in temp directory.
* @returns {object}
* {
* file: nsIFile,
* uri: nsIURI,
* data: byte Array,
* mimetype: String,
* }
*/
async function createFavicon(aFileName) {
// Copy the favicon file we have to the specified file in temp directory.
let originalFaviconFile = do_get_file("favicon-normal16.png");
let tempDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
let faviconFile = tempDir.clone();
faviconFile.append(aFileName);
await IOUtils.copy(originalFaviconFile.path, faviconFile.path);
// Append some data that sniffers/encoders will ignore that will distinguish
// the different favicons we'll create.
let stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
Ci.nsIFileOutputStream
);
const WRONLY_PERMISSION = 0o600;
stream.init(
faviconFile,
FileUtils.MODE_WRONLY | FileUtils.MODE_APPEND,
WRONLY_PERMISSION,
0
);
uniqueFaviconId++;
let uniqueStr = "uid:" + uniqueFaviconId;
stream.write(uniqueStr, uniqueStr.length);
stream.close();
Assert.equal(faviconFile.leafName.substr(0, aFileName.length), aFileName);
return {
file: faviconFile,
uri: uri(faviconFile),
data: readFileData(faviconFile),
mimeType: "image/png",
};
}
/**
* Create nsIURI for given favicon object.
*
* @param {object} aFavicon
* Favicon object created by createFavicon().
* @returns {nsIURI}
*/
async function createDataURLForFavicon(aFavicon) {
let dataURL = await toDataURL(aFavicon.data, aFavicon.mimeType);
return uri(dataURL);
}
/**
* Create data URL string from given byte array and type.
*
* @param {Array} data
* Byte array.
* @param {string} type
* The type of this data.
* @returns {string}
*/
function toDataURL(data, type) {
let blob = new Blob([new Uint8Array(data)], { type });
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.addEventListener("load", () => resolve(reader.result));
reader.addEventListener("error", reject);
reader.readAsDataURL(blob);
});
}

View file

@ -68,12 +68,14 @@ add_task(async function () {
info("Test that the content type of a favicon we add is correct."); info("Test that the content type of a favicon we add is correct.");
let testURI = uri("http://mozilla.org/"); let testURI = uri("http://mozilla.org/");
// Add the data before opening // Add the data before opening
await PlacesTestUtils.addVisits(testURI); PlacesUtils.favicons.replaceFaviconDataFromDataURL(
await PlacesTestUtils.setFaviconForPage(
testURI,
testIconURI, testIconURI,
testFaviconData testFaviconData,
0,
systemPrincipal
); );
await PlacesTestUtils.addVisits(testURI);
await setFaviconForPage(testURI, testIconURI);
// Open the channel // Open the channel
let channel = NetUtil.newChannel({ let channel = NetUtil.newChannel({
uri: PlacesUtils.favicons.getFaviconLinkForIcon(testIconURI).spec, uri: PlacesUtils.favicons.getFaviconLinkForIcon(testIconURI).spec,
@ -118,12 +120,14 @@ add_task(async function test_userpass() {
CACHED_ICON_NORMAL, CACHED_ICON_NORMAL,
CACHED_ICON_USERPASS, CACHED_ICON_USERPASS,
]) { ]) {
await PlacesTestUtils.addVisits(pageURI); PlacesUtils.favicons.replaceFaviconDataFromDataURL(
await PlacesTestUtils.setFaviconForPage(
pageURI,
iconURI, iconURI,
testFaviconData testFaviconData,
0,
systemPrincipal
); );
await PlacesTestUtils.addVisits(pageURI);
await setFaviconForPage(pageURI, iconURI);
// Open the channel // Open the channel
let channel = NetUtil.newChannel({ let channel = NetUtil.newChannel({

View file

@ -28,23 +28,23 @@ add_task(async function test_expire_associated() {
]; ];
for (let icon of favicons) { for (let icon of favicons) {
let dataURL = await readFileDataAsDataURL( let data = readFileData(do_get_file(icon.name));
do_get_file(icon.name), PlacesUtils.favicons.replaceFaviconData(
NetUtil.newURI(TEST_URL + icon.name),
data,
icon.mimeType icon.mimeType
); );
await PlacesTestUtils.setFaviconForPage( await setFaviconForPage(TEST_URL, TEST_URL + icon.name);
TEST_URL,
TEST_URL + icon.name,
dataURL
);
if (icon.expired) { if (icon.expired) {
await expireIconRelationsForPage(TEST_URL); await expireIconRelationsForPage(TEST_URL);
// Add the same icon to another page. // Add the same icon to another page.
await PlacesTestUtils.setFaviconForPage( PlacesUtils.favicons.replaceFaviconData(
TEST_URL2, NetUtil.newURI(TEST_URL + icon.name),
TEST_URL + icon.name, data,
dataURL icon.mimeType,
icon.expire
); );
await setFaviconForPage(TEST_URL2, TEST_URL + icon.name);
} }
} }
@ -88,7 +88,13 @@ add_task(async function test_expire_root() {
// Insert an expired icon. // Insert an expired icon.
let iconURI = NetUtil.newURI(pageURI.spec + "favicon-normal16.png"); let iconURI = NetUtil.newURI(pageURI.spec + "favicon-normal16.png");
await PlacesTestUtils.setFaviconForPage(pageURI, iconURI, SMALLPNG_DATA_URI); PlacesUtils.favicons.replaceFaviconDataFromDataURL(
iconURI,
SMALLPNG_DATA_URI.spec,
0,
systemPrincipal
);
await setFaviconForPage(pageURI, iconURI);
Assert.equal( Assert.equal(
await countEntries("moz_icons_to_pages"), await countEntries("moz_icons_to_pages"),
1, 1,
@ -99,11 +105,13 @@ add_task(async function test_expire_root() {
// Now insert a new root icon. // Now insert a new root icon.
let rootIconURI = NetUtil.newURI(pageURI.spec + "favicon.ico"); let rootIconURI = NetUtil.newURI(pageURI.spec + "favicon.ico");
await PlacesTestUtils.setFaviconForPage( PlacesUtils.favicons.replaceFaviconDataFromDataURL(
pageURI,
rootIconURI, rootIconURI,
SMALLPNG_DATA_URI SMALLPNG_DATA_URI.spec,
0,
systemPrincipal
); );
await setFaviconForPage(pageURI, rootIconURI);
// Only the root icon should have survived. // Only the root icon should have survived.
Assert.equal( Assert.equal(

View file

@ -43,24 +43,29 @@ async function checkFaviconDataConversion(
}); });
let faviconURI = NetUtil.newURI("http://places.test/icon/" + aFileName); let faviconURI = NetUtil.newURI("http://places.test/icon/" + aFileName);
let fileData = readFileOfLength(aFileName, aFileLength); let fileData = readFileOfLength(aFileName, aFileLength);
let fileDataURL = await fileDataToDataURL(fileData, aFileMimeType);
await PlacesTestUtils.setFaviconForPage(
pageURI.spec,
faviconURI.spec,
fileDataURL
);
PlacesUtils.favicons.replaceFaviconData(faviconURI, fileData, aFileMimeType);
await new Promise(resolve => { await new Promise(resolve => {
PlacesUtils.favicons.setAndFetchFaviconForPage(
pageURI,
faviconURI,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
(aURI, aDataLen, aData, aMimeType) => {
if (!aExpectConversion) { if (!aExpectConversion) {
checkFaviconDataForPage(pageURI, aFileMimeType, fileData, resolve); Assert.ok(compareArrays(aData, fileData));
} else if (!aVaryOnWindows || !isWindows) { Assert.equal(aMimeType, aFileMimeType);
let expectedFile = do_get_file("expected-" + aFileName + ".png");
let expectedData = readFileData(expectedFile);
checkFaviconDataForPage(pageURI, "image/png", expectedData, resolve);
} else { } else {
// Not check the favicon data. if (!aVaryOnWindows || !isWindows) {
checkFaviconDataForPage(pageURI, "image/png", null, resolve); let expectedFile = do_get_file("expected-" + aFileName + ".png");
Assert.ok(compareArrays(aData, readFileData(expectedFile)));
} }
Assert.equal(aMimeType, "image/png");
}
resolve();
},
Services.scriptSecurityManager.getSystemPrincipal()
);
}); });
} }

View file

@ -9,16 +9,21 @@ const ICON32_URL = "http://places.test/favicon-normal32.png";
add_task(async function () { add_task(async function () {
await PlacesTestUtils.addVisits(PAGE_URL); await PlacesTestUtils.addVisits(PAGE_URL);
// Add 2 differently sized favicons for this page. // Add 2 differently sized favicons for this page.
let dataURL16 = await readFileDataAsDataURL(
do_get_file("favicon-normal16.png"), let data = readFileData(do_get_file("favicon-normal16.png"));
PlacesUtils.favicons.replaceFaviconData(
Services.io.newURI(ICON16_URL),
data,
"image/png" "image/png"
); );
await PlacesTestUtils.setFaviconForPage(PAGE_URL, ICON16_URL, dataURL16); await setFaviconForPage(PAGE_URL, ICON16_URL);
let dataURL32 = await readFileDataAsDataURL( data = readFileData(do_get_file("favicon-normal32.png"));
do_get_file("favicon-normal32.png"), PlacesUtils.favicons.replaceFaviconData(
Services.io.newURI(ICON32_URL),
data,
"image/png" "image/png"
); );
await PlacesTestUtils.setFaviconForPage(PAGE_URL, ICON32_URL, dataURL32); await setFaviconForPage(PAGE_URL, ICON32_URL);
const PAGE_ICON_URL = "page-icon:" + PAGE_URL; const PAGE_ICON_URL = "page-icon:" + PAGE_URL;

View file

@ -60,8 +60,12 @@ add_task(async function test_fallback() {
info("Set icon for the root"); info("Set icon for the root");
await PlacesTestUtils.addVisits(ROOT_URL); await PlacesTestUtils.addVisits(ROOT_URL);
let data = readFileData(do_get_file("favicon-normal16.png")); let data = readFileData(do_get_file("favicon-normal16.png"));
let dataURL = await fileDataToDataURL(data, "image/png"); PlacesUtils.favicons.replaceFaviconData(
await PlacesTestUtils.setFaviconForPage(ROOT_URL, ROOT_ICON_URL, dataURL); NetUtil.newURI(ROOT_ICON_URL),
data,
"image/png"
);
await setFaviconForPage(ROOT_URL, ROOT_ICON_URL);
info("check fallback icons"); info("check fallback icons");
await new Promise(resolve => { await new Promise(resolve => {
@ -92,8 +96,12 @@ add_task(async function test_fallback() {
info("Now add a proper icon for the page"); info("Now add a proper icon for the page");
await PlacesTestUtils.addVisits(SUBPAGE_URL); await PlacesTestUtils.addVisits(SUBPAGE_URL);
let data32 = readFileData(do_get_file("favicon-normal32.png")); let data32 = readFileData(do_get_file("favicon-normal32.png"));
let dataURL32 = await fileDataToDataURL(data32, "image/png"); PlacesUtils.favicons.replaceFaviconData(
await PlacesTestUtils.setFaviconForPage(SUBPAGE_URL, ICON32_URL, dataURL32); NetUtil.newURI(ICON32_URL),
data32,
"image/png"
);
await setFaviconForPage(SUBPAGE_URL, ICON32_URL);
info("check no fallback icons"); info("check no fallback icons");
await new Promise(resolve => { await new Promise(resolve => {

View file

@ -57,11 +57,13 @@ add_task(async function test_fallback() {
info("Set icon for the root"); info("Set icon for the root");
await PlacesTestUtils.addVisits(ROOT_URL); await PlacesTestUtils.addVisits(ROOT_URL);
let dataURL = await readFileDataAsDataURL( let data = readFileData(do_get_file("favicon-normal16.png"));
do_get_file("favicon-normal16.png"), PlacesUtils.favicons.replaceFaviconData(
NetUtil.newURI(ROOT_ICON_URL),
data,
"image/png" "image/png"
); );
await PlacesTestUtils.setFaviconForPage(ROOT_URL, ROOT_ICON_URL, dataURL); await setFaviconForPage(ROOT_URL, ROOT_ICON_URL);
info("check fallback icons"); info("check fallback icons");
Assert.equal( Assert.equal(
@ -77,11 +79,13 @@ add_task(async function test_fallback() {
info("Now add a proper icon for the page"); info("Now add a proper icon for the page");
await PlacesTestUtils.addVisits(SUBPAGE_URL); await PlacesTestUtils.addVisits(SUBPAGE_URL);
let dataURL32 = await readFileDataAsDataURL( let data32 = readFileData(do_get_file("favicon-normal32.png"));
do_get_file("favicon-normal32.png"), PlacesUtils.favicons.replaceFaviconData(
NetUtil.newURI(ICON32_URL),
data32,
"image/png" "image/png"
); );
await PlacesTestUtils.setFaviconForPage(SUBPAGE_URL, ICON32_URL, dataURL32); await setFaviconForPage(SUBPAGE_URL, ICON32_URL);
info("check no fallback icons"); info("check no fallback icons");
Assert.equal( Assert.equal(

View file

@ -26,8 +26,8 @@ add_task(async function () {
let pageURI = uri("http://foo.bar/"); let pageURI = uri("http://foo.bar/");
await PlacesTestUtils.addVisits(pageURI); await PlacesTestUtils.addVisits(pageURI);
let dataURI = await fileDataToDataURL(icon.data, icon.mimetype); PlacesUtils.favicons.replaceFaviconData(icon.uri, icon.data, icon.mimetype);
await PlacesTestUtils.setFaviconForPage(pageURI.spec, icon.uri.spec, dataURI); await setFaviconForPage(pageURI, icon.uri);
Assert.equal( Assert.equal(
await getFaviconUrlForPage(pageURI), await getFaviconUrlForPage(pageURI),
icon.uri.spec, icon.uri.spec,

View file

@ -16,9 +16,10 @@ add_task(async function () {
let url = "http://foo.bar/"; let url = "http://foo.bar/";
await PlacesTestUtils.addVisits(url); await PlacesTestUtils.addVisits(url);
for (let i = 0; i < 10; ++i) { for (let i = 0; i < 10; ++i) {
let iconUri = "http://mozilla.org/" + i; let iconUri = NetUtil.newURI("http://mozilla.org/" + i);
let dataURL = await readFileDataAsDataURL(icon.file, icon.mimetype); let data = readFileData(icon.file);
await PlacesTestUtils.setFaviconForPage(url, iconUri, dataURL); PlacesUtils.favicons.replaceFaviconData(iconUri, data, icon.mimetype);
await setFaviconForPage(url, iconUri);
} }
let promise = TestUtils.topicObserved("places-favicons-expired"); let promise = TestUtils.topicObserved("places-favicons-expired");

View file

@ -14,15 +14,9 @@ add_task(async function () {
let faviconURI = NetUtil.newURI("http://places.test/icon/favicon-multi.ico"); let faviconURI = NetUtil.newURI("http://places.test/icon/favicon-multi.ico");
// Fake window. // Fake window.
let win = { devicePixelRatio: 1.0 }; let win = { devicePixelRatio: 1.0 };
let icoDataURL = await readFileDataAsDataURL( let icoData = readFileData(do_get_file("favicon-multi.ico"));
do_get_file("favicon-multi.ico"), PlacesUtils.favicons.replaceFaviconData(faviconURI, icoData, "image/x-icon");
"image/x-icon" await setFaviconForPage(pageURI, faviconURI);
);
await PlacesTestUtils.setFaviconForPage(
pageURI.spec,
faviconURI.spec,
icoDataURL
);
for (let size of [16, 32, 64]) { for (let size of [16, 32, 64]) {
let file = do_get_file(`favicon-multi-frame${size}.png`); let file = do_get_file(`favicon-multi-frame${size}.png`);

View file

@ -76,12 +76,25 @@ var gFavicon;
add_task(async function setup() { add_task(async function setup() {
await PlacesTestUtils.addVisits(TEST_URI); await PlacesTestUtils.addVisits(TEST_URI);
await PlacesTestUtils.setFaviconForPage(
TEST_URI, PlacesUtils.favicons.replaceFaviconDataFromDataURL(
ICON_URI, ICON_URI,
ICON_DATAURL, ICON_DATAURL,
(Date.now() + 8640000) * 1000 (Date.now() + 8640000) * 1000,
Services.scriptSecurityManager.getSystemPrincipal()
); );
await new Promise(resolve => {
PlacesUtils.favicons.setAndFetchFaviconForPage(
TEST_URI,
ICON_URI,
false,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
resolve,
Services.scriptSecurityManager.getSystemPrincipal()
);
});
gDefaultFavicon = await fetchIconForSpec( gDefaultFavicon = await fetchIconForSpec(
PlacesUtils.favicons.defaultFavicon.spec PlacesUtils.favicons.defaultFavicon.spec
); );
@ -120,11 +133,13 @@ add_task(async function subpage_url_fallback() {
add_task(async function svg_icon() { add_task(async function svg_icon() {
let faviconURI = NetUtil.newURI("http://places.test/favicon.svg"); let faviconURI = NetUtil.newURI("http://places.test/favicon.svg");
await PlacesTestUtils.setFaviconForPage( PlacesUtils.favicons.replaceFaviconDataFromDataURL(
TEST_URI,
faviconURI, faviconURI,
SMALLSVG_DATA_URI SMALLSVG_DATA_URI.spec,
0,
Services.scriptSecurityManager.getSystemPrincipal()
); );
await setFaviconForPage(TEST_URI, faviconURI);
let svgIcon = await fetchIconForSpec(SMALLSVG_DATA_URI.spec); let svgIcon = await fetchIconForSpec(SMALLSVG_DATA_URI.spec);
info(svgIcon.contentType); info(svgIcon.contentType);
let pageIcon = await fetchIconForSpec("page-icon:" + TEST_URI.spec); let pageIcon = await fetchIconForSpec("page-icon:" + TEST_URI.spec);
@ -255,8 +270,23 @@ add_task(async function test_with_user_pass() {
for (const { pageURI, iconURI } of testData) { for (const { pageURI, iconURI } of testData) {
for (const loadingIconURISpec of [PAGE_ICON_NORMAL, PAGE_ICON_USERPASS]) { for (const loadingIconURISpec of [PAGE_ICON_NORMAL, PAGE_ICON_USERPASS]) {
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
iconURI,
ICON_DATAURL,
0,
systemPrincipal
);
await PlacesTestUtils.addVisits(pageURI); await PlacesTestUtils.addVisits(pageURI);
await PlacesTestUtils.setFaviconForPage(pageURI, iconURI, ICON_DATAURL); await new Promise(resolve => {
PlacesUtils.favicons.setAndFetchFaviconForPage(
pageURI,
iconURI,
false,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
resolve,
Services.scriptSecurityManager.getSystemPrincipal()
);
});
let { data, contentType } = await fetchIconForSpec(loadingIconURISpec); let { data, contentType } = await fetchIconForSpec(loadingIconURISpec);
Assert.equal(contentType, gFavicon.contentType); Assert.equal(contentType, gFavicon.contentType);

View file

@ -0,0 +1,395 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests for replaceFaviconData()
*/
var iconsvc = PlacesUtils.favicons;
var originalFavicon = {
file: do_get_file("favicon-normal16.png"),
uri: uri(do_get_file("favicon-normal16.png")),
data: readFileData(do_get_file("favicon-normal16.png")),
mimetype: "image/png",
};
var uniqueFaviconId = 0;
function createFavicon(fileName) {
let tempdir = Services.dirsvc.get("TmpD", Ci.nsIFile);
// remove any existing file at the path we're about to copy to
let outfile = tempdir.clone();
outfile.append(fileName);
try {
outfile.remove(false);
} catch (e) {}
originalFavicon.file.copyToFollowingLinks(tempdir, fileName);
let stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
Ci.nsIFileOutputStream
);
stream.init(outfile, 0x02 | 0x08 | 0x10, 0o600, 0);
// append some data that sniffers/encoders will ignore that will distinguish
// the different favicons we'll create
uniqueFaviconId++;
let uniqueStr = "uid:" + uniqueFaviconId;
stream.write(uniqueStr, uniqueStr.length);
stream.close();
Assert.equal(outfile.leafName.substr(0, fileName.length), fileName);
return {
file: outfile,
uri: uri(outfile),
data: readFileData(outfile),
mimetype: "image/png",
};
}
function checkCallbackSucceeded(
callbackMimetype,
callbackData,
sourceMimetype,
sourceData
) {
Assert.equal(callbackMimetype, sourceMimetype);
Assert.ok(compareArrays(callbackData, sourceData));
}
function run_test() {
// check that the favicon loaded correctly
Assert.equal(originalFavicon.data.length, 286);
run_next_test();
}
add_task(async function test_replaceFaviconData_validHistoryURI() {
info("test replaceFaviconData for valid history uri");
let pageURI = uri("http://test1.bar/");
await PlacesTestUtils.addVisits(pageURI);
let favicon = createFavicon("favicon1.png");
iconsvc.replaceFaviconData(favicon.uri, favicon.data, favicon.mimetype);
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
favicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconData_validHistoryURI_check(
aURI,
aDataLen,
aData,
aMimeType
) {
dump("GOT " + aMimeType + "\n");
checkCallbackSucceeded(
aMimeType,
aData,
favicon.mimetype,
favicon.data
);
checkFaviconDataForPage(
pageURI,
favicon.mimetype,
favicon.data,
function test_replaceFaviconData_validHistoryURI_callback() {
favicon.file.remove(false);
resolve();
}
);
},
systemPrincipal
);
});
await PlacesUtils.history.clear();
});
add_task(async function test_replaceFaviconData_overrideDefaultFavicon() {
info("test replaceFaviconData to override a later setAndFetchFaviconForPage");
let pageURI = uri("http://test2.bar/");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = createFavicon("favicon2.png");
let secondFavicon = createFavicon("favicon3.png");
iconsvc.replaceFaviconData(
firstFavicon.uri,
secondFavicon.data,
secondFavicon.mimetype
);
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
firstFavicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconData_overrideDefaultFavicon_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
secondFavicon.mimetype,
secondFavicon.data
);
checkFaviconDataForPage(
pageURI,
secondFavicon.mimetype,
secondFavicon.data,
function test_replaceFaviconData_overrideDefaultFavicon_callback() {
firstFavicon.file.remove(false);
secondFavicon.file.remove(false);
resolve();
}
);
},
systemPrincipal
);
});
await PlacesUtils.history.clear();
});
add_task(async function test_replaceFaviconData_replaceExisting() {
info(
"test replaceFaviconData to override a previous setAndFetchFaviconForPage"
);
let pageURI = uri("http://test3.bar");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = createFavicon("favicon4.png");
let secondFavicon = createFavicon("favicon5.png");
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
firstFavicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconData_replaceExisting_firstSet_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
firstFavicon.mimetype,
firstFavicon.data
);
checkFaviconDataForPage(
pageURI,
firstFavicon.mimetype,
firstFavicon.data,
function test_replaceFaviconData_overrideDefaultFavicon_firstCallback() {
iconsvc.replaceFaviconData(
firstFavicon.uri,
secondFavicon.data,
secondFavicon.mimetype
);
PlacesTestUtils.promiseAsyncUpdates().then(() => {
checkFaviconDataForPage(
pageURI,
secondFavicon.mimetype,
secondFavicon.data,
function test_replaceFaviconData_overrideDefaultFavicon_secondCallback() {
firstFavicon.file.remove(false);
secondFavicon.file.remove(false);
resolve();
},
systemPrincipal
);
});
}
);
},
systemPrincipal
);
});
await PlacesUtils.history.clear();
});
add_task(async function test_replaceFaviconData_unrelatedReplace() {
info("test replaceFaviconData to not make unrelated changes");
let pageURI = uri("http://test4.bar/");
await PlacesTestUtils.addVisits(pageURI);
let favicon = createFavicon("favicon6.png");
let unrelatedFavicon = createFavicon("favicon7.png");
iconsvc.replaceFaviconData(
unrelatedFavicon.uri,
unrelatedFavicon.data,
unrelatedFavicon.mimetype
);
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
favicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconData_unrelatedReplace_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
favicon.mimetype,
favicon.data
);
checkFaviconDataForPage(
pageURI,
favicon.mimetype,
favicon.data,
function test_replaceFaviconData_unrelatedReplace_callback() {
favicon.file.remove(false);
unrelatedFavicon.file.remove(false);
resolve();
}
);
},
systemPrincipal
);
});
await PlacesUtils.history.clear();
});
add_task(async function test_replaceFaviconData_badInputs() {
info("test replaceFaviconData to throw on bad inputs");
let icon = createFavicon("favicon8.png");
Assert.throws(
() => iconsvc.replaceFaviconData(icon.uri, icon.data, ""),
/NS_ERROR_ILLEGAL_VALUE/
);
Assert.throws(
() => iconsvc.replaceFaviconData(icon.uri, icon.data, "not-an-image"),
/NS_ERROR_ILLEGAL_VALUE/
);
Assert.throws(
() => iconsvc.replaceFaviconData(null, icon.data, icon.mimetype),
/NS_ERROR_ILLEGAL_VALUE/
);
Assert.throws(
() => iconsvc.replaceFaviconData(icon.uri, [], icon.mimetype),
/NS_ERROR_ILLEGAL_VALUE/
);
Assert.throws(
() => iconsvc.replaceFaviconData(icon.uri, null, icon.mimetype),
/NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY/
);
icon.file.remove(false);
await PlacesUtils.history.clear();
});
add_task(async function test_replaceFaviconData_twiceReplace() {
info("test replaceFaviconData on multiple replacements");
let pageURI = uri("http://test5.bar/");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = createFavicon("favicon9.png");
let secondFavicon = createFavicon("favicon10.png");
iconsvc.replaceFaviconData(
firstFavicon.uri,
firstFavicon.data,
firstFavicon.mimetype
);
iconsvc.replaceFaviconData(
firstFavicon.uri,
secondFavicon.data,
secondFavicon.mimetype
);
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
firstFavicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconData_twiceReplace_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
secondFavicon.mimetype,
secondFavicon.data
);
checkFaviconDataForPage(
pageURI,
secondFavicon.mimetype,
secondFavicon.data,
function test_replaceFaviconData_twiceReplace_callback() {
firstFavicon.file.remove(false);
secondFavicon.file.remove(false);
resolve();
},
systemPrincipal
);
},
systemPrincipal
);
});
await PlacesUtils.history.clear();
});
add_task(async function test_replaceFaviconData_rootOverwrite() {
info("test replaceFaviconData doesn't overwrite root = 1");
async function getRootValue(url) {
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.execute(
"SELECT root FROM moz_icons WHERE icon_url = :url",
{ url }
);
return rows[0].getResultByName("root");
}
const PAGE_URL = "http://rootoverwrite.bar/";
let pageURI = Services.io.newURI(PAGE_URL);
const ICON_URL = "http://rootoverwrite.bar/favicon.ico";
let iconURI = Services.io.newURI(ICON_URL);
await PlacesTestUtils.addVisits(pageURI);
let icon = createFavicon("favicon9.png");
PlacesUtils.favicons.replaceFaviconData(iconURI, icon.data, icon.mimetype);
await PlacesTestUtils.addFavicons(new Map([[PAGE_URL, ICON_URL]]));
Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1");
let icon2 = createFavicon("favicon10.png");
PlacesUtils.favicons.replaceFaviconData(iconURI, icon2.data, icon2.mimetype);
// replaceFaviconData doesn't have a callback, but we must wait its updated.
await PlacesTestUtils.promiseAsyncUpdates();
Assert.equal(await getRootValue(ICON_URL), 1, "Check root did not change");
await PlacesUtils.history.clear();
});

View file

@ -0,0 +1,537 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests for replaceFaviconData()
*/
var iconsvc = PlacesUtils.favicons;
var originalFavicon = {
file: do_get_file("favicon-normal16.png"),
uri: uri(do_get_file("favicon-normal16.png")),
data: readFileData(do_get_file("favicon-normal16.png")),
mimetype: "image/png",
};
var uniqueFaviconId = 0;
function createFavicon(fileName) {
let tempdir = Services.dirsvc.get("TmpD", Ci.nsIFile);
// remove any existing file at the path we're about to copy to
let outfile = tempdir.clone();
outfile.append(fileName);
try {
outfile.remove(false);
} catch (e) {}
originalFavicon.file.copyToFollowingLinks(tempdir, fileName);
let stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
Ci.nsIFileOutputStream
);
stream.init(outfile, 0x02 | 0x08 | 0x10, 0o600, 0);
// append some data that sniffers/encoders will ignore that will distinguish
// the different favicons we'll create
uniqueFaviconId++;
let uniqueStr = "uid:" + uniqueFaviconId;
stream.write(uniqueStr, uniqueStr.length);
stream.close();
Assert.equal(outfile.leafName.substr(0, fileName.length), fileName);
return {
file: outfile,
uri: uri(outfile),
data: readFileData(outfile),
mimetype: "image/png",
};
}
function createDataURLForFavicon(favicon) {
return "data:" + favicon.mimetype + ";base64," + toBase64(favicon.data);
}
function checkCallbackSucceeded(
callbackMimetype,
callbackData,
sourceMimetype,
sourceData
) {
Assert.equal(callbackMimetype, sourceMimetype);
Assert.ok(compareArrays(callbackData, sourceData));
}
function run_test() {
// check that the favicon loaded correctly
Assert.equal(originalFavicon.data.length, 286);
run_next_test();
}
add_task(async function test_replaceFaviconDataFromDataURL_validHistoryURI() {
info("test replaceFaviconDataFromDataURL for valid history uri");
let pageURI = uri("http://test1.bar/");
await PlacesTestUtils.addVisits(pageURI);
let favicon = createFavicon("favicon1.png");
iconsvc.replaceFaviconDataFromDataURL(
favicon.uri,
createDataURLForFavicon(favicon),
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
favicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconDataFromDataURL_validHistoryURI_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
favicon.mimetype,
favicon.data
);
checkFaviconDataForPage(
pageURI,
favicon.mimetype,
favicon.data,
function test_replaceFaviconDataFromDataURL_validHistoryURI_callback() {
favicon.file.remove(false);
resolve();
}
);
},
Services.scriptSecurityManager.getSystemPrincipal()
);
});
await PlacesUtils.history.clear();
});
add_task(
async function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon() {
info(
"test replaceFaviconDataFromDataURL to override a later setAndFetchFaviconForPage"
);
let pageURI = uri("http://test2.bar/");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = createFavicon("favicon2.png");
let secondFavicon = createFavicon("favicon3.png");
iconsvc.replaceFaviconDataFromDataURL(
firstFavicon.uri,
createDataURLForFavicon(secondFavicon),
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
firstFavicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
secondFavicon.mimetype,
secondFavicon.data
);
checkFaviconDataForPage(
pageURI,
secondFavicon.mimetype,
secondFavicon.data,
function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon_callback() {
firstFavicon.file.remove(false);
secondFavicon.file.remove(false);
resolve();
}
);
},
Services.scriptSecurityManager.getSystemPrincipal()
);
});
await PlacesUtils.history.clear();
}
);
add_task(async function test_replaceFaviconDataFromDataURL_replaceExisting() {
info(
"test replaceFaviconDataFromDataURL to override a previous setAndFetchFaviconForPage"
);
let pageURI = uri("http://test3.bar");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = createFavicon("favicon4.png");
let secondFavicon = createFavicon("favicon5.png");
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
firstFavicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconDataFromDataURL_replaceExisting_firstSet_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
firstFavicon.mimetype,
firstFavicon.data
);
checkFaviconDataForPage(
pageURI,
firstFavicon.mimetype,
firstFavicon.data,
function test_replaceFaviconDataFromDataURL_replaceExisting_firstCallback() {
iconsvc.replaceFaviconDataFromDataURL(
firstFavicon.uri,
createDataURLForFavicon(secondFavicon),
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
checkFaviconDataForPage(
pageURI,
secondFavicon.mimetype,
secondFavicon.data,
function test_replaceFaviconDataFromDataURL_replaceExisting_secondCallback() {
firstFavicon.file.remove(false);
secondFavicon.file.remove(false);
resolve();
}
);
}
);
},
Services.scriptSecurityManager.getSystemPrincipal()
);
});
await PlacesUtils.history.clear();
});
add_task(async function test_replaceFaviconDataFromDataURL_unrelatedReplace() {
info("test replaceFaviconDataFromDataURL to not make unrelated changes");
let pageURI = uri("http://test4.bar/");
await PlacesTestUtils.addVisits(pageURI);
let favicon = createFavicon("favicon6.png");
let unrelatedFavicon = createFavicon("favicon7.png");
iconsvc.replaceFaviconDataFromDataURL(
unrelatedFavicon.uri,
createDataURLForFavicon(unrelatedFavicon),
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
favicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconDataFromDataURL_unrelatedReplace_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
favicon.mimetype,
favicon.data
);
checkFaviconDataForPage(
pageURI,
favicon.mimetype,
favicon.data,
function test_replaceFaviconDataFromDataURL_unrelatedReplace_callback() {
favicon.file.remove(false);
unrelatedFavicon.file.remove(false);
resolve();
}
);
},
Services.scriptSecurityManager.getSystemPrincipal()
);
});
await PlacesUtils.history.clear();
});
add_task(async function test_replaceFaviconDataFromDataURL_badInputs() {
info("test replaceFaviconDataFromDataURL to throw on bad inputs");
let favicon = createFavicon("favicon8.png");
let ex = null;
try {
iconsvc.replaceFaviconDataFromDataURL(
favicon.uri,
"",
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
} catch (e) {
ex = e;
} finally {
Assert.ok(!!ex);
}
ex = null;
try {
iconsvc.replaceFaviconDataFromDataURL(
null,
createDataURLForFavicon(favicon),
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
} catch (e) {
ex = e;
} finally {
Assert.ok(!!ex);
}
favicon.file.remove(false);
await PlacesUtils.history.clear();
});
add_task(async function test_replaceFaviconDataFromDataURL_twiceReplace() {
info("test replaceFaviconDataFromDataURL on multiple replacements");
let pageURI = uri("http://test5.bar/");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = createFavicon("favicon9.png");
let secondFavicon = createFavicon("favicon10.png");
iconsvc.replaceFaviconDataFromDataURL(
firstFavicon.uri,
createDataURLForFavicon(firstFavicon),
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
iconsvc.replaceFaviconDataFromDataURL(
firstFavicon.uri,
createDataURLForFavicon(secondFavicon),
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
firstFavicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconDataFromDataURL_twiceReplace_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
secondFavicon.mimetype,
secondFavicon.data
);
checkFaviconDataForPage(
pageURI,
secondFavicon.mimetype,
secondFavicon.data,
function test_replaceFaviconDataFromDataURL_twiceReplace_callback() {
firstFavicon.file.remove(false);
secondFavicon.file.remove(false);
resolve();
}
);
},
Services.scriptSecurityManager.getSystemPrincipal()
);
});
await PlacesUtils.history.clear();
});
add_task(
async function test_replaceFaviconDataFromDataURL_afterRegularAssign() {
info("test replaceFaviconDataFromDataURL after replaceFaviconData");
let pageURI = uri("http://test6.bar/");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = createFavicon("favicon11.png");
let secondFavicon = createFavicon("favicon12.png");
iconsvc.replaceFaviconData(
firstFavicon.uri,
firstFavicon.data,
firstFavicon.mimetype
);
iconsvc.replaceFaviconDataFromDataURL(
firstFavicon.uri,
createDataURLForFavicon(secondFavicon),
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
firstFavicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconDataFromDataURL_afterRegularAssign_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
secondFavicon.mimetype,
secondFavicon.data
);
checkFaviconDataForPage(
pageURI,
secondFavicon.mimetype,
secondFavicon.data,
function test_replaceFaviconDataFromDataURL_afterRegularAssign_callback() {
firstFavicon.file.remove(false);
secondFavicon.file.remove(false);
resolve();
}
);
},
Services.scriptSecurityManager.getSystemPrincipal()
);
});
await PlacesUtils.history.clear();
}
);
add_task(
async function test_replaceFaviconDataFromDataURL_beforeRegularAssign() {
info("test replaceFaviconDataFromDataURL before replaceFaviconData");
let pageURI = uri("http://test7.bar/");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = createFavicon("favicon13.png");
let secondFavicon = createFavicon("favicon14.png");
iconsvc.replaceFaviconDataFromDataURL(
firstFavicon.uri,
createDataURLForFavicon(firstFavicon),
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
iconsvc.replaceFaviconData(
firstFavicon.uri,
secondFavicon.data,
secondFavicon.mimetype
);
await new Promise(resolve => {
iconsvc.setAndFetchFaviconForPage(
pageURI,
firstFavicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceFaviconDataFromDataURL_beforeRegularAssign_check(
aURI,
aDataLen,
aData,
aMimeType
) {
checkCallbackSucceeded(
aMimeType,
aData,
secondFavicon.mimetype,
secondFavicon.data
);
checkFaviconDataForPage(
pageURI,
secondFavicon.mimetype,
secondFavicon.data,
function test_replaceFaviconDataFromDataURL_beforeRegularAssign_callback() {
firstFavicon.file.remove(false);
secondFavicon.file.remove(false);
resolve();
}
);
},
Services.scriptSecurityManager.getSystemPrincipal()
);
});
await PlacesUtils.history.clear();
}
);
/* toBase64 copied from image/test/unit/test_encoder_png.js */
/* Convert data (an array of integers) to a Base64 string. */
const toBase64Table =
// eslint-disable-next-line no-useless-concat
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789+/";
const base64Pad = "=";
function toBase64(data) {
let result = "";
let length = data.length;
let i;
// Convert every three bytes to 4 ascii characters.
for (i = 0; i < length - 2; i += 3) {
result += toBase64Table[data[i] >> 2];
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
result += toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];
result += toBase64Table[data[i + 2] & 0x3f];
}
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
if (length % 3) {
i = length - (length % 3);
result += toBase64Table[data[i] >> 2];
if (length % 3 == 2) {
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
result += toBase64Table[(data[i + 1] & 0x0f) << 2];
result += base64Pad;
} else {
result += toBase64Table[(data[i] & 0x03) << 4];
result += base64Pad + base64Pad;
}
}
return result;
}

View file

@ -9,11 +9,13 @@ add_task(async function () {
let pageURI = NetUtil.newURI("http://www.places.test/page/"); let pageURI = NetUtil.newURI("http://www.places.test/page/");
await PlacesTestUtils.addVisits(pageURI); await PlacesTestUtils.addVisits(pageURI);
let faviconURI = NetUtil.newURI("http://www.places.test/favicon.ico"); let faviconURI = NetUtil.newURI("http://www.places.test/favicon.ico");
await PlacesTestUtils.setFaviconForPage( PlacesUtils.favicons.replaceFaviconDataFromDataURL(
pageURI,
faviconURI, faviconURI,
SMALLPNG_DATA_URI SMALLPNG_DATA_URI.spec,
0,
systemPrincipal
); );
await setFaviconForPage(pageURI, faviconURI);
// Sanity checks. // Sanity checks.
Assert.equal(await getFaviconUrlForPage(pageURI), faviconURI.spec); Assert.equal(await getFaviconUrlForPage(pageURI), faviconURI.spec);
@ -68,18 +70,22 @@ add_task(async function test_removePagesByTimeframe() {
// Add a normal icon to the most recent page. // Add a normal icon to the most recent page.
let faviconURI = NetUtil.newURI(`${BASE_URL}/page/favicon.ico`); let faviconURI = NetUtil.newURI(`${BASE_URL}/page/favicon.ico`);
await PlacesTestUtils.setFaviconForPage( PlacesUtils.favicons.replaceFaviconDataFromDataURL(
pageURI,
faviconURI, faviconURI,
SMALLSVG_DATA_URI SMALLSVG_DATA_URI.spec,
0,
systemPrincipal
); );
await setFaviconForPage(pageURI, faviconURI);
// Add a root icon to the most recent page. // Add a root icon to the most recent page.
let rootIconURI = NetUtil.newURI(`${BASE_URL}/favicon.ico`); let rootIconURI = NetUtil.newURI(`${BASE_URL}/favicon.ico`);
await PlacesTestUtils.setFaviconForPage( PlacesUtils.favicons.replaceFaviconDataFromDataURL(
pageURI,
rootIconURI, rootIconURI,
SMALLPNG_DATA_URI SMALLPNG_DATA_URI.spec,
0,
systemPrincipal
); );
await setFaviconForPage(pageURI, rootIconURI);
// Sanity checks. // Sanity checks.
Assert.equal( Assert.equal(
@ -135,11 +141,13 @@ add_task(async function test_different_host() {
let pageURI = NetUtil.newURI("http://places.test/page/"); let pageURI = NetUtil.newURI("http://places.test/page/");
await PlacesTestUtils.addVisits(pageURI); await PlacesTestUtils.addVisits(pageURI);
let faviconURI = NetUtil.newURI("http://mozilla.test/favicon.ico"); let faviconURI = NetUtil.newURI("http://mozilla.test/favicon.ico");
await PlacesTestUtils.setFaviconForPage( PlacesUtils.favicons.replaceFaviconDataFromDataURL(
pageURI,
faviconURI, faviconURI,
SMALLPNG_DATA_URI SMALLPNG_DATA_URI.spec,
0,
systemPrincipal
); );
await setFaviconForPage(pageURI, faviconURI);
Assert.equal( Assert.equal(
await getFaviconUrlForPage(pageURI), await getFaviconUrlForPage(pageURI),
@ -158,25 +166,16 @@ add_task(async function test_different_host() {
add_task(async function test_same_size() { add_task(async function test_same_size() {
// Add two icons with the same size, one is a root icon. Check that the // Add two icons with the same size, one is a root icon. Check that the
// non-root icon is preferred when a smaller size is requested. // non-root icon is preferred when a smaller size is requested.
let dataURL = await readFileDataAsDataURL( let data = readFileData(do_get_file("favicon-normal32.png"));
do_get_file("favicon-normal32.png"),
"image/png"
);
let pageURI = NetUtil.newURI("http://new_places.test/page/"); let pageURI = NetUtil.newURI("http://new_places.test/page/");
await PlacesTestUtils.addVisits(pageURI); await PlacesTestUtils.addVisits(pageURI);
let faviconURI = NetUtil.newURI("http://new_places.test/favicon.ico"); let faviconURI = NetUtil.newURI("http://new_places.test/favicon.ico");
await PlacesTestUtils.setFaviconForPage( PlacesUtils.favicons.replaceFaviconData(faviconURI, data, "image/png");
pageURI.spec, await setFaviconForPage(pageURI, faviconURI);
faviconURI.spec,
dataURL
);
faviconURI = NetUtil.newURI("http://new_places.test/another_icon.ico"); faviconURI = NetUtil.newURI("http://new_places.test/another_icon.ico");
await PlacesTestUtils.setFaviconForPage( PlacesUtils.favicons.replaceFaviconData(faviconURI, data, "image/png");
pageURI.spec, await setFaviconForPage(pageURI, faviconURI);
faviconURI.spec,
dataURL
);
Assert.equal( Assert.equal(
await getFaviconUrlForPage(pageURI, 20), await getFaviconUrlForPage(pageURI, 20),
@ -208,7 +207,13 @@ add_task(async function test_root_on_different_host() {
// Root favicon for TEST_URL1. // Root favicon for TEST_URL1.
const ICON_URL = "http://places1.test/favicon.ico"; const ICON_URL = "http://places1.test/favicon.ico";
let iconURI = NetUtil.newURI(ICON_URL); let iconURI = NetUtil.newURI(ICON_URL);
await PlacesTestUtils.setFaviconForPage(pageURI1, iconURI, SMALLPNG_DATA_URI); PlacesUtils.favicons.replaceFaviconDataFromDataURL(
iconURI,
SMALLPNG_DATA_URI.spec,
0,
systemPrincipal
);
await setFaviconForPage(pageURI1, iconURI);
Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1"); Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1");
Assert.equal( Assert.equal(
await getFaviconUrlForPage(pageURI1, 16), await getFaviconUrlForPage(pageURI1, 16),
@ -217,7 +222,13 @@ add_task(async function test_root_on_different_host() {
); );
// Same favicon for TEST_URL2. // Same favicon for TEST_URL2.
await PlacesTestUtils.setFaviconForPage(pageURI2, iconURI, SMALLPNG_DATA_URI); PlacesUtils.favicons.replaceFaviconDataFromDataURL(
iconURI,
SMALLPNG_DATA_URI.spec,
0,
systemPrincipal
);
await setFaviconForPage(pageURI2, iconURI);
Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1"); Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1");
Assert.equal( Assert.equal(
await getFaviconUrlForPage(pageURI2, 16), await getFaviconUrlForPage(pageURI2, 16),

View file

@ -1,245 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* https://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests for setFaviconForPage()
*/
add_task(async function test_validHistoryURI() {
let pageURI = uri("http://test1.bar/");
await PlacesTestUtils.addVisits(pageURI);
let favicon = await createFavicon("favicon1.png");
await doTestSetFaviconForPage({
pageURI,
faviconURI: favicon.uri,
dataURL: await createDataURLForFavicon(favicon),
expectedFaviconData: favicon.data,
expectedFaviconMimeType: favicon.mimeType,
});
await IOUtils.remove(favicon.file.path);
await PlacesUtils.history.clear();
});
add_task(async function test_overrideDefaultFavicon() {
let pageURI = uri("http://test2.bar/");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = await createFavicon("favicon2.png");
let secondFavicon = await createFavicon("favicon3.png");
await doTestSetFaviconForPage({
pageURI,
faviconURI: firstFavicon.uri,
dataURL: await createDataURLForFavicon(secondFavicon),
expectedFaviconData: secondFavicon.data,
expectedFaviconMimeType: secondFavicon.mimeType,
});
await IOUtils.remove(firstFavicon.file.path);
await IOUtils.remove(secondFavicon.file.path);
await PlacesUtils.history.clear();
});
add_task(async function test_replaceExisting() {
let pageURI = uri("http://test3.bar");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = await createFavicon("favicon4.png");
let secondFavicon = await createFavicon("favicon5.png");
await new Promise(resolve => {
PlacesUtils.favicons.setAndFetchFaviconForPage(
pageURI,
firstFavicon.uri,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
function test_replaceExisting_firstSet_check(
aURI,
aDataLen,
aData,
aMimeType
) {
Assert.equal(aMimeType, firstFavicon.mimeType);
Assert.ok(compareArrays(aData, firstFavicon.data));
checkFaviconDataForPage(
pageURI,
firstFavicon.mimeType,
firstFavicon.data,
resolve
);
},
Services.scriptSecurityManager.getSystemPrincipal()
);
});
await doTestSetFaviconForPage({
pageURI,
faviconURI: firstFavicon.uri,
dataURL: await createDataURLForFavicon(secondFavicon),
expectedFaviconData: secondFavicon.data,
expectedFaviconMimeType: secondFavicon.mimeType,
});
await IOUtils.remove(firstFavicon.file.path);
await IOUtils.remove(secondFavicon.file.path);
await PlacesUtils.history.clear();
});
add_task(async function test_twiceReplace() {
let pageURI = uri("http://test5.bar/");
await PlacesTestUtils.addVisits(pageURI);
let firstFavicon = await createFavicon("favicon9.png");
let secondFavicon = await createFavicon("favicon10.png");
let firstFaviconDataURL = await createDataURLForFavicon(firstFavicon);
await new Promise(resolve => {
PlacesUtils.favicons.setFaviconForPage(
pageURI,
firstFavicon.uri,
firstFaviconDataURL,
null,
resolve
);
});
await doTestSetFaviconForPage({
pageURI,
faviconURI: firstFavicon.uri,
dataURL: await createDataURLForFavicon(secondFavicon),
expectedFaviconData: secondFavicon.data,
expectedFaviconMimeType: secondFavicon.mimeType,
});
await IOUtils.remove(firstFavicon.file.path);
await IOUtils.remove(secondFavicon.file.path);
await PlacesUtils.history.clear();
});
add_task(async function test_userpass() {
let pageURI = uri("http://user:pass@test1.bar/");
await PlacesTestUtils.addVisits(pageURI);
let favicon = await createFavicon("favicon1.png");
let faviconURI = uri("http://user:pass@test1.bar/favicon1.png");
await doTestSetFaviconForPage({
pageURI,
faviconURI,
dataURL: await createDataURLForFavicon(favicon),
expectedFaviconData: favicon.data,
expectedFaviconMimeType: favicon.mimeType,
});
await IOUtils.remove(favicon.file.path);
await PlacesUtils.history.clear();
});
add_task(async function test_svg() {
let pageURI = uri("http://example.com/");
await PlacesTestUtils.addVisits(pageURI);
const svgContent = "<svg><rect width='1px' height='1px%'/></svg>";
await doTestSetFaviconForPage({
pageURI,
faviconURI: uri("http://example.com/favicon.svg"),
dataURL: uri(`data:image/svg+xml;utf8,${svgContent}`),
expectedFaviconData: Array.from(new TextEncoder().encode(svgContent)),
expectedFaviconMimeType: "image/svg+xml",
});
await PlacesUtils.history.clear();
});
add_task(async function test_invalidPageURI() {
await PlacesTestUtils.addVisits(uri("http://example.com/"));
let favicon = await createFavicon("favicon-invalidPageURI.png");
for (let invalidURI of [null, "", "http://example.com"]) {
try {
info(`Invalid page URI test for [${invalidURI}]`);
PlacesUtils.favicons.setFaviconForPage(
invalidURI,
favicon.uri,
await createDataURLForFavicon(favicon)
);
Assert.ok(false, "Error should happened");
} catch (e) {
Assert.ok(true, `Expected error happend [${e.message}]`);
}
}
});
add_task(async function test_invalidFaviconURI() {
let pageURI = uri("http://example.com/");
await PlacesTestUtils.addVisits(pageURI);
let favicon = await createFavicon("favicon-invalidFaviconURI.png");
for (let invalidURI of [null, "", favicon.uri.spec]) {
try {
info(`Invalid favicon URI test for [${invalidURI}]`);
PlacesUtils.favicons.setFaviconForPage(
pageURI,
invalidURI,
await createDataURLForFavicon(favicon)
);
Assert.ok(false, "Error should happened");
} catch (e) {
Assert.ok(true, `Expected error happend [${e.message}]`);
}
}
});
add_task(async function test_invalidFaviconDataURI() {
let pageURI = uri("http://example.com/");
await PlacesTestUtils.addVisits(pageURI);
let favicon = createFavicon("favicon-invalidFaviconDataURI.png");
for (let invalidURI of [
null,
"",
"data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==",
]) {
try {
info(`Invalid favicon data URI test for [${invalidURI}]`);
PlacesUtils.favicons.setFaviconForPage(pageURI, favicon.uri, invalidURI);
Assert.ok(false, "Error should happened");
} catch (e) {
Assert.ok(true, `Expected error happend [${e.message}]`);
}
}
});
async function doTestSetFaviconForPage({
pageURI,
faviconURI,
dataURL,
expectedFaviconData,
expectedFaviconMimeType,
}) {
let result = await new Promise(resolve => {
PlacesUtils.favicons.setFaviconForPage(
pageURI,
faviconURI,
dataURL,
null,
resolve
);
});
info("Check the result of setFaviconForPage");
Assert.equal(result, 0);
await new Promise(resolve => {
checkFaviconDataForPage(
pageURI,
expectedFaviconMimeType,
expectedFaviconData,
resolve
);
});
}

View file

@ -57,6 +57,10 @@ support-files = [
["test_query_result_favicon_changed_on_child.js"] ["test_query_result_favicon_changed_on_child.js"]
["test_replaceFaviconData.js"]
["test_replaceFaviconDataFromDataURL.js"]
["test_root_icons.js"] ["test_root_icons.js"]
["test_setAndFetchFaviconForPage.js"] ["test_setAndFetchFaviconForPage.js"]
@ -65,6 +69,4 @@ support-files = [
["test_setAndFetchFaviconForPage_redirects.js"] ["test_setAndFetchFaviconForPage_redirects.js"]
["test_setFaviconForPage.js"]
["test_svg_favicon.js"] ["test_svg_favicon.js"]

View file

@ -169,6 +169,7 @@ function readFileData(aFile) {
} }
return bytes; return bytes;
} }
/** /**
* Reads the data from the named file, verifying the expected file length. * Reads the data from the named file, verifying the expected file length.
* *
@ -185,42 +186,6 @@ function readFileOfLength(aFileName, aExpectedLength) {
return data; return data;
} }
/**
* Reads the data from the specified nsIFile, then returns it as data URL.
*
* @param file
* The nsIFile to read from.
* @param mimeType
* The mime type of the file content.
* @return Promise that retunes data URL.
*/
async function readFileDataAsDataURL(file, mimeType) {
const data = readFileData(file);
return fileDataToDataURL(data, mimeType);
}
/**
* Converts the given data to the data URL.
*
* @param data
* The file data.
* @param mimeType
* The mime type of the file content.
* @return Promise that retunes data URL.
*/
async function fileDataToDataURL(data, mimeType) {
const dataURL = await new Promise(resolve => {
const buffer = new Uint8ClampedArray(data);
const blob = new Blob([buffer], { type: mimeType });
const reader = new FileReader();
reader.onload = e => {
resolve(e.target.result);
};
reader.readAsDataURL(blob);
});
return dataURL;
}
/** /**
* Returns the base64-encoded version of the given string. This function is * Returns the base64-encoded version of the given string. This function is
* similar to window.btoa, but is available to xpcshell tests also. * similar to window.btoa, but is available to xpcshell tests also.

View file

@ -292,7 +292,20 @@ add_task(async function test_orphans() {
); );
// Also create a root icon. // Also create a root icon.
let faviconURI = Services.io.newURI(uri.spec + "favicon.ico"); let faviconURI = Services.io.newURI(uri.spec + "favicon.ico");
await PlacesTestUtils.setFaviconForPage(uri, faviconURI, SMALLPNG_DATA_URI); PlacesUtils.favicons.replaceFaviconDataFromDataURL(
faviconURI,
SMALLPNG_DATA_URI.spec,
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
PlacesUtils.favicons.setAndFetchFaviconForPage(
uri,
faviconURI,
true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null,
Services.scriptSecurityManager.getSystemPrincipal()
);
await PlacesUtils.history.update({ await PlacesUtils.history.update({
url: uri, url: uri,