2010-09-22 Pavel Feldman <pfeldman@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     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     } else if (this.nodeType === Node.ATTRIBUTE_NODE) {
78         this.name = payload.name;
79         this.value = payload.value;
80     }
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         this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, function() {});
102     },
103
104     getAttribute: function(name)
105     {
106         var attr = this._attributesMap[name];
107         return attr ? attr.value : undefined;
108     },
109
110     setAttribute: function(name, value)
111     {
112         var self = this;
113         var callback = function()
114         {
115             var attr = self._attributesMap[name];
116             if (attr)
117                 attr.value = value;
118             else
119                 attr = self._addAttribute(name, value);
120         };
121         this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback);
122     },
123
124     removeAttribute: function(name)
125     {
126         var self = this;
127         var callback = function()
128         {
129             delete self._attributesMap[name];
130             for (var i = 0;  i < self.attributes.length; ++i) {
131                 if (self.attributes[i].name == name) {
132                     self.attributes.splice(i, 1);
133                     break;
134                 }
135             }
136         };
137         this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback);
138     },
139
140     path: function()
141     {
142         var path = [];
143         var node = this;
144         while (node && "index" in node && node.nodeName.length) {
145             path.push([node.index, node.nodeName]);
146             node = node.parentNode;
147         }
148         path.reverse();
149         return path.join(",");
150     },
151
152     setBreakpoint: function(type)
153     {
154         return WebInspector.domBreakpointManager.setBreakpoint(this.id, type, true, this.path());
155     },
156
157     hasBreakpoint: function(type)
158     {
159         return !!WebInspector.domBreakpointManager.findBreakpoint(this.id, type);
160     },
161
162     removeBreakpoint: function(type)
163     {
164         var breakpoint = WebInspector.domBreakpointManager.findBreakpoint(this.id, type);
165         if (breakpoint)
166             breakpoint.remove();
167     },
168
169     removeBreakpoints: function()
170     {
171         WebInspector.domBreakpointManager.removeBreakpointsForNode(this.id);
172     },
173
174     _setAttributesPayload: function(attrs)
175     {
176         this.attributes = [];
177         this._attributesMap = {};
178         for (var i = 0; i < attrs.length; i += 2)
179             this._addAttribute(attrs[i], attrs[i + 1]);
180     },
181
182     _insertChild: function(prev, payload)
183     {
184         var node = new WebInspector.DOMNode(this.ownerDocument, payload);
185         if (!prev) {
186             if (!this.children) {
187                 // First node
188                 this.children = [ node ];
189             } else
190                 this.children.unshift(node);
191         } else
192             this.children.splice(this.children.indexOf(prev) + 1, 0, node);
193         this._renumber();
194         return node;
195     },
196
197     removeChild_: function(node)
198     {
199         this.children.splice(this.children.indexOf(node), 1);
200         node.parentNode = null;
201         this._renumber();
202     },
203
204     _setChildrenPayload: function(payloads)
205     {
206         this.children = [];
207         for (var i = 0; i < payloads.length; ++i) {
208             var payload = payloads[i];
209             var node = new WebInspector.DOMNode(this.ownerDocument, payload);
210             this.children.push(node);
211         }
212         this._renumber();
213     },
214
215     _renumber: function()
216     {
217         this._childNodeCount = this.children.length;
218         if (this._childNodeCount == 0) {
219             this.firstChild = null;
220             this.lastChild = null;
221             return;
222         }
223         this.firstChild = this.children[0];
224         this.lastChild = this.children[this._childNodeCount - 1];
225         for (var i = 0; i < this._childNodeCount; ++i) {
226             var child = this.children[i];
227             child.index = i;
228             child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
229             child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
230             child.parentNode = this;
231         }
232     },
233
234     _addAttribute: function(name, value)
235     {
236         var attr = {
237             "name": name,
238             "value": value,
239             "_node": this
240         };
241         this._attributesMap[name] = attr;
242         this.attributes.push(attr);
243     }
244 }
245
246 WebInspector.DOMDocument = function(domAgent, defaultView, payload)
247 {
248     WebInspector.DOMNode.call(this, this, payload);
249     this._listeners = {};
250     this._domAgent = domAgent;
251     this.defaultView = defaultView;
252 }
253
254 WebInspector.DOMDocument.prototype = {
255
256     addEventListener: function(name, callback)
257     {
258         var listeners = this._listeners[name];
259         if (!listeners) {
260             listeners = [];
261             this._listeners[name] = listeners;
262         }
263         listeners.push(callback);
264     },
265
266     removeEventListener: function(name, callback)
267     {
268         var listeners = this._listeners[name];
269         if (!listeners)
270             return;
271
272         var index = listeners.indexOf(callback);
273         if (index != -1)
274             listeners.splice(index, 1);
275     },
276
277     _fireDomEvent: function(name, event)
278     {
279         var listeners = this._listeners[name];
280         if (!listeners)
281             return;
282
283         for (var i = 0; i < listeners.length; ++i) {
284             var listener = listeners[i];
285             listener.call(this, event);
286         }
287     }
288 }
289
290 WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;
291
292
293 WebInspector.DOMWindow = function(domAgent)
294 {
295     this._domAgent = domAgent;
296 }
297
298 WebInspector.DOMWindow.prototype = {
299     get document()
300     {
301         return this._domAgent.document;
302     },
303
304     get Node()
305     {
306         return WebInspector.DOMNode;
307     },
308
309     get Element()
310     {
311         return WebInspector.DOMNode;
312     },
313
314     Object: function()
315     {
316     }
317 }
318
319 WebInspector.DOMAgent = function() {
320     this._window = new WebInspector.DOMWindow(this);
321     this._idToDOMNode = null;
322     this.document = null;
323 }
324
325 WebInspector.DOMAgent.prototype = {
326     get domWindow()
327     {
328         return this._window;
329     },
330
331     getChildNodesAsync: function(parent, callback)
332     {
333         var children = parent.children;
334         if (children) {
335             callback(children);
336             return;
337         }
338         function mycallback() {
339             callback(parent.children);
340         }
341         InspectorBackend.getChildNodes(parent.id, mycallback);
342     },
343
344     setAttributeAsync: function(node, name, value, callback)
345     {
346         var mycallback = this._didApplyDomChange.bind(this, node, callback);
347         InspectorBackend.setAttribute(node.id, name, value, mycallback);
348     },
349
350     removeAttributeAsync: function(node, name, callback)
351     {
352         var mycallback = this._didApplyDomChange.bind(this, node, callback);
353         InspectorBackend.removeAttribute(node.id, name, mycallback);
354     },
355
356     setTextNodeValueAsync: function(node, text, callback)
357     {
358         var mycallback = this._didApplyDomChange.bind(this, node, callback);
359         InspectorBackend.setTextNodeValue(node.id, text, mycallback);
360     },
361
362     _didApplyDomChange: function(node, callback, success)
363     {
364         if (!success)
365             return;
366         callback();
367         // TODO(pfeldman): Fix this hack.
368         var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node);
369         if (elem)
370             elem.updateTitle();
371     },
372
373     _attributesUpdated: function(nodeId, attrsArray)
374     {
375         var node = this._idToDOMNode[nodeId];
376         node._setAttributesPayload(attrsArray);
377         var event = {target: node};
378         this.document._fireDomEvent("DOMAttrModified", event);
379     },
380
381     _characterDataModified: function(nodeId, newValue)
382     {
383         var node = this._idToDOMNode[nodeId];
384         node._nodeValue = newValue;
385         node.textContent = newValue;
386         var event = { target : node };
387         this.document._fireDomEvent("DOMCharacterDataModified", event);
388     },
389
390     nodeForId: function(nodeId)
391     {
392         return this._idToDOMNode[nodeId];
393     },
394
395     _setDocument: function(payload)
396     {
397         this._idToDOMNode = {};
398         if (payload && "id" in payload) {
399             this.document = new WebInspector.DOMDocument(this, this._window, payload);
400             this._idToDOMNode[payload.id] = this.document;
401             this._bindNodes(this.document.children);
402             WebInspector.domBreakpointManager.restoreBreakpoints();
403         } else
404             this.document = null;
405         WebInspector.panels.elements.setDocument(this.document);
406     },
407
408     _setDetachedRoot: function(payload)
409     {
410         var root = new WebInspector.DOMNode(this.document, payload);
411         this._idToDOMNode[payload.id] = root;
412     },
413
414     _setChildNodes: function(parentId, payloads)
415     {
416         var parent = this._idToDOMNode[parentId];
417         parent._setChildrenPayload(payloads);
418         this._bindNodes(parent.children);
419     },
420
421     _bindNodes: function(children)
422     {
423         for (var i = 0; i < children.length; ++i) {
424             var child = children[i];
425             this._idToDOMNode[child.id] = child;
426             if (child.children)
427                 this._bindNodes(child.children);
428         }
429     },
430
431     _childNodeCountUpdated: function(nodeId, newValue)
432     {
433         var node = this._idToDOMNode[nodeId];
434         node._childNodeCount = newValue;
435         var outline = WebInspector.panels.elements.treeOutline;
436         var treeElement = outline.findTreeElement(node);
437         if (treeElement)
438             treeElement.hasChildren = newValue;
439     },
440
441     _childNodeInserted: function(parentId, prevId, payload)
442     {
443         var parent = this._idToDOMNode[parentId];
444         var prev = this._idToDOMNode[prevId];
445         var node = parent._insertChild(prev, payload);
446         this._idToDOMNode[node.id] = node;
447         var event = { target : node, relatedNode : parent };
448         this.document._fireDomEvent("DOMNodeInserted", event);
449     },
450
451     _childNodeRemoved: function(parentId, nodeId)
452     {
453         var parent = this._idToDOMNode[parentId];
454         var node = this._idToDOMNode[nodeId];
455         parent.removeChild_(node);
456         var event = { target : node, relatedNode : parent };
457         this.document._fireDomEvent("DOMNodeRemoved", event);
458         delete this._idToDOMNode[nodeId];
459         this._removeBreakpoints(node);
460     },
461
462     _removeBreakpoints: function(node)
463     {
464         node.removeBreakpoints();
465         if (!node.children)
466             return;
467         for (var i = 0; i < node.children.length; ++i)
468             this._removeBreakpoints(node.children[i]);
469      }
470 }
471
472 WebInspector.ApplicationCache = {}
473
474 WebInspector.ApplicationCache.getApplicationCachesAsync = function(callback)
475 {
476     function mycallback(applicationCaches)
477     {
478         // FIXME: Currently, this list only returns a single application cache.
479         if (applicationCaches)
480             callback(applicationCaches);
481     }
482
483     InspectorBackend.getApplicationCaches(mycallback);
484 }
485
486 WebInspector.Cookies = {}
487
488 WebInspector.Cookies.getCookiesAsync = function(callback)
489 {
490     function mycallback(cookies, cookiesString)
491     {
492         if (cookiesString)
493             callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false);
494         else
495             callback(cookies, true);
496     }
497
498     InspectorBackend.getCookies(mycallback);
499 }
500
501 WebInspector.Cookies.buildCookiesFromString = function(rawCookieString)
502 {
503     var rawCookies = rawCookieString.split(/;\s*/);
504     var cookies = [];
505
506     if (!(/^\s*$/.test(rawCookieString))) {
507         for (var i = 0; i < rawCookies.length; ++i) {
508             var cookie = rawCookies[i];
509             var delimIndex = cookie.indexOf("=");
510             var name = cookie.substring(0, delimIndex);
511             var value = cookie.substring(delimIndex + 1);
512             var size = name.length + value.length;
513             cookies.push({ name: name, value: value, size: size });
514         }
515     }
516
517     return cookies;
518 }
519
520 WebInspector.Cookies.cookieMatchesResourceURL = function(cookie, resourceURL)
521 {
522     var match = resourceURL.match(WebInspector.GenericURLRegExp);
523     if (!match)
524         return false;
525     // See WebInspector.URLRegExp for definitions of the group index constants.
526     if (!this.cookieDomainMatchesResourceDomain(cookie.domain, match[2]))
527         return false;
528     var resourcePort = match[3] ? match[3] : undefined;
529     var resourcePath = match[4] ? match[4] : '/';
530     return (resourcePath.indexOf(cookie.path) === 0
531         && (!cookie.port || resourcePort == cookie.port)
532         && (!cookie.secure || match[1].toLowerCase() === 'https'));
533 }
534
535 WebInspector.Cookies.cookieDomainMatchesResourceDomain = function(cookieDomain, resourceDomain)
536 {
537     if (cookieDomain.charAt(0) !== '.')
538         return resourceDomain === cookieDomain;
539     return !!resourceDomain.match(new RegExp("^([^\\.]+\\.)?" + cookieDomain.substring(1).escapeForRegExp() + "$"), "i");
540 }
541
542 WebInspector.EventListeners = {}
543
544 WebInspector.EventListeners.getEventListenersForNodeAsync = function(node, callback)
545 {
546     if (!node)
547         return;
548     InspectorBackend.getEventListenersForNode(node.id, callback);
549 }
550
551 WebInspector.CSSStyleDeclaration = function(payload)
552 {
553     this.id = payload.id;
554     this.parentStyleSheetId = payload.parentStyleSheetId;
555     this.width = payload.width;
556     this.height = payload.height;
557     this.__disabledProperties = {};
558     this.__disabledPropertyValues = {};
559     this.__disabledPropertyPriorities = {};
560     if (payload.disabled) {
561         for (var i = 0; i < payload.disabled.length; ++i) {
562             var property = payload.disabled[i];
563             this.__disabledProperties[property.name] = true;
564             this.__disabledPropertyValues[property.name] = property.value;
565             this.__disabledPropertyPriorities[property.name] = property.priority;
566         }
567     }
568
569     this._shorthandValues = payload.shorthandValues;
570     this._propertyMap = {};
571     this._longhandProperties = {};
572     this.length = payload.properties.length;
573
574     for (var i = 0; i < this.length; ++i) {
575         var property = payload.properties[i];
576         var name = property.name;
577         this[i] = name;
578         this._propertyMap[name] = property;
579
580         // Index longhand properties.
581         if (property.shorthand) {
582             var longhands = this._longhandProperties[property.shorthand];
583             if (!longhands) {
584                 longhands = [];
585                 this._longhandProperties[property.shorthand] = longhands;
586             }
587             longhands.push(name);
588         }
589     }
590 }
591
592 WebInspector.CSSStyleDeclaration.parseStyle = function(payload)
593 {
594     return new WebInspector.CSSStyleDeclaration(payload);
595 }
596
597 WebInspector.CSSStyleDeclaration.parseRule = function(payload)
598 {
599     var rule = {};
600     rule.id = payload.id;
601     rule.selectorText = payload.selectorText;
602     rule.style = new WebInspector.CSSStyleDeclaration(payload.style);
603     rule.style.parentRule = rule;
604     rule.isUserAgent = payload.isUserAgent;
605     rule.isUser = payload.isUser;
606     rule.isViaInspector = payload.isViaInspector;
607     rule.sourceLine = payload.sourceLine;
608     rule.documentURL = payload.documentURL;
609     if (payload.parentStyleSheet)
610         rule.parentStyleSheet = { href: payload.parentStyleSheet.href };
611
612     return rule;
613 }
614
615 WebInspector.CSSStyleDeclaration.prototype = {
616     getPropertyValue: function(name)
617     {
618         var property = this._propertyMap[name];
619         return property ? property.value : "";
620     },
621
622     getPropertyPriority: function(name)
623     {
624         var property = this._propertyMap[name];
625         return property ? property.priority : "";
626     },
627
628     getPropertyShorthand: function(name)
629     {
630         var property = this._propertyMap[name];
631         return property ? property.shorthand : "";
632     },
633
634     isPropertyImplicit: function(name)
635     {
636         var property = this._propertyMap[name];
637         return property ? property.implicit : "";
638     },
639
640     styleTextWithShorthands: function()
641     {
642         var cssText = "";
643         var foundProperties = {};
644         for (var i = 0; i < this.length; ++i) {
645             var individualProperty = this[i];
646             var shorthandProperty = this.getPropertyShorthand(individualProperty);
647             var propertyName = (shorthandProperty || individualProperty);
648
649             if (propertyName in foundProperties)
650                 continue;
651
652             if (shorthandProperty) {
653                 var value = this.getShorthandValue(shorthandProperty);
654                 var priority = this.getShorthandPriority(shorthandProperty);
655             } else {
656                 var value = this.getPropertyValue(individualProperty);
657                 var priority = this.getPropertyPriority(individualProperty);
658             }
659
660             foundProperties[propertyName] = true;
661
662             cssText += propertyName + ": " + value;
663             if (priority)
664                 cssText += " !" + priority;
665             cssText += "; ";
666         }
667
668         return cssText;
669     },
670
671     getLonghandProperties: function(name)
672     {
673         return this._longhandProperties[name] || [];
674     },
675
676     getShorthandValue: function(shorthandProperty)
677     {
678         return this._shorthandValues[shorthandProperty];
679     },
680
681     getShorthandPriority: function(shorthandProperty)
682     {
683         var priority = this.getPropertyPriority(shorthandProperty);
684         if (priority)
685             return priority;
686
687         var longhands = this._longhandProperties[shorthandProperty];
688         return longhands ? this.getPropertyPriority(longhands[0]) : null;
689     }
690 }
691
692 WebInspector.attributesUpdated = function()
693 {
694     this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
695 }
696
697 WebInspector.characterDataModified = function()
698 {
699     this.domAgent._characterDataModified.apply(this.domAgent, arguments);
700 }
701
702 WebInspector.setDocument = function()
703 {
704     this.domAgent._setDocument.apply(this.domAgent, arguments);
705 }
706
707 WebInspector.setDetachedRoot = function()
708 {
709     this.domAgent._setDetachedRoot.apply(this.domAgent, arguments);
710 }
711
712 WebInspector.setChildNodes = function()
713 {
714     this.domAgent._setChildNodes.apply(this.domAgent, arguments);
715 }
716
717 WebInspector.childNodeCountUpdated = function()
718 {
719     this.domAgent._childNodeCountUpdated.apply(this.domAgent, arguments);
720 }
721
722 WebInspector.childNodeInserted = function()
723 {
724     this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
725 }
726
727 WebInspector.childNodeRemoved = function()
728 {
729     this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
730 }
731
732 WebInspector.DOMBreakpointManager = function()
733 {
734     this._breakpoints = {};
735     this._pathCache = {};
736 }
737
738 WebInspector.DOMBreakpointManager.prototype = {
739     setBreakpoint: function(nodeId, type, enabled, path)
740     {
741         if (!(nodeId in this._breakpoints))
742             this._breakpoints[nodeId] = {};
743         else if (type in this._breakpoints[nodeId])
744             return;
745
746         var breakpoint = new WebInspector.DOMBreakpoint(nodeId, type, enabled);
747         this._breakpoints[nodeId][type] = breakpoint;
748         breakpoint.addEventListener("removed", this._breakpointRemoved, this);
749
750         if (!(nodeId in this._pathCache))
751             this._pathCache[nodeId] = path;
752
753         this.dispatchEventToListeners("dom-breakpoint-added", breakpoint);
754     },
755
756     findBreakpoint: function(nodeId, type)
757     {
758         var nodeBreakpoints = this._breakpoints[nodeId];
759         if (nodeBreakpoints && type in nodeBreakpoints)
760             return nodeBreakpoints[type];
761     },
762
763     removeBreakpointsForNode: function(nodeId)
764     {
765         var nodeBreakpoints = this._breakpoints[nodeId];
766         for (var type in nodeBreakpoints)
767             nodeBreakpoints[type].remove();
768     },
769
770     _breakpointRemoved: function(event)
771     {
772         var breakpoint = event.target;
773
774         var nodeBreakpoints = this._breakpoints[breakpoint.nodeId];
775         delete nodeBreakpoints[breakpoint.type];
776         for (var type in nodeBreakpoints)
777             return;
778
779         delete this._breakpoints[breakpoint.nodeId];
780         delete this._pathCache[breakpoint.nodeId];
781     },
782
783     restoreBreakpoints: function()
784     {
785         var breakpoints = this._breakpoints;
786         this._breakpoints = {};
787         var pathCache = this._pathCache;
788         this._pathCache = {};
789
790         for (var oldNodeId in breakpoints) {
791             var path = pathCache[oldNodeId];
792             InspectorBackend.pushNodeByPathToFrontend(path, restoreBreakpointsForNode.bind(this, breakpoints[oldNodeId], path));
793         }
794
795         function restoreBreakpointsForNode(nodeBreakpoints, path, nodeId)
796         {
797             if (!nodeId)
798                 return;
799             for (var type in nodeBreakpoints) {
800                 var breakpoint = nodeBreakpoints[type];
801                 this.setBreakpoint(nodeId, breakpoint.type, breakpoint.enabled, path);
802             }
803         }
804     }
805 }
806
807 WebInspector.DOMBreakpointManager.prototype.__proto__ = WebInspector.Object.prototype;
808
809 WebInspector.DOMBreakpoint = function(nodeId, type, enabled)
810 {
811     this._nodeId = nodeId;
812     this._type = type;
813     this._enabled = enabled;
814
815     if (this.enabled)
816         InspectorBackend.setDOMBreakpoint(this.nodeId, this.type);
817 }
818
819 WebInspector.DOMBreakpoint.Types = {
820     SubtreeModified: 0,
821     AttributeModified: 1,
822     NodeRemoved: 2
823 };
824
825 WebInspector.DOMBreakpoint.labelForType = function(type)
826 {
827     if (!WebInspector.DOMBreakpoint._labels) {
828         WebInspector.DOMBreakpoint._labels = {};
829         WebInspector.DOMBreakpoint._labels[WebInspector.DOMBreakpoint.Types.SubtreeModified] = WebInspector.UIString("Subtree Modified");
830         WebInspector.DOMBreakpoint._labels[WebInspector.DOMBreakpoint.Types.AttributeModified] = WebInspector.UIString("Attribute Modified");
831         WebInspector.DOMBreakpoint._labels[WebInspector.DOMBreakpoint.Types.NodeRemoved] = WebInspector.UIString("Node Removed");
832     }
833     return WebInspector.DOMBreakpoint._labels[type];
834 }
835
836 WebInspector.DOMBreakpoint.contextMenuLabelForType = function(type)
837 {
838     if (!WebInspector.DOMBreakpoint._contextMenuLabels) {
839         WebInspector.DOMBreakpoint._contextMenuLabels = {};
840         WebInspector.DOMBreakpoint._contextMenuLabels[WebInspector.DOMBreakpoint.Types.SubtreeModified] = WebInspector.UIString("Break on Subtree Modifications");
841         WebInspector.DOMBreakpoint._contextMenuLabels[WebInspector.DOMBreakpoint.Types.AttributeModified] = WebInspector.UIString("Break on Attributes Modifications");
842         WebInspector.DOMBreakpoint._contextMenuLabels[WebInspector.DOMBreakpoint.Types.NodeRemoved] = WebInspector.UIString("Break on Node Removal");
843     }
844     return WebInspector.DOMBreakpoint._contextMenuLabels[type];
845 }
846
847 WebInspector.DOMBreakpoint.prototype = {
848     get nodeId()
849     {
850         return this._nodeId;
851     },
852
853     get type()
854     {
855         return this._type;
856     },
857
858     get enabled()
859     {
860         return this._enabled;
861     },
862
863     set enabled(enabled)
864     {
865         if (this._enabled === enabled)
866             return;
867
868         this._enabled = enabled;
869         if (this.enabled)
870             InspectorBackend.setDOMBreakpoint(this.nodeId, this.type);
871         else
872             InspectorBackend.removeDOMBreakpoint(this.nodeId, this.type);
873
874         this.dispatchEventToListeners("enable-changed");
875     },
876
877     remove: function()
878     {
879         if (this.enabled)
880             InspectorBackend.removeDOMBreakpoint(this.nodeId, this.type);
881         this.dispatchEventToListeners("removed");
882     }
883 }
884
885 WebInspector.DOMBreakpoint.prototype.__proto__ = WebInspector.Object.prototype;
886