Home | History | Annotate | Download | only in sdk
      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  */
     36 WebInspector.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 
     50 WebInspector.RuntimeModel.Events = {
     51     ExecutionContextCreated: "ExecutionContextCreated",
     52     ExecutionContextDestroyed: "ExecutionContextDestroyed",
     53 }
     54 
     55 WebInspector.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  */
    140 WebInspector.RuntimeDispatcher = function(runtimeModel)
    141 {
    142     this._runtimeModel = runtimeModel;
    143 }
    144 
    145 WebInspector.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  */
    173 WebInspector.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  */
    189 WebInspector.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 
    199 WebInspector.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  */
    412 WebInspector.runtimeModel;
    413