Merge autoland to mozilla-central. a=merge
[gecko.git] / browser / actors / LinkHandlerChild.sys.mjs
blob95c86b2d0f12039ed18487a51bdabccefbded578
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/. */
5 const lazy = {};
7 ChromeUtils.defineESModuleGetters(lazy, {
8   FaviconLoader: "resource:///modules/FaviconLoader.sys.mjs",
9 });
11 export class LinkHandlerChild extends JSWindowActorChild {
12   constructor() {
13     super();
15     this.seenTabIcon = false;
16     this._iconLoader = null;
17   }
19   get iconLoader() {
20     if (!this._iconLoader) {
21       this._iconLoader = new lazy.FaviconLoader(this);
22     }
23     return this._iconLoader;
24   }
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   }
42   onHeadParsed(event) {
43     if (event.target.ownerDocument != this.document) {
44       return;
45     }
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();
52     // We're likely done with icon parsing so load the pending icons now.
53     if (this._iconLoader) {
54       this._iconLoader.onPageShow();
55     }
56   }
58   onPageShow(event) {
59     if (event.target != this.document) {
60       return;
61     }
63     this.addRootIcon();
65     if (this._iconLoader) {
66       this._iconLoader.onPageShow();
67     }
68   }
70   onPageHide(event) {
71     if (event.target != this.document) {
72       return;
73     }
75     if (this._iconLoader) {
76       this._iconLoader.onPageHide();
77     }
79     this.seenTabIcon = false;
80   }
82   onLinkEvent(event) {
83     let link = event.target;
84     // Ignore sub-frames (bugs 305472, 479408).
85     if (link.ownerGlobal != this.contentWindow) {
86       return;
87     }
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     }
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     }
105     for (let relVal in rels) {
106       let isRichIcon = false;
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("mask")) {
116             // Masked icons are not supported yet.
117             break;
118           }
120           if (!Services.prefs.getBoolPref("browser.chrome.site_icons", true)) {
121             return;
122           }
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           }
139           if (!searchAdded && event.type == "DOMLinkAdded") {
140             let type = link.type && link.type.toLowerCase();
141             type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
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   }
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   }