Javascript  |  725行  |  21.77 KB

/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WebInspector.DOMNode = function(doc, payload) {
    this.ownerDocument = doc;

    this._id = payload.id;
    this.nodeType = payload.nodeType;
    this.nodeName = payload.nodeName;
    this._nodeValue = payload.nodeValue;
    this.textContent = this.nodeValue;

    this.attributes = [];
    this._attributesMap = {};
    if (payload.attributes)
        this._setAttributesPayload(payload.attributes);

    this._childNodeCount = payload.childNodeCount;
    this.children = null;

    this.nextSibling = null;
    this.prevSibling = null;
    this.firstChild = null;
    this.parentNode = null;

    if (payload.childNodes)
        this._setChildrenPayload(payload.childNodes);

    this._computedStyle = null;
    this.style = null;
    this._matchedCSSRules = [];
}

WebInspector.DOMNode.prototype = {
    hasAttributes: function()
    {
        return this.attributes.length > 0;
    },

    hasChildNodes: function()  {
        return this._childNodeCount > 0;
    },

    get nodeValue() {
        return this._nodeValue;
    },

    set nodeValue(value) {
        if (this.nodeType != Node.TEXT_NODE)
            return;
        var self = this;
        var callback = function()
        {
            self._nodeValue = value;
            self.textContent = value;
        };
        this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, callback);
    },

    getAttribute: function(name)
    {
        var attr = this._attributesMap[name];
        return attr ? attr.value : undefined;
    },

    setAttribute: function(name, value)
    {
        var self = this;
        var callback = function()
        {
            var attr = self._attributesMap[name];
            if (attr)
                attr.value = value;
            else
                attr = self._addAttribute(name, value);
        };
        this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback);
    },

    removeAttribute: function(name)
    {
        var self = this;
        var callback = function()
        {
            delete self._attributesMap[name];
            for (var i = 0;  i < self.attributes.length; ++i) {
                if (self.attributes[i].name == name) {
                    self.attributes.splice(i, 1);
                    break;
                }
            }
        };
        this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback);
    },

    _setAttributesPayload: function(attrs)
    {
        for (var i = 0; i < attrs.length; i += 2)
            this._addAttribute(attrs[i], attrs[i + 1]);
    },

    _insertChild: function(prev, payload)
    {
        var node = new WebInspector.DOMNode(this.ownerDocument, payload);
        if (!prev)
            // First node
            this.children = [ node ];
        else
            this.children.splice(this.children.indexOf(prev) + 1, 0, node);
        this._renumber();
        return node;
    },

    removeChild_: function(node)
    {
        this.children.splice(this.children.indexOf(node), 1);
        node.parentNode = null;
        this._renumber();
    },

    _setChildrenPayload: function(payloads)
    {
        this.children = [];
        for (var i = 0; i < payloads.length; ++i) {
            var payload = payloads[i];
            var node = new WebInspector.DOMNode(this.ownerDocument, payload);
            this.children.push(node);
        }
        this._renumber();
    },

    _renumber: function()
    {
        this._childNodeCount = this.children.length;
        if (this._childNodeCount == 0) {
            this.firstChild = null;
            return;
        }
        this.firstChild = this.children[0];
        for (var i = 0; i < this._childNodeCount; ++i) {
            var child = this.children[i];
            child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null;
            child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null;
            child.parentNode = this;
        }
    },

    _addAttribute: function(name, value)
    {
        var attr = {
            "name": name,
            "value": value,
            "_node": this
        };
        this._attributesMap[name] = attr;
        this.attributes.push(attr);
    },

    _setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules)
    {
        this._computedStyle = new WebInspector.CSSStyleDeclaration(computedStyle);
        this.style = new WebInspector.CSSStyleDeclaration(inlineStyle);

        for (var name in styleAttributes) {
            if (this._attributesMap[name])
                this._attributesMap[name].style = new WebInspector.CSSStyleDeclaration(styleAttributes[name]);
        }

        this._matchedCSSRules = [];
        for (var i = 0; i < matchedCSSRules.length; i++)
            this._matchedCSSRules.push(WebInspector.CSSStyleDeclaration.parseRule(matchedCSSRules[i]));
    },

    _clearStyles: function()
    {
        this.computedStyle = null;
        this.style = null;
        for (var name in this._attributesMap)
            this._attributesMap[name].style = null;
        this._matchedCSSRules = null;
    }
}

WebInspector.DOMDocument = function(domAgent, defaultView)
{
    WebInspector.DOMNode.call(this, null,
        {
            id: 0,
            nodeType: Node.DOCUMENT_NODE,
            nodeName: "",
            nodeValue: "",
            attributes: [],
            childNodeCount: 0
        });
    this._listeners = {};
    this._domAgent = domAgent;
    this.defaultView = defaultView;
}

