summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/UrlbarProviderClipboard.sys.mjs')
-rw-r--r--browser/components/urlbar/UrlbarProviderClipboard.sys.mjs182
1 files changed, 182 insertions, 0 deletions
diff --git a/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs b/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs
new file mode 100644
index 0000000000..f1d0a0beb2
--- /dev/null
+++ b/browser/components/urlbar/UrlbarProviderClipboard.sys.mjs
@@ -0,0 +1,182 @@
+/* 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 {
+ UrlbarProvider,
+ UrlbarUtils,
+} from "resource:///modules/UrlbarUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
+ UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
+ UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs",
+});
+
+const RESULT_MENU_COMMANDS = {
+ DISMISS: "dismiss",
+};
+const CLIPBOARD_IMPRESSION_LIMIT = 2;
+
+/**
+ * A provider that returns a suggested url to the user based
+ * on a valid URL stored in the clipboard.
+ */
+class ProviderClipboard extends UrlbarProvider {
+ #previousClipboard = {
+ value: "",
+ impressionsLeft: CLIPBOARD_IMPRESSION_LIMIT,
+ };
+
+ constructor() {
+ super();
+ }
+
+ get name() {
+ return "UrlbarProviderClipboard";
+ }
+
+ get type() {
+ return UrlbarUtils.PROVIDER_TYPE.PROFILE;
+ }
+
+ setPreviousClipboardValue(newValue) {
+ this.#previousClipboard.value = newValue;
+ }
+
+ isActive(queryContext, controller) {
+ // Return clipboard results only for empty searches.
+ if (
+ !lazy.UrlbarPrefs.get("clipboard.featureGate") ||
+ !lazy.UrlbarPrefs.get("suggest.clipboard") ||
+ queryContext.searchString
+ ) {
+ return false;
+ }
+ const obj = {};
+ if (
+ !TelemetryStopwatch.running(
+ "FX_URLBAR_PROVIDER_CLIPBOARD_READ_TIME_MS",
+ obj
+ )
+ ) {
+ TelemetryStopwatch.start(
+ "FX_URLBAR_PROVIDER_CLIPBOARD_READ_TIME_MS",
+ obj
+ );
+ }
+ let textFromClipboard = controller.browserWindow.readFromClipboard();
+ TelemetryStopwatch.finish("FX_URLBAR_PROVIDER_CLIPBOARD_READ_TIME_MS", obj);
+
+ // Check for spaces in clipboard text to avoid suggesting
+ // clipboard content including both a url and the following text.
+ if (
+ !textFromClipboard ||
+ textFromClipboard.length > 2048 ||
+ lazy.UrlbarTokenizer.REGEXP_SPACES.test(textFromClipboard)
+ ) {
+ return false;
+ }
+ textFromClipboard =
+ controller.input.sanitizeTextFromClipboard(textFromClipboard);
+ const validUrl = this.#validUrl(textFromClipboard);
+ if (!validUrl) {
+ return false;
+ }
+
+ if (this.#previousClipboard.value === validUrl) {
+ if (this.#previousClipboard.impressionsLeft <= 0) {
+ return false;
+ }
+ } else {
+ this.#previousClipboard = {
+ value: validUrl,
+ impressionsLeft: CLIPBOARD_IMPRESSION_LIMIT,
+ };
+ }
+
+ return true;
+ }
+
+ #validUrl(clipboardVal) {
+ try {
+ let givenUrl;
+ givenUrl = new URL(clipboardVal);
+ if (givenUrl.protocol == "http:" || givenUrl.protocol == "https:") {
+ return givenUrl.href;
+ }
+ } catch (ex) {
+ // Not a valid URI.
+ }
+ return null;
+ }
+
+ getPriority(queryContext) {
+ // Zero-prefix suggestions have the same priority as top sites.
+ return 1;
+ }
+
+ async startQuery(queryContext, addCallback) {
+ // If the query was started, isActive should have cached a url already.
+ let result = new lazy.UrlbarResult(
+ UrlbarUtils.RESULT_TYPE.URL,
+ UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
+ ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
+ fallbackTitle: [
+ UrlbarUtils.prepareUrlForDisplay(this.#previousClipboard.value, {
+ trimURL: false,
+ }),
+ UrlbarUtils.HIGHLIGHT.NONE,
+ ],
+ url: [this.#previousClipboard.value, UrlbarUtils.HIGHLIGHT.NONE],
+ icon: "chrome://global/skin/icons/clipboard.svg",
+ isBlockable: true,
+ blockL10n: {
+ id: "urlbar-result-menu-dismiss-firefox-suggest",
+ },
+ })
+ );
+
+ addCallback(this, result);
+ }
+
+ onEngagement(state, queryContext, details, controller) {
+ if (!["engagement", "abandonment"].includes(state)) {
+ return;
+ }
+ const visibleResults = controller.view?.visibleResults ?? [];
+ for (const result of visibleResults) {
+ if (
+ result.providerName === this.name &&
+ result.payload.url === this.#previousClipboard.value
+ ) {
+ this.#previousClipboard.impressionsLeft--; // Clipboard value was suggested
+ }
+ }
+
+ if (details.result?.providerName != this.name) {
+ return;
+ }
+ this.#previousClipboard.impressionsLeft = 0; // User has picked the suggested clipboard result
+ // Handle commands.
+ this.#handlePossibleCommand(
+ controller.view,
+ details.result,
+ details.selType
+ );
+ }
+
+ #handlePossibleCommand(view, result, selType) {
+ switch (selType) {
+ case RESULT_MENU_COMMANDS.DISMISS:
+ view.controller.removeResult(result);
+ this.#previousClipboard.impressionsLeft = 0;
+ break;
+ }
+ }
+}
+
+const UrlbarProviderClipboard = new ProviderClipboard();
+export { UrlbarProviderClipboard, CLIPBOARD_IMPRESSION_LIMIT };