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.TargetAwareObject}
     34  * @param {!WebInspector.Target} target
     35  */
     36 WebInspector.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 
     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.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  */
    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.TargetAware}
    166  * @param {!WebInspector.Target} target
    167  * @param {number|undefined} id
    168  * @param {string} name
    169  * @param {boolean} isPageContext
    170  * @param {string=} frameId
    171  */
    172 WebInspector.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  */
    187 WebInspector.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 
    197 WebInspector.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  */
    409 WebInspector.runtimeModel;
    410