diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 2702903ee225..b1701a2864be 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -2203,6 +2203,7 @@ var gBrowserInit = { let globalHistoryOptions = undefined; let triggeringRemoteType = undefined; let forceAllowDataURI = false; + let wasSchemelessInput = false; if (window.arguments[1]) { if (!(window.arguments[1] instanceof Ci.nsIPropertyBag2)) { throw new Error( @@ -2241,6 +2242,10 @@ var gBrowserInit = { forceAllowDataURI = extraOptions.getPropertyAsBool("forceAllowDataURI"); } + if (extraOptions.hasKey("wasSchemelessInput")) { + wasSchemelessInput = + extraOptions.getPropertyAsBool("wasSchemelessInput"); + } } try { @@ -2264,6 +2269,7 @@ var gBrowserInit = { fromExternal, globalHistoryOptions, triggeringRemoteType, + wasSchemelessInput, }); } catch (e) { console.error(e); diff --git a/browser/base/content/tabbrowser.js b/browser/base/content/tabbrowser.js index 1374eee9c7d8..f1d264d84fec 100644 --- a/browser/base/content/tabbrowser.js +++ b/browser/base/content/tabbrowser.js @@ -2590,6 +2590,7 @@ insertTab = true, globalHistoryOptions, triggeringRemoteType, + wasSchemelessInput, } = {} ) { // all callers of addTab that pass a params object need to pass @@ -2779,6 +2780,7 @@ csp, globalHistoryOptions, triggeringRemoteType, + wasSchemelessInput, }); } @@ -3020,6 +3022,7 @@ csp, globalHistoryOptions, triggeringRemoteType, + wasSchemelessInput, } ) { if ( @@ -3083,6 +3086,7 @@ csp, globalHistoryOptions, triggeringRemoteType, + wasSchemelessInput, }); } catch (ex) { console.error(ex); diff --git a/browser/components/urlbar/UrlbarInput.sys.mjs b/browser/components/urlbar/UrlbarInput.sys.mjs index 1fcc7b0bdfbb..a2b34555123c 100644 --- a/browser/components/urlbar/UrlbarInput.sys.mjs +++ b/browser/components/urlbar/UrlbarInput.sys.mjs @@ -741,6 +741,9 @@ export class UrlbarInput { isValidUrl = true; } catch (ex) {} if (isValidUrl) { + // Annotate if the untrimmed value contained a scheme, to later potentially + // be upgraded by schemeless HTTPS-First. + openParams.wasSchemelessInput = this.#isSchemeless(this.untrimmedValue); this._loadURL(url, event, where, openParams); return; } @@ -788,13 +791,24 @@ export class UrlbarInput { if (this.isPrivate) { flags |= Ci.nsIURIFixup.FIXUP_FLAG_PRIVATE_CONTEXT; } - let { preferredURI: uri, postData } = - Services.uriFixup.getFixupURIInfo(url, flags); + let { + preferredURI: uri, + postData, + keywordAsSent, + } = Services.uriFixup.getFixupURIInfo(url, flags); if ( where != "current" || browser.lastLocationChange == lastLocationChange ) { openParams.postData = postData; + if (!keywordAsSent) { + // `uri` is not a search engine url, so we annotate if the untrimmed + // value contained a scheme, to potentially be later upgraded by + // schemeless HTTPS-First. + openParams.wasSchemelessInput = this.#isSchemeless( + this.untrimmedValue + ); + } this._loadURL(uri.spec, event, where, openParams, null, browser); } } @@ -992,30 +1006,36 @@ export class UrlbarInput { switch (result.type) { case lazy.UrlbarUtils.RESULT_TYPE.URL: { - // Bug 1578856: both the provider and the docshell run heuristics to - // decide how to handle a non-url string, either fixing it to a url, or - // searching for it. - // Some preferences can control the docshell behavior, for example - // if dns_first_for_single_words is true, the docshell looks up the word - // against the dns server, and either loads it as an url or searches for - // it, depending on the lookup result. The provider instead will always - // return a fixed url in this case, because URIFixup is synchronous and - // can't do a synchronous dns lookup. A possible long term solution - // would involve sharing the docshell logic with the provider, along - // with the dns lookup. - // For now, in this specific case, we'll override the result's url - // with the input value, and let it pass through to _loadURL(), and - // finally to the docshell. - // This also means that in some cases the heuristic result will show a - // Visit entry, but the docshell will instead execute a search. It's a - // rare case anyway, most likely to happen for enterprises customizing - // the urifixup prefs. - if ( - result.heuristic && - lazy.UrlbarPrefs.get("browser.fixup.dns_first_for_single_words") && - lazy.UrlbarUtils.looksLikeSingleWordHost(originalUntrimmedValue) - ) { - url = originalUntrimmedValue; + if (result.heuristic) { + // Bug 1578856: both the provider and the docshell run heuristics to + // decide how to handle a non-url string, either fixing it to a url, or + // searching for it. + // Some preferences can control the docshell behavior, for example + // if dns_first_for_single_words is true, the docshell looks up the word + // against the dns server, and either loads it as an url or searches for + // it, depending on the lookup result. The provider instead will always + // return a fixed url in this case, because URIFixup is synchronous and + // can't do a synchronous dns lookup. A possible long term solution + // would involve sharing the docshell logic with the provider, along + // with the dns lookup. + // For now, in this specific case, we'll override the result's url + // with the input value, and let it pass through to _loadURL(), and + // finally to the docshell. + // This also means that in some cases the heuristic result will show a + // Visit entry, but the docshell will instead execute a search. It's a + // rare case anyway, most likely to happen for enterprises customizing + // the urifixup prefs. + if ( + lazy.UrlbarPrefs.get("browser.fixup.dns_first_for_single_words") && + lazy.UrlbarUtils.looksLikeSingleWordHost(originalUntrimmedValue) + ) { + url = originalUntrimmedValue; + } + // Annotate if the untrimmed value contained a scheme, to later potentially + // be upgraded by schemeless HTTPS-First. + openParams.wasSchemelessInput = this.#isSchemeless( + originalUntrimmedValue + ); } break; } @@ -2718,6 +2738,8 @@ export class UrlbarInput { * The POST data associated with a search submission. * @param {boolean} [params.allowInheritPrincipal] * Whether the principal can be inherited. + * @param {boolean} [params.wasSchemelessInput] + * Whether the search/URL term was without an explicit scheme. * @param {object} [resultDetails] * Details of the selected result, if any. * @param {UrlbarUtils.RESULT_TYPE} [resultDetails.type] @@ -3948,6 +3970,18 @@ export class UrlbarInput { this._initPasteAndGo(); this._initStripOnShare(); } + + /** + * @param {string} value A untrimmed address bar input. + * @returns {boolean} + * `true` if the input doesn't start with a scheme relevant for + * schemeless HTTPS-First (http://, https:// and file://). + */ + #isSchemeless(value) { + return ["http://", "https://", "file://"].every( + scheme => !value.trim().startsWith(scheme) + ); + } } /** diff --git a/browser/modules/URILoadingHelper.sys.mjs b/browser/modules/URILoadingHelper.sys.mjs index b8b8c180290d..232cf05b94c1 100644 --- a/browser/modules/URILoadingHelper.sys.mjs +++ b/browser/modules/URILoadingHelper.sys.mjs @@ -129,6 +129,12 @@ function openInWindow(url, params, sourceWindow) { ); } } + if (params.wasSchemelessInput !== undefined) { + extraOptions.setPropertyAsBool( + "wasSchemelessInput", + params.wasSchemelessInput + ); + } var allowThirdPartyFixupSupports = Cc[ "@mozilla.org/supports-PRBool;1" @@ -260,6 +266,7 @@ function openInCurrentTab(targetBrowser, url, uriObj, params) { hasValidUserGestureActivation, globalHistoryOptions, triggeringRemoteType, + wasSchemelessInput, } = params; targetBrowser.fixupAndLoadURIString(url, { @@ -272,6 +279,7 @@ function openInCurrentTab(targetBrowser, url, uriObj, params) { hasValidUserGestureActivation, globalHistoryOptions, triggeringRemoteType, + wasSchemelessInput, }); params.resolveOnContentBrowserCreated?.(targetBrowser); } @@ -363,6 +371,8 @@ export const URILoadingHelper = { * @param {string} params.charset * Character set to use for the load. Only honoured for tabs. * Legacy argument - do not use. + * @param {string} params.wasSchemelessInput + * Whether the search/URL term was without an explicit scheme. * * Options relating to security, whether the load is allowed to happen, * and what cookie container to use for the load: @@ -577,6 +587,7 @@ export const URILoadingHelper = { openerBrowser: params.openerBrowser, fromExternal: params.fromExternal, globalHistoryOptions, + wasSchemelessInput: params.wasSchemelessInput, }); targetBrowser = tabUsedForLoad.linkedBrowser; diff --git a/docshell/base/URIFixup.sys.mjs b/docshell/base/URIFixup.sys.mjs index e68652c5c002..c42fb2efa8df 100644 --- a/docshell/base/URIFixup.sys.mjs +++ b/docshell/base/URIFixup.sys.mjs @@ -384,6 +384,7 @@ URIFixup.prototype = { if (uriWithProtocol) { info.fixedURI = uriWithProtocol; info.fixupChangedProtocol = true; + info.wasSchemelessInput = true; maybeSetAlternateFixedURI(info, fixupFlags); info.preferredURI = info.fixedURI; // Check if it's a forced visit. The user can enforce a visit by @@ -686,6 +687,13 @@ URIFixupInfo.prototype = { return this._keywordAsSent || ""; }, + set wasSchemelessInput(changed) { + this._wasSchemelessInput = changed; + }, + get wasSchemelessInput() { + return !!this._wasSchemelessInput; + }, + set fixupChangedProtocol(changed) { this._fixupChangedProtocol = changed; }, diff --git a/docshell/base/nsIURIFixup.idl b/docshell/base/nsIURIFixup.idl index 9c63c79efafc..2261c0cb402d 100644 --- a/docshell/base/nsIURIFixup.idl +++ b/docshell/base/nsIURIFixup.idl @@ -49,6 +49,11 @@ interface nsIURIFixupInfo : nsISupports */ attribute AString keywordAsSent; + /** + * Whether there was no protocol at all and we had to add one in the first place. + */ + attribute boolean wasSchemelessInput; + /** * Whether we changed the protocol instead of using one from the input as-is. */