/* * Copyright (C) 2007 Apple Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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. */ var InjectedScript = { _styles: {}, _styleRules: {}, _lastStyleId: 0, _lastStyleRuleId: 0 }; InjectedScript.getStyles = function(nodeId, authorOnly) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; var matchedRules = InjectedScript._window().getMatchedCSSRules(node, "", authorOnly); var matchedCSSRules = []; for (var i = 0; matchedRules && i < matchedRules.length; ++i) matchedCSSRules.push(InjectedScript._serializeRule(matchedRules[i])); var styleAttributes = {}; var attributes = node.attributes; for (var i = 0; attributes && i < attributes.length; ++i) { if (attributes[i].style) styleAttributes[attributes[i].name] = InjectedScript._serializeStyle(attributes[i].style, true); } var result = {}; result.inlineStyle = InjectedScript._serializeStyle(node.style, true); result.computedStyle = InjectedScript._serializeStyle(InjectedScript._window().getComputedStyle(node)); result.matchedCSSRules = matchedCSSRules; result.styleAttributes = styleAttributes; return result; } InjectedScript.getComputedStyle = function(nodeId) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; return InjectedScript._serializeStyle(InjectedScript._window().getComputedStyle(node)); } InjectedScript.getInlineStyle = function(nodeId) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; return InjectedScript._serializeStyle(node.style, true); } InjectedScript.applyStyleText = function(styleId, styleText, propertyName) { var style = InjectedScript._styles[styleId]; if (!style) return false; var styleTextLength = styleText.length; // Create a new element to parse the user input CSS. var parseElement = document.createElement("span"); parseElement.setAttribute("style", styleText); var tempStyle = parseElement.style; if (tempStyle.length || !styleTextLength) { // The input was parsable or the user deleted everything, so remove the // original property from the real style declaration. If this represents // a shorthand remove all the longhand properties. if (style.getPropertyShorthand(propertyName)) { var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName); for (var i = 0; i < longhandProperties.length; ++i) style.removeProperty(longhandProperties[i]); } else style.removeProperty(propertyName); } if (!tempStyle.length) return false; // Iterate of the properties on the test element's style declaration and // add them to the real style declaration. We take care to move shorthands. var foundShorthands = {}; var changedProperties = []; var uniqueProperties = InjectedScript._getUniqueStyleProperties(tempStyle); for (var i = 0; i < uniqueProperties.length; ++i) { var name = uniqueProperties[i]; var shorthand = tempStyle.getPropertyShorthand(name); if (shorthand && shorthand in foundShorthands) continue; if (shorthand) { var value = InjectedScript._getShorthandValue(tempStyle, shorthand); var priority = InjectedScript._getShorthandPriority(tempStyle, shorthand); foundShorthands[shorthand] = true; } else { var value = tempStyle.getPropertyValue(name); var priority = tempStyle.getPropertyPriority(name); } // Set the property on the real style declaration. style.setProperty((shorthand || name), value, priority); changedProperties.push(shorthand || name); } return [InjectedScript._serializeStyle(style, true), changedProperties]; } InjectedScript.setStyleText = function(style, cssText) { style.cssText = cssText; } InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled) { var style = InjectedScript._styles[styleId]; if (!style) return false; if (disabled) { if (!style.__disabledPropertyValues || !style.__disabledPropertyPriorities) { var inspectedWindow = InjectedScript._window(); style.__disabledProperties = new inspectedWindow.Object; style.__disabledPropertyValues = new inspectedWindow.Object; style.__disabledPropertyPriorities = new inspectedWindow.Object; } style.__disabledPropertyValues[propertyName] = style.getPropertyValue(propertyName); style.__disabledPropertyPriorities[propertyName] = style.getPropertyPriority(propertyName); if (style.getPropertyShorthand(propertyName)) { var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName); for (var i = 0; i < longhandProperties.length; ++i) { style.__disabledProperties[longhandProperties[i]] = true; style.removeProperty(longhandProperties[i]); } } else { style.__disabledProperties[propertyName] = true; style.removeProperty(propertyName); } } else if (style.__disabledProperties && style.__disabledProperties[propertyName]) { var value = style.__disabledPropertyValues[propertyName]; var priority = style.__disabledPropertyPriorities[propertyName]; style.setProperty(propertyName, value, priority); delete style.__disabledProperties[propertyName]; delete style.__disabledPropertyValues[propertyName]; delete style.__disabledPropertyPriorities[propertyName]; } return InjectedScript._serializeStyle(style, true); } InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNode) { var rule = InjectedScript._styleRules[ruleId]; if (!rule) return false; try { var stylesheet = rule.parentStyleSheet; stylesheet.addRule(newContent); var newRule = stylesheet.cssRules[stylesheet.cssRules.length - 1]; newRule.style.cssText = rule.style.cssText; var parentRules = stylesheet.cssRules; for (var i = 0; i < parentRules.length; ++i) { if (parentRules[i] === rule) { rule.parentStyleSheet.removeRule(i); break; } } var nodes = selectedNode.ownerDocument.querySelectorAll(newContent); for (var i = 0; i < nodes.length; ++i) { if (nodes[i] === selectedNode) { return [InjectedScript._serializeRule(newRule), true]; } } return [InjectedScript._serializeRule(newRule), false]; } catch(e) { // Report invalid syntax. return false; } } InjectedScript.addStyleSelector = function(newContent) { var stylesheet = InjectedScript.stylesheet; if (!stylesheet) { var inspectedDocument = InjectedScript._window().document; var head = inspectedDocument.getElementsByTagName("head")[0]; var styleElement = inspectedDocument.createElement("style"); styleElement.type = "text/css"; head.appendChild(styleElement); stylesheet = inspectedDocument.styleSheets[inspectedDocument.styleSheets.length - 1]; InjectedScript.stylesheet = stylesheet; } try { stylesheet.addRule(newContent); } catch (e) { // Invalid Syntax for a Selector return false; } return InjectedScript._serializeRule(stylesheet.cssRules[stylesheet.cssRules.length - 1]); } InjectedScript.setStyleProperty = function(styleId, name, value) { var style = InjectedScript._styles[styleId]; if (!style) return false; style.setProperty(name, value, ""); return true; } InjectedScript._serializeRule = function(rule) { var parentStyleSheet = rule.parentStyleSheet; var ruleValue = {}; ruleValue.selectorText = rule.selectorText; if (parentStyleSheet) { ruleValue.parentStyleSheet = {}; ruleValue.parentStyleSheet.href = parentStyleSheet.href; } ruleValue.isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && !parentStyleSheet.href; ruleValue.isUser = parentStyleSheet && parentStyleSheet.ownerNode && parentStyleSheet.ownerNode.nodeName == "#document"; // Bind editable scripts only. var doBind = !ruleValue.isUserAgent && !ruleValue.isUser; ruleValue.style = InjectedScript._serializeStyle(rule.style, doBind); if (doBind) { if (!rule._id) { rule._id = InjectedScript._lastStyleRuleId++; InjectedScript._styleRules[rule._id] = rule; } ruleValue.id = rule._id; } return ruleValue; } InjectedScript._serializeStyle = function(style, doBind) { var result = {}; result.width = style.width; result.height = style.height; result.__disabledProperties = style.__disabledProperties; result.__disabledPropertyValues = style.__disabledPropertyValues; result.__disabledPropertyPriorities = style.__disabledPropertyPriorities; result.properties = []; result.shorthandValues = {}; var foundShorthands = {}; for (var i = 0; i < style.length; ++i) { var property = {}; var name = style[i]; property.name = name; property.priority = style.getPropertyPriority(name); property.implicit = style.isPropertyImplicit(name); var shorthand = style.getPropertyShorthand(name); property.shorthand = shorthand; if (shorthand && !(shorthand in foundShorthands)) { foundShorthands[shorthand] = true; result.shorthandValues[shorthand] = InjectedScript._getShorthandValue(style, shorthand); } property.value = style.getPropertyValue(name); result.properties.push(property); } result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style); if (doBind) { if (!style._id) { style._id = InjectedScript._lastStyleId++; InjectedScript._styles[style._id] = style; } result.id = style._id; } return result; } InjectedScript._getUniqueStyleProperties = function(style) { var properties = []; var foundProperties = {}; for (var i = 0; i < style.length; ++i) { var property = style[i]; if (property in foundProperties) continue; foundProperties[property] = true; properties.push(property); } return properties; } InjectedScript._getLonghandProperties = function(style, shorthandProperty) { var properties = []; var foundProperties = {}; for (var i = 0; i < style.length; ++i) { var individualProperty = style[i]; if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty) continue; foundProperties[individualProperty] = true; properties.push(individualProperty); } return properties; } InjectedScript._getShorthandValue = function(style, shorthandProperty) { var value = style.getPropertyValue(shorthandProperty); if (!value) { // Some shorthands (like border) return a null value, so compute a shorthand value. // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed. var foundProperties = {}; for (var i = 0; i < style.length; ++i) { var individualProperty = style[i]; if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty) continue; var individualValue = style.getPropertyValue(individualProperty); if (style.isPropertyImplicit(individualProperty) || individualValue === "initial") continue; foundProperties[individualProperty] = true; if (!value) value = ""; else if (value.length) value += " "; value += individualValue; } } return value; } InjectedScript._getShorthandPriority = function(style, shorthandProperty) { var priority = style.getPropertyPriority(shorthandProperty); if (!priority) { for (var i = 0; i < style.length; ++i) { var individualProperty = style[i]; if (style.getPropertyShorthand(individualProperty) !== shorthandProperty) continue; priority = style.getPropertyPriority(individualProperty); break; } } return priority; } InjectedScript.getPrototypes = function(nodeId) { var node = InjectedScript._nodeForId(nodeId); if (!node) return false; var result = []; for (var prototype = node; prototype; prototype = prototype.__proto__) { var title = Object.describe(prototype); if (title.match(/Prototype$/)) { title = title.replace(/Prototype$/, ""); } result.push(title); } return result; } InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty) { var object = InjectedScript._resolveObject(objectProxy); if (!object) return false; var properties = []; // Go over properties, prepare results. for (var propertyName in object) { if (!ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName)) continue; //TODO: remove this once object becomes really remote. if (propertyName === "__treeElementIdentifier") continue; var property = {}; property.name = propertyName; var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName); if (!property.isGetter) { var childObject = object[propertyName]; property.type = typeof childObject; property.textContent = Object.describe(childObject, true); property.parentObjectProxy = objectProxy; var parentPath = objectProxy.path.slice(); property.childObjectProxy = { objectId : objectProxy.objectId, path : parentPath.splice(parentPath.length, 0, propertyName), protoDepth : objectProxy.protoDepth }; if (childObject && (property.type === "object" || property.type === "function")) { for (var subPropertyName in childObject) { if (propertyName === "__treeElementIdentifier") continue; property.hasChildren = true; break; } } } else { // FIXME: this should show something like "getter" (bug 16734). property.textContent = "\u2014"; // em dash property.isGetter = true; } properties.push(property); } return properties; } InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression) { var object = InjectedScript._resolveObject(objectProxy); if (!object) return false; var expressionLength = expression.length; if (!expressionLength) { delete object[propertyName]; return !(propertyName in object); } try { // Surround the expression in parenthesis so the result of the eval is the result // of the whole expression not the last potential sub-expression. // There is a regression introduced here: eval is now happening against global object, // not call frame while on a breakpoint. // TODO: bring evaluation against call frame back. var result = InjectedScript._window().eval("(" + expression + ")"); // Store the result in the property. object[propertyName] = result; return true; } catch(e) { try { var result = InjectedScript._window().eval("\"" + expression.escapeCharacters("\"") + "\""); object[propertyName] = result; return true; } catch(e) { return false; } } } InjectedScript._resolveObject = function(objectProxy) { var object = InjectedScript._objectForId(objectProxy.objectId); var path = objectProxy.path; var protoDepth = objectProxy.protoDepth; // Follow the property path. for (var i = 0; object && i < path.length; ++i) object = object[path[i]]; // Get to the necessary proto layer. for (var i = 0; object && i < protoDepth; ++i) object = object.__proto__; return object; } InjectedScript._window = function() { // TODO: replace with 'return window;' once this script is injected into // the page's context. return InspectorController.inspectedWindow(); } InjectedScript._nodeForId = function(nodeId) { // TODO: replace with node lookup in the InspectorDOMAgent once DOMAgent nodes are used. return nodeId; } InjectedScript._objectForId = function(objectId) { // TODO: replace with node lookups for node ids and evaluation result lookups for the rest of ids. return objectId; }