Bug 1675375 Part 7: Update expectations in helper_hittest_clippath.html. r=botond
[gecko.git] / browser / modules / BrowserUIUtils.jsm
blob6fbd6576ac4fdcdc57790454c38a97ed1ebfee33
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 "use strict";
8 var { XPCOMUtils } = ChromeUtils.import(
9   "resource://gre/modules/XPCOMUtils.jsm"
12 var EXPORTED_SYMBOLS = ["BrowserUIUtils"];
14 var BrowserUIUtils = {
15   /**
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
19    * a 'blank' new tab).
20    *
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.
24    *
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
29    *        if omitted).
30    *
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)
35    */
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) {
40       return false;
41     }
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.
52     //
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
56     // yet.
57     let uriToCheck = browser.documentURI || uri;
58     if (
59       (uriToCheck.spec == "about:blank" && contentPrincipal.isNullPrincipal) ||
60       contentPrincipal.spec == "about:blank"
61     ) {
62       return true;
63     }
64     if (contentPrincipal.isContentPrincipal) {
65       return contentPrincipal.equalsURI(uri);
66     }
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;
70   },
72   /**
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
77    * the font-size.
78    *
79    * @param element An element within the toolbar whose height is desired.
80    */
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;
90     }
91     if (!toolbarItem) {
92       return;
93     }
94     let bounds = dwu.getBoundsWithoutFlushing(toolbarItem);
95     if (!bounds.height) {
96       await window.promiseDocumentFlushed(() => {
97         bounds = dwu.getBoundsWithoutFlushing(toolbarItem);
98       });
99     }
100     if (bounds.height) {
101       toolbarItem.style.setProperty(
102         "--toolbarbutton-height",
103         bounds.height + "px"
104       );
105     }
106   },
108   /**
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.
113    *
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.
128    */
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");
134       }
135     }
136     let numberOfInsertionPoints = msg.match(/%\d+\$S/g).length;
137     if (numberOfInsertionPoints != nodesOrStrings.length) {
138       Cu.reportError(
139         `Message has ${numberOfInsertionPoints} insertion points, ` +
140           `but got ${nodesOrStrings.length} replacement parameters!`
141       );
142     }
144     let fragment = doc.createDocumentFragment();
145     let parts = [msg];
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)
151       );
152       if (partIndex == -1) {
153         fragment.appendChild(doc.createTextNode(msg));
154         return fragment;
155       }
157       if (typeof replacement == "string") {
158         parts[partIndex] = parts[partIndex].replace(
159           insertionString,
160           replacement
161         );
162       } else {
163         let [firstBit, lastBit] = parts[partIndex].split(insertionString);
164         parts.splice(partIndex, 1, firstBit, replacement, lastBit);
165       }
166     }
168     // Put everything in a document fragment:
169     for (let part of parts) {
170       if (typeof part == "string") {
171         if (part) {
172           fragment.appendChild(doc.createTextNode(part));
173         }
174       } else {
175         fragment.appendChild(part);
176       }
177     }
178     return fragment;
179   },
181   removeSingleTrailingSlashFromURL(aURL) {
182     // remove single trailing slash for http/https/ftp URLs
183     return aURL.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1");
184   },
186   /**
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.
194    *
195    * @param {string} aURL The URL to trim.
196    * @returns {string} The trimmed string.
197    */
198   get trimURLProtocol() {
199     return "http://";
200   },
201   trimURL(aURL) {
202     let url = this.removeSingleTrailingSlashFromURL(aURL);
203     // Remove "http://" prefix.
204     return url.startsWith(this.trimURLProtocol)
205       ? url.substring(this.trimURLProtocol.length)
206       : url;
207   },
210 XPCOMUtils.defineLazyPreferenceGetter(
211   BrowserUIUtils,
212   "quitShortcutDisabled",
213   "browser.quitShortcut.disabled",
214   false