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