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                 console.error(error);
    155                 callback(null, false);
    156                 return;
    157             }
    158 
    159             if (returnByValue)
    160                 callback(null, !!wasThrown, wasThrown ? null : result);
    161             else
    162                 callback(WebInspector.RemoteObject.fromPayload(result), !!wasThrown);
    163         }
    164         RuntimeAgent.evaluate(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, this._currentExecutionContext ? this._currentExecutionContext.id : undefined, returnByValue, generatePreview, evalCallback);
    165     },
    166 
    167     /**
    168      * @param {Element} proxyElement
    169      * @param {Range} wordRange
    170      * @param {boolean} force
    171      * @param {function(!Array.<string>, number=)} completionsReadyCallback
    172      */
    173     completionsForTextPrompt: function(proxyElement, wordRange, force, completionsReadyCallback)
    174     {
    175         // Pass less stop characters to rangeOfWord so the range will be a more complete expression.
    176         var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, " =:[({;,!+-*/&|^<>", proxyElement, "backward");
    177         var expressionString = expressionRange.toString();
    178         var prefix = wordRange.toString();
    179         this._completionsForExpression(expressionString, prefix, force, completionsReadyCallback);
    180     },
    181 
    182     /**
    183      * @param {string} expressionString
    184      * @param {string} prefix
    185      * @param {boolean} force
    186      * @param {function(!Array.<string>, number=)} completionsReadyCallback
    187      */
    188     _completionsForExpression: function(expressionString, prefix, force, completionsReadyCallback)
    189     {
    190         var lastIndex = expressionString.length - 1;
    191 
    192         var dotNotation = (expressionString[lastIndex] === ".");
    193         var bracketNotation = (expressionString[lastIndex] === "[");
    194 
    195         if (dotNotation || bracketNotation)
    196             expressionString = expressionString.substr(0, lastIndex);
    197 
    198         if (expressionString && parseInt(expressionString, 10) == expressionString) {
    199             // User is entering float value, do not suggest anything.
    200             completionsReadyCallback([]);
    201             return;
    202         }
    203 
    204         if (!prefix && !expressionString && !force) {
    205             completionsReadyCallback([]);
    206             return;
    207         }
    208 
    209         if (!expressionString && WebInspector.debuggerModel.selectedCallFrame())
    210             WebInspector.debuggerModel.getSelectedCallFrameVariables(receivedPropertyNames.bind(this));
    211         else
    212             this.evaluate(expressionString, "completion", true, true, false, false, evaluated.bind(this));
    213 
    214         function evaluated(result, wasThrown)
    215         {
    216             if (!result || wasThrown) {
    217                 completionsReadyCallback([]);
    218                 return;
    219             }
    220 
    221             function getCompletions(primitiveType)
    222             {
    223                 var object;
    224                 if (primitiveType === "string")
    225                     object = new String("");
    226                 else if (primitiveType === "number")
    227                     object = new Number(0);
    228                 else if (primitiveType === "boolean")
    229                     object = new Boolean(false);
    230                 else
    231                     object = this;
    232 
    233                 var resultSet = {};
    234                 for (var o = object; o; o = o.__proto__) {
    235                     try {
    236                         var names = Object.getOwnPropertyNames(o);
    237                         for (var i = 0; i < names.length; ++i)
    238                             resultSet[names[i]] = true;
    239                     } catch (e) {
    240                     }
    241                 }
    242                 return resultSet;
    243             }
    244 
    245             if (result.type === "object" || result.type === "function")
    246                 result.callFunctionJSON(getCompletions, undefined, receivedPropertyNames.bind(this));
    247             else if (result.type === "string" || result.type === "number" || result.type === "boolean")
    248                 this.evaluate("(" + getCompletions + ")(\"" + result.type + "\")", "completion", false, true, true, false, receivedPropertyNamesFromEval.bind(this));
    249         }
    250 
    251         function receivedPropertyNamesFromEval(notRelevant, wasThrown, result)
    252         {
    253             if (result && !wasThrown)
    254                 receivedPropertyNames.call(this, result.value);
    255             else
    256                 completionsReadyCallback([]);
    257         }
    258 
    259         function receivedPropertyNames(propertyNames)
    260         {
    261             RuntimeAgent.releaseObjectGroup("completion");
    262             if (!propertyNames) {
    263                 completionsReadyCallback([]);
    264                 return;
    265             }
    266             var includeCommandLineAPI = (!dotNotation && !bracketNotation);
    267             if (includeCommandLineAPI) {
    268                 const commandLineAPI = ["dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear",
    269                     "getEventListeners", "debug", "undebug", "monitor", "unmonitor", "table", "$", "$$", "$x"];
    270                 for (var i = 0; i < commandLineAPI.length; ++i)
    271                     propertyNames[commandLineAPI[i]] = true;
    272             }
    273             this._reportCompletions(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, Object.keys(propertyNames));
    274         }
    275     },
    276 
    277     /**
    278      * @param {function(!Array.<string>, number=)} completionsReadyCallback
    279      * @param {boolean} dotNotation
    280      * @param {boolean} bracketNotation
    281      * @param {string} expressionString
    282      * @param {string} prefix
    283      * @param {Array.<string>} properties
    284      */
    285     _reportCompletions: function(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, properties) {
    286         if (bracketNotation) {
    287             if (prefix.length && prefix[0] === "'")
    288                 var quoteUsed = "'";
    289             else
    290                 var quoteUsed = "\"";
    291         }
    292 
    293         var results = [];
    294 
    295         if (!expressionString) {
    296             const keywords = ["break", "case", "catch", "continue", "default", "delete", "do", "else", "finally", "for", "function", "if", "in",
    297                               "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with"];
    298             properties = properties.concat(keywords);
    299         }
    300 
    301         properties.sort();
    302 
    303         for (var i = 0; i < properties.length; ++i) {
    304             var property = properties[i];
    305 
    306             if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
    307                 continue;
    308 
    309             if (bracketNotation) {
    310                 if (!/^[0-9]+$/.test(property))
    311                     property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
    312                 property += "]";
    313             }
    314 
    315             if (property.length < prefix.length)
    316                 continue;
    317             if (prefix.length && !property.startsWith(prefix))
    318                 continue;
    319 
    320             results.push(property);
    321         }
    322         completionsReadyCallback(results);
    323     },
    324 
    325     __proto__: WebInspector.Object.prototype
    326 }
    327 
    328 /**
    329  * @type {WebInspector.RuntimeModel}
    330  */
    331 WebInspector.runtimeModel = null;
    332 
    333 /**
    334  * @constructor
    335  * @implements {RuntimeAgent.Dispatcher}
    336  * @param {WebInspector.RuntimeModel} runtimeModel
    337  */
    338 WebInspector.RuntimeDispatcher = function(runtimeModel)
    339 {
    340     this._runtimeModel = runtimeModel;
    341 }
    342 
    343 WebInspector.RuntimeDispatcher.prototype = {
    344     executionContextCreated: function(context)
    345     {
    346         this._runtimeModel._executionContextCreated(context);
    347     }
    348 }
    349 
    350 /**
    351  * @constructor
    352  * @extends {WebInspector.Object}
    353  */
    354 WebInspector.ExecutionContext = function(id, name, isPageContext)
    355 {
    356     this.id = id;
    357     this.name = (isPageContext && !name) ? "<page context>" : name;
    358     this.isMainWorldContext = isPageContext;
    359 }
    360 
    361 /**
    362  * @param {!WebInspector.ExecutionContext} a
    363  * @param {!WebInspector.ExecutionContext} b
    364  * @return {number}
    365  */
    366 WebInspector.ExecutionContext.comparator = function(a, b)
    367 {
    368     // Main world context should always go first.
    369     if (a.isMainWorldContext)
    370         return -1;
    371     if (b.isMainWorldContext)
    372         return +1;
    373     return a.name.localeCompare(b.name);
    374 }
    375 
    376 /**
    377  * @constructor
    378  * @extends {WebInspector.Object}
    379  */
    380 WebInspector.FrameExecutionContextList = function(frame)
    381 {
    382     this._frame = frame;
    383     this._executionContexts = [];
    384 }
    385 
    386 WebInspector.FrameExecutionContextList.EventTypes = {
    387     ContextsUpdated: "ContextsUpdated",
    388     ContextAdded: "ContextAdded"
    389 }
    390 
    391 WebInspector.FrameExecutionContextList.prototype =
    392 {
    393     _frameNavigated: function(frame)
    394     {
    395         this._frame = frame;
    396         this._executionContexts = [];
    397         this.dispatchEventToListeners(WebInspector.FrameExecutionContextList.EventTypes.ContextsUpdated, this);
    398     },
    399 
    400     /**
    401      * @param {!WebInspector.ExecutionContext} context
    402      */
    403     _addExecutionContext: function(context)
    404     {
    405         var insertAt = insertionIndexForObjectInListSortedByFunction(context, this._executionContexts, WebInspector.ExecutionContext.comparator);
    406         this._executionContexts.splice(insertAt, 0, context);
    407         this.dispatchEventToListeners(WebInspector.FrameExecutionContextList.EventTypes.ContextAdded, this);
    408     },
    409 
    410     executionContexts: function()
    411     {
    412         return this._executionContexts;
    413     },
    414 
    415     mainWorldContext: function()
    416     {
    417         return this._executionContexts[0];
    418     },
    419 
    420     /**
    421      * @param {string} securityOrigin
    422      */
    423     contextBySecurityOrigin: function(securityOrigin)
    424     {
    425         for (var i = 0; i < this._executionContexts.length; ++i) {
    426             var context = this._executionContexts[i];
    427             if (!context.isMainWorldContext && context.name === securityOrigin)
    428                 return context;
    429         }
    430     },
    431 
    432     get frameId()
    433     {
    434         return this._frame.id;
    435     },
    436 
    437     get url()
    438     {
    439         return this._frame.url;
    440     },
    441 
    442     get displayName()
    443     {
    444         if (!this._frame.parentFrame)
    445             return "<top frame>";
    446         var name = this._frame.name || "";
    447         var subtitle = new WebInspector.ParsedURL(this._frame.url).displayName;
    448         if (subtitle) {
    449             if (!name)
    450                 return subtitle;
    451             return name + "( " + subtitle + " )";
    452         }
    453         return "<iframe>";
    454     },
    455 
    456     __proto__: WebInspector.Object.prototype
    457 }
    458