UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

410 lines (373 loc) 13.7 kB
// Copyright 2017 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. /** * @implements {SDK.SDKModelObserver<!SDK.ResourceTreeModel>} */ Bindings.ResourceMapping = class { /** * @param {!SDK.TargetManager} targetManager * @param {!Workspace.Workspace} workspace */ constructor(targetManager, workspace) { this._workspace = workspace; /** @type {!Map<!SDK.ResourceTreeModel, !Bindings.ResourceMapping.ModelInfo>} */ this._modelToInfo = new Map(); targetManager.observeModels(SDK.ResourceTreeModel, this); } /** * @override * @param {!SDK.ResourceTreeModel} resourceTreeModel */ modelAdded(resourceTreeModel) { const info = new Bindings.ResourceMapping.ModelInfo(this._workspace, resourceTreeModel); this._modelToInfo.set(resourceTreeModel, info); } /** * @override * @param {!SDK.ResourceTreeModel} resourceTreeModel */ modelRemoved(resourceTreeModel) { const info = this._modelToInfo.get(resourceTreeModel); info.dispose(); this._modelToInfo.delete(resourceTreeModel); } /** * @param {!SDK.Target} target * @return {?Bindings.ResourceMapping.ModelInfo} */ _infoForTarget(target) { const resourceTreeModel = target.model(SDK.ResourceTreeModel); return resourceTreeModel ? this._modelToInfo.get(resourceTreeModel) : null; } /** * @param {!SDK.CSSLocation} cssLocation * @return {?Workspace.UILocation} */ cssLocationToUILocation(cssLocation) { const header = cssLocation.header(); if (!header) return null; const info = this._infoForTarget(cssLocation.cssModel().target()); if (!info) return null; const uiSourceCode = info._project.uiSourceCodeForURL(cssLocation.url); if (!uiSourceCode) return null; const offset = header[Bindings.ResourceMapping._offsetSymbol] || TextUtils.TextRange.createFromLocation(header.startLine, header.startColumn); const lineNumber = cssLocation.lineNumber + offset.startLine - header.startLine; let columnNumber = cssLocation.columnNumber; if (cssLocation.lineNumber === header.startLine) columnNumber += offset.startColumn - header.startColumn; return uiSourceCode.uiLocation(lineNumber, columnNumber); } /** * @param {!SDK.DebuggerModel.Location} jsLocation * @return {?Workspace.UILocation} */ jsLocationToUILocation(jsLocation) { const script = jsLocation.script(); if (!script) return null; const info = this._infoForTarget(jsLocation.debuggerModel.target()); if (!info) return null; const uiSourceCode = info._project.uiSourceCodeForURL(script.sourceURL); if (!uiSourceCode) return null; const offset = script[Bindings.ResourceMapping._offsetSymbol] || TextUtils.TextRange.createFromLocation(script.lineOffset, script.columnOffset); const lineNumber = jsLocation.lineNumber + offset.startLine - script.lineOffset; let columnNumber = jsLocation.columnNumber; if (jsLocation.lineNumber === script.lineOffset) columnNumber += offset.startColumn - script.columnOffset; return uiSourceCode.uiLocation(lineNumber, columnNumber); } /** * @param {!Workspace.UISourceCode} uiSourceCode * @param {number} lineNumber * @param {number} columnNumber * @return {?SDK.DebuggerModel.Location} */ uiLocationToJSLocation(uiSourceCode, lineNumber, columnNumber) { if (!uiSourceCode[Bindings.ResourceMapping._symbol]) return null; const target = Bindings.NetworkProject.targetForUISourceCode(uiSourceCode); if (!target) return null; const debuggerModel = target.model(SDK.DebuggerModel); if (!debuggerModel) return null; return debuggerModel.createRawLocationByURL(uiSourceCode.url(), lineNumber, columnNumber); } /** * @param {!SDK.Target} target */ _resetForTest(target) { const resourceTreeModel = target.model(SDK.ResourceTreeModel); const info = resourceTreeModel ? this._modelToInfo.get(resourceTreeModel) : null; if (info) info._resetForTest(); } }; Bindings.ResourceMapping.ModelInfo = class { /** * @param {!Workspace.Workspace} workspace * @param {!SDK.ResourceTreeModel} resourceTreeModel */ constructor(workspace, resourceTreeModel) { const target = resourceTreeModel.target(); this._project = new Bindings.ContentProviderBasedProject( workspace, 'resources:' + target.id(), Workspace.projectTypes.Network, '', false /* isServiceProject */); Bindings.NetworkProject.setTargetForProject(this._project, target); /** @type {!Map<string, !Bindings.ResourceMapping.Binding>} */ this._bindings = new Map(); const cssModel = target.model(SDK.CSSModel); this._cssModel = cssModel; this._eventListeners = [ resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.ResourceAdded, this._resourceAdded, this), resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.FrameWillNavigate, this._frameWillNavigate, this), resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.FrameDetached, this._frameDetached, this), cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetChanged, this._styleSheetChanged, this) ]; } /** * @param {!Common.Event} event */ _styleSheetChanged(event) { const header = this._cssModel.styleSheetHeaderForId(event.data.styleSheetId); if (!header || !header.isInline) return; const binding = this._bindings.get(header.resourceURL()); if (!binding) return; binding._styleSheetChanged(header, event.data.edit); } /** * @param {!SDK.Resource} resource */ _acceptsResource(resource) { const resourceType = resource.resourceType(); // Only load selected resource types from resources. if (resourceType !== Common.resourceTypes.Image && resourceType !== Common.resourceTypes.Font && resourceType !== Common.resourceTypes.Document && resourceType !== Common.resourceTypes.Manifest) return false; // Ignore non-images and non-fonts. if (resourceType === Common.resourceTypes.Image && resource.mimeType && !resource.mimeType.startsWith('image')) return false; if (resourceType === Common.resourceTypes.Font && resource.mimeType && !resource.mimeType.includes('font')) return false; if ((resourceType === Common.resourceTypes.Image || resourceType === Common.resourceTypes.Font) && resource.contentURL().startsWith('data:')) return false; return true; } /** * @param {!Common.Event} event */ _resourceAdded(event) { const resource = /** @type {!SDK.Resource} */ (event.data); if (!this._acceptsResource(resource)) return; let binding = this._bindings.get(resource.url); if (!binding) { binding = new Bindings.ResourceMapping.Binding(this._project, resource); this._bindings.set(resource.url, binding); } else { binding.addResource(resource); } } /** * @param {!SDK.ResourceTreeFrame} frame */ _removeFrameResources(frame) { for (const resource of frame.resources()) { if (!this._acceptsResource(resource)) continue; const binding = this._bindings.get(resource.url); if (binding._resources.size === 1) { binding.dispose(); this._bindings.delete(resource.url); } else { binding.removeResource(resource); } } } /** * @param {!Common.Event} event */ _frameWillNavigate(event) { const frame = /** @type {!SDK.ResourceTreeFrame} */ (event.data); this._removeFrameResources(frame); } /** * @param {!Common.Event} event */ _frameDetached(event) { const frame = /** @type {!SDK.ResourceTreeFrame} */ (event.data); this._removeFrameResources(frame); } _resetForTest() { for (const binding of this._bindings.valuesArray()) binding.dispose(); this._bindings.clear(); } dispose() { Common.EventTarget.removeEventListeners(this._eventListeners); for (const binding of this._bindings.valuesArray()) binding.dispose(); this._bindings.clear(); this._project.removeProject(); } }; /** * @implements {Common.ContentProvider} */ Bindings.ResourceMapping.Binding = class { /** * @param {!Bindings.ContentProviderBasedProject} project * @param {!SDK.Resource} resource */ constructor(project, resource) { this._resources = new Set([resource]); this._project = project; this._uiSourceCode = this._project.createUISourceCode(resource.url, resource.contentType()); this._uiSourceCode[Bindings.ResourceMapping._symbol] = true; Bindings.NetworkProject.setInitialFrameAttribution(this._uiSourceCode, resource.frameId); this._project.addUISourceCodeWithProvider( this._uiSourceCode, this, Bindings.resourceMetadata(resource), resource.mimeType); /** @type {!Array<{stylesheet: !SDK.CSSStyleSheetHeader, edit: !SDK.CSSModel.Edit}>} */ this._edits = []; } /** * @return {!Array<!SDK.CSSStyleSheetHeader>} */ _inlineStyles() { const target = Bindings.NetworkProject.targetForUISourceCode(this._uiSourceCode); const cssModel = target.model(SDK.CSSModel); const stylesheets = []; if (cssModel) { for (const headerId of cssModel.styleSheetIdsForURL(this._uiSourceCode.url())) { const header = cssModel.styleSheetHeaderForId(headerId); if (header) stylesheets.push(header); } } return stylesheets; } /** * @return {!Array<!SDK.Script>} */ _inlineScripts() { const target = Bindings.NetworkProject.targetForUISourceCode(this._uiSourceCode); const debuggerModel = target.model(SDK.DebuggerModel); if (!debuggerModel) return []; return debuggerModel.scriptsForSourceURL(this._uiSourceCode.url()); } /** * @param {!SDK.CSSStyleSheetHeader} stylesheet * @param {!SDK.CSSModel.Edit} edit */ async _styleSheetChanged(stylesheet, edit) { this._edits.push({stylesheet, edit}); if (this._edits.length > 1) return; // There is already a _styleSheetChanged loop running const content = await this._uiSourceCode.requestContent(); if (content !== null) this._innerStyleSheetChanged(content); this._edits = []; } /** * @param {string} content */ _innerStyleSheetChanged(content) { const scripts = this._inlineScripts(); const styles = this._inlineStyles(); let text = new TextUtils.Text(content); for (const data of this._edits) { const edit = data.edit; const stylesheet = data.stylesheet; const startLocation = stylesheet[Bindings.ResourceMapping._offsetSymbol] || TextUtils.TextRange.createFromLocation(stylesheet.startLine, stylesheet.startColumn); const oldRange = edit.oldRange.relativeFrom(startLocation.startLine, startLocation.startColumn); const newRange = edit.newRange.relativeFrom(startLocation.startLine, startLocation.startColumn); text = new TextUtils.Text(text.replaceRange(oldRange, edit.newText)); for (const script of scripts) { const scriptOffset = script[Bindings.ResourceMapping._offsetSymbol] || TextUtils.TextRange.createFromLocation(script.lineOffset, script.columnOffset); if (!scriptOffset.follows(oldRange)) continue; script[Bindings.ResourceMapping._offsetSymbol] = scriptOffset.rebaseAfterTextEdit(oldRange, newRange); Bindings.debuggerWorkspaceBinding.updateLocations(script); } for (const style of styles) { const styleOffset = style[Bindings.ResourceMapping._offsetSymbol] || TextUtils.TextRange.createFromLocation(style.startLine, style.startColumn); if (!styleOffset.follows(oldRange)) continue; style[Bindings.ResourceMapping._offsetSymbol] = styleOffset.rebaseAfterTextEdit(oldRange, newRange); Bindings.cssWorkspaceBinding.updateLocations(style); } } this._uiSourceCode.addRevision(text.value()); } /** * @param {!SDK.Resource} resource */ addResource(resource) { this._resources.add(resource); Bindings.NetworkProject.addFrameAttribution(this._uiSourceCode, resource.frameId); } /** * @param {!SDK.Resource} resource */ removeResource(resource) { this._resources.delete(resource); Bindings.NetworkProject.removeFrameAttribution(this._uiSourceCode, resource.frameId); } dispose() { this._project.removeFile(this._uiSourceCode.url()); } /** * @override * @return {string} */ contentURL() { return this._resources.firstValue().contentURL(); } /** * @override * @return {!Common.ResourceType} */ contentType() { return this._resources.firstValue().contentType(); } /** * @override * @return {!Promise<boolean>} */ contentEncoded() { return this._resources.firstValue().contentEncoded(); } /** * @override * @return {!Promise<?string>} */ requestContent() { return this._resources.firstValue().requestContent(); } /** * @override * @param {string} query * @param {boolean} caseSensitive * @param {boolean} isRegex * @return {!Promise<!Array<!Common.ContentProvider.SearchMatch>>} */ searchInContent(query, caseSensitive, isRegex) { return this._resources.firstValue().searchInContent(query, caseSensitive, isRegex); } }; Bindings.ResourceMapping._symbol = Symbol('Bindings.ResourceMapping._symbol'); Bindings.ResourceMapping._offsetSymbol = Symbol('Bindings.ResourceMapping._offsetSymbol');