--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -9,57 +9,45 @@
/* eslint-env mozilla/frame-script */
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
- E10SUtils: "resource:///modules/E10SUtils.jsm",
BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
ContentLinkHandler: "resource:///modules/ContentLinkHandler.jsm",
ContentWebRTC: "resource:///modules/ContentWebRTC.jsm",
- SpellCheckHelper: "resource://gre/modules/InlineSpellChecker.jsm",
InlineSpellCheckerContent: "resource://gre/modules/InlineSpellCheckerContent.jsm",
LoginManagerContent: "resource://gre/modules/LoginManagerContent.jsm",
LoginFormFactory: "resource://gre/modules/LoginManagerContent.jsm",
InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.jsm",
PluginContent: "resource:///modules/PluginContent.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
FormSubmitObserver: "resource:///modules/FormSubmitObserver.jsm",
PageMetadata: "resource://gre/modules/PageMetadata.jsm",
PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
Utils: "resource://gre/modules/sessionstore/Utils.jsm",
WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
Feeds: "resource:///modules/Feeds.jsm",
- findCssSelector: "resource://gre/modules/css-selector.js",
+ ContextMenu: "resource:///modules/ContextMenu.jsm",
});
-XPCOMUtils.defineLazyGetter(this, "PageMenuChild", function() {
- let tmp = {};
- Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
- return new tmp.PageMenuChild();
-});
-
-Cu.importGlobalProperties(["URL"]);
-
// TabChildGlobal
var global = this;
+var contextMenu = this.contextMenu = new ContextMenu(global);
+
// Load the form validation popup handler
var formSubmitObserver = new FormSubmitObserver(content, this);
-addMessageListener("ContextMenu:DoCustomCommand", function(message) {
- E10SUtils.wrapHandlingUserInput(
- content, message.data.handlingUserInput,
- () => PageMenuChild.executeMenu(message.data.generatedItemId));
-});
-
addMessageListener("RemoteLogins:fillForm", function(message) {
+ // intercept if ContextMenu.jsm had sent a plain object for remote targets
+ message.objects.inputElement = contextMenu.getTarget(message, "inputElement");
LoginManagerContent.receiveMessage(message, content);
});
addEventListener("DOMFormHasPassword", function(event) {
LoginManagerContent.onDOMFormHasPassword(event, content);
let formLike = LoginFormFactory.createFromForm(event.target);
InsecurePasswordUtils.reportInsecurePasswords(formLike);
});
addEventListener("DOMInputPasswordAdded", function(event) {
@@ -72,154 +60,16 @@ addEventListener("pageshow", function(ev
});
addEventListener("DOMAutoComplete", function(event) {
LoginManagerContent.onUsernameInput(event);
});
addEventListener("blur", function(event) {
LoginManagerContent.onUsernameInput(event);
});
-var handleContentContextMenu = function(event) {
- let defaultPrevented = event.defaultPrevented;
- if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
- let plugin = null;
- try {
- plugin = event.target.QueryInterface(Ci.nsIObjectLoadingContent);
- } catch (e) {}
- if (plugin && plugin.displayedType == Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
- // Don't open a context menu for plugins.
- return;
- }
-
- defaultPrevented = false;
- }
-
- if (defaultPrevented)
- return;
-
- let addonInfo = {};
- let subject = {
- event,
- addonInfo,
- };
- subject.wrappedJSObject = subject;
- Services.obs.notifyObservers(subject, "content-contextmenu");
-
- let doc = event.target.ownerDocument;
- let docLocation = doc.mozDocumentURIIfNotForErrorPages;
- docLocation = docLocation && docLocation.spec;
- let charSet = doc.characterSet;
- let baseURI = doc.baseURI;
- let referrer = doc.referrer;
- let referrerPolicy = doc.referrerPolicy;
- let frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView);
- let loginFillInfo = LoginManagerContent.getFieldContext(event.target);
-
- // The same-origin check will be done in nsContextMenu.openLinkInTab.
- let parentAllowsMixedContent = !!docShell.mixedContentChannel;
-
- // get referrer attribute from clicked link and parse it
- let referrerAttrValue = Services.netUtils.parseAttributePolicyString(event.target.
- getAttribute("referrerpolicy"));
- if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
- referrerPolicy = referrerAttrValue;
- }
-
- let disableSetDesktopBg = null;
- // Media related cache info parent needs for saving
- let contentType = null;
- let contentDisposition = null;
- if (event.target.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
- event.target instanceof Ci.nsIImageLoadingContent &&
- event.target.currentURI) {
- disableSetDesktopBg = disableSetDesktopBackground(event.target);
-
- try {
- let imageCache =
- Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
- .getImgCacheForDocument(doc);
- let props =
- imageCache.findEntryProperties(event.target.currentURI, doc);
- try {
- contentType = props.get("type", Ci.nsISupportsCString).data;
- } catch (e) {}
- try {
- contentDisposition =
- props.get("content-disposition", Ci.nsISupportsCString).data;
- } catch (e) {}
- } catch (e) {}
- }
-
- let selectionInfo = BrowserUtils.getSelectionDetails(content);
-
- let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
- let userContextId = loadContext.originAttributes.userContextId;
- let popupNodeSelectors = getNodeSelectors(event.target);
-
- if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
- let editFlags = SpellCheckHelper.isEditable(event.target, content);
- let spellInfo;
- if (editFlags &
- (SpellCheckHelper.EDITABLE | SpellCheckHelper.CONTENTEDITABLE)) {
- spellInfo =
- InlineSpellCheckerContent.initContextMenu(event, editFlags, this);
- }
-
- // 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.
- docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
- .setCommandNode(event.target);
- event.target.ownerGlobal.updateCommands("contentcontextmenu");
-
- let customMenuItems = PageMenuChild.build(event.target);
- let principal = doc.nodePrincipal;
-
- sendRpcMessage("contextmenu",
- { editFlags, spellInfo, customMenuItems, addonInfo,
- principal, docLocation, charSet, baseURI, referrer,
- referrerPolicy, contentType, contentDisposition,
- frameOuterWindowID, selectionInfo, disableSetDesktopBg,
- loginFillInfo, parentAllowsMixedContent, userContextId,
- popupNodeSelectors,
- }, {
- event,
- popupNode: event.target,
- });
- } else {
- // Break out to the parent window and pass the add-on info along
- let browser = docShell.chromeEventHandler;
- let mainWin = browser.ownerGlobal;
- mainWin.setContextMenuContentData({
- isRemote: false,
- event,
- popupNode: event.target,
- popupNodeSelectors,
- browser,
- addonInfo,
- documentURIObject: doc.documentURIObject,
- docLocation,
- charSet,
- referrer,
- referrerPolicy,
- contentType,
- contentDisposition,
- selectionInfo,
- disableSetDesktopBackground: disableSetDesktopBg,
- loginFillInfo,
- parentAllowsMixedContent,
- userContextId,
- });
- }
-}
-
-Cc["@mozilla.org/eventlistenerservice;1"]
- .getService(Ci.nsIEventListenerService)
- .addSystemEventListener(global, "contextmenu", handleContentContextMenu, false);
-
// Values for telemtery bins: see TLS_ERROR_REPORT_UI in Histograms.json
const TLS_ERROR_REPORT_TELEMETRY_UI_SHOWN = 0;
const TLS_ERROR_REPORT_TELEMETRY_EXPANDED = 1;
const TLS_ERROR_REPORT_TELEMETRY_SUCCESS = 6;
const TLS_ERROR_REPORT_TELEMETRY_FAILURE = 7;
const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE;
@@ -232,39 +82,16 @@ const SEC_ERROR_OCSP_OLD_RESPONSE
const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 5;
const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 6;
const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
-/**
- * Retrieve the array of CSS selectors corresponding to the provided node. The first item
- * of the array is the selector of the node in its owner document. Additional items are
- * used if the node is inside a frame, each representing the CSS selector for finding the
- * frame element in its parent document.
- *
- * This format is expected by DevTools in order to handle the Inspect Node context menu
- * item.
- *
- * @param {Node}
- * The node for which the CSS selectors should be computed
- * @return {Array} array of css selectors (strings).
- */
-function getNodeSelectors(node) {
- let selectors = [];
- while (node) {
- selectors.push(findCssSelector(node));
- node = node.ownerGlobal.frameElement;
- }
-
- return selectors;
-}
-
function getSerializedSecurityInfo(docShell) {
let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
.getService(Ci.nsISerializationHelper);
let securityInfo = docShell.failedChannel && docShell.failedChannel.securityInfo;
if (!securityInfo) {
return "";
}
@@ -805,175 +632,32 @@ addEventListener("pagehide", function(ev
var PageMetadataMessenger = {
init() {
addMessageListener("PageMetadata:GetPageData", this);
addMessageListener("PageMetadata:GetMicroformats", this);
},
receiveMessage(message) {
switch (message.name) {
case "PageMetadata:GetPageData": {
- let target = message.objects.target;
+ let target = contextMenu.getTarget(message);
let result = PageMetadata.getData(content.document, target);
sendAsyncMessage("PageMetadata:PageDataResult", result);
break;
}
case "PageMetadata:GetMicroformats": {
- let target = message.objects.target;
+ let target = contextMenu.getTarget(message);
let result = PageMetadata.getMicroformats(content.document, target);
sendAsyncMessage("PageMetadata:MicroformatsResult", result);
break;
}
}
}
}
PageMetadataMessenger.init();
-addMessageListener("ContextMenu:SaveVideoFrameAsImage", (message) => {
- let video = message.objects.target;
- let canvas = 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);
- sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage:Result", {
- dataURL: canvas.toDataURL("image/jpeg", ""),
- });
-});
-
-addMessageListener("ContextMenu:MediaCommand", (message) => {
- E10SUtils.wrapHandlingUserInput(
- content, message.data.handlingUserInput,
- () => {
- let media = message.objects.element;
- 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 (content.document.fullscreenEnabled)
- media.requestFullscreen();
- break;
- }
- });
-});
-
-addMessageListener("ContextMenu:Canvas:ToBlobURL", (message) => {
- message.objects.target.toBlob((blob) => {
- let blobURL = URL.createObjectURL(blob);
- sendAsyncMessage("ContextMenu:Canvas:ToBlobURL:Result", { blobURL });
- });
-});
-
-addMessageListener("ContextMenu:ReloadFrame", (message) => {
- message.objects.target.ownerDocument.location.reload();
-});
-
-addMessageListener("ContextMenu:ReloadImage", (message) => {
- let image = message.objects.target;
- if (image instanceof Ci.nsIImageLoadingContent)
- image.forceReload();
-});
-
-addMessageListener("ContextMenu:BookmarkFrame", (message) => {
- let frame = message.objects.target.ownerDocument;
- sendAsyncMessage("ContextMenu:BookmarkFrame:Result",
- { title: frame.title,
- description: PlacesUIUtils.getDescriptionFromDocument(frame) });
-});
-
-addMessageListener("ContextMenu:SearchFieldBookmarkData", (message) => {
- let node = message.objects.target;
-
- 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;
- let description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument);
-
- let formData = [];
-
- function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) {
- if (aIsFormUrlEncoded) {
- return escape(aName + "=" + aValue);
- }
- return escape(aName) + "=" + escape(aValue);
- }
-
- for (let el of node.form.elements) {
- if (!el.type) // happens with fieldsets
- continue;
-
- if (el == node) {
- formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) :
- // Don't escape "%s", just append
- escapeNameValuePair(el.name, "", false) + "%s");
- continue;
- }
-
- let type = el.type.toLowerCase();
-
- if (((el instanceof content.HTMLInputElement && el.mozIsTextField(true)) ||
- type == "hidden" || type == "textarea") ||
- ((type == "checkbox" || type == "radio") && el.checked)) {
- formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded));
- } else if (el instanceof content.HTMLSelectElement && el.selectedIndex >= 0) {
- for (let j = 0; j < el.options.length; j++) {
- if (el.options[j].selected)
- formData.push(escapeNameValuePair(el.name, el.options[j].value,
- isURLEncoded));
- }
- }
- }
-
- let postData;
-
- if (isURLEncoded)
- postData = formData.join("&");
- else {
- let separator = spec.includes("?") ? "&" : "?";
- spec += separator + formData.join("&");
- }
-
- sendAsyncMessage("ContextMenu:SearchFieldBookmarkData:Result",
- { spec, title, description, postData, charset });
-});
-
addMessageListener("Bookmarks:GetPageDetails", (message) => {
let doc = content.document;
let isErrorPage = /^about:(neterror|certerror|blocked)/.test(doc.documentURI);
sendAsyncMessage("Bookmarks:GetPageDetails:Result",
{ isErrorPage,
description: PlacesUIUtils.getDescriptionFromDocument(doc) });
});
@@ -1023,66 +707,16 @@ var LightWeightThemeWebInstallListener =
_resetPreviewWindow() {
this._previewWindow.removeEventListener("pagehide", this, true);
this._previewWindow = null;
}
};
LightWeightThemeWebInstallListener.init();
-function disableSetDesktopBackground(aTarget) {
- // Disable the Set as Desktop Background menu item if we're still trying
- // to load the image or the load failed.
- if (!(aTarget instanceof Ci.nsIImageLoadingContent))
- return true;
-
- if (("complete" in aTarget) && !aTarget.complete)
- return true;
-
- if (aTarget.currentURI.schemeIs("javascript"))
- return true;
-
- let request = aTarget.QueryInterface(Ci.nsIImageLoadingContent)
- .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
- if (!request)
- return true;
-
- return false;
-}
-
-addMessageListener("ContextMenu:SetAsDesktopBackground", (message) => {
- let target = message.objects.target;
-
- // Paranoia: check disableSetDesktopBackground again, in case the
- // image changed since the context menu was initiated.
- let disable = disableSetDesktopBackground(target);
-
- if (!disable) {
- try {
- BrowserUtils.urlSecurityCheck(target.currentURI.spec, target.ownerDocument.nodePrincipal);
- let canvas = 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);
- sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
- { dataUrl, imageName });
- } catch (e) {
- Cu.reportError(e);
- disable = true;
- }
- }
-
- if (disable)
- sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result", { disable });
-});
-
var PageInfoListener = {
init() {
addMessageListener("PageInfo:getData", this);
},
receiveMessage(message) {
let strings = message.data.strings;
@@ -1095,43 +729,27 @@ var PageInfoListener = {
if (frameOuterWindowID != undefined) {
window = Services.wm.getOuterWindowWithId(frameOuterWindowID);
document = window.document;
} else {
window = content.window;
document = content.document;
}
- let imageElement = message.objects.imageElement;
-
let pageInfoData = {metaViewRows: this.getMetaInfo(document),
docInfo: this.getDocumentInfo(document),
feeds: this.getFeedsInfo(document, strings),
- windowInfo: this.getWindowInfo(window),
- imageInfo: this.getImageInfo(imageElement)};
+ windowInfo: this.getWindowInfo(window)};
sendAsyncMessage("PageInfo:data", pageInfoData);
// Separate step so page info dialog isn't blank while waiting for this to finish.
this.getMediaInfo(document, window, strings);
},
- getImageInfo(imageElement) {
- let imageInfo = null;
- if (imageElement) {
- imageInfo = {
- currentSrc: imageElement.currentSrc,
- width: imageElement.width,
- height: imageElement.height,
- imageText: imageElement.title || imageElement.alt
- };
- }
- return imageInfo;
- },
-
getMetaInfo(document) {
let metaViewRows = [];
// Get the meta tags from the page.
let metaNodes = document.getElementsByTagName("meta");
for (let metaNode of metaNodes) {
metaViewRows.push([metaNode.name || metaNode.httpEquiv || metaNode.getAttribute("property"),
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -22,26 +22,27 @@ var gContextMenuContentData = null;
function setContextMenuContentData(data) {
gContextMenuContentData = data;
}
function openContextMenu(aMessage) {
let data = aMessage.data;
let browser = aMessage.target;
+ let spellInfo = data.spellInfo;
- let spellInfo = data.spellInfo;
- if (spellInfo)
+ if (spellInfo) {
spellInfo.target = aMessage.target.messageManager;
+ }
+
let documentURIObject = makeURI(data.docLocation,
data.charSet,
makeURI(data.baseURI));
- gContextMenuContentData = { isRemote: true,
- event: aMessage.objects.event,
- popupNode: aMessage.objects.popupNode,
+ gContextMenuContentData = { context: data.context,
+ isRemote: data.isRemote,
popupNodeSelectors: data.popupNodeSelectors,
browser,
editFlags: data.editFlags,
spellInfo,
principal: data.principal,
customMenuItems: data.customMenuItems,
addonInfo: data.addonInfo,
documentURIObject,
@@ -53,40 +54,41 @@ function openContextMenu(aMessage) {
contentDisposition: data.contentDisposition,
frameOuterWindowID: data.frameOuterWindowID,
selectionInfo: data.selectionInfo,
disableSetDesktopBackground: data.disableSetDesktopBg,
loginFillInfo: data.loginFillInfo,
parentAllowsMixedContent: data.parentAllowsMixedContent,
userContextId: data.userContextId,
};
+
let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
- let event = gContextMenuContentData.event;
+ let context = gContextMenuContentData.context;
// The event is a CPOW that can't be passed into the native openPopupAtScreen
// function. Therefore we synthesize a new MouseEvent to propagate the
// inputSource to the subsequently triggered popupshowing event.
var newEvent = document.createEvent("MouseEvent");
- newEvent.initNSMouseEvent("contextmenu", true, true, null, 0, event.screenX, event.screenY,
- 0, 0, false, false, false, false, 0, null, 0, event.mozInputSource);
+ 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);
}
function nsContextMenu(aXulMenu, aIsShift) {
this.shouldDisplay = true;
this.initMenu(aXulMenu, aIsShift);
}
// Prototype for nsContextMenu "class."
nsContextMenu.prototype = {
initMenu: function CM_initMenu(aXulMenu, aIsShift) {
// Get contextual info.
- this.setTarget(document.popupNode, document.popupRangeParent,
- document.popupRangeOffset);
+ this.setContext();
+
if (!this.shouldDisplay)
return;
this.hasPageMenu = false;
this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
if (!aIsShift) {
if (this.isRemote) {
this.hasPageMenu =
@@ -140,17 +142,144 @@ nsContextMenu.prototype = {
// Initialize (disable/remove) menu items.
this.initItems();
// Register this opening of the menu with telemetry:
this._checkTelemetryForMenu(aXulMenu);
},
+ 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;
+
+ // Assign what's _possibly_ needed from `context` sent by ContextMenu.jsm
+ // Keep this consistent with the similar code in ContextMenu's _setContext
+ this.bgImageURL = context.bgImageURL;
+ this.imageDescURL = context.imageDescURL;
+ this.imageInfo = context.imageInfo;
+ this.mediaURL = context.mediaURL;
+ this.webExtBrowserType = context.webExtBrowserType;
+
+ this.canSpellCheck = context.canSpellCheck;
+ this.hasBGImage = context.hasBGImage;
+ this.hasMultipleBGImages = context.hasMultipleBGImages;
+ this.isDesignMode = context.isDesignMode;
+ this.inFrame = context.inFrame;
+ this.inSrcdocFrame = context.inSrcdocFrame;
+ this.inSyntheticDoc = context.inSyntheticDoc;
+ this.inTabBrowser = context.inTabBrowser;
+ this.inWebExtBrowser = context.inWebExtBrowser;
+
+ this.link = context.link;
+ this.linkDownload = context.linkDownload;
+ this.linkHasNoReferrer = context.linkHasNoReferrer;
+ this.linkProtocol = context.linkProtocol;
+ this.linkTextStr = context.linkTextStr;
+ this.linkURL = context.linkURL;
+ this.linkURI = this.getLinkURI(); // can't send; regenerate
+
+ this.onAudio = context.onAudio;
+ this.onCanvas = context.onCanvas;
+ this.onCompletedImage = context.onCompletedImage;
+ this.onCTPPlugin = context.onCTPPlugin;
+ this.onDRMMedia = context.onDRMMedia;
+ this.onEditableArea = context.onEditableArea;
+ this.onImage = context.onImage;
+ this.onKeywordField = context.onKeywordField;
+ this.onLink = context.onLink;
+ this.onLoadedImage = context.onLoadedImage;
+ this.onMailtoLink = context.onMailtoLink;
+ this.onMathML = context.onMathML;
+ this.onMozExtLink = context.onMozExtLink;
+ this.onNumeric = context.onNumeric;
+ this.onPassword = context.onPassword;
+ this.onSaveableLink = context.onSaveableLink;
+ this.onTextInput = context.onTextInput;
+ this.onVideo = context.onVideo;
+
+ this.target = this.isRemote ? context.target : document.popupNode;
+
+ this.principal = context.principal;
+ this.frameOuterWindowID = context.frameOuterWindowID;
+
+ this.inSyntheticDoc = context.inSyntheticDoc;
+
+ // Everything after this isn't sent directly from ContextMenu
+ this.ownerDoc = this.target.ownerDocument;
+
+ // Remember the CSS selectors corresponding to clicked node. gContextMenuContentData
+ // can be null if the menu was triggered by tests in which case use an empty array.
+ this.targetSelectors = gContextMenuContentData
+ ? gContextMenuContentData.popupNodeSelectors
+ : [];
+
+ if (this.isRemote) {
+ this.browser = gContextMenuContentData.browser;
+ this.selectionInfo = gContextMenuContentData.selectionInfo;
+ } else {
+ this.browser = this.ownerDoc.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ this.selectionInfo = BrowserUtils.getSelectionDetails(window);
+ }
+
+ this.textSelected = this.selectionInfo.text;
+ this.isTextSelected = this.textSelected.length != 0;
+ this.webExtBrowserType = this.browser.getAttribute("webextension-view-type");
+ this.inWebExtBrowser = !!this.webExtBrowserType;
+ this.inTabBrowser = this.browser.ownerGlobal.gBrowser ?
+ !!this.browser.ownerGlobal.gBrowser.getTabForBrowser(this.browser) : false;
+
+ if (context.shouldInitInlineSpellCheckerUINoChildren) {
+ if (this.isRemote) {
+ InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
+ } else {
+ InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
+ InlineSpellCheckerUI.initFromEvent(document.popupRangeParent,
+ document.popupRangeOffset);
+ }
+ }
+
+ if (context.shouldInitInlineSpellCheckerUIWithChildren) {
+ if (this.isRemote) {
+ InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
+ } else {
+ var targetWin = this.ownerDoc.defaultView;
+ var editingSession = targetWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+
+ 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);
+ }
+ }, // setContext
+
hiding: function CM_hiding() {
+ if (this.browser) {
+ this.browser.messageManager.sendAsyncMessage("ContextMenu:Hiding");
+ }
+
gContextMenuContentData = null;
InlineSpellCheckerUI.clearSuggestionsFromMenu();
InlineSpellCheckerUI.clearDictionaryListFromMenu();
InlineSpellCheckerUI.uninit();
if (Cu.isModuleLoaded("resource://gre/modules/LoginManagerContextMenu.jsm")) {
LoginManagerContextMenu.clearLoginsFromMenu(document);
}
@@ -273,31 +402,20 @@ nsContextMenu.prototype = {
this.showItem("context-savevideo", this.onVideo);
this.showItem("context-saveaudio", this.onAudio);
this.showItem("context-video-saveimage", this.onVideo);
this.setItemAttr("context-savevideo", "disabled", !this.mediaURL);
this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL);
// Send media URL (but not for canvas, since it's a big data: URL)
this.showItem("context-sendimage", this.onImage);
this.showItem("context-sendvideo", this.onVideo);
- this.showItem("context-castvideo", this.onVideo);
this.showItem("context-sendaudio", this.onAudio);
let mediaIsBlob = this.mediaURL.startsWith("blob:");
this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL || mediaIsBlob);
this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL || mediaIsBlob);
- let shouldShowCast = Services.prefs.getBoolPref("browser.casting.enabled");
- // getServicesForVideo alone would be sufficient here (it depends on
- // SimpleServiceDiscovery.services), but SimpleServiceDiscovery is guaranteed
- // to be already loaded, since we load it on startup in nsBrowserGlue,
- // and CastingApps isn't, so check SimpleServiceDiscovery.services first
- // to avoid needing to load CastingApps.jsm if we don't need to.
- shouldShowCast = shouldShowCast && this.mediaURL &&
- SimpleServiceDiscovery.services.length > 0 &&
- CastingApps.getServicesForVideo(this.target).length > 0;
- this.setItemAttr("context-castvideo", "disabled", !shouldShowCast);
},
initViewItems: function CM_initViewItems() {
// View source is always OK, unless in directory listing.
this.showItem("context-viewpartialsource-selection",
this.isContentSelected);
this.showItem("context-viewpartialsource-mathml",
this.onMathML && !this.isContentSelected);
@@ -349,20 +467,20 @@ nsContextMenu.prototype = {
// View video depends on not having a standalone video.
this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame));
this.setItemAttr("context-viewvideo", "disabled", !this.mediaURL);
// View background image depends on whether there is one, but don't make
// background images of a stand-alone media document available.
this.showItem("context-viewbgimage", shouldShow &&
- !this._hasMultipleBGImages &&
+ !this.hasMultipleBGImages &&
!this.inSyntheticDoc);
this.showItem("context-sep-viewbgimage", shouldShow &&
- !this._hasMultipleBGImages &&
+ !this.hasMultipleBGImages &&
!this.inSyntheticDoc);
document.getElementById("context-viewbgimage")
.disabled = !this.hasBGImage;
this.showItem("context-viewimageinfo", this.onImage);
// The image info popup is broken for WebExtension popups, since the browser
// is destroyed when the popup is closed.
this.setItemAttr("context-viewimageinfo", "disabled", this.webExtBrowserType === "popup");
@@ -619,437 +737,16 @@ nsContextMenu.prototype = {
openPasswordManager() {
LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
},
inspectNode() {
return DevToolsShim.inspectNode(gBrowser.selectedTab, this.targetSelectors);
},
- /**
- * Set various context menu attributes based on the state of the world.
- * Note: If the context menu is on a remote process the supplied parameters
- * will be overwritten with data from gContextMenuContentData.
- *
- * @param {Object} aNode The node that this menu is being opened on.
- * @param {nsIDOMNode} aRangeParent The parent node for where the selection ends.
- * @param {Integer} aRangeOffset The end position of where the selction ends.
- */
- setTarget(aNode, aRangeParent, aRangeOffset) {
- // gContextMenuContentData.isRemote tells us if the event came from a remote
- // process. gContextMenuContentData can be null if something (like tests)
- // opens the context menu directly.
- this.isRemote = gContextMenuContentData && gContextMenuContentData.isRemote;
- if (this.isRemote) {
- aNode = gContextMenuContentData.event.target;
- aRangeParent = gContextMenuContentData.event.rangeParent;
- aRangeOffset = gContextMenuContentData.event.rangeOffset;
- }
-
- const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
- if (aNode.nodeType == Node.DOCUMENT_NODE ||
- // Not display on XUL element but relax for <label class="text-link">
- (aNode.namespaceURI == xulNS && !this._isXULTextLinkLabel(aNode))) {
- this.shouldDisplay = false;
- return;
- }
-
- // Initialize contextual info.
- this.onImage = false;
- this.onLoadedImage = false;
- this.onCompletedImage = false;
- this.imageDescURL = "";
- this.onCanvas = false;
- this.onVideo = false;
- this.onAudio = false;
- this.onDRMMedia = false;
- this.onTextInput = false;
- this.onNumeric = false;
- this.onKeywordField = false;
- this.mediaURL = "";
- this.onLink = false;
- this.onMailtoLink = false;
- this.onSaveableLink = false;
- this.link = null;
- this.linkURL = "";
- this.linkURI = null;
- this.linkTextStr = "";
- this.linkProtocol = "";
- this.linkDownload = "";
- this.linkHasNoReferrer = false;
- this.onMathML = false;
- this.inFrame = false;
- this.inSrcdocFrame = false;
- this.inSyntheticDoc = false;
- this.hasBGImage = false;
- this.bgImageURL = "";
- this.onEditableArea = false;
- this.isDesignMode = false;
- this.onCTPPlugin = false;
- this.canSpellCheck = false;
- this.onPassword = false;
- this.webExtBrowserType = "";
- this.inWebExtBrowser = false;
- this.inTabBrowser = true;
- this.onMozExtLink = false;
-
- if (this.isRemote) {
- this.selectionInfo = gContextMenuContentData.selectionInfo;
- } else {
- this.selectionInfo = BrowserUtils.getSelectionDetails(window);
- }
-
- this.textSelected = this.selectionInfo.text;
- this.isTextSelected = this.textSelected.length != 0;
-
- // Remember the node that was clicked.
- this.target = aNode;
-
- // Remember the CSS selectors corresponding to clicked node. gContextMenuContentData
- // can be null if the menu was triggered by tests in which case use an empty array.
- this.targetSelectors = gContextMenuContentData
- ? gContextMenuContentData.popupNodeSelectors
- : [];
-
- let ownerDoc = this.target.ownerDocument;
- this.ownerDoc = ownerDoc;
-
- let editFlags;
-
- // If this is a remote context menu event, use the information from
- // gContextMenuContentData instead.
- if (this.isRemote) {
- this.browser = gContextMenuContentData.browser;
- this.principal = gContextMenuContentData.principal;
- this.frameOuterWindowID = gContextMenuContentData.frameOuterWindowID;
- editFlags = gContextMenuContentData.editFlags;
- } else {
- editFlags = SpellCheckHelper.isEditable(this.target, window);
- this.browser = ownerDoc.defaultView
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell)
- .chromeEventHandler;
- this.principal = ownerDoc.nodePrincipal;
- this.frameOuterWindowID = WebNavigationFrames.getFrameId(ownerDoc.defaultView);
- }
- this.webExtBrowserType = this.browser.getAttribute("webextension-view-type");
- this.inWebExtBrowser = !!this.webExtBrowserType;
- this.inTabBrowser = this.browser.ownerGlobal.gBrowser ?
- !!this.browser.ownerGlobal.gBrowser.getTabForBrowser(this.browser) : false;
-
- // Check if we are in a synthetic document (stand alone image, video, etc.).
- this.inSyntheticDoc = ownerDoc.mozSyntheticDocument;
-
- this._setTargetForNodesNoChildren(editFlags, aRangeParent, aRangeOffset);
-
- this._setTargetForNodesWithChildren(editFlags, aRangeParent, aRangeOffset);
- },
-
- /**
- * Sets up the parts of the context menu for when when nodes have no children.
- *
- * @param {Integer} editFlags The edit flags for the node. See SpellCheckHelper
- * for the details.
- * @param {nsIDOMNode} rangeParent The parent node for where the selection ends.
- * @param {Integer} rangeOffset The end position of where the selction ends.
- */
- _setTargetForNodesNoChildren(editFlags, rangeParent, rangeOffset) {
- if (this.target.nodeType == Node.TEXT_NODE) {
- // For text nodes, look at the parent node to determine the spellcheck attribute.
- this.canSpellCheck = this.target.parentNode &&
- this._isSpellCheckEnabled(this.target);
- return;
- }
-
- // We only deal with TEXT_NODE and ELEMENT_NODE in this function, so return
- // early if we don't have one.
- if (this.target.nodeType != Node.ELEMENT_NODE) {
- return;
- }
- // See if the user clicked on an image. This check mirrors
- // nsDocumentViewer::GetInImage. Make sure to update both if this is
- // changed.
- if (this.target instanceof Ci.nsIImageLoadingContent &&
- this.target.currentURI) {
- this.onImage = true;
-
- var request =
- this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
- if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
- this.onLoadedImage = true;
- if (request &&
- (request.imageStatus & request.STATUS_LOAD_COMPLETE) &&
- !(request.imageStatus & request.STATUS_ERROR)) {
- this.onCompletedImage = true;
- }
-
- this.mediaURL = this.target.currentURI.spec;
-
- var descURL = this.target.getAttribute("longdesc");
- if (descURL) {
- this.imageDescURL = makeURLAbsolute(this.ownerDoc.body.baseURI, descURL);
- }
- } else if (this.target instanceof HTMLCanvasElement) {
- this.onCanvas = true;
- } else if (this.target instanceof HTMLVideoElement) {
- let mediaURL = this.target.currentSrc || this.target.src;
- if (this.isMediaURLReusable(mediaURL)) {
- this.mediaURL = mediaURL;
- }
- if (this._isProprietaryDRM()) {
- this.onDRMMedia = true;
- }
- // Firefox always creates a HTMLVideoElement when loading an ogg file
- // directly. If the media is actually audio, be smarter and provide a
- // context menu with audio operations.
- if (this.target.readyState >= this.target.HAVE_METADATA &&
- (this.target.videoWidth == 0 || this.target.videoHeight == 0)) {
- this.onAudio = true;
- } else {
- this.onVideo = true;
- }
- } else if (this.target instanceof HTMLAudioElement) {
- this.onAudio = true;
- let mediaURL = this.target.currentSrc || this.target.src;
- if (this.isMediaURLReusable(mediaURL)) {
- this.mediaURL = mediaURL;
- }
- if (this._isProprietaryDRM()) {
- this.onDRMMedia = true;
- }
- } else if (editFlags & (SpellCheckHelper.INPUT | SpellCheckHelper.TEXTAREA)) {
- this.onTextInput = (editFlags & SpellCheckHelper.TEXTINPUT) !== 0;
- this.onNumeric = (editFlags & SpellCheckHelper.NUMERIC) !== 0;
- this.onEditableArea = (editFlags & SpellCheckHelper.EDITABLE) !== 0;
- this.onPassword = (editFlags & SpellCheckHelper.PASSWORD) !== 0;
- if (this.onEditableArea) {
- if (this.isRemote) {
- InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
- } else {
- InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
- InlineSpellCheckerUI.initFromEvent(rangeParent, rangeOffset);
- }
- }
- this.onKeywordField = (editFlags & SpellCheckHelper.KEYWORD);
- } else if (this.target instanceof HTMLHtmlElement) {
- var bodyElt = this.ownerDoc.body;
- if (bodyElt) {
- let computedURL;
- try {
- computedURL = this.getComputedURL(bodyElt, "background-image");
- this._hasMultipleBGImages = false;
- } catch (e) {
- this._hasMultipleBGImages = true;
- }
- if (computedURL) {
- this.hasBGImage = true;
- this.bgImageURL = makeURLAbsolute(bodyElt.baseURI,
- computedURL);
- }
- }
- } else if ((this.target instanceof HTMLEmbedElement ||
- this.target instanceof HTMLObjectElement) &&
- this.target.displayedType == HTMLObjectElement.TYPE_NULL &&
- this.target.pluginFallbackType == HTMLObjectElement.PLUGIN_CLICK_TO_PLAY) {
- this.onCTPPlugin = true;
- }
-
- this.canSpellCheck = this._isSpellCheckEnabled(this.target);
- },
-
- /**
- * Sets up the parts of the context menu for when when nodes have children.
- *
- * @param {Integer} editFlags The edit flags for the node. See SpellCheckHelper
- * for the details.
- * @param {nsIDOMNode} rangeParent The parent node for where the selection ends.
- * @param {Integer} rangeOffset The end position of where the selction ends.
- */
- _setTargetForNodesWithChildren(editFlags, rangeParent, rangeOffset) {
- // Second, bubble out, looking for items of interest that can have childen.
- // Always pick the innermost link, background image, etc.
- var elem = this.target;
- while (elem) {
- if (elem.nodeType == Node.ELEMENT_NODE) {
- // Link?
- const XLINKNS = "http://www.w3.org/1999/xlink";
- if (!this.onLink &&
- // Be consistent with what hrefAndLinkNodeForClickEvent
- // does in browser.js
- (this._isXULTextLinkLabel(elem) ||
- (elem instanceof HTMLAnchorElement && elem.href) ||
- (elem instanceof SVGAElement &&
- (elem.href || elem.hasAttributeNS(XLINKNS, "href"))) ||
- (elem instanceof HTMLAreaElement && elem.href) ||
- elem instanceof HTMLLinkElement ||
- elem.getAttributeNS(XLINKNS, "type") == "simple")) {
-
- // Target is a link or a descendant of a link.
- this.onLink = true;
-
- // Remember corresponding element.
- this.link = elem;
- this.linkURL = this.getLinkURL();
- this.linkURI = this.getLinkURI();
- this.linkTextStr = this.getLinkText();
- this.linkProtocol = this.getLinkProtocol();
- this.onMailtoLink = (this.linkProtocol == "mailto");
- this.onMozExtLink = (this.linkProtocol == "moz-extension");
- this.onSaveableLink = this.isLinkSaveable( this.link );
- this.linkHasNoReferrer = BrowserUtils.linkHasNoReferrer(elem);
- try {
- if (elem.download) {
- // Ignore download attribute on cross-origin links
- this.principal.checkMayLoad(this.linkURI, false, true);
- this.linkDownload = elem.download;
- }
- } catch (ex) {}
- }
-
- // Background image? Don't bother if we've already found a
- // background image further down the hierarchy. Otherwise,
- // we look for the computed background-image style.
- if (!this.hasBGImage &&
- !this._hasMultipleBGImages) {
- let bgImgUrl;
- try {
- bgImgUrl = this.getComputedURL(elem, "background-image");
- this._hasMultipleBGImages = false;
- } catch (e) {
- this._hasMultipleBGImages = true;
- }
- if (bgImgUrl) {
- this.hasBGImage = true;
- this.bgImageURL = makeURLAbsolute(elem.baseURI,
- bgImgUrl);
- }
- }
- }
-
- elem = elem.parentNode;
- }
-
- // See if the user clicked on MathML
- const NS_MathML = "http://www.w3.org/1998/Math/MathML";
- if ((this.target.nodeType == Node.TEXT_NODE &&
- this.target.parentNode.namespaceURI == NS_MathML)
- || (this.target.namespaceURI == NS_MathML))
- this.onMathML = true;
-
- // See if the user clicked in a frame.
- var docDefaultView = this.ownerDoc.defaultView;
- if (docDefaultView != docDefaultView.top) {
- this.inFrame = true;
-
- if (this.ownerDoc.isSrcdocDocument) {
- this.inSrcdocFrame = true;
- }
- }
-
- // if the document is editable, show context menu like in text inputs
- if (!this.onEditableArea) {
- if (editFlags & SpellCheckHelper.CONTENTEDITABLE) {
- // If this.onEditableArea is false but editFlags is CONTENTEDITABLE, then
- // the document itself must be editable.
- this.onTextInput = true;
- this.onKeywordField = false;
- this.onImage = false;
- this.onLoadedImage = false;
- this.onCompletedImage = false;
- this.onMathML = false;
- this.inFrame = false;
- this.inSrcdocFrame = false;
- this.hasBGImage = false;
- this.isDesignMode = true;
- this.onEditableArea = true;
- if (this.isRemote) {
- InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
- } else {
- var targetWin = this.ownerDoc.defaultView;
- var editingSession = targetWin.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIEditingSession);
- InlineSpellCheckerUI.init(editingSession.getEditorForWindow(targetWin));
- InlineSpellCheckerUI.initFromEvent(rangeParent, rangeOffset);
- }
- var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
- this.showItem("spell-check-enabled", canSpell);
- this.showItem("spell-separator", canSpell);
- }
- }
- },
-
- /**
- * Determines if a node is a XUL Text link.
- *
- * @param {Object} node The object to test.
- * @returns {Boolean} true if the object is a XUL text link.
- */
- _isXULTextLinkLabel(node) {
- const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
- return node.namespaceURI == xulNS &&
- node.tagName == "label" &&
- node.classList.contains("text-link") &&
- node.href;
- },
-
- // Returns the computed style attribute for the given element.
- getComputedStyle(aElem, aProp) {
- return aElem.ownerGlobal
- .getComputedStyle(aElem).getPropertyValue(aProp);
- },
-
- // Returns a "url"-type computed style attribute value, with the url() stripped.
- getComputedURL(aElem, aProp) {
- var url = aElem.ownerGlobal.getComputedStyle(aElem)
- .getPropertyCSSValue(aProp);
- if (url instanceof CSSValueList) {
- if (url.length != 1)
- throw "found multiple URLs";
- url = url[0];
- }
- return url.primitiveType == CSSPrimitiveValue.CSS_URI ?
- url.getStringValue() : null;
- },
-
- // Returns true if clicked-on link targets a resource that can be saved.
- isLinkSaveable(aLink) {
- // We don't do the Right Thing for news/snews yet, so turn them off
- // until we do.
- return this.linkProtocol && !(
- this.linkProtocol == "mailto" ||
- this.linkProtocol == "javascript" ||
- this.linkProtocol == "news" ||
- this.linkProtocol == "snews");
- },
-
- _isSpellCheckEnabled(aNode) {
- // We can always force-enable spellchecking on textboxes
- if (this.isTargetATextBox(aNode)) {
- return true;
- }
- // We can never spell check something which is not content editable
- var editable = aNode.isContentEditable;
- if (!editable && aNode.ownerDocument) {
- editable = aNode.ownerDocument.designMode == "on";
- }
- if (!editable) {
- return false;
- }
- // Otherwise make sure that nothing in the parent chain disables spellchecking
- return aNode.spellcheck;
- },
-
- _isProprietaryDRM() {
- return this.target.isEncrypted && this.target.mediaKeys &&
- this.target.mediaKeys.keySystem != "org.w3.clearkey";
- },
-
_openLinkInParameters(extra) {
let params = { charset: gContextMenuContentData.charSet,
originPrincipal: this.principal,
triggeringPrincipal: this.principal,
referrerURI: gContextMenuContentData.documentURIObject,
referrerPolicy: gContextMenuContentData.referrerPolicy,
frameOuterWindowID: gContextMenuContentData.frameOuterWindowID,
noReferrer: this.linkHasNoReferrer };
@@ -1189,17 +886,17 @@ nsContextMenu.prototype = {
},
viewInfo() {
BrowserPageInfo(gContextMenuContentData.docLocation, null, null, null, this.browser);
},
viewImageInfo() {
BrowserPageInfo(gContextMenuContentData.docLocation, "mediaTab",
- this.target, null, this.browser);
+ this.imageInfo, null, this.browser);
},
viewImageDesc(e) {
urlSecurityCheck(this.imageDescURL,
this.browser.contentPrincipal,
Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
openUILink(this.imageDescURL, e, { disallowInheritPrincipal: true,
referrerURI: gContextMenuContentData.documentURIObject });
@@ -1343,17 +1040,17 @@ nsContextMenu.prototype = {
// Save URL of clicked-on frame.
saveFrame() {
saveBrowser(this.browser, false, this.frameOuterWindowID);
},
// Helper function to wait for appropriate MIME-type headers and
// then prompt the user with a file picker
saveHelper(linkURL, linkText, dialogTitle, bypassCache, doc, docURI,
- windowID, linkDownload) {
+ windowID, linkDownload, isContentWindowPrivate) {
// canonical def in nsURILoader.h
const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
// an object to proxy the data through to
// nsIExternalHelperAppService.doContent, which will wait for the
// appropriate MIME-type headers and then prompt the user with a
// file picker
function saveAsListener() {}
@@ -1402,17 +1099,17 @@ nsContextMenu.prototype = {
},
onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
aStatusCode) {
if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
// do it the old fashioned way, which will pick the best filename
// it can without waiting.
saveURL(linkURL, linkText, dialogTitle, bypassCache, false, docURI,
- doc);
+ doc, isContentWindowPrivate);
}
if (this.extListener)
this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
},
onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
aInputStream,
aOffset, aCount) {
@@ -1491,31 +1188,35 @@ nsContextMenu.prototype = {
// kick off the channel with our proxy object as the listener
channel.asyncOpen2(new saveAsListener());
},
// Save URL of clicked-on link.
saveLink() {
urlSecurityCheck(this.linkURL, this.principal);
+
+ let isContentWindowPrivate = this.isRemote ? this.ownerDoc.isPrivate : undefined;
this.saveHelper(this.linkURL, this.linkTextStr, null, true, this.ownerDoc,
gContextMenuContentData.documentURIObject,
this.frameOuterWindowID,
- this.linkDownload);
+ this.linkDownload,
+ isContentWindowPrivate);
},
// Backwards-compatibility wrapper
saveImage() {
if (this.onCanvas || this.onImage)
this.saveMedia();
},
// Save URL of the clicked upon image, video, or audio.
saveMedia() {
let doc = this.ownerDoc;
+ 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.target).then(function(blobURL) {
saveImageURL(blobURL, "canvas.png", "SaveImageTitle",
true, false, referrerURI, null, null, null,
isPrivate);
@@ -1524,49 +1225,30 @@ nsContextMenu.prototype = {
urlSecurityCheck(this.mediaURL, this.principal);
saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
false, referrerURI, null, gContextMenuContentData.contentType,
gContextMenuContentData.contentDisposition, isPrivate);
} else if (this.onVideo || this.onAudio) {
urlSecurityCheck(this.mediaURL, this.principal);
var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
this.saveHelper(this.mediaURL, null, dialogTitle, false, doc, referrerURI,
- this.frameOuterWindowID, "");
+ this.frameOuterWindowID, "", isContentWindowPrivate);
}
},
// Backwards-compatibility wrapper
sendImage() {
if (this.onCanvas || this.onImage)
this.sendMedia();
},
sendMedia() {
MailIntegration.sendMessage(this.mediaURL, "");
},
- castVideo() {
- CastingApps.openExternal(this.target, window);
- },
-
- populateCastVideoMenu(popup) {
- let videoEl = this.target;
- popup.innerHTML = null;
- let doc = popup.ownerDocument;
- let services = CastingApps.getServicesForVideo(videoEl);
- services.forEach(service => {
- let item = doc.createElement("menuitem");
- item.setAttribute("label", service.friendlyName);
- item.addEventListener("command", event => {
- CastingApps.sendVideoToService(videoEl, service);
- });
- popup.appendChild(item);
- });
- },
-
playPlugin() {
gPluginHandler.contextMenuCommand(this.browser, this.target, "play");
},
hidePlugin() {
gPluginHandler.contextMenuCommand(this.browser, this.target, "hide");
},
@@ -1662,99 +1344,31 @@ nsContextMenu.prototype = {
var attr = attrs.item(i);
node.setAttribute(attr.nodeName, attr.nodeValue);
}
// Voila!
return node;
},
- // Generate fully qualified URL for clicked-on link.
- getLinkURL() {
- var href = this.link.href;
- if (href) {
- // Handle SVG links:
- if (typeof href == "object" && href.animVal) {
- return href.animVal;
- }
- return href;
- }
-
- href = this.link.getAttribute("href") ||
- this.link.getAttributeNS("http://www.w3.org/1999/xlink", "href");
-
- if (!href || !href.match(/\S/)) {
- // Without this we try to save as the current doc,
- // for example, HTML case also throws if empty
- throw "Empty href";
- }
-
- return makeURLAbsolute(this.link.baseURI, href);
- },
-
getLinkURI() {
try {
return makeURI(this.linkURL);
} catch (ex) {
// e.g. empty URL string
}
return null;
},
- getLinkProtocol() {
- if (this.linkURI)
- return this.linkURI.scheme; // can be |undefined|
-
- return null;
- },
-
- // Get text of link.
- getLinkText() {
- var text = gatherTextUnder(this.link);
- if (!text || !text.match(/\S/)) {
- text = this.link.getAttribute("title");
- if (!text || !text.match(/\S/)) {
- text = this.link.getAttribute("alt");
- if (!text || !text.match(/\S/))
- text = this.linkURL;
- }
- }
-
- return text;
- },
-
// Kept for addon compat
linkText() {
return this.linkTextStr;
},
- isMediaURLReusable(aURL) {
- if (aURL.startsWith("blob:")) {
- return URL.isValidURL(aURL);
- }
- return true;
- },
-
- toString() {
- return "contextMenu.target = " + this.target + "\n" +
- "contextMenu.onImage = " + this.onImage + "\n" +
- "contextMenu.onLink = " + this.onLink + "\n" +
- "contextMenu.link = " + this.link + "\n" +
- "contextMenu.inFrame = " + this.inFrame + "\n" +
- "contextMenu.hasBGImage = " + this.hasBGImage + "\n";
- },
-
- isTargetATextBox(node) {
- if (node instanceof HTMLInputElement)
- return node.mozIsTextField(false);
-
- return (node instanceof HTMLTextAreaElement);
- },
-
// Determines whether or not the separator with the specified ID should be
// shown or not by determining if there are any non-hidden items between it
// and the previous separator.
shouldShowSeparator(aSeparatorID) {
var separator = document.getElementById(aSeparatorID);
if (separator) {
var sibling = separator.previousSibling;
while (sibling && sibling.localName != "menuseparator") {
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -343,34 +343,34 @@ function loadPageInfo(frameOuterWindowID
let mm = browser.messageManager;
gStrings["application/rss+xml"] = gBundle.getString("feedRss");
gStrings["application/atom+xml"] = gBundle.getString("feedAtom");
gStrings["text/xml"] = gBundle.getString("feedXML");
gStrings["application/xml"] = gBundle.getString("feedXML");
gStrings["application/rdf+xml"] = gBundle.getString("feedXML");
+ let imageInfo = imageElement;
+
// Look for pageInfoListener in content.js. Sends message to listener with arguments.
- mm.sendAsyncMessage("PageInfo:getData", {strings: gStrings,
- frameOuterWindowID},
- { imageElement });
+ mm.sendAsyncMessage("PageInfo:getData", {strings: gStrings, frameOuterWindowID});
let pageInfoData;
// Get initial pageInfoData needed to display the general, feeds, permission and security tabs.
mm.addMessageListener("PageInfo:data", function onmessage(message) {
mm.removeMessageListener("PageInfo:data", onmessage);
pageInfoData = message.data;
let docInfo = pageInfoData.docInfo;
let windowInfo = pageInfoData.windowInfo;
let uri = makeURI(docInfo.documentURIObject.spec);
let principal = docInfo.principal;
gDocInfo = docInfo;
- gImageElement = pageInfoData.imageInfo;
+ gImageElement = imageInfo;
var titleFormat = windowInfo.isTopWindow ? "pageInfo.page.title"
: "pageInfo.frame.title";
document.title = gBundle.getFormattedString(titleFormat, [docInfo.location]);
document.getElementById("main-window").setAttribute("relatedUrl", docInfo.location);
makeGeneralTab(pageInfoData.metaViewRows, docInfo);
--- a/browser/base/content/test/general/browser_addKeywordSearch.js
+++ b/browser/base/content/test/general/browser_addKeywordSearch.js
@@ -24,17 +24,17 @@ add_task(async function() {
let count = 0;
for (let method of ["GET", "POST"]) {
for (let {desc, action, param } of testData) {
info(`Running ${method} keyword test '${desc}'`);
let id = `keyword-form-${count++}`;
let contextMenu = document.getElementById("contentAreaContextMenu");
let contextMenuPromise =
BrowserTestUtils.waitForEvent(contextMenu, "popupshown")
- .then(() => gContextMenuContentData.popupNode);
+ .then(() => gContextMenuContentData.target);
await ContentTask.spawn(tab.linkedBrowser,
{ action, param, method, id }, async function(args) {
let doc = content.document;
let form = doc.createElement("form");
form.id = args.id;
form.method = args.method;
form.action = args.action;
--- a/browser/base/content/test/general/browser_contextmenu.js
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -18,17 +18,22 @@ const chrome_base = "chrome://mochitests
/* import-globals-from contextmenu_common.js */
Services.scriptloader.loadSubScript(chrome_base + "contextmenu_common.js", this);
// Below are test cases for XUL element
add_task(async function test_xul_text_link_label() {
let url = chrome_base + "subtst_contextmenu_xul.xul";
- await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url,
+ waitForLoad: true,
+ waitForStateStop: true,
+ });
await test_contextmenu("#test-xul-text-link-label",
["context-openlinkintab", true,
...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
// We need a blank entry here because the containers submenu is
// dynamically generated with no ids.
...(hasContainers ? ["", null] : []),
"context-openlink", true,
--- a/browser/base/content/test/pageinfo/browser_pageinfo_image_info.js
+++ b/browser/base/content/test/pageinfo/browser_pageinfo_image_info.js
@@ -1,20 +1,28 @@
/* Make sure that "View Image Info" loads the correct image data */
+function getImageInfo(imageElement) {
+ return {
+ currentSrc: imageElement.currentSrc,
+ width: imageElement.width,
+ height: imageElement.height,
+ imageText: imageElement.title || imageElement.alt
+ };
+}
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
gBrowser.selectedBrowser.addEventListener("load", function() {
var doc = gBrowser.contentDocument;
var testImg = doc.getElementById("test-image");
var pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec,
- "mediaTab", testImg);
+ "mediaTab", getImageInfo(testImg));
pageInfo.addEventListener("load", function() {
pageInfo.onFinished.push(function() {
var pageInfoImg = pageInfo.document.getElementById("thepreviewimage");
pageInfoImg.addEventListener("loadend", function() {
is(pageInfoImg.src, testImg.src, "selected image has the correct source");
is(pageInfoImg.width, testImg.width, "selected image has the correct width");
new file mode 100644
--- /dev/null
+++ b/browser/modules/ContextMenu.jsm
@@ -0,0 +1,1037 @@
+/* -*- 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";
+
+this.EXPORTED_SYMBOLS = ["ContextMenu"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.importGlobalProperties(["URL"]);
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ E10SUtils: "resource:///modules/E10SUtils.jsm",
+ CastingApps: "resource:///modules/CastingApps.jsm",
+ BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
+ PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
+ findCssSelector: "resource://gre/modules/css-selector.js",
+ SpellCheckHelper: "resource://gre/modules/InlineSpellChecker.jsm",
+ LoginManagerContent: "resource://gre/modules/LoginManagerContent.jsm",
+ WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
+ InlineSpellCheckerContent: "resource://gre/modules/InlineSpellCheckerContent.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(this, "PageMenuChild", () => {
+ let tmp = {};
+ Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
+ return new tmp.PageMenuChild();
+});
+
+const messageListeners = {
+ "ContextMenu:BookmarkFrame": function(aMessage) {
+ let frame = this.getTarget(aMessage).ownerDocument;
+
+ this.global.sendAsyncMessage("ContextMenu:BookmarkFrame:Result",
+ { title: frame.title,
+ description: PlacesUIUtils.getDescriptionFromDocument(frame) });
+ },
+
+ "ContextMenu:Canvas:ToBlobURL": function(aMessage) {
+ this.getTarget(aMessage).toBlob((blob) => {
+ let blobURL = URL.createObjectURL(blob);
+ this.global.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;
+ }
+ }
+ );
+ },
+
+ "ContextMenu:ReloadFrame": function(aMessage) {
+ this.getTarget(aMessage).ownerDocument.location.reload();
+ },
+
+ "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;
+ let description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument);
+ let formData = [];
+
+ function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) {
+ if (aIsFormUrlEncoded) {
+ return escape(aName + "=" + aValue);
+ }
+
+ return escape(aName) + "=" + escape(aValue);
+ }
+
+ for (let el of node.form.elements) {
+ if (!el.type) // happens with fieldsets
+ continue;
+
+ if (el == node) {
+ formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) :
+ // Don't escape "%s", just append
+ escapeNameValuePair(el.name, "", false) + "%s");
+ continue;
+ }
+
+ let type = el.type.toLowerCase();
+
+ if (((el instanceof this.content.HTMLInputElement && el.mozIsTextField(true)) ||
+ type == "hidden" || type == "textarea") ||
+ ((type == "checkbox" || type == "radio") && el.checked)) {
+ formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded));
+ } else if (el instanceof this.content.HTMLSelectElement && el.selectedIndex >= 0) {
+ for (let j = 0; j < el.options.length; j++) {
+ if (el.options[j].selected)
+ formData.push(escapeNameValuePair(el.name, el.options[j].value,
+ isURLEncoded));
+ }
+ }
+ }
+
+ let postData;
+
+ if (isURLEncoded) {
+ postData = formData.join("&");
+ } else {
+ let separator = spec.includes("?") ? "&" : "?";
+ spec += separator + formData.join("&");
+ }
+
+ this.global.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData:Result",
+ { spec, title, description, 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.global.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.global.sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
+ { dataUrl, imageName });
+ } catch (e) {
+ Cu.reportError(e);
+ disable = true;
+ }
+ }
+
+ if (disable) {
+ this.global.sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
+ { disable });
+ }
+ },
+};
+
+class ContextMenu {
+ // PUBLIC
+ constructor(global) {
+ this.target = null;
+ this.context = null;
+ this.global = global;
+ this.content = global.content;
+
+ Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService)
+ .addSystemEventListener(global, "contextmenu",
+ this._handleContentContextMenu.bind(this), false);
+
+ Object.keys(messageListeners).forEach(key =>
+ global.addMessageListener(key, messageListeners[key].bind(this))
+ );
+ }
+
+ /**
+ * Returns the event target of the context menu, using a locally stored
+ * reference if possible. If not, and aMessage.objects is defined,
+ * aMessage.objects[aKey] is returned. Otherwise null.
+ * @param {Object} aMessage Message with a objects property
+ * @param {String} aKey Key for the target on aMessage.objects
+ * @return {Object} Context menu target
+ */
+ getTarget(aMessage, aKey = "target") {
+ return this.target || (aMessage.objects && aMessage.objects[aKey]);
+ }
+
+ // PRIVATE
+ _isXULTextLinkLabel(aNode) {
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ return aNode.namespaceURI == XUL_NS &&
+ aNode.tagName == "label" &&
+ aNode.classList.contains("text-link") &&
+ aNode.href;
+ }
+
+ // Generate fully qualified URL for clicked-on link.
+ _getLinkURL() {
+ let href = this.context.link.href;
+
+ if (href) {
+ // Handle SVG links:
+ if (typeof href == "object" && href.animVal) {
+ return href.animVal;
+ }
+
+ return href;
+ }
+
+ href = this.context.link.getAttribute("href") ||
+ this.context.link.getAttributeNS("http://www.w3.org/1999/xlink", "href");
+
+ if (!href || !href.match(/\S/)) {
+ // Without this we try to save as the current doc,
+ // for example, HTML case also throws if empty
+ throw "Empty href";
+ }
+
+ return this._makeURLAbsolute(this.context.link.baseURI, href);
+ }
+
+ _getLinkURI() {
+ try {
+ return Services.io.newURI(this.context.linkURL);
+ } catch (ex) {
+ // e.g. empty URL string
+ }
+
+ return null;
+ }
+
+ // Get text of link.
+ _getLinkText() {
+ let text = this._gatherTextUnder(this.context.link);
+
+ if (!text || !text.match(/\S/)) {
+ text = this.context.link.getAttribute("title");
+ if (!text || !text.match(/\S/)) {
+ text = this.context.link.getAttribute("alt");
+ if (!text || !text.match(/\S/)) {
+ text = this.context.linkURL;
+ }
+ }
+ }
+
+ return text;
+ }
+
+ _getLinkProtocol() {
+ if (this.context.linkURI) {
+ return this.context.linkURI.scheme; // can be |undefined|
+ }
+
+ return null;
+ }
+
+ // Returns true if clicked-on link targets a resource that can be saved.
+ _isLinkSaveable(aLink) {
+ // We don't do the Right Thing for news/snews yet, so turn them off
+ // until we do.
+ return this.context.linkProtocol && !(
+ this.context.linkProtocol == "mailto" ||
+ this.context.linkProtocol == "javascript" ||
+ this.context.linkProtocol == "news" ||
+ this.context.linkProtocol == "snews");
+ }
+
+ // Gather all descendent text under given document node.
+ _gatherTextUnder(root) {
+ let text = "";
+ let node = root.firstChild;
+ let depth = 1;
+ while (node && depth > 0) {
+ // See if this node is text.
+ if (node.nodeType == Ci.nsIDOMNode.TEXT_NODE) {
+ // Add this text to our collection.
+ text += " " + node.data;
+ } else if (node instanceof this.content.HTMLImageElement) {
+ // If it has an "alt" attribute, add that.
+ let altText = node.getAttribute( "alt" );
+ if ( altText && altText != "" ) {
+ text += " " + altText;
+ }
+ }
+ // Find next node to test.
+ // First, see if this node has children.
+ if (node.hasChildNodes()) {
+ // Go to first child.
+ node = node.firstChild;
+ depth++;
+ } else {
+ // No children, try next sibling (or parent next sibling).
+ while (depth > 0 && !node.nextSibling) {
+ node = node.parentNode;
+ depth--;
+ }
+ if (node.nextSibling) {
+ node = node.nextSibling;
+ }
+ }
+ }
+
+ // Strip leading and tailing whitespace.
+ text = text.trim();
+ // Compress remaining whitespace.
+ text = text.replace(/\s+/g, " ");
+ return text;
+ }
+
+ // Returns a "url"-type computed style attribute value, with the url() stripped.
+ _getComputedURL(aElem, aProp) {
+ let url = aElem.ownerGlobal.getComputedStyle(aElem).getPropertyCSSValue(aProp);
+
+ if (url instanceof this.content.CSSValueList) {
+ if (url.length != 1) {
+ throw "found multiple URLs";
+ }
+
+ url = url[0];
+ }
+
+ return url.primitiveType == this.content.CSSPrimitiveValue.CSS_URI ?
+ url.getStringValue() : null;
+ }
+
+ _makeURLAbsolute(aBase, aUrl) {
+ return Services.io.newURI(aUrl, null, Services.io.newURI(aBase)).spec;
+ }
+
+ _isProprietaryDRM() {
+ return this.context.target.isEncrypted && this.context.target.mediaKeys &&
+ this.context.target.mediaKeys.keySystem != "org.w3.clearkey";
+ }
+
+ _isMediaURLReusable(aURL) {
+ if (aURL.startsWith("blob:")) {
+ return URL.isValidURL(aURL);
+ }
+
+ return true;
+ }
+
+ _isTargetATextBox(node) {
+ if (node instanceof this.content.HTMLInputElement) {
+ return node.mozIsTextField(false);
+ }
+
+ return (node instanceof this.content.HTMLTextAreaElement);
+ }
+
+ _isSpellCheckEnabled(aNode) {
+ // We can always force-enable spellchecking on textboxes
+ if (this._isTargetATextBox(aNode)) {
+ return true;
+ }
+
+ // We can never spell check something which is not content editable
+ let editable = aNode.isContentEditable;
+
+ if (!editable && aNode.ownerDocument) {
+ editable = aNode.ownerDocument.designMode == "on";
+ }
+
+ if (!editable) {
+ return false;
+ }
+
+ // Otherwise make sure that nothing in the parent chain disables spellchecking
+ return aNode.spellcheck;
+ }
+
+ _disableSetDesktopBackground(aTarget) {
+ // Disable the Set as Desktop Background menu item if we're still trying
+ // to load the image or the load failed.
+ if (!(aTarget instanceof Ci.nsIImageLoadingContent)) {
+ return true;
+ }
+
+ if (("complete" in aTarget) && !aTarget.complete) {
+ return true;
+ }
+
+ if (aTarget.currentURI.schemeIs("javascript")) {
+ return true;
+ }
+
+ let request = aTarget.QueryInterface(Ci.nsIImageLoadingContent)
+ .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+
+ if (!request) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve the array of CSS selectors corresponding to the provided node. The first item
+ * of the array is the selector of the node in its owner document. Additional items are
+ * used if the node is inside a frame, each representing the CSS selector for finding the
+ * frame element in its parent document.
+ *
+ * This format is expected by DevTools in order to handle the Inspect Node context menu
+ * item.
+ *
+ * @param {aNode}
+ * The node for which the CSS selectors should be computed
+ * @return {Array} array of css selectors (strings).
+ */
+ _getNodeSelectors(aNode) {
+ let selectors = [];
+ while (aNode) {
+ selectors.push(findCssSelector(aNode));
+ aNode = aNode.ownerGlobal.frameElement;
+ }
+
+ return selectors;
+ }
+
+ _handleContentContextMenu(aEvent) {
+ let defaultPrevented = aEvent.defaultPrevented;
+
+ if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
+ let plugin = null;
+
+ try {
+ plugin = aEvent.target.QueryInterface(Ci.nsIObjectLoadingContent);
+ } catch (e) {}
+
+ if (plugin && plugin.displayedType == Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
+ // Don't open a context menu for plugins.
+ return;
+ }
+
+ defaultPrevented = false;
+ }
+
+ if (defaultPrevented) {
+ return;
+ }
+
+ let addonInfo = Object.create(null);
+ let subject = {
+ aEvent,
+ addonInfo,
+ };
+
+ subject.wrappedJSObject = subject;
+ Services.obs.notifyObservers(subject, "content-contextmenu");
+
+ let doc = aEvent.target.ownerDocument;
+ let {
+ mozDocumentURIIfNotForErrorPages: docLocation,
+ characterSet: charSet,
+ baseURI,
+ referrer,
+ referrerPolicy
+ } = doc;
+ docLocation = docLocation && docLocation.spec;
+ let frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView);
+ let loginFillInfo = LoginManagerContent.getFieldContext(aEvent.target);
+
+ // The same-origin check will be done in nsContextMenu.openLinkInTab.
+ let parentAllowsMixedContent = !!this.global.docShell.mixedContentChannel;
+
+ // Get referrer attribute from clicked link and parse it
+ let referrerAttrValue = Services.netUtils.parseAttributePolicyString(aEvent.target.
+ getAttribute("referrerpolicy"));
+
+ if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
+ referrerPolicy = referrerAttrValue;
+ }
+
+ let disableSetDesktopBg = null;
+
+ // Media related cache info parent needs for saving
+ let contentType = null;
+ let contentDisposition = null
+ if (aEvent.target.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
+ aEvent.target instanceof Ci.nsIImageLoadingContent &&
+ aEvent.target.currentURI) {
+ disableSetDesktopBg = this._disableSetDesktopBackground(aEvent.target);
+
+ try {
+ let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .getImgCacheForDocument(doc);
+ let props = imageCache.findEntryProperties(aEvent.target.currentURI, doc);
+
+ try {
+ contentType = props.get("type", Ci.nsISupportsCString).data;
+ } catch (e) {}
+
+ try {
+ contentDisposition = props.get("content-disposition", Ci.nsISupportsCString).data;
+ } catch (e) {}
+ } catch (e) {}
+ }
+
+ let selectionInfo = BrowserUtils.getSelectionDetails(this.content);
+ let loadContext = this.global.docShell.QueryInterface(Ci.nsILoadContext);
+ let userContextId = loadContext.originAttributes.userContextId;
+ let popupNodeSelectors = this._getNodeSelectors(aEvent.target);
+
+ this._setContext(aEvent);
+ let context = this.context;
+ this.target = context.target;
+
+ let spellInfo = null;
+ let editFlags = null;
+ let principal = null;
+ let customMenuItems = null;
+
+ if (context.target) {
+ this._cleanContext();
+ }
+
+ let isRemote = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+
+ if (isRemote) {
+ editFlags = SpellCheckHelper.isEditable(aEvent.target, this.content);
+
+ if (editFlags &
+ (SpellCheckHelper.EDITABLE | SpellCheckHelper.CONTENTEDITABLE)) {
+ spellInfo = InlineSpellCheckerContent.initContextMenu(aEvent, editFlags, this.global);
+ }
+
+ // 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.global.docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
+ .setCommandNode(aEvent.target);
+ aEvent.target.ownerGlobal.updateCommands("contentcontextmenu");
+
+ customMenuItems = PageMenuChild.build(aEvent.target);
+ principal = doc.nodePrincipal;
+ }
+
+ let data = {
+ context,
+ charSet,
+ baseURI,
+ isRemote,
+ referrer,
+ addonInfo,
+ editFlags,
+ principal,
+ spellInfo,
+ contentType,
+ docLocation,
+ loginFillInfo,
+ selectionInfo,
+ userContextId,
+ referrerPolicy,
+ customMenuItems,
+ contentDisposition,
+ frameOuterWindowID,
+ popupNodeSelectors,
+ disableSetDesktopBg,
+ parentAllowsMixedContent,
+ };
+
+ if (isRemote) {
+ this.global.sendAsyncMessage("contextmenu", data);
+ } else {
+ let browser = this.global.docShell.chromeEventHandler;
+ let mainWin = browser.ownerGlobal;
+
+ data.documentURIObject = doc.documentURIObject;
+ data.disableSetDesktopBackground = data.disableSetDesktopBg;
+ delete data.disableSetDesktopBg;
+
+ mainWin.setContextMenuContentData(data);
+ }
+ }
+
+ /**
+ * Some things are not serializable, so we either have to only send
+ * their needed data or regenerate them in nsContextMenu.js
+ * - target and target.ownerDocument
+ * - link
+ * - linkURI
+ */
+ _cleanContext(aEvent) {
+ const context = this.context;
+ const cleanTarget = Object.create(null);
+
+ cleanTarget.ownerDocument = {
+ // used for nsContextMenu.initLeaveDOMFullScreenItems and
+ // nsContextMenu.initMediaPlayerItems
+ fullscreenElement: context.target.ownerDocument.fullscreenElement,
+
+ // used for nsContextMenu.initMiscItems
+ contentType: context.target.ownerDocument.contentType,
+
+ // used for nsContextMenu.saveLink
+ isPrivate: context.target.ownerDocument.isPrivate,
+ };
+
+ // used for nsContextMenu.initMediaPlayerItems
+ Object.assign(cleanTarget, {
+ ended: context.target.ended,
+ muted: context.target.muted,
+ paused: context.target.paused,
+ controls: context.target.controls,
+ duration: context.target.duration,
+ });
+
+ const onMedia = context.onVideo || context.onAudio;
+
+ if (onMedia) {
+ Object.assign(cleanTarget, {
+ loop: context.target.loop,
+ error: context.target.error,
+ networkState: context.target.networkState,
+ playbackRate: context.target.playbackRate,
+ NETWORK_NO_SOURCE: context.target.NETWORK_NO_SOURCE,
+ });
+
+ if (context.onVideo) {
+ Object.assign(cleanTarget, {
+ readyState: context.target.readyState,
+ HAVE_CURRENT_DATA: context.target.HAVE_CURRENT_DATA,
+ });
+ }
+ }
+
+ context.target = cleanTarget;
+
+ if (context.link) {
+ context.link = { href: context.link.href };
+ }
+
+ delete context.linkURI;
+ }
+
+ _setContext(aEvent) {
+ this.context = Object.create(null);
+ const context = this.context;
+
+ context.screenX = aEvent.screenX;
+ context.screenY = aEvent.screenY;
+ context.mozInputSource = aEvent.mozInputSource;
+
+ const node = aEvent.target;
+
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ context.shouldDisplay = true;
+
+ if (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ||
+ // Don't display for XUL element unless <label class="text-link">
+ (node.namespaceURI == XUL_NS && !this._isXULTextLinkLabel(node))) {
+ context.shouldDisplay = false;
+ return;
+ }
+
+ // Initialize context to be sent to nsContextMenu
+ // Keep this consistent with the similar code in nsContextMenu's setContext
+ context.bgImageURL = "";
+ context.imageDescURL = "";
+ context.imageInfo = null;
+ context.mediaURL = "";
+ context.webExtBrowserType = "";
+
+ context.canSpellCheck = false;
+ context.hasBGImage = false;
+ context.hasMultipleBGImages = false;
+ context.isDesignMode = false;
+ context.inFrame = false;
+ context.inSrcdocFrame = false;
+ context.inSyntheticDoc = false;
+ context.inTabBrowser = true;
+ context.inWebExtBrowser = false;
+
+ context.link = null;
+ context.linkDownload = "";
+ context.linkHasNoReferrer = false;
+ context.linkProtocol = "";
+ context.linkTextStr = "";
+ context.linkURL = "";
+ context.linkURI = null;
+
+ context.onAudio = false;
+ context.onCanvas = false;
+ context.onCompletedImage = false;
+ context.onCTPPlugin = false;
+ context.onDRMMedia = false;
+ context.onEditableArea = false;
+ context.onImage = false;
+ context.onKeywordField = false;
+ context.onLink = false;
+ context.onLoadedImage = false;
+ context.onMailtoLink = false;
+ context.onMathML = false;
+ context.onMozExtLink = false;
+ context.onNumeric = false;
+ context.onPassword = false;
+ context.onSaveableLink = false;
+ context.onTextInput = false;
+ context.onVideo = false;
+
+ // Remember the node and its owner document that was clicked
+ // This may be modifed before sending to nsContextMenu
+ context.target = node;
+
+ context.principal = context.target.ownerDocument.nodePrincipal;
+ context.frameOuterWindowID = WebNavigationFrames.getFrameId(context.target.ownerGlobal);
+
+ // Check if we are in a synthetic document (stand alone image, video, etc.).
+ context.inSyntheticDoc = context.target.ownerDocument.mozSyntheticDocument;
+
+ context.shouldInitInlineSpellCheckerUINoChildren = false;
+ context.shouldInitInlineSpellCheckerUIWithChildren = false;
+
+ let editFlags = SpellCheckHelper.isEditable(context.target, this.content);
+ this._setContextForNodesNoChildren(editFlags);
+ this._setContextForNodesWithChildren(editFlags);
+ }
+
+ /**
+ * Sets up the parts of the context menu for when when nodes have no children.
+ *
+ * @param {Integer} editFlags The edit flags for the node. See SpellCheckHelper
+ * for the details.
+ */
+ _setContextForNodesNoChildren(editFlags) {
+ const context = this.context;
+
+ if (context.target.nodeType == Ci.nsIDOMNode.TEXT_NODE) {
+ // For text nodes, look at the parent node to determine the spellcheck attribute.
+ context.canSpellCheck = context.target.parentNode &&
+ this._isSpellCheckEnabled(context.target);
+ return;
+ }
+
+ // We only deal with TEXT_NODE and ELEMENT_NODE in this function, so return
+ // early if we don't have one.
+ if (context.target.nodeType != Ci.nsIDOMNode.ELEMENT_NODE) {
+ return;
+ }
+
+ // See if the user clicked on an image. This check mirrors
+ // nsDocumentViewer::GetInImage. Make sure to update both if this is
+ // changed.
+ if (context.target instanceof Ci.nsIImageLoadingContent &&
+ context.target.currentURI) {
+ context.onImage = true;
+
+ context.imageInfo = {
+ currentSrc: context.target.currentSrc,
+ width: context.target.width,
+ height: context.target.height,
+ imageText: context.target.title || context.target.alt
+ };
+
+ const request = context.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+
+ if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE)) {
+ context.onLoadedImage = true;
+ }
+
+ if (request &&
+ (request.imageStatus & request.STATUS_LOAD_COMPLETE) &&
+ !(request.imageStatus & request.STATUS_ERROR)) {
+ context.onCompletedImage = true;
+ }
+
+ context.mediaURL = context.target.currentURI.spec;
+
+ const descURL = context.target.getAttribute("longdesc");
+
+ if (descURL) {
+ context.imageDescURL = this._makeURLAbsolute(context.target.ownerDocument.body.baseURI,
+ descURL);
+ }
+ } else if (context.target instanceof this.content.HTMLCanvasElement) {
+ context.onCanvas = true;
+ } else if (context.target instanceof this.content.HTMLVideoElement) {
+ const mediaURL = context.target.currentSrc || context.target.src;
+
+ if (this._isMediaURLReusable(mediaURL)) {
+ context.mediaURL = mediaURL;
+ }
+
+ if (this._isProprietaryDRM()) {
+ context.onDRMMedia = true;
+ }
+
+ // Firefox always creates a HTMLVideoElement when loading an ogg file
+ // directly. If the media is actually audio, be smarter and provide a
+ // context menu with audio operations.
+ if (context.target.readyState >= context.target.HAVE_METADATA &&
+ (context.target.videoWidth == 0 || context.target.videoHeight == 0)) {
+ context.onAudio = true;
+ } else {
+ context.onVideo = true;
+ }
+ } else if (context.target instanceof this.content.HTMLAudioElement) {
+ context.onAudio = true;
+ const mediaURL = context.target.currentSrc || context.target.src;
+
+ if (this._isMediaURLReusable(mediaURL)) {
+ context.mediaURL = mediaURL;
+ }
+
+ if (this._isProprietaryDRM()) {
+ context.onDRMMedia = true;
+ }
+ } else if (editFlags & (SpellCheckHelper.INPUT | SpellCheckHelper.TEXTAREA)) {
+ context.onTextInput = (editFlags & SpellCheckHelper.TEXTINPUT) !== 0;
+ context.onNumeric = (editFlags & SpellCheckHelper.NUMERIC) !== 0;
+ context.onEditableArea = (editFlags & SpellCheckHelper.EDITABLE) !== 0;
+ context.onPassword = (editFlags & SpellCheckHelper.PASSWORD) !== 0;
+
+ if (context.onEditableArea) {
+ context.shouldInitInlineSpellCheckerUINoChildren = true;
+ }
+
+ context.onKeywordField = (editFlags & SpellCheckHelper.KEYWORD);
+ } else if (context.target instanceof this.content.HTMLHtmlElement) {
+ const bodyElt = context.target.ownerDocument.body;
+
+ if (bodyElt) {
+ let computedURL;
+
+ try {
+ computedURL = this._getComputedURL(bodyElt, "background-image");
+ context.hasMultipleBGImages = false;
+ } catch (e) {
+ context.hasMultipleBGImages = true;
+ }
+
+ if (computedURL) {
+ context.hasBGImage = true;
+ context.bgImageURL = this._makeURLAbsolute(bodyElt.baseURI,
+ computedURL);
+ }
+ }
+ } 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;
+ }
+
+ context.canSpellCheck = this._isSpellCheckEnabled(context.target);
+ }
+
+ /**
+ * Sets up the parts of the context menu for when when nodes have children.
+ *
+ * @param {Integer} editFlags The edit flags for the node. See SpellCheckHelper
+ * for the details.
+ */
+ _setContextForNodesWithChildren(editFlags) {
+ const context = this.context;
+
+ // Second, bubble out, looking for items of interest that can have childen.
+ // Always pick the innermost link, background image, etc.
+ let elem = context.target;
+
+ while (elem) {
+ if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
+ // Link?
+ const XLINK_NS = "http://www.w3.org/1999/xlink";
+
+ if (!context.onLink &&
+ // Be consistent with what hrefAndLinkNodeForClickEvent
+ // does in browser.js
+ (this._isXULTextLinkLabel(elem) ||
+ (elem instanceof this.content.HTMLAnchorElement && elem.href) ||
+ (elem instanceof this.content.SVGAElement &&
+ (elem.href || elem.hasAttributeNS(XLINK_NS, "href"))) ||
+ (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;
+
+ // Remember corresponding element.
+ context.link = elem;
+ context.linkURL = this._getLinkURL();
+ context.linkURI = this._getLinkURI();
+ context.linkTextStr = this._getLinkText();
+ context.linkProtocol = this._getLinkProtocol();
+ context.onMailtoLink = (context.linkProtocol == "mailto");
+ context.onMozExtLink = (context.linkProtocol == "moz-extension");
+ context.onSaveableLink = this._isLinkSaveable(context.link);
+ context.linkHasNoReferrer = BrowserUtils.linkHasNoReferrer(elem);
+
+ try {
+ if (elem.download) {
+ // Ignore download attribute on cross-origin links
+ context.principal.checkMayLoad(context.linkURI, false, true);
+ context.linkDownload = elem.download;
+ }
+ } catch (ex) {}
+ }
+
+ // Background image? Don't bother if we've already found a
+ // background image further down the hierarchy. Otherwise,
+ // we look for the computed background-image style.
+ if (!context.hasBGImage &&
+ !context.hasMultipleBGImages) {
+ let bgImgUrl = null;
+
+ try {
+ bgImgUrl = this._getComputedURL(elem, "background-image");
+ context.hasMultipleBGImages = false;
+ } catch (e) {
+ context.hasMultipleBGImages = true;
+ }
+
+ if (bgImgUrl) {
+ context.hasBGImage = true;
+ context.bgImageURL = this._makeURLAbsolute(elem.baseURI,
+ bgImgUrl);
+ }
+ }
+ }
+
+ elem = elem.parentNode;
+ }
+
+ // See if the user clicked on MathML
+ const MathML_NS = "http://www.w3.org/1998/Math/MathML";
+
+ if ((context.target.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
+ context.target.parentNode.namespaceURI == MathML_NS) ||
+ (context.target.namespaceURI == MathML_NS)) {
+ context.onMathML = true;
+ }
+
+ // See if the user clicked in a frame.
+ const docDefaultView = context.target.ownerGlobal;
+
+ if (docDefaultView != docDefaultView.top) {
+ context.inFrame = true;
+
+ if (context.target.ownerDocument.isSrcdocDocument) {
+ context.inSrcdocFrame = true;
+ }
+ }
+
+ // if the document is editable, show context menu like in text inputs
+ if (!context.onEditableArea) {
+ if (editFlags & SpellCheckHelper.CONTENTEDITABLE) {
+ // If this._onEditableArea is false but editFlags is CONTENTEDITABLE, then
+ // the document itself must be editable.
+ context.onTextInput = true;
+ context.onKeywordField = false;
+ context.onImage = false;
+ context.onLoadedImage = false;
+ context.onCompletedImage = false;
+ context.onMathML = false;
+ context.inFrame = false;
+ context.inSrcdocFrame = false;
+ context.hasBGImage = false;
+ context.isDesignMode = true;
+ context.onEditableArea = true;
+ context.shouldInitInlineSpellCheckerUIWithChildren = true;
+ }
+ }
+ }
+}
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -20,17 +20,17 @@ XPCOMUtils.defineLazyGetter(this, "gNavi
return Services.strings.createBundle(url);
});
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
this.PluginContent = function(global) {
this.init(global);
-}
+};
const FLASH_MIME_TYPE = "application/x-shockwave-flash";
const REPLACEMENT_STYLE_SHEET = Services.io.newURI("chrome://pluginproblem/content/pluginReplaceBinding.css");
PluginContent.prototype = {
init(global) {
this.global = global;
// Need to hold onto the content window or else it'll get destroyed
@@ -93,22 +93,24 @@ PluginContent.prototype = {
switch (msg.name) {
case "BrowserPlugins:ActivatePlugins":
this.activatePlugins(msg.data.pluginInfo, msg.data.newState);
break;
case "BrowserPlugins:NotificationShown":
setTimeout(() => this.updateNotificationUI(), 0);
break;
case "BrowserPlugins:ContextMenuCommand":
+ let contextMenu = this.global.contextMenu;
+
switch (msg.data.command) {
case "play":
- this._showClickToPlayNotification(msg.objects.plugin, true);
+ this._showClickToPlayNotification(contextMenu.getTarget(msg, "plugin"), true);
break;
case "hide":
- this.hideClickToPlayOverlay(msg.objects.plugin);
+ this.hideClickToPlayOverlay(contextMenu.getTarget(msg, "plugin"));
break;
}
break;
case "BrowserPlugins:NPAPIPluginProcessCrashed":
this.NPAPIPluginProcessCrashed({
pluginName: msg.data.pluginName,
runID: msg.data.runID,
state: msg.data.state,
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -130,16 +130,17 @@ EXTRA_JS_MODULES += [
'BrowserUsageTelemetry.jsm',
'CastingApps.jsm',
'ContentClick.jsm',
'ContentCrashHandlers.jsm',
'ContentLinkHandler.jsm',
'ContentObservers.js',
'ContentSearch.jsm',
'ContentWebRTC.jsm',
+ 'ContextMenu.jsm',
'DirectoryLinksProvider.jsm',
'E10SUtils.jsm',
'ExtensionsUI.jsm',
'Feeds.jsm',
'FormSubmitObserver.jsm',
'FormValidationHandler.jsm',
'LaterRun.jsm',
'offlineAppCache.jsm',