• 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.TargetAwareObject}
34 * @param {!WebInspector.Target} target
35 */
36WebInspector.RuntimeModel = function(target)
37{
38    WebInspector.TargetAwareObject.call(this, 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.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.TargetAwareObject.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.TargetAware}
166 * @param {!WebInspector.Target} target
167 * @param {number|undefined} id
168 * @param {string} name
169 * @param {boolean} isPageContext
170 * @param {string=} frameId
171 */
172WebInspector.ExecutionContext = function(target, id, name, isPageContext, frameId)
173{
174    WebInspector.TargetAware.call(this, target);
175    this.id = id;
176    this.name = (isPageContext && !name) ? "<page context>" : name;
177    this.isMainWorldContext = isPageContext;
178    this._debuggerModel = target.debuggerModel;
179    this.frameId = frameId;
180}
181
182/**
183 * @param {!WebInspector.ExecutionContext} a
184 * @param {!WebInspector.ExecutionContext} b
185 * @return {number}
186 */
187WebInspector.ExecutionContext.comparator = function(a, b)
188{
189    // Main world context should always go first.
190    if (a.isMainWorldContext)
191        return -1;
192    if (b.isMainWorldContext)
193        return +1;
194    return a.name.localeCompare(b.name);
195}
196
197WebInspector.ExecutionContext.prototype = {
198
199    /**
200     * @param {string} expression
201     * @param {string} objectGroup
202     * @param {boolean} includeCommandLineAPI
203     * @param {boolean} doNotPauseOnExceptionsAndMuteConsole
204     * @param {boolean} returnByValue
205     * @param {boolean} generatePreview
206     * @param {function(?WebInspector.RemoteObject, boolean, ?RuntimeAgent.RemoteObject=)} callback
207     */
208    evaluate: function(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback)
209    {
210        //FIXME: It will be moved to separate ExecutionContext
211        if (this._debuggerModel.selectedCallFrame()) {
212            this._debuggerModel.evaluateOnSelectedCallFrame(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback);
213            return;
214        }
215
216        if (!expression) {
217            // There is no expression, so the completion should happen against global properties.
218            expression = "this";
219        }
220
221        /**
222         * @this {WebInspector.ExecutionContext}
223         * @param {?Protocol.Error} error
224         * @param {!RuntimeAgent.RemoteObject} result
225         * @param {boolean=} wasThrown
226         */
227        function evalCallback(error, result, wasThrown)
228        {
229            if (error) {
230                callback(null, false);
231                return;
232            }
233
234            if (returnByValue)
235                callback(null, !!wasThrown, wasThrown ? null : result);
236            else
237                callback(this.target().runtimeModel.createRemoteObject(result), !!wasThrown);
238        }
239        this.target().runtimeAgent().evaluate(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, this.id, returnByValue, generatePreview, evalCallback.bind(this));
240    },
241
242    /**
243     * @param {string} expressionString
244     * @param {string} prefix
245     * @param {boolean} force
246     * @param {function(!Array.<string>, number=)} completionsReadyCallback
247     */
248    completionsForExpression: function(expressionString, prefix, force, completionsReadyCallback)
249    {
250        var lastIndex = expressionString.length - 1;
251
252        var dotNotation = (expressionString[lastIndex] === ".");
253        var bracketNotation = (expressionString[lastIndex] === "[");
254
255        if (dotNotation || bracketNotation)
256            expressionString = expressionString.substr(0, lastIndex);
257
258        if (expressionString && parseInt(expressionString, 10) == expressionString) {
259            // User is entering float value, do not suggest anything.
260            completionsReadyCallback([]);
261            return;
262        }
263
264        if (!prefix && !expressionString && !force) {
265            completionsReadyCallback([]);
266            return;
267        }
268
269        if (!expressionString && this._debuggerModel.selectedCallFrame())
270            this._debuggerModel.getSelectedCallFrameVariables(receivedPropertyNames.bind(this));
271        else
272            this.evaluate(expressionString, "completion", true, true, false, false, evaluated.bind(this));
273
274        /**
275         * @this {WebInspector.ExecutionContext}
276         */
277        function evaluated(result, wasThrown)
278        {
279            if (!result || wasThrown) {
280                completionsReadyCallback([]);
281                return;
282            }
283
284            /**
285             * @param {string} primitiveType
286             * @suppressReceiverCheck
287             * @this {WebInspector.ExecutionContext}
288             */
289            function getCompletions(primitiveType)
290            {
291                var object;
292                if (primitiveType === "string")
293                    object = new String("");
294                else if (primitiveType === "number")
295                    object = new Number(0);
296                else if (primitiveType === "boolean")
297                    object = new Boolean(false);
298                else
299                    object = this;
300
301                var resultSet = {};
302                for (var o = object; o; o = o.__proto__) {
303                    try {
304                        var names = Object.getOwnPropertyNames(o);
305                        for (var i = 0; i < names.length; ++i)
306                            resultSet[names[i]] = true;
307                    } catch (e) {
308                    }
309                }
310                return resultSet;
311            }
312
313            if (result.type === "object" || result.type === "function")
314                result.callFunctionJSON(getCompletions, undefined, receivedPropertyNames.bind(this));
315            else if (result.type === "string" || result.type === "number" || result.type === "boolean")
316                this.evaluate("(" + getCompletions + ")(\"" + result.type + "\")", "completion", false, true, true, false, receivedPropertyNamesFromEval.bind(this));
317        }
318
319        /**
320         * @param {?WebInspector.RemoteObject} notRelevant
321         * @param {boolean} wasThrown
322         * @param {?RuntimeAgent.RemoteObject=} result
323         * @this {WebInspector.ExecutionContext}
324         */
325        function receivedPropertyNamesFromEval(notRelevant, wasThrown, result)
326        {
327            if (result && !wasThrown)
328                receivedPropertyNames.call(this, result.value);
329            else
330                completionsReadyCallback([]);
331        }
332
333        /**
334         * @this {WebInspector.ExecutionContext}
335         */
336        function receivedPropertyNames(propertyNames)
337        {
338            this.target().runtimeAgent().releaseObjectGroup("completion");
339            if (!propertyNames) {
340                completionsReadyCallback([]);
341                return;
342            }
343            var includeCommandLineAPI = (!dotNotation && !bracketNotation);
344            if (includeCommandLineAPI) {
345                const commandLineAPI = ["dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear",
346                    "getEventListeners", "debug", "undebug", "monitor", "unmonitor", "table", "$", "$$", "$x"];
347                for (var i = 0; i < commandLineAPI.length; ++i)
348                    propertyNames[commandLineAPI[i]] = true;
349            }
350            this._reportCompletions(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, Object.keys(propertyNames));
351        }
352    },
353
354    /**
355     * @param {function(!Array.<string>, number=)} completionsReadyCallback
356     * @param {boolean} dotNotation
357     * @param {boolean} bracketNotation
358     * @param {string} expressionString
359     * @param {string} prefix
360     * @param {!Array.<string>} properties
361     */
362    _reportCompletions: function(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, properties) {
363        if (bracketNotation) {
364            if (prefix.length && prefix[0] === "'")
365                var quoteUsed = "'";
366            else
367                var quoteUsed = "\"";
368        }
369
370        var results = [];
371
372        if (!expressionString) {
373            const keywords = ["break", "case", "catch", "continue", "default", "delete", "do", "else", "finally", "for", "function", "if", "in",
374                              "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with"];
375            properties = properties.concat(keywords);
376        }
377
378        properties.sort();
379
380        for (var i = 0; i < properties.length; ++i) {
381            var property = properties[i];
382
383            // Assume that all non-ASCII characters are letters and thus can be used as part of identifier.
384            if (dotNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/.test(property))
385                continue;
386
387            if (bracketNotation) {
388                if (!/^[0-9]+$/.test(property))
389                    property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
390                property += "]";
391            }
392
393            if (property.length < prefix.length)
394                continue;
395            if (prefix.length && !property.startsWith(prefix))
396                continue;
397
398            results.push(property);
399        }
400        completionsReadyCallback(results);
401    },
402
403    __proto__: WebInspector.TargetAware.prototype
404}
405
406/**
407 * @type {!WebInspector.RuntimeModel}
408 */
409WebInspector.runtimeModel;
410