Home | History | Annotate | Download | only in front-end
      1 /*
      2  * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
      3  * Copyright (C) 2009 Joseph Pecoraro
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  *     * Redistributions of source code must retain the above copyright
     10  * notice, this list of conditions and the following disclaimer.
     11  *     * Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *     * Neither the name of Google Inc. nor the names of its
     16  * contributors may be used to endorse or promote products derived from
     17  * this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 WebInspector.DOMNode = function(doc, payload) {
     33     this.ownerDocument = doc;
     34 
     35     this.id = payload.id;
     36     this._nodeType = payload.nodeType;
     37     this._nodeName = payload.nodeName;
     38     this._localName = payload.localName;
     39     this._nodeValue = payload.nodeValue;
     40 
     41     this._attributes = [];
     42     this._attributesMap = {};
     43     if (payload.attributes)
     44         this._setAttributesPayload(payload.attributes);
     45 
     46     this._childNodeCount = payload.childNodeCount;
     47     this.children = null;
     48 
     49     this.nextSibling = null;
     50     this.prevSibling = null;
     51     this.firstChild = null;
     52     this.lastChild = null;
     53     this.parentNode = null;
     54 
     55     if (payload.children)
     56         this._setChildrenPayload(payload.children);
     57 
     58     this._computedStyle = null;
     59     this.style = null;
     60     this._matchedCSSRules = [];
     61 
     62     if (this._nodeType === Node.ELEMENT_NODE) {
     63         // HTML and BODY from internal iframes should not overwrite top-level ones.
     64         if (!this.ownerDocument.documentElement && this._nodeName === "HTML")
     65             this.ownerDocument.documentElement = this;
     66         if (!this.ownerDocument.body && this._nodeName === "BODY")
     67             this.ownerDocument.body = this;
     68         if (payload.documentURL)
     69             this.documentURL = payload.documentURL;
     70     } else if (this._nodeType === Node.DOCUMENT_TYPE_NODE) {
     71         this.publicId = payload.publicId;
     72         this.systemId = payload.systemId;
     73         this.internalSubset = payload.internalSubset;
     74     } else if (this._nodeType === Node.DOCUMENT_NODE) {
     75         this.documentURL = payload.documentURL;
     76     } else if (this._nodeType === Node.ATTRIBUTE_NODE) {
     77         this.name = payload.name;
     78         this.value = payload.value;
     79     }
     80 }
     81 
     82 WebInspector.DOMNode.prototype = {
     83     hasAttributes: function()
     84     {
     85         return this._attributes.length > 0;
     86     },
     87 
     88     hasChildNodes: function()
     89     {
     90         return this._childNodeCount > 0;
     91     },
     92 
     93     nodeType: function()
     94     {
     95         return this._nodeType;
     96     },
     97 
     98     nodeName: function()
     99     {
    100         return this._nodeName;
    101     },
    102 
    103     setNodeName: function(name, callback)
    104     {
    105         DOMAgent.setNodeName(this.id, name, callback);
    106     },
    107 
    108     localName: function()
    109     {
    110         return this._localName;
    111     },
    112 
    113     nodeValue: function()
    114     {
    115         return this._nodeValue;
    116     },
    117 
    118     setNodeValue: function(value, callback)
    119     {
    120         DOMAgent.setNodeValue(this.id, value, callback);
    121     },
    122 
    123     getAttribute: function(name)
    124     {
    125         var attr = this._attributesMap[name];
    126         return attr ? attr.value : undefined;
    127     },
    128 
    129     setAttribute: function(name, value, callback)
    130     {
    131         function mycallback(error)
    132         {
    133             if (!error) {
    134                 var attr = this._attributesMap[name];
    135                 if (attr)
    136                     attr.value = value;
    137                 else
    138                     attr = this._addAttribute(name, value);
    139             }
    140 
    141             if (callback)
    142                 callback();
    143         }
    144         DOMAgent.setAttribute(this.id, name, value, mycallback.bind(this));
    145     },
    146 
    147     attributes: function()
    148     {
    149         return this._attributes;
    150     },
    151 
    152     removeAttribute: function(name, callback)
    153     {
    154         function mycallback(error, success)
    155         {
    156             if (!error) {
    157                 delete this._attributesMap[name];
    158                 for (var i = 0;  i < this._attributes.length; ++i) {
    159                     if (this._attributes[i].name === name) {
    160                         this._attributes.splice(i, 1);
    161                         break;
    162                     }
    163                 }
    164             }
    165 
    166             if (callback)
    167                 callback();
    168         }
    169         DOMAgent.removeAttribute(this.id, name, mycallback.bind(this));
    170     },
    171 
    172     getChildNodes: function(callback)
    173     {
    174         if (this.children) {
    175             if (callback)
    176                 callback(this.children);
    177             return;
    178         }
    179 
    180         function mycallback(error) {
    181             if (!error && callback)
    182                 callback(this.children);
    183         }
    184         DOMAgent.getChildNodes(this.id, mycallback.bind(this));
    185     },
    186 
    187     getOuterHTML: function(callback)
    188     {
    189         DOMAgent.getOuterHTML(this.id, callback);
    190     },
    191 
    192     setOuterHTML: function(html, callback)
    193     {
    194         DOMAgent.setOuterHTML(this.id, html, callback);
    195     },
    196 
    197     removeNode: function(callback)
    198     {
    199         DOMAgent.removeNode(this.id, callback);
    200     },
    201 
    202     copyNode: function(callback)
    203     {
    204         DOMAgent.copyNode(this.id, callback);
    205     },
    206 
    207     eventListeners: function(callback)
    208     {
    209         DOMAgent.getEventListenersForNode(this.id, callback);
    210     },
    211 
    212     path: function()
    213     {
    214         var path = [];
    215         var node = this;
    216         while (node && "index" in node && node._nodeName.length) {
    217             path.push([node.index, node._nodeName]);
    218             node = node.parentNode;
    219         }
    220         path.reverse();
    221         return path.join(",");
    222     },
    223 
    224     appropriateSelectorFor: function(justSelector)
    225     {
    226         var lowerCaseName = this.localName() || node.nodeName().toLowerCase();
    227 
    228         var id = this.getAttribute("id");
    229         if (id) {
    230             var selector = "#" + id;
    231             return (justSelector ? selector : lowerCaseName + selector);
    232         }
    233 
    234         var className = this.getAttribute("class");
    235         if (className) {
    236             var selector = "." + className.replace(/\s+/, ".");
    237             return (justSelector ? selector : lowerCaseName + selector);
    238         }
    239 
    240         if (lowerCaseName === "input" && this.getAttribute("type"))
    241             return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"]";
    242 
    243         return lowerCaseName;
    244     },
    245 
    246     _setAttributesPayload: function(attrs)
    247     {
    248         this._attributes = [];
    249         this._attributesMap = {};
    250         for (var i = 0; i < attrs.length; i += 2)
    251             this._addAttribute(attrs[i], attrs[i + 1]);
    252     },
    253 
    254     _insertChild: function(prev, payload)
    255     {
    256         var node = new WebInspector.DOMNode(this.ownerDocument, payload);
    257         if (!prev) {
    258             if (!this.children) {
    259                 // First node
    260                 this.children = [ node ];
    261             } else
    262                 this.children.unshift(node);
    263         } else
    264             this.children.splice(this.children.indexOf(prev) + 1, 0, node);
    265         this._renumber();
    266         return node;
    267     },
    268 
    269     removeChild_: function(node)
    270     {
    271         this.children.splice(this.children.indexOf(node), 1);
    272         node.parentNode = null;
    273         this._renumber();
    274     },
    275 
    276     _setChildrenPayload: function(payloads)
    277     {
    278         this.children = [];
    279         for (var i = 0; i < payloads.length; ++i) {
    280             var payload = payloads[i];
    281             var node = new WebInspector.DOMNode(this.ownerDocument, payload);
    282             this.children.push(node);
    283         }
    284         this._renumber();
    285     },
    286 
    287     _renumber: function()
    288     {
    289         this._childNodeCount = this.children.length;
    290         if (this._childNodeCount == 0) {
    291             this.firstChild = null;
    292             this.lastChild = null;
    293             return;
    294         }
    295         this.firstChild = this.children[0];
    296         this.lastChild = this.children[this._childNodeCount - 1];
    297         for (var i = 0; i < this._childNodeCount; ++i) {
    298             var child = this.children[i];
    299             child.index = i;
    300             child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
    301             child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
    302             child.parentNode = this;
    303         }
    304     },
    305 
    306     _addAttribute: function(name, value)
    307     {
    308         var attr = {
    309             "name": name,
    310             "value": value,
    311             "_node": this
    312         };
    313         this._attributesMap[name] = attr;
    314         this._attributes.push(attr);
    315     },
    316 
    317     ownerDocumentElement: function()
    318     {
    319         // document element is the child of the document / frame owner node that has documentURL property.
    320         // FIXME: return document nodes as a part of the DOM tree structure.
    321         var node = this;
    322         while (node.parentNode && !node.parentNode.documentURL)
    323             node = node.parentNode;
    324         return node;
    325     }
    326 }
    327 
    328 WebInspector.DOMDocument = function(domAgent, payload)
    329 {
    330     WebInspector.DOMNode.call(this, this, payload);
    331     this._listeners = {};
    332     this._domAgent = domAgent;
    333 }
    334 
    335 WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;
    336 
    337 WebInspector.DOMAgent = function() {
    338     this._idToDOMNode = null;
    339     this._document = null;
    340     InspectorBackend.registerDomainDispatcher("DOM", new WebInspector.DOMDispatcher(this));
    341 }
    342 
    343 WebInspector.DOMAgent.Events = {
    344     AttrModified: "AttrModified",
    345     CharacterDataModified: "CharacterDataModified",
    346     NodeInserted: "NodeInserted",
    347     NodeRemoved: "NodeRemoved",
    348     DocumentUpdated: "DocumentUpdated",
    349     ChildNodeCountUpdated: "ChildNodeCountUpdated"
    350 }
    351 
    352 WebInspector.DOMAgent.prototype = {
    353     requestDocument: function(callback)
    354     {
    355         if (this._document) {
    356             if (callback)
    357                 callback(this._document);
    358             return;
    359         }
    360 
    361         if (this._pendingDocumentRequestCallbacks) {
    362             this._pendingDocumentRequestCallbacks.push(callback);
    363             return;
    364         }
    365 
    366         this._pendingDocumentRequestCallbacks = [callback];
    367 
    368         function onDocumentAvailable(error, root)
    369         {
    370             if (!error)
    371                 this._setDocument(root);
    372 
    373             for (var i = 0; i < this._pendingDocumentRequestCallbacks.length; ++i) {
    374                 var callback = this._pendingDocumentRequestCallbacks[i];
    375                 if (callback)
    376                     callback(this._document);
    377             }
    378             delete this._pendingDocumentRequestCallbacks;
    379         }
    380 
    381         DOMAgent.getDocument(onDocumentAvailable.bind(this));
    382     },
    383 
    384     pushNodeToFrontend: function(objectId, callback)
    385     {
    386         this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeToFrontend.bind(DOMAgent), objectId, callback);
    387     },
    388 
    389     pushNodeByPathToFrontend: function(path, callback)
    390     {
    391         this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent), path, callback);
    392     },
    393 
    394     _wrapClientCallback: function(callback)
    395     {
    396         if (!callback)
    397             return;
    398         return function(error, result)
    399         {
    400             if (error)
    401                 console.error("Error during DOMAgent operation: " + error);
    402             callback(error ? null : result);
    403         }
    404     },
    405 
    406     _dispatchWhenDocumentAvailable: function(action)
    407     {
    408         var requestArguments = Array.prototype.slice.call(arguments, 1);
    409         var callbackWrapper;
    410 
    411         if (typeof requestArguments[requestArguments.length - 1] === "function") {
    412             var callback = requestArguments.pop();
    413             callbackWrapper = this._wrapClientCallback(callback);
    414             requestArguments.push(callbackWrapper);
    415         }
    416         function onDocumentAvailable()
    417         {
    418             if (this._document)
    419                 action.apply(null, requestArguments);
    420             else {
    421                 if (callbackWrapper)
    422                     callbackWrapper("No document");
    423             }
    424         }
    425         this.requestDocument(onDocumentAvailable.bind(this));
    426     },
    427 
    428     _attributesUpdated: function(nodeId, attrsArray)
    429     {
    430         var node = this._idToDOMNode[nodeId];
    431         node._setAttributesPayload(attrsArray);
    432         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrModified, node);
    433     },
    434 
    435     _characterDataModified: function(nodeId, newValue)
    436     {
    437         var node = this._idToDOMNode[nodeId];
    438         node._nodeValue = newValue;
    439         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.CharacterDataModified, node);
    440     },
    441 
    442     nodeForId: function(nodeId)
    443     {
    444         return this._idToDOMNode[nodeId];
    445     },
    446 
    447     _documentUpdated: function()
    448     {
    449         this._setDocument(null);
    450         this.requestDocument();
    451     },
    452 
    453     _setDocument: function(payload)
    454     {
    455         this._idToDOMNode = {};
    456         if (payload && "id" in payload) {
    457             this._document = new WebInspector.DOMDocument(this, payload);
    458             this._idToDOMNode[payload.id] = this._document;
    459             if (this._document.children)
    460                 this._bindNodes(this._document.children);
    461         } else
    462             this._document = null;
    463         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.DocumentUpdated, this._document);
    464     },
    465 
    466     _setDetachedRoot: function(payload)
    467     {
    468         var root = new WebInspector.DOMNode(this._document, payload);
    469         this._idToDOMNode[payload.id] = root;
    470     },
    471 
    472     _setChildNodes: function(parentId, payloads)
    473     {
    474         if (!parentId && payloads.length) {
    475             this._setDetachedRoot(payloads[0]);
    476             return;
    477         }
    478 
    479         var parent = this._idToDOMNode[parentId];
    480         parent._setChildrenPayload(payloads);
    481         this._bindNodes(parent.children);
    482     },
    483 
    484     _bindNodes: function(children)
    485     {
    486         for (var i = 0; i < children.length; ++i) {
    487             var child = children[i];
    488             this._idToDOMNode[child.id] = child;
    489             if (child.children)
    490                 this._bindNodes(child.children);
    491         }
    492     },
    493 
    494     _childNodeCountUpdated: function(nodeId, newValue)
    495     {
    496         var node = this._idToDOMNode[nodeId];
    497         node._childNodeCount = newValue;
    498         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.ChildNodeCountUpdated, node);
    499     },
    500 
    501     _childNodeInserted: function(parentId, prevId, payload)
    502     {
    503         var parent = this._idToDOMNode[parentId];
    504         var prev = this._idToDOMNode[prevId];
    505         var node = parent._insertChild(prev, payload);
    506         this._idToDOMNode[node.id] = node;
    507         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeInserted, node);
    508     },
    509 
    510     _childNodeRemoved: function(parentId, nodeId)
    511     {
    512         var parent = this._idToDOMNode[parentId];
    513         var node = this._idToDOMNode[nodeId];
    514         parent.removeChild_(node);
    515         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeRemoved, {node:node, parent:parent});
    516         delete this._idToDOMNode[nodeId];
    517         if (Preferences.nativeInstrumentationEnabled)
    518             WebInspector.panels.elements.sidebarPanes.domBreakpoints.nodeRemoved(node);
    519     },
    520 
    521     performSearch: function(query, searchResultCollector, searchSynchronously)
    522     {
    523         this._searchResultCollector = searchResultCollector;
    524         DOMAgent.performSearch(query, !!searchSynchronously);
    525     },
    526 
    527     cancelSearch: function()
    528     {
    529         delete this._searchResultCollector;
    530         DOMAgent.cancelSearch();
    531     },
    532 
    533     querySelector: function(nodeId, selectors, callback)
    534     {
    535         DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callback));
    536     },
    537 
    538     querySelectorAll: function(nodeId, selectors, callback)
    539     {
    540         DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback));
    541     }
    542 }
    543 
    544 WebInspector.DOMAgent.prototype.__proto__ = WebInspector.Object.prototype;
    545 
    546 WebInspector.DOMDispatcher = function(domAgent)
    547 {
    548     this._domAgent = domAgent;
    549 }
    550 
    551 WebInspector.DOMDispatcher.prototype = {
    552     documentUpdated: function()
    553     {
    554         this._domAgent._documentUpdated();
    555     },
    556 
    557     attributesUpdated: function(nodeId, attrsArray)
    558     {
    559         this._domAgent._attributesUpdated(nodeId, attrsArray);
    560     },
    561 
    562     characterDataModified: function(nodeId, newValue)
    563     {
    564         this._domAgent._characterDataModified(nodeId, newValue);
    565     },
    566 
    567     setChildNodes: function(parentId, payloads)
    568     {
    569         this._domAgent._setChildNodes(parentId, payloads);
    570     },
    571 
    572     childNodeCountUpdated: function(nodeId, newValue)
    573     {
    574         this._domAgent._childNodeCountUpdated(nodeId, newValue);
    575     },
    576 
    577     childNodeInserted: function(parentId, prevId, payload)
    578     {
    579         this._domAgent._childNodeInserted(parentId, prevId, payload);
    580     },
    581 
    582     childNodeRemoved: function(parentId, nodeId)
    583     {
    584         this._domAgent._childNodeRemoved(parentId, nodeId);
    585     },
    586 
    587     inspectElementRequested: function(nodeId)
    588     {
    589         WebInspector.updateFocusedNode(nodeId);
    590     },
    591 
    592     searchResults: function(nodeIds)
    593     {
    594         if (this._domAgent._searchResultCollector)
    595             this._domAgent._searchResultCollector(nodeIds);
    596     }
    597 }
    598