diff options
Diffstat (limited to 'browser/actors/LinkHandlerChild.sys.mjs')
-rw-r--r-- | browser/actors/LinkHandlerChild.sys.mjs | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/browser/actors/LinkHandlerChild.sys.mjs b/browser/actors/LinkHandlerChild.sys.mjs new file mode 100644 index 0000000000..95c86b2d0f --- /dev/null +++ b/browser/actors/LinkHandlerChild.sys.mjs @@ -0,0 +1,175 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + FaviconLoader: "resource:///modules/FaviconLoader.sys.mjs", +}); + +export class LinkHandlerChild extends JSWindowActorChild { + constructor() { + super(); + + this.seenTabIcon = false; + this._iconLoader = null; + } + + get iconLoader() { + if (!this._iconLoader) { + this._iconLoader = new lazy.FaviconLoader(this); + } + return this._iconLoader; + } + + addRootIcon() { + if ( + !this.seenTabIcon && + Services.prefs.getBoolPref("browser.chrome.guess_favicon", true) && + Services.prefs.getBoolPref("browser.chrome.site_icons", true) + ) { + // Inject the default icon. Use documentURIObject so that we do the right + // thing with about:-style error pages. See bug 453442 + let pageURI = this.document.documentURIObject; + if (["http", "https"].includes(pageURI.scheme)) { + this.seenTabIcon = true; + this.iconLoader.addDefaultIcon(pageURI); + } + } + } + + onHeadParsed(event) { + if (event.target.ownerDocument != this.document) { + return; + } + + // Per spec icons are meant to be in the <head> tag so we should have seen + // all the icons now so add the root icon if no other tab icons have been + // seen. + this.addRootIcon(); + + // We're likely done with icon parsing so load the pending icons now. + if (this._iconLoader) { + this._iconLoader.onPageShow(); + } + } + + onPageShow(event) { + if (event.target != this.document) { + return; + } + + this.addRootIcon(); + + if (this._iconLoader) { + this._iconLoader.onPageShow(); + } + } + + onPageHide(event) { + if (event.target != this.document) { + return; + } + + if (this._iconLoader) { + this._iconLoader.onPageHide(); + } + + this.seenTabIcon = false; + } + + onLinkEvent(event) { + let link = event.target; + // Ignore sub-frames (bugs 305472, 479408). + if (link.ownerGlobal != this.contentWindow) { + return; + } + + let rel = link.rel && link.rel.toLowerCase(); + // We also check .getAttribute, since an empty href attribute will give us + // a link.href that is the same as the document. + if (!rel || !link.href || !link.getAttribute("href")) { + return; + } + + // Note: following booleans only work for the current link, not for the + // whole content + let iconAdded = false; + let searchAdded = false; + let rels = {}; + for (let relString of rel.split(/\s+/)) { + rels[relString] = true; + } + + for (let relVal in rels) { + let isRichIcon = false; + + switch (relVal) { + case "apple-touch-icon": + case "apple-touch-icon-precomposed": + case "fluid-icon": + isRichIcon = true; + // fall through + case "icon": + if (iconAdded || link.hasAttribute("mask")) { + // Masked icons are not supported yet. + break; + } + + if (!Services.prefs.getBoolPref("browser.chrome.site_icons", true)) { + return; + } + + if (this.iconLoader.addIconFromLink(link, isRichIcon)) { + iconAdded = true; + if (!isRichIcon) { + this.seenTabIcon = true; + } + } + break; + case "search": + if ( + Services.policies && + !Services.policies.isAllowed("installSearchEngine") + ) { + break; + } + + if (!searchAdded && event.type == "DOMLinkAdded") { + let type = link.type && link.type.toLowerCase(); + type = type.replace(/^\s+|\s*(?:;.*)?$/g, ""); + + // Note: This protocol list should be kept in sync with + // the one in OpenSearchEngine's install function. + let re = /^https?:/i; + if ( + type == "application/opensearchdescription+xml" && + link.title && + re.test(link.href) + ) { + let engine = { title: link.title, href: link.href }; + this.sendAsyncMessage("Link:AddSearch", { + engine, + }); + searchAdded = true; + } + } + break; + } + } + } + + handleEvent(event) { + switch (event.type) { + case "pageshow": + return this.onPageShow(event); + case "pagehide": + return this.onPageHide(event); + case "DOMHeadElementParsed": + return this.onHeadParsed(event); + default: + return this.onLinkEvent(event); + } + } +} |