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