ef708c4
[chromium-blink-merge.git] / 
blobef708c43d07eb7aa71b169adee1472a74977b614
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  */
32 /**
33  * @constructor
34  * @param {WebInspector.DOMAgent} domAgent
35  * @param {?WebInspector.DOMDocument} doc
36  * @param {DOMAgent.Node} payload
37  */
38 WebInspector.DOMNode = function(domAgent, doc, payload) {
39     this._domAgent = domAgent;
40     this.ownerDocument = doc;
42     this.id = payload.nodeId;
43     domAgent._idToDOMNode[this.id] = this;
44     this._nodeType = payload.nodeType;
45     this._nodeName = payload.nodeName;
46     this._localName = payload.localName;
47     this._nodeValue = payload.nodeValue;
49     this._attributes = [];
50     this._attributesMap = {};
51     if (payload.attributes)
52         this._setAttributesPayload(payload.attributes);
54     this._childNodeCount = payload.childNodeCount;
55     this.children = null;
57     this.nextSibling = null;
58     this.previousSibling = null;
59     this.firstChild = null;
60     this.lastChild = null;
61     this.parentNode = null;
63     if (payload.children)
64         this._setChildrenPayload(payload.children);
66     if (payload.contentDocument) {
67         this._contentDocument = new WebInspector.DOMDocument(domAgent, payload.contentDocument);
68         this.children = [this._contentDocument];
69         this._renumber();
70     }
72     if (this._nodeType === Node.ELEMENT_NODE) {
73         // HTML and BODY from internal iframes should not overwrite top-level ones.
74         if (this.ownerDocument && !this.ownerDocument.documentElement && this._nodeName === "HTML")
75             this.ownerDocument.documentElement = this;
76         if (this.ownerDocument && !this.ownerDocument.body && this._nodeName === "BODY")
77             this.ownerDocument.body = this;
78     } else if (this._nodeType === Node.DOCUMENT_TYPE_NODE) {
79         this.publicId = payload.publicId;
80         this.systemId = payload.systemId;
81         this.internalSubset = payload.internalSubset;
82     } else if (this._nodeType === Node.ATTRIBUTE_NODE) {
83         this.name = payload.name;
84         this.value = payload.value;
85     }
88 /**
89  * @constructor
90  * @param {string} value
91  * @param {boolean} optimized
92  */
93 WebInspector.DOMNode.XPathStep = function(value, optimized)
95     this.value = value;
96     this.optimized = optimized;
99 WebInspector.DOMNode.XPathStep.prototype = {
100     toString: function()
101     {
102         return this.value;
103     }
106 WebInspector.DOMNode.prototype = {
107     /**
108      * @return {boolean}
109      */
110     hasAttributes: function()
111     {
112         return this._attributes.length > 0;
113     },
115     /**
116      * @return {boolean}
117      */
118     hasChildNodes: function()
119     {
120         return this._childNodeCount > 0;
121     },
123     /**
124      * @return {number}
125      */
126     nodeType: function()
127     {
128         return this._nodeType;
129     },
131     /**
132      * @return {string}
133      */
134     nodeName: function()
135     {
136         return this._nodeName;
137     },
139     /**
140      * @return {string}
141      */
142     nodeNameInCorrectCase: function()
143     {
144         return this.isXMLNode() ? this.nodeName() : this.nodeName().toLowerCase();
145     },
147     /**
148      * @param {string} name
149      * @param {function(?Protocol.Error)=} callback
150      */
151     setNodeName: function(name, callback)
152     {
153         DOMAgent.setNodeName(this.id, name, WebInspector.domAgent._markRevision(this, callback));
154     },
156     /**
157      * @return {string}
158      */
159     localName: function()
160     {
161         return this._localName;
162     },
164     /**
165      * @return {string}
166      */
167     nodeValue: function()
168     {
169         return this._nodeValue;
170     },
172     /**
173      * @param {string} value
174      * @param {function(?Protocol.Error)=} callback
175      */
176     setNodeValue: function(value, callback)
177     {
178         DOMAgent.setNodeValue(this.id, value, WebInspector.domAgent._markRevision(this, callback));
179     },
181     /**
182      * @param {string} name
183      * @return {string}
184      */
185     getAttribute: function(name)
186     {
187         var attr = this._attributesMap[name];
188         return attr ? attr.value : undefined;
189     },
191     /**
192      * @param {string} name
193      * @param {string} text
194      * @param {function(?Protocol.Error)=} callback
195      */
196     setAttribute: function(name, text, callback)
197     {
198         DOMAgent.setAttributesAsText(this.id, text, name, WebInspector.domAgent._markRevision(this, callback));
199     },
201     /**
202      * @param {string} name
203      * @param {string} value
204      * @param {function(?Protocol.Error)=} callback
205      */
206     setAttributeValue: function(name, value, callback)
207     {
208         DOMAgent.setAttributeValue(this.id, name, value, WebInspector.domAgent._markRevision(this, callback));
209     },
211     /**
212      * @return {Object}
213      */
214     attributes: function()
215     {
216         return this._attributes;
217     },
219     /**
220      * @param {string} name
221      * @param {function(?Protocol.Error)=} callback
222      */
223     removeAttribute: function(name, callback)
224     {
225         function mycallback(error, success)
226         {
227             if (!error) {
228                 delete this._attributesMap[name];
229                 for (var i = 0;  i < this._attributes.length; ++i) {
230                     if (this._attributes[i].name === name) {
231                         this._attributes.splice(i, 1);
232                         break;
233                     }
234                 }
235             }
237             WebInspector.domAgent._markRevision(this, callback)(error);
238         }
239         DOMAgent.removeAttribute(this.id, name, mycallback.bind(this));
240     },
242     /**
243      * @param {function(Array.<WebInspector.DOMNode>)=} callback
244      */
245     getChildNodes: function(callback)
246     {
247         if (this.children) {
248             if (callback)
249                 callback(this.children);
250             return;
251         }
253         /**
254          * @this {WebInspector.DOMNode}
255          * @param {?Protocol.Error} error
256          */
257         function mycallback(error)
258         {
259             if (!error && callback)
260                 callback(this.children);
261         }
263         DOMAgent.requestChildNodes(this.id, mycallback.bind(this));
264     },
266     /**
267      * @param {function(?Protocol.Error)=} callback
268      */
269     getOuterHTML: function(callback)
270     {
271         DOMAgent.getOuterHTML(this.id, callback);
272     },
274     /**
275      * @param {string} html
276      * @param {function(?Protocol.Error)=} callback
277      */
278     setOuterHTML: function(html, callback)
279     {
280         DOMAgent.setOuterHTML(this.id, html, WebInspector.domAgent._markRevision(this, callback));
281     },
283     /**
284      * @param {function(?Protocol.Error)=} callback
285      */
286     removeNode: function(callback)
287     {
288         DOMAgent.removeNode(this.id, WebInspector.domAgent._markRevision(this, callback));
289     },
291     copyNode: function()
292     {
293         function copy(error, text)
294         {
295             if (!error)
296                 InspectorFrontendHost.copyText(text);
297         }
298         DOMAgent.getOuterHTML(this.id, copy);
299     },
301     /**
302      * @param {boolean} optimized
303      */
304     copyXPath: function(optimized)
305     {
306         InspectorFrontendHost.copyText(this.xPath(optimized));
307     },
309     /**
310      * @param {function(?Protocol.Error)=} callback
311      */
312     eventListeners: function(callback)
313     {
314         DOMAgent.getEventListenersForNode(this.id, callback);
315     },
317     /**
318      * @return {string}
319      */
320     path: function()
321     {
322         var path = [];
323         var node = this;
324         while (node && "index" in node && node._nodeName.length) {
325             path.push([node.index, node._nodeName]);
326             node = node.parentNode;
327         }
328         path.reverse();
329         return path.join(",");
330     },
332     /**
333      * @param {boolean} justSelector
334      * @return {string}
335      */
336     appropriateSelectorFor: function(justSelector)
337     {
338         var lowerCaseName = this.localName() || this.nodeName().toLowerCase();
340         var id = this.getAttribute("id");
341         if (id) {
342             var selector = "#" + id;
343             return (justSelector ? selector : lowerCaseName + selector);
344         }
346         var className = this.getAttribute("class");
347         if (className) {
348             var selector = "." + className.replace(/\s+/, ".");
349             return (justSelector ? selector : lowerCaseName + selector);
350         }
352         if (lowerCaseName === "input" && this.getAttribute("type"))
353             return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"]";
355         return lowerCaseName;
356     },
358     /**
359      * @param {WebInspector.DOMNode} node
360      * @return {boolean}
361      */
362     isAncestor: function(node)
363     {
364         if (!node)
365             return false;
367         var currentNode = node.parentNode;
368         while (currentNode) {
369             if (this === currentNode)
370                 return true;
371             currentNode = currentNode.parentNode;
372         }
373         return false;
374     },
376     /**
377      * @param {WebInspector.DOMNode} descendant
378      * @return {boolean}
379      */
380     isDescendant: function(descendant)
381     {
382         return descendant !== null && descendant.isAncestor(this);
383     },
385     /**
386      * @param {Array.<string>} attrs
387      */
388     _setAttributesPayload: function(attrs)
389     {
390         this._attributes = [];
391         this._attributesMap = {};
392         for (var i = 0; i < attrs.length; i += 2)
393             this._addAttribute(attrs[i], attrs[i + 1]);
394     },
396     /**
397      * @param {WebInspector.DOMNode} prev
398      * @param {DOMAgent.Node} payload
399      * @return {WebInspector.DOMNode}
400      */
401     _insertChild: function(prev, payload)
402     {
403         var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, payload);
404         if (!prev) {
405             if (!this.children) {
406                 // First node
407                 this.children = [ node ];
408             } else
409                 this.children.unshift(node);
410         } else
411             this.children.splice(this.children.indexOf(prev) + 1, 0, node);
412         this._renumber();
413         return node;
414     },
416     /**
417      * @param {WebInspector.DOMNode} node
418      */
419     _removeChild: function(node)
420     {
421         this.children.splice(this.children.indexOf(node), 1);
422         node.parentNode = null;
423         this._renumber();
424     },
426     /**
427      * @param {Array.<DOMAgent.Node>} payloads
428      */
429     _setChildrenPayload: function(payloads)
430     {
431         // We set children in the constructor.
432         if (this._contentDocument)
433             return;
435         this.children = [];
436         for (var i = 0; i < payloads.length; ++i) {
437             var payload = payloads[i];
438             var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, payload);
439             this.children.push(node);
440         }
441         this._renumber();
442     },
444     _renumber: function()
445     {
446         this._childNodeCount = this.children.length;
447         if (this._childNodeCount == 0) {
448             this.firstChild = null;
449             this.lastChild = null;
450             return;
451         }
452         this.firstChild = this.children[0];
453         this.lastChild = this.children[this._childNodeCount - 1];
454         for (var i = 0; i < this._childNodeCount; ++i) {
455             var child = this.children[i];
456             child.index = i;
457             child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
458             child.previousSibling = i - 1 >= 0 ? this.children[i - 1] : null;
459             child.parentNode = this;
460         }
461     },
463     /**
464      * @param {string} name
465      * @param {string} value
466      */
467     _addAttribute: function(name, value)
468     {
469         var attr = {
470             name: name,
471             value: value,
472             _node: this
473         };
474         this._attributesMap[name] = attr;
475         this._attributes.push(attr);
476     },
478     /**
479      * @param {string} name
480      * @param {string} value
481      */
482     _setAttribute: function(name, value)
483     {
484         var attr = this._attributesMap[name];
485         if (attr)
486             attr.value = value;
487         else
488             this._addAttribute(name, value);
489     },
491     /**
492      * @param {string} name
493      */
494     _removeAttribute: function(name)
495     {
496         var attr = this._attributesMap[name];
497         if (attr) {
498             this._attributes.remove(attr);
499             delete this._attributesMap[name];
500         }
501     },
503     /**
504      * @param {WebInspector.DOMNode} targetNode
505      * @param {?WebInspector.DOMNode} anchorNode
506      * @param {function(?Protocol.Error)=} callback
507      */
508     moveTo: function(targetNode, anchorNode, callback)
509     {
510         DOMAgent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, WebInspector.domAgent._markRevision(this, callback));
511     },
513     /**
514      * @return {boolean}
515      */
516     isXMLNode: function()
517     {
518         return !!this.ownerDocument && !!this.ownerDocument.xmlVersion;
519     },
521     /**
522      * @param {boolean} optimized
523      * @return {string}
524      */
525     xPath: function(optimized)
526     {
527         if (this._nodeType === Node.DOCUMENT_NODE)
528             return "/";
530         var steps = [];
531         var contextNode = this;
532         while (contextNode) {
533             var step = contextNode._xPathValue(optimized);
534             if (!step)
535                 break; // Error - bail out early.
536             steps.push(step);
537             if (step.optimized)
538                 break;
539             contextNode = contextNode.parentNode;
540         }
542         steps.reverse();
543         return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/");
544     },
546     /**
547      * @param {boolean} optimized
548      * @return {WebInspector.DOMNode.XPathStep}
549      */
550     _xPathValue: function(optimized)
551     {
552         var ownValue;
553         var ownIndex = this._xPathIndex();
554         if (ownIndex === -1)
555             return null; // Error.
557         switch (this._nodeType) {
558         case Node.ELEMENT_NODE:
559             if (optimized && this.getAttribute("id"))
560                 return new WebInspector.DOMNode.XPathStep("//*[@id=\"" + this.getAttribute("id") + "\"]", true);
561             ownValue = this._localName;
562             break;
563         case Node.ATTRIBUTE_NODE:
564             ownValue = "@" + this._nodeName;
565             break;
566         case Node.TEXT_NODE:
567         case Node.CDATA_SECTION_NODE:
568             ownValue = "text()";
569             break;
570         case Node.PROCESSING_INSTRUCTION_NODE:
571             ownValue = "processing-instruction()";
572             break;
573         case Node.COMMENT_NODE:
574             ownValue = "comment()";
575             break;
576         case Node.DOCUMENT_NODE:
577             ownValue = "";
578             break;
579         default:
580             ownValue = "";
581             break;
582         }
584         if (ownIndex > 0)
585             ownValue += "[" + ownIndex + "]";
587         return new WebInspector.DOMNode.XPathStep(ownValue, this._nodeType === Node.DOCUMENT_NODE);
588     },
590     /**
591      * @return {number}
592      */
593     _xPathIndex: function()
594     {
595         // Returns -1 in case of error, 0 if no siblings matching the same expression, <XPath index among the same expression-matching sibling nodes> otherwise.
596         function areNodesSimilar(left, right)
597         {
598             if (left === right)
599                 return true;
601             if (left._nodeType === Node.ELEMENT_NODE && right._nodeType === Node.ELEMENT_NODE)
602                 return left._localName === right._localName;
604             if (left._nodeType === right._nodeType)
605                 return true;
607             // XPath treats CDATA as text nodes.
608             var leftType = left._nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left._nodeType;
609             var rightType = right._nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right._nodeType;
610             return leftType === rightType;
611         }
613         var siblings = this.parentNode ? this.parentNode.children : null;
614         if (!siblings)
615             return 0; // Root node - no siblings.
616         var hasSameNamedElements;
617         for (var i = 0; i < siblings.length; ++i) {
618             if (areNodesSimilar(this, siblings[i]) && siblings[i] !== this) {
619                 hasSameNamedElements = true;
620                 break;
621             }
622         }
623         if (!hasSameNamedElements)
624             return 0;
625         var ownIndex = 1; // XPath indices start with 1.
626         for (var i = 0; i < siblings.length; ++i) {
627             if (areNodesSimilar(this, siblings[i])) {
628                 if (siblings[i] === this)
629                     return ownIndex;
630                 ++ownIndex;
631             }
632         }
633         return -1; // An error occurred: |this| not found in parent's children.
634     }
638  * @extends {WebInspector.DOMNode}
639  * @constructor
640  * @param {WebInspector.DOMAgent} domAgent
641  * @param {DOMAgent.Node} payload
642  */
643 WebInspector.DOMDocument = function(domAgent, payload)
645     WebInspector.DOMNode.call(this, domAgent, this, payload);
646     this.documentURL = payload.documentURL || "";
647     this.xmlVersion = payload.xmlVersion;
648     this._listeners = {};
651 WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;
654  * @extends {WebInspector.Object}
655  * @constructor
656  */
657 WebInspector.DOMAgent = function() {
658     /** @type {Object|undefined} */
659     this._idToDOMNode = {};
660     this._document = null;
661     this._attributeLoadNodeIds = {};
662     InspectorBackend.registerDOMDispatcher(new WebInspector.DOMDispatcher(this));
663     if (WebInspector.experimentsSettings.freeFlowDOMEditing.isEnabled())
664         new WebInspector.DOMModelResourceBinding(this);
666     if (WebInspector.settings.emulateTouchEvents.get())
667         this._emulateTouchEventsChanged();
668     WebInspector.settings.emulateTouchEvents.addChangeListener(this._emulateTouchEventsChanged, this);
671 WebInspector.DOMAgent.Events = {
672     AttrModified: "AttrModified",
673     AttrRemoved: "AttrRemoved",
674     CharacterDataModified: "CharacterDataModified",
675     NodeInserted: "NodeInserted",
676     NodeRemoved: "NodeRemoved",
677     DocumentUpdated: "DocumentUpdated",
678     ChildNodeCountUpdated: "ChildNodeCountUpdated",
679     InspectElementRequested: "InspectElementRequested",
680     StyleInvalidated: "StyleInvalidated",
681     UndoRedoRequested: "UndoRedoRequested",
682     UndoRedoCompleted: "UndoRedoCompleted"
685 WebInspector.DOMAgent.prototype = {
686     /**
687      * @param {function(WebInspector.DOMDocument)=} callback
688      */
689     requestDocument: function(callback)
690     {
691         if (this._document) {
692             if (callback)
693                 callback(this._document);
694             return;
695         }
697         if (this._pendingDocumentRequestCallbacks) {
698             this._pendingDocumentRequestCallbacks.push(callback);
699             return;
700         }
702         this._pendingDocumentRequestCallbacks = [callback];
704         /**
705          * @this {WebInspector.DOMAgent}
706          * @param {?Protocol.Error} error
707          * @param {DOMAgent.Node} root
708          */
709         function onDocumentAvailable(error, root)
710         {
711             if (!error)
712                 this._setDocument(root);
714             for (var i = 0; i < this._pendingDocumentRequestCallbacks.length; ++i) {
715                 var callback = this._pendingDocumentRequestCallbacks[i];
716                 if (callback)
717                     callback(this._document);
718             }
719             delete this._pendingDocumentRequestCallbacks;
720         }
722         DOMAgent.getDocument(onDocumentAvailable.bind(this));
723     },
725     /**
726      * @param {RuntimeAgent.RemoteObjectId} objectId
727      * @param {function()=} callback
728      */
729     pushNodeToFrontend: function(objectId, callback)
730     {
731         this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callback);
732     },
734     /**
735      * @param {string} path
736      * @param {function(?WebInspector.DOMNode)=} callback
737      */
738     pushNodeByPathToFrontend: function(path, callback)
739     {
740         var callbackCast = /** @type {function(*)} */ callback;
741         this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent, path), callbackCast);
742     },
744     /**
745      * @param {function(*)=} callback
746      * @return {function(?Protocol.Error,*=)|undefined}
747      */
748     _wrapClientCallback: function(callback)
749     {
750         if (!callback)
751             return;
752         return function(error, result)
753         {
754             if (error)
755                 console.error("Error during DOMAgent operation: " + error);
756             callback(error ? null : result);
757         }
758     },
760     /**
761      * @param {function(function()=)} func
762      * @param {function(*)=} callback
763      */
764     _dispatchWhenDocumentAvailable: function(func, callback)
765     {
766         var callbackWrapper = /** @type {function(?Protocol.Error, *=)} */ this._wrapClientCallback(callback);
768         function onDocumentAvailable()
769         {
770             if (this._document)
771                 func(callbackWrapper);
772             else {
773                 if (callbackWrapper)
774                     callbackWrapper("No document");
775             }
776         }
777         this.requestDocument(onDocumentAvailable.bind(this));
778     },
780     /**
781      * @param {DOMAgent.NodeId} nodeId
782      * @param {string} name
783      * @param {string} value
784      */
785     _attributeModified: function(nodeId, name, value)
786     {
787         var node = this._idToDOMNode[nodeId];
788         if (!node)
789             return;
790         node._setAttribute(name, value);
791         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrModified, { node: node, name: name });
792     },
794     /**
795      * @param {DOMAgent.NodeId} nodeId
796      * @param {string} name
797      */
798     _attributeRemoved: function(nodeId, name)
799     {
800         var node = this._idToDOMNode[nodeId];
801         if (!node)
802             return;
803         node._removeAttribute(name);
804         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrRemoved, { node: node, name: name });
805     },
807     /**
808      * @param {Array.<DOMAgent.NodeId>} nodeIds
809      */
810     _inlineStyleInvalidated: function(nodeIds)
811     {
812         for (var i = 0; i < nodeIds.length; ++i)
813             this._attributeLoadNodeIds[nodeIds[i]] = true;
814         if ("_loadNodeAttributesTimeout" in this)
815             return;
816         this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0);
817     },
819     _loadNodeAttributes: function()
820     {
821         /**
822          * @this {WebInspector.DOMAgent}
823          * @param {DOMAgent.NodeId} nodeId
824          * @param {?Protocol.Error} error
825          * @param {Array.<string>} attributes
826          */
827         function callback(nodeId, error, attributes)
828         {
829             if (error) {
830                 console.error("Error during DOMAgent operation: " + error);
831                 return;
832             }
833             var node = this._idToDOMNode[nodeId];
834             if (node) {
835                 node._setAttributesPayload(attributes);
836                 this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrModified, { node: node, name: "style" });
837                 this.dispatchEventToListeners(WebInspector.DOMAgent.Events.StyleInvalidated, node);                
838             }
839         }
841         delete this._loadNodeAttributesTimeout;
843         for (var nodeId in this._attributeLoadNodeIds) {
844             var nodeIdAsNumber = parseInt(nodeId, 10);
845             DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
846         }
847         this._attributeLoadNodeIds = {};
848     },
850     /**
851      * @param {DOMAgent.NodeId} nodeId
852      * @param {string} newValue
853      */
854     _characterDataModified: function(nodeId, newValue)
855     {
856         var node = this._idToDOMNode[nodeId];
857         node._nodeValue = newValue;
858         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.CharacterDataModified, node);
859     },
861     /**
862      * @param {DOMAgent.NodeId} nodeId
863      * @return {WebInspector.DOMNode|undefined}
864      */
865     nodeForId: function(nodeId)
866     {
867         return this._idToDOMNode[nodeId];
868     },
870     _documentUpdated: function()
871     {
872         this._setDocument(null);
873     },
875     /**
876      * @param {DOMAgent.Node} payload
877      */
878     _setDocument: function(payload)
879     {
880         this._idToDOMNode = {};
881         if (payload && "nodeId" in payload)
882             this._document = new WebInspector.DOMDocument(this, payload);
883         else
884             this._document = null;
885         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.DocumentUpdated, this._document);
886     },
888     /**
889      * @param {DOMAgent.Node} payload
890      */
891     _setDetachedRoot: function(payload)
892     {
893         new WebInspector.DOMNode(this, null, payload);
894     },
896     /**
897      * @param {DOMAgent.NodeId} parentId
898      * @param {Array.<DOMAgent.Node>} payloads
899      */
900     _setChildNodes: function(parentId, payloads)
901     {
902         if (!parentId && payloads.length) {
903             this._setDetachedRoot(payloads[0]);
904             return;
905         }
907         var parent = this._idToDOMNode[parentId];
908         parent._setChildrenPayload(payloads);
909     },
911     /**
912      * @param {DOMAgent.NodeId} nodeId
913      * @param {number} newValue
914      */
915     _childNodeCountUpdated: function(nodeId, newValue)
916     {
917         var node = this._idToDOMNode[nodeId];
918         node._childNodeCount = newValue;
919         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.ChildNodeCountUpdated, node);
920     },
922     /**
923      * @param {DOMAgent.NodeId} parentId
924      * @param {DOMAgent.NodeId} prevId
925      * @param {DOMAgent.Node} payload
926      */
927     _childNodeInserted: function(parentId, prevId, payload)
928     {
929         var parent = this._idToDOMNode[parentId];
930         var prev = this._idToDOMNode[prevId];
931         var node = parent._insertChild(prev, payload);
932         this._idToDOMNode[node.id] = node;
933         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeInserted, node);
934     },
936     /**
937      * @param {DOMAgent.NodeId} parentId
938      * @param {DOMAgent.NodeId} nodeId
939      */
940     _childNodeRemoved: function(parentId, nodeId)
941     {
942         var parent = this._idToDOMNode[parentId];
943         var node = this._idToDOMNode[nodeId];
944         parent._removeChild(node);
945         this._unbind(node);
946         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeRemoved, {node:node, parent:parent});
947     },
949     /**
950      * @param {DOMAgent.Node} node
951      */
952     _unbind: function(node)
953     {
954         delete this._idToDOMNode[node.id];
955         for (var i = 0; node.children && i < node.children.length; ++i)
956             this._unbind(node.children[i]);
957     },
959     /**
960      * @param {number} nodeId
961      */
962     inspectElement: function(nodeId)
963     {
964         var node = this._idToDOMNode[nodeId];
965         if (node)
966             this.dispatchEventToListeners(WebInspector.DOMAgent.Events.InspectElementRequested, node);
967     },
969     /**
970      * @param {string} query
971      * @param {function(number)} searchCallback
972      */
973     performSearch: function(query, searchCallback)
974     {
975         this.cancelSearch();
977         /**
978          * @param {?Protocol.Error} error
979          * @param {string} searchId
980          * @param {number} resultsCount
981          */
982         function callback(error, searchId, resultsCount)
983         {
984             this._searchId = searchId;
985             searchCallback(resultsCount);
986         }
987         DOMAgent.performSearch(query, callback.bind(this));
988     },
990     /**
991      * @param {number} index
992      * @param {?function(DOMAgent.Node)} callback
993      */
994     searchResult: function(index, callback)
995     {
996         if (this._searchId) {
997             /**
998              * @param {?Protocol.Error} error
999              * @param {Array.<number>} nodeIds
1000              */
1001             function mycallback(error, nodeIds)
1002             {
1003                 if (error) {
1004                     console.error(error);
1005                     callback(null);
1006                     return;
1007                 }
1008                 if (nodeIds.length != 1)
1009                     return;
1011                 callback(this._idToDOMNode[nodeIds[0]]);
1012             }
1013             DOMAgent.getSearchResults(this._searchId, index, index + 1, mycallback.bind(this));
1014         } else
1015             callback(null);
1016     },
1018     cancelSearch: function()
1019     {
1020         if (this._searchId) {
1021             DOMAgent.discardSearchResults(this._searchId);
1022             delete this._searchId;
1023         }
1024     },
1026     /**
1027      * @param {DOMAgent.NodeId} nodeId
1028      * @param {string} selectors
1029      * @param {function(?DOMAgent.NodeId)=} callback
1030      */
1031     querySelector: function(nodeId, selectors, callback)
1032     {
1033         var callbackCast = /** @type {function(*)|undefined} */callback;
1034         DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callbackCast));
1035     },
1037     /**
1038      * @param {DOMAgent.NodeId} nodeId
1039      * @param {string} selectors
1040      * @param {function(?Array.<DOMAgent.NodeId>)=} callback
1041      */
1042     querySelectorAll: function(nodeId, selectors, callback)
1043     {
1044         var callbackCast = /** @type {function(*)|undefined} */callback;
1045         DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callbackCast));
1046     },
1048     /**
1049      * @param {?number} nodeId
1050      * @param {string=} mode
1051      */
1052     highlightDOMNode: function(nodeId, mode)
1053     {
1054         if (this._hideDOMNodeHighlightTimeout) {
1055             clearTimeout(this._hideDOMNodeHighlightTimeout);
1056             delete this._hideDOMNodeHighlightTimeout;
1057         }
1059         this._highlightedDOMNodeId = nodeId;
1060         if (nodeId)
1061             DOMAgent.highlightNode(nodeId, this._buildHighlightConfig(mode));
1062         else
1063             DOMAgent.hideHighlight();
1064     },
1066     hideDOMNodeHighlight: function()
1067     {
1068         this.highlightDOMNode(0);
1069     },
1071     /**
1072      * @param {?DOMAgent.NodeId} nodeId
1073      */
1074     highlightDOMNodeForTwoSeconds: function(nodeId)
1075     {
1076         this.highlightDOMNode(nodeId);
1077         this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000);
1078     },
1080     /**
1081      * @param {boolean} enabled
1082      * @param {function()=} callback
1083      */
1084     setInspectModeEnabled: function(enabled, callback)
1085     {
1086         DOMAgent.setInspectModeEnabled(enabled, this._buildHighlightConfig(), callback);
1087     },
1089     /**
1090      * @param {string=} mode
1091      */
1092     _buildHighlightConfig: function(mode)
1093     {
1094         mode = mode || "all";
1095         var highlightConfig = { showInfo: mode === "all" };
1096         if (mode === "all" || mode === "content")
1097             highlightConfig.contentColor = WebInspector.Color.PageHighlight.Content.toProtocolRGBA();
1099         if (mode === "all" || mode === "padding")
1100             highlightConfig.paddingColor = WebInspector.Color.PageHighlight.Padding.toProtocolRGBA();
1102         if (mode === "all" || mode === "border")
1103             highlightConfig.borderColor = WebInspector.Color.PageHighlight.Border.toProtocolRGBA();
1105         if (mode === "all" || mode === "margin")
1106             highlightConfig.marginColor = WebInspector.Color.PageHighlight.Margin.toProtocolRGBA();
1108         return highlightConfig;
1109     },
1111     /**
1112      * @param {WebInspector.DOMNode} node
1113      * @param {function(?Protocol.Error)=} callback
1114      * @return {function(?Protocol.Error)}
1115      */
1116     _markRevision: function(node, callback)
1117     {
1118         function wrapperFunction(error)
1119         {
1120             if (callback)
1121                 callback.apply(this, arguments);
1122             if (error || !WebInspector.experimentsSettings.freeFlowDOMEditing.isEnabled())
1123                 return;
1124             if (this._captureDOMTimer)
1125                clearTimeout(this._captureDOMTimer);
1126             this._captureDOMTimer = setTimeout(this._captureDOM.bind(this, node), 500);
1127         }
1128         return wrapperFunction.bind(this);
1129     },
1131     /**
1132      * @param {WebInspector.DOMNode} node
1133      */
1134     _captureDOM: function(node)
1135     {
1136         delete this._captureDOMTimer;
1137         if (!node.ownerDocument)
1138             return;
1140         function callback(error, text)
1141         {
1142             if (error) {
1143                 console.error(error);
1144                 return;
1145             }
1147             var url = node.ownerDocument.documentURL;
1148             if (!url)
1149                 return;
1151             var resource = WebInspector.resourceForURL(url);
1152             if (!resource)
1153                 return;
1155             resource.addRevision(text);
1156         }
1157         DOMAgent.getOuterHTML(node.ownerDocument.id, callback);
1158         
1159     },
1161     _emulateTouchEventsChanged: function()
1162     {
1163         DOMAgent.setTouchEmulationEnabled(WebInspector.settings.emulateTouchEvents.get());
1164     },
1166     /**
1167      * @param {function(?Protocol.Error)=} callback
1168      */
1169     undo: function(callback)
1170     {
1171         function mycallback(error)
1172         {
1173             this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoCompleted);
1174             callback(error);
1175         }
1177         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoRequested);
1178         DOMAgent.undo(callback);
1179     },
1181     /**
1182      * @param {function(?Protocol.Error)=} callback
1183      */
1184     redo: function(callback)
1185     {
1186         function mycallback(error)
1187         {
1188             this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoCompleted);
1189             callback(error);
1190         }
1192         this.dispatchEventToListeners(WebInspector.DOMAgent.Events.UndoRedoRequested);
1193         DOMAgent.redo(callback);
1194     }
1197 WebInspector.DOMAgent.prototype.__proto__ = WebInspector.Object.prototype;
1200  * @constructor
1201  * @implements {DOMAgent.Dispatcher}
1202  * @param {WebInspector.DOMAgent} domAgent
1203  */
1204 WebInspector.DOMDispatcher = function(domAgent)
1206     this._domAgent = domAgent;
1209 WebInspector.DOMDispatcher.prototype = {
1210     documentUpdated: function()
1211     {
1212         this._domAgent._documentUpdated();
1213     },
1215     /**
1216      * @param {DOMAgent.NodeId} nodeId
1217      * @param {string} name
1218      * @param {string} value
1219      */
1220     attributeModified: function(nodeId, name, value)
1221     {
1222         this._domAgent._attributeModified(nodeId, name, value);
1223     },
1225     /**
1226      * @param {DOMAgent.NodeId} nodeId
1227      * @param {string} name
1228      */
1229     attributeRemoved: function(nodeId, name)
1230     {
1231         this._domAgent._attributeRemoved(nodeId, name);
1232     },
1234     /**
1235      * @param {Array.<DOMAgent.NodeId>} nodeIds
1236      */
1237     inlineStyleInvalidated: function(nodeIds)
1238     {
1239         this._domAgent._inlineStyleInvalidated(nodeIds);
1240     },
1242     /**
1243      * @param {DOMAgent.NodeId} nodeId
1244      * @param {string} characterData
1245      */
1246     characterDataModified: function(nodeId, characterData)
1247     {
1248         this._domAgent._characterDataModified(nodeId, characterData);
1249     },
1251     /**
1252      * @param {DOMAgent.NodeId} parentId
1253      * @param {Array.<DOMAgent.Node>} payloads
1254      */
1255     setChildNodes: function(parentId, payloads)
1256     {
1257         this._domAgent._setChildNodes(parentId, payloads);
1258     },
1260     /**
1261      * @param {DOMAgent.NodeId} nodeId
1262      * @param {number} childNodeCount
1263      */
1264     childNodeCountUpdated: function(nodeId, childNodeCount)
1265     {
1266         this._domAgent._childNodeCountUpdated(nodeId, childNodeCount);
1267     },
1269     /**
1270      * @param {DOMAgent.NodeId} parentNodeId
1271      * @param {DOMAgent.NodeId} previousNodeId
1272      * @param {DOMAgent.Node} payload
1273      */
1274     childNodeInserted: function(parentNodeId, previousNodeId, payload)
1275     {
1276         this._domAgent._childNodeInserted(parentNodeId, previousNodeId, payload);
1277     },
1279     /**
1280      * @param {DOMAgent.NodeId} parentNodeId
1281      * @param {DOMAgent.NodeId} nodeId
1282      */
1283     childNodeRemoved: function(parentNodeId, nodeId)
1284     {
1285         this._domAgent._childNodeRemoved(parentNodeId, nodeId);
1286     }
1290  * @type {?WebInspector.DOMAgent}
1291  */
1292 WebInspector.domAgent = null;
1295  * @constructor
1296  * @implements {WebInspector.ResourceDomainModelBinding}
1297  */
1298 WebInspector.DOMModelResourceBinding = function(domAgent)
1300     this._domAgent = domAgent;
1301     WebInspector.Resource.registerDomainModelBinding(WebInspector.Resource.Type.Document, this);
1304 WebInspector.DOMModelResourceBinding.prototype = {
1305     setContent: function(resource, content, majorChange, userCallback)
1306     {
1307         var frameId = resource.frameId;
1308         if (!frameId)
1309             return;
1311         PageAgent.setDocumentContent(frameId, content, callbackWrapper);
1313         function callbackWrapper(error)
1314         {
1315             if (majorChange)
1316                 resource.addRevision(content);
1317             if (userCallback)
1318                 userCallback(error);
1319         }
1320     },
1322     canSetContent: function()
1323     {
1324         return true;
1325     }
1328 WebInspector.DOMModelResourceBinding.prototype.__proto__ = WebInspector.ResourceDomainModelBinding.prototype;