Source code

Revision control

Other Tools

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