diff --git a/.gitignore b/.gitignore index dc1d164..d12e4bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .DS_Store node_modules -resource/cs +test/dev/resource/cs # Editor backup files *.bak diff --git a/Makefile b/Makefile index 0c38f97..8505981 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY:parse parse-debug +.PHONY:parse parse-debug test all:parse @@ -8,7 +8,10 @@ parse: #jison clearsilver.y clearsilver.l -m amd - parse-debug: @cd parse && \ jison clearsilver.y clearsilver.l --debug true + +test: + @cd test/dev && \ + jencs test.cs test.hdf --debug-brk=true diff --git a/README.md b/README.md index 3a4c3c9..df1d871 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,13 @@ clearsilver template engine with debugger ##usage -1.安装依赖 + Usage: jencs [data] [options] - npm -g install express - npm -g install socket.io - npm -g install nomnom + path clearsilver template file + data hdf data file -2.使用示例 - - git clone https://github.com/penglei/jencs - cd jencs - node test.js --enable-debugger - -使用chrome浏览器访问下面的地址: - - http://127.0.0.1:10080/ + Options: + --debug enable debugger [false] + --debug-brk enable debugger and break on first line. [false] + --include the dir of include command [.] + --ignore-whitespace ignore \r\n\t of clearsilver template file [false] diff --git a/bin/jencs b/bin/jencs index d3c5aba..f85ca32 100755 --- a/bin/jencs +++ b/bin/jencs @@ -3,30 +3,86 @@ var fs = require('fs'); var path = require('path'); -var CSInterpreter = require('../interpreter'); - +var CSInterpreter = require('jencs'); var Engine = CSInterpreter.Engine; +var AST = CSInterpreter.AST; -var dataHdfFile; -var entryCsFile; +//内容\n\r\t过滤器 +function ContentWhiteFilter(valueStr, astNode){ + return astNode instanceof AST.AST_Content ? valueStr.replace(/[\r\n\t]/g, '') : valueStr; +} -process.argv.forEach(function (val, index, array) { - if (/\.cs$/.test(val)){ - entryCsFile = val; - } else if (/\.hdf$/.test(val)){ - dataHdfFile = val; - } -}); +var opts = require('nomnom') + .script("jencs") + .options({ + "path": { + position: 0, + required: true, + list: false, + help: "clearsilver template file" + }, + "data": { + position: 1, + required: false, + help: "hdf data file" + }, + "debug": { + //flag: true, + default: false, + help: "enable debugger" + }, + "debug-brk": { + //flag: true, + default: false, + help: "enable debugger and break on first line." + }, + "include":{ + default: '.', + help: "the dir of include command " + }, + "ignore-whitespace": { + default: false, + help: "ignore \\r\\n\\t of clearsilver template file" + }, + "port": { + default: "10080", + help:"debugger web server listen port" + } + }).parse(); + +if (opts["debug-brk"]) { + opts["debug"] = true; + opts.debugBreakFirst = true; +} + +var mainCsSource = fs.readFileSync(opts.path, "utf8"); +var csIncludeRoot = path.resolve(opts.include || "."); +if (opts.data){ + var dataSource = fs.readFileSync(path.resolve(opts.data), "utf8"); +} else { + var dataSource = ""; +} -var mainCsSource = fs.readFileSync(entryCsFile, "utf8"); -var dataSource = fs.readFileSync(dataHdfFile, "utf8"); +var csEngine = new Engine(); -var TestCSEngine = new Engine(); +//必须先设置inlcude的回调,否者分析源码时会找不到包含的文件 +csEngine.setLexerInclude(function(filename){ + return fs.readFileSync(path.resolve(csIncludeRoot, filename), "utf8"); +}); + +if (opts["ignore-whitespace"]) csEngine.addOutputFilter(ContentWhiteFilter); + +csEngine.setConfig({ + "debug": opts.debug, + "port":opts.port, + "debugBreakFirst": opts.debugBreakFirst, + "includeBase": csIncludeRoot//包含文件的起始路径。暂时只给调试器使用这个选项 +}); -TestCSEngine.initEntrySource(mainCsSource); -TestCSEngine.setEndListener(function(){ +csEngine.setEndListener(function(result){ process.stdout.write(this.result); }); -TestCSEngine.run(dataSource); +csEngine.initEntrySource(mainCsSource, opts.path); +csEngine.run(dataSource); diff --git a/interpreter/block.js b/interpreter/block.js index 4199148..1542532 100644 --- a/interpreter/block.js +++ b/interpreter/block.js @@ -16,6 +16,7 @@ def_execute(ast.AST_If, function(context){ if (testExpr.isTrue()){ this.gen_body(context); } else if (this.alternate) { + //在else处暂停一下 this.executer.command(this.alternate.execute, this.alternate); //this.alternate.execute(context); } @@ -172,8 +173,10 @@ def_execute(ast.AST_Loop, function(context){ }); -ast.AST_MacroDef.proto("execJump", function(context, _symbolAlias) { +ast.AST_MacroDef.proto("execJump", function(context, _symbolAlias, caller) { var scope = context.enterScope(this); + context.currentMacro = this; + scope.caller = caller; //初始化实参 for (var name in _symbolAlias){ diff --git a/interpreter/block_.js b/interpreter/block_.js index 5fac426..7c89b3f 100644 --- a/interpreter/block_.js +++ b/interpreter/block_.js @@ -144,8 +144,9 @@ def_execute(ast.AST_Loop, function(context){ context.leaveScope(); }); -ast.AST_MacroDef.proto("execJump", function(context, _symbolAlias) { +ast.AST_MacroDef.proto("execJump", function(context, _symbolAlias, caller) { var scope = context.enterScope(this); + scope.caller = caller; //初始化实参 for(var name in _symbolAlias){ @@ -172,10 +173,10 @@ def_execute(ast.AST_Escape, function(context){ def_execute(ast.AST_Program, function(context){ - context.enterScope(this); + //context.enterScope(this); this.gen_body(context); - context.leaveScope(); + //context.leaveScope(); this.executer.emit("end"); }); diff --git a/interpreter/debugger/client/Backend.js b/interpreter/debugger/client/Backend.js index 2d6f7d7..9770780 100644 --- a/interpreter/debugger/client/Backend.js +++ b/interpreter/debugger/client/Backend.js @@ -3,11 +3,13 @@ define(function(require){ var EventEmitter = require("events").EventEmitter; function Backend(conn){ + this._conn = conn; this._connected = false; this._requests = {}; this._requests._count = 0; + this.replyArgs = { }; conn.on('message', this._dispatchHandler.bind(this)); conn.on('connect', this._connectedHandler.bind(this)); @@ -23,12 +25,17 @@ define(function(require){ } Backend.prototype.sendMessage = function(message){ if (!this._connected) return false; + var request = this._requests[message.id]; + if (request.callback && message.method && this.replyArgs[message.method]){ + request.replyArg = this.replyArgs[message.method]; + } var payload = typeof message == 'string' ? message : JSON.stringify(message); this._conn.send(payload); }; Backend.prototype._connectedHandler = function(){ this._connected = true; + this.dispatchEventToListeners("connected"); console.log("debugger connected >>>"); }; @@ -54,12 +61,21 @@ define(function(require){ Backend.prototype._dispatchHandler = function(message){ var messageObject = (typeof message === "string") ? JSON.parse(message) : message; if ("id" in messageObject){//confirm request - console.log(messageObject); + //console.log(message); var request = this._requests[messageObject.id]; delete this._requests[messageObject.id]; this._requests._count--; - if (request.callback){ - request.callback.call(request.that); + if (messageObject.error){ + } else { + if (request.callback){ + var argumentsArray = [ null ]; + var paramNames = request.replyArg; + if (paramNames && messageObject.result){ + for(var i = 0; i < paramNames.length; i++) + argumentsArray.push(messageObject.result[paramNames[i]]); + } + request.callback.apply(request.that, argumentsArray); + } } } else { var methodName = messageObject.method; diff --git a/interpreter/debugger/client/BreakpointManager.js b/interpreter/debugger/client/BreakpointManager.js new file mode 100644 index 0000000..301b2c3 --- /dev/null +++ b/interpreter/debugger/client/BreakpointManager.js @@ -0,0 +1,685 @@ +define(function(require, exports){ +/* + * Copyright (C) 2011 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. + */ + +var EventObjectEmitter = require("events").EventObjectEmitter; +var DebugModel = require("DebugModel"); +var Workspace = require("Workspace").Workspace; +var ResourceTypes = require("Resource").ResourceTypes; +var UISourceCode = require("UISourceCode"); +var UILocation = require("UILocation"); + +/** + * @constructor + * @extends {Object} + * @param {Setting} breakpointStorage + * @param {DebuggerModel} debuggerModel + * @param {Workspace} workspace + */ +function BreakpointManager(breakpointStorage, debuggerModel, workspace) +{ + this._storage = new BreakpointManager.Storage(this, breakpointStorage); + this._debuggerModel = debuggerModel; + this._workspace = workspace; + + this._breakpoints = new Map(); + this._breakpointForDebuggerId = {}; + this._breakpointsForUISourceCode = new Map(); + this._sourceFilesWithRestoredBreakpoints = {}; + + this._debuggerModel.addEventListener(DebugModel.Events.BreakpointResolved, this._breakpointResolved, this); + this._workspace.addEventListener(Workspace.Events.ProjectWillReset, this._projectWillReset, this); + this._workspace.addEventListener(Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this); +} + +BreakpointManager.Events = { + BreakpointAdded: "breakpoint-added", + BreakpointRemoved: "breakpoint-removed" +} + +BreakpointManager.sourceFileId = function(uiSourceCode) +{ + if (!uiSourceCode.url) + return ""; + var deobfuscatedPrefix = uiSourceCode.formatted() ? "deobfuscated:" : ""; + return deobfuscatedPrefix + uiSourceCode.uri(); +} + +BreakpointManager.prototype = { + /** + * @param {UISourceCode} uiSourceCode + */ + _restoreBreakpoints: function(uiSourceCode) + { + var sourceFileId = BreakpointManager.sourceFileId(uiSourceCode); + if (!sourceFileId || this._sourceFilesWithRestoredBreakpoints[sourceFileId]) + return; + this._sourceFilesWithRestoredBreakpoints[sourceFileId] = true; + + // Erase provisional breakpoints prior to restoring them. + for (var debuggerId in this._breakpointForDebuggerId) { + var breakpoint = this._breakpointForDebuggerId[debuggerId]; + if (breakpoint._sourceFileId !== sourceFileId) + continue; + breakpoint.remove(true); + } + this._storage._restoreBreakpoints(uiSourceCode); + }, + + /** + * @param {Event} event + */ + _uiSourceCodeAdded: function(event) + { + var uiSourceCode = /** @type {UISourceCode} */ (event.data); + this._restoreBreakpoints(uiSourceCode); + if (uiSourceCode.contentType() === ResourceTypes.Script || uiSourceCode.contentType() === resourceTypes.Document) { + uiSourceCode.addEventListener(UISourceCode.Events.SourceMappingChanged, this._uiSourceCodeMappingChanged, this); + uiSourceCode.addEventListener(UISourceCode.Events.FormattedChanged, this._uiSourceCodeFormatted, this); + } + }, + + /** + * @param {Event} event + */ + _uiSourceCodeFormatted: function(event) + { + var uiSourceCode = /** @type {UISourceCode} */ (event.target); + this._restoreBreakpoints(uiSourceCode); + }, + + /** + * @param {UISourceCode} uiSourceCode + */ + _resetBreakpoints: function(uiSourceCode) + { + var sourceFileId = BreakpointManager.sourceFileId(uiSourceCode); + var breakpoints = this._breakpoints.keys(); + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = breakpoints[i]; + if (breakpoint._sourceFileId !== sourceFileId) + return; + if (breakpoint.enabled()) { + breakpoint._removeFromDebugger(); + breakpoint._setInDebugger(); + } + } + }, + + /** + * @param {Event} event + */ + _uiSourceCodeMappingChanged: function(event) + { + var identityHasChanged = /** @type {boolean} */ (event.data.identityHasChanged); + if (!identityHasChanged) + return; + var uiSourceCode = /** @type {UISourceCode} */ (event.target); + this._resetBreakpoints(uiSourceCode); + }, + + /** + * @param {UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {string} condition + * @param {boolean} enabled + * @return {BreakpointManager.Breakpoint} + */ + setBreakpoint: function(uiSourceCode, lineNumber, condition, enabled) + { + this._debuggerModel.setBreakpointsActive(true); + return this._innerSetBreakpoint(uiSourceCode, lineNumber, condition, enabled); + }, + + /** + * @param {UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {string} condition + * @param {boolean} enabled + * @return {BreakpointManager.Breakpoint} + */ + _innerSetBreakpoint: function(uiSourceCode, lineNumber, condition, enabled) + { + var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber); + if (breakpoint) { + breakpoint._updateBreakpoint(condition, enabled); + return breakpoint; + } + breakpoint = new BreakpointManager.Breakpoint(this, uiSourceCode, lineNumber, condition, enabled); + this._breakpoints.put(breakpoint); + return breakpoint; + }, + + /** + * @param {UISourceCode} uiSourceCode + * @param {number} lineNumber + * @return {?BreakpointManager.Breakpoint} + */ + findBreakpoint: function(uiSourceCode, lineNumber) + { + var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode); + var lineBreakpoints = breakpoints ? breakpoints[lineNumber] : null; + return lineBreakpoints ? lineBreakpoints[0] : null; + }, + + /** + * @param {UISourceCode} uiSourceCode + * @return {Array.} + */ + breakpointsForUISourceCode: function(uiSourceCode) + { + var result = []; + var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = breakpoints[i]; + var uiLocation = breakpoint._primaryUILocation; + if (uiLocation.uiSourceCode === uiSourceCode) + result.push(breakpoint); + } + return result; + }, + + /** + * @return {Array.} + */ + allBreakpoints: function() + { + var result = []; + var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); + return breakpoints; + }, + + /** + * @param {UISourceCode} uiSourceCode + * @return {Array.<{breakpoint: BreakpointManager.Breakpoint, uiLocation: UILocation}>} + */ + breakpointLocationsForUISourceCode: function(uiSourceCode) + { + var result = []; + var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = breakpoints[i]; + var uiLocations = Object.values(breakpoint._uiLocations); + for (var j = 0; j < uiLocations.length; ++j) { + var uiLocation = uiLocations[j]; + if (uiLocation.uiSourceCode === uiSourceCode) + result.push({breakpoint: breakpoint, uiLocation: uiLocations[j]}); + } + } + return result; + }, + + /** + * @return {Array.<{breakpoint: BreakpointManager.Breakpoint, uiLocation: UILocation}>} + */ + allBreakpointLocations: function() + { + var result = []; + var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = breakpoints[i]; + var uiLocations = Object.values(breakpoint._uiLocations); + for (var j = 0; j < uiLocations.length; ++j) + result.push({breakpoint: breakpoint, uiLocation: uiLocations[j]}); + } + return result; + }, + + /** + * @param {boolean} toggleState + */ + toggleAllBreakpoints: function(toggleState) + { + var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = breakpoints[i]; + if (breakpoint.enabled() != toggleState) + breakpoint.setEnabled(toggleState); + } + }, + + removeAllBreakpoints: function() + { + var breakpoints = /** @type {Array.} */(this._breakpoints.keys()); + for (var i = 0; i < breakpoints.length; ++i) + breakpoints[i].remove(); + }, + + reset: function() + { + // Remove all breakpoints from UI and debugger, do not update storage. + this._storage._muted = true; + this.removeAllBreakpoints(); + delete this._storage._muted; + + // Remove all provisional breakpoints from the debugger. + for (var debuggerId in this._breakpointForDebuggerId) + this._debuggerModel.removeBreakpoint(debuggerId); + this._breakpointForDebuggerId = {}; + this._sourceFilesWithRestoredBreakpoints = {}; + }, + + _projectWillReset: function(event) + { + var project = /** @type {Project} */ (event.data); + var uiSourceCodes = project.uiSourceCodes(); + for (var i = 0; i < uiSourceCodes.length; ++i) { + var uiSourceCode = uiSourceCodes[i]; + var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode) || []; + for (var lineNumber in breakpoints) { + var lineBreakpoints = breakpoints[lineNumber]; + for (var j = 0; j < lineBreakpoints.length; ++j) { + var breakpoint = lineBreakpoints[j]; + breakpoint._resetLocations(); + } + } + this._breakpointsForUISourceCode.remove(uiSourceCode); + + breakpoints = this.breakpointsForUISourceCode(uiSourceCode); + for (var j = 0; j < breakpoints.length; ++j) { + var breakpoint = breakpoints[j]; + this._breakpoints.remove(breakpoint); + } + + var sourceFileId = BreakpointManager.sourceFileId(uiSourceCode); + delete this._sourceFilesWithRestoredBreakpoints[sourceFileId]; + } + }, + + _breakpointResolved: function(event) + { + var breakpointId = /** @type {DebuggerAgent.BreakpointId} */ (event.data.breakpointId); + var location = /** @type {DebuggerModel.Location} */ (event.data.location); + var breakpoint = this._breakpointForDebuggerId[breakpointId]; + if (!breakpoint) + return; + if (!this._breakpoints.contains(breakpoint)) + this._breakpoints.put(breakpoint); + breakpoint._addResolvedLocation(location); + }, + + /** + * @param {BreakpointManager.Breakpoint} breakpoint + * @param {boolean} removeFromStorage + */ + _removeBreakpoint: function(breakpoint, removeFromStorage) + { + console.assert(!breakpoint._debuggerId) + this._breakpoints.remove(breakpoint); + if (removeFromStorage) + this._storage._removeBreakpoint(breakpoint); + }, + + /** + * @param {BreakpointManager.Breakpoint} breakpoint + * @param {UILocation} uiLocation + */ + _uiLocationAdded: function(breakpoint, uiLocation) + { + var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode); + if (!breakpoints) { + breakpoints = {}; + this._breakpointsForUISourceCode.put(uiLocation.uiSourceCode, breakpoints); + } + + var lineBreakpoints = breakpoints[uiLocation.lineNumber]; + if (!lineBreakpoints) { + lineBreakpoints = []; + breakpoints[uiLocation.lineNumber] = lineBreakpoints; + } + + lineBreakpoints.push(breakpoint); + this.dispatchEventToListeners(BreakpointManager.Events.BreakpointAdded, {breakpoint: breakpoint, uiLocation: uiLocation}); + }, + + /** + * @param {BreakpointManager.Breakpoint} breakpoint + * @param {UILocation} uiLocation + */ + _uiLocationRemoved: function(breakpoint, uiLocation) + { + var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode); + if (!breakpoints) + return; + + var lineBreakpoints = breakpoints[uiLocation.lineNumber]; + if (!lineBreakpoints) + return; + + lineBreakpoints.remove(breakpoint); + if (!lineBreakpoints.length) + delete breakpoints[uiLocation.lineNumber]; + this.dispatchEventToListeners(BreakpointManager.Events.BreakpointRemoved, {breakpoint: breakpoint, uiLocation: uiLocation}); + }, + + __proto__: EventObjectEmitter.prototype +} + +/** + * @constructor + * @param {BreakpointManager} breakpointManager + * @param {UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {string} condition + * @param {boolean} enabled + */ +BreakpointManager.Breakpoint = function(breakpointManager, uiSourceCode, lineNumber, condition, enabled) +{ + this._breakpointManager = breakpointManager; + this._primaryUILocation = new UILocation(uiSourceCode, lineNumber, 0); + this._sourceFileId = BreakpointManager.sourceFileId(uiSourceCode); + /** @type {Array.} */ + this._liveLocations = []; + /** @type {!Object.} */ + this._uiLocations = {}; + + // Force breakpoint update. + /** @type {string} */ this._condition; + /** @type {boolean} */ this._enabled; + this._updateBreakpoint(condition, enabled); +} + +BreakpointManager.Breakpoint.prototype = { + /** + * @return {UILocation} + */ + primaryUILocation: function() + { + return this._primaryUILocation; + }, + + /** + * @param {DebuggerModel.Location} location + */ + _addResolvedLocation: function(location) + { + this._liveLocations.push(this._breakpointManager._debuggerModel.createLiveLocation(location, this._locationUpdated.bind(this, location))); + }, + + /** + * @param {DebuggerModel.Location} location + * @param {UILocation} uiLocation + */ + _locationUpdated: function(location, uiLocation) + { + var stringifiedLocation = location.scriptId + ":" + location.lineNumber + ":" + location.columnNumber; + var oldUILocation = /** @type {UILocation} */ (this._uiLocations[stringifiedLocation]); + if (oldUILocation) + this._breakpointManager._uiLocationRemoved(this, oldUILocation); + if (this._uiLocations[""]) { + delete this._uiLocations[""]; + this._breakpointManager._uiLocationRemoved(this, this._primaryUILocation); + } + this._uiLocations[stringifiedLocation] = uiLocation; + this._breakpointManager._uiLocationAdded(this, uiLocation); + }, + + /** + * @return {boolean} + */ + enabled: function() + { + return this._enabled; + }, + + /** + * @param {boolean} enabled + */ + setEnabled: function(enabled) + { + this._updateBreakpoint(this._condition, enabled); + }, + + /** + * @return {string} + */ + condition: function() + { + return this._condition; + }, + + /** + * @param {string} condition + */ + setCondition: function(condition) + { + this._updateBreakpoint(condition, this._enabled); + }, + + /** + * @param {string} condition + * @param {boolean} enabled + */ + _updateBreakpoint: function(condition, enabled) + { + if (this._enabled === enabled && this._condition === condition) + return; + + if (this._enabled) + this._removeFromDebugger(); + + this._enabled = enabled; + this._condition = condition; + this._breakpointManager._storage._updateBreakpoint(this); + + //x var scriptFile = this._primaryUILocation.uiSourceCode.scriptFile(); + if (this._enabled/*x && !(scriptFile && scriptFile.hasDivergedFromVM())*/) { + if (this._setInDebugger()) + return; + } + + this._fakeBreakpointAtPrimaryLocation(); + }, + + /** + * @param {boolean=} keepInStorage + */ + remove: function(keepInStorage) + { + var removeFromStorage = !keepInStorage; + this._resetLocations(); + this._removeFromDebugger(); + this._breakpointManager._removeBreakpoint(this, removeFromStorage); + }, + + _setInDebugger: function() + { + console.assert(!this._debuggerId); + var rawLocation = this._primaryUILocation.uiLocationToRawLocation(); + var debuggerModelLocation = /** @type {DebuggerModel.Location} */ (rawLocation); + if (debuggerModelLocation) { + this._breakpointManager._debuggerModel.setBreakpointByScriptLocation(debuggerModelLocation, this._condition, this._didSetBreakpointInDebugger.bind(this)); + return true; + } + if (this._primaryUILocation.uiSourceCode.url) { + this._breakpointManager._debuggerModel.setBreakpointByURL(this._primaryUILocation.uiSourceCode.url, this._primaryUILocation.lineNumber, 0, this._condition, this._didSetBreakpointInDebugger.bind(this)); + return true; + } + return false; + }, + + /** + * @this {BreakpointManager.Breakpoint} + * @param {?DebuggerAgent.BreakpointId} breakpointId + * @param {Array.} locations + */ + _didSetBreakpointInDebugger: function(breakpointId, locations) + { + if (!breakpointId) { + this._resetLocations(); + this._breakpointManager._removeBreakpoint(this, false); + return; + } + + this._debuggerId = breakpointId; + this._breakpointManager._breakpointForDebuggerId[breakpointId] = this; + + if (!locations.length) { + this._fakeBreakpointAtPrimaryLocation(); + return; + } + + this._resetLocations(); + for (var i = 0; i < locations.length; ++i) { + var script = this._breakpointManager._debuggerModel.scriptForId(locations[i].scriptId); + var uiLocation = script.rawLocationToUILocation(locations[i].lineNumber, locations[i].columnNumber); + if (this._breakpointManager.findBreakpoint(uiLocation.uiSourceCode, uiLocation.lineNumber)) { + // location clash + this.remove(); + return; + } + } + + for (var i = 0; i < locations.length; ++i) + this._addResolvedLocation(locations[i]); + }, + + _removeFromDebugger: function() + { + if (this._debuggerId) { + this._breakpointManager._debuggerModel.removeBreakpoint(this._debuggerId); + delete this._breakpointManager._breakpointForDebuggerId[this._debuggerId]; + delete this._debuggerId; + } + }, + + _resetLocations: function() + { + for (var stringifiedLocation in this._uiLocations) + this._breakpointManager._uiLocationRemoved(this, this._uiLocations[stringifiedLocation]); + + for (var i = 0; i < this._liveLocations.length; ++i) + this._liveLocations[i].dispose(); + this._liveLocations = []; + + this._uiLocations = {}; + }, + + /** + * @return {string} + */ + _breakpointStorageId: function() + { + if (!this._sourceFileId) + return ""; + return this._sourceFileId + ":" + this._primaryUILocation.lineNumber; + }, + + _fakeBreakpointAtPrimaryLocation: function() + { + this._resetLocations(); + this._uiLocations[""] = this._primaryUILocation; + this._breakpointManager._uiLocationAdded(this, this._primaryUILocation); + } +} + +/** + * @constructor + * @param {BreakpointManager} breakpointManager + * @param {Setting} setting + */ +BreakpointManager.Storage = function(breakpointManager, setting) +{ + this._breakpointManager = breakpointManager; + this._setting = setting; + var breakpoints = this._setting.get(); + /** @type {Object.} */ + this._breakpoints = {}; + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = /** @type {BreakpointManager.Storage.Item} */ (breakpoints[i]); + this._breakpoints[breakpoint.sourceFileId + ":" + breakpoint.lineNumber] = breakpoint; + } +} + +BreakpointManager.Storage.prototype = { + /** + * @param {UISourceCode} uiSourceCode + */ + _restoreBreakpoints: function(uiSourceCode) + { + this._muted = true; + var sourceFileId = BreakpointManager.sourceFileId(uiSourceCode); + for (var id in this._breakpoints) { + var breakpoint = this._breakpoints[id]; + if (breakpoint.sourceFileId === sourceFileId) + this._breakpointManager._innerSetBreakpoint(uiSourceCode, breakpoint.lineNumber, breakpoint.condition, breakpoint.enabled); + } + delete this._muted; + }, + + /** + * @param {BreakpointManager.Breakpoint} breakpoint + */ + _updateBreakpoint: function(breakpoint) + { + if (this._muted || !breakpoint._breakpointStorageId()) + return; + this._breakpoints[breakpoint._breakpointStorageId()] = new BreakpointManager.Storage.Item(breakpoint); + this._save(); + }, + + /** + * @param {BreakpointManager.Breakpoint} breakpoint + */ + _removeBreakpoint: function(breakpoint) + { + if (this._muted) + return; + delete this._breakpoints[breakpoint._breakpointStorageId()]; + this._save(); + }, + + _save: function() + { + var breakpointsArray = []; + for (var id in this._breakpoints) + breakpointsArray.push(this._breakpoints[id]); + this._setting.set(breakpointsArray); + } +} + +/** + * @constructor + * @param {BreakpointManager.Breakpoint} breakpoint + */ +BreakpointManager.Storage.Item = function(breakpoint) +{ + var primaryUILocation = breakpoint.primaryUILocation(); + this.sourceFileId = breakpoint._sourceFileId; + this.lineNumber = primaryUILocation.lineNumber; + this.condition = breakpoint.condition(); + this.enabled = breakpoint.enabled(); +} + +/** @type {BreakpointManager} */ +//CSInspector.breakpointManager = null; + +return BreakpointManager; +}); diff --git a/interpreter/debugger/client/BreakpointsSidebarPane.js b/interpreter/debugger/client/BreakpointsSidebarPane.js new file mode 100644 index 0000000..251e412 --- /dev/null +++ b/interpreter/debugger/client/BreakpointsSidebarPane.js @@ -0,0 +1,286 @@ +define(function (require, exports) { + +/* + * Copyright (C) 2008 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + +//require("breakpointsList.css"); + +var SidebarPane = require("SidebarPane").SidebarPane; +var BreakpointManager = require("BreakpointManager"); + +/** + * @constructor + * @param {BreakpointManager} breakpointManager + * @extends {SidebarPane} + */ +function BreakpointsSidebarPane(breakpointManager, showSourceLineDelegate) +{ + SidebarPane.call(this, UIString("Breakpoints")); + + this._breakpointManager = breakpointManager; + this._showSourceLineDelegate = showSourceLineDelegate; + + this.listElement = document.createElement("ol"); + this.listElement.className = "breakpoint-list"; + + this.emptyElement = document.createElement("div"); + this.emptyElement.className = "info"; + this.emptyElement.textContent = UIString("No Breakpoints"); + + this.bodyElement.appendChild(this.emptyElement); + + this._items = new Map(); + + var breakpointLocations = this._breakpointManager.allBreakpointLocations(); + for (var i = 0; i < breakpointLocations.length; ++i) + this._addBreakpoint(breakpointLocations[i].breakpoint, breakpointLocations[i].uiLocation); + + this._breakpointManager.addEventListener(BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this); + this._breakpointManager.addEventListener(BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this); + + //this.emptyElement.addEventListener("contextmenu", this._emptyElementContextMenu.bind(this), true); +} + +BreakpointsSidebarPane.prototype = { + _emptyElementContextMenu: function(event) + { + var contextMenu = new ContextMenu(event); + var breakpointActive = CSInspector.debugModel.breakpointsActive(); + var breakpointActiveTitle = breakpointActive ? + UIString(useLowerCaseMenuTitles() ? "Deactivate breakpoints" : "Deactivate Breakpoints") : + UIString(useLowerCaseMenuTitles() ? "Activate breakpoints" : "Activate Breakpoints"); + contextMenu.appendItem(breakpointActiveTitle, CSInspector.debuggerModel.setBreakpointsActive.bind(CSInspector.debugModel, !breakpointActive)); + contextMenu.show(); + }, + + /** + * @param {Event} event + */ + _breakpointAdded: function(event) + { + this._breakpointRemoved(event); + + var breakpoint = /** @type {BreakpointManager.Breakpoint} */ (event.data.breakpoint); + var uiLocation = /** @type {UILocation} */ (event.data.uiLocation); + this._addBreakpoint(breakpoint, uiLocation); + }, + + /** + * @param {BreakpointManager.Breakpoint} breakpoint + * @param {UILocation} uiLocation + */ + _addBreakpoint: function(breakpoint, uiLocation) + { + var element = document.createElement("li"); + element.addStyleClass("cursor-pointer"); + //x element.addEventListener("contextmenu", this._breakpointContextMenu.bind(this, breakpoint), true); + element.addEventListener("click", this._breakpointClicked.bind(this, uiLocation), false); + + var checkbox = document.createElement("input"); + checkbox.className = "checkbox-elem"; + checkbox.type = "checkbox"; + checkbox.checked = breakpoint.enabled(); + checkbox.addEventListener("click", this._breakpointCheckboxClicked.bind(this, breakpoint), false); + element.appendChild(checkbox); + + var labelElement = document.createTextNode(uiLocation.linkText()); + element.appendChild(labelElement); + + var snippetElement = document.createElement("div"); + snippetElement.className = "source-text monospace"; + element.appendChild(snippetElement); + + /** + * @param {?string} content + * @param {boolean} contentEncoded + * @param {string} mimeType + */ + function didRequestContent(content, contentEncoded, mimeType) + { + var lineEndings = content.lineEndings(); + if (uiLocation.lineNumber < lineEndings.length) + snippetElement.textContent = content.substring(lineEndings[uiLocation.lineNumber - 1], lineEndings[uiLocation.lineNumber]); + } + uiLocation.uiSourceCode.requestContent(didRequestContent.bind(this)); + + element._data = uiLocation; + var currentElement = this.listElement.firstChild; + while (currentElement) { + if (currentElement._data && this._compareBreakpoints(currentElement._data, element._data) > 0) + break; + currentElement = currentElement.nextSibling; + } + this._addListElement(element, currentElement); + + var breakpointItem = {}; + breakpointItem.element = element; + breakpointItem.checkbox = checkbox; + this._items.put(breakpoint, breakpointItem); + + this.expand(); + }, + + /** + * @param {Event} event + */ + _breakpointRemoved: function(event) + { + var breakpoint = /** @type {BreakpointManager.Breakpoint} */ (event.data.breakpoint); + var uiLocation = /** @type {UILocation} */ (event.data.uiLocation); + var breakpointItem = this._items.get(breakpoint); + if (!breakpointItem) + return; + this._items.remove(breakpoint); + this._removeListElement(breakpointItem.element); + }, + + /** + * @param {BreakpointManager.Breakpoint} breakpoint + */ + highlightBreakpoint: function(breakpoint) + { + var breakpointItem = this._items.get(breakpoint); + if (!breakpointItem) + return; + breakpointItem.element.addStyleClass("breakpoint-hit"); + this._highlightedBreakpointItem = breakpointItem; + }, + + clearBreakpointHighlight: function() + { + if (this._highlightedBreakpointItem) { + this._highlightedBreakpointItem.element.removeStyleClass("breakpoint-hit"); + delete this._highlightedBreakpointItem; + } + }, + + _breakpointClicked: function(uiLocation, event) + { + this._showSourceLineDelegate(uiLocation.uiSourceCode, uiLocation.lineNumber); + }, + + /** + * @param {BreakpointManager.Breakpoint} breakpoint + */ + _breakpointCheckboxClicked: function(breakpoint, event) + { + // Breakpoint element has it's own click handler. + event.consume(); + breakpoint.setEnabled(event.target.checked); + }, + + /** + * @param {BreakpointManager.Breakpoint} breakpoint + */ + _breakpointContextMenu: function(breakpoint, event) + { + var breakpoints = this._items.values(); + var contextMenu = new ContextMenu(event); + contextMenu.appendItem(UIString(useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), breakpoint.remove.bind(breakpoint)); + if (breakpoints.length > 1) { + var removeAllTitle = UIString(useLowerCaseMenuTitles() ? "Remove all breakpoints" : "Remove All Breakpoints"); + contextMenu.appendItem(removeAllTitle, this._breakpointManager.removeAllBreakpoints.bind(this._breakpointManager)); + } + + contextMenu.appendSeparator(); + var breakpointActive = CSInspector.debuggerModel.breakpointsActive(); + var breakpointActiveTitle = breakpointActive ? + UIString(useLowerCaseMenuTitles() ? "Deactivate breakpoints" : "Deactivate Breakpoints") : + UIString(useLowerCaseMenuTitles() ? "Activate breakpoints" : "Activate Breakpoints"); + contextMenu.appendItem(breakpointActiveTitle, CSInspector.debugModel.setBreakpointsActive.bind(CSInspector.debugModel, !breakpointActive)); + + function enabledBreakpointCount(breakpoints) + { + var count = 0; + for (var i = 0; i < breakpoints.length; ++i) { + if (breakpoints[i].checkbox.checked) + count++; + } + return count; + } + if (breakpoints.length > 1) { + var enableBreakpointCount = enabledBreakpointCount(breakpoints); + var enableTitle = UIString(useLowerCaseMenuTitles() ? "Enable all breakpoints" : "Enable All Breakpoints"); + var disableTitle = UIString(useLowerCaseMenuTitles() ? "Disable all breakpoints" : "Disable All Breakpoints"); + + contextMenu.appendSeparator(); + + contextMenu.appendItem(enableTitle, this._breakpointManager.toggleAllBreakpoints.bind(this._breakpointManager, true), !(enableBreakpointCount != breakpoints.length)); + contextMenu.appendItem(disableTitle, this._breakpointManager.toggleAllBreakpoints.bind(this._breakpointManager, false), !(enableBreakpointCount > 1)); + } + + contextMenu.show(); + }, + + _addListElement: function(element, beforeElement) + { + if (beforeElement) + this.listElement.insertBefore(element, beforeElement); + else { + if (!this.listElement.firstChild) { + this.bodyElement.removeChild(this.emptyElement); + this.bodyElement.appendChild(this.listElement); + } + this.listElement.appendChild(element); + } + }, + + _removeListElement: function(element) + { + this.listElement.removeChild(element); + if (!this.listElement.firstChild) { + this.bodyElement.removeChild(this.listElement); + this.bodyElement.appendChild(this.emptyElement); + } + }, + + _compare: function(x, y) + { + if (x !== y) + return x < y ? -1 : 1; + return 0; + }, + + _compareBreakpoints: function(b1, b2) + { + return this._compare(b1.uiSourceCode.originURL(), b2.uiSourceCode.originURL()) || this._compare(b1.lineNumber, b2.lineNumber); + }, + + reset: function() + { + this.listElement.removeChildren(); + if (this.listElement.parentElement) { + this.bodyElement.removeChild(this.listElement); + this.bodyElement.appendChild(this.emptyElement); + } + this._items.clear(); + }, + + __proto__: SidebarPane.prototype +} + + +return BreakpointsSidebarPane; +}); diff --git a/interpreter/debugger/client/CallFrame.js b/interpreter/debugger/client/CallFrame.js new file mode 100644 index 0000000..2ebfafa --- /dev/null +++ b/interpreter/debugger/client/CallFrame.js @@ -0,0 +1,164 @@ +define(function(){ + /** + * @constructor + * @param {Script} script + * @param {DebuggerAgent.CallFrame} payload + */ + function CallFrame(script, payload) + { + this._script = script; + this._payload = payload; + this._locations = []; + } + + CallFrame.prototype = { + /** + * @return {Script} + */ + get script() + { + return this._script; + }, + + /** + * @return {string} + */ + get type() + { + return this._payload.type; + }, + + /** + * @return {string} + */ + get id() + { + return this._payload.callFrameId; + }, + + /** + * @return {Array.} + */ + get scopeChain() + { + return this._payload.scopeChain; + }, + + /** + * @return {RuntimeAgent.RemoteObject} + */ + get this() + { + return this._payload.this; + }, + + /** + * @return {string} + */ + get functionName() + { + return this._payload.functionName; + }, + + /** + * @return {Location} + */ + get location() + { + var rawLocation = /** @type {Location} */ (this._payload.location); + return rawLocation; + }, + + /** + * @param {string} code + * @param {string} objectGroup + * @param {boolean} includeCommandLineAPI + * @param {boolean} doNotPauseOnExceptionsAndMuteConsole + * @param {boolean} returnByValue + * @param {boolean} generatePreview + * @param {function(?RuntimeAgent.RemoteObject, boolean=)=} callback + */ + evaluate: function(code, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback) + { + /** + * @this {CallFrame} + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} result + * @param {boolean=} wasThrown + */ + function didEvaluateOnCallFrame(error, result, wasThrown) + { + if (error) { + console.error(error); + callback(null, false); + return; + } + callback(result, wasThrown); + } + CSInspector.debugAgent.evaluateOnCallFrame(this._payload.callFrameId, code, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, didEvaluateOnCallFrame.bind(this)); + }, + + /** + * @param {function(?Protocol.Error=)=} callback + */ + restart: function(callback) + { + /** + * @this {CallFrame} + * @param {?Protocol.Error} error + * @param {Array.=} callFrames + * @param {Object=} details + */ + function protocolCallback(error, callFrames, details) + { + if (!error) + CSInspector.debugModel.callStackModified(callFrames, details); + if (callback) + callback(error); + } + DebuggerAgent.restartFrame(this._payload.callFrameId, protocolCallback); + }, + + /** + * @param {function(Array.)} callback + */ + getStepIntoLocations: function(callback) + { + if (this._stepInLocations) { + callback(this._stepInLocations.slice(0)); + return; + } + /** + * @param {?string} error + * @param {Array.=} stepInPositions + */ + function getStepInPositionsCallback(error, stepInPositions) { + if (error) { + return; + } + this._stepInLocations = stepInPositions; + callback(this._stepInLocations.slice(0)); + } + DebuggerAgent.getStepInPositions(this.id, getStepInPositionsCallback.bind(this)); + }, + + /** + * @param {function(UILocation):(boolean|undefined)} updateDelegate + */ + createLiveLocation: function(updateDelegate) + { + var location = this._script.createLiveLocation(this.location, updateDelegate); + this._locations.push(location); + return location; + }, + + dispose: function(updateDelegate) + { + for (var i = 0; i < this._locations.length; ++i) + this._locations[i].dispose(); + this._locations = []; + } + } + + return CallFrame; +}); diff --git a/interpreter/debugger/client/CallStackSidebarPane.js b/interpreter/debugger/client/CallStackSidebarPane.js new file mode 100644 index 0000000..29cdbfb --- /dev/null +++ b/interpreter/debugger/client/CallStackSidebarPane.js @@ -0,0 +1,230 @@ +define(function(require, exports){ +/* + * Copyright (C) 2008 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ + + +var Placard = require("Placard"); +var SidebarPane = require("SidebarPane").SidebarPane; +var ScriptsPanelDescriptor = require("ScriptsPanelDescriptor"); + +/** + * @constructor + * @extends {SidebarPane} + */ +function CallStackSidebarPane() +{ + SidebarPane.call(this, UIString("Call Stack")); + this._model = CSInspector.debugModel; + + this.bodyElement.addEventListener("keydown", this._keyDown.bind(this), true); + this.bodyElement.tabIndex = 0; +} + +CallStackSidebarPane.Events = { + CallFrameSelected: "CallFrameSelected" +} + +CallStackSidebarPane.prototype = { + update: function(callFrames) + { + this.bodyElement.removeChildren(); + delete this._statusMessageElement; + this.placards = []; + + if (!callFrames) { + var infoElement = document.createElement("div"); + infoElement.className = "info"; + infoElement.textContent = UIString("Not Paused"); + this.bodyElement.appendChild(infoElement); + return; + } + + for (var i = 0; i < callFrames.length; ++i) { + var callFrame = callFrames[i]; + var placard = new CallStackSidebarPane.Placard(callFrame, this); + placard.element.addEventListener("click", this._placardSelected.bind(this, placard), false); + this.placards.push(placard); + this.bodyElement.appendChild(placard.element); + } + }, + + setSelectedCallFrame: function(x) + { + for (var i = 0; i < this.placards.length; ++i) { + var placard = this.placards[i]; + placard.selected = (placard._callFrame === x); + } + }, + + /** + * @param {Event=} event + * @return {boolean} + */ + _selectNextCallFrameOnStack: function(event) + { + var index = this._selectedCallFrameIndex(); + if (index == -1) + return true; + this._selectedPlacardByIndex(index + 1); + return true; + }, + + /** + * @param {Event=} event + * @return {boolean} + */ + _selectPreviousCallFrameOnStack: function(event) + { + var index = this._selectedCallFrameIndex(); + if (index == -1) + return true; + this._selectedPlacardByIndex(index - 1); + return true; + }, + + /** + * @param {number} index + */ + _selectedPlacardByIndex: function(index) + { + if (index < 0 || index >= this.placards.length) + return; + this._placardSelected(this.placards[index]) + }, + + /** + * @return {number} + */ + _selectedCallFrameIndex: function() + { + if (!this._model.selectedCallFrame()) + return -1; + for (var i = 0; i < this.placards.length; ++i) { + var placard = this.placards[i]; + if (placard._callFrame === this._model.selectedCallFrame()) + return i; + } + return -1; + }, + + _placardSelected: function(placard) + { + this.dispatchEventToListeners(CallStackSidebarPane.Events.CallFrameSelected, placard._callFrame); + }, + + _copyStackTrace: function() + { + var text = ""; + for (var i = 0; i < this.placards.length; ++i) + text += this.placards[i].title + " (" + this.placards[i].subtitle + ")\n"; + InspectorFrontendHost.copyText(text); + }, + + /** + * @param {function(!Array., function(Event=):boolean)} registerShortcutDelegate + */ + registerShortcuts: function(registerShortcutDelegate) + { + registerShortcutDelegate(ScriptsPanelDescriptor.ShortcutKeys.NextCallFrame, this._selectNextCallFrameOnStack.bind(this)); + registerShortcutDelegate(ScriptsPanelDescriptor.ShortcutKeys.PrevCallFrame, this._selectPreviousCallFrameOnStack.bind(this)); + }, + + setStatus: function(status) + { + if (!this._statusMessageElement) { + this._statusMessageElement = document.createElement("div"); + this._statusMessageElement.className = "info"; + this.bodyElement.appendChild(this._statusMessageElement); + } + if (typeof status === "string") + this._statusMessageElement.textContent = status; + else { + this._statusMessageElement.removeChildren(); + this._statusMessageElement.appendChild(status); + } + }, + + _keyDown: function(event) + { + if (event.altKey || event.shiftKey || event.metaKey || event.ctrlKey) + return; + + if (event.keyIdentifier === "Up") { + this._selectPreviousCallFrameOnStack(); + event.consume(); + } else if (event.keyIdentifier === "Down") { + this._selectNextCallFrameOnStack(); + event.consume(); + } + }, + + __proto__: SidebarPane.prototype +} + +/** + * @constructor + * @extends {Placard} + * @param {DebuggerModel.CallFrame} callFrame + * @param {CallStackSidebarPane} pane + */ +CallStackSidebarPane.Placard = function(callFrame, pane) +{ + Placard.call(this, callFrame.functionName || UIString("(anonymous function)"), ""); + callFrame.createLiveLocation(this._update.bind(this)); + this.element.addEventListener("contextmenu", this._placardContextMenu.bind(this), true); + this._callFrame = callFrame; + this._pane = pane; +} + +CallStackSidebarPane.Placard.prototype = { + _update: function(uiLocation) + { + this.subtitle = uiLocation.linkText().trimMiddle(100); + }, + + _placardContextMenu: function(event) + { + var contextMenu = new ContextMenu(event); + + if (debuggerModel.canSetScriptSource()) { + contextMenu.appendItem(UIString(useLowerCaseMenuTitles() ? "Restart frame" : "Restart Frame"), this._restartFrame.bind(this)); + contextMenu.appendSeparator(); + } + contextMenu.appendItem(UIString(useLowerCaseMenuTitles() ? "Copy stack trace" : "Copy Stack Trace"), this._pane._copyStackTrace.bind(this._pane)); + + contextMenu.show(); + }, + + _restartFrame: function() + { + this._callFrame.restart(undefined); + }, + + __proto__: Placard.prototype +} + +return CallStackSidebarPane; +}); + diff --git a/interpreter/debugger/client/CodeCMEditor.js b/interpreter/debugger/client/CodeCMEditor.js deleted file mode 100644 index ca58f3b..0000000 --- a/interpreter/debugger/client/CodeCMEditor.js +++ /dev/null @@ -1,138 +0,0 @@ -define(function(require, exports) { - var Util = require("util"); - var EventEmitter = require("events").EventEmitter; - var $ = require('jquery'); - - function CodeCMEditor(){ - EventEmitter.call(this); - this._sourceNavView = new SourceNavView(this); - - var editor = new CodeMirror($("#code").get(0), { - //value: code || "", - cursorBlinkRate: 0, //0 is disabled - readOnly: true, - tabMode: "indent", - theme: "monokai", - lineNumbers: true, - indentUnit: 4, - mode: "text/clearsilver" - }); - editor.setSize("auto", "500"); - - this._sources = {}; - this._currentSourceid = null; - this._editor = editor; - this._execLineHandle = null; - this._coordinateMode = "local"; - this._scrollMaginOfExeclineNums = 4; - } - - Util.inherits(CodeCMEditor, EventEmitter); - - CodeCMEditor.prototype.setSources = function(sources){ - /* - sobj { - "name":name, - "source":source, - "id": id - } - */ - for(var i = 0; i < sources.length; i++){ - var sobj = sources[i]; - this._sources[sobj.id] = sobj; - } - this._sourceNavView.init(sources); - }; - - CodeCMEditor.prototype.pausedDetail = function(lineInfo){ - /* - "evaluateLine": { - "stype": astnode.type, - "line": astnode.pos.first_line, - "colnum": astnode.pos.first_column, - "sourceName": astnode.pos.name, - "sourceId": astnode.pos.fileid, - }, - */ - this._sourceNavView.activeSource(lineInfo.sourceId); - this.unActiveExecutionLine(); - this.activeExecutionLine(lineInfo.line); - }; - - CodeCMEditor.prototype.scrollLineToClientView = function(linenum){ - var editor = this._editor; - var scrollInfo = editor.getScrollInfo(); - - var targetLineTop = editor.heightAtLine(linenum, this._coordinateMode); - - var clientviewTop = scrollInfo.top, - clientviewBottom = scrollInfo.clientHeight + scrollInfo.top; - - if (targetLineTop > clientviewTop && targetLineTop < clientviewBottom - editor.defaultTextHeight()){ - //不需要做任何操作 - } else { - if (scrollInfo.height - targetLineTop < scrollInfo.clientHeight){ - //在最后一屏 - editor.scrollTo(0, scrollInfo.height - scrollInfo.clientHeight); - } else { - //把目标行滚动到最上面,留4行方便看到上下文 - editor.scrollTo(0, editor.heightAtLine(linenum - this._scrollMaginOfExeclineNums, this._coordinateMode)); - //editor.scrollTo(0, targetLineTop); - } - } - }; - - CodeCMEditor.prototype.activeExecutionLine = function(linenum){ - var editor = this._editor; - linenum = linenum - 1; - - this.scrollLineToClientView(linenum); - - this._execLineHandle = editor.getLineHandle(linenum); - this._editor.addLineClass(this._execLineHandle, "background", "cm-execution-line"); - }; - - CodeCMEditor.prototype.unActiveExecutionLine = function(){ - if (this._execLineHandle) this._editor.removeLineClass(this._execLineHandle, "background", "cm-execution-line"); - }; - - CodeCMEditor.prototype.switchSourceFrame = function(id){ - if (!this._sources[id]){ - console.error("source not found"); - return; - } - if (this._currentSourceid != id) { - this._editor.setValue(this._sources[id].source); - this._currentSourceid = id; - } - }; - - var SourceNavItemTpl = '
  • {name}
  • '; - function SourceNavView(sourcePanel){ - $("#files-nav").html(require("sourceview.html")); - this._itemClass = ".source-item"; - this._elem = $("#sources-list"); - this._sourcePanel = sourcePanel; - this._elem.delegate(this._itemClass, "click", this._sourceFrameClickHandler.bind(this)); - } - - SourceNavView.prototype.init = function(datas){ - for(var i = 0; i < datas.length; i++){ - this._elem.append(Util.format(SourceNavItemTpl, datas[i])); - } - }; - - SourceNavView.prototype._sourceFrameClickHandler = function(evt){ - var $target = $(evt.target).parents(this._itemClass).eq(0) || $(evt.target); - var fileid = $target.data("id"); - this.activeSource(fileid); - }; - - SourceNavView.prototype.activeSource = function(id){ - this._elem.find(".active").toggleClass("active"); - var $elem = this._elem.find('li[data-id=' + id + ']').addClass("active"); - this._sourcePanel.switchSourceFrame(id); - }; - - return CodeCMEditor; -}); diff --git a/interpreter/debugger/client/CodeMirrorTextEditor.js b/interpreter/debugger/client/CodeMirrorTextEditor.js new file mode 100644 index 0000000..8e30f00 --- /dev/null +++ b/interpreter/debugger/client/CodeMirrorTextEditor.js @@ -0,0 +1,1018 @@ +define(function(require) { + var View = require("View"); + var TextUtils = require("TextUtils"); + var TextRange = require("TextRange"); + var TextEditor= require("TextEditor").TextEditor; + + /** + * @constructor + * @extends {View} + * @implements {TextEditor} + * @param {?string} url + * @param {TextEditorDelegate} delegate + */ + function CodeMirrorTextEditor(url, delegate){ + View.call(this); + + this._delegate = delegate; + this._url = url; + + this._lineSeparator = "\n"; + + this._codeMirror = window.CodeMirror(this.element, { + cursorBlinkRate: 0, //0 is disabled + //theme: "monokai", + readOnly: true, + lineNumbers: true, + gutters: ["CodeMirror-linenumbers"], + matchBrackets: true, + smartIndent: false, + styleSelectedText: true, + electricChars: false, + autoCloseBrackets: { explode: false } + }); + this._codeMirror._codeMirrorTextEditor = this; + + this._codeMirror.setOption("flattenSpans", false); + this._codeMirror.setOption("maxHighlightLength", 1000); + this._codeMirror.setOption("mode", null); + + + this._codeMirror.on("gutterClick", this._gutterClick.bind(this)); + this._codeMirror.on("scroll", this._scroll.bind(this)); + this._codeMirror.on("focus", this._focus.bind(this)); + + this.element.addStyleClass("fill"); + this.element.style.overflow = "hidden"; + this.element.firstChild.addStyleClass("source-code"); + this.element.firstChild.addStyleClass("fill"); + + this.element.addEventListener("focus", this._handleElementFocus.bind(this), false); + + this._setupSelectionColor(); + } + + CodeMirrorTextEditor.prototype = { + wasShown: function() + { + this._codeMirror.refresh(); + }, + + _guessIndentationLevel: function() + { + var tabRegex = /^\t+/; + var tabLines = 0; + var indents = {}; + function processLine(lineHandle) + { + var text = lineHandle.text; + if (text.length === 0 || !TextUtils.isSpaceChar(text[0])) + return; + if (tabRegex.test(text)) { + ++tabLines; + return; + } + var i = 0; + while (i < text.length && TextUtils.isSpaceChar(text[i])) + ++i; + if (i % 2 !== 0) + return; + indents[i] = 1 + (indents[i] || 0); + } + this._codeMirror.eachLine(processLine); + + var onePercentFilterThreshold = this.linesCount / 100; + if (tabLines && tabLines > onePercentFilterThreshold) + return "\t"; + var minimumIndent = Infinity; + for (var i in indents) { + if (indents[i] < onePercentFilterThreshold) + continue; + var indent = parseInt(i, 10); + if (minimumIndent > indent) + minimumIndent = indent; + } + if (minimumIndent === Infinity) + return TextUtils.Indent.FourSpaces; + return new Array(minimumIndent + 1).join(" "); + }, + + _updateEditorIndentation: function() + { + var extraKeys = {}; + var indent = CSInspector.settings.textEditorIndent.get(); + if (CSInspector.settings.textEditorAutoDetectIndent.get()) + indent = this._guessIndentationLevel(); + if (indent === TextUtils.Indent.TabCharacter) { + this._codeMirror.setOption("indentWithTabs", true); + this._codeMirror.setOption("indentUnit", 4); + } else { + this._codeMirror.setOption("indentWithTabs", false); + this._codeMirror.setOption("indentUnit", indent.length); + extraKeys.Tab = function(codeMirror) + { + if (codeMirror.somethingSelected()) + return CodeMirror.Pass; + var pos = codeMirror.getCursor("head"); + codeMirror.replaceRange(indent.substring(pos.ch % indent.length), codeMirror.getCursor()); + } + } + this._codeMirror.setOption("extraKeys", extraKeys); + this._indentationLevel = indent; + }, + + /** + * @return {string} + */ + indent: function() + { + return this._indentationLevel; + }, + + /** + * @param {!RegExp} regex + * @param {TextRange} range + */ + highlightSearchResults: function(regex, range) + { + function innerHighlightRegex() + { + if (range) { + this.revealLine(range.startLine); + this.setSelection(TextRange.createFromLocation(range.startLine, range.startColumn)); + } else { + // Collapse selection to end on search start so that we jump to next occurence on the first enter press. + this.setSelection(this.selection().collapseToEnd()); + } + this._tokenHighlighter.highlightSearchResults(regex, range); + } + + this._codeMirror.operation(innerHighlightRegex.bind(this)); + }, + + cancelSearchResultsHighlight: function() + { + this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter)); + }, + + undo: function() + { + this._codeMirror.undo(); + }, + + redo: function() + { + this._codeMirror.redo(); + }, + + _setupSelectionColor: function() + { + if (CodeMirrorTextEditor._selectionStyleInjected) + return; + CodeMirrorTextEditor._selectionStyleInjected = true; + var backgroundColor = "#6e86ff";//getSelectionBackgroundColor(); + var backgroundColorRule = backgroundColor ? ".CodeMirror .CodeMirror-selected { background-color: " + backgroundColor + ";}" : ""; + var foregroundColor = "#ffffff";//getSelectionForegroundColor(); + var foregroundColorRule = foregroundColor ? ".CodeMirror .CodeMirror-selectedtext:not(.CodeMirror-persist-highlight) { color: " + foregroundColor + "!important;}" : ""; + if (!foregroundColorRule && !backgroundColorRule) + return; + + var style = document.createElement("style"); + style.textContent = backgroundColorRule + foregroundColorRule; + document.head.appendChild(style); + }, + + _setupWhitespaceHighlight: function() + { + if (CodeMirrorTextEditor._whitespaceStyleInjected || !CSInspector.settings.showWhitespacesInEditor.get()) + return; + CodeMirrorTextEditor._whitespaceStyleInjected = true; + const classBase = ".cm-whitespace-"; + const spaceChar = "·"; + var spaceChars = ""; + var rules = ""; + for(var i = 1; i <= CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan; ++i) { + spaceChars += spaceChar; + var rule = classBase + i + "::before { content: '" + spaceChars + "';}\n"; + rules += rule; + } + rules += ".cm-tab:before { display: block !important; }\n"; + var style = document.createElement("style"); + style.textContent = rules; + document.head.appendChild(style); + }, + + _handleKeyDown: function(e) + { + if (this._autocompleteController.keyDown(e)) + e.consume(true); + }, + + _shouldProcessWordForAutocompletion: function(word) + { + return word.length && (word[0] < '0' || word[0] > '9'); + }, + + /** + * @param {string} text + */ + _addTextToCompletionDictionary: function(text) + { + var words = TextUtils.textToWords(text); + for(var i = 0; i < words.length; ++i) { + if (this._shouldProcessWordForAutocompletion(words[i])) + this._dictionary.addWord(words[i]); + } + }, + + /** + * @param {string} text + */ + _removeTextFromCompletionDictionary: function(text) + { + var words = TextUtils.textToWords(text); + for(var i = 0; i < words.length; ++i) { + if (this._shouldProcessWordForAutocompletion(words[i])) + this._dictionary.removeWord(words[i]); + } + }, + + /** + * @param {CompletionDictionary} dictionary + */ + setCompletionDictionary: function(dictionary) + { + this._dictionary = dictionary; + this._addTextToCompletionDictionary(this.text()); + }, + + /** + * @param {number} lineNumber + * @param {number} column + * @return {?{x: number, y: number, height: number}} + */ + cursorPositionToCoordinates: function(lineNumber, column) + { + if (lineNumber >= this._codeMirror.lineCount() || lineNumber < 0 || column < 0 || column > this._codeMirror.getLine(lineNumber).length) + return null; + + var metrics = this._codeMirror.cursorCoords(new CodeMirror.Pos(lineNumber, column)); + + return { + x: metrics.left, + y: metrics.top, + height: metrics.bottom - metrics.top + }; + }, + + /** + * @param {number} x + * @param {number} y + * @return {?TextRange} + */ + coordinatesToCursorPosition: function(x, y) + { + var element = document.elementFromPoint(x, y); + if (!element || !element.isSelfOrDescendant(this._codeMirror.getWrapperElement())) + return null; + var gutterBox = this._codeMirror.getGutterElement().boxInWindow(); + if (x >= gutterBox.x && x <= gutterBox.x + gutterBox.width && + y >= gutterBox.y && y <= gutterBox.y + gutterBox.height) + return null; + var coords = this._codeMirror.coordsChar({left: x, top: y}); + return this._toRange(coords, coords); + }, + + /** + * @param {number} lineNumber + * @param {number} column + * @return {?{startColumn: number, endColumn: number, type: string}} + */ + tokenAtTextPosition: function(lineNumber, column) + { + if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount()) + return null; + var token = this._codeMirror.getTokenAt(new CodeMirror.Pos(lineNumber, (column || 0) + 1)); + if (!token || !token.type) + return null; + var convertedType = CodeMirrorUtils.convertTokenType(token.type); + if (!convertedType) + return null; + return { + startColumn: token.start, + endColumn: token.end - 1, + type: convertedType + }; + }, + + /** + * @param {TextRange} textRange + * @return {string} + */ + copyRange: function(textRange) + { + var pos = this._toPos(textRange.normalize()); + return this._codeMirror.getRange(pos.start, pos.end); + }, + + /** + * @return {boolean} + */ + isClean: function() + { + return this._codeMirror.isClean(); + }, + + markClean: function() + { + this._codeMirror.markClean(); + }, + + _hasLongLines: function() + { + function lineIterator(lineHandle) + { + if (lineHandle.text.length > CodeMirrorTextEditor.LongLineModeLineLengthThreshold) + hasLongLines = true; + return hasLongLines; + } + var hasLongLines = false; + this._codeMirror.eachLine(lineIterator); + return hasLongLines; + }, + + /** + * @param {string} mimeType + * @return {string} + */ + _whitespaceOverlayMode: function(mimeType) + { + var modeName = CodeMirror.mimeModes[mimeType] + "+whitespaces"; + if (CodeMirror.modes[modeName]) + return modeName; + + function modeConstructor(config, parserConfig) + { + function nextToken(stream) + { + if (stream.peek() === " ") { + var spaces = 0; + while (spaces < CodeMirrorTextEditor.MaximumNumberOfWhitespacesPerSingleSpan && stream.peek() === " ") { + ++spaces; + stream.next(); + } + return "whitespace whitespace-" + spaces; + } + while (!stream.eol() && stream.peek() !== " ") + stream.next(); + return null; + } + var whitespaceMode = { + token: nextToken + }; + return CodeMirror.overlayMode(CodeMirror.getMode(config, mimeType), whitespaceMode, false); + } + CodeMirror.defineMode(modeName, modeConstructor); + return modeName; + }, + + _enableLongLinesMode: function() + { + this._codeMirror.setOption("styleSelectedText", false); + this._longLinesMode = true; + }, + + _disableLongLinesMode: function() + { + this._codeMirror.setOption("styleSelectedText", true); + this._longLinesMode = false; + }, + + _updateCodeMirrorMode: function() + { + var showWhitespaces = CSInspector.settings.showWhitespacesInEditor.get(); + this._codeMirror.setOption("mode", showWhitespaces ? this._whitespaceOverlayMode(this._mimeType) : this._mimeType); + }, + + /** + * @param {string} mimeType + */ + setMimeType: function(mimeType) + { + this._mimeType = mimeType; + if (this._hasLongLines()) + this._enableLongLinesMode(); + else + this._disableLongLinesMode(); + this._updateCodeMirrorMode(); + }, + + /** + * @param {boolean} readOnly + */ + setReadOnly: function(readOnly) + { + this.element.enableStyleClass("CodeMirror-readonly", readOnly) + this._codeMirror.setOption("readOnly", readOnly); + }, + + /** + * @return {boolean} + */ + readOnly: function() + { + return !!this._codeMirror.getOption("readOnly"); + }, + + /** + * @param {Object} highlightDescriptor + */ + removeHighlight: function(highlightDescriptor) + { + highlightDescriptor.clear(); + }, + + /** + * @param {TextRange} range + * @param {string} cssClass + * @return {Object} + */ + highlightRange: function(range, cssClass) + { + cssClass = "CodeMirror-persist-highlight " + cssClass; + var pos = this._toPos(range); + ++pos.end.ch; + return this._codeMirror.markText(pos.start, pos.end, { + className: cssClass, + startStyle: cssClass + "-start", + endStyle: cssClass + "-end" + }); + }, + + /** + * @param {string} regex + * @param {string} cssClass + * @return {Object} + */ + highlightRegex: function(regex, cssClass) { }, + + /** + * @return {Element} + */ + defaultFocusedElement: function() + { + return this.element; + }, + + focus: function() + { + this._codeMirror.focus(); + }, + + _handleElementFocus: function() + { + this._codeMirror.focus(); + }, + + beginUpdates: function() + { + ++this._nestedUpdatesCounter; + }, + + endUpdates: function() + { + if (!--this._nestedUpdatesCounter) + this._codeMirror.refresh(); + }, + + /** + * @param {number} lineNumber + */ + revealLine: function(lineNumber) + { + this._innerRevealLine(lineNumber, this._codeMirror.getScrollInfo()); + }, + + /** + * @param {number} lineNumber + * @param {{left: number, top: number, width: number, height: number, clientWidth: number, clientHeight: number}} scrollInfo + */ + _innerRevealLine: function(lineNumber, scrollInfo) + { + var topLine = this._codeMirror.lineAtHeight(scrollInfo.top, "local"); + var bottomLine = this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local"); + var linesPerScreen = bottomLine - topLine + 1; + if (lineNumber < topLine) { + var topLineToReveal = Math.max(lineNumber - (linesPerScreen / 2) + 1, 0) | 0; + this._codeMirror.scrollIntoView(new CodeMirror.Pos(topLineToReveal, 0)); + } else if (lineNumber > bottomLine) { + var bottomLineToReveal = Math.min(lineNumber + (linesPerScreen / 2) - 1, this.linesCount - 1) | 0; + this._codeMirror.scrollIntoView(new CodeMirror.Pos(bottomLineToReveal, 0)); + } + }, + + _gutterClick: function(instance, lineNumber, gutter, event) + { + this.dispatchEventToListeners(TextEditor.Events.GutterClick, { lineNumber: lineNumber, event: event }); + }, + + _contextMenu: function(event) + { + var contextMenu = new ContextMenu(event); + var target = event.target.enclosingNodeOrSelfWithClass("CodeMirror-gutter-elt"); + if (target) + this._delegate.populateLineGutterContextMenu(contextMenu, parseInt(target.textContent, 10) - 1); + else + this._delegate.populateTextAreaContextMenu(contextMenu, 0); + contextMenu.show(); + }, + + /** + * @param {number} lineNumber + * @param {boolean} disabled + * @param {boolean} conditional + */ + addBreakpoint: function(lineNumber, disabled, conditional) + { + if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount()) + return; + var className = "cm-breakpoint" + (conditional ? " cm-breakpoint-conditional" : "") + (disabled ? " cm-breakpoint-disabled" : ""); + this._codeMirror.addLineClass(lineNumber, "wrap", className); + }, + + /** + * @param {number} lineNumber + */ + removeBreakpoint: function(lineNumber) + { + if (lineNumber < 0 || lineNumber >= this._codeMirror.lineCount()) + return; + var wrapClasses = this._codeMirror.getLineHandle(lineNumber).wrapClass; + if (!wrapClasses) + return; + var classes = wrapClasses.split(" "); + for(var i = 0; i < classes.length; ++i) { + if (classes[i].startsWith("cm-breakpoint")) + this._codeMirror.removeLineClass(lineNumber, "wrap", classes[i]); + } + }, + + /** + * @param {number} lineNumber + */ + setExecutionLine: function(lineNumber) + { + this._executionLine = this._codeMirror.getLineHandle(lineNumber); + this._codeMirror.addLineClass(this._executionLine, "wrap", "cm-execution-line"); + }, + + clearExecutionLine: function() + { + if (this._executionLine) + this._codeMirror.removeLineClass(this._executionLine, "wrap", "cm-execution-line"); + delete this._executionLine; + }, + + /** + * @param {number} lineNumber + * @param {Element} element + */ + addDecoration: function(lineNumber, element) + { + var widget = this._codeMirror.addLineWidget(lineNumber, element); + this._elementToWidget.put(element, widget); + }, + + /** + * @param {number} lineNumber + * @param {Element} element + */ + removeDecoration: function(lineNumber, element) + { + var widget = this._elementToWidget.remove(element); + if (widget) + this._codeMirror.removeLineWidget(widget); + }, + + /** + * @param {number} lineNumber + * @param {number=} columnNumber + */ + highlightPosition: function(lineNumber, columnNumber) + { + if (lineNumber < 0) + return; + lineNumber = Math.min(lineNumber, this._codeMirror.lineCount() - 1); + if (typeof columnNumber !== "number" || columnNumber < 0 || columnNumber > this._codeMirror.getLine(lineNumber).length) + columnNumber = 0; + + this.clearPositionHighlight(); + this._highlightedLine = this._codeMirror.getLineHandle(lineNumber); + if (!this._highlightedLine) + return; + this.revealLine(lineNumber); + this._codeMirror.addLineClass(this._highlightedLine, null, "cm-highlight"); + this._clearHighlightTimeout = setTimeout(this.clearPositionHighlight.bind(this), 2000); + if (!this.readOnly()) + this._codeMirror.setSelection(new CodeMirror.Pos(lineNumber, columnNumber)); + }, + + clearPositionHighlight: function() + { + if (this._clearHighlightTimeout) + clearTimeout(this._clearHighlightTimeout); + delete this._clearHighlightTimeout; + + if (this._highlightedLine) + this._codeMirror.removeLineClass(this._highlightedLine, null, "cm-highlight"); + delete this._highlightedLine; + }, + + /** + * @return {Array.} + */ + elementsToRestoreScrollPositionsFor: function() + { + return []; + }, + + /** + * @param {TextEditor} textEditor + */ + inheritScrollPositions: function(textEditor) + { + }, + + /** + * @param {number} width + * @param {number} height + */ + _updatePaddingBottom: function(width, height) + { + var scrollInfo = this._codeMirror.getScrollInfo(); + var newPaddingBottom; + var linesElement = this.element.firstChild.querySelector(".CodeMirror-lines"); + var lineCount = this._codeMirror.lineCount(); + if (lineCount <= 1) + newPaddingBottom = 0; + else + newPaddingBottom = Math.max(scrollInfo.clientHeight - this._codeMirror.getLineHandle(this._codeMirror.lastLine()).height, 0); + newPaddingBottom += "px"; + linesElement.style.paddingBottom = newPaddingBottom; + this._codeMirror.setSize(width, height); + }, + + _resizeEditor: function() + { + var parentElement = this.element.parentElement; + if (!parentElement || !this.isShowing()) + return; + var scrollInfo = this._codeMirror.getScrollInfo(); + var width = parentElement.offsetWidth; + var height = parentElement.offsetHeight; + this._codeMirror.setSize(width, height); + this._updatePaddingBottom(width, height); + this._codeMirror.scrollTo(scrollInfo.left, scrollInfo.top); + }, + + onResize: function() + { + this._resizeEditor(); + }, + + /** + * @param {TextRange} range + * @param {string} text + * @return {TextRange} + */ + editRange: function(range, text) + { + var pos = this._toPos(range); + this._codeMirror.replaceRange(text, pos.start, pos.end); + var newRange = this._toRange(pos.start, this._codeMirror.posFromIndex(this._codeMirror.indexFromPos(pos.start) + text.length)); + this._delegate.onTextChanged(range, newRange); + if (CSInspector.settings.textEditorAutoDetectIndent.get()) + this._updateEditorIndentation(); + return newRange; + }, + + /** + * @param {number} lineNumber + * @param {number} column + * @param {boolean=} prefixOnly + * @return {?TextRange} + */ + _wordRangeForCursorPosition: function(lineNumber, column, prefixOnly) + { + var line = this.line(lineNumber); + if (column === 0 || !TextUtils.isWordChar(line.charAt(column - 1))) + return null; + var wordStart = column - 1; + while(wordStart > 0 && TextUtils.isWordChar(line.charAt(wordStart - 1))) + --wordStart; + if (prefixOnly) + return new TextRange(lineNumber, wordStart, lineNumber, column); + var wordEnd = column; + while(wordEnd < line.length && TextUtils.isWordChar(line.charAt(wordEnd))) + ++wordEnd; + return new TextRange(lineNumber, wordStart, lineNumber, wordEnd); + }, + + _beforeChange: function(codeMirror, changeObject) + { + if (!this._dictionary) + return; + this._updatedLines = this._updatedLines || {}; + for(var i = changeObject.from.line; i <= changeObject.to.line; ++i) + this._updatedLines[i] = this.line(i); + }, + + /** + * @param {CodeMirror} codeMirror + * @param {{origin: string, text: Array., removed: Array.}} changeObject + */ + _change: function(codeMirror, changeObject) + { + // We do not show "scroll beyond end of file" span for one line documents, so we need to check if "document has one line" changed. + var hasOneLine = this._codeMirror.lineCount() === 1; + if (hasOneLine !== this._hasOneLine) + this._resizeEditor(); + this._hasOneLine = hasOneLine; + var widgets = this._elementToWidget.values(); + for (var i = 0; i < widgets.length; ++i) + this._codeMirror.removeLineWidget(widgets[i]); + this._elementToWidget.clear(); + + if (this._updatedLines) { + for(var lineNumber in this._updatedLines) + this._removeTextFromCompletionDictionary(this._updatedLines[lineNumber]); + delete this._updatedLines; + } + + var linesToUpdate = {}; + var singleCharInput = false; + do { + var oldRange = this._toRange(changeObject.from, changeObject.to); + var newRange = oldRange.clone(); + var linesAdded = changeObject.text.length; + singleCharInput = (changeObject.origin === "+input" && changeObject.text.length === 1 && changeObject.text[0].length === 1) || + (changeObject.origin === "+delete" && changeObject.removed.length === 1 && changeObject.removed[0].length === 1); + if (linesAdded === 0) { + newRange.endLine = newRange.startLine; + newRange.endColumn = newRange.startColumn; + } else if (linesAdded === 1) { + newRange.endLine = newRange.startLine; + newRange.endColumn = newRange.startColumn + changeObject.text[0].length; + } else { + newRange.endLine = newRange.startLine + linesAdded - 1; + newRange.endColumn = changeObject.text[linesAdded - 1].length; + } + + if (!this._muteTextChangedEvent) + this._delegate.onTextChanged(oldRange, newRange); + + for(var i = newRange.startLine; i <= newRange.endLine; ++i) { + linesToUpdate[i] = true; + } + if (this._dictionary) { + for(var i = newRange.startLine; i <= newRange.endLine; ++i) + linesToUpdate[i] = this.line(i); + } + } while (changeObject = changeObject.next); + if (this._dictionary) { + for(var lineNumber in linesToUpdate) + this._addTextToCompletionDictionary(linesToUpdate[lineNumber]); + } + if (singleCharInput) + this._autocompleteController.autocomplete(); + }, + + _cursorActivity: function() + { + var start = this._codeMirror.getCursor("anchor"); + var end = this._codeMirror.getCursor("head"); + this._delegate.selectionChanged(this._toRange(start, end)); + if (!this._tokenHighlighter.highlightedRegex()) + this._codeMirror.operation(this._tokenHighlighter.highlightSelectedTokens.bind(this._tokenHighlighter)); + }, + + _scroll: function() + { + if (this._scrollTimer) + clearTimeout(this._scrollTimer); + var topmostLineNumber = this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local"); + this._scrollTimer = setTimeout(this._delegate.scrollChanged.bind(this._delegate, topmostLineNumber), 100); + }, + + _focus: function() + { + this._delegate.editorFocused(); + }, + + _blur: function() + { + this._autocompleteController.finishAutocomplete(); + }, + + /** + * @param {number} lineNumber + */ + scrollToLine: function(lineNumber) + { + var pos = new CodeMirror.Pos(lineNumber, 0); + var coords = this._codeMirror.charCoords(pos, "local"); + this._codeMirror.scrollTo(0, coords.top); + }, + + /** + * @return {number} + */ + firstVisibleLine: function() + { + return this._codeMirror.lineAtHeight(this._codeMirror.getScrollInfo().top, "local"); + }, + + /** + * @return {number} + */ + lastVisibleLine: function() + { + var scrollInfo = this._codeMirror.getScrollInfo(); + return this._codeMirror.lineAtHeight(scrollInfo.top + scrollInfo.clientHeight, "local"); + }, + + /** + * @return {TextRange} + */ + selection: function() + { + var start = this._codeMirror.getCursor("anchor"); + var end = this._codeMirror.getCursor("head"); + + return this._toRange(start, end); + }, + + /** + * @return {TextRange?} + */ + lastSelection: function() + { + return this._lastSelection; + }, + + /** + * @param {TextRange} textRange + */ + setSelection: function(textRange) + { + this._lastSelection = textRange; + var pos = this._toPos(textRange); + this._codeMirror.setSelection(pos.start, pos.end); + }, + + /** + * @param {string} text + */ + _detectLineSeparator: function(text) + { + this._lineSeparator = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n"; + }, + + /** + * @param {string} text + */ + setText: function(text) + { + this._muteTextChangedEvent = true; + this._codeMirror.setValue(text); + this._updateEditorIndentation(); + if (this._shouldClearHistory) { + this._codeMirror.clearHistory(); + this._shouldClearHistory = false; + } + this._detectLineSeparator(text); + delete this._muteTextChangedEvent; + }, + + /** + * @return {string} + */ + text: function() + { + return this._codeMirror.getValue().replace(/\n/g, this._lineSeparator); + }, + + /** + * @return {TextRange} + */ + range: function() + { + var lineCount = this.linesCount; + var lastLine = this._codeMirror.getLine(lineCount - 1); + return this._toRange(new CodeMirror.Pos(0, 0), new CodeMirror.Pos(lineCount - 1, lastLine.length)); + }, + + /** + * @param {number} lineNumber + * @return {string} + */ + line: function(lineNumber) + { + return this._codeMirror.getLine(lineNumber); + }, + + /** + * @return {number} + */ + get linesCount() + { + return this._codeMirror.lineCount(); + }, + + /** + * @param {number} line + * @param {string} name + * @param {Object?} value + */ + setAttribute: function(line, name, value) + { + if (line < 0 || line >= this._codeMirror.lineCount()) + return; + var handle = this._codeMirror.getLineHandle(line); + if (handle.attributes === undefined) handle.attributes = {}; + handle.attributes[name] = value; + }, + + /** + * @param {number} line + * @param {string} name + * @return {?Object} value + */ + getAttribute: function(line, name) + { + if (line < 0 || line >= this._codeMirror.lineCount()) + return null; + var handle = this._codeMirror.getLineHandle(line); + return handle.attributes && handle.attributes[name] !== undefined ? handle.attributes[name] : null; + }, + + /** + * @param {number} line + * @param {string} name + */ + removeAttribute: function(line, name) + { + if (line < 0 || line >= this._codeMirror.lineCount()) + return; + var handle = this._codeMirror.getLineHandle(line); + if (handle && handle.attributes) + delete handle.attributes[name]; + }, + + /** + * @param {TextRange} range + * @return {{start: CodeMirror.Pos, end: CodeMirror.Pos}} + */ + _toPos: function(range) + { + return { + start: new CodeMirror.Pos(range.startLine, range.startColumn), + end: new CodeMirror.Pos(range.endLine, range.endColumn) + } + }, + + _toRange: function(start, end) + { + return new TextRange(start.line, start.ch, end.line, end.ch); + }, + + __proto__: View.prototype + } + + /* + CodeMirrorTextEditor.prototype.scrollLineToClientView = function(linenum){ + var editor = this._editor; + var scrollInfo = editor.getScrollInfo(); + + var targetLineTop = editor.heightAtLine(linenum, this._coordinateMode); + + var clientviewTop = scrollInfo.top, + clientviewBottom = scrollInfo.clientHeight + scrollInfo.top; + + if (targetLineTop > clientviewTop && targetLineTop < clientviewBottom - editor.defaultTextHeight()){ + //不需要做任何操作 + } else { + if (scrollInfo.height - targetLineTop < scrollInfo.clientHeight){ + //在最后一屏 + editor.scrollTo(0, scrollInfo.height - scrollInfo.clientHeight); + } else { + //把目标行滚动到最上面,留4行方便看到上下文 + editor.scrollTo(0, editor.heightAtLine(linenum - this._scrollMaginOfExeclineNums, this._coordinateMode)); + //editor.scrollTo(0, targetLineTop); + } + } + }; + */ + + return CodeMirrorTextEditor; +}); diff --git a/interpreter/debugger/client/ConsolePanel.js b/interpreter/debugger/client/ConsolePanel.js new file mode 100644 index 0000000..a3b62f8 --- /dev/null +++ b/interpreter/debugger/client/ConsolePanel.js @@ -0,0 +1,14 @@ +define(function(require, exports){ + var Panel = require("Panel").Panel; + + function ConsolePanel(){ + Panel.call(this); + } + + ConsolePanel.prototype = { + + __proto__: Panel.prototype + }; + + return ConsolePanel; +}); diff --git a/interpreter/debugger/client/ContentProviderBasedProjectDelegate.js b/interpreter/debugger/client/ContentProviderBasedProjectDelegate.js new file mode 100644 index 0000000..746255a --- /dev/null +++ b/interpreter/debugger/client/ContentProviderBasedProjectDelegate.js @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2013 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. + */ +define(function(require){ + +var EventObjectEmitter = require("events").EventObjectEmitter; +var ProjectDelegate = require("ProjectDelegate"); +var FileDescriptor = require("FileDescriptor"); + + /** + * @constructor + * @implements {ProjectDelegate} + * @extends {EventObjectEmitter} + * @param {string} type + */ +function ContentProviderBasedProjectDelegate(type) +{ + EventObjectEmitter.call(this); + this._type = type; + /** @type {!Object.} */ + this._contentProviders = {}; + /** @type {Object.} */ + this._isContentScriptMap = {}; +} + +ContentProviderBasedProjectDelegate.prototype = { + /** + * @return {string} + */ + id: function() + { + // Overriddden by subclasses + return ""; + }, + + /** + * @return {string} + */ + type: function() + { + return this._type; + }, + + /** + * @return {string} + */ + displayName: function() + { + // Overriddden by subclasses + return ""; + }, + + /** + * @param {string} path + * @param {function(?Date, ?number)} callback + */ + requestMetadata: function(path, callback) + { + callback(null, null); + }, + + /** + * @param {string} path + * @param {function(?string,boolean,string)} callback + */ + requestFileContent: function(path, callback) + { + var contentProvider = this._contentProviders[path]; + contentProvider.requestContent(callback); + }, + + /** + * @return {boolean} + */ + canSetFileContent: function() + { + return false; + }, + + /** + * @param {string} path + * @param {string} newContent + * @param {function(?string)} callback + */ + setFileContent: function(path, newContent, callback) + { + callback(null); + }, + + /** + * @return {boolean} + */ + canRename: function() + { + return false; + }, + + /** + * @param {string} path + * @param {string} newName + * @param {function(boolean, string=)} callback + */ + rename: function(path, newName, callback) + { + this.performRename(path, newName, innerCallback.bind(this)); + + /** + * @param {boolean} success + * @param {string=} newName + */ + function innerCallback(success, newName) + { + if (success) + this._updateName(path, newName); + callback(success, newName); + } + }, + + /** + * @param {string} path + */ + refresh: function(path) + { + }, + + /** + * @param {string} path + */ + excludeFolder: function(path) + { + }, + + /** + * @param {string} path + * @param {?string} name + * @param {function(?string)} callback + */ + createFile: function(path, name, callback) + { + }, + + /** + * @param {string} path + */ + deleteFile: function(path) + { + }, + + remove: function() + { + }, + + /** + * @param {string} path + * @param {string} newName + * @param {function(boolean, string=)} callback + */ + performRename: function(path, newName, callback) + { + callback(false); + }, + + /** + * @param {string} path + * @param {string} newName + */ + _updateName: function(path, newName) + { + var oldPath = path; + var copyOfPath = path.split("/"); + copyOfPath[copyOfPath.length - 1] = newName; + var newPath = copyOfPath.join("/"); + this._contentProviders[newPath] = this._contentProviders[oldPath]; + delete this._contentProviders[oldPath]; + }, + + /** + * @param {string} path + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInFileContent: function(path, query, caseSensitive, isRegex, callback) + { + var contentProvider = this._contentProviders[path]; + contentProvider.searchInContent(query, caseSensitive, isRegex, callback); + }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {Progress} progress + * @param {function(StringMap)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, progress, callback) + { + var result = new StringMap(); + + var paths = Object.keys(this._contentProviders); + var totalCount = paths.length; + if (totalCount === 0) { + // searchInContent should call back later. + setTimeout(doneCallback, 0); + return; + } + + function filterOutContentScripts(path) + { + return !this._isContentScriptMap[path]; + } + + if (!CSInspector.settings.searchInContentScripts.get()) + paths = paths.filter(filterOutContentScripts.bind(this)); + + var barrier = new CallbackBarrier(); + progress.setTotalWork(paths.length); + for (var i = 0; i < paths.length; ++i) + this._contentProviders[paths[i]].searchInContent(query, caseSensitive, isRegex, barrier.createCallback(contentCallback.bind(this, i))); + barrier.callWhenDone(doneCallback); + + function contentCallback(i, searchMatches) + { + result.put(paths[i], searchMatches); + progress.worked(1); + } + + function doneCallback() + { + callback(result); + progress.done(); + } + }, + + /** + * @param {Progress} progress + * @param {function()} callback + */ + indexContent: function(progress, callback) + { + setTimeout(innerCallback, 0); + + function innerCallback() + { + progress.done(); + callback(); + } + }, + + /** + * @param {string} parentPath + * @param {string} name + * @param {string} url + * @param {ContentProvider} contentProvider + * @param {boolean} isEditable + * @param {boolean=} isContentScript + * @return {string} + */ + addContentProvider: function(parentPath, name, url, contentProvider, isEditable, isContentScript) + { + var path = parentPath ? parentPath + "/" + name : name; + var fileDescriptor = new FileDescriptor(parentPath, name, url, url, contentProvider.contentType(), isEditable, isContentScript); + this._contentProviders[path] = contentProvider; + this._isContentScriptMap[path] = isContentScript || false; + this.dispatchEventToListeners(ProjectDelegate.Events.FileAdded, fileDescriptor); + return path; + }, + + /** + * @param {string} path + */ + removeFile: function(path) + { + delete this._contentProviders[path]; + delete this._isContentScriptMap[path]; + this.dispatchEventToListeners(ProjectDelegate.Events.FileRemoved, path); + }, + + /** + * @return {Object.} + */ + contentProviders: function() + { + return this._contentProviders; + }, + + reset: function() + { + this._contentProviders = {}; + this._isContentScriptMap = {}; + this.dispatchEventToListeners(ProjectDelegate.Events.Reset, null); + }, + + __proto__: EventObjectEmitter.prototype +} + +return ContentProviderBasedProjectDelegate; + +}); diff --git a/interpreter/debugger/client/ContextMenu.js b/interpreter/debugger/client/ContextMenu.js new file mode 100644 index 0000000..b868b20 --- /dev/null +++ b/interpreter/debugger/client/ContextMenu.js @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2009 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 + * @param {ContextSubMenuItem} topLevelMenu + * @param {string} type + * @param {string=} label + * @param {boolean=} disabled + * @param {boolean=} checked + */ +function ContextMenuItem(topLevelMenu, type, label, disabled, checked) +{ + this._type = type; + this._label = label; + this._disabled = disabled; + this._checked = checked; + this._contextMenu = topLevelMenu; + if (type === "item" || type === "checkbox") + this._id = topLevelMenu.nextId(); +} + +ContextMenuItem.prototype = { + id: function() + { + return this._id; + }, + + type: function() + { + return this._type; + }, + + /** + * @return {boolean} + */ + isEnabled: function() + { + return !this._disabled; + }, + + /** + * @param {boolean} enabled + */ + setEnabled: function(enabled) + { + this._disabled = !enabled; + }, + + _buildDescriptor: function() + { + switch (this._type) { + case "item": + return { type: "item", id: this._id, label: this._label, enabled: !this._disabled }; + case "separator": + return { type: "separator" }; + case "checkbox": + return { type: "checkbox", id: this._id, label: this._label, checked: !!this._checked, enabled: !this._disabled }; + } + } +} + +/** + * @constructor + * @extends {ContextMenuItem} + * @param topLevelMenu + * @param {string=} label + * @param {boolean=} disabled + */ +function ContextSubMenuItem(topLevelMenu, label, disabled) +{ + ContextMenuItem.call(this, topLevelMenu, "subMenu", label, disabled); + /** @type {!Array.} */ + this._items = []; +} + +ContextSubMenuItem.prototype = { + /** + * @param {string} label + * @param {function(?)} handler + * @param {boolean=} disabled + * @return {ContextMenuItem} + */ + appendItem: function(label, handler, disabled) + { + var item = new ContextMenuItem(this._contextMenu, "item", label, disabled); + this._pushItem(item); + this._contextMenu._setHandler(item.id(), handler); + return item; + }, + + /** + * @param {string} label + * @param {boolean=} disabled + * @return {ContextMenuItem} + */ + appendSubMenuItem: function(label, disabled) + { + var item = new ContextSubMenuItem(this._contextMenu, label, disabled); + this._pushItem(item); + return item; + }, + + /** + * @param {boolean=} disabled + */ + appendCheckboxItem: function(label, handler, checked, disabled) + { + var item = new ContextMenuItem(this._contextMenu, "checkbox", label, disabled, checked); + this._pushItem(item); + this._contextMenu._setHandler(item.id(), handler); + return item; + }, + + appendSeparator: function() + { + if (this._items.length) + this._pendingSeparator = true; + }, + + /** + * @param {!ContextMenuItem} item + */ + _pushItem: function(item) + { + if (this._pendingSeparator) { + this._items.push(new ContextMenuItem(this._contextMenu, "separator")); + delete this._pendingSeparator; + } + this._items.push(item); + }, + + /** + * @return {boolean} + */ + isEmpty: function() + { + return !this._items.length; + }, + + _buildDescriptor: function() + { + var result = { type: "subMenu", label: this._label, enabled: !this._disabled, subItems: [] }; + for (var i = 0; i < this._items.length; ++i) + result.subItems.push(this._items[i]._buildDescriptor()); + return result; + }, + + __proto__: ContextMenuItem.prototype +} + +/** + * @constructor + * @extends {ContextSubMenuItem} + */ +function ContextMenu(event) { + ContextSubMenuItem.call(this, this, ""); + this._event = event; + this._handlers = {}; + this._id = 0; +} + +ContextMenu.prototype = { + nextId: function() + { + return this._id++; + }, + + show: function() + { + var menuObject = this._buildDescriptor(); + + if (menuObject.length) { + _contextMenu = this; + InspectorFrontendHost.showContextMenu(this._event, menuObject); + this._event.consume(); + } + }, + + showSoftMenu: function() + { + var menuObject = this._buildDescriptor(); + + if (menuObject.length) { + _contextMenu = this; + var softMenu = new SoftContextMenu(menuObject); + softMenu.show(this._event, true); + } + this._event.consume(); + }, + + _setHandler: function(id, handler) + { + if (handler) + this._handlers[id] = handler; + }, + + _buildDescriptor: function() + { + var result = []; + for (var i = 0; i < this._items.length; ++i) + result.push(this._items[i]._buildDescriptor()); + return result; + }, + + _itemSelected: function(id) + { + if (this._handlers[id]) + this._handlers[id].call(this); + }, + + /** + * @param {Object} target + */ + appendApplicableItems: function(target) + { + for (var i = 0; i < ContextMenu._providers.length; ++i) { + var provider = ContextMenu._providers[i]; + this.appendSeparator(); + provider.appendApplicableItems(this._event, this, target); + this.appendSeparator(); + } + }, + + __proto__: ContextSubMenuItem.prototype +} + +/** + * @interface + */ +ContextMenu.Provider = function() { +} + +ContextMenu.Provider.prototype = { + /** + * @param {ContextMenu} contextMenu + * @param {Object} target + */ + appendApplicableItems: function(event, contextMenu, target) { } +} + +/** + * @param {ContextMenu.Provider} provider + */ +ContextMenu.registerProvider = function(provider) +{ + ContextMenu._providers.push(provider); +} + +ContextMenu._providers = []; + +/* +WebInspector.contextMenuItemSelected = function(id) +{ + if (WebInspector._contextMenu) + WebInspector._contextMenu._itemSelected(id); +} + +WebInspector.contextMenuCleared = function() +{ + // FIXME: Unfortunately, contextMenuCleared is invoked between show and item selected + // so we can't delete last menu object from WebInspector. Fix the contract. +} +*/ diff --git a/interpreter/debugger/client/DOMExtension.js b/interpreter/debugger/client/DOMExtension.js new file mode 100644 index 0000000..158298f --- /dev/null +++ b/interpreter/debugger/client/DOMExtension.js @@ -0,0 +1,613 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * 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: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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. + * + * Contains diff method based on Javascript Diff Algorithm By John Resig + * http://ejohn.org/files/jsdiff.js (released under the MIT license). + */ + +/** + * @param {string=} direction + */ +Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction) +{ + var startNode; + var startOffset = 0; + var endNode; + var endOffset = 0; + + if (!stayWithinNode) + stayWithinNode = this; + + if (!direction || direction === "backward" || direction === "both") { + var node = this; + while (node) { + if (node === stayWithinNode) { + if (!startNode) + startNode = stayWithinNode; + break; + } + + if (node.nodeType === Node.TEXT_NODE) { + var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1)); + for (var i = start; i >= 0; --i) { + if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { + startNode = node; + startOffset = i + 1; + break; + } + } + } + + if (startNode) + break; + + node = node.traversePreviousNode(stayWithinNode); + } + + if (!startNode) { + startNode = stayWithinNode; + startOffset = 0; + } + } else { + startNode = this; + startOffset = offset; + } + + if (!direction || direction === "forward" || direction === "both") { + node = this; + while (node) { + if (node === stayWithinNode) { + if (!endNode) + endNode = stayWithinNode; + break; + } + + if (node.nodeType === Node.TEXT_NODE) { + var start = (node === this ? offset : 0); + for (var i = start; i < node.nodeValue.length; ++i) { + if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { + endNode = node; + endOffset = i; + break; + } + } + } + + if (endNode) + break; + + node = node.traverseNextNode(stayWithinNode); + } + + if (!endNode) { + endNode = stayWithinNode; + endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length; + } + } else { + endNode = this; + endOffset = offset; + } + + var result = this.ownerDocument.createRange(); + result.setStart(startNode, startOffset); + result.setEnd(endNode, endOffset); + + return result; +} + +Node.prototype.traverseNextTextNode = function(stayWithin) +{ + var node = this.traverseNextNode(stayWithin); + if (!node) + return; + + while (node && node.nodeType !== Node.TEXT_NODE) + node = node.traverseNextNode(stayWithin); + + return node; +} + +Node.prototype.rangeBoundaryForOffset = function(offset) +{ + var node = this.traverseNextTextNode(this); + while (node && offset > node.nodeValue.length) { + offset -= node.nodeValue.length; + node = node.traverseNextTextNode(this); + } + if (!node) + return { container: this, offset: 0 }; + return { container: node, offset: offset }; +} + +/** + * @param {string} className + */ +Element.prototype.removeStyleClass = function(className) +{ + this.classList.remove(className); +} + +Element.prototype.removeMatchingStyleClasses = function(classNameRegex) +{ + var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)"); + if (regex.test(this.className)) + this.className = this.className.replace(regex, " "); +} + +/** + * @param {string} className + */ +Element.prototype.addStyleClass = function(className) +{ + this.classList.add(className); +} + +/** + * @param {string} className + * @return {boolean} + */ +Element.prototype.hasStyleClass = function(className) +{ + return this.classList.contains(className); +} + +/** + * @param {string} className + * @param {*} enable + */ +Element.prototype.enableStyleClass = function(className, enable) +{ + if (enable) + this.addStyleClass(className); + else + this.removeStyleClass(className); +} + +/** + * @param {number|undefined} x + * @param {number|undefined} y + */ +Element.prototype.positionAt = function(x, y) +{ + if (typeof x === "number") + this.style.setProperty("left", x + "px"); + else + this.style.removeProperty("left"); + + if (typeof y === "number") + this.style.setProperty("top", y + "px"); + else + this.style.removeProperty("top"); +} + +Element.prototype.isScrolledToBottom = function() +{ + // This code works only for 0-width border + return this.scrollTop + this.clientHeight === this.scrollHeight; +} + +/** + * @param {Node} fromNode + * @param {Node} toNode + */ +function removeSubsequentNodes(fromNode, toNode) +{ + for (var node = fromNode; node && node !== toNode; ) { + var nodeToRemove = node; + node = node.nextSibling; + nodeToRemove.remove(); + } +} + +/** + * @constructor + * @param {number} width + * @param {number} height + */ +function Size(width, height) +{ + this.width = width; + this.height = height; +} + +/** + * @param {Element=} containerElement + * @return {Size} + */ +Element.prototype.measurePreferredSize = function(containerElement) +{ + containerElement = containerElement || document.body; + containerElement.appendChild(this); + this.positionAt(0, 0); + var result = new Size(this.offsetWidth, this.offsetHeight); + this.positionAt(undefined, undefined); + this.remove(); + return result; +} + +Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray) +{ + for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) + for (var i = 0; i < nameArray.length; ++i) + if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase()) + return node; + return null; +} + +Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName) +{ + return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]); +} + +/** + * @param {string} className + * @param {Element=} stayWithin + */ +Node.prototype.enclosingNodeOrSelfWithClass = function(className, stayWithin) +{ + for (var node = this; node && node !== stayWithin && node !== this.ownerDocument; node = node.parentNode) + if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className)) + return node; + return null; +} + +Element.prototype.query = function(query) +{ + return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; +} + +Element.prototype.removeChildren = function() +{ + if (this.firstChild) + this.textContent = ""; +} + +Element.prototype.isInsertionCaretInside = function() +{ + var selection = window.getSelection(); + if (!selection.rangeCount || !selection.isCollapsed) + return false; + var selectionRange = selection.getRangeAt(0); + return selectionRange.startContainer.isSelfOrDescendant(this); +} + +/** + * @param {string=} className + */ +Element.prototype.createChild = function(elementName, className) +{ + var element = this.ownerDocument.createElement(elementName); + if (className) + element.className = className; + this.appendChild(element); + return element; +} + +DocumentFragment.prototype.createChild = Element.prototype.createChild; + +/** + * @param {string} text + */ +Element.prototype.createTextChild = function(text) +{ + var element = this.ownerDocument.createTextNode(text); + this.appendChild(element); + return element; +} + +DocumentFragment.prototype.createTextChild = Element.prototype.createTextChild; + +/** + * @return {number} + */ +Element.prototype.totalOffsetLeft = function() +{ + return this.totalOffset().left; +} + +/** + * @return {number} + */ +Element.prototype.totalOffsetTop = function() +{ + return this.totalOffset().top; + +} + +Element.prototype.totalOffset = function() +{ + var totalLeft = 0; + var totalTop = 0; + + for (var element = this; element; element = element.offsetParent) { + totalLeft += element.offsetLeft; + totalTop += element.offsetTop; + if (this !== element) { + totalLeft += element.clientLeft - element.scrollLeft; + totalTop += element.clientTop - element.scrollTop; + } + } + + return { left: totalLeft, top: totalTop }; +} + +Element.prototype.scrollOffset = function() +{ + var curLeft = 0; + var curTop = 0; + for (var element = this; element; element = element.scrollParent) { + curLeft += element.scrollLeft; + curTop += element.scrollTop; + } + return { left: curLeft, top: curTop }; +} + +/** + * @constructor + * @param {number=} x + * @param {number=} y + * @param {number=} width + * @param {number=} height + */ +function AnchorBox(x, y, width, height) +{ + this.x = x || 0; + this.y = y || 0; + this.width = width || 0; + this.height = height || 0; +} + +/** + * @param {Window} targetWindow + * @return {AnchorBox} + */ +Element.prototype.offsetRelativeToWindow = function(targetWindow) +{ + var elementOffset = new AnchorBox(); + var curElement = this; + var curWindow = this.ownerDocument.defaultView; + while (curWindow && curElement) { + elementOffset.x += curElement.totalOffsetLeft(); + elementOffset.y += curElement.totalOffsetTop(); + if (curWindow === targetWindow) + break; + + curElement = curWindow.frameElement; + curWindow = curWindow.parent; + } + + return elementOffset; +} + +/** + * @param {Window} targetWindow + * @return {AnchorBox} + */ +Element.prototype.boxInWindow = function(targetWindow) +{ + targetWindow = targetWindow || this.ownerDocument.defaultView; + + var anchorBox = this.offsetRelativeToWindow(window); + anchorBox.width = Math.min(this.offsetWidth, window.innerWidth - anchorBox.x); + anchorBox.height = Math.min(this.offsetHeight, window.innerHeight - anchorBox.y); + + return anchorBox; +} + +/** + * @param {string} text + */ +Element.prototype.setTextAndTitle = function(text) +{ + this.textContent = text; + this.title = text; +} + +KeyboardEvent.prototype.__defineGetter__("data", function() +{ + // Emulate "data" attribute from DOM 3 TextInput event. + // See http://www.w3.org/TR/DOM-Level-3-Events/#events-Events-TextEvent-data + switch (this.type) { + case "keypress": + if (!this.ctrlKey && !this.metaKey) + return String.fromCharCode(this.charCode); + else + return ""; + case "keydown": + case "keyup": + if (!this.ctrlKey && !this.metaKey && !this.altKey) + return String.fromCharCode(this.which); + else + return ""; + } +}); + +/** + * @param {boolean=} preventDefault + */ +Event.prototype.consume = function(preventDefault) +{ + this.stopImmediatePropagation(); + if (preventDefault) + this.preventDefault(); + this.handled = true; +} + +Text.prototype.select = function(start, end) +{ + start = start || 0; + end = end || this.textContent.length; + + if (start < 0) + start = end + start; + + var selection = this.ownerDocument.defaultView.getSelection(); + selection.removeAllRanges(); + var range = this.ownerDocument.createRange(); + range.setStart(this, start); + range.setEnd(this, end); + selection.addRange(range); + return this; +} + +Element.prototype.selectionLeftOffset = function() +{ + // Calculate selection offset relative to the current element. + + var selection = window.getSelection(); + if (!selection.containsNode(this, true)) + return null; + + var leftOffset = selection.anchorOffset; + var node = selection.anchorNode; + + while (node !== this) { + while (node.previousSibling) { + node = node.previousSibling; + leftOffset += node.textContent.length; + } + node = node.parentNode; + } + + return leftOffset; +} + +Node.prototype.isAncestor = function(node) +{ + if (!node) + return false; + + var currentNode = node.parentNode; + while (currentNode) { + if (this === currentNode) + return true; + currentNode = currentNode.parentNode; + } + return false; +} + +Node.prototype.isDescendant = function(descendant) +{ + return !!descendant && descendant.isAncestor(this); +} + +Node.prototype.isSelfOrAncestor = function(node) +{ + return !!node && (node === this || this.isAncestor(node)); +} + +Node.prototype.isSelfOrDescendant = function(node) +{ + return !!node && (node === this || this.isDescendant(node)); +} + +Node.prototype.traverseNextNode = function(stayWithin) +{ + var node = this.firstChild; + if (node) + return node; + + if (stayWithin && this === stayWithin) + return null; + + node = this.nextSibling; + if (node) + return node; + + node = this; + while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin)) + node = node.parentNode; + if (!node) + return null; + + return node.nextSibling; +} + +Node.prototype.traversePreviousNode = function(stayWithin) +{ + if (stayWithin && this === stayWithin) + return null; + var node = this.previousSibling; + while (node && node.lastChild) + node = node.lastChild; + if (node) + return node; + return this.parentNode; +} + +function isEnterKey(event) { + // Check if in IME. + return event.keyCode !== 229 && event.keyIdentifier === "Enter"; +} + +function consumeEvent(e) +{ + e.consume(); +} + +/** + * Mutation observers leak memory. Keep track of them and disconnect + * on unload. + * @constructor + * @param {function(Array.)} handler + */ +function NonLeakingMutationObserver(handler) +{ + this._observer = new WebKitMutationObserver(handler); + NonLeakingMutationObserver._instances.push(this); + if (!NonLeakingMutationObserver._unloadListener) { + NonLeakingMutationObserver._unloadListener = function() { + while (NonLeakingMutationObserver._instances.length) + NonLeakingMutationObserver._instances[NonLeakingMutationObserver._instances.length - 1].disconnect(); + }; + window.addEventListener("unload", NonLeakingMutationObserver._unloadListener, false); + } +} + +NonLeakingMutationObserver._instances = []; + +NonLeakingMutationObserver.prototype = { + /** + * @param {Element} element + * @param {Object} config + */ + observe: function(element, config) + { + if (this._observer) + this._observer.observe(element, config); + }, + + disconnect: function() + { + if (this._observer) + this._observer.disconnect(); + NonLeakingMutationObserver._instances.remove(this); + delete this._observer; + } +} + diff --git a/interpreter/debugger/client/DebugAgent.js b/interpreter/debugger/client/DebugAgent.js index 322c249..e54a3eb 100644 --- a/interpreter/debugger/client/DebugAgent.js +++ b/interpreter/debugger/client/DebugAgent.js @@ -4,39 +4,112 @@ define(function(){ this._backend = backend; this._closed = false; this._backend.on("close", this._backendCloseHandler.bind(this)); + + this._backend.replyArgs["setBreakpointBySourceId"] = ["breakpointId", "locations"]; + this._backend.replyArgs["evaluate"] = ["result", "wasThrown"]; + this._backend.replyArgs["getObjectProperties"] = ["result", "wasThrown"]; } + DebugAgent.prototype = { + getScriptSource: function(scriptId, cb){ + //TODO + }, + setBreakpointsActive: function(active){ + var message = { + "method": "toggleBreakpointsActive", + "active": active + }; + this.sendMessageToBackend(message); + }, + + setBreakpointByUrl: function(url, lineNumber, columnNumber, cb){ + }, + + setBreakpointBySourceId: function(rawLocation, condition, cb){ + var message = { + "method": "setBreakpointBySourceId", + "rawLocation": rawLocation + }; + this.sendMessageToBackend(message, cb); + }, + removeBreakpoint: function(breakpointId){ + var message = { + "method":"removeBreakpoint", + "breakpointId": breakpointId + }; + this.sendMessageToBackend(message); + }, + evaluate: function() { + if (CSInspector.debugModel.selectedCallFrame()){ + CSInspector.debugModel.evaluateOnSelectedCallFrame.apply(CSInspector.debugModel, Array.prototype.slice.call(arguments, 0)); + } else { + //console.error("can't evalute expresss, because of no callFrame selected"); + } + }, + evaluateOnCallFrame: function( + callFrameId, + code, + objectGroup, + includeCommandLineAPI, + doNotPauseOnExceptionsAndMuteConsole, + returnByValue, + generatePreview, + callback) + { + var message = { + "method":"evaluate", + "callFrameId": callFrameId, + "expression": code + }; + this.sendMessageToBackend(message, callback); + }, + getProperties: function(objectId, ownProperties, accessorPropertiesOnly, cb){ + var message = { + "method": "getObjectProperties", + "objectId": objectId + }; + + this.sendMessageToBackend(message, cb); + } + + }; + DebugAgent.prototype._backendCloseHandler = function(){ this._closed = true; }; - DebugAgent.prototype.sendMessageToBackend = function(message){ + DebugAgent.prototype.sendMessageToBackend = function(message, callback){ + this._wrap(message, callback); if (!this._closed) this._backend.sendMessage(message); - else console.log("remote had closed!"); + else console.notice("remote had closed!"); }; DebugAgent.prototype.resume = function(cb){ var message = { "method": "resume" }; - this._wrap(message, cb); - this.sendMessageToBackend(message); + this.sendMessageToBackend(message, cb); + }; + + DebugAgent.prototype.stepOut = function(cb){ + var message = { + "method": "stepOut" + }; + this.sendMessageToBackend(message, cb); }; DebugAgent.prototype.stepOver = function(message, cb){ var message = { "method": "stepOver" }; - this._wrap(message, cb); - this.sendMessageToBackend(message); + this.sendMessageToBackend(message, cb); }; DebugAgent.prototype.stepInto = function(message, cb){ var message = { "method": "stepInto" }; - this._wrap(message, cb); - this.sendMessageToBackend(message); + this.sendMessageToBackend(message, cb); }; DebugAgent.prototype._wrap = function(message, cb){ @@ -57,7 +130,6 @@ define(function(){ "resume": resume ? true : false }; - this._wrap(message); this.sendMessageToBackend(message); }; diff --git a/interpreter/debugger/client/DebugModel.js b/interpreter/debugger/client/DebugModel.js index e2b97d0..98075c3 100644 --- a/interpreter/debugger/client/DebugModel.js +++ b/interpreter/debugger/client/DebugModel.js @@ -1,39 +1,555 @@ define(function(require){ - var Util = require("util"); var EventEmitter = require("events").EventEmitter; + var DebuggerPausedDetails = require("DebuggerPausedDetails"); + var Script = require("Script"); + var RemoteObject = require("RemoteObject").RemoteObject; - function DebugModel(backend){ + /** + * @constructor + * @implements {RawLocation} + * @param {string} scriptId + * @param {number} lineNumber + * @param {number} columnNumber + */ + function Location(scriptId, lineNumber, columnNumber) + { + this.scriptId = scriptId; + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + } + + function DebuggerModel(backend){ + EventEmitter.call(this); this._backend = backend; this._listeners = {}; backend.registerCommand(new Dispatcher(this)); + /** + * @type {Object.} + */ + this._scripts = {}; + /** @type {!Object.>} */ + this._scriptsBySourceURL = {}; + + this._debuggerPausedDetails = null; + this._breakpointsActive = true; this._backend.on("errorExit", this._connectErrorHandler.bind(this)); } - Util.inherits(DebugModel, EventEmitter); + DebuggerModel.prototype = { + + /** + * @return {boolean} + */ + debuggerEnabled: function() + { + return !!this._debuggerEnabled; + }, + + enableDebugger: function() + { + if (this._debuggerEnabled) + return; + + function callback(error, result) + { + this._canSetScriptSource = result; + } + DebuggerAgent.canSetScriptSource(callback.bind(this)); + DebuggerAgent.enable(this._debuggerWasEnabled.bind(this)); + }, + + disableDebugger: function() + { + if (!this._debuggerEnabled) + return; + + DebuggerAgent.disable(this._debuggerWasDisabled.bind(this)); + }, + + /** + * @return {boolean} + */ + canSetScriptSource: function() + { + return this._canSetScriptSource; + }, + + _debuggerWasEnabled: function() + { + this._debuggerEnabled = true; + this._pauseOnExceptionStateChanged(); + this.dispatchEventToListeners(DebuggerModel.Events.DebuggerWasEnabled); + }, + + _pauseOnExceptionStateChanged: function() + { + DebuggerAgent.setPauseOnExceptions(CSInspector.settings.pauseOnExceptionStateString.get()); + }, + + _debuggerWasDisabled: function() + { + this._debuggerEnabled = false; + this.dispatchEventToListeners(DebuggerModel.Events.DebuggerWasDisabled); + }, + + /** + * @param {DebuggerModel.Location} rawLocation + */ + continueToLocation: function(rawLocation) + { + DebuggerAgent.continueToLocation(rawLocation); + }, + + /** + * @param {DebuggerModel.Location} rawLocation + */ + stepIntoSelection: function(rawLocation) + { + /** + * @param {DebuggerModel.Location} requestedLocation + * @param {?string} error + */ + function callback(requestedLocation, error) + { + if (error) + return; + this._pendingStepIntoLocation = requestedLocation; + }; + DebuggerAgent.continueToLocation(rawLocation, true, callback.bind(this, rawLocation)); + }, + + /** + * @param {DebuggerModel.Location} rawLocation + * @param {string} condition + * @param {function(?DebuggerAgent.BreakpointId, Array.):void=} callback + */ + setBreakpointByScriptLocation: function(rawLocation, condition, callback) + { + var script = this.scriptForId(rawLocation.scriptId); + if (script.sourceURL) + this.setBreakpointByURL(script.sourceURL, rawLocation.lineNumber, rawLocation.columnNumber, condition, callback); + else + this.setBreakpointBySourceId(rawLocation, condition, callback); + }, + + /** + * @param {string} url + * @param {number} lineNumber + * @param {number=} columnNumber + * @param {string=} condition + * @param {function(?DebuggerAgent.BreakpointId, Array.)=} callback + */ + setBreakpointByURL: function(url, lineNumber, columnNumber, condition, callback) + { + // Adjust column if needed. + var minColumnNumber = 0; + var scripts = this._scriptsBySourceURL[url] || []; + for (var i = 0, l = scripts.length; i < l; ++i) { + var script = scripts[i]; + if (lineNumber === script.lineOffset) + minColumnNumber = minColumnNumber ? Math.min(minColumnNumber, script.columnOffset) : script.columnOffset; + } + columnNumber = Math.max(columnNumber, minColumnNumber); + + /** + * @this {DebuggerModel} + * @param {?Protocol.Error} error + * @param {DebuggerAgent.BreakpointId} breakpointId + * @param {Array.} locations + */ + function didSetBreakpoint(error, breakpointId, locations) + { + if (callback) { + var rawLocations = /** @type {Array.} */ (locations); + callback(error ? null : breakpointId, rawLocations); + } + } + //CSInspector.debugAgent.setBreakpointByUrl(url, lineNumber, columnNumber, didSetBreakpoint.bind(this)); + var rawLocations = new Location(script.scriptId, lineNumber, columnNumber); + CSInspector.debugAgent.setBreakpointBySourceId(rawLocations, "", didSetBreakpoint.bind(this)); + }, + + /** + * @param {DebuggerModel.Location} rawLocation + * @param {string} condition + * @param {function(?DebuggerAgent.BreakpointId, Array.)=} callback + */ + setBreakpointBySourceId: function(rawLocation, condition, callback) + { + /** + * @this {DebuggerModel} + * @param {?Protocol.Error} error + * @param {DebuggerAgent.BreakpointId} breakpointId + * @param {DebuggerAgent.Location} actualLocation + */ + function didSetBreakpoint(error, breakpointId, actualLocation) + { + if (callback) { + var rawLocation = /** @type {DebuggerModel.Location} */ (actualLocation); + callback(error ? null : breakpointId, [rawLocation]); + } + } + DebuggerAgent.setBreakpoint(rawLocation, condition, didSetBreakpoint.bind(this)); + }, + + /** + * @param {DebuggerAgent.BreakpointId} breakpointId + * @param {function(?Protocol.Error)=} callback + */ + removeBreakpoint: function(breakpointId, callback) + { + CSInspector.debugAgent.removeBreakpoint(breakpointId, callback); + }, + + /** + * @param {DebuggerAgent.BreakpointId} breakpointId + * @param {DebuggerAgent.Location} location + */ + _breakpointResolved: function(breakpointId, location) + { + this.dispatchEventToListeners(DebuggerModel.Events.BreakpointResolved, {breakpointId: breakpointId, location: location}); + }, + + _globalObjectCleared: function() + { + this._setDebuggerPausedDetails(null); + this._reset(); + this.dispatchEventToListeners(DebuggerModel.Events.GlobalObjectCleared); + }, + + _reset: function() + { + this._scripts = {}; + this._scriptsBySourceURL = {}; + }, + + /** + * @return {Object.} + */ + get scripts() + { + return this._scripts; + }, + + /** + * @param {DebuggerAgent.ScriptId} scriptId + * @return {Script} + */ + scriptForId: function(scriptId) + { + return this._scripts[scriptId] || null; + }, + + /** + * @return {!Array.} + */ + scriptsForSourceURL: function(sourceURL) + { + if (!sourceURL) + return []; + return this._scriptsBySourceURL[sourceURL] || []; + }, + + /** + * @param {DebuggerAgent.ScriptId} scriptId + * @param {string} newSource + * @param {function(?Protocol.Error, DebuggerAgent.SetScriptSourceError=)} callback + */ + setScriptSource: function(scriptId, newSource, callback) + { + this._scripts[scriptId].editSource(newSource, this._didEditScriptSource.bind(this, scriptId, newSource, callback)); + }, + + /** + * @return {Array.} + */ + get callFrames() + { + return this._debuggerPausedDetails ? this._debuggerPausedDetails.callFrames : null; + }, + + /** + * @return {?DebuggerPausedDetails} + */ + debuggerPausedDetails: function() + { + return this._debuggerPausedDetails; + }, + + /** + * @param {Array.} callFrames + * @param {string} reason + * @param {Object|undefined} auxData + * @param {Array.} breakpointIds + */ + _pausedScript: function(callFrames, reason, auxData, breakpointIds) + { + if (this._pendingStepIntoLocation) { + var requestedLocation = this._pendingStepIntoLocation; + delete this._pendingStepIntoLocation; - DebugModel.prototype._connectErrorHandler = function(){ - this.emit(DebugModel.EventTypes.DebugFinished, true); + if (callFrames.length > 0) { + var topLocation = callFrames[0].location; + if (topLocation.lineNumber == requestedLocation.lineNumber && topLocation.columnNumber == requestedLocation.columnNumber && topLocation.scriptId == requestedLocation.scriptId) { + DebuggerAgent.stepInto(); + return; + } + } + } + + this._setDebuggerPausedDetails(new DebuggerPausedDetails(this, callFrames, reason, auxData, breakpointIds)); + }, + + /** + * @param {?DebuggerPausedDetails} debuggerPausedDetails + */ + _setDebuggerPausedDetails: function(debuggerPausedDetails) + { + if (this._debuggerPausedDetails) + this._debuggerPausedDetails.dispose(); + this._debuggerPausedDetails = debuggerPausedDetails; + if (this._debuggerPausedDetails) + this.dispatchEventToListeners(DebuggerModel.Events.DebuggerPaused, this._debuggerPausedDetails); + if (debuggerPausedDetails) { + this.setSelectedCallFrame(debuggerPausedDetails.callFrames[0]); + //DebuggerAgent.setOverlayMessage(UIString("Paused in debugger")); + } else { + this.setSelectedCallFrame(null); + //DebuggerAgent.setOverlayMessage(); + } + }, + + /** + * @param {DebuggerAgent.ScriptId} scriptId + * @param {string} sourceURL + * @param {number} startLine + * @param {number} startColumn + * @param {number} endLine + * @param {number} endColumn + * @param {boolean} isContentScript + * @param {string=} sourceMapURL + * @param {boolean=} hasSourceURL + */ + _parsedScriptSource: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL) + { + var script = new Script(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL, hasSourceURL); + this._registerScript(script); + this.dispatchEventToListeners(DebuggerModel.Events.ParsedScriptSource, script); + }, + + /** + * all source load when startup + */ + _parseScriptSources: function(scriptResources){ + for(var i = 0, l = scriptResources.length; i < l; i++) { + var fileResourceItem = scriptResources[i]; + + var script = new Script(fileResourceItem.id, fileResourceItem.url, 0,0,0,0, true); + script.setSource(fileResourceItem.content); + this._registerScript(script); + this.dispatchEventToListeners(DebuggerModel.Events.ParsedScriptSource, script); + } + }, + + /** + * @param {Script} script + */ + _registerScript: function(script) + { + this._scripts[script.scriptId] = script; + if (script.isAnonymousScript()) + return; + + var scripts = this._scriptsBySourceURL[script.sourceURL]; + if (!scripts) { + scripts = []; + this._scriptsBySourceURL[script.sourceURL] = scripts; + } + scripts.push(script); + }, + + /** + * @param {Script} script + * @param {number} lineNumber + * @param {number} columnNumber + * @return {DebuggerModel.Location} + */ + createRawLocation: function(script, lineNumber, columnNumber) + { + if (script.sourceURL) + return this.createRawLocationByURL(script.sourceURL, lineNumber, columnNumber) + return new Location(script.scriptId, lineNumber, columnNumber); + }, + + /** + * @param {string} sourceURL + * @param {number} lineNumber + * @param {number} columnNumber + * @return {DebuggerModel.Location} + */ + createRawLocationByURL: function(sourceURL, lineNumber, columnNumber) + { + var closestScript = null; + var scripts = this._scriptsBySourceURL[sourceURL] || []; + for (var i = 0, l = scripts.length; i < l; ++i) { + var script = scripts[i]; + if (!closestScript) + closestScript = script; + if (script.lineOffset > lineNumber || (script.lineOffset === lineNumber && script.columnOffset > columnNumber)) + continue; + if (script.endLine < lineNumber || (script.endLine === lineNumber && script.endColumn <= columnNumber)) + continue; + closestScript = script; + break; + } + return closestScript ? new Location(closestScript.scriptId, lineNumber, columnNumber) : null; + }, + + /** + * @return {boolean} + */ + isPaused: function() + { + return !!this.debuggerPausedDetails(); + }, + + /** + * @param {?CallFrame} callFrame + */ + setSelectedCallFrame: function(callFrame) + { + this._selectedCallFrame = callFrame; + if (!this._selectedCallFrame) + return; + + this.dispatchEventToListeners(DebuggerModel.Events.CallFrameSelected, callFrame); + }, + + /** + * @return {?CallFrame} + */ + selectedCallFrame: function() + { + return this._selectedCallFrame; + }, + + /** + * @param {string} code + * @param {string} objectGroup + * @param {boolean} includeCommandLineAPI + * @param {boolean} doNotPauseOnExceptionsAndMuteConsole + * @param {boolean} returnByValue + * @param {boolean} generatePreview + * @param {function(?RemoteObject, boolean, RemoteObject=)} callback + */ + evaluateOnSelectedCallFrame: function(code, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, callback) + { + /** + * @param {?RemoteObject} result + * @param {boolean=} wasThrown + */ + function didEvaluate(result, wasThrown) + { + if (returnByValue) + callback(null, !!wasThrown, wasThrown ? null : result); + else + callback(RemoteObject.fromPayload(result), !!wasThrown); + + if (objectGroup === "console") + this.dispatchEventToListeners(DebuggerModel.Events.ConsoleCommandEvaluatedInSelectedCallFrame); + } + + this.selectedCallFrame().evaluate(code, objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, didEvaluate.bind(this)); + }, + + /** + * @param {function(Object)} callback + */ + getSelectedCallFrameVariables: function(callback) + { + var result = { this: true }; + + var selectedCallFrame = this._selectedCallFrame; + if (!selectedCallFrame) + callback(result); + + var pendingRequests = 0; + + function propertiesCollected(properties) + { + for (var i = 0; properties && i < properties.length; ++i) + result[properties[i].name] = true; + if (--pendingRequests == 0) + callback(result); + } + + for (var i = 0; i < selectedCallFrame.scopeChain.length; ++i) { + var scope = selectedCallFrame.scopeChain[i]; + var object = RemoteObject.fromPayload(scope.object); + pendingRequests++; + object.getAllProperties(false, propertiesCollected); + } + }, + + /** + * @param {boolean} active + */ + setBreakpointsActive: function(active) + { + if (this._breakpointsActive === active) + return; + this._breakpointsActive = active; + CSInspector.debugAgent.setBreakpointsActive(active); + this.dispatchEventToListeners(DebuggerModel.Events.BreakpointsActiveStateChanged, active); + }, + + /** + * @return {boolean} + */ + breakpointsActive: function() + { + return this._breakpointsActive; + }, + + /** + * @param {DebuggerModel.Location} rawLocation + * @param {function(UILocation):(boolean|undefined)} updateDelegate + * @return {Script.Location} + */ + createLiveLocation: function(rawLocation, updateDelegate) + { + var script = this._scripts[rawLocation.scriptId]; + return script.createLiveLocation(rawLocation, updateDelegate); + }, + + __proto__: EventEmitter.prototype + }; + + DebuggerModel.prototype._connectErrorHandler = function(){ + this.emit(DebuggerModel.Events.DebugFinished, true); console.error("remote debugger closed unexpectedly."); }; - DebugModel.EventTypes = { - SessionInit: "SessionInit", - RenderSnippet: "RenderSnippet", - DebuggerPaused: "DebuggerPaused", - DebugFinished: "DebugFinished", + DebuggerModel.Events = { + SessionInit: "sessionInit", + RenderSnippet: "renderSnippet", + DebuggerPaused: "debuggerPaused", + DebugFinished: "debugFinished", + ParsedScriptSource: "ParsedScriptSource", + CallFrameSelected: "CallFrameSelected", /* DebuggerWasEnabled: "DebuggerWasEnabled", DebuggerWasDisabled: "DebuggerWasDisabled", DebuggerResumed: "DebuggerResumed", - ParsedScriptSource: "ParsedScriptSource", + */ + /* FailedToParseScriptSource: "FailedToParseScriptSource", BreakpointResolved: "BreakpointResolved", - CallFrameSelected: "CallFrameSelected", ConsoleCommandEvaluatedInSelectedCallFrame: "ConsoleCommandEvaluatedInSelectedCallFrame", - BreakpointsActiveStateChanged: "BreakpointsActiveStateChanged" */ + BreakpointsActiveStateChanged: "BreakpointsActiveStateChanged" }; function Dispatcher(model){ @@ -41,21 +557,21 @@ define(function(require){ } Dispatcher.prototype = { - "SessionInit": function(sources, resumeEvaluate){ - this._model.emit(DebugModel.EventTypes.SessionInit, sources); - DebugAgent.sessionReady(resumeEvaluate); + "SessionInit": function(resources, resumeEvaluate){ + this._model._parseScriptSources(resources); + CSInspector.debugAgent.sessionReady(resumeEvaluate); }, - "DebugPaused": function(evaluateLine, scopeChain, watchExpressions){ - this._model.emit(DebugModel.EventTypes.DebuggerPaused, evaluateLine, scopeChain, watchExpressions); + "DebugPaused": function(callFrames, watchExpressions, breakpointIds){ + this._model._pausedScript(callFrames, 0, null, breakpointIds || []) }, "DebugFinished": function(exit){ - this._model.emit(DebugModel.EventTypes.DebugFinished, exit); + this._model.emit(DebuggerModel.Events.DebugFinished, exit); } }; - Dispatcher.prototype.SessionInit.parameters = ["sources", "resumeEvaluate"]; - Dispatcher.prototype.DebugPaused.parameters = ["executeLine", "scopeChain", "watchExpressions"]; + Dispatcher.prototype.SessionInit.parameters = ["resources", "resumeEvaluate"]; + Dispatcher.prototype.DebugPaused.parameters = ["callFrames", "watchExpressions"]; Dispatcher.prototype.DebugFinished.parameters = ["exitOnFinished"]; - return DebugModel; + return DebuggerModel; }); diff --git a/interpreter/debugger/client/DebugToolbar.js b/interpreter/debugger/client/DebugToolbar.js index 296f479..5ed7561 100644 --- a/interpreter/debugger/client/DebugToolbar.js +++ b/interpreter/debugger/client/DebugToolbar.js @@ -14,15 +14,15 @@ define(function (require, exports){ } DebugToolbar.prototype.resume = function(){ - if (!this._disabled) DebugAgent.resume(); + if (!this._disabled) DebuggerAgent.resume(); }; DebugToolbar.prototype.stepOver = function(){ - if (!this._disabled) DebugAgent.stepOver(); + if (!this._disabled) DebuggerAgent.stepOver(); }; DebugToolbar.prototype.stepInto = function(){ - if (!this._disabled) DebugAgent.stepInto(); + if (!this._disabled) DebuggerAgent.stepInto(); }; DebugToolbar.prototype.disable = function(){ diff --git a/interpreter/debugger/client/DebuggerPausedDetails.js b/interpreter/debugger/client/DebuggerPausedDetails.js new file mode 100644 index 0000000..2c75e8b --- /dev/null +++ b/interpreter/debugger/client/DebuggerPausedDetails.js @@ -0,0 +1,37 @@ +define(function(require){ + + var CallFrame = require("CallFrame"); + /** + * @constructor + * @param {DebuggerModel} model + * @param {Array.} callFrames + * @param {string} reason + * @param {Object|undefined} auxData + * @param {Array.} breakpointIds + */ + function DebuggerPausedDetails(model, callFrames, reason, auxData, breakpointIds) + { + this.callFrames = []; + for (var i = 0; i < callFrames.length; ++i) { + var callFrame = callFrames[i]; + var script = model.scriptForId(callFrame.location.scriptId); + if (script) + this.callFrames.push(new CallFrame(script, callFrame)); + } + this.reason = reason; + this.auxData = auxData; + this.breakpointIds = breakpointIds; + } + + DebuggerPausedDetails.prototype = { + dispose: function() + { + for (var i = 0; i < this.callFrames.length; ++i) { + var callFrame = this.callFrames[i]; + callFrame.dispose(); + } + } + } + + return DebuggerPausedDetails; +}); diff --git a/interpreter/debugger/client/FileDescriptor.js b/interpreter/debugger/client/FileDescriptor.js new file mode 100644 index 0000000..30a02c5 --- /dev/null +++ b/interpreter/debugger/client/FileDescriptor.js @@ -0,0 +1,14 @@ +define(function(){ + function FileDescriptor(parentPath, name, originURL, url, contentType, isEditable, isContentScript) + { + this.parentPath = parentPath; + this.name = name; + this.originURL = originURL; + this.url = url; + this.contentType = contentType; + this.isEditable = isEditable; + this.isContentScript = isContentScript || false; + } + + return FileDescriptor; +}); diff --git a/interpreter/debugger/client/Images/addIcon.png b/interpreter/debugger/client/Images/addIcon.png new file mode 100644 index 0000000..f48f0ab Binary files /dev/null and b/interpreter/debugger/client/Images/addIcon.png differ diff --git a/interpreter/debugger/client/Images/applicationCache.png b/interpreter/debugger/client/Images/applicationCache.png new file mode 100644 index 0000000..254fd5b Binary files /dev/null and b/interpreter/debugger/client/Images/applicationCache.png differ diff --git a/interpreter/debugger/client/Images/back.png b/interpreter/debugger/client/Images/back.png new file mode 100644 index 0000000..b1c0c19 Binary files /dev/null and b/interpreter/debugger/client/Images/back.png differ diff --git a/interpreter/debugger/client/Images/breakpoint2.png b/interpreter/debugger/client/Images/breakpoint2.png new file mode 100644 index 0000000..6d87745 Binary files /dev/null and b/interpreter/debugger/client/Images/breakpoint2.png differ diff --git a/interpreter/debugger/client/Images/breakpoint2_2x.png b/interpreter/debugger/client/Images/breakpoint2_2x.png new file mode 100644 index 0000000..f77370d Binary files /dev/null and b/interpreter/debugger/client/Images/breakpoint2_2x.png differ diff --git a/interpreter/debugger/client/Images/breakpointBorder.png b/interpreter/debugger/client/Images/breakpointBorder.png new file mode 100644 index 0000000..f15e02f Binary files /dev/null and b/interpreter/debugger/client/Images/breakpointBorder.png differ diff --git a/interpreter/debugger/client/Images/breakpointConditional2.png b/interpreter/debugger/client/Images/breakpointConditional2.png new file mode 100644 index 0000000..87bbc0e Binary files /dev/null and b/interpreter/debugger/client/Images/breakpointConditional2.png differ diff --git a/interpreter/debugger/client/Images/breakpointConditional2_2x.png b/interpreter/debugger/client/Images/breakpointConditional2_2x.png new file mode 100644 index 0000000..e2aa575 Binary files /dev/null and b/interpreter/debugger/client/Images/breakpointConditional2_2x.png differ diff --git a/interpreter/debugger/client/Images/breakpointConditionalBorder.png b/interpreter/debugger/client/Images/breakpointConditionalBorder.png new file mode 100644 index 0000000..4bd5806 Binary files /dev/null and b/interpreter/debugger/client/Images/breakpointConditionalBorder.png differ diff --git a/interpreter/debugger/client/Images/breakpointConditionalCounterBorder.png b/interpreter/debugger/client/Images/breakpointConditionalCounterBorder.png new file mode 100644 index 0000000..897b7a0 Binary files /dev/null and b/interpreter/debugger/client/Images/breakpointConditionalCounterBorder.png differ diff --git a/interpreter/debugger/client/Images/breakpointCounterBorder.png b/interpreter/debugger/client/Images/breakpointCounterBorder.png new file mode 100644 index 0000000..0b3ea14 Binary files /dev/null and b/interpreter/debugger/client/Images/breakpointCounterBorder.png differ diff --git a/interpreter/debugger/client/Images/checker.png b/interpreter/debugger/client/Images/checker.png new file mode 100644 index 0000000..816a4ec Binary files /dev/null and b/interpreter/debugger/client/Images/checker.png differ diff --git a/interpreter/debugger/client/Images/cookie.png b/interpreter/debugger/client/Images/cookie.png new file mode 100644 index 0000000..846db58 Binary files /dev/null and b/interpreter/debugger/client/Images/cookie.png differ diff --git a/interpreter/debugger/client/Images/database.png b/interpreter/debugger/client/Images/database.png new file mode 100644 index 0000000..39a8506 Binary files /dev/null and b/interpreter/debugger/client/Images/database.png differ diff --git a/interpreter/debugger/client/Images/databaseTable.png b/interpreter/debugger/client/Images/databaseTable.png new file mode 100644 index 0000000..5508174 Binary files /dev/null and b/interpreter/debugger/client/Images/databaseTable.png differ diff --git a/interpreter/debugger/client/Images/deleteIcon.png b/interpreter/debugger/client/Images/deleteIcon.png new file mode 100644 index 0000000..f0dd0db Binary files /dev/null and b/interpreter/debugger/client/Images/deleteIcon.png differ diff --git a/interpreter/debugger/client/Images/domain.png b/interpreter/debugger/client/Images/domain.png new file mode 100644 index 0000000..68c75f3 Binary files /dev/null and b/interpreter/debugger/client/Images/domain.png differ diff --git a/interpreter/debugger/client/Images/fileSystem.png b/interpreter/debugger/client/Images/fileSystem.png new file mode 100644 index 0000000..f801ce6 Binary files /dev/null and b/interpreter/debugger/client/Images/fileSystem.png differ diff --git a/interpreter/debugger/client/Images/forward.png b/interpreter/debugger/client/Images/forward.png new file mode 100644 index 0000000..843392b Binary files /dev/null and b/interpreter/debugger/client/Images/forward.png differ diff --git a/interpreter/debugger/client/Images/frame.png b/interpreter/debugger/client/Images/frame.png new file mode 100644 index 0000000..e17c829 Binary files /dev/null and b/interpreter/debugger/client/Images/frame.png differ diff --git a/interpreter/debugger/client/Images/glossyHeader.png b/interpreter/debugger/client/Images/glossyHeader.png new file mode 100644 index 0000000..6b77999 Binary files /dev/null and b/interpreter/debugger/client/Images/glossyHeader.png differ diff --git a/interpreter/debugger/client/Images/glossyHeaderPressed.png b/interpreter/debugger/client/Images/glossyHeaderPressed.png new file mode 100644 index 0000000..9a64b7c Binary files /dev/null and b/interpreter/debugger/client/Images/glossyHeaderPressed.png differ diff --git a/interpreter/debugger/client/Images/glossyHeaderSelected.png b/interpreter/debugger/client/Images/glossyHeaderSelected.png new file mode 100644 index 0000000..f7d615c Binary files /dev/null and b/interpreter/debugger/client/Images/glossyHeaderSelected.png differ diff --git a/interpreter/debugger/client/Images/glossyHeaderSelectedPressed.png b/interpreter/debugger/client/Images/glossyHeaderSelectedPressed.png new file mode 100644 index 0000000..75d37fb Binary files /dev/null and b/interpreter/debugger/client/Images/glossyHeaderSelectedPressed.png differ diff --git a/interpreter/debugger/client/Images/graphLabelCalloutLeft.png b/interpreter/debugger/client/Images/graphLabelCalloutLeft.png new file mode 100644 index 0000000..29fc042 Binary files /dev/null and b/interpreter/debugger/client/Images/graphLabelCalloutLeft.png differ diff --git a/interpreter/debugger/client/Images/graphLabelCalloutRight.png b/interpreter/debugger/client/Images/graphLabelCalloutRight.png new file mode 100644 index 0000000..6c56a2b Binary files /dev/null and b/interpreter/debugger/client/Images/graphLabelCalloutRight.png differ diff --git a/interpreter/debugger/client/Images/indexedDB.png b/interpreter/debugger/client/Images/indexedDB.png new file mode 100644 index 0000000..d9b3c02 Binary files /dev/null and b/interpreter/debugger/client/Images/indexedDB.png differ diff --git a/interpreter/debugger/client/Images/indexedDBIndex.png b/interpreter/debugger/client/Images/indexedDBIndex.png new file mode 100644 index 0000000..b58959c Binary files /dev/null and b/interpreter/debugger/client/Images/indexedDBIndex.png differ diff --git a/interpreter/debugger/client/Images/indexedDBObjectStore.png b/interpreter/debugger/client/Images/indexedDBObjectStore.png new file mode 100644 index 0000000..ce2396b Binary files /dev/null and b/interpreter/debugger/client/Images/indexedDBObjectStore.png differ diff --git a/interpreter/debugger/client/Images/localStorage.png b/interpreter/debugger/client/Images/localStorage.png new file mode 100644 index 0000000..003ac5d Binary files /dev/null and b/interpreter/debugger/client/Images/localStorage.png differ diff --git a/interpreter/debugger/client/Images/namedFlowOverflow.png b/interpreter/debugger/client/Images/namedFlowOverflow.png new file mode 100644 index 0000000..f966b1a Binary files /dev/null and b/interpreter/debugger/client/Images/namedFlowOverflow.png differ diff --git a/interpreter/debugger/client/Images/paneAddButtons.png b/interpreter/debugger/client/Images/paneAddButtons.png new file mode 100644 index 0000000..ff25b0f Binary files /dev/null and b/interpreter/debugger/client/Images/paneAddButtons.png differ diff --git a/interpreter/debugger/client/Images/paneElementStateButtons.png b/interpreter/debugger/client/Images/paneElementStateButtons.png new file mode 100644 index 0000000..af8dac0 Binary files /dev/null and b/interpreter/debugger/client/Images/paneElementStateButtons.png differ diff --git a/interpreter/debugger/client/Images/paneFilterButtons.png b/interpreter/debugger/client/Images/paneFilterButtons.png new file mode 100644 index 0000000..6c52820 Binary files /dev/null and b/interpreter/debugger/client/Images/paneFilterButtons.png differ diff --git a/interpreter/debugger/client/Images/paneRefreshButtons.png b/interpreter/debugger/client/Images/paneRefreshButtons.png new file mode 100644 index 0000000..92ff209 Binary files /dev/null and b/interpreter/debugger/client/Images/paneRefreshButtons.png differ diff --git a/interpreter/debugger/client/Images/paneSettingsButtons.png b/interpreter/debugger/client/Images/paneSettingsButtons.png new file mode 100644 index 0000000..cb4f090 Binary files /dev/null and b/interpreter/debugger/client/Images/paneSettingsButtons.png differ diff --git a/interpreter/debugger/client/Images/popoverArrows.png b/interpreter/debugger/client/Images/popoverArrows.png new file mode 100644 index 0000000..8b98caf Binary files /dev/null and b/interpreter/debugger/client/Images/popoverArrows.png differ diff --git a/interpreter/debugger/client/Images/popoverBackground.png b/interpreter/debugger/client/Images/popoverBackground.png new file mode 100644 index 0000000..e9ba86e Binary files /dev/null and b/interpreter/debugger/client/Images/popoverBackground.png differ diff --git a/interpreter/debugger/client/Images/profileGroupIcon.png b/interpreter/debugger/client/Images/profileGroupIcon.png new file mode 100644 index 0000000..ff78cb4 Binary files /dev/null and b/interpreter/debugger/client/Images/profileGroupIcon.png differ diff --git a/interpreter/debugger/client/Images/profileIcon.png b/interpreter/debugger/client/Images/profileIcon.png new file mode 100644 index 0000000..c0c4600 Binary files /dev/null and b/interpreter/debugger/client/Images/profileIcon.png differ diff --git a/interpreter/debugger/client/Images/profileSmallIcon.png b/interpreter/debugger/client/Images/profileSmallIcon.png new file mode 100644 index 0000000..e5c4ad5 Binary files /dev/null and b/interpreter/debugger/client/Images/profileSmallIcon.png differ diff --git a/interpreter/debugger/client/Images/programCounterBorder.png b/interpreter/debugger/client/Images/programCounterBorder.png new file mode 100644 index 0000000..10b0250 Binary files /dev/null and b/interpreter/debugger/client/Images/programCounterBorder.png differ diff --git a/interpreter/debugger/client/Images/radioDot.png b/interpreter/debugger/client/Images/radioDot.png new file mode 100644 index 0000000..5d50890 Binary files /dev/null and b/interpreter/debugger/client/Images/radioDot.png differ diff --git a/interpreter/debugger/client/Images/regionEmpty.png b/interpreter/debugger/client/Images/regionEmpty.png new file mode 100644 index 0000000..ed64c22 Binary files /dev/null and b/interpreter/debugger/client/Images/regionEmpty.png differ diff --git a/interpreter/debugger/client/Images/regionFit.png b/interpreter/debugger/client/Images/regionFit.png new file mode 100644 index 0000000..90d4d50 Binary files /dev/null and b/interpreter/debugger/client/Images/regionFit.png differ diff --git a/interpreter/debugger/client/Images/regionOverset.png b/interpreter/debugger/client/Images/regionOverset.png new file mode 100644 index 0000000..0738f1b Binary files /dev/null and b/interpreter/debugger/client/Images/regionOverset.png differ diff --git a/interpreter/debugger/client/Images/resourceCSSIcon.png b/interpreter/debugger/client/Images/resourceCSSIcon.png new file mode 100644 index 0000000..18828d0 Binary files /dev/null and b/interpreter/debugger/client/Images/resourceCSSIcon.png differ diff --git a/interpreter/debugger/client/Images/resourceDocumentIcon.png b/interpreter/debugger/client/Images/resourceDocumentIcon.png new file mode 100644 index 0000000..fdc10e4 Binary files /dev/null and b/interpreter/debugger/client/Images/resourceDocumentIcon.png differ diff --git a/interpreter/debugger/client/Images/resourceDocumentIconSmall.png b/interpreter/debugger/client/Images/resourceDocumentIconSmall.png new file mode 100644 index 0000000..64d9735 Binary files /dev/null and b/interpreter/debugger/client/Images/resourceDocumentIconSmall.png differ diff --git a/interpreter/debugger/client/Images/resourceJSIcon.png b/interpreter/debugger/client/Images/resourceJSIcon.png new file mode 100644 index 0000000..c1b7218 Binary files /dev/null and b/interpreter/debugger/client/Images/resourceJSIcon.png differ diff --git a/interpreter/debugger/client/Images/resourcePlainIcon.png b/interpreter/debugger/client/Images/resourcePlainIcon.png new file mode 100644 index 0000000..8c82a4c Binary files /dev/null and b/interpreter/debugger/client/Images/resourcePlainIcon.png differ diff --git a/interpreter/debugger/client/Images/resourcePlainIconSmall.png b/interpreter/debugger/client/Images/resourcePlainIconSmall.png new file mode 100644 index 0000000..0349c0d Binary files /dev/null and b/interpreter/debugger/client/Images/resourcePlainIconSmall.png differ diff --git a/interpreter/debugger/client/Images/resourcesTimeGraphIcon.png b/interpreter/debugger/client/Images/resourcesTimeGraphIcon.png new file mode 100644 index 0000000..87de550 Binary files /dev/null and b/interpreter/debugger/client/Images/resourcesTimeGraphIcon.png differ diff --git a/interpreter/debugger/client/Images/searchNext.png b/interpreter/debugger/client/Images/searchNext.png new file mode 100644 index 0000000..69e519e Binary files /dev/null and b/interpreter/debugger/client/Images/searchNext.png differ diff --git a/interpreter/debugger/client/Images/searchPrev.png b/interpreter/debugger/client/Images/searchPrev.png new file mode 100644 index 0000000..733d40b Binary files /dev/null and b/interpreter/debugger/client/Images/searchPrev.png differ diff --git a/interpreter/debugger/client/Images/searchSmallBlue.png b/interpreter/debugger/client/Images/searchSmallBlue.png new file mode 100644 index 0000000..08350f7 Binary files /dev/null and b/interpreter/debugger/client/Images/searchSmallBlue.png differ diff --git a/interpreter/debugger/client/Images/searchSmallBrightBlue.png b/interpreter/debugger/client/Images/searchSmallBrightBlue.png new file mode 100644 index 0000000..09813af Binary files /dev/null and b/interpreter/debugger/client/Images/searchSmallBrightBlue.png differ diff --git a/interpreter/debugger/client/Images/searchSmallGray.png b/interpreter/debugger/client/Images/searchSmallGray.png new file mode 100644 index 0000000..a26aa6f Binary files /dev/null and b/interpreter/debugger/client/Images/searchSmallGray.png differ diff --git a/interpreter/debugger/client/Images/searchSmallWhite.png b/interpreter/debugger/client/Images/searchSmallWhite.png new file mode 100644 index 0000000..a42bf1e Binary files /dev/null and b/interpreter/debugger/client/Images/searchSmallWhite.png differ diff --git a/interpreter/debugger/client/Images/segment.png b/interpreter/debugger/client/Images/segment.png new file mode 100644 index 0000000..348c18c Binary files /dev/null and b/interpreter/debugger/client/Images/segment.png differ diff --git a/interpreter/debugger/client/Images/segmentEnd.png b/interpreter/debugger/client/Images/segmentEnd.png new file mode 100644 index 0000000..89b155d Binary files /dev/null and b/interpreter/debugger/client/Images/segmentEnd.png differ diff --git a/interpreter/debugger/client/Images/segmentHover.png b/interpreter/debugger/client/Images/segmentHover.png new file mode 100644 index 0000000..b41a21c Binary files /dev/null and b/interpreter/debugger/client/Images/segmentHover.png differ diff --git a/interpreter/debugger/client/Images/segmentHoverEnd.png b/interpreter/debugger/client/Images/segmentHoverEnd.png new file mode 100644 index 0000000..4a7d679 Binary files /dev/null and b/interpreter/debugger/client/Images/segmentHoverEnd.png differ diff --git a/interpreter/debugger/client/Images/segmentSelected.png b/interpreter/debugger/client/Images/segmentSelected.png new file mode 100644 index 0000000..8de6058 Binary files /dev/null and b/interpreter/debugger/client/Images/segmentSelected.png differ diff --git a/interpreter/debugger/client/Images/segmentSelectedEnd.png b/interpreter/debugger/client/Images/segmentSelectedEnd.png new file mode 100644 index 0000000..2e01b4e Binary files /dev/null and b/interpreter/debugger/client/Images/segmentSelectedEnd.png differ diff --git a/interpreter/debugger/client/Images/sessionStorage.png b/interpreter/debugger/client/Images/sessionStorage.png new file mode 100644 index 0000000..732536f Binary files /dev/null and b/interpreter/debugger/client/Images/sessionStorage.png differ diff --git a/interpreter/debugger/client/Images/settingsListRemove.png b/interpreter/debugger/client/Images/settingsListRemove.png new file mode 100644 index 0000000..39f26bc Binary files /dev/null and b/interpreter/debugger/client/Images/settingsListRemove.png differ diff --git a/interpreter/debugger/client/Images/settingsListRemove_2x.png b/interpreter/debugger/client/Images/settingsListRemove_2x.png new file mode 100644 index 0000000..af0b56c Binary files /dev/null and b/interpreter/debugger/client/Images/settingsListRemove_2x.png differ diff --git a/interpreter/debugger/client/Images/spinner.gif b/interpreter/debugger/client/Images/spinner.gif new file mode 100644 index 0000000..5f68c02 Binary files /dev/null and b/interpreter/debugger/client/Images/spinner.gif differ diff --git a/interpreter/debugger/client/Images/spinnerActive.gif b/interpreter/debugger/client/Images/spinnerActive.gif new file mode 100644 index 0000000..b75745c Binary files /dev/null and b/interpreter/debugger/client/Images/spinnerActive.gif differ diff --git a/interpreter/debugger/client/Images/spinnerActiveSelected.gif b/interpreter/debugger/client/Images/spinnerActiveSelected.gif new file mode 100644 index 0000000..1ffb18b Binary files /dev/null and b/interpreter/debugger/client/Images/spinnerActiveSelected.gif differ diff --git a/interpreter/debugger/client/Images/spinnerInactive.gif b/interpreter/debugger/client/Images/spinnerInactive.gif new file mode 100644 index 0000000..309cca0 Binary files /dev/null and b/interpreter/debugger/client/Images/spinnerInactive.gif differ diff --git a/interpreter/debugger/client/Images/spinnerInactiveSelected.gif b/interpreter/debugger/client/Images/spinnerInactiveSelected.gif new file mode 100644 index 0000000..40bc274 Binary files /dev/null and b/interpreter/debugger/client/Images/spinnerInactiveSelected.gif differ diff --git a/interpreter/debugger/client/Images/src/breakpoints2.svg b/interpreter/debugger/client/Images/src/breakpoints2.svg new file mode 100644 index 0000000..0ae7d3d --- /dev/null +++ b/interpreter/debugger/client/Images/src/breakpoints2.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/interpreter/debugger/client/Images/src/settingListRemove.svg b/interpreter/debugger/client/Images/src/settingListRemove.svg new file mode 100644 index 0000000..c279b00 --- /dev/null +++ b/interpreter/debugger/client/Images/src/settingListRemove.svg @@ -0,0 +1,195 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interpreter/debugger/client/Images/src/statusbarButtonGlyphs.svg b/interpreter/debugger/client/Images/src/statusbarButtonGlyphs.svg new file mode 100644 index 0000000..f6fc9bf --- /dev/null +++ b/interpreter/debugger/client/Images/src/statusbarButtonGlyphs.svg @@ -0,0 +1,1745 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + + + + + \ No newline at end of file diff --git a/interpreter/debugger/client/Images/statusbarButtonGlyphs.png b/interpreter/debugger/client/Images/statusbarButtonGlyphs.png new file mode 100644 index 0000000..3249067 Binary files /dev/null and b/interpreter/debugger/client/Images/statusbarButtonGlyphs.png differ diff --git a/interpreter/debugger/client/Images/statusbarButtonGlyphs2x.png b/interpreter/debugger/client/Images/statusbarButtonGlyphs2x.png new file mode 100644 index 0000000..673fac2 Binary files /dev/null and b/interpreter/debugger/client/Images/statusbarButtonGlyphs2x.png differ diff --git a/interpreter/debugger/client/Images/statusbarResizerHorizontal.png b/interpreter/debugger/client/Images/statusbarResizerHorizontal.png new file mode 100644 index 0000000..674b895 Binary files /dev/null and b/interpreter/debugger/client/Images/statusbarResizerHorizontal.png differ diff --git a/interpreter/debugger/client/Images/statusbarResizerVertical.png b/interpreter/debugger/client/Images/statusbarResizerVertical.png new file mode 100644 index 0000000..bf84d1e Binary files /dev/null and b/interpreter/debugger/client/Images/statusbarResizerVertical.png differ diff --git a/interpreter/debugger/client/Images/thumbActiveHoriz.png b/interpreter/debugger/client/Images/thumbActiveHoriz.png new file mode 100644 index 0000000..492c53f Binary files /dev/null and b/interpreter/debugger/client/Images/thumbActiveHoriz.png differ diff --git a/interpreter/debugger/client/Images/thumbActiveVert.png b/interpreter/debugger/client/Images/thumbActiveVert.png new file mode 100644 index 0000000..96d0745 Binary files /dev/null and b/interpreter/debugger/client/Images/thumbActiveVert.png differ diff --git a/interpreter/debugger/client/Images/thumbHoriz.png b/interpreter/debugger/client/Images/thumbHoriz.png new file mode 100644 index 0000000..caed321 Binary files /dev/null and b/interpreter/debugger/client/Images/thumbHoriz.png differ diff --git a/interpreter/debugger/client/Images/thumbHoverHoriz.png b/interpreter/debugger/client/Images/thumbHoverHoriz.png new file mode 100644 index 0000000..6942e23 Binary files /dev/null and b/interpreter/debugger/client/Images/thumbHoverHoriz.png differ diff --git a/interpreter/debugger/client/Images/thumbHoverVert.png b/interpreter/debugger/client/Images/thumbHoverVert.png new file mode 100644 index 0000000..125bf79 Binary files /dev/null and b/interpreter/debugger/client/Images/thumbHoverVert.png differ diff --git a/interpreter/debugger/client/Images/thumbVert.png b/interpreter/debugger/client/Images/thumbVert.png new file mode 100644 index 0000000..b90e94a Binary files /dev/null and b/interpreter/debugger/client/Images/thumbVert.png differ diff --git a/interpreter/debugger/client/Images/timelineHollowPillBlue.png b/interpreter/debugger/client/Images/timelineHollowPillBlue.png new file mode 100644 index 0000000..ed68e45 Binary files /dev/null and b/interpreter/debugger/client/Images/timelineHollowPillBlue.png differ diff --git a/interpreter/debugger/client/Images/timelineHollowPillGray.png b/interpreter/debugger/client/Images/timelineHollowPillGray.png new file mode 100644 index 0000000..12a6662 Binary files /dev/null and b/interpreter/debugger/client/Images/timelineHollowPillGray.png differ diff --git a/interpreter/debugger/client/Images/timelineHollowPillGreen.png b/interpreter/debugger/client/Images/timelineHollowPillGreen.png new file mode 100644 index 0000000..7b31b9e Binary files /dev/null and b/interpreter/debugger/client/Images/timelineHollowPillGreen.png differ diff --git a/interpreter/debugger/client/Images/timelineHollowPillOrange.png b/interpreter/debugger/client/Images/timelineHollowPillOrange.png new file mode 100644 index 0000000..b76928e Binary files /dev/null and b/interpreter/debugger/client/Images/timelineHollowPillOrange.png differ diff --git a/interpreter/debugger/client/Images/timelineHollowPillPurple.png b/interpreter/debugger/client/Images/timelineHollowPillPurple.png new file mode 100644 index 0000000..ce56400 Binary files /dev/null and b/interpreter/debugger/client/Images/timelineHollowPillPurple.png differ diff --git a/interpreter/debugger/client/Images/timelineHollowPillRed.png b/interpreter/debugger/client/Images/timelineHollowPillRed.png new file mode 100644 index 0000000..5cc45b8 Binary files /dev/null and b/interpreter/debugger/client/Images/timelineHollowPillRed.png differ diff --git a/interpreter/debugger/client/Images/timelineHollowPillYellow.png b/interpreter/debugger/client/Images/timelineHollowPillYellow.png new file mode 100644 index 0000000..b62f774 Binary files /dev/null and b/interpreter/debugger/client/Images/timelineHollowPillYellow.png differ diff --git a/interpreter/debugger/client/Images/timelinePillBlue.png b/interpreter/debugger/client/Images/timelinePillBlue.png new file mode 100644 index 0000000..ad4821d Binary files /dev/null and b/interpreter/debugger/client/Images/timelinePillBlue.png differ diff --git a/interpreter/debugger/client/Images/timelinePillGray.png b/interpreter/debugger/client/Images/timelinePillGray.png new file mode 100644 index 0000000..4b4d182 Binary files /dev/null and b/interpreter/debugger/client/Images/timelinePillGray.png differ diff --git a/interpreter/debugger/client/Images/timelinePillGreen.png b/interpreter/debugger/client/Images/timelinePillGreen.png new file mode 100644 index 0000000..e6a62a8 Binary files /dev/null and b/interpreter/debugger/client/Images/timelinePillGreen.png differ diff --git a/interpreter/debugger/client/Images/timelinePillOrange.png b/interpreter/debugger/client/Images/timelinePillOrange.png new file mode 100644 index 0000000..76c9f56 Binary files /dev/null and b/interpreter/debugger/client/Images/timelinePillOrange.png differ diff --git a/interpreter/debugger/client/Images/timelinePillPurple.png b/interpreter/debugger/client/Images/timelinePillPurple.png new file mode 100644 index 0000000..b82cbd8 Binary files /dev/null and b/interpreter/debugger/client/Images/timelinePillPurple.png differ diff --git a/interpreter/debugger/client/Images/timelinePillRed.png b/interpreter/debugger/client/Images/timelinePillRed.png new file mode 100644 index 0000000..d8359fd Binary files /dev/null and b/interpreter/debugger/client/Images/timelinePillRed.png differ diff --git a/interpreter/debugger/client/Images/timelinePillYellow.png b/interpreter/debugger/client/Images/timelinePillYellow.png new file mode 100644 index 0000000..a5e0d8f Binary files /dev/null and b/interpreter/debugger/client/Images/timelinePillYellow.png differ diff --git a/interpreter/debugger/client/Images/toolbarItemSelected.png b/interpreter/debugger/client/Images/toolbarItemSelected.png new file mode 100644 index 0000000..505daf2 Binary files /dev/null and b/interpreter/debugger/client/Images/toolbarItemSelected.png differ diff --git a/interpreter/debugger/client/Images/trackHoriz.png b/interpreter/debugger/client/Images/trackHoriz.png new file mode 100644 index 0000000..d5356d3 Binary files /dev/null and b/interpreter/debugger/client/Images/trackHoriz.png differ diff --git a/interpreter/debugger/client/Images/trackVert.png b/interpreter/debugger/client/Images/trackVert.png new file mode 100644 index 0000000..e9eddfb Binary files /dev/null and b/interpreter/debugger/client/Images/trackVert.png differ diff --git a/interpreter/debugger/client/InspectorView.js b/interpreter/debugger/client/InspectorView.js new file mode 100644 index 0000000..512cdaf --- /dev/null +++ b/interpreter/debugger/client/InspectorView.js @@ -0,0 +1,79 @@ +define(function(require, exports){ + var View = require("View"); + + function InspectorView(){ + View.call(this); + this.markAsRoot(); + + this._panelsElement = this.element.createChild("div", "fill"); + + this._panels = {}; + this._panelOrder = []; + + this._currentPanel = null; + + this._footerElementContainer = this.element.createChild("div", "inspector-footer status-bar hidden"); + } + + InspectorView.prototype = { + + /** + * @param {Panel} x + */ + setCurrentPanel: function(x) + { + if (this._currentPanel === x) + return; + + // FIXME: remove search controller. + //WebInspector.searchController.cancelSearch(); + + if (this._currentPanel) + this._currentPanel.detach(); + + this._currentPanel = x; + + if (x) { + x.show(); + this.dispatchEventToListeners(InspectorView.Events.PanelSelected); + } + for (var panelName in CSInspector.panels) { + if (CSInspector.panels[panelName] === x) { + CSInspector.settings.lastActivePanel.set(panelName); + this._pushToHistory(panelName); + //x没什么用 CSInspector.userMetrics.panelShown(panelName); + } + } + }, + + __proto__:View.prototype + }; + + InspectorView.Events = { + PanelSelected: "PanelSelected" + } + + InspectorView.prototype.addPanel = function(panel) { + this._panelOrder.push(panel.name); + this._panels[panel.name] = panel; + } + + InspectorView.prototype.showPanel = function(panelName){ + var panel = this._panels[panelName]; + if (panel) { + this._currentPanel = panel; + panel.show(); + this.dispatchEventToListeners(InspectorView.Events.PanelSelected); + } + } + + InspectorView.prototype.getCurrentPanel = function (argument) { + return this._currentPanel; + } + + InspectorView.prototype.panelsElement = function(){ + return this._panelsElement; + } + + return InspectorView; +}); diff --git a/interpreter/debugger/client/KeyboardShortcut.js b/interpreter/debugger/client/KeyboardShortcut.js new file mode 100644 index 0000000..976a47b --- /dev/null +++ b/interpreter/debugger/client/KeyboardShortcut.js @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 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: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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. + */ + +define(function (require) { + +var UIUtils = require("UIUtils"); + +/** + * @constructor + */ +function KeyboardShortcut() +{ +} + +/** + * Constants for encoding modifier key set as a bit mask. + * @see #_makeKeyFromCodeAndModifiers + */ +KeyboardShortcut.Modifiers = { + None: 0, // Constant for empty modifiers set. + Shift: 1, + Ctrl: 2, + Alt: 4, + Meta: 8, // Command key on Mac, Win key on other platforms. + get CtrlOrMeta() + { + // "default" command/ctrl key for platform, Command on Mac, Ctrl on other platforms + return UIUtils.isMac() ? this.Meta : this.Ctrl; + } +}; + +/** @typedef {{code: number, name: (string|Object.)}} */ +KeyboardShortcut.Key; + +/** @type {!Object.} */ +KeyboardShortcut.Keys = { + Backspace: { code: 8, name: "\u21a4" }, + Tab: { code: 9, name: { mac: "\u21e5", other: "Tab" } }, + Enter: { code: 13, name: { mac: "\u21a9", other: "Enter" } }, + Esc: { code: 27, name: { mac: "\u238b", other: "Esc" } }, + Space: { code: 32, name: "Space" }, + PageUp: { code: 33, name: { mac: "\u21de", other: "PageUp" } }, // also NUM_NORTH_EAST + PageDown: { code: 34, name: { mac: "\u21df", other: "PageDown" } }, // also NUM_SOUTH_EAST + End: { code: 35, name: { mac: "\u2197", other: "End" } }, // also NUM_SOUTH_WEST + Home: { code: 36, name: { mac: "\u2196", other: "Home" } }, // also NUM_NORTH_WEST + Left: { code: 37, name: "\u2190" }, // also NUM_WEST + Up: { code: 38, name: "\u2191" }, // also NUM_NORTH + Right: { code: 39, name: "\u2192" }, // also NUM_EAST + Down: { code: 40, name: "\u2193" }, // also NUM_SOUTH + Delete: { code: 46, name: "Del" }, + Zero: { code: 48, name: "0" }, + F1: { code: 112, name: "F1" }, + F2: { code: 113, name: "F2" }, + F3: { code: 114, name: "F3" }, + F4: { code: 115, name: "F4" }, + F5: { code: 116, name: "F5" }, + F6: { code: 117, name: "F6" }, + F7: { code: 118, name: "F7" }, + F8: { code: 119, name: "F8" }, + F9: { code: 120, name: "F9" }, + F10: { code: 121, name: "F10" }, + F11: { code: 122, name: "F11" }, + F12: { code: 123, name: "F12" }, + Semicolon: { code: 186, name: ";" }, + Plus: { code: 187, name: "+" }, + Comma: { code: 188, name: "," }, + Minus: { code: 189, name: "-" }, + Period: { code: 190, name: "." }, + Slash: { code: 191, name: "/" }, + Apostrophe: { code: 192, name: "`" }, + Backslash: { code: 220, name: "\\" }, + SingleQuote: { code: 222, name: "\'" }, + H: { code: 72, name: "H" }, + Ctrl: { code: 17, name: "Ctrl" }, + Meta: { code: 91, name: "Meta" }, + Tilde: { code: 192, name: "Tilde" }, + get CtrlOrMeta() + { + // "default" command/ctrl key for platform, Command on Mac, Ctrl on other platforms + return UIUtils.isMac() ? this.Meta : this.Ctrl; + }, +}; + +/** + * Creates a number encoding keyCode in the lower 8 bits and modifiers mask in the higher 8 bits. + * It is useful for matching pressed keys. + * + * @param {number|string} keyCode The Code of the key, or a character "a-z" which is converted to a keyCode value. + * @param {number=} modifiers Optional list of modifiers passed as additional paramerters. + * @return {number} + */ +KeyboardShortcut.makeKey = function(keyCode, modifiers) +{ + if (typeof keyCode === "string") + keyCode = keyCode.charCodeAt(0) - 32; + modifiers = modifiers || KeyboardShortcut.Modifiers.None; + return KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCode, modifiers); +} + +/** + * @param {KeyboardEvent} keyboardEvent + * @return {number} + */ +KeyboardShortcut.makeKeyFromEvent = function(keyboardEvent) +{ + var modifiers = KeyboardShortcut.Modifiers.None; + if (keyboardEvent.shiftKey) + modifiers |= KeyboardShortcut.Modifiers.Shift; + if (keyboardEvent.ctrlKey) + modifiers |= KeyboardShortcut.Modifiers.Ctrl; + if (keyboardEvent.altKey) + modifiers |= KeyboardShortcut.Modifiers.Alt; + if (keyboardEvent.metaKey) + modifiers |= KeyboardShortcut.Modifiers.Meta; + return KeyboardShortcut._makeKeyFromCodeAndModifiers(keyboardEvent.keyCode, modifiers); +} + +/** + * @param {KeyboardEvent} event + * @return {boolean} + */ +KeyboardShortcut.eventHasCtrlOrMeta = function(event) +{ + return UIUtils.isMac() ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey; +} + +/** + * @param {Event} event + * @return {boolean} + */ +KeyboardShortcut.hasNoModifiers = function(event) +{ + return !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey; +} + +/** @typedef {{key: number, name: string}} */ +KeyboardShortcut.Descriptor; + +/** + * @param {string|KeyboardShortcut.Key} key + * @param {number=} modifiers + * @return {KeyboardShortcut.Descriptor} + */ +KeyboardShortcut.makeDescriptor = function(key, modifiers) +{ + return { + key: KeyboardShortcut.makeKey(typeof key === "string" ? key : key.code, modifiers), + name: KeyboardShortcut.shortcutToString(key, modifiers) + }; +} + +/** + * @param {string|KeyboardShortcut.Key} key + * @param {number=} modifiers + * @return {string} + */ +KeyboardShortcut.shortcutToString = function(key, modifiers) +{ + return KeyboardShortcut._modifiersToString(modifiers) + KeyboardShortcut._keyName(key); +} + +/** + * @param {string|KeyboardShortcut.Key} key + * @return {string} + */ +KeyboardShortcut._keyName = function(key) +{ + if (typeof key === "string") + return key.toUpperCase(); + if (typeof key.name === "string") + return key.name; + return key.name[UIUtils.platform()] || key.name.other || ''; +} + +/** + * @param {number} keyCode + * @param {?number} modifiers + * @return {number} + */ +KeyboardShortcut._makeKeyFromCodeAndModifiers = function(keyCode, modifiers) +{ + return (keyCode & 255) | (modifiers << 8); +}; + +/** + * @param {number|undefined} modifiers + * @return {string} + */ +KeyboardShortcut._modifiersToString = function(modifiers) +{ + const cmdKey = "\u2318"; + const optKey = "\u2325"; + const shiftKey = "\u21e7"; + const ctrlKey = "\u2303"; + + var isMac = UIUtils.isMac(); + var res = ""; + if (modifiers & KeyboardShortcut.Modifiers.Ctrl) + res += isMac ? ctrlKey : "Ctrl + "; + if (modifiers & KeyboardShortcut.Modifiers.Alt) + res += isMac ? optKey : "Alt + "; + if (modifiers & KeyboardShortcut.Modifiers.Shift) + res += isMac ? shiftKey : "Shift + "; + if (modifiers & KeyboardShortcut.Modifiers.Meta) + res += isMac ? cmdKey : "Win + "; + + return res; +}; + +KeyboardShortcut.SelectAll = KeyboardShortcut.makeKey("a", KeyboardShortcut.Modifiers.CtrlOrMeta); + +return KeyboardShortcut + +}); diff --git a/interpreter/debugger/client/LiveLocation.js b/interpreter/debugger/client/LiveLocation.js new file mode 100644 index 0000000..9b6cc19 --- /dev/null +++ b/interpreter/debugger/client/LiveLocation.js @@ -0,0 +1,55 @@ +define(function(){ +/** + * @constructor + * @param {RawLocation} rawLocation + * @param {function(UILocation):(boolean|undefined)} updateDelegate + */ +function LiveLocation(rawLocation, updateDelegate) +{ + this._rawLocation = rawLocation; + this._updateDelegate = updateDelegate; + this._uiSourceCodes = []; +} + +LiveLocation.prototype = { + update: function() + { + var uiLocation = this.uiLocation(); + if (uiLocation) { + var uiSourceCode = uiLocation.uiSourceCode; + if (this._uiSourceCodes.indexOf(uiSourceCode) === -1) { + uiSourceCode.addLiveLocation(this); + this._uiSourceCodes.push(uiSourceCode); + } + var oneTime = this._updateDelegate(uiLocation); + if (oneTime) + this.dispose(); + } + }, + + /** + * @return {RawLocation} + */ + rawLocation: function() + { + return this._rawLocation; + }, + + /** + * @return {UILocation} + */ + uiLocation: function() + { + // Should be overridden by subclasses. + }, + + dispose: function() + { + for (var i = 0; i < this._uiSourceCodes.length; ++i) + this._uiSourceCodes[i].removeLiveLocation(this); + this._uiSourceCodes = []; + } +} + +return LiveLocation; +}); diff --git a/interpreter/debugger/client/NavigatorOverlayController.js b/interpreter/debugger/client/NavigatorOverlayController.js new file mode 100644 index 0000000..79967df --- /dev/null +++ b/interpreter/debugger/client/NavigatorOverlayController.js @@ -0,0 +1,189 @@ +/* + * 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: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS 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 GOOGLE INC. + * OR ITS 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. + */ + +define(function(require){ + +var StatusBarButton = require("StatusBarButton").StatusBarButton; +var SidebarOverlay = require("SidebarOverlay"); +var Preferences = require("Settings").Preferences; + +/** + * @constructor + * @param {SidebarView} parentSidebarView + * @param {View} navigatorView + * @param {View} editorView + */ +function NavigatorOverlayController(parentSidebarView, navigatorView, editorView) +{ + this._parentSidebarView = parentSidebarView; + this._navigatorView = navigatorView; + this._editorView = editorView; + + this._navigatorSidebarResizeWidgetElement = this._navigatorView.element.createChild("div", "resizer-widget"); + this._parentSidebarView.installResizer(this._navigatorSidebarResizeWidgetElement); + + this._navigatorShowHideButton = new StatusBarButton(UIString("Hide navigator"), "left-sidebar-show-hide-button scripts-navigator-show-hide-button", 3); + this._navigatorShowHideButton.state = "left"; + this._navigatorShowHideButton.addEventListener("click", this._toggleNavigator, this); + this._editorView.element.appendChild(this._navigatorShowHideButton.element); + + CSInspector.settings.navigatorHidden = CSInspector.settings.createSetting("navigatorHidden", true); + if (CSInspector.settings.navigatorHidden.get()) + this._toggleNavigator(); +} + +NavigatorOverlayController.prototype = { + wasShown: function() + { + window.setTimeout(this._maybeShowNavigatorOverlay.bind(this), 0); + }, + + _maybeShowNavigatorOverlay: function() + { + if (CSInspector.settings.navigatorHidden.get() && !CSInspector.settings.navigatorWasOnceHidden.get()) + this.showNavigatorOverlay(); + }, + + _toggleNavigator: function() + { + if (this._navigatorShowHideButton.state === "overlay") + this._pinNavigator(); + else if (this._navigatorShowHideButton.state === "right") + this.showNavigatorOverlay(); + else + this._hidePinnedNavigator(); + }, + + _hidePinnedNavigator: function() + { + this._navigatorShowHideButton.state = "right"; + this._navigatorShowHideButton.title = UIString("Show navigator"); + this._parentSidebarView.element.appendChild(this._navigatorShowHideButton.element); + + this._editorView.element.addStyleClass("navigator-hidden"); + this._navigatorSidebarResizeWidgetElement.addStyleClass("hidden"); + + this._parentSidebarView.hideSidebarElement(); + this._navigatorView.detach(); + this._editorView.focus(); + + CSInspector.settings.navigatorWasOnceHidden.set(true); + CSInspector.settings.navigatorHidden.set(true); + }, + + _pinNavigator: function() + { + this._navigatorShowHideButton.state = "left"; + this._navigatorShowHideButton.title = UIString("Hide navigator"); + + this._editorView.element.removeStyleClass("navigator-hidden"); + this._navigatorSidebarResizeWidgetElement.removeStyleClass("hidden"); + this._editorView.element.appendChild(this._navigatorShowHideButton.element); + + this._innerHideNavigatorOverlay(); + this._parentSidebarView.showSidebarElement(); + this._navigatorView.show(this._parentSidebarView.sidebarElement); + this._navigatorView.focus(); + CSInspector.settings.navigatorHidden.set(false); + }, + + showNavigatorOverlay: function() + { + if (this._navigatorShowHideButton.state === "overlay") + return; + + this._navigatorShowHideButton.state = "overlay"; + this._navigatorShowHideButton.title = UIString("Pin navigator"); + + this._sidebarOverlay = new SidebarOverlay(this._navigatorView, "scriptsPanelNavigatorOverlayWidth", Preferences.minScriptsSidebarWidth); + this._boundKeyDown = this._keyDown.bind(this); + this._sidebarOverlay.element.addEventListener("keydown", this._boundKeyDown, false); + + var navigatorOverlayResizeWidgetElement = document.createElement("div"); + navigatorOverlayResizeWidgetElement.addStyleClass("resizer-widget"); + this._sidebarOverlay.resizerWidgetElement = navigatorOverlayResizeWidgetElement; + + this._navigatorView.element.appendChild(this._navigatorShowHideButton.element); + this._boundContainingElementFocused = this._containingElementFocused.bind(this); + this._parentSidebarView.element.addEventListener("mousedown", this._boundContainingElementFocused, false); + + this._sidebarOverlay.show(this._parentSidebarView.element); + this._navigatorView.focus(); + }, + + _keyDown: function(event) + { + if (event.handled) + return; + + if (event.keyCode === KeyboardShortcut.Keys.Esc.code) { + this.hideNavigatorOverlay(); + event.consume(true); + } + }, + + hideNavigatorOverlay: function() + { + if (this._navigatorShowHideButton.state !== "overlay") + return; + + this._navigatorShowHideButton.state = "right"; + this._navigatorShowHideButton.title = UIString("Show navigator"); + this._parentSidebarView.element.appendChild(this._navigatorShowHideButton.element); + + this._innerHideNavigatorOverlay(); + this._editorView.focus(); + }, + + _innerHideNavigatorOverlay: function() + { + this._parentSidebarView.element.removeEventListener("mousedown", this._boundContainingElementFocused, false); + this._sidebarOverlay.element.removeEventListener("keydown", this._boundKeyDown, false); + this._sidebarOverlay.hide(); + }, + + _containingElementFocused: function(event) + { + if (!event.target.isSelfOrDescendant(this._sidebarOverlay.element)) + this.hideNavigatorOverlay(); + }, + + isNavigatorPinned: function() + { + return this._navigatorShowHideButton.state === "left"; + }, + + isNavigatorHidden: function() + { + return this._navigatorShowHideButton.state === "right"; + } +} + +return NavigatorOverlayController; + +}); diff --git a/interpreter/debugger/client/NavigatorView.js b/interpreter/debugger/client/NavigatorView.js new file mode 100644 index 0000000..07a15c3 --- /dev/null +++ b/interpreter/debugger/client/NavigatorView.js @@ -0,0 +1,1058 @@ +/* + * 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: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS 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 GOOGLE INC. + * OR ITS 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. + */ +define(function(require) { + +//require("navigatorView.css"); + +var View = require("View"); +var TreeOutline = require("treeoutline").TreeOutline; +var TreeElement = require("treeoutline").TreeElement; +var ContextMenu = require("ContextMenu"); +var Workspace = require("Workspace").Workspace; +var UISourceCode = require("UISourceCode"); +var UIUtils = require("UIUtils"); + +/** + * @extends {View} + * @constructor + */ +function NavigatorView() +{ + View.call(this); + + var scriptsTreeElement = document.createElement("ol"); + this._scriptsTree = new NavigatorTreeOutline(scriptsTreeElement); + this._scriptsTree.childrenListElement.addEventListener("keypress", this._treeKeyPress.bind(this), true); + + var scriptsOutlineElement = document.createElement("div"); + scriptsOutlineElement.addStyleClass("outline-disclosure"); + scriptsOutlineElement.addStyleClass("navigator"); + scriptsOutlineElement.appendChild(scriptsTreeElement); + + this.element.addStyleClass("fill"); + this.element.addStyleClass("navigator-container"); + this.element.appendChild(scriptsOutlineElement); + this.setDefaultFocusedElement(this._scriptsTree.element); + + /** @type {!Map.} */ + this._uiSourceCodeNodes = new Map(); + /** @type {!Map.>} */ + this._subfolderNodes = new Map(); + + this._rootNode = new NavigatorRootTreeNode(this); + this._rootNode.populate(); + + //this.element.addEventListener("contextmenu", this.handleContextMenu.bind(this), false); +} + +NavigatorView.Events = { + ItemSelected: "ItemSelected", + ItemSearchStarted: "ItemSearchStarted", + ItemCreationRequested: "ItemCreationRequested" +} + +NavigatorView.iconClassForType = function(type) +{ + if (type === NavigatorTreeOutline.Types.Domain) + return "navigator-domain-tree-item"; + if (type === NavigatorTreeOutline.Types.FileSystem) + return "navigator-folder-tree-item"; + return "navigator-folder-tree-item"; +} + +NavigatorView.prototype = { + /** + * @param {UISourceCode} uiSourceCode + */ + addUISourceCode: function(uiSourceCode) + { + var projectNode = this._projectNode(uiSourceCode.project()); + var folderNode = this._folderNode(projectNode, uiSourceCode.parentPath()); + var uiSourceCodeNode = new NavigatorUISourceCodeTreeNode(this, uiSourceCode); + this._uiSourceCodeNodes.put(uiSourceCode, uiSourceCodeNode); + folderNode.appendChild(uiSourceCodeNode); + /*InspectedURLChanged是主资源url,一般就是调试程序的入口 + if (uiSourceCode.url === WebInspector.inspectedPageURL) + this.revealUISourceCode(uiSourceCode); + */ + }, + + /** + * @param {Project} project + * @return {NavigatorTreeNode} + */ + _projectNode: function(project) + { + if (!project.displayName()) + return this._rootNode; + + var projectNode = this._rootNode.child(project.id()); + if (!projectNode) { + var type = project.type() === Workspace.ProjectTypes.FileSystem ? NavigatorTreeOutline.Types.FileSystem : NavigatorTreeOutline.Types.Domain; + projectNode = new NavigatorFolderTreeNode(this, project, project.id(), type, "", project.displayName()); + this._rootNode.appendChild(projectNode); + } + return projectNode; + }, + + /** + * @param {NavigatorTreeNode} projectNode + * @param {string} folderPath + * @return {NavigatorTreeNode} + */ + _folderNode: function(projectNode, folderPath) + { + if (!folderPath) + return projectNode; + + var subfolderNodes = this._subfolderNodes.get(projectNode); + if (!subfolderNodes) { + subfolderNodes = /** @type {!StringMap.} */ (new StringMap()); + this._subfolderNodes.put(projectNode, subfolderNodes); + } + + var folderNode = subfolderNodes.get(folderPath); + if (folderNode) + return folderNode; + + var parentNode = projectNode; + var index = folderPath.lastIndexOf("/"); + if (index !== -1) + parentNode = this._folderNode(projectNode, folderPath.substring(0, index)); + + var name = folderPath.substring(index + 1); + folderNode = new NavigatorFolderTreeNode(this, null, name, NavigatorTreeOutline.Types.Folder, folderPath, name); + subfolderNodes.put(folderPath, folderNode); + parentNode.appendChild(folderNode); + return folderNode; + }, + + /** + * @param {UISourceCode} uiSourceCode + * @param {boolean=} select + */ + revealUISourceCode: function(uiSourceCode, select) + { + var node = this._uiSourceCodeNodes.get(uiSourceCode); + if (!node) + return null; + if (this._scriptsTree.selectedTreeElement) + this._scriptsTree.selectedTreeElement.deselect(); + this._lastSelectedUISourceCode = uiSourceCode; + node.reveal(select); + }, + + /** + * @param {UISourceCode} uiSourceCode + * @param {boolean} focusSource + */ + _scriptSelected: function(uiSourceCode, focusSource) + { + this._lastSelectedUISourceCode = uiSourceCode; + var data = { uiSourceCode: uiSourceCode, focusSource: focusSource}; + this.dispatchEventToListeners(NavigatorView.Events.ItemSelected, data); + }, + + /** + * @param {UISourceCode} uiSourceCode + */ + removeUISourceCode: function(uiSourceCode) + { + var node = this._uiSourceCodeNodes.get(uiSourceCode); + if (!node) + return; + + var projectNode = this._projectNode(uiSourceCode.project()); + var subfolderNodes = this._subfolderNodes.get(projectNode); + var parentNode = node.parent; + this._uiSourceCodeNodes.remove(uiSourceCode); + parentNode.removeChild(node); + node = parentNode; + + while (node) { + parentNode = node.parent; + if (!parentNode || !node.isEmpty()) + break; + if (subfolderNodes) + subfolderNodes.remove(node._folderPath); + parentNode.removeChild(node); + node = parentNode; + } + }, + + reset: function() + { + var nodes = this._uiSourceCodeNodes.values(); + for (var i = 0; i < nodes.length; ++i) + nodes[i].dispose(); + + this._scriptsTree.removeChildren(); + this._uiSourceCodeNodes.clear(); + this._subfolderNodes.clear(); + this._rootNode.reset(); + }, + + /** + * @param {Event} event + */ + handleContextMenu: function(event) + { + var contextMenu = new ContextMenu(event); + this._appendAddFolderItem(contextMenu); + contextMenu.show(); + }, + + /** + * @param {ContextMenu} contextMenu + */ + _appendAddFolderItem: function(contextMenu) + { + function addFolder() + { + WebInspector.isolatedFileSystemManager.addFileSystem(); + } + + var addFolderLabel = UIString(UIUtils.useLowerCaseMenuTitles() ? "Add folder to workspace" : "Add Folder to Workspace"); + contextMenu.appendItem(addFolderLabel, addFolder); + }, + + /** + * @param {Event} event + * @param {UISourceCode} uiSourceCode + */ + handleFileContextMenu: function(event, uiSourceCode) + { + var contextMenu = new ContextMenu(event); + contextMenu.appendApplicableItems(uiSourceCode); + contextMenu.appendSeparator(); + this._appendAddFolderItem(contextMenu); + contextMenu.show(); + }, + + /** + * @param {Event} event + */ + _treeKeyPress: function(event) + { + if (UIUtils.isBeingEdited(this._scriptsTree.childrenListElement)) + return; + + var searchText = String.fromCharCode(event.charCode); + if (searchText.trim() !== searchText) + return; + this.dispatchEventToListeners(NavigatorView.Events.ItemSearchStarted, searchText); + event.consume(true); + }, + + __proto__: View.prototype +} + +/** + * @constructor + * @extends {TreeOutline} + * @param {Element} element + */ +function NavigatorTreeOutline(element) +{ + TreeOutline.call(this, element); + this.element = element; + + this.comparator = NavigatorTreeOutline._treeElementsCompare; +} + +NavigatorTreeOutline.Types = { + Root: "Root", + Domain: "Domain", + Folder: "Folder", + UISourceCode: "UISourceCode", + FileSystem: "FileSystem" +} + +NavigatorTreeOutline._treeElementsCompare = function compare(treeElement1, treeElement2) +{ + // Insert in the alphabetical order, first domains, then folders, then scripts. + function typeWeight(treeElement) + { + var type = treeElement.type(); + if (type === NavigatorTreeOutline.Types.Domain) { + if (treeElement.titleText === CSInspector.inspectedPageDomain) + return 1; + return 2; + } + if (type === NavigatorTreeOutline.Types.FileSystem) + return 3; + if (type === NavigatorTreeOutline.Types.Folder) + return 4; + return 5; + } + + var typeWeight1 = typeWeight(treeElement1); + var typeWeight2 = typeWeight(treeElement2); + + var result; + if (typeWeight1 > typeWeight2) + result = 1; + else if (typeWeight1 < typeWeight2) + result = -1; + else { + var title1 = treeElement1.titleText; + var title2 = treeElement2.titleText; + result = title1.compareTo(title2); + } + return result; +} + +NavigatorTreeOutline.prototype = { + /** + * @return {Array.} + */ + scriptTreeElements: function() + { + var result = []; + if (this.children.length) { + for (var treeElement = this.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this, true)) { + if (treeElement instanceof NavigatorSourceTreeElement) + result.push(treeElement.uiSourceCode); + } + } + return result; + }, + + __proto__: TreeOutline.prototype +} + +/** + * @constructor + * @extends {TreeElement} + * @param {string} type + * @param {string} title + * @param {Array.} iconClasses + * @param {boolean} hasChildren + * @param {boolean=} noIcon + */ +function BaseNavigatorTreeElement(type, title, iconClasses, hasChildren, noIcon) +{ + this._type = type; + TreeElement.call(this, "", null, hasChildren); + this._titleText = title; + this._iconClasses = iconClasses; + this._noIcon = noIcon; +} + +BaseNavigatorTreeElement.prototype = { + onattach: function() + { + this.listItemElement.removeChildren(); + if (this._iconClasses) { + for (var i = 0; i < this._iconClasses.length; ++i) + this.listItemElement.addStyleClass(this._iconClasses[i]); + } + + var selectionElement = document.createElement("div"); + selectionElement.className = "selection"; + this.listItemElement.appendChild(selectionElement); + + if (!this._noIcon) { + this.imageElement = document.createElement("img"); + this.imageElement.className = "icon"; + this.listItemElement.appendChild(this.imageElement); + } + + this.titleElement = document.createElement("div"); + this.titleElement.className = "base-navigator-tree-element-title"; + this._titleTextNode = document.createTextNode(""); + this._titleTextNode.textContent = this._titleText; + this.titleElement.appendChild(this._titleTextNode); + this.listItemElement.appendChild(this.titleElement); + }, + + onreveal: function() + { + if (this.listItemElement) + this.listItemElement.scrollIntoViewIfNeeded(true); + }, + + /** + * @return {string} + */ + get titleText() + { + return this._titleText; + }, + + set titleText(titleText) + { + if (this._titleText === titleText) + return; + this._titleText = titleText || ""; + if (this.titleElement) + this.titleElement.textContent = this._titleText; + }, + + /** + * @return {string} + */ + type: function() + { + return this._type; + }, + + __proto__: TreeElement.prototype +} + +/** + * @constructor + * @extends {BaseNavigatorTreeElement} + * @param {NavigatorView} navigatorView + * @param {string} type + * @param {string} title + */ +NavigatorFolderTreeElement = function(navigatorView, type, title) +{ + var iconClass = NavigatorView.iconClassForType(type); + BaseNavigatorTreeElement.call(this, type, title, [iconClass], true); + this._navigatorView = navigatorView; +} + +NavigatorFolderTreeElement.prototype = { + onpopulate: function() + { + this._node.populate(); + }, + + onattach: function() + { + BaseNavigatorTreeElement.prototype.onattach.call(this); + this.collapse(); + }, + + /** + * @param {NavigatorFolderTreeNode} node + */ + setNode: function(node) + { + this._node = node; + var paths = []; + while (node && !node.isRoot()) { + paths.push(node._title); + node = node.parent; + } + paths.reverse(); + this.tooltip = paths.join("/"); + }, + + __proto__: BaseNavigatorTreeElement.prototype +} + +/** + * @constructor + * @extends {BaseNavigatorTreeElement} + * @param {NavigatorView} navigatorView + * @param {UISourceCode} uiSourceCode + * @param {string} title + */ +function NavigatorSourceTreeElement(navigatorView, uiSourceCode, title) +{ + BaseNavigatorTreeElement.call(this, NavigatorTreeOutline.Types.UISourceCode, title, ["navigator-" + uiSourceCode.contentType().name() + "-tree-item"], false); + this._navigatorView = navigatorView; + this._uiSourceCode = uiSourceCode; + this.tooltip = uiSourceCode.originURL(); +} + +NavigatorSourceTreeElement.prototype = { + /** + * @return {UISourceCode} + */ + get uiSourceCode() + { + return this._uiSourceCode; + }, + + onattach: function() + { + BaseNavigatorTreeElement.prototype.onattach.call(this); + this.listItemElement.draggable = true; + this.listItemElement.addEventListener("click", this._onclick.bind(this), false); + this.listItemElement.addEventListener("mousedown", this._onmousedown.bind(this), false); + this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false); + }, + + _onmousedown: function(event) + { + if (event.which === 1) // Warm-up data for drag'n'drop + this._uiSourceCode.requestContent(callback.bind(this)); + /** + * @param {?string} content + * @param {boolean} contentEncoded + * @param {string} mimeType + */ + function callback(content, contentEncoded, mimeType) + { + this._warmedUpContent = content; + } + }, + + _shouldRenameOnMouseDown: function() + { + if (!this._uiSourceCode.canRename()) + return false; + var isSelected = this === this.treeOutline.selectedTreeElement; + var isFocused = this.treeOutline.childrenListElement.isSelfOrAncestor(document.activeElement); + return isSelected && isFocused && !UIUtils.isBeingEdited(this.treeOutline.element); + }, + + selectOnMouseDown: function(event) + { + if (event.which !== 1 || !this._shouldRenameOnMouseDown()) { + TreeElement.prototype.selectOnMouseDown.call(this, event); + return; + } + setTimeout(rename.bind(this), 300); + + function rename() + { + if (this._shouldRenameOnMouseDown()) + this._navigatorView.requestRename(this._uiSourceCode); + } + }, + + _ondragstart: function(event) + { + event.dataTransfer.setData("text/plain", this._warmedUpContent); + event.dataTransfer.effectAllowed = "copy"; + return true; + }, + + onspace: function() + { + this._navigatorView._scriptSelected(this.uiSourceCode, true); + return true; + }, + + /** + * @param {Event} event + */ + _onclick: function(event) + { + this._navigatorView._scriptSelected(this.uiSourceCode, false); + }, + + /** + * @param {Event} event + */ + ondblclick: function(event) + { + var middleClick = event.button === 1; + this._navigatorView._scriptSelected(this.uiSourceCode, !middleClick); + }, + + onenter: function() + { + this._navigatorView._scriptSelected(this.uiSourceCode, true); + return true; + }, + + __proto__: BaseNavigatorTreeElement.prototype +} + +/** + * @constructor + * @param {string} id + */ +function NavigatorTreeNode(id) +{ + this.id = id; + /** @type {!StringMap.} */ + this._children = new StringMap(); +} + +NavigatorTreeNode.prototype = { + /** + * @return {TreeElement} + */ + treeElement: function() { }, + + dispose: function() { }, + + /** + * @return {boolean} + */ + isRoot: function() + { + return false; + }, + + /** + * @return {boolean} + */ + hasChildren: function() + { + return true; + }, + + populate: function() + { + if (this.isPopulated()) + return; + if (this.parent) + this.parent.populate(); + this._populated = true; + this.wasPopulated(); + }, + + wasPopulated: function() + { + var children = this.children(); + for (var i = 0; i < children.length; ++i) + this.treeElement().appendChild(children[i].treeElement()); + }, + + /** + * @param {!NavigatorTreeNode} node + */ + didAddChild: function(node) + { + if (this.isPopulated()) + this.treeElement().appendChild(node.treeElement()); + }, + + /** + * @param {!NavigatorTreeNode} node + */ + willRemoveChild: function(node) + { + if (this.isPopulated()) + this.treeElement().removeChild(node.treeElement()); + }, + + /** + * @return {boolean} + */ + isPopulated: function() + { + return this._populated; + }, + + /** + * @return {boolean} + */ + isEmpty: function() + { + return !this._children.size(); + }, + + /** + * @param {string} id + * @return {NavigatorTreeNode} + */ + child: function(id) + { + return this._children.get(id); + }, + + /** + * @return {!Array.} + */ + children: function() + { + return this._children.values(); + }, + + /** + * @param {!NavigatorTreeNode} node + */ + appendChild: function(node) + { + this._children.put(node.id, node); + node.parent = this; + this.didAddChild(node); + }, + + /** + * @param {!NavigatorTreeNode} node + */ + removeChild: function(node) + { + this.willRemoveChild(node); + this._children.remove(node.id); + delete node.parent; + node.dispose(); + }, + + reset: function() + { + this._children.clear(); + } +} + +/** + * @constructor + * @extends {NavigatorTreeNode} + * @param {NavigatorView} navigatorView + */ +function NavigatorRootTreeNode(navigatorView) +{ + NavigatorTreeNode.call(this, ""); + this._navigatorView = navigatorView; +} + +NavigatorRootTreeNode.prototype = { + /** + * @return {boolean} + */ + isRoot: function() + { + return true; + }, + + /** + * @return {TreeOutline} + */ + treeElement: function() + { + return this._navigatorView._scriptsTree; + }, + + __proto__: NavigatorTreeNode.prototype +} + +/** + * @constructor + * @extends {NavigatorTreeNode} + * @param {NavigatorView} navigatorView + * @param {UISourceCode} uiSourceCode + */ +function NavigatorUISourceCodeTreeNode(navigatorView, uiSourceCode) +{ + NavigatorTreeNode.call(this, uiSourceCode.name()); + this._navigatorView = navigatorView; + this._uiSourceCode = uiSourceCode; + this._treeElement = null; +} + +NavigatorUISourceCodeTreeNode.prototype = { + /** + * @return {UISourceCode} + */ + uiSourceCode: function() + { + return this._uiSourceCode; + }, + + /** + * @return {TreeElement} + */ + treeElement: function() + { + if (this._treeElement) + return this._treeElement; + + this._treeElement = new NavigatorSourceTreeElement(this._navigatorView, this._uiSourceCode, ""); + this.updateTitle(); + + this._uiSourceCode.addEventListener(UISourceCode.Events.TitleChanged, this._titleChanged, this); + /* + this._uiSourceCode.addEventListener(UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); + this._uiSourceCode.addEventListener(UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); + this._uiSourceCode.addEventListener(UISourceCode.Events.FormattedChanged, this._formattedChanged, this); + */ + + return this._treeElement; + }, + + /** + * @param {boolean=} ignoreIsDirty + */ + updateTitle: function(ignoreIsDirty) + { + if (!this._treeElement) + return; + + var titleText = this._uiSourceCode.displayName(); + if (!ignoreIsDirty && (this._uiSourceCode.isDirty() || this._uiSourceCode.hasUnsavedCommittedChanges())) + titleText = "*" + titleText; + this._treeElement.titleText = titleText; + }, + + /** + * @return {boolean} + */ + hasChildren: function() + { + return false; + }, + + dispose: function() + { + if (!this._treeElement) + return; + this._uiSourceCode.removeEventListener(UISourceCode.Events.TitleChanged, this._titleChanged, this); + /* + this._uiSourceCode.removeEventListener(UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); + this._uiSourceCode.removeEventListener(UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this); + this._uiSourceCode.removeEventListener(UISourceCode.Events.FormattedChanged, this._formattedChanged, this); + */ + }, + + _titleChanged: function(event) + { + this.updateTitle(); + }, + + _workingCopyChanged: function(event) + { + this.updateTitle(); + }, + + _workingCopyCommitted: function(event) + { + this.updateTitle(); + }, + + _formattedChanged: function(event) + { + this.updateTitle(); + }, + + /** + * @param {boolean=} select + */ + reveal: function(select) + { + this.parent.populate(); + this.parent.treeElement().expand(); + this._treeElement.reveal(); + if (select) + this._treeElement.select(); + }, + + /** + * @param {function(boolean)=} callback + */ + rename: function(callback) + { + if (!this._treeElement) + return; + + // Tree outline should be marked as edited as well as the tree element to prevent search from starting. + var treeOutlineElement = this._treeElement.treeOutline.element; + UIUtils.markBeingEdited(treeOutlineElement, true); + + function commitHandler(element, newTitle, oldTitle) + { + if (newTitle !== oldTitle) { + this._treeElement.titleText = newTitle; + this._uiSourceCode.rename(newTitle, renameCallback.bind(this)); + return; + } + afterEditing.call(this, true); + } + + function renameCallback(success) + { + if (!success) { + UIUtils.markBeingEdited(treeOutlineElement, false); + this.updateTitle(); + this.rename(callback); + return; + } + afterEditing.call(this, true); + } + + function cancelHandler() + { + afterEditing.call(this, false); + } + + /** + * @param {boolean} committed + */ + function afterEditing(committed) + { + UIUtils.markBeingEdited(treeOutlineElement, false); + this.updateTitle(); + this._treeElement.treeOutline.childrenListElement.focus(); + if (callback) + callback(committed); + } + + var editingConfig = new WebInspector.EditingConfig(commitHandler.bind(this), cancelHandler.bind(this)); + this.updateTitle(true); + WebInspector.startEditing(this._treeElement.titleElement, editingConfig); + window.getSelection().setBaseAndExtent(this._treeElement.titleElement, 0, this._treeElement.titleElement, 1); + }, + + __proto__: NavigatorTreeNode.prototype +} + +/** + * @constructor + * @extends {NavigatorTreeNode} + * @param {NavigatorView} navigatorView + * @param {Project} project + * @param {string} id + * @param {string} type + * @param {string} folderPath + * @param {string} title + */ +function NavigatorFolderTreeNode(navigatorView, project, id, type, folderPath, title) +{ + NavigatorTreeNode.call(this, id); + this._navigatorView = navigatorView; + this._project = project; + this._type = type; + this._folderPath = folderPath; + this._title = title; +} + +NavigatorFolderTreeNode.prototype = { + /** + * @return {TreeElement} + */ + treeElement: function() + { + if (this._treeElement) + return this._treeElement; + this._treeElement = this._createTreeElement(this._title, this); + return this._treeElement; + }, + + /** + * @return {TreeElement} + */ + _createTreeElement: function(title, node) + { + var treeElement = new NavigatorFolderTreeElement(this._navigatorView, this._type, title); + treeElement.setNode(node); + return treeElement; + }, + + wasPopulated: function() + { + if (!this._treeElement || this._treeElement._node !== this) + return; + this._addChildrenRecursive(); + }, + + _addChildrenRecursive: function() + { + var children = this.children(); + for (var i = 0; i < children.length; ++i) { + var child = children[i]; + this.didAddChild(child); + if (child instanceof NavigatorFolderTreeNode) + child._addChildrenRecursive(); + } + }, + + _shouldMerge: function(node) + { + return this._type !== NavigatorTreeOutline.Types.Domain && node instanceof NavigatorFolderTreeNode; + }, + + didAddChild: function(node) + { + function titleForNode(node) + { + return node._title; + } + + if (!this._treeElement) + return; + + var children = this.children(); + + if (children.length === 1 && this._shouldMerge(node)) { + node._isMerged = true; + this._treeElement.titleText = this._treeElement.titleText + "/" + node._title; + node._treeElement = this._treeElement; + this._treeElement.setNode(node); + return; + } + + var oldNode; + if (children.length === 2) + oldNode = children[0] !== node ? children[0] : children[1]; + if (oldNode && oldNode._isMerged) { + delete oldNode._isMerged; + var mergedToNodes = []; + mergedToNodes.push(this); + var treeNode = this; + while (treeNode._isMerged) { + treeNode = treeNode.parent; + mergedToNodes.push(treeNode); + } + mergedToNodes.reverse(); + var titleText = mergedToNodes.map(titleForNode).join("/"); + + var nodes = []; + treeNode = oldNode; + do { + nodes.push(treeNode); + children = treeNode.children(); + treeNode = children.length === 1 ? children[0] : null; + } while (treeNode && treeNode._isMerged); + + if (!this.isPopulated()) { + this._treeElement.titleText = titleText; + this._treeElement.setNode(this); + for (var i = 0; i < nodes.length; ++i) { + delete nodes[i]._treeElement; + delete nodes[i]._isMerged; + } + return; + } + var oldTreeElement = this._treeElement; + var treeElement = this._createTreeElement(titleText, this); + for (var i = 0; i < mergedToNodes.length; ++i) + mergedToNodes[i]._treeElement = treeElement; + oldTreeElement.parent.appendChild(treeElement); + + oldTreeElement.setNode(nodes[nodes.length - 1]); + oldTreeElement.titleText = nodes.map(titleForNode).join("/"); + oldTreeElement.parent.removeChild(oldTreeElement); + this._treeElement.appendChild(oldTreeElement); + if (oldTreeElement.expanded) + treeElement.expand(); + } + if (this.isPopulated()) + this._treeElement.appendChild(node.treeElement()); + }, + + willRemoveChild: function(node) + { + if (node._isMerged || !this.isPopulated()) + return; + this._treeElement.removeChild(node._treeElement); + }, + + __proto__: NavigatorTreeNode.prototype +} + +return NavigatorView; +}); diff --git a/interpreter/debugger/client/NetworkUISourceCodeProvider.js b/interpreter/debugger/client/NetworkUISourceCodeProvider.js new file mode 100644 index 0000000..85b74fc --- /dev/null +++ b/interpreter/debugger/client/NetworkUISourceCodeProvider.js @@ -0,0 +1,133 @@ +/* + * 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. + */ + +define(function(require){ + +var Workspace = require("Workspace").Workspace; +var ParsedURL = require("ParsedURL"); +var DebugModel = require("DebugModel"); +var Resource = require("Resource").Resource; +var ResourceTypes = require("Resource").ResourceTypes; + +/** + * @constructor + * @param {SimpleWorkspaceProvider} networkWorkspaceProvider + * @param {Workspace} workspace + */ +function NetworkUISourceCodeProvider(networkWorkspaceProvider, workspace) +{ + this._networkWorkspaceProvider = networkWorkspaceProvider; + this._workspace = workspace; + + //CSInspector.debugModel.addEventListener(DebugModel.Events.FileSourcesAdded, this._fileResourcesAdded, this) + //WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, this._resourceAdded, this); + CSInspector.debugModel.addEventListener(DebugModel.Events.ParsedScriptSource, this._parsedScriptSource, this); + + this._processedURLs = {}; +} + +NetworkUISourceCodeProvider.prototype = { + _populate: function() + { + function populateFrame(frame) + { + for (var i = 0; i < frame.childFrames.length; ++i) + populateFrame.call(this, frame.childFrames[i]); + + var resources = frame.resources(); + for (var i = 0; i < resources.length; ++i) + this._resourceAdded({data:resources[i]}); + } + + populateFrame.call(this, ResourceTreeModel.mainFrame); + }, + + /** + * @param {Event} event + */ + _resourceAdded: function(event) + { + var resource = /** @type {Resource} */ (event.data); + + this._addFile(resource.url, resource); + }, + + /** + * @param {Event} event + */ + _parsedScriptSource: function(event) + { + var script = /** @type {Script} */ (event.data); + // Filter out embedder injected content scripts. + /*x + if (script.isContentScript && !script.hasSourceURL) { + var parsedURL = new ParsedURL(script.sourceURL); + if (!parsedURL.host) + return; + }*/ + this._addFile(script.sourceURL, script, script.isContentScript); + }, + + /* + _fileResourcesAdded: function(event) { + var fileResources = event.data; + for(var i = 0, l = fileResources.length; i < l; i++) { + var fileResourceItem = fileResources[i]; + var resourceProvider = new Resource(fileResourceItem.url, fileResourceItem.mimeType, ResourceTypes.Script, fileResourceItem.content); + this._addFile(fileResourceItem.url, resourceProvider, true); + } + }, + */ + + /** + * @param {string} url + * @param {ContentProvider} contentProvider + */ + _addFile: function(url, contentProvider, isContentScript) + { + if (this._processedURLs[url]) + return; + + //进入下面这条语句可以看出ContentProvider只要提供四个接口:contentType, contentURL, requestContent, searchInContent + //contentProvider = new ContentProviderOverridingMimeType(contentProvider, mimeType); + this._processedURLs[url] = true; + this._networkWorkspaceProvider.addFileForURL(url, contentProvider, false, isContentScript); + }, + + _reset: function() + { + this._processedURLs = {}; + this._networkWorkspaceProvider.reset(); + this._populate(); + } +} + +return NetworkUISourceCodeProvider; +}); diff --git a/interpreter/debugger/client/ObjectPropertiesSection.js b/interpreter/debugger/client/ObjectPropertiesSection.js new file mode 100644 index 0000000..58e10b8 --- /dev/null +++ b/interpreter/debugger/client/ObjectPropertiesSection.js @@ -0,0 +1,937 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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. + */ +define(function(require, exports){ + +var PropertiesSection = require("PropertiesSection"); +var TreeElement = require("treeoutline").TreeElement; +var TextPrompt = require("TextPrompt"); +var UIUtils = require("UIUtils"); +var RemoteObject = require("RemoteObject").RemoteObject; + +/** + * @constructor + * @extends {PropertiesSection} + * @param {RemoteObject} object + * @param {?string|Element=} title + * @param {string=} subtitle + * @param {?string=} emptyPlaceholder + * @param {boolean=} ignoreHasOwnProperty + * @param {Array.=} extraProperties + * @param {function(new:TreeElement, RemoteObjectProperty)=} treeElementConstructor + */ +function ObjectPropertiesSection(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor) +{ + this.emptyPlaceholder = (emptyPlaceholder || UIString("No Properties")); + this.object = object; + this.ignoreHasOwnProperty = ignoreHasOwnProperty; + this.extraProperties = extraProperties; + this.treeElementConstructor = treeElementConstructor || ObjectPropertyTreeElement; + this.editable = true; + this.skipProto = false; + + PropertiesSection.call(this, title || "", subtitle); +} + +ObjectPropertiesSection._arrayLoadThreshold = 100; + +ObjectPropertiesSection.prototype = { + enableContextMenu: function() + { + this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false); + }, + + _contextMenuEventFired: function(event) + { + var contextMenu = new ContextMenu(event); + contextMenu.appendApplicableItems(this.object); + contextMenu.show(); + }, + + onpopulate: function() + { + this.update(); + }, + + update: function() + { + if (this.object.arrayLength() > ObjectPropertiesSection._arrayLoadThreshold) { + this.propertiesTreeOutline.removeChildren(); + ArrayGroupingTreeElement._populateArray(this.propertiesTreeOutline, this.object, 0, this.object.arrayLength() - 1); + return; + } + + /** + * @param {?Array.} properties + * @param {?Array.} internalProperties + */ + function callback(properties, internalProperties) + { + if (!properties) + return; + this.updateProperties(properties, internalProperties); + } + + RemoteObject.loadFromObject(this.object, !!this.ignoreHasOwnProperty, callback.bind(this)); + }, + + updateProperties: function(properties, internalProperties, rootTreeElementConstructor, rootPropertyComparer) + { + if (!rootTreeElementConstructor) + rootTreeElementConstructor = this.treeElementConstructor; + + if (!rootPropertyComparer) + rootPropertyComparer = ObjectPropertiesSection.CompareProperties; + + if (this.extraProperties) { + for (var i = 0; i < this.extraProperties.length; ++i) + properties.push(this.extraProperties[i]); + } + + this.propertiesTreeOutline.removeChildren(); + + ObjectPropertyTreeElement.populateWithProperties(this.propertiesTreeOutline, + properties, internalProperties, + rootTreeElementConstructor, rootPropertyComparer, + this.skipProto, this.object); + + this.propertiesForTest = properties; + + if (!this.propertiesTreeOutline.children.length) { + var title = document.createElement("div"); + title.className = "info"; + title.textContent = this.emptyPlaceholder; + var infoElement = new TreeElement(title, null, false); + this.propertiesTreeOutline.appendChild(infoElement); + } + }, + + __proto__: PropertiesSection.prototype +} + +/** + * @param {RemoteObjectProperty} propertyA + * @param {RemoteObjectProperty} propertyB + * @return {number} + */ +ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB) +{ + var a = propertyA.name; + var b = propertyB.name; + if (a === "__proto__") + return 1; + if (b === "__proto__") + return -1; + return String.naturalOrderComparator(a, b); +} + +/** + * @constructor + * @extends {TreeElement} + * @param {RemoteObjectProperty} property + */ +function ObjectPropertyTreeElement(property) +{ + this.property = property; + + // Pass an empty title, the title gets made later in onattach. + TreeElement.call(this, "", null, false); + this.toggleOnClick = true; + this.selectable = false; +} + +ObjectPropertyTreeElement.prototype = { + onpopulate: function() + { + return ObjectPropertyTreeElement.populate(this, this.property.value); + }, + + ondblclick: function(event) + { + if (this.property.writable || this.property.setter) + this.startEditing(event); + }, + + onattach: function() + { + this.update(); + }, + + update: function() + { + this.nameElement = document.createElement("span"); + this.nameElement.className = "name"; + this.nameElement.textContent = this.property.name; + if (!this.property.enumerable) + this.nameElement.addStyleClass("dimmed"); + if (this.property.isAccessorProperty()) + this.nameElement.addStyleClass("properties-accessor-property-name"); + + + var separatorElement = document.createElement("span"); + separatorElement.className = "separator"; + separatorElement.textContent = ": "; + + if (this.property.value) { + this.valueElement = document.createElement("span"); + this.valueElement.className = "value"; + var description = this.property.value.description; + // Render \n as a nice unicode cr symbol. + if (this.property.wasThrown) + this.valueElement.textContent = "[Exception: " + description + "]"; + else if (this.property.value.type === "string" && typeof description === "string") { + this.valueElement.textContent = "\"" + description.replace(/\n/g, "\u21B5") + "\""; + this.valueElement._originalTextContent = "\"" + description + "\""; + } else if (this.property.value.type === "function" && typeof description === "string") { + this.valueElement.textContent = /.*/.exec(description)[0].replace(/ +$/g, ""); + this.valueElement._originalTextContent = description; + } else if (this.property.value.type !== "object" || this.property.value.subtype !== "node") { + //用description来描述hdf节点的值 + description = description + ""; + this.valueElement.textContent = description.length ? "\"" + description + "\"" : description; + } + + if (this.property.wasThrown) + this.valueElement.addStyleClass("error"); + if (this.property.value.subtype) + this.valueElement.addStyleClass("console-formatted-" + this.property.value.subtype); + else if (this.property.value.type) + this.valueElement.addStyleClass("console-formatted-" + this.property.value.type); + + this.valueElement.addEventListener("contextmenu", this._contextMenuFired.bind(this, this.property.value), false); + if (this.property.value.type === "object" && this.property.value.subtype === "node" && this.property.value.description) { + DOMPresentationUtils.createSpansForNodeTitle(this.valueElement, this.property.value.description); + this.valueElement.addEventListener("mousemove", this._mouseMove.bind(this, this.property.value), false); + this.valueElement.addEventListener("mouseout", this._mouseOut.bind(this, this.property.value), false); + } else + this.valueElement.title = description || ""; + + this.listItemElement.removeChildren(); + + this.hasChildren = this.property.value.hasChildren && !this.property.wasThrown; + } else { + if (this.property.getter) { + this.valueElement = document.createElement("span"); + this.valueElement.addStyleClass("properties-calculate-value-button"); + this.valueElement.textContent = "(...)"; + this.valueElement.title = "Invoke property getter"; + this.valueElement.addEventListener("click", this._onInvokeGetterClick.bind(this), false); + } else { + this.valueElement = document.createElement("span"); + this.valueElement.textContent = "" + } + } + + this.listItemElement.appendChild(this.nameElement); + this.listItemElement.appendChild(separatorElement); + this.listItemElement.appendChild(this.valueElement); + }, + + _contextMenuFired: function(value, event) + { + var contextMenu = new ContextMenu(event); + this.populateContextMenu(contextMenu); + contextMenu.appendApplicableItems(value); + contextMenu.show(); + }, + + /** + * @param {ContextMenu} contextMenu + */ + populateContextMenu: function(contextMenu) + { + }, + + _mouseMove: function(event) + { + this.property.value.highlightAsDOMNode(); + }, + + _mouseOut: function(event) + { + this.property.value.hideDOMNodeHighlight(); + }, + + updateSiblings: function() + { + if (this.parent.root) + this.treeOutline.section.update(); + else + this.parent.shouldRefreshChildren = true; + }, + + renderPromptAsBlock: function() + { + return false; + }, + + /** + * @param {Event=} event + */ + elementAndValueToEdit: function(event) + { + return [this.valueElement, (typeof this.valueElement._originalTextContent === "string") ? this.valueElement._originalTextContent : undefined]; + }, + + startEditing: function(event) + { + var elementAndValueToEdit = this.elementAndValueToEdit(event); + var elementToEdit = elementAndValueToEdit[0]; + var valueToEdit = elementAndValueToEdit[1]; + + if (UIUtils.isBeingEdited(elementToEdit) || !this.treeOutline.section.editable || this._readOnly) + return; + + // Edit original source. + if (typeof valueToEdit !== "undefined") + elementToEdit.textContent = valueToEdit; + + var context = { expanded: this.expanded, elementToEdit: elementToEdit, previousContent: elementToEdit.textContent }; + + // Lie about our children to prevent expanding on double click and to collapse subproperties. + this.hasChildren = false; + + this.listItemElement.addStyleClass("editing-sub-part"); + + this._prompt = new ObjectPropertyPrompt(this.editingCommitted.bind(this, null, elementToEdit.textContent, context.previousContent, context), this.editingCancelled.bind(this, null, context), this.renderPromptAsBlock()); + + function blurListener() + { + this.editingCommitted(null, elementToEdit.textContent, context.previousContent, context); + } + + var proxyElement = this._prompt.attachAndStartEditing(elementToEdit, blurListener.bind(this)); + window.getSelection().setBaseAndExtent(elementToEdit, 0, elementToEdit, 1); + proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this, context), false); + }, + + /** + * @return {boolean} + */ + isEditing: function() + { + return !!this._prompt; + }, + + editingEnded: function(context) + { + this._prompt.detach(); + delete this._prompt; + + this.listItemElement.scrollLeft = 0; + this.listItemElement.removeStyleClass("editing-sub-part"); + if (context.expanded) + this.expand(); + }, + + editingCancelled: function(element, context) + { + this.editingEnded(context); + this.update(); + }, + + editingCommitted: function(element, userInput, previousContent, context) + { + if (userInput === previousContent) + return this.editingCancelled(element, context); // nothing changed, so cancel + + this.editingEnded(context); + this.applyExpression(userInput, true); + }, + + _promptKeyDown: function(context, event) + { + if (isEnterKey(event)) { + event.consume(true); + return this.editingCommitted(null, context.elementToEdit.textContent, context.previousContent, context); + } + if (event.keyIdentifier === "U+001B") { // Esc + event.consume(); + return this.editingCancelled(null, context); + } + }, + + applyExpression: function(expression, updateInterface) + { + expression = expression.trim(); + var expressionLength = expression.length; + function callback(error) + { + if (!updateInterface) + return; + + if (error) + this.update(); + + if (!expressionLength) { + // The property was deleted, so remove this tree element. + this.parent.removeChild(this); + } else { + // Call updateSiblings since their value might be based on the value that just changed. + this.updateSiblings(); + } + }; + this.property.parentObject.setPropertyValue(this.property.name, expression.trim(), callback.bind(this)); + }, + + propertyPath: function() + { + if ("_cachedPropertyPath" in this) + return this._cachedPropertyPath; + + var current = this; + var result; + + do { + if (current.property) { + if (result) + result = current.property.name + "." + result; + else + result = current.property.name; + } + current = current.parent; + } while (current && !current.root); + + this._cachedPropertyPath = result; + return result; + }, + + _onInvokeGetterClick: function(event) + { + /** + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} result + * @param {boolean=} wasThrown + */ + function evaluateCallback(error, result, wasThrown) + { + if (error) + return; + var remoteObject = RemoteObject.fromPayload(result); + this.property.value = remoteObject; + this.property.wasThrown = wasThrown; + + this.update(); + this.shouldRefreshChildren = true; + } + + event.consume(); + + if (!this.property.getter) + return; + + var functionText = "function(th){return this.call(th);}" + var functionArguments = [ {objectId: this.property.parentObject.objectId} ] + RuntimeAgent.callFunctionOn(this.property.getter.objectId, functionText, functionArguments, + undefined, false, undefined, evaluateCallback.bind(this)); + }, + + __proto__: TreeElement.prototype +} + +/** + * @param {TreeElement} treeElement + * @param {RemoteObject} value + */ +ObjectPropertyTreeElement.populate = function(treeElement, value) { + if (treeElement.children.length && !treeElement.shouldRefreshChildren) + return; + + if (value.arrayLength() > ObjectPropertiesSection._arrayLoadThreshold) { + treeElement.removeChildren(); + ArrayGroupingTreeElement._populateArray(treeElement, value, 0, value.arrayLength() - 1); + return; + } + + /** + * @param {Array.=} properties + * @param {Array.=} internalProperties + */ + function callback(properties, internalProperties) + { + treeElement.removeChildren(); + if (!properties) + return; + if (!internalProperties) + internalProperties = []; + + ObjectPropertyTreeElement.populateWithProperties(treeElement, properties, internalProperties, + treeElement.treeOutline.section.treeElementConstructor, ObjectPropertiesSection.CompareProperties, + treeElement.treeOutline.section.skipProto, value); + } + + RemoteObject.loadFromObjectPerProto(value, callback); +} + +/** + * @param {!TreeElement|!TreeOutline} treeElement + * @param {Array.} properties + * @param {?Array.} internalProperties + * @param {function(new:TreeElement, RemoteObjectProperty)} treeElementConstructor + * @param {function (RemoteObjectProperty, RemoteObjectProperty): number} comparator + * @param {boolean} skipProto + * @param {?RemoteObject} value + */ +ObjectPropertyTreeElement.populateWithProperties = function(treeElement, properties, internalProperties, treeElementConstructor, comparator, skipProto, value) { + properties.sort(comparator); + + for (var i = 0; i < properties.length; ++i) { + var property = properties[i]; + if (skipProto && property.name === "__proto__") + continue; + if (property.isAccessorProperty()) { + if (property.name !== "__proto__" && property.getter) { + property.parentObject = value; + treeElement.appendChild(new treeElementConstructor(property)); + } + if (property.isOwn) { + if (property.getter) { + var getterProperty = new RemoteObjectProperty("get " + property.name, property.getter); + getterProperty.parentObject = value; + treeElement.appendChild(new treeElementConstructor(getterProperty)); + } + if (property.setter) { + var setterProperty = new RemoteObjectProperty("set " + property.name, property.setter); + setterProperty.parentObject = value; + treeElement.appendChild(new treeElementConstructor(setterProperty)); + } + } + } else { + property.parentObject = value; + treeElement.appendChild(new treeElementConstructor(property)); + } + } + if (value && value.type === "function") { + // Whether function has TargetFunction internal property. + // This is a simple way to tell that the function is actually a bound function (we are not told). + // Bound function never has inner scope and doesn't need corresponding UI node. + var hasTargetFunction = false; + + if (internalProperties) { + for (var i = 0; i < internalProperties.length; i++) { + if (internalProperties[i].name == "[[TargetFunction]]") { + hasTargetFunction = true; + break; + } + } + } + if (!hasTargetFunction) + treeElement.appendChild(new FunctionScopeMainTreeElement(value)); + } + if (internalProperties) { + for (var i = 0; i < internalProperties.length; i++) { + internalProperties[i].parentObject = value; + treeElement.appendChild(new treeElementConstructor(internalProperties[i])); + } + } +} + +/** + * @constructor + * @extends {TreeElement} + * @param {RemoteObject} remoteObject + */ +function FunctionScopeMainTreeElement(remoteObject) +{ + TreeElement.call(this, "", null, false); + this.toggleOnClick = true; + this.selectable = false; + this._remoteObject = remoteObject; + this.hasChildren = true; +} + +FunctionScopeMainTreeElement.prototype = { + onpopulate: function() + { + if (this.children.length && !this.shouldRefreshChildren) + return; + + function didGetDetails(error, response) + { + if (error) { + console.error(error); + return; + } + this.removeChildren(); + + var scopeChain = response.scopeChain; + if (!scopeChain) + return; + for (var i = 0; i < scopeChain.length; ++i) { + var scope = scopeChain[i]; + var title = null; + var isTrueObject; + + switch (scope.type) { + case "local": + // Not really expecting this scope type here. + title = UIString("Local"); + isTrueObject = false; + break; + case "closure": + title = UIString("Closure"); + isTrueObject = false; + break; + case "catch": + title = UIString("Catch"); + isTrueObject = false; + break; + case "with": + title = UIString("With Block"); + isTrueObject = true; + break; + case "global": + title = UIString("Global"); + isTrueObject = true; + break; + default: + console.error("Unknown scope type: " + scope.type); + continue; + } + + var scopeRef; + if (isTrueObject) + scopeRef = undefined; + else + scopeRef = new ScopeRef(i, undefined, this._remoteObject.objectId); + + var remoteObject = ScopeRemoteObject.fromPayload(scope.object, scopeRef); + if (isTrueObject) { + var property = RemoteObjectProperty.fromScopeValue(title, remoteObject); + property.parentObject = null; + this.appendChild(new this.treeOutline.section.treeElementConstructor(property)); + } else { + var scopeTreeElement = new ScopeTreeElement(title, null, remoteObject); + this.appendChild(scopeTreeElement); + } + } + + } + DebuggerAgent.getFunctionDetails(this._remoteObject.objectId, didGetDetails.bind(this)); + }, + + __proto__: TreeElement.prototype +} + +/** + * @constructor + * @extends {TreeElement} + * @param {RemoteObject} remoteObject + */ +function ScopeTreeElement(title, subtitle, remoteObject) +{ + // TODO: use subtitle parameter. + TreeElement.call(this, title, null, false); + this.toggleOnClick = true; + this.selectable = false; + this._remoteObject = remoteObject; + this.hasChildren = true; +} + +ScopeTreeElement.prototype = { + onpopulate: function() + { + return ObjectPropertyTreeElement.populate(this, this._remoteObject); + }, + + __proto__: TreeElement.prototype +} + +/** + * @constructor + * @extends {TreeElement} + * @param {RemoteObject} object + * @param {number} fromIndex + * @param {number} toIndex + * @param {number} propertyCount + */ +function ArrayGroupingTreeElement(object, fromIndex, toIndex, propertyCount) +{ + TreeElement.call(this, String.sprintf("[%d \u2026 %d]", fromIndex, toIndex), undefined, true); + this._fromIndex = fromIndex; + this._toIndex = toIndex; + this._object = object; + this._readOnly = true; + this._propertyCount = propertyCount; + this._populated = false; +} + +ArrayGroupingTreeElement._bucketThreshold = 100; +ArrayGroupingTreeElement._sparseIterationThreshold = 250000; + +/** + * @param {TreeElement|TreeOutline} treeElement + * @param {RemoteObject} object + * @param {number} fromIndex + * @param {number} toIndex + */ +ArrayGroupingTreeElement._populateArray = function(treeElement, object, fromIndex, toIndex) +{ + ArrayGroupingTreeElement._populateRanges(treeElement, object, fromIndex, toIndex, true); +} + +/** + * @param {TreeElement|TreeOutline} treeElement + * @param {RemoteObject} object + * @param {number} fromIndex + * @param {number} toIndex + * @param {boolean} topLevel + */ +ArrayGroupingTreeElement._populateRanges = function(treeElement, object, fromIndex, toIndex, topLevel) +{ + object.callFunctionJSON(packRanges, [{value: fromIndex}, {value: toIndex}, {value: ArrayGroupingTreeElement._bucketThreshold}, {value: ArrayGroupingTreeElement._sparseIterationThreshold}], callback.bind(this)); + + /** + * @this {Object} + * @param {number=} fromIndex // must declare optional + * @param {number=} toIndex // must declare optional + * @param {number=} bucketThreshold // must declare optional + * @param {number=} sparseIterationThreshold // must declare optional + */ + function packRanges(fromIndex, toIndex, bucketThreshold, sparseIterationThreshold) + { + var ownPropertyNames = null; + function doLoop(iterationCallback) + { + if (toIndex - fromIndex < sparseIterationThreshold) { + for (var i = fromIndex; i <= toIndex; ++i) { + if (i in this) + iterationCallback(i); + } + } else { + ownPropertyNames = ownPropertyNames || Object.getOwnPropertyNames(this); + for (var i = 0; i < ownPropertyNames.length; ++i) { + var name = ownPropertyNames[i]; + var index = name >>> 0; + if (String(index) === name && fromIndex <= index && index <= toIndex) + iterationCallback(index); + } + } + } + + var count = 0; + function countIterationCallback() + { + ++count; + } + doLoop.call(this, countIterationCallback); + + var bucketSize = count; + if (count <= bucketThreshold) + bucketSize = count; + else + bucketSize = Math.pow(bucketThreshold, Math.ceil(Math.log(count) / Math.log(bucketThreshold)) - 1); + + var ranges = []; + count = 0; + var groupStart = -1; + var groupEnd = 0; + function loopIterationCallback(i) + { + if (groupStart === -1) + groupStart = i; + + groupEnd = i; + if (++count === bucketSize) { + ranges.push([groupStart, groupEnd, count]); + count = 0; + groupStart = -1; + } + } + doLoop.call(this, loopIterationCallback); + + if (count > 0) + ranges.push([groupStart, groupEnd, count]); + return ranges; + } + + function callback(ranges) + { + if (ranges.length == 1) + ArrayGroupingTreeElement._populateAsFragment(treeElement, object, ranges[0][0], ranges[0][1]); + else { + for (var i = 0; i < ranges.length; ++i) { + var fromIndex = ranges[i][0]; + var toIndex = ranges[i][1]; + var count = ranges[i][2]; + if (fromIndex == toIndex) + ArrayGroupingTreeElement._populateAsFragment(treeElement, object, fromIndex, toIndex); + else + treeElement.appendChild(new ArrayGroupingTreeElement(object, fromIndex, toIndex, count)); + } + } + if (topLevel) + ArrayGroupingTreeElement._populateNonIndexProperties(treeElement, object); + } +} + +/** + * @param {TreeElement|TreeOutline} treeElement + * @param {RemoteObject} object + * @param {number} fromIndex + * @param {number} toIndex + */ +ArrayGroupingTreeElement._populateAsFragment = function(treeElement, object, fromIndex, toIndex) +{ + object.callFunction(buildArrayFragment, [{value: fromIndex}, {value: toIndex}, {value: ArrayGroupingTreeElement._sparseIterationThreshold}], processArrayFragment.bind(this)); + + /** + * @this {Object} + * @param {number=} fromIndex // must declare optional + * @param {number=} toIndex // must declare optional + * @param {number=} sparseIterationThreshold // must declare optional + */ + function buildArrayFragment(fromIndex, toIndex, sparseIterationThreshold) + { + var result = Object.create(null); + if (toIndex - fromIndex < sparseIterationThreshold) { + for (var i = fromIndex; i <= toIndex; ++i) { + if (i in this) + result[i] = this[i]; + } + } else { + var ownPropertyNames = Object.getOwnPropertyNames(this); + for (var i = 0; i < ownPropertyNames.length; ++i) { + var name = ownPropertyNames[i]; + var index = name >>> 0; + if (String(index) === name && fromIndex <= index && index <= toIndex) + result[index] = this[index]; + } + } + return result; + } + + /** @this {ArrayGroupingTreeElement} */ + function processArrayFragment(arrayFragment) + { + arrayFragment.getAllProperties(false, processProperties.bind(this)); + } + + /** @this {ArrayGroupingTreeElement} */ + function processProperties(properties, internalProperties) + { + if (!properties) + return; + + properties.sort(ObjectPropertiesSection.CompareProperties); + for (var i = 0; i < properties.length; ++i) { + properties[i].parentObject = this._object; + var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]); + childTreeElement._readOnly = true; + treeElement.appendChild(childTreeElement); + } + } +} + +/** + * @param {TreeElement|TreeOutline} treeElement + * @param {RemoteObject} object + */ +ArrayGroupingTreeElement._populateNonIndexProperties = function(treeElement, object) +{ + object.callFunction(buildObjectFragment, undefined, processObjectFragment.bind(this)); + + /** @this {Object} */ + function buildObjectFragment() + { + var result = Object.create(this.__proto__); + var names = Object.getOwnPropertyNames(this); + for (var i = 0; i < names.length; ++i) { + var name = names[i]; + // Array index check according to the ES5-15.4. + if (String(name >>> 0) === name && name >>> 0 !== 0xffffffff) + continue; + var descriptor = Object.getOwnPropertyDescriptor(this, name); + if (descriptor) + Object.defineProperty(result, name, descriptor); + } + return result; + } + + function processObjectFragment(arrayFragment) + { + arrayFragment.getOwnProperties(processProperties.bind(this)); + } + + /** @this {ArrayGroupingTreeElement} */ + function processProperties(properties, internalProperties) + { + if (!properties) + return; + + properties.sort(ObjectPropertiesSection.CompareProperties); + for (var i = 0; i < properties.length; ++i) { + properties[i].parentObject = this._object; + var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]); + childTreeElement._readOnly = true; + treeElement.appendChild(childTreeElement); + } + } +} + +ArrayGroupingTreeElement.prototype = { + onpopulate: function() + { + if (this._populated) + return; + + this._populated = true; + + if (this._propertyCount >= ArrayGroupingTreeElement._bucketThreshold) { + ArrayGroupingTreeElement._populateRanges(this, this._object, this._fromIndex, this._toIndex, false); + return; + } + ArrayGroupingTreeElement._populateAsFragment(this, this._object, this._fromIndex, this._toIndex); + }, + + onattach: function() + { + this.listItemElement.addStyleClass("name"); + }, + + __proto__: TreeElement.prototype +} + +/** + * @constructor + * @extends {TextPrompt} + * @param {boolean=} renderAsBlock + */ +function ObjectPropertyPrompt(commitHandler, cancelHandler, renderAsBlock) +{ + //x TextPrompt.call(this, WebInspector.runtimeModel.completionsForTextPrompt.bind(WebInspector.runtimeModel)); + TextPrompt.call(this, function(){}); + this.setSuggestBoxEnabled("generic-suggest"); + if (renderAsBlock) + this.renderAsBlock(); +} + +ObjectPropertyPrompt.prototype = { + __proto__: TextPrompt.prototype +} + +exports.ObjectPropertiesSection = ObjectPropertiesSection; +exports.ObjectPropertyTreeElement = ObjectPropertyTreeElement; +}); diff --git a/interpreter/debugger/client/Panel.js b/interpreter/debugger/client/Panel.js index a5fdd36..a03dc1e 100644 --- a/interpreter/debugger/client/Panel.js +++ b/interpreter/debugger/client/Panel.js @@ -1,15 +1,181 @@ define(function(require, exports){ + var View = require("View"); + var inherits = require("util").inherits; + var SidebarView = require("SidebarView"); - exports.codeEditor = null; - exports.toolbar = null; + var KeyboardShortcut = require("KeyboardShortcut"); - exports.setReSources = function(sources){ - this.codeEditor.setSources(sources); + function Panel(name){ + View.call(this); - }; + this._panelName = name; + this.element.addStyleClass("panel"); + this.element.addStyleClass(name); - exports.debuggerPaused = function(executeLine, scopeChain, watchExpressions){ - this.codeEditor.pausedDetail(executeLine); - }; + this._shortcuts = /** !Object. */ ({}); + } + inherits(Panel, View); + + Panel.prototype.createSidebarView = function(parentElement, position, defaultWidth, defaultHeight) { + if (this.splitView) + return; + + if (!parentElement) + parentElement = this.element; + + this.splitView = new SidebarView(position, this._sidebarWidthSettingName(), defaultWidth, defaultHeight); + this.splitView.show(parentElement); + this.splitView.addEventListener(SidebarView.Events.Resized, this.sidebarResized.bind(this)); + + this.sidebarElement = this.splitView.sidebarElement; + } + + Panel.prototype._sidebarWidthSettingName = function() { + return this._panelName + "SidebarWidth"; + } + + + // Should be implemented by ancestors. + + Panel.prototype.getStatusBarItems = function(event) + { + } + + /** + * @param {Event} event + */ + Panel.prototype.sidebarResized = function(event) + { + } + + Panel.prototype.statusBarResized = function() + { + } + + + Panel.prototype.show = function() + { + View.prototype.show.call(this, CSInspector.inspectorView.panelsElement()); + } + + Panel.prototype.wasShown = function() + { + var panelStatusBar = document.getElementById("panel-status-bar") + var drawerViewAnchor = document.getElementById("drawer-view-anchor"); + var statusBarItems = this.getStatusBarItems(); + if (statusBarItems) { + this._statusBarItemContainer = document.createElement("div"); + for (var i = 0; i < statusBarItems.length; ++i) + this._statusBarItemContainer.appendChild(statusBarItems[i]); + panelStatusBar.insertBefore(this._statusBarItemContainer, drawerViewAnchor); + } + var statusBarText = this.statusBarText(); + if (statusBarText) { + this._statusBarTextElement = statusBarText; + panelStatusBar.appendChild(statusBarText); + } + + this.focus(); + } + + Panel.prototype.willHide = function() + { + if (this._statusBarItemContainer) + this._statusBarItemContainer.remove(); + delete this._statusBarItemContainer; + + if (this._statusBarTextElement) + this._statusBarTextElement.remove(); + delete this._statusBarTextElement; + } + + Panel.prototype.reset = function() + { + this.searchCanceled(); + } + + Panel.prototype.defaultFocusedElement = function() + { + return this.sidebarTreeElement || this.element; + } + + + + /** + * @param {KeyboardEvent} event + */ + Panel.prototype.handleShortcut = function(event) + { + var shortcutKey = KeyboardShortcut.makeKeyFromEvent(event); + var handler = this._shortcuts[shortcutKey]; + if (handler && handler(event)) + event.handled = true; + } + + /** + * @param {!Array.} keys + * @param {function(Event=):boolean} handler + */ + Panel.prototype.registerShortcuts = function(keys, handler) + { + for (var i = 0; i < keys.length; ++i) + this._shortcuts[keys[i].key] = handler; + } + + + /** + * @constructor + * @param {string} name + * @param {string} title + * @param {string=} className + * @param {string=} scriptName + * @param {Panel=} panel + */ + function PanelDescriptor(name, title, className, scriptName, panel) + { + this._name = name; + this._title = title; + this._className = className; + this._scriptName = scriptName; + this._panel = panel; + } + + PanelDescriptor.prototype = { + /** + * @return {string} + */ + name: function() + { + return this._name; + }, + + /** + * @return {string} + */ + title: function() + { + return this._title; + }, + + /** + * @return {Panel} + */ + panel: function() + { + /* + if (this._panel) + return this._panel; + if (this._scriptName) + loadScript(this._scriptName); + this._panel = new WebInspector[this._className]; + */ + return this._panel; + }, + + registerShortcuts: function() {} + } + + exports.Panel = Panel; + exports.PanelDescriptor = PanelDescriptor; }); diff --git a/interpreter/debugger/client/ParsedURL.js b/interpreter/debugger/client/ParsedURL.js new file mode 100644 index 0000000..cc96e8c --- /dev/null +++ b/interpreter/debugger/client/ParsedURL.js @@ -0,0 +1,229 @@ +/* + * 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: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS 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 GOOGLE INC. + * OR ITS 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. + */ +define(function(){ +/** + * @constructor + * @param {string} url + */ +function ParsedURL(url) +{ + this.isValid = false; + this.url = url; + this.scheme = ""; + this.host = ""; + this.port = ""; + this.path = ""; + this.queryParams = ""; + this.fragment = ""; + this.folderPathComponents = ""; + this.lastPathComponent = ""; + + // RegExp groups: + // 1 - scheme (using the RFC3986 grammar) + // 2 - hostname + // 3 - ?port + // 4 - ?path + // 5 - ?fragment + var match = url.match(/^([A-Za-z][A-Za-z0-9+.-]*):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i); + if (match) { + this.isValid = true; + this.scheme = match[1].toLowerCase(); + this.host = match[2]; + this.port = match[3]; + this.path = match[4] || "/"; + this.fragment = match[5]; + } else { + if (this.url.startsWith("data:")) { + this.scheme = "data"; + return; + } + if (this.url === "about:blank") { + this.scheme = "about"; + return; + } + this.path = this.url; + } + + // First cut the query params. + var path = this.path; + var indexOfQuery = path.indexOf("?"); + if (indexOfQuery !== -1) { + this.queryParams = path.substring(indexOfQuery + 1) + path = path.substring(0, indexOfQuery); + } + + // Then take last path component. + var lastSlashIndex = path.lastIndexOf("/"); + if (lastSlashIndex !== -1) { + this.folderPathComponents = path.substring(0, lastSlashIndex); + this.lastPathComponent = path.substring(lastSlashIndex + 1); + } else + this.lastPathComponent = path; +} + +/** + * @param {string} url + * @return {Array.} + */ +ParsedURL.splitURL = function(url) +{ + var parsedURL = new ParsedURL(url); + var origin; + var folderPath; + var name; + if (parsedURL.isValid) { + origin = parsedURL.scheme + "://" + parsedURL.host; + if (parsedURL.port) + origin += ":" + parsedURL.port; + folderPath = parsedURL.folderPathComponents; + name = parsedURL.lastPathComponent; + if (parsedURL.queryParams) + name += "?" + parsedURL.queryParams; + } else { + origin = ""; + folderPath = ""; + name = url; + } + var result = [origin]; + var splittedPath = folderPath.split("/"); + for (var i = 1; i < splittedPath.length; ++i) + result.push(splittedPath[i]); + result.push(name); + return result; +} + +/** + * @param {string} baseURL + * @param {string} href + * @return {?string} + */ +ParsedURL.completeURL = function(baseURL, href) +{ + if (href) { + // Return special URLs as-is. + var trimmedHref = href.trim(); + if (trimmedHref.startsWith("data:") || trimmedHref.startsWith("blob:") || trimmedHref.startsWith("javascript:")) + return href; + + // Return absolute URLs as-is. + var parsedHref = trimmedHref.asParsedURL(); + if (parsedHref && parsedHref.scheme) + return trimmedHref; + } else + return baseURL; + + var parsedURL = baseURL.asParsedURL(); + if (parsedURL) { + if (parsedURL.isDataURL()) + return href; + var path = href; + if (path.charAt(0) !== "/") { + var basePath = parsedURL.path; + + // Trim off the query part of the basePath. + var questionMarkIndex = basePath.indexOf("?"); + if (questionMarkIndex > 0) + basePath = basePath.substring(0, questionMarkIndex); + // A href of "?foo=bar" implies "basePath?foo=bar". + // With "basePath?a=b" and "?foo=bar" we should get "basePath?foo=bar". + var prefix; + if (path.charAt(0) === "?") { + var basePathCutIndex = basePath.indexOf("?"); + if (basePathCutIndex !== -1) + prefix = basePath.substring(0, basePathCutIndex); + else + prefix = basePath; + } else + prefix = basePath.substring(0, basePath.lastIndexOf("/")) + "/"; + + path = prefix + path; + } else if (path.length > 1 && path.charAt(1) === "/") { + // href starts with "//" which is a full URL with the protocol dropped (use the baseURL protocol). + return parsedURL.scheme + ":" + path; + } + return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + path; + } + return null; +} + +ParsedURL.prototype = { + get displayName() + { + if (this._displayName) + return this._displayName; + + if (this.isDataURL()) + return this.dataURLDisplayName(); + if (this.isAboutBlank()) + return this.url; + + this._displayName = this.lastPathComponent; + if (!this._displayName && this.host) + this._displayName = this.host + "/"; + if (!this._displayName && this.url) + this._displayName = this.url.trimURL(WebInspector.inspectedPageDomain ? WebInspector.inspectedPageDomain : ""); + if (this._displayName === "/") + this._displayName = this.url; + return this._displayName; + }, + + dataURLDisplayName: function() + { + if (this._dataURLDisplayName) + return this._dataURLDisplayName; + if (!this.isDataURL()) + return ""; + this._dataURLDisplayName = this.url.trimEnd(20); + return this._dataURLDisplayName; + }, + + isAboutBlank: function() + { + return this.url === "about:blank"; + }, + + isDataURL: function() + { + return this.scheme === "data"; + } +} + +/** + * @return {?ParsedURL} + */ +String.prototype.asParsedURL = function() +{ + var parsedURL = new ParsedURL(this.toString()); + if (parsedURL.isValid) + return parsedURL; + return null; +} + +return ParsedURL; + +}); diff --git a/interpreter/debugger/client/Placard.js b/interpreter/debugger/client/Placard.js new file mode 100644 index 0000000..1747300 --- /dev/null +++ b/interpreter/debugger/client/Placard.js @@ -0,0 +1,122 @@ +define(function (require, exports) { +/* + * Copyright (C) 2008 Apple 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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 + * @param {string} title + * @param {string} subtitle + */ +function Placard(title, subtitle) +{ + this.element = document.createElement("div"); + this.element.className = "placard"; + this.element.placard = this; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "title"; + + this.subtitleElement = document.createElement("div"); + this.subtitleElement.className = "subtitle"; + + this.element.appendChild(this.subtitleElement); + this.element.appendChild(this.titleElement); + + this.title = title; + this.subtitle = subtitle; + this.selected = false; +} + +Placard.prototype = { + /** @return {string} */ + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + this.titleElement.textContent = x; + }, + + /** @return {string} */ + get subtitle() + { + return this._subtitle; + }, + + set subtitle(x) + { + if (this._subtitle === x) + return; + this._subtitle = x; + this.subtitleElement.textContent = x; + }, + + /** @return {boolean} */ + get selected() + { + return this._selected; + }, + + set selected(x) + { + if (x) + this.select(); + else + this.deselect(); + }, + + select: function() + { + if (this._selected) + return; + this._selected = true; + this.element.addStyleClass("selected"); + }, + + deselect: function() + { + if (!this._selected) + return; + this._selected = false; + this.element.removeStyleClass("selected"); + }, + + toggleSelected: function() + { + this.selected = !this.selected; + }, + + discard: function() + { + } +} + +return Placard; +}); diff --git a/interpreter/debugger/client/ProjectDelegate.js b/interpreter/debugger/client/ProjectDelegate.js new file mode 100644 index 0000000..8f84e8f --- /dev/null +++ b/interpreter/debugger/client/ProjectDelegate.js @@ -0,0 +1,117 @@ +define(function(){ + +/** + * @interface + * @extends {EventTarget} + */ +function ProjectDelegate() { } + +ProjectDelegate.Events = { + FileAdded: "FileAdded", + FileRemoved: "FileRemoved", + Reset: "Reset", +} + +ProjectDelegate.prototype = { + /** + * @return {string} + */ + id: function() { }, + + /** + * @return {string} + */ + type: function() { }, + + /** + * @return {string} + */ + displayName: function() { }, + + /** + * @param {string} path + * @param {function(?Date, ?number)} callback + */ + requestMetadata: function(path, callback) { }, + + /** + * @param {string} path + * @param {function(?string,boolean,string)} callback + */ + requestFileContent: function(path, callback) { }, + + /** + * @return {boolean} + */ + canSetFileContent: function() { }, + + /** + * @param {string} path + * @param {string} newContent + * @param {function(?string)} callback + */ + setFileContent: function(path, newContent, callback) { }, + + /** + * @return {boolean} + */ + canRename: function() { }, + + /** + * @param {string} path + * @param {string} newName + * @param {function(boolean, string=)} callback + */ + rename: function(path, newName, callback) { }, + + /** + * @param {string} path + */ + refresh: function(path) { }, + + /** + * @param {string} path + */ + excludeFolder: function(path) { }, + + /** + * @param {string} path + * @param {?string} name + * @param {function(?string)} callback + */ + createFile: function(path, name, callback) { }, + + /** + * @param {string} path + */ + deleteFile: function(path) { }, + + remove: function() { }, + + /** + * @param {string} path + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInFileContent: function(path, query, caseSensitive, isRegex, callback) { }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {Progress} progress + * @param {function(StringMap)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, progress, callback) { }, + + /** + * @param {Progress} progress + * @param {function()} callback + */ + indexContent: function(progress, callback) { } +} + +return ProjectDelegate; +}); diff --git a/interpreter/debugger/client/PropertiesSection.js b/interpreter/debugger/client/PropertiesSection.js new file mode 100644 index 0000000..8338fb7 --- /dev/null +++ b/interpreter/debugger/client/PropertiesSection.js @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * Copyright (C) 2009 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: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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. + */ +define(function(require){ + +var Section = require("Section"); +var TreeOutline = require("treeoutline").TreeOutline ; +/** + * @constructor + * @extends {Section} + * @param {string|Element} title + * @param {string=} subtitle + */ +function PropertiesSection(title, subtitle) +{ + Section.call(this, title, subtitle); + + this.headerElement.addStyleClass("monospace"); + this.propertiesElement = document.createElement("ol"); + this.propertiesElement.className = "properties properties-tree monospace"; + this.propertiesTreeOutline = new TreeOutline(this.propertiesElement, true); + this.propertiesTreeOutline.setFocusable(false); + this.propertiesTreeOutline.section = this; + + this.element.appendChild(this.propertiesElement); +} + +PropertiesSection.prototype = { + __proto__: Section.prototype +} + +return PropertiesSection; + +}); diff --git a/interpreter/debugger/client/RemoteObject.js b/interpreter/debugger/client/RemoteObject.js new file mode 100644 index 0000000..1df44e3 --- /dev/null +++ b/interpreter/debugger/client/RemoteObject.js @@ -0,0 +1,800 @@ +/* + * Copyright (C) 2009 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. + */ +define(function(require, exports){ +/** + * @constructor + * @param {string|undefined} objectId + * @param {string} type + * @param {string|undefined} subtype + * @param {*} value + * @param {string=} description + * @param {RuntimeAgent.ObjectPreview=} preview + */ +function RemoteObject(objectId, type, subtype, value, description, preview) +{ + this._type = type; + this._subtype = subtype; + if (objectId) { + // handle + this._objectId = objectId; + this._description = description; + this._hasChildren = true; + this._preview = preview; + } else { + // Primitive or null object. + console.assert(type !== "object" || value === null); + this._description = description || (value + ""); + this._hasChildren = false; + this.value = value; + } +} + +/** + * @param {number|string|boolean} value + * @return {RemoteObject} + */ +RemoteObject.fromPrimitiveValue = function(value) +{ + return new RemoteObject(undefined, typeof value, undefined, value); +} + +/** + * @param {*} value + * @return {RemoteObject} + */ +RemoteObject.fromLocalObject = function(value) +{ + return new LocalJSONObject(value); +} + +/** + * @param {DOMNode} node + * @param {string} objectGroup + * @param {function(?RemoteObject)} callback + */ +RemoteObject.resolveNode = function(node, objectGroup, callback) +{ + /** + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} object + */ + function mycallback(error, object) + { + if (!callback) + return; + + if (error || !object) + callback(null); + else + callback(RemoteObject.fromPayload(object)); + } + DOMAgent.resolveNode(node.id, objectGroup, mycallback); +} + +/** + * @param {RuntimeAgent.RemoteObject=} payload + * @return {RemoteObject} + */ +RemoteObject.fromPayload = function(payload) +{ + console.assert(typeof payload === "object", "Remote object payload should only be an object"); + + return new RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview); +} + +/** + * @param {RemoteObject} remoteObject + * @return {string} + */ +RemoteObject.type = function(remoteObject) +{ + if (remoteObject === null) + return "null"; + + var type = typeof remoteObject; + if (type !== "object" && type !== "function") + return type; + + return remoteObject.type; +} + +RemoteObject.prototype = { + /** @return {RuntimeAgent.RemoteObjectId} */ + get objectId() + { + return this._objectId; + }, + + /** @return {string} */ + get type() + { + return this._type; + }, + + /** @return {string|undefined} */ + get subtype() + { + return this._subtype; + }, + + /** @return {string|undefined} */ + get description() + { + return this._description; + }, + + /** @return {boolean} */ + get hasChildren() + { + return this._hasChildren; + }, + + /** @return {RuntimeAgent.ObjectPreview|undefined} */ + get preview() + { + return this._preview; + }, + + /** + * @param {function(Array., Array.=)} callback + */ + getOwnProperties: function(callback) + { + this.doGetProperties(true, false, callback); + }, + + /** + * @param {boolean} accessorPropertiesOnly + * @param {function(?Array., ?Array.)} callback + */ + getAllProperties: function(accessorPropertiesOnly, callback) + { + this.doGetProperties(false, accessorPropertiesOnly, callback); + }, + + /** + * @param {boolean} ownProperties + * @param {boolean} accessorPropertiesOnly + * @param {?function(Array., ?Array.)} callback + */ + doGetProperties: function(ownProperties, accessorPropertiesOnly, callback) + { + if (!this._objectId) { + callback([], null); + return; + } + + /** + * @param {?Protocol.Error} error + * @param {Array.=} properties + * @param {Array.=} internalProperties + */ + function remoteObjectBinder(error, properties, internalProperties) + { + if (error) { + callback(null, null); + return; + } + var result = []; + for (var i = 0; properties && i < properties.length; ++i) { + var property = properties[i]; + result.push(new RemoteObjectProperty(property.name, null, property)); + } + var internalPropertiesResult = null; + if (internalProperties) { + internalPropertiesResult = []; + for (var i = 0; i < internalProperties.length; i++) { + var property = internalProperties[i]; + internalPropertiesResult.push(new RemoteObjectProperty(property.name, RemoteObject.fromPayload(property.value))); + } + } + callback(result, internalPropertiesResult); + } + CSInspector.debugAgent.getProperties(this._objectId, ownProperties, accessorPropertiesOnly, remoteObjectBinder); + }, + + /** + * @param {string} name + * @param {string} value + * @param {function(string=)} callback + */ + setPropertyValue: function(name, value, callback) + { + if (!this._objectId) { + callback("Can't set a property of non-object."); + return; + } + + RuntimeAgent.evaluate.invoke({expression:value, doNotPauseOnExceptionsAndMuteConsole:true}, evaluatedCallback.bind(this)); + + /** + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} result + * @param {boolean=} wasThrown + */ + function evaluatedCallback(error, result, wasThrown) + { + if (error || wasThrown) { + callback(error || result.description); + return; + } + + this.doSetObjectPropertyValue(result, name, callback); + + if (result.objectId) + RuntimeAgent.releaseObject(result.objectId); + } + }, + + /** + * @param {RuntimeAgent.RemoteObject} result + * @param {string} name + * @param {function(string=)} callback + */ + doSetObjectPropertyValue: function(result, name, callback) + { + // This assignment may be for a regular (data) property, and for an acccessor property (with getter/setter). + // Note the sensitive matter about accessor property: the property may be physically defined in some proto object, + // but logically it is bound to the object in question. JavaScript passes this object to getters/setters, not the object + // where property was defined; so do we. + var setPropertyValueFunction = "function(a, b) { this[a] = b; }"; + + // Special case for NaN, Infinity and -Infinity + if (result.type === "number" && typeof result.value !== "number") + setPropertyValueFunction = "function(a) { this[a] = " + result.description + "; }"; + + delete result.description; // Optimize on traffic. + RuntimeAgent.callFunctionOn(this._objectId, setPropertyValueFunction, [{ value:name }, result], true, undefined, undefined, propertySetCallback.bind(this)); + + /** + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} result + * @param {boolean=} wasThrown + */ + function propertySetCallback(error, result, wasThrown) + { + if (error || wasThrown) { + callback(error || result.description); + return; + } + callback(); + } + }, + + /** + * @param {function(?DOMAgent.NodeId)} callback + */ + pushNodeToFrontend: function(callback) + { + if (this._objectId) + domAgent.pushNodeToFrontend(this._objectId, callback); + else + callback(0); + }, + + highlightAsDOMNode: function() + { + domAgent.highlightDOMNode(undefined, undefined, this._objectId); + }, + + hideDOMNodeHighlight: function() + { + domAgent.hideDOMNodeHighlight(); + }, + + /** + * @param {function(this:Object)} functionDeclaration + * @param {Array.=} args + * @param {function(?RemoteObject)=} callback + */ + callFunction: function(functionDeclaration, args, callback) + { + /** + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} result + * @param {boolean=} wasThrown + */ + function mycallback(error, result, wasThrown) + { + if (!callback) + return; + + callback((error || wasThrown) ? null : RemoteObject.fromPayload(result)); + } + + RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, undefined, undefined, mycallback); + }, + + /** + * @param {function(this:Object)} functionDeclaration + * @param {Array.|undefined} args + * @param {function(*)} callback + */ + callFunctionJSON: function(functionDeclaration, args, callback) + { + /** + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} result + * @param {boolean=} wasThrown + */ + function mycallback(error, result, wasThrown) + { + callback((error || wasThrown) ? null : result.value); + } + + RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, true, false, mycallback); + }, + + release: function() + { + if (!this._objectId) + return; + RuntimeAgent.releaseObject(this._objectId); + }, + + /** + * @return {number} + */ + arrayLength: function() + { + if (this.subtype !== "array") + return 0; + + var matches = this._description.match(/\[([0-9]+)\]/); + if (!matches) + return 0; + return parseInt(matches[1], 10); + } +}; + + +/** + * @param {RemoteObject} object + * @param {boolean} flattenProtoChain + * @param {function(?Array., ?Array.)} callback + */ +RemoteObject.loadFromObject = function(object, flattenProtoChain, callback) +{ + if (flattenProtoChain) + object.getAllProperties(false, callback); + else + RemoteObject.loadFromObjectPerProto(object, callback); +}; + +/** + * @param {RemoteObject} object + * @param {function(?Array., ?Array.)} callback + */ +RemoteObject.loadFromObjectPerProto = function(object, callback) +{ + // Combines 2 asynch calls. Doesn't rely on call-back orders (some calls may be loop-back). + var savedOwnProperties; //hdf没有ownProperties的区分 + var savedAccessorProperties = []; + var savedInternalProperties; + var resultCounter = 1; //hdf没有ownProperties的区分 + + function processCallback() + { + if (--resultCounter) + return; + if (savedOwnProperties && savedAccessorProperties) { + var combinedList = savedAccessorProperties.slice(0); + for (var i = 0; i < savedOwnProperties.length; i++) { + var property = savedOwnProperties[i]; + if (!property.isAccessorProperty()) + combinedList.push(property); + } + return callback(combinedList, savedInternalProperties ? savedInternalProperties : null); + } else { + callback(null, null); + } + } + + /** + * @param {Array.} properties + * @param {Array.=} internalProperties + */ + function allAccessorPropertiesCallback(properties, internalProperties) + { + savedAccessorProperties = properties; + processCallback(); + } + + /** + * @param {Array.} properties + * @param {Array.=} internalProperties + */ + function ownPropertiesCallback(properties, internalProperties) + { + savedOwnProperties = properties; + savedInternalProperties = internalProperties; + processCallback(); + } + + //x 重复了(对hdf来说) object.getAllProperties(true, allAccessorPropertiesCallback); + object.getOwnProperties(ownPropertiesCallback);//hdf所以child相当于ownProperties +}; + + +/** + * @constructor + * @extends {RemoteObject} + * @param {string|undefined} objectId + * @param {ScopeRef} scopeRef + * @param {string} type + * @param {string|undefined} subtype + * @param {*} value + * @param {string=} description + * @param {RuntimeAgent.ObjectPreview=} preview + */ +function ScopeRemoteObject(objectId, scopeRef, type, subtype, value, description, preview) +{ + RemoteObject.call(this, objectId, type, subtype, value, description, preview); + this._scopeRef = scopeRef; + this._savedScopeProperties = undefined; +}; + +/** + * @param {RuntimeAgent.RemoteObject} payload + * @param {ScopeRef=} scopeRef + * @return {RemoteObject} + */ +ScopeRemoteObject.fromPayload = function(payload, scopeRef) +{ + if (scopeRef) + return new ScopeRemoteObject(payload.objectId, scopeRef, payload.type, payload.subtype, payload.value, payload.description, payload.preview); + else + return new RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview); +} + +ScopeRemoteObject.prototype = { + /** + * @param {boolean} ownProperties + * @param {boolean} accessorPropertiesOnly + * @param {function(Array., Array.=)} callback + * @override + */ + doGetProperties: function(ownProperties, accessorPropertiesOnly, callback) + { + if (accessorPropertiesOnly) { + callback([], []); + return; + } + if (this._savedScopeProperties) { + // No need to reload scope variables, as the remote object never + // changes its properties. If variable is updated, the properties + // array is patched locally. + callback(this._savedScopeProperties.slice(), []); + return; + } + + /** + * @param {Array.} properties + * @param {Array.=} internalProperties + */ + function wrappedCallback(properties, internalProperties) + { + if (this._scopeRef && properties instanceof Array) + this._savedScopeProperties = properties.slice(); + callback(properties, internalProperties); + } + + RemoteObject.prototype.doGetProperties.call(this, ownProperties, accessorPropertiesOnly, wrappedCallback.bind(this)); + }, + + /** + * @override + * @param {RuntimeAgent.RemoteObject} result + * @param {string} name + * @param {function(string=)} callback + */ + doSetObjectPropertyValue: function(result, name, callback) + { + var newValue; + + switch (result.type) { + case "undefined": + newValue = {}; + break; + case "object": + case "function": + newValue = { objectId: result.objectId }; + break; + default: + newValue = { value: result.value }; + } + + DebuggerAgent.setVariableValue(this._scopeRef.number, name, newValue, this._scopeRef.callFrameId, this._scopeRef.functionId, setVariableValueCallback.bind(this)); + + /** + * @param {?Protocol.Error} error + */ + function setVariableValueCallback(error) + { + if (error) { + callback(error); + return; + } + if (this._savedScopeProperties) { + for (var i = 0; i < this._savedScopeProperties.length; i++) { + if (this._savedScopeProperties[i].name === name) + this._savedScopeProperties[i].value = RemoteObject.fromPayload(result); + } + } + callback(); + } + }, + + __proto__: RemoteObject.prototype +}; + +/** + * Either callFrameId or functionId (exactly one) must be defined. + * @constructor + * @param {number} number + * @param {string=} callFrameId + * @param {string=} functionId + */ +ScopeRef = function(number, callFrameId, functionId) +{ + this.number = number; + this.callFrameId = callFrameId; + this.functionId = functionId; +} + +/** + * @constructor + * @param {string} name + * @param {?RemoteObject} value + * @param {RuntimeAgent.PropertyDescriptor=} descriptor + */ +RemoteObjectProperty = function(name, value, descriptor) +{ + this.name = name; + this.enumerable = descriptor ? !!descriptor.enumerable : true; + this.writable = descriptor ? !!descriptor.writable : true; + + if (value === null && descriptor) { + if (descriptor.value) + this.value = RemoteObject.fromPayload(descriptor.value) + if (descriptor.get && descriptor.get.type !== "undefined") + this.getter = RemoteObject.fromPayload(descriptor.get); + if (descriptor.set && descriptor.set.type !== "undefined") + this.setter = RemoteObject.fromPayload(descriptor.set); + } else { + this.value = value; + } + + if (descriptor) { + this.isOwn = descriptor.isOwn; + this.wasThrown = !!descriptor.wasThrown; + } +} + +RemoteObjectProperty.prototype = { + isAccessorProperty: function() + { + return this.getter || this.setter; + } +}; + +/** + * @param {string} name + * @param {string} value + * @return {RemoteObjectProperty} + */ +RemoteObjectProperty.fromPrimitiveValue = function(name, value) +{ + return new RemoteObjectProperty(name, RemoteObject.fromPrimitiveValue(value)); +} + +/** + * @param {string} name + * @param {RemoteObject} value + * @return {RemoteObjectProperty} + */ +RemoteObjectProperty.fromScopeValue = function(name, value) +{ + var result = new RemoteObjectProperty(name, value); + result.writable = false; + return result; +} + +// The below is a wrapper around a local object that provides an interface comaptible +// with RemoteObject, to be used by the UI code (primarily ObjectPropertiesSection). +// Note that only JSON-compliant objects are currently supported, as there's no provision +// for traversing prototypes, extracting class names via constuctor, handling properties +// or functions. + +/** + * @constructor + * @extends {RemoteObject} + * @param {*} value + */ +function LocalJSONObject(value) +{ + this._value = value; +} + +LocalJSONObject.prototype = { + /** + * @return {string} + */ + get description() + { + if (this._cachedDescription) + return this._cachedDescription; + + if (this.type === "object") { + switch (this.subtype) { + case "array": + function formatArrayItem(property) + { + return property.value.description; + } + this._cachedDescription = this._concatenate("[", "]", formatArrayItem); + break; + case "date": + this._cachedDescription = "" + this._value; + break; + case "null": + this._cachedDescription = "null"; + break; + default: + function formatObjectItem(property) + { + return property.name + ":" + property.value.description; + } + this._cachedDescription = this._concatenate("{", "}", formatObjectItem); + } + } else + this._cachedDescription = String(this._value); + + return this._cachedDescription; + }, + + /** + * @param {string} prefix + * @param {string} suffix + * @return {string} + */ + _concatenate: function(prefix, suffix, formatProperty) + { + const previewChars = 100; + + var buffer = prefix; + var children = this._children(); + for (var i = 0; i < children.length; ++i) { + var itemDescription = formatProperty(children[i]); + if (buffer.length + itemDescription.length > previewChars) { + buffer += ",\u2026"; + break; + } + if (i) + buffer += ", "; + buffer += itemDescription; + } + buffer += suffix; + return buffer; + }, + + /** + * @return {string} + */ + get type() + { + return typeof this._value; + }, + + /** + * @return {string|undefined} + */ + get subtype() + { + if (this._value === null) + return "null"; + + if (this._value instanceof Array) + return "array"; + + if (this._value instanceof Date) + return "date"; + + return undefined; + }, + + /** + * @return {boolean} + */ + get hasChildren() + { + if ((typeof this._value !== "object") || (this._value === null)) + return false; + return !!Object.keys(/** @type {!Object} */ (this._value)).length; + }, + + /** + * @param {function(Array.)} callback + */ + getOwnProperties: function(callback) + { + callback(this._children()); + }, + + /** + * @param {boolean} accessorPropertiesOnly + * @param {function(Array.)} callback + */ + getAllProperties: function(accessorPropertiesOnly, callback) + { + if (accessorPropertiesOnly) + callback([]); + else + callback(this._children()); + }, + + /** + * @return {Array.} + */ + _children: function() + { + if (!this.hasChildren) + return []; + var value = /** @type {!Object} */ (this._value); + + function buildProperty(propName) + { + return new RemoteObjectProperty(propName, new LocalJSONObject(this._value[propName])); + } + if (!this._cachedChildren) + this._cachedChildren = Object.keys(value).map(buildProperty.bind(this)); + return this._cachedChildren; + }, + + /** + * @return {boolean} + */ + isError: function() + { + return false; + }, + + /** + * @return {number} + */ + arrayLength: function() + { + return this._value instanceof Array ? this._value.length : 0; + } +} + +exports.RemoteObject = RemoteObject; +exports.RemoteObjectProperty = RemoteObjectProperty; +}); diff --git a/interpreter/debugger/client/Resource.js b/interpreter/debugger/client/Resource.js new file mode 100644 index 0000000..56466d9 --- /dev/null +++ b/interpreter/debugger/client/Resource.js @@ -0,0 +1,353 @@ +define(function(require, exports){ +var EventObjectEmitter = require("events").EventObjectEmitter; +var ParsedURL = require("ParsedURL"); +/** + * @constructor + * @param {string} name + * @param {string} title + * @param {string} categoryTitle + * @param {string} color + * @param {boolean} isTextType + */ +function ResourceType(name, title, categoryTitle, color, isTextType) +{ + this._name = name; + this._title = title; + this._categoryTitle = categoryTitle; + this._color = color; + this._isTextType = isTextType; +} + +ResourceType.prototype = { + /** + * @return {string} + */ + name: function() + { + return this._name; + }, + + /** + * @return {string} + */ + title: function() + { + return this._title; + }, + + /** + * @return {string} + */ + categoryTitle: function() + { + return this._categoryTitle; + }, + + /** + * @return {string} + */ + color: function() + { + return this._color; + }, + + /** + * @return {boolean} + */ + isTextType: function() + { + return this._isTextType; + }, + + /** + * @return {string} + */ + toString: function() + { + return this._name; + }, + + /** + * @return {string} + */ + canonicalMimeType: function() + { + if (this === ResourceTypes.Document) + return "text/html"; + if (this === ResourceTypes.Script) + return "text/javascript"; + if (this === ResourceTypes.Stylesheet) + return "text/css"; + return ""; + } +} + +var ResourceTypes = {}; +ResourceTypes.Script = new ResourceType("script", "Script", "Scripts", "rgb(255,121,0)", true); + +function Resource(url, mimeType, type, content) +{ + this.url = url; + this._mimeType = mimeType; + this._type = type || ResourceTypes.Script; + + /** @type {?string} */ this._content = content; + /** @type {boolean} */ this._contentEncoded = "utf-8"; + this._pendingContentCallbacks = []; +} + +Resource.prototype = { + + /** + * @return {string} + */ + get url() + { + return this._url; + }, + + set url(x) + { + this._url = x; + this._parsedURL = new ParsedURL(x); + }, + + get parsedURL() + { + return this._parsedURL; + }, + + /** + * @return {string} + */ + get displayName() + { + return this._parsedURL.displayName; + }, + + /** + * @return {string} + */ + get mimeType() + { + return this._request ? this._request.mimeType : this._mimeType; + }, + + /** + * @return {ResourceType} + */ + get type() + { + return this._type; + }, + + /** + * @return {Array.} + */ + get messages() + { + return this._messages || []; + }, + + /** + * @param {ConsoleMessage} msg + */ + addMessage: function(msg) + { + if (!msg.isErrorOrWarning() || !msg.message) + return; + + if (!this._messages) + this._messages = []; + this._messages.push(msg); + this.dispatchEventToListeners(Resource.Events.MessageAdded, msg); + }, + + /** + * @return {number} + */ + get errors() + { + return this._errors || 0; + }, + + set errors(x) + { + this._errors = x; + }, + + /** + * @return {number} + */ + get warnings() + { + return this._warnings || 0; + }, + + set warnings(x) + { + this._warnings = x; + }, + + /** + * @return {?string} + */ + get content() + { + return this._content; + }, + + /** + * @return {boolean} + */ + get contentEncoded() + { + return this._contentEncoded; + }, + + /** + * @return {string} + */ + contentURL: function() + { + return this._url; + }, + + /** + * @return {ResourceType} + */ + contentType: function() + { + return this.type; + }, + + /** + * @param {function(?string, boolean, string)} callback + */ + requestContent: function(callback) + { + if (typeof this._content !== "undefined") { + callback(this._content, !!this._contentEncoded, this.canonicalMimeType()); + return; + } + + this._pendingContentCallbacks.push(callback); + if (!this._request || this._request.finished) + this._innerRequestContent(); + }, + + + _innerRequestContent: function() + { + if (this._contentRequested) + return; + this._contentRequested = true; + + /** + * @param {?Protocol.Error} error + * @param {?string} content + * @param {boolean} contentEncoded + */ + function contentLoaded(error, content, contentEncoded) + { + if (error || content === null) { + loadFallbackContent.call(this, error); + return; + } + replyWithContent.call(this, content, contentEncoded); + } + + /** + * @param {?string} content + * @param {boolean} contentEncoded + */ + function replyWithContent(content, contentEncoded) + { + this._content = content; + this._contentEncoded = contentEncoded; + var callbacks = this._pendingContentCallbacks.slice(); + for (var i = 0; i < callbacks.length; ++i) + callbacks[i](this._content, this._contentEncoded, this.canonicalMimeType()); + this._pendingContentCallbacks.length = 0; + delete this._contentRequested; + } + + /** + * @param {?Protocol.Error} error + * @param {string} content + * @param {boolean} contentEncoded + */ + function resourceContentLoaded(error, content, contentEncoded) + { + contentLoaded.call(this, error, content, contentEncoded); + } + + /** + * @param {?Protocol.Error} error + */ + function loadFallbackContent(error) + { + var scripts = CSInspector.debugModel.scriptsForSourceURL(this.url); + if (!scripts.length) { + console.error("Resource content request failed: " + error); + replyWithContent.call(this, null, false); + return; + } + + var contentProvider; + if (this.type === ResourceTypes.Document) + contentProvider = new WebInspector.ConcatenatedScriptsContentProvider(scripts); + else if (this.type === ResourceTypes.Script) + contentProvider = scripts[0]; + + if (!contentProvider) { + console.error("Resource content request failed: " + error); + replyWithContent.call(this, null, false); + return; + } + + contentProvider.requestContent(fallbackContentLoaded.bind(this)); + } + + /** + * @param {?string} content + * @param {boolean} contentEncoded + * @param {string} mimeType + */ + function fallbackContentLoaded(content, contentEncoded, mimeType) + { + replyWithContent.call(this, content, contentEncoded); + } + + if (this.request) { + /** + * @param {?string} content + * @param {boolean} contentEncoded + * @param {string} mimeType + */ + function requestContentLoaded(content, contentEncoded, mimeType) + { + contentLoaded.call(this, null, content, contentEncoded); + } + + this.request.requestContent(requestContentLoaded.bind(this)); + return; + } + PageAgent.getResourceContent(this.frameId, this.url, resourceContentLoaded.bind(this)); + }, + + /** + * @return {string} + */ + canonicalMimeType: function() + { + //return this.contentType().canonicalMimeType() || this._mimeType; + return this._mimeType; + }, + + __proto__: EventObjectEmitter.prototype +} + +exports.Resource = Resource; +exports.ResourceTypes = ResourceTypes; +}); diff --git a/interpreter/debugger/client/RuntimeModel.js b/interpreter/debugger/client/RuntimeModel.js new file mode 100644 index 0000000..adfbafc --- /dev/null +++ b/interpreter/debugger/client/RuntimeModel.js @@ -0,0 +1,457 @@ +/* + * 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.} + */ + 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) { + console.error(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., 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., 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., number=)} completionsReadyCallback + * @param {boolean} dotNotation + * @param {boolean} bracketNotation + * @param {string} expressionString + * @param {string} prefix + * @param {Array.} 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) ? "" : 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 ""; + var name = this._frame.name || ""; + var subtitle = new WebInspector.ParsedURL(this._frame.url).displayName; + if (subtitle) { + if (!name) + return subtitle; + return name + "( " + subtitle + " )"; + } + return "