blob: cd57c51f828d97119165908ad9a7d43944608269 [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @constructor
* @extends {WebInspector.Object}
* @param {WebInspector.ResourceTreeModel} resourceTreeModel
*/
WebInspector.RuntimeModel = function(resourceTreeModel)
{
resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this);
resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._didLoadCachedResources, this);
this._frameIdToContextList = {};
}
WebInspector.RuntimeModel.Events = {
FrameExecutionContextListAdded: "FrameExecutionContextListAdded",
FrameExecutionContextListRemoved: "FrameExecutionContextListRemoved",
}
WebInspector.RuntimeModel.prototype = {
/**
* @param {WebInspector.ExecutionContext} executionContext
*/
setCurrentExecutionContext: function(executionContext)
{
this._currentExecutionContext = executionContext;
},
/**
* @return {WebInspector.ExecutionContext}
*/
currentExecutionContext: function()
{
return this._currentExecutionContext;
},
/**
* @return {Array.<WebInspector.FrameExecutionContextList>}
*/
contextLists: function()
{
return Object.values(this._frameIdToContextList);
},
/**
* @param {WebInspector.ResourceTreeFrame} frame
* @return {WebInspector.FrameExecutionContextList}
*/
contextListByFrame: function(frame)
{
return this._frameIdToContextList[frame.id];
},
_frameAdded: function(event)
{
var frame = event.data;
var context = new WebInspector.FrameExecutionContextList(frame);
this._frameIdToContextList[frame.id] = context;
this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.FrameExecutionContextListAdded, context);
},
_frameNavigated: function(event)
{
var frame = event.data;
var context = this._frameIdToContextList[frame.id];
if (context)
context._frameNavigated(frame);
},
_frameDetached: function(event)
{
var frame = event.data;
var context = this._frameIdToContextList[frame.id];
if (!context)
return;
this.dispatchEventToListeners(WebInspector.RuntimeModel.Events.FrameExecutionContextListRemoved, context);
delete this._frameIdToContextList[frame.id];
},
_didLoadCachedResources: function()
{
InspectorBackend.registerRuntimeDispatcher(new WebInspector.RuntimeDispatcher(this));
RuntimeAgent.enable();
},
_executionContextCreated: function(context)
{
var contextList = this._frameIdToContextList[context.frameId];
// FIXME(85708): this should never happen
if (!contextList)
return;
contextList._addExecutionContext(new WebInspector.ExecutionContext(context.id, context.name, context.isPageContext));
},
/**
* @param {string} expression
* @param {string} objectGroup
* @param {boolean} includeCommandLineAPI
* @param {boolean} doNotPauseOnExceptionsAndMuteConsole
* @param {boolean} returnByValue
* @param {boolean} generatePreview
* @param {function(?WebInspector.RemoteObject, boolean, RuntimeAgent.RemoteObject=)} callback
*/
evaluate: function(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback)
{
if (WebInspector.debuggerModel.selectedCallFrame()) {
WebInspector.debuggerModel.evaluateOnSelectedCallFrame(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback);
return;
}
if (!expression) {
// There is no expression, so the completion should happen against global properties.
expression = "this";
}
/**
* @param {?Protocol.Error} error
* @param {RuntimeAgent.RemoteObject} result
* @param {boolean=} wasThrown
*/
function evalCallback(error, result, wasThrown)
{
if (error) {
callback(null, false);
return;
}
if (returnByValue)
callback(null, !!wasThrown, wasThrown ? null : result);
else
callback(WebInspector.RemoteObject.fromPayload(result), !!wasThrown);
}
RuntimeAgent.evaluate(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, this._currentExecutionContext ? this._currentExecutionContext.id : undefined, returnByValue, generatePreview, evalCallback);
},
/**
* @param {Element} proxyElement
* @param {Range} wordRange
* @param {boolean} force
* @param {function(!Array.<string>, number=)} completionsReadyCallback
*/
completionsForTextPrompt: function(proxyElement, wordRange, force, completionsReadyCallback)
{
// Pass less stop characters to rangeOfWord so the range will be a more complete expression.
var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, " =:[({;,!+-*/&|^<>", proxyElement, "backward");
var expressionString = expressionRange.toString();
var prefix = wordRange.toString();
this._completionsForExpression(expressionString, prefix, force, completionsReadyCallback);
},
/**
* @param {string} expressionString
* @param {string} prefix
* @param {boolean} force
* @param {function(!Array.<string>, number=)} completionsReadyCallback
*/
_completionsForExpression: function(expressionString, prefix, force, completionsReadyCallback)
{
var lastIndex = expressionString.length - 1;
var dotNotation = (expressionString[lastIndex] === ".");
var bracketNotation = (expressionString[lastIndex] === "[");
if (dotNotation || bracketNotation)
expressionString = expressionString.substr(0, lastIndex);
if (expressionString && parseInt(expressionString, 10) == expressionString) {
// User is entering float value, do not suggest anything.
completionsReadyCallback([]);
return;
}
if (!prefix && !expressionString && !force) {
completionsReadyCallback([]);
return;
}
if (!expressionString && WebInspector.debuggerModel.selectedCallFrame())
WebInspector.debuggerModel.getSelectedCallFrameVariables(receivedPropertyNames.bind(this));
else
this.evaluate(expressionString, "completion", true, true, false, false, evaluated.bind(this));
function evaluated(result, wasThrown)
{
if (!result || wasThrown) {
completionsReadyCallback([]);
return;
}
function getCompletions(primitiveType)
{
var object;
if (primitiveType === "string")
object = new String("");
else if (primitiveType === "number")
object = new Number(0);
else if (primitiveType === "boolean")
object = new Boolean(false);
else
object = this;
var resultSet = {};
for (var o = object; o; o = o.__proto__) {
try {
var names = Object.getOwnPropertyNames(o);
for (var i = 0; i < names.length; ++i)
resultSet[names[i]] = true;
} catch (e) {
}
}
return resultSet;
}
if (result.type === "object" || result.type === "function")
result.callFunctionJSON(getCompletions, undefined, receivedPropertyNames.bind(this));
else if (result.type === "string" || result.type === "number" || result.type === "boolean")
this.evaluate("(" + getCompletions + ")(\"" + result.type + "\")", "completion", false, true, true, false, receivedPropertyNamesFromEval.bind(this));
}
function receivedPropertyNamesFromEval(notRelevant, wasThrown, result)
{
if (result && !wasThrown)
receivedPropertyNames.call(this, result.value);
else
completionsReadyCallback([]);
}
function receivedPropertyNames(propertyNames)
{
RuntimeAgent.releaseObjectGroup("completion");
if (!propertyNames) {
completionsReadyCallback([]);
return;
}
var includeCommandLineAPI = (!dotNotation && !bracketNotation);
if (includeCommandLineAPI) {
const commandLineAPI = ["dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear",
"getEventListeners", "debug", "undebug", "monitor", "unmonitor", "table", "$", "$$", "$x"];
for (var i = 0; i < commandLineAPI.length; ++i)
propertyNames[commandLineAPI[i]] = true;
}
this._reportCompletions(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, Object.keys(propertyNames));
}
},
/**
* @param {function(!Array.<string>, number=)} completionsReadyCallback
* @param {boolean} dotNotation
* @param {boolean} bracketNotation
* @param {string} expressionString
* @param {string} prefix
* @param {Array.<string>} properties
*/
_reportCompletions: function(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, properties) {
if (bracketNotation) {
if (prefix.length && prefix[0] === "'")
var quoteUsed = "'";
else
var quoteUsed = "\"";
}
var results = [];
if (!expressionString) {
const keywords = ["break", "case", "catch", "continue", "default", "delete", "do", "else", "finally", "for", "function", "if", "in",
"instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with"];
properties = properties.concat(keywords);
}
properties.sort();
for (var i = 0; i < properties.length; ++i) {
var property = properties[i];
if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
continue;
if (bracketNotation) {
if (!/^[0-9]+$/.test(property))
property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
property += "]";
}
if (property.length < prefix.length)
continue;
if (prefix.length && !property.startsWith(prefix))
continue;
results.push(property);
}
completionsReadyCallback(results);
},
__proto__: WebInspector.Object.prototype
}
/**
* @type {WebInspector.RuntimeModel}
*/
WebInspector.runtimeModel = null;
/**
* @constructor
* @implements {RuntimeAgent.Dispatcher}
* @param {WebInspector.RuntimeModel} runtimeModel
*/
WebInspector.RuntimeDispatcher = function(runtimeModel)
{
this._runtimeModel = runtimeModel;
}
WebInspector.RuntimeDispatcher.prototype = {
executionContextCreated: function(context)
{
this._runtimeModel._executionContextCreated(context);
}
}
/**
* @constructor
* @extends {WebInspector.Object}
*/
WebInspector.ExecutionContext = function(id, name, isPageContext)
{
this.id = id;
this.name = (isPageContext && !name) ? "<page context>" : name;
this.isMainWorldContext = isPageContext;
}
/**
* @param {!WebInspector.ExecutionContext} a
* @param {!WebInspector.ExecutionContext} b
* @return {number}
*/
WebInspector.ExecutionContext.comparator = function(a, b)
{
// Main world context should always go first.
if (a.isMainWorldContext)
return -1;
if (b.isMainWorldContext)
return +1;
return a.name.localeCompare(b.name);
}
/**
* @constructor
* @extends {WebInspector.Object}
*/
WebInspector.FrameExecutionContextList = function(frame)
{
this._frame = frame;
this._executionContexts = [];
}
WebInspector.FrameExecutionContextList.EventTypes = {
ContextsUpdated: "ContextsUpdated",
ContextAdded: "ContextAdded"
}
WebInspector.FrameExecutionContextList.prototype =
{
_frameNavigated: function(frame)
{
this._frame = frame;
this._executionContexts = [];
this.dispatchEventToListeners(WebInspector.FrameExecutionContextList.EventTypes.ContextsUpdated, this);
},
/**
* @param {!WebInspector.ExecutionContext} context
*/
_addExecutionContext: function(context)
{
var insertAt = insertionIndexForObjectInListSortedByFunction(context, this._executionContexts, WebInspector.ExecutionContext.comparator);
this._executionContexts.splice(insertAt, 0, context);
this.dispatchEventToListeners(WebInspector.FrameExecutionContextList.EventTypes.ContextAdded, this);
},
executionContexts: function()
{
return this._executionContexts;
},
mainWorldContext: function()
{
return this._executionContexts[0];
},
/**
* @param {string} securityOrigin
*/
contextBySecurityOrigin: function(securityOrigin)
{
for (var i = 0; i < this._executionContexts.length; ++i) {
var context = this._executionContexts[i];
if (!context.isMainWorldContext && context.name === securityOrigin)
return context;
}
},
get frameId()
{
return this._frame.id;
},
get url()
{
return this._frame.url;
},
get displayName()
{
if (!this._frame.parentFrame)
return "<top frame>";
var name = this._frame.name || "";
var subtitle = new WebInspector.ParsedURL(this._frame.url).displayName;
if (subtitle) {
if (!name)
return subtitle;
return name + "( " + subtitle + " )";
}
return "<iframe>";
},
__proto__: WebInspector.Object.prototype
}