1/* 2 * Copyright (C) 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2009 Joseph Pecoraro 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30var InjectedScript = { 31 _styles: {}, 32 _styleRules: {}, 33 _lastStyleId: 0, 34 _lastStyleRuleId: 0 35}; 36 37InjectedScript.getStyles = function(nodeId, authorOnly) 38{ 39 var node = InjectedScript._nodeForId(nodeId); 40 if (!node) 41 return false; 42 var matchedRules = InjectedScript._window().getMatchedCSSRules(node, "", authorOnly); 43 var matchedCSSRules = []; 44 for (var i = 0; matchedRules && i < matchedRules.length; ++i) 45 matchedCSSRules.push(InjectedScript._serializeRule(matchedRules[i])); 46 47 var styleAttributes = {}; 48 var attributes = node.attributes; 49 for (var i = 0; attributes && i < attributes.length; ++i) { 50 if (attributes[i].style) 51 styleAttributes[attributes[i].name] = InjectedScript._serializeStyle(attributes[i].style, true); 52 } 53 var result = {}; 54 result.inlineStyle = InjectedScript._serializeStyle(node.style, true); 55 result.computedStyle = InjectedScript._serializeStyle(InjectedScript._window().getComputedStyle(node)); 56 result.matchedCSSRules = matchedCSSRules; 57 result.styleAttributes = styleAttributes; 58 return result; 59} 60 61InjectedScript.getComputedStyle = function(nodeId) 62{ 63 var node = InjectedScript._nodeForId(nodeId); 64 if (!node) 65 return false; 66 return InjectedScript._serializeStyle(InjectedScript._window().getComputedStyle(node)); 67} 68 69InjectedScript.getInlineStyle = function(nodeId) 70{ 71 var node = InjectedScript._nodeForId(nodeId); 72 if (!node) 73 return false; 74 return InjectedScript._serializeStyle(node.style, true); 75} 76 77InjectedScript.applyStyleText = function(styleId, styleText, propertyName) 78{ 79 var style = InjectedScript._styles[styleId]; 80 if (!style) 81 return false; 82 83 var styleTextLength = styleText.length; 84 85 // Create a new element to parse the user input CSS. 86 var parseElement = document.createElement("span"); 87 parseElement.setAttribute("style", styleText); 88 89 var tempStyle = parseElement.style; 90 if (tempStyle.length || !styleTextLength) { 91 // The input was parsable or the user deleted everything, so remove the 92 // original property from the real style declaration. If this represents 93 // a shorthand remove all the longhand properties. 94 if (style.getPropertyShorthand(propertyName)) { 95 var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName); 96 for (var i = 0; i < longhandProperties.length; ++i) 97 style.removeProperty(longhandProperties[i]); 98 } else 99 style.removeProperty(propertyName); 100 } 101 102 if (!tempStyle.length) 103 return false; 104 105 // Iterate of the properties on the test element's style declaration and 106 // add them to the real style declaration. We take care to move shorthands. 107 var foundShorthands = {}; 108 var changedProperties = []; 109 var uniqueProperties = InjectedScript._getUniqueStyleProperties(tempStyle); 110 for (var i = 0; i < uniqueProperties.length; ++i) { 111 var name = uniqueProperties[i]; 112 var shorthand = tempStyle.getPropertyShorthand(name); 113 114 if (shorthand && shorthand in foundShorthands) 115 continue; 116 117 if (shorthand) { 118 var value = InjectedScript._getShorthandValue(tempStyle, shorthand); 119 var priority = InjectedScript._getShorthandPriority(tempStyle, shorthand); 120 foundShorthands[shorthand] = true; 121 } else { 122 var value = tempStyle.getPropertyValue(name); 123 var priority = tempStyle.getPropertyPriority(name); 124 } 125 126 // Set the property on the real style declaration. 127 style.setProperty((shorthand || name), value, priority); 128 changedProperties.push(shorthand || name); 129 } 130 return [InjectedScript._serializeStyle(style, true), changedProperties]; 131} 132 133InjectedScript.setStyleText = function(style, cssText) 134{ 135 style.cssText = cssText; 136} 137 138InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled) 139{ 140 var style = InjectedScript._styles[styleId]; 141 if (!style) 142 return false; 143 144 if (disabled) { 145 if (!style.__disabledPropertyValues || !style.__disabledPropertyPriorities) { 146 var inspectedWindow = InjectedScript._window(); 147 style.__disabledProperties = new inspectedWindow.Object; 148 style.__disabledPropertyValues = new inspectedWindow.Object; 149 style.__disabledPropertyPriorities = new inspectedWindow.Object; 150 } 151 152 style.__disabledPropertyValues[propertyName] = style.getPropertyValue(propertyName); 153 style.__disabledPropertyPriorities[propertyName] = style.getPropertyPriority(propertyName); 154 155 if (style.getPropertyShorthand(propertyName)) { 156 var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName); 157 for (var i = 0; i < longhandProperties.length; ++i) { 158 style.__disabledProperties[longhandProperties[i]] = true; 159 style.removeProperty(longhandProperties[i]); 160 } 161 } else { 162 style.__disabledProperties[propertyName] = true; 163 style.removeProperty(propertyName); 164 } 165 } else if (style.__disabledProperties && style.__disabledProperties[propertyName]) { 166 var value = style.__disabledPropertyValues[propertyName]; 167 var priority = style.__disabledPropertyPriorities[propertyName]; 168 169 style.setProperty(propertyName, value, priority); 170 delete style.__disabledProperties[propertyName]; 171 delete style.__disabledPropertyValues[propertyName]; 172 delete style.__disabledPropertyPriorities[propertyName]; 173 } 174 return InjectedScript._serializeStyle(style, true); 175} 176 177InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNode) 178{ 179 var rule = InjectedScript._styleRules[ruleId]; 180 if (!rule) 181 return false; 182 183 try { 184 var stylesheet = rule.parentStyleSheet; 185 stylesheet.addRule(newContent); 186 var newRule = stylesheet.cssRules[stylesheet.cssRules.length - 1]; 187 newRule.style.cssText = rule.style.cssText; 188 189 var parentRules = stylesheet.cssRules; 190 for (var i = 0; i < parentRules.length; ++i) { 191 if (parentRules[i] === rule) { 192 rule.parentStyleSheet.removeRule(i); 193 break; 194 } 195 } 196 197 var nodes = selectedNode.ownerDocument.querySelectorAll(newContent); 198 for (var i = 0; i < nodes.length; ++i) { 199 if (nodes[i] === selectedNode) { 200 return [InjectedScript._serializeRule(newRule), true]; 201 } 202 } 203 return [InjectedScript._serializeRule(newRule), false]; 204 } catch(e) { 205 // Report invalid syntax. 206 return false; 207 } 208} 209 210InjectedScript.addStyleSelector = function(newContent) 211{ 212 var stylesheet = InjectedScript.stylesheet; 213 if (!stylesheet) { 214 var inspectedDocument = InjectedScript._window().document; 215 var head = inspectedDocument.getElementsByTagName("head")[0]; 216 var styleElement = inspectedDocument.createElement("style"); 217 styleElement.type = "text/css"; 218 head.appendChild(styleElement); 219 stylesheet = inspectedDocument.styleSheets[inspectedDocument.styleSheets.length - 1]; 220 InjectedScript.stylesheet = stylesheet; 221 } 222 223 try { 224 stylesheet.addRule(newContent); 225 } catch (e) { 226 // Invalid Syntax for a Selector 227 return false; 228 } 229 230 return InjectedScript._serializeRule(stylesheet.cssRules[stylesheet.cssRules.length - 1]); 231} 232 233InjectedScript.setStyleProperty = function(styleId, name, value) { 234 var style = InjectedScript._styles[styleId]; 235 if (!style) 236 return false; 237 238 style.setProperty(name, value, ""); 239 return true; 240} 241 242InjectedScript._serializeRule = function(rule) 243{ 244 var parentStyleSheet = rule.parentStyleSheet; 245 246 var ruleValue = {}; 247 ruleValue.selectorText = rule.selectorText; 248 if (parentStyleSheet) { 249 ruleValue.parentStyleSheet = {}; 250 ruleValue.parentStyleSheet.href = parentStyleSheet.href; 251 } 252 ruleValue.isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && !parentStyleSheet.href; 253 ruleValue.isUser = parentStyleSheet && parentStyleSheet.ownerNode && parentStyleSheet.ownerNode.nodeName == "#document"; 254 255 // Bind editable scripts only. 256 var doBind = !ruleValue.isUserAgent && !ruleValue.isUser; 257 ruleValue.style = InjectedScript._serializeStyle(rule.style, doBind); 258 259 if (doBind) { 260 if (!rule._id) { 261 rule._id = InjectedScript._lastStyleRuleId++; 262 InjectedScript._styleRules[rule._id] = rule; 263 } 264 ruleValue.id = rule._id; 265 } 266 return ruleValue; 267} 268 269InjectedScript._serializeStyle = function(style, doBind) 270{ 271 var result = {}; 272 result.width = style.width; 273 result.height = style.height; 274 result.__disabledProperties = style.__disabledProperties; 275 result.__disabledPropertyValues = style.__disabledPropertyValues; 276 result.__disabledPropertyPriorities = style.__disabledPropertyPriorities; 277 result.properties = []; 278 result.shorthandValues = {}; 279 var foundShorthands = {}; 280 for (var i = 0; i < style.length; ++i) { 281 var property = {}; 282 var name = style[i]; 283 property.name = name; 284 property.priority = style.getPropertyPriority(name); 285 property.implicit = style.isPropertyImplicit(name); 286 var shorthand = style.getPropertyShorthand(name); 287 property.shorthand = shorthand; 288 if (shorthand && !(shorthand in foundShorthands)) { 289 foundShorthands[shorthand] = true; 290 result.shorthandValues[shorthand] = InjectedScript._getShorthandValue(style, shorthand); 291 } 292 property.value = style.getPropertyValue(name); 293 result.properties.push(property); 294 } 295 result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style); 296 297 if (doBind) { 298 if (!style._id) { 299 style._id = InjectedScript._lastStyleId++; 300 InjectedScript._styles[style._id] = style; 301 } 302 result.id = style._id; 303 } 304 return result; 305} 306 307InjectedScript._getUniqueStyleProperties = function(style) 308{ 309 var properties = []; 310 var foundProperties = {}; 311 312 for (var i = 0; i < style.length; ++i) { 313 var property = style[i]; 314 if (property in foundProperties) 315 continue; 316 foundProperties[property] = true; 317 properties.push(property); 318 } 319 320 return properties; 321} 322 323 324InjectedScript._getLonghandProperties = function(style, shorthandProperty) 325{ 326 var properties = []; 327 var foundProperties = {}; 328 329 for (var i = 0; i < style.length; ++i) { 330 var individualProperty = style[i]; 331 if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty) 332 continue; 333 foundProperties[individualProperty] = true; 334 properties.push(individualProperty); 335 } 336 337 return properties; 338} 339 340InjectedScript._getShorthandValue = function(style, shorthandProperty) 341{ 342 var value = style.getPropertyValue(shorthandProperty); 343 if (!value) { 344 // Some shorthands (like border) return a null value, so compute a shorthand value. 345 // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed. 346 347 var foundProperties = {}; 348 for (var i = 0; i < style.length; ++i) { 349 var individualProperty = style[i]; 350 if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty) 351 continue; 352 353 var individualValue = style.getPropertyValue(individualProperty); 354 if (style.isPropertyImplicit(individualProperty) || individualValue === "initial") 355 continue; 356 357 foundProperties[individualProperty] = true; 358 359 if (!value) 360 value = ""; 361 else if (value.length) 362 value += " "; 363 value += individualValue; 364 } 365 } 366 return value; 367} 368 369InjectedScript._getShorthandPriority = function(style, shorthandProperty) 370{ 371 var priority = style.getPropertyPriority(shorthandProperty); 372 if (!priority) { 373 for (var i = 0; i < style.length; ++i) { 374 var individualProperty = style[i]; 375 if (style.getPropertyShorthand(individualProperty) !== shorthandProperty) 376 continue; 377 priority = style.getPropertyPriority(individualProperty); 378 break; 379 } 380 } 381 return priority; 382} 383 384InjectedScript.getPrototypes = function(nodeId) 385{ 386 var node = InjectedScript._nodeForId(nodeId); 387 if (!node) 388 return false; 389 390 var result = []; 391 for (var prototype = node; prototype; prototype = prototype.__proto__) { 392 var title = Object.describe(prototype); 393 if (title.match(/Prototype$/)) { 394 title = title.replace(/Prototype$/, ""); 395 } 396 result.push(title); 397 } 398 return result; 399} 400 401InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty) 402{ 403 var object = InjectedScript._resolveObject(objectProxy); 404 if (!object) 405 return false; 406 407 var properties = []; 408 // Go over properties, prepare results. 409 for (var propertyName in object) { 410 if (!ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName)) 411 continue; 412 413 //TODO: remove this once object becomes really remote. 414 if (propertyName === "__treeElementIdentifier") 415 continue; 416 var property = {}; 417 property.name = propertyName; 418 var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName); 419 if (!property.isGetter) { 420 var childObject = object[propertyName]; 421 property.type = typeof childObject; 422 property.textContent = Object.describe(childObject, true); 423 property.parentObjectProxy = objectProxy; 424 var parentPath = objectProxy.path.slice(); 425 property.childObjectProxy = { 426 objectId : objectProxy.objectId, 427 path : parentPath.splice(parentPath.length, 0, propertyName), 428 protoDepth : objectProxy.protoDepth 429 }; 430 if (childObject && (property.type === "object" || property.type === "function")) { 431 for (var subPropertyName in childObject) { 432 if (propertyName === "__treeElementIdentifier") 433 continue; 434 property.hasChildren = true; 435 break; 436 } 437 } 438 } else { 439 // FIXME: this should show something like "getter" (bug 16734). 440 property.textContent = "\u2014"; // em dash 441 property.isGetter = true; 442 } 443 properties.push(property); 444 } 445 return properties; 446} 447 448InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression) 449{ 450 var object = InjectedScript._resolveObject(objectProxy); 451 if (!object) 452 return false; 453 454 var expressionLength = expression.length; 455 if (!expressionLength) { 456 delete object[propertyName]; 457 return !(propertyName in object); 458 } 459 460 try { 461 // Surround the expression in parenthesis so the result of the eval is the result 462 // of the whole expression not the last potential sub-expression. 463 464 // There is a regression introduced here: eval is now happening against global object, 465 // not call frame while on a breakpoint. 466 // TODO: bring evaluation against call frame back. 467 var result = InjectedScript._window().eval("(" + expression + ")"); 468 // Store the result in the property. 469 object[propertyName] = result; 470 return true; 471 } catch(e) { 472 try { 473 var result = InjectedScript._window().eval("\"" + expression.escapeCharacters("\"") + "\""); 474 object[propertyName] = result; 475 return true; 476 } catch(e) { 477 return false; 478 } 479 } 480} 481 482InjectedScript._resolveObject = function(objectProxy) 483{ 484 var object = InjectedScript._objectForId(objectProxy.objectId); 485 var path = objectProxy.path; 486 var protoDepth = objectProxy.protoDepth; 487 488 // Follow the property path. 489 for (var i = 0; object && i < path.length; ++i) 490 object = object[path[i]]; 491 492 // Get to the necessary proto layer. 493 for (var i = 0; object && i < protoDepth; ++i) 494 object = object.__proto__; 495 496 return object; 497} 498 499InjectedScript._window = function() 500{ 501 // TODO: replace with 'return window;' once this script is injected into 502 // the page's context. 503 return InspectorController.inspectedWindow(); 504} 505 506InjectedScript._nodeForId = function(nodeId) 507{ 508 // TODO: replace with node lookup in the InspectorDOMAgent once DOMAgent nodes are used. 509 return nodeId; 510} 511 512InjectedScript._objectForId = function(objectId) 513{ 514 // TODO: replace with node lookups for node ids and evaluation result lookups for the rest of ids. 515 return objectId; 516} 517