зеркало из https://github.com/mozilla/gecko-dev.git
163 строки
6.1 KiB
JavaScript
163 строки
6.1 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* 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/. */
|
|
|
|
var EXPORTED_SYMBOLS = ["ClickHandlerChild"];
|
|
|
|
const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
|
|
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
|
|
ChromeUtils.defineModuleGetter(this, "BrowserUtils",
|
|
"resource://gre/modules/BrowserUtils.jsm");
|
|
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
|
|
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
|
ChromeUtils.defineModuleGetter(this, "WebNavigationFrames",
|
|
"resource://gre/modules/WebNavigationFrames.jsm");
|
|
|
|
class ClickHandlerChild extends ActorChild {
|
|
handleEvent(event) {
|
|
if (!event.isTrusted || event.defaultPrevented || event.button == 2) {
|
|
return;
|
|
}
|
|
|
|
let originalTarget = event.originalTarget;
|
|
let ownerDoc = originalTarget.ownerDocument;
|
|
if (!ownerDoc) {
|
|
return;
|
|
}
|
|
|
|
// Handle click events from about pages
|
|
if (event.button == 0) {
|
|
if (ownerDoc.documentURI.startsWith("about:blocked")) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
let [href, node, principal] = this._hrefAndLinkNodeForClickEvent(event);
|
|
|
|
// get referrer attribute from clicked link and parse it
|
|
// if per element referrer is enabled, the element referrer overrules
|
|
// the document wide referrer
|
|
let referrerPolicy = ownerDoc.referrerPolicy;
|
|
if (node) {
|
|
let referrerAttrValue = Services.netUtils.parseAttributePolicyString(node.
|
|
getAttribute("referrerpolicy"));
|
|
if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
|
|
referrerPolicy = referrerAttrValue;
|
|
}
|
|
}
|
|
|
|
let frameOuterWindowID = WebNavigationFrames.getFrameId(ownerDoc.defaultView);
|
|
|
|
let json = { button: event.button, shiftKey: event.shiftKey,
|
|
ctrlKey: event.ctrlKey, metaKey: event.metaKey,
|
|
altKey: event.altKey, href: null, title: null,
|
|
frameOuterWindowID, referrerPolicy,
|
|
triggeringPrincipal: principal,
|
|
originAttributes: principal ? principal.originAttributes : {},
|
|
isContentWindowPrivate: PrivateBrowsingUtils.isContentWindowPrivate(ownerDoc.defaultView)};
|
|
|
|
if (href) {
|
|
try {
|
|
BrowserUtils.urlSecurityCheck(href, principal);
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
|
|
json.href = href;
|
|
if (node) {
|
|
json.title = node.getAttribute("title");
|
|
}
|
|
json.noReferrer = BrowserUtils.linkHasNoReferrer(node);
|
|
|
|
// Check if the link needs to be opened with mixed content allowed.
|
|
// Only when the owner doc has |mixedContentChannel| and the same origin
|
|
// should we allow mixed content.
|
|
json.allowMixedContent = false;
|
|
let docshell = ownerDoc.defaultView.docShell;
|
|
if (this.mm.docShell.mixedContentChannel) {
|
|
const sm = Services.scriptSecurityManager;
|
|
try {
|
|
let targetURI = Services.io.newURI(href);
|
|
let isPrivateWin = ownerDoc.nodePrincipal.originAttributes.privateBrowsingId > 0;
|
|
sm.checkSameOriginURI(docshell.mixedContentChannel.URI, targetURI, false, isPrivateWin);
|
|
json.allowMixedContent = true;
|
|
} catch (e) {}
|
|
}
|
|
json.originPrincipal = ownerDoc.nodePrincipal;
|
|
json.triggeringPrincipal = ownerDoc.nodePrincipal;
|
|
|
|
// If a link element is clicked with middle button, user wants to open
|
|
// the link somewhere rather than pasting clipboard content. Therefore,
|
|
// when it's clicked with middle button, we should prevent multiple
|
|
// actions here to avoid leaking clipboard content unexpectedly.
|
|
// Note that whether the link will work actually or not does not matter
|
|
// because in this case, user does not intent to paste clipboard content.
|
|
if (event.button === 1) {
|
|
event.preventMultipleActions();
|
|
}
|
|
|
|
this.mm.sendAsyncMessage("Content:Click", json);
|
|
return;
|
|
}
|
|
|
|
// This might be middle mouse navigation.
|
|
if (event.button == 1) {
|
|
this.mm.sendAsyncMessage("Content:Click", json);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extracts linkNode and href for the current click target.
|
|
*
|
|
* @param event
|
|
* The click event.
|
|
* @return [href, linkNode, linkPrincipal].
|
|
*
|
|
* @note linkNode will be null if the click wasn't on an anchor
|
|
* element. This includes SVG links, because callers expect |node|
|
|
* to behave like an <a> element, which SVG links (XLink) don't.
|
|
*/
|
|
_hrefAndLinkNodeForClickEvent(event) {
|
|
let {content} = this.mm;
|
|
function isHTMLLink(aNode) {
|
|
// Be consistent with what nsContextMenu.js does.
|
|
return ((aNode instanceof content.HTMLAnchorElement && aNode.href) ||
|
|
(aNode instanceof content.HTMLAreaElement && aNode.href) ||
|
|
aNode instanceof content.HTMLLinkElement);
|
|
}
|
|
|
|
let node = event.composedTarget;
|
|
while (node && !isHTMLLink(node)) {
|
|
node = node.flattenedTreeParentNode;
|
|
}
|
|
|
|
if (node)
|
|
return [node.href, node, node.ownerDocument.nodePrincipal];
|
|
|
|
// If there is no linkNode, try simple XLink.
|
|
let href, baseURI;
|
|
node = event.composedTarget;
|
|
while (node && !href) {
|
|
if (node.nodeType == content.Node.ELEMENT_NODE &&
|
|
(node.localName == "a" ||
|
|
node.namespaceURI == "http://www.w3.org/1998/Math/MathML")) {
|
|
href = node.getAttribute("href") ||
|
|
node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
|
|
if (href) {
|
|
baseURI = node.ownerDocument.baseURIObject;
|
|
break;
|
|
}
|
|
}
|
|
node = node.flattenedTreeParentNode;
|
|
}
|
|
|
|
// In case of XLink, we don't return the node we got href from since
|
|
// callers expect <a>-like elements.
|
|
// Note: makeURI() will throw if aUri is not a valid URI.
|
|
return [href ? Services.io.newURI(href, null, baseURI).spec : null, null,
|
|
node && node.ownerDocument.nodePrincipal];
|
|
}
|
|
}
|