зеркало из https://github.com/mozilla/gecko-dev.git
220 строки
6.6 KiB
JavaScript
220 строки
6.6 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 { 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"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"E10SUtils",
|
|
"resource://gre/modules/E10SUtils.jsm"
|
|
);
|
|
|
|
class ClickHandlerChild extends JSWindowActorChild {
|
|
handleEvent(event) {
|
|
if (
|
|
!event.isTrusted ||
|
|
event.defaultPrevented ||
|
|
event.button == 2 ||
|
|
(event.type == "click" && event.button == 1)
|
|
) {
|
|
return;
|
|
}
|
|
// Don't do anything on editable things, we shouldn't open links in
|
|
// contenteditables, and editor needs to possibly handle middlemouse paste
|
|
let composedTarget = event.composedTarget;
|
|
if (
|
|
composedTarget.isContentEditable ||
|
|
(composedTarget.ownerDocument &&
|
|
composedTarget.ownerDocument.designMode == "on") ||
|
|
ChromeUtils.getClassName(composedTarget) == "HTMLInputElement" ||
|
|
ChromeUtils.getClassName(composedTarget) == "HTMLTextAreaElement"
|
|
) {
|
|
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);
|
|
|
|
let csp = ownerDoc.csp;
|
|
if (csp) {
|
|
csp = E10SUtils.serializeCSP(csp);
|
|
}
|
|
|
|
let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(
|
|
Ci.nsIReferrerInfo
|
|
);
|
|
if (node) {
|
|
referrerInfo.initWithElement(node);
|
|
} else {
|
|
referrerInfo.initWithDocument(ownerDoc);
|
|
}
|
|
referrerInfo = E10SUtils.serializeReferrerInfo(referrerInfo);
|
|
let frameID = 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,
|
|
frameID,
|
|
triggeringPrincipal: principal,
|
|
csp,
|
|
referrerInfo,
|
|
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");
|
|
}
|
|
|
|
// 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.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.originStoragePrincipal = ownerDoc.effectiveStoragePrincipal;
|
|
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.sendAsyncMessage("Content:Click", json);
|
|
return;
|
|
}
|
|
|
|
// This might be middle mouse navigation.
|
|
if (event.button == 1) {
|
|
this.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.contentWindow;
|
|
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,
|
|
];
|
|
}
|
|
}
|