• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
32WebInspector.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
83WebInspector.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
238WebInspector.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
246WebInspector.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
282WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;
283
284
285WebInspector.DOMWindow = function(domAgent)
286{
287    this._domAgent = domAgent;
288}
289
290WebInspector.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
321WebInspector.DOMAgent = function() {
322    this._window = new WebInspector.DOMWindow(this);
323    this._idToDOMNode = null;
324    this.document = null;
325}
326
327WebInspector.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
454WebInspector.Cookies = {}
455
456WebInspector.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
468WebInspector.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
487WebInspector.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
502WebInspector.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
509WebInspector.EventListeners = {}
510
511WebInspector.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
520WebInspector.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
557WebInspector.CSSStyleDeclaration.parseStyle = function(payload)
558{
559    return new WebInspector.CSSStyleDeclaration(payload);
560}
561
562WebInspector.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
579WebInspector.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
656WebInspector.attributesUpdated = function()
657{
658    this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
659}
660
661WebInspector.setDocument = function()
662{
663    this.domAgent._setDocument.apply(this.domAgent, arguments);
664}
665
666WebInspector.setDetachedRoot = function()
667{
668    this.domAgent._setDetachedRoot.apply(this.domAgent, arguments);
669}
670
671WebInspector.setChildNodes = function()
672{
673    this.domAgent._setChildNodes.apply(this.domAgent, arguments);
674}
675
676WebInspector.childNodeCountUpdated = function()
677{
678    this.domAgent._childNodeCountUpdated.apply(this.domAgent, arguments);
679}
680
681WebInspector.childNodeInserted = function()
682{
683    this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
684}
685
686WebInspector.childNodeRemoved = function()
687{
688    this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
689}
690
691WebInspector.didGetCookies = WebInspector.Callback.processCallback;
692WebInspector.didGetChildNodes = WebInspector.Callback.processCallback;
693WebInspector.didPerformSearch = WebInspector.Callback.processCallback;
694WebInspector.didApplyDomChange = WebInspector.Callback.processCallback;
695WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback;
696WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback;
697WebInspector.didGetEventListenersForNode = WebInspector.Callback.processCallback;
698