2009-11-27 Pavel Feldman <pfeldman@dhcp-172-28-174-220.spb.corp.google.com>
[WebKit-https.git] / WebCore / inspector / front-end / DOMAgent.js
1 /*
2  * Copyright (C) 2009 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     this.textContent = this.nodeValue;
41
42     this.attributes = [];
43     this._attributesMap = {};
44     if (payload.attributes)
45         this._setAttributesPayload(payload.attributes);
46
47     this._childNodeCount = payload.childNodeCount;
48     this.children = null;
49
50     this.nextSibling = null;
51     this.prevSibling = null;
52     this.firstChild = null;
53     this.lastChild = null;
54     this.parentNode = null;
55
56     if (payload.children)
57         this._setChildrenPayload(payload.children);
58
59     this._computedStyle = null;
60     this.style = null;
61     this._matchedCSSRules = [];
62
63     if (this.nodeType == Node.ELEMENT_NODE) {
64         // HTML and BODY from internal iframes should not overwrite top-level ones.
65         if (!this.ownerDocument.documentElement && this.nodeName === "HTML")
66             this.ownerDocument.documentElement = this;
67         if (!this.ownerDocument.body && this.nodeName === "BODY")
68             this.ownerDocument.body = this;
69     }
70 }
71
72 WebInspector.DOMNode.prototype = {
73     hasAttributes: function()
74     {
75         return this.attributes.length > 0;
76     },
77
78     hasChildNodes: function()  {
79         return this._childNodeCount > 0;
80     },
81
82     get nodeValue() {
83         return this._nodeValue;
84     },
85
86     set nodeValue(value) {
87         if (this.nodeType != Node.TEXT_NODE)
88             return;
89         var self = this;
90         var callback = function()
91         {
92             self._nodeValue = value;
93             self.textContent = value;
94         };
95         this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, callback);
96     },
97
98     getAttribute: function(name)
99     {
100         var attr = this._attributesMap[name];
101         return attr ? attr.value : undefined;
102     },
103
104     setAttribute: function(name, value)
105     {
106         var self = this;
107         var callback = function()
108         {
109             var attr = self._attributesMap[name];
110             if (attr)
111                 attr.value = value;
112             else
113                 attr = self._addAttribute(name, value);
114         };
115         this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback);
116     },
117
118     removeAttribute: function(name)
119     {
120         var self = this;
121         var callback = function()
122         {
123             delete self._attributesMap[name];
124             for (var i = 0;  i < self.attributes.length; ++i) {
125                 if (self.attributes[i].name == name) {
126                     self.attributes.splice(i, 1);
127                     break;
128                 }
129             }
130         };
131         this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback);
132     },
133
134     _setAttributesPayload: function(attrs)
135     {
136         for (var i = 0; i < attrs.length; i += 2)
137             this._addAttribute(attrs[i], attrs[i + 1]);
138     },
139
140     _insertChild: function(prev, payload)
141     {
142         var node = new WebInspector.DOMNode(this.ownerDocument, payload);
143         if (!prev)
144             // First node
145             this.children = [ node ];
146         else
147             this.children.splice(this.children.indexOf(prev) + 1, 0, node);
148         this._renumber();
149         return node;
150     },
151
152     removeChild_: function(node)
153     {
154         this.children.splice(this.children.indexOf(node), 1);
155         node.parentNode = null;
156         this._renumber();
157     },
158
159     _setChildrenPayload: function(payloads)
160     {
161         this.children = [];
162         for (var i = 0; i < payloads.length; ++i) {
163             var payload = payloads[i];
164             var node = new WebInspector.DOMNode(this.ownerDocument, payload);
165             this.children.push(node);
166         }
167         this._renumber();
168     },
169
170     _renumber: function()
171     {
172         this._childNodeCount = this.children.length;
173         if (this._childNodeCount == 0) {
174             this.firstChild = null;
175             this.lastChild = null;
176             return;
177         }
178         this.firstChild = this.children[0];
179         this.lastChild = this.children[this._childNodeCount - 1];
180         for (var i = 0; i < this._childNodeCount; ++i) {
181             var child = this.children[i];
182             child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
183             child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
184             child.parentNode = this;
185         }
186     },
187
188     _addAttribute: function(name, value)
189     {
190         var attr = {
191             "name": name,
192             "value": value,
193             "_node": this
194         };
195         this._attributesMap[name] = attr;
196         this.attributes.push(attr);
197     },
198
199     _setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules)
200     {
201         this._computedStyle = new WebInspector.CSSStyleDeclaration(computedStyle);
202         this.style = new WebInspector.CSSStyleDeclaration(inlineStyle);
203
204         for (var name in styleAttributes) {
205             if (this._attributesMap[name])
206                 this._attributesMap[name].style = new WebInspector.CSSStyleDeclaration(styleAttributes[name]);
207         }
208
209         this._matchedCSSRules = [];
210         for (var i = 0; i < matchedCSSRules.length; i++)
211             this._matchedCSSRules.push(WebInspector.CSSStyleDeclaration.parseRule(matchedCSSRules[i]));
212     },
213
214     _clearStyles: function()
215     {
216         this.computedStyle = null;
217         this.style = null;
218         for (var name in this._attributesMap)
219             this._attributesMap[name].style = null;
220         this._matchedCSSRules = null;
221     }
222 }
223
224 WebInspector.DOMDocument = function(domAgent, defaultView, payload)
225 {
226     WebInspector.DOMNode.call(this, this, payload);
227     this._listeners = {};
228     this._domAgent = domAgent;
229     this.defaultView = defaultView;
230 }
231
232 WebInspector.DOMDocument.prototype = {
233
234     addEventListener: function(name, callback)
235     {
236         var listeners = this._listeners[name];
237         if (!listeners) {
238             listeners = [];
239             this._listeners[name] = listeners;
240         }
241         listeners.push(callback);
242     },
243
244     removeEventListener: function(name, callback)
245     {
246         var listeners = this._listeners[name];
247         if (!listeners)
248             return;
249
250         var index = listeners.indexOf(callback);
251         if (index != -1)
252             listeners.splice(index, 1);
253     },
254
255     _fireDomEvent: function(name, event)
256     {
257         var listeners = this._listeners[name];
258         if (!listeners)
259             return;
260
261         for (var i = 0; i < listeners.length; ++i) {
262             var listener = listeners[i];
263             listener.call(this, event);
264         }
265     }
266 }
267
268 WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;
269
270
271 WebInspector.DOMWindow = function(domAgent)
272 {
273     this._domAgent = domAgent;
274 }
275
276 WebInspector.DOMWindow.prototype = {
277     get document()
278     {
279         return this._domAgent.document;
280     },
281
282     get Node()
283     {
284         return WebInspector.DOMNode;
285     },
286
287     get Element()
288     {
289         return WebInspector.DOMNode;
290     },
291
292     Object: function()
293     {
294     },
295
296     getComputedStyle: function(node)
297     {
298         return node._computedStyle;
299     },
300
301     getMatchedCSSRules: function(node, pseudoElement, authorOnly)
302     {
303         return node._matchedCSSRules;
304     }
305 }
306
307 WebInspector.DOMAgent = function() {
308     this._window = new WebInspector.DOMWindow(this);
309     this._idToDOMNode = null;
310     this.document = null;
311 }
312
313 WebInspector.DOMAgent.prototype = {
314     get domWindow()
315     {
316         return this._window;
317     },
318
319     getChildNodesAsync: function(parent, callback)
320     {
321         var children = parent.children;
322         if (children) {
323             callback(children);
324             return;
325         }
326         function mycallback() {
327             callback(parent.children);
328         }
329         var callId = WebInspector.Callback.wrap(mycallback);
330         InspectorBackend.getChildNodes(callId, parent.id);
331     },
332
333     setAttributeAsync: function(node, name, value, callback)
334     {
335         var mycallback = this._didApplyDomChange.bind(this, node, callback);
336         InspectorBackend.setAttribute(WebInspector.Callback.wrap(mycallback), node.id, name, value);
337     },
338
339     removeAttributeAsync: function(node, name, callback)
340     {
341         var mycallback = this._didApplyDomChange.bind(this, node, callback);
342         InspectorBackend.removeAttribute(WebInspector.Callback.wrap(mycallback), node.id, name);
343     },
344
345     setTextNodeValueAsync: function(node, text, callback)
346     {
347         var mycallback = this._didApplyDomChange.bind(this, node, callback);
348         InspectorBackend.setTextNodeValue(WebInspector.Callback.wrap(mycallback), node.id, text);
349     },
350
351     _didApplyDomChange: function(node, callback, success)
352     {
353         if (!success)
354             return;
355         callback();
356         // TODO(pfeldman): Fix this hack.
357         var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node);
358         if (elem) {
359             elem._updateTitle();
360         }
361     },
362
363     _attributesUpdated: function(nodeId, attrsArray)
364     {
365         var node = this._idToDOMNode[nodeId];
366         node._setAttributesPayload(attrsArray);
367     },
368
369     nodeForId: function(nodeId) {
370         return this._idToDOMNode[nodeId];
371     },
372
373     _setDocument: function(payload)
374     {
375         this._idToDOMNode = {};
376         if (payload) {
377             this.document = new WebInspector.DOMDocument(this, this._window, payload);
378             this._idToDOMNode[payload.id] = this.document;
379             this._bindNodes(this.document.children);
380         } else
381             this.document = null;
382         WebInspector.panels.elements.reset();
383     },
384
385     _setDetachedRoot: function(payload)
386     {
387         var root = new WebInspector.DOMNode(this.document, payload);
388         this._idToDOMNode[payload.id] = root;
389     },
390
391     _setChildNodes: function(parentId, payloads)
392     {
393         var parent = this._idToDOMNode[parentId];
394         parent._setChildrenPayload(payloads);
395         this._bindNodes(parent.children);
396     },
397
398     _bindNodes: function(children)
399     {
400         for (var i = 0; i < children.length; ++i) {
401             var child = children[i];
402             this._idToDOMNode[child.id] = child;
403             if (child.children)
404                 this._bindNodes(child.children);
405         }
406     },
407
408     _childNodeCountUpdated: function(nodeId, newValue)
409     {
410         var node = this._idToDOMNode[nodeId];
411         node._childNodeCount = newValue;
412         var outline = WebInspector.panels.elements.treeOutline;
413         var treeElement = outline.findTreeElement(node);
414         if (treeElement)
415             treeElement.hasChildren = newValue;
416     },
417
418     _childNodeInserted: function(parentId, prevId, payload)
419     {
420         var parent = this._idToDOMNode[parentId];
421         var prev = this._idToDOMNode[prevId];
422         var node = parent._insertChild(prev, payload);
423         this._idToDOMNode[node.id] = node;
424         var event = { target : node, relatedNode : parent };
425         this.document._fireDomEvent("DOMNodeInserted", event);
426     },
427
428     _childNodeRemoved: function(parentId, nodeId)
429     {
430         var parent = this._idToDOMNode[parentId];
431         var node = this._idToDOMNode[nodeId];
432         parent.removeChild_(node);
433         var event = { target : node, relatedNode : parent };
434         this.document._fireDomEvent("DOMNodeRemoved", event);
435         delete this._idToDOMNode[nodeId];
436     }
437 }
438
439 WebInspector.Cookies = {}
440
441 WebInspector.Cookies.getCookiesAsync = function(callback, cookieDomain)
442 {
443     function mycallback(cookies, cookiesString) {
444         if (cookiesString)
445             callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false);
446         else
447             callback(cookies, true);
448     }
449     var callId = WebInspector.Callback.wrap(mycallback);
450     InspectorBackend.getCookies(callId, cookieDomain);
451 }
452
453 WebInspector.Cookies.buildCookiesFromString = function(rawCookieString)
454 {
455     var rawCookies = rawCookieString.split(/;\s*/);
456     var cookies = [];
457
458     if (!(/^\s*$/.test(rawCookieString))) {
459         for (var i = 0; i < rawCookies.length; ++i) {
460             var cookie = rawCookies[i];
461             var delimIndex = cookie.indexOf("=");
462             var name = cookie.substring(0, delimIndex);
463             var value = cookie.substring(delimIndex + 1);
464             var size = name.length + value.length;
465             cookies.push({ name: name, value: value, size: size });
466         }
467     }
468
469     return cookies;
470 }
471
472 WebInspector.EventListeners = {}
473
474 WebInspector.EventListeners.getEventListenersForNodeAsync = function(node, callback)
475 {
476     if (!node)
477         return;
478
479     var callId = WebInspector.Callback.wrap(callback);
480     InspectorBackend.getEventListenersForNode(callId, node.id);
481 }
482
483 WebInspector.CSSStyleDeclaration = function(payload)
484 {
485     this.id = payload.id;
486     this.width = payload.width;
487     this.height = payload.height;
488     this.__disabledProperties = payload.__disabledProperties;
489     this.__disabledPropertyValues = payload.__disabledPropertyValues;
490     this.__disabledPropertyPriorities = payload.__disabledPropertyPriorities;
491     this.uniqueStyleProperties = payload.uniqueStyleProperties;
492     this._shorthandValues = payload.shorthandValues;
493     this._propertyMap = {};
494     this._longhandProperties = {};
495     this.length = payload.properties.length;
496
497     for (var i = 0; i < this.length; ++i) {
498         var property = payload.properties[i];
499         var name = property.name;
500         this[i] = name;
501         this._propertyMap[name] = property;
502     }
503
504     // Index longhand properties.
505     for (var i = 0; i < this.uniqueStyleProperties.length; ++i) {
506         var name = this.uniqueStyleProperties[i];
507         var property = this._propertyMap[name];
508         if (property.shorthand) {
509             var longhands = this._longhandProperties[property.shorthand];
510             if (!longhands) {
511                 longhands = [];
512                 this._longhandProperties[property.shorthand] = longhands;
513             }
514             longhands.push(name);
515         }
516     }
517 }
518
519 WebInspector.CSSStyleDeclaration.parseStyle = function(payload)
520 {
521     return new WebInspector.CSSStyleDeclaration(payload);
522 }
523
524 WebInspector.CSSStyleDeclaration.parseRule = function(payload)
525 {
526     var rule = {};
527     rule.id = payload.id;
528     rule.selectorText = payload.selectorText;
529     rule.style = new WebInspector.CSSStyleDeclaration(payload.style);
530     rule.style.parentRule = rule;
531     rule.isUserAgent = payload.isUserAgent;
532     rule.isUser = payload.isUser;
533     rule.isViaInspector = payload.isViaInspector;
534     if (payload.parentStyleSheet)
535         rule.parentStyleSheet = { href: payload.parentStyleSheet.href };
536
537     return rule;
538 }
539
540 WebInspector.CSSStyleDeclaration.prototype = {
541     getPropertyValue: function(name)
542     {
543         var property = this._propertyMap[name];
544         return property ? property.value : "";
545     },
546
547     getPropertyPriority: function(name)
548     {
549         var property = this._propertyMap[name];
550         return property ? property.priority : "";
551     },
552
553     getPropertyShorthand: function(name)
554     {
555         var property = this._propertyMap[name];
556         return property ? property.shorthand : "";
557     },
558
559     isPropertyImplicit: function(name)
560     {
561         var property = this._propertyMap[name];
562         return property ? property.implicit : "";
563     },
564
565     styleTextWithShorthands: function()
566     {
567         var cssText = "";
568         var foundProperties = {};
569         for (var i = 0; i < this.length; ++i) {
570             var individualProperty = this[i];
571             var shorthandProperty = this.getPropertyShorthand(individualProperty);
572             var propertyName = (shorthandProperty || individualProperty);
573
574             if (propertyName in foundProperties)
575                 continue;
576
577             if (shorthandProperty) {
578                 var value = this.getPropertyValue(shorthandProperty);
579                 var priority = this.getShorthandPriority(shorthandProperty);
580             } else {
581                 var value = this.getPropertyValue(individualProperty);
582                 var priority = this.getPropertyPriority(individualProperty);
583             }
584
585             foundProperties[propertyName] = true;
586
587             cssText += propertyName + ": " + value;
588             if (priority)
589                 cssText += " !" + priority;
590             cssText += "; ";
591         }
592
593         return cssText;
594     },
595
596     getLonghandProperties: function(name)
597     {
598         return this._longhandProperties[name] || [];
599     },
600
601     getShorthandValue: function(shorthandProperty)
602     {
603         return this._shorthandValues[shorthandProperty];
604     },
605
606     getShorthandPriority: function(shorthandProperty)
607     {
608         var priority = this.getPropertyPriority(shorthandProperty);
609         if (priority)
610             return priority;
611
612         var longhands = this._longhandProperties[shorthandProperty];
613         return longhands ? this.getPropertyPriority(longhands[0]) : null;
614     }
615 }
616
617 WebInspector.attributesUpdated = function()
618 {
619     this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
620 }
621
622 WebInspector.setDocument = function()
623 {
624     this.domAgent._setDocument.apply(this.domAgent, arguments);
625 }
626
627 WebInspector.setDetachedRoot = function()
628 {
629     this.domAgent._setDetachedRoot.apply(this.domAgent, arguments);
630 }
631
632 WebInspector.setChildNodes = function()
633 {
634     this.domAgent._setChildNodes.apply(this.domAgent, arguments);
635 }
636
637 WebInspector.childNodeCountUpdated = function()
638 {
639     this.domAgent._childNodeCountUpdated.apply(this.domAgent, arguments);
640 }
641
642 WebInspector.childNodeInserted = function()
643 {
644     this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
645 }
646
647 WebInspector.childNodeRemoved = function()
648 {
649     this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
650 }
651
652 WebInspector.didGetCookies = WebInspector.Callback.processCallback;
653 WebInspector.didGetChildNodes = WebInspector.Callback.processCallback;
654 WebInspector.didPerformSearch = WebInspector.Callback.processCallback;
655 WebInspector.didApplyDomChange = WebInspector.Callback.processCallback;
656 WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback;
657 WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback;
658 WebInspector.didGetEventListenersForNode = WebInspector.Callback.processCallback;