]> git.saurik.com Git - apple/javascriptcore.git/blame - inspector/InjectedScriptSource.js
JavaScriptCore-7600.1.4.15.12.tar.gz
[apple/javascriptcore.git] / inspector / InjectedScriptSource.js
CommitLineData
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.
39var Object = {}.constructor;
40
41/**
42 * @constructor
43 */
44var 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 */
57InjectedScript.primitiveTypes = {
58 undefined: true,
59 boolean: true,
60 number: true,
61 string: true
62}
63
64InjectedScript.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 */
839var 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 */
849InjectedScript.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
880InjectedScript.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 */
1018InjectedScript.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
1027InjectedScript.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 */
1050InjectedScript.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
1071function BasicCommandLineAPI()
1072{
1073 this.$_ = injectedScript._lastResult;
1074}
1075
1076return injectedScript;
1077})