WebInspector.DOMDocument.prototype = {

    addEventListener: function(name, callback, useCapture)
    {
        var listeners = this._listeners[name];
        if (!listeners) {
            listeners = [];
            this._listeners[name] = listeners;
        }
        listeners.push(callback);
    },

    removeEventListener: function(name, callback, useCapture)
    {
        var listeners = this._listeners[name];
        if (!listeners)
            return;

        var index = listeners.indexOf(callback);
        if (index != -1)
            listeners.splice(index, 1);
    },

    _fireDomEvent: function(name, event)
    {
        var listeners = this._listeners[name];
        if (!listeners)
          return;

        for (var i = 0; i < listeners.length; ++i)
          listeners[i](event);
    }
}

WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype;


WebInspector.DOMWindow = function(domAgent)
{
    this._domAgent = domAgent;
}

WebInspector.DOMWindow.prototype = {
    get document()
    {
        return this._domAgent.document;
    },

    get Node()
    {
        return WebInspector.DOMNode;
    },

    get Element()
    {
        return WebInspector.DOMNode;
    },

    Object: function()
    {
    },

    getComputedStyle: function(node)
    {
        return node._computedStyle;
    },

    getMatchedCSSRules: function(node, pseudoElement, authorOnly)
    {
        return node._matchedCSSRules;
    }
}

WebInspector.DOMAgent = function() {
    this._window = new WebInspector.DOMWindow(this);
    this._idToDOMNode = null;
    this.document = null;

    // Install onpopulate handler. This is a temporary measure.
    // TODO: add this code into the original updateChildren once domAgent
    // becomes primary source of DOM information.
    // TODO2: update ElementsPanel to not track embedded iframes - it is already being handled
    // in the agent backend.
    var domAgent = this;
    var originalUpdateChildren = WebInspector.ElementsTreeElement.prototype.updateChildren;
    WebInspector.ElementsTreeElement.prototype.updateChildren = function()
    {
        domAgent.getChildNodesAsync(this.representedObject, originalUpdateChildren.bind(this));
    };

    // Mute console handle to avoid crash on selection change.
    // TODO: Re-implement inspectorConsoleAPI to work in a serialized way and remove this workaround.
    WebInspector.Console.prototype.addInspectedNode = function()
    {
    };

    // Whitespace is ignored in InspectorDOMAgent already -> no need to filter.
    // TODO: Either remove all of its usages or push value into the agent backend.
    Preferences.ignoreWhitespace = false;
}

WebInspector.DOMAgent.prototype = {
    get inspectedWindow()
    {
        return this._window;
    },

    getChildNodesAsync: function(parent, opt_callback)
    {
        var children = parent.children;
        if (children && opt_callback) {
          opt_callback(children);
          return;
        }
        var mycallback = function() {
            if (opt_callback) {
                opt_callback(parent.children);
            }
        };
        var callId = WebInspector.Callback.wrap(mycallback);
        InspectorController.getChildNodes(callId, parent._id);
    },

    setAttributeAsync: function(node, name, value, callback)
    {
        var mycallback = this._didApplyDomChange.bind(this, node, callback);
        InspectorController.setAttribute(WebInspector.Callback.wrap(mycallback), node._id, name, value);
    },

    removeAttributeAsync: function(node, name, callback)
    {
        var mycallback = this._didApplyDomChange.bind(this, node, callback);
        InspectorController.removeAttribute(WebInspector.Callback.wrap(mycallback), node._id, name);
    },

    setTextNodeValueAsync: function(node, text, callback)
    {
        var mycallback = this._didApplyDomChange.bind(this, node, callback);
        InspectorController.setTextNodeValue(WebInspector.Callback.wrap(mycallback), node._id, text);
    },

    _didApplyDomChange: function(node, callback, success)
    {
        if (!success)
            return;
        callback();
        // TODO(pfeldman): Fix this hack.
        var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node);
        if (elem) {
            elem._updateTitle();
        }
    },

    _attributesUpdated: function(nodeId, attrsArray)
    {
        var node = this._idToDOMNode[nodeId];
        node._setAttributesPayload(attrsArray);
    },

    getNodeForId: function(nodeId) {
        return this._idToDOMNode[nodeId];
    },

    _setDocumentElement: function(payload)
    {
        this.document = new WebInspector.DOMDocument(this, this._window);
        this._idToDOMNode = { 0 : this.document };
        this._setChildNodes(0, [payload]);
        this.document.documentElement = this.document.firstChild;
        this.document.documentElement.ownerDocument = this.document;
        WebInspector.panels.elements.reset();
    },

    _setChildNodes: function(parentId, payloads)
    {
        var parent = this._idToDOMNode[parentId];
        if (parent.children) {
          return;
        }
        parent._setChildrenPayload(payloads);
        this._bindNodes(parent.children);
    },

    _bindNodes: function(children)
    {
        for (var i = 0; i < children.length; ++i) {
            var child = children[i];
            this._idToDOMNode[child._id] = child;
            if (child.children)
                this._bindNodes(child.children);
        }
    },

    _hasChildrenUpdated: function(nodeId, newValue)
    {
        var node = this._idToDOMNode[nodeId];
        var outline = WebInspector.panels.elements.treeOutline;
        var treeElement = outline.findTreeElement(node);
        if (treeElement) {
            treeElement.hasChildren = newValue;
            treeElement.whitespaceIgnored = Preferences.ignoreWhitespace;
        }
    },

    _childNodeInserted: function(parentId, prevId, payload)
    {
        var parent = this._idToDOMNode[parentId];
        var prev = this._idToDOMNode[prevId];
        var node = parent._insertChild(prev, payload);
        this._idToDOMNode[node._id] = node;
        var event = { target : node, relatedNode : parent };
        this.document._fireDomEvent("DOMNodeInserted", event);
    },

    _childNodeRemoved: function(parentId, nodeId)
    {
        var parent = this._idToDOMNode[parentId];
        var node = this._idToDOMNode[nodeId];
        parent.removeChild_(node);
        var event = { target : node, relatedNode : parent };
        this.document._fireDomEvent("DOMNodeRemoved", event);
        delete this._idToDOMNode[nodeId];
    }
}

