/* * 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. */ import * as Common from '../../core/common/common.js'; import * as i18n from '../../core/i18n/i18n.js'; import type * as Platform from '../../core/platform/platform.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as Protocol from '../../generated/protocol.js'; import type * as TextUtils from '../text_utils/text_utils.js'; import * as Workspace from '../workspace/workspace.js'; import {BreakpointManager, type Breakpoint} from './BreakpointManager.js'; import {ContentProviderBasedProject} from './ContentProviderBasedProject.js'; import {DebuggerWorkspaceBinding, type DebuggerSourceMapping} from './DebuggerWorkspaceBinding.js'; import {NetworkProject} from './NetworkProject.js'; import {metadataForURL} from './ResourceUtils.js'; const UIStrings = { /** *@description Error text displayed in the console when editing a live script fails. LiveEdit is *the name of the feature for editing code that is already running. *@example {warning} PH1 */ liveEditFailed: '`LiveEdit` failed: {PH1}', /** *@description Error text displayed in the console when compiling a live-edited script fails. LiveEdit is *the name of the feature for editing code that is already running. *@example {connection lost} PH1 */ liveEditCompileFailed: '`LiveEdit` compile failed: {PH1}', }; const str_ = i18n.i18n.registerUIStrings('models/bindings/ResourceScriptMapping.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export class ResourceScriptMapping implements DebuggerSourceMapping { readonly debuggerModel: SDK.DebuggerModel.DebuggerModel; #workspace: Workspace.Workspace.WorkspaceImpl; readonly debuggerWorkspaceBinding: DebuggerWorkspaceBinding; readonly #uiSourceCodeToScriptFile: Map; readonly #projects: Map; readonly #scriptToUISourceCode: Map; readonly #eventListeners: Common.EventTarget.EventDescriptor[]; constructor( debuggerModel: SDK.DebuggerModel.DebuggerModel, workspace: Workspace.Workspace.WorkspaceImpl, debuggerWorkspaceBinding: DebuggerWorkspaceBinding) { this.debuggerModel = debuggerModel; this.#workspace = workspace; this.debuggerWorkspaceBinding = debuggerWorkspaceBinding; this.#uiSourceCodeToScriptFile = new Map(); this.#projects = new Map(); this.#scriptToUISourceCode = new Map(); const runtimeModel = debuggerModel.runtimeModel(); this.#eventListeners = [ this.debuggerModel.addEventListener( SDK.DebuggerModel.Events.ParsedScriptSource, event => this.addScript(event.data), this), this.debuggerModel.addEventListener(SDK.DebuggerModel.Events.GlobalObjectCleared, this.globalObjectCleared, this), runtimeModel.addEventListener( SDK.RuntimeModel.Events.ExecutionContextDestroyed, this.executionContextDestroyed, this), runtimeModel.target().targetManager().addEventListener( SDK.TargetManager.Events.InspectedURLChanged, this.inspectedURLChanged, this), ]; } private project(script: SDK.Script.Script): ContentProviderBasedProject { const prefix = script.isContentScript() ? 'js:extensions:' : 'js::'; const projectId = prefix + this.debuggerModel.target().id() + ':' + script.frameId; let project = this.#projects.get(projectId); if (!project) { const projectType = script.isContentScript() ? Workspace.Workspace.projectTypes.ContentScripts : Workspace.Workspace.projectTypes.Network; project = new ContentProviderBasedProject( this.#workspace, projectId, projectType, '' /* displayName */, false /* isServiceProject */); NetworkProject.setTargetForProject(project, this.debuggerModel.target()); this.#projects.set(projectId, project); } return project; } uiSourceCodeForScript(script: SDK.Script.Script): Workspace.UISourceCode.UISourceCode|null { return this.#scriptToUISourceCode.get(script) ?? null; } rawLocationToUILocation(rawLocation: SDK.DebuggerModel.Location): Workspace.UISourceCode.UILocation|null { const script = rawLocation.script(); if (!script) { return null; } const uiSourceCode = this.#scriptToUISourceCode.get(script); if (!uiSourceCode) { return null; } const scriptFile = this.#uiSourceCodeToScriptFile.get(uiSourceCode); if (!scriptFile) { return null; } if ((scriptFile.hasDivergedFromVM() && !scriptFile.isMergingToVM()) || scriptFile.isDivergingFromVM()) { return null; } if (!scriptFile.hasScripts([script])) { return null; } const {lineNumber, columnNumber = 0} = rawLocation; return uiSourceCode.uiLocation(lineNumber, columnNumber); } uiLocationToRawLocations(uiSourceCode: Workspace.UISourceCode.UISourceCode, lineNumber: number, columnNumber: number): SDK.DebuggerModel.Location[] { const scriptFile = this.#uiSourceCodeToScriptFile.get(uiSourceCode); if (!scriptFile) { return []; } const {script} = scriptFile; if (!script) { return []; } return [this.debuggerModel.createRawLocation(script, lineNumber, columnNumber)]; } uiLocationRangeToRawLocationRanges( uiSourceCode: Workspace.UISourceCode.UISourceCode, {startLine, startColumn, endLine, endColumn}: TextUtils.TextRange.TextRange): SDK.DebuggerModel.LocationRange[]|null { const scriptFile = this.#uiSourceCodeToScriptFile.get(uiSourceCode); if (!scriptFile) { return null; } const {script} = scriptFile; if (!script) { return null; } const start = this.debuggerModel.createRawLocation(script, startLine, startColumn); const end = this.debuggerModel.createRawLocation(script, endLine, endColumn); return [{start, end}]; } private inspectedURLChanged(event: Common.EventTarget.EventTargetEvent): void { for (let target: SDK.Target.Target|null = this.debuggerModel.target(); target !== event.data; target = target.parentTarget()) { if (target === null) { return; } } // Just remove and readd all scripts to ensure their URLs are reflected correctly. for (const script of Array.from(this.#scriptToUISourceCode.keys())) { this.removeScript(script); this.addScript(script); } } private addScript(script: SDK.Script.Script): void { // Ignore live edit scripts here. if (script.isLiveEdit()) { return; } let url = script.sourceURL; if (!url) { return; } if (script.hasSourceURL) { // Try to resolve `//# sourceURL=` annotations relative to // the base URL, according to the sourcemap specification. url = SDK.SourceMapManager.SourceMapManager.resolveRelativeSourceURL(script.debuggerModel.target(), url); } else { // Ignore inline