diff options
Diffstat (limited to 'browser/modules/BrowserUIUtils.jsm')
-rw-r--r-- | browser/modules/BrowserUIUtils.jsm | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/browser/modules/BrowserUIUtils.jsm b/browser/modules/BrowserUIUtils.jsm new file mode 100644 index 0000000000..c338b26fca --- /dev/null +++ b/browser/modules/BrowserUIUtils.jsm @@ -0,0 +1,179 @@ +/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* 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/. */ + +"use strict"; + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +var EXPORTED_SYMBOLS = ["BrowserUIUtils"]; + +var BrowserUIUtils = { + /** + * Check whether a page can be considered as 'empty', that its URI + * reflects its origin, and that if it's loaded in a tab, that tab + * could be considered 'empty' (e.g. like the result of opening + * a 'blank' new tab). + * + * We have to do more than just check the URI, because especially + * for things like about:blank, it is possible that the opener or + * some other page has control over the contents of the page. + * + * @param {Browser} browser + * The browser whose page we're checking. + * @param {nsIURI} [uri] + * The URI against which we're checking (the browser's currentURI + * if omitted). + * + * @return {boolean} false if the page was opened by or is controlled by + * arbitrary web content, unless that content corresponds with the URI. + * true if the page is blank and controlled by a principal matching + * that URI (or the system principal if the principal has no URI) + */ + checkEmptyPageOrigin(browser, uri = browser.currentURI) { + // If another page opened this page with e.g. window.open, this page might + // be controlled by its opener. + if (browser.hasContentOpener) { + return false; + } + let contentPrincipal = browser.contentPrincipal; + // Not all principals have URIs... + // There are two special-cases involving about:blank. One is where + // the user has manually loaded it and it got created with a null + // principal. The other involves the case where we load + // some other empty page in a browser and the current page is the + // initial about:blank page (which has that as its principal, not + // just URI in which case it could be web-based). Especially in + // e10s, we need to tackle that case specifically to avoid race + // conditions when updating the URL bar. + // + // Note that we check the documentURI here, since the currentURI on + // the browser might have been set by SessionStore in order to + // support switch-to-tab without having actually loaded the content + // yet. + let uriToCheck = browser.documentURI || uri; + if ( + (uriToCheck.spec == "about:blank" && contentPrincipal.isNullPrincipal) || + contentPrincipal.spec == "about:blank" + ) { + return true; + } + if (contentPrincipal.isContentPrincipal) { + return contentPrincipal.equalsURI(uri); + } + // ... so for those that don't have them, enforce that the page has the + // system principal (this matches e.g. on about:newtab). + return contentPrincipal.isSystemPrincipal; + }, + + /** + * Generate a document fragment for a localized string that has DOM + * node replacements. This avoids using getFormattedString followed + * by assigning to innerHTML. Fluent can probably replace this when + * it is in use everywhere. + * + * @param {Document} doc + * @param {String} msg + * The string to put replacements in. Fetch from + * a stringbundle using getString or GetStringFromName, + * or even an inserted dtd string. + * @param {Node|String} nodesOrStrings + * The replacement items. Can be a mix of Nodes + * and Strings. However, for correct behaviour, the + * number of items provided needs to exactly match + * the number of replacement strings in the l10n string. + * @returns {DocumentFragment} + * A document fragment. In the trivial case (no + * replacements), this will simply be a fragment with 1 + * child, a text node containing the localized string. + */ + getLocalizedFragment(doc, msg, ...nodesOrStrings) { + // Ensure replacement points are indexed: + for (let i = 1; i <= nodesOrStrings.length; i++) { + if (!msg.includes("%" + i + "$S")) { + msg = msg.replace(/%S/, "%" + i + "$S"); + } + } + let numberOfInsertionPoints = msg.match(/%\d+\$S/g).length; + if (numberOfInsertionPoints != nodesOrStrings.length) { + console.error( + `Message has ${numberOfInsertionPoints} insertion points, ` + + `but got ${nodesOrStrings.length} replacement parameters!` + ); + } + + let fragment = doc.createDocumentFragment(); + let parts = [msg]; + let insertionPoint = 1; + for (let replacement of nodesOrStrings) { + let insertionString = "%" + insertionPoint++ + "$S"; + let partIndex = parts.findIndex( + part => typeof part == "string" && part.includes(insertionString) + ); + if (partIndex == -1) { + fragment.appendChild(doc.createTextNode(msg)); + return fragment; + } + + if (typeof replacement == "string") { + parts[partIndex] = parts[partIndex].replace( + insertionString, + replacement + ); + } else { + let [firstBit, lastBit] = parts[partIndex].split(insertionString); + parts.splice(partIndex, 1, firstBit, replacement, lastBit); + } + } + + // Put everything in a document fragment: + for (let part of parts) { + if (typeof part == "string") { + if (part) { + fragment.appendChild(doc.createTextNode(part)); + } + } else { + fragment.appendChild(part); + } + } + return fragment; + }, + + removeSingleTrailingSlashFromURL(aURL) { + // remove single trailing slash for http/https/ftp URLs + return aURL.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1"); + }, + + /** + * Returns a URL which has been trimmed by removing 'http://' and any + * trailing slash (in http/https/ftp urls). + * Note that a trimmed url may not load the same page as the original url, so + * before loading it, it must be passed through URIFixup, to check trimming + * doesn't change its destination. We don't run the URIFixup check here, + * because trimURL is in the page load path (see onLocationChange), so it + * must be fast and simple. + * + * @param {string} aURL The URL to trim. + * @returns {string} The trimmed string. + */ + get trimURLProtocol() { + return "http://"; + }, + trimURL(aURL) { + let url = this.removeSingleTrailingSlashFromURL(aURL); + // Remove "http://" prefix. + return url.startsWith(this.trimURLProtocol) + ? url.substring(this.trimURLProtocol.length) + : url; + }, +}; + +XPCOMUtils.defineLazyPreferenceGetter( + BrowserUIUtils, + "quitShortcutDisabled", + "browser.quitShortcut.disabled", + false +); |