summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places')
-rw-r--r--toolkit/components/places/BookmarkHTMLUtils.sys.mjs18
-rw-r--r--toolkit/components/places/BookmarkJSONUtils.sys.mjs22
-rw-r--r--toolkit/components/places/FaviconHelpers.cpp101
-rw-r--r--toolkit/components/places/FaviconHelpers.h42
-rw-r--r--toolkit/components/places/History.cpp105
-rw-r--r--toolkit/components/places/History.h10
-rw-r--r--toolkit/components/places/PlacesCompletionCallback.idl19
-rw-r--r--toolkit/components/places/PlacesQuery.sys.mjs20
-rw-r--r--toolkit/components/places/moz.build1
-rw-r--r--toolkit/components/places/nsFaviconService.cpp406
-rw-r--r--toolkit/components/places/nsFaviconService.h26
-rw-r--r--toolkit/components/places/nsIFaviconService.idl82
-rw-r--r--toolkit/components/places/tests/PlacesTestUtils.sys.mjs37
-rw-r--r--toolkit/components/places/tests/browser/browser.toml12
-rw-r--r--toolkit/components/places/tests/browser/browser_visituri_restriction.js227
-rw-r--r--toolkit/components/places/tests/browser/browser_visituri_restriction_origin.js92
-rw-r--r--toolkit/components/places/tests/browser/head.js77
-rw-r--r--toolkit/components/places/tests/chrome/test_cached_favicon.xhtml15
-rw-r--r--toolkit/components/places/tests/expiration/test_debug_expiration.js16
-rw-r--r--toolkit/components/places/tests/favicons/head_favicons.js87
-rw-r--r--toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js20
-rw-r--r--toolkit/components/places/tests/favicons/test_expire_on_new_icons.js38
-rw-r--r--toolkit/components/places/tests/favicons/test_favicons_conversions.js37
-rw-r--r--toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js17
-rw-r--r--toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js16
-rw-r--r--toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js16
-rw-r--r--toolkit/components/places/tests/favicons/test_heavy_favicon.js4
-rw-r--r--toolkit/components/places/tests/favicons/test_incremental_vacuum.js7
-rw-r--r--toolkit/components/places/tests/favicons/test_multiple_frames.js12
-rw-r--r--toolkit/components/places/tests/favicons/test_page-icon_protocol.js44
-rw-r--r--toolkit/components/places/tests/favicons/test_replaceFaviconData.js395
-rw-r--r--toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js537
-rw-r--r--toolkit/components/places/tests/favicons/test_root_icons.js67
-rw-r--r--toolkit/components/places/tests/favicons/test_setFaviconForPage.js245
-rw-r--r--toolkit/components/places/tests/favicons/xpcshell.toml6
-rw-r--r--toolkit/components/places/tests/gtest/places_test_harness_tail.h8
-rw-r--r--toolkit/components/places/tests/head_common.js37
-rw-r--r--toolkit/components/places/tests/history/test_remove.js15
-rw-r--r--toolkit/components/places/tests/unit/test_PlacesQuery_history.js33
39 files changed, 1373 insertions, 1596 deletions
diff --git a/toolkit/components/places/BookmarkHTMLUtils.sys.mjs b/toolkit/components/places/BookmarkHTMLUtils.sys.mjs
index 81bd791730..d619f9fed2 100644
--- a/toolkit/components/places/BookmarkHTMLUtils.sys.mjs
+++ b/toolkit/components/places/BookmarkHTMLUtils.sys.mjs
@@ -1082,21 +1082,11 @@ BookmarkExporter.prototype = {
function insertFaviconForNode(node) {
if (node.icon) {
try {
- // 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(
+ PlacesUtils.favicons.setFaviconForPage(
Services.io.newURI(node.url),
- faviconURI,
- false,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
- null,
- Services.scriptSecurityManager.getSystemPrincipal()
+ // Create a fake favicon URI to use (FIXME: bug 523932)
+ Services.io.newURI("fake-favicon-uri:" + node.url),
+ Services.io.newURI(node.icon)
);
} catch (ex) {
console.error("Failed to import favicon data:", ex);
diff --git a/toolkit/components/places/BookmarkJSONUtils.sys.mjs b/toolkit/components/places/BookmarkJSONUtils.sys.mjs
index 0a27db3696..e453e7e094 100644
--- a/toolkit/components/places/BookmarkJSONUtils.sys.mjs
+++ b/toolkit/components/places/BookmarkJSONUtils.sys.mjs
@@ -21,7 +21,7 @@ const OLD_BOOKMARK_QUERY_TRANSLATIONS = {
MOBILE_BOOKMARKS: PlacesUtils.bookmarks.mobileGuid,
};
-export var BookmarkJSONUtils = Object.freeze({
+export var BookmarkJSONUtils = {
/**
* Import bookmarks from a url.
*
@@ -162,7 +162,7 @@ export var BookmarkJSONUtils = Object.freeze({
});
return { count, hash };
},
-});
+};
function BookmarkImporter(aReplace, aSource) {
this._replace = aReplace;
@@ -503,21 +503,11 @@ function translateTreeTypes(node) {
function insertFaviconForNode(node) {
if (node.icon) {
try {
- // 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(
+ PlacesUtils.favicons.setFaviconForPage(
Services.io.newURI(node.url),
- faviconURI,
- false,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
- null,
- Services.scriptSecurityManager.getSystemPrincipal()
+ // Create a fake faviconURI to use (FIXME: bug 523932)
+ Services.io.newURI("fake-favicon-uri:" + node.url),
+ Services.io.newURI(node.icon)
);
} catch (ex) {
console.error("Failed to import favicon data:", ex);
diff --git a/toolkit/components/places/FaviconHelpers.cpp b/toolkit/components/places/FaviconHelpers.cpp
index 1c565a3de1..df06885e1f 100644
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -960,6 +960,49 @@ AsyncAssociateIconToPage::Run() {
}
////////////////////////////////////////////////////////////////////////////////
+//// AsyncSetIconForPage
+
+AsyncSetIconForPage::AsyncSetIconForPage(const IconData& aIcon,
+ const PageData& aPage,
+ PlacesCompletionCallback* aCallback)
+ : Runnable("places::AsyncSetIconForPage"),
+ mCallback(new nsMainThreadPtrHolder<PlacesCompletionCallback>(
+ "AsyncSetIconForPage::mCallback", aCallback, false)),
+ mIcon(aIcon),
+ mPage(aPage) {}
+
+NS_IMETHODIMP
+AsyncSetIconForPage::Run() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(mIcon.payloads.Length(), "The icon should have valid data");
+ MOZ_ASSERT(mPage.spec.Length(), "The page should have spec");
+ MOZ_ASSERT(mPage.guid.IsEmpty(), "The page should not have guid");
+
+ nsresult rv = NS_OK;
+ auto guard = MakeScopeExit([&]() {
+ if (mCallback) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("AsyncSetIconForPage::Callback",
+ [rv, callback = std::move(mCallback)]() {
+ (void)callback->Complete(rv);
+ }));
+ }
+ });
+
+ // Fetch the page data.
+ RefPtr<Database> DB = Database::GetDatabase();
+ if (MOZ_UNLIKELY(!DB)) {
+ return (rv = NS_ERROR_UNEXPECTED);
+ }
+ rv = FetchPageInfo(DB, mPage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMainThreadPtrHandle<nsIFaviconDataCallback> nullCallback;
+ AsyncAssociateIconToPage event(mIcon, mPage, nullCallback);
+ return (rv = event.Run());
+}
+
+////////////////////////////////////////////////////////////////////////////////
//// AsyncGetFaviconURLForPage
AsyncGetFaviconURLForPage::AsyncGetFaviconURLForPage(
@@ -1041,61 +1084,6 @@ AsyncGetFaviconDataForPage::Run() {
}
////////////////////////////////////////////////////////////////////////////////
-//// AsyncReplaceFaviconData
-
-AsyncReplaceFaviconData::AsyncReplaceFaviconData(const IconData& aIcon)
- : Runnable("places::AsyncReplaceFaviconData"), mIcon(aIcon) {
- MOZ_ASSERT(NS_IsMainThread());
-}
-
-NS_IMETHODIMP
-AsyncReplaceFaviconData::Run() {
- MOZ_ASSERT(!NS_IsMainThread());
-
- RefPtr<Database> DB = Database::GetDatabase();
- NS_ENSURE_STATE(DB);
-
- mozStorageTransaction transaction(
- DB->MainConn(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
-
- // XXX Handle the error, bug 1696133.
- Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
-
- nsresult rv = SetIconInfo(DB, mIcon, true);
- if (rv == NS_ERROR_NOT_AVAILABLE) {
- // There's no previous icon to replace, we don't need to do anything.
- (void)transaction.Commit();
- return NS_OK;
- }
- NS_ENSURE_SUCCESS(rv, rv);
- rv = transaction.Commit();
- NS_ENSURE_SUCCESS(rv, rv);
-
- // We can invalidate the cache version since we now persist the icon.
- nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
- "places::AsyncReplaceFaviconData::RemoveIconDataCacheEntry", this,
- &AsyncReplaceFaviconData::RemoveIconDataCacheEntry);
- rv = NS_DispatchToMainThread(event);
- NS_ENSURE_SUCCESS(rv, rv);
-
- return NS_OK;
-}
-
-nsresult AsyncReplaceFaviconData::RemoveIconDataCacheEntry() {
- MOZ_ASSERT(NS_IsMainThread());
-
- nsCOMPtr<nsIURI> iconURI;
- nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
- NS_ENSURE_SUCCESS(rv, rv);
-
- nsFaviconService* favicons = nsFaviconService::GetFaviconService();
- NS_ENSURE_STATE(favicons);
- favicons->mUnassociatedIcons.RemoveEntry(iconURI);
-
- return NS_OK;
-}
-
-////////////////////////////////////////////////////////////////////////////////
//// NotifyIconObservers
NotifyIconObservers::NotifyIconObservers(
@@ -1207,7 +1195,8 @@ 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);
diff --git a/toolkit/components/places/FaviconHelpers.h b/toolkit/components/places/FaviconHelpers.h
index ba3407722c..aaa5396fe6 100644
--- a/toolkit/components/places/FaviconHelpers.h
+++ b/toolkit/components/places/FaviconHelpers.h
@@ -14,6 +14,7 @@
#include "nsThreadUtils.h"
#include "nsProxyRelease.h"
#include "imgLoader.h"
+#include "PlacesCompletionCallback.h"
class nsIPrincipal;
@@ -192,6 +193,33 @@ class AsyncAssociateIconToPage final : public Runnable {
};
/**
+ * 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.
*/
@@ -256,18 +284,6 @@ 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.
*/
@@ -276,7 +292,7 @@ class NotifyIconObservers final : public Runnable {
NS_DECL_NSIRUNNABLE
/**
- * Constructor.
+ * Constructor for nsIFaviconDataCallback.
*
* @param aIcon
* Icon information. Can be empty if no icon is associated to the page.
diff --git a/toolkit/components/places/History.cpp b/toolkit/components/places/History.cpp
index 6c066f47be..474b2cdac8 100644
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -1434,6 +1434,13 @@ void NotifyEmbedVisit(VisitData& aPlace,
(void)NS_DispatchToMainThread(event);
}
+void NotifyOriginRestrictedVisit(nsIURI* aURI) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
+
+ nsCOMPtr<nsIRunnable> event = new NotifyManyVisitsObservers(VisitData(aURI));
+ (void)NS_DispatchToMainThread(event);
+}
+
void NotifyVisitIfHavingUserPass(nsIURI* aURI) {
MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
@@ -1868,6 +1875,64 @@ const mozIStorageConnection* History::GetConstDBConn() {
return mDB->MainConn();
}
+void History::UpdateOriginFloodingRestriction(nsACString& aOrigin) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ TimeStamp latestInputTimeStamp = UserActivation::LatestUserInputStart();
+ if (latestInputTimeStamp.IsNull()) {
+ // Probably just after browser initialization.
+ return;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+
+ // Remove expired flooding resrictions.
+ for (auto iter = mOriginFloodingRestrictions.Iter(); !iter.Done();
+ iter.Next()) {
+ if ((now - iter.Data().mLastVisitTimeStamp).ToSeconds() >
+ iter.Data().mExpireIntervalSeconds) {
+ iter.Remove();
+ }
+ }
+
+ // If not long enough time elapsed sincer last user interaction, this visit
+ // can be stored.
+ if ((now - latestInputTimeStamp).ToSeconds() <
+ StaticPrefs::
+ places_history_floodingPrevention_maxSecondsFromLastUserInteraction()) {
+ mOriginFloodingRestrictions.Remove(aOrigin);
+ return;
+ }
+
+ // Otherwise, update the flooding restriction for the origin.
+ auto restriction = mOriginFloodingRestrictions.Lookup(aOrigin);
+ if (restriction) {
+ if (restriction->mAllowedVisitCount) {
+ // Count down to 0. If 0, the origin should be restricted.
+ restriction->mAllowedVisitCount -= 1;
+ } else {
+ // Since the origin is marked as restricted make its expiration time
+ // longer.
+ restriction->mExpireIntervalSeconds *= 2;
+ }
+ } else {
+ // Initialize OriginFloodingRestriction to store the origin restriction.
+ mOriginFloodingRestrictions.InsertOrUpdate(
+ aOrigin,
+ OriginFloodingRestriction{
+ now,
+ StaticPrefs::
+ places_history_floodingPrevention_restrictionExpireSeconds(),
+ StaticPrefs::places_history_floodingPrevention_restrictionCount()});
+ }
+}
+
+bool History::IsRestrictedOrigin(nsACString& aOrigin) {
+ auto restriction = mOriginFloodingRestrictions.Lookup(aOrigin);
+ // If the count is 0, need to restrict the origin.
+ return restriction && !restriction->mAllowedVisitCount;
+}
+
void History::Shutdown() {
MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock lockedScope(mBlockShutdownMutex);
@@ -2019,6 +2084,31 @@ History::VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI,
// Error pages should never be autocompleted.
place.isUnrecoverableError = aFlags & IHistory::UNRECOVERABLE_ERROR;
+ // EMBED visits should not go through the database.
+ // They exist only to keep track of isVisited status during the session.
+ if (place.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
+ NotifyEmbedVisit(place);
+ return NS_OK;
+ }
+
+ if (StaticPrefs::places_history_floodingPrevention_enabled()) {
+ // If the origin is restricted, make isVisited status available during the
+ // session but not stored in the database.
+ nsAutoCString origin;
+ Unused << visitedURI->GetHost(origin);
+ if (StringBeginsWith(origin, "www."_ns)) {
+ origin.Cut(0, 4);
+ }
+
+ UpdateOriginFloodingRestriction(origin);
+
+ if (IsRestrictedOrigin(origin)) {
+ NotifyOriginRestrictedVisit(visitedURI);
+ NotifyVisitIfHavingUserPass(aURI);
+ return NS_OK;
+ }
+ }
+
nsCOMPtr<nsIBrowserWindowTracker> bwt =
do_ImportESModule("resource:///modules/BrowserWindowTracker.sys.mjs",
"BrowserWindowTracker", &rv);
@@ -2075,17 +2165,11 @@ History::VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI,
}
}
- // EMBED visits should not go through the database.
- // They exist only to keep track of isVisited status during the session.
- if (place.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
- NotifyEmbedVisit(place);
- } else {
- mozIStorageConnection* dbConn = GetDBConn();
- NS_ENSURE_STATE(dbConn);
+ mozIStorageConnection* dbConn = GetDBConn();
+ NS_ENSURE_STATE(dbConn);
- rv = InsertVisitedURIs::Start(dbConn, std::move(placeArray));
- NS_ENSURE_SUCCESS(rv, rv);
- }
+ rv = InsertVisitedURIs::Start(dbConn, std::move(placeArray));
+ NS_ENSURE_SUCCESS(rv, rv);
// URIs with a userpass component are not stored in the database for security
// reasons, we store the exposable URI version of them instead.
@@ -2323,6 +2407,7 @@ History::IsURIVisited(nsIURI* aURI, mozIVisitedStatusCallback* aCallback) {
NS_IMETHODIMP
History::ClearCache() {
mRecentlyVisitedURIs.Clear();
+ mOriginFloodingRestrictions.Clear();
return NS_OK;
}
diff --git a/toolkit/components/places/History.h b/toolkit/components/places/History.h
index 03a31186fd..a5007082cd 100644
--- a/toolkit/components/places/History.h
+++ b/toolkit/components/places/History.h
@@ -196,6 +196,16 @@ class History final : public BaseHistory,
};
nsTHashMap<nsURIHashKey, RecentURIVisit> mRecentlyVisitedURIs;
+
+ struct OriginFloodingRestriction {
+ TimeStamp mLastVisitTimeStamp;
+ uint32_t mExpireIntervalSeconds;
+ uint32_t mAllowedVisitCount;
+ };
+ nsTHashMap<nsCStringHashKey, OriginFloodingRestriction>
+ mOriginFloodingRestrictions;
+ void UpdateOriginFloodingRestriction(nsACString& aOrigin);
+ bool IsRestrictedOrigin(nsACString& aOrigin);
};
} // namespace mozilla::places
diff --git a/toolkit/components/places/PlacesCompletionCallback.idl b/toolkit/components/places/PlacesCompletionCallback.idl
new file mode 100644
index 0000000000..8cc089c0e7
--- /dev/null
+++ b/toolkit/components/places/PlacesCompletionCallback.idl
@@ -0,0 +1,19 @@
+/* -*- 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);
+};
diff --git a/toolkit/components/places/PlacesQuery.sys.mjs b/toolkit/components/places/PlacesQuery.sys.mjs
index 7ddf5ee988..d969687def 100644
--- a/toolkit/components/places/PlacesQuery.sys.mjs
+++ b/toolkit/components/places/PlacesQuery.sys.mjs
@@ -58,7 +58,7 @@ export class PlacesQuery {
#historyListenerCallback = null;
/** @type {DeferredTask} */
#historyObserverTask = null;
- searchInProgress = false;
+ #searchInProgress = false;
/**
* Get a snapshot of history visits at this moment.
@@ -118,14 +118,18 @@ export class PlacesQuery {
groupBy = "url";
break;
}
+ const whereClause =
+ daysOld == Infinity
+ ? ""
+ : `WHERE visit_date >= (strftime('%s','now','localtime','start of day','-${Number(
+ daysOld
+ )} days','utc') * 1000000)`;
const sql = `SELECT MAX(visit_date) as visit_date, title, url
FROM moz_historyvisits v
JOIN moz_places h
ON v.place_id = h.id
- WHERE visit_date >= (strftime('%s','now','localtime','start of day','-${Number(
- daysOld
- )} days','utc') * 1000000)
AND hidden = 0
+ ${whereClause}
GROUP BY ${groupBy}
ORDER BY visit_date DESC
LIMIT ${limit > 0 ? limit : -1}`;
@@ -169,11 +173,11 @@ export class PlacesQuery {
GROUP BY url
ORDER BY ${orderBy}
LIMIT ${limit > 0 ? limit : -1}`;
- if (this.searchInProgress) {
+ if (this.#searchInProgress) {
db.interrupt();
}
try {
- this.searchInProgress = true;
+ this.#searchInProgress = true;
const rows = await db.executeCached(sql, {
query,
matchBehavior: Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE_UNMODIFIED,
@@ -181,7 +185,7 @@ export class PlacesQuery {
});
return rows.map(row => this.formatRowAsVisit(row));
} finally {
- this.searchInProgress = false;
+ this.#searchInProgress = false;
}
}
@@ -309,7 +313,7 @@ export class PlacesQuery {
}
this.#historyListener = null;
this.#historyListenerCallback = null;
- if (!this.#historyObserverTask.isFinalized) {
+ if (this.#historyObserverTask && !this.#historyObserverTask.isFinalized) {
this.#historyObserverTask.disarm();
this.#historyObserverTask.finalize();
}
diff --git a/toolkit/components/places/moz.build b/toolkit/components/places/moz.build
index 28a7421fba..797d5709f6 100644
--- a/toolkit/components/places/moz.build
+++ b/toolkit/components/places/moz.build
@@ -23,6 +23,7 @@ if CONFIG["MOZ_PLACES"]:
"nsINavBookmarksService.idl",
"nsIPlacesPreviewsHelperService.idl",
"nsITaggingService.idl",
+ "PlacesCompletionCallback.idl",
]
EXPORTS.mozilla.places = [
diff --git a/toolkit/components/places/nsFaviconService.cpp b/toolkit/components/places/nsFaviconService.cpp
index c236d7a680..ba338e9d52 100644
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -14,6 +14,7 @@
*/
#include "nsFaviconService.h"
+#include "PlacesCompletionCallback.h"
#include "nsNavHistory.h"
#include "nsPlacesMacros.h"
@@ -24,6 +25,7 @@
#include "nsStreamUtils.h"
#include "plbase64.h"
#include "nsIClassInfoImpl.h"
+#include "mozilla/AppShutdown.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/NullPrincipal.h"
@@ -35,13 +37,6 @@
#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;
@@ -123,12 +118,10 @@ 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, nsITimerCallback,
- nsINamed)
+NS_IMPL_ISUPPORTS_CI(nsFaviconService, nsIFaviconService)
nsFaviconService::nsFaviconService()
- : mUnassociatedIcons(UNASSOCIATED_FAVICONS_LENGTH),
- mDefaultIconURIPreferredSize(UINT16_MAX) {
+ : mDefaultIconURIPreferredSize(UINT16_MAX) {
NS_ASSERTION(!gFaviconService,
"Attempting to create two instances of the service!");
gFaviconService = this;
@@ -152,10 +145,6 @@ 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;
}
@@ -188,41 +177,6 @@ nsFaviconService::ExpireAllFavicons() {
}
////////////////////////////////////////////////////////////////////////////////
-//// 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
NS_IMETHODIMP
@@ -259,6 +213,161 @@ 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,
uint32_t aFaviconLoadType, nsIFaviconDataCallback* aCallback,
@@ -310,20 +419,13 @@ nsFaviconService::SetAndFetchFaviconForPage(
// Build icon data.
IconData icon;
- // If we have an in-memory icon payload, it overwrites the actual request.
- UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(faviconURI);
- if (iconKey) {
- icon = iconKey->iconData;
- mUnassociatedIcons.RemoveEntry(iconKey);
- } else {
- icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING;
- 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);
- }
+ 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
@@ -361,182 +463,6 @@ nsFaviconService::SetAndFetchFaviconForPage(
}
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,
uint16_t aPreferredWidth) {
diff --git a/toolkit/components/places/nsFaviconService.h b/toolkit/components/places/nsFaviconService.h
index 4485bbaf28..ab142bb5fc 100644
--- a/toolkit/components/places/nsFaviconService.h
+++ b/toolkit/components/places/nsFaviconService.h
@@ -16,8 +16,6 @@
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsIFaviconService.h"
-#include "nsINamed.h"
-#include "nsITimer.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsTHashtable.h"
@@ -32,21 +30,7 @@ extern const uint16_t gFaviconSizes[7];
// forward class definitions
class mozIStorageStatementCallback;
-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 {
+class nsFaviconService final : public nsIFaviconService {
public:
nsFaviconService();
@@ -107,8 +91,6 @@ class nsFaviconService final : public nsIFaviconService,
NS_DECL_ISUPPORTS
NS_DECL_NSIFAVICONSERVICE
- NS_DECL_NSITIMERCALLBACK
- NS_DECL_NSINAMED
private:
imgITools* GetImgTools() {
@@ -122,7 +104,6 @@ class nsFaviconService final : public nsIFaviconService,
RefPtr<mozilla::places::Database> mDB;
- nsCOMPtr<nsITimer> mExpireUnassociatedIconsTimer;
nsCOMPtr<imgITools> mImgTools;
static nsFaviconService* gFaviconService;
@@ -134,11 +115,6 @@ 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;
};
diff --git a/toolkit/components/places/nsIFaviconService.idl b/toolkit/components/places/nsIFaviconService.idl
index 5bc26545c6..f0aeda711f 100644
--- a/toolkit/components/places/nsIFaviconService.idl
+++ b/toolkit/components/places/nsIFaviconService.idl
@@ -9,6 +9,7 @@ interface nsIURI;
interface nsIPrincipal;
interface mozIPlacesPendingOperation;
interface nsIFaviconDataCallback;
+interface PlacesCompletionCallback;
[scriptable, uuid(e81e0b0c-b9f1-4c2e-8f3c-b809933cf73c)]
interface nsIFaviconService : nsISupports
@@ -141,72 +142,35 @@ interface nsIFaviconService : nsISupports
[optional] in unsigned long long aRequestContextID);
/**
- * 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 aFaviconURI
- * 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.
- * @throws NS_ERROR_FAILURE
- * Thrown if the favicon is overbloated and won't be saved to the db.
- */
- 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
+ * 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.
*
+ * @param aPageURI
+ * URI of the page whose favicon is being set.
* @param aFaviconURI
- * URI of the favicon whose data is being set.
+ * URI of the favicon to associate with the page.
* @param aDataURL
- * string containing a data URL that represents the contents of
- * the favicon to save
+ * String that represents a data URL to replace as the favicon content.
* @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.
+ * 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.
*/
- void replaceFaviconDataFromDataURL(in nsIURI aFaviconURI,
- in AString aDataURL,
- [optional] in PRTime aExpiration,
- [optional] in nsIPrincipal aLoadingPrincipal);
+ void setFaviconForPage(
+ in nsIURI aPageURI,
+ in nsIURI aFaviconURI,
+ in nsIURI aDataURL,
+ [optional] in PRTime aExpiration,
+ [optional] in PlacesCompletionCallback aCallback
+ );
/**
* Retrieves the favicon URI associated to the given page, if any.
diff --git a/toolkit/components/places/tests/PlacesTestUtils.sys.mjs b/toolkit/components/places/tests/PlacesTestUtils.sys.mjs
index 4e459d2e32..97dda6367f 100644
--- a/toolkit/components/places/tests/PlacesTestUtils.sys.mjs
+++ b/toolkit/components/places/tests/PlacesTestUtils.sys.mjs
@@ -156,6 +156,43 @@ 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.
*/
diff --git a/toolkit/components/places/tests/browser/browser.toml b/toolkit/components/places/tests/browser/browser.toml
index b7d688c980..aceee48f24 100644
--- a/toolkit/components/places/tests/browser/browser.toml
+++ b/toolkit/components/places/tests/browser/browser.toml
@@ -117,3 +117,15 @@ support-files = [
"favicon.html",
"final.html",
]
+
+["browser_visituri_restriction.js"]
+skip-if = [
+ "verify",
+ "os == 'linux' && (asan || tsan)", # Bug 1891145
+]
+
+["browser_visituri_restriction_origin.js"]
+skip-if = [
+ "verify",
+ "os == 'linux' && (asan || tsan)", # Bug 1891145
+]
diff --git a/toolkit/components/places/tests/browser/browser_visituri_restriction.js b/toolkit/components/places/tests/browser/browser_visituri_restriction.js
new file mode 100644
index 0000000000..2af41e6f51
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_visituri_restriction.js
@@ -0,0 +1,227 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const TEST_PAGE = `data:text/html,
+ <a href="https://example.com/" target="_blank">https://example.com</a>
+ <a href="http://example.com/" target="_blank">http://example.com</a>
+ <a href="https://www.example.com/" target="_blank">https://www.example.com</a>
+ <a href="https://example.org/" target="_blank">https://example.org</a>`;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Enable restriction feature.
+ ["places.history.floodingPrevention.enabled", true],
+ // Restrict from the second visit.
+ ["places.history.floodingPrevention.restrictionCount", 1],
+ ["places.history.floodingPrevention.restrictionExpireSeconds", 4],
+ // Not apply flooding prevention until some seconds elapse after user
+ // interaction begins.
+ [
+ "places.history.floodingPrevention.maxSecondsFromLastUserInteraction",
+ 4,
+ ],
+ // To enable UserActivation by EventUtils.synthesizeMouseAtCenter() in
+ // ContentTask.spawn() in synthesizeVisitByUser().
+ ["test.events.async.enabled", true],
+ ],
+ });
+ await clearHistoryAndHistoryCache();
+});
+
+add_task(async function basic() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_PAGE },
+ async browser => {
+ info("Sanity check");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+ await assertLinkVisitedStatus(browser, "https://www.example.com/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+ await assertLinkVisitedStatus(browser, "https://example.org/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+
+ info("Visit https://example.com/ by user");
+ await synthesizeVisitByUser(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 1,
+ isVisited: true,
+ });
+
+ info(
+ "Visit https://example.com/ by script within maxSecondsFromLastUserInteraction"
+ );
+ await synthesizeVisitByScript(browser, "https://example.com/");
+ await synthesizeVisitByScript(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 3,
+ isVisited: true,
+ });
+
+ await waitForPrefSeconds("maxSecondsFromLastUserInteraction");
+
+ info(
+ "Visit https://example.com/ by script without maxSecondsFromLastUserInteraction"
+ );
+ await synthesizeVisitByScript(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 4,
+ isVisited: true,
+ });
+
+ info("Visit again, but it should be restricted");
+ await synthesizeVisitByScript(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 4,
+ isVisited: true,
+ });
+
+ info("Check other");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await assertLinkVisitedStatus(browser, "http://example.com/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+ await assertLinkVisitedStatus(browser, "https://www.example.com/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+ await assertLinkVisitedStatus(browser, "https://example.org/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+ }
+ );
+
+ await clearHistoryAndHistoryCache();
+});
+
+add_task(async function expireRestriction() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_PAGE },
+ async browser => {
+ info("Visit https://example.com/ by user");
+ await synthesizeVisitByUser(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 1,
+ isVisited: true,
+ });
+
+ info(
+ "Visit https://example.com/ by script within maxSecondsFromLastUserInteraction"
+ );
+ await synthesizeVisitByScript(browser, "https://example.com/");
+ await synthesizeVisitByScript(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 3,
+ isVisited: true,
+ });
+
+ await waitForPrefSeconds("maxSecondsFromLastUserInteraction");
+
+ info(
+ "Visit https://example.com/ by script without maxSecondsFromLastUserInteraction"
+ );
+ await synthesizeVisitByScript(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 4,
+ isVisited: true,
+ });
+
+ await waitForPrefSeconds("restrictionExpireSeconds");
+
+ info("Visit again, it should not be restricted");
+ await synthesizeVisitByScript(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 5,
+ isVisited: true,
+ });
+ }
+ );
+
+ await clearHistoryAndHistoryCache();
+});
+
+add_task(async function userInputAlwaysAcceptable() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_PAGE },
+ async browser => {
+ info("Visit by user");
+ await synthesizeVisitByUser(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 1,
+ isVisited: true,
+ });
+ await synthesizeVisitByUser(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 2,
+ isVisited: true,
+ });
+
+ await waitForPrefSeconds("maxSecondsFromLastUserInteraction");
+
+ info("Visit by user input");
+ await synthesizeVisitByUser(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 3,
+ isVisited: true,
+ });
+ await synthesizeVisitByUser(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 4,
+ isVisited: true,
+ });
+ }
+ );
+
+ await clearHistoryAndHistoryCache();
+});
+
+add_task(async function disable() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["places.history.floodingPrevention.enabled", false]],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_PAGE },
+ async browser => {
+ info("Any visits are stored");
+ for (let i = 0; i < 3; i++) {
+ await synthesizeVisitByScript(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: i + 1,
+ isVisited: true,
+ });
+ }
+ }
+ );
+
+ await clearHistoryAndHistoryCache();
+});
+
+async function waitForPrefSeconds(pref) {
+ info(`Wait until elapsing ${pref}`);
+ return new Promise(r =>
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(
+ r,
+ Services.prefs.getIntPref(`places.history.floodingPrevention.${pref}`) *
+ 1000 +
+ 100
+ )
+ );
+}
diff --git a/toolkit/components/places/tests/browser/browser_visituri_restriction_origin.js b/toolkit/components/places/tests/browser/browser_visituri_restriction_origin.js
new file mode 100644
index 0000000000..ee691c79e8
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_visituri_restriction_origin.js
@@ -0,0 +1,92 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const TEST_PAGE = `data:text/html,
+ <a href="https://example.com/" target="_blank">https://example.com</a>
+ <a href="http://example.com/" target="_blank">http://example.com</a>
+ <a href="https://www.example.com/" target="_blank">https://www.example.com</a>
+ <a href="https://example.org/" target="_blank">https://example.org</a>`;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Enable restriction feature.
+ ["places.history.floodingPrevention.enabled", true],
+ // Restrict from the second visit.
+ ["places.history.floodingPrevention.restrictionCount", 1],
+ // Stop expiring.
+ ["places.history.floodingPrevention.restrictionExpireSeconds", 100],
+ // Always appply flooding preveition.
+ [
+ "places.history.floodingPrevention.maxSecondsFromLastUserInteraction",
+ 0,
+ ],
+ // To enable UserActivation by EventUtils.synthesizeMouseAtCenter() in ContentTask.spawn() in synthesizeVisitByUser().
+ ["test.events.async.enabled", true],
+ ],
+ });
+});
+
+add_task(async function same_origin_but_other() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_PAGE },
+ async browser => {
+ info("Sanity check");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await assertLinkVisitedStatus(browser, "http://example.com/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+ await assertLinkVisitedStatus(browser, "https://www.example.com/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+ await assertLinkVisitedStatus(browser, "https://example.org/", {
+ visitCount: 0,
+ isVisited: false,
+ });
+
+ info("Visit https://example.com/ by user");
+ await synthesizeVisitByUser(browser, "https://example.com/");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 1,
+ isVisited: true,
+ });
+
+ info("Visit others by Scripts");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await synthesizeVisitByScript(browser, "http://example.com/");
+ await synthesizeVisitByScript(browser, "https://www.example.com/");
+ await synthesizeVisitByScript(browser, "https://example.org/");
+
+ info("Check the status");
+ await assertLinkVisitedStatus(browser, "https://example.com/", {
+ visitCount: 1,
+ isVisited: true,
+ });
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await assertLinkVisitedStatus(browser, "http://example.com/", {
+ visitCount: 0,
+ isVisited: true,
+ });
+ await assertLinkVisitedStatus(browser, "https://www.example.com/", {
+ visitCount: 0,
+ isVisited: true,
+ });
+ await assertLinkVisitedStatus(browser, "https://example.org/", {
+ visitCount: 1,
+ isVisited: true,
+ });
+ }
+ );
+
+ await clearHistoryAndHistoryCache();
+});
diff --git a/toolkit/components/places/tests/browser/head.js b/toolkit/components/places/tests/browser/head.js
index b1b916ee29..da40a832c6 100644
--- a/toolkit/components/places/tests/browser/head.js
+++ b/toolkit/components/places/tests/browser/head.js
@@ -17,3 +17,80 @@ function whenNewWindowLoaded(aOptions, aCallback) {
BrowserTestUtils.waitForNewWindow().then(aCallback);
OpenBrowserWindow(aOptions);
}
+
+async function clearHistoryAndHistoryCache() {
+ await PlacesUtils.history.clear();
+ // Clear HistoryRestiction cache as well.
+ Cc["@mozilla.org/browser/history;1"]
+ .getService(Ci.mozIAsyncHistory)
+ .clearCache();
+}
+
+async function synthesizeVisitByUser(browser, url) {
+ let onNewTab = BrowserTestUtils.waitForNewTab(browser.ownerGlobal.gBrowser);
+ // We intentionally turn off this a11y check, because the following click is
+ // purposefully sent on an arbitrary web content that is not expected to be
+ // tested by itself with the browser mochitests, therefore this rule check
+ // shall be ignored by a11y_checks suite.
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
+ await ContentTask.spawn(browser, [url], async ([href]) => {
+ EventUtils.synthesizeMouseAtCenter(
+ content.document.querySelector(`a[href='${href}'`),
+ {},
+ content
+ );
+ });
+ AccessibilityUtils.resetEnv();
+ let tab = await onNewTab;
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function synthesizeVisitByScript(browser, url) {
+ let onNewTab = BrowserTestUtils.waitForNewTab(browser.ownerGlobal.gBrowser);
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
+ await ContentTask.spawn(browser, [url], async ([href]) => {
+ let a = content.document.querySelector(`a[href='${href}'`);
+ a.click();
+ });
+ AccessibilityUtils.resetEnv();
+ let tab = await onNewTab;
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function assertLinkVisitedStatus(
+ browser,
+ url,
+ { visitCount: expectedVisitCount, isVisited: expectedVisited }
+) {
+ await BrowserTestUtils.waitForCondition(async () => {
+ let visitCount =
+ (await PlacesTestUtils.getDatabaseValue("moz_places", "visit_count", {
+ url,
+ })) ?? 0;
+
+ if (visitCount != expectedVisitCount) {
+ return false;
+ }
+
+ Assert.equal(visitCount, expectedVisitCount, "The visit count is correct");
+ return true;
+ });
+
+ await ContentTask.spawn(
+ browser,
+ [url, expectedVisited],
+ async ([href, visited]) => {
+ // ElementState::VISITED
+ const VISITED_STATE = 1 << 18;
+ await ContentTaskUtils.waitForCondition(() => {
+ let isVisited = !!(
+ content.InspectorUtils.getContentState(
+ content.document.querySelector(`a[href='${href}']`)
+ ) & VISITED_STATE
+ );
+ return isVisited == visited;
+ });
+ }
+ );
+ Assert.ok(true, "The visited state is corerct");
+}
diff --git a/toolkit/components/places/tests/chrome/test_cached_favicon.xhtml b/toolkit/components/places/tests/chrome/test_cached_favicon.xhtml
index f7e7f4f1d8..1046574bdf 100644
--- a/toolkit/components/places/tests/chrome/test_cached_favicon.xhtml
+++ b/toolkit/components/places/tests/chrome/test_cached_favicon.xhtml
@@ -104,19 +104,12 @@ function test()
// icon with a page explicitly in order for it to be visible through
// the protocol.
info("Replace favicon data");
- 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(
+ PlacesUtils.favicons.setFaviconForPage(
Services.io.newURI("https://example.com/favicon_annotations"),
Services.io.newURI(tests[1].url),
- true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
- systemPrincipal);
+ Services.io.newURI(tests[1].expectedIcon),
+ (Date.now() + 86400) * 1000,
+ );
// And start our test process.
loadNextTest();
diff --git a/toolkit/components/places/tests/expiration/test_debug_expiration.js b/toolkit/components/places/tests/expiration/test_debug_expiration.js
index 204295d46c..22657e6b24 100644
--- a/toolkit/components/places/tests/expiration/test_debug_expiration.js
+++ b/toolkit/components/places/tests/expiration/test_debug_expiration.js
@@ -356,13 +356,7 @@ add_task(async function test_expire_icons() {
}
if (entry.icon) {
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
- Services.io.newURI(entry.icon),
- dataUrl,
- 0,
- Services.scriptSecurityManager.getSystemPrincipal()
- );
- await PlacesTestUtils.addFavicons(new Map([[entry.page, entry.icon]]));
+ await PlacesTestUtils.setFaviconForPage(entry.page, entry.icon, dataUrl);
Assert.equal(
await getFaviconUrlForPage(entry.page),
entry.icon,
@@ -380,13 +374,7 @@ add_task(async function test_expire_icons() {
}
if (entry.root) {
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
- Services.io.newURI(entry.root),
- dataUrl,
- 0,
- Services.scriptSecurityManager.getSystemPrincipal()
- );
- await PlacesTestUtils.addFavicons(new Map([[entry.page, entry.root]]));
+ await PlacesTestUtils.setFaviconForPage(entry.page, entry.root, dataUrl);
}
if (entry.iconExpired) {
diff --git a/toolkit/components/places/tests/favicons/head_favicons.js b/toolkit/components/places/tests/favicons/head_favicons.js
index afd2c4924f..73dd2a61ed 100644
--- a/toolkit/components/places/tests/favicons/head_favicons.js
+++ b/toolkit/components/places/tests/favicons/head_favicons.js
@@ -15,6 +15,9 @@
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+// Used in createFavicon().
+let uniqueFaviconId = 0;
+
/**
* Checks that the favicon for the given page matches the provided data.
*
@@ -24,6 +27,7 @@ const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
* 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.
*/
@@ -37,7 +41,9 @@ function checkFaviconDataForPage(
aPageURI,
async function (aURI, aDataLen, aData, aMimeType) {
Assert.equal(aExpectedMimeType, aMimeType);
- Assert.ok(compareArrays(aExpectedData, aData));
+ if (aExpectedData) {
+ Assert.ok(compareArrays(aExpectedData, aData));
+ }
await check_guid_for_uri(aPageURI);
aCallback();
}
@@ -76,3 +82,82 @@ 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);
+ });
+}
diff --git a/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js b/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js
index c1f6689a70..52eb81ae85 100644
--- a/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js
+++ b/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js
@@ -68,14 +68,12 @@ 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
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ await PlacesTestUtils.addVisits(testURI);
+ await PlacesTestUtils.setFaviconForPage(
+ testURI,
testIconURI,
- testFaviconData,
- 0,
- systemPrincipal
+ testFaviconData
);
- await PlacesTestUtils.addVisits(testURI);
- await setFaviconForPage(testURI, testIconURI);
// Open the channel
let channel = NetUtil.newChannel({
uri: PlacesUtils.favicons.getFaviconLinkForIcon(testIconURI).spec,
@@ -120,14 +118,12 @@ add_task(async function test_userpass() {
CACHED_ICON_NORMAL,
CACHED_ICON_USERPASS,
]) {
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ await PlacesTestUtils.addVisits(pageURI);
+ await PlacesTestUtils.setFaviconForPage(
+ pageURI,
iconURI,
- testFaviconData,
- 0,
- systemPrincipal
+ testFaviconData
);
- await PlacesTestUtils.addVisits(pageURI);
- await setFaviconForPage(pageURI, iconURI);
// Open the channel
let channel = NetUtil.newChannel({
diff --git a/toolkit/components/places/tests/favicons/test_expire_on_new_icons.js b/toolkit/components/places/tests/favicons/test_expire_on_new_icons.js
index d5a7c42ba3..f0089d737b 100644
--- a/toolkit/components/places/tests/favicons/test_expire_on_new_icons.js
+++ b/toolkit/components/places/tests/favicons/test_expire_on_new_icons.js
@@ -28,23 +28,23 @@ add_task(async function test_expire_associated() {
];
for (let icon of favicons) {
- let data = readFileData(do_get_file(icon.name));
- PlacesUtils.favicons.replaceFaviconData(
- NetUtil.newURI(TEST_URL + icon.name),
- data,
+ let dataURL = await readFileDataAsDataURL(
+ do_get_file(icon.name),
icon.mimeType
);
- await setFaviconForPage(TEST_URL, TEST_URL + icon.name);
+ await PlacesTestUtils.setFaviconForPage(
+ TEST_URL,
+ TEST_URL + icon.name,
+ dataURL
+ );
if (icon.expired) {
await expireIconRelationsForPage(TEST_URL);
// Add the same icon to another page.
- PlacesUtils.favicons.replaceFaviconData(
- NetUtil.newURI(TEST_URL + icon.name),
- data,
- icon.mimeType,
- icon.expire
+ await PlacesTestUtils.setFaviconForPage(
+ TEST_URL2,
+ TEST_URL + icon.name,
+ dataURL
);
- await setFaviconForPage(TEST_URL2, TEST_URL + icon.name);
}
}
@@ -88,13 +88,7 @@ add_task(async function test_expire_root() {
// Insert an expired icon.
let iconURI = NetUtil.newURI(pageURI.spec + "favicon-normal16.png");
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
- iconURI,
- SMALLPNG_DATA_URI.spec,
- 0,
- systemPrincipal
- );
- await setFaviconForPage(pageURI, iconURI);
+ await PlacesTestUtils.setFaviconForPage(pageURI, iconURI, SMALLPNG_DATA_URI);
Assert.equal(
await countEntries("moz_icons_to_pages"),
1,
@@ -105,13 +99,11 @@ add_task(async function test_expire_root() {
// Now insert a new root icon.
let rootIconURI = NetUtil.newURI(pageURI.spec + "favicon.ico");
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ await PlacesTestUtils.setFaviconForPage(
+ pageURI,
rootIconURI,
- SMALLPNG_DATA_URI.spec,
- 0,
- systemPrincipal
+ SMALLPNG_DATA_URI
);
- await setFaviconForPage(pageURI, rootIconURI);
// Only the root icon should have survived.
Assert.equal(
diff --git a/toolkit/components/places/tests/favicons/test_favicons_conversions.js b/toolkit/components/places/tests/favicons/test_favicons_conversions.js
index 28a0fffb7f..dd8a35c5ac 100644
--- a/toolkit/components/places/tests/favicons/test_favicons_conversions.js
+++ b/toolkit/components/places/tests/favicons/test_favicons_conversions.js
@@ -43,29 +43,24 @@ 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 => {
- 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()
- );
+ 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);
+ }
});
}
diff --git a/toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js b/toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js
index aa0241a3d2..4d359f7307 100644
--- a/toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js
+++ b/toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js
@@ -9,21 +9,16 @@ 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 data = readFileData(do_get_file("favicon-normal16.png"));
- PlacesUtils.favicons.replaceFaviconData(
- Services.io.newURI(ICON16_URL),
- data,
+ let dataURL16 = await readFileDataAsDataURL(
+ do_get_file("favicon-normal16.png"),
"image/png"
);
- await setFaviconForPage(PAGE_URL, ICON16_URL);
- data = readFileData(do_get_file("favicon-normal32.png"));
- PlacesUtils.favicons.replaceFaviconData(
- Services.io.newURI(ICON32_URL),
- data,
+ await PlacesTestUtils.setFaviconForPage(PAGE_URL, ICON16_URL, dataURL16);
+ let dataURL32 = await readFileDataAsDataURL(
+ do_get_file("favicon-normal32.png"),
"image/png"
);
- await setFaviconForPage(PAGE_URL, ICON32_URL);
+ await PlacesTestUtils.setFaviconForPage(PAGE_URL, ICON32_URL, dataURL32);
const PAGE_ICON_URL = "page-icon:" + PAGE_URL;
diff --git a/toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js b/toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js
index 80f498f33f..8c6fb0a2bb 100644
--- a/toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js
+++ b/toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js
@@ -60,12 +60,8 @@ 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"));
- PlacesUtils.favicons.replaceFaviconData(
- NetUtil.newURI(ROOT_ICON_URL),
- data,
- "image/png"
- );
- await setFaviconForPage(ROOT_URL, ROOT_ICON_URL);
+ let dataURL = await fileDataToDataURL(data, "image/png");
+ await PlacesTestUtils.setFaviconForPage(ROOT_URL, ROOT_ICON_URL, dataURL);
info("check fallback icons");
await new Promise(resolve => {
@@ -96,12 +92,8 @@ 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"));
- PlacesUtils.favicons.replaceFaviconData(
- NetUtil.newURI(ICON32_URL),
- data32,
- "image/png"
- );
- await setFaviconForPage(SUBPAGE_URL, ICON32_URL);
+ let dataURL32 = await fileDataToDataURL(data32, "image/png");
+ await PlacesTestUtils.setFaviconForPage(SUBPAGE_URL, ICON32_URL, dataURL32);
info("check no fallback icons");
await new Promise(resolve => {
diff --git a/toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js b/toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js
index e8f459cb08..3fa11ae6c0 100644
--- a/toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js
+++ b/toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js
@@ -57,13 +57,11 @@ 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"));
- PlacesUtils.favicons.replaceFaviconData(
- NetUtil.newURI(ROOT_ICON_URL),
- data,
+ let dataURL = await readFileDataAsDataURL(
+ do_get_file("favicon-normal16.png"),
"image/png"
);
- await setFaviconForPage(ROOT_URL, ROOT_ICON_URL);
+ await PlacesTestUtils.setFaviconForPage(ROOT_URL, ROOT_ICON_URL, dataURL);
info("check fallback icons");
Assert.equal(
@@ -79,13 +77,11 @@ 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"));
- PlacesUtils.favicons.replaceFaviconData(
- NetUtil.newURI(ICON32_URL),
- data32,
+ let dataURL32 = await readFileDataAsDataURL(
+ do_get_file("favicon-normal32.png"),
"image/png"
);
- await setFaviconForPage(SUBPAGE_URL, ICON32_URL);
+ await PlacesTestUtils.setFaviconForPage(SUBPAGE_URL, ICON32_URL, dataURL32);
info("check no fallback icons");
Assert.equal(
diff --git a/toolkit/components/places/tests/favicons/test_heavy_favicon.js b/toolkit/components/places/tests/favicons/test_heavy_favicon.js
index 09adcaf6fa..4541de9bff 100644
--- a/toolkit/components/places/tests/favicons/test_heavy_favicon.js
+++ b/toolkit/components/places/tests/favicons/test_heavy_favicon.js
@@ -26,8 +26,8 @@ add_task(async function () {
let pageURI = uri("http://foo.bar/");
await PlacesTestUtils.addVisits(pageURI);
- PlacesUtils.favicons.replaceFaviconData(icon.uri, icon.data, icon.mimetype);
- await setFaviconForPage(pageURI, icon.uri);
+ let dataURI = await fileDataToDataURL(icon.data, icon.mimetype);
+ await PlacesTestUtils.setFaviconForPage(pageURI.spec, icon.uri.spec, dataURI);
Assert.equal(
await getFaviconUrlForPage(pageURI),
icon.uri.spec,
diff --git a/toolkit/components/places/tests/favicons/test_incremental_vacuum.js b/toolkit/components/places/tests/favicons/test_incremental_vacuum.js
index ab93121d47..2c1a5ad3f9 100644
--- a/toolkit/components/places/tests/favicons/test_incremental_vacuum.js
+++ b/toolkit/components/places/tests/favicons/test_incremental_vacuum.js
@@ -16,10 +16,9 @@ add_task(async function () {
let url = "http://foo.bar/";
await PlacesTestUtils.addVisits(url);
for (let i = 0; i < 10; ++i) {
- 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 iconUri = "http://mozilla.org/" + i;
+ let dataURL = await readFileDataAsDataURL(icon.file, icon.mimetype);
+ await PlacesTestUtils.setFaviconForPage(url, iconUri, dataURL);
}
let promise = TestUtils.topicObserved("places-favicons-expired");
diff --git a/toolkit/components/places/tests/favicons/test_multiple_frames.js b/toolkit/components/places/tests/favicons/test_multiple_frames.js
index 5c7f585715..a6aae572de 100644
--- a/toolkit/components/places/tests/favicons/test_multiple_frames.js
+++ b/toolkit/components/places/tests/favicons/test_multiple_frames.js
@@ -14,9 +14,15 @@ add_task(async function () {
let faviconURI = NetUtil.newURI("http://places.test/icon/favicon-multi.ico");
// Fake window.
let win = { devicePixelRatio: 1.0 };
- let icoData = readFileData(do_get_file("favicon-multi.ico"));
- PlacesUtils.favicons.replaceFaviconData(faviconURI, icoData, "image/x-icon");
- await setFaviconForPage(pageURI, faviconURI);
+ let icoDataURL = await readFileDataAsDataURL(
+ do_get_file("favicon-multi.ico"),
+ "image/x-icon"
+ );
+ await PlacesTestUtils.setFaviconForPage(
+ pageURI.spec,
+ faviconURI.spec,
+ icoDataURL
+ );
for (let size of [16, 32, 64]) {
let file = do_get_file(`favicon-multi-frame${size}.png`);
diff --git a/toolkit/components/places/tests/favicons/test_page-icon_protocol.js b/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
index 287484868f..2241dec990 100644
--- a/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
+++ b/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
@@ -76,25 +76,12 @@ var gFavicon;
add_task(async function setup() {
await PlacesTestUtils.addVisits(TEST_URI);
-
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ await PlacesTestUtils.setFaviconForPage(
+ TEST_URI,
ICON_URI,
ICON_DATAURL,
- (Date.now() + 8640000) * 1000,
- Services.scriptSecurityManager.getSystemPrincipal()
+ (Date.now() + 8640000) * 1000
);
-
- 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
);
@@ -133,13 +120,11 @@ add_task(async function subpage_url_fallback() {
add_task(async function svg_icon() {
let faviconURI = NetUtil.newURI("http://places.test/favicon.svg");
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ await PlacesTestUtils.setFaviconForPage(
+ TEST_URI,
faviconURI,
- SMALLSVG_DATA_URI.spec,
- 0,
- Services.scriptSecurityManager.getSystemPrincipal()
+ SMALLSVG_DATA_URI
);
- 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);
@@ -270,23 +255,8 @@ 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 new Promise(resolve => {
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- pageURI,
- iconURI,
- false,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
- resolve,
- Services.scriptSecurityManager.getSystemPrincipal()
- );
- });
+ await PlacesTestUtils.setFaviconForPage(pageURI, iconURI, ICON_DATAURL);
let { data, contentType } = await fetchIconForSpec(loadingIconURISpec);
Assert.equal(contentType, gFavicon.contentType);
diff --git a/toolkit/components/places/tests/favicons/test_replaceFaviconData.js b/toolkit/components/places/tests/favicons/test_replaceFaviconData.js
deleted file mode 100644
index 2e9835eaa9..0000000000
--- a/toolkit/components/places/tests/favicons/test_replaceFaviconData.js
+++ /dev/null
@@ -1,395 +0,0 @@
-/* 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();
-});
diff --git a/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js b/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js
deleted file mode 100644
index c1b83fc8a7..0000000000
--- a/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js
+++ /dev/null
@@ -1,537 +0,0 @@
-/* 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;
-}
diff --git a/toolkit/components/places/tests/favicons/test_root_icons.js b/toolkit/components/places/tests/favicons/test_root_icons.js
index f0487cc162..5a101ed6a6 100644
--- a/toolkit/components/places/tests/favicons/test_root_icons.js
+++ b/toolkit/components/places/tests/favicons/test_root_icons.js
@@ -9,13 +9,11 @@ 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");
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ await PlacesTestUtils.setFaviconForPage(
+ pageURI,
faviconURI,
- SMALLPNG_DATA_URI.spec,
- 0,
- systemPrincipal
+ SMALLPNG_DATA_URI
);
- await setFaviconForPage(pageURI, faviconURI);
// Sanity checks.
Assert.equal(await getFaviconUrlForPage(pageURI), faviconURI.spec);
@@ -70,22 +68,18 @@ add_task(async function test_removePagesByTimeframe() {
// Add a normal icon to the most recent page.
let faviconURI = NetUtil.newURI(`${BASE_URL}/page/favicon.ico`);
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ await PlacesTestUtils.setFaviconForPage(
+ pageURI,
faviconURI,
- SMALLSVG_DATA_URI.spec,
- 0,
- systemPrincipal
+ SMALLSVG_DATA_URI
);
- await setFaviconForPage(pageURI, faviconURI);
// Add a root icon to the most recent page.
let rootIconURI = NetUtil.newURI(`${BASE_URL}/favicon.ico`);
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ await PlacesTestUtils.setFaviconForPage(
+ pageURI,
rootIconURI,
- SMALLPNG_DATA_URI.spec,
- 0,
- systemPrincipal
+ SMALLPNG_DATA_URI
);
- await setFaviconForPage(pageURI, rootIconURI);
// Sanity checks.
Assert.equal(
@@ -141,13 +135,11 @@ 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");
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ await PlacesTestUtils.setFaviconForPage(
+ pageURI,
faviconURI,
- SMALLPNG_DATA_URI.spec,
- 0,
- systemPrincipal
+ SMALLPNG_DATA_URI
);
- await setFaviconForPage(pageURI, faviconURI);
Assert.equal(
await getFaviconUrlForPage(pageURI),
@@ -166,16 +158,25 @@ 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 data = readFileData(do_get_file("favicon-normal32.png"));
+ let dataURL = await readFileDataAsDataURL(
+ do_get_file("favicon-normal32.png"),
+ "image/png"
+ );
let pageURI = NetUtil.newURI("http://new_places.test/page/");
await PlacesTestUtils.addVisits(pageURI);
let faviconURI = NetUtil.newURI("http://new_places.test/favicon.ico");
- PlacesUtils.favicons.replaceFaviconData(faviconURI, data, "image/png");
- await setFaviconForPage(pageURI, faviconURI);
+ await PlacesTestUtils.setFaviconForPage(
+ pageURI.spec,
+ faviconURI.spec,
+ dataURL
+ );
faviconURI = NetUtil.newURI("http://new_places.test/another_icon.ico");
- PlacesUtils.favicons.replaceFaviconData(faviconURI, data, "image/png");
- await setFaviconForPage(pageURI, faviconURI);
+ await PlacesTestUtils.setFaviconForPage(
+ pageURI.spec,
+ faviconURI.spec,
+ dataURL
+ );
Assert.equal(
await getFaviconUrlForPage(pageURI, 20),
@@ -207,13 +208,7 @@ 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);
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
- iconURI,
- SMALLPNG_DATA_URI.spec,
- 0,
- systemPrincipal
- );
- await setFaviconForPage(pageURI1, iconURI);
+ await PlacesTestUtils.setFaviconForPage(pageURI1, iconURI, SMALLPNG_DATA_URI);
Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1");
Assert.equal(
await getFaviconUrlForPage(pageURI1, 16),
@@ -222,13 +217,7 @@ add_task(async function test_root_on_different_host() {
);
// Same favicon for TEST_URL2.
- PlacesUtils.favicons.replaceFaviconDataFromDataURL(
- iconURI,
- SMALLPNG_DATA_URI.spec,
- 0,
- systemPrincipal
- );
- await setFaviconForPage(pageURI2, iconURI);
+ await PlacesTestUtils.setFaviconForPage(pageURI2, iconURI, SMALLPNG_DATA_URI);
Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1");
Assert.equal(
await getFaviconUrlForPage(pageURI2, 16),
diff --git a/toolkit/components/places/tests/favicons/test_setFaviconForPage.js b/toolkit/components/places/tests/favicons/test_setFaviconForPage.js
new file mode 100644
index 0000000000..22364e5410
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_setFaviconForPage.js
@@ -0,0 +1,245 @@
+/* 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
+ );
+ });
+}
diff --git a/toolkit/components/places/tests/favicons/xpcshell.toml b/toolkit/components/places/tests/favicons/xpcshell.toml
index 997aa48e0b..2716e72fda 100644
--- a/toolkit/components/places/tests/favicons/xpcshell.toml
+++ b/toolkit/components/places/tests/favicons/xpcshell.toml
@@ -57,10 +57,6 @@ support-files = [
["test_query_result_favicon_changed_on_child.js"]
-["test_replaceFaviconData.js"]
-
-["test_replaceFaviconDataFromDataURL.js"]
-
["test_root_icons.js"]
["test_setAndFetchFaviconForPage.js"]
@@ -69,4 +65,6 @@ support-files = [
["test_setAndFetchFaviconForPage_redirects.js"]
+["test_setFaviconForPage.js"]
+
["test_svg_favicon.js"]
diff --git a/toolkit/components/places/tests/gtest/places_test_harness_tail.h b/toolkit/components/places/tests/gtest/places_test_harness_tail.h
index 0464d14e0d..27de44eaa9 100644
--- a/toolkit/components/places/tests/gtest/places_test_harness_tail.h
+++ b/toolkit/components/places/tests/gtest/places_test_harness_tail.h
@@ -20,7 +20,7 @@ class RunNextTest : public mozilla::Runnable {
public:
RunNextTest() : mozilla::Runnable("RunNextTest") {}
NS_IMETHOD Run() override {
- NS_ASSERTION(NS_IsMainThread(), "Not running on the main thread?");
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Not running on the main thread?");
if (gTestsIndex < int(mozilla::ArrayLength(gTests))) {
do_test_pending();
Test& test = gTests[gTestsIndex++];
@@ -47,7 +47,7 @@ void run_next_test() {
int gPendingTests = 0;
void do_test_pending() {
- NS_ASSERTION(NS_IsMainThread(), "Not running on the main thread?");
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Not running on the main thread?");
if (kDebugRunNextTest) {
printf_stderr("do_test_pending()\n");
MozWalkTheStack(stderr);
@@ -56,8 +56,8 @@ void do_test_pending() {
}
void do_test_finished() {
- NS_ASSERTION(NS_IsMainThread(), "Not running on the main thread?");
- NS_ASSERTION(gPendingTests > 0, "Invalid pending test count!");
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Not running on the main thread?");
+ MOZ_RELEASE_ASSERT(gPendingTests > 0, "Invalid pending test count!");
gPendingTests--;
}
diff --git a/toolkit/components/places/tests/head_common.js b/toolkit/components/places/tests/head_common.js
index d7a465786e..22d89db139 100644
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -169,7 +169,6 @@ function readFileData(aFile) {
}
return bytes;
}
-
/**
* Reads the data from the named file, verifying the expected file length.
*
@@ -187,6 +186,42 @@ function readFileOfLength(aFileName, aExpectedLength) {
}
/**
+ * 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.
*
diff --git a/toolkit/components/places/tests/history/test_remove.js b/toolkit/components/places/tests/history/test_remove.js
index c018a996f3..8c469b5930 100644
--- a/toolkit/components/places/tests/history/test_remove.js
+++ b/toolkit/components/places/tests/history/test_remove.js
@@ -292,20 +292,7 @@ add_task(async function test_orphans() {
);
// Also create a root icon.
let faviconURI = Services.io.newURI(uri.spec + "favicon.ico");
- 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 PlacesTestUtils.setFaviconForPage(uri, faviconURI, SMALLPNG_DATA_URI);
await PlacesUtils.history.update({
url: uri,
diff --git a/toolkit/components/places/tests/unit/test_PlacesQuery_history.js b/toolkit/components/places/tests/unit/test_PlacesQuery_history.js
index 9546e03730..24a046bcdc 100644
--- a/toolkit/components/places/tests/unit/test_PlacesQuery_history.js
+++ b/toolkit/components/places/tests/unit/test_PlacesQuery_history.js
@@ -3,6 +3,10 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
const { PlacesQuery } = ChromeUtils.importESModule(
"resource://gre/modules/PlacesQuery.sys.mjs"
);
@@ -243,3 +247,32 @@ add_task(async function test_search_visits() {
await PlacesUtils.history.clear();
});
+
+add_task(async function test_search_interrupt() {
+ const { promise, reject } = Promise.withResolvers();
+ const mockDatabase = {
+ executeCached: async (_, { query }) => {
+ if (query === "First Query") {
+ // Simulate a slow-running query which runs long enough to be
+ // interrupted by the next one.
+ await promise;
+ }
+ return [];
+ },
+ interrupt: () => reject("interrupt() was called."),
+ };
+ const stub = sinon
+ .stub(PlacesUtils, "promiseLargeCacheDBConnection")
+ .resolves(mockDatabase);
+
+ const promiseFirstQuery = placesQuery.searchHistory("First Query");
+ const promiseSecondQuery = placesQuery.searchHistory("Second Query");
+ await Assert.rejects(
+ promiseFirstQuery,
+ /interrupt/,
+ "The first query was interrupted."
+ );
+ ok(await promiseSecondQuery, "The second query resolved normally.");
+
+ stub.restore();
+});