forked from mirrors/gecko-dev
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:
parent
a290e8b405
commit
a5a1a182a7
41 changed files with 1691 additions and 933 deletions
|
|
@ -21,16 +21,17 @@ add_task(async function test_notificationClose() {
|
|||
Services.prefs.setBoolPref("alerts.showFavicons", true);
|
||||
|
||||
await PlacesTestUtils.addVisits(notificationURI);
|
||||
let dataURL = makeURI(
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC"
|
||||
);
|
||||
await new Promise(resolve => {
|
||||
PlacesUtils.favicons.setFaviconForPage(
|
||||
let faviconURI = await new Promise(resolve => {
|
||||
let uri = makeURI(
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC"
|
||||
);
|
||||
PlacesUtils.favicons.setAndFetchFaviconForPage(
|
||||
notificationURI,
|
||||
dataURL,
|
||||
dataURL,
|
||||
null,
|
||||
resolve
|
||||
uri,
|
||||
true,
|
||||
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
|
||||
uriResult => resolve(uriResult),
|
||||
Services.scriptSecurityManager.getSystemPrincipal()
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -66,7 +67,11 @@ add_task(async function test_notificationClose() {
|
|||
"Body text of notification should be present"
|
||||
);
|
||||
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");
|
||||
is(alertCloseButton.localName, "toolbarbutton", "close button found");
|
||||
|
|
|
|||
|
|
@ -247,10 +247,21 @@ DistributionCustomizer.prototype = {
|
|||
|
||||
if (item.icon && item.iconData) {
|
||||
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.icon),
|
||||
Services.io.newURI(item.iconData)
|
||||
faviconURI,
|
||||
false,
|
||||
lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
|
||||
null,
|
||||
Services.scriptSecurityManager.getSystemPrincipal()
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
|
|||
|
|
@ -204,30 +204,44 @@ async function insertBookmark(bookmark) {
|
|||
}
|
||||
|
||||
function setFaviconForBookmark(bookmark) {
|
||||
let faviconURI;
|
||||
let nullPrincipal = Services.scriptSecurityManager.createNullPrincipal({});
|
||||
|
||||
switch (bookmark.Favicon.protocol) {
|
||||
case "data:": {
|
||||
lazy.PlacesUtils.favicons.setFaviconForPage(
|
||||
bookmark.URL.URI,
|
||||
Services.io.newURI("fake-favicon-uri:" + bookmark.URL.href),
|
||||
bookmark.Favicon.URI
|
||||
case "data:":
|
||||
// data urls must first call replaceFaviconDataFromDataURL, using a
|
||||
// fake URL. Later, it's needed to call setAndFetchFaviconForPage
|
||||
// with the same URL.
|
||||
faviconURI = Services.io.newURI("fake-favicon-uri:" + bookmark.URL.href);
|
||||
|
||||
lazy.PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
faviconURI,
|
||||
bookmark.Favicon.href,
|
||||
0 /* max expiration length */,
|
||||
nullPrincipal
|
||||
);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case "http:":
|
||||
case "https:": {
|
||||
lazy.PlacesUtils.favicons.setAndFetchFaviconForPage(
|
||||
bookmark.URL.URI,
|
||||
bookmark.Favicon.URI,
|
||||
false /* forceReload */,
|
||||
lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
|
||||
null,
|
||||
Services.scriptSecurityManager.createNullPrincipal({})
|
||||
case "https:":
|
||||
faviconURI = Services.io.newURI(bookmark.Favicon.href);
|
||||
break;
|
||||
|
||||
default:
|
||||
lazy.log.error(
|
||||
`Bad URL given for favicon on bookmark "${bookmark.Title}"`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lazy.log.error(`Bad URL given for favicon on bookmark "${bookmark.Title}"`);
|
||||
lazy.PlacesUtils.favicons.setAndFetchFaviconForPage(
|
||||
Services.io.newURI(bookmark.URL.href),
|
||||
faviconURI,
|
||||
false /* forceReload */,
|
||||
lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
|
||||
null,
|
||||
nullPrincipal
|
||||
);
|
||||
}
|
||||
|
||||
// Cache of folder names to guids to be used by the getParentGuid
|
||||
|
|
|
|||
|
|
@ -785,8 +785,7 @@ async function GetBookmarksResource(aProfileFolder, aBrowserKey) {
|
|||
}
|
||||
|
||||
// Import Bookmark Favicons
|
||||
MigrationUtils.insertManyFavicons(favicons).catch(console.error);
|
||||
|
||||
MigrationUtils.insertManyFavicons(favicons);
|
||||
if (gotErrors) {
|
||||
throw new Error("The migration included errors.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ Bookmarks.prototype = {
|
|||
}
|
||||
|
||||
await MigrationUtils.insertManyBookmarksWrapper(bookmarks, aDestFolderGuid);
|
||||
MigrationUtils.insertManyFavicons(favicons).catch(console.error);
|
||||
MigrationUtils.insertManyFavicons(favicons);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -870,53 +870,36 @@ class MigrationUtils {
|
|||
* Iterates through the favicons, sniffs for a mime type,
|
||||
* 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
|
||||
* An array of Objects with these properties:
|
||||
* {Uint8Array} faviconData: The binary data of a favicon
|
||||
* {nsIURI} uri: The URI of the associated page
|
||||
*/
|
||||
async insertManyFavicons(favicons) {
|
||||
insertManyFavicons(favicons) {
|
||||
let sniffer = Cc["@mozilla.org/image/loader;1"].createInstance(
|
||||
Ci.nsIContentSniffer
|
||||
);
|
||||
|
||||
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(
|
||||
null,
|
||||
faviconDataItem.faviconData,
|
||||
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 mimeType = sniffer.getMIMETypeFromContent(
|
||||
null,
|
||||
faviconDataItem.faviconData,
|
||||
faviconDataItem.faviconData.length
|
||||
);
|
||||
let fakeFaviconURI = Services.io.newURI(
|
||||
"fake-favicon-uri:" + faviconDataItem.uri.spec
|
||||
);
|
||||
lazy.PlacesUtils.favicons.setFaviconForPage(
|
||||
lazy.PlacesUtils.favicons.replaceFaviconData(
|
||||
fakeFaviconURI,
|
||||
faviconDataItem.faviconData,
|
||||
mimeType
|
||||
);
|
||||
lazy.PlacesUtils.favicons.setAndFetchFaviconForPage(
|
||||
faviconDataItem.uri,
|
||||
fakeFaviconURI,
|
||||
Services.io.newURI(dataURL)
|
||||
true,
|
||||
lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
|
||||
null,
|
||||
Services.scriptSecurityManager.getSystemPrincipal()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ Bookmarks.prototype = {
|
|||
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
|
||||
dbPath,
|
||||
"Safari favicons",
|
||||
`SELECT I.uuid, I.url AS favicon_url, P.url
|
||||
`SELECT I.uuid, I.url AS favicon_url, P.url
|
||||
FROM icon_info I
|
||||
INNER JOIN page_url P ON I.uuid = P.uuid;`
|
||||
);
|
||||
|
|
@ -253,7 +253,7 @@ Bookmarks.prototype = {
|
|||
parentGuid
|
||||
);
|
||||
|
||||
MigrationUtils.insertManyFavicons(favicons).catch(console.error);
|
||||
MigrationUtils.insertManyFavicons(favicons);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -71,13 +71,11 @@ async function testBookmarks(migratorKey, subDirs) {
|
|||
).path;
|
||||
await IOUtils.copy(sourcePath, target.path);
|
||||
|
||||
// Get page url and the image data for each favicon
|
||||
let favicons = await MigrationUtils.getRowsFromDBWithoutLocks(
|
||||
// Get page url for each favicon
|
||||
let faviconURIs = await MigrationUtils.getRowsFromDBWithoutLocks(
|
||||
sourcePath,
|
||||
"Chrome Bookmark Favicons",
|
||||
`SELECT page_url, image_data FROM icon_mapping
|
||||
INNER JOIN favicon_bitmaps ON (favicon_bitmaps.icon_id = icon_mapping.icon_id)
|
||||
`
|
||||
`select page_url from icon_mapping`
|
||||
);
|
||||
|
||||
target.append("Bookmarks");
|
||||
|
|
@ -173,14 +171,10 @@ async function testBookmarks(migratorKey, subDirs) {
|
|||
"Telemetry reporting correct."
|
||||
);
|
||||
Assert.ok(observerNotified, "The observer should be notified upon migration");
|
||||
|
||||
for (const favicon of favicons) {
|
||||
await assertFavicon(
|
||||
favicon.getResultByName("page_url"),
|
||||
favicon.getResultByName("image_data"),
|
||||
"image/png"
|
||||
);
|
||||
}
|
||||
let pageUrls = Array.from(faviconURIs, f =>
|
||||
Services.io.newURI(f.getResultByName("page_url"))
|
||||
);
|
||||
await assertFavicons(pageUrls);
|
||||
}
|
||||
|
||||
add_task(async function test_Chrome() {
|
||||
|
|
|
|||
|
|
@ -199,24 +199,21 @@ let InternalFaviconLoader = {
|
|||
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:
|
||||
let loadType = lazy.PrivateBrowsingUtils.isWindowPrivate(win)
|
||||
? lazy.PlacesUtils.favicons.FAVICON_LOAD_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(
|
||||
pageURI,
|
||||
|
|
|
|||
|
|
@ -764,7 +764,26 @@ add_task(async function test_onFaviconChanged() {
|
|||
let iconURL =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
|
||||
"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([]);
|
||||
Assert.equal(tracker.score, 0);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1082,11 +1082,21 @@ BookmarkExporter.prototype = {
|
|||
function insertFaviconForNode(node) {
|
||||
if (node.icon) {
|
||||
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),
|
||||
// Create a fake favicon URI to use (FIXME: bug 523932)
|
||||
Services.io.newURI("fake-favicon-uri:" + node.url),
|
||||
Services.io.newURI(node.icon)
|
||||
faviconURI,
|
||||
false,
|
||||
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
|
||||
null,
|
||||
Services.scriptSecurityManager.getSystemPrincipal()
|
||||
);
|
||||
} catch (ex) {
|
||||
console.error("Failed to import favicon data:", ex);
|
||||
|
|
|
|||
|
|
@ -503,11 +503,21 @@ function translateTreeTypes(node) {
|
|||
function insertFaviconForNode(node) {
|
||||
if (node.icon) {
|
||||
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),
|
||||
// Create a fake faviconURI to use (FIXME: bug 523932)
|
||||
Services.io.newURI("fake-favicon-uri:" + node.url),
|
||||
Services.io.newURI(node.icon)
|
||||
faviconURI,
|
||||
false,
|
||||
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
|
||||
null,
|
||||
Services.scriptSecurityManager.getSystemPrincipal()
|
||||
);
|
||||
} catch (ex) {
|
||||
console.error("Failed to import favicon data:", ex);
|
||||
|
|
|
|||
|
|
@ -959,49 +959,6 @@ AsyncAssociateIconToPage::Run() {
|
|||
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
|
||||
|
||||
|
|
@ -1083,6 +1040,61 @@ AsyncGetFaviconDataForPage::Run() {
|
|||
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
|
||||
|
||||
|
|
@ -1195,8 +1207,7 @@ AsyncCopyFavicons::Run() {
|
|||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Get just one icon, to check whether the page has any, and to notify
|
||||
// later.
|
||||
// Get just one icon, to check whether the page has any, and to notify later.
|
||||
rv = FetchIconPerSpec(DB, mFromPage.spec, ""_ns, icon, UINT16_MAX);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
#include "nsThreadUtils.h"
|
||||
#include "nsProxyRelease.h"
|
||||
#include "imgLoader.h"
|
||||
#include "PlacesCompletionCallback.h"
|
||||
|
||||
class nsIPrincipal;
|
||||
|
||||
|
|
@ -192,33 +191,6 @@ class AsyncAssociateIconToPage final : public Runnable {
|
|||
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
|
||||
* given observer.
|
||||
|
|
@ -284,6 +256,18 @@ class AsyncGetFaviconDataForPage final : public Runnable {
|
|||
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.
|
||||
*/
|
||||
|
|
@ -292,7 +276,7 @@ class NotifyIconObservers final : public Runnable {
|
|||
NS_DECL_NSIRUNNABLE
|
||||
|
||||
/**
|
||||
* Constructor for nsIFaviconDataCallback.
|
||||
* Constructor.
|
||||
*
|
||||
* @param aIcon
|
||||
* Icon information. Can be empty if no icon is associated to the page.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -23,7 +23,6 @@ if CONFIG["MOZ_PLACES"]:
|
|||
"nsINavBookmarksService.idl",
|
||||
"nsIPlacesPreviewsHelperService.idl",
|
||||
"nsITaggingService.idl",
|
||||
"PlacesCompletionCallback.idl",
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.places = [
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
*/
|
||||
|
||||
#include "nsFaviconService.h"
|
||||
#include "PlacesCompletionCallback.h"
|
||||
|
||||
#include "nsNavHistory.h"
|
||||
#include "nsPlacesMacros.h"
|
||||
|
|
@ -36,6 +35,13 @@
|
|||
#include "nsContentUtils.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::places;
|
||||
|
||||
|
|
@ -117,10 +123,12 @@ nsresult GetFramesInfoForContainer(imgIContainer* aContainer,
|
|||
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService)
|
||||
|
||||
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()
|
||||
: mDefaultIconURIPreferredSize(UINT16_MAX) {
|
||||
: mUnassociatedIcons(UNASSOCIATED_FAVICONS_LENGTH),
|
||||
mDefaultIconURIPreferredSize(UINT16_MAX) {
|
||||
NS_ASSERTION(!gFaviconService,
|
||||
"Attempting to create two instances of the service!");
|
||||
gFaviconService = this;
|
||||
|
|
@ -144,6 +152,10 @@ nsFaviconService::StoreLastInsertedId(const nsACString& aTable,
|
|||
nsresult nsFaviconService::Init() {
|
||||
mDB = Database::GetDatabase();
|
||||
NS_ENSURE_STATE(mDB);
|
||||
|
||||
mExpireUnassociatedIconsTimer = NS_NewTimer();
|
||||
NS_ENSURE_STATE(mExpireUnassociatedIconsTimer);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
@ -175,6 +187,41 @@ nsFaviconService::ExpireAllFavicons() {
|
|||
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
|
||||
|
||||
|
|
@ -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
|
||||
nsFaviconService::SetAndFetchFaviconForPage(
|
||||
nsIURI* aPageURI, nsIURI* aFaviconURI, bool aForceReload,
|
||||
|
|
@ -418,13 +310,20 @@ nsFaviconService::SetAndFetchFaviconForPage(
|
|||
|
||||
// Build icon data.
|
||||
IconData icon;
|
||||
icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING;
|
||||
rv = faviconURI->GetSpec(icon.spec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// URIs can arguably lack a host.
|
||||
Unused << faviconURI->GetHost(icon.host);
|
||||
if (StringBeginsWith(icon.host, "www."_ns)) {
|
||||
icon.host.Cut(0, 4);
|
||||
// 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;
|
||||
rv = faviconURI->GetSpec(icon.spec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// URIs can arguably lack a host.
|
||||
Unused << faviconURI->GetHost(icon.host);
|
||||
if (StringBeginsWith(icon.host, "www."_ns)) {
|
||||
icon.host.Cut(0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
// A root icon is when the icon and page have the same host and the path
|
||||
|
|
@ -461,6 +360,182 @@ nsFaviconService::SetAndFetchFaviconForPage(
|
|||
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
|
||||
nsFaviconService::GetFaviconURLForPage(nsIURI* aPageURI,
|
||||
nsIFaviconDataCallback* aCallback,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
#include "nsCOMPtr.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsIFaviconService.h"
|
||||
#include "nsINamed.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTHashtable.h"
|
||||
|
|
@ -30,7 +32,21 @@ extern const uint16_t gFaviconSizes[7];
|
|||
// forward class definitions
|
||||
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:
|
||||
nsFaviconService();
|
||||
|
||||
|
|
@ -91,6 +107,8 @@ class nsFaviconService final : public nsIFaviconService {
|
|||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIFAVICONSERVICE
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
NS_DECL_NSINAMED
|
||||
|
||||
private:
|
||||
imgITools* GetImgTools() {
|
||||
|
|
@ -104,6 +122,7 @@ class nsFaviconService final : public nsIFaviconService {
|
|||
|
||||
RefPtr<mozilla::places::Database> mDB;
|
||||
|
||||
nsCOMPtr<nsITimer> mExpireUnassociatedIconsTimer;
|
||||
nsCOMPtr<imgITools> mImgTools;
|
||||
|
||||
static nsFaviconService* gFaviconService;
|
||||
|
|
@ -115,6 +134,11 @@ class nsFaviconService final : public nsIFaviconService {
|
|||
* they get back. May be null, in which case it needs initialization.
|
||||
*/
|
||||
nsCOMPtr<nsIURI> mDefaultIcon;
|
||||
|
||||
// This class needs access to the icons cache.
|
||||
friend class mozilla::places::AsyncReplaceFaviconData;
|
||||
nsTHashtable<UnassociatedIconHashKey> mUnassociatedIcons;
|
||||
|
||||
uint16_t mDefaultIconURIPreferredSize;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ interface nsIURI;
|
|||
interface nsIPrincipal;
|
||||
interface mozIPlacesPendingOperation;
|
||||
interface nsIFaviconDataCallback;
|
||||
interface PlacesCompletionCallback;
|
||||
|
||||
[scriptable, uuid(e81e0b0c-b9f1-4c2e-8f3c-b809933cf73c)]
|
||||
interface nsIFaviconService : nsISupports
|
||||
|
|
@ -142,35 +141,72 @@ interface nsIFaviconService : nsISupports
|
|||
[optional] in unsigned long long aRequestContextID);
|
||||
|
||||
/**
|
||||
* Stores the relation between a page URI and a favicon URI, whose icon data
|
||||
* is provided through a data URL.
|
||||
* The process is asynchronous and a callback with the status of the operation
|
||||
* will be invoked at the end of it.
|
||||
* Sets the data for a given favicon URI either by replacing existing data in
|
||||
* the database or taking the place of otherwise fetched icon data when
|
||||
* calling setAndFetchFaviconForPage later.
|
||||
*
|
||||
* 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
|
||||
* URI of the favicon to associate with the page.
|
||||
* @param aDataURL
|
||||
* String that represents a data URL to replace as the favicon content.
|
||||
* URI of the favicon whose data is being set.
|
||||
* @param aData
|
||||
* 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
|
||||
* Time in microseconds since the epoch when this favicon expires.
|
||||
* Until this time, we won't try to load it again.
|
||||
* If this argument is omitted, the expiration defaults to
|
||||
* 7 days (FaviconHelpers::MAX_FAVICON_EXPIRATION) from now.
|
||||
* @param [optional] aCallback
|
||||
* Once we're done setting and/or fetching the favicon, we invoke this
|
||||
* callback.
|
||||
*
|
||||
* @see PlacesCompletionCallback in PlacesCompletionCallback.idl.
|
||||
* @throws NS_ERROR_FAILURE
|
||||
* Thrown if the favicon is overbloated and won't be saved to the db.
|
||||
*/
|
||||
void setFaviconForPage(
|
||||
in nsIURI aPageURI,
|
||||
in nsIURI aFaviconURI,
|
||||
in nsIURI aDataURL,
|
||||
[optional] in PRTime aExpiration,
|
||||
[optional] in PlacesCompletionCallback aCallback
|
||||
);
|
||||
void replaceFaviconData(in nsIURI aFaviconURI,
|
||||
in Array<octet> aData,
|
||||
in AUTF8String aMimeType,
|
||||
[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 nsIPrincipal aLoadingPrincipal);
|
||||
|
||||
/**
|
||||
* Retrieves the favicon URI associated to the given page, if any.
|
||||
|
|
|
|||
|
|
@ -156,43 +156,6 @@ export var PlacesTestUtils = Object.freeze({
|
|||
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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -104,12 +104,19 @@ function test()
|
|||
// icon with a page explicitly in order for it to be visible through
|
||||
// the protocol.
|
||||
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(tests[1].url),
|
||||
Services.io.newURI(tests[1].expectedIcon),
|
||||
(Date.now() + 86400) * 1000,
|
||||
);
|
||||
true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
|
||||
systemPrincipal);
|
||||
|
||||
// And start our test process.
|
||||
loadNextTest();
|
||||
|
|
|
|||
|
|
@ -356,7 +356,13 @@ add_task(async function test_expire_icons() {
|
|||
}
|
||||
|
||||
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(
|
||||
await getFaviconUrlForPage(entry.page),
|
||||
entry.icon,
|
||||
|
|
@ -374,7 +380,13 @@ add_task(async function test_expire_icons() {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,6 @@
|
|||
|
||||
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
|
||||
// Used in createFavicon().
|
||||
let uniqueFaviconId = 0;
|
||||
|
||||
/**
|
||||
* 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".
|
||||
* @param aExpectedData
|
||||
* Expected icon data, expressed as an array of byte values.
|
||||
* If set null, skip the test for the favicon data.
|
||||
* @param aCallback
|
||||
* This function is called after the check finished.
|
||||
*/
|
||||
|
|
@ -41,9 +37,7 @@ function checkFaviconDataForPage(
|
|||
aPageURI,
|
||||
async function (aURI, aDataLen, aData, aMimeType) {
|
||||
Assert.equal(aExpectedMimeType, aMimeType);
|
||||
if (aExpectedData) {
|
||||
Assert.ok(compareArrays(aExpectedData, aData));
|
||||
}
|
||||
Assert.ok(compareArrays(aExpectedData, aData));
|
||||
await check_guid_for_uri(aPageURI);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,12 +68,14 @@ add_task(async function () {
|
|||
info("Test that the content type of a favicon we add is correct.");
|
||||
let testURI = uri("http://mozilla.org/");
|
||||
// Add the data before opening
|
||||
await PlacesTestUtils.addVisits(testURI);
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
testURI,
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
testIconURI,
|
||||
testFaviconData
|
||||
testFaviconData,
|
||||
0,
|
||||
systemPrincipal
|
||||
);
|
||||
await PlacesTestUtils.addVisits(testURI);
|
||||
await setFaviconForPage(testURI, testIconURI);
|
||||
// Open the channel
|
||||
let channel = NetUtil.newChannel({
|
||||
uri: PlacesUtils.favicons.getFaviconLinkForIcon(testIconURI).spec,
|
||||
|
|
@ -118,12 +120,14 @@ add_task(async function test_userpass() {
|
|||
CACHED_ICON_NORMAL,
|
||||
CACHED_ICON_USERPASS,
|
||||
]) {
|
||||
await PlacesTestUtils.addVisits(pageURI);
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
pageURI,
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
iconURI,
|
||||
testFaviconData
|
||||
testFaviconData,
|
||||
0,
|
||||
systemPrincipal
|
||||
);
|
||||
await PlacesTestUtils.addVisits(pageURI);
|
||||
await setFaviconForPage(pageURI, iconURI);
|
||||
|
||||
// Open the channel
|
||||
let channel = NetUtil.newChannel({
|
||||
|
|
|
|||
|
|
@ -28,23 +28,23 @@ add_task(async function test_expire_associated() {
|
|||
];
|
||||
|
||||
for (let icon of favicons) {
|
||||
let dataURL = await readFileDataAsDataURL(
|
||||
do_get_file(icon.name),
|
||||
let data = readFileData(do_get_file(icon.name));
|
||||
PlacesUtils.favicons.replaceFaviconData(
|
||||
NetUtil.newURI(TEST_URL + icon.name),
|
||||
data,
|
||||
icon.mimeType
|
||||
);
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
TEST_URL,
|
||||
TEST_URL + icon.name,
|
||||
dataURL
|
||||
);
|
||||
await setFaviconForPage(TEST_URL, TEST_URL + icon.name);
|
||||
if (icon.expired) {
|
||||
await expireIconRelationsForPage(TEST_URL);
|
||||
// Add the same icon to another page.
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
TEST_URL2,
|
||||
TEST_URL + icon.name,
|
||||
dataURL
|
||||
PlacesUtils.favicons.replaceFaviconData(
|
||||
NetUtil.newURI(TEST_URL + icon.name),
|
||||
data,
|
||||
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.
|
||||
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(
|
||||
await countEntries("moz_icons_to_pages"),
|
||||
1,
|
||||
|
|
@ -99,11 +105,13 @@ add_task(async function test_expire_root() {
|
|||
|
||||
// Now insert a new root icon.
|
||||
let rootIconURI = NetUtil.newURI(pageURI.spec + "favicon.ico");
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
pageURI,
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
rootIconURI,
|
||||
SMALLPNG_DATA_URI
|
||||
SMALLPNG_DATA_URI.spec,
|
||||
0,
|
||||
systemPrincipal
|
||||
);
|
||||
await setFaviconForPage(pageURI, rootIconURI);
|
||||
|
||||
// Only the root icon should have survived.
|
||||
Assert.equal(
|
||||
|
|
|
|||
|
|
@ -43,24 +43,29 @@ async function checkFaviconDataConversion(
|
|||
});
|
||||
let faviconURI = NetUtil.newURI("http://places.test/icon/" + aFileName);
|
||||
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 => {
|
||||
if (!aExpectConversion) {
|
||||
checkFaviconDataForPage(pageURI, aFileMimeType, fileData, resolve);
|
||||
} else if (!aVaryOnWindows || !isWindows) {
|
||||
let expectedFile = do_get_file("expected-" + aFileName + ".png");
|
||||
let expectedData = readFileData(expectedFile);
|
||||
checkFaviconDataForPage(pageURI, "image/png", expectedData, resolve);
|
||||
} else {
|
||||
// Not check the favicon data.
|
||||
checkFaviconDataForPage(pageURI, "image/png", null, resolve);
|
||||
}
|
||||
PlacesUtils.favicons.setAndFetchFaviconForPage(
|
||||
pageURI,
|
||||
faviconURI,
|
||||
true,
|
||||
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
|
||||
(aURI, aDataLen, aData, aMimeType) => {
|
||||
if (!aExpectConversion) {
|
||||
Assert.ok(compareArrays(aData, fileData));
|
||||
Assert.equal(aMimeType, aFileMimeType);
|
||||
} else {
|
||||
if (!aVaryOnWindows || !isWindows) {
|
||||
let expectedFile = do_get_file("expected-" + aFileName + ".png");
|
||||
Assert.ok(compareArrays(aData, readFileData(expectedFile)));
|
||||
}
|
||||
Assert.equal(aMimeType, "image/png");
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
Services.scriptSecurityManager.getSystemPrincipal()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,16 +9,21 @@ const ICON32_URL = "http://places.test/favicon-normal32.png";
|
|||
add_task(async function () {
|
||||
await PlacesTestUtils.addVisits(PAGE_URL);
|
||||
// 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"
|
||||
);
|
||||
await PlacesTestUtils.setFaviconForPage(PAGE_URL, ICON16_URL, dataURL16);
|
||||
let dataURL32 = await readFileDataAsDataURL(
|
||||
do_get_file("favicon-normal32.png"),
|
||||
await setFaviconForPage(PAGE_URL, ICON16_URL);
|
||||
data = readFileData(do_get_file("favicon-normal32.png"));
|
||||
PlacesUtils.favicons.replaceFaviconData(
|
||||
Services.io.newURI(ICON32_URL),
|
||||
data,
|
||||
"image/png"
|
||||
);
|
||||
await PlacesTestUtils.setFaviconForPage(PAGE_URL, ICON32_URL, dataURL32);
|
||||
await setFaviconForPage(PAGE_URL, ICON32_URL);
|
||||
|
||||
const PAGE_ICON_URL = "page-icon:" + PAGE_URL;
|
||||
|
||||
|
|
|
|||
|
|
@ -60,8 +60,12 @@ add_task(async function test_fallback() {
|
|||
info("Set icon for the root");
|
||||
await PlacesTestUtils.addVisits(ROOT_URL);
|
||||
let data = readFileData(do_get_file("favicon-normal16.png"));
|
||||
let dataURL = await fileDataToDataURL(data, "image/png");
|
||||
await PlacesTestUtils.setFaviconForPage(ROOT_URL, ROOT_ICON_URL, dataURL);
|
||||
PlacesUtils.favicons.replaceFaviconData(
|
||||
NetUtil.newURI(ROOT_ICON_URL),
|
||||
data,
|
||||
"image/png"
|
||||
);
|
||||
await setFaviconForPage(ROOT_URL, ROOT_ICON_URL);
|
||||
|
||||
info("check fallback icons");
|
||||
await new Promise(resolve => {
|
||||
|
|
@ -92,8 +96,12 @@ add_task(async function test_fallback() {
|
|||
info("Now add a proper icon for the page");
|
||||
await PlacesTestUtils.addVisits(SUBPAGE_URL);
|
||||
let data32 = readFileData(do_get_file("favicon-normal32.png"));
|
||||
let dataURL32 = await fileDataToDataURL(data32, "image/png");
|
||||
await PlacesTestUtils.setFaviconForPage(SUBPAGE_URL, ICON32_URL, dataURL32);
|
||||
PlacesUtils.favicons.replaceFaviconData(
|
||||
NetUtil.newURI(ICON32_URL),
|
||||
data32,
|
||||
"image/png"
|
||||
);
|
||||
await setFaviconForPage(SUBPAGE_URL, ICON32_URL);
|
||||
|
||||
info("check no fallback icons");
|
||||
await new Promise(resolve => {
|
||||
|
|
|
|||
|
|
@ -57,11 +57,13 @@ add_task(async function test_fallback() {
|
|||
|
||||
info("Set icon for the root");
|
||||
await PlacesTestUtils.addVisits(ROOT_URL);
|
||||
let dataURL = await readFileDataAsDataURL(
|
||||
do_get_file("favicon-normal16.png"),
|
||||
let data = readFileData(do_get_file("favicon-normal16.png"));
|
||||
PlacesUtils.favicons.replaceFaviconData(
|
||||
NetUtil.newURI(ROOT_ICON_URL),
|
||||
data,
|
||||
"image/png"
|
||||
);
|
||||
await PlacesTestUtils.setFaviconForPage(ROOT_URL, ROOT_ICON_URL, dataURL);
|
||||
await setFaviconForPage(ROOT_URL, ROOT_ICON_URL);
|
||||
|
||||
info("check fallback icons");
|
||||
Assert.equal(
|
||||
|
|
@ -77,11 +79,13 @@ add_task(async function test_fallback() {
|
|||
|
||||
info("Now add a proper icon for the page");
|
||||
await PlacesTestUtils.addVisits(SUBPAGE_URL);
|
||||
let dataURL32 = await readFileDataAsDataURL(
|
||||
do_get_file("favicon-normal32.png"),
|
||||
let data32 = readFileData(do_get_file("favicon-normal32.png"));
|
||||
PlacesUtils.favicons.replaceFaviconData(
|
||||
NetUtil.newURI(ICON32_URL),
|
||||
data32,
|
||||
"image/png"
|
||||
);
|
||||
await PlacesTestUtils.setFaviconForPage(SUBPAGE_URL, ICON32_URL, dataURL32);
|
||||
await setFaviconForPage(SUBPAGE_URL, ICON32_URL);
|
||||
|
||||
info("check no fallback icons");
|
||||
Assert.equal(
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ add_task(async function () {
|
|||
let pageURI = uri("http://foo.bar/");
|
||||
await PlacesTestUtils.addVisits(pageURI);
|
||||
|
||||
let dataURI = await fileDataToDataURL(icon.data, icon.mimetype);
|
||||
await PlacesTestUtils.setFaviconForPage(pageURI.spec, icon.uri.spec, dataURI);
|
||||
PlacesUtils.favicons.replaceFaviconData(icon.uri, icon.data, icon.mimetype);
|
||||
await setFaviconForPage(pageURI, icon.uri);
|
||||
Assert.equal(
|
||||
await getFaviconUrlForPage(pageURI),
|
||||
icon.uri.spec,
|
||||
|
|
|
|||
|
|
@ -16,9 +16,10 @@ add_task(async function () {
|
|||
let url = "http://foo.bar/";
|
||||
await PlacesTestUtils.addVisits(url);
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
let iconUri = "http://mozilla.org/" + i;
|
||||
let dataURL = await readFileDataAsDataURL(icon.file, icon.mimetype);
|
||||
await PlacesTestUtils.setFaviconForPage(url, iconUri, dataURL);
|
||||
let iconUri = NetUtil.newURI("http://mozilla.org/" + i);
|
||||
let data = readFileData(icon.file);
|
||||
PlacesUtils.favicons.replaceFaviconData(iconUri, data, icon.mimetype);
|
||||
await setFaviconForPage(url, iconUri);
|
||||
}
|
||||
|
||||
let promise = TestUtils.topicObserved("places-favicons-expired");
|
||||
|
|
|
|||
|
|
@ -14,15 +14,9 @@ add_task(async function () {
|
|||
let faviconURI = NetUtil.newURI("http://places.test/icon/favicon-multi.ico");
|
||||
// Fake window.
|
||||
let win = { devicePixelRatio: 1.0 };
|
||||
let icoDataURL = await readFileDataAsDataURL(
|
||||
do_get_file("favicon-multi.ico"),
|
||||
"image/x-icon"
|
||||
);
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
pageURI.spec,
|
||||
faviconURI.spec,
|
||||
icoDataURL
|
||||
);
|
||||
let icoData = readFileData(do_get_file("favicon-multi.ico"));
|
||||
PlacesUtils.favicons.replaceFaviconData(faviconURI, icoData, "image/x-icon");
|
||||
await setFaviconForPage(pageURI, faviconURI);
|
||||
|
||||
for (let size of [16, 32, 64]) {
|
||||
let file = do_get_file(`favicon-multi-frame${size}.png`);
|
||||
|
|
|
|||
|
|
@ -76,12 +76,25 @@ var gFavicon;
|
|||
|
||||
add_task(async function setup() {
|
||||
await PlacesTestUtils.addVisits(TEST_URI);
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
TEST_URI,
|
||||
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
ICON_URI,
|
||||
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(
|
||||
PlacesUtils.favicons.defaultFavicon.spec
|
||||
);
|
||||
|
|
@ -120,11 +133,13 @@ add_task(async function subpage_url_fallback() {
|
|||
|
||||
add_task(async function svg_icon() {
|
||||
let faviconURI = NetUtil.newURI("http://places.test/favicon.svg");
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
TEST_URI,
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
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);
|
||||
info(svgIcon.contentType);
|
||||
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 loadingIconURISpec of [PAGE_ICON_NORMAL, PAGE_ICON_USERPASS]) {
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
iconURI,
|
||||
ICON_DATAURL,
|
||||
0,
|
||||
systemPrincipal
|
||||
);
|
||||
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);
|
||||
Assert.equal(contentType, gFavicon.contentType);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -9,11 +9,13 @@ add_task(async function () {
|
|||
let pageURI = NetUtil.newURI("http://www.places.test/page/");
|
||||
await PlacesTestUtils.addVisits(pageURI);
|
||||
let faviconURI = NetUtil.newURI("http://www.places.test/favicon.ico");
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
pageURI,
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
faviconURI,
|
||||
SMALLPNG_DATA_URI
|
||||
SMALLPNG_DATA_URI.spec,
|
||||
0,
|
||||
systemPrincipal
|
||||
);
|
||||
await setFaviconForPage(pageURI, faviconURI);
|
||||
|
||||
// Sanity checks.
|
||||
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.
|
||||
let faviconURI = NetUtil.newURI(`${BASE_URL}/page/favicon.ico`);
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
pageURI,
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
faviconURI,
|
||||
SMALLSVG_DATA_URI
|
||||
SMALLSVG_DATA_URI.spec,
|
||||
0,
|
||||
systemPrincipal
|
||||
);
|
||||
await setFaviconForPage(pageURI, faviconURI);
|
||||
// Add a root icon to the most recent page.
|
||||
let rootIconURI = NetUtil.newURI(`${BASE_URL}/favicon.ico`);
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
pageURI,
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
rootIconURI,
|
||||
SMALLPNG_DATA_URI
|
||||
SMALLPNG_DATA_URI.spec,
|
||||
0,
|
||||
systemPrincipal
|
||||
);
|
||||
await setFaviconForPage(pageURI, rootIconURI);
|
||||
|
||||
// Sanity checks.
|
||||
Assert.equal(
|
||||
|
|
@ -135,11 +141,13 @@ add_task(async function test_different_host() {
|
|||
let pageURI = NetUtil.newURI("http://places.test/page/");
|
||||
await PlacesTestUtils.addVisits(pageURI);
|
||||
let faviconURI = NetUtil.newURI("http://mozilla.test/favicon.ico");
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
pageURI,
|
||||
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
|
||||
faviconURI,
|
||||
SMALLPNG_DATA_URI
|
||||
SMALLPNG_DATA_URI.spec,
|
||||
0,
|
||||
systemPrincipal
|
||||
);
|
||||
await setFaviconForPage(pageURI, faviconURI);
|
||||
|
||||
Assert.equal(
|
||||
await getFaviconUrlForPage(pageURI),
|
||||
|
|
@ -158,25 +166,16 @@ add_task(async function test_different_host() {
|
|||
add_task(async function test_same_size() {
|
||||
// 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.
|
||||
let dataURL = await readFileDataAsDataURL(
|
||||
do_get_file("favicon-normal32.png"),
|
||||
"image/png"
|
||||
);
|
||||
let data = readFileData(do_get_file("favicon-normal32.png"));
|
||||
let pageURI = NetUtil.newURI("http://new_places.test/page/");
|
||||
await PlacesTestUtils.addVisits(pageURI);
|
||||
|
||||
let faviconURI = NetUtil.newURI("http://new_places.test/favicon.ico");
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
pageURI.spec,
|
||||
faviconURI.spec,
|
||||
dataURL
|
||||
);
|
||||
PlacesUtils.favicons.replaceFaviconData(faviconURI, data, "image/png");
|
||||
await setFaviconForPage(pageURI, faviconURI);
|
||||
faviconURI = NetUtil.newURI("http://new_places.test/another_icon.ico");
|
||||
await PlacesTestUtils.setFaviconForPage(
|
||||
pageURI.spec,
|
||||
faviconURI.spec,
|
||||
dataURL
|
||||
);
|
||||
PlacesUtils.favicons.replaceFaviconData(faviconURI, data, "image/png");
|
||||
await setFaviconForPage(pageURI, faviconURI);
|
||||
|
||||
Assert.equal(
|
||||
await getFaviconUrlForPage(pageURI, 20),
|
||||
|
|
@ -208,7 +207,13 @@ add_task(async function test_root_on_different_host() {
|
|||
// Root favicon for TEST_URL1.
|
||||
const ICON_URL = "http://places1.test/favicon.ico";
|
||||
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 getFaviconUrlForPage(pageURI1, 16),
|
||||
|
|
@ -217,7 +222,13 @@ add_task(async function test_root_on_different_host() {
|
|||
);
|
||||
|
||||
// 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 getFaviconUrlForPage(pageURI2, 16),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -57,6 +57,10 @@ support-files = [
|
|||
|
||||
["test_query_result_favicon_changed_on_child.js"]
|
||||
|
||||
["test_replaceFaviconData.js"]
|
||||
|
||||
["test_replaceFaviconDataFromDataURL.js"]
|
||||
|
||||
["test_root_icons.js"]
|
||||
|
||||
["test_setAndFetchFaviconForPage.js"]
|
||||
|
|
@ -65,6 +69,4 @@ support-files = [
|
|||
|
||||
["test_setAndFetchFaviconForPage_redirects.js"]
|
||||
|
||||
["test_setFaviconForPage.js"]
|
||||
|
||||
["test_svg_favicon.js"]
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ function readFileData(aFile) {
|
|||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the data from the named file, verifying the expected file length.
|
||||
*
|
||||
|
|
@ -185,42 +186,6 @@ function readFileOfLength(aFileName, aExpectedLength) {
|
|||
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
|
||||
* similar to window.btoa, but is available to xpcshell tests also.
|
||||
|
|
|
|||
|
|
@ -292,7 +292,20 @@ add_task(async function test_orphans() {
|
|||
);
|
||||
// Also create a root icon.
|
||||
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({
|
||||
url: uri,
|
||||
|
|
|
|||
Loading…
Reference in a new issue