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