LinkHandlerChild.sys.mjs (4735B)
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 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 FaviconLoader: "resource:///modules/FaviconLoader.sys.mjs", 9 }); 10 11 export class LinkHandlerChild extends JSWindowActorChild { 12 constructor() { 13 super(); 14 15 this.seenTabIcon = false; 16 this._iconLoader = null; 17 } 18 19 get iconLoader() { 20 if (!this._iconLoader) { 21 this._iconLoader = new lazy.FaviconLoader(this); 22 } 23 return this._iconLoader; 24 } 25 26 addRootIcon() { 27 if ( 28 !this.seenTabIcon && 29 Services.prefs.getBoolPref("browser.chrome.guess_favicon", true) && 30 Services.prefs.getBoolPref("browser.chrome.site_icons", true) 31 ) { 32 // Inject the default icon. Use documentURIObject so that we do the right 33 // thing with about:-style error pages. See bug 453442 34 let pageURI = this.document.documentURIObject; 35 if (["http", "https"].includes(pageURI.scheme)) { 36 this.seenTabIcon = true; 37 this.iconLoader.addDefaultIcon(pageURI); 38 } 39 } 40 } 41 42 onHeadParsed(event) { 43 if (event.target.ownerDocument != this.document) { 44 return; 45 } 46 47 // Per spec icons are meant to be in the <head> tag so we should have seen 48 // all the icons now so add the root icon if no other tab icons have been 49 // seen. 50 this.addRootIcon(); 51 52 // We're likely done with icon parsing so load the pending icons now. 53 if (this._iconLoader) { 54 this._iconLoader.onPageShow(); 55 } 56 } 57 58 onPageShow(event) { 59 if (event.target != this.document) { 60 return; 61 } 62 63 this.addRootIcon(); 64 65 if (this._iconLoader) { 66 this._iconLoader.onPageShow(); 67 } 68 } 69 70 onPageHide(event) { 71 if (event.target != this.document) { 72 return; 73 } 74 75 if (this._iconLoader) { 76 this._iconLoader.onPageHide(); 77 } 78 79 this.seenTabIcon = false; 80 } 81 82 onLinkEvent(event) { 83 let link = event.target; 84 // Ignore sub-frames (bugs 305472, 479408). 85 if (link.ownerGlobal != this.contentWindow) { 86 return; 87 } 88 89 let rel = link.rel && link.rel.toLowerCase(); 90 // We also check .getAttribute, since an empty href attribute will give us 91 // a link.href that is the same as the document. 92 if (!rel || !link.href || !link.getAttribute("href")) { 93 return; 94 } 95 96 // Note: following booleans only work for the current link, not for the 97 // whole content 98 let iconAdded = false; 99 let searchAdded = false; 100 let rels = {}; 101 for (let relString of rel.split(/\s+/)) { 102 rels[relString] = true; 103 } 104 105 for (let relVal in rels) { 106 let isRichIcon = false; 107 108 switch (relVal) { 109 case "apple-touch-icon": 110 case "apple-touch-icon-precomposed": 111 case "fluid-icon": 112 isRichIcon = true; 113 // fall through 114 case "icon": 115 if (iconAdded || link.hasAttribute("color") || rel.includes("mask")) { 116 // TODO (Bug 1337397): Add support for mask-icon favicons. 117 break; 118 } 119 120 if (!Services.prefs.getBoolPref("browser.chrome.site_icons", true)) { 121 return; 122 } 123 124 if (this.iconLoader.addIconFromLink(link, isRichIcon)) { 125 iconAdded = true; 126 if (!isRichIcon) { 127 this.seenTabIcon = true; 128 } 129 } 130 break; 131 case "search": 132 if ( 133 Services.policies && 134 !Services.policies.isAllowed("installSearchEngine") 135 ) { 136 break; 137 } 138 139 if (!searchAdded && event.type == "DOMLinkAdded") { 140 let type = link.type && link.type.toLowerCase(); 141 type = type.replace(/^\s+|\s*(?:;.*)?$/g, ""); 142 143 // Note: This protocol list should be kept in sync with 144 // the one in OpenSearchEngine's install function. 145 let re = /^https?:/i; 146 if ( 147 type == "application/opensearchdescription+xml" && 148 link.title && 149 re.test(link.href) 150 ) { 151 let engine = { title: link.title, href: link.href }; 152 this.sendAsyncMessage("Link:AddSearch", { 153 engine, 154 }); 155 searchAdded = true; 156 } 157 } 158 break; 159 } 160 } 161 } 162 163 handleEvent(event) { 164 switch (event.type) { 165 case "pageshow": 166 return this.onPageShow(event); 167 case "pagehide": 168 return this.onPageHide(event); 169 case "DOMHeadElementParsed": 170 return this.onHeadParsed(event); 171 default: 172 return this.onLinkEvent(event); 173 } 174 } 175 }