// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview Dom and DomNode are used to represent remote DOM in the * web inspector. */ goog.provide('devtools.DomAgent'); goog.provide('devtools.DomDocument'); goog.provide('devtools.DomNode'); goog.require('devtools.Callback'); /** * Defines indexes for the node payload properties. */ devtools.PayloadIndex = { ID : 0, TYPE : 1, NAME : 2, VALUE : 3, ATTRS : 4, HAS_CHILDREN : 5, CHILD_NODES : 6 }; /** * Creates document node in a given document based on a given payload data. * @param {devtools.Doc} doc Document to create node in. * @param {Array.} payload Data to build node based upon. * @constructor */ devtools.DomNode = function(doc, payload) { this.ownerDocument = doc; this.id_ = payload[devtools.PayloadIndex.ID]; this.nodeType = payload[devtools.PayloadIndex.TYPE]; this.nodeName = payload[devtools.PayloadIndex.NAME]; this.nodeValue_ = payload[devtools.PayloadIndex.VALUE]; this.textContent = this.nodeValue; this.attributes = []; this.attributesMap_ = {}; if (payload.length > devtools.PayloadIndex.ATTRS) { this.setAttributesPayload_(payload[devtools.PayloadIndex.ATTRS]); } this.childNodesCount_ = payload[devtools.PayloadIndex.HAS_CHILDREN]; this.children = null; this.nextSibling = null; this.prevSibling = null; this.firstChild = null; this.parentNode = null; this.disabledStyleProperties_ = {}; if (payload.length > devtools.PayloadIndex.CHILD_NODES) { // Has children payloads this.setChildrenPayload_( payload[devtools.PayloadIndex.CHILD_NODES]); } this.computedStyle_ = null; this.style = null; this.matchedCSSRules_ = []; }; /** * Overrides for getters and setters. */ devtools.DomNode.prototype = { get nodeValue() { return this.nodeValue_; }, set nodeValue(value) { if (this.nodeType != Node.TEXT_NODE) { return; } var self = this; this.ownerDocument.domAgent_.setTextNodeValueAsync(this, value, function() { self.nodeValue_ = value; self.textContent = value; }); } }; /** * Sets attributes for a given node based on a given attrs payload. * @param {Array.} attrs Attribute key-value pairs to set. * @private */ devtools.DomNode.prototype.setAttributesPayload_ = function(attrs) { for (var i = 0; i < attrs.length; i += 2) { this.addAttribute_(attrs[i], attrs[i + 1]); } }; /** * @return True iff node has attributes. */ devtools.DomNode.prototype.hasAttributes = function() { return this.attributes.length > 0; }; /** * @return True iff node has child nodes. */ devtools.DomNode.prototype.hasChildNodes = function() { return this.childNodesCount_ > 0; }; /** * Inserts child node into this node after a given anchor. * @param {devtools.DomNode} prev Node to insert child after. * @param {Array.} payload Child node data. * @private */ devtools.DomNode.prototype.insertChild_ = function(prev, payload) { var node = new devtools.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; }; /** * Removes child node from this node. * @param {devtools.DomNode} node Node to remove. * @private */ devtools.DomNode.prototype.removeChild_ = function(node) { this.children.splice(this.children.indexOf(node), 1); node.parentNode = null; this.renumber_(); }; /** * Sets children for this node based on the given payload. * @param {Array.} payload Data for children. * @private */ devtools.DomNode.prototype.setChildrenPayload_ = function(payloads) { this.children = []; for (var i = 0; i < payloads.length; ++i) { var payload = payloads[i]; var node = new devtools.DomNode(this.ownerDocument, payload); this.children.push(node); } this.renumber_(); }; /** * Normalizes prev/next/parent/firstChild links for this node's children. * @private */ devtools.DomNode.prototype.renumber_ = function() { this.childNodesCount_ = this.children.length; if (this.childNodesCount_ == 0) { this.firstChild = null; return; } this.firstChild = this.children[0]; for (var i = 0; i < this.childNodesCount_; ++i) { var child = this.children[i]; child.nextSibling = i + 1 < this.childNodesCount_ ? this.children[i + 1] : null; child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null; child.parentNode = this; } }; /** * Returns attribute value. * @param {string} name Attribute name to get value for. * @return {string} Attribute value. */ devtools.DomNode.prototype.getAttribute = function(name) { var attr = this.attributesMap_[name]; return attr ? attr.value : undefined; }; /** * Sends 'set attribute' command to the remote agent. * @param {string} name Attribute name to set value for. * @param {string} value Attribute value to set. */ devtools.DomNode.prototype.setAttribute = function(name, value) { var self = this; this.ownerDocument.domAgent_.setAttributeAsync(this, name, value, function() { var attr = self.attributesMap_[name]; if (attr) { attr.value = value; } else { attr = self.addAttribute_(name, value); } }); }; /** * Creates an attribute-like object and adds it to the object. * @param {string} name Attribute name to set value for. * @param {string} value Attribute value to set. */ devtools.DomNode.prototype.addAttribute_ = function(name, value) { var attr = { 'name': name, 'value': value, node_: this }; this.attributesMap_[name] = attr; this.attributes.push(attr); }; /** * Sends 'remove attribute' command to the remote agent. * @param {string} name Attribute name to set value for. */ devtools.DomNode.prototype.removeAttribute = function(name) { var self = this; this.ownerDocument.domAgent_.removeAttributeAsync(this, name, 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; } } }); }; /** * Makes available the following methods and properties: * - node.style property * - node.document.defaultView.getComputedStyles(node) * - node.document.defaultView.getMatchedCSSRules(node, ...) * - style attribute of node's attributes * @param {string} computedStyle is a cssText of result of getComputedStyle(). * @param {string} inlineStyle is a style.cssText (defined in the STYLE * attribute). * @param {Object} styleAttributes represents 'style' property * of attributes. * @param {Array.} matchedCSSRules represents result of the * getMatchedCSSRules(node, '', authorOnly). Each elemet consists of: * selector, rule.style.cssText[, rule.parentStyleSheet.href * [, rule.parentStyleSheet.ownerNode.nodeName]]. */ devtools.DomNode.prototype.setStyles = function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules) { this.computedStyle_ = this.makeStyle_(computedStyle); this.style = this.makeStyle_(inlineStyle); for (var name in styleAttributes) { if (this.attributesMap_[name]) { this.attributesMap_[name].style = this.makeStyle_(styleAttributes[name]); } } this.matchedCSSRules_ = []; for (var i = 0; i < matchedCSSRules.length; i++) { var descr = matchedCSSRules[i]; var rule = {}; rule.selectorText = descr['selector']; rule.style = this.makeStyle_(descr['style']); if (descr['parentStyleSheet']) { var parentStyleMock = {}; parentStyleMock.href = descr['parentStyleSheet']['href']; var nodeName = descr['parentStyleSheet']['ownerNodeName']; if (nodeName) { parentStyleMock.ownerNode = { 'nodeName': nodeName }; } rule.parentStyleSheet = parentStyleMock; } this.matchedCSSRules_.push(rule); } }; /** * Creates a style declaration. * @param {payload} payload * @return {devtools.CSSStyleDeclaration:undefined} * @see devtools.CSSStyleDeclaration */ devtools.DomNode.prototype.makeStyle_ = function(payload) { var style = new devtools.CSSStyleDeclaration(payload); style.nodeId_ = this.id_; return style; }; /** * Remove references to the style information to release * resources when styles are not going to be used. * @see setStyles. */ devtools.DomNode.prototype.clearStyles = function() { this.computedStyle = null; this.style = null; for (var name in this.attributesMap_) { this.attributesMap_[name].style = null; } this.matchedCSSRules_ = null; }; /** * Remote Dom document abstraction. * @param {devtools.DomAgent} domAgent owner agent. * @param {devtools.DomWindow} defaultView owner window. * @constructor. */ devtools.DomDocument = function(domAgent, defaultView) { devtools.DomNode.call(this, null, [ 0, // id 9, // type = Node.DOCUMENT_NODE, '', // nodeName '', // nodeValue [], // attributes 0, // childNodeCount ]); this.listeners_ = {}; this.domAgent_ = domAgent; this.defaultView = defaultView; }; goog.inherits(devtools.DomDocument, devtools.DomNode); /** * Adds event listener to the Dom. * @param {string} name Event name. * @param {function(Event):undefined} callback Listener callback. * @param {bool} useCapture Listener's useCapture settings. */ devtools.DomDocument.prototype.addEventListener = function(name, callback, useCapture) { var listeners = this.listeners_[name]; if (!listeners) { listeners = []; this.listeners_[name] = listeners; } listeners.push(callback); }; /** * Removes event listener from the Dom. * @param {string} name Event name. * @param {function(Event):undefined} callback Listener callback. * @param {bool} useCapture Listener's useCapture settings. */ devtools.DomDocument.prototype.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); } }; /** * Fires Dom event to the listeners for given event type. * @param {string} name Event type. * @param {Event} event Event to fire. * @private */ devtools.DomDocument.prototype.fireDomEvent_ = function(name, event) { var listeners = this.listeners_[name]; if (!listeners) { return; } for (var i = 0; i < listeners.length; ++i) { listeners[i](event); } }; /** * Simulation of inspected DOMWindow. * @param {devtools.DomAgent} domAgent owner agent. * @constructor */ devtools.DomWindow = function(domAgent) { this.document = new devtools.DomDocument(domAgent, this); }; /** * Represents DOM Node class. */ devtools.DomWindow.prototype.__defineGetter__('Node', function() { return devtools.DomNode; }); /** * Represents DOM Element class. * @constructor */ devtools.DomWindow.prototype.__defineGetter__('Element', function() { return devtools.DomNode; }); /** * See usages in ScopeChainSidebarPane.js where it's called as * constructor. */ devtools.DomWindow.prototype.Object = function() { }; /** * Simulates the DOM interface for styles. * @param {devtools.DomNode} node * @return {CSSStyleDescription} */ devtools.DomWindow.prototype.getComputedStyle = function(node) { return node.computedStyle_; }; /** * Simulates the DOM interface for styles. * @param {devtools.DomNode} nodeStyles * @param {string} pseudoElement assumed to be empty string. * @param {boolean} authorOnly assumed to be equal to authorOnly argument of * getNodeStylesAsync. * @return {CSSStyleDescription} */ devtools.DomWindow.prototype.getMatchedCSSRules = function(node, pseudoElement, authorOnly) { return node.matchedCSSRules_; }; /** * Creates DomAgent Js representation. * @constructor */ devtools.DomAgent = function() { RemoteDomAgent.DidGetChildNodes = devtools.Callback.processCallback; RemoteDomAgent.DidPerformSearch = devtools.Callback.processCallback; RemoteDomAgent.DidApplyDomChange = devtools.Callback.processCallback; RemoteDomAgent.DidRemoveAttribute = devtools.Callback.processCallback; RemoteDomAgent.DidSetTextNodeValue = devtools.Callback.processCallback; RemoteDomAgent.AttributesUpdated = goog.bind(this.attributesUpdated_, this); RemoteDomAgent.SetDocumentElement = goog.bind(this.setDocumentElement_, this); RemoteDomAgent.SetChildNodes = goog.bind(this.setChildNodes_, this); RemoteDomAgent.HasChildrenUpdated = goog.bind(this.hasChildrenUpdated_, this); RemoteDomAgent.ChildNodeInserted = goog.bind(this.childNodeInserted_, this); RemoteDomAgent.ChildNodeRemoved = goog.bind(this.childNodeRemoved_, this); /** * Top-level (and the only) document. * @type {devtools.DomWindow} * @private */ this.window_ = null; /** * Id to node mapping. * @type {Object} * @private */ this.idToDomNode_ = null; /** * @type {Array.} Node ids for search results. * @private */ this.searchResults_ = null; }; /** * Resets dom agent to its initial state. */ devtools.DomAgent.prototype.reset = function() { this.window_ = new devtools.DomWindow(this); this.idToDomNode_ = { 0 : this.getDocument() }; this.searchResults_ = []; }; /** * @return {devtools.DomWindow} Window for the top level (and the only) document. */ devtools.DomAgent.prototype.getWindow = function() { return this.window_; }; /** * @return {devtools.DomDocument} A document of the top level window. */ devtools.DomAgent.prototype.getDocument = function() { return this.window_.document; }; /** * Requests that the document element is sent from the agent. */ devtools.DomAgent.prototype.getDocumentElementAsync = function() { if (this.getDocument().documentElement) { return; } RemoteDomAgent.GetDocumentElement(); }; /** * Asynchronously fetches children from the element with given id. * @param {devtools.DomNode} parent Element to get children for. * @param {function(devtools.DomNode):undefined} opt_callback Callback with * the result. */ devtools.DomAgent.prototype.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 = devtools.Callback.wrap(mycallback); RemoteDomAgent.GetChildNodes(callId, parent.id_); }; /** * Sends 'set attribute' command to the remote agent. * @param {devtools.DomNode} node Node to change. * @param {string} name Attribute name to set value for. * @param {string} value Attribute value to set. * @param {function():undefined} opt_callback Callback on success. */ devtools.DomAgent.prototype.setAttributeAsync = function(node, name, value, callback) { var mycallback = goog.bind(this.didApplyDomChange_, this, node, callback); RemoteDomAgent.SetAttribute(devtools.Callback.wrap(mycallback), node.id_, name, value); }; /** * Sends 'remove attribute' command to the remote agent. * @param {devtools.DomNode} node Node to change. * @param {string} name Attribute name to set value for. * @param {function():undefined} opt_callback Callback on success. */ devtools.DomAgent.prototype.removeAttributeAsync = function(node, name, callback) { var mycallback = goog.bind(this.didApplyDomChange_, this, node, callback); RemoteDomAgent.RemoveAttribute(devtools.Callback.wrap(mycallback), node.id_, name); }; /** * Sends 'set text node value' command to the remote agent. * @param {devtools.DomNode} node Node to change. * @param {string} text Text value to set. * @param {function():undefined} opt_callback Callback on success. */ devtools.DomAgent.prototype.setTextNodeValueAsync = function(node, text, callback) { var mycallback = goog.bind(this.didApplyDomChange_, this, node, callback); RemoteDomAgent.SetTextNodeValue(devtools.Callback.wrap(mycallback), node.id_, text); }; /** * Universal callback wrapper for edit dom operations. * @param {devtools.DomNode} node Node to apply local changes on. * @param {Function} callback Post-operation call. * @param {boolean} success True iff operation has completed successfully. * @private */ devtools.DomAgent.prototype.didApplyDomChange_ = function(node, callback, success) { if (!success) { return; } callback(); var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node); if (elem) { elem._updateTitle(); } }; /** * @see DomAgentDelegate. * {@inheritDoc}. * @private */ devtools.DomAgent.prototype.attributesUpdated_ = function(nodeId, attrsArray) { var node = this.idToDomNode_[nodeId]; node.setAttributesPayload_(attrsArray); }; /** * Returns node for id. * @param {number} nodeId Id to get node for. * @return {devtools.DomNode} Node with given id. */ devtools.DomAgent.prototype.getNodeForId = function(nodeId) { return this.idToDomNode_[nodeId]; }; /** * @see DomAgentDelegate. * {@inheritDoc}. * @private */ devtools.DomAgent.prototype.setDocumentElement_ = function(payload) { var doc = this.getDocument(); if (doc.documentElement) { this.reset(); doc = this.getDocument(); } this.setChildNodes_(0, [payload]); doc.documentElement = doc.firstChild; doc.documentElement.ownerDocument = doc; WebInspector.panels.elements.reset(); }; /** * @see DomAgentDelegate. * {@inheritDoc}. * @private */ devtools.DomAgent.prototype.setChildNodes_ = function(parentId, payloads) { var parent = this.idToDomNode_[parentId]; if (parent.children) { return; } parent.setChildrenPayload_(payloads); this.bindNodes_(parent.children); }; /** * Binds nodes to ids recursively. * @param {Array.} children Nodes to bind. * @private */ devtools.DomAgent.prototype.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); } } }; /** * @see DomAgentDelegate. * {@inheritDoc}. * @private */ devtools.DomAgent.prototype.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; } }; /** * @see DomAgentDelegate. * {@inheritDoc}. * @private */ devtools.DomAgent.prototype.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.getDocument().fireDomEvent_('DOMNodeInserted', event); }; /** * @see DomAgentDelegate. * {@inheritDoc}. * @private */ devtools.DomAgent.prototype.childNodeRemoved_ = function( parentId, nodeId) { var parent = this.idToDomNode_[parentId]; var node = this.idToDomNode_[nodeId]; parent.removeChild_(node); var event = { target : node, relatedNode : parent }; this.getDocument().fireDomEvent_('DOMNodeRemoved', event); delete this.idToDomNode_[nodeId]; }; /** * @see DomAgentDelegate. * {@inheritDoc}. */ devtools.DomAgent.prototype.performSearch = function(query, callback) { this.searchResults_ = []; RemoteDomAgent.PerformSearch( devtools.Callback.wrap( goog.bind(this.performSearchCallback_, this, callback, this.searchResults_)), query); }; /** * Invokes callback for nodes that needs to clear highlighting. * @param {function(Array.)} callback to accept the result. */ devtools.DomAgent.prototype.searchCanceled = function(callback) { if (!this.searchResults_) return; var nodes = []; for (var i = 0; i < this.searchResults_.length; ++i) { var nodeId = this.searchResults_[i]; var node = this.idToDomNode_[nodeId]; nodes.push(node); } callback(nodes); this.searchResults_ = null; }; /** * Invokes callback for each node that needs to gain highlighting. * @param {function(Array.)} callback to accept the result. * @param {Array.} searchResults to be populated. * @param {Array.} nodeIds Ids to highlight. * @private */ devtools.DomAgent.prototype.performSearchCallback_ = function(callback, searchResults, nodeIds) { if (this.searchResults_ !== searchResults) return; // another search has requested and this results are obsolete var nodes = []; for (var i = 0; i < nodeIds.length; ++i) { var node = this.idToDomNode_[nodeIds[i]]; searchResults.push(nodeIds[i]); nodes.push(node); } callback(nodes); }; /** * Returns a node by index from the actual search results * (last performSearch). * @param {number} index in the results. * @return {devtools.DomNode} */ devtools.DomAgent.prototype.getSearchResultNode = function(index) { return this.idToDomNode_[this.searchResults_[index]]; }; /** * Returns all properties of the given node. * @param {number} nodeId Node to get properties for. * @param {Array.} path Path to the object. * @param {number} protoDepth Depth to the exact proto level. * @param {function(string):undefined} callback Function to call with the * result. */ devtools.DomAgent.prototype.getNodePropertiesAsync = function(nodeId, path, protoDepth, callback) { var callbackId = this.utilityFunctionCallbackWrapper_(callback); RemoteToolsAgent.ExecuteUtilityFunction(callbackId, 'getProperties', JSON.stringify([nodeId, path, protoDepth])); }; /** * Returns prototype chain for a given node. * @param {number} nodeId Node to get prototypes for. * @param {Function} callback. */ devtools.DomAgent.prototype.getNodePrototypesAsync = function(nodeId, callback) { var callbackId = this.utilityFunctionCallbackWrapper_(callback); RemoteToolsAgent.ExecuteUtilityFunction(callbackId, 'getPrototypes', JSON.stringify([nodeId])); }; /** * Returns styles for given node. * @param {devtools.DomNode} node Node to get prototypes for. * @param {boolean} authorOnly Returns only author styles if true. * @param {Function} callback. */ devtools.DomAgent.prototype.getNodeStylesAsync = function(node, authorOnly, callback) { var callbackId = this.utilityFunctionCallbackWrapper_(callback); RemoteToolsAgent.ExecuteUtilityFunction(callbackId, 'getStyles', JSON.stringify([node.id_, authorOnly])); }; /** * Toggles style with given id on/off. * @param {devtools.CSSStyleDeclaration} style Style to toggle. * @param {boolean} enabled True if style should be enabled. * @param {string} name Style name. * @param {Function} callback. */ devtools.DomAgent.prototype.toggleNodeStyleAsync = function( style, enabled, name, callback) { var callbackId = this.utilityFunctionCallbackWrapper_(callback); RemoteToolsAgent.ExecuteUtilityFunction(callbackId, 'toggleNodeStyle', JSON.stringify([style.nodeId_, style.id_, enabled, name])); }; /** * Applies new text to a style. * @param {devtools.CSSStyleDeclaration} style Style to edit. * @param {string} name Property name to edit. * @param {string} styleText Text to set the style from. * @param {Function} callback. */ devtools.DomAgent.prototype.applyStyleTextAsync = function( style, name, styleText, callback) { var callbackId = this.utilityFunctionCallbackWrapper_(callback); RemoteToolsAgent.ExecuteUtilityFunction( callbackId, 'applyStyleText', JSON.stringify([style.nodeId_, style.id_, name, styleText])); }; /** * Sets style property with given name to a value. * @param {devtools.DomNode} node Node to edit style for. * @param {string} name Property name to edit. * @param {string} value New value. * @param {Function} callback. */ devtools.DomAgent.prototype.setStylePropertyAsync = function( node, name, value, callback) { var callbackId = this.utilityFunctionCallbackWrapper_(callback); RemoteToolsAgent.ExecuteUtilityFunction( callbackId, 'setStyleProperty', JSON.stringify([node.id_, name, value])); }; /** * Dumps exception if something went wrong in ExecuteUtilityFunction. * @param {Function} callback Callback to wrap. * @return {number} Callback id. */ devtools.DomAgent.prototype.utilityFunctionCallbackWrapper_ = function(callback) { var mycallback = function(result, exception) { if (exception && exception.length) { debugPrint('Exception in ExecuteUtilityFunction styles:' + exception); return; } callback(result); }; return devtools.Callback.wrap(mycallback); }; /** * Represents remote CSSStyleDeclaration for using in StyleSidebarPane. * @param {id, Array} payload built by inject's getStyle from the * injected js. * @constructor */ devtools.CSSStyleDeclaration = function(payload) { this.id_ = payload[0]; this.width = payload[1]; this.height = payload[2]; this.__disabledProperties = payload[3]; this.__disabledPropertyValues = payload[4]; this.__disabledPropertyPriorities = payload[5]; this.length = payload.length - 6; this.priority_ = {}; this.implicit_ = {}; this.shorthand_ = {}; this.value_ = {}; for (var i = 6; i < payload.length; ++i) { var p = payload[i]; var name = p[0]; this.priority_[name] = p[1]; this.implicit_[name] = p[2]; this.shorthand_[name] = p[3]; this.value_[name] = p[4]; this[i - 6] = name; } }; /** * @param {string} name of a CSS property. * @return {string} */ devtools.CSSStyleDeclaration.prototype.getPropertyValue = function(name) { return this.value_[name] || ''; }; /** * @param {string} name of a CSS property. * @return {string} 'important' | ''. */ devtools.CSSStyleDeclaration.prototype.getPropertyPriority = function(name) { return this.priority_[name] || ''; }; /** * @param {string} name of a CSS property. * @return {string} shorthand name or '' */ devtools.CSSStyleDeclaration.prototype.getPropertyShorthand = function(name) { return this.shorthand_[name] || ''; }; /** * @param {string} name of a CSS property. * @return {boolean} */ devtools.CSSStyleDeclaration.prototype.isPropertyImplicit = function(name) { return !!this.implicit_[name]; }; function firstChildSkippingWhitespace() { return this.firstChild; } function onlyTextChild() { if (!this.children) { return null; } else if (this.children.length == 1 && this.children[0].nodeType == Node.TEXT_NODE) { return this.children[0]; } else { return null; } }