DOMAgent constructs and maintains a tree of {DOMNode}s that represents the
rendered DOM tree in the remote browser. Nodes can be accessed by id or
location (source offset). To update the DOM tree in response to a change of
the source document (replace [from,to] with text) call
applyChange(from, to, text)
.
The DOMAgent triggers getDocument
once it has loaded
the document.
Eliminate the query string from a URL
function _cleanURL(url) {
var index = url.search(/[#\?]/);
if (index >= 0) {
url = url.substr(0, index);
}
return url;
}
Map the DOM document to the source text
function _mapDocumentToSource(source) {
var node = exports.root;
DOMHelpers.eachNode(source, function each(payload) {
if (!node) {
return true;
}
if (payload.closing) {
var parent = node.findParentForNextNodeMatchingPayload(payload);
if (!parent) {
return console.warn("Matching Parent not at " + payload.sourceOffset + " (" + payload.nodeName + ")");
}
parent.closeLocation = payload.sourceOffset;
parent.closeLength = payload.sourceLength;
} else {
var next = node.findNextNodeMatchingPayload(payload);
if (!next) {
return console.warn("Skipping Source Node at " + payload.sourceOffset);
}
node = next;
node.location = payload.sourceOffset;
node.length = payload.sourceLength;
if (payload.closed) {
node.closed = payload.closed;
}
}
});
}
Load the source document and match it with the DOM tree
function _onFinishedLoadingDOM() {
var request = new XMLHttpRequest();
request.open("GET", exports.url);
request.onload = function onLoad() {
if ((request.status >= 200 && request.status < 300) ||
request.status === 304 || request.status === 0) {
_mapDocumentToSource(request.response);
_load.resolve();
} else {
var msg = "Received status " + request.status + " from XMLHttpRequest while attempting to load source file at " + exports.url;
_load.reject(msg, { message: msg });
}
};
request.onerror = function onError() {
var msg = "Could not load source file at " + exports.url;
_load.reject(msg, { message: msg });
};
request.send(null);
}
// WebInspector Event: Page.loadEventFired
function _onLoadEventFired(event, res) {
// res = {timestamp}
Inspector.DOM.getDocument(function onGetDocument(res) {
exports.trigger("getDocument", res);
// res = {root}
_idToNode = {};
_pendingRequests = 0;
exports.root = new DOMNode(exports, res.root);
});
}
// WebInspector Event: Page.frameNavigated
function _onFrameNavigated(event, res) {
// res = {frame}
if (!res.frame.parentId) {
exports.url = _cleanURL(res.frame.url);
}
}
// WebInspector Event: DOM.documentUpdated
function _onDocumentUpdated(event, res) {
// res = {}
}
// WebInspector Event: DOM.setChildNodes
function _onSetChildNodes(event, res) {
// res = {parentId, nodes}
var node = nodeWithId(res.parentId);
node.setChildrenPayload(res.nodes);
if (_pendingRequests > 0 && --_pendingRequests === 0) {
_onFinishedLoadingDOM();
}
}
// WebInspector Event: DOM.childNodeCountUpdated
function _onChildNodeCountUpdated(event, res) {
// res = {nodeId, childNodeCount}
if (res.nodeId > 0) {
Inspector.DOM.requestChildNodes(res.nodeId);
}
}
// WebInspector Event: DOM.childNodeInserted
function _onChildNodeInserted(event, res) {
// res = {parentNodeId, previousNodeId, node}
if (res.node.nodeId > 0) {
var parent = nodeWithId(res.parentNodeId);
var previousNode = nodeWithId(res.previousNodeId);
var node = new DOMNode(exports, res.node);
parent.insertChildAfter(node, previousNode);
}
}
// WebInspector Event: DOM.childNodeRemoved
function _onChildNodeRemoved(event, res) {
// res = {parentNodeId, nodeId}
if (res.nodeId > 0) {
var node = nodeWithId(res.nodeId);
node.remove();
}
}
Update the node index
function addNode(node) {
if (node.nodeId) {
_idToNode[node.nodeId] = node;
}
}
Get the element node that encloses the given location
function allNodesAtLocation(location) {
var nodes = [];
exports.root.each(function each(n) {
if (n.type === DOMNode.TYPE_ELEMENT && n.isAtLocation(location)) {
nodes.push(n);
}
});
return nodes;
}
Apply a change
function applyChange(from, to, text) {
var delta = from - to + text.length;
var node = nodeAtLocation(from);
// insert a text node
if (!node) {
if (!(/^\s*$/).test(text)) {
console.warn("Inserting nodes not supported.");
node = nodeBeforeLocation(from);
}
} else if (node.type === 3) {
// update a text node
var value = node.value.substr(0, from - node.location);
value += text;
value += node.value.substr(to - node.location);
node.value = value;
if (!EditAgent.isEditing) {
// only update the DOM if the change was not caused by the edit agent
Inspector.DOM.setNodeValue(node.nodeId, node.value);
}
} else {
console.warn("Changing non-text nodes not supported.");
}
// adjust the location of all nodes after the change
if (node) {
node.length += delta;
exports.root.each(function each(n) {
if (n.location > node.location) {
n.location += delta;
}
if (n.closeLocation !== undefined && n.closeLocation > node.location) {
n.closeLocation += delta;
}
});
}
}
Initialize the agent
function load() {
_load = new $.Deferred();
Inspector.Page
.on("frameNavigated.DOMAgent", _onFrameNavigated)
.on("loadEventFired.DOMAgent", _onLoadEventFired);
Inspector.DOM
.on("documentUpdated.DOMAgent", _onDocumentUpdated)
.on("setChildNodes.DOMAgent", _onSetChildNodes)
.on("childNodeCountUpdated.DOMAgent", _onChildNodeCountUpdated)
.on("childNodeInserted.DOMAgent", _onChildNodeInserted)
.on("childNodeRemoved.DOMAgent", _onChildNodeRemoved);
return _load.promise();
}
Get the node at the given location
function nodeAtLocation(location) {
return exports.root.find(function each(n) {
return n.isAtLocation(location, false);
});
}
Get the last node before the given location
function nodeBeforeLocation(location) {
var node;
exports.root.each(function each(n) {
if (!n.location || location < n.location) {
return true;
}
if (!node || node.location < n.location) {
node = n;
}
});
return node;
}
Find the node for the given id
function nodeWithId(nodeId) {
return _idToNode[nodeId];
}
Update the node index
function removeNode(node) {
if (node.nodeId) {
delete _idToNode[node.nodeId];
}
}
Request the child nodes for a node
function requestChildNodes(node) {
if (_pendingRequests >= 0) {
_pendingRequests++;
}
Inspector.DOM.requestChildNodes(node.nodeId);
}
Clean up
function unload() {
Inspector.Page.off(".DOMAgent");
Inspector.DOM.off(".DOMAgent");
}
EventDispatcher.makeEventDispatcher(exports);
// Export private functions
exports.enable = enable;
exports.disable = disable;
exports.nodeBeforeLocation = nodeBeforeLocation;
exports.allNodesAtLocation = allNodesAtLocation;
exports.nodeAtLocation = nodeAtLocation;
exports.nodeWithId = nodeWithId;
exports.removeNode = removeNode;
exports.addNode = addNode;
exports.requestChildNodes = requestChildNodes;
exports.applyChange = applyChange;
exports.load = load;
exports.unload = unload;
});