UNPKG

debug-server-next

Version:

Dev server for hippy-core.

265 lines (264 loc) 11.1 kB
// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as TextUtils from '../../models/text_utils/text_utils.js'; import * as Common from '../common/common.js'; import * as i18n from '../i18n/i18n.js'; import { Location } from './DebuggerModel.js'; import { ResourceTreeModel } from './ResourceTreeModel.js'; const UIStrings = { /** *@description Error message for when a script can't be loaded which had been previously */ scriptRemovedOrDeleted: 'Script removed or deleted.', /** *@description Error message when failing to load a script source text */ unableToFetchScriptSource: 'Unable to fetch script source.', }; const str_ = i18n.i18n.registerUIStrings('core/sdk/Script.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export class Script { debuggerModel; scriptId; sourceURL; lineOffset; columnOffset; endLine; endColumn; executionContextId; hash; _isContentScript; _isLiveEdit; sourceMapURL; debugSymbols; hasSourceURL; contentLength; _originalContentProvider; originStackTrace; _codeOffset; _language; _contentPromise; _embedderName; isModule; constructor(debuggerModel, scriptId, sourceURL, startLine, startColumn, endLine, endColumn, executionContextId, hash, isContentScript, isLiveEdit, sourceMapURL, hasSourceURL, length, isModule, originStackTrace, codeOffset, scriptLanguage, debugSymbols, embedderName) { this.debuggerModel = debuggerModel; this.scriptId = scriptId; this.sourceURL = sourceURL; this.lineOffset = startLine; this.columnOffset = startColumn; this.endLine = endLine; this.endColumn = endColumn; this.isModule = isModule; this.executionContextId = executionContextId; this.hash = hash; this._isContentScript = isContentScript; this._isLiveEdit = isLiveEdit; this.sourceMapURL = sourceMapURL; this.debugSymbols = debugSymbols; this.hasSourceURL = hasSourceURL; this.contentLength = length; this._originalContentProvider = null; this.originStackTrace = originStackTrace; this._codeOffset = codeOffset; this._language = scriptLanguage; this._contentPromise = null; this._embedderName = embedderName; } embedderName() { return this._embedderName; } target() { return this.debuggerModel.target(); } 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); } isContentScript() { return this._isContentScript; } codeOffset() { return this._codeOffset; } isJavaScript() { return this._language === "JavaScript" /* JavaScript */; } isWasm() { return this._language === "WebAssembly" /* WebAssembly */; } scriptLanguage() { return this._language; } executionContext() { return this.debuggerModel.runtimeModel().executionContext(this.executionContextId); } isLiveEdit() { return this._isLiveEdit; } contentURL() { return this.sourceURL; } contentType() { return Common.ResourceType.resourceTypes.Script; } async contentEncoded() { return false; } requestContent() { if (!this._contentPromise) { this._contentPromise = this.originalContentProvider().requestContent(); } return this._contentPromise; } async getWasmBytecode() { const base64 = await this.debuggerModel.target().debuggerAgent().invoke_getWasmBytecode({ scriptId: this.scriptId }); const response = await fetch(`data:application/wasm;base64,${base64.bytecode}`); return response.arrayBuffer(); } originalContentProvider() { if (!this._originalContentProvider) { /* } */ let lazyContentPromise; this._originalContentProvider = new TextUtils.StaticContentProvider.StaticContentProvider(this.contentURL(), this.contentType(), () => { if (!lazyContentPromise) { lazyContentPromise = (async () => { if (!this.scriptId) { return { content: null, error: i18nString(UIStrings.scriptRemovedOrDeleted), isEncoded: false }; } try { const result = await this.debuggerModel.target().debuggerAgent().invoke_getScriptSource({ scriptId: this.scriptId }); if (result.getError()) { throw new Error(result.getError()); } const { scriptSource, bytecode } = result; if (bytecode) { return { content: bytecode, isEncoded: true }; } let content = scriptSource || ''; if (this.hasSourceURL) { content = Script._trimSourceURLComment(content); } return { content, isEncoded: false }; } catch (err) { // TODO(bmeurer): Propagate errors as exceptions / rejections. return { content: null, error: i18nString(UIStrings.unableToFetchScriptSource), isEncoded: false }; } })(); } return lazyContentPromise; }); } return this._originalContentProvider; } async searchInContent(query, caseSensitive, isRegex) { if (!this.scriptId) { return []; } const matches = await this.debuggerModel.target().debuggerAgent().invoke_searchInContent({ scriptId: this.scriptId, query, caseSensitive, isRegex }); return (matches.result || []) .map(match => new TextUtils.ContentProvider.SearchMatch(match.lineNumber, match.lineContent)); } _appendSourceURLCommentIfNeeded(source) { if (!this.hasSourceURL) { return source; } return source + '\n //# sourceURL=' + this.sourceURL; } 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; } const { content: oldSource } = await this.requestContent(); if (oldSource === newSource) { callback(null); return; } const response = await this.debuggerModel.target().debuggerAgent().invoke_setScriptSource({ scriptId: this.scriptId, scriptSource: newSource }); if (!response.getError() && !response.exceptionDetails) { this._contentPromise = Promise.resolve({ content: newSource, isEncoded: false }); } const needsStepIn = Boolean(response.stackChanged); callback(response.getError() || null, response.exceptionDetails, response.callFrames, response.asyncStackTrace, response.asyncStackTraceId, needsStepIn); } rawLocation(lineNumber, columnNumber) { if (this.containsLocation(lineNumber, columnNumber)) { return new Location(this.debuggerModel, this.scriptId, lineNumber, columnNumber); } return null; } 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]; } isInlineScript() { const startsAtZero = !this.lineOffset && !this.columnOffset; return !this.isWasm() && Boolean(this.sourceURL) && !startsAtZero; } isAnonymousScript() { return !this.sourceURL; } isInlineScriptWithSourceURL() { return Boolean(this.hasSourceURL) && this.isInlineScript(); } async setBlackboxedRanges(positions) { const response = await this.debuggerModel.target().debuggerAgent().invoke_setBlackboxedRanges({ scriptId: this.scriptId, positions }); return !response.getError(); } 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; } get frameId() { // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // @ts-expect-error if (typeof this[frameIdSymbol] !== 'string') { // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // @ts-expect-error this[frameIdSymbol] = frameIdForScript(this); } // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // @ts-expect-error return this[frameIdSymbol] || ''; } createPageResourceLoadInitiator() { return { target: this.target(), frameId: this.frameId, initiatorUrl: this.embedderName() }; } } const frameIdSymbol = Symbol('frameid'); function frameIdForScript(script) { const executionContext = script.executionContext(); if (executionContext) { return executionContext.frameId || ''; } // This is to overcome compilation cache which doesn't get reset. const resourceTreeModel = script.debuggerModel.target().model(ResourceTreeModel); if (!resourceTreeModel || !resourceTreeModel.mainFrame) { return ''; } return resourceTreeModel.mainFrame.id; } export const sourceURLRegex = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/;