Bug 1356415 - get CSS selector in content for contextmenu to fix devtools CPOW;r?mixedpuppy
MozReview-Commit-ID: 3TYaYcS1W0h
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -46,16 +46,21 @@ XPCOMUtils.defineLazyGetter(this, "PageM
let tmp = {};
Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
return new tmp.PageMenuChild();
});
XPCOMUtils.defineLazyModuleGetter(this, "WebNavigationFrames",
"resource://gre/modules/WebNavigationFrames.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
"resource:///modules/Feeds.jsm");
+XPCOMUtils.defineLazyGetter(this, "findCssSelector", () => {
+ let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ let { findCssSelector } = require("devtools/shared/inspector/css-logic");
+ return findCssSelector;
+});
Cu.importGlobalProperties(["URL"]);
// TabChildGlobal
var global = this;
// Load the form validation popup handler
var formSubmitObserver = new FormSubmitObserver(content, this);
@@ -159,16 +164,17 @@ var handleContentContextMenu = function(
} 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);
@@ -178,31 +184,37 @@ var handleContentContextMenu = function(
// 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 },
- { event, popupNode: event.target });
+ 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.gContextMenuContentData = {
isRemote: false,
event,
popupNode: event.target,
+ popupNodeSelectors,
browser,
addonInfo,
documentURIObject: doc.documentURIObject,
docLocation,
charSet,
referrer,
referrerPolicy,
contentType,
@@ -240,16 +252,38 @@ const MOZILLA_PKIX_ERROR_NOT_YET_VALID_I
const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
const PREF_SSL_IMPACT = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
return prefs.concat(Services.prefs.getChildList(root));
}, []);
+/**
+ * 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 "";
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -28,16 +28,17 @@ function openContextMenu(aMessage) {
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,
+ popupNodeSelectors: data.popupNodeSelectors,
browser,
editFlags: data.editFlags,
spellInfo,
principal: data.principal,
customMenuItems: data.customMenuItems,
addonInfo: data.addonInfo,
documentURIObject,
docLocation: data.docLocation,
@@ -607,17 +608,17 @@ nsContextMenu.prototype = {
openPasswordManager() {
LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
},
inspectNode() {
let gBrowser = this.browser.ownerGlobal.gBrowser;
let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
let { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
- return gDevToolsBrowser.inspectNode(gBrowser.selectedTab, this.target);
+ return gDevToolsBrowser.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.
@@ -685,16 +686,22 @@ nsContextMenu.prototype = {
}
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) {
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -19,17 +19,16 @@ const Telemetry = require("devtools/clie
const {gDevTools} = require("./devtools");
// Load target and toolbox lazily as they need gDevTools to be fully initialized
loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
loader.lazyRequireGetter(this, "BrowserMenus", "devtools/client/framework/browser-menus");
-loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
loader.lazyRequireGetter(this, "appendStyleSheet", "devtools/client/shared/stylesheet-utils", true);
loader.lazyImporter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
loader.lazyImporter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm");
loader.lazyImporter(this, "LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm");
const {LocalizationHelper} = require("devtools/shared/l10n");
const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
@@ -302,41 +301,34 @@ var gDevToolsBrowser = exports.gDevTools
let win = Services.wm.getMostRecentWindow("devtools:webide");
if (win) {
win.focus();
} else {
Services.ww.openWindow(null, "chrome://webide/content/", "webide", "chrome,centerscreen,resizable", null);
}
},
- async inspectNode(tab, node) {
+ async inspectNode(tab, nodeSelectors) {
let target = TargetFactory.forTab(tab);
- // Generate a cross iframes query selector
- let selectors = [];
- while (node) {
- selectors.push(findCssSelector(node));
- node = node.ownerDocument.defaultView.frameElement;
- }
-
let toolbox = await gDevTools.showToolbox(target, "inspector");
let inspector = toolbox.getCurrentPanel();
// new-node-front tells us when the node has been selected, whether the
// browser is remote or not.
let onNewNode = inspector.selection.once("new-node-front");
// Evaluate the cross iframes query selectors
async function querySelectors(nodeFront) {
- let selector = selectors.pop();
+ let selector = nodeSelectors.pop();
if (!selector) {
return nodeFront;
}
nodeFront = await inspector.walker.querySelector(nodeFront, selector);
- if (selectors.length > 0) {
+ if (nodeSelectors.length > 0) {
let { nodes } = await inspector.walker.children(nodeFront);
// This is the NodeFront for the document node inside the iframe
nodeFront = nodes[0];
}
return querySelectors(nodeFront);
}
let nodeFront = await inspector.walker.getRootNode();
nodeFront = await querySelectors(nodeFront);