ext-ipp.js (10252B)
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 /* global ExtensionAPI, ExtensionCommon, Cr */ 6 7 const lazy = {}; 8 ChromeUtils.defineESModuleGetters(lazy, { 9 ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs", 10 IPPExceptionsManager: 11 "moz-src:///browser/components/ipprotection/IPPExceptionsManager.sys.mjs", 12 IPPProxyManager: 13 "moz-src:///browser/components/ipprotection/IPPProxyManager.sys.mjs", 14 IPPProxyStates: 15 "moz-src:///browser/components/ipprotection/IPPProxyManager.sys.mjs", 16 }); 17 18 ChromeUtils.defineLazyGetter(lazy, "tabTracker", () => { 19 return lazy.ExtensionParent.apiManager.global.tabTracker; 20 }); 21 22 const PREF_DYNAMIC_TAB_BREAKAGES = 23 "extensions.ippactivator.dynamicTabBreakages"; 24 const PREF_DYNAMIC_WEBREQUEST_BREAKAGES = 25 "extensions.ippactivator.dynamicWebRequestBreakages"; 26 const PREF_NOTIFIED_DOMAINS = "extensions.ippactivator.notifiedDomains"; 27 28 this.ippActivator = class extends ExtensionAPI { 29 onStartup() {} 30 31 onShutdown(_isAppShutdown) {} 32 33 getAPI(context) { 34 return { 35 ippActivator: { 36 onIPPActivated: new ExtensionCommon.EventManager({ 37 context, 38 name: "ippActivator.onIPPActivated", 39 register: fire => { 40 const topics = ["IPPProxyManager:StateChanged"]; 41 const observer = _event => { 42 fire.async(); 43 }; 44 45 topics.forEach(topic => 46 lazy.IPPProxyManager.addEventListener(topic, observer) 47 ); 48 49 return () => { 50 topics.forEach(topic => 51 lazy.IPPProxyManager.removeEventListener(topic, observer) 52 ); 53 }; 54 }, 55 }).api(), 56 isTesting() { 57 return Services.prefs.getBoolPref( 58 "extensions.ippactivator.testMode", 59 false 60 ); 61 }, 62 hideMessage(tabId) { 63 try { 64 const tab = tabId 65 ? lazy.tabTracker.getTab(tabId) 66 : lazy.tabTracker.activeTab; 67 const browser = tab?.linkedBrowser; 68 const win = browser?.ownerGlobal; 69 if (!browser || !win || !win.gBrowser) { 70 return; 71 } 72 73 const nbox = win.gBrowser.getNotificationBox(browser); 74 const id = "ipp-activator-notification"; 75 const existing = nbox.getNotificationWithValue?.(id); 76 if (existing) { 77 nbox.removeNotification(existing); 78 } 79 } catch (e) { 80 console.warn("Unable to hide the message", e); 81 } 82 }, 83 isIPPActive() { 84 return lazy.IPPProxyManager.state === lazy.IPPProxyStates.ACTIVE; 85 }, 86 getDynamicTabBreakages() { 87 try { 88 const json = Services.prefs.getStringPref( 89 PREF_DYNAMIC_TAB_BREAKAGES, 90 "[]" 91 ); 92 const arr = JSON.parse(json); 93 return Array.isArray(arr) ? arr : []; 94 } catch (_) { 95 return []; 96 } 97 }, 98 getDynamicWebRequestBreakages() { 99 try { 100 const json = Services.prefs.getStringPref( 101 PREF_DYNAMIC_WEBREQUEST_BREAKAGES, 102 "[]" 103 ); 104 const arr = JSON.parse(json); 105 return Array.isArray(arr) ? arr : []; 106 } catch (_) { 107 return []; 108 } 109 }, 110 getNotifiedDomains() { 111 try { 112 const json = Services.prefs.getStringPref( 113 PREF_NOTIFIED_DOMAINS, 114 "[]" 115 ); 116 const arr = JSON.parse(json); 117 return Array.isArray(arr) ? arr : []; 118 } catch (_) { 119 return []; 120 } 121 }, 122 addNotifiedDomain(domain) { 123 const d = String(domain || ""); 124 if (!d) { 125 return; 126 } 127 let arr = []; 128 try { 129 const json = Services.prefs.getStringPref( 130 PREF_NOTIFIED_DOMAINS, 131 "[]" 132 ); 133 arr = JSON.parse(json); 134 if (!Array.isArray(arr)) { 135 arr = []; 136 } 137 } catch (_) { 138 arr = []; 139 } 140 if (!arr.includes(d)) { 141 arr.push(d); 142 Services.prefs.setStringPref( 143 PREF_NOTIFIED_DOMAINS, 144 JSON.stringify(arr) 145 ); 146 } 147 }, 148 getBaseDomainFromURL(url) { 149 try { 150 const host = Services.io.newURI(url).host; 151 if (!host) { 152 return { baseDomain: "", host: "" }; 153 } 154 let baseDomain = ""; 155 try { 156 baseDomain = Services.eTLD.getBaseDomainFromHost(host); 157 } catch (e) { 158 if (e.result === Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { 159 baseDomain = host; 160 } else { 161 baseDomain = ""; 162 } 163 } 164 return { baseDomain, host }; 165 } catch (_) { 166 return { baseDomain: "", host: "" }; 167 } 168 }, 169 hasExclusion(url) { 170 if ( 171 !Services.prefs.getBoolPref( 172 "browser.ipProtection.features.siteExceptions", 173 false 174 ) 175 ) { 176 return false; 177 } 178 179 try { 180 const uri = Services.io.newURI(url); 181 const principal = 182 Services.scriptSecurityManager.createContentPrincipal(uri, {}); 183 return lazy.IPPExceptionsManager.hasExclusion(principal); 184 } catch (e) { 185 return false; 186 } 187 }, 188 async showMessage(message, tabId) { 189 try { 190 // Choose the target tab (by id if provided, else active tab) 191 const tab = tabId 192 ? lazy.tabTracker.getTab(tabId) 193 : lazy.tabTracker.activeTab; 194 const browser = tab?.linkedBrowser; 195 const win = browser?.ownerGlobal; 196 if (!browser || !win || !win.gBrowser) { 197 return Promise.resolve(false); 198 } 199 200 const nbox = win.gBrowser.getNotificationBox(browser); 201 const id = "ipp-activator-notification"; 202 203 const existing = nbox.getNotificationWithValue?.(id); 204 if (existing) { 205 nbox.removeNotification(existing); 206 } 207 208 const buildLabel = msg => { 209 // Accept either string or array of parts {text, modifier} 210 if (Array.isArray(msg)) { 211 const frag = win.document.createDocumentFragment(); 212 for (const part of msg) { 213 const text = String(part?.text ?? ""); 214 const mods = Array.isArray(part?.modifier) 215 ? part.modifier 216 : []; 217 if (mods.includes("strong")) { 218 const strong = win.document.createElement("strong"); 219 strong.textContent = text; 220 frag.append(strong); 221 } else { 222 frag.append(win.document.createTextNode(text)); 223 } 224 } 225 return frag; 226 } 227 return String(msg ?? ""); 228 }; 229 230 const label = buildLabel(message); 231 232 // Promise that resolves when the notification is dismissed 233 let resolveDismiss; 234 const dismissedPromise = new Promise(resolve => { 235 resolveDismiss = resolve; 236 }); 237 238 // Create the notification; set persistence when available 239 nbox 240 .appendNotification( 241 id, 242 { 243 // If label is a string, pass it through; if it's a Node, the 244 // notification box will handle it as rich content. 245 label, 246 priority: nbox.PRIORITY_WARNING_HIGH, 247 eventCallback: param => { 248 resolveDismiss(param === "dismissed"); 249 }, 250 }, 251 [] 252 ) 253 .then(notification => { 254 // Persist the notification until the user removes so it 255 // doesn't get removed on redirects. 256 notification.persistence = -1; 257 }); 258 259 return dismissedPromise; 260 } catch (e) { 261 console.warn("Unable to show the message", e); 262 return Promise.resolve(false); 263 } 264 }, 265 onDynamicTabBreakagesUpdated: new ExtensionCommon.EventManager({ 266 context, 267 name: "ippActivator.onDynamicTabBreakagesUpdated", 268 register: fire => { 269 const observer = { 270 observe(subject, topic, data) { 271 if ( 272 topic === "nsPref:changed" && 273 data === PREF_DYNAMIC_TAB_BREAKAGES 274 ) { 275 fire.async(); 276 } 277 }, 278 }; 279 Services.prefs.addObserver(PREF_DYNAMIC_TAB_BREAKAGES, observer); 280 return () => 281 Services.prefs.removeObserver( 282 PREF_DYNAMIC_TAB_BREAKAGES, 283 observer 284 ); 285 }, 286 }).api(), 287 onDynamicWebRequestBreakagesUpdated: new ExtensionCommon.EventManager({ 288 context, 289 name: "ippActivator.onDynamicWebRequestBreakagesUpdated", 290 register: fire => { 291 const observer = { 292 observe(subject, topic, data) { 293 if ( 294 topic === "nsPref:changed" && 295 data === PREF_DYNAMIC_WEBREQUEST_BREAKAGES 296 ) { 297 fire.async(); 298 } 299 }, 300 }; 301 Services.prefs.addObserver( 302 PREF_DYNAMIC_WEBREQUEST_BREAKAGES, 303 observer 304 ); 305 return () => 306 Services.prefs.removeObserver( 307 PREF_DYNAMIC_WEBREQUEST_BREAKAGES, 308 observer 309 ); 310 }, 311 }).api(), 312 }, 313 }; 314 } 315 };