]> git.saurik.com Git - apple/javascriptcore.git/blob - inspector/InjectedScriptSource.js
JavaScriptCore-7601.1.46.3.tar.gz
[apple/javascriptcore.git] / inspector / InjectedScriptSource.js
1 /*
2 * Copyright (C) 2007, 2014-2015 Apple Inc. All rights reserved.
3 * Copyright (C) 2013 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 //# sourceURL=__WebInspectorInjectedScript__
31
32 (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) {
33
34 // Protect against Object overwritten by the user code.
35 var Object = {}.constructor;
36
37 function toString(obj)
38 {
39 return String(obj);
40 }
41
42 function toStringDescription(obj)
43 {
44 if (obj === 0 && 1 / obj < 0)
45 return "-0";
46
47 return toString(obj);
48 }
49
50 function isUInt32(obj)
51 {
52 if (typeof obj === "number")
53 return obj >>> 0 === obj && (obj > 0 || 1 / obj > 0);
54 return "" + (obj >>> 0) === obj;
55 }
56
57 function isSymbol(obj)
58 {
59 return typeof obj === "symbol";
60 }
61
62 var InjectedScript = function()
63 {
64 this._lastBoundObjectId = 1;
65 this._idToWrappedObject = {};
66 this._idToObjectGroupName = {};
67 this._objectGroups = {};
68 this._modules = {};
69 this._nextSavedResultIndex = 1;
70 this._savedResults = [];
71 }
72
73 InjectedScript.primitiveTypes = {
74 undefined: true,
75 boolean: true,
76 number: true,
77 string: true,
78 }
79
80 InjectedScript.CollectionMode = {
81 OwnProperties: 1 << 0, // own properties.
82 NativeGetterProperties: 1 << 1, // native getter properties in the prototype chain.
83 AllProperties: 1 << 2, // all properties in the prototype chain.
84 }
85
86 InjectedScript.prototype = {
87 isPrimitiveValue: function(object)
88 {
89 // FIXME(33716): typeof document.all is always 'undefined'.
90 return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
91 },
92
93 wrapObject: function(object, groupName, canAccessInspectedGlobalObject, generatePreview)
94 {
95 if (canAccessInspectedGlobalObject)
96 return this._wrapObject(object, groupName, false, generatePreview);
97 return this._fallbackWrapper(object);
98 },
99
100 setExceptionValue: function(value)
101 {
102 this._exceptionValue = value;
103 },
104
105 clearExceptionValue: function()
106 {
107 delete this._exceptionValue;
108 },
109
110 _fallbackWrapper: function(object)
111 {
112 var result = {};
113 result.type = typeof object;
114 if (this.isPrimitiveValue(object))
115 result.value = object;
116 else
117 result.description = toString(object);
118 return result;
119 },
120
121 wrapTable: function(canAccessInspectedGlobalObject, table, columns)
122 {
123 if (!canAccessInspectedGlobalObject)
124 return this._fallbackWrapper(table);
125
126 // FIXME: Currently columns are ignored. Instead, the frontend filters all
127 // properties based on the provided column names and in the provided order.
128 // Should we filter here too?
129
130 var columnNames = null;
131 if (typeof columns === "string")
132 columns = [columns];
133
134 if (InjectedScriptHost.subtype(columns) === "array") {
135 columnNames = [];
136 for (var i = 0; i < columns.length; ++i)
137 columnNames.push(toString(columns[i]));
138 }
139
140 return this._wrapObject(table, "console", false, true, columnNames);
141 },
142
143 inspectObject: function(object)
144 {
145 if (this._commandLineAPIImpl)
146 this._commandLineAPIImpl.inspect(object);
147 },
148
149 _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames)
150 {
151 try {
152 return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType, generatePreview, columnNames);
153 } catch (e) {
154 try {
155 var description = injectedScript._describe(e);
156 } catch (ex) {
157 var description = "<failed to convert exception to string>";
158 }
159 return new InjectedScript.RemoteObject(description);
160 }
161 },
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 _parseObjectId: function(objectId)
181 {
182 return InjectedScriptHost.evaluate("(" + objectId + ")");
183 },
184
185 releaseObjectGroup: function(objectGroupName)
186 {
187 if (objectGroupName === "console") {
188 delete this._lastResult;
189 this._nextSavedResultIndex = 1;
190 this._savedResults = [];
191 }
192
193 var group = this._objectGroups[objectGroupName];
194 if (!group)
195 return;
196
197 for (var i = 0; i < group.length; i++)
198 this._releaseObject(group[i]);
199
200 delete this._objectGroups[objectGroupName];
201 },
202
203 dispatch: function(methodName, args)
204 {
205 var argsArray = InjectedScriptHost.evaluate("(" + args + ")");
206 var result = this[methodName].apply(this, argsArray);
207 if (typeof result === "undefined") {
208 if (inspectedGlobalObject.console)
209 inspectedGlobalObject.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
210 result = null;
211 }
212 return result;
213 },
214
215 _getProperties: function(objectId, collectionMode, generatePreview)
216 {
217 var parsedObjectId = this._parseObjectId(objectId);
218 var object = this._objectForId(parsedObjectId);
219 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
220
221 if (!this._isDefined(object))
222 return false;
223
224 if (isSymbol(object))
225 return false;
226
227 var descriptors = this._propertyDescriptors(object, collectionMode);
228
229 // Go over properties, wrap object values.
230 for (var i = 0; i < descriptors.length; ++i) {
231 var descriptor = descriptors[i];
232 if ("get" in descriptor)
233 descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
234 if ("set" in descriptor)
235 descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
236 if ("value" in descriptor)
237 descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
238 if (!("configurable" in descriptor))
239 descriptor.configurable = false;
240 if (!("enumerable" in descriptor))
241 descriptor.enumerable = false;
242 if ("symbol" in descriptor)
243 descriptor.symbol = this._wrapObject(descriptor.symbol, objectGroupName);
244 }
245
246 return descriptors;
247 },
248
249 getProperties: function(objectId, ownProperties, generatePreview)
250 {
251 var collectionMode = ownProperties ? InjectedScript.CollectionMode.OwnProperties : InjectedScript.CollectionMode.AllProperties;
252 return this._getProperties(objectId, collectionMode, generatePreview);
253 },
254
255 getDisplayableProperties: function(objectId, generatePreview)
256 {
257 var collectionMode = InjectedScript.CollectionMode.OwnProperties | InjectedScript.CollectionMode.NativeGetterProperties;
258 return this._getProperties(objectId, collectionMode, generatePreview);
259 },
260
261 getInternalProperties: function(objectId, generatePreview)
262 {
263 var parsedObjectId = this._parseObjectId(objectId);
264 var object = this._objectForId(parsedObjectId);
265 var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
266
267 if (!this._isDefined(object))
268 return false;
269
270 if (isSymbol(object))
271 return false;
272
273 var descriptors = this._internalPropertyDescriptors(object);
274 if (!descriptors)
275 return [];
276
277 // Go over properties, wrap object values.
278 for (var i = 0; i < descriptors.length; ++i) {
279 var descriptor = descriptors[i];
280 if ("value" in descriptor)
281 descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
282 }
283
284 return descriptors;
285 },
286
287 getCollectionEntries: function(objectId, objectGroupName, startIndex, numberToFetch)
288 {
289 var parsedObjectId = this._parseObjectId(objectId);
290 var object = this._objectForId(parsedObjectId);
291 var objectGroupName = objectGroupName || this._idToObjectGroupName[parsedObjectId.id];
292
293 if (!this._isDefined(object))
294 return;
295
296 if (typeof object !== "object")
297 return;
298
299 var entries = this._entries(object, InjectedScriptHost.subtype(object), startIndex, numberToFetch);
300 return entries.map(function(entry) {
301 entry.value = injectedScript._wrapObject(entry.value, objectGroupName, false, true);
302 if ("key" in entry)
303 entry.key = injectedScript._wrapObject(entry.key, objectGroupName, false, true);
304 return entry;
305 });
306 },
307
308 saveResult: function(callArgumentJSON)
309 {
310 this._savedResultIndex = 0;
311
312 try {
313 var callArgument = InjectedScriptHost.evaluate("(" + callArgumentJSON + ")");
314 var value = this._resolveCallArgument(callArgument);
315 this._saveResult(value);
316 } catch (e) {}
317
318 return this._savedResultIndex;
319 },
320
321 getFunctionDetails: function(functionId)
322 {
323 var parsedFunctionId = this._parseObjectId(functionId);
324 var func = this._objectForId(parsedFunctionId);
325 if (typeof func !== "function")
326 return "Cannot resolve function by id.";
327 var details = InjectedScriptHost.functionDetails(func);
328 if (!details)
329 return "Cannot resolve function details.";
330 if ("rawScopes" in details) {
331 var objectGroupName = this._idToObjectGroupName[parsedFunctionId.id];
332 var rawScopes = details.rawScopes;
333 var scopes = [];
334 delete details.rawScopes;
335 for (var i = 0; i < rawScopes.length; i++)
336 scopes.push(InjectedScript.CallFrameProxy._createScopeJson(rawScopes[i].type, rawScopes[i].object, objectGroupName));
337 details.scopeChain = scopes;
338 }
339 return details;
340 },
341
342 releaseObject: function(objectId)
343 {
344 var parsedObjectId = this._parseObjectId(objectId);
345 this._releaseObject(parsedObjectId.id);
346 },
347
348 _releaseObject: function(id)
349 {
350 delete this._idToWrappedObject[id];
351 delete this._idToObjectGroupName[id];
352 },
353
354 evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
355 {
356 return this._evaluateAndWrap(InjectedScriptHost.evaluate, InjectedScriptHost, expression, objectGroup, false, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
357 },
358
359 callFunctionOn: function(objectId, expression, args, returnByValue, generatePreview)
360 {
361 var parsedObjectId = this._parseObjectId(objectId);
362 var object = this._objectForId(parsedObjectId);
363 if (!this._isDefined(object))
364 return "Could not find object with given id";
365
366 if (args) {
367 var resolvedArgs = [];
368 var callArgs = InjectedScriptHost.evaluate(args);
369 for (var i = 0; i < callArgs.length; ++i) {
370 try {
371 resolvedArgs[i] = this._resolveCallArgument(callArgs[i]);
372 } catch (e) {
373 return String(e);
374 }
375 }
376 }
377
378 try {
379 var objectGroup = this._idToObjectGroupName[parsedObjectId.id];
380 var func = InjectedScriptHost.evaluate("(" + expression + ")");
381 if (typeof func !== "function")
382 return "Given expression does not evaluate to a function";
383
384 return {
385 wasThrown: false,
386 result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue, generatePreview)
387 };
388 } catch (e) {
389 return this._createThrownValue(e, objectGroup);
390 }
391 },
392
393 _resolveCallArgument: function(callArgumentJSON)
394 {
395 if ("value" in callArgumentJSON)
396 return callArgumentJSON.value;
397
398 var objectId = callArgumentJSON.objectId;
399 if (objectId) {
400 var parsedArgId = this._parseObjectId(objectId);
401 if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
402 throw "Arguments should belong to the same JavaScript world as the target object.";
403
404 var resolvedArg = this._objectForId(parsedArgId);
405 if (!this._isDefined(resolvedArg))
406 throw "Could not find object with given id";
407
408 return resolvedArg;
409 }
410
411 return undefined;
412 },
413
414 _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
415 {
416 try {
417 this._savedResultIndex = 0;
418
419 var returnObject = {
420 wasThrown: false,
421 result: this._wrapObject(this._evaluateOn(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult), objectGroup, returnByValue, generatePreview)
422 };
423
424 if (saveResult && this._savedResultIndex)
425 returnObject.savedResultIndex = this._savedResultIndex;
426
427 return returnObject;
428 } catch (e) {
429 return this._createThrownValue(e, objectGroup);
430 }
431 },
432
433 _createThrownValue: function(value, objectGroup)
434 {
435 var remoteObject = this._wrapObject(value, objectGroup);
436 try {
437 remoteObject.description = toStringDescription(value);
438 } catch (e) {}
439 return {
440 wasThrown: true,
441 result: remoteObject
442 };
443 },
444
445 _evaluateOn: function(evalFunction, object, objectGroup, expression, isEvalOnCallFrame, injectCommandLineAPI, saveResult)
446 {
447 var commandLineAPI = null;
448 if (injectCommandLineAPI) {
449 if (this.CommandLineAPI)
450 commandLineAPI = new this.CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
451 else
452 commandLineAPI = new BasicCommandLineAPI;
453 }
454
455 if (isEvalOnCallFrame) {
456 // We can only use this approach if the evaluate function is the true 'eval'. That allows us to use it with
457 // the 'eval' identifier when calling it. Using 'eval' grants access to the local scope of the closure we
458 // create that provides the command line APIs.
459
460 var parameters = [InjectedScriptHost.evaluate, expression];
461 var expressionFunctionBody = "" +
462 "var global = Function('return this')() || (1, eval)('this');" +
463 "var __originalEval = global.eval; global.eval = __eval;" +
464 "try { return eval(__currentExpression); }" +
465 "finally { global.eval = __originalEval; }";
466
467 if (commandLineAPI) {
468 // To avoid using a 'with' statement (which fails in strict mode and requires injecting the API object)
469 // we instead create a closure where we evaluate the expression. The command line APIs are passed as
470 // parameters to the closure so they are in scope but not injected. This allows the code evaluated in
471 // the console to stay in strict mode (if is was already set), or to get strict mode by prefixing
472 // expressions with 'use strict';.
473
474 var parameterNames = Object.getOwnPropertyNames(commandLineAPI);
475 for (var i = 0; i < parameterNames.length; ++i)
476 parameters.push(commandLineAPI[parameterNames[i]]);
477
478 var expressionFunctionString = "(function(__eval, __currentExpression, " + parameterNames.join(", ") + ") { " + expressionFunctionBody + " })";
479 } else {
480 // Use a closure in this case too to keep the same behavior of 'var' being captured by the closure instead
481 // of leaking out into the calling scope.
482 var expressionFunctionString = "(function(__eval, __currentExpression) { " + expressionFunctionBody + " })";
483 }
484
485 // Bind 'this' to the function expression using another closure instead of Function.prototype.bind. This ensures things will work if the page replaces bind.
486 var boundExpressionFunctionString = "(function(__function, __thisObject) { return function() { return __function.apply(__thisObject, arguments) }; })(" + expressionFunctionString + ", this)";
487 var expressionFunction = evalFunction.call(object, boundExpressionFunctionString);
488 var result = expressionFunction.apply(null, parameters);
489
490 if (objectGroup === "console" && saveResult)
491 this._saveResult(result);
492
493 return result;
494 }
495
496 // When not evaluating on a call frame we use a 'with' statement to allow var and function statements to leak
497 // into the global scope. This allow them to stick around between evaluations.
498
499 try {
500 if (commandLineAPI) {
501 if (inspectedGlobalObject.console)
502 inspectedGlobalObject.console.__commandLineAPI = commandLineAPI;
503 else
504 inspectedGlobalObject.__commandLineAPI = commandLineAPI;
505 expression = "with ((this && (this.console ? this.console.__commandLineAPI : this.__commandLineAPI)) || {}) { " + expression + "\n}";
506 }
507
508 var result = evalFunction.call(inspectedGlobalObject, expression);
509
510 if (objectGroup === "console" && saveResult)
511 this._saveResult(result);
512
513 return result;
514 } finally {
515 if (commandLineAPI) {
516 if (inspectedGlobalObject.console)
517 delete inspectedGlobalObject.console.__commandLineAPI;
518 else
519 delete inspectedGlobalObject.__commandLineAPI;
520 }
521 }
522 },
523
524 wrapCallFrames: function(callFrame)
525 {
526 if (!callFrame)
527 return false;
528
529 var result = [];
530 var depth = 0;
531 do {
532 result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
533 callFrame = callFrame.caller;
534 } while (callFrame);
535 return result;
536 },
537
538 evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue, generatePreview, saveResult)
539 {
540 var callFrame = this._callFrameForId(topCallFrame, callFrameId);
541 if (!callFrame)
542 return "Could not find call frame with given id";
543 return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue, generatePreview, saveResult);
544 },
545
546 _callFrameForId: function(topCallFrame, callFrameId)
547 {
548 var parsedCallFrameId = InjectedScriptHost.evaluate("(" + callFrameId + ")");
549 var ordinal = parsedCallFrameId["ordinal"];
550 var callFrame = topCallFrame;
551 while (--ordinal >= 0 && callFrame)
552 callFrame = callFrame.caller;
553 return callFrame;
554 },
555
556 _objectForId: function(objectId)
557 {
558 return this._idToWrappedObject[objectId.id];
559 },
560
561 findObjectById: function(objectId)
562 {
563 var parsedObjectId = this._parseObjectId(objectId);
564 return this._objectForId(parsedObjectId);
565 },
566
567 module: function(name)
568 {
569 return this._modules[name];
570 },
571
572 injectModule: function(name, source, host)
573 {
574 delete this._modules[name];
575
576 var moduleFunction = InjectedScriptHost.evaluate("(" + source + ")");
577 if (typeof moduleFunction !== "function") {
578 if (inspectedGlobalObject.console)
579 inspectedGlobalObject.console.error("Web Inspector error: A function was expected for module %s evaluation", name);
580 return null;
581 }
582
583 var module = moduleFunction.call(inspectedGlobalObject, InjectedScriptHost, inspectedGlobalObject, injectedScriptId, this, host);
584 this._modules[name] = module;
585 return module;
586 },
587
588 _internalPropertyDescriptors: function(object, completeDescriptor)
589 {
590 var internalProperties = InjectedScriptHost.getInternalProperties(object);
591 if (!internalProperties)
592 return null;
593
594 var descriptors = [];
595 for (var i = 0; i < internalProperties.length; i++) {
596 var property = internalProperties[i];
597 var descriptor = {name: property.name, value: property.value};
598 if (completeDescriptor) {
599 descriptor.writable = false;
600 descriptor.configurable = false;
601 descriptor.enumerable = false;
602 descriptor.isOwn = true;
603 }
604 descriptors.push(descriptor);
605 }
606 return descriptors;
607 },
608
609 _propertyDescriptors: function(object, collectionMode)
610 {
611 var descriptors = [];
612 var nameProcessed = new Set;
613
614 function createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, possibleNativeBindingGetter)
615 {
616 try {
617 var descriptor = {name, value: object[name], writable: descriptor.writable || false, configurable: descriptor.configurable || false, enumerable: descriptor.enumerable || false};
618 if (possibleNativeBindingGetter)
619 descriptor.nativeGetter = true;
620 if (isOwnProperty)
621 descriptor.isOwn = true;
622 if (symbol)
623 descriptor.symbol = symbol;
624 return descriptor;
625 } catch (e) {
626 var errorDescriptor = {name, value: e, wasThrown: true};
627 if (isOwnProperty)
628 errorDescriptor.isOwn = true;
629 if (symbol)
630 errorDescriptor.symbol = symbol;
631 return errorDescriptor;
632 }
633 }
634
635 function processDescriptor(descriptor, isOwnProperty, possibleNativeBindingGetter)
636 {
637 // All properties.
638 if (collectionMode & InjectedScript.CollectionMode.AllProperties) {
639 descriptors.push(descriptor);
640 return;
641 }
642
643 // Own properties.
644 if (collectionMode & InjectedScript.CollectionMode.OwnProperties && isOwnProperty) {
645 descriptors.push(descriptor);
646 return;
647 }
648
649 // Native Getter properties.
650 if (collectionMode & InjectedScript.CollectionMode.NativeGetterProperties) {
651 // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
652 // if (descriptor.hasOwnProperty("get") && descriptor.get && isNativeFunction(descriptor.get)) { ... }
653
654 if (possibleNativeBindingGetter) {
655 // Possible getter property in the prototype chain.
656 descriptors.push(descriptor);
657 return;
658 }
659 }
660 }
661
662 function processProperties(o, properties, isOwnProperty)
663 {
664 for (var i = 0; i < properties.length; ++i) {
665 var property = properties[i];
666 if (nameProcessed.has(property) || property === "__proto__")
667 continue;
668
669 nameProcessed.add(property);
670
671 var name = toString(property);
672 var symbol = isSymbol(property) ? property : null;
673
674 var descriptor = Object.getOwnPropertyDescriptor(o, property);
675 if (!descriptor) {
676 // FIXME: Bad descriptor. Can we get here?
677 // Fall back to very restrictive settings.
678 var fakeDescriptor = createFakeValueDescriptor(name, symbol, {writable: false, configurable: false, enumerable: false}, isOwnProperty);
679 processDescriptor(fakeDescriptor, isOwnProperty);
680 continue;
681 }
682
683 if (descriptor.hasOwnProperty("get") && descriptor.hasOwnProperty("set") && !descriptor.get && !descriptor.set) {
684 // FIXME: <https://webkit.org/b/140575> Web Inspector: Native Bindings Descriptors are Incomplete
685 // Developers may create such a descriptors, so we should be resilient:
686 // var x = {}; Object.defineProperty(x, "p", {get:undefined}); Object.getOwnPropertyDescriptor(x, "p")
687 var fakeDescriptor = createFakeValueDescriptor(name, symbol, descriptor, isOwnProperty, true);
688 processDescriptor(fakeDescriptor, isOwnProperty, true);
689 continue;
690 }
691
692 descriptor.name = name;
693 if (isOwnProperty)
694 descriptor.isOwn = true;
695 if (symbol)
696 descriptor.symbol = symbol;
697 processDescriptor(descriptor, isOwnProperty);
698 }
699 }
700
701 function arrayIndexPropertyNames(o, length)
702 {
703 var array = new Array(length);
704 for (var i = 0; i < length; ++i) {
705 if (i in o)
706 array.push("" + i);
707 }
708 return array;
709 }
710
711 // FIXME: <https://webkit.org/b/143589> Web Inspector: Better handling for large collections in Object Trees
712 // For array types with a large length we attempt to skip getOwnPropertyNames and instead just sublist of indexes.
713 var isArrayTypeWithLargeLength = false;
714 try {
715 isArrayTypeWithLargeLength = injectedScript._subtype(object) === "array" && isFinite(object.length) && object.length > 100;
716 } catch(e) {}
717
718 for (var o = object; this._isDefined(o); o = o.__proto__) {
719 var isOwnProperty = o === object;
720
721 if (isArrayTypeWithLargeLength && isOwnProperty)
722 processProperties(o, arrayIndexPropertyNames(o, 100), isOwnProperty);
723 else {
724 processProperties(o, Object.getOwnPropertyNames(o), isOwnProperty);
725 if (Object.getOwnPropertySymbols)
726 processProperties(o, Object.getOwnPropertySymbols(o), isOwnProperty);
727 }
728
729 if (collectionMode === InjectedScript.CollectionMode.OwnProperties)
730 break;
731 }
732
733 // Always include __proto__ at the end.
734 try {
735 if (object.__proto__)
736 descriptors.push({name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false, isOwn: true});
737 } catch (e) {}
738
739 return descriptors;
740 },
741
742 _isDefined: function(object)
743 {
744 return !!object || this._isHTMLAllCollection(object);
745 },
746
747 _isHTMLAllCollection: function(object)
748 {
749 // document.all is reported as undefined, but we still want to process it.
750 return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
751 },
752
753 _subtype: function(obj)
754 {
755 if (obj === null)
756 return "null";
757
758 if (this.isPrimitiveValue(obj) || isSymbol(obj))
759 return null;
760
761 if (this._isHTMLAllCollection(obj))
762 return "array";
763
764 var preciseType = InjectedScriptHost.subtype(obj);
765 if (preciseType)
766 return preciseType;
767
768 // FireBug's array detection.
769 try {
770 if (typeof obj.splice === "function" && isFinite(obj.length))
771 return "array";
772 } catch (e) {
773 }
774
775 return null;
776 },
777
778 _nodeDescription: function(node)
779 {
780 var isXMLDocument = node.ownerDocument && !!node.ownerDocument.xmlVersion;
781 var description = isXMLDocument ? node.nodeName : node.nodeName.toLowerCase();
782
783 switch (node.nodeType) {
784 case 1: // Node.ELEMENT_NODE
785 if (node.id)
786 description += "#" + node.id;
787 if (node.hasAttribute("class")) {
788 // Using .getAttribute() is a workaround for SVG*Element.className returning SVGAnimatedString,
789 // which doesn't have any useful String methods. See <https://webkit.org/b/145363/>.
790 description += "." + node.getAttribute("class").trim().replace(/\s+/g, ".");
791 }
792 return description;
793
794 default:
795 return description;
796 }
797 },
798
799 _classPreview: function(classConstructorValue)
800 {
801 return "class " + classConstructorValue.name;
802 },
803
804 _nodePreview: function(node)
805 {
806 var isXMLDocument = node.ownerDocument && !!node.ownerDocument.xmlVersion;
807 var nodeName = isXMLDocument ? node.nodeName : node.nodeName.toLowerCase();
808
809 switch (node.nodeType) {
810 case 1: // Node.ELEMENT_NODE
811 if (node.id)
812 return "<" + nodeName + " id=\"" + node.id + "\">";
813 if (node.className)
814 return "<" + nodeName + " class=\"" + node.className + "\">";
815 if (nodeName === "input" && node.type)
816 return "<" + nodeName + " type=\"" + node.type + "\">";
817 return "<" + nodeName + ">";
818
819 case 3: // Node.TEXT_NODE
820 return nodeName + " \"" + node.nodeValue + "\"";
821
822 case 8: // Node.COMMENT_NODE
823 return "<!--" + node.nodeValue + "-->";
824
825 case 10: // Node.DOCUMENT_TYPE_NODE
826 return "<!DOCTYPE " + nodeName + ">";
827
828 default:
829 return nodeName;
830 }
831 },
832
833 _describe: function(obj)
834 {
835 if (this.isPrimitiveValue(obj))
836 return null;
837
838 if (isSymbol(obj))
839 return toString(obj);
840
841 var subtype = this._subtype(obj);
842
843 if (subtype === "regexp")
844 return toString(obj);
845
846 if (subtype === "date")
847 return toString(obj);
848
849 if (subtype === "error")
850 return toString(obj);
851
852 if (subtype === "node")
853 return this._nodeDescription(obj);
854
855 var className = InjectedScriptHost.internalConstructorName(obj);
856 if (subtype === "array")
857 return className;
858
859 // NodeList in JSC is a function, check for array prior to this.
860 if (typeof obj === "function")
861 return toString(obj);
862
863 // If Object, try for a better name from the constructor.
864 if (className === "Object") {
865 var constructorName = obj.constructor && obj.constructor.name;
866 if (constructorName)
867 return constructorName;
868 }
869
870 return className;
871 },
872
873 _getSetEntries: function(object, skip, numberToFetch)
874 {
875 var entries = [];
876
877 for (var value of object) {
878 if (skip > 0) {
879 skip--;
880 continue;
881 }
882
883 entries.push({value});
884
885 if (numberToFetch && entries.length === numberToFetch)
886 break;
887 }
888
889 return entries;
890 },
891
892 _getMapEntries: function(object, skip, numberToFetch)
893 {
894 var entries = [];
895
896 for (var [key, value] of object) {
897 if (skip > 0) {
898 skip--;
899 continue;
900 }
901
902 entries.push({key, value});
903
904 if (numberToFetch && entries.length === numberToFetch)
905 break;
906 }
907
908 return entries;
909 },
910
911 _getWeakMapEntries: function(object, numberToFetch)
912 {
913 return InjectedScriptHost.weakMapEntries(object, numberToFetch);
914 },
915
916 _getWeakSetEntries: function(object, numberToFetch)
917 {
918 return InjectedScriptHost.weakSetEntries(object, numberToFetch);
919 },
920
921 _getIteratorEntries: function(object, numberToFetch)
922 {
923 return InjectedScriptHost.iteratorEntries(object, numberToFetch);
924 },
925
926 _entries: function(object, subtype, startIndex, numberToFetch)
927 {
928 if (subtype === "set")
929 return this._getSetEntries(object, startIndex, numberToFetch);
930 if (subtype === "map")
931 return this._getMapEntries(object, startIndex, numberToFetch);
932 if (subtype === "weakmap")
933 return this._getWeakMapEntries(object, numberToFetch);
934 if (subtype === "weakset")
935 return this._getWeakSetEntries(object, numberToFetch);
936 if (subtype === "iterator")
937 return this._getIteratorEntries(object, numberToFetch);
938
939 throw "unexpected type";
940 },
941
942 _saveResult: function(result)
943 {
944 this._lastResult = result;
945
946 if (result === undefined || result === null)
947 return;
948
949 var existingIndex = this._savedResults.indexOf(result);
950 if (existingIndex !== -1) {
951 this._savedResultIndex = existingIndex;
952 return;
953 }
954
955 this._savedResultIndex = this._nextSavedResultIndex;
956 this._savedResults[this._nextSavedResultIndex++] = result;
957
958 // $n is limited from $1-$99. $0 is special.
959 if (this._nextSavedResultIndex >= 100)
960 this._nextSavedResultIndex = 1;
961 },
962
963 _savedResult: function(index)
964 {
965 return this._savedResults[index];
966 }
967 }
968
969 var injectedScript = new InjectedScript;
970
971
972 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType, generatePreview, columnNames)
973 {
974 this.type = typeof object;
975
976 if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object))
977 this.type = "object";
978
979 if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
980 // We don't send undefined values over JSON.
981 if (this.type !== "undefined")
982 this.value = object;
983
984 // Null object is object with 'null' subtype.
985 if (object === null)
986 this.subtype = "null";
987
988 // Provide user-friendly number values.
989 if (this.type === "number")
990 this.description = toStringDescription(object);
991 return;
992 }
993
994 this.objectId = injectedScript._bind(object, objectGroupName);
995
996 var subtype = injectedScript._subtype(object);
997 if (subtype)
998 this.subtype = subtype;
999
1000 this.className = InjectedScriptHost.internalConstructorName(object);
1001 this.description = injectedScript._describe(object);
1002
1003 if (subtype === "array")
1004 this.size = typeof object.length === "number" ? object.length : 0;
1005 else if (subtype === "set" || subtype === "map")
1006 this.size = object.size;
1007 else if (subtype === "weakmap")
1008 this.size = InjectedScriptHost.weakMapSize(object);
1009 else if (subtype === "weakset")
1010 this.size = InjectedScriptHost.weakSetSize(object);
1011 else if (subtype === "class") {
1012 this.classPrototype = injectedScript._wrapObject(object.prototype, objectGroupName);
1013 this.className = object.name;
1014 }
1015
1016 if (generatePreview && this.type === "object")
1017 this.preview = this._generatePreview(object, undefined, columnNames);
1018 }
1019
1020 InjectedScript.RemoteObject.prototype = {
1021 _emptyPreview: function()
1022 {
1023 var preview = {
1024 type: this.type,
1025 description: this.description || toString(this.value),
1026 lossless: true,
1027 };
1028
1029 if (this.subtype) {
1030 preview.subtype = this.subtype;
1031 if (this.subtype !== "null") {
1032 preview.overflow = false;
1033 preview.properties = [];
1034 }
1035 }
1036
1037 if ("size" in this)
1038 preview.size = this.size;
1039
1040 return preview;
1041 },
1042
1043 _createObjectPreviewForValue: function(value)
1044 {
1045 var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, true, undefined);
1046 if (remoteObject.objectId)
1047 injectedScript.releaseObject(remoteObject.objectId);
1048 if (remoteObject.classPrototype && remoteObject.classPrototype.objectId)
1049 injectedScript.releaseObject(remoteObject.classPrototype.objectId);
1050
1051 return remoteObject.preview || remoteObject._emptyPreview();
1052 },
1053
1054 _generatePreview: function(object, firstLevelKeys, secondLevelKeys)
1055 {
1056 var preview = this._emptyPreview();
1057
1058 // Primitives just have a value.
1059 if (this.type !== "object")
1060 return;
1061
1062 var isTableRowsRequest = secondLevelKeys === null || secondLevelKeys;
1063 var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
1064
1065 var propertiesThreshold = {
1066 properties: isTableRowsRequest ? 1000 : Math.max(5, firstLevelKeysCount),
1067 indexes: isTableRowsRequest ? 1000 : Math.max(10, firstLevelKeysCount)
1068 };
1069
1070 try {
1071 // Maps, Sets, and Iterators have entries.
1072 if (this.subtype === "map" || this.subtype === "set" || this.subtype === "weakmap" || this.subtype === "weakset" || this.subtype === "iterator")
1073 this._appendEntryPreviews(object, preview);
1074
1075 preview.properties = [];
1076
1077 // Internal Properties.
1078 var internalPropertyDescriptors = injectedScript._internalPropertyDescriptors(object, true);
1079 if (internalPropertyDescriptors) {
1080 this._appendPropertyPreviews(preview, internalPropertyDescriptors, true, propertiesThreshold, firstLevelKeys, secondLevelKeys);
1081 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
1082 return preview;
1083 }
1084
1085 if (preview.entries)
1086 return preview;
1087
1088 // Properties.
1089 var descriptors = injectedScript._propertyDescriptors(object, InjectedScript.CollectionMode.AllProperties);
1090 this._appendPropertyPreviews(preview, descriptors, false, propertiesThreshold, firstLevelKeys, secondLevelKeys);
1091 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
1092 return preview;
1093 } catch (e) {
1094 preview.lossless = false;
1095 }
1096
1097 return preview;
1098 },
1099
1100 _appendPropertyPreviews: function(preview, descriptors, internal, propertiesThreshold, firstLevelKeys, secondLevelKeys)
1101 {
1102 for (var descriptor of descriptors) {
1103 // Seen enough.
1104 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
1105 break;
1106
1107 // Error in descriptor.
1108 if (descriptor.wasThrown) {
1109 preview.lossless = false;
1110 continue;
1111 }
1112
1113 // Do not show "__proto__" in preview.
1114 var name = descriptor.name;
1115 if (name === "__proto__")
1116 continue;
1117
1118 // For arrays, only allow indexes.
1119 if (this.subtype === "array" && !isUInt32(name))
1120 continue;
1121
1122 // Do not show non-enumerable non-own properties. Special case to allow array indexes that may be on the prototype.
1123 if (!descriptor.enumerable && !descriptor.isOwn && this.subtype !== "array")
1124 continue;
1125
1126 // If we have a filter, only show properties in the filter.
1127 // FIXME: Currently these filters do nothing on the backend.
1128 if (firstLevelKeys && !firstLevelKeys.includes(name))
1129 continue;
1130
1131 // Getter/setter.
1132 if (!("value" in descriptor)) {
1133 preview.lossless = false;
1134 this._appendPropertyPreview(preview, internal, {name, type: "accessor"}, propertiesThreshold);
1135 continue;
1136 }
1137
1138 // Null value.
1139 var value = descriptor.value;
1140 if (value === null) {
1141 this._appendPropertyPreview(preview, internal, {name, type: "object", subtype: "null", value: "null"}, propertiesThreshold);
1142 continue;
1143 }
1144
1145 // Ignore non-enumerable functions.
1146 var type = typeof value;
1147 if (!descriptor.enumerable && type === "function")
1148 continue;
1149
1150 // Fix type of document.all.
1151 if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
1152 type = "object";
1153
1154 // Primitive.
1155 const maxLength = 100;
1156 if (InjectedScript.primitiveTypes[type]) {
1157 if (type === "string" && value.length > maxLength) {
1158 value = this._abbreviateString(value, maxLength, true);
1159 preview.lossless = false;
1160 }
1161 this._appendPropertyPreview(preview, internal, {name, type, value: toStringDescription(value)}, propertiesThreshold);
1162 continue;
1163 }
1164
1165 // Symbol.
1166 if (isSymbol(value)) {
1167 var symbolString = toString(value);
1168 if (symbolString.length > maxLength) {
1169 symbolString = this._abbreviateString(symbolString, maxLength, true);
1170 preview.lossless = false;
1171 }
1172 this._appendPropertyPreview(preview, internal, {name, type, value: symbolString}, propertiesThreshold);
1173 return;
1174 }
1175
1176 // Object.
1177 var property = {name, type};
1178 var subtype = injectedScript._subtype(value);
1179 if (subtype)
1180 property.subtype = subtype;
1181
1182 // Second level.
1183 if ((secondLevelKeys === null || secondLevelKeys) || this._isPreviewableObject(value)) {
1184 // FIXME: If we want secondLevelKeys filter to continue we would need some refactoring.
1185 var subPreview = this._createObjectPreviewForValue(value);
1186 property.valuePreview = subPreview;
1187 if (!subPreview.lossless)
1188 preview.lossless = false;
1189 if (subPreview.overflow)
1190 preview.overflow = true;
1191 } else {
1192 var description = "";
1193 if (type !== "function" || subtype === "class") {
1194 var fullDescription;
1195 if (subtype === "class")
1196 fullDescription = "class " + value.name;
1197 else if (subtype === "node")
1198 fullDescription = injectedScript._nodePreview(value);
1199 else
1200 fullDescription = injectedScript._describe(value);
1201 description = this._abbreviateString(fullDescription, maxLength, subtype === "regexp");
1202 }
1203 property.value = description;
1204 preview.lossless = false;
1205 }
1206
1207 this._appendPropertyPreview(preview, internal, property, propertiesThreshold);
1208 }
1209 },
1210
1211 _appendPropertyPreview: function(preview, internal, property, propertiesThreshold)
1212 {
1213 if (toString(property.name >>> 0) === property.name)
1214 propertiesThreshold.indexes--;
1215 else
1216 propertiesThreshold.properties--;
1217
1218 if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) {
1219 preview.overflow = true;
1220 preview.lossless = false;
1221 return;
1222 }
1223
1224 if (internal)
1225 property.internal = true;
1226
1227 preview.properties.push(property);
1228 },
1229
1230 _appendEntryPreviews: function(object, preview)
1231 {
1232 // Fetch 6, but only return 5, so we can tell if we overflowed.
1233 var entries = injectedScript._entries(object, this.subtype, 0, 6);
1234 if (!entries)
1235 return;
1236
1237 if (entries.length > 5) {
1238 entries.pop();
1239 preview.overflow = true;
1240 preview.lossless = false;
1241 }
1242
1243 preview.entries = entries.map(function(entry) {
1244 entry.value = this._createObjectPreviewForValue(entry.value);
1245 if ("key" in entry)
1246 entry.key = this._createObjectPreviewForValue(entry.key);
1247 return entry;
1248 }, this);
1249 },
1250
1251 _isPreviewableObject: function(object)
1252 {
1253 return this._isPreviewableObjectInternal(object, new Set, 1);
1254 },
1255
1256 _isPreviewableObjectInternal: function(object, knownObjects, depth)
1257 {
1258 // Deep object.
1259 if (depth > 3)
1260 return false;
1261
1262 // Primitive.
1263 if (injectedScript.isPrimitiveValue(object) || isSymbol(object))
1264 return true;
1265
1266 // Null.
1267 if (object === null)
1268 return true;
1269
1270 // Cyclic objects.
1271 if (knownObjects.has(object))
1272 return false;
1273
1274 ++depth;
1275 knownObjects.add(object);
1276
1277 // Arrays are simple if they have 5 or less simple objects.
1278 var subtype = injectedScript._subtype(object);
1279 if (subtype === "array") {
1280 var length = object.length;
1281 if (length > 5)
1282 return false;
1283 for (var i = 0; i < length; ++i) {
1284 if (!this._isPreviewableObjectInternal(object[i], knownObjects, depth))
1285 return false;
1286 }
1287 return true;
1288 }
1289
1290 // Not a basic object.
1291 if (object.__proto__ && object.__proto__.__proto__)
1292 return false;
1293
1294 // Objects are simple if they have 3 or less simple properties.
1295 var ownPropertyNames = Object.getOwnPropertyNames(object);
1296 if (ownPropertyNames.length > 3)
1297 return false;
1298 for (var propertyName of ownPropertyNames) {
1299 if (!this._isPreviewableObjectInternal(object[propertyName], knownObjects, depth))
1300 return false;
1301 }
1302
1303 return true;
1304 },
1305
1306 _abbreviateString: function(string, maxLength, middle)
1307 {
1308 if (string.length <= maxLength)
1309 return string;
1310
1311 if (middle) {
1312 var leftHalf = maxLength >> 1;
1313 var rightHalf = maxLength - leftHalf - 1;
1314 return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
1315 }
1316
1317 return string.substr(0, maxLength) + "\u2026";
1318 }
1319 }
1320
1321 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
1322 {
1323 this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
1324 this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
1325 this.location = {scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column};
1326 this.scopeChain = this._wrapScopeChain(callFrame);
1327 this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
1328 }
1329
1330 InjectedScript.CallFrameProxy.prototype = {
1331 _wrapScopeChain: function(callFrame)
1332 {
1333 var scopeChain = callFrame.scopeChain;
1334 var scopeChainProxy = [];
1335 for (var i = 0; i < scopeChain.length; i++)
1336 scopeChainProxy[i] = InjectedScript.CallFrameProxy._createScopeJson(callFrame.scopeType(i), scopeChain[i], "backtrace");
1337 return scopeChainProxy;
1338 }
1339 }
1340
1341 InjectedScript.CallFrameProxy._scopeTypeNames = {
1342 0: "global", // GLOBAL_SCOPE
1343 1: "local", // LOCAL_SCOPE
1344 2: "with", // WITH_SCOPE
1345 3: "closure", // CLOSURE_SCOPE
1346 4: "catch", // CATCH_SCOPE
1347 5: "functionName", // FUNCTION_NAME_SCOPE
1348 }
1349
1350 InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeObject, groupId)
1351 {
1352 return {
1353 object: injectedScript._wrapObject(scopeObject, groupId),
1354 type: InjectedScript.CallFrameProxy._scopeTypeNames[scopeTypeCode]
1355 };
1356 }
1357
1358
1359 function slice(array, index)
1360 {
1361 var result = [];
1362 for (var i = index || 0; i < array.length; ++i)
1363 result.push(array[i]);
1364 return result;
1365 }
1366
1367 function bind(func, thisObject, var_args)
1368 {
1369 var args = slice(arguments, 2);
1370 return function(var_args) {
1371 return func.apply(thisObject, args.concat(slice(arguments)));
1372 }
1373 }
1374
1375 function BasicCommandLineAPI()
1376 {
1377 this.$_ = injectedScript._lastResult;
1378 this.$exception = injectedScript._exceptionValue;
1379
1380 // $1-$99
1381 for (var i = 1; i <= injectedScript._savedResults.length; ++i) {
1382 var member = "$" + i;
1383 if (member in inspectedGlobalObject)
1384 continue;
1385 this.__defineGetter__("$" + i, bind(injectedScript._savedResult, injectedScript, i));
1386 }
1387 }
1388
1389 return injectedScript;
1390 })