WebInspector.CSSStyleDeclaration = function(payload) {
    this._id = payload.id;
    this.width = payload.width;
    this.height = payload.height;
    this.__disabledProperties = payload.__disabledProperties;
    this.__disabledPropertyValues = payload.__disabledPropertyValues;
    this.__disabledPropertyPriorities = payload.__disabledPropertyPriorities;
    this.uniqueStyleProperties = payload.uniqueStyleProperties;
    this._shorthandValues = payload.shorthandValues;
    this._propertyMap = {};
    this._longhandProperties = {};
    this.length = payload.properties.length;

    for (var i = 0; i < this.length; ++i) {
        var property = payload.properties[i];
        var name = property.name;
        this[i] = name;
        this._propertyMap[name] = property;
    }

    // Index longhand properties.
    for (var i = 0; i < this.uniqueStyleProperties.length; ++i) {
        var name = this.uniqueStyleProperties[i];
        var property = this._propertyMap[name];
        if (property.shorthand) {
            var longhands = this._longhandProperties[property.shorthand];
            if (!longhands) {
                longhands = [];
                this._longhandProperties[property.shorthand] = longhands;
            }
            longhands.push(name);
        }
    }
}

WebInspector.CSSStyleDeclaration.parseStyle = function(payload)
{
    return new WebInspector.CSSStyleDeclaration(payload);
}

WebInspector.CSSStyleDeclaration.parseRule = function(payload)
{
    var rule = {};
    rule._id = payload.id;
    rule.selectorText = payload.selectorText;
    rule.style = new WebInspector.CSSStyleDeclaration(payload.style);
    rule.style.parentRule = rule;
    rule.isUserAgent = payload.isUserAgent;
    rule.isUser = payload.isUser;
    if (payload.parentStyleSheet)
        rule.parentStyleSheet = { href: payload.parentStyleSheet.href };

    return rule;
}

WebInspector.CSSStyleDeclaration.prototype = {
    getPropertyValue: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.value : "";
    },

    getPropertyPriority: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.priority : "";
    },

    getPropertyShorthand: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.shorthand : "";
    },

    isPropertyImplicit: function(name)
    {
        var property = this._propertyMap[name];
        return property ? property.implicit : "";
    },

    styleTextWithShorthands: function()
    {
        var cssText = "";
        var foundProperties = {};
        for (var i = 0; i < this.length; ++i) {
            var individualProperty = this[i];
            var shorthandProperty = this.getPropertyShorthand(individualProperty);
            var propertyName = (shorthandProperty || individualProperty);

            if (propertyName in foundProperties)
                continue;

            if (shorthandProperty) {
                var value = this.getPropertyValue(shorthandProperty);
                var priority = this.getShorthandPriority(shorthandProperty);
            } else {
                var value = this.getPropertyValue(individualProperty);
                var priority = this.getPropertyPriority(individualProperty);
            }

            foundProperties[propertyName] = true;

            cssText += propertyName + ": " + value;
            if (priority)
                cssText += " !" + priority;
            cssText += "; ";
        }

        return cssText;
    },

    getLonghandProperties: function(name)
    {
        return this._longhandProperties[name] || [];
    },

    getShorthandValue: function(shorthandProperty)
    {
        return this._shorthandValues[shorthandProperty];
    },

    getShorthandPriority: function(shorthandProperty)
    {
        var priority = this.getPropertyPriority(shorthandProperty);
        if (priority)
            return priority;

        var longhands = this._longhandProperties[shorthandProperty];
        return longhands ? this.getPropertyPriority(longhands[0]) : null;
    }
}

WebInspector.attributesUpdated = function()
{
    this.domAgent._attributesUpdated.apply(this.domAgent, arguments);
}

WebInspector.setDocumentElement = function()
{
    this.domAgent._setDocumentElement.apply(this.domAgent, arguments);
}

WebInspector.setChildNodes = function()
{
    this.domAgent._setChildNodes.apply(this.domAgent, arguments);
}

WebInspector.hasChildrenUpdated = function()
{
    this.domAgent._hasChildrenUpdated.apply(this.domAgent, arguments);
}

WebInspector.childNodeInserted = function()
{
    this.domAgent._childNodeInserted.apply(this.domAgent, arguments);
    this._childNodeInserted.bind(this);
}

WebInspector.childNodeRemoved = function()
{
    this.domAgent._childNodeRemoved.apply(this.domAgent, arguments);
    this._childNodeRemoved.bind(this);
}

WebInspector.didGetChildNodes = WebInspector.Callback.processCallback;
WebInspector.didPerformSearch = WebInspector.Callback.processCallback;
WebInspector.didApplyDomChange = WebInspector.Callback.processCallback;
WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback;
WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback;

// Temporary methods for DOMAgent migration.
WebInspector.wrapNodeWithStyles = function(node, styles)
{
    var windowStub = new WebInspector.DOMWindow(null);
    var docStub = new WebInspector.DOMDocument(null, windowStub);
    var payload = {};
    payload.nodeType = node.nodeType;
    payload.nodeName = node.nodeName;
    payload.nodeValue = node.nodeValue;
    payload.attributes = [];
    payload.childNodeCount = 0;

    for (var i = 0; i < node.attributes.length; ++i) {
        var attr = node.attributes[i];
        payload.attributes.push(attr.name);
        payload.attributes.push(attr.value);
    }
    var nodeStub = new WebInspector.DOMNode(docStub, payload);
    nodeStub._setStyles(styles.computedStyle, styles.inlineStyle, styles.styleAttributes, styles.matchedCSSRules);
    return nodeStub;
}

// Temporary methods that will be dispatched via InspectorController into the injected context.
InspectorController.getStyles = function(nodeId, authorOnly, callback)
{
    setTimeout(function() {
        callback(InjectedScript.getStyles(nodeId, authorOnly));
    }, 0)
}

InspectorController.getComputedStyle = function(nodeId, callback)
{
    setTimeout(function() {
        callback(InjectedScript.getComputedStyle(nodeId));
    }, 0)
}

InspectorController.getInlineStyle = function(nodeId, callback)
{
    setTimeout(function() {
        callback(InjectedScript.getInlineStyle(nodeId));
    }, 0)
}

InspectorController.applyStyleText = function(styleId, styleText, propertyName, callback)
{
    setTimeout(function() {
        callback(InjectedScript.applyStyleText(styleId, styleText, propertyName));
    }, 0)
}

InspectorController.setStyleText = function(style, cssText, callback)
{
    setTimeout(function() {
        callback(InjectedScript.setStyleText(style, cssText));
    }, 0)
}

InspectorController.toggleStyleEnabled = function(styleId, propertyName, disabled, callback)
{
    setTimeout(function() {
        callback(InjectedScript.toggleStyleEnabled(styleId, propertyName, disabled));
    }, 0)
}

InspectorController.applyStyleRuleText = function(ruleId, newContent, selectedNode, callback)
{
    setTimeout(function() {
        callback(InjectedScript.applyStyleRuleText(ruleId, newContent, selectedNode));
    }, 0)
}

InspectorController.addStyleSelector = function(newContent, callback)
{
    setTimeout(function() {
        callback(InjectedScript.addStyleSelector(newContent));
    }, 0)
}

InspectorController.setStyleProperty = function(styleId, name, value, callback) {
    setTimeout(function() {
        callback(InjectedScript.setStyleProperty(styleId, name, value));
    }, 0)
}

InspectorController.getPrototypes = function(objectProxy, callback) {
    setTimeout(function() {
        callback(InjectedScript.getPrototypes(objectProxy));
    }, 0)
}

InspectorController.getProperties = function(objectProxy, ignoreHasOwnProperty, callback) {
    setTimeout(function() {
        callback(InjectedScript.getProperties(objectProxy, ignoreHasOwnProperty));
    }, 0)
}

InspectorController.setPropertyValue = function(objectProxy, propertyName, expression, callback) {
    setTimeout(function() {
        callback(InjectedScript.setPropertyValue(objectProxy, propertyName, expression));
    }, 0)
}