UrlbarProviderClipboard.sys.mjs (4203B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { 6 UrlbarProvider, 7 UrlbarUtils, 8 } from "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs"; 9 10 const lazy = {}; 11 12 ChromeUtils.defineESModuleGetters(lazy, { 13 UrlbarResult: "moz-src:///browser/components/urlbar/UrlbarResult.sys.mjs", 14 UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs", 15 UrlUtils: "resource://gre/modules/UrlUtils.sys.mjs", 16 }); 17 18 const RESULT_MENU_COMMANDS = { 19 DISMISS: "dismiss", 20 }; 21 export const CLIPBOARD_IMPRESSION_LIMIT = 2; 22 23 /** 24 * A provider that returns a suggested url to the user based 25 * on a valid URL stored in the clipboard. 26 */ 27 export class UrlbarProviderClipboard extends UrlbarProvider { 28 #previousClipboard = { 29 value: "", 30 impressionsLeft: CLIPBOARD_IMPRESSION_LIMIT, 31 }; 32 33 constructor() { 34 super(); 35 } 36 37 /** 38 * @returns {Values<typeof UrlbarUtils.PROVIDER_TYPE>} 39 */ 40 get type() { 41 return UrlbarUtils.PROVIDER_TYPE.PROFILE; 42 } 43 44 setPreviousClipboardValue(newValue) { 45 this.#previousClipboard.value = newValue; 46 } 47 48 async isActive(queryContext, controller) { 49 // Return clipboard results only for empty searches. 50 if ( 51 !lazy.UrlbarPrefs.get("clipboard.featureGate") || 52 !lazy.UrlbarPrefs.get("suggest.clipboard") || 53 queryContext.searchString || 54 queryContext.searchMode 55 ) { 56 return false; 57 } 58 let textFromClipboard = controller.browserWindow.readFromClipboard(); 59 60 // Check for spaces in clipboard text to avoid suggesting 61 // clipboard content including both a url and the following text. 62 if ( 63 !textFromClipboard || 64 textFromClipboard.length > 2048 || 65 lazy.UrlUtils.REGEXP_SPACES.test(textFromClipboard) 66 ) { 67 return false; 68 } 69 textFromClipboard = 70 controller.input.sanitizeTextFromClipboard(textFromClipboard); 71 const validUrl = this.#validUrl(textFromClipboard); 72 if (!validUrl) { 73 return false; 74 } 75 76 if (this.#previousClipboard.value === validUrl) { 77 if (this.#previousClipboard.impressionsLeft <= 0) { 78 return false; 79 } 80 } else { 81 this.#previousClipboard = { 82 value: validUrl, 83 impressionsLeft: CLIPBOARD_IMPRESSION_LIMIT, 84 }; 85 } 86 87 return true; 88 } 89 90 #validUrl(clipboardVal) { 91 let givenUrl = URL.parse(clipboardVal); 92 if (!givenUrl) { 93 // Not a valid URI. 94 return null; 95 } 96 97 if (givenUrl.protocol == "http:" || givenUrl.protocol == "https:") { 98 return givenUrl.href; 99 } 100 101 return null; 102 } 103 104 getPriority() { 105 // Zero-prefix suggestions have the same priority as top sites. 106 return 1; 107 } 108 109 /** 110 * Starts querying. 111 * 112 * @param {UrlbarQueryContext} queryContext 113 * @param {(provider: UrlbarProvider, result: UrlbarResult) => void} addCallback 114 * Callback invoked by the provider to add a new result. 115 */ 116 async startQuery(queryContext, addCallback) { 117 // If the query was started, isActive should have cached a url already. 118 let result = new lazy.UrlbarResult({ 119 type: UrlbarUtils.RESULT_TYPE.URL, 120 source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, 121 payload: { 122 title: UrlbarUtils.prepareUrlForDisplay(this.#previousClipboard.value, { 123 trimURL: false, 124 }), 125 url: this.#previousClipboard.value, 126 icon: "chrome://global/skin/icons/clipboard.svg", 127 isBlockable: true, 128 }, 129 }); 130 131 addCallback(this, result); 132 } 133 134 onEngagement(queryContext, controller, details) { 135 this.#previousClipboard.impressionsLeft = 0; // User has picked the suggested clipboard result 136 // Handle commands. 137 this.#handlePossibleCommand( 138 controller.view, 139 details.result, 140 details.selType 141 ); 142 } 143 144 onImpression() { 145 this.#previousClipboard.impressionsLeft--; 146 } 147 148 #handlePossibleCommand(view, result, selType) { 149 switch (selType) { 150 case RESULT_MENU_COMMANDS.DISMISS: 151 view.controller.removeResult(result); 152 this.#previousClipboard.impressionsLeft = 0; 153 break; 154 } 155 } 156 }