blob: cc2b1688c08f3628a9bc46da680bef3b279b926e [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
function DebuggerClient() {
this.DEBUGGER_DETACHED = 0;
this.DEBUGGER_ATTACHING = 1;
this.DEBUGGER_ATTACHED = 2;
this.scripts = [];
this.attachState = this.DEBUGGER_DETACHED;
this.onAttachCallback = this.onAttach.bind(this);
this.onEventCallback = this.onEvent.bind(this);
this.executionContext = undefined;
}
// Attaches to the debugger and listens for debug events.
// Enables the domains we care about here.
DebuggerClient.prototype.attach = function() {
if (this.attachState == this.DEBUGGER_DETACHED) {
this.attachState = this.DEBUGGER_ATTACHING;
printToMessageLog(messageLog.INTERACTIVE,
'Attempting to attach to debugger...');
this.scripts = [];
debugHub.onEvent.addListener(this.onEventCallback);
debugHub.attach(this.onAttachCallback);
this.sendCommand('Console.enable');
this.sendCommand('Log.enable');
this.sendCommand('Runtime.enable');
} else if (this.attachState == this.DEBUGGER_ATTACHING) {
printToMessageLog(messageLog.INTERACTIVE,
'Still attempting to attach to debugger...');
}
}
// Local method to list the parsed scripts the client has been notifed of
// via the |Debugger.scriptParsed| event. Maps the possibly very long script id
// from the debug dispatcher to a more human-readable 0-based index.
DebuggerClient.prototype.getScripts = function(scriptId) {
for (var i in this.scripts) {
var index = this.pad(i, 3);
var scriptUrl = this.scripts[i].url;
printToMessageLog(messageLog.INTERACTIVE, index + ': ' + scriptUrl);
}
}
//--- Commands.
// Each debugger command has an associated callback to get the result.
DebuggerClient.prototype.getScriptSource = function(scriptId) {
// If the id looks like an index into the local script array, look it up there.
if (scriptId >= 0 && scriptId < this.scripts.length) {
scriptId = this.scripts[scriptId].scriptId;
}
var method = 'Debugger.getScriptSource';
var params = { 'scriptId': scriptId.toString() };
var callback = this.getScriptSourceCallback.bind(this);
this.sendCommand(method, params, callback);
}
DebuggerClient.prototype.getScriptSourceCallback = function(result) {
var scriptSource = result.scriptSource;
var lines = scriptSource.split('\n');
for (var i = 0; i < lines.length; i++) {
var index = this.pad(i + 1, 4);
printToMessageLog(messageLog.INFO, index + ': ' + lines[i]);
}
}
DebuggerClient.prototype.evaluate = function(expression) {
var callback = this.evaluateCallback.bind(this);
var method = 'Runtime.evaluate';
var params = {};
params.contextId = this.executionContext;
params.expression = expression;
params.generatePreview = true;
params.includeCommandLineAPI = true;
params.objectGroup = 'console';
params.returnByValue = false;
this.sendCommand(method, params, callback);
}
DebuggerClient.prototype.evaluateCallback = function(result) {
if (result.wasThrown) {
printToMessageLog(messageLog.ERROR,
'Uncaught ' + result.result.description);
} else if (result.result.preview) {
printToMessageLog(messageLog.INFO, result.result.preview.description);
if (result.result.preview.properties) {
for (var i = 0; i < result.result.preview.properties.length; ++i) {
var property = result.result.preview.properties[i];
printToMessageLog(messageLog.INFO,
' ' + property.name + ': ' + property.value);
}
}
if (result.result.preview.overflow) {
printToMessageLog(messageLog.INFO, ' ...');
}
} else if (result.result.description) {
printToMessageLog(messageLog.INFO, result.result.description);
} else if (result.result.value) {
printToMessageLog(messageLog.INFO, result.result.value.toString());
}
printToMessageLog(messageLog.INFO, '');
}
// All debugger commands are routed through this method. Converts the command
// parameters into a JSON string to pass to the debug dispatcher.
DebuggerClient.prototype.sendCommand = function(method, commandParams,
callback) {
var jsonParams = JSON.stringify(commandParams);
var responseCallback = this.responseCallback.bind(this, method, callback);
debugHub.sendCommand(method, jsonParams, responseCallback);
}
// All command responses are routed through this method. Parses the JSON
// response from the debug dispatcher, checks for errors and passes on to the
// command-specific callback to handle the result.
DebuggerClient.prototype.responseCallback = function(method, callback,
responseString) {
var response = JSON.parse(responseString);
if (response && response.error) {
printToMessageLog(
messageLog.ERROR,
'[ERROR(' + response.error.code + '):' + method + '] ' +
response.error.message);
} else if (callback) {
if (response) {
callback(response.result);
} else {
callback(null);
}
}
}
//-- Events.
DebuggerClient.prototype.onAttach = function() {
if (debugHub.lastError) {
printToMessageLog(messageLog.WARNING, 'Could not attach to debugger.');
this.attachState = this.DEBUGGER_DETACHED;
} else {
printToMessageLog(messageLog.INTERACTIVE, 'Debugger attached.');
this.attachState = this.DEBUGGER_ATTACHED;
}
}
// All events generated by the debug dispatcher are routed through this method.
// Parses the JSON string and passes on to the appropriate handler according to
// the method name.
DebuggerClient.prototype.onEvent = function(method, paramString) {
var params = JSON.parse(paramString);
if (method == 'Console.messageAdded') {
this.onConsoleMessageAdded(params)
} else if (method == 'Debugger.scriptParsed') {
this.onScriptParsed(params);
} else if (method == 'Inspector.detached') {
this.onDetached(params);
} else if (method == 'Log.browserEntryAdded' || method == 'Log.entryAdded') {
this.onLogEntryAdded(params);
} else if (method == 'Runtime.consoleAPICalled') {
this.onConsoleApiCalled(params);
} else if (method == 'Runtime.executionContextCreated') {
this.onExecutionContextCreated(params);
}
}
DebuggerClient.prototype.onDetached = function() {
printToMessageLog(messageLog.INTERACTIVE, 'Debugger detached.');
this.attachState = this.DEBUGGER_DETACHED;
}
DebuggerClient.prototype.onExecutionContextCreated = function(params) {
this.executionContext = params.context.id;
printToMessageLog(messageLog.INFO,
'Execution context created: ' + this.executionContext);
}
DebuggerClient.prototype.onLogEntryAdded = function(params) {
printToMessageLog(params.entry.level, params.entry.text);
}
DebuggerClient.prototype.onConsoleMessageAdded = function(params) {
// Translate Console.messageAdded params to Runtime.consoleAPICalled params.
var consoleApiParams = {
type: params.message.level,
args: [ { type: 'string', value: params.message.text } ]
};
this.onConsoleApiCalled(consoleApiParams);
}
DebuggerClient.prototype.onConsoleApiCalled = function(params) {
var severity = params.type;
if (severity === "assert") {
severity = messageLog.ERROR;
} else if (severity === "log") {
severity = messageLog.INFO;
}
var message = '';
if (params.args.length) {
var arg = params.args[0];
if (arg.unserializableValue) {
message = arg.unserializableValue;
} else if (typeof arg.value !== 'object' || arg.value === null) {
message = arg.value + '';
} else if (arg.description) {
message = arg.description;
}
}
printToMessageLog(messageLog.CONSOLE + severity, message);
}
DebuggerClient.prototype.onScriptParsed = function(params) {
this.scripts.push(params);
}
//--- Utils.
DebuggerClient.prototype.pad = function(number, minLength) {
var result = number.toString();
while (result.length < minLength) {
result = ' ' + result;
}
return result;
}