| /* |
| * 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. |
| */ |
| /** |
| * @unrestricted |
| * @implements {SDK.SDKModelObserver<!SDK.DebuggerModel>} |
| * @implements {Bindings.DebuggerSourceMapping} |
| */ |
| Snippets.ScriptSnippetModel = class extends Common.Object { |
| /** |
| * @param {!Workspace.Workspace} workspace |
| */ |
| constructor(workspace) { |
| super(); |
| this._workspace = workspace; |
| /** @type {!Object.<string, !Workspace.UISourceCode>} */ |
| this._uiSourceCodeForSnippetId = {}; |
| /** @type {!Map.<!Workspace.UISourceCode, string>} */ |
| this._snippetIdForUISourceCode = new Map(); |
| |
| /** @type {!Map.<!SDK.DebuggerModel, !Snippets.SnippetScriptMapping>} */ |
| this._mappingForDebuggerModel = new Map(); |
| this._snippetStorage = new Snippets.SnippetStorage('script', 'Script snippet #'); |
| this._lastSnippetEvaluationIndexSetting = Common.settings.createSetting('lastSnippetEvaluationIndex', 0); |
| this._project = new Snippets.SnippetsProject(workspace, this); |
| this._loadSnippets(); |
| SDK.targetManager.observeModels(SDK.DebuggerModel, this); |
| Bindings.debuggerWorkspaceBinding.addSourceMapping(this); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.DebuggerModel} debuggerModel |
| */ |
| modelAdded(debuggerModel) { |
| this._mappingForDebuggerModel.set(debuggerModel, new Snippets.SnippetScriptMapping(debuggerModel, this)); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.DebuggerModel} debuggerModel |
| */ |
| modelRemoved(debuggerModel) { |
| this._mappingForDebuggerModel.remove(debuggerModel); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.DebuggerModel.Location} rawLocation |
| * @return {?Workspace.UILocation} |
| */ |
| rawLocationToUILocation(rawLocation) { |
| const mapping = this._mappingForDebuggerModel.get(rawLocation.debuggerModel); |
| if (!mapping) |
| return null; |
| return mapping.rawLocationToUILocation(rawLocation); |
| } |
| |
| /** |
| * @override |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| * @return {?SDK.DebuggerModel.Location} |
| */ |
| uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber) { |
| for (const mapping of this._mappingForDebuggerModel.values()) { |
| const rawLocation = mapping.uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber); |
| if (rawLocation) |
| return rawLocation; |
| } |
| return null; |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel} debuggerModel |
| * @return {!Snippets.SnippetScriptMapping|undefined} |
| */ |
| snippetScriptMapping(debuggerModel) { |
| return this._mappingForDebuggerModel.get(debuggerModel); |
| } |
| |
| /** |
| * @return {!Workspace.Project} |
| */ |
| project() { |
| return this._project; |
| } |
| |
| _loadSnippets() { |
| for (const snippet of this._snippetStorage.snippets()) |
| this._addScriptSnippet(snippet); |
| } |
| |
| /** |
| * @param {string} content |
| * @return {!Workspace.UISourceCode} |
| */ |
| createScriptSnippet(content) { |
| const snippet = this._snippetStorage.createSnippet(); |
| snippet.content = content; |
| return this._addScriptSnippet(snippet); |
| } |
| |
| /** |
| * @param {!Snippets.Snippet} snippet |
| * @return {!Workspace.UISourceCode} |
| */ |
| _addScriptSnippet(snippet) { |
| const uiSourceCode = this._project.addSnippet(snippet.name, new Snippets.SnippetContentProvider(snippet)); |
| uiSourceCode.addEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this); |
| this._snippetIdForUISourceCode.set(uiSourceCode, snippet.id); |
| const breakpointLocations = this._removeBreakpoints(uiSourceCode); |
| this._restoreBreakpoints(uiSourceCode, breakpointLocations); |
| this._uiSourceCodeForSnippetId[snippet.id] = uiSourceCode; |
| return uiSourceCode; |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _workingCopyChanged(event) { |
| const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data); |
| this._scriptSnippetEdited(uiSourceCode); |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| */ |
| deleteScriptSnippet(uiSourceCode) { |
| const snippetId = this._snippetIdForUISourceCode.get(uiSourceCode) || ''; |
| const snippet = this._snippetStorage.snippetForId(snippetId); |
| if (!snippet) |
| return; |
| this._snippetStorage.deleteSnippet(snippet); |
| this._removeBreakpoints(uiSourceCode); |
| this._releaseSnippetScript(uiSourceCode); |
| delete this._uiSourceCodeForSnippetId[snippet.id]; |
| this._snippetIdForUISourceCode.remove(uiSourceCode); |
| this._project.removeFile(snippet.name); |
| } |
| |
| /** |
| * @param {string} name |
| * @param {string} newName |
| * @param {function(boolean, string=)} callback |
| */ |
| renameScriptSnippet(name, newName, callback) { |
| newName = newName.trim(); |
| if (!newName || newName.indexOf('/') !== -1 || name === newName || this._snippetStorage.snippetForName(newName)) { |
| callback(false); |
| return; |
| } |
| const snippet = this._snippetStorage.snippetForName(name); |
| console.assert(snippet, 'Snippet \'' + name + '\' was not found.'); |
| const uiSourceCode = this._uiSourceCodeForSnippetId[snippet.id]; |
| console.assert(uiSourceCode, 'No uiSourceCode was found for snippet \'' + name + '\'.'); |
| |
| const breakpointLocations = this._removeBreakpoints(uiSourceCode); |
| snippet.name = newName; |
| this._restoreBreakpoints(uiSourceCode, breakpointLocations); |
| callback(true, newName); |
| } |
| |
| /** |
| * @param {string} name |
| * @param {string} newContent |
| */ |
| _setScriptSnippetContent(name, newContent) { |
| const snippet = this._snippetStorage.snippetForName(name); |
| snippet.content = newContent; |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| */ |
| _scriptSnippetEdited(uiSourceCode) { |
| const breakpointLocations = this._removeBreakpoints(uiSourceCode); |
| this._releaseSnippetScript(uiSourceCode); |
| this._restoreBreakpoints(uiSourceCode, breakpointLocations); |
| this._mappingForDebuggerModel.valuesArray().forEach(function(mapping) { |
| mapping._restoreBreakpoints(uiSourceCode, breakpointLocations); |
| }); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| _nextEvaluationIndex() { |
| const evaluationIndex = this._lastSnippetEvaluationIndexSetting.get() + 1; |
| this._lastSnippetEvaluationIndexSetting.set(evaluationIndex); |
| return evaluationIndex; |
| } |
| |
| /** |
| * @param {!SDK.ExecutionContext} executionContext |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| * @return {!Promise<undefined>} |
| */ |
| async evaluateScriptSnippet(executionContext, uiSourceCode) { |
| console.assert(uiSourceCode.project().type() === Workspace.projectTypes.Snippets); |
| let breakpointLocations = this._removeBreakpoints(uiSourceCode); |
| this._releaseSnippetScript(uiSourceCode); |
| this._restoreBreakpoints(uiSourceCode, breakpointLocations); |
| |
| const runtimeModel = executionContext.runtimeModel; |
| const debuggerModel = executionContext.debuggerModel; |
| const evaluationIndex = this._nextEvaluationIndex(); |
| const mapping = this._mappingForDebuggerModel.get(debuggerModel); |
| mapping._setEvaluationIndex(evaluationIndex, uiSourceCode); |
| const evaluationUrl = mapping._evaluationSourceURL(uiSourceCode); |
| await uiSourceCode.requestContent(); |
| const expression = uiSourceCode.workingCopy(); |
| Common.console.show(); |
| const result = await runtimeModel.compileScript(expression, '', true, executionContext.id); |
| if (!result || mapping.evaluationIndex(uiSourceCode) !== evaluationIndex) |
| return; |
| const script = /** @type {!SDK.Script} */ ( |
| debuggerModel.scriptForId(/** @type {string} */ (result.scriptId || result.exceptionDetails.scriptId))); |
| mapping._addScript(script, uiSourceCode); |
| if (!result.scriptId) { |
| this._printRunOrCompileScriptResultFailure( |
| runtimeModel, /** @type {!Protocol.Runtime.ExceptionDetails} */ (result.exceptionDetails), evaluationUrl); |
| return; |
| } |
| |
| breakpointLocations = this._removeBreakpoints(uiSourceCode); |
| this._restoreBreakpoints(uiSourceCode, breakpointLocations); |
| |
| this._runScript(script.scriptId, executionContext, evaluationUrl); |
| } |
| |
| /** |
| * @param {!Protocol.Runtime.ScriptId} scriptId |
| * @param {!SDK.ExecutionContext} executionContext |
| * @param {?string=} sourceURL |
| */ |
| async _runScript(scriptId, executionContext, sourceURL) { |
| const runtimeModel = executionContext.runtimeModel; |
| const result = await runtimeModel.runScript( |
| scriptId, executionContext.id, 'console', /* silent */ false, /* includeCommandLineAPI */ true, |
| /* returnByValue */ false, /* generatePreview */ true); |
| if (result.error) |
| return; |
| if (!result.exceptionDetails) |
| this._printRunScriptResult(runtimeModel, result.object || null, scriptId, sourceURL); |
| else |
| this._printRunOrCompileScriptResultFailure(runtimeModel, result.exceptionDetails, sourceURL); |
| } |
| |
| /** |
| * @param {!SDK.RuntimeModel} runtimeModel |
| * @param {?SDK.RemoteObject} result |
| * @param {!Protocol.Runtime.ScriptId} scriptId |
| * @param {?string=} sourceURL |
| */ |
| _printRunScriptResult(runtimeModel, result, scriptId, sourceURL) { |
| const consoleMessage = new SDK.ConsoleMessage( |
| runtimeModel, SDK.ConsoleMessage.MessageSource.JS, SDK.ConsoleMessage.MessageLevel.Info, '', |
| SDK.ConsoleMessage.MessageType.Result, sourceURL, undefined, undefined, [result], undefined, undefined, |
| undefined, scriptId); |
| SDK.consoleModel.addMessage(consoleMessage); |
| } |
| |
| /** |
| * @param {!SDK.RuntimeModel} runtimeModel |
| * @param {!Protocol.Runtime.ExceptionDetails} exceptionDetails |
| * @param {?string=} sourceURL |
| */ |
| _printRunOrCompileScriptResultFailure(runtimeModel, exceptionDetails, sourceURL) { |
| SDK.consoleModel.addMessage( |
| SDK.ConsoleMessage.fromException(runtimeModel, exceptionDetails, undefined, undefined, sourceURL || undefined)); |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| * @return {!Array.<!{breakpoint: !Bindings.BreakpointManager.Breakpoint, uiLocation: !Workspace.UILocation}>} |
| */ |
| _removeBreakpoints(uiSourceCode) { |
| const breakpointLocations = Bindings.breakpointManager.breakpointLocationsForUISourceCode(uiSourceCode); |
| for (let i = 0; i < breakpointLocations.length; ++i) |
| breakpointLocations[i].breakpoint.remove(false /* keepInStorage */); |
| return breakpointLocations; |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| * @param {!Array.<!{breakpoint: !Bindings.BreakpointManager.Breakpoint, uiLocation: !Workspace.UILocation}>} breakpointLocations |
| */ |
| _restoreBreakpoints(uiSourceCode, breakpointLocations) { |
| for (let i = 0; i < breakpointLocations.length; ++i) { |
| const uiLocation = breakpointLocations[i].uiLocation; |
| const breakpoint = breakpointLocations[i].breakpoint; |
| Bindings.breakpointManager.setBreakpoint( |
| uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber, breakpoint.condition(), breakpoint.enabled()); |
| } |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| */ |
| _releaseSnippetScript(uiSourceCode) { |
| this._mappingForDebuggerModel.valuesArray().forEach(function(mapping) { |
| mapping._releaseSnippetScript(uiSourceCode); |
| }); |
| } |
| |
| /** |
| * @param {string} sourceURL |
| * @return {?string} |
| */ |
| _snippetIdForSourceURL(sourceURL) { |
| const snippetPrefix = Snippets.ScriptSnippetModel.snippetSourceURLPrefix; |
| if (!sourceURL.startsWith(snippetPrefix)) |
| return null; |
| const splitURL = sourceURL.substring(snippetPrefix.length).split('_'); |
| const snippetId = splitURL[0]; |
| return snippetId; |
| } |
| }; |
| |
| Snippets.ScriptSnippetModel.snippetSourceURLPrefix = 'snippets:///'; |
| |
| /** |
| * @unrestricted |
| */ |
| Snippets.SnippetScriptMapping = class { |
| /** |
| * @param {!SDK.DebuggerModel} debuggerModel |
| * @param {!Snippets.ScriptSnippetModel} scriptSnippetModel |
| */ |
| constructor(debuggerModel, scriptSnippetModel) { |
| this._debuggerModel = debuggerModel; |
| this._scriptSnippetModel = scriptSnippetModel; |
| /** @type {!Object.<string, !Workspace.UISourceCode>} */ |
| this._uiSourceCodeForScriptId = {}; |
| /** @type {!Map.<!Workspace.UISourceCode, !SDK.Script>} */ |
| this._scriptForUISourceCode = new Map(); |
| /** @type {!Map.<!Workspace.UISourceCode, number>} */ |
| this._evaluationIndexForUISourceCode = new Map(); |
| debuggerModel.addEventListener(SDK.DebuggerModel.Events.GlobalObjectCleared, this._reset, this); |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| */ |
| _releaseSnippetScript(uiSourceCode) { |
| const script = this._scriptForUISourceCode.get(uiSourceCode); |
| if (!script) |
| return; |
| |
| delete this._uiSourceCodeForScriptId[script.scriptId]; |
| this._scriptForUISourceCode.remove(uiSourceCode); |
| this._evaluationIndexForUISourceCode.remove(uiSourceCode); |
| } |
| |
| /** |
| * @param {number} evaluationIndex |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| */ |
| _setEvaluationIndex(evaluationIndex, uiSourceCode) { |
| this._evaluationIndexForUISourceCode.set(uiSourceCode, evaluationIndex); |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| * @return {number|undefined} |
| */ |
| evaluationIndex(uiSourceCode) { |
| return this._evaluationIndexForUISourceCode.get(uiSourceCode); |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| * @return {string} |
| */ |
| _evaluationSourceURL(uiSourceCode) { |
| const evaluationSuffix = '_' + this._evaluationIndexForUISourceCode.get(uiSourceCode); |
| const snippetId = this._scriptSnippetModel._snippetIdForUISourceCode.get(uiSourceCode); |
| return Snippets.ScriptSnippetModel.snippetSourceURLPrefix + snippetId + evaluationSuffix; |
| } |
| |
| _reset() { |
| this._uiSourceCodeForScriptId = {}; |
| this._scriptForUISourceCode.clear(); |
| this._evaluationIndexForUISourceCode.clear(); |
| } |
| |
| /** |
| * @param {!SDK.DebuggerModel.Location} rawLocation |
| * @return {?Workspace.UILocation} |
| */ |
| rawLocationToUILocation(rawLocation) { |
| const debuggerModelLocation = /** @type {!SDK.DebuggerModel.Location} */ (rawLocation); |
| const uiSourceCode = this._uiSourceCodeForScriptId[debuggerModelLocation.scriptId]; |
| if (!uiSourceCode) |
| return null; |
| |
| return uiSourceCode.uiLocation(debuggerModelLocation.lineNumber, debuggerModelLocation.columnNumber || 0); |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| * @return {?SDK.DebuggerModel.Location} |
| */ |
| uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber) { |
| const script = this._scriptForUISourceCode.get(uiSourceCode); |
| if (!script) |
| return null; |
| |
| return this._debuggerModel.createRawLocation(script, lineNumber, columnNumber); |
| } |
| |
| /** |
| * @param {!SDK.Script} script |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| */ |
| _addScript(script, uiSourceCode) { |
| console.assert(!this._scriptForUISourceCode.get(uiSourceCode)); |
| this._uiSourceCodeForScriptId[script.scriptId] = uiSourceCode; |
| this._scriptForUISourceCode.set(uiSourceCode, script); |
| Bindings.debuggerWorkspaceBinding.updateLocations(script); |
| } |
| |
| /** |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| * @param {!Array.<!{breakpoint: !Bindings.BreakpointManager.Breakpoint, uiLocation: !Workspace.UILocation}>} breakpointLocations |
| */ |
| _restoreBreakpoints(uiSourceCode, breakpointLocations) { |
| const script = this._scriptForUISourceCode.get(uiSourceCode); |
| if (!script) |
| return; |
| const rawLocation = |
| /** @type {!SDK.DebuggerModel.Location} */ (this._debuggerModel.createRawLocation(script, 0, 0)); |
| const uiLocation = Bindings.debuggerWorkspaceBinding.rawLocationToUILocation(rawLocation); |
| if (uiLocation) |
| this._scriptSnippetModel._restoreBreakpoints(uiLocation.uiSourceCode, breakpointLocations); |
| } |
| }; |
| |
| /** |
| * @implements {Common.ContentProvider} |
| * @unrestricted |
| */ |
| Snippets.SnippetContentProvider = class { |
| /** |
| * @param {!Snippets.Snippet} snippet |
| */ |
| constructor(snippet) { |
| this._snippet = snippet; |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| contentURL() { |
| return ''; |
| } |
| |
| /** |
| * @override |
| * @return {!Common.ResourceType} |
| */ |
| contentType() { |
| return Common.resourceTypes.Snippet; |
| } |
| |
| /** |
| * @override |
| * @return {!Promise<boolean>} |
| */ |
| contentEncoded() { |
| return Promise.resolve(false); |
| } |
| |
| /** |
| * @override |
| * @return {!Promise<?string>} |
| */ |
| requestContent() { |
| return Promise.resolve(/** @type {?string} */ (this._snippet.content)); |
| } |
| |
| /** |
| * @override |
| * @param {string} query |
| * @param {boolean} caseSensitive |
| * @param {boolean} isRegex |
| * @return {!Promise<!Array<!Common.ContentProvider.SearchMatch>>} |
| */ |
| async searchInContent(query, caseSensitive, isRegex) { |
| return Common.ContentProvider.performSearchInContent(this._snippet.content, query, caseSensitive, isRegex); |
| } |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Snippets.SnippetsProject = class extends Bindings.ContentProviderBasedProject { |
| /** |
| * @param {!Workspace.Workspace} workspace |
| * @param {!Snippets.ScriptSnippetModel} model |
| */ |
| constructor(workspace, model) { |
| super(workspace, 'snippets:', Workspace.projectTypes.Snippets, '', false /* isServiceProject */); |
| this._model = model; |
| } |
| |
| /** |
| * @param {string} name |
| * @param {!Common.ContentProvider} contentProvider |
| * @return {!Workspace.UISourceCode} |
| */ |
| addSnippet(name, contentProvider) { |
| return this.addContentProvider(name, contentProvider, 'text/javascript'); |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| canSetFileContent() { |
| return true; |
| } |
| |
| /** |
| * @override |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| * @param {string} newContent |
| * @param {boolean} isBase64 |
| * @return {!Promise} |
| */ |
| async setFileContent(uiSourceCode, newContent, isBase64) { |
| this._model._setScriptSnippetContent(uiSourceCode.url(), newContent); |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| canRename() { |
| return true; |
| } |
| |
| /** |
| * @override |
| * @param {string} url |
| * @param {string} newName |
| * @param {function(boolean, string=)} callback |
| */ |
| performRename(url, newName, callback) { |
| this._model.renameScriptSnippet(url, newName, callback); |
| } |
| |
| /** |
| * @override |
| * @param {string} url |
| * @param {?string} name |
| * @param {string} content |
| * @param {boolean=} isBase64 |
| * @return {!Promise<?Workspace.UISourceCode>} |
| */ |
| createFile(url, name, content, isBase64) { |
| return /** @type {!Promise<?Workspace.UISourceCode>} */ (Promise.resolve(this._model.createScriptSnippet(content))); |
| } |
| |
| /** |
| * @override |
| * @param {!Workspace.UISourceCode} uiSourceCode |
| */ |
| deleteFile(uiSourceCode) { |
| this._model.deleteScriptSnippet(uiSourceCode); |
| } |
| }; |
| |
| /** |
| * @type {!Snippets.ScriptSnippetModel} |
| */ |
| Snippets.scriptSnippetModel = new Snippets.ScriptSnippetModel(Workspace.workspace); |