24e29ec0b9fd0cd2c1a8c4a1ac8ccd794c7260a3
[WebKit-https.git] / WebCore / inspector / front-end / DOMAgent.js
1 /*
2  * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
3  * Copyright (C) 2009 Joseph Pecoraro
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 WebInspector.DOMNode = function(doc, payload) {
33     this.ownerDocument = doc;
34
35     this.id = payload.id;
36     this.nodeType = payload.nodeType;
37     this.nodeName = payload.nodeName;
38     this.localName = payload.localName;
39     this._nodeValue = payload.nodeValue;
40     this.textContent = this.nodeValue;
41
42     this.attributes = [];
43     this._attributesMap = {};
44     if (payload.attributes)
45         this._setAttributesPayload(payload.attributes);
46
47     this._childNodeCount = payload.childNodeCount;
48     this.children = null;
49
50     this.nextSibling = null;
51     this.prevSibling = null;
52     this.firstChild = null;
53     this.lastChild = null;
54     this.parentNode = null;
55
56     if (payload.children)
57         this._setChildrenPayload(payload.children);
58
59     this._computedStyle = null;
60     this.style = null;
61     this._matchedCSSRules = [];
62
63     this.breakpoints = {};
64
65     if (this.nodeType === Node.ELEMENT_NODE) {
66         // HTML and BODY from internal iframes should not overwrite top-level ones.
67         if (!this.ownerDocument.documentElement && this.nodeName === "HTML")
68             this.ownerDocument.documentElement = this;
69         if (!this.ownerDocument.body && this.nodeName === "BODY")
70             this.ownerDocument.body = this;
71         if (payload.documentURL)
72             this.documentURL = payload.documentURL;
73     } else if (this.nodeType === Node.DOCUMENT_TYPE_NODE) {
74         this.publicId = payload.publicId;
75         this.systemId = payload.systemId;
76         this.internalSubset = payload.internalSubset;
77     } else if (this.nodeType === Node.DOCUMENT_NODE) {
78         this.documentURL = payload.documentURL;
79     } else if (this.nodeType === Node.ATTRIBUTE_NODE) {
80         this.name = payload.name;
81         this.value = payload.value;
82     }
83
84     InspectorBackend.registerDomainDispatcher("DOM", this);
85 }
86
87 WebInspector.DOMNode.prototype = {
88     hasAttributes: function()
89     {
90         return this.attributes.length > 0;
91     },
92
93     hasChildNodes: function()
94     {
95         return this._childNodeCount > 0;
96     },
97
98     get nodeValue() {
99         return this._nodeValue;
100     },
101
102     set nodeValue(value) {
103         if (this.nodeType != Node.TEXT_NODE)
104             return;
105         this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, function() {});
106     },
107
108     getAttribute: function(name)
109     {
110         var attr = this._attributesMap[name];
111         return attr ? attr.value : undefined;
112     },
113
114     setAttribute: function(name, value)
115     {
116         var self = this;
117         var callback = function()
118         {
119             var attr = self._attributesMap[name];
120             if (attr)
121                 attr.value = value;
122             else
123                 attr = self._addAttribute(name, value);
124         };
125         this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback);
126     },
127
128     removeAttribute: function(name)
129     {
130         var self = this;
131         var callback = function()
132         {
133             delete self._attributesMap[name];
134             for (var i = 0;  i < self.attributes.length; ++i) {
135                 if (self.attributes[i].name == name) {
136                     self.attributes.splice(i, 1);
137                     break;
138                 }
139             }
140         };
141         this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback);
142     },
143
144     path: function()
145     {
146         var path = [];
147         var node = this;
148         while (node && "index" in node && node.nodeName.length) {
149             path.push([node.index, node.nodeName]);
150             node = node.parentNode;
151         }
152         path.reverse();
153         return path.join(",");
154     },
155
156     _setAttributesPayload: function(attrs)
157     {
158         this.attributes = [];
159         this._attributesMap = {};
160         for (var i = 0; i < attrs.length; i += 2)
161             this._addAttribute(attrs[i], attrs[i + 1]);
162     },
163
164     _insertChild: function(prev, payload)
165     {
166         var node = new WebInspector.DOMNode(this.ownerDocument, payload);
167         if (!prev) {
168             if (!this.children) {
169                 // First node
170                 this.children = [ node ];
171             } else
172                 this.children.unshift(node);
173         } else
174             this.children.splice(this.children.indexOf(prev) + 1, 0, node);
175         this._renumber();
176         return node;
177     },
178
179     removeChild_: function(node)
180     {
181         this.children.splice(this.children.indexOf(node), 1);
182         node.parentNode = null;
183         this._renumber();
184     },
185
186     _setChildrenPayload: function(payloads)
187     {
188         this.children = [];
189         for (var i = 0; i < payloads.length; ++i) {
190             var payload = payloads[i];
191             var node = new WebInspector.DOMNode(this.ownerDocument, payload);
192             this.children.push(node);
193         }
194         this._renumber();
195     },
196
197     _renumber: function()
198     {
199         this._childNodeCount = this.children.length;
200         if (this._childNodeCount == 0) {
201             this.firstChild = null;
202             this.lastChild = null;
203             return;
204         }
205         this.firstChild = this.children[0];
206         this.lastChild = this.children[this._childNodeCount - 1];
207         for (var i = 0; i < this._childNodeCount; ++i) {
208             var child = this.children[i];
209             child.index = i;
210             child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
211             child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
212             child.parentNode = this;
213         }
214     },
215
216     _addAttribute: function(name, value)
217     {
218         var attr = {
219             "name": name,
220             "value": value,
221             "_node": this
222         };
223         this._attributesMap[name] = attr;
224         this.attributes.push(attr);
225     }
226 }
227
228 WebInspector.DOMDocument = function(domAgent, defaultView, payload)
229 {
230     WebInspector.DOMNode.call(this, this, payload);
231     this._listeners = {};
232     this._domAgent = domAgent;
233     this.defaultView = defaultView;
234 }
235
236 WebInspector.DOMDocument.prototype = {
237
238     addEventListener: function(name, callback)
239     {
240         var listeners = this._listeners[name];
241         if (!listeners) {
242             listeners = [];
243             this._listeners[name] = listeners;
244         }
245         listeners.push(callback);
246     },
247
248     removeEventListener: function(name, callback)
249     {
250         var listeners = this._listeners[name];
251         if (!listeners)
252             return;
253
254         var index = listeners.indexOf(callback);
255         if (index != -1)
256             listeners.splice(index, 1);
257     },
258
259     _fireDomEvent: function(name, event)
260     {
261         var listeners = this._listeners[name];
262         if (!listeners)
263             return;
264
265         for (var i = 0; i < listeners.length; ++i) {
266             var listener = listeners[i];
267             listener.call(this, event);
268         }
269     }
270 }
271
272 WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;
273
274
275 WebInspector.DOMWindow = function(domAgent)
276 {
277     this._domAgent = domAgent;
278 }
279
280 WebInspector.DOMWindow.prototype = {
281     get document()
282     {
283         return this._domAgent.document;
284     },
285
286     get Node()
287     {
288         return WebInspector.DOMNode;
289     },
290
291     get Element()
292     {
293         return WebInspector.DOMNode;
294     },
295
296     Object: function()
297     {
298     }
299 }
300
301 WebInspector.DOMAgent = function() {
302     this._window = new WebInspector.DOMWindow(this);
303     this._idToDOMNode = null;
304     this.document = null;
305 }
306
307 WebInspector.DOMAgent.prototype = {
308     get domWindow()
309     {
310         return this._window;
311     },
312
313     getChildNodesAsync: function(parent, callback)
314     {
315         var children = parent.children;
316         if (children) {
317             callback(children);
318             return;
319         }
320         function mycallback() {
321             callback(parent.children);
322         }
323         InspectorBackend.getChildNodes(parent.id, mycallback);
324     },
325
326     setAttributeAsync: function(node, name, value, callback)
327     {
328         var mycallback = this._didApplyDomChange.bind(this, node, callback);
329         InspectorBackend.setAttribute(node.id, name, value, mycallback);
330     },
331
332     removeAttributeAsync: function(node, name, callback)
333     {
334         var mycallback = this._didApplyDomChange.bind(this, node, callback);
335         InspectorBackend.removeAttribute(node.id, name, mycallback);
336     },
337
338     setTextNodeValueAsync: function(node, text, callback)
339     {
340         var mycallback = this._didApplyDomChange.bind(this, node, callback);
341         InspectorBackend.setTextNodeValue(node.id, text, mycallback);
342     },
343
344     _didApplyDomChange: function(node, callback, success)
345     {
346         if (!success)
347             return;
348         callback();
349         // TODO(pfeldman): Fix this hack.
350         var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node);
351         if (elem)
352             elem.updateTitle();
353     },
354
355     _attributesUpdated: function(nodeId, attrsArray)
356     {
357         var node = this._idToDOMNode[nodeId];
358         node._setAttributesPayload(attrsArray);
359         var event = {target: node};
360         this.document._fireDomEvent("DOMAttrModified", event);
361     },
362
363     _characterDataModified: function(nodeId, newValue)
364     {
365         var node = this._idToDOMNode[nodeId];
366         node._nodeValue = newValue;
367         node.textContent = newValue;
368         var event = { target : node };
369         this.document._fireDomEvent("DOMCharacterDataModified", event);
370     },
371
372     nodeForId: function(nodeId)
373     {
374         return this._idToDOMNode[nodeId];
375     },
376
377     _setDocument: function(payload)
378     {
379         this._idToDOMNode = {};
380         if (payload && "id" in payload) {
381             this.document = new WebInspector.DOMDocument(this, this._window, payload);
382             this._idToDOMNode[payload.id] = this.document;
383             this._bindNodes(this.document.children);
384             WebInspector.breakpointManager.restoreDOMBreakpoints();
385         } else
386             this.document = null;
387         WebInspector.panels.elements.setDocument(this.document);
388     },
389
390     _setDetachedRoot: function(payload)
391     {
392         var root = new WebInspector.DOMNode(this.document, payload);
393         this._idToDOMNode[payload.id] = root;
394     },
395
396     _setChildNodes: function(parentId, payloads)
397     {
398         var parent = this._idToDOMNode[parentId];
399         parent._setChildrenPayload(payloads);
400         this._bindNodes(parent.children);
401     },
402
403     _bindNodes: function(children)
404     {
405         for (var i = 0; i < children.length; ++i) {
406             var child = children[i];
407             this._idToDOMNode[child.id] = child;
408             if (child.children)
409                 this._bindNodes(child.children);
410         }
411     },
412
413     _childNodeCountUpdated: function(nodeId, newValue)
414     {
415         var node = this._idToDOMNode[nodeId];
416         node._childNodeCount = newValue;
417         var outline = WebInspector.panels.elements.treeOutline;
418         var treeElement = outline.findTreeElement(node);
419         if (treeElement)
420             treeElement.hasChildren = newValue;
421     },
422
423     _childNodeInserted: function(parentId, prevId, payload)
424     {
425         var parent = this._idToDOMNode[parentId];
426         var prev = this._idToDOMNode[prevId];
427         var node = parent._insertChild(prev, payload);
428         this._idToDOMNode[node.id] = node;
429         var event = { target : node, relatedNode : parent };
430         this.document._fireDomEvent("DOMNodeInserted", event);
431     },
432
433     _childNodeRemoved: function(parentId, nodeId)
434     {
435         var parent = this._idToDOMNode[parentId];
436         var node = this._idToDOMNode[nodeId];
437         parent.removeChild_(node);
438         var event = { target : node, relatedNode : parent };
439         this.document._fireDomEvent("DOMNodeRemoved", event);
440         delete this._idToDOMNode[nodeId];
441         this._removeBreakpoints(node);
442     },
443
444     _removeBreakpoints: function(node)
445     {
446         for (var type in node.breakpoints)
447             node.breakpoints[type].remove();
448         if (!node.children)
449             return;
450         for (var i = 0; i < node.children.length; ++i)
451             this._removeBreakpoints(node.children[i]);
452     }
453 }
454
455 WebInspector.ApplicationCache = {}
456
457 WebInspector.ApplicationCache.getApplicationCachesAsync = function(callback)
458 {
459     function mycallback(applicationCaches)
460     {
461         // FIXME: Currently, this list only returns a single application cache.
462         if (applicationCaches)
463             callback(applicationCaches);
464     }
465
466     InspectorBackend.getApplicationCaches(mycallback);
467 }
468
469 WebInspector.Cookies = {}
470
471 WebInspector.Cookies.getCookiesAsync = function(callback)
472 {
473     function mycallback(cookies, cookiesString)
474     {
475         if (cookiesString)
476             callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false);
477         else
478             callback(cookies, true);
479     }
480
481     InspectorBackend.getCookies(mycallback);
482 }
483
484 WebInspector.Cookies.buildCookiesFromString = function(rawCookieString)
485 {
486     var rawCookies = rawCookieString.split(/;\s*/);
487     var cookies = [];
488
489     if (!(/^\s*$/.test(rawCookieString))) {
490         for (var i = 0; i < rawCookies.length; ++i) {
491             var cookie = rawCookies[i];
492             var delimIndex = cookie.indexOf("=");
493             var name = cookie.substring(0, delimIndex);
494             var value = cookie.substring(delimIndex + 1);
495             var size = name.length + value.length;
496             cookies.push({ name: name, value: value, size: size });
497         }
498     }
499
500     return cookies;
501 }
502
503 WebInspector.Cookies.cookieMatchesResourceURL = function(cookie, resourceURL)
504 {
505     var url = resourceURL.asParsedURL();
506     if (!url || !this.cookieDomainMatchesResourceDomain(cookie.domain, url.host))
507         return false;
508     return (url.path.indexOf(cookie.path) === 0
509         && (!cookie.port || url.port == cookie.port)
510         && (!cookie.secure || url.scheme === "https"));
511 }
512
513 WebInspector.Cookies.cookieDomainMatchesResourceDomain = function(cookieDomain, resourceDomain)
514 {
515     if (cookieDomain.charAt(0) !== '.')
516         return resourceDomain === cookieDomain;
517     return !!resourceDomain.match(new RegExp("^([^\\.]+\\.)?" + cookieDomain.substring(1).escapeForRegExp() + "$"), "i");
518 }
519
520 WebInspector.EventListeners = {}
521
522 WebInspector.EventListeners.getEventListenersForNodeAsync = function(node, callback)
523 {
524     if (!node)
525         return;
526     InspectorBackend.getEventListenersForNode(node.id, callback);
527 }
528
529 WebInspector.attributesUpdated = function()
530 {
531     this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
532 }
533
534 WebInspector.characterDataModified = function()
535 {
536     this.domAgent._characterDataModified.apply(this.domAgent, arguments);
537 }
538
539 WebInspector.setDocument = function()
540 {
541     this.domAgent._setDocument.apply(this.domAgent, arguments);
542 }
543
544 WebInspector.setDetachedRoot = function()
545 {
546     this.domAgent._setDetachedRoot.apply(this.domAgent, arguments);
547 }
548
549 WebInspector.setChildNodes = function()
550 {
551     this.domAgent._setChildNodes.apply(this.domAgent, arguments);
552 }
553
554 WebInspector.childNodeCountUpdated = function()
555 {
556     this.domAgent._childNodeCountUpdated.apply(this.domAgent, arguments);
557 }
558
559 WebInspector.childNodeInserted = function()
560 {
561     this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
562 }
563
564 WebInspector.childNodeRemoved = function()
565 {
566     this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
567 }