|  | // 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>} | 
|  | */ | 
|  | export default class ResourceMapping { | 
|  | /** | 
|  | * @param {!SDK.TargetManager} targetManager | 
|  | * @param {!Workspace.Workspace} workspace | 
|  | */ | 
|  | constructor(targetManager, workspace) { | 
|  | this._workspace = workspace; | 
|  | /** @type {!Map<!SDK.ResourceTreeModel, !ModelInfo>} */ | 
|  | this._modelToInfo = new Map(); | 
|  | targetManager.observeModels(SDK.ResourceTreeModel, this); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @override | 
|  | * @param {!SDK.ResourceTreeModel} resourceTreeModel | 
|  | */ | 
|  | modelAdded(resourceTreeModel) { | 
|  | const info = new 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 {?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[_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[_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 {!Array<!SDK.DebuggerModel.Location>} | 
|  | */ | 
|  | uiLocationToJSLocations(uiSourceCode, lineNumber, columnNumber) { | 
|  | if (!uiSourceCode[_symbol]) { | 
|  | return []; | 
|  | } | 
|  | const target = Bindings.NetworkProject.targetForUISourceCode(uiSourceCode); | 
|  | if (!target) { | 
|  | return []; | 
|  | } | 
|  | const debuggerModel = target.model(SDK.DebuggerModel); | 
|  | if (!debuggerModel) { | 
|  | return []; | 
|  | } | 
|  | const location = debuggerModel.createRawLocationByURL(uiSourceCode.url(), lineNumber, columnNumber); | 
|  | if (location && location.script().containsLocation(lineNumber, columnNumber)) { | 
|  | return [location]; | 
|  | } | 
|  | return []; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param {!Workspace.UILocation} uiLocation | 
|  | * @return {!Array<!SDK.CSSLocation>} | 
|  | */ | 
|  | uiLocationToCSSLocations(uiLocation) { | 
|  | if (!uiLocation.uiSourceCode[_symbol]) { | 
|  | return []; | 
|  | } | 
|  | const target = Bindings.NetworkProject.targetForUISourceCode(uiLocation.uiSourceCode); | 
|  | if (!target) { | 
|  | return []; | 
|  | } | 
|  | const cssModel = target.model(SDK.CSSModel); | 
|  | if (!cssModel) { | 
|  | return []; | 
|  | } | 
|  | return cssModel.createRawLocationsByURL( | 
|  | uiLocation.uiSourceCode.url(), uiLocation.lineNumber, uiLocation.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(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | class ModelInfo { | 
|  | /** | 
|  | * @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, !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 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} | 
|  | */ | 
|  | class Binding { | 
|  | /** | 
|  | * @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[_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[_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[_offsetSymbol] || TextUtils.TextRange.createFromLocation(script.lineOffset, script.columnOffset); | 
|  | if (!scriptOffset.follows(oldRange)) { | 
|  | continue; | 
|  | } | 
|  | script[_offsetSymbol] = scriptOffset.rebaseAfterTextEdit(oldRange, newRange); | 
|  | Bindings.debuggerWorkspaceBinding.updateLocations(script); | 
|  | } | 
|  | for (const style of styles) { | 
|  | const styleOffset = | 
|  | style[_offsetSymbol] || TextUtils.TextRange.createFromLocation(style.startLine, style.startColumn); | 
|  | if (!styleOffset.follows(oldRange)) { | 
|  | continue; | 
|  | } | 
|  | style[_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<!Common.DeferredContent>} | 
|  | */ | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | export const _symbol = Symbol('Bindings.ResourceMapping._symbol'); | 
|  | export const _offsetSymbol = Symbol('Bindings.ResourceMapping._offsetSymbol'); | 
|  |  | 
|  | /* Legacy exported object */ | 
|  | self.Bindings = self.Bindings || {}; | 
|  |  | 
|  | /* Legacy exported object */ | 
|  | Bindings = Bindings || {}; | 
|  |  | 
|  | /** @constructor */ | 
|  | Bindings.ResourceMapping = ResourceMapping; | 
|  |  | 
|  | Bindings.ResourceMapping._symbol = _symbol; | 
|  | Bindings.ResourceMapping._offsetSymbol = _offsetSymbol; |