Bug 877320 - Expose a node front in the Selection object in addition to a raw node. r=paul

This commit is contained in:
Dave Camp 2013-06-07 14:32:32 -07:00
Родитель b8dd40d720
Коммит 41f96a13b0
10 изменённых файлов: 211 добавлений и 116 удалений

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

@ -1058,6 +1058,7 @@ pref("devtools.toolbox.sideEnabled", true);
pref("devtools.inspector.enabled", true);
pref("devtools.inspector.activeSidebar", "ruleview");
pref("devtools.inspector.markupPreview", false);
pref("devtools.inspector.remote", false);
// Enable the Layout View
pref("devtools.layoutview.enabled", true);

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

@ -54,6 +54,7 @@ FontInspector.prototype = {
*/
onNewNode: function FI_onNewNode() {
if (this.isActive() &&
this.inspector.selection.isLocal() &&
this.inspector.selection.isConnected() &&
this.inspector.selection.isElementNode() &&
this.inspector.selection.reason != "highlighter") {

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

@ -15,6 +15,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
"resource://gre/modules/devtools/dbg-client.jsm");
loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
const targets = new WeakMap();
const promiseTargets = new WeakMap();
@ -250,6 +252,17 @@ TabTarget.prototype = {
return !!this._isThreadPaused;
},
get inspector() {
if (!this.form) {
throw new Error("Target.inspector requires an initialized remote actor.");
}
if (this._inspector) {
return this._inspector;
}
this._inspector = InspectorFront(this.client, this.form);
return this._inspector;
},
/**
* Adds remote protocol capabilities to the target, so that it can be used
* for tools that support the Remote Debugging Protocol even for local

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

@ -228,6 +228,15 @@ Highlighter.prototype = {
*/
invalidateSize: function Highlighter_invalidateSize()
{
// The highlighter runs locally while the selection runs remotely,
// so we can't quite trust the selection's isConnected to protect us
// here, do the check manually.
if (!this.selection.node ||
!this.selection.node.ownerDocument ||
!this.selection.node.ownerDocument.defaultView) {
return;
}
let canHiglightNode = this.selection.isNode() &&
this.selection.isConnected() &&
this.selection.isElementNode();

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

@ -13,7 +13,7 @@ let EventEmitter = require("devtools/shared/event-emitter");
let {CssLogic} = require("devtools/styleinspector/css-logic");
loader.lazyGetter(this, "MarkupView", () => require("devtools/markupview/markup-view").MarkupView);
loader.lazyGetter(this, "Selection", () => require ("devtools/inspector/selection").Selection);
loader.lazyGetter(this, "Selection", () => require("devtools/inspector/selection").Selection);
loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/inspector/breadcrumbs").HTMLBreadcrumbs);
loader.lazyGetter(this, "Highlighter", () => require("devtools/inspector/highlighter").Highlighter);
loader.lazyGetter(this, "ToolSidebar", () => require("devtools/framework/sidebar").ToolSidebar);
@ -44,6 +44,20 @@ InspectorPanel.prototype = {
* open is effectively an asynchronous constructor
*/
open: function InspectorPanel_open() {
return this.target.makeRemote().then(() => {
return this.target.inspector.getWalker();
}).then(walker => {
if (this._destroyPromise) {
walker.release().then(null, console.error);
}
this.walker = walker;
return this._getDefaultNodeForSelection();
}).then(defaultSelection => {
return this._deferredOpen(defaultSelection);
}).then(null, console.error);
},
_deferredOpen: function(defaultSelection) {
let deferred = Promise.defer();
this.onNavigatedAway = this.onNavigatedAway.bind(this);
@ -57,13 +71,13 @@ InspectorPanel.prototype = {
this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
// Create an empty selection
this._selection = new Selection();
this._selection = new Selection(this.walker);
this.onNewSelection = this.onNewSelection.bind(this);
this.selection.on("new-node", this.onNewSelection);
this.selection.on("new-node-front", this.onNewSelection);
this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
this.selection.on("before-new-node", this.onBeforeNewSelection);
this.onDetached = this.onDetached.bind(this);
this.selection.on("detached", this.onDetached);
this.selection.on("detached-front", this.onDetached);
this.breadcrumbs = new HTMLBreadcrumbs(this);
@ -121,13 +135,7 @@ InspectorPanel.prototype = {
this.isReady = true;
// All the components are initialized. Let's select a node.
if (this.target.isLocalTab) {
this._selection.setNode(
this._getDefaultNodeForSelection(this.browser.contentDocument));
} else if (this.target.window) {
this._selection.setNode(
this._getDefaultNodeForSelection(this.target.window.document));
}
this._selection.setNodeFront(defaultSelection);
if (this.highlighter) {
this.highlighter.unlock();
@ -146,13 +154,17 @@ InspectorPanel.prototype = {
},
/**
* Select node for default selection
* Return a promise that will resolve to the default node for selection.
*/
_getDefaultNodeForSelection : function(document) {
_getDefaultNodeForSelection : function() {
// if available set body node as default selected node
// else set documentElement
var defaultNode = document.body || document.documentElement;
return defaultNode;
return this.walker.querySelector(this.walker.rootNode, "body").then(front => {
if (front) {
return front;
}
return this.walker.documentElement(this.walker.rootNode);
});
},
/**
@ -262,35 +274,34 @@ InspectorPanel.prototype = {
*/
onNavigatedAway: function InspectorPanel_onNavigatedAway(event, payload) {
let newWindow = payload._navPayload || payload;
this.selection.setNode(null);
this.walker.release().then(null, console.error);
this.walker = null;
this.selection.setNodeFront(null);
this.selection.setWalker(null);
this._destroyMarkup();
this.isDirty = false;
let onDOMReady = function() {
newWindow.removeEventListener("DOMContentLoaded", onDOMReady, true);
if (this._destroyed) {
this.target.inspector.getWalker().then(walker => {
if (this._destroyPromise) {
walker.release().then(null, console.error);
return;
}
if (!this.selection.node) {
let defaultNode = this._getDefaultNodeForSelection(newWindow.document);
this.selection.setNode(defaultNode, "navigateaway");
}
this._initMarkup();
this.walker = walker;
this.selection.setWalker(walker);
this._getDefaultNodeForSelection().then(defaultNode => {
if (this._destroyPromise) {
return;
}
this.selection.setNodeFront(defaultNode, "navigateaway");
this.once("markuploaded", () => {
this.markup.expandNode(this.selection.node);
this._initMarkup();
this.once("markuploaded", () => {
this.markup.expandNode(this.selection.node);
this.setupSearchBox();
});
});
this.setupSearchBox();
}.bind(this);
if (newWindow.document.readyState == "loading") {
newWindow.addEventListener("DOMContentLoaded", onDOMReady, true);
} else {
onDOMReady();
}
});
},
/**
@ -317,17 +328,22 @@ InspectorPanel.prototype = {
onDetached: function InspectorPanel_onDetached(event, parentNode) {
this.cancelLayoutChange();
this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
this.selection.setNode(parentNode, "detached");
this.selection.setNodeFront(parentNode, "detached");
},
/**
* Destroy the inspector.
*/
destroy: function InspectorPanel__destroy() {
if (this._destroyed) {
return Promise.resolve(null);
if (this._destroyPromise) {
return this._destroyPromise;
}
if (this.walker) {
this._destroyPromise = this.walker.release().then(null, console.error);
delete this.walker;
} else {
this._destroyPromise = Promise.resolve(null);
}
this._destroyed = true;
this.cancelLayoutChange();
@ -358,9 +374,9 @@ InspectorPanel.prototype = {
this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
this.breadcrumbs.destroy();
this.searchSuggestions.destroy();
this.selection.off("new-node", this.onNewSelection);
this.selection.off("new-node-front", this.onNewSelection);
this.selection.off("before-new-node", this.onBeforeNewSelection);
this.selection.off("detached", this.onDetached);
this.selection.off("detached-front", this.onDetached);
this._destroyMarkup();
this._selection.destroy();
this._selection = null;
@ -374,7 +390,7 @@ InspectorPanel.prototype = {
this.nodemenu = null;
this.highlighter = null;
return Promise.resolve(null);
return this._destroyPromise;
},
/**
@ -534,6 +550,9 @@ InspectorPanel.prototype = {
*/
toggleHighlighter: function InspectorPanel_toggleHighlighter(event)
{
if (!this.highlighter) {
return;
}
if (event.type == "mouseover") {
this.highlighter.hide();
}

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

@ -4,13 +4,13 @@
* 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/. */
const {Cu} = require("chrome");
const {Cu, Ci} = require("chrome");
let EventEmitter = require("devtools/shared/event-emitter");
/**
* API
*
* new Selection(node=null, track={attributes,detached});
* new Selection(walker=null, node=null, track={attributes,detached});
* destroy()
* node (readonly)
* setNode(node, origin="unknown")
@ -53,19 +53,22 @@ let EventEmitter = require("devtools/shared/event-emitter");
* @param node Inner node.
* Can be null. Can be (un)set in the future via the "node" property;
* @param trackAttribute Tell if events should be fired when the attributes of
* the ndoe change.
* the node change.
*
*/
function Selection(node=null, track={attributes:true,detached:true}) {
function Selection(walker, node=null, track={attributes:true,detached:true}) {
EventEmitter.decorate(this);
this._onMutations = this._onMutations.bind(this);
this.track = track;
this.setWalker(walker);
this.setNode(node);
}
exports.Selection = Selection;
Selection.prototype = {
_walker: null,
_node: null,
_onMutations: function(mutations) {
@ -86,60 +89,41 @@ Selection.prototype = {
if (attributeChange)
this.emit("attribute-changed");
if (detached)
this.emit("detached", parentNode);
},
_attachEvents: function SN__attachEvents() {
if (!this.window || !this.isNode() || !this.track) {
return;
if (detached) {
this.emit("detached", parentNode ? parentNode.rawNode() : null);
this.emit("detached-front", parentNode);
}
if (this.track.attributes) {
this._nodeObserver = new this.window.MutationObserver(this._onMutations);
this._nodeObserver.observe(this.node, {attributes: true});
}
if (this.track.detached) {
this._docObserver = new this.window.MutationObserver(this._onMutations);
this._docObserver.observe(this.document.documentElement, {childList: true, subtree: true});
}
},
_detachEvents: function SN__detachEvents() {
// `disconnect` fail if node's document has
// been deleted.
try {
if (this._nodeObserver)
this._nodeObserver.disconnect();
} catch(e) {}
try {
if (this._docObserver)
this._docObserver.disconnect();
} catch(e) {}
},
destroy: function SN_destroy() {
this._detachEvents();
this.setNode(null);
this.setWalker(null);
},
setNode: function SN_setNode(value, reason="unknown") {
this.reason = reason;
if (value !== this._node) {
this.emit("before-new-node", value, reason);
let previousNode = this._node;
this._detachEvents();
this._node = value;
this._attachEvents();
this.emit("new-node", previousNode, this.reason);
setWalker: function(walker) {
if (this._walker) {
this._walker.off("mutations", this._onMutations);
}
this._walker = walker;
if (this._walker) {
this._walker.on("mutations", this._onMutations);
}
},
// Not remote-safe
setNode: function SN_setNode(value, reason="unknown") {
if (value) {
value = this._walker.frontForRawNode(value);
}
this.setNodeFront(value, reason);
},
// Not remote-safe
get node() {
return this._node;
},
// Not remote-safe
get window() {
if (this.isNode()) {
return this.node.ownerDocument.defaultView;
@ -147,6 +131,7 @@ Selection.prototype = {
return null;
},
// Not remote-safe
get document() {
if (this.isNode()) {
return this.node.ownerDocument;
@ -154,28 +139,79 @@ Selection.prototype = {
return null;
},
setNodeFront: function(value, reason="unknown") {
this.reason = reason;
if (value !== this._nodeFront) {
let rawValue = value ? value.rawNode() : value;
this.emit("before-new-node", rawValue, reason);
this.emit("before-new-node-front", value, reason);
let previousNode = this._node;
let previousFront = this._nodeFront;
this._node = rawValue;
this._nodeFront = value;
this.emit("new-node", previousNode, this.reason);
this.emit("new-node-front", value, this.reason);
}
},
get documentFront() {
return this._walker.document(this._nodeFront);
},
get nodeFront() {
return this._nodeFront;
},
isRoot: function SN_isRootNode() {
return this.isNode() &&
this.isConnected() &&
this.node.ownerDocument.documentElement === this.node;
this._nodeFront.isDocumentElement;
},
isNode: function SN_isNode() {
return (this.node &&
!Cu.isDeadWrapper(this.node) &&
this.node.ownerDocument &&
this.node.ownerDocument.defaultView &&
this.node instanceof this.node.ownerDocument.defaultView.Node);
if (!this._nodeFront) {
return false;
}
// As long as tools are still accessing node.rawNode(),
// this needs to stay here.
if (this._node && Cu.isDeadWrapper(this._node)) {
return false;
}
return true;
},
isLocal: function SN_nsLocal() {
return !!this._node;
},
isConnected: function SN_isConnected() {
try {
let doc = this.document;
return doc && doc.defaultView && doc.documentElement.contains(this.node);
} catch (e) {
// "can't access dead object" error
let node = this._nodeFront;
if (!node || !node.actorID) {
return false;
}
// As long as there are still tools going around
// accessing node.rawNode, this needs to stay.
let rawNode = node.rawNode();
if (rawNode) {
try {
let doc = this.document;
return (doc && doc.defaultView && doc.documentElement.contains(rawNode));
} catch (e) {
// "can't access dead object" error
return false;
}
}
while(node) {
if (node === this._walker.rootNode) {
return true;
}
node = node.parentNode();
};
return false;
},
isHTMLNode: function SN_isHTMLNode() {
@ -186,50 +222,50 @@ Selection.prototype = {
// Node type
isElementNode: function SN_isElementNode() {
return this.isNode() && this.node.nodeType == this.window.Node.ELEMENT_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ELEMENT_NODE;
},
isAttributeNode: function SN_isAttributeNode() {
return this.isNode() && this.node.nodeType == this.window.Node.ATTRIBUTE_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ATTRIBUTE_NODE;
},
isTextNode: function SN_isTextNode() {
return this.isNode() && this.node.nodeType == this.window.Node.TEXT_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.TEXT_NODE;
},
isCDATANode: function SN_isCDATANode() {
return this.isNode() && this.node.nodeType == this.window.Node.CDATA_SECTION_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.CDATA_SECTION_NODE;
},
isEntityRefNode: function SN_isEntityRefNode() {
return this.isNode() && this.node.nodeType == this.window.Node.ENTITY_REFERENCE_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_REFERENCE_NODE;
},
isEntityNode: function SN_isEntityNode() {
return this.isNode() && this.node.nodeType == this.window.Node.ENTITY_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_NODE;
},
isProcessingInstructionNode: function SN_isProcessingInstructionNode() {
return this.isNode() && this.node.nodeType == this.window.Node.PROCESSING_INSTRUCTION_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
},
isCommentNode: function SN_isCommentNode() {
return this.isNode() && this.node.nodeType == this.window.Node.PROCESSING_INSTRUCTION_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE;
},
isDocumentNode: function SN_isDocumentNode() {
return this.isNode() && this.node.nodeType == this.window.Node.DOCUMENT_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE;
},
isDocumentTypeNode: function SN_isDocumentTypeNode() {
return this.isNode() && this.node.nodeType ==this.window. Node.DOCUMENT_TYPE_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE;
},
isDocumentFragmentNode: function SN_isDocumentFragmentNode() {
return this.isNode() && this.node.nodeType == this.window.Node.DOCUMENT_FRAGMENT_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE;
},
isNotationNode: function SN_isNotationNode() {
return this.isNode() && this.node.nodeType == this.window.Node.NOTATION_NODE;
return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.NOTATION_NODE;
},
}

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

@ -55,6 +55,7 @@ function test()
executeSoon(function() {
performTest();
cursor++;
if (cursor >= nodes.length) {
inspector.selection.off("new-node", nodeSelected);
finishUp();

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

@ -34,9 +34,10 @@ function test()
let tmp = {};
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tmp);
ok(!tmp.LayoutHelpers.isNodeConnected(node), "Node considered as disconnected.");
executeSoon(function() {
is(inspector.selection.node, parentNode, "parent of selection got selected");
// Wait for the selection to process the mutation
inspector.walker.on("mutations", () => {
is(inspector.selection.node, parentNode, "parent of selection got selected");
finishUp();
});
}

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

@ -5,6 +5,13 @@
const Cu = Components.utils;
const Ci = Components.interfaces;
const Cc = Components.classes;
Services.prefs.setBoolPref("devtools.debugger.log", true);
SimpleTest.registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.debugger.log");
});
let tempScope = {};
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tempScope);
let LayoutHelpers = tempScope.LayoutHelpers;

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

@ -307,8 +307,11 @@ function test() {
id: "node18",
});
is(inspector.highlighter.nodeInfo.classesBox.textContent, "",
"No classes in the infobar before edit.");
/**
* XXX: disabled until the remote markup view is enabled
* is(inspector.highlighter.nodeInfo.classesBox.textContent, "",
* "No classes in the infobar before edit.");
*/
},
execute: function(after) {
inspector.once("markupmutation", function() {
@ -326,8 +329,12 @@ function test() {
class: "newclass",
style: "color:green"
});
is(inspector.highlighter.nodeInfo.classesBox.textContent, ".newclass",
"Correct classes in the infobar after edit.");
/**
* XXX: disabled until the remote markup view is enabled
*is(inspector.highlighter.nodeInfo.classesBox.textContent, ".newclass",
* "Correct classes in the infobar after edit.");
*/
}
};
testAsyncSetup(test, editTagName);