Bug 952277 - DOMNodes can be highlighted and selected from the debugger [Australis]; r=past, vporof

This commit is contained in:
Patrick Brosset 2014-02-01 10:24:44 +01:00
parent b4e1527c16
commit 23e6c7b9fc
47 changed files with 666 additions and 226 deletions

View File

@ -1730,7 +1730,6 @@ function VariableBubbleView() {
this._onMouseMove = this._onMouseMove.bind(this);
this._onMouseLeave = this._onMouseLeave.bind(this);
this._onMouseScroll = this._onMouseScroll.bind(this);
this._onPopupHiding = this._onPopupHiding.bind(this);
}
@ -1741,16 +1740,23 @@ VariableBubbleView.prototype = {
initialize: function() {
dumpn("Initializing the VariableBubbleView");
this._tooltip = new Tooltip(document);
this._editorContainer = document.getElementById("editor");
this._tooltip.defaultPosition = EDITOR_VARIABLE_POPUP_POSITION;
this._tooltip.defaultShowDelay = EDITOR_VARIABLE_HOVER_DELAY;
this._tooltip.panel.addEventListener("popuphiding", this._onPopupHiding);
this._editorContainer.addEventListener("mousemove", this._onMouseMove, false);
this._editorContainer.addEventListener("mouseleave", this._onMouseLeave, false);
this._editorContainer.addEventListener("scroll", this._onMouseScroll, true);
this._tooltip = new Tooltip(document, {
closeOnEvents: [{
emitter: DebuggerController._toolbox,
event: "select"
}, {
emitter: this._editorContainer,
event: "scroll",
useCapture: true
}]
});
this._tooltip.defaultPosition = EDITOR_VARIABLE_POPUP_POSITION;
this._tooltip.defaultShowDelay = EDITOR_VARIABLE_HOVER_DELAY;
this._tooltip.panel.addEventListener("popuphiding", this._onPopupHiding);
},
/**
@ -1762,7 +1768,6 @@ VariableBubbleView.prototype = {
this._tooltip.panel.removeEventListener("popuphiding", this._onPopupHiding);
this._editorContainer.removeEventListener("mousemove", this._onMouseMove, false);
this._editorContainer.removeEventListener("mouseleave", this._onMouseLeave, false);
this._editorContainer.removeEventListener("scroll", this._onMouseScroll, true);
},
/**
@ -1908,7 +1913,7 @@ VariableBubbleView.prototype = {
DebuggerView.VariableBubble.hideContents();
DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
}
}]);
}], DebuggerController._toolbox);
}
this._tooltip.show(this._markedText.anchor);
@ -1974,13 +1979,6 @@ VariableBubbleView.prototype = {
clearNamedTimeout("editor-mouse-move");
},
/**
* The mousescroll listener for the source editor container node.
*/
_onMouseScroll: function() {
this.hideContents();
},
/**
* Listener handling the popup hiding event.
*/

View File

@ -169,6 +169,10 @@ let DebuggerView = {
lazyEmpty: true
});
// Attach the current toolbox to the VView so it can link DOMNodes to
// the inspector/highlighter
this.Variables.toolbox = DebuggerController._toolbox;
// Attach a controller that handles interfacing with the debugger protocol.
VariablesViewController.attach(this.Variables, {
getEnvironmentClient: aObject => gThreadClient.environment(aObject),

View File

@ -20,6 +20,7 @@ function DebuggerPanel(iframeWindow, toolbox) {
this._controller = this.panelWin.DebuggerController;
this._view._hostType = this._toolbox.hostType;
this._controller._target = this.target;
this._controller._toolbox = this._toolbox;
this.handleHostChanged = this.handleHostChanged.bind(this);
this.highlightWhenPaused = this.highlightWhenPaused.bind(this);

View File

@ -100,7 +100,7 @@ function testExpandVariables() {
is(thisVar.get("document").target.querySelector(".value").getAttribute("value"),
"HTMLDocument \u2192 doc_frame-parameters.html",
"Should have the right property value for 'document'.");
ok(thisVar.get("document").target.querySelector(".value").className.contains("token-other"),
ok(thisVar.get("document").target.querySelector(".value").className.contains("token-domnode"),
"Should have the right token class for 'document'.");
let argsProps = argsVar.target.querySelectorAll(".variables-view-property");

View File

@ -64,7 +64,7 @@ function performTest() {
"Should have the right property name for 'button'.");
is(buttonVar.target.querySelector(".value").getAttribute("value"), "<button>",
"Should have the right property value for 'button'.");
ok(buttonVar.target.querySelector(".value").className.contains("token-other"),
ok(buttonVar.target.querySelector(".value").className.contains("token-domnode"),
"Should have the right token class for 'button'.");
is(buttonAsProtoVar.target.querySelector(".name").getAttribute("value"), "buttonAsProto",
@ -79,7 +79,7 @@ function performTest() {
is(documentVar.target.querySelector(".value").getAttribute("value"),
"HTMLDocument \u2192 doc_frame-parameters.html",
"Should have the right property value for 'document'.");
ok(documentVar.target.querySelector(".value").className.contains("token-other"),
ok(documentVar.target.querySelector(".value").className.contains("token-domnode"),
"Should have the right token class for 'document'.");
is(buttonVar.expanded, false,
@ -147,7 +147,7 @@ function performTest() {
"Should have the right property name for '__proto__'.");
is(buttonAsProtoProtoVar.target.querySelector(".value").getAttribute("value"), "<button>",
"Should have the right property value for '__proto__'.");
ok(buttonAsProtoProtoVar.target.querySelector(".value").className.contains("token-other"),
ok(buttonAsProtoProtoVar.target.querySelector(".value").className.contains("token-domnode"),
"Should have the right token class for '__proto__'.");
is(documentProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__",

View File

@ -64,22 +64,6 @@ function test()
keysetMap.inspector.synthesizeKey();
}
function moveMouseOver(aElement, aInspector, cb)
{
EventUtils.synthesizeMouse(aElement, 2, 2, {type: "mousemove"},
aElement.ownerDocument.defaultView);
aInspector.toolbox.once("picker-node-hovered", () => {
executeSoon(cb);
});
}
function isHighlighting()
{
let outline = gBrowser.selectedBrowser.parentNode
.querySelector(".highlighter-container .highlighter-outline");
return outline && !outline.hasAttribute("hidden");
}
function inspectorShouldBeOpenAndHighlighting(aInspector, aToolbox)
{
is (aToolbox.currentToolId, "inspector", "Correct tool has been loaded");

View File

@ -73,7 +73,7 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
this._refreshHostTitle = this._refreshHostTitle.bind(this);
this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this)
this.destroy = this.destroy.bind(this);
this.stopPicker = this.stopPicker.bind(this);
this.highlighterUtils = new ToolboxHighlighterUtils(this);
this._target.on("close", this.destroy);
@ -192,7 +192,7 @@ Toolbox.prototype = {
* initialized first. Use `initInspector()` if needed.
*/
get highlighter() {
if (this.isRemoteHighlightable) {
if (this.highlighterUtils.isRemoteHighlightable) {
return this._highlighter;
} else {
return null;
@ -557,8 +557,8 @@ Toolbox.prototype = {
let container = this.doc.querySelector("#toolbox-buttons");
container.appendChild(this._pickerButton);
this.togglePicker = this.togglePicker.bind(this);
this._pickerButton.addEventListener("command", this.togglePicker, false);
this._togglePicker = this.highlighterUtils.togglePicker.bind(this.highlighterUtils);
this._pickerButton.addEventListener("command", this._togglePicker, false);
},
/**
@ -1036,7 +1036,7 @@ Toolbox.prototype = {
this._inspector.getWalker().then(walker => {
this._walker = walker;
this._selection = new Selection(this._walker);
if (this.isRemoteHighlightable) {
if (this.highlighterUtils.isRemoteHighlightable) {
this._inspector.getHighlighter().then(highlighter => {
this._highlighter = highlighter;
deferred.resolve();
@ -1085,98 +1085,6 @@ Toolbox.prototype = {
return deferred.promise;
},
/**
* Start/stop the element picker on the debuggee target.
*/
togglePicker: function() {
if (this._isPicking) {
return this.stopPicker();
} else {
return this.startPicker();
}
},
get isRemoteHighlightable() {
return this._target.client.traits.highlightable;
},
/**
* Start the element picker on the debuggee target.
* This will request the inspector actor to start listening for mouse/touch
* events on the target to highlight the hovered/picked element.
* Depending on the server-side capabilities, this may fire events when nodes
* are hovered.
* @return A promise that resolves when the picker has started
*/
startPicker: function() {
let deferred = promise.defer();
let done = () => {
this.emit("picker-started");
this.on("select", this.stopPicker);
deferred.resolve();
};
promise.all([
this.initInspector(),
this.selectTool("inspector")
]).then(() => {
this._isPicking = true;
this._pickerButton.setAttribute("checked", "true");
if (this.isRemoteHighlightable) {
this.highlighter.pick().then(done);
this._onPickerNodeHovered = res => {
this.emit("picker-node-hovered", res.node);
};
this.walker.on("picker-node-hovered", this._onPickerNodeHovered);
this._onPickerNodePicked = res => {
this.selection.setNodeFront(res.node, "picker-node-picked");
this.stopPicker();
};
this.walker.on("picker-node-picked", this._onPickerNodePicked);
} else {
this.walker.pick().then(node => {
this.selection.setNodeFront(node, "picker-node-picked");
this.stopPicker();
});
done();
}
});
return deferred.promise;
},
/**
* Stop the element picker
* @return A promise that resolves when the picker has stopped
*/
stopPicker: function() {
let deferred = promise.defer();
let done = () => {
this.emit("picker-stopped");
this.off("select", this.stopPicker);
deferred.resolve();
};
this.initInspector().then(() => {
this._isPicking = false;
this._pickerButton.removeAttribute("checked");
if (this.isRemoteHighlightable) {
this.highlighter.cancelPick().then(done);
this.walker.off("picker-node-hovered", this._onPickerNodeHovered);
this.walker.off("picker-node-picked", this._onPickerNodePicked);
} else {
this.walker.cancelPick().then(done);
}
});
return deferred.promise;
},
/**
* Get the toolbox's notification box
*
@ -1228,7 +1136,7 @@ Toolbox.prototype = {
outstanding.push(this.destroyInspector());
// Removing buttons
this._pickerButton.removeEventListener("command", this.togglePicker, false);
this._pickerButton.removeEventListener("command", this._togglePicker, false);
this._pickerButton = null;
let container = this.doc.getElementById("toolbox-buttons");
while (container.firstChild) {
@ -1260,3 +1168,184 @@ Toolbox.prototype = {
}).then(null, console.error);
}
};
/**
* The ToolboxHighlighterUtils is what you should use for anything related to
* node highlighting and picking.
* It encapsulates the logic to connecting to the HighlighterActor.
*/
function ToolboxHighlighterUtils(toolbox) {
this.toolbox = toolbox;
this._onPickerNodeHovered = this._onPickerNodeHovered.bind(this);
this._onPickerNodePicked = this._onPickerNodePicked.bind(this);
this.stopPicker = this.stopPicker.bind(this);
}
ToolboxHighlighterUtils.prototype = {
/**
* Indicates whether the highlighter actor exists on the server.
*/
get isRemoteHighlightable() {
return this.toolbox._target.client.traits.highlightable;
},
/**
* Start/stop the element picker on the debuggee target.
*/
togglePicker: function() {
if (this._isPicking) {
return this.stopPicker();
} else {
return this.startPicker();
}
},
_onPickerNodeHovered: function(res) {
this.toolbox.emit("picker-node-hovered", res.node);
},
_onPickerNodePicked: function(res) {
this.toolbox.selection.setNodeFront(res.node, "picker-node-picked");
this.stopPicker();
},
/**
* Start the element picker on the debuggee target.
* This will request the inspector actor to start listening for mouse/touch
* events on the target to highlight the hovered/picked element.
* Depending on the server-side capabilities, this may fire events when nodes
* are hovered.
* @return A promise that resolves when the picker has started
*/
startPicker: function() {
let deferred = promise.defer();
let done = () => {
this.toolbox.emit("picker-started");
this.toolbox.on("select", this.stopPicker);
deferred.resolve();
};
promise.all([
this.toolbox.initInspector(),
this.toolbox.selectTool("inspector")
]).then(() => {
this._isPicking = true;
this.toolbox._pickerButton.setAttribute("checked", "true");
if (this.isRemoteHighlightable) {
this.toolbox.highlighter.pick().then(done);
this.toolbox.walker.on("picker-node-hovered", this._onPickerNodeHovered);
this.toolbox.walker.on("picker-node-picked", this._onPickerNodePicked);
} else {
this.toolbox.walker.pick().then(node => {
this.toolbox.selection.setNodeFront(node, "picker-node-picked");
this.stopPicker();
});
done();
}
});
return deferred.promise;
},
/**
* Stop the element picker
* @return A promise that resolves when the picker has stopped
*/
stopPicker: function() {
let deferred = promise.defer();
let done = () => {
this.toolbox.emit("picker-stopped");
this.toolbox.off("select", this.stopPicker);
deferred.resolve();
};
this.toolbox.initInspector().then(() => {
this._isPicking = false;
this.toolbox._pickerButton.removeAttribute("checked");
if (this.isRemoteHighlightable) {
this.toolbox.highlighter.cancelPick().then(done);
this.toolbox.walker.off("picker-node-hovered", this._onPickerNodeHovered);
this.toolbox.walker.off("picker-node-picked", this._onPickerNodePicked);
} else {
this.toolbox.walker.cancelPick().then(done);
}
});
return deferred.promise;
},
/**
* Show the box model highlighter on a node, given its NodeFront (this type
* of front is normally returned by the WalkerActor).
* @return a promise that resolves to the nodeFront when the node has been
* highlit
*/
highlightNodeFront: function(nodeFront, options={}) {
let deferred = promise.defer();
// If the remote highlighter exists on the target, use it
if (this.isRemoteHighlightable) {
this.toolbox.initInspector().then(() => {
this.toolbox.highlighter.showBoxModel(nodeFront, options).then(() => {
this.toolbox.emit("node-highlight", nodeFront);
deferred.resolve(nodeFront);
});
});
}
// Else, revert to the "older" version of the highlighter in the walker
// actor
else {
this.toolbox.walker.highlight(nodeFront).then(() => {
this.toolbox.emit("node-highlight", nodeFront);
deferred.resolve(nodeFront);
});
}
return deferred.promise;
},
/**
* This is a convenience method in case you don't have a nodeFront but a
* valueGrip. This is often the case with VariablesView properties.
* This method will simply translate the grip into a nodeFront and call
* highlightNodeFront
* @return a promise that resolves to the nodeFront when the node has been
* highlit
*/
highlightDomValueGrip: function(valueGrip, options={}) {
return this._translateGripToNodeFront(valueGrip).then(nodeFront => {
if (nodeFront) {
return this.highlightNodeFront(nodeFront, options);
} else {
return promise.reject();
}
});
},
_translateGripToNodeFront: function(grip) {
return this.toolbox.initInspector().then(() => {
return this.toolbox.walker.getNodeActorFromObjectActor(grip.actor);
});
},
/**
* Hide the highlighter.
* @return a promise that resolves when the highlighter is hidden
*/
unhighlight: function() {
if (this.isRemoteHighlightable) {
// If the remote highlighter exists on the target, use it
return this.toolbox.initInspector().then(() => {
return this.toolbox.highlighter.hideBoxModel();
});
} else {
// If not, no need to unhighlight as the older highlight method uses a
// setTimeout to hide itself
return promise.resolve();
}
}
};

View File

@ -47,7 +47,7 @@ function test() {
let container = getContainerForRawNode(inspector.markup, doc.querySelector("h1"));
EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
inspector.markup.doc.defaultView);
inspector.markup.once("node-highlight", deferred.resolve);
inspector.toolbox.once("node-highlight", deferred.resolve);
return deferred.promise;
}

View File

@ -58,7 +58,7 @@ function test()
function runTests()
{
inspector.toolbox.startPicker().then(() => {
inspector.toolbox.highlighterUtils.startPicker().then(() => {
moveMouseOver(iframeNode, 1, 1, isTheIframeHighlighted);
});
}
@ -86,7 +86,7 @@ function test()
let outlineRect = getHighlighterOutlineRect();
is(outlineRect.height, 184, "highlighter height");
inspector.toolbox.stopPicker().then(() => {
inspector.toolbox.highlighterUtils.stopPicker().then(() => {
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);
finishUp();

View File

@ -15,7 +15,7 @@ function test() {
}
function showHighlighter(cb) {
inspector.toolbox.startPicker().then(() => {
inspector.toolbox.highlighterUtils.startPicker().then(() => {
EventUtils.synthesizeMouse(content.document.body, 1, 1,
{type: "mousemove"}, content);
inspector.toolbox.once("picker-node-hovered", () => {
@ -63,7 +63,7 @@ function test() {
is(iframeLoads, 2, "iframe loads");
ok(checksAfterLoads, "the Inspector tests got the chance to run after iframe reloads");
inspector.toolbox.stopPicker().then(() => {
inspector.toolbox.highlighterUtils.stopPicker().then(() => {
iframe = null;
gBrowser.removeCurrentTab();
executeSoon(finish);

View File

@ -24,7 +24,7 @@ function startTests() {
Task.spawn(function() {
yield openToolbox();
yield startPickerAndAssertSwitchToInspector();
yield toolbox.stopPicker();
yield toolbox.highlighterUtils.stopPicker();
finishTests();
}).then(null, Cu.reportError);

View File

@ -87,7 +87,7 @@ function hoverContainer(container) {
let deferred = promise.defer();
EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
markupView.doc.defaultView);
inspector.markup.once("node-highlight", deferred.resolve);
inspector.toolbox.once("node-highlight", deferred.resolve);
return deferred.promise;
}

View File

@ -33,7 +33,7 @@ function test() {
});
Task.spawn(function() {
yield toolbox.startPicker();
yield toolbox.highlighterUtils.startPicker();
yield toolbox.selectNextTool();
}).then(null, Cu.reportError);
});

View File

@ -52,7 +52,7 @@ function createDocument() {
inspector.selection.setNode(div, null);
inspector.once("inspector-updated", () => {
getHighlighterOutline().setAttribute("disable-transitions", "true");
inspector.toolbox.startPicker().then(testMouseOverH1Highlights);
inspector.toolbox.highlighterUtils.startPicker().then(testMouseOverH1Highlights);
});
});
}
@ -109,7 +109,7 @@ function testOutlineDimensions() {
}
function finishUp() {
inspector.toolbox.stopPicker().then(() => {
inspector.toolbox.highlighterUtils.stopPicker().then(() => {
doc = h1 = inspector = null;
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);

View File

@ -34,7 +34,7 @@ function createDocument() {
// Open the inspector, start the picker mode, and start the tests
openInspector(aInspector => {
inspector = aInspector;
inspector.toolbox.startPicker().then(runTests);
inspector.toolbox.highlighterUtils.startPicker().then(runTests);
});
}, false);
@ -75,14 +75,14 @@ function testDiv2Highlighter() {
function selectRoot() {
// Select the root document element to clear the breadcrumbs.
inspector.selection.setNode(doc.documentElement);
inspector.selection.setNode(doc.documentElement, null);
inspector.once("inspector-updated", selectIframe);
}
function selectIframe() {
// Directly select an element in an iframe (without navigating to it
// with mousemoves).
inspector.selection.setNode(div2);
inspector.selection.setNode(div2, null);
inspector.once("inspector-updated", () => {
let breadcrumbs = inspector.breadcrumbs;
is(breadcrumbs.nodeHierarchy.length, 9, "Should have 9 items");
@ -91,7 +91,7 @@ function selectIframe() {
}
function finishUp() {
inspector.toolbox.stopPicker().then(() => {
inspector.toolbox.highlighterUtils.stopPicker().then(() => {
doc = div1 = div2 = iframe1 = iframe2 = inspector = null;
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.closeToolbox(target);

View File

@ -109,7 +109,7 @@ Tools.inspector = {
preventClosingOnKey: true,
onkey: function(panel) {
panel.toolbox.togglePicker();
panel.toolbox.highlighterUtils.togglePicker();
},
isTargetSupported: function(target) {

View File

@ -54,10 +54,6 @@ loader.lazyGetter(this, "AutocompletePopup", () => {
* The inspector we're watching.
* @param iframe aFrame
* An iframe in which the caller has kindly loaded markup-view.xhtml.
*
* Fires the following events:
* - node-highlight: When a node in the markup-view is hovered and the
* corresponding node in the content gets highlighted
*/
function MarkupView(aInspector, aFrame, aControllerWindow) {
this._inspector = aInspector;
@ -174,41 +170,11 @@ MarkupView.prototype = {
},
_showBoxModel: function(nodeFront, options={}) {
let toolbox = this._inspector.toolbox;
// If the remote highlighter exists on the target, use it
if (toolbox.isRemoteHighlightable) {
toolbox.initInspector().then(() => {
toolbox.highlighter.showBoxModel(nodeFront, options).then(() => {
this.emit("node-highlight", nodeFront);
});
});
}
// Else, revert to the "older" version of the highlighter in the walker
// actor
else {
this.walker.highlight(nodeFront).then(() => {
this.emit("node-highlight", nodeFront);
});
}
this._inspector.toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
},
_hideBoxModel: function() {
let deferred = promise.defer();
let toolbox = this._inspector.toolbox;
// If the remote highlighter exists on the target, use it
if (toolbox.isRemoteHighlightable) {
toolbox.initInspector().then(() => {
toolbox.highlighter.hideBoxModel().then(deferred.resolve);
});
} else {
deferred.resolve();
}
// If not, no need to unhighlight as the older highlight method uses a
// setTimeout to hide itself
return deferred.promise;
this._inspector.toolbox.highlighterUtils.unhighlight();
},
_briefBoxModelTimer: null,

View File

@ -34,6 +34,7 @@ const XHTML_NS = "http://www.w3.org/1999/xhtml";
const SPECTRUM_FRAME = "chrome://browser/content/devtools/spectrum-frame.xhtml";
const ESCAPE_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE;
const ENTER_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
const POPUP_EVENTS = ["shown", "hidden", "showing", "hiding"];
/**
* Tooltip widget.
@ -136,14 +137,27 @@ let PanelFactory = {
* @param {XULDocument} doc
* The XUL document hosting this tooltip
* @param {Object} options
* Optional options that give options to consumers
* Optional options that give options to consumers:
* - consumeOutsideClick {Boolean} Wether the first click outside of the
* tooltip should close the tooltip and be consumed or not.
* Defaults to false
* Defaults to false.
* - closeOnKeys {Array} An array of key codes that should close the
* tooltip. Defaults to [27] (escape key)
* tooltip. Defaults to [27] (escape key).
* - closeOnEvents [{emitter: {Object}, event: {String}, useCapture: {Boolean}}]
* Provide an optional list of emitter objects and event names here to
* trigger the closing of the tooltip when these events are fired by the
* emitters. The emitter objects should either implement on/off(event, cb)
* or addEventListener/removeEventListener(event, cb). Defaults to [].
* For instance, the following would close the tooltip whenever the
* toolbox selects a new tool and when a DOM node gets scrolled:
* new Tooltip(doc, {
* closeOnEvents: [
* {emitter: toolbox, event: "select"},
* {emitter: myContainer, event: "scroll", useCapture: true}
* ]
* });
* - noAutoFocus {Boolean} Should the focus automatically go to the panel
* when it opens. Defaults to true
* when it opens. Defaults to true.
*
* Fires these events:
* - showing : just before the tooltip shows
@ -159,7 +173,8 @@ function Tooltip(doc, options) {
this.options = new OptionsStore({
consumeOutsideClick: false,
closeOnKeys: [ESCAPE_KEYCODE],
noAutoFocus: true
noAutoFocus: true,
closeOnEvents: []
}, options);
this.panel = PanelFactory.get(doc, this.options);
@ -167,7 +182,7 @@ function Tooltip(doc, options) {
this.uid = "tooltip-" + Date.now();
// Emit show/hide events
for (let event of ["shown", "hidden", "showing", "hiding"]) {
for (let event of POPUP_EVENTS) {
this["_onPopup" + event] = ((e) => {
return () => this.emit(e);
})(event);
@ -187,6 +202,18 @@ function Tooltip(doc, options) {
}
};
win.addEventListener("keypress", this._onKeyPress, false);
// Listen to custom emitters' events to close the tooltip
this.hide = this.hide.bind(this);
let closeOnEvents = this.options.get("closeOnEvents");
for (let {emitter, event, useCapture} of closeOnEvents) {
for (let add of ["addEventListener", "on"]) {
if (add in emitter) {
emitter[add](event, this.hide, useCapture);
break;
}
}
}
}
module.exports.Tooltip = Tooltip;
@ -264,7 +291,7 @@ Tooltip.prototype = {
destroy: function () {
this.hide();
for (let event of ["shown", "hidden", "showing", "hiding"]) {
for (let event of POPUP_EVENTS) {
this.panel.removeEventListener("popup" + event,
this["_onPopup" + event], false);
}
@ -272,6 +299,16 @@ Tooltip.prototype = {
let win = this.doc.querySelector("window");
win.removeEventListener("keypress", this._onKeyPress, false);
let closeOnEvents = this.options.get("closeOnEvents");
for (let {emitter, event, useCapture} of closeOnEvents) {
for (let remove of ["removeEventListener", "off"]) {
if (remove in emitter) {
emitter[remove](event, this.hide, useCapture);
break;
}
}
}
this.content = null;
if (this._basedNode) {
@ -476,13 +513,17 @@ Tooltip.prototype = {
* Pass false to instantiate a brand new widget for this variable.
* Otherwise, if a variable was previously inspected, its widget
* will be reused.
* @param {Toolbox} toolbox [optional]
* Pass the instance of the current toolbox if you want the variables
* view widget to allow highlighting and selection of DOM nodes
*/
setVariableContent: function(
objectActor,
viewOptions = {},
controllerOptions = {},
relayEvents = {},
extraButtons = []) {
extraButtons = [],
toolbox = null) {
let vbox = this.doc.createElement("vbox");
vbox.className = "devtools-tooltip-variables-view-box";
@ -503,6 +544,11 @@ Tooltip.prototype = {
let widget = new VariablesView(innerbox, viewOptions);
// If a toolbox was provided, link it to the vview
if (toolbox) {
widget.toolbox = toolbox;
}
// Analyzing state history isn't useful with transient object inspectors.
widget.commitHierarchy = () => {};

View File

@ -23,6 +23,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://gre/modules/devtools/Loader.jsm");
@ -210,6 +212,12 @@ VariablesView.prototype = {
}, aTimeout);
},
/**
* Optional DevTools toolbox containing this VariablesView. Used to
* communicate with the inspector and highlighter.
*/
toolbox: null,
/**
* The controller for this VariablesView, if it has one.
*/
@ -324,6 +332,15 @@ VariablesView.prototype = {
*/
editButtonTooltip: STR.GetStringFromName("variablesEditButtonTooltip"),
/**
* The tooltip text shown on a variable or property's value if that value is
* a DOMNode that can be highlighted and selected in the inspector.
*
* This flag is applied recursively onto each scope in this view and
* affects only the child nodes when they're created.
*/
domNodeValueTooltip: STR.GetStringFromName("variablesDomNodeValueTooltip"),
/**
* The tooltip text shown on a variable or property's delete button if a
* |delete| function is provided, in order to delete the variable or property.
@ -1209,6 +1226,7 @@ function Scope(aView, aName, aFlags = {}) {
this.editableValueTooltip = aView.editableValueTooltip;
this.editButtonTooltip = aView.editButtonTooltip;
this.deleteButtonTooltip = aView.deleteButtonTooltip;
this.domNodeValueTooltip = aView.domNodeValueTooltip;
this.contextMenuId = aView.contextMenuId;
this.separatorStr = aView.separatorStr;
@ -2069,6 +2087,7 @@ Scope.prototype = {
editableValueTooltip: "",
editButtonTooltip: "",
deleteButtonTooltip: "",
domNodeValueTooltip: "",
contextMenuId: "",
separatorStr: "",
@ -2119,6 +2138,9 @@ function Variable(aScope, aName, aDescriptor) {
this._setTooltips = this._setTooltips.bind(this);
this._activateNameInput = this._activateNameInput.bind(this);
this._activateValueInput = this._activateValueInput.bind(this);
this.openNodeInInspector = this.openNodeInInspector.bind(this);
this.highlightDomNode = this.highlightDomNode.bind(this);
this.unhighlightDomNode = this.unhighlightDomNode.bind(this);
// Treat safe getter descriptors as descriptors with a value.
if ("getterValue" in aDescriptor) {
@ -2171,6 +2193,13 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
* Remove this Variable from its parent and remove all children recursively.
*/
remove: function() {
if (this._linkedToInspector) {
this.unhighlightDomNode();
this._valueLabel.removeEventListener("mouseover", this.highlightDomNode, false);
this._valueLabel.removeEventListener("mouseout", this.unhighlightDomNode, false);
this._openInspectorNode.removeEventListener("mousedown", this.openNodeInInspector, false);
}
this.ownerView._store.delete(this._nameString);
this._variablesView._itemsByElement.delete(this._target);
this._variablesView._currHierarchy.delete(this._absoluteName);
@ -2385,6 +2414,11 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
this._valueLabel.classList.add(this._valueClassName);
this._valueLabel.setAttribute("value", this._valueString);
this._separatorLabel.hidden = false;
// DOMNodes get special treatment since they can be linked to the inspector
if (this._valueGrip.preview && this._valueGrip.preview.kind === "DOMNode") {
this._linkToInspector();
}
},
/**
@ -2555,7 +2589,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
if (!descriptor.writable && !ownerView.getter && !ownerView.setter) {
let nonWritableIcon = this.document.createElement("hbox");
nonWritableIcon.className = "variable-or-property-non-writable-icon";
nonWritableIcon.className = "plain variable-or-property-non-writable-icon";
nonWritableIcon.setAttribute("optional-visibility", "");
this._title.appendChild(nonWritableIcon);
}
@ -2623,6 +2657,9 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
if (this._editNode && ownerView.eval) {
this._editNode.setAttribute("tooltiptext", ownerView.editButtonTooltip);
}
if (this._openInspectorNode && this._linkedToInspector) {
this._openInspectorNode.setAttribute("tooltiptext", this.ownerView.domNodeValueTooltip);
}
if (this._valueLabel && ownerView.eval) {
this._valueLabel.setAttribute("tooltiptext", ownerView.editableValueTooltip);
}
@ -2634,6 +2671,110 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
}
},
/**
* Get the parent variablesview toolbox, if any.
*/
get toolbox() {
return this._variablesView.toolbox;
},
/**
* Checks if this variable is a DOMNode and is part of a variablesview that
* has been linked to the toolbox, so that highlighting and jumping to the
* inspector can be done.
*/
_isLinkableToInspector: function() {
let isDomNode = this._valueGrip && this._valueGrip.preview.kind === "DOMNode";
let hasBeenLinked = this._linkedToInspector;
let hasToolbox = !!this.toolbox;
return isDomNode && !hasBeenLinked && hasToolbox;
},
/**
* If the variable is a DOMNode, and if a toolbox is set, then link it to the
* inspector (highlight on hover, and jump to markup-view on click)
*/
_linkToInspector: function() {
if (!this._isLinkableToInspector()) {
return;
}
// Listen to value mouseover/click events to highlight and jump
this._valueLabel.addEventListener("mouseover", this.highlightDomNode, false);
this._valueLabel.addEventListener("mouseout", this.unhighlightDomNode, false);
// Add a button to open the node in the inspector
this._openInspectorNode = this.document.createElement("toolbarbutton");
this._openInspectorNode.className = "plain variables-view-open-inspector";
this._openInspectorNode.addEventListener("mousedown", this.openNodeInInspector, false);
this._title.insertBefore(this._openInspectorNode, this._title.querySelector("toolbarbutton"));
this._linkedToInspector = true;
},
/**
* In case this variable is a DOMNode and part of a variablesview that has been
* linked to the toolbox's inspector, then select the corresponding node in
* the inspector, and switch the inspector tool in the toolbox
* @return a promise that resolves when the node is selected and the inspector
* has been switched to and is ready
*/
openNodeInInspector: function(event) {
if (!this.toolbox) {
return promise.reject(new Error("Toolbox not available"));
}
event && event.stopPropagation();
return Task.spawn(function*() {
yield this.toolbox.initInspector();
let nodeFront = this._nodeFront;
if (!nodeFront) {
nodeFront = yield this.toolbox.walker.getNodeActorFromObjectActor(this._valueGrip.actor);
}
if (nodeFront) {
yield this.toolbox.selectTool("inspector");
let inspectorReady = promise.defer();
this.toolbox.getPanel("inspector").once("inspector-updated", inspectorReady.resolve);
yield this.toolbox.selection.setNodeFront(nodeFront, "variables-view");
yield inspectorReady.promise;
}
}.bind(this));
},
/**
* In case this variable is a DOMNode and part of a variablesview that has been
* linked to the toolbox's inspector, then highlight the corresponding node
*/
highlightDomNode: function() {
if (this.toolbox) {
if (this._nodeFront) {
// If the nodeFront has been retrieved before, no need to ask the server
// again for it
this.toolbox.highlighterUtils.highlightNodeFront(this._nodeFront);
return;
}
this.toolbox.highlighterUtils.highlightDomValueGrip(this._valueGrip).then(front => {
this._nodeFront = front;
});
}
},
/**
* Unhighlight a previously highlit node
* @see highlightDomNode
*/
unhighlightDomNode: function() {
if (this.toolbox) {
this.toolbox.highlighterUtils.unhighlight();
}
},
/**
* Sets a variable's configurable, enumerable and writable attributes,
* and specifies if it's a 'this', '<exception>', '<return>' or '__proto__'
@ -2740,6 +2881,9 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
_activateValueInput: function(e) {
EditableValue.create(this, {
onSave: aString => {
if (this._linkedToInspector) {
this.unhighlightDomNode();
}
if (!this._variablesView.preventDisableOnChange) {
this._disable();
}
@ -3573,6 +3717,13 @@ VariablesView.stringifiers._getNMoreString = function(aNumber) {
*/
VariablesView.getClass = function(aGrip) {
if (aGrip && typeof aGrip == "object") {
if (aGrip.preview) {
switch (aGrip.preview.kind) {
case "DOMNode":
return "token-domnode";
}
}
switch (aGrip.type) {
case "undefined":
return "token-undefined";

View File

@ -79,6 +79,7 @@
.variable-or-property[pseudo-item] > tooltip,
.variable-or-property[pseudo-item] > .title > .variables-view-edit,
.variable-or-property[pseudo-item] > .title > .variables-view-open-inspector,
.variable-or-property[pseudo-item] > .title > .variables-view-delete,
.variable-or-property[pseudo-item] > .title > .variables-view-add-property,
.variable-or-property[pseudo-item] > .title > .variable-or-property-frozen-label,
@ -89,11 +90,13 @@
}
*:not(:hover) .variables-view-delete,
*:not(:hover) .variables-view-open-inspector,
*:not(:hover) .variables-view-add-property {
visibility: hidden;
}
.variables-view-delete > .toolbarbutton-text,
.variables-view-open-inspector > .toolbarbutton-text,
.variables-view-add-property > .toolbarbutton-text {
display: none;
}

View File

@ -104,6 +104,7 @@ support-files =
test-bug_923281_test1.js
test-bug_923281_test2.js
test-bug_939783_console_trace_duplicates.html
test-bug-952277-highlight-nodes-in-vview.html
[browser_bug664688_sandbox_update_after_navigation.js]
[browser_bug_638949_copy_link_location.js]
@ -258,4 +259,5 @@ run-if = os == "mac"
[browser_webconsole_output_03.js]
[browser_webconsole_output_04.js]
[browser_webconsole_output_events.js]
[browser_console_variables_view_highlighter.js]
[browser_webconsole_console_trace_duplicates.js]

View File

@ -0,0 +1,91 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Check that variables view is linked to the inspector for highlighting and
// selecting DOM nodes
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-952277-highlight-nodes-in-vview.html";
let gWebConsole, gJSTerm, gVariablesView, gToolbox;
function test()
{
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}
function consoleOpened(hud)
{
gWebConsole = hud;
gJSTerm = hud.jsterm;
gToolbox = gDevTools.getToolbox(hud.target);
gJSTerm.execute("document.querySelectorAll('p')", onQSAexecuted);
}
function onQSAexecuted(msg)
{
ok(msg, "output message found");
let anchor = msg.querySelector("a");
ok(anchor, "object link found");
gJSTerm.once("variablesview-fetched", onNodeListVviewFetched);
executeSoon(() =>
EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)
);
}
function onNodeListVviewFetched(aEvent, aVar)
{
gVariablesView = aVar._variablesView;
ok(gVariablesView, "variables view object");
// Transform the vview into an array we can filter properties from
let props = [[id, prop] for([id, prop] of aVar)];
// These properties are the DOM nodes ones
props = props.filter(v => v[0].match(/[0-9]+/));
function hoverOverDomNodeVariableAndAssertHighlighter(index) {
if (props[index]) {
let prop = props[index][1];
let valueEl = prop._valueLabel;
gToolbox.once("node-highlight", () => {
ok(true, "The highlighter was shown on hover of the DOMNode");
gToolbox.highlighterUtils.unhighlight().then(() => {
clickOnDomNodeVariableAndAssertInspectorSelected(index);
});
});
// Rather than trying to emulate a mouseenter event, let's call the
// variable's highlightDomNode and see if it has the desired effect
prop.highlightDomNode();
} else {
finishTest();
}
}
function clickOnDomNodeVariableAndAssertInspectorSelected(index) {
let prop = props[index][1];
// Make sure the inspector is initialized so we can listen to its events
gToolbox.initInspector().then(() => {
// Rather than trying to click on the value here, let's just call the
// variable's openNodeInInspector function and see if it has the
// desired effect
prop.openNodeInInspector().then(() => {
is(gToolbox.currentToolId, "inspector", "The toolbox switched over the inspector on DOMNode click");
gToolbox.selectTool("webconsole").then(() => {
hoverOverDomNodeVariableAndAssertHighlighter(index + 1);
});
});
});
}
hoverOverDomNodeVariableAndAssertHighlighter(0);
}

View File

@ -49,7 +49,7 @@ function setupHighlighterTests() {
function runSelectionTests(aInspector) {
inspector = aInspector;
inspector.toolbox.startPicker();
inspector.toolbox.highlighterUtils.startPicker();
inspector.toolbox.once("picker-started", () => {
info("Picker mode started, now clicking on H1 to select that node");
executeSoon(() => {

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Web Console test for bug 952277 - Highlighting and selecting nodes from the variablesview</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
</head>
<body>
<p>Web Console test for bug 952277 - Highlighting and selecting nodes from the variablesview</p>
<p>Web Console test for bug 952277 - Highlighting and selecting nodes from the variablesview</p>
<p>Web Console test for bug 952277 - Highlighting and selecting nodes from the variablesview</p>
</body>
</html>

View File

@ -3420,6 +3420,7 @@ JSTerm.prototype = {
_createVariablesView: function JST__createVariablesView(aOptions)
{
let view = new VariablesView(aOptions.container);
view.toolbox = gDevTools.getToolbox(this.hud.owner.target);
view.searchPlaceholder = l10n.getStr("propertiesFilterPlaceholder");
view.emptyText = l10n.getStr("emptyPropertiesList");
view.searchEnabled = !aOptions.hideFilterInput;

View File

@ -266,6 +266,11 @@ variablesCloseButtonTooltip=Click to remove
# in the variables list on a getter or setter which can be edited.
variablesEditButtonTooltip=Click to set value
# LOCALIZATION NOTE (variablesEditableValueTooltip): The text that is displayed
# in a tooltip on the "open in inspector" button in the the variables list for a
# DOMNode item.
variablesDomNodeValueTooltip=Click to select the node in the inspector
# LOCALIZATION NOTE (configurable|...|Tooltip): The text that is displayed
# in the variables list on certain variables or properties as tooltips.
# Expanations of what these represent can be found at the following links:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 B

View File

@ -224,8 +224,8 @@ browser.jar:
skin/classic/browser/devtools/dock-side@2x.png (../shared/devtools/images/dock-side@2x.png)
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
skin/classic/browser/devtools/inspector.css (devtools/inspector.css)
skin/classic/browser/devtools/profiler-stopwatch.png (../shared/devtools/images/profiler-stopwatch.png)
skin/classic/browser/devtools/inspector.css (devtools/inspector.css)
skin/classic/browser/devtools/profiler-stopwatch.png (../shared/devtools/images/profiler-stopwatch.png)
skin/classic/browser/devtools/tool-options.svg (../shared/devtools/images/tool-options.svg)
skin/classic/browser/devtools/tool-webconsole.svg (../shared/devtools/images/tool-webconsole.svg)
skin/classic/browser/devtools/tool-debugger.svg (../shared/devtools/images/tool-debugger.svg)
@ -236,16 +236,18 @@ browser.jar:
skin/classic/browser/devtools/tool-profiler.svg (../shared/devtools/images/tool-profiler.svg)
skin/classic/browser/devtools/tool-network.svg (../shared/devtools/images/tool-network.svg)
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
skin/classic/browser/devtools/close.png (../shared/devtools/images/close.png)
skin/classic/browser/devtools/close@2x.png (../shared/devtools/images/close@2x.png)
skin/classic/browser/devtools/vview-delete.png (devtools/vview-delete.png)
skin/classic/browser/devtools/vview-edit.png (devtools/vview-edit.png)
skin/classic/browser/devtools/undock@2x.png (../shared/devtools/images/undock@2x.png)
skin/classic/browser/devtools/font-inspector.css (devtools/font-inspector.css)
skin/classic/browser/devtools/computedview.css (devtools/computedview.css)
skin/classic/browser/devtools/arrow-e.png (devtools/arrow-e.png)
skin/classic/browser/devtools/responsiveui-rotate.png (../shared/devtools/responsiveui-rotate.png)
skin/classic/browser/devtools/responsiveui-touch.png (../shared/devtools/responsiveui-touch.png)
skin/classic/browser/devtools/close.png (../shared/devtools/images/close.png)
skin/classic/browser/devtools/close@2x.png (../shared/devtools/images/close@2x.png)
skin/classic/browser/devtools/vview-delete.png (../shared/devtools/images/vview-delete.png)
skin/classic/browser/devtools/vview-lock.png (../shared/devtools/images/vview-lock.png)
skin/classic/browser/devtools/vview-edit.png (../shared/devtools/images/vview-edit.png)
skin/classic/browser/devtools/vview-open-inspector.png (../shared/devtools/images/vview-open-inspector.png)
skin/classic/browser/devtools/undock@2x.png (../shared/devtools/images/undock@2x.png)
skin/classic/browser/devtools/font-inspector.css (devtools/font-inspector.css)
skin/classic/browser/devtools/computedview.css (devtools/computedview.css)
skin/classic/browser/devtools/arrow-e.png (devtools/arrow-e.png)
skin/classic/browser/devtools/responsiveui-rotate.png (../shared/devtools/responsiveui-rotate.png)
skin/classic/browser/devtools/responsiveui-touch.png (../shared/devtools/responsiveui-touch.png)
skin/classic/browser/devtools/responsiveui-screenshot.png (../shared/devtools/responsiveui-screenshot.png)
skin/classic/browser/devtools/app-manager/connection-footer.css (../shared/devtools/app-manager/connection-footer.css)
skin/classic/browser/devtools/app-manager/index.css (../shared/devtools/app-manager/index.css)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 B

View File

@ -345,8 +345,10 @@ browser.jar:
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
skin/classic/browser/devtools/close.png (../shared/devtools/images/close.png)
skin/classic/browser/devtools/close@2x.png (../shared/devtools/images/close@2x.png)
skin/classic/browser/devtools/vview-delete.png (devtools/vview-delete.png)
skin/classic/browser/devtools/vview-edit.png (devtools/vview-edit.png)
skin/classic/browser/devtools/vview-delete.png (../shared/devtools/images/vview-delete.png)
skin/classic/browser/devtools/vview-lock.png (../shared/devtools/images/vview-lock.png)
skin/classic/browser/devtools/vview-edit.png (../shared/devtools/images/vview-edit.png)
skin/classic/browser/devtools/vview-open-inspector.png (../shared/devtools/images/vview-open-inspector.png)
skin/classic/browser/devtools/undock@2x.png (../shared/devtools/images/undock@2x.png)
skin/classic/browser/devtools/font-inspector.css (devtools/font-inspector.css)
skin/classic/browser/devtools/computedview.css (devtools/computedview.css)

View File

@ -148,10 +148,15 @@
.cm-s-mozilla .cm-quote,
.cm-s-mozilla .cm-error,
.variable-or-property .token-boolean,
.variable-or-property .token-domnode,
.variable-or-property[exception] > .title > .name { /* Red */
color: #bf5656;
}
.variable-or-property .token-domnode {
font-weight: bold;
}
.theme-toolbar,
.devtools-toolbar,
.devtools-sidebar-tabs > tabs { /* General toolbar styling */

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -147,10 +147,15 @@
.cm-s-mozilla .cm-quote,
.cm-s-mozilla .cm-error,
.variable-or-property .token-boolean,
.variable-or-property .token-domnode,
.variable-or-property[exception] > .title > .name { /* Red */
color: #bf5656;
}
.variable-or-property .token-domnode {
font-weight: bold;
}
.theme-fg-contrast { /* To be used for text on theme-bg-contrast */
color: black;
}

View File

@ -643,19 +643,18 @@
}
.variable-or-property-non-writable-icon {
background: url("chrome://browser/skin/identity-icons-https.png") no-repeat;
background: url("chrome://browser/skin/devtools/vview-lock.png") no-repeat;
width: 16px;
height: 16px;
opacity: 0.5;
}
@media (min-resolution: 2dppx) {
/*@media (min-resolution: 2dppx) {
.variable-or-property-non-writable-icon {
background-image: url("chrome://browser/skin/identity-icons-https@2x.png");
background-size: 32px;
}
}
*/
.variable-or-property-frozen-label,
.variable-or-property-sealed-label,
.variable-or-property-non-extensible-label {
@ -686,6 +685,7 @@
/* Actions first */
.variables-view-container[actions-first] .variables-view-delete,
.variables-view-container[actions-first] .variables-view-open-inspector,
.variables-view-container[actions-first] .variables-view-add-property {
-moz-box-ordinal-group: 0;
}
@ -727,18 +727,52 @@
}
.variables-view-delete:hover {
-moz-image-region: rect(0,32px,16px,16px);
}
.variables-view-delete:active {
-moz-image-region: rect(0,48px,16px,32px);
}
.variables-view-delete:active {
-moz-image-region: rect(0,32px,16px,16px);
}
.variable-or-property:focus .variables-view-delete {
-moz-image-region: rect(0,16px,16px,0);
}
.variables-view-edit {
background: url("chrome://browser/skin/devtools/vview-edit.png") center no-repeat;
width: 20px;
height: 16px;
list-style-image: url("chrome://browser/skin/devtools/vview-edit.png");
-moz-image-region: rect(0,16px,16px,0);
cursor: pointer;
padding-left: 2px;
}
.variables-view-edit:hover {
-moz-image-region: rect(0,48px,16px,32px);
}
.variables-view-edit:active {
-moz-image-region: rect(0,32px,16px,16px);
}
.variable-or-property:focus .variables-view-edit {
-moz-image-region: rect(0,16px,16px,0);
}
.variables-view-open-inspector {
list-style-image: url("chrome://browser/skin/devtools/vview-open-inspector.png");
-moz-image-region: rect(0,16px,16px,0);
cursor: pointer;
}
.variables-view-open-inspector:hover {
-moz-image-region: rect(0,48px,16px,32px);
}
.variables-view-open-inspector:active {
-moz-image-region: rect(0,32px,16px,16px);
}
.variable-or-property:focus .variables-view-open-inspector {
-moz-image-region: rect(0,16px,16px,0);
}
.variables-view-throbber {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 B

View File

@ -266,8 +266,10 @@ browser.jar:
skin/classic/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
skin/classic/browser/devtools/close.png (../shared/devtools/images/close.png)
skin/classic/browser/devtools/close@2x.png (../shared/devtools/images/close@2x.png)
skin/classic/browser/devtools/vview-delete.png (devtools/vview-delete.png)
skin/classic/browser/devtools/vview-edit.png (devtools/vview-edit.png)
skin/classic/browser/devtools/vview-delete.png (../shared/devtools/images/vview-delete.png)
skin/classic/browser/devtools/vview-lock.png (../shared/devtools/images/vview-lock.png)
skin/classic/browser/devtools/vview-edit.png (../shared/devtools/images/vview-edit.png)
skin/classic/browser/devtools/vview-open-inspector.png (../shared/devtools/images/vview-open-inspector.png)
skin/classic/browser/devtools/undock@2x.png (../shared/devtools/images/undock@2x.png)
skin/classic/browser/devtools/font-inspector.css (devtools/font-inspector.css)
skin/classic/browser/devtools/computedview.css (devtools/computedview.css)
@ -572,8 +574,10 @@ browser.jar:
skin/classic/aero/browser/devtools/tool-scratchpad.svg (../shared/devtools/images/tool-scratchpad.svg)
skin/classic/aero/browser/devtools/close.png (../shared/devtools/images/close.png)
skin/classic/aero/browser/devtools/close@2x.png (../shared/devtools/images/close@2x.png)
skin/classic/aero/browser/devtools/vview-delete.png (devtools/vview-delete.png)
skin/classic/aero/browser/devtools/vview-edit.png (devtools/vview-edit.png)
skin/classic/aero/browser/devtools/vview-delete.png (../shared/devtools/images/vview-delete.png)
skin/classic/aero/browser/devtools/vview-lock.png (../shared/devtools/images/vview-lock.png)
skin/classic/aero/browser/devtools/vview-edit.png (../shared/devtools/images/vview-edit.png)
skin/classic/aero/browser/devtools/vview-open-inspector.png (../shared/devtools/images/vview-open-inspector.png)
skin/classic/aero/browser/devtools/undock@2x.png (../shared/devtools/images/undock@2x.png)
skin/classic/aero/browser/devtools/font-inspector.css (devtools/font-inspector.css)
skin/classic/aero/browser/devtools/computedview.css (devtools/computedview.css)

View File

@ -2086,7 +2086,31 @@ var WalkerActor = protocol.ActorClass({
// Need to force a release of this node, because those nodes can't
// be accessed anymore.
this.releaseNode(documentActor, { force: true });
}
},
/**
* Given an ObjectActor (identified by its ID), commonly used in the debugger,
* webconsole and variablesView, return the corresponding inspector's NodeActor
*/
getNodeActorFromObjectActor: method(function(objectActorID) {
let debuggerObject = this.conn.poolFor(objectActorID).get(objectActorID).obj;
let rawNode = debuggerObject.unsafeDereference();
// This is a special case for the document object whereby it is considered
// as document.documentElement (the <html> node)
if (rawNode.defaultView && rawNode === rawNode.defaultView.document) {
rawNode = rawNode.documentElement;
}
return this.attachElement(rawNode);
}, {
request: {
objectActorID: Arg(0, "string")
},
response: {
nodeFront: RetVal("disconnectedNode")
}
}),
});
/**
@ -2220,6 +2244,14 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
impl: "_querySelector"
}),
getNodeActorFromObjectActor: protocol.custom(function(objectActorID) {
return this._getNodeActorFromObjectActor(objectActorID).then(response => {
return response.node;
});
}, {
impl: "_getNodeActorFromObjectActor"
}),
_releaseFront: function(node, force) {
if (node.retained && !force) {
node.reparent(null);