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