28dafeb162bbfbe23cb92e69b77930ac50732a5a
[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.index = i;
183             child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
184             child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
185             child.parentNode = this;
186         }
187     },
188
189     _addAttribute: function(name, value)
190     {
191         var attr = {
192             "name": name,
193             "value": value,
194             "_node": this
195         };
196         this._attributesMap[name] = attr;
197         this.attributes.push(attr);
198     },
199
200     _setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules)
201     {
202         this._computedStyle = new WebInspector.CSSStyleDeclaration(computedStyle);
203         this.style = new WebInspector.CSSStyleDeclaration(inlineStyle);
204
205         for (var name in styleAttributes) {
206             if (this._attributesMap[name])
207                 this._attributesMap[name].style = new WebInspector.CSSStyleDeclaration(styleAttributes[name]);
208         }
209
210         this._matchedCSSRules = [];
211         for (var i = 0; i < matchedCSSRules.length; i++)
212             this._matchedCSSRules.push(WebInspector.CSSStyleDeclaration.parseRule(matchedCSSRules[i]));
213     },
214
215     _clearStyles: function()
216     {
217         this.computedStyle = null;
218         this.style = null;
219         for (var name in this._attributesMap)
220             this._attributesMap[name].style = null;
221         this._matchedCSSRules = null;
222     }
223 }
224
225 WebInspector.DOMDocument = function(domAgent, defaultView, payload)
226 {
227     WebInspector.DOMNode.call(this, this, payload);
228     this._listeners = {};
229     this._domAgent = domAgent;
230     this.defaultView = defaultView;
231 }
232
233 WebInspector.DOMDocument.prototype = {
234
235     addEventListener: function(name, callback)
236     {
237         var listeners = this._listeners[name];
238         if (!listeners) {
239             listeners = [];
240             this._listeners[name] = listeners;
241         }
242         listeners.push(callback);
243     },
244
245     removeEventListener: function(name, callback)
246     {
247         var listeners = this._listeners[name];
248         if (!listeners)
249             return;
250
251         var index = listeners.indexOf(callback);
252         if (index != -1)
253             listeners.splice(index, 1);
254     },
255
256     _fireDomEvent: function(name, event)
257     {
258         var listeners = this._listeners[name];
259         if (!listeners)
260             return;
261
262         for (var i = 0; i < listeners.length; ++i) {
263             var listener = listeners[i];
264             listener.call(this, event);
265         }
266     }
267 }
268
269 WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;
270
271
272 WebInspector.DOMWindow = function(domAgent)
273 {
274     this._domAgent = domAgent;
275 }
276
277 WebInspector.DOMWindow.prototype = {
278     get document()
279     {
280         return this._domAgent.document;
281     },
282
283     get Node()
284     {
285         return WebInspector.DOMNode;
286     },
287
288     get Element()
289     {
290         return WebInspector.DOMNode;
291     },
292
293     Object: function()
294     {
295     },
296
297     getComputedStyle: function(node)
298     {
299         return node._computedStyle;
300     },
301
302     getMatchedCSSRules: function(node, pseudoElement, authorOnly)
303     {
304         return node._matchedCSSRules;
305     }
306 }
307
308 WebInspector.DOMAgent = function() {
309     this._window = new WebInspector.DOMWindow(this);
310     this._idToDOMNode = null;
311     this.document = null;
312 }
313
314 WebInspector.DOMAgent.prototype = {
315     get domWindow()
316     {
317         return this._window;
318     },
319
320     getChildNodesAsync: function(parent, callback)
321     {
322         var children = parent.children;
323         if (children) {
324             callback(children);
325             return;
326         }
327         function mycallback() {
328             callback(parent.children);
329         }
330         var callId = WebInspector.Callback.wrap(mycallback);
331         InspectorBackend.getChildNodes(callId, parent.id);
332     },
333
334     setAttributeAsync: function(node, name, value, callback)
335     {
336         var mycallback = this._didApplyDomChange.bind(this, node, callback);
337         InspectorBackend.setAttribute(WebInspector.Callback.wrap(mycallback), node.id, name, value);
338     },
339
340     removeAttributeAsync: function(node, name, callback)
341     {
342         var mycallback = this._didApplyDomChange.bind(this, node, callback);
343         InspectorBackend.removeAttribute(WebInspector.Callback.wrap(mycallback), node.id, name);
344     },
345
346     setTextNodeValueAsync: function(node, text, callback)
347     {
348         var mycallback = this._didApplyDomChange.bind(this, node, callback);
349         InspectorBackend.setTextNodeValue(WebInspector.Callback.wrap(mycallback), node.id, text);
350     },
351
352     _didApplyDomChange: function(node, callback, success)
353     {
354         if (!success)
355             return;
356         callback();
357         // TODO(pfeldman): Fix this hack.
358         var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node);
359         if (elem) {
360             elem._updateTitle();
361         }
362     },
363
364     _attributesUpdated: function(nodeId, attrsArray)
365     {
366         var node = this._idToDOMNode[nodeId];
367         node._setAttributesPayload(attrsArray);
368     },
369
370     nodeForId: function(nodeId) {
371         return this._idToDOMNode[nodeId];
372     },
373
374     _setDocument: function(payload)
375     {
376         this._idToDOMNode = {};
377         if (payload && "id" in payload) {
378             this.document = new WebInspector.DOMDocument(this, this._window, payload);
379             this._idToDOMNode[payload.id] = this.document;
380             this._bindNodes(this.document.children);
381         } else
382             this.document = null;
383         WebInspector.panels.elements.setDocument(this.document);
384     },
385
386     _setDetachedRoot: function(payload)
387     {
388         var root = new WebInspector.DOMNode(this.document, payload);
389         this._idToDOMNode[payload.id] = root;
390     },
391
392     _setChildNodes: function(parentId, payloads)
393     {
394         var parent = this._idToDOMNode[parentId];
395         parent._setChildrenPayload(payloads);
396         this._bindNodes(parent.children);
397     },
398
399     _bindNodes: function(children)
400     {
401         for (var i = 0; i < children.length; ++i) {
402             var child = children[i];
403             this._idToDOMNode[child.id] = child;
404             if (child.children)
405                 this._bindNodes(child.children);
406         }
407     },
408
409     _childNodeCountUpdated: function(nodeId, newValue)
410     {
411         var node = this._idToDOMNode[nodeId];
412         node._childNodeCount = newValue;
413         var outline = WebInspector.panels.elements.treeOutline;
414         var treeElement = outline.findTreeElement(node);
415         if (treeElement)
416             treeElement.hasChildren = newValue;
417     },
418
419     _childNodeInserted: function(parentId, prevId, payload)
420     {
421         var parent = this._idToDOMNode[parentId];
422         var prev = this._idToDOMNode[prevId];
423         var node = parent._insertChild(prev, payload);
424         this._idToDOMNode[node.id] = node;
425         var event = { target : node, relatedNode : parent };
426         this.document._fireDomEvent("DOMNodeInserted", event);
427     },
428
429     _childNodeRemoved: function(parentId, nodeId)
430     {
431         var parent = this._idToDOMNode[parentId];
432         var node = this._idToDOMNode[nodeId];
433         parent.removeChild_(node);
434         var event = { target : node, relatedNode : parent };
435         this.document._fireDomEvent("DOMNodeRemoved", event);
436         delete this._idToDOMNode[nodeId];
437     }
438 }
439
440 WebInspector.Cookies = {}
441
442 WebInspector.Cookies.getCookiesAsync = function(callback, cookieDomain)
443 {
444     function mycallback(cookies, cookiesString) {
445         if (cookiesString)
446             callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false);
447         else
448             callback(cookies, true);
449     }
450     var callId = WebInspector.Callback.wrap(mycallback);
451     InspectorBackend.getCookies(callId, cookieDomain);
452 }
453
454 WebInspector.Cookies.buildCookiesFromString = function(rawCookieString)
455 {
456     var rawCookies = rawCookieString.split(/;\s*/);
457     var cookies = [];
458
459     if (!(/^\s*$/.test(rawCookieString))) {
460         for (var i = 0; i < rawCookies.length; ++i) {
461             var cookie = rawCookies[i];
462             var delimIndex = cookie.indexOf("=");
463             var name = cookie.substring(0, delimIndex);
464             var value = cookie.substring(delimIndex + 1);
465             var size = name.length + value.length;
466             cookies.push({ name: name, value: value, size: size });
467         }
468     }
469
470     return cookies;
471 }
472
473 WebInspector.EventListeners = {}
474
475 WebInspector.EventListeners.getEventListenersForNodeAsync = function(node, callback)
476 {
477     if (!node)
478         return;
479
480     var callId = WebInspector.Callback.wrap(callback);
481     InspectorBackend.getEventListenersForNode(callId, node.id);
482 }
483
484 WebInspector.CSSStyleDeclaration = function(payload)
485 {
486     this.id = payload.id;
487     this.width = payload.width;
488     this.height = payload.height;
489     this.__disabledProperties = payload.__disabledProperties;
490     this.__disabledPropertyValues = payload.__disabledPropertyValues;
491     this.__disabledPropertyPriorities = payload.__disabledPropertyPriorities;
492     this.uniqueStyleProperties = payload.uniqueStyleProperties;
493     this._shorthandValues = payload.shorthandValues;
494     this._propertyMap = {};
495     this._longhandProperties = {};
496     this.length = payload.properties.length;
497
498     for (var i = 0; i < this.length; ++i) {
499         var property = payload.properties[i];
500         var name = property.name;
501         this[i] = name;
502         this._propertyMap[name] = property;
503     }
504
505     // Index longhand properties.
506     for (var i = 0; i < this.uniqueStyleProperties.length; ++i) {
507         var name = this.uniqueStyleProperties[i];
508         var property = this._propertyMap[name];
509         if (property.shorthand) {
510             var longhands = this._longhandProperties[property.shorthand];
511             if (!longhands) {
512                 longhands = [];
513                 this._longhandProperties[property.shorthand] = longhands;
514             }
515             longhands.push(name);
516         }
517     }
518 }
519
520 WebInspector.CSSStyleDeclaration.parseStyle = function(payload)
521 {
522     return new WebInspector.CSSStyleDeclaration(payload);
523 }
524
525 WebInspector.CSSStyleDeclaration.parseRule = function(payload)
526 {
527     var rule = {};
528     rule.id = payload.id;
529     rule.selectorText = payload.selectorText;
530     rule.style = new WebInspector.CSSStyleDeclaration(payload.style);
531     rule.style.parentRule = rule;
532     rule.isUserAgent = payload.isUserAgent;
533     rule.isUser = payload.isUser;
534     rule.isViaInspector = payload.isViaInspector;
535     if (payload.parentStyleSheet)
536         rule.parentStyleSheet = { href: payload.parentStyleSheet.href };
537
538     return rule;
539 }
540
541 WebInspector.CSSStyleDeclaration.prototype = {
542     getPropertyValue: function(name)
543     {
544         var property = this._propertyMap[name];
545         return property ? property.value : "";
546     },
547
548     getPropertyPriority: function(name)
549     {
550         var property = this._propertyMap[name];
551         return property ? property.priority : "";
552     },
553
554     getPropertyShorthand: function(name)
555     {
556         var property = this._propertyMap[name];
557         return property ? property.shorthand : "";
558     },
559
560     isPropertyImplicit: function(name)
561     {
562         var property = this._propertyMap[name];
563         return property ? property.implicit : "";
564     },
565
566     styleTextWithShorthands: function()
567     {
568         var cssText = "";
569         var foundProperties = {};
570         for (var i = 0; i < this.length; ++i) {
571             var individualProperty = this[i];
572             var shorthandProperty = this.getPropertyShorthand(individualProperty);
573             var propertyName = (shorthandProperty || individualProperty);
574
575             if (propertyName in foundProperties)
576                 continue;
577
578             if (shorthandProperty) {
579                 var value = this.getPropertyValue(shorthandProperty);
580                 var priority = this.getShorthandPriority(shorthandProperty);
581             } else {
582                 var value = this.getPropertyValue(individualProperty);
583                 var priority = this.getPropertyPriority(individualProperty);
584             }
585
586             foundProperties[propertyName] = true;
587
588             cssText += propertyName + ": " + value;
589             if (priority)
590                 cssText += " !" + priority;
591             cssText += "; ";
592         }
593
594         return cssText;
595     },
596
597     getLonghandProperties: function(name)
598     {
599         return this._longhandProperties[name] || [];
600     },
601
602     getShorthandValue: function(shorthandProperty)
603     {
604         return this._shorthandValues[shorthandProperty];
605     },
606
607     getShorthandPriority: function(shorthandProperty)
608     {
609         var priority = this.getPropertyPriority(shorthandProperty);
610         if (priority)
611             return priority;
612
613         var longhands = this._longhandProperties[shorthandProperty];
614         return longhands ? this.getPropertyPriority(longhands[0]) : null;
615     }
616 }
617
618 WebInspector.attributesUpdated = function()
619 {
620     this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
621 }
622
623 WebInspector.setDocument = function()
624 {
625     this.domAgent._setDocument.apply(this.domAgent, arguments);
626 }
627
628 WebInspector.setDetachedRoot = function()
629 {
630     this.domAgent._setDetachedRoot.apply(this.domAgent, arguments);
631 }
632
633 WebInspector.setChildNodes = function()
634 {
635     this.domAgent._setChildNodes.apply(this.domAgent, arguments);
636 }
637
638 WebInspector.childNodeCountUpdated = function()
639 {
640     this.domAgent._childNodeCountUpdated.apply(this.domAgent, arguments);
641 }
642
643 WebInspector.childNodeInserted = function()
644 {
645     this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
646 }
647
648 WebInspector.childNodeRemoved = function()
649 {
650     this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
651 }
652
653 WebInspector.didGetCookies = WebInspector.Callback.processCallback;
654 WebInspector.didGetChildNodes = WebInspector.Callback.processCallback;
655 WebInspector.didPerformSearch = WebInspector.Callback.processCallback;
656 WebInspector.didApplyDomChange = WebInspector.Callback.processCallback;
657 WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback;
658 WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback;
659 WebInspector.didGetEventListenersForNode = WebInspector.Callback.processCallback;