BrowserUIUtils.sys.mjs (6995B)
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/. */ 5 6 import { UrlbarPrefs } from "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs"; 7 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 8 9 export var BrowserUIUtils = { 10 /** 11 * Check whether a page can be considered as 'empty', that its URI 12 * reflects its origin, and that if it's loaded in a tab, that tab 13 * could be considered 'empty' (e.g. like the result of opening 14 * a 'blank' new tab). 15 * 16 * We have to do more than just check the URI, because especially 17 * for things like about:blank, it is possible that the opener or 18 * some other page has control over the contents of the page. 19 * 20 * @param {Browser} browser 21 * The browser whose page we're checking. 22 * @param {nsIURI} [uri] 23 * The URI against which we're checking (the browser's currentURI 24 * if omitted). 25 * 26 * @return {boolean} false if the page was opened by or is controlled by 27 * arbitrary web content, unless that content corresponds with the URI. 28 * true if the page is blank and controlled by a principal matching 29 * that URI (or the system principal if the principal has no URI) 30 */ 31 checkEmptyPageOrigin(browser, uri = browser.currentURI) { 32 // If another page opened this page with e.g. window.open, this page might 33 // be controlled by its opener. 34 if (browser.hasContentOpener) { 35 return false; 36 } 37 let contentPrincipal = browser.contentPrincipal; 38 // Not all principals have URIs... 39 // There are two special-cases involving about:blank. One is where 40 // the user has manually loaded it and it got created with a null 41 // principal. The other involves the case where we load 42 // some other empty page in a browser and the current page is the 43 // initial about:blank page (which has that as its principal, not 44 // just URI in which case it could be web-based). Especially in 45 // e10s, we need to tackle that case specifically to avoid race 46 // conditions when updating the URL bar. 47 // 48 // Note that we check the documentURI here, since the currentURI on 49 // the browser might have been set by SessionStore in order to 50 // support switch-to-tab without having actually loaded the content 51 // yet. 52 let uriToCheck = browser.documentURI || uri; 53 if ( 54 (uriToCheck.spec == "about:blank" && contentPrincipal.isNullPrincipal) || 55 contentPrincipal.spec == "about:blank" 56 ) { 57 return true; 58 } 59 if (contentPrincipal.isContentPrincipal) { 60 return contentPrincipal.equalsURI(uri); 61 } 62 // ... so for those that don't have them, enforce that the page has the 63 // system principal (this matches e.g. on about:newtab). 64 return contentPrincipal.isSystemPrincipal; 65 }, 66 67 /** 68 * Generate a document fragment for a localized string that has DOM 69 * node replacements. This avoids using getFormattedString followed 70 * by assigning to innerHTML. Fluent can probably replace this when 71 * it is in use everywhere. 72 * 73 * @param {Document} doc 74 * @param {string} msg 75 * The string to put replacements in. Fetch from 76 * a stringbundle using getString or GetStringFromName, 77 * or even an inserted dtd string. 78 * @param {Node | string} nodesOrStrings 79 * The replacement items. Can be a mix of Nodes 80 * and Strings. However, for correct behaviour, the 81 * number of items provided needs to exactly match 82 * the number of replacement strings in the l10n string. 83 * @returns {DocumentFragment} 84 * A document fragment. In the trivial case (no 85 * replacements), this will simply be a fragment with 1 86 * child, a text node containing the localized string. 87 */ 88 getLocalizedFragment(doc, msg, ...nodesOrStrings) { 89 // Ensure replacement points are indexed: 90 for (let i = 1; i <= nodesOrStrings.length; i++) { 91 if (!msg.includes("%" + i + "$S")) { 92 msg = msg.replace(/%S/, "%" + i + "$S"); 93 } 94 } 95 let numberOfInsertionPoints = msg.match(/%\d+\$S/g).length; 96 if (numberOfInsertionPoints != nodesOrStrings.length) { 97 console.error( 98 `Message has ${numberOfInsertionPoints} insertion points, ` + 99 `but got ${nodesOrStrings.length} replacement parameters!` 100 ); 101 } 102 103 let fragment = doc.createDocumentFragment(); 104 let parts = [msg]; 105 let insertionPoint = 1; 106 for (let replacement of nodesOrStrings) { 107 let insertionString = "%" + insertionPoint++ + "$S"; 108 let partIndex = parts.findIndex( 109 part => typeof part == "string" && part.includes(insertionString) 110 ); 111 if (partIndex == -1) { 112 fragment.appendChild(doc.createTextNode(msg)); 113 return fragment; 114 } 115 116 if (typeof replacement == "string") { 117 parts[partIndex] = parts[partIndex].replace( 118 insertionString, 119 replacement 120 ); 121 } else { 122 let [firstBit, lastBit] = parts[partIndex].split(insertionString); 123 parts.splice(partIndex, 1, firstBit, replacement, lastBit); 124 } 125 } 126 127 // Put everything in a document fragment: 128 for (let part of parts) { 129 if (typeof part == "string") { 130 if (part) { 131 fragment.appendChild(doc.createTextNode(part)); 132 } 133 } else { 134 fragment.appendChild(part); 135 } 136 } 137 return fragment; 138 }, 139 140 removeSingleTrailingSlashFromURL(aURL) { 141 // remove single trailing slash for http/https/ftp URLs 142 return aURL.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1"); 143 }, 144 145 get trimURLProtocol() { 146 return UrlbarPrefs.getScotchBonnetPref("trimHttps") 147 ? "https://" 148 : "http://"; 149 }, 150 151 /** 152 * Returns a URL which has been trimmed by removing 'http://' or 'https://', 153 * when the pref 'trimHttps' is set to true, and any trailing slash 154 * (in http/https/ftp urls). Note that a trimmed url may not load the same 155 * page as the original url, so before loading it, it must be passed through 156 * URIFixup, to check trimming doesn't change its destination. We don't run 157 * the URIFixup check here, because trimURL is in the page load path 158 * (see onLocationChange), so it must be fast and simple. 159 * 160 * @param {string} aURL The URL to trim. 161 * @returns {string} The trimmed string. 162 */ 163 trimURL(aURL) { 164 let url = this.removeSingleTrailingSlashFromURL(aURL); 165 return url.startsWith(this.trimURLProtocol) 166 ? url.substring(this.trimURLProtocol.length) 167 : url; 168 }, 169 }; 170 171 XPCOMUtils.defineLazyPreferenceGetter( 172 BrowserUIUtils, 173 "quitShortcutDisabled", 174 "browser.quitShortcut.disabled", 175 false 176 );