/* * 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) }