Home | History | Annotate | Download | only in front_end
      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.Object}
     34  * @param {!WebInspector.ResourceTreeModel} resourceTreeModel
     35  */
     36 WebInspector.RuntimeModel = function(resourceTreeModel)
     37 {
     38     resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
     39     resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
     40     resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this);
     41     resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._didLoadCachedResources, this);
     42     this._frameIdToContextList = {};
     43 }
     44 
     45 WebInspector.RuntimeModel.Events = {
     46     FrameExecutionContextListAdded: "FrameExecutionContextListAdded",
     47     FrameExecutionContextListRemoved: "FrameExecutionContextListRemoved",
     48 }
     49 
     50 WebInspector.RuntimeModel.prototype = {
     51     /**
     52      * @param {?WebInspector.ExecutionContext} executionContext
     53      */
     54     setCurrentExecutionContext: function(executionContext)
     55     {
     56         this._currentExecutionContext = executionContext;
     57     },
     58 
     59     /**
     60      * @return {?WebInspector.ExecutionContext}
     61      */
     62     currentExecutionContext: function()
     63     {
     64         return this._currentExecutionContext;
     65     },
     66 
     67     /**
     68      * @return {!Array.<!WebInspector.FrameExecutionContextList>}
     69      */
     70     contextLists: function()
     71     {
     72         return Object.values(this._frameIdToContextList);
     73     },
     74 
     75     /**
     76      * @param {!WebInspector.ResourceTreeFrame} frame
     77      * @return {!WebInspector.FrameExecutionContextList}
     78      */
     79     contextListByFrame: function(frame)
     80     {
     81         return this._frameIdToContextList[frame.id];
     82     },
     83 
     84     _frameAdded: function(event)
     85     {
     86         var frame = event.data;
     87         var context = new WebInspector.FrameExecutionContextList(frame);
     88         this._frameIdToContextList[frame.id] = context;
     89         this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.FrameExecutionContextListAdded, context);
     90     },
     91 
     92     _frameNavigated: function(event)
     93     {
     94         var frame = event.data;
     95         var context = this._frameIdToContextList[frame.id];
     96         if (context)
     97             context._frameNavigated(frame);
     98     },
     99 
    100     _frameDetached: function(event)
    101     {
    102         var frame = event.data;
    103         var context = this._frameIdToContextList[frame.id];
    104         if (!context)
    105             return;
    106         this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.FrameExecutionContextListRemoved, context);
    107         delete this._frameIdToContextList[frame.id];
    108     },
    109 
    110     _didLoadCachedResources: function()
    111     {
    112         InspectorBackend.registerRuntimeDispatcher(new WebInspector.RuntimeDispatcher(this));
    113         RuntimeAgent.enable();
    114     },
    115 
    116     _executionContextCreated: function(context)
    117     {
    118         var contextList = this._frameIdToContextList[context.frameId];
    119         // FIXME(85708): this should never happen
    120         if (!contextList)
    121             return;
    122         contextList._addExecutionContext(new WebInspector.ExecutionContext(context.id, context.name, context.isPageContext));
    123     },
    124 
    125     /**
    126      * @param {string} expression
    127      * @param {string} objectGroup
    128      * @param {boolean} includeCommandLineAPI
    129      * @param {boolean} doNotPauseOnExceptionsAndMuteConsole
    130      * @param {boolean} returnByValue
    131      * @param {boolean} generatePreview
    132      * @param {function(?WebInspector.RemoteObject, boolean, ?RuntimeAgent.RemoteObject=)} callback
    133      */
    134     evaluate: function(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback)
    135     {
    136         if (WebInspector.debuggerModel.selectedCallFrame()) {
    137             WebInspector.debuggerModel.evaluateOnSelectedCallFrame(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback);
    138             return;
    139         }
    140 
    141         if (!expression) {
    142             // There is no expression, so the completion should happen against global properties.
    143             expression = "this";
    144         }
    145 
    146         /**
    147          * @param {?Protocol.Error} error
    148          * @param {!RuntimeAgent.RemoteObject} result
    149          * @param {boolean=} wasThrown
    150          */
    151         function evalCallback(error, result, wasThrown)
    152         {
    153             if (error) {
    154                 callback(null, false);
    155                 return;
    156             }
    157 
    158             if (returnByValue)
    159                 callback(null, !!wasThrown, wasThrown ? null : result);
    160             else
    161                 callback(WebInspector.RemoteObject.fromPayload(result), !!wasThrown);
    162         }
    163         RuntimeAgent.evaluate(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, this._currentExecutionContext ? this._currentExecutionContext.id : undefined, returnByValue, generatePreview, evalCallback);
    164     },
    165 
    166     /**
    167      * @param {!Element} proxyElement
    168      * @param {!Range} wordRange
    169      * @param {boolean} force
    170      * @param {function(!Array.<string>, number=)} completionsReadyCallback
    171      */
    172     completionsForTextPrompt: function(proxyElement, wordRange, force, completionsReadyCallback)
    173     {
    174         // Pass less stop characters to rangeOfWord so the range will be a more complete expression.
    175         var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, " =:[({;,!+-*/&|^<>", proxyElement, "backward");
    176         var expressionString = expressionRange.toString();
    177         var prefix = wordRange.toString();
    178         this._completionsForExpression(expressionString, prefix, force, completionsReadyCallback);
    179     },
    180 
    181     /**
    182      * @param {string} expressionString
    183      * @param {string} prefix
    184      * @param {boolean} force
    185      * @param {function(!Array.<string>, number=)} completionsReadyCallback
    186      */
    187     _completionsForExpression: function(expressionString, prefix, force, completionsReadyCallback)
    188     {
    189         var lastIndex = expressionString.length - 1;
    190 
    191         var dotNotation = (expressionString[lastIndex] === ".");
    192         var bracketNotation = (expressionString[lastIndex] === "[");
    193 
    194         if (dotNotation || bracketNotation)
    195             expressionString = expressionString.substr(0, lastIndex);
    196 
    197         if (expressionString && parseInt(expressionString, 10) == expressionString) {
    198             // User is entering float value, do not suggest anything.
    199             completionsReadyCallback([]);
    200             return;
    201         }
    202 
    203         if (!prefix && !expressionString && !force) {
    204             completionsReadyCallback([]);
    205             return;
    206         }
    207 
    208         if (!expressionString && WebInspector.debuggerModel.selectedCallFrame())
    209             WebInspector.debuggerModel.getSelectedCallFrameVariables(receivedPropertyNames.bind(this));
    210         else
    211             this.evaluate(expressionString, "completion", true, true, false, false, evaluated.bind(this));
    212 
    213         /**
    214          * @this {WebInspector.RuntimeModel}
    215          */
    216         function evaluated(result, wasThrown)
    217         {
    218             if (!result || wasThrown) {
    219                 completionsReadyCallback([]);
    220                 return;
    221             }
    222 
    223             /**
    224              * @param {string} primitiveType
    225              * @this {WebInspector.RuntimeModel}
    226              */
    227             function getCompletions(primitiveType)
    228             {
    229                 var object;
    230                 if (primitiveType === "string")
    231                     object = new String("");
    232                 else if (primitiveType === "number")
    233                     object = new Number(0);
    234                 else if (primitiveType === "boolean")
    235                     object = new Boolean(false);
    236                 else
    237                     object = this;
    238 
    239                 var resultSet = {};
    240                 for (var o = object; o; o = o.__proto__) {
    241                     try {
    242                         var names = Object.getOwnPropertyNames(o);
    243                         for (var i = 0; i < names.length; ++i)
    244                             resultSet[names[i]] = true;
    245                     } catch (e) {
    246                     }
    247                 }
    248                 return resultSet;
    249             }
    250 
    251             if (result.type === "object" || result.type === "function")
    252                 result.callFunctionJSON(getCompletions, undefined, receivedPropertyNames.bind(this));
    253             else if (result.type === "string" || result.type === "number" || result.type === "boolean")
    254                 this.evaluate("(" + getCompletions + ")(\"" + result.type + "\")", "completion", false, true, true, false, receivedPropertyNamesFromEval.bind(this));
    255         }
    256 
    257         /**
    258          * @param {?WebInspector.RemoteObject} notRelevant
    259          * @param {boolean} wasThrown
    260          * @param {?RuntimeAgent.RemoteObject=} result
    261          * @this {WebInspector.RuntimeModel}
    262          */
    263         function receivedPropertyNamesFromEval(notRelevant, wasThrown, result)
    264         {
    265             if (result && !wasThrown)
    266                 receivedPropertyNames.call(this, result.value);
    267             else
    268                 completionsReadyCallback([]);
    269         }
    270 
    271         /**
    272          * @this {WebInspector.RuntimeModel}
    273          */
    274         function receivedPropertyNames(propertyNames)
    275         {
    276             RuntimeAgent.releaseObjectGroup("completion");
    277             if (!propertyNames) {
    278                 completionsReadyCallback([]);
    279                 return;
    280             }
    281             var includeCommandLineAPI = (!dotNotation && !bracketNotation);
    282             if (includeCommandLineAPI) {
    283                 const commandLineAPI = ["dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear",
    284                     "getEventListeners", "debug", "undebug", "monitor", "unmonitor", "table", "$", "$$", "$x"];
    285                 for (var i = 0; i < commandLineAPI.length; ++i)
    286                     propertyNames[commandLineAPI[i]] = true;
    287             }
    288             this._reportCompletions(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, Object.keys(propertyNames));
    289         }
    290     },
    291 
    292     /**
    293      * @param {function(!Array.<string>, number=)} completionsReadyCallback
    294      * @param {boolean} dotNotation
    295      * @param {boolean} bracketNotation
    296      * @param {string} expressionString
    297      * @param {string} prefix
    298      * @param {!Array.<string>} properties
    299      */
    300     _reportCompletions: function(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, properties) {
    301         if (bracketNotation) {
    302             if (prefix.length && prefix[0] === "'")
    303                 var quoteUsed = "'";
    304             else
    305                 var quoteUsed = "\"";
    306         }
    307 
    308         var results = [];
    309 
    310         if (!expressionString) {
    311             const keywords = ["break", "case", "catch", "continue", "default", "delete", "do", "else", "finally", "for", "function", "if", "in",
    312                               "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with"];
    313             properties = properties.concat(keywords);
    314         }
    315 
    316         properties.sort();
    317 
    318         for (var i = 0; i < properties.length; ++i) {
    319             var property = properties[i];
    320 
    321             // Assume that all non-ASCII characters are letters and thus can be used as part of identifier.
    322             if (dotNotation && !/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/.test(property))
    323                 continue;
    324 
    325             if (bracketNotation) {
    326                 if (!/^[0-9]+$/.test(property))
    327                     property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
    328                 property += "]";
    329             }
    330 
    331             if (property.length < prefix.length)
    332                 continue;
    333             if (prefix.length && !property.startsWith(prefix))
    334                 continue;
    335 
    336             results.push(property);
    337         }
    338         completionsReadyCallback(results);
    339     },
    340 
    341     __proto__: WebInspector.Object.prototype
    342 }
    343 
    344 /**
    345  * @type {!WebInspector.RuntimeModel}
    346  */
    347 WebInspector.runtimeModel;
    348 
    349 /**
    350  * @constructor
    351  * @implements {RuntimeAgent.Dispatcher}
    352  * @param {!WebInspector.RuntimeModel} runtimeModel
    353  */
    354 WebInspector.RuntimeDispatcher = function(runtimeModel)
    355 {
    356     this._runtimeModel = runtimeModel;
    357 }
    358 
    359 WebInspector.RuntimeDispatcher.prototype = {
    360     executionContextCreated: function(context)
    361     {
    362         this._runtimeModel._executionContextCreated(context);
    363     }
    364 }
    365 
    366 /**
    367  * @constructor
    368  * @extends {WebInspector.Object}
    369  */
    370 WebInspector.ExecutionContext = function(id, name, isPageContext)
    371 {
    372     this.id = id;
    373     this.name = (isPageContext && !name) ? "<page context>" : name;
    374     this.isMainWorldContext = isPageContext;
    375 }
    376 
    377 /**
    378  * @param {!WebInspector.ExecutionContext} a
    379  * @param {!WebInspector.ExecutionContext} b
    380  * @return {number}
    381  */
    382 WebInspector.ExecutionContext.comparator = function(a, b)
    383 {
    384     // Main world context should always go first.
    385     if (a.isMainWorldContext)
    386         return -1;
    387     if (b.isMainWorldContext)
    388         return +1;
    389     return a.name.localeCompare(b.name);
    390 }
    391 
    392 /**
    393  * @constructor
    394  * @extends {WebInspector.Object}
    395  */
    396 WebInspector.FrameExecutionContextList = function(frame)
    397 {
    398     this._frame = frame;
    399     this._executionContexts = [];
    400 }
    401 
    402 WebInspector.FrameExecutionContextList.EventTypes = {
    403     ContextsUpdated: "ContextsUpdated",
    404     ContextAdded: "ContextAdded"
    405 }
    406 
    407 WebInspector.FrameExecutionContextList.prototype =
    408 {
    409     _frameNavigated: function(frame)
    410     {
    411         this._frame = frame;
    412         this._executionContexts = [];
    413         this.dispatchEventToListeners(WebInspector.FrameExecutionContextList.EventTypes.ContextsUpdated, this);
    414     },
    415 
    416     /**
    417      * @param {!WebInspector.ExecutionContext} context
    418      */
    419     _addExecutionContext: function(context)
    420     {
    421         var insertAt = insertionIndexForObjectInListSortedByFunction(context, this._executionContexts, WebInspector.ExecutionContext.comparator);
    422         this._executionContexts.splice(insertAt, 0, context);
    423         this.dispatchEventToListeners(WebInspector.FrameExecutionContextList.EventTypes.ContextAdded, this);
    424     },
    425 
    426     executionContexts: function()
    427     {
    428         return this._executionContexts;
    429     },
    430 
    431     mainWorldContext: function()
    432     {
    433         return this._executionContexts[0];
    434     },
    435 
    436     /**
    437      * @param {string} securityOrigin
    438      */
    439     contextBySecurityOrigin: function(securityOrigin)
    440     {
    441         for (var i = 0; i < this._executionContexts.length; ++i) {
    442             var context = this._executionContexts[i];
    443             if (!context.isMainWorldContext && context.name === securityOrigin)
    444                 return context;
    445         }
    446     },
    447 
    448     get frameId()
    449     {
    450         return this._frame.id;
    451     },
    452 
    453     get url()
    454     {
    455         return this._frame.url;
    456     },
    457 
    458     get displayName()
    459     {
    460         if (!this._frame.parentFrame)
    461             return "<top frame>";
    462         var name = this._frame.name || "";
    463         var subtitle = new WebInspector.ParsedURL(this._frame.url).displayName;
    464         if (subtitle) {
    465             if (!name)
    466                 return subtitle;
    467             return name + "( " + subtitle + " )";
    468         }
    469         return "<iframe>";
    470     },
    471 
    472     __proto__: WebInspector.Object.prototype
    473 }
    474