summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/private/BlockedSuggestions.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/private/BlockedSuggestions.sys.mjs')
-rw-r--r--browser/components/urlbar/private/BlockedSuggestions.sys.mjs187
1 files changed, 187 insertions, 0 deletions
diff --git a/browser/components/urlbar/private/BlockedSuggestions.sys.mjs b/browser/components/urlbar/private/BlockedSuggestions.sys.mjs
new file mode 100644
index 0000000000..d74a0979d1
--- /dev/null
+++ b/browser/components/urlbar/private/BlockedSuggestions.sys.mjs
@@ -0,0 +1,187 @@
+/* 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/. */
+
+import { BaseFeature } from "resource:///modules/urlbar/private/BaseFeature.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ TaskQueue: "resource:///modules/UrlbarUtils.sys.mjs",
+ UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
+});
+
+/**
+ * A set of blocked suggestions for quick suggest.
+ */
+export class BlockedSuggestions extends BaseFeature {
+ constructor() {
+ super();
+ this.#taskQueue = new lazy.TaskQueue();
+ lazy.UrlbarPrefs.addObserver(this);
+ }
+
+ get shouldEnable() {
+ // Return true so that we'll always load blocked digests when quick suggest
+ // is enabled, even if blocking new suggestions is currently disabled.
+ // Blocking may have been enabled previously, and blocked suggestions should
+ // remain blocked as long as quick suggest as a whole remains enabled.
+ return true;
+ }
+
+ enable(enabled) {
+ if (enabled) {
+ this.#loadDigests();
+ }
+ }
+
+ /**
+ * Blocks a suggestion.
+ *
+ * @param {string} originalUrl
+ * The suggestion's original URL with its unreplaced timestamp template.
+ */
+ async add(originalUrl) {
+ this.logger.debug(`Queueing add: ${originalUrl}`);
+ await this.#taskQueue.queue(async () => {
+ this.logger.info(`Blocking suggestion: ${originalUrl}`);
+ let digest = await this.#getDigest(originalUrl);
+ this.logger.debug(`Got digest for '${originalUrl}': ${digest}`);
+ this.#digests.add(digest);
+ let json = JSON.stringify([...this.#digests]);
+ this.#updatingDigests = true;
+ try {
+ lazy.UrlbarPrefs.set("quicksuggest.blockedDigests", json);
+ } finally {
+ this.#updatingDigests = false;
+ }
+ this.logger.debug(`All blocked suggestions: ${json}`);
+ });
+ }
+
+ /**
+ * Gets whether a suggestion is blocked.
+ *
+ * @param {string} originalUrl
+ * The suggestion's original URL with its unreplaced timestamp template.
+ * @returns {boolean}
+ * Whether the suggestion is blocked.
+ */
+ async has(originalUrl) {
+ this.logger.debug(`Queueing has: ${originalUrl}`);
+ return this.#taskQueue.queue(async () => {
+ this.logger.info(`Getting blocked status: ${originalUrl}`);
+ let digest = await this.#getDigest(originalUrl);
+ this.logger.debug(`Got digest for '${originalUrl}': ${digest}`);
+ let isBlocked = this.#digests.has(digest);
+ this.logger.info(`Blocked status for '${originalUrl}': ${isBlocked}`);
+ return isBlocked;
+ });
+ }
+
+ /**
+ * Unblocks all suggestions.
+ */
+ async clear() {
+ this.logger.debug(`Queueing clearBlockedSuggestions`);
+ await this.#taskQueue.queue(() => {
+ this.logger.info(`Clearing all blocked suggestions`);
+ this.#digests.clear();
+ lazy.UrlbarPrefs.clear("quicksuggest.blockedDigests");
+ });
+ }
+
+ /**
+ * Called when a urlbar pref changes.
+ *
+ * @param {string} pref
+ * The name of the pref relative to `browser.urlbar`.
+ */
+ onPrefChanged(pref) {
+ switch (pref) {
+ case "quicksuggest.blockedDigests":
+ if (!this.#updatingDigests) {
+ this.logger.info(
+ "browser.urlbar.quicksuggest.blockedDigests changed"
+ );
+ this.#loadDigests();
+ }
+ break;
+ }
+ }
+
+ /**
+ * Loads blocked suggestion digests from the pref into `#digests`.
+ */
+ async #loadDigests() {
+ this.logger.debug(`Queueing #loadDigests`);
+ await this.#taskQueue.queue(() => {
+ this.logger.info(`Loading blocked suggestion digests`);
+ let json = lazy.UrlbarPrefs.get("quicksuggest.blockedDigests");
+ this.logger.debug(
+ `browser.urlbar.quicksuggest.blockedDigests value: ${json}`
+ );
+ if (!json) {
+ this.logger.info(`There are no blocked suggestion digests`);
+ this.#digests.clear();
+ } else {
+ try {
+ this.#digests = new Set(JSON.parse(json));
+ this.logger.info(`Successfully loaded blocked suggestion digests`);
+ } catch (error) {
+ this.logger.error(
+ `Error loading blocked suggestion digests: ${error}`
+ );
+ }
+ }
+ });
+ }
+
+ /**
+ * Returns the SHA-1 digest of a string as a 40-character hex-encoded string.
+ *
+ * @param {string} string
+ * The string to convert to SHA-1
+ * @returns {string}
+ * The hex-encoded digest of the given string.
+ */
+ async #getDigest(string) {
+ let stringArray = new TextEncoder().encode(string);
+ let hashBuffer = await crypto.subtle.digest("SHA-1", stringArray);
+ let hashArray = new Uint8Array(hashBuffer);
+ return Array.from(hashArray, b => b.toString(16).padStart(2, "0")).join("");
+ }
+
+ get _test_readyPromise() {
+ return this.#taskQueue.emptyPromise;
+ }
+
+ get _test_digests() {
+ return this.#digests;
+ }
+
+ _test_getDigest(string) {
+ return this.#getDigest(string);
+ }
+
+ // Set of digests of the original URLs of blocked suggestions. A suggestion's
+ // "original URL" is its URL straight from the source with an unreplaced
+ // timestamp template. For details on the digests, see `#getDigest()`.
+ //
+ // The only reason we use URL digests is that suggestions currently do not
+ // have persistent IDs. We could use the URLs themselves but SHA-1 digests are
+ // only 40 chars long, so they save a little space. This is also consistent
+ // with how blocked tiles on the newtab page are stored, but they use MD5. We
+ // do *not* store digests for any security or obfuscation reason.
+ //
+ // This value is serialized as a JSON'ed array to the
+ // `browser.urlbar.quicksuggest.blockedDigests` pref.
+ #digests = new Set();
+
+ // Used to serialize access to blocked suggestions. This is only necessary
+ // because getting a suggestion's URL digest is async.
+ #taskQueue = null;
+
+ // Whether blocked digests are currently being updated.
+ #updatingDigests = false;
+}