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 |