|  | /* | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * @implements {Common.ContentProvider} | 
|  | * @unrestricted | 
|  | */ | 
|  | export default class Script { | 
|  | /** | 
|  | * @param {!SDK.DebuggerModel} debuggerModel | 
|  | * @param {string} scriptId | 
|  | * @param {string} sourceURL | 
|  | * @param {number} startLine | 
|  | * @param {number} startColumn | 
|  | * @param {number} endLine | 
|  | * @param {number} endColumn | 
|  | * @param {!Protocol.Runtime.ExecutionContextId} executionContextId | 
|  | * @param {string} hash | 
|  | * @param {boolean} isContentScript | 
|  | * @param {boolean} isLiveEdit | 
|  | * @param {string|undefined} sourceMapURL | 
|  | * @param {boolean} hasSourceURL | 
|  | * @param {number} length | 
|  | * @param {?Protocol.Runtime.StackTrace} originStackTrace | 
|  | */ | 
|  | constructor( | 
|  | debuggerModel, scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, hash, | 
|  | isContentScript, isLiveEdit, sourceMapURL, hasSourceURL, length, originStackTrace) { | 
|  | this.debuggerModel = debuggerModel; | 
|  | this.scriptId = scriptId; | 
|  | this.sourceURL = sourceURL; | 
|  | this.lineOffset = startLine; | 
|  | this.columnOffset = startColumn; | 
|  | this.endLine = endLine; | 
|  | this.endColumn = endColumn; | 
|  |  | 
|  | this.executionContextId = executionContextId; | 
|  | this.hash = hash; | 
|  | this._isContentScript = isContentScript; | 
|  | this._isLiveEdit = isLiveEdit; | 
|  | this.sourceMapURL = sourceMapURL; | 
|  | this.hasSourceURL = hasSourceURL; | 
|  | this.contentLength = length; | 
|  | this._originalContentProvider = null; | 
|  | this._originalSource = null; | 
|  | this.originStackTrace = originStackTrace; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} source | 
|  | * @return {string} | 
|  | */ | 
|  | static _trimSourceURLComment(source) { | 
|  | let sourceURLIndex = source.lastIndexOf('//# sourceURL='); | 
|  | if (sourceURLIndex === -1) { | 
|  | sourceURLIndex = source.lastIndexOf('//@ sourceURL='); | 
|  | if (sourceURLIndex === -1) { | 
|  | return source; | 
|  | } | 
|  | } | 
|  | const sourceURLLineIndex = source.lastIndexOf('\n', sourceURLIndex); | 
|  | if (sourceURLLineIndex === -1) { | 
|  | return source; | 
|  | } | 
|  | const sourceURLLine = source.substr(sourceURLLineIndex + 1); | 
|  | if (!sourceURLLine.match(sourceURLRegex)) { | 
|  | return source; | 
|  | } | 
|  | return source.substr(0, sourceURLLineIndex); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | isContentScript() { | 
|  | return this._isContentScript; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {?SDK.ExecutionContext} | 
|  | */ | 
|  | executionContext() { | 
|  | return this.debuggerModel.runtimeModel().executionContext(this.executionContextId); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | isLiveEdit() { | 
|  | return this._isLiveEdit; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @return {string} | 
|  | */ | 
|  | contentURL() { | 
|  | return this.sourceURL; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @return {!Common.ResourceType} | 
|  | */ | 
|  | contentType() { | 
|  | return Common.resourceTypes.Script; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @return {!Promise<boolean>} | 
|  | */ | 
|  | contentEncoded() { | 
|  | return Promise.resolve(false); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @return {!Promise<!Common.DeferredContent>} | 
|  | */ | 
|  | async requestContent() { | 
|  | if (this._source) { | 
|  | return {content: this._source, isEncoded: false}; | 
|  | } | 
|  | if (!this.scriptId) { | 
|  | return {error: ls`Script removed or deleted.`, isEncoded: false}; | 
|  | } | 
|  |  | 
|  | try { | 
|  | const source = await this.debuggerModel.target().debuggerAgent().getScriptSource(this.scriptId); | 
|  | if (source && this.hasSourceURL) { | 
|  | this._source = SDK.Script._trimSourceURLComment(source); | 
|  | } else { | 
|  | this._source = source || ''; | 
|  | } | 
|  |  | 
|  | if (this._originalSource === null) { | 
|  | this._originalSource = this._source; | 
|  | } | 
|  | return {content: this._source, isEncoded: false}; | 
|  | } catch (err) { | 
|  | return {error: ls`Unable to fetch script source.`, isEncoded: false}; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!Promise<!ArrayBuffer>} | 
|  | */ | 
|  | async getWasmBytecode() { | 
|  | const base64 = await this.debuggerModel.target().debuggerAgent().getWasmBytecode(this.scriptId); | 
|  | const response = await fetch(`data:application/wasm;base64,${base64}`); | 
|  | return response.arrayBuffer(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {!Common.ContentProvider} | 
|  | */ | 
|  | originalContentProvider() { | 
|  | if (!this._originalContentProvider) { | 
|  | const lazyContent = () => this.requestContent().then(() => { | 
|  | return { | 
|  | content: this._originalSource, | 
|  | isEncoded: false, | 
|  | }; | 
|  | }); | 
|  | this._originalContentProvider = | 
|  | new Common.StaticContentProvider(this.contentURL(), this.contentType(), lazyContent); | 
|  | } | 
|  | return this._originalContentProvider; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {string} query | 
|  | * @param {boolean} caseSensitive | 
|  | * @param {boolean} isRegex | 
|  | * @return {!Promise<!Array<!Common.ContentProvider.SearchMatch>>} | 
|  | */ | 
|  | async searchInContent(query, caseSensitive, isRegex) { | 
|  | if (!this.scriptId) { | 
|  | return []; | 
|  | } | 
|  |  | 
|  | const matches = | 
|  | await this.debuggerModel.target().debuggerAgent().searchInContent(this.scriptId, query, caseSensitive, isRegex); | 
|  | return (matches || []).map(match => new Common.ContentProvider.SearchMatch(match.lineNumber, match.lineContent)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} source | 
|  | * @return {string} | 
|  | */ | 
|  | _appendSourceURLCommentIfNeeded(source) { | 
|  | if (!this.hasSourceURL) { | 
|  | return source; | 
|  | } | 
|  | return source + '\n //# sourceURL=' + this.sourceURL; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {string} newSource | 
|  | * @param {function(?Protocol.Error, !Protocol.Runtime.ExceptionDetails=, !Array.<!Protocol.Debugger.CallFrame>=, !Protocol.Runtime.StackTrace=, !Protocol.Runtime.StackTraceId=, boolean=)} callback | 
|  | */ | 
|  | async editSource(newSource, callback) { | 
|  | newSource = Script._trimSourceURLComment(newSource); | 
|  | // We append correct sourceURL to script for consistency only. It's not actually needed for things to work correctly. | 
|  | newSource = this._appendSourceURLCommentIfNeeded(newSource); | 
|  |  | 
|  | if (!this.scriptId) { | 
|  | callback('Script failed to parse'); | 
|  | return; | 
|  | } | 
|  |  | 
|  | await this.requestContent(); | 
|  | if (this._source === newSource) { | 
|  | callback(null); | 
|  | return; | 
|  | } | 
|  | const response = await this.debuggerModel.target().debuggerAgent().invoke_setScriptSource( | 
|  | {scriptId: this.scriptId, scriptSource: newSource}); | 
|  |  | 
|  | if (!response[Protocol.Error] && !response.exceptionDetails) { | 
|  | this._source = newSource; | 
|  | } | 
|  |  | 
|  | const needsStepIn = !!response.stackChanged; | 
|  | callback( | 
|  | response[Protocol.Error], response.exceptionDetails, response.callFrames, response.asyncStackTrace, | 
|  | response.asyncStackTraceId, needsStepIn); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {number} lineNumber | 
|  | * @param {number=} columnNumber | 
|  | * @return {?SDK.DebuggerModel.Location} | 
|  | */ | 
|  | rawLocation(lineNumber, columnNumber) { | 
|  | if (this.containsLocation(lineNumber, columnNumber)) { | 
|  | return new SDK.DebuggerModel.Location(this.debuggerModel, this.scriptId, lineNumber, columnNumber); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * | 
|  | * @param {!SDK.DebuggerModel.Location} location | 
|  | * @return {!Array.<number>} | 
|  | */ | 
|  | toRelativeLocation(location) { | 
|  | console.assert( | 
|  | location.scriptId === this.scriptId, '`toRelativeLocation` must be used with location of the same script'); | 
|  | const relativeLineNumber = location.lineNumber - this.lineOffset; | 
|  | const relativeColumnNumber = (location.columnNumber || 0) - (relativeLineNumber === 0 ? this.columnOffset : 0); | 
|  | return [relativeLineNumber, relativeColumnNumber]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | isInlineScript() { | 
|  | const startsAtZero = !this.lineOffset && !this.columnOffset; | 
|  | return !!this.sourceURL && !startsAtZero; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | isAnonymousScript() { | 
|  | return !this.sourceURL; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return {boolean} | 
|  | */ | 
|  | isInlineScriptWithSourceURL() { | 
|  | return !!this.hasSourceURL && this.isInlineScript(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Array<!Protocol.Debugger.ScriptPosition>} positions | 
|  | * @return {!Promise<boolean>} | 
|  | */ | 
|  | async setBlackboxedRanges(positions) { | 
|  | const response = await this.debuggerModel.target().debuggerAgent().invoke_setBlackboxedRanges( | 
|  | {scriptId: this.scriptId, positions}); | 
|  | return !response[Protocol.Error]; | 
|  | } | 
|  |  | 
|  | containsLocation(lineNumber, columnNumber) { | 
|  | const afterStart = | 
|  | (lineNumber === this.lineOffset && columnNumber >= this.columnOffset) || lineNumber > this.lineOffset; | 
|  | const beforeEnd = lineNumber < this.endLine || (lineNumber === this.endLine && columnNumber <= this.endColumn); | 
|  | return afterStart && beforeEnd; | 
|  | } | 
|  | } | 
|  |  | 
|  | export const sourceURLRegex = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/; | 
|  |  | 
|  | /* Legacy exported object */ | 
|  | self.SDK = self.SDK || {}; | 
|  |  | 
|  | /* Legacy exported object */ | 
|  | SDK = SDK || {}; | 
|  |  | 
|  | /** @constructor */ | 
|  | SDK.Script = Script; | 
|  |  | 
|  | SDK.Script.sourceURLRegex = sourceURLRegex; |