Source code

Revision control

Other Tools

1
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
* License, v. 2.0. If a copy of the MPL was not distributed with this
4
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
var EXPORTED_SYMBOLS = ["ClickHandlerChild"];
7
8
const { ActorChild } = ChromeUtils.import(
10
);
11
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
12
13
ChromeUtils.defineModuleGetter(
14
this,
15
"BrowserUtils",
17
);
18
ChromeUtils.defineModuleGetter(
19
this,
20
"PrivateBrowsingUtils",
22
);
23
ChromeUtils.defineModuleGetter(
24
this,
25
"WebNavigationFrames",
27
);
28
ChromeUtils.defineModuleGetter(
29
this,
30
"E10SUtils",
32
);
33
34
class ClickHandlerChild extends ActorChild {
35
handleEvent(event) {
36
if (
37
!event.isTrusted ||
38
event.defaultPrevented ||
39
event.button == 2 ||
40
(event.type == "click" && event.button == 1)
41
) {
42
return;
43
}
44
// Don't do anything on editable things, we shouldn't open links in
45
// contenteditables, and editor needs to possibly handle middlemouse paste
46
let composedTarget = event.composedTarget;
47
if (
48
composedTarget.isContentEditable ||
49
(composedTarget.ownerDocument &&
50
composedTarget.ownerDocument.designMode == "on") ||
51
ChromeUtils.getClassName(composedTarget) == "HTMLInputElement" ||
52
ChromeUtils.getClassName(composedTarget) == "HTMLTextAreaElement"
53
) {
54
return;
55
}
56
57
let originalTarget = event.originalTarget;
58
let ownerDoc = originalTarget.ownerDocument;
59
if (!ownerDoc) {
60
return;
61
}
62
63
// Handle click events from about pages
64
if (event.button == 0) {
65
if (ownerDoc.documentURI.startsWith("about:blocked")) {
66
return;
67
}
68
}
69
70
let [href, node, principal] = this._hrefAndLinkNodeForClickEvent(event);
71
72
let csp = ownerDoc.csp;
73
if (csp) {
74
csp = E10SUtils.serializeCSP(csp);
75
}
76
77
let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(
78
Ci.nsIReferrerInfo
79
);
80
if (node) {
81
referrerInfo.initWithNode(node);
82
} else {
83
referrerInfo.initWithDocument(ownerDoc);
84
}
85
referrerInfo = E10SUtils.serializeReferrerInfo(referrerInfo);
86
let frameOuterWindowID = WebNavigationFrames.getFrameId(
87
ownerDoc.defaultView
88
);
89
90
let json = {
91
button: event.button,
92
shiftKey: event.shiftKey,
93
ctrlKey: event.ctrlKey,
94
metaKey: event.metaKey,
95
altKey: event.altKey,
96
href: null,
97
title: null,
98
frameOuterWindowID,
99
triggeringPrincipal: principal,
100
csp,
101
referrerInfo,
102
originAttributes: principal ? principal.originAttributes : {},
103
isContentWindowPrivate: PrivateBrowsingUtils.isContentWindowPrivate(
104
ownerDoc.defaultView
105
),
106
};
107
108
if (href) {
109
try {
110
BrowserUtils.urlSecurityCheck(href, principal);
111
} catch (e) {
112
return;
113
}
114
115
json.href = href;
116
if (node) {
117
json.title = node.getAttribute("title");
118
}
119
120
// Check if the link needs to be opened with mixed content allowed.
121
// Only when the owner doc has |mixedContentChannel| and the same origin
122
// should we allow mixed content.
123
json.allowMixedContent = false;
124
let docshell = ownerDoc.defaultView.docShell;
125
if (this.mm.docShell.mixedContentChannel) {
126
const sm = Services.scriptSecurityManager;
127
try {
128
let targetURI = Services.io.newURI(href);
129
let isPrivateWin =
130
ownerDoc.nodePrincipal.originAttributes.privateBrowsingId > 0;
131
sm.checkSameOriginURI(
132
docshell.mixedContentChannel.URI,
133
targetURI,
134
false,
135
isPrivateWin
136
);
137
json.allowMixedContent = true;
138
} catch (e) {}
139
}
140
json.originPrincipal = ownerDoc.nodePrincipal;
141
json.originStoragePrincipal = ownerDoc.effectiveStoragePrincipal;
142
json.triggeringPrincipal = ownerDoc.nodePrincipal;
143
144
// If a link element is clicked with middle button, user wants to open
145
// the link somewhere rather than pasting clipboard content. Therefore,
146
// when it's clicked with middle button, we should prevent multiple
147
// actions here to avoid leaking clipboard content unexpectedly.
148
// Note that whether the link will work actually or not does not matter
149
// because in this case, user does not intent to paste clipboard content.
150
if (event.button === 1) {
151
event.preventMultipleActions();
152
}
153
154
this.mm.sendAsyncMessage("Content:Click", json);
155
return;
156
}
157
158
// This might be middle mouse navigation.
159
if (event.button == 1) {
160
this.mm.sendAsyncMessage("Content:Click", json);
161
}
162
}
163
164
/**
165
* Extracts linkNode and href for the current click target.
166
*
167
* @param event
168
* The click event.
169
* @return [href, linkNode, linkPrincipal].
170
*
171
* @note linkNode will be null if the click wasn't on an anchor
172
* element. This includes SVG links, because callers expect |node|
173
* to behave like an <a> element, which SVG links (XLink) don't.
174
*/
175
_hrefAndLinkNodeForClickEvent(event) {
176
let { content } = this.mm;
177
function isHTMLLink(aNode) {
178
// Be consistent with what nsContextMenu.js does.
179
return (
180
(aNode instanceof content.HTMLAnchorElement && aNode.href) ||
181
(aNode instanceof content.HTMLAreaElement && aNode.href) ||
182
aNode instanceof content.HTMLLinkElement
183
);
184
}
185
186
let node = event.composedTarget;
187
while (node && !isHTMLLink(node)) {
188
node = node.flattenedTreeParentNode;
189
}
190
191
if (node) {
192
return [node.href, node, node.ownerDocument.nodePrincipal];
193
}
194
195
// If there is no linkNode, try simple XLink.
196
let href, baseURI;
197
node = event.composedTarget;
198
while (node && !href) {
199
if (
200
node.nodeType == content.Node.ELEMENT_NODE &&
201
(node.localName == "a" ||
202
node.namespaceURI == "http://www.w3.org/1998/Math/MathML")
203
) {
204
href =
205
node.getAttribute("href") ||
206
node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
207
if (href) {
208
baseURI = node.ownerDocument.baseURIObject;
209
break;
210
}
211
}
212
node = node.flattenedTreeParentNode;
213
}
214
215
// In case of XLink, we don't return the node we got href from since
216
// callers expect <a>-like elements.
217
// Note: makeURI() will throw if aUri is not a valid URI.
218
return [
219
href ? Services.io.newURI(href, null, baseURI).spec : null,
220
null,
221
node && node.ownerDocument.nodePrincipal,
222
];
223
}
224
}