Modules (181)

DOMAgent

Description

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.

Dependencies

Functions

Private

_cleanURL

Eliminate the query string from a URL

URL string
    function _cleanURL(url) {
        var index = url.search(/[#\?]/);
        if (index >= 0) {
            url = url.substr(0, index);
        }
        return url;
    }
Private

_mapDocumentToSource

Map the DOM document to the source text

source string
    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;
                }
            }
        });
    }
Private

_onFinishedLoadingDOM

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();
        }
    }
Public API

addNode

Update the node index

node DOMNode
    function addNode(node) {
        if (node.nodeId) {
            _idToNode[node.nodeId] = node;
        }
    }
Public API

allNodesAtLocation

Get the element node that encloses the given location

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;
    }
Public API

applyChange

Apply a change

start integer
offset of the change
end integer
offset of the change
change string
text
    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;
                }
            });
        }
    }
Public API

disable

Disable the domain

    function disable() {
        return Inspector.DOM.disable();
    }
Public API

enable

Enable the domain

    function enable() {
        return Inspector.DOM.enable();
    }
Public API

load

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();
    }
Public API

nodeAtLocation

Get the node at the given location

location
    function nodeAtLocation(location) {
        return exports.root.find(function each(n) {
            return n.isAtLocation(location, false);
        });
    }
Public API

nodeBeforeLocation

Get the last node before the given location

location integer
    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;
    }
Public API

nodeWithId

Find the node for the given id

node DOMNode
    function nodeWithId(nodeId) {
        return _idToNode[nodeId];
    }
Public API

removeNode

Update the node index

node DOMNode
    function removeNode(node) {
        if (node.nodeId) {
            delete _idToNode[node.nodeId];
        }
    }
Public API

requestChildNodes

Request the child nodes for a node

node DOMNode
    function requestChildNodes(node) {
        if (_pendingRequests >= 0) {
            _pendingRequests++;
        }
        Inspector.DOM.requestChildNodes(node.nodeId);
    }
Public API

unload

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;
});