Web Inspector: 'use strict' exceptions stop in inspector code
[WebKit-https.git] / Source / WebCore / inspector / InjectedScriptSource.js
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 Computer, 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 (function (InjectedScriptHost, inspectedWindow, injectedScriptId) {
30
31 function bind(thisObject, memberFunction)
32 {
33     var func = memberFunction;
34     var args = Array.prototype.slice.call(arguments, 2);
35     function bound()
36     {
37         return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0)));
38     }
39     bound.toString = function() {
40         return "bound: " + func;
41     };
42     return bound;
43 }
44
45 var InjectedScript = function()
46 {
47     this._lastBoundObjectId = 1;
48     this._idToWrappedObject = {};
49     this._idToObjectGroupName = {};
50     this._objectGroups = {};
51 }
52
53 InjectedScript.primitiveTypes = {
54     undefined: true,
55     boolean: true,
56     number: true,
57     string: true
58 }
59
60 InjectedScript.prototype = {
61     isPrimitiveValue: function(object)
62     {
63         // FIXME(33716): typeof document.all is always 'undefined'.
64         return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
65     },
66
67     wrapObject: function(object, groupName, canAccessInspectedWindow)
68     {
69         if (canAccessInspectedWindow)
70             return this._wrapObject(object, groupName);
71
72         var result = {};
73         result.type = typeof object;
74         if (this._isPrimitiveValue(object))
75             result.value = object;
76         else
77             result.description = this._toString(object);
78         return result;
79     },
80
81     inspectNode: function(object)
82     {
83         this._inspect(object);
84     },
85
86     _inspect: function(object)
87     {
88         if (arguments.length === 0)
89             return;
90
91         var objectId = this._wrapObject(object, "");
92         var hints = {};
93
94         switch (injectedScript._describe(object)) {
95             case "Database":
96                 var databaseId = InjectedScriptHost.databaseId(object)
97                 if (databaseId)
98                     hints.databaseId = databaseId;
99                 break;
100             case "Storage":
101                 var storageId = InjectedScriptHost.storageId(object)
102                 if (storageId)
103                     hints.domStorageId = storageId;
104                 break;
105         }
106         InjectedScriptHost.inspect(objectId, hints);
107         return object;
108     },
109
110     // This method cannot throw.
111     _wrapObject: function(object, objectGroupName, forceValueType)
112     {
113         try {
114             return new InjectedScript.RemoteObject(object, objectGroupName, forceValueType);
115         } catch (e) {
116             try {
117                 var description = injectedScript._describe(e);
118             } catch (ex) {
119                 var description = "<failed to convert exception to string>";
120             }
121             return new InjectedScript.RemoteObject(description);
122         }
123     },
124
125     _bind: function(object, objectGroupName)
126     {
127         var id = this._lastBoundObjectId++;
128         this._idToWrappedObject[id] = object;
129         var objectId = "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
130         if (objectGroupName) {
131             var group = this._objectGroups[objectGroupName];
132             if (!group) {
133                 group = [];
134                 this._objectGroups[objectGroupName] = group;
135             }
136             group.push(id);
137             this._idToObjectGroupName[id] = objectGroupName;
138         }
139         return objectId;
140     },
141
142     _parseObjectId: function(objectId)
143     {
144         return eval("(" + objectId + ")");
145     },
146
147     releaseObjectGroup: function(objectGroupName)
148     {
149         var group = this._objectGroups[objectGroupName];
150         if (!group)
151             return;
152         for (var i = 0; i < group.length; i++)
153             this._releaseObject(group[i]);
154         delete this._objectGroups[objectGroupName];
155     },
156
157     dispatch: function(methodName, args)
158     {
159         var argsArray = eval("(" + args + ")");
160         var result = this[methodName].apply(this, argsArray);
161         if (typeof result === "undefined") {
162             inspectedWindow.console.error("Web Inspector error: InjectedScript.%s returns undefined", methodName);
163             result = null;
164         }
165         return result;
166     },
167
168     getProperties: function(objectId, ownProperties)
169     {
170         var parsedObjectId = this._parseObjectId(objectId);
171         var object = this._objectForId(parsedObjectId);
172         var objectGroupName = this._idToObjectGroupName[parsedObjectId.id];
173
174         if (!this._isDefined(object))
175             return false;
176         var descriptors = this._propertyDescriptors(object, ownProperties);
177
178         // Go over properties, wrap object values.
179         if (descriptors.length === 0 && "arguments" in object) {
180             // Fill in JSC scope object.
181             for (var key in object)
182                 descriptors.push({ name: key, value: object[key], writable: false, configurable: false, enumerable: true});
183         }
184
185         for (var i = 0; i < descriptors.length; ++i) {
186             var descriptor = descriptors[i];
187             if ("get" in descriptor)
188                 descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
189             if ("set" in descriptor)
190                 descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
191             if ("value" in descriptor)
192                 descriptor.value = this._wrapObject(descriptor.value, objectGroupName);
193             if (!("configurable" in descriptor))
194                 descriptor.configurable = false;
195             if (!("enumerable" in descriptor))
196                 descriptor.enumerable = false;
197             
198         }
199         return descriptors;
200     },
201
202     getFunctionDetails: function(functionId)
203     {
204         var parsedFunctionId = this._parseObjectId(functionId);
205         var func = this._objectForId(parsedFunctionId);
206         if (typeof func !== "function")
207             return "Cannot resolve function by id.";
208         return InjectedScriptHost.functionDetails(func);
209     },
210
211     releaseObject: function(objectId)
212     {
213         var parsedObjectId = this._parseObjectId(objectId);
214         this._releaseObject(parsedObjectId.id);
215     },
216
217     _releaseObject: function(id)
218     {
219         delete this._idToWrappedObject[id];
220         delete this._idToObjectGroupName[id];
221     },
222
223     _propertyDescriptors: function(object, ownProperties)
224     {
225         var descriptors = [];
226         var nameProcessed = {};
227         nameProcessed.__proto__ = null;
228         for (var o = object; this._isDefined(o); o = o.__proto__) {
229             var names = Object.getOwnPropertyNames(o);
230             for (var i = 0; i < names.length; ++i) {
231                 var name = names[i];
232                 if (nameProcessed[name])
233                     continue;
234
235                 try {
236                     nameProcessed[name] = true;
237                     var descriptor = Object.getOwnPropertyDescriptor(object, name);
238                     if (!descriptor) {
239                         // Not all bindings provide proper descriptors. Fall back to the writable, configurable property.
240                         try {
241                             descriptors.push({ name: name, value: object[name], writable: false, configurable: false, enumerable: false});
242                         } catch (e) {
243                             // Silent catch.
244                         }
245                         continue;
246                     }
247                 } catch (e) {
248                     var descriptor = {};
249                     descriptor.value = e;
250                     descriptor.wasThrown = true;
251                 }
252
253                 descriptor.name = name;
254                 descriptors.push(descriptor); 
255             }
256             if (ownProperties) {
257                 if (object.__proto__)
258                     descriptors.push({ name: "__proto__", value: object.__proto__, writable: true, configurable: true, enumerable: false});
259                 break;
260             }
261         }
262         return descriptors;
263     },
264
265     evaluate: function(expression, objectGroup, injectCommandLineAPI, returnByValue)
266     {
267         return this._evaluateAndWrap(inspectedWindow.eval, inspectedWindow, expression, objectGroup, false, injectCommandLineAPI, returnByValue);
268     },
269
270     callFunctionOn: function(objectId, expression, args, returnByValue)
271     {
272         var parsedObjectId = this._parseObjectId(objectId);
273         var object = this._objectForId(parsedObjectId);
274         if (!object)
275             return "Could not find object with given id";
276
277         if (args) {
278             var resolvedArgs = [];
279             args = eval(args);
280             for (var i = 0; i < args.length; ++i) {
281                 var objectId = args[i].objectId;
282                 if (objectId) {
283                     var parsedArgId = this._parseObjectId(objectId);
284                     if (!parsedArgId || parsedArgId.injectedScriptId !== injectedScriptId)
285                         return "Arguments should belong to the same JavaScript world as the target object.";
286
287                     var resolvedArg = this._objectForId(parsedArgId);
288                     if (!resolvedArg)
289                         return "Could not find object with given id";
290
291                     resolvedArgs.push(resolvedArg);
292                 } else if ("value" in args[i])
293                     resolvedArgs.push(args[i].value);
294                 else
295                     resolvedArgs.push(undefined);
296             }
297         }
298
299         try {
300             var objectGroup = this._idToObjectGroupName[parsedObjectId.id];
301             var func = eval("(" + expression + ")");
302             if (typeof func !== "function")
303                 return "Given expression does not evaluate to a function";
304
305             return { wasThrown: false,
306                      result: this._wrapObject(func.apply(object, resolvedArgs), objectGroup, returnByValue) };
307         } catch (e) {
308             return this._createThrownValue(e, objectGroup);
309         }
310     },
311
312     _evaluateAndWrap: function(evalFunction, object, expression, objectGroup, isEvalOnCallFrame, injectCommandLineAPI, returnByValue)
313     {
314         try {
315             return { wasThrown: false,
316                      result: this._wrapObject(this._evaluateOn(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI), objectGroup, returnByValue) };
317         } catch (e) {
318             return this._createThrownValue(e, objectGroup);
319         }
320     },
321
322     _createThrownValue: function(value, objectGroup)
323     {
324         var remoteObject = this._wrapObject(value, objectGroup);
325         try {
326             remoteObject.description = this._toString(value);
327         } catch (e) {}
328         return { wasThrown: true,
329                  result: remoteObject };
330     },
331
332     _evaluateOn: function(evalFunction, object, expression, isEvalOnCallFrame, injectCommandLineAPI)
333     {
334         // Only install command line api object for the time of evaluation.
335         // Surround the expression in with statements to inject our command line API so that
336         // the window object properties still take more precedent than our API functions.
337
338         try {
339             if (injectCommandLineAPI && inspectedWindow.console) {
340                 inspectedWindow.console._commandLineAPI = new CommandLineAPI(this._commandLineAPIImpl, isEvalOnCallFrame ? object : null);
341                 expression = "with ((window && window.console && window.console._commandLineAPI) || {}) {\n" + expression + "\n}";
342             }
343             return evalFunction.call(object, expression);
344         } finally {
345             if (injectCommandLineAPI && inspectedWindow.console)
346                 delete inspectedWindow.console._commandLineAPI;
347         }
348     },
349
350     wrapCallFrames: function(callFrame)
351     {
352         if (!callFrame)
353             return false;
354
355         var result = [];
356         var depth = 0;
357         do {
358             result.push(new InjectedScript.CallFrameProxy(depth++, callFrame));
359             callFrame = callFrame.caller;
360         } while (callFrame);
361         return result;
362     },
363
364     evaluateOnCallFrame: function(topCallFrame, callFrameId, expression, objectGroup, injectCommandLineAPI, returnByValue)
365     {
366         var callFrame = this._callFrameForId(topCallFrame, callFrameId);
367         if (!callFrame)
368             return "Could not find call frame with given id";
369         return this._evaluateAndWrap(callFrame.evaluate, callFrame, expression, objectGroup, true, injectCommandLineAPI, returnByValue);
370     },
371
372     _callFrameForId: function(topCallFrame, callFrameId)
373     {
374         var parsedCallFrameId = eval("(" + callFrameId + ")");
375         var ordinal = parsedCallFrameId.ordinal;
376         var callFrame = topCallFrame;
377         while (--ordinal >= 0 && callFrame)
378             callFrame = callFrame.caller;
379         return callFrame;
380     },
381
382     _objectForId: function(objectId)
383     {
384         return this._idToWrappedObject[objectId.id];
385     },
386
387     nodeForObjectId: function(objectId)
388     {
389         var parsedObjectId = this._parseObjectId(objectId);
390         var object = this._objectForId(parsedObjectId);
391         if (!object || this._subtype(object) !== "node")
392             return null;
393         return object;
394     },
395
396     _isDefined: function(object)
397     {
398         return object || this._isHTMLAllCollection(object);
399     },
400
401     _isHTMLAllCollection: function(object)
402     {
403         // document.all is reported as undefined, but we still want to process it.
404         return (typeof object === "undefined") && InjectedScriptHost.isHTMLAllCollection(object);
405     },
406
407     _subtype: function(obj)
408     {
409         if (obj === null)
410             return "null";
411
412         var type = typeof obj;
413         if (this.isPrimitiveValue(obj))
414             return null;
415
416         if (this._isHTMLAllCollection(obj))
417             return "array";
418
419         var preciseType = InjectedScriptHost.type(obj);
420         if (preciseType)
421             return preciseType;
422
423         // FireBug's array detection.
424         try {
425             if (typeof obj.splice === "function" && isFinite(obj.length))
426                 return "array";
427             if (Object.prototype.toString.call(obj) === "[object Arguments]" && isFinite(obj.length)) // arguments.
428                 return "array";
429         } catch (e) {
430         }
431
432         // If owning frame has navigated to somewhere else window properties will be undefined.
433         return null;
434     },
435
436     _describe: function(obj)
437     {
438         if (this.isPrimitiveValue(obj))
439             return null;
440
441         // Type is object, get subtype.
442         var subtype = this._subtype(obj);
443
444         if (subtype === "regexp")
445             return this._toString(obj);
446
447         if (subtype === "date")
448             return this._toString(obj);
449
450         var className = InjectedScriptHost.internalConstructorName(obj);
451         if (subtype === "array") {
452             if (typeof obj.length === "number")
453                 className += "[" + obj.length + "]";
454             return className;
455         }
456
457         // NodeList in JSC is a function, check for array prior to this.
458         if (typeof obj === "function")
459             return this._toString(obj);
460
461         if (className === "Object") {
462             // In Chromium DOM wrapper prototypes will have Object as their constructor name,
463             // get the real DOM wrapper name from the constructor property.
464             var constructorName = obj.constructor && obj.constructor.name;
465             if (constructorName)
466                 return constructorName;
467         }
468         return className;
469     },
470
471     _toString: function(obj)
472     {
473         // We don't use String(obj) because inspectedWindow.String is undefined if owning frame navigated to another page.
474         return "" + obj;
475     }
476 }
477
478 var injectedScript = new InjectedScript();
479
480 InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType)
481 {
482     this.type = typeof object;
483     if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
484         // We don't send undefined values over JSON.
485         if (typeof object !== "undefined")
486             this.value = object;
487
488         // Null object is object with 'null' subtype'
489         if (object === null)
490             this.subtype = "null";
491
492         // Provide user-friendly number values.
493         if (typeof object === "number")
494             this.description = object + "";
495         return;
496     }
497
498     this.objectId = injectedScript._bind(object, objectGroupName);
499     var subtype = injectedScript._subtype(object)
500     if (subtype)
501         this.subtype = subtype;
502     this.className = InjectedScriptHost.internalConstructorName(object);
503     this.description = injectedScript._describe(object);
504 }
505
506 InjectedScript.CallFrameProxy = function(ordinal, callFrame)
507 {
508     this.callFrameId = "{\"ordinal\":" + ordinal + ",\"injectedScriptId\":" + injectedScriptId + "}";
509     this.functionName = (callFrame.type === "function" ? callFrame.functionName : "");
510     this.location = { scriptId: String(callFrame.sourceID), lineNumber: callFrame.line, columnNumber: callFrame.column };
511     this.scopeChain = this._wrapScopeChain(callFrame);
512     this.this = injectedScript._wrapObject(callFrame.thisObject, "backtrace");
513 }
514
515 InjectedScript.CallFrameProxy.prototype = {
516     _wrapScopeChain: function(callFrame)
517     {
518         const GLOBAL_SCOPE = 0;
519         const LOCAL_SCOPE = 1;
520         const WITH_SCOPE = 2;
521         const CLOSURE_SCOPE = 3;
522         const CATCH_SCOPE = 4;
523
524         var scopeTypeNames = {};
525         scopeTypeNames[GLOBAL_SCOPE] = "global";
526         scopeTypeNames[LOCAL_SCOPE] = "local";
527         scopeTypeNames[WITH_SCOPE] = "with";
528         scopeTypeNames[CLOSURE_SCOPE] = "closure";
529         scopeTypeNames[CATCH_SCOPE] = "catch";
530
531         var scopeChain = callFrame.scopeChain;
532         var scopeChainProxy = [];
533         var foundLocalScope = false;
534         for (var i = 0; i < scopeChain.length; i++) {
535             var scope = {};
536             scope.object = injectedScript._wrapObject(scopeChain[i], "backtrace");
537
538             var scopeType = callFrame.scopeType(i);
539             scope.type = scopeTypeNames[scopeType];
540             scopeChainProxy.push(scope);
541         }
542         return scopeChainProxy;
543     }
544 }
545
546 function CommandLineAPI(commandLineAPIImpl, callFrame)
547 {
548     function inScopeVariables(member)
549     {
550         if (!callFrame)
551             return false;
552
553         var scopeChain = callFrame.scopeChain;
554         for (var i = 0; i < scopeChain.length; ++i) {
555             if (member in scopeChain[i])
556                 return true;
557         }
558         return false;
559     }
560
561     for (var i = 0; i < CommandLineAPI.members_.length; ++i) {
562         var member = CommandLineAPI.members_[i];
563         if (member in inspectedWindow || inScopeVariables(member))
564             continue;
565
566         this[member] = bind(commandLineAPIImpl, commandLineAPIImpl[member]);
567     }
568
569     for (var i = 0; i < 5; ++i) {
570         var member = "$" + i;
571         if (member in inspectedWindow || inScopeVariables(member))
572             continue;
573
574         this.__defineGetter__("$" + i, bind(commandLineAPIImpl, commandLineAPIImpl._inspectedObject, i));
575     }
576 }
577
578 CommandLineAPI.members_ = [
579     "$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd",
580     "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners"
581 ];
582
583 function CommandLineAPIImpl()
584 {
585 }
586
587 CommandLineAPIImpl.prototype = {
588     $: function()
589     {
590         return document.getElementById.apply(document, arguments)
591     },
592
593     $$: function()
594     {
595         return document.querySelectorAll.apply(document, arguments)
596     },
597
598     $x: function(xpath, context)
599     {
600         var doc = (context && context.ownerDocument) || inspectedWindow.document;
601         var result = doc.evaluate(xpath, context || doc, null, XPathResult.ANY_TYPE, null);
602         switch (result.resultType) {
603         case XPathResult.NUMBER_TYPE:
604             return result.numberValue;
605         case XPathResult.STRING_TYPE:
606             return result.stringValue;
607         case XPathResult.BOOLEAN_TYPE:
608             return result.booleanValue;
609         default:
610             var nodes = [];
611             var node;
612             while (node = result.iterateNext())
613                 nodes.push(node);
614             return nodes;
615         }
616     },
617
618     dir: function()
619     {
620         return console.dir.apply(console, arguments)
621     },
622
623     dirxml: function()
624     {
625         return console.dirxml.apply(console, arguments)
626     },
627
628     keys: function(object)
629     {
630         return Object.keys(object);
631     },
632
633     values: function(object)
634     {
635         var result = [];
636         for (var key in object)
637             result.push(object[key]);
638         return result;
639     },
640
641     profile: function()
642     {
643         return console.profile.apply(console, arguments)
644     },
645
646     profileEnd: function()
647     {
648         return console.profileEnd.apply(console, arguments)
649     },
650
651     monitorEvents: function(object, types)
652     {
653         if (!object || !object.addEventListener || !object.removeEventListener)
654             return;
655         types = this._normalizeEventTypes(types);
656         for (var i = 0; i < types.length; ++i) {
657             object.removeEventListener(types[i], this._logEvent, false);
658             object.addEventListener(types[i], this._logEvent, false);
659         }
660     },
661
662     unmonitorEvents: function(object, types)
663     {
664         if (!object || !object.addEventListener || !object.removeEventListener)
665             return;
666         types = this._normalizeEventTypes(types);
667         for (var i = 0; i < types.length; ++i)
668             object.removeEventListener(types[i], this._logEvent, false);
669     },
670
671     inspect: function(object)
672     {
673         return injectedScript._inspect(object);
674     },
675
676     copy: function(object)
677     {
678         if (injectedScript._subtype(object) === "node")
679             object = object.outerHTML;
680         InjectedScriptHost.copyText(object);
681     },
682
683     clear: function()
684     {
685         InjectedScriptHost.clearConsoleMessages();
686     },
687
688     getEventListeners: function(node)
689     {
690         return InjectedScriptHost.getEventListeners(node);
691     },
692
693     _inspectedObject: function(num)
694     {
695         return InjectedScriptHost.inspectedObject(num);
696     },
697
698     _normalizeEventTypes: function(types)
699     {
700         if (typeof types === "undefined")
701             types = [ "mouse", "key", "touch", "control", "load", "unload", "abort", "error", "select", "change", "submit", "reset", "focus", "blur", "resize", "scroll", "search", "devicemotion", "deviceorientation" ];
702         else if (typeof types === "string")
703             types = [ types ];
704
705         var result = [];
706         for (var i = 0; i < types.length; i++) {
707             if (types[i] === "mouse")
708                 result.splice(0, 0, "mousedown", "mouseup", "click", "dblclick", "mousemove", "mouseover", "mouseout", "mousewheel");
709             else if (types[i] === "key")
710                 result.splice(0, 0, "keydown", "keyup", "keypress", "textInput");
711             else if (types[i] === "touch")
712                 result.splice(0, 0, "touchstart", "touchmove", "touchend", "touchcancel");
713             else if (types[i] === "control")
714                 result.splice(0, 0, "resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset");
715             else
716                 result.push(types[i]);
717         }
718         return result;
719     },
720
721     _logEvent: function(event)
722     {
723         console.log(event.type, event);
724     }
725 }
726
727 injectedScript._commandLineAPIImpl = new CommandLineAPIImpl();
728 return injectedScript;
729 })