Bug 1595155, support click handler which allows modifier+click in out of process iframes, r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D52642

--HG--
rename : browser/modules/ContentClick.jsm => browser/actors/ClickHandlerParent.jsm
extra : moz-landing-system : lando
This commit is contained in:
Neil Deakin 2019-11-14 00:47:48 +00:00
Родитель ebcead8d1a
Коммит 785a88d297
8 изменённых файлов: 125 добавлений и 58 удалений

Просмотреть файл

@ -5,9 +5,6 @@
var EXPORTED_SYMBOLS = ["ClickHandlerChild"];
const { ActorChild } = ChromeUtils.import(
"resource://gre/modules/ActorChild.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(
@ -31,7 +28,7 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/E10SUtils.jsm"
);
class ClickHandlerChild extends ActorChild {
class ClickHandlerChild extends JSWindowActorChild {
handleEvent(event) {
if (
!event.isTrusted ||
@ -122,7 +119,7 @@ class ClickHandlerChild extends ActorChild {
// should we allow mixed content.
json.allowMixedContent = false;
let docshell = ownerDoc.defaultView.docShell;
if (this.mm.docShell.mixedContentChannel) {
if (this.docShell.mixedContentChannel) {
const sm = Services.scriptSecurityManager;
try {
let targetURI = Services.io.newURI(href);
@ -151,13 +148,13 @@ class ClickHandlerChild extends ActorChild {
event.preventMultipleActions();
}
this.mm.sendAsyncMessage("Content:Click", json);
this.sendAsyncMessage("Content:Click", json);
return;
}
// This might be middle mouse navigation.
if (event.button == 1) {
this.mm.sendAsyncMessage("Content:Click", json);
this.sendAsyncMessage("Content:Click", json);
}
}
@ -173,7 +170,7 @@ class ClickHandlerChild extends ActorChild {
* to behave like an <a> element, which SVG links (XLink) don't.
*/
_hrefAndLinkNodeForClickEvent(event) {
let { content } = this.mm;
let content = this.contentWindow;
function isHTMLLink(aNode) {
// Be consistent with what nsContextMenu.js does.
return (

Просмотреть файл

@ -5,7 +5,7 @@
"use strict";
var EXPORTED_SYMBOLS = ["ContentClick"];
var EXPORTED_SYMBOLS = ["ClickHandlerParent"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
@ -25,34 +25,48 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/E10SUtils.jsm"
);
var ContentClick = {
// Listeners are added in BrowserGlue.jsm
let gContentClickListeners = new Set();
class ClickHandlerParent extends JSWindowActorParent {
static addContentClickListener(listener) {
gContentClickListeners.add(listener);
}
static removeContentClickListener(listener) {
gContentClickListeners.delete(listener);
}
receiveMessage(message) {
switch (message.name) {
case "Content:Click":
this.contentAreaClick(message.json, message.target);
this.contentAreaClick(message.data);
this.notifyClickListeners(message.data);
break;
}
},
}
/**
* Handles clicks in the content area.
*
* @param json {Object} JSON object that looks like an Event
* @param data {Object} object that looks like an Event
* @param browser {Element<browser>}
*/
contentAreaClick(json, browser) {
contentAreaClick(data) {
// This is heavily based on contentAreaClick from browser.js (Bug 903016)
// The json is set up in a way to look like an Event.
// The data is set up in a way to look like an Event.
let browser = this.manager.browsingContext.top.embedderElement;
if (browser.outerBrowser) {
browser = browser.outerBrowser; // handle RDM mode
}
let window = browser.ownerGlobal;
if (!json.href) {
if (!data.href) {
// Might be middle mouse navigation.
if (
Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
!Services.prefs.getBoolPref("general.autoScroll")
) {
window.middleMousePaste(json);
window.middleMousePaste(data);
}
return;
}
@ -70,14 +84,14 @@ var ContentClick = {
// visits across frames should be preserved.
try {
if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
PlacesUIUtils.markPageAsFollowedLink(json.href);
PlacesUIUtils.markPageAsFollowedLink(data.href);
}
} catch (ex) {
/* Skip invalid URIs. */
}
// This part is based on handleLinkClick.
var where = window.whereToOpenLink(json);
var where = window.whereToOpenLink(data);
if (where == "current") {
return;
}
@ -86,23 +100,38 @@ var ContentClick = {
let params = {
charset: browser.characterSet,
referrerInfo: E10SUtils.deserializeReferrerInfo(json.referrerInfo),
allowMixedContent: json.allowMixedContent,
isContentWindowPrivate: json.isContentWindowPrivate,
originPrincipal: json.originPrincipal,
originStoragePrincipal: json.originStoragePrincipal,
triggeringPrincipal: json.triggeringPrincipal,
csp: json.csp ? E10SUtils.deserializeCSP(json.csp) : null,
frameOuterWindowID: json.frameOuterWindowID,
referrerInfo: E10SUtils.deserializeReferrerInfo(data.referrerInfo),
allowMixedContent: data.allowMixedContent,
isContentWindowPrivate: data.isContentWindowPrivate,
originPrincipal: data.originPrincipal,
originStoragePrincipal: data.originStoragePrincipal,
triggeringPrincipal: data.triggeringPrincipal,
csp: data.csp ? E10SUtils.deserializeCSP(data.csp) : null,
frameOuterWindowID: data.frameOuterWindowID,
};
// The new tab/window must use the same userContextId.
if (json.originAttributes.userContextId) {
params.userContextId = json.originAttributes.userContextId;
if (data.originAttributes.userContextId) {
params.userContextId = data.originAttributes.userContextId;
}
params.allowInheritPrincipal = true;
window.openLinkIn(json.href, where, params);
},
};
window.openLinkIn(data.href, where, params);
}
notifyClickListeners(data) {
for (let listener of gContentClickListeners) {
try {
let browser = this.browsingContext.top.embedderElement;
if (browser.outerBrowser) {
browser = browser.outerBrowser; // Special-case responsive design mode
}
listener.onContentClick(browser, data);
} catch (ex) {
Cu.reportError(ex);
}
}
}
}

Просмотреть файл

@ -28,6 +28,7 @@ FINAL_TARGET_FILES.actors += [
'BrowserTabChild.jsm',
'BrowserTabParent.jsm',
'ClickHandlerChild.jsm',
'ClickHandlerParent.jsm',
'ContentMetaChild.jsm',
'ContentMetaParent.jsm',
'ContentSearchChild.jsm',

Просмотреть файл

@ -8,7 +8,7 @@
* and confirms those files are on the download list.
*
* The difference between this and the test "browser_contentAreaClick.js" is that
* the code path in e10s uses ContentClick.jsm instead of browser.js::contentAreaClick() util.
* the code path in e10s uses the ClickHandler actor instead of browser.js::contentAreaClick() util.
*/
"use strict";
@ -26,7 +26,8 @@ function setup() {
'<p><a id="commonlink" href="http://mochi.test/moz/">Common link</a></p>' +
'<p><math id="mathlink" xmlns="http://www.w3.org/1998/Math/MathML" href="http://mochi.test/moz/"><mtext>MathML XLink</mtext></math></p>' +
'<p><svg id="svgxlink" xmlns="http://www.w3.org/2000/svg" width="100px" height="50px" version="1.1"><a xlink:type="simple" xlink:href="http://mochi.test/moz/"><text transform="translate(10, 25)">SVG XLink</text></a></svg></p><br>' +
'<span id="host"></span><script>document.getElementById("host").attachShadow({mode: "closed"}).appendChild(document.getElementById("commonlink").cloneNode(true));</script>';
'<span id="host"></span><script>document.getElementById("host").attachShadow({mode: "closed"}).appendChild(document.getElementById("commonlink").cloneNode(true));</script>' +
'<iframe id="frame" src="https://test2.example.com:443/browser/browser/base/content/test/general/file_with_link_to_http.html"></iframe>';
return BrowserTestUtils.openNewForegroundTab(gBrowser, testPage);
}
@ -165,3 +166,41 @@ add_task(async function test_alt_click_on_xlinks() {
await clean_up();
});
// Alt+Click a link in a frame from another domain as the outer document.
add_task(async function test_alt_click_in_frame() {
await setup();
let downloadList = await Downloads.getList(Downloads.ALL);
let downloads = [];
let downloadView;
// When the download has been attempted, resolve the promise.
let finishedAllDownloads = new Promise(resolve => {
downloadView = {
onDownloadAdded(aDownload) {
downloads.push(aDownload);
resolve();
},
};
});
await downloadList.addView(downloadView);
await BrowserTestUtils.synthesizeMouseAtCenter(
"#linkToExample",
{ altKey: true },
gBrowser.selectedBrowser.browsingContext.getChildren()[0]
);
// Wait for all downloads to be added to the download list.
await finishedAllDownloads;
await downloadList.removeView(downloadView);
is(downloads.length, 1, "1 downloads");
is(
downloads[0].source.url,
"http://example.org/",
"Downloaded link in iframe."
);
await clean_up();
});

Просмотреть файл

@ -57,6 +57,21 @@ let ACTORS = {
},
},
ClickHandler: {
parent: {
moduleURI: "resource:///actors/ClickHandlerParent.jsm",
},
child: {
moduleURI: "resource:///actors/ClickHandlerChild.jsm",
events: {
click: { capture: true, mozSystemGroup: true },
auxclick: { capture: true, mozSystemGroup: true },
},
},
allFrames: true,
},
// Collects description and icon information from meta tags.
ContentMeta: {
parent: {
@ -313,16 +328,6 @@ let LEGACY_ACTORS = {
},
},
ClickHandler: {
child: {
module: "resource:///actors/ClickHandlerChild.jsm",
events: {
click: { capture: true, mozSystemGroup: true },
auxclick: { capture: true, mozSystemGroup: true },
},
},
},
ContentSearch: {
child: {
module: "resource:///actors/ContentSearchChild.jsm",
@ -567,7 +572,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
XPCOMUtils.defineLazyModuleGetters(this, {
AboutLoginsParent: "resource:///modules/AboutLoginsParent.jsm",
AsyncPrefs: "resource://gre/modules/AsyncPrefs.jsm",
ContentClick: "resource:///modules/ContentClick.jsm",
PluginManager: "resource:///actors/PluginParent.jsm",
ReaderParent: "resource:///modules/ReaderParent.jsm",
});
@ -666,7 +670,6 @@ const listeners = {
"AboutLogins:SyncEnable": ["AboutLoginsParent"],
"AboutLogins:SyncOptions": ["AboutLoginsParent"],
"AboutLogins:UpdateLogin": ["AboutLoginsParent"],
"Content:Click": ["ContentClick"],
ContentSearch: ["ContentSearch"],
"Reader:FaviconRequest": ["ReaderParent"],
"Reader:UpdateReaderButton": ["ReaderParent"],

Просмотреть файл

@ -136,7 +136,6 @@ EXTRA_JS_MODULES += [
'AsyncTabSwitcher.jsm',
'BrowserUsageTelemetry.jsm',
'BrowserWindowTracker.jsm',
'ContentClick.jsm',
'ContentCrashHandlers.jsm',
'ContentObservers.js',
'ContentSearch.jsm',

Просмотреть файл

@ -47,10 +47,6 @@ const SWAPPED_BROWSER_STATE = [
const PROPERTIES_FROM_BROWSER_WINDOW = [
// This is used by PermissionUI.jsm for permission doorhangers.
"PopupNotifications",
// These are used by ContentClick.jsm when opening links in ways other than just
// navigating the viewport, such as a new tab by pressing Cmd-Click.
"whereToOpenLink",
"openLinkIn",
// This is used by various event handlers, typically to call `getTabForBrowser` to map
// a browser back to a tab.
"gBrowser",

Просмотреть файл

@ -23,6 +23,11 @@ ChromeUtils.defineModuleGetter(
"UrlbarUtils",
"resource:///modules/UrlbarUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"ClickHandlerParent",
"resource:///actors/ClickHandlerParent.jsm"
);
// Maximum amount of time that can be passed and still consider
// the data recent (similar to how is done in nsNavHistory,
@ -47,7 +52,8 @@ var Manager = {
Services.obs.addObserver(this, "webNavigation-createdNavigationTarget");
Services.mm.addMessageListener("Content:Click", this);
ClickHandlerParent.addContentClickListener(this);
Services.mm.addMessageListener("Extension:DOMContentLoaded", this);
Services.mm.addMessageListener("Extension:StateChange", this);
Services.mm.addMessageListener("Extension:DocumentChange", this);
@ -65,7 +71,8 @@ var Manager = {
Services.obs.removeObserver(this, "urlbar-user-start-navigation");
Services.obs.removeObserver(this, "webNavigation-createdNavigationTarget");
Services.mm.removeMessageListener("Content:Click", this);
ClickHandlerParent.removeContentClickListener(this);
Services.mm.removeMessageListener("Extension:StateChange", this);
Services.mm.removeMessageListener("Extension:DocumentChange", this);
Services.mm.removeMessageListener("Extension:HistoryChange", this);
@ -305,10 +312,6 @@ var Manager = {
this.onLoad(target, data);
break;
case "Content:Click":
this.onContentClick(target, data);
break;
case "Extension:CreatedNavigationTarget":
this.onCreatedNavigationTarget(target, data);
break;