Backed out 6 changesets (bug 1505909) for failures in browser_ext_webNavigation_onCreatedNavigationTarget_contextmenu.js CLOSED TREE

Backed out changeset 57336967a6c7 (bug 1505909)
Backed out changeset 8adcacadd689 (bug 1505909)
Backed out changeset bcca6bb913ef (bug 1505909)
Backed out changeset afc11a5ebb6d (bug 1505909)
Backed out changeset 40f0a56ed3af (bug 1505909)
Backed out changeset 3e31f9726798 (bug 1505909)
This commit is contained in:
Noemi Erli 2019-06-07 19:19:14 +03:00
Родитель ea44e068e2
Коммит ea35f4b13b
26 изменённых файлов: 534 добавлений и 758 удалений

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

@ -11,6 +11,8 @@ var EXPORTED_SYMBOLS = ["ContextMenuChild"];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
XPCOMUtils.defineLazyModuleGetters(this, {
@ -22,7 +24,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
InlineSpellCheckerContent: "resource://gre/modules/InlineSpellCheckerContent.jsm",
ContentDOMReference: "resource://gre/modules/ContentDOMReference.jsm",
});
XPCOMUtils.defineLazyGetter(this, "PageMenuChild", () => {
@ -31,207 +32,206 @@ XPCOMUtils.defineLazyGetter(this, "PageMenuChild", () => {
return new tmp.PageMenuChild();
});
const messageListeners = {
"ContextMenu:BookmarkFrame": function(aMessage) {
let frame = this.getTarget(aMessage).ownerDocument;
this.mm.sendAsyncMessage("ContextMenu:BookmarkFrame:Result",
{ title: frame.title });
},
"ContextMenu:Canvas:ToBlobURL": function(aMessage) {
this.getTarget(aMessage).toBlob((blob) => {
let blobURL = URL.createObjectURL(blob);
this.mm.sendAsyncMessage("ContextMenu:Canvas:ToBlobURL:Result", { blobURL });
});
},
"ContextMenu:DoCustomCommand": function(aMessage) {
E10SUtils.wrapHandlingUserInput(
this.content,
aMessage.data.handlingUserInput,
() => PageMenuChild.executeMenu(aMessage.data.generatedItemId)
);
},
"ContextMenu:Hiding": function() {
this.context = null;
this.target = null;
},
"ContextMenu:MediaCommand": function(aMessage) {
E10SUtils.wrapHandlingUserInput(
this.content, aMessage.data.handlingUserInput, () => {
let media = this.getTarget(aMessage, "element");
switch (aMessage.data.command) {
case "play":
media.play();
break;
case "pause":
media.pause();
break;
case "loop":
media.loop = !media.loop;
break;
case "mute":
media.muted = true;
break;
case "unmute":
media.muted = false;
break;
case "playbackRate":
media.playbackRate = aMessage.data.data;
break;
case "hidecontrols":
media.removeAttribute("controls");
break;
case "showcontrols":
media.setAttribute("controls", "true");
break;
case "fullscreen":
if (this.content.document.fullscreenEnabled) {
media.requestFullscreen();
}
break;
case "pictureinpicture":
let event = new this.content.CustomEvent("MozTogglePictureInPicture", {
bubbles: true,
}, this.content);
media.dispatchEvent(event);
break;
}
}
);
},
"ContextMenu:ReloadFrame": function(aMessage) {
let forceReload = aMessage.objects && aMessage.objects.forceReload;
this.getTarget(aMessage).ownerDocument.location.reload(forceReload);
},
"ContextMenu:ReloadImage": function(aMessage) {
let image = this.getTarget(aMessage);
if (image instanceof Ci.nsIImageLoadingContent) {
image.forceReload();
}
},
"ContextMenu:SearchFieldBookmarkData": function(aMessage) {
let node = this.getTarget(aMessage);
let charset = node.ownerDocument.characterSet;
let formBaseURI = Services.io.newURI(node.form.baseURI, charset);
let formURI = Services.io.newURI(node.form.getAttribute("action"),
charset, formBaseURI);
let spec = formURI.spec;
let isURLEncoded = (node.form.method.toUpperCase() == "POST" &&
(node.form.enctype == "application/x-www-form-urlencoded" ||
node.form.enctype == ""));
let title = node.ownerDocument.title;
function escapeNameValuePair([aName, aValue]) {
if (isURLEncoded) {
return escape(aName + "=" + aValue);
}
return escape(aName) + "=" + escape(aValue);
}
let formData = new this.content.FormData(node.form);
formData.delete(node.name);
formData = Array.from(formData).map(escapeNameValuePair);
formData.push(escape(node.name) + (isURLEncoded ? escape("=%s") : "=%s"));
let postData;
if (isURLEncoded) {
postData = formData.join("&");
} else {
let separator = spec.includes("?") ? "&" : "?";
spec += separator + formData.join("&");
}
this.mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData:Result",
{ spec, title, postData, charset });
},
"ContextMenu:SaveVideoFrameAsImage": function(aMessage) {
let video = this.getTarget(aMessage);
let canvas = this.content.document.createElementNS("http://www.w3.org/1999/xhtml",
"canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
let ctxDraw = canvas.getContext("2d");
ctxDraw.drawImage(video, 0, 0);
this.mm.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage:Result", {
dataURL: canvas.toDataURL("image/jpeg", ""),
});
},
"ContextMenu:SetAsDesktopBackground": function(aMessage) {
let target = this.getTarget(aMessage);
// Paranoia: check disableSetDesktopBackground again, in case the
// image changed since the context menu was initiated.
let disable = this._disableSetDesktopBackground(target);
if (!disable) {
try {
BrowserUtils.urlSecurityCheck(target.currentURI.spec,
target.ownerDocument.nodePrincipal);
let canvas = this.content.document.createElement("canvas");
canvas.width = target.naturalWidth;
canvas.height = target.naturalHeight;
let ctx = canvas.getContext("2d");
ctx.drawImage(target, 0, 0);
let dataUrl = canvas.toDataURL();
let url = (new URL(target.ownerDocument.location.href)).pathname;
let imageName = url.substr(url.lastIndexOf("/") + 1);
this.mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
{ dataUrl, imageName });
} catch (e) {
Cu.reportError(e);
disable = true;
}
}
if (disable) {
this.mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
{ disable });
}
},
};
let contextMenus = new WeakMap();
class ContextMenuChild extends JSWindowActorChild {
class ContextMenuChild extends ActorChild {
// PUBLIC
constructor() {
super();
constructor(dispatcher) {
super(dispatcher);
contextMenus.set(this.mm, this);
this.target = null;
this.context = null;
this.lastMenuTarget = null;
Object.keys(messageListeners).forEach(key =>
this.mm.addMessageListener(key, messageListeners[key].bind(this))
);
}
static getTarget(browsingContext, message, key) {
let actor = contextMenus.get(browsingContext);
if (!actor) {
throw new Error("Can't find ContextMenu actor for browsing context with " +
"ID: " + browsingContext.id);
}
return actor.getTarget(message, key);
static getTarget(mm, message, key) {
return contextMenus.get(mm).getTarget(message, key);
}
static getLastTarget(browsingContext) {
let contextMenu = contextMenus.get(browsingContext);
static getLastTarget(mm) {
let contextMenu = contextMenus.get(mm);
return contextMenu && contextMenu.lastMenuTarget;
}
receiveMessage(message) {
switch (message.name) {
case "ContextMenu:GetFrameTitle": {
let target = ContentDOMReference.resolve(message.data.targetIdentifier);
return Promise.resolve(target.ownerDocument.title);
}
case "ContextMenu:Canvas:ToBlobURL": {
let target = ContentDOMReference.resolve(message.data.targetIdentifier);
return new Promise(resolve => {
target.toBlob(blob => {
let blobURL = URL.createObjectURL(blob);
resolve(blobURL);
});
});
}
case "ContextMenu:DoCustomCommand": {
E10SUtils.wrapHandlingUserInput(
this.contentWindow,
message.data.handlingUserInput,
() => PageMenuChild.executeMenu(message.data.generatedItemId)
);
break;
}
case "ContextMenu:Hiding": {
this.context = null;
this.target = null;
break;
}
case "ContextMenu:MediaCommand": {
E10SUtils.wrapHandlingUserInput(
this.contentWindow, message.data.handlingUserInput, () => {
let media = ContentDOMReference.resolve(message.data.targetIdentifier);
switch (message.data.command) {
case "play":
media.play();
break;
case "pause":
media.pause();
break;
case "loop":
media.loop = !media.loop;
break;
case "mute":
media.muted = true;
break;
case "unmute":
media.muted = false;
break;
case "playbackRate":
media.playbackRate = message.data.data;
break;
case "hidecontrols":
media.removeAttribute("controls");
break;
case "showcontrols":
media.setAttribute("controls", "true");
break;
case "fullscreen":
if (this.document.fullscreenEnabled) {
media.requestFullscreen();
}
break;
case "pictureinpicture":
let event = new this.contentWindow.CustomEvent("MozTogglePictureInPicture", {
bubbles: true,
}, this.contentWindow);
media.dispatchEvent(event);
break;
}
}
);
break;
}
case "ContextMenu:ReloadFrame": {
let target = ContentDOMReference.resolve(message.data.targetIdentifier);
target.ownerDocument.location.reload(message.data.forceReload);
break;
}
case "ContextMenu:ReloadImage": {
let image = ContentDOMReference.resolve(message.data.targetIdentifier);
if (image instanceof Ci.nsIImageLoadingContent) {
image.forceReload();
}
break;
}
case "ContextMenu:SearchFieldBookmarkData": {
let node = ContentDOMReference.resolve(message.data.targetIdentifier);
let charset = node.ownerDocument.characterSet;
let formBaseURI = Services.io.newURI(node.form.baseURI, charset);
let formURI = Services.io.newURI(node.form.getAttribute("action"),
charset, formBaseURI);
let spec = formURI.spec;
let isURLEncoded = (node.form.method.toUpperCase() == "POST" &&
(node.form.enctype == "application/x-www-form-urlencoded" ||
node.form.enctype == ""));
let title = node.ownerDocument.title;
function escapeNameValuePair([aName, aValue]) {
if (isURLEncoded) {
return escape(aName + "=" + aValue);
}
return escape(aName) + "=" + escape(aValue);
}
let formData = new this.contentWindow.FormData(node.form);
formData.delete(node.name);
formData = Array.from(formData).map(escapeNameValuePair);
formData.push(escape(node.name) + (isURLEncoded ? escape("=%s") : "=%s"));
let postData;
if (isURLEncoded) {
postData = formData.join("&");
} else {
let separator = spec.includes("?") ? "&" : "?";
spec += separator + formData.join("&");
}
return Promise.resolve({ spec, title, postData, charset });
}
case "ContextMenu:SaveVideoFrameAsImage": {
let video = ContentDOMReference.resolve(message.data.targetIdentifier);
let canvas = this.document.createElementNS("http://www.w3.org/1999/xhtml",
"canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
let ctxDraw = canvas.getContext("2d");
ctxDraw.drawImage(video, 0, 0);
return Promise.resolve(canvas.toDataURL("image/jpeg", ""));
}
case "ContextMenu:SetAsDesktopBackground": {
let target = ContentDOMReference.resolve(message.data.targetIdentifier);
// Paranoia: check disableSetDesktopBackground again, in case the
// image changed since the context menu was initiated.
let disable = this._disableSetDesktopBackground(target);
if (!disable) {
try {
BrowserUtils.urlSecurityCheck(target.currentURI.spec,
target.ownerDocument.nodePrincipal);
let canvas = this.document.createElement("canvas");
canvas.width = target.naturalWidth;
canvas.height = target.naturalHeight;
let ctx = canvas.getContext("2d");
ctx.drawImage(target, 0, 0);
let dataURL = canvas.toDataURL();
let url = (new URL(target.ownerDocument.location.href)).pathname;
let imageName = url.substr(url.lastIndexOf("/") + 1);
return Promise.resolve({ failed: false, dataURL, imageName });
} catch (e) {
Cu.reportError(e);
}
}
return Promise.resolve({ failed: true, dataURL: null, imageName: null });
}
}
return undefined;
}
/**
* Returns the event target of the context menu, using a locally stored
* reference if possible. If not, and aMessage.objects is defined,
@ -334,7 +334,7 @@ class ContextMenuChild extends JSWindowActorChild {
if (node.nodeType == node.TEXT_NODE) {
// Add this text to our collection.
text += " " + node.data;
} else if (node instanceof this.contentWindow.HTMLImageElement) {
} else if (node instanceof this.content.HTMLImageElement) {
// If it has an "alt" attribute, add that.
let altText = node.getAttribute( "alt" );
if ( altText && altText != "" ) {
@ -399,11 +399,11 @@ class ContextMenuChild extends JSWindowActorChild {
}
_isTargetATextBox(node) {
if (node instanceof this.contentWindow.HTMLInputElement) {
if (node instanceof this.content.HTMLInputElement) {
return node.mozIsTextField(false);
}
return (node instanceof this.contentWindow.HTMLTextAreaElement);
return (node instanceof this.content.HTMLTextAreaElement);
}
_isSpellCheckEnabled(aNode) {
@ -452,8 +452,6 @@ class ContextMenuChild extends JSWindowActorChild {
}
handleEvent(aEvent) {
contextMenus.set(this.browsingContext, this);
let defaultPrevented = aEvent.defaultPrevented;
if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
@ -488,7 +486,7 @@ class ContextMenuChild extends JSWindowActorChild {
// The same-origin check will be done in nsContextMenu.openLinkInTab.
let parentAllowsMixedContent = !!this.docShell.mixedContentChannel;
let disableSetDesktopBackground = null;
let disableSetDesktopBg = null;
// Media related cache info parent needs for saving
let contentType = null;
@ -496,8 +494,7 @@ class ContextMenuChild extends JSWindowActorChild {
if (aEvent.composedTarget.nodeType == aEvent.composedTarget.ELEMENT_NODE &&
aEvent.composedTarget instanceof Ci.nsIImageLoadingContent &&
aEvent.composedTarget.currentURI) {
disableSetDesktopBackground =
this._disableSetDesktopBackground(aEvent.composedTarget);
disableSetDesktopBg = this._disableSetDesktopBackground(aEvent.composedTarget);
try {
let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
@ -516,7 +513,7 @@ class ContextMenuChild extends JSWindowActorChild {
} catch (e) {}
}
let selectionInfo = BrowserUtils.getSelectionDetails(this.contentWindow);
let selectionInfo = BrowserUtils.getSelectionDetails(this.content);
let loadContext = this.docShell.QueryInterface(Ci.nsILoadContext);
let userContextId = loadContext.originAttributes.userContextId;
let popupNodeSelectors = findAllCssSelectors(aEvent.composedTarget);
@ -532,32 +529,37 @@ class ContextMenuChild extends JSWindowActorChild {
let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(Ci.nsIReferrerInfo);
referrerInfo.initWithNode(context.onLink ? context.link : aEvent.composedTarget);
referrerInfo = E10SUtils.serializeReferrerInfo(referrerInfo);
let target = context.target;
if (target) {
let targetAsCPOW = context.target;
if (targetAsCPOW) {
this._cleanContext();
}
editFlags = SpellCheckHelper.isEditable(aEvent.composedTarget, this.contentWindow);
let isRemote = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
if (editFlags & SpellCheckHelper.SPELLCHECKABLE) {
spellInfo = InlineSpellCheckerContent.initContextMenu(aEvent, editFlags, this);
if (isRemote) {
editFlags = SpellCheckHelper.isEditable(aEvent.composedTarget, this.content);
if (editFlags & SpellCheckHelper.SPELLCHECKABLE) {
spellInfo = InlineSpellCheckerContent.initContextMenu(aEvent, editFlags, this.mm);
}
// Set the event target first as the copy image command needs it to
// determine what was context-clicked on. Then, update the state of the
// commands on the context menu.
this.docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
.setCommandNode(aEvent.composedTarget);
aEvent.composedTarget.ownerGlobal.updateCommands("contentcontextmenu");
customMenuItems = PageMenuChild.build(aEvent.composedTarget);
principal = doc.nodePrincipal;
}
// Set the event target first as the copy image command needs it to
// determine what was context-clicked on. Then, update the state of the
// commands on the context menu.
this.docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
.setCommandNode(aEvent.composedTarget);
aEvent.composedTarget.ownerGlobal.updateCommands("contentcontextmenu");
principal = doc.nodePrincipal;
let data = {
context,
charSet,
baseURI,
isRemote,
referrerInfo,
editFlags,
principal,
@ -571,7 +573,7 @@ class ContextMenuChild extends JSWindowActorChild {
contentDisposition,
frameOuterWindowID,
popupNodeSelectors,
disableSetDesktopBackground,
disableSetDesktopBg,
parentAllowsMixedContent,
};
@ -579,20 +581,28 @@ class ContextMenuChild extends JSWindowActorChild {
data.frameReferrerInfo = doc.referrerInfo;
}
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
data.customMenuItems = PageMenuChild.build(aEvent.composedTarget);
}
Services.obs.notifyObservers({wrappedJSObject: data}, "on-prepare-contextmenu");
// In the event that the content is running in the parent process, we don't
// actually want the contextmenu events to reach the parent - we'll dispatch
// a new contextmenu event after the async message has reached the parent
// instead.
aEvent.preventDefault();
aEvent.stopPropagation();
if (isRemote) {
data.referrerInfo = E10SUtils.serializeReferrerInfo(data.referrerInfo);
if (data.frameReferrerInfo) {
data.frameReferrerInfo = E10SUtils.serializeReferrerInfo(data.frameReferrerInfo);
}
this.sendAsyncMessage("contextmenu", data);
this.mm.sendAsyncMessage("contextmenu", data, {
targetAsCPOW,
});
} else {
let browser = this.docShell.chromeEventHandler;
let mainWin = browser.ownerGlobal;
data.documentURIObject = doc.documentURIObject;
data.disableSetDesktopBackground = data.disableSetDesktopBg;
delete data.disableSetDesktopBg;
data.context.targetAsCPOW = targetAsCPOW;
mainWin.setContextMenuContentData(data);
}
}
/**
@ -668,13 +678,13 @@ class ContextMenuChild extends JSWindowActorChild {
// Set the node to containing <video>/<audio>/<embed>/<object> if the node
// is in the videocontrols/pluginProblem UA Widget.
if (this.contentWindow.ShadowRoot) {
if (this.content.ShadowRoot) {
let n = node;
while (n) {
if (n instanceof this.contentWindow.ShadowRoot) {
if (n.host instanceof this.contentWindow.HTMLMediaElement ||
n.host instanceof this.contentWindow.HTMLEmbedElement ||
n.host instanceof this.contentWindow.HTMLObjectElement) {
if (n instanceof this.content.ShadowRoot) {
if (n.host instanceof this.content.HTMLMediaElement ||
n.host instanceof this.content.HTMLEmbedElement ||
n.host instanceof this.content.HTMLObjectElement) {
node = n.host;
break;
}
@ -696,8 +706,8 @@ class ContextMenuChild extends JSWindowActorChild {
}
const isAboutDevtoolsToolbox =
this.document.documentURI.startsWith("about:devtools-toolbox");
const editFlags = SpellCheckHelper.isEditable(node, this.contentWindow);
this.content.document.documentURI.startsWith("about:devtools-toolbox");
const editFlags = SpellCheckHelper.isEditable(node, this.content);
if (isAboutDevtoolsToolbox && (editFlags & SpellCheckHelper.TEXTINPUT) === 0) {
// Don't display for about:devtools-toolbox page unless the source was text input.
@ -753,7 +763,6 @@ class ContextMenuChild extends JSWindowActorChild {
// Remember the node and its owner document that was clicked
// This may be modifed before sending to nsContextMenu
context.target = node;
context.targetIdentifier = ContentDOMReference.get(node);
context.principal = context.target.ownerDocument.nodePrincipal;
context.csp = E10SUtils.serializeCSP(context.target.ownerDocument.csp);
@ -847,9 +856,9 @@ class ContextMenuChild extends JSWindowActorChild {
context.imageDescURL = this._makeURLAbsolute(context.target.ownerDocument.body.baseURI,
descURL);
}
} else if (context.target instanceof this.contentWindow.HTMLCanvasElement) {
} else if (context.target instanceof this.content.HTMLCanvasElement) {
context.onCanvas = true;
} else if (context.target instanceof this.contentWindow.HTMLVideoElement) {
} else if (context.target instanceof this.content.HTMLVideoElement) {
const mediaURL = context.target.currentSrc || context.target.src;
if (this._isMediaURLReusable(mediaURL)) {
@ -873,7 +882,7 @@ class ContextMenuChild extends JSWindowActorChild {
} else {
context.onVideo = true;
}
} else if (context.target instanceof this.contentWindow.HTMLAudioElement) {
} else if (context.target instanceof this.content.HTMLAudioElement) {
context.onAudio = true;
const mediaURL = context.target.currentSrc || context.target.src;
@ -898,7 +907,7 @@ class ContextMenuChild extends JSWindowActorChild {
}
context.onKeywordField = (editFlags & SpellCheckHelper.KEYWORD);
} else if (context.target instanceof this.contentWindow.HTMLHtmlElement) {
} else if (context.target instanceof this.content.HTMLHtmlElement) {
const bodyElt = context.target.ownerDocument.body;
if (bodyElt) {
@ -917,10 +926,10 @@ class ContextMenuChild extends JSWindowActorChild {
computedURL);
}
}
} else if ((context.target instanceof this.contentWindow.HTMLEmbedElement ||
context.target instanceof this.contentWindow.HTMLObjectElement) &&
context.target.displayedType == this.contentWindow.HTMLObjectElement.TYPE_NULL &&
context.target.pluginFallbackType == this.contentWindow.HTMLObjectElement.PLUGIN_CLICK_TO_PLAY) {
} else if ((context.target instanceof this.content.HTMLEmbedElement ||
context.target instanceof this.content.HTMLObjectElement) &&
context.target.displayedType == this.content.HTMLObjectElement.TYPE_NULL &&
context.target.pluginFallbackType == this.content.HTMLObjectElement.PLUGIN_CLICK_TO_PLAY) {
context.onCTPPlugin = true;
}
@ -949,11 +958,11 @@ class ContextMenuChild extends JSWindowActorChild {
// Be consistent with what hrefAndLinkNodeForClickEvent
// does in browser.js
(this._isXULTextLinkLabel(elem) ||
(elem instanceof this.contentWindow.HTMLAnchorElement && elem.href) ||
(elem instanceof this.contentWindow.SVGAElement &&
(elem instanceof this.content.HTMLAnchorElement && elem.href) ||
(elem instanceof this.content.SVGAElement &&
(elem.href || elem.hasAttributeNS(XLINK_NS, "href"))) ||
(elem instanceof this.contentWindow.HTMLAreaElement && elem.href) ||
elem instanceof this.contentWindow.HTMLLinkElement ||
(elem instanceof this.content.HTMLAreaElement && elem.href) ||
elem instanceof this.content.HTMLLinkElement ||
elem.getAttributeNS(XLINK_NS, "type") == "simple")) {
// Target is a link or a descendant of a link.
context.onLink = true;

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

@ -1,84 +0,0 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["ContextMenuParent"];
class ContextMenuParent extends JSWindowActorParent {
receiveMessage(message) {
let browser = this.manager.rootFrameLoader.ownerElement;
let win = browser.ownerGlobal;
// It's possible that the <xul:browser> associated with this
// ContextMenu message doesn't belong to a window that actually
// loads nsContextMenu.js. In that case, try to find the chromeEventHandler,
// since that'll likely be the "top" <xul:browser>, and then use its window's
// nsContextMenu instance instead.
if (!win.openContextMenu) {
let topBrowser = browser.ownerGlobal.docShell.chromeEventHandler;
win = topBrowser.ownerGlobal;
}
win.openContextMenu(message, browser, this);
}
hiding() {
this.sendAsyncMessage("ContextMenu:Hiding", {});
}
reloadFrame(targetIdentifier, forceReload) {
this.sendAsyncMessage("ContextMenu:ReloadFrame", {
targetIdentifier,
forceReload,
});
}
reloadImage(targetIdentifier) {
this.sendAsyncMessage("ContextMenu:ReloadImage", { targetIdentifier });
}
getFrameTitle(targetIdentifier) {
return this.sendQuery("ContextMenu:GetFrameTitle", { targetIdentifier });
}
mediaCommand(targetIdentifier, command, data) {
let windowGlobal = this.manager.browsingContext.currentWindowGlobal;
let browser = windowGlobal.rootFrameLoader.ownerElement;
let win = browser.ownerGlobal;
let windowUtils = win.windowUtils;
this.sendAsyncMessage("ContextMenu:MediaCommand", {
targetIdentifier,
command,
data,
handlingUserInput: windowUtils.isHandlingUserInput,
});
}
canvasToBlobURL(targetIdentifier) {
return this.sendQuery("ContextMenu:Canvas:ToBlobURL",
{ targetIdentifier });
}
saveVideoFrameAsImage(targetIdentifier) {
return this.sendQuery("ContextMenu:SaveVideoFrameAsImage",
{ targetIdentifier });
}
setAsDesktopBackground(targetIdentifier) {
return this.sendQuery("ContextMenu:SetAsDesktopBackground",
{ targetIdentifier });
}
getSearchFieldBookmarkData(targetIdentifier) {
return this.sendQuery("ContextMenu:SearchFieldBookmarkData",
{ targetIdentifier });
}
doCustomCommand(generatedItemId, handlingUserInput) {
this.sendAsyncMessage("ContextMenu:DoCustomCommand", {
generatedItemId,
handlingUserInput,
});
}
}

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

@ -1,25 +0,0 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["ContextMenuSpecialProcessChild"];
const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {E10SUtils} = ChromeUtils.import("resource://gre/modules/E10SUtils.jsm");
/**
* This module is a workaround for bug 1555154, where the contextmenu event doesn't
* cause the JS Window Actor Child to be constructed automatically in the parent
* process or extension process documents.
*/
class ContextMenuSpecialProcessChild extends ActorChild {
handleEvent(event) {
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT ||
Services.appinfo.remoteType == E10SUtils.EXTENSION_REMOTE_TYPE) {
this.content.getWindowGlobalChild().getActor("ContextMenu").handleEvent(event);
}
}
}

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

@ -53,10 +53,10 @@ class PluginChild extends ActorChild {
case "BrowserPlugins:ContextMenuCommand":
switch (msg.data.command) {
case "play":
this._showClickToPlayNotification(ContextMenuChild.getTarget(this.docShell.browsingContext, msg, "plugin"), true);
this._showClickToPlayNotification(ContextMenuChild.getTarget(this.mm, msg, "plugin"), true);
break;
case "hide":
this.hideClickToPlayOverlay(ContextMenuChild.getTarget(this.docShell.browsingContext, msg, "plugin"));
this.hideClickToPlayOverlay(ContextMenuChild.getTarget(this.mm, msg, "plugin"));
break;
}
break;

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

@ -29,8 +29,6 @@ FINAL_TARGET_FILES.actors += [
'ClickHandlerChild.jsm',
'ContentSearchChild.jsm',
'ContextMenuChild.jsm',
'ContextMenuParent.jsm',
'ContextMenuSpecialProcessChild.jsm',
'DOMFullscreenChild.jsm',
'FormValidationChild.jsm',
'LightweightThemeChild.jsm',

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

@ -124,7 +124,8 @@ XPCOMUtils.defineLazyScriptGetter(this, "gSync",
"chrome://browser/content/browser-sync.js");
XPCOMUtils.defineLazyScriptGetter(this, "gBrowserThumbnails",
"chrome://browser/content/browser-thumbnails.js");
XPCOMUtils.defineLazyScriptGetter(this, ["openContextMenu", "nsContextMenu"],
XPCOMUtils.defineLazyScriptGetter(this, ["setContextMenuContentData",
"openContextMenu", "nsContextMenu"],
"chrome://browser/content/nsContextMenu.js");
XPCOMUtils.defineLazyScriptGetter(this, ["DownloadsPanel",
"DownloadsOverlayLoader",
@ -7628,11 +7629,27 @@ function BrowserOpenAddonsMgr(aView) {
}
function AddKeywordForSearchField() {
if (!gContextMenu) {
throw new Error("Context menu doesn't seem to be open.");
}
let mm = gBrowser.selectedBrowser.messageManager;
gContextMenu.addKeywordForSearchField();
let onMessage = (message) => {
mm.removeMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
let bookmarkData = message.data;
let title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
[bookmarkData.title]);
PlacesUIUtils.showBookmarkDialog({ action: "add",
type: "bookmark",
uri: makeURI(bookmarkData.spec),
title,
keyword: "",
postData: bookmarkData.postData,
charSet: bookmarkData.charset,
hiddenRows: [ "location", "tags" ],
}, window);
};
mm.addMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData", {}, { target: gContextMenu.target });
}
/**

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

@ -18,6 +18,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
ContentMetaHandler: "resource:///modules/ContentMetaHandler.jsm",
LoginFormFactory: "resource://gre/modules/LoginFormFactory.jsm",
InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.jsm",
ContextMenuChild: "resource:///actors/ContextMenuChild.jsm",
});
XPCOMUtils.defineLazyGetter(this, "LoginManagerContent", () => {
@ -30,6 +31,7 @@ XPCOMUtils.defineLazyGetter(this, "LoginManagerContent", () => {
// NOTE: Much of this logic is duplicated in BrowserCLH.js for Android.
addMessageListener("PasswordManager:fillForm", function(message) {
// intercept if ContextMenu.jsm had sent a plain object for remote targets
message.objects.inputElement = ContextMenuChild.getTarget(global, message, "inputElement");
LoginManagerContent.receiveMessage(message, content);
});

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

@ -27,15 +27,25 @@ XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () =>
var gContextMenuContentData = null;
function openContextMenu(aMessage, aBrowser, aActor) {
function setContextMenuContentData(data) {
gContextMenuContentData = data;
}
function openContextMenu(aMessage) {
let data = aMessage.data;
let browser = aBrowser;
let actor = aActor;
let browser = aMessage.target;
let spellInfo = data.spellInfo;
let frameReferrerInfo = data.frameReferrerInfo;
// ContextMenu.jsm sends us the target as a CPOW only so that
// we can send that CPOW back down to the content process and
// have it resolve to a DOM node. The parent should not attempt
// to access any properties on this CPOW (in fact, doing so
// will throw an exception).
data.context.targetAsCPOW = aMessage.objects.targetAsCPOW;
if (spellInfo) {
spellInfo.target = browser.messageManager;
spellInfo.target = aMessage.target.messageManager;
}
let documentURIObject = makeURI(data.docLocation,
@ -48,9 +58,9 @@ function openContextMenu(aMessage, aBrowser, aActor) {
}
gContextMenuContentData = { context: data.context,
isRemote: data.isRemote,
popupNodeSelectors: data.popupNodeSelectors,
browser,
actor,
editFlags: data.editFlags,
spellInfo,
principal: data.principal,
@ -64,7 +74,7 @@ function openContextMenu(aMessage, aBrowser, aActor) {
contentDisposition: data.contentDisposition,
frameOuterWindowID: data.frameOuterWindowID,
selectionInfo: data.selectionInfo,
disableSetDesktopBackground: data.disableSetDesktopBackground,
disableSetDesktopBackground: data.disableSetDesktopBg,
loginFillInfo: data.loginFillInfo,
parentAllowsMixedContent: data.parentAllowsMixedContent,
userContextId: data.userContextId,
@ -80,6 +90,7 @@ function openContextMenu(aMessage, aBrowser, aActor) {
var newEvent = document.createEvent("MouseEvent");
newEvent.initNSMouseEvent("contextmenu", true, true, null, 0, context.screenX, context.screenY,
0, 0, false, false, false, false, 0, null, 0, context.mozInputSource);
popup.openPopupAtScreen(newEvent.screenX, newEvent.screenY, true, newEvent);
}
@ -100,9 +111,13 @@ nsContextMenu.prototype = {
this.hasPageMenu = false;
this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
if (!aIsShift) {
this.hasPageMenu =
PageMenuParent.addToPopup(gContextMenuContentData.customMenuItems,
this.browser, aXulMenu);
if (this.isRemote) {
this.hasPageMenu =
PageMenuParent.addToPopup(gContextMenuContentData.customMenuItems,
this.browser, aXulMenu);
} else {
this.hasPageMenu = PageMenuParent.buildAndAddToPopup(this.target, aXulMenu);
}
let tab = gBrowser && gBrowser.getTabForBrowser ?
gBrowser.getTabForBrowser(this.browser) : undefined;
@ -160,10 +175,12 @@ nsContextMenu.prototype = {
setContext() {
let context = Object.create(null);
this.isRemote = false;
if (gContextMenuContentData) {
context = gContextMenuContentData.context;
gContextMenuContentData.context = null;
this.isRemote = gContextMenuContentData.isRemote;
}
this.shouldDisplay = context.shouldDisplay;
@ -214,8 +231,8 @@ nsContextMenu.prototype = {
this.onTextInput = context.onTextInput;
this.onVideo = context.onVideo;
this.target = context.target;
this.targetIdentifier = context.targetIdentifier;
this.target = this.isRemote ? context.target : document.popupNode;
this.targetAsCPOW = context.targetAsCPOW;
this.principal = context.principal;
this.frameOuterWindowID = context.frameOuterWindowID;
@ -223,10 +240,9 @@ nsContextMenu.prototype = {
this.inSyntheticDoc = context.inSyntheticDoc;
this.inAboutDevtoolsToolbox = context.inAboutDevtoolsToolbox;
// Everything after this isn't sent directly from ContextMenu
if (this.target) {
this.ownerDoc = this.target.ownerDocument;
}
this.ownerDoc = this.target.ownerDocument;
this.csp = E10SUtils.deserializeCSP(context.csp);
@ -236,14 +252,12 @@ nsContextMenu.prototype = {
? gContextMenuContentData.popupNodeSelectors
: [];
if (gContextMenuContentData) {
if (this.isRemote) {
this.browser = gContextMenuContentData.browser;
this.selectionInfo = gContextMenuContentData.selectionInfo;
this.actor = gContextMenuContentData.actor;
} else {
this.browser = this.ownerDoc.defaultView.docShell.chromeEventHandler;
this.selectionInfo = BrowserUtils.getSelectionDetails(window);
this.actor = this.browser.browsingContext.currentWindowGlobal.getActor("ContextMenu");
}
const {gBrowser} = this.browser.ownerGlobal;
@ -256,11 +270,27 @@ nsContextMenu.prototype = {
!!gBrowser.getTabForBrowser(this.browser) : false;
if (context.shouldInitInlineSpellCheckerUINoChildren) {
InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo, this.actor.manager);
if (this.isRemote) {
InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
} else {
InlineSpellCheckerUI.init(this.target.editor);
InlineSpellCheckerUI.initFromEvent(document.popupRangeParent,
document.popupRangeOffset);
}
}
if (context.shouldInitInlineSpellCheckerUIWithChildren) {
InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo, this.actor.manager);
if (this.isRemote) {
InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
} else {
var targetWin = this.ownerDoc.defaultView;
var {editingSession} = targetWin.docShell;
InlineSpellCheckerUI.init(editingSession.getEditorForWindow(targetWin));
InlineSpellCheckerUI.initFromEvent(document.popupRangeParent,
document.popupRangeOffset);
}
let canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
this.showItem("spell-check-enabled", canSpell);
this.showItem("spell-separator", canSpell);
@ -268,8 +298,8 @@ nsContextMenu.prototype = {
}, // setContext
hiding: function CM_hiding() {
if (this.actor) {
this.actor.hiding();
if (this.browser && this.browser.messageManager) {
this.browser.messageManager.sendAsyncMessage("ContextMenu:Hiding");
}
gContextMenuContentData = null;
@ -732,11 +762,8 @@ nsContextMenu.prototype = {
if (!showFill || disableFill) {
return;
}
let documentURI = gContextMenuContentData.documentURIObject;
let fragment = LoginManagerContextMenu.addLoginsToMenu(this.targetIdentifier,
this.browser,
documentURI);
let fragment = LoginManagerContextMenu.addLoginsToMenu(this.targetAsCPOW, this.browser, documentURI);
this.showItem("fill-login-no-logins", !fragment);
@ -777,6 +804,12 @@ nsContextMenu.prototype = {
params[p] = extra[p];
}
if (!this.isRemote) {
// Propagate the frameOuterWindowID value saved when
// the context menu has been opened.
params.frameOuterWindowID = this.frameOuterWindowID;
}
let referrerInfo = gContextMenuContentData.referrerInfo;
// If we want to change userContextId, we must be sure that we don't
// propagate the referrer.
@ -846,7 +879,8 @@ nsContextMenu.prototype = {
// Reload clicked-in frame.
reloadFrame(aEvent) {
let forceReload = aEvent.shiftKey;
this.actor.reloadFrame(this.targetIdentifier, forceReload);
this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadFrame",
null, { target: this.target, forceReload });
},
// Open clicked-in frame in its own window.
@ -939,11 +973,22 @@ nsContextMenu.prototype = {
urlSecurityCheck(this.mediaURL,
this.principal,
Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
this.actor.reloadImage(this.targetIdentifier);
this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadImage",
null, { target: this.target });
},
_canvasToBlobURL(targetIdentifier) {
return this.actor.canvasToBlobURL(targetIdentifier);
_canvasToBlobURL(target) {
let mm = this.browser.messageManager;
return new Promise(function(resolve) {
mm.sendAsyncMessage("ContextMenu:Canvas:ToBlobURL", {}, { target });
let onMessage = (message) => {
mm.removeMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
resolve(message.data.blobURL);
};
mm.addMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
});
},
// Change current window to the URL of the image, video, or audio.
@ -951,7 +996,7 @@ nsContextMenu.prototype = {
let referrerInfo = gContextMenuContentData.referrerInfo;
let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
if (this.onCanvas) {
this._canvasToBlobURL(this.targetIdentifier).then(function(blobURL) {
this._canvasToBlobURL(this.target).then(function(blobURL) {
openUILink(blobURL, e, { referrerInfo,
triggeringPrincipal: systemPrincipal});
}, Cu.reportError);
@ -968,6 +1013,7 @@ nsContextMenu.prototype = {
},
saveVideoFrameAsImage() {
let mm = this.browser.messageManager;
let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
let name = "";
@ -982,11 +1028,17 @@ nsContextMenu.prototype = {
if (!name)
name = "snapshot.jpg";
mm.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage", {}, {
target: this.target,
});
// Cache this because we fetch the data async
let {documentURIObject} = gContextMenuContentData;
this.actor.saveVideoFrameAsImage(this.targetIdentifier).then(dataURL => {
let onMessage = (message) => {
mm.removeMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
// FIXME can we switch this to a blob URL?
let dataURL = message.data.dataURL;
saveImageURL(dataURL, name, "SaveImageTitle",
true, // bypass cache
false, // don't skip prompt for where to save
@ -996,7 +1048,8 @@ nsContextMenu.prototype = {
null, // content disposition
isPrivate,
this.principal);
});
};
mm.addMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
},
leaveDOMFullScreen() {
@ -1016,17 +1069,23 @@ nsContextMenu.prototype = {
},
setDesktopBackground() {
if (!Services.policies.isAllowed("setDesktopBackground")) {
return;
}
let mm = this.browser.messageManager;
this.actor.setAsDesktopBackground(this.targetIdentifier).then(({ failed, dataURL, imageName }) => {
if (failed) {
mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground", null,
{ target: this.target });
let onMessage = (message) => {
mm.removeMessageListener("ContextMenu:SetAsDesktopBackground:Result",
onMessage);
if (message.data.disable ||
!Services.policies.isAllowed("setDesktopBackground")) {
return;
}
let image = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
image.src = dataURL;
image.src = message.data.dataUrl;
let imageName = message.data.imageName;
// Confirm since it's annoying if you hit this accidentally.
const kDesktopBackgroundURL =
@ -1050,7 +1109,9 @@ nsContextMenu.prototype = {
"centerscreen,chrome,dialog,modal,dependent",
image, imageName);
}
});
};
mm.addMessageListener("ContextMenu:SetAsDesktopBackground:Result", onMessage);
},
// Save URL of clicked-on frame.
@ -1205,7 +1266,7 @@ nsContextMenu.prototype = {
// Save URL of clicked-on link.
saveLink() {
let isContentWindowPrivate = this.ownerDoc.isPrivate;
let isContentWindowPrivate = this.isRemote ? this.ownerDoc.isPrivate : undefined;
this.saveHelper(this.linkURL, this.linkTextStr, null, true, this.ownerDoc,
gContextMenuContentData.documentURIObject,
this.frameOuterWindowID,
@ -1222,12 +1283,12 @@ nsContextMenu.prototype = {
// Save URL of the clicked upon image, video, or audio.
saveMedia() {
let doc = this.ownerDoc;
let isContentWindowPrivate = this.ownerDoc.isPrivate;
let isContentWindowPrivate = this.isRemote ? this.ownerDoc.isPrivate : undefined;
let referrerURI = gContextMenuContentData.documentURIObject;
let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
if (this.onCanvas) {
// Bypass cache, since it's a data: URL.
this._canvasToBlobURL(this.targetIdentifier).then(function(blobURL) {
this._canvasToBlobURL(this.target).then(function(blobURL) {
saveImageURL(blobURL, "canvas.png", "SaveImageTitle",
true, false, referrerURI, null, null, null,
isPrivate,
@ -1298,22 +1359,6 @@ nsContextMenu.prototype = {
clipboard.copyString(linkURL);
},
addKeywordForSearchField() {
this.actor.getSearchFieldBookmarkData(this.targetIdentifier).then(data => {
let title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
[data.title]);
PlacesUIUtils.showBookmarkDialog({ action: "add",
type: "bookmark",
uri: makeURI(data.spec),
title,
keyword: "",
postData: data.postData,
charSet: data.charset,
hiddenRows: [ "location", "tags" ],
}, window);
});
},
/**
* Utilities
*/
@ -1431,11 +1476,17 @@ nsContextMenu.prototype = {
addBookmarkForFrame: function CM_addBookmarkForFrame() {
let uri = gContextMenuContentData.documentURIObject;
let mm = this.browser.messageManager;
this.actor.getFrameTitle(this.targetIdentifier).then(title => {
window.top.PlacesCommandHook.bookmarkLink(uri.spec, title)
let onMessage = (message) => {
mm.removeMessageListener("ContextMenu:BookmarkFrame:Result", onMessage);
window.top.PlacesCommandHook.bookmarkLink(uri.spec, message.data.title)
.catch(Cu.reportError);
});
};
mm.addMessageListener("ContextMenu:BookmarkFrame:Result", onMessage);
mm.sendAsyncMessage("ContextMenu:BookmarkFrame", null, { target: this.target });
},
savePageAs: function CM_savePageAs() {
@ -1451,7 +1502,14 @@ nsContextMenu.prototype = {
},
mediaCommand: function CM_mediaCommand(command, data) {
this.actor.mediaCommand(this.targetIdentifier, command, data);
let mm = this.browser.messageManager;
let win = this.browser.ownerGlobal;
let windowUtils = win.windowUtils;
mm.sendAsyncMessage("ContextMenu:MediaCommand",
{command,
data,
handlingUserInput: windowUtils.isHandlingUserInput},
{element: this.target});
},
copyMediaLocation() {
@ -1514,8 +1572,4 @@ nsContextMenu.prototype = {
};
return createUserContextMenu(aEvent, createMenuOptions);
},
doCustomCommand(generatedItemId, handlingUserInput) {
this.actor.doCustomCommand(generatedItemId, handlingUserInput);
},
};

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

@ -42,11 +42,9 @@ window._gBrowser = {
}
let messageManager = window.getGroupMessageManager("browsers");
window.messageManager.addMessageListener("contextmenu", this);
if (gMultiProcessBrowser) {
messageManager.addMessageListener("DOMTitleChanged", this);
messageManager.addMessageListener("DOMWindowClose", this);
window.messageManager.addMessageListener("contextmenu", this);
messageManager.addMessageListener("Browser:Init", this);
} else {
this._outerWindowIDBrowserMap.set(this.selectedBrowser.outerWindowID,

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

@ -48,20 +48,28 @@ add_task(async function() {
await BrowserTestUtils.synthesizeMouseAtCenter(`#${id} > input`,
{ type: "contextmenu", button: 2 },
tab.linkedBrowser);
await contextMenuPromise;
let url = action || tab.linkedBrowser.currentURI.spec;
let actor = gContextMenu.actor;
let target = await contextMenuPromise;
let data = await actor.getSearchFieldBookmarkData(gContextMenu.targetIdentifier);
if (method == "GET") {
ok(data.spec.endsWith(`${param}=%s`),
`Check expected url for field named ${param} and action ${action}`);
} else {
is(data.spec, url,
`Check expected url for field named ${param} and action ${action}`);
is(data.postData, `${param}%3D%25s`,
`Check expected POST data for field named ${param} and action ${action}`);
}
await new Promise(resolve => {
let url = action || tab.linkedBrowser.currentURI.spec;
let mm = tab.linkedBrowser.messageManager;
let onMessage = (message) => {
mm.removeMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
if (method == "GET") {
ok(message.data.spec.endsWith(`${param}=%s`),
`Check expected url for field named ${param} and action ${action}`);
} else {
is(message.data.spec, url,
`Check expected url for field named ${param} and action ${action}`);
is(message.data.postData, `${param}%3D%25s`,
`Check expected POST data for field named ${param} and action ${action}`);
}
resolve();
};
mm.addMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData", null, { target });
});
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
contextMenu.hidePopup();

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

@ -16,21 +16,6 @@ ChromeUtils.defineModuleGetter(this, "ActorManagerParent",
const PREF_PDFJS_ENABLED_CACHE_STATE = "pdfjs.enabledCache.state";
let ACTORS = {
ContextMenu: {
parent: {
moduleURI: "resource:///actors/ContextMenuParent.jsm",
},
child: {
moduleURI: "resource:///actors/ContextMenuChild.jsm",
events: {
"contextmenu": { mozSystemGroup: true },
},
},
allFrames: true,
},
SubframeCrash: {
parent: {
moduleURI: "resource:///actors/SubframeCrashParent.jsm",
@ -132,6 +117,15 @@ let LEGACY_ACTORS = {
},
},
ContextMenu: {
child: {
module: "resource:///actors/ContextMenuChild.jsm",
events: {
"contextmenu": {mozSystemGroup: true},
},
},
},
ContentSearch: {
child: {
module: "resource:///actors/ContentSearchChild.jsm",
@ -147,16 +141,6 @@ let LEGACY_ACTORS = {
},
},
ContextMenuSpecialProcess: {
child: {
module: "resource:///actors/ContextMenuSpecialProcessChild.jsm",
events: {
"contextmenu": {mozSystemGroup: true},
},
},
allFrames: true,
},
DOMFullscreen: {
child: {
module: "resource:///actors/DOMFullscreenChild.jsm",

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

@ -9,7 +9,7 @@ this.menusChild = class extends ExtensionAPI {
menus: {
getTargetElement(targetElementId) {
let element;
let lastMenuTarget = ContextMenuChild.getLastTarget(context.contentWindow.docShell.browsingContext);
let lastMenuTarget = ContextMenuChild.getLastTarget(context.messageManager);
if (lastMenuTarget && Math.floor(lastMenuTarget.timeStamp) === targetElementId) {
element = lastMenuTarget.targetRef.get();
}

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

@ -332,10 +332,6 @@ var PlacesUIUtils = {
getViewForNode: function PUIU_getViewForNode(aNode) {
let node = aNode;
if (Cu.isDeadWrapper(node)) {
return null;
}
if (node.localName == "panelview" && node._placesView) {
return node._placesView;
}

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

@ -1,38 +0,0 @@
/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["InlineSpellCheckerChild"];
ChromeUtils.defineModuleGetter(this, "InlineSpellCheckerContent",
"resource://gre/modules/InlineSpellCheckerContent.jsm");
class InlineSpellCheckerChild extends JSWindowActorChild {
receiveMessage(msg) {
switch (msg.name) {
case "InlineSpellChecker:selectDictionary":
InlineSpellCheckerContent.selectDictionary(msg.data.localeCode);
break;
case "InlineSpellChecker:replaceMisspelling":
InlineSpellCheckerContent.replaceMisspelling(msg.data.index);
break;
case "InlineSpellChecker:toggleEnabled":
InlineSpellCheckerContent.toggleEnabled();
break;
case "InlineSpellChecker:recheck":
InlineSpellCheckerContent.recheck();
break;
case "InlineSpellChecker:uninit":
InlineSpellCheckerContent.uninitContextMenu();
break;
}
}
}

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

@ -1,33 +0,0 @@
/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["InlineSpellCheckerParent"];
class InlineSpellCheckerParent extends JSWindowActorParent {
selectDictionary({ localeCode }) {
this.sendAsyncMessage("InlineSpellChecker:selectDictionary",
{ localeCode });
}
replaceMisspelling({ index }) {
this.sendAsyncMessage("InlineSpellChecker:replaceMisspelling",
{ index });
}
toggleEnabled() {
this.sendAsyncMessage("InlineSpellChecker:toggleEnabled", {});
}
recheckSpelling() {
this.sendAsyncMessage("InlineSpellChecker:recheck", {});
}
uninit() {
this.sendAsyncMessage("InlineSpellChecker:uninit", {});
}
}

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

@ -28,8 +28,6 @@ FINAL_TARGET_FILES.actors += [
'ExtFindChild.jsm',
'FindBarChild.jsm',
'FinderChild.jsm',
'InlineSpellCheckerChild.jsm',
'InlineSpellCheckerParent.jsm',
'KeyPressEventModelCheckerChild.jsm',
'PictureInPictureChild.jsm',
'PopupBlockingChild.jsm',

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

@ -34,8 +34,6 @@ ChromeUtils.defineModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
ChromeUtils.defineModuleGetter(this, "InsecurePasswordUtils",
"resource://gre/modules/InsecurePasswordUtils.jsm");
ChromeUtils.defineModuleGetter(this, "ContentDOMReference",
"resource://gre/modules/ContentDOMReference.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gNetUtil",
"@mozilla.org/network/util;1",
@ -306,7 +304,7 @@ this.LoginManagerContent = {
loginFormOrigin: msg.data.loginFormOrigin,
loginsFound: LoginHelper.vanillaObjectsToLogins(msg.data.logins),
recipes: msg.data.recipes,
inputElementIdentifier: msg.data.inputElementIdentifier,
inputElement: msg.objects.inputElement,
});
return;
}
@ -686,22 +684,15 @@ this.LoginManagerContent = {
* from the origin of the form used for the fill.
* recipes:
* Fill recipes transmitted together with the original message.
* inputElementIdentifier:
* An identifier generated for the input element via ContentDOMReference.
* inputElement:
* Username or password input element from the form we want to fill.
* }
*/
fillForm({ topDocument, loginFormOrigin, loginsFound, recipes, inputElementIdentifier }) {
if (!inputElementIdentifier) {
fillForm({ topDocument, loginFormOrigin, loginsFound, recipes, inputElement }) {
if (!inputElement) {
log("fillForm: No input element specified");
return;
}
let inputElement = ContentDOMReference.resolve(inputElementIdentifier);
if (!inputElement) {
log("fillForm: Could not resolve inputElementIdentifier to a living element.");
return;
}
if (LoginHelper.getLoginOrigin(topDocument.documentURI) != loginFormOrigin) {
if (!inputElement ||
LoginHelper.getLoginOrigin(inputElement.ownerDocument.documentURI) != loginFormOrigin) {

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

@ -21,8 +21,8 @@ this.LoginManagerContextMenu = {
/**
* Look for login items and add them to the contextual menu.
*
* @param {Object} inputElementIdentifier
* An identifier generated for the input element via ContentDOMReference.
* @param {HTMLInputElement} inputElement
* The target input element of the context menu click.
* @param {xul:browser} browser
* The browser for the document the context menu was open on.
* @param {nsIURI} documentURI
@ -31,7 +31,7 @@ this.LoginManagerContextMenu = {
* when subframes are involved.
* @returns {DocumentFragment} a document fragment with all the login items.
*/
addLoginsToMenu(inputElementIdentifier, browser, documentURI) {
addLoginsToMenu(inputElement, browser, documentURI) {
let foundLogins = this._findLogins(documentURI);
if (!foundLogins.length) {
@ -58,7 +58,7 @@ this.LoginManagerContextMenu = {
// login is bound so we can keep the reference to each object.
item.addEventListener("command", function(login, event) {
this._fillTargetField(login, inputElementIdentifier, browser, documentURI);
this._fillTargetField(login, inputElement, browser, documentURI);
}.bind(this, login));
fragment.appendChild(item);
@ -149,8 +149,8 @@ this.LoginManagerContextMenu = {
/**
* @param {nsILoginInfo} login
* The login we want to fill the form with.
* @param {Object} inputElementIdentifier
* An identifier generated for the input element via ContentDOMReference.
* @param {Element} inputElement
* The target input element we want to fill.
* @param {xul:browser} browser
* The target tab browser.
* @param {nsIURI} documentURI
@ -158,12 +158,12 @@ this.LoginManagerContextMenu = {
* This isn't the same as the browser's top-level
* document URI when subframes are involved.
*/
_fillTargetField(login, inputElementIdentifier, browser, documentURI) {
_fillTargetField(login, inputElement, browser, documentURI) {
LoginManagerParent.fillForm({
browser,
inputElementIdentifier,
loginFormOrigin: documentURI.displayPrePath,
login,
inputElement,
}).catch(Cu.reportError);
},

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

@ -168,7 +168,7 @@ this.LoginManagerParent = {
* Trigger a login form fill and send relevant data (e.g. logins and recipes)
* to the child process (LoginManagerContent).
*/
async fillForm({ browser, loginFormOrigin, login, inputElementIdentifier }) {
async fillForm({ browser, loginFormOrigin, login, inputElement }) {
let recipes = [];
if (loginFormOrigin) {
let formHost;
@ -185,12 +185,12 @@ this.LoginManagerParent = {
// doesn't support structured cloning.
let jsLogins = [LoginHelper.loginToVanillaObject(login)];
let objects = inputElement ? {inputElement} : null;
browser.messageManager.sendAsyncMessage("PasswordManager:fillForm", {
inputElementIdentifier,
loginFormOrigin,
logins: jsLogins,
recipes,
});
}, objects);
},
/**

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

@ -115,18 +115,6 @@ let ACTORS = {
allFrames: true,
},
InlineSpellChecker: {
parent: {
moduleURI: "resource://gre/actors/InlineSpellCheckerParent.jsm",
},
child: {
moduleURI: "resource://gre/actors/InlineSpellCheckerChild.jsm",
},
allFrames: true,
},
};
let LEGACY_ACTORS = {

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

@ -1,141 +0,0 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
/**
* This module holds weak references to DOM elements that exist within the
* current content process, and converts them to a unique identifier that can be
* passed between processes. The identifer, if received by the same content process
* that issued it, can then be converted back into the DOM element (presuming the
* element hasn't had all of its other references dropped).
*
* The hope is that this module can eliminate the need for passing CPOW references
* between processes during runtime.
*/
var EXPORTED_SYMBOLS = ["ContentDOMReference"];
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
/**
* An identifier generated by ContentDOMReference is a unique pair of BrowsingContext
* ID and a UUID. gRegistry maps BrowsingContext's to an object with the following
* properties:
*
* UUIDToElement:
* A Map of UUID strings to WeakReference's to the elements they refer to.
*
* elementToUUID:
* A WeakMap from a DOM element to a UUID that refers to it.
*/
var gRegistry = new WeakMap();
var ContentDOMReference = {
/**
* Generate and return an identifier for a given DOM element.
*
* @param {Element} element The DOM element to generate the identifier for.
* @return {Object} The identifier for the DOM element that can be passed between
* processes as a message.
*/
get(element) {
if (!element) {
throw new Error("Can't create a ContentDOMReference identifier for " +
"non-existant nodes.");
}
let browsingContext = element.ownerGlobal.getWindowGlobalChild().browsingContext;
let mappings = gRegistry.get(browsingContext);
if (!mappings) {
mappings = {
UUIDToElement: new Map(),
elementToUUID: new WeakMap(),
};
gRegistry.set(browsingContext, mappings);
}
let uuid = mappings.elementToUUID.get(element);
if (uuid) {
// We already had this element registered, so return the pre-existing UUID.
return { browsingContextId: browsingContext.id, uuid };
}
// We must be registering a new element at this point.
uuid = gUUIDGenerator.generateUUID().toString();
mappings.elementToUUID.set(element, uuid);
mappings.UUIDToElement.set(uuid, Cu.getWeakReference(element));
return { browsingContextId: browsingContext.id, uuid };
},
/**
* Resolves an identifier back into the DOM Element that it was generated from.
*
* @param {Object} The identifier generated via ContentDOMReference.get for a
* DOM element.
* @return {Element} The DOM element that the identifier was generated for, or
* null if the element does not still exist.
*/
resolve(identifier) {
let browsingContext = BrowsingContext.get(identifier.browsingContextId);
let uuid = identifier.uuid;
return this._resolveUUIDToElement(browsingContext, uuid);
},
/**
* Removes an identifier from the registry so that subsequent attempts
* to resolve it will result in null. This is generally a good idea to avoid
* identifiers lying around taking up space (identifiers don't keep the
* DOM element alive, but the weak pointers themselves consume memory, and
* that's what we reclaim when revoking).
*
* @param {Object} The identifier to revoke, issued by ContentDOMReference.get for
* a DOM element.
*/
revoke(identifier) {
let browsingContext = BrowsingContext.get(identifier.browsingContextId);
let uuid = identifier.uuid;
let mappings = gRegistry.get(browsingContext);
if (!mappings) {
return;
}
let element = this._resolveUUIDToElement(browsingContext, uuid);
if (element) {
mappings.elementToUUID.delete(element);
}
mappings.UUIDToElement.delete(uuid);
},
/**
* Private helper function that resolves a BrowsingContext and UUID (the
* pair that makes up an identifier) to a DOM element.
*
* @param {BrowsingContext} browsingContext The BrowsingContext that was hosting
* the DOM element at the time that the identifier was generated.
* @param {String} uuid The UUID generated for the DOM element.
*
* @return {Element} The DOM element that the identifier was generated for, or
* null if the element does not still exist.
*/
_resolveUUIDToElement(browsingContext, uuid) {
let mappings = gRegistry.get(browsingContext);
if (!mappings) {
return null;
}
let weakReference = mappings.UUIDToElement.get(uuid);
if (!weakReference) {
return null;
}
return weakReference.get();
},
};

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

@ -26,14 +26,14 @@ InlineSpellChecker.prototype = {
}
},
initFromRemote(aSpellInfo, aWindowGlobalParent) {
initFromRemote(aSpellInfo) {
if (this.mRemote)
throw new Error("Unexpected state");
this.uninit();
if (!aSpellInfo)
return;
this.mInlineSpellChecker = this.mRemote = new RemoteSpellChecker(aSpellInfo, aWindowGlobalParent);
this.mInlineSpellChecker = this.mRemote = new RemoteSpellChecker(aSpellInfo);
this.mOverMisspelling = aSpellInfo.overMisspelling;
this.mMisspelling = aSpellInfo.misspelling;
},
@ -428,10 +428,9 @@ var SpellCheckHelper = {
},
};
function RemoteSpellChecker(aSpellInfo, aWindowGlobalParent) {
function RemoteSpellChecker(aSpellInfo) {
this._spellInfo = aSpellInfo;
this._suggestionGenerator = null;
this._actor = aWindowGlobalParent.getActor("InlineSpellChecker");
}
RemoteSpellChecker.prototype = {
@ -460,16 +459,16 @@ RemoteSpellChecker.prototype = {
get dictionaryList() { return this._spellInfo.dictionaryList.slice(); },
selectDictionary(localeCode) {
this._actor.selectDictionary({ localeCode });
this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:selectDictionary",
{ localeCode });
},
replaceMisspelling(index) {
this._actor.replaceMisspelling({ index });
this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:replaceMisspelling",
{ index });
},
toggleEnabled() {
this._actor.toggleEnabled();
},
toggleEnabled() { this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:toggleEnabled", {}); },
addToDictionary() {
// This is really ugly. There is an nsISpellChecker somewhere in the
// parent that corresponds to our current element's spell checker in the
@ -482,21 +481,22 @@ RemoteSpellChecker.prototype = {
let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
.getService(Ci.mozIPersonalDictionary);
dictionary.addWord(this._spellInfo.misspelling);
this._actor.recheckSpelling();
this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
},
undoAddToDictionary(word) {
let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
.getService(Ci.mozIPersonalDictionary);
dictionary.removeWord(word);
this._actor.recheckSpelling();
this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
},
ignoreWord() {
let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
.getService(Ci.mozIPersonalDictionary);
dictionary.ignoreWord(this._spellInfo.misspelling);
this._actor.recheckSpelling();
},
uninit() {
this._actor.uninit();
this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
},
uninit() { this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:uninit", {}); },
};

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

@ -12,10 +12,10 @@ var EXPORTED_SYMBOLS = [ "InlineSpellCheckerContent" ];
var InlineSpellCheckerContent = {
_spellChecker: null,
_actor: null,
_manager: null,
initContextMenu(event, editFlags, actor) {
this._actor = actor;
initContextMenu(event, editFlags, messageManager) {
this._manager = messageManager;
let spellChecker;
if (!(editFlags & (SpellCheckHelper.TEXTAREA | SpellCheckHelper.INPUT))) {
@ -32,6 +32,8 @@ var InlineSpellCheckerContent = {
this._spellChecker.initFromEvent(event.rangeParent, event.rangeOffset);
this._addMessageListeners();
if (!spellChecker.canSpellCheck) {
return { canSpellCheck: false,
initialSpellCheckPending: true,
@ -64,7 +66,10 @@ var InlineSpellCheckerContent = {
},
uninitContextMenu() {
this._actor = null;
for (let i of this._messages)
this._manager.removeMessageListener(i, this);
this._manager = null;
this._spellChecker = null;
},
@ -89,19 +94,42 @@ var InlineSpellCheckerContent = {
return suggestions;
},
selectDictionary(localeCode) {
this._spellChecker.selectDictionary(localeCode);
_messages: [
"InlineSpellChecker:selectDictionary",
"InlineSpellChecker:replaceMisspelling",
"InlineSpellChecker:toggleEnabled",
"InlineSpellChecker:recheck",
"InlineSpellChecker:uninit",
],
_addMessageListeners() {
for (let i of this._messages)
this._manager.addMessageListener(i, this);
},
replaceMisspelling(index) {
this._spellChecker.replaceMisspelling(index);
},
receiveMessage(msg) {
switch (msg.name) {
case "InlineSpellChecker:selectDictionary":
this._spellChecker.selectDictionary(msg.data.localeCode);
break;
toggleEnabled() {
this._spellChecker.toggleEnabled();
},
case "InlineSpellChecker:replaceMisspelling":
this._spellChecker.replaceMisspelling(msg.data.index);
break;
recheck() {
this._spellChecker.mInlineSpellChecker.enableRealTimeSpell = true;
case "InlineSpellChecker:toggleEnabled":
this._spellChecker.toggleEnabled();
break;
case "InlineSpellChecker:recheck":
this._spellChecker.mInlineSpellChecker.enableRealTimeSpell = true;
break;
case "InlineSpellChecker:uninit":
this.uninitContextMenu();
break;
}
},
};

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

@ -161,8 +161,10 @@ PageMenu.prototype = {
} else if (this._browser) {
let win = target.ownerGlobal;
let windowUtils = win.windowUtils;
win.gContextMenu.doCustomCommand(target.getAttribute(this.GENERATEDITEMID_ATTR),
windowUtils.isHandlingUserInput);
this._browser.messageManager.sendAsyncMessage("ContextMenu:DoCustomCommand", {
generatedItemId: target.getAttribute(this.GENERATEDITEMID_ATTR),
handlingUserInput: windowUtils.isHandlingUserInput,
});
}
} else if (type == "popuphidden" && this._popup == target) {
this.removeGeneratedContent(this._popup);
@ -242,8 +244,26 @@ function PageMenuParent() {
PageMenuParent.prototype = {
__proto__: PageMenu.prototype,
/*
* Given a JSON menu object and popup, add the context menu to the popup.
* Given a target node and popup, add the context menu to the popup. This is
* intended to be called when a single process is used. This is equivalent to
* calling PageMenuChild.build and PageMenuParent.addToPopup in sequence.
*
* Returns true if custom menu items were present.
*/
buildAndAddToPopup(aTarget, aPopup) {
let menuObject = this.maybeBuild(aTarget);
if (!menuObject) {
return false;
}
return this.buildAndAttachMenuWithObject(menuObject, null, aPopup);
},
/*
* Given a JSON menu object and popup, add the context menu to the popup. This
* is intended to be called when the child page is in a different process.
* aBrowser should be the browser containing the page the context menu is
* displayed for, which may be null.
*

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

@ -175,7 +175,6 @@ EXTRA_JS_MODULES += [
'CharsetMenu.jsm',
'Color.jsm',
'Console.jsm',
'ContentDOMReference.jsm',
'CreditCard.jsm',
'css-selector.js',
'DateTimePickerPanel.jsm',

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

@ -3331,6 +3331,13 @@ var gDetailView = {
document.getElementById("contentAreaContextMenu").openPopupAtScreen = (...args) => {
return parentContextMenuPopup.openPopupAtScreen(...args);
};
// Subscribe a "contextmenu" listener to handle the context menus for the extension option page
// running in the extension process (the context menu will be handled only for extension running
// in OOP mode, but that's ok as it is the default on any platform that uses these extensions
// options pages).
browser.messageManager.addMessageListener(
"contextmenu", message => parentChromeWindow.openContextMenu(message));
});
} else {
readyPromise = promiseEvent("load", browser, true);