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