]>
Commit | Line | Data |
---|---|---|
81345200 A |
1 | /* |
2 | * Copyright (C) 2007 Apple Inc. All rights reserved. | |
3 | * | |
4 | * Redistribution and use in source and binary forms, with or without | |
5 | * modification, are permitted provided that the following conditions | |
6 | * are met: | |
7 | * | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 3. Neither the name of Apple Inc. ("Apple") nor the names of | |
14 | * its contributors may be used to endorse or promote products derived | |
15 | * from this software without specific prior written permission. | |
16 | * | |
17 | * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | |
18 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
20 | * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | |
21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
27 | */ | |
28 | ||
29 | //# sourceURL=__WebInspectorInjectedScript__ | |
30 | ||
31 | /** | |
32 | * @param {InjectedScriptHost} InjectedScriptHost | |
33 | * @param {GlobalObject} inspectedGlobalObject | |
34 | * @param {number} injectedScriptId | |
35 | */ | |
36 | (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) { | |
37 | ||
38 | // Protect against Object overwritten by the user code. | |
39 | var Object = {}.constructor; | |
40 | ||
41 | /** | |
42 | * @constructor | |
43 | */ | |
44 | var InjectedScript = function() | |
45 | { | |
46 | this._lastBoundObjectId = 1; | |
47 | this._idToWrappedObject = {}; | |
48 | this._idToObjectGroupName = {}; | |
49 | this._objectGroups = {}; | |
50 | this._modules = {}; | |
51 | } | |
52 | ||
53 | /** | |
54 | * @type {Object.<string, boolean>} | |
55 | * @const | |
56 | */ | |
57 | InjectedScript.primitiveTypes = { | |
58 | undefined: true, | |
59 | boolean: true, | |
60 | number: true, | |
61 | string: true | |
62 | } | |
63 | ||
64 | InjectedScript.prototype = { | |
65 | /** | |
66 | * @param {*} object | |
67 | * @return {boolean} | |
68 | */ | |
69 | isPrimitiveValue: function(object) | |
70 | { | |
71 | // FIXME(33716): typeof document.all is always 'undefined'. | |
72 | return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object); | |
73 | }, | |
74 | ||
75 | /** | |
76 | * @param {*} object | |
77 | * @param {string} groupName | |
78 | * @param {boolean} canAccessInspectedGlobalObject | |
79 | * @param {boolean} generatePreview | |
80 | * @return {!RuntimeAgent.RemoteObject} | |
81 | */ | |
82 | wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview) | |
83 | { | |
84 | if (canAccessInspectedGlobalObject) | |
85 | return this._wrapObject(object, groupName, false, generatePreview); | |
86 | return this._fallbackWrapper(object); | |
87 | }, | |
88 | ||
89 | /** | |
90 | * @param {*} object | |
91 | * @return {!RuntimeAgent.RemoteObject} | |
92 | */ | |
93 | _fallbackWrapper: function(object) | |
94 | { | |
95 | var result = {}; | |
96 | result.type = typeof object; | |
97 | if (this.isPrimitiveValue(object)) | |
98 | result.value = object; | |
99 | else | |
100 | result.description = this._toString(object); | |
101 | return /** @type {!RuntimeAgent.RemoteObject} */ (result); | |
102 | }, | |
103 | ||
104 | /** | |
105 | * @param {boolean} canAccessInspectedGlobalObject | |
106 | * @param {Object} table | |
107 | * @param {Array.<string>|string|boolean} columns | |
108 | * @return {!RuntimeAgent.RemoteObject} | |
109 | */ | |
110 | wrapTable: function(canAccessInspectedGlobalObject, table, columns) | |
111 | { | |
112 | if (!canAccessInspectedGlobalObject) | |
113 | return this._fallbackWrapper(table); | |
114 | var columnNames = null; | |
115 | if (typeof columns === "string") | |
116 | columns = [columns]; | |
117 | if (InjectedScriptHost.type(columns) == "array") { | |
118 | columnNames = []; | |
119 | for (var i = 0; i < columns.length; ++i) | |
120 | columnNames.push(String(columns[i])); | |
121 | } | |
122 | return this._wrapObject(table, "console", false, true, columnNames); | |
123 | }, | |
124 | ||
125 | /** | |
126 | * @param {*} object | |
127 | */ | |
128 | inspectObject: function(object) | |
129 | { | |
130 | if (this._commandLineAPIImpl) | |
131 | this._commandLineAPIImpl.inspect(object); | |
132 | }, | |
133 | ||
134 | /** | |
135 | * This method cannot throw. | |
136 | * @param {*} object | |
137 | * @param {string=} objectGroupName | |
138 | * @param {boolean=} forceValueType | |
139 | * @param {boolean=} generatePreview | |
140 | * @param {?Array.<string>=} columnNames | |
141 | * @return {!RuntimeAgent.RemoteObject} | |
142 | * @suppress {checkTypes} | |
143 | */ | |
144 | _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames) | |
145 | { | |
146 | try { | |
147 | return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames); | |
148 | } catch (e) { | |
149 | try { | |
150 | var description = injectedScript._describe(e); | |
151 | } catch (ex) { | |
152 | var description = "<failed to convert exception to string>"; | |
153 | } | |
154 | return new InjectedScript.RemoteObject(description); | |
155 | } | |
156 | }, | |
157 | ||
158 | /** | |
159 | * @param {*} object | |
160 | * @param {string=} objectGroupName | |
161 | * @return {string} | |
162 | */ | |
163 | _bind: function(object, objectGroupName) | |
164 | { | |
165 | var id = this._lastBoundObjectId++; | |
166 | this._idToWrappedObject[id] = object; | |
167 | var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}"; | |
168 | if (objectGroupName) { | |
169 | var group = this._objectGroups[objectGroupName]; | |
170 | if (!group) { | |
171 | group = []; | |
172 | this._objectGroups[objectGroupName] = group; | |
173 | } | |
174 | group.push(id); | |
175 | this._idToObjectGroupName[id] = objectGroupName; | |
176 | } | |
177 | return objectId; | |
178 | }, | |
179 | ||
180 | /** | |
181 | * @param {string} objectId | |
182 | * @return {Object} | |
183 | */ | |
184 | _parseObjectId: function(objectId) | |
185 | { | |
186 | return InjectedScriptHost.evaluate("(" + objectId + ")"); | |
187 | }, | |
188 | ||
189 | /** | |
190 | * @param {string} objectGroupName | |
191 | */ | |
192 | releaseObjectGroup: function(objectGroupName) | |
193 | { | |
194 | var group = this._objectGroups[objectGroupName]; | |
195 | if (!group) | |
196 | return; | |
197 | for (var i = 0; i < group.length; i++) | |
198 | this._releaseObject(group[i]); | |
199 | delete this._objectGroups[objectGroupName]; | |
200 | }, | |
201 | ||
202 | /** | |
203 | * @param {string} methodName | |
204 | * @param {string} args | |
205 | * @return {*} | |
206 | */ | |
207 | dispatch: function(methodName, args) | |
208 | { | |
209 | var argsArray = InjectedScriptHost.evaluate("(" + args + ")"); | |
210 | var result = this[methodName].apply(this, argsArray); | |
211 | if (typeof result === "undefined") { | |
212 | if (inspectedGlobalObject.console) | |
213 | inspectedGlobalObject.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName); | |
214 | result = null; | |
215 | } | |
216 | return result; | |
217 | }, | |
218 | ||
40a37d08 | 219 | getProperties: function(objectId, ownProperties, ownAndGetterProperties) |
81345200 A |
220 | { |
221 | var parsedObjectId = this._parseObjectId(objectId); | |
222 | var object = this._objectForId(parsedObjectId); | |
223 | var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; | |
224 | ||
225 | if (!this._isDefined(object)) | |
226 | return false; | |
40a37d08 A |
227 | |
228 | var descriptors = this._propertyDescriptors(object, ownProperties, ownAndGetterProperties); | |
81345200 A |
229 | |
230 | // Go over properties, wrap object values. | |
231 | for (var i = 0; i < descriptors.length; ++i) { | |
232 | var descriptor = descriptors[i]; | |
233 | if ("get" in descriptor) | |
234 | descriptor.get = this._wrapObject(descriptor.get, objectGroupName); | |
235 | if ("set" in descriptor) | |
236 | descriptor.set = this._wrapObject(descriptor.set, objectGroupName); | |
237 | if ("value" in descriptor) | |
238 | descriptor.value = this._wrapObject(descriptor.value, objectGroupName); | |
239 | if (!("configurable" in descriptor)) | |
240 | descriptor.configurable = false; | |
241 | if (!("enumerable" in descriptor)) | |
242 | descriptor.enumerable = false; | |
243 | } | |
40a37d08 | 244 | |
81345200 A |
245 | return descriptors; |
246 | }, | |
247 | ||
248 | /** | |
249 | * @param {string} objectId | |
250 | * @return {Array.<Object>|boolean} | |
251 | */ | |
252 | getInternalProperties: function(objectId, ownProperties) | |
253 | { | |
254 | var parsedObjectId = this._parseObjectId(objectId); | |
255 | var object = this._objectForId(parsedObjectId); | |
256 | var objectGroupName = this._idToObjectGroupName[parsedObjectId.id]; | |
257 | if (!this._isDefined(object)) | |
258 | return false; | |
259 | var descriptors = []; | |
260 | var internalProperties = InjectedScriptHost.getInternalProperties(object); | |
261 | if (internalProperties) { | |
262 | for (var i = 0; i < internalProperties.length; i++) { | |
263 | var property = internalProperties[i]; | |
264 | var descriptor = { | |
265 | name: property.name, | |
266 | value: this._wrapObject(property.value, objectGroupName) | |
267 | }; | |
268 | descriptors.push(descriptor); | |
269 | } | |
270 | } | |
271 | return descriptors; | |
272 | }, | |
273 | ||
274 | /** | |
275 | * @param {string} functionId | |
276 | * @return {!DebuggerAgent.FunctionDetails|string} | |
277 | */ | |
278 | getFunctionDetails: function(functionId) | |
279 | { | |
280 | var parsedFunctionId = this._parseObjectId(functionId); | |
281 | var func = this._objectForId(parsedFunctionId); | |
282 | if (typeof func !== "function") | |
283 | return "Cannot resolve function by id."; | |
284 | var details = InjectedScriptHost.functionDetails(func); | |
285 | if (!details) | |
286 | return "Cannot resolve function details."; | |
287 | if ("rawScopes" in details) { | |
288 | var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id]; | |
289 | var rawScopes = details.rawScopes; | |
290 | var scopes = []; | |
291 | delete details.rawScopes; | |
292 | for (var i = 0; i < rawScopes.length; i++) | |
293 | scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName)); | |
294 | details.scopeChain = scopes; | |
295 | } | |
296 | return details; | |
297 | }, | |
298 | ||
299 | /** | |
300 | * @param {string} objectId | |
301 | */ | |
302 | releaseObject: function(objectId) | |
303 | { | |
304 | var parsedObjectId = this._parseObjectId(objectId); | |
305 | this._releaseObject(parsedObjectId.id); | |
306 | }, | |
307 | ||
308 | /** | |
309 | * @param {string} id | |
310 | */ | |
311 | _releaseObject: function(id) | |
312 | { | |
313 | delete this._idToWrappedObject[id]; | |
314 | delete this._idToObjectGroupName[id]; | |
315 | }, | |
316 | ||
81345200 A |
317 | /** |
318 | * @param {string} expression | |
319 | * @param {string} objectGroup | |
320 | * @param {boolean} injectCommandLineAPI | |
321 | * @param {boolean} returnByValue | |
322 | * @param {boolean} generatePreview | |
323 | * @return {*} | |
324 | */ | |
325 | evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview) | |
326 | { | |
327 | return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview); | |
328 | }, | |
329 | ||
330 | /** | |
331 | * @param {string} objectId | |
332 | * @param {string} expression | |
333 | * @param {boolean} returnByValue | |
334 | * @return {Object|string} | |
335 | */ | |
336 | callFunctionOn: function(objectId, expression, args, returnByValue) | |
337 | { | |
338 | var parsedObjectId = this._parseObjectId(objectId); | |
339 | var object = this._objectForId(parsedObjectId); | |
340 | if (!this._isDefined(object)) | |
341 | return "Could not find object with given id"; | |
342 | ||
343 | if (args) { | |
344 | var resolvedArgs = []; | |
345 | args = InjectedScriptHost.evaluate(args); | |
346 | for (var i = 0; i < args.length; ++i) { | |
347 | var resolvedCallArgument; | |
348 | try { | |
349 | resolvedCallArgument = this._resolveCallArgument(args[i]); | |
350 | } catch (e) { | |
351 | return String(e); | |
352 | } | |
353 | resolvedArgs.push(resolvedCallArgument) | |
354 | } | |
355 | } | |
356 | ||
357 | try { | |
358 | var objectGroup = this._idToObjectGroupName[parsedObjectId.id]; | |
359 | var func = InjectedScriptHost.evaluate("(" + expression + ")"); | |
360 | if (typeof func !== "function") | |
361 | return "Given expression does not evaluate to a function"; | |
362 | ||
363 | return { wasThrown: false, | |
364 | result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue) }; | |
365 | } catch (e) { | |
366 | return this._createThrownValue(e, objectGroup); | |
367 | } | |
368 | }, | |
369 | ||
370 | /** | |
371 | * Resolves a value from CallArgument description. | |
372 | * @param {RuntimeAgent.CallArgument} callArgumentJson | |
373 | * @return {*} resolved value | |
374 | * @throws {string} error message | |
375 | */ | |
376 | _resolveCallArgument: function(callArgumentJson) { | |
377 | var objectId = callArgumentJson.objectId; | |
378 | if (objectId) { | |
379 | var parsedArgId = this._parseObjectId(objectId); | |
380 | if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId) | |
381 | throw "Arguments should belong to the same JavaScript world as the target object."; | |
382 | ||
383 | var resolvedArg = this._objectForId(parsedArgId); | |
384 | if (!this._isDefined(resolvedArg)) | |
385 | throw "Could not find object with given id"; | |
386 | ||
387 | return resolvedArg; | |
388 | } else if ("value" in callArgumentJson) | |
389 | return callArgumentJson.value; | |
390 | else | |
391 | return undefined; | |
392 | }, | |
393 | ||
394 | /** | |
395 | * @param {Function} evalFunction | |
396 | * @param {Object} object | |
397 | * @param {string} objectGroup | |
398 | * @param {boolean} isEvalOnCallFrame | |
399 | * @param {boolean} injectCommandLineAPI | |
400 | * @param {boolean} returnByValue | |
401 | * @param {boolean} generatePreview | |
402 | * @return {*} | |
403 | */ | |
404 | _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview) | |
405 | { | |
406 | try { | |
407 | return { wasThrown: false, | |
408 | result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup, returnByValue, generatePreview) }; | |
409 | } catch (e) { | |
410 | return this._createThrownValue(e, objectGroup); | |
411 | } | |
412 | }, | |
413 | ||
414 | /** | |
415 | * @param {*} value | |
416 | * @param {string} objectGroup | |
417 | * @return {Object} | |
418 | */ | |
419 | _createThrownValue: function(value, objectGroup) | |
420 | { | |
421 | var remoteObject = this._wrapObject(value, objectGroup); | |
422 | try { | |
423 | remoteObject.description = this._toString(value); | |
424 | } catch (e) {} | |
425 | return { wasThrown: true, | |
426 | result: remoteObject }; | |
427 | }, | |
428 | ||
429 | /** | |
430 | * @param {Function} evalFunction | |
431 | * @param {Object} object | |
432 | * @param {string} objectGroup | |
433 | * @param {string} expression | |
434 | * @param {boolean} isEvalOnCallFrame | |
435 | * @param {boolean} injectCommandLineAPI | |
436 | * @return {*} | |
437 | */ | |
438 | _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI) | |
439 | { | |
440 | var commandLineAPI = null; | |
441 | if (injectCommandLineAPI) { | |
442 | if (this.CommandLineAPI) | |
443 | commandLineAPI = new this.CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null); | |
444 | else | |
445 | commandLineAPI = new BasicCommandLineAPI; | |
446 | } | |
447 | ||
448 | if (isEvalOnCallFrame) { | |
449 | // We can only use this approach if the evaluate function is the true 'eval'. That allows us to use it with | |
450 | // the 'eval' identifier when calling it. Using 'eval' grants access to the local scope of the closure we | |
451 | // create that provides the command line APIs. | |
452 | ||
453 | var parameters = [InjectedScriptHost.evaluate, expression]; | |
454 | var expressionFunctionBody = "" + | |
455 | "var global = Function('return this')() || (1, eval)('this');" + | |
456 | "var __originalEval = global.eval; global.eval = __eval;" + | |
457 | "try { return eval(__currentExpression); }" + | |
458 | "finally { global.eval = __originalEval; }"; | |
459 | ||
460 | if (commandLineAPI) { | |
461 | // To avoid using a 'with' statement (which fails in strict mode and requires injecting the API object) | |
462 | // we instead create a closure where we evaluate the expression. The command line APIs are passed as | |
463 | // parameters to the closure so they are in scope but not injected. This allows the code evaluated in | |
464 | // the console to stay in strict mode (if is was already set), or to get strict mode by prefixing | |
465 | // expressions with 'use strict';. | |
466 | ||
467 | var parameterNames = Object.getOwnPropertyNames(commandLineAPI); | |
468 | for (var i = 0; i < parameterNames.length; ++i) | |
469 | parameters.push(commandLineAPI[parameterNames[i]]); | |
470 | ||
471 | var expressionFunctionString = "(function(__eval, __currentExpression, " + parameterNames.join(", ") + ") { " + expressionFunctionBody + " })"; | |
472 | } else { | |
473 | // Use a closure in this case too to keep the same behavior of 'var' being captured by the closure instead | |
474 | // of leaking out into the calling scope. | |
475 | var expressionFunctionString = "(function(__eval, __currentExpression) { " + expressionFunctionBody + " })"; | |
476 | } | |
477 | ||
478 | // Bind 'this' to the function expression using another closure instead of Function.prototype.bind. This ensures things will work if the page replaces bind. | |
479 | var boundExpressionFunctionString = "(function(__function, __thisObject) { return function() { return __function.apply(__thisObject, arguments) }; })(" + expressionFunctionString + ", this)"; | |
480 | var expressionFunction = evalFunction.call(object, boundExpressionFunctionString); | |
481 | var result = expressionFunction.apply(null, parameters); | |
482 | ||
483 | if (objectGroup === "console") | |
484 | this._lastResult = result; | |
485 | ||
486 | return result; | |
487 | } | |
488 | ||
489 | // When not evaluating on a call frame we use a 'with' statement to allow var and function statements to leak | |
490 | // into the global scope. This allow them to stick around between evaluations. | |
491 | ||
492 | try { | |
493 | if (commandLineAPI) { | |
494 | if (inspectedGlobalObject.console) | |
495 | inspectedGlobalObject.console.__commandLineAPI = commandLineAPI; | |
496 | else | |
497 | inspectedGlobalObject.__commandLineAPI = commandLineAPI; | |
498 | expression = "with ((this && (this.console ? this.console.__commandLineAPI : this.__commandLineAPI)) || {}) { " + expression + "\n}"; | |
499 | } | |
500 | ||
501 | var result = evalFunction.call(inspectedGlobalObject, expression); | |
502 | ||
503 | if (objectGroup === "console") | |
504 | this._lastResult = result; | |
505 | ||
506 | return result; | |
507 | } finally { | |
508 | if (commandLineAPI) { | |
509 | if (inspectedGlobalObject.console) | |
510 | delete inspectedGlobalObject.console.__commandLineAPI; | |
511 | else | |
512 | delete inspectedGlobalObject.__commandLineAPI; | |
513 | } | |
514 | } | |
515 | }, | |
516 | ||
517 | /** | |
518 | * @param {Object} callFrame | |
519 | * @return {Array.<InjectedScript.CallFrameProxy>|boolean} | |
520 | */ | |
521 | wrapCallFrames: function(callFrame) | |
522 | { | |
523 | if (!callFrame) | |
524 | return false; | |
525 | ||
526 | var result = []; | |
527 | var depth = 0; | |
528 | do { | |
529 | result.push(new InjectedScript.CallFrameProxy(depth++, callFrame)); | |
530 | callFrame = callFrame.caller; | |
531 | } while (callFrame); | |
532 | return result; | |
533 | }, | |
534 | ||
535 | /** | |
536 | * @param {Object} topCallFrame | |
537 | * @param {string} callFrameId | |
538 | * @param {string} expression | |
539 | * @param {string} objectGroup | |
540 | * @param {boolean} injectCommandLineAPI | |
541 | * @param {boolean} returnByValue | |
542 | * @param {boolean} generatePreview | |
543 | * @return {*} | |
544 | */ | |
545 | evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview) | |
546 | { | |
547 | var callFrame = this._callFrameForId(topCallFrame, callFrameId); | |
548 | if (!callFrame) | |
549 | return "Could not find call frame with given id"; | |
550 | return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview); | |
551 | }, | |
552 | ||
553 | /** | |
554 | * @param {Object} topCallFrame | |
555 | * @param {string} callFrameId | |
556 | * @return {Object} | |
557 | */ | |
558 | _callFrameForId: function(topCallFrame, callFrameId) | |
559 | { | |
560 | var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")"); | |
561 | var ordinal = parsedCallFrameId["ordinal"]; | |
562 | var callFrame = topCallFrame; | |
563 | while (--ordinal >= 0 && callFrame) | |
564 | callFrame = callFrame.caller; | |
565 | return callFrame; | |
566 | }, | |
567 | ||
568 | /** | |
569 | * @param {Object} objectId | |
570 | * @return {Object} | |
571 | */ | |
572 | _objectForId: function(objectId) | |
573 | { | |
574 | return this._idToWrappedObject[objectId.id]; | |
575 | }, | |
576 | ||
577 | /** | |
578 | * @param {string} objectId | |
579 | * @return {Object} | |
580 | */ | |
581 | findObjectById: function(objectId) | |
582 | { | |
583 | var parsedObjectId = this._parseObjectId(objectId); | |
584 | return this._objectForId(parsedObjectId); | |
585 | }, | |
586 | ||
587 | /** | |
588 | * @param {string} name | |
589 | * @return {Object} | |
590 | */ | |
591 | module: function(name) | |
592 | { | |
593 | return this._modules[name]; | |
594 | }, | |
595 | ||
596 | /** | |
597 | * @param {string} name | |
598 | * @param {string} source | |
599 | * @return {Object} | |
600 | */ | |
601 | injectModule: function(name, source, host) | |
602 | { | |
603 | delete this._modules[name]; | |
604 | var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")"); | |
605 | if (typeof moduleFunction !== "function") { | |
606 | if (inspectedGlobalObject.console) | |
607 | inspectedGlobalObject.console.error("Web Inspector error: A function was expected for module %s evaluation", name); | |
608 | return null; | |
609 | } | |
610 | var module = moduleFunction.call(inspectedGlobalObject, InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, host); | |
611 | this._modules[name] = module; | |
612 | return module; | |
613 | }, | |
614 | ||
40a37d08 A |
615 | _propertyDescriptors: function(object, ownProperties, ownAndGetterProperties) |
616 | { | |
617 | // Modes: | |
618 | // - ownProperties - only own properties and __proto__ | |
619 | // - ownAndGetterProperties - own properties, __proto__, and getters in the prototype chain | |
620 | // - neither - get all properties in the prototype chain, exclude __proto__ | |
621 | ||
622 | var descriptors = []; | |
623 | var nameProcessed = {}; | |
624 | nameProcessed["__proto__"] = null; | |
625 | ||
626 | function createFakeValueDescriptor(name, descriptor, isOwnProperty) | |
627 | { | |
628 | try { | |
629 | return {name: name, value: object[name], writable: descriptor.writable || false, configurable: descriptor.configurable || false, enumerable: descriptor.enumerable || false}; | |
630 | } catch (e) { | |
631 | var errorDescriptor = {name: name, value: e, wasThrown: true}; | |
632 | if (isOwnProperty) | |
633 | errorDescriptor.isOwn = true; | |
634 | return errorDescriptor; | |
635 | } | |
636 | } | |
637 | ||
638 | function processDescriptor(descriptor, isOwnProperty, possibleNativeBindingGetter) | |
639 | { | |
640 | // Own properties only. | |
641 | if (ownProperties) { | |
642 | if (isOwnProperty) | |
643 | descriptors.push(descriptor); | |
644 | return; | |
645 | } | |
646 | ||
647 | // Own and getter properties. | |
648 | if (ownAndGetterProperties) { | |
649 | if (isOwnProperty) { | |
650 | // Own property, include the descriptor as is. | |
651 | descriptors.push(descriptor); | |
652 | } else if (descriptor.hasOwnProperty("get") && descriptor.get) { | |
653 | // Getter property in the prototype chain. Create a fake value descriptor. | |
654 | descriptors.push(createFakeValueDescriptor(descriptor.name, descriptor, isOwnProperty)); | |
655 | } else if (possibleNativeBindingGetter) { | |
656 | // Possible getter property in the prototype chain. | |
657 | descriptors.push(descriptor); | |
658 | } | |
659 | return; | |
660 | } | |
661 | ||
662 | // All properties. | |
663 | descriptors.push(descriptor); | |
664 | } | |
665 | ||
666 | function processPropertyNames(o, names, isOwnProperty) | |
667 | { | |
668 | for (var i = 0; i < names.length; ++i) { | |
669 | var name = names[i]; | |
670 | if (nameProcessed[name] || name === "__proto__") | |
671 | continue; | |
672 | ||
673 | nameProcessed[name] = true; | |
674 | ||
675 | var descriptor = Object.getOwnPropertyDescriptor(o, name); | |
676 | if (!descriptor) { | |
677 | // FIXME: Bad descriptor. Can we get here? | |
678 | // Fall back to very restrictive settings. | |
679 | var fakeDescriptor = createFakeValueDescriptor(name, {writable: false, configurable: false, enumerable: false}, isOwnProperty); | |
680 | processDescriptor(fakeDescriptor, isOwnProperty); | |
681 | continue; | |
682 | } | |
683 | ||
684 | if (descriptor.hasOwnProperty("get") && descriptor.hasOwnProperty("set") && !descriptor.get && !descriptor.set) { | |
685 | // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete | |
686 | // Developers may create such a descriptors, so we should be resilient: | |
687 | // var x = {}; Object.defineProperty(x, "p", {get:undefined}); Object.getOwnPropertyDescriptor(x, "p") | |
688 | var fakeDescriptor = createFakeValueDescriptor(name, descriptor, isOwnProperty); | |
689 | processDescriptor(fakeDescriptor, isOwnProperty, true); | |
690 | continue; | |
691 | } | |
692 | ||
693 | descriptor.name = name; | |
694 | if (isOwnProperty) | |
695 | descriptor.isOwn = true; | |
696 | processDescriptor(descriptor, isOwnProperty); | |
697 | } | |
698 | } | |
699 | ||
700 | // Iterate prototype chain. | |
701 | for (var o = object; this._isDefined(o); o = o.__proto__) { | |
702 | var isOwnProperty = o === object; | |
703 | processPropertyNames(o, Object.getOwnPropertyNames(o), isOwnProperty); | |
704 | if (ownProperties) | |
705 | break; | |
706 | } | |
707 | ||
708 | // Include __proto__ at the end. | |
709 | try { | |
710 | if (object.__proto__) | |
711 | descriptors.push({name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true}); | |
712 | } catch (e) {} | |
713 | ||
714 | return descriptors; | |
715 | }, | |
716 | ||
81345200 A |
717 | /** |
718 | * @param {*} object | |
719 | * @return {boolean} | |
720 | */ | |
721 | _isDefined: function(object) | |
722 | { | |
723 | return !!object || this._isHTMLAllCollection(object); | |
724 | }, | |
725 | ||
726 | /** | |
727 | * @param {*} object | |
728 | * @return {boolean} | |
729 | */ | |
730 | _isHTMLAllCollection: function(object) | |
731 | { | |
732 | // document.all is reported as undefined, but we still want to process it. | |
733 | return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object); | |
734 | }, | |
735 | ||
736 | /** | |
737 | * @param {Object=} obj | |
738 | * @return {string?} | |
739 | */ | |
740 | _subtype: function(obj) | |
741 | { | |
742 | if (obj === null) | |
743 | return "null"; | |
744 | ||
745 | if (this.isPrimitiveValue(obj)) | |
746 | return null; | |
747 | ||
748 | if (this._isHTMLAllCollection(obj)) | |
749 | return "array"; | |
750 | ||
751 | var preciseType = InjectedScriptHost.type(obj); | |
752 | if (preciseType) | |
753 | return preciseType; | |
754 | ||
755 | // FireBug's array detection. | |
756 | try { | |
757 | if (typeof obj.splice === "function" && isFinite(obj.length)) | |
758 | return "array"; | |
759 | if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments. | |
760 | return "array"; | |
761 | } catch (e) { | |
762 | } | |
763 | ||
764 | // If owning frame has navigated to somewhere else window properties will be undefined. | |
765 | return null; | |
766 | }, | |
767 | ||
768 | /** | |
769 | * @param {*} obj | |
770 | * @return {string?} | |
771 | */ | |
772 | _describe: function(obj) | |
773 | { | |
774 | if (this.isPrimitiveValue(obj)) | |
775 | return null; | |
776 | ||
777 | obj = /** @type {Object} */ (obj); | |
778 | ||
779 | // Type is object, get subtype. | |
780 | var subtype = this._subtype(obj); | |
781 | ||
782 | if (subtype === "regexp") | |
783 | return this._toString(obj); | |
784 | ||
785 | if (subtype === "date") | |
786 | return this._toString(obj); | |
787 | ||
788 | if (subtype === "node") { | |
789 | var description = obj.nodeName.toLowerCase(); | |
790 | switch (obj.nodeType) { | |
791 | case 1 /* Node.ELEMENT_NODE */: | |
792 | description += obj.id ? "#" + obj.id : ""; | |
793 | var className = obj.className; | |
794 | description += className ? "." + className : ""; | |
795 | break; | |
796 | case 10 /*Node.DOCUMENT_TYPE_NODE */: | |
797 | description = "<!DOCTYPE " + description + ">"; | |
798 | break; | |
799 | } | |
800 | return description; | |
801 | } | |
802 | ||
803 | var className = InjectedScriptHost.internalConstructorName(obj); | |
804 | if (subtype === "array") { | |
805 | if (typeof obj.length === "number") | |
806 | className += "[" + obj.length + "]"; | |
807 | return className; | |
808 | } | |
809 | ||
810 | // NodeList in JSC is a function, check for array prior to this. | |
811 | if (typeof obj === "function") | |
812 | return this._toString(obj); | |
813 | ||
814 | if (className === "Object") { | |
815 | // In Chromium DOM wrapper prototypes will have Object as their constructor name, | |
816 | // get the real DOM wrapper name from the constructor property. | |
817 | var constructorName = obj.constructor && obj.constructor.name; | |
818 | if (constructorName) | |
819 | return constructorName; | |
820 | } | |
821 | return className; | |
822 | }, | |
823 | ||
824 | /** | |
825 | * @param {*} obj | |
826 | * @return {string} | |
827 | */ | |
828 | _toString: function(obj) | |
829 | { | |
830 | // We don't use String(obj) because inspectedGlobalObject.String is undefined if owning frame navigated to another page. | |
831 | return "" + obj; | |
832 | } | |
833 | } | |
834 | ||
835 | /** | |
836 | * @type {InjectedScript} | |
837 | * @const | |
838 | */ | |
839 | var injectedScript = new InjectedScript(); | |
840 | ||
841 | /** | |
842 | * @constructor | |
843 | * @param {*} object | |
844 | * @param {string=} objectGroupName | |
845 | * @param {boolean=} forceValueType | |
846 | * @param {boolean=} generatePreview | |
847 | * @param {?Array.<string>=} columnNames | |
848 | */ | |
849 | InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames) | |
850 | { | |
851 | this.type = typeof object; | |
852 | if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) { | |
853 | // We don't send undefined values over JSON. | |
854 | if (typeof object !== "undefined") | |
855 | this.value = object; | |
856 | ||
857 | // Null object is object with 'null' subtype' | |
858 | if (object === null) | |
859 | this.subtype = "null"; | |
860 | ||
861 | // Provide user-friendly number values. | |
862 | if (typeof object === "number") | |
863 | this.description = object + ""; | |
864 | return; | |
865 | } | |
866 | ||
867 | object = /** @type {Object} */ (object); | |
868 | ||
869 | this.objectId = injectedScript._bind(object, objectGroupName); | |
870 | var subtype = injectedScript._subtype(object); | |
871 | if (subtype) | |
872 | this.subtype = subtype; | |
873 | this.className = InjectedScriptHost.internalConstructorName(object); | |
874 | this.description = injectedScript._describe(object); | |
875 | ||
876 | if (generatePreview && (this.type === "object" || injectedScript._isHTMLAllCollection(object))) | |
877 | this.preview = this._generatePreview(object, undefined, columnNames); | |
878 | } | |
879 | ||
880 | InjectedScript.RemoteObject.prototype = { | |
881 | /** | |
882 | * @param {Object} object | |
883 | * @param {Array.<string>=} firstLevelKeys | |
884 | * @param {?Array.<string>=} secondLevelKeys | |
885 | * @return {Object} preview | |
886 | */ | |
887 | _generatePreview: function(object, firstLevelKeys, secondLevelKeys) | |
888 | { | |
889 | var preview = {}; | |
890 | preview.lossless = true; | |
891 | preview.overflow = false; | |
892 | preview.properties = []; | |
893 | ||
894 | var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys; | |
895 | var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0; | |
896 | ||
897 | var propertiesThreshold = { | |
898 | properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount), | |
899 | indexes: isTableRowsRequest ? 1000 : Math.max(100, firstLevelKeysCount) | |
900 | }; | |
901 | for (var o = object; injectedScript._isDefined(o); o = o.__proto__) | |
902 | this._generateProtoPreview(o, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys); | |
903 | return preview; | |
904 | }, | |
905 | ||
906 | /** | |
907 | * @param {Object} object | |
908 | * @param {Object} preview | |
909 | * @param {Object} propertiesThreshold | |
910 | * @param {Array.<string>=} firstLevelKeys | |
911 | * @param {Array.<string>=} secondLevelKeys | |
912 | */ | |
913 | _generateProtoPreview: function(object, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys) | |
914 | { | |
915 | var propertyNames = firstLevelKeys ? firstLevelKeys : Object.keys(/** @type {!Object} */(object)); | |
916 | try { | |
917 | for (var i = 0; i < propertyNames.length; ++i) { | |
918 | if (!propertiesThreshold.properties || !propertiesThreshold.indexes) { | |
919 | preview.overflow = true; | |
920 | preview.lossless = false; | |
921 | break; | |
922 | } | |
923 | var name = propertyNames[i]; | |
924 | if (this.subtype === "array" && name === "length") | |
925 | continue; | |
926 | ||
927 | var descriptor = Object.getOwnPropertyDescriptor(/** @type {!Object} */(object), name); | |
928 | if (!("value" in descriptor) || !descriptor.enumerable) { | |
929 | preview.lossless = false; | |
930 | continue; | |
931 | } | |
932 | ||
933 | var value = descriptor.value; | |
934 | if (value === null) { | |
935 | this._appendPropertyPreview(preview, { name: name, type: "object", value: "null" }, propertiesThreshold); | |
936 | continue; | |
937 | } | |
938 | ||
939 | const maxLength = 100; | |
940 | var type = typeof value; | |
941 | ||
942 | if (InjectedScript.primitiveTypes[type]) { | |
943 | if (type === "string") { | |
944 | if (value.length > maxLength) { | |
945 | value = this._abbreviateString(value, maxLength, true); | |
946 | preview.lossless = false; | |
947 | } | |
948 | value = value.replace(/\n/g, "\u21B5"); | |
949 | } | |
950 | this._appendPropertyPreview(preview, { name: name, type: type, value: value + "" }, propertiesThreshold); | |
951 | continue; | |
952 | } | |
953 | ||
954 | if (secondLevelKeys === null || secondLevelKeys) { | |
955 | var subPreview = this._generatePreview(value, secondLevelKeys || undefined); | |
956 | var property = { name: name, type: type, valuePreview: subPreview }; | |
957 | this._appendPropertyPreview(preview, property, propertiesThreshold); | |
958 | if (!subPreview.lossless) | |
959 | preview.lossless = false; | |
960 | if (subPreview.overflow) | |
961 | preview.overflow = true; | |
962 | continue; | |
963 | } | |
964 | ||
965 | preview.lossless = false; | |
966 | ||
967 | var subtype = injectedScript._subtype(value); | |
968 | var description = ""; | |
969 | if (type !== "function") | |
970 | description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp"); | |
971 | ||
972 | var property = { name: name, type: type, value: description }; | |
973 | if (subtype) | |
974 | property.subtype = subtype; | |
975 | this._appendPropertyPreview(preview, property, propertiesThreshold); | |
976 | } | |
977 | } catch (e) { | |
978 | } | |
979 | }, | |
980 | ||
981 | /** | |
982 | * @param {Object} preview | |
983 | * @param {Object} property | |
984 | * @param {Object} propertiesThreshold | |
985 | */ | |
986 | _appendPropertyPreview: function(preview, property, propertiesThreshold) | |
987 | { | |
988 | if (isNaN(property.name)) | |
989 | propertiesThreshold.properties--; | |
990 | else | |
991 | propertiesThreshold.indexes--; | |
992 | preview.properties.push(property); | |
993 | }, | |
994 | ||
995 | /** | |
996 | * @param {string} string | |
997 | * @param {number} maxLength | |
998 | * @param {boolean=} middle | |
999 | * @returns | |
1000 | */ | |
1001 | _abbreviateString: function(string, maxLength, middle) | |
1002 | { | |
1003 | if (string.length <= maxLength) | |
1004 | return string; | |
1005 | if (middle) { | |
1006 | var leftHalf = maxLength >> 1; | |
1007 | var rightHalf = maxLength - leftHalf - 1; | |
1008 | return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf); | |
1009 | } | |
1010 | return string.substr(0, maxLength) + "\u2026"; | |
1011 | } | |
1012 | } | |
1013 | /** | |
1014 | * @constructor | |
1015 | * @param {number} ordinal | |
1016 | * @param {Object} callFrame | |
1017 | */ | |
1018 | InjectedScript.CallFrameProxy = function(ordinal, callFrame) | |
1019 | { | |
1020 | this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}"; | |
1021 | this.functionName = (callFrame.type === "function" ? callFrame.functionName : ""); | |
1022 | this.location = { scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column }; | |
1023 | this.scopeChain = this._wrapScopeChain(callFrame); | |
1024 | this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace"); | |
1025 | } | |
1026 | ||
1027 | InjectedScript.CallFrameProxy.prototype = { | |
1028 | /** | |
1029 | * @param {Object} callFrame | |
1030 | * @return {!Array.<DebuggerAgent.Scope>} | |
1031 | */ | |
1032 | _wrapScopeChain: function(callFrame) | |
1033 | { | |
1034 | var scopeChain = callFrame.scopeChain; | |
1035 | var scopeChainProxy = []; | |
1036 | for (var i = 0; i < scopeChain.length; i++) { | |
1037 | var scope = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace"); | |
1038 | scopeChainProxy.push(scope); | |
1039 | } | |
1040 | return scopeChainProxy; | |
1041 | } | |
1042 | } | |
1043 | ||
1044 | /** | |
1045 | * @param {number} scopeTypeCode | |
1046 | * @param {*} scopeObject | |
1047 | * @param {string} groupId | |
1048 | * @return {!DebuggerAgent.Scope} | |
1049 | */ | |
1050 | InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId) { | |
1051 | const GLOBAL_SCOPE = 0; | |
1052 | const LOCAL_SCOPE = 1; | |
1053 | const WITH_SCOPE = 2; | |
1054 | const CLOSURE_SCOPE = 3; | |
1055 | const CATCH_SCOPE = 4; | |
1056 | ||
1057 | /** @type {!Object.<number, string>} */ | |
1058 | var scopeTypeNames = {}; | |
1059 | scopeTypeNames[GLOBAL_SCOPE] = "global"; | |
1060 | scopeTypeNames[LOCAL_SCOPE] = "local"; | |
1061 | scopeTypeNames[WITH_SCOPE] = "with"; | |
1062 | scopeTypeNames[CLOSURE_SCOPE] = "closure"; | |
1063 | scopeTypeNames[CATCH_SCOPE] = "catch"; | |
1064 | ||
1065 | return { | |
1066 | object: injectedScript._wrapObject(scopeObject, groupId), | |
1067 | type: /** @type {DebuggerAgent.ScopeType} */ (scopeTypeNames[scopeTypeCode]) | |
1068 | }; | |
1069 | } | |
1070 | ||
1071 | function BasicCommandLineAPI() | |
1072 | { | |
1073 | this.$_ = injectedScript._lastResult; | |
1074 | } | |
1075 | ||
1076 | return injectedScript; | |
1077 | }) |