• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2009 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31WebInspector.DOMNode = function(doc, payload) {
32    this.ownerDocument = doc;
33
34    this._id = payload.id;
35    this.nodeType = payload.nodeType;
36    this.nodeName = payload.nodeName;
37    this._nodeValue = payload.nodeValue;
38    this.textContent = this.nodeValue;
39
40    this.attributes = [];
41    this._attributesMap = {};
42    if (payload.attributes)
43        this._setAttributesPayload(payload.attributes);
44
45    this._childNodeCount = payload.childNodeCount;
46    this.children = null;
47
48    this.nextSibling = null;
49    this.prevSibling = null;
50    this.firstChild = null;
51    this.parentNode = null;
52
53    if (payload.childNodes)
54        this._setChildrenPayload(payload.childNodes);
55
56    this._computedStyle = null;
57    this.style = null;
58    this._matchedCSSRules = [];
59}
60
61WebInspector.DOMNode.prototype = {
62    hasAttributes: function()
63    {
64        return this.attributes.length > 0;
65    },
66
67    hasChildNodes: function()  {
68        return this._childNodeCount > 0;
69    },
70
71    get nodeValue() {
72        return this._nodeValue;
73    },
74
75    set nodeValue(value) {
76        if (this.nodeType != Node.TEXT_NODE)
77            return;
78        var self = this;
79        var callback = function()
80        {
81            self._nodeValue = value;
82            self.textContent = value;
83        };
84        this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, callback);
85    },
86
87    getAttribute: function(name)
88    {
89        var attr = this._attributesMap[name];
90        return attr ? attr.value : undefined;
91    },
92
93    setAttribute: function(name, value)
94    {
95        var self = this;
96        var callback = function()
97        {
98            var attr = self._attributesMap[name];
99            if (attr)
100                attr.value = value;
101            else
102                attr = self._addAttribute(name, value);
103        };
104        this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback);
105    },
106
107    removeAttribute: function(name)
108    {
109        var self = this;
110        var callback = function()
111        {
112            delete self._attributesMap[name];
113            for (var i = 0;  i < self.attributes.length; ++i) {
114                if (self.attributes[i].name == name) {
115                    self.attributes.splice(i, 1);
116                    break;
117                }
118            }
119        };
120        this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback);
121    },
122
123    _setAttributesPayload: function(attrs)
124    {
125        for (var i = 0; i < attrs.length; i += 2)
126            this._addAttribute(attrs[i], attrs[i + 1]);
127    },
128
129    _insertChild: function(prev, payload)
130    {
131        var node = new WebInspector.DOMNode(this.ownerDocument, payload);
132        if (!prev)
133            // First node
134            this.children = [ node ];
135        else
136            this.children.splice(this.children.indexOf(prev) + 1, 0, node);
137        this._renumber();
138        return node;
139    },
140
141    removeChild_: function(node)
142    {
143        this.children.splice(this.children.indexOf(node), 1);
144        node.parentNode = null;
145        this._renumber();
146    },
147
148    _setChildrenPayload: function(payloads)
149    {
150        this.children = [];
151        for (var i = 0; i < payloads.length; ++i) {
152            var payload = payloads[i];
153            var node = new WebInspector.DOMNode(this.ownerDocument, payload);
154            this.children.push(node);
155        }
156        this._renumber();
157    },
158
159    _renumber: function()
160    {
161        this._childNodeCount = this.children.length;
162        if (this._childNodeCount == 0) {
163            this.firstChild = null;
164            return;
165        }
166        this.firstChild = this.children[0];
167        for (var i = 0; i < this._childNodeCount; ++i) {
168            var child = this.children[i];
169            child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
170            child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
171            child.parentNode = this;
172        }
173    },
174
175    _addAttribute: function(name, value)
176    {
177        var attr = {
178            "name": name,
179            "value": value,
180            "_node": this
181        };
182        this._attributesMap[name] = attr;
183        this.attributes.push(attr);
184    },
185
186    _setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules)
187    {
188        this._computedStyle = new WebInspector.CSSStyleDeclaration(computedStyle);
189        this.style = new WebInspector.CSSStyleDeclaration(inlineStyle);
190
191        for (var name in styleAttributes) {
192            if (this._attributesMap[name])
193                this._attributesMap[name].style = new WebInspector.CSSStyleDeclaration(styleAttributes[name]);
194        }
195
196        this._matchedCSSRules = [];
197        for (var i = 0; i < matchedCSSRules.length; i++)
198            this._matchedCSSRules.push(WebInspector.CSSStyleDeclaration.parseRule(matchedCSSRules[i]));
199    },
200
201    _clearStyles: function()
202    {
203        this.computedStyle = null;
204        this.style = null;
205        for (var name in this._attributesMap)
206            this._attributesMap[name].style = null;
207        this._matchedCSSRules = null;
208    }
209}
210
211WebInspector.DOMDocument = function(domAgent, defaultView)
212{
213    WebInspector.DOMNode.call(this, null,
214        {
215            id: 0,
216            nodeType: Node.DOCUMENT_NODE,
217            nodeName: "",
218            nodeValue: "",
219            attributes: [],
220            childNodeCount: 0
221        });
222    this._listeners = {};
223    this._domAgent = domAgent;
224    this.defaultView = defaultView;
225}
226
227WebInspector.DOMDocument.prototype = {
228
229    addEventListener: function(name, callback, useCapture)
230    {
231        var listeners = this._listeners[name];
232        if (!listeners) {
233            listeners = [];
234            this._listeners[name] = listeners;
235        }
236        listeners.push(callback);
237    },
238
239    removeEventListener: function(name, callback, useCapture)
240    {
241        var listeners = this._listeners[name];
242        if (!listeners)
243            return;
244
245        var index = listeners.indexOf(callback);
246        if (index != -1)
247            listeners.splice(index, 1);
248    },
249
250    _fireDomEvent: function(name, event)
251    {
252        var listeners = this._listeners[name];
253        if (!listeners)
254          return;
255
256        for (var i = 0; i < listeners.length; ++i)
257          listeners[i](event);
258    }
259}
260
261WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;
262
263
264WebInspector.DOMWindow = function(domAgent)
265{
266    this._domAgent = domAgent;
267}
268
269WebInspector.DOMWindow.prototype = {
270    get document()
271    {
272        return this._domAgent.document;
273    },
274
275    get Node()
276    {
277        return WebInspector.DOMNode;
278    },
279
280    get Element()
281    {
282        return WebInspector.DOMNode;
283    },
284
285    Object: function()
286    {
287    },
288
289    getComputedStyle: function(node)
290    {
291        return node._computedStyle;
292    },
293
294    getMatchedCSSRules: function(node, pseudoElement, authorOnly)
295    {
296        return node._matchedCSSRules;
297    }
298}
299
300WebInspector.DOMAgent = function() {
301    this._window = new WebInspector.DOMWindow(this);
302    this._idToDOMNode = null;
303    this.document = null;
304
305    // Install onpopulate handler. This is a temporary measure.
306    // TODO: add this code into the original updateChildren once domAgent
307    // becomes primary source of DOM information.
308    // TODO2: update ElementsPanel to not track embedded iframes - it is already being handled
309    // in the agent backend.
310    var domAgent = this;
311    var originalUpdateChildren = WebInspector.ElementsTreeElement.prototype.updateChildren;
312    WebInspector.ElementsTreeElement.prototype.updateChildren = function()
313    {
314        domAgent.getChildNodesAsync(this.representedObject, originalUpdateChildren.bind(this));
315    };
316
317    // Mute console handle to avoid crash on selection change.
318    // TODO: Re-implement inspectorConsoleAPI to work in a serialized way and remove this workaround.
319    WebInspector.Console.prototype.addInspectedNode = function()
320    {
321    };
322
323    // Whitespace is ignored in InspectorDOMAgent already -> no need to filter.
324    // TODO: Either remove all of its usages or push value into the agent backend.
325    Preferences.ignoreWhitespace = false;
326}
327
328WebInspector.DOMAgent.prototype = {
329    get inspectedWindow()
330    {
331        return this._window;
332    },
333
334    getChildNodesAsync: function(parent, opt_callback)
335    {
336        var children = parent.children;
337        if (children && opt_callback) {
338          opt_callback(children);
339          return;
340        }
341        var mycallback = function() {
342            if (opt_callback) {
343                opt_callback(parent.children);
344            }
345        };
346        var callId = WebInspector.Callback.wrap(mycallback);
347        InspectorController.getChildNodes(callId, parent._id);
348    },
349
350    setAttributeAsync: function(node, name, value, callback)
351    {
352        var mycallback = this._didApplyDomChange.bind(this, node, callback);
353        InspectorController.setAttribute(WebInspector.Callback.wrap(mycallback), node._id, name, value);
354    },
355
356    removeAttributeAsync: function(node, name, callback)
357    {
358        var mycallback = this._didApplyDomChange.bind(this, node, callback);
359        InspectorController.removeAttribute(WebInspector.Callback.wrap(mycallback), node._id, name);
360    },
361
362    setTextNodeValueAsync: function(node, text, callback)
363    {
364        var mycallback = this._didApplyDomChange.bind(this, node, callback);
365        InspectorController.setTextNodeValue(WebInspector.Callback.wrap(mycallback), node._id, text);
366    },
367
368    _didApplyDomChange: function(node, callback, success)
369    {
370        if (!success)
371            return;
372        callback();
373        // TODO(pfeldman): Fix this hack.
374        var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node);
375        if (elem) {
376            elem._updateTitle();
377        }
378    },
379
380    _attributesUpdated: function(nodeId, attrsArray)
381    {
382        var node = this._idToDOMNode[nodeId];
383        node._setAttributesPayload(attrsArray);
384    },
385
386    getNodeForId: function(nodeId) {
387        return this._idToDOMNode[nodeId];
388    },
389
390    _setDocumentElement: function(payload)
391    {
392        this.document = new WebInspector.DOMDocument(this, this._window);
393        this._idToDOMNode = { 0 : this.document };
394        this._setChildNodes(0, [payload]);
395        this.document.documentElement = this.document.firstChild;
396        this.document.documentElement.ownerDocument = this.document;
397        WebInspector.panels.elements.reset();
398    },
399
400    _setChildNodes: function(parentId, payloads)
401    {
402        var parent = this._idToDOMNode[parentId];
403        if (parent.children) {
404          return;
405        }
406        parent._setChildrenPayload(payloads);
407        this._bindNodes(parent.children);
408    },
409
410    _bindNodes: function(children)
411    {
412        for (var i = 0; i < children.length; ++i) {
413            var child = children[i];
414            this._idToDOMNode[child._id] = child;
415            if (child.children)
416                this._bindNodes(child.children);
417        }
418    },
419
420    _hasChildrenUpdated: function(nodeId, newValue)
421    {
422        var node = this._idToDOMNode[nodeId];
423        var outline = WebInspector.panels.elements.treeOutline;
424        var treeElement = outline.findTreeElement(node);
425        if (treeElement) {
426            treeElement.hasChildren = newValue;
427            treeElement.whitespaceIgnored = Preferences.ignoreWhitespace;
428        }
429    },
430
431    _childNodeInserted: function(parentId, prevId, payload)
432    {
433        var parent = this._idToDOMNode[parentId];
434        var prev = this._idToDOMNode[prevId];
435        var node = parent._insertChild(prev, payload);
436        this._idToDOMNode[node._id] = node;
437        var event = { target : node, relatedNode : parent };
438        this.document._fireDomEvent("DOMNodeInserted", event);
439    },
440
441    _childNodeRemoved: function(parentId, nodeId)
442    {
443        var parent = this._idToDOMNode[parentId];
444        var node = this._idToDOMNode[nodeId];
445        parent.removeChild_(node);
446        var event = { target : node, relatedNode : parent };
447        this.document._fireDomEvent("DOMNodeRemoved", event);
448        delete this._idToDOMNode[nodeId];
449    }
450}
451
452WebInspector.CSSStyleDeclaration = function(payload) {
453    this._id = payload.id;
454    this.width = payload.width;
455    this.height = payload.height;
456    this.__disabledProperties = payload.__disabledProperties;
457    this.__disabledPropertyValues = payload.__disabledPropertyValues;
458    this.__disabledPropertyPriorities = payload.__disabledPropertyPriorities;
459    this.uniqueStyleProperties = payload.uniqueStyleProperties;
460    this._shorthandValues = payload.shorthandValues;
461    this._propertyMap = {};
462    this._longhandProperties = {};
463    this.length = payload.properties.length;
464
465    for (var i = 0; i < this.length; ++i) {
466        var property = payload.properties[i];
467        var name = property.name;
468        this[i] = name;
469        this._propertyMap[name] = property;
470    }
471
472    // Index longhand properties.
473    for (var i = 0; i < this.uniqueStyleProperties.length; ++i) {
474        var name = this.uniqueStyleProperties[i];
475        var property = this._propertyMap[name];
476        if (property.shorthand) {
477            var longhands = this._longhandProperties[property.shorthand];
478            if (!longhands) {
479                longhands = [];
480                this._longhandProperties[property.shorthand] = longhands;
481            }
482            longhands.push(name);
483        }
484    }
485}
486
487WebInspector.CSSStyleDeclaration.parseStyle = function(payload)
488{
489    return new WebInspector.CSSStyleDeclaration(payload);
490}
491
492WebInspector.CSSStyleDeclaration.parseRule = function(payload)
493{
494    var rule = {};
495    rule._id = payload.id;
496    rule.selectorText = payload.selectorText;
497    rule.style = new WebInspector.CSSStyleDeclaration(payload.style);
498    rule.style.parentRule = rule;
499    rule.isUserAgent = payload.isUserAgent;
500    rule.isUser = payload.isUser;
501    if (payload.parentStyleSheet)
502        rule.parentStyleSheet = { href: payload.parentStyleSheet.href };
503
504    return rule;
505}
506
507WebInspector.CSSStyleDeclaration.prototype = {
508    getPropertyValue: function(name)
509    {
510        var property = this._propertyMap[name];
511        return property ? property.value : "";
512    },
513
514    getPropertyPriority: function(name)
515    {
516        var property = this._propertyMap[name];
517        return property ? property.priority : "";
518    },
519
520    getPropertyShorthand: function(name)
521    {
522        var property = this._propertyMap[name];
523        return property ? property.shorthand : "";
524    },
525
526    isPropertyImplicit: function(name)
527    {
528        var property = this._propertyMap[name];
529        return property ? property.implicit : "";
530    },
531
532    styleTextWithShorthands: function()
533    {
534        var cssText = "";
535        var foundProperties = {};
536        for (var i = 0; i < this.length; ++i) {
537            var individualProperty = this[i];
538            var shorthandProperty = this.getPropertyShorthand(individualProperty);
539            var propertyName = (shorthandProperty || individualProperty);
540
541            if (propertyName in foundProperties)
542                continue;
543
544            if (shorthandProperty) {
545                var value = this.getPropertyValue(shorthandProperty);
546                var priority = this.getShorthandPriority(shorthandProperty);
547            } else {
548                var value = this.getPropertyValue(individualProperty);
549                var priority = this.getPropertyPriority(individualProperty);
550            }
551
552            foundProperties[propertyName] = true;
553
554            cssText += propertyName + ": " + value;
555            if (priority)
556                cssText += " !" + priority;
557            cssText += "; ";
558        }
559
560        return cssText;
561    },
562
563    getLonghandProperties: function(name)
564    {
565        return this._longhandProperties[name] || [];
566    },
567
568    getShorthandValue: function(shorthandProperty)
569    {
570        return this._shorthandValues[shorthandProperty];
571    },
572
573    getShorthandPriority: function(shorthandProperty)
574    {
575        var priority = this.getPropertyPriority(shorthandProperty);
576        if (priority)
577            return priority;
578
579        var longhands = this._longhandProperties[shorthandProperty];
580        return longhands ? this.getPropertyPriority(longhands[0]) : null;
581    }
582}
583
584WebInspector.attributesUpdated = function()
585{
586    this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
587}
588
589WebInspector.setDocumentElement = function()
590{
591    this.domAgent._setDocumentElement.apply(this.domAgent, arguments);
592}
593
594WebInspector.setChildNodes = function()
595{
596    this.domAgent._setChildNodes.apply(this.domAgent, arguments);
597}
598
599WebInspector.hasChildrenUpdated = function()
600{
601    this.domAgent._hasChildrenUpdated.apply(this.domAgent, arguments);
602}
603
604WebInspector.childNodeInserted = function()
605{
606    this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
607    this._childNodeInserted.bind(this);
608}
609
610WebInspector.childNodeRemoved = function()
611{
612    this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
613    this._childNodeRemoved.bind(this);
614}
615
616WebInspector.didGetChildNodes = WebInspector.Callback.processCallback;
617WebInspector.didPerformSearch = WebInspector.Callback.processCallback;
618WebInspector.didApplyDomChange = WebInspector.Callback.processCallback;
619WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback;
620WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback;
621
622// Temporary methods for DOMAgent migration.
623WebInspector.wrapNodeWithStyles = function(node, styles)
624{
625    var windowStub = new WebInspector.DOMWindow(null);
626    var docStub = new WebInspector.DOMDocument(null, windowStub);
627    var payload = {};
628    payload.nodeType = node.nodeType;
629    payload.nodeName = node.nodeName;
630    payload.nodeValue = node.nodeValue;
631    payload.attributes = [];
632    payload.childNodeCount = 0;
633
634    for (var i = 0; i < node.attributes.length; ++i) {
635        var attr = node.attributes[i];
636        payload.attributes.push(attr.name);
637        payload.attributes.push(attr.value);
638    }
639    var nodeStub = new WebInspector.DOMNode(docStub, payload);
640    nodeStub._setStyles(styles.computedStyle, styles.inlineStyle, styles.styleAttributes, styles.matchedCSSRules);
641    return nodeStub;
642}
643
644// Temporary methods that will be dispatched via InspectorController into the injected context.
645InspectorController.getStyles = function(nodeId, authorOnly, callback)
646{
647    setTimeout(function() {
648        callback(InjectedScript.getStyles(nodeId, authorOnly));
649    }, 0)
650}
651
652InspectorController.getComputedStyle = function(nodeId, callback)
653{
654    setTimeout(function() {
655        callback(InjectedScript.getComputedStyle(nodeId));
656    }, 0)
657}
658
659InspectorController.getInlineStyle = function(nodeId, callback)
660{
661    setTimeout(function() {
662        callback(InjectedScript.getInlineStyle(nodeId));
663    }, 0)
664}
665
666InspectorController.applyStyleText = function(styleId, styleText, propertyName, callback)
667{
668    setTimeout(function() {
669        callback(InjectedScript.applyStyleText(styleId, styleText, propertyName));
670    }, 0)
671}
672
673InspectorController.setStyleText = function(style, cssText, callback)
674{
675    setTimeout(function() {
676        callback(InjectedScript.setStyleText(style, cssText));
677    }, 0)
678}
679
680InspectorController.toggleStyleEnabled = function(styleId, propertyName, disabled, callback)
681{
682    setTimeout(function() {
683        callback(InjectedScript.toggleStyleEnabled(styleId, propertyName, disabled));
684    }, 0)
685}
686
687InspectorController.applyStyleRuleText = function(ruleId, newContent, selectedNode, callback)
688{
689    setTimeout(function() {
690        callback(InjectedScript.applyStyleRuleText(ruleId, newContent, selectedNode));
691    }, 0)
692}
693
694InspectorController.addStyleSelector = function(newContent, callback)
695{
696    setTimeout(function() {
697        callback(InjectedScript.addStyleSelector(newContent));
698    }, 0)
699}
700
701InspectorController.setStyleProperty = function(styleId, name, value, callback) {
702    setTimeout(function() {
703        callback(InjectedScript.setStyleProperty(styleId, name, value));
704    }, 0)
705}
706
707InspectorController.getPrototypes = function(objectProxy, callback) {
708    setTimeout(function() {
709        callback(InjectedScript.getPrototypes(objectProxy));
710    }, 0)
711}
712
713InspectorController.getProperties = function(objectProxy, ignoreHasOwnProperty, callback) {
714    setTimeout(function() {
715        callback(InjectedScript.getProperties(objectProxy, ignoreHasOwnProperty));
716    }, 0)
717}
718
719InspectorController.setPropertyValue = function(objectProxy, propertyName, expression, callback) {
720    setTimeout(function() {
721        callback(InjectedScript.setPropertyValue(objectProxy, propertyName, expression));
722    }, 0)
723}
724
725