tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit b656ac6c1251ce1d28760cb9eca3b8ebc11b8f0e
parent ffcae969c4f8f28ed884e504b6142e5c1eb7f7bf
Author: Mark Banner <standard8@mozilla.com>
Date:   Fri, 31 Oct 2025 14:52:05 +0000

Bug 1996615 - Fix type issues and add more type information to UrlbarValueFormatter.sys.mjs. r=urlbar-reviewers,daisuke

Differential Revision: https://phabricator.services.mozilla.com/D270207

Diffstat:
Mbrowser/components/urlbar/UrlbarValueFormatter.sys.mjs | 278+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mbrowser/components/urlbar/tsconfig.json | 1-
2 files changed, 166 insertions(+), 113 deletions(-)

diff --git a/browser/components/urlbar/UrlbarValueFormatter.sys.mjs b/browser/components/urlbar/UrlbarValueFormatter.sys.mjs @@ -2,9 +2,9 @@ * 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/. */ -const lazy = {}; +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; -ChromeUtils.defineESModuleGetters(lazy, { +const lazy = XPCOMUtils.declareLazy({ BrowserUIUtils: "resource:///modules/BrowserUIUtils.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs", @@ -21,38 +21,24 @@ export class UrlbarValueFormatter { * The parent instance of UrlbarInput */ constructor(urlbarInput) { - this.urlbarInput = urlbarInput; - this.window = this.urlbarInput.window; - this.document = this.window.document; + this.#urlbarInput = urlbarInput; - // This is used only as an optimization to avoid removing formatting in - // the _remove* format methods when no formatting is actually applied. - this._formattingApplied = false; - - this.window.addEventListener("resize", this); - } - - get inputField() { - return this.urlbarInput.inputField; - } - - get scheme() { - return this.urlbarInput.querySelector("#urlbar-scheme"); + this.#window.addEventListener("resize", this); } async update() { - let instance = (this._updateInstance = {}); + let instance = (this.#updateInstance = {}); - // _getUrlMetaData does URI fixup, which depends on the search service, so + // #getUrlMetaData does URI fixup, which depends on the search service, so // make sure it's initialized, or URIFixup may force synchronous // initialization. It can be uninitialized here on session restore. Skip // this if the service is already initialized in order to avoid the async // call in the common case. However, we can't access Service.search before // first paint (delayed startup) because there's a performance test that // prohibits it, so first await delayed startup. - if (!this.window.gBrowserInit.delayedStartupFinished) { - await this.window.delayedStartupPromise; - if (this._updateInstance != instance) { + if (!this.#window.gBrowserInit.delayedStartupFinished) { + await this.#window.delayedStartupPromise; + if (this.#updateInstance != instance) { return; } } @@ -61,39 +47,62 @@ export class UrlbarValueFormatter { await Services.search.init(); } catch {} - if (this._updateInstance != instance) { + if (this.#updateInstance != instance) { return; } } // If this window is being torn down, stop here - if (!this.window.docShell) { + if (!this.#window.docShell) { return; } // Cleanup that must be done in any case, even if there's no value. - this.urlbarInput.removeAttribute("domaindir"); - this.scheme.value = ""; + this.#urlbarInput.removeAttribute("domaindir"); + this.#scheme.value = ""; - if (!this.urlbarInput.value) { + if (!this.#urlbarInput.value) { return; } // Remove the current formatting. - this._removeURLFormat(); - this._removeSearchAliasFormat(); + this.#removeURLFormat(); + this.#removeSearchAliasFormat(); // Apply new formatting. Formatter methods should return true if they // successfully formatted the value and false if not. We apply only // one formatter at a time, so we stop at the first successful one. - this.window.requestAnimationFrame(() => { - if (this._updateInstance != instance) { + this.#window.requestAnimationFrame(() => { + if (this.#updateInstance != instance) { return; } - this._formattingApplied = this._formatURL() || this._formatSearchAlias(); + this.#formattingApplied = this.#formatURL() || this.#formatSearchAlias(); }); } + /** + * The parent instance of UrlbarInput + */ + #urlbarInput; + + get #document() { + return this.#urlbarInput.document; + } + + get #inputField() { + return this.#urlbarInput.inputField; + } + + get #window() { + return this.#urlbarInput.window; + } + + get #scheme() { + return /** @type {HTMLInputElement} */ ( + this.#urlbarInput.querySelector("#urlbar-scheme") + ); + } + #ensureFormattedHostVisible(urlMetaData) { // Make sure the host is always visible. Since it is aligned on // the first strong directional character, we set scrollLeft @@ -102,39 +111,39 @@ export class UrlbarValueFormatter { // In the future, for example in bug 525831, we may add a forceRTL // char just after the domain, and in such a case we should not // scroll to the left. - urlMetaData = urlMetaData || this._getUrlMetaData(); + urlMetaData = urlMetaData || this.#getUrlMetaData(); if (!urlMetaData) { - this.urlbarInput.removeAttribute("domaindir"); + this.#urlbarInput.removeAttribute("domaindir"); return; } let { url, preDomain, domain } = urlMetaData; - let directionality = this.window.windowUtils.getDirectionFromText(domain); + let directionality = this.#window.windowUtils.getDirectionFromText(domain); if ( - directionality == this.window.windowUtils.DIRECTION_RTL && + directionality == this.#window.windowUtils.DIRECTION_RTL && url[preDomain.length + domain.length] != "\u200E" ) { - this.urlbarInput.setAttribute("domaindir", "rtl"); - this.inputField.scrollLeft = this.inputField.scrollLeftMax; + this.#urlbarInput.setAttribute("domaindir", "rtl"); + this.#inputField.scrollLeft = this.#inputField.scrollLeftMax; } else { - this.urlbarInput.setAttribute("domaindir", "ltr"); - this.inputField.scrollLeft = 0; + this.#urlbarInput.setAttribute("domaindir", "ltr"); + this.#inputField.scrollLeft = 0; } - this.urlbarInput.updateTextOverflow(); + this.#urlbarInput.updateTextOverflow(); } - _getUrlMetaData() { - if (this.urlbarInput.focused) { + #getUrlMetaData() { + if (this.#urlbarInput.focused) { return null; } - let inputValue = this.urlbarInput.value; + let inputValue = this.#urlbarInput.value; // getFixupURIInfo logs an error if the URL is empty. Avoid that by // returning early. if (!inputValue) { return null; } - let browser = this.window.gBrowser.selectedBrowser; - let browserState = this.urlbarInput.getBrowserState(browser); + let browser = this.#window.gBrowser.selectedBrowser; + let browserState = this.#urlbarInput.getBrowserState(browser); // Since doing a full URIFixup and offset calculations is expensive, we // keep the metadata cached in the browser itself, so when switching tabs @@ -142,13 +151,14 @@ export class UrlbarValueFormatter { if ( browserState.urlMetaData && browserState.urlMetaData.inputValue == inputValue && - browserState.urlMetaData.untrimmedValue == this.urlbarInput.untrimmedValue + browserState.urlMetaData.untrimmedValue == + this.#urlbarInput.untrimmedValue ) { return browserState.urlMetaData.data; } browserState.urlMetaData = { inputValue, - untrimmedValue: this.urlbarInput.untrimmedValue, + untrimmedValue: this.#urlbarInput.untrimmedValue, data: null, }; @@ -156,14 +166,14 @@ export class UrlbarValueFormatter { let flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS | Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; - if (lazy.PrivateBrowsingUtils.isWindowPrivate(this.window)) { + if (lazy.PrivateBrowsingUtils.isWindowPrivate(this.#window)) { flags |= Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT; } let uriInfo; try { uriInfo = Services.uriFixup.getFixupURIInfo( - this.urlbarInput.untrimmedValue, + this.#urlbarInput.untrimmedValue, flags ); } catch (ex) {} @@ -187,7 +197,7 @@ export class UrlbarValueFormatter { let trimmedLength = 0; let trimmedProtocol = lazy.BrowserUIUtils.trimURLProtocol; if ( - this.urlbarInput.untrimmedValue.startsWith(trimmedProtocol) && + this.#urlbarInput.untrimmedValue.startsWith(trimmedProtocol) && !inputValue.startsWith(trimmedProtocol) ) { // The protocol has been trimmed, so we add it back. @@ -227,17 +237,17 @@ export class UrlbarValueFormatter { return null; } if (replaceUrl) { - if (this._inGetUrlMetaData) { + if (this.#inGetUrlMetaData) { // Protect from infinite recursion. return null; } try { - this._inGetUrlMetaData = true; - this.window.gBrowser.userTypedValue = null; - this.urlbarInput.setURI(uriInfo.fixedURI); - return this._getUrlMetaData(); + this.#inGetUrlMetaData = true; + this.#window.gBrowser.userTypedValue = null; + this.#urlbarInput.setURI(uriInfo.fixedURI); + return this.#getUrlMetaData(); } finally { - this._inGetUrlMetaData = false; + this.#inGetUrlMetaData = false; } } @@ -251,18 +261,18 @@ export class UrlbarValueFormatter { }); } - _removeURLFormat() { - if (!this._formattingApplied) { + #removeURLFormat() { + if (!this.#formattingApplied) { return; } - let controller = this.urlbarInput.editor.selectionController; + let controller = this.#urlbarInput.editor.selectionController; let strikeOut = controller.getSelection(controller.SELECTION_URLSTRIKEOUT); strikeOut.removeAllRanges(); let selection = controller.getSelection(controller.SELECTION_URLSECONDARY); selection.removeAllRanges(); - this._formatScheme(controller.SELECTION_URLSTRIKEOUT, true); - this._formatScheme(controller.SELECTION_URLSECONDARY, true); - this.inputField.style.setProperty("--urlbar-scheme-size", "0px"); + this.#formatScheme(controller.SELECTION_URLSTRIKEOUT, true); + this.#formatScheme(controller.SELECTION_URLSECONDARY, true); + this.#inputField.style.setProperty("--urlbar-scheme-size", "0px"); } /** @@ -288,21 +298,66 @@ export class UrlbarValueFormatter { this.formattingEnabled && !lazy.UrlbarPrefs.get("security.insecure_connection_text.enabled") && val.startsWith("https://") && - val == this.urlbarInput.value && + val == this.#urlbarInput.value && this.#showingMixedContentLoadedPageUrl ); } /** + * This is used only as an optimization to avoid removing formatting in + * the _remove* format methods when no formatting is actually applied. + * + * @type {boolean} + */ + #formattingApplied = false; + + /** + * An empty object, which is used as a lock to avoid updating old instances. + * + * @type {?object} + */ + #updateInstance; + + /** + * The previously selected result. + * + * @type {?UrlbarResult} + */ + #selectedResult; + + /** + * The timer handling the resize throttling. + * + * @type {?number} + */ + #resizeThrottleTimeout; + + /** + * An empty object, which is used to avoid updating old instances. + * + * @type {?object} + */ + #resizeInstance; + + /** + * Used to protect against re-entry in getUrlMetaData. + * + * @type {boolean} + */ + #inGetUrlMetaData = false; + + /** * Whether the currently loaded page is in mixed content mode. * * @returns {boolean} whether the loaded page has active mixed content. */ get #showingMixedContentLoadedPageUrl() { return ( - this.urlbarInput.getAttribute("pageproxystate") == "valid" && - this.window.gBrowser.securityUI.state & + this.#urlbarInput.getAttribute("pageproxystate") == "valid" && + !!( + this.#window.gBrowser.securityUI.state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT + ) ); } @@ -315,13 +370,13 @@ export class UrlbarValueFormatter { * @returns {boolean} * True if formatting was applied and false if not. */ - _formatURL() { - let urlMetaData = this._getUrlMetaData(); + #formatURL() { + let urlMetaData = this.#getUrlMetaData(); if (!urlMetaData) { return false; } - let state = this.urlbarInput.getBrowserState( - this.window.gBrowser.selectedBrowser + let state = this.#urlbarInput.getBrowserState( + this.#window.gBrowser.selectedBrowser ); if (state.searchTerms) { return false; @@ -343,10 +398,10 @@ export class UrlbarValueFormatter { if ( !lazy.UrlbarPrefs.get("security.insecure_connection_text.enabled") && !isUnformattedMixedContent && - this.urlbarInput.value.startsWith(schemeWSlashes) + this.#urlbarInput.value.startsWith(schemeWSlashes) ) { - this.scheme.value = schemeWSlashes; - this.inputField.style.setProperty( + this.#scheme.value = schemeWSlashes; + this.#inputField.style.setProperty( "--urlbar-scheme-size", schemeWSlashes.length + "ch" ); @@ -358,24 +413,24 @@ export class UrlbarValueFormatter { return false; } - let editor = this.urlbarInput.editor; + let editor = this.#urlbarInput.editor; let controller = editor.selectionController; - this._formatScheme(controller.SELECTION_URLSECONDARY); + this.#formatScheme(controller.SELECTION_URLSECONDARY); let textNode = editor.rootElement.firstChild; // Strike out the "https" part if mixed active content status should be // shown. - if (this.willShowFormattedMixedContentProtocol(this.urlbarInput.value)) { - let range = this.document.createRange(); + if (this.willShowFormattedMixedContentProtocol(this.#urlbarInput.value)) { + let range = this.#document.createRange(); range.setStart(textNode, 0); range.setEnd(textNode, 5); let strikeOut = controller.getSelection( controller.SELECTION_URLSTRIKEOUT ); strikeOut.addRange(range); - this._formatScheme(controller.SELECTION_URLSTRIKEOUT); + this.#formatScheme(controller.SELECTION_URLSTRIKEOUT); } let baseDomain = domain; @@ -400,7 +455,7 @@ export class UrlbarValueFormatter { let rangeLength = preDomain.length + subDomain.length - trimmedLength; if (rangeLength) { - let range = this.document.createRange(); + let range = this.#document.createRange(); range.setStart(textNode, 0); range.setEnd(textNode, rangeLength); selection.addRange(range); @@ -408,7 +463,7 @@ export class UrlbarValueFormatter { let startRest = preDomain.length + domain.length - trimmedLength; if (startRest < url.length - trimmedLength) { - let range = this.document.createRange(); + let range = this.#document.createRange(); range.setStart(textNode, startRest); range.setEnd(textNode, url.length - trimmedLength); selection.addRange(range); @@ -417,26 +472,26 @@ export class UrlbarValueFormatter { return true; } - _formatScheme(selectionType, clear) { - let editor = this.scheme.editor; + #formatScheme(selectionType, clear) { + let editor = this.#scheme.editor; let controller = editor.selectionController; let textNode = editor.rootElement.firstChild; let selection = controller.getSelection(selectionType); if (clear) { selection.removeAllRanges(); } else { - let r = this.document.createRange(); + let r = this.#document.createRange(); r.setStart(textNode, 0); r.setEnd(textNode, textNode.textContent.length); selection.addRange(r); } } - _removeSearchAliasFormat() { - if (!this._formattingApplied) { + #removeSearchAliasFormat() { + if (!this.#formattingApplied) { return; } - let selection = this.urlbarInput.editor.selectionController.getSelection( + let selection = this.#urlbarInput.editor.selectionController.getSelection( Ci.nsISelectionController.SELECTION_FIND ); selection.removeAllRanges(); @@ -448,25 +503,24 @@ export class UrlbarValueFormatter { * @returns {boolean} * True if formatting was applied and false if not. */ - _formatSearchAlias() { + #formatSearchAlias() { if (!this.formattingEnabled) { return false; } - let editor = this.urlbarInput.editor; + let editor = this.#urlbarInput.editor; let textNode = editor.rootElement.firstChild; let value = textNode.textContent; let trimmedValue = value.trim(); if ( !trimmedValue.startsWith("@") || - (this.urlbarInput.popup || this.urlbarInput.view).oneOffSearchButtons - .selectedButton + this.#urlbarInput.view.oneOffSearchButtons.selectedButton ) { return false; } - let alias = this._findEngineAliasOrRestrictKeyword(); + let alias = this.#findEngineAliasOrRestrictKeyword(); if (!alias) { return false; } @@ -491,7 +545,7 @@ export class UrlbarValueFormatter { Ci.nsISelectionController.SELECTION_FIND ); - let range = this.document.createRange(); + let range = this.#document.createRange(); range.setStart(textNode, index); range.setEnd(textNode, index + alias.length); selection.addRange(range); @@ -511,8 +565,8 @@ export class UrlbarValueFormatter { // them, which we can do by passing "currentColor". See // nsTextPaintStyle::GetHighlightColors for details. if ( - this.document.documentElement.hasAttribute("lwtheme") || - this.window.matchMedia("(prefers-contrast)").matches + this.#document.documentElement.hasAttribute("lwtheme") || + this.#window.matchMedia("(prefers-contrast)").matches ) { // non-default theme(s) selection.setColors(fg, bg, "currentColor", "currentColor"); @@ -524,24 +578,24 @@ export class UrlbarValueFormatter { return true; } - _findEngineAliasOrRestrictKeyword() { + #findEngineAliasOrRestrictKeyword() { // To determine whether the input contains a valid alias, check if the // selected result is a search result with an alias. If there is no selected // result, we check the first result in the view, for cases when we do not // highlight token alias results. The selected result is null when the popup // is closed, but we want to continue highlighting the alias when the popup // is closed, and that's why we keep around the previously selected result - // in _selectedResult. - this._selectedResult = - this.urlbarInput.view.selectedResult || - this.urlbarInput.view.getResultAtIndex(0) || - this._selectedResult; + // in #selectedResult. + this.#selectedResult = + this.#urlbarInput.view.selectedResult || + this.#urlbarInput.view.getResultAtIndex(0) || + this.#selectedResult; - if (!this._selectedResult) { + if (!this.#selectedResult) { return null; } - let { type, payload } = this._selectedResult; + let { type, payload } = this.#selectedResult; if (type === lazy.UrlbarUtils.RESULT_TYPE.SEARCH) { return payload.keyword || null; @@ -570,20 +624,20 @@ export class UrlbarValueFormatter { } _on_resize(event) { - if (event.target != this.window) { + if (event.target != this.#window) { return; } // Make sure the host remains visible in the input field when the window is // resized. We don't want to hurt resize performance though, so do this // only after resize events have stopped and a small timeout has elapsed. - if (this._resizeThrottleTimeout) { - this.window.clearTimeout(this._resizeThrottleTimeout); - } - this._resizeThrottleTimeout = this.window.setTimeout(() => { - this._resizeThrottleTimeout = null; - let instance = (this._resizeInstance = {}); - this.window.requestAnimationFrame(() => { - if (instance == this._resizeInstance) { + if (this.#resizeThrottleTimeout) { + this.#window.clearTimeout(this.#resizeThrottleTimeout); + } + this.#resizeThrottleTimeout = this.#window.setTimeout(() => { + this.#resizeThrottleTimeout = null; + let instance = (this.#resizeInstance = {}); + this.#window.requestAnimationFrame(() => { + if (instance == this.#resizeInstance) { this.#ensureFormattedHostVisible(); } }); diff --git a/browser/components/urlbar/tsconfig.json b/browser/components/urlbar/tsconfig.json @@ -6,7 +6,6 @@ "UrlbarProviderPlaces.sys.mjs", "UrlbarProvidersManager.sys.mjs", "UrlbarSearchOneOffs.sys.mjs", - "UrlbarValueFormatter.sys.mjs", "UrlbarView.sys.mjs", "private/MLSuggest.sys.mjs", "private/SuggestBackendRust.sys.mjs",