split-view-footer.js (6416B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 // This is loaded into chrome windows with the subscript loader. Wrap in 8 // a block to prevent accidentally leaking globals onto `window`. 9 { 10 ChromeUtils.defineESModuleGetters(this, { 11 BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs", 12 }); 13 14 /** 15 * A footer which appears in the corner of the inactive panel in split view. 16 * 17 * The footer displays the favicon and domain name of the site. 18 */ 19 class MozSplitViewFooter extends MozXULElement { 20 #initialized = false; 21 22 #isInsecure = false; 23 /** @type {HTMLSpanElement} */ 24 securityElement = null; 25 26 /** @type {HTMLImageElement} */ 27 iconElement = null; 28 #iconSrc = ""; 29 30 /** @type {HTMLSpanElement} */ 31 uriElement = null; 32 /** @type {nsIURI} */ 33 #uri = null; 34 35 #browserProgressListener = { 36 QueryInterface: ChromeUtils.generateQI([ 37 Ci.nsIWebProgressListener, 38 Ci.nsISupportsWeakReference, 39 ]), 40 onLocationChange: (aWebProgress, aRequest, aLocation) => { 41 if (aWebProgress?.isTopLevel && aLocation) { 42 this.#updateUri(aLocation); 43 } 44 }, 45 onSecurityChange: (aWebProgress, aRequest, aState) => 46 this.#toggleInsecure( 47 !!( 48 aState & Ci.nsIWebProgressListener.STATE_IS_INSECURE || 49 aState & Ci.nsIWebProgressListener.STATE_IS_BROKEN 50 ) 51 ), 52 }; 53 54 /** @type {XULElement} */ 55 #tab = null; 56 57 static markup = ` 58 <hbox class="split-view-security-warning" hidden=""> 59 <html:img role="presentation" src="chrome://global/skin/icons/security-broken.svg" /> 60 <html:span data-l10n-id="urlbar-trust-icon-notsecure-label"></html:span> 61 </hbox> 62 <html:img class="split-view-icon" hidden="" role="presentation"/> 63 <html:span class="split-view-uri"></html:span> 64 <toolbarbutton image="chrome://global/skin/icons/more.svg" 65 data-l10n-id="urlbar-split-view-button" /> 66 `; 67 68 connectedCallback() { 69 if (this.#initialized) { 70 return; 71 } 72 this.appendChild(this.constructor.fragment); 73 74 this.securityElement = this.querySelector(".split-view-security-warning"); 75 this.iconElement = this.querySelector(".split-view-icon"); 76 this.uriElement = this.querySelector(".split-view-uri"); 77 this.menuButtonElement = this.querySelector("toolbarbutton"); 78 79 // Ensure these elements are up-to-date, as this info may have been set 80 // prior to inserting this element into the DOM. 81 this.#updateSecurityElement(); 82 this.#updateIconElement(); 83 this.#updateUriElement(); 84 85 this.menuButtonElement.addEventListener("command", this); 86 87 this.#initialized = true; 88 } 89 90 disconnectedCallback() { 91 this.#resetTab(); 92 } 93 94 handleEvent(e) { 95 switch (e.type) { 96 case "command": 97 gBrowser.openSplitViewMenu(this.menuButtonElement); 98 break; 99 case "TabAttrModified": 100 for (const attribute of e.detail.changed) { 101 this.#handleTabAttrModified(attribute); 102 } 103 break; 104 } 105 } 106 107 #handleTabAttrModified(attribute) { 108 switch (attribute) { 109 case "image": 110 this.#updateIconSrc(this.#tab.image); 111 break; 112 } 113 } 114 115 /** 116 * Update the insecure flag and refresh the security warning visibility. 117 * 118 * @param {boolean} isInsecure 119 */ 120 #toggleInsecure(isInsecure) { 121 this.#isInsecure = isInsecure; 122 if (this.securityElement) { 123 this.#updateSecurityElement(); 124 } 125 if (this.iconElement) { 126 this.#updateIconElement(); 127 } 128 } 129 130 #updateSecurityElement() { 131 const isWebsite = 132 this.#uri.schemeIs("http") || this.#uri.schemeIs("https"); 133 this.securityElement.hidden = !isWebsite || !this.#isInsecure; 134 } 135 136 /** 137 * Update the footer icon to the given source URI. 138 * 139 * @param {string} iconSrc 140 */ 141 #updateIconSrc(iconSrc) { 142 this.#iconSrc = iconSrc; 143 if (this.iconElement) { 144 this.#updateIconElement(); 145 } 146 } 147 148 #updateIconElement() { 149 let canShowIcon = !this.#isInsecure && this.#iconSrc; 150 if (canShowIcon) { 151 this.iconElement.setAttribute("src", this.#iconSrc); 152 } else { 153 this.iconElement.removeAttribute("src"); 154 } 155 this.iconElement.hidden = !canShowIcon; 156 } 157 158 /** 159 * Update the footer URI display with the formatted domain string. 160 * 161 * @param {nsIURI} uri 162 */ 163 #updateUri(uri) { 164 this.hidden = uri.specIgnoringRef === "about:opentabs"; 165 this.#uri = uri; 166 if (this.uriElement) { 167 this.#updateUriElement(); 168 } 169 if (this.securityElement) { 170 this.#updateSecurityElement(); 171 } 172 } 173 174 #updateUriElement() { 175 const uriString = this.#uri 176 ? BrowserUtils.formatURIForDisplay(this.#uri) 177 : ""; 178 this.uriElement.textContent = uriString; 179 } 180 181 /** 182 * Link the footer to the provided tab so it stays in sync. 183 * 184 * @param {MozTabbrowserTab} tab 185 */ 186 setTab(tab) { 187 this.#resetTab(); 188 189 // Track favicon changes. 190 this.#updateIconSrc(tab.image); 191 tab.addEventListener("TabAttrModified", this); 192 193 // Track URI and security changes. 194 this.#updateUri(tab.linkedBrowser.currentURI); 195 const securityState = tab.linkedBrowser.securityUI.state; 196 this.#toggleInsecure( 197 !!( 198 securityState & Ci.nsIWebProgressListener.STATE_IS_INSECURE || 199 securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN 200 ) 201 ); 202 tab.linkedBrowser.addProgressListener( 203 this.#browserProgressListener, 204 Ci.nsIWebProgress.NOTIFY_LOCATION | Ci.nsIWebProgress.NOTIFY_SECURITY 205 ); 206 207 this.#tab = tab; 208 } 209 210 /** 211 * Remove the footer's association with the current tab. 212 */ 213 #resetTab() { 214 if (this.#tab) { 215 this.#tab.removeEventListener("TabAttrModified", this); 216 this.#tab.linkedBrowser?.removeProgressListener( 217 this.#browserProgressListener 218 ); 219 } 220 this.#tab = null; 221 } 222 } 223 224 customElements.define("split-view-footer", MozSplitViewFooter); 225 }