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/. */
8 var { XPCOMUtils } = ChromeUtils.import(
9 "resource://gre/modules/XPCOMUtils.jsm"
12 var EXPORTED_SYMBOLS = ["BrowserUIUtils"];
14 var BrowserUIUtils = {
16 * Check whether a page can be considered as 'empty', that its URI
17 * reflects its origin, and that if it's loaded in a tab, that tab
18 * could be considered 'empty' (e.g. like the result of opening
21 * We have to do more than just check the URI, because especially
22 * for things like about:blank, it is possible that the opener or
23 * some other page has control over the contents of the page.
25 * @param {Browser} browser
26 * The browser whose page we're checking.
27 * @param {nsIURI} [uri]
28 * The URI against which we're checking (the browser's currentURI
31 * @return {boolean} false if the page was opened by or is controlled by
32 * arbitrary web content, unless that content corresponds with the URI.
33 * true if the page is blank and controlled by a principal matching
34 * that URI (or the system principal if the principal has no URI)
36 checkEmptyPageOrigin(browser, uri = browser.currentURI) {
37 // If another page opened this page with e.g. window.open, this page might
38 // be controlled by its opener.
39 if (browser.hasContentOpener) {
42 let contentPrincipal = browser.contentPrincipal;
43 // Not all principals have URIs...
44 // There are two special-cases involving about:blank. One is where
45 // the user has manually loaded it and it got created with a null
46 // principal. The other involves the case where we load
47 // some other empty page in a browser and the current page is the
48 // initial about:blank page (which has that as its principal, not
49 // just URI in which case it could be web-based). Especially in
50 // e10s, we need to tackle that case specifically to avoid race
51 // conditions when updating the URL bar.
53 // Note that we check the documentURI here, since the currentURI on
54 // the browser might have been set by SessionStore in order to
55 // support switch-to-tab without having actually loaded the content
57 let uriToCheck = browser.documentURI || uri;
59 (uriToCheck.spec == "about:blank" && contentPrincipal.isNullPrincipal) ||
60 contentPrincipal.spec == "about:blank"
64 if (contentPrincipal.isContentPrincipal) {
65 return contentPrincipal.equalsURI(uri);
67 // ... so for those that don't have them, enforce that the page has the
68 // system principal (this matches e.g. on about:newtab).
69 return contentPrincipal.isSystemPrincipal;
73 * Sets the --toolbarbutton-button-height CSS property on the closest
74 * toolbar to the provided element. Useful if you need to vertically
75 * center a position:absolute element within a toolbar that uses
76 * -moz-pack-align:stretch, and thus a height which is dependant on
79 * @param element An element within the toolbar whose height is desired.
81 async setToolbarButtonHeightProperty(element) {
82 let window = element.ownerGlobal;
83 let dwu = window.windowUtils;
84 let toolbarItem = element;
85 let urlBarContainer = element.closest("#urlbar-container");
86 if (urlBarContainer) {
87 // The stop-reload-button, which is contained in #urlbar-container,
88 // needs to use #urlbar-container to calculate the bounds.
89 toolbarItem = urlBarContainer;
94 let bounds = dwu.getBoundsWithoutFlushing(toolbarItem);
96 await window.promiseDocumentFlushed(() => {
97 bounds = dwu.getBoundsWithoutFlushing(toolbarItem);
101 toolbarItem.style.setProperty(
102 "--toolbarbutton-height",
109 * Generate a document fragment for a localized string that has DOM
110 * node replacements. This avoids using getFormattedString followed
111 * by assigning to innerHTML. Fluent can probably replace this when
112 * it is in use everywhere.
114 * @param {Document} doc
115 * @param {String} msg
116 * The string to put replacements in. Fetch from
117 * a stringbundle using getString or GetStringFromName,
118 * or even an inserted dtd string.
119 * @param {Node|String} nodesOrStrings
120 * The replacement items. Can be a mix of Nodes
121 * and Strings. However, for correct behaviour, the
122 * number of items provided needs to exactly match
123 * the number of replacement strings in the l10n string.
124 * @returns {DocumentFragment}
125 * A document fragment. In the trivial case (no
126 * replacements), this will simply be a fragment with 1
127 * child, a text node containing the localized string.
129 getLocalizedFragment(doc, msg, ...nodesOrStrings) {
130 // Ensure replacement points are indexed:
131 for (let i = 1; i <= nodesOrStrings.length; i++) {
132 if (!msg.includes("%" + i + "$S")) {
133 msg = msg.replace(/%S/, "%" + i + "$S");
136 let numberOfInsertionPoints = msg.match(/%\d+\$S/g).length;
137 if (numberOfInsertionPoints != nodesOrStrings.length) {
139 `Message has ${numberOfInsertionPoints} insertion points, ` +
140 `but got ${nodesOrStrings.length} replacement parameters!`
144 let fragment = doc.createDocumentFragment();
146 let insertionPoint = 1;
147 for (let replacement of nodesOrStrings) {
148 let insertionString = "%" + insertionPoint++ + "$S";
149 let partIndex = parts.findIndex(
150 part => typeof part == "string" && part.includes(insertionString)
152 if (partIndex == -1) {
153 fragment.appendChild(doc.createTextNode(msg));
157 if (typeof replacement == "string") {
158 parts[partIndex] = parts[partIndex].replace(
163 let [firstBit, lastBit] = parts[partIndex].split(insertionString);
164 parts.splice(partIndex, 1, firstBit, replacement, lastBit);
168 // Put everything in a document fragment:
169 for (let part of parts) {
170 if (typeof part == "string") {
172 fragment.appendChild(doc.createTextNode(part));
175 fragment.appendChild(part);
181 removeSingleTrailingSlashFromURL(aURL) {
182 // remove single trailing slash for http/https/ftp URLs
183 return aURL.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1");
187 * Returns a URL which has been trimmed by removing 'http://' and any
188 * trailing slash (in http/https/ftp urls).
189 * Note that a trimmed url may not load the same page as the original url, so
190 * before loading it, it must be passed through URIFixup, to check trimming
191 * doesn't change its destination. We don't run the URIFixup check here,
192 * because trimURL is in the page load path (see onLocationChange), so it
193 * must be fast and simple.
195 * @param {string} aURL The URL to trim.
196 * @returns {string} The trimmed string.
198 get trimURLProtocol() {
202 let url = this.removeSingleTrailingSlashFromURL(aURL);
203 // Remove "http://" prefix.
204 return url.startsWith(this.trimURLProtocol)
205 ? url.substring(this.trimURLProtocol.length)
210 XPCOMUtils.defineLazyPreferenceGetter(
212 "quitShortcutDisabled",
213 "browser.quitShortcut.disabled",