• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2012 Google 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 are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @constructor
33 * @extends {WebInspector.SDKModel}
34 * @param {!WebInspector.Target} target
35 */
36WebInspector.RuntimeModel = function(target)
37{
38    WebInspector.SDKModel.call(this, WebInspector.RuntimeModel, target);
39
40    this._debuggerModel = target.debuggerModel;
41    this._agent = target.runtimeAgent();
42    this.target().registerRuntimeDispatcher(new WebInspector.RuntimeDispatcher(this));
43    this._agent.enable();
44    /**
45     * @type {!Object.<number, !WebInspector.ExecutionContext>}
46     */
47    this._executionContextById = {};
48}
49
50WebInspector.RuntimeModel.Events = {
51    ExecutionContextCreated: "ExecutionContextCreated",
52    ExecutionContextDestroyed: "ExecutionContextDestroyed",
53}
54
55WebInspector.RuntimeModel.prototype = {
56
57    /**
58     * @return {!Array.<!WebInspector.ExecutionContext>}
59     */
60    executionContexts: function()
61    {
62        return Object.values(this._executionContextById);
63    },
64
65    /**
66     * @param {!RuntimeAgent.ExecutionContextDescription} context
67     */
68    _executionContextCreated: function(context)
69    {
70        var executionContext = new WebInspector.ExecutionContext(this.target(), context.id, context.name, context.origin, context.isPageContext, context.frameId);
71        this._executionContextById[executionContext.id] = executionContext;
72        this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.ExecutionContextCreated, executionContext);
73    },
74
75    /**
76     * @param {number} executionContextId
77     */
78    _executionContextDestroyed: function(executionContextId)
79    {
80        var executionContext = this._executionContextById[executionContextId];
81        delete this._executionContextById[executionContextId];
82        this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.ExecutionContextDestroyed, executionContext);
83    },
84
85    _executionContextsCleared: function()
86    {
87        var contexts = this.executionContexts();
88        this._executionContextById = {};
89        for (var  i = 0; i < contexts.length; ++i)
90            this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.ExecutionContextDestroyed, contexts[i]);
91    },
92
93    /**
94     * @param {!RuntimeAgent.RemoteObject} payload
95     * @return {!WebInspector.RemoteObject}
96     */
97    createRemoteObject: function(payload)
98    {
99        console.assert(typeof payload === "object", "Remote object payload should only be an object");
100        return new WebInspector.RemoteObjectImpl(this.target(), payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
101    },
102
103    /**
104     * @param {!RuntimeAgent.RemoteObject} payload
105     * @param {!WebInspector.ScopeRef} scopeRef
106     * @return {!WebInspector.RemoteObject}
107     */
108    createScopeRemoteObject: function(payload, scopeRef)
109    {
110        return new WebInspector.ScopeRemoteObject(this.target(), payload.objectId, scopeRef, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
111    },
112
113    /**
114     * @param {number|string|boolean} value
115     * @return {!WebInspector.RemoteObject}
116     */
117    createRemoteObjectFromPrimitiveValue: function(value)
118    {
119        return new WebInspector.RemoteObjectImpl(this.target(), undefined, typeof value, undefined, value);
120    },
121
122    /**
123     * @param {string} name
124     * @param {number|string|boolean} value
125     * @return {!WebInspector.RemoteObjectProperty}
126     */
127    createRemotePropertyFromPrimitiveValue: function(name, value)
128    {
129        return new WebInspector.RemoteObjectProperty(name, this.createRemoteObjectFromPrimitiveValue(value));
130    },
131
132    __proto__: WebInspector.SDKModel.prototype
133}
134
135/**
136 * @constructor
137 * @implements {RuntimeAgent.Dispatcher}
138 * @param {!WebInspector.RuntimeModel} runtimeModel
139 */
140WebInspector.RuntimeDispatcher = function(runtimeModel)
141{
142    this._runtimeModel = runtimeModel;
143}
144
145WebInspector.RuntimeDispatcher.prototype = {
146    executionContextCreated: function(context)
147    {
148        this._runtimeModel._executionContextCreated(context);
149    },
150
151    executionContextDestroyed: function(executionContextId)
152    {
153        this._runtimeModel._executionContextDestroyed(executionContextId);
154    },
155
156    executionContextsCleared: function()
157    {
158        this._runtimeModel._executionContextsCleared();
159    }
160
161}
162
163/**
164 * @constructor
165 * @extends {WebInspector.SDKObject}
166 * @param {!WebInspector.Target} target
167 * @param {number|undefined} id
168 * @param {string} name
169 * @param {string} origin
170 * @param {boolean} isPageContext
171 * @param {string=} frameId
172 */
173WebInspector.ExecutionContext = function(target, id, name, origin, isPageContext, frameId)
174{
175    WebInspector.SDKObject.call(this, target);
176    this.id = id;
177    this.name = name;
178    this.origin = origin;
179    this.isMainWorldContext = isPageContext;
180    this._debuggerModel = target.debuggerModel;
181    this.frameId = frameId;
182}
183
184/**
185 * @param {!WebInspector.ExecutionContext} a
186 * @param {!WebInspector.ExecutionContext} b
187 * @return {number}
188 */
189WebInspector.ExecutionContext.comparator = function(a, b)
190{
191    // Main world context should always go first.
192    if (a.isMainWorldContext)
193        return -1;
194    if (b.isMainWorldContext)
195        return +1;
196    return a.name.localeCompare(b.name);
197}
198
199WebInspector.ExecutionContext.prototype = {
200
201    /**
202     * @param {string} expression
203     * @param {string} objectGroup
204     * @param {boolean} includeCommandLineAPI
205     * @param {boolean} doNotPauseOnExceptionsAndMuteConsole
206     * @param {boolean} returnByValue
207     * @param {boolean} generatePreview
208     * @param {function(?WebInspector.RemoteObject, boolean, ?RuntimeAgent.RemoteObject=, ?DebuggerAgent.ExceptionDetails=)} callback
209     */
210    evaluate: function(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback)
211    {
212        //FIXME: It will be moved to separate ExecutionContext
213        if (this._debuggerModel.selectedCallFrame()) {
214            this._debuggerModel.evaluateOnSelectedCallFrame(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback);
215            return;
216        }
217
218        if (!expression) {
219            // There is no expression, so the completion should happen against global properties.
220            expression = "this";
221        }
222
223        /**
224         * @this {WebInspector.ExecutionContext}
225         * @param {?Protocol.Error} error
226         * @param {!RuntimeAgent.RemoteObject} result
227         * @param {boolean=} wasThrown
228         * @param {?DebuggerAgent.ExceptionDetails=} exceptionDetails
229         */
230        function evalCallback(error, result, wasThrown, exceptionDetails)
231        {
232            if (error) {
233                callback(null, false);
234                return;
235            }
236
237            if (returnByValue)
238                callback(null, !!wasThrown, wasThrown ? null : result, exceptionDetails);
239            else
240                callback(this.target().runtimeModel.createRemoteObject(result), !!wasThrown, undefined, exceptionDetails);
241        }
242        this.target().runtimeAgent().evaluate(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, this.id, returnByValue, generatePreview, evalCallback.bind(this));
243    },
244
245    /**
246     * @param {string} expressionString
247     * @param {string} prefix
248     * @param {boolean} force
249     * @param {function(!Array.<string>, number=)} completionsReadyCallback
250     */
251    completionsForExpression: function(expressionString, prefix, force, completionsReadyCallback)
252    {
253        var lastIndex = expressionString.length - 1;
254
255        var dotNotation = (expressionString[lastIndex] === ".");
256        var bracketNotation = (expressionString[lastIndex] === "[");
257
258        if (dotNotation || bracketNotation)
259            expressionString = expressionString.substr(0, lastIndex);
260
261        if (expressionString && parseInt(expressionString, 10) == expressionString) {
262            // User is entering float value, do not suggest anything.
263            completionsReadyCallback([]);
264            return;
265        }
266
267        if (!prefix && !expressionString && !force) {
268            completionsReadyCallback([]);
269            return;
270        }
271
272        if (!expressionString && this._debuggerModel.selectedCallFrame())
273            this._debuggerModel.getSelectedCallFrameVariables(receivedPropertyNames.bind(this));
274        else
275            this.evaluate(expressionString, "completion", true, true, false, false, evaluated.bind(this));
276
277        /**
278         * @this {WebInspector.ExecutionContext}
279         */
280        function evaluated(result, wasThrown)
281        {
282            if (!result || wasThrown) {
283                completionsReadyCallback([]);
284                return;
285            }
286
287            /**
288             * @param {string} primitiveType
289             * @suppressReceiverCheck
290             * @this {WebInspector.ExecutionContext}
291             */
292            function getCompletions(primitiveType)
293            {
294                var object;
295                if (primitiveType === "string")
296                    object = new String("");
297                else if (primitiveType === "number")
298                    object = new Number(0);
299                else if (primitiveType === "boolean")
300                    object = new Boolean(false);
301                else
302                    object = this;
303
304                var resultSet = {};
305                for (var o = object; o; o = o.__proto__) {
306                    try {
307                        var names = Object.getOwnPropertyNames(o);
308                        for (var i = 0; i < names.length; ++i)
309                            resultSet[names[i]] = true;
310                    } catch (e) {
311                    }
312                }
313                return resultSet;
314            }
315
316            if (result.type === "object" || result.type === "function")
317                result.callFunctionJSON(getCompletions, undefined, receivedPropertyNames.bind(this));
318            else if (result.type === "string" || result.type === "number" || result.type === "boolean")
319                this.evaluate("(" + getCompletions + ")(\"" + result.type + "\")", "completion", false, true, true, false, receivedPropertyNamesFromEval.bind(this));
320        }
321
322        /**
323         * @param {?WebInspector.RemoteObject} notRelevant
324         * @param {boolean} wasThrown
325         * @param {?RuntimeAgent.RemoteObject=} result
326         * @this {WebInspector.ExecutionContext}
327         */
328        function receivedPropertyNamesFromEval(notRelevant, wasThrown, result)
329        {
330            if (result && !wasThrown)
331                receivedPropertyNames.call(this, result.value);
332            else
333                completionsReadyCallback([]);
334        }
335
336        /**
337         * @this {WebInspector.ExecutionContext}
338         */
339        function receivedPropertyNames(propertyNames)
340        {
341            this.target().runtimeAgent().releaseObjectGroup("completion");
342            if (!propertyNames) {
343                completionsReadyCallback([]);
344                return;
345            }
346            var includeCommandLineAPI = (!dotNotation && !bracketNotation);
347            if (includeCommandLineAPI) {
348                const commandLineAPI = ["dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear",
349                    "getEventListeners", "debug", "undebug", "monitor", "unmonitor", "table", "$", "$$", "$x"];
350                for (var i = 0; i < commandLineAPI.length; ++i)
351                    propertyNames[commandLineAPI[i]] = true;
352            }
353            this._reportCompletions(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, Object.keys(propertyNames));
354        }
355    },
356
357    /**
358     * @param {function(!Array.<string>, number=)} completionsReadyCallback
359     * @param {boolean} dotNotation
360     * @param {boolean} bracketNotation
361     * @param {string} expressionString
362     * @param {string} prefix
363     * @param {!Array.<string>} properties
364     */
365    _reportCompletions: function(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, properties) {
366        if (bracketNotation) {
367            if (prefix.length && prefix[0] === "'")
368                var quoteUsed = "'";
369            else
370                var quoteUsed = "\"";
371        }
372
373        var results = [];
374
375        if (!expressionString) {
376            const keywords = ["break", "case", "catch", "continue", "default", "delete", "do", "else", "finally", "for", "function", "if", "in",
377                              "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with"];
378            properties = properties.concat(keywords);
379        }
380
381        properties.sort();
382
383        for (var i = 0; i < properties.length; ++i) {
384            var property = properties[i];
385
386            // Assume that all non-ASCII characters are letters and thus can be used as part of identifier.
387            if (dotNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/.test(property))
388                continue;
389
390            if (bracketNotation) {
391                if (!/^[0-9]+$/.test(property))
392                    property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
393                property += "]";
394            }
395
396            if (property.length < prefix.length)
397                continue;
398            if (prefix.length && !property.startsWith(prefix))
399                continue;
400
401            results.push(property);
402        }
403        completionsReadyCallback(results);
404    },
405
406    __proto__: WebInspector.SDKObject.prototype
407}
408
409/**
410 * @type {!WebInspector.RuntimeModel}
411 */
412WebInspector.runtimeModel;
413