1 /* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
10 XPCOMUtils.defineLazyPreferenceGetter(
13 "browser.urlbar.trimHttps"
15 export var BrowserUIUtils = {
17 * Check whether a page can be considered as 'empty', that its URI
18 * reflects its origin, and that if it's loaded in a tab, that tab
19 * could be considered 'empty' (e.g. like the result of opening
22 * We have to do more than just check the URI, because especially
23 * for things like about:blank, it is possible that the opener or
24 * some other page has control over the contents of the page.
26 * @param {Browser} browser
27 * The browser whose page we're checking.
28 * @param {nsIURI} [uri]
29 * The URI against which we're checking (the browser's currentURI
32 * @return {boolean} false if the page was opened by or is controlled by
33 * arbitrary web content, unless that content corresponds with the URI.
34 * true if the page is blank and controlled by a principal matching
35 * that URI (or the system principal if the principal has no URI)
37 checkEmptyPageOrigin(browser, uri = browser.currentURI) {
38 // If another page opened this page with e.g. window.open, this page might
39 // be controlled by its opener.
40 if (browser.hasContentOpener) {
43 let contentPrincipal = browser.contentPrincipal;
44 // Not all principals have URIs...
45 // There are two special-cases involving about:blank. One is where
46 // the user has manually loaded it and it got created with a null
47 // principal. The other involves the case where we load
48 // some other empty page in a browser and the current page is the
49 // initial about:blank page (which has that as its principal, not
50 // just URI in which case it could be web-based). Especially in
51 // e10s, we need to tackle that case specifically to avoid race
52 // conditions when updating the URL bar.
54 // Note that we check the documentURI here, since the currentURI on
55 // the browser might have been set by SessionStore in order to
56 // support switch-to-tab without having actually loaded the content
58 let uriToCheck = browser.documentURI || uri;
60 (uriToCheck.spec == "about:blank" && contentPrincipal.isNullPrincipal) ||
61 contentPrincipal.spec == "about:blank"
65 if (contentPrincipal.isContentPrincipal) {
66 return contentPrincipal.equalsURI(uri);
68 // ... so for those that don't have them, enforce that the page has the
69 // system principal (this matches e.g. on about:newtab).
70 return contentPrincipal.isSystemPrincipal;
74 * Generate a document fragment for a localized string that has DOM
75 * node replacements. This avoids using getFormattedString followed
76 * by assigning to innerHTML. Fluent can probably replace this when
77 * it is in use everywhere.
79 * @param {Document} doc
81 * The string to put replacements in. Fetch from
82 * a stringbundle using getString or GetStringFromName,
83 * or even an inserted dtd string.
84 * @param {Node|String} nodesOrStrings
85 * The replacement items. Can be a mix of Nodes
86 * and Strings. However, for correct behaviour, the
87 * number of items provided needs to exactly match
88 * the number of replacement strings in the l10n string.
89 * @returns {DocumentFragment}
90 * A document fragment. In the trivial case (no
91 * replacements), this will simply be a fragment with 1
92 * child, a text node containing the localized string.
94 getLocalizedFragment(doc, msg, ...nodesOrStrings) {
95 // Ensure replacement points are indexed:
96 for (let i = 1; i <= nodesOrStrings.length; i++) {
97 if (!msg.includes("%" + i + "$S")) {
98 msg = msg.replace(/%S/, "%" + i + "$S");
101 let numberOfInsertionPoints = msg.match(/%\d+\$S/g).length;
102 if (numberOfInsertionPoints != nodesOrStrings.length) {
104 `Message has ${numberOfInsertionPoints} insertion points, ` +
105 `but got ${nodesOrStrings.length} replacement parameters!`
109 let fragment = doc.createDocumentFragment();
111 let insertionPoint = 1;
112 for (let replacement of nodesOrStrings) {
113 let insertionString = "%" + insertionPoint++ + "$S";
114 let partIndex = parts.findIndex(
115 part => typeof part == "string" && part.includes(insertionString)
117 if (partIndex == -1) {
118 fragment.appendChild(doc.createTextNode(msg));
122 if (typeof replacement == "string") {
123 parts[partIndex] = parts[partIndex].replace(
128 let [firstBit, lastBit] = parts[partIndex].split(insertionString);
129 parts.splice(partIndex, 1, firstBit, replacement, lastBit);
133 // Put everything in a document fragment:
134 for (let part of parts) {
135 if (typeof part == "string") {
137 fragment.appendChild(doc.createTextNode(part));
140 fragment.appendChild(part);
146 removeSingleTrailingSlashFromURL(aURL) {
147 // remove single trailing slash for http/https/ftp URLs
148 return aURL.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1");
151 get trimURLProtocol() {
152 return lazy.trimHttps ? "https://" : "http://";
156 * Returns a URL which has been trimmed by removing 'http://' or 'https://',
157 * when the pref 'trimHttps' is set to true, and any trailing slash
158 * (in http/https/ftp urls). Note that a trimmed url may not load the same
159 * page as the original url, so before loading it, it must be passed through
160 * URIFixup, to check trimming doesn't change its destination. We don't run
161 * the URIFixup check here, because trimURL is in the page load path
162 * (see onLocationChange), so it must be fast and simple.
164 * @param {string} aURL The URL to trim.
165 * @returns {string} The trimmed string.
168 let url = this.removeSingleTrailingSlashFromURL(aURL);
169 return url.startsWith(this.trimURLProtocol)
170 ? url.substring(this.trimURLProtocol.length)
175 XPCOMUtils.defineLazyPreferenceGetter(
177 "quitShortcutDisabled",
178 "browser.quitShortcut.disabled",