| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview Javascript that is being injected into the inspectable page | 6 * @fileoverview Javascript that is being injected into the inspectable page |
| 7 * while debugging. | 7 * while debugging. |
| 8 */ | 8 */ |
| 9 goog.provide('devtools.Injected'); | 9 goog.provide('devtools.Injected'); |
| 10 | 10 |
| 11 | 11 |
| 12 /** | 12 /** |
| 13 * Main injected object. | 13 * Main injected object. |
| 14 * @constructor. | 14 * @constructor. |
| 15 */ | 15 */ |
| 16 devtools.Injected = function() { | 16 devtools.Injected = function() { |
| 17 /** | |
| 18 * Unique style id generator. | |
| 19 * @type {number} | |
| 20 * @private | |
| 21 */ | |
| 22 this.lastStyleId_ = 1; | |
| 23 | |
| 24 /** | |
| 25 * This array is not unused as it may seem. It stores references to the | |
| 26 * styles so that they could be found for future editing. | |
| 27 * @type {Array<CSSStyleDeclaration>} | |
| 28 * @private | |
| 29 */ | |
| 30 this.styles_ = []; | |
| 31 }; | 17 }; |
| 32 | 18 |
| 33 | 19 |
| 34 /** | 20 /** |
| 35 * Returns array of properties for a given node on a given path. | 21 * Dispatches given method with given args on the host object. |
| 36 * @param {Node} node Node to get property value for. | 22 * @param {string} method Method name. |
| 37 * @param {Array.<string>} path Path to the nested object. | |
| 38 * @param {number} protoDepth Depth of the actual proto to inspect. | |
| 39 * @return {Array.<Object>} Array where each property is represented | |
| 40 * by the tree entries [{string} type, {string} name, {Object} value]. | |
| 41 */ | 23 */ |
| 42 devtools.Injected.prototype.getProperties = function(node, path, protoDepth) { | 24 devtools.Injected.prototype.InspectorController = function(method, var_args) { |
| 43 var result = []; | 25 var args = Array.prototype.slice.call(arguments, 1); |
| 44 var obj = node; | 26 return InspectorController[method].apply(InspectorController, args); |
| 27 }; |
| 45 | 28 |
| 46 // Follow the path. | |
| 47 for (var i = 0; obj && i < path.length; ++i) { | |
| 48 obj = obj[path[i]]; | |
| 49 } | |
| 50 | 29 |
| 51 if (!obj) { | 30 /** |
| 52 return []; | 31 * Dispatches given method with given args on the InjectedScript. |
| 53 } | 32 * @param {string} method Method name. |
| 54 | 33 */ |
| 55 // Get to the necessary proto layer. | 34 devtools.Injected.prototype.InjectedScript = function(method, var_args) { |
| 56 for (var i = 0; obj && i < protoDepth; ++i) { | 35 var args = Array.prototype.slice.call(arguments, 1); |
| 57 obj = obj.__proto__; | 36 var result = InjectedScript[method].apply(InjectedScript, args); |
| 58 } | |
| 59 | |
| 60 if (!obj) { | |
| 61 return []; | |
| 62 } | |
| 63 | |
| 64 // Go over properties, prepare results. | |
| 65 for (var name in obj) { | |
| 66 if (protoDepth != -1 && 'hasOwnProperty' in obj && | |
| 67 !obj.hasOwnProperty(name)) { | |
| 68 continue; | |
| 69 } | |
| 70 var type = typeof obj[name]; | |
| 71 result.push(type); | |
| 72 result.push(name); | |
| 73 if (type == 'string') { | |
| 74 var str = obj[name]; | |
| 75 result.push(str.length > 99 ? str.substr(0, 99) + '...' : str); | |
| 76 } else if (type != 'object' && type != 'array' && | |
| 77 type != 'function') { | |
| 78 result.push(obj[name]); | |
| 79 } else { | |
| 80 result.push(undefined); | |
| 81 } | |
| 82 } | |
| 83 return result; | 37 return result; |
| 84 }; | 38 }; |
| 85 | 39 |
| 86 | 40 |
| 87 /** | 41 // Plugging into upstreamed support. |
| 88 * Returns array of prototypes for a given node. | 42 InjectedScript._window = function() { |
| 89 * @param {Node} node Node to get prorotypes for. | 43 return contentWindow; |
| 90 * @return {Array<string>} Array of proto names. | |
| 91 */ | |
| 92 devtools.Injected.prototype.getPrototypes = function(node) { | |
| 93 var result = []; | |
| 94 for (var prototype = node; prototype; prototype = prototype.__proto__) { | |
| 95 var description = Object.prototype.toString.call(prototype); | |
| 96 result.push(description.replace(/^\[object (.*)\]$/i, '$1')); | |
| 97 } | |
| 98 return result; | |
| 99 }; | 44 }; |
| 100 | 45 |
| 101 | 46 |
| 102 /** | 47 Object.type = function(obj, win) |
| 103 * Returns style information that is used in devtools.js. | 48 { |
| 104 * @param {Node} node Node to get prorotypes for. | 49 if (obj === null) |
| 105 * @param {boolean} authorOnly Determines whether only author styles need to | 50 return "null"; |
| 106 * be added. | |
| 107 * @return {string} Style collection descriptor. | |
| 108 */ | |
| 109 devtools.Injected.prototype.getStyles = function(node, authorOnly) { | |
| 110 if (!node.nodeType == Node.ELEMENT_NODE) { | |
| 111 return {}; | |
| 112 } | |
| 113 var matchedRules = window.getMatchedCSSRules(node, '', false); | |
| 114 var matchedCSSRulesObj = []; | |
| 115 for (var i = 0; matchedRules && i < matchedRules.length; ++i) { | |
| 116 var rule = matchedRules[i]; | |
| 117 var parentStyleSheet = rule.parentStyleSheet; | |
| 118 var isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && | |
| 119 !parentStyleSheet.href; | |
| 120 var isUser = parentStyleSheet && parentStyleSheet.ownerNode && | |
| 121 parentStyleSheet.ownerNode.nodeName == '#document'; | |
| 122 | 51 |
| 123 var style = this.serializeStyle_(rule.style, !isUserAgent && !isUser); | 52 var type = typeof obj; |
| 124 var ruleValue = { | 53 if (type !== "object" && type !== "function") |
| 125 'selector' : rule.selectorText, | 54 return type; |
| 126 'style' : style | 55 |
| 127 }; | 56 win = win || window; |
| 128 if (parentStyleSheet) { | 57 |
| 129 ruleValue['parentStyleSheet'] = { | 58 if (obj instanceof win.Node) |
| 130 'href' : parentStyleSheet.href, | 59 return (obj.nodeType === undefined ? type : "node"); |
| 131 'ownerNodeName' : parentStyleSheet.ownerNode ? | 60 if (obj instanceof win.String) |
| 132 parentStyleSheet.ownerNode.name : null | 61 return "string"; |
| 133 }; | 62 if (obj instanceof win.Array) |
| 63 return "array"; |
| 64 if (obj instanceof win.Boolean) |
| 65 return "boolean"; |
| 66 if (obj instanceof win.Number) |
| 67 return "number"; |
| 68 if (obj instanceof win.Date) |
| 69 return "date"; |
| 70 if (obj instanceof win.RegExp) |
| 71 return "regexp"; |
| 72 if (obj instanceof win.Error) |
| 73 return "error"; |
| 74 return type; |
| 75 } |
| 76 |
| 77 // Temporarily moved into the injected context. |
| 78 Object.hasProperties = function(obj) |
| 79 { |
| 80 if (typeof obj === "undefined" || typeof obj === "null") |
| 81 return false; |
| 82 for (var name in obj) |
| 83 return true; |
| 84 return false; |
| 85 } |
| 86 |
| 87 Object.describe = function(obj, abbreviated) |
| 88 { |
| 89 var type1 = Object.type(obj); |
| 90 var type2 = (obj == null) ? "null" : obj.constructor.name; |
| 91 |
| 92 switch (type1) { |
| 93 case "object": |
| 94 case "node": |
| 95 return type2; |
| 96 case "array": |
| 97 return "[" + obj.toString() + "]"; |
| 98 case "string": |
| 99 if (obj.length > 100) |
| 100 return "\"" + obj.substring(0, 100) + "\u2026\""; |
| 101 return "\"" + obj + "\""; |
| 102 case "function": |
| 103 var objectText = String(obj); |
| 104 if (!/^function /.test(objectText)) |
| 105 objectText = (type2 == "object") ? type1 : type2; |
| 106 else if (abbreviated) |
| 107 objectText = /.*/.exec(obj)[0].replace(/ +$/g, ""); |
| 108 return objectText; |
| 109 case "regexp": |
| 110 return String(obj).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/,
"$1").substring(1); |
| 111 default: |
| 112 return String(obj); |
| 134 } | 113 } |
| 135 matchedCSSRulesObj.push(ruleValue); | 114 } |
| 136 } | |
| 137 | 115 |
| 138 var attributeStyles = {}; | 116 Function.prototype.bind = function(thisObject) |
| 139 var attributes = node.attributes; | 117 { |
| 140 for (var i = 0; attributes && i < attributes.length; ++i) { | 118 var func = this; |
| 141 if (attributes[i].style) { | 119 var args = Array.prototype.slice.call(arguments, 1); |
| 142 attributeStyles[attributes[i].name] = | 120 return function() { return func.apply(thisObject, args.concat(Array.prototyp
e.slice.call(arguments, 0))) }; |
| 143 this.serializeStyle_(attributes[i].style, true); | 121 } |
| 144 } | |
| 145 } | |
| 146 var result = { | |
| 147 'inlineStyle' : this.serializeStyle_(node.style, true), | |
| 148 'computedStyle' : this.serializeStyle_( | |
| 149 window.getComputedStyle(node, '')), | |
| 150 'matchedCSSRules' : matchedCSSRulesObj, | |
| 151 'styleAttributes' : attributeStyles | |
| 152 }; | |
| 153 return result; | |
| 154 }; | |
| 155 | 122 |
| 156 | 123 String.prototype.escapeCharacters = function(chars) |
| 157 /** | 124 { |
| 158 * Returns style decoration object for given id. | 125 var foundChar = false; |
| 159 * @param {Node} node Node to get prorotypes for. | 126 for (var i = 0; i < chars.length; ++i) { |
| 160 * @param {number} id Style id. | 127 if (this.indexOf(chars.charAt(i)) !== -1) { |
| 161 * @return {Object} Style object. | 128 foundChar = true; |
| 162 * @private | 129 break; |
| 163 */ | 130 } |
| 164 devtools.Injected.prototype.getStyleForId_ = function(node, id) { | |
| 165 var matchedRules = window.getMatchedCSSRules(node, '', false); | |
| 166 for (var i = 0; matchedRules && i < matchedRules.length; ++i) { | |
| 167 var rule = matchedRules[i]; | |
| 168 if (rule.style.__id == id) { | |
| 169 return rule.style; | |
| 170 } | |
| 171 } | |
| 172 var attributes = node.attributes; | |
| 173 for (var i = 0; attributes && i < attributes.length; ++i) { | |
| 174 if (attributes[i].style && attributes[i].style.__id == id) { | |
| 175 return attributes[i].style; | |
| 176 } | |
| 177 } | |
| 178 if (node.style.__id == id) { | |
| 179 return node.style; | |
| 180 } | |
| 181 return null; | |
| 182 }; | |
| 183 | |
| 184 | |
| 185 | |
| 186 | |
| 187 /** | |
| 188 * Converts given style into serializable object. | |
| 189 * @param {CSSStyleDeclaration} style Style to serialize. | |
| 190 * @param {boolean} opt_bind Determins whether this style should be bound. | |
| 191 * @return {Array<Object>} Serializable object. | |
| 192 * @private | |
| 193 */ | |
| 194 devtools.Injected.prototype.serializeStyle_ = function(style, opt_bind) { | |
| 195 if (!style) { | |
| 196 return []; | |
| 197 } | |
| 198 var id = style.__id; | |
| 199 if (opt_bind && !id) { | |
| 200 id = style.__id = this.lastStyleId_++; | |
| 201 this.styles_.push(style); | |
| 202 } | |
| 203 var result = [ | |
| 204 id, | |
| 205 style.width, | |
| 206 style.height, | |
| 207 style.__disabledProperties, | |
| 208 style.__disabledPropertyValues, | |
| 209 style.__disabledPropertyPriorities | |
| 210 ]; | |
| 211 for (var i = 0; i < style.length; ++i) { | |
| 212 var name = style[i]; | |
| 213 result.push([ | |
| 214 name, | |
| 215 style.getPropertyPriority(name), | |
| 216 style.isPropertyImplicit(name), | |
| 217 style.getPropertyShorthand(name), | |
| 218 style.getPropertyValue(name) | |
| 219 ]); | |
| 220 } | |
| 221 return result; | |
| 222 }; | |
| 223 | |
| 224 | |
| 225 /** | |
| 226 * Toggles style with given id on/off. | |
| 227 * @param {Node} node Node to get prorotypes for. | |
| 228 * @param {number} styleId Id of style to toggle. | |
| 229 * @param {boolean} enabled Determines value to toggle to, | |
| 230 * @param {string} name Name of the property. | |
| 231 */ | |
| 232 devtools.Injected.prototype.toggleNodeStyle = function(node, styleId, enabled, | |
| 233 name) { | |
| 234 var style = this.getStyleForId_(node, styleId); | |
| 235 if (!style) { | |
| 236 return false; | |
| 237 } | |
| 238 | |
| 239 if (!enabled) { | |
| 240 if (!style.__disabledPropertyValues || | |
| 241 !style.__disabledPropertyPriorities) { | |
| 242 style.__disabledProperties = {}; | |
| 243 style.__disabledPropertyValues = {}; | |
| 244 style.__disabledPropertyPriorities = {}; | |
| 245 } | 131 } |
| 246 | 132 |
| 247 style.__disabledPropertyValues[name] = style.getPropertyValue(name); | 133 if (!foundChar) |
| 248 style.__disabledPropertyPriorities[name] = style.getPropertyPriority(name); | 134 return this; |
| 249 | 135 |
| 250 if (style.getPropertyShorthand(name)) { | 136 var result = ""; |
| 251 var longhandProperties = this.getLonghandProperties_(style, name); | 137 for (var i = 0; i < this.length; ++i) { |
| 252 for (var i = 0; i < longhandProperties.length; ++i) { | 138 if (chars.indexOf(this.charAt(i)) !== -1) |
| 253 style.__disabledProperties[longhandProperties[i]] = true; | 139 result += "\\"; |
| 254 style.removeProperty(longhandProperties[i]); | 140 result += this.charAt(i); |
| 255 } | |
| 256 } else { | |
| 257 style.__disabledProperties[name] = true; | |
| 258 style.removeProperty(name); | |
| 259 } | |
| 260 } else if (style.__disabledProperties && | |
| 261 style.__disabledProperties[name]) { | |
| 262 var value = style.__disabledPropertyValues[name]; | |
| 263 var priority = style.__disabledPropertyPriorities[name]; | |
| 264 style.setProperty(name, value, priority); | |
| 265 | |
| 266 delete style.__disabledProperties[name]; | |
| 267 delete style.__disabledPropertyValues[name]; | |
| 268 delete style.__disabledPropertyPriorities[name]; | |
| 269 } | |
| 270 return true; | |
| 271 }; | |
| 272 | |
| 273 | |
| 274 /** | |
| 275 * Applies given text to a style. | |
| 276 * @param {Node} node Node to get prorotypes for. | |
| 277 * @param {number} styleId Id of style to toggle. | |
| 278 * @param {string} name Style element name. | |
| 279 * @param {string} styleText New style text. | |
| 280 * @return {boolean} True iff style has been edited successfully. | |
| 281 */ | |
| 282 devtools.Injected.prototype.applyStyleText = function(node, styleId, | |
| 283 name, styleText) { | |
| 284 var style = this.getStyleForId_(node, styleId); | |
| 285 if (!style) { | |
| 286 return false; | |
| 287 } | |
| 288 | |
| 289 var styleTextLength = this.trimWhitespace_(styleText).length; | |
| 290 | |
| 291 // Create a new element to parse the user input CSS. | |
| 292 var parseElement = document.createElement("span"); | |
| 293 parseElement.setAttribute("style", styleText); | |
| 294 | |
| 295 var tempStyle = parseElement.style; | |
| 296 if (tempStyle.length || !styleTextLength) { | |
| 297 // The input was parsable or the user deleted everything, so remove the | |
| 298 // original property from the real style declaration. If this represents | |
| 299 // a shorthand remove all the longhand properties. | |
| 300 if (style.getPropertyShorthand(name)) { | |
| 301 var longhandProperties = this.getLonghandProperties_(style, name); | |
| 302 for (var i = 0; i < longhandProperties.length; ++i) { | |
| 303 style.removeProperty(longhandProperties[i]); | |
| 304 } | |
| 305 } else { | |
| 306 style.removeProperty(name); | |
| 307 } | |
| 308 } | |
| 309 if (!tempStyle.length) { | |
| 310 // The user typed something, but it didn't parse. Just abort and restore | |
| 311 // the original title for this property. | |
| 312 return false; | |
| 313 } | |
| 314 | |
| 315 // Iterate of the properties on the test element's style declaration and | |
| 316 // add them to the real style declaration. We take care to move shorthands. | |
| 317 var foundShorthands = {}; | |
| 318 var uniqueProperties = this.getUniqueStyleProperties_(tempStyle); | |
| 319 for (var i = 0; i < uniqueProperties.length; ++i) { | |
| 320 var name = uniqueProperties[i]; | |
| 321 var shorthand = tempStyle.getPropertyShorthand(name); | |
| 322 | |
| 323 if (shorthand && shorthand in foundShorthands) { | |
| 324 continue; | |
| 325 } | 141 } |
| 326 | 142 |
| 327 if (shorthand) { | 143 return result; |
| 328 var value = this.getShorthandValue_(tempStyle, shorthand); | 144 } |
| 329 var priority = this.getShorthandPriority_(tempStyle, shorthand); | |
| 330 foundShorthands[shorthand] = true; | |
| 331 } else { | |
| 332 var value = tempStyle.getPropertyValue(name); | |
| 333 var priority = tempStyle.getPropertyPriority(name); | |
| 334 } | |
| 335 // Set the property on the real style declaration. | |
| 336 style.setProperty((shorthand || name), value, priority); | |
| 337 } | |
| 338 return true; | |
| 339 }; | |
| 340 | |
| 341 | |
| 342 /** | |
| 343 * Sets style property with given name to a value. | |
| 344 * @param {Node} node Node to get prorotypes for. | |
| 345 * @param {string} name Style element name. | |
| 346 * @param {string} value Value. | |
| 347 * @return {boolean} True iff style has been edited successfully. | |
| 348 */ | |
| 349 devtools.Injected.prototype.setStyleProperty = function(node, | |
| 350 name, value) { | |
| 351 node.style.setProperty(name, value, ""); | |
| 352 return true; | |
| 353 }; | |
| 354 | |
| 355 | |
| 356 /** | |
| 357 * Taken from utilities.js as is for injected evaluation. | |
| 358 */ | |
| 359 devtools.Injected.prototype.getLonghandProperties_ = function(style, | |
| 360 shorthandProperty) { | |
| 361 var properties = []; | |
| 362 var foundProperties = {}; | |
| 363 | |
| 364 for (var i = 0; i < style.length; ++i) { | |
| 365 var individualProperty = style[i]; | |
| 366 if (individualProperty in foundProperties || | |
| 367 style.getPropertyShorthand(individualProperty) != shorthandProperty) { | |
| 368 continue; | |
| 369 } | |
| 370 foundProperties[individualProperty] = true; | |
| 371 properties.push(individualProperty); | |
| 372 } | |
| 373 return properties; | |
| 374 }; | |
| 375 | |
| 376 | |
| 377 /** | |
| 378 * Taken from utilities.js as is for injected evaluation. | |
| 379 */ | |
| 380 devtools.Injected.prototype.getShorthandValue_ = function(style, | |
| 381 shorthandProperty) { | |
| 382 var value = style.getPropertyValue(shorthandProperty); | |
| 383 if (!value) { | |
| 384 // Some shorthands (like border) return a null value, so compute a | |
| 385 // shorthand value. | |
| 386 // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 | |
| 387 // is fixed. | |
| 388 | |
| 389 var foundProperties = {}; | |
| 390 for (var i = 0; i < style.length; ++i) { | |
| 391 var individualProperty = style[i]; | |
| 392 if (individualProperty in foundProperties || | |
| 393 style.getPropertyShorthand(individualProperty) !== | |
| 394 shorthandProperty) { | |
| 395 continue; | |
| 396 } | |
| 397 | |
| 398 var individualValue = style.getPropertyValue(individualProperty); | |
| 399 if (style.isPropertyImplicit(individualProperty) || | |
| 400 individualValue === "initial") { | |
| 401 continue; | |
| 402 } | |
| 403 | |
| 404 foundProperties[individualProperty] = true; | |
| 405 | |
| 406 if (!value) { | |
| 407 value = ""; | |
| 408 } else if (value.length) { | |
| 409 value += " "; | |
| 410 } | |
| 411 value += individualValue; | |
| 412 } | |
| 413 } | |
| 414 return value; | |
| 415 }; | |
| 416 | |
| 417 | |
| 418 /** | |
| 419 * Taken from utilities.js as is for injected evaluation. | |
| 420 */ | |
| 421 devtools.Injected.prototype.getShorthandPriority_ = function(style, | |
| 422 shorthandProperty) { | |
| 423 var priority = style.getPropertyPriority(shorthandProperty); | |
| 424 if (!priority) { | |
| 425 for (var i = 0; i < style.length; ++i) { | |
| 426 var individualProperty = style[i]; | |
| 427 if (style.getPropertyShorthand(individualProperty) !== | |
| 428 shorthandProperty) { | |
| 429 continue; | |
| 430 } | |
| 431 priority = style.getPropertyPriority(individualProperty); | |
| 432 break; | |
| 433 } | |
| 434 } | |
| 435 return priority; | |
| 436 }; | |
| 437 | |
| 438 | |
| 439 /** | |
| 440 * Taken from utilities.js as is for injected evaluation. | |
| 441 */ | |
| 442 devtools.Injected.prototype.trimWhitespace_ = function(str) { | |
| 443 return str.replace(/^[\s\xA0]+|[\s\xA0]+$/g, ''); | |
| 444 }; | |
| 445 | |
| 446 | |
| 447 /** | |
| 448 * Taken from utilities.js as is for injected evaluation. | |
| 449 */ | |
| 450 devtools.Injected.prototype.getUniqueStyleProperties_ = function(style) { | |
| 451 var properties = []; | |
| 452 var foundProperties = {}; | |
| 453 | |
| 454 for (var i = 0; i < style.length; ++i) { | |
| 455 var property = style[i]; | |
| 456 if (property in foundProperties) { | |
| 457 continue; | |
| 458 } | |
| 459 foundProperties[property] = true; | |
| 460 properties.push(property); | |
| 461 } | |
| 462 return properties; | |
| 463 }; | |
| OLD | NEW |