| /* |
| * Copyright (C) 2013-2019 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. AND ITS 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 APPLE INC. OR ITS 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. |
| */ |
| |
| WI.ResourceContentView = class ResourceContentView extends WI.ContentView |
| { |
| constructor(resource, styleClassName) |
| { |
| console.assert(resource instanceof WI.Resource || resource instanceof WI.CSSStyleSheet, resource); |
| console.assert(typeof styleClassName === "string"); |
| |
| super(resource); |
| |
| this._resource = resource; |
| |
| this.element.classList.add(styleClassName, "resource"); |
| |
| this._spinnerTimeout = setTimeout(() => { |
| if (!this._hasContent()) { |
| // Append a spinner while waiting for contentAvailable. Subclasses are responsible for |
| // removing the spinner before showing the resource content by calling removeLoadingIndicator. |
| let spinner = new WI.IndeterminateProgressSpinner; |
| this.element.appendChild(spinner.element); |
| } |
| |
| this._spinnerTimeout = undefined; |
| }, 100); |
| |
| this.element.addEventListener("click", this._mouseWasClicked.bind(this), false); |
| |
| // Request content last so the spinner will always be removed in case the content is immediately available. |
| resource.requestContent().then(this._contentAvailable.bind(this)).catch(this.showGenericErrorMessage.bind(this)); |
| |
| if (!this.managesOwnIssues) { |
| WI.consoleManager.addEventListener(WI.ConsoleManager.Event.IssueAdded, this._issueWasAdded, this); |
| |
| var issues = WI.consoleManager.issuesForSourceCode(resource); |
| for (var i = 0; i < issues.length; ++i) |
| this.addIssue(issues[i]); |
| } |
| |
| if (WI.NetworkManager.supportsOverridingResponses()) { |
| if (resource.localResourceOverride) { |
| this._localResourceOverrideBannerView = new WI.LocalResourceOverrideLabelView(resource.localResourceOverride); |
| |
| this._importLocalResourceOverrideButtonNavigationItem = new WI.ButtonNavigationItem("import-local-resource-override", WI.UIString("Import"), "Images/Import.svg", 15, 15); |
| this._importLocalResourceOverrideButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; |
| this._importLocalResourceOverrideButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; |
| this._importLocalResourceOverrideButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportLocalResourceOverride, this); |
| |
| if (resource.localResourceOverride.canMapToFile) { |
| this._mapLocalResourceOverrideToFileButtonNavigationItem = new WI.ButtonNavigationItem("map-local-resource-override", WI.UIString("Map to File"), "Images/Disk.svg", 15, 15); |
| this._mapLocalResourceOverrideToFileButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; |
| this._mapLocalResourceOverrideToFileButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; |
| this._mapLocalResourceOverrideToFileButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleMapLocalResourceOverrideToFile, this); |
| |
| resource.addEventListener(WI.LocalResource.Event.MappedFilePathChanged, this._handleMappedFilePathChanged, this); |
| } |
| |
| this._removeLocalResourceOverrideButtonNavigationItem = new WI.ButtonNavigationItem("remove-local-resource-override", WI.UIString("Delete Local Override"), "Images/NavigationItemTrash.svg", 15, 15); |
| this._removeLocalResourceOverrideButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleRemoveLocalResourceOverride, this); |
| this._removeLocalResourceOverrideButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; |
| } else { |
| this._localResourceOverrideBannerView = new WI.LocalResourceOverrideWarningView(resource); |
| |
| this._createLocalResourceOverrideButtonNavigationItem = new WI.ButtonNavigationItem("create-local-resource-override", this.createLocalResourceOverrideTooltip, "Images/NavigationItemNetworkOverride.svg", 13, 14); |
| this._createLocalResourceOverrideButtonNavigationItem.enabled = false; // Enabled when the content is available. |
| this._createLocalResourceOverrideButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; |
| if (WI.NetworkManager.supportsOverridingRequests() || WI.NetworkManager.supportsBlockingRequests()) |
| WI.addMouseDownContextMenuHandlers(this._createLocalResourceOverrideButtonNavigationItem.element, this._populateCreateLocalResourceOverrideContextMenu.bind(this)); |
| else |
| this._createLocalResourceOverrideButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateLocalResourceOverride, this); |
| } |
| |
| WI.networkManager.addEventListener(WI.NetworkManager.Event.LocalResourceOverrideAdded, this._handleLocalResourceOverrideChanged, this); |
| WI.networkManager.addEventListener(WI.NetworkManager.Event.LocalResourceOverrideRemoved, this._handleLocalResourceOverrideChanged, this); |
| } |
| } |
| |
| // Public |
| |
| get resource() { return this._resource; } |
| |
| get navigationItems() |
| { |
| let items = []; |
| |
| if (this._importLocalResourceOverrideButtonNavigationItem) |
| items.push(this._importLocalResourceOverrideButtonNavigationItem); |
| if (this._mapLocalResourceOverrideToFileButtonNavigationItem) |
| items.push(this._mapLocalResourceOverrideToFileButtonNavigationItem); |
| |
| if (items.length) |
| items.push(new WI.DividerNavigationItem); |
| |
| if (this._removeLocalResourceOverrideButtonNavigationItem) |
| items.push(this._removeLocalResourceOverrideButtonNavigationItem); |
| if (this._createLocalResourceOverrideButtonNavigationItem) |
| items.push(this._createLocalResourceOverrideButtonNavigationItem); |
| |
| return items; |
| } |
| |
| get supportsSave() |
| { |
| return this._resource.finished; |
| } |
| |
| get saveMode() |
| { |
| return WI.FileUtilities.SaveMode.SingleFile; |
| } |
| |
| get saveData() |
| { |
| let saveData = { |
| url: this._resource.url, |
| content: this._resource.content, |
| }; |
| |
| if (this._resource.urlComponents.path === "/") { |
| let extension = WI.fileExtensionForMIMEType(this._resource.mimeType); |
| if (extension) |
| saveData.suggestedName = `index.${extension}`; |
| } |
| |
| return saveData; |
| } |
| |
| contentAvailable(content, base64Encoded) |
| { |
| throw WI.NotImplementedError.subclassMustOverride(); |
| } |
| |
| get createLocalResourceOverrideTooltip() |
| { |
| return WI.UIString("Click to import a file and create a Local Override\nShift-click to create a Local Override from this content"); |
| } |
| |
| requestLocalResourceOverrideInitialContent() |
| { |
| // Overridden by subclasses if needed. |
| |
| return new Promise((resolve, reject) => { |
| WI.FileUtilities.import(async (fileList) => { |
| console.assert(fileList.length === 1); |
| |
| this._getContentForLocalResourceOverrideFromFile(fileList[0], ({mimeType, base64Encoded, content}) => { |
| resolve({mimeType, base64Encoded, content}); |
| }); |
| }); |
| }); |
| } |
| |
| showGenericNoContentMessage() |
| { |
| this.showMessage(WI.UIString("Resource has no content.")); |
| |
| this.dispatchEventToListeners(WI.ResourceContentView.Event.ContentError); |
| } |
| |
| showNoCachedContentMessage() |
| { |
| this.showMessage(WI.UIString("Resource has no cached content.", "Resource has no cached content. @ Resource Preview", "An error message shown when there is no cached content for a HTTP 304 Not Modified resource response.")); |
| |
| this.dispatchEventToListeners(WI.ResourceContentView.Event.ContentError); |
| } |
| |
| showGenericErrorMessage() |
| { |
| this._contentError(WI.UIString("An error occurred trying to load the resource.")); |
| } |
| |
| showMessage(message) |
| { |
| this.removeAllSubviews(); |
| |
| this.element.appendChild(WI.createMessageTextView(message)); |
| } |
| |
| addIssue(issue) |
| { |
| // This generically shows only the last issue, subclasses can override for better handling. |
| this.removeAllSubviews(); |
| |
| this.element.appendChild(WI.createMessageTextView(issue.text, issue.level === WI.IssueMessage.Level.Error)); |
| } |
| |
| closed() |
| { |
| super.closed(); |
| |
| if (WI.NetworkManager.supportsOverridingResponses()) { |
| WI.networkManager.removeEventListener(WI.NetworkManager.Event.LocalResourceOverrideAdded, this._handleLocalResourceOverrideChanged, this); |
| WI.networkManager.removeEventListener(WI.NetworkManager.Event.LocalResourceOverrideRemoved, this._handleLocalResourceOverrideChanged, this); |
| } |
| |
| if (!this.managesOwnIssues) |
| WI.consoleManager.removeEventListener(WI.ConsoleManager.Event.IssueAdded, this._issueWasAdded, this); |
| } |
| |
| // Protected |
| |
| removeLoadingIndicator() |
| { |
| if (this._spinnerTimeout) { |
| clearTimeout(this._spinnerTimeout); |
| this._spinnerTimeout = undefined; |
| } |
| |
| this.removeAllSubviews(); |
| |
| if (this._localResourceOverrideBannerView) |
| this.addSubview(this._localResourceOverrideBannerView); |
| } |
| |
| // Private |
| |
| _contentAvailable(parameters) |
| { |
| if (parameters.error) { |
| // A 304 Not Modified request that is missing content means we didn't have a cached copy. |
| if (parameters.sourceCode.statusCode == 304 && parameters.reason === "Missing content of resource for given requestId") { |
| this.showNoCachedContentMessage(); |
| return; |
| } |
| |
| this._contentError(parameters.error); |
| return; |
| } |
| |
| if (parameters.message) { |
| this.showMessage(parameters.message); |
| return; |
| } |
| |
| if (parameters.sourceCode instanceof WI.LocalResource) { |
| if (this.resource.mappedFilePath) { |
| this._handleMappedFilePathChanged(); |
| return; |
| } |
| } |
| |
| // The view maybe populated with inline scripts content by the time resource |
| // content arrives. SourceCodeTextEditor will handle that. |
| if (this._hasContent()) |
| return; |
| |
| if (!parameters.sourceCode.content && !parameters.sourceCode.mimeType) { |
| this.showGenericNoContentMessage(); |
| return; |
| } |
| |
| // Content is ready to show, call the public method now. |
| console.assert(parameters.sourceCode === this._resource); |
| this.contentAvailable(parameters.sourceCode.content, parameters.base64Encoded); |
| |
| if (this._createLocalResourceOverrideButtonNavigationItem) |
| this._createLocalResourceOverrideButtonNavigationItem.enabled = WI.networkManager.canBeOverridden(this._resource); |
| } |
| |
| _contentError(error) |
| { |
| if (this._hasContent()) |
| return; |
| |
| this.removeLoadingIndicator(); |
| |
| this.element.appendChild(WI.createMessageTextView(error, true)); |
| |
| this.dispatchEventToListeners(WI.ResourceContentView.Event.ContentError); |
| } |
| |
| _hasContent() |
| { |
| return this.element.hasChildNodes() && !this.element.querySelector(".indeterminate-progress-spinner"); |
| } |
| |
| _issueWasAdded(event) |
| { |
| console.assert(!this.managesOwnIssues); |
| |
| var issue = event.data.issue; |
| if (!WI.ConsoleManager.issueMatchSourceCode(issue, this._resource)) |
| return; |
| |
| this.addIssue(issue); |
| } |
| |
| async _getContentForLocalResourceOverrideFromFile(file, callback) |
| { |
| let mimeType = file.type || WI.mimeTypeForFileExtension(WI.fileExtensionForFilename(file.name)); |
| if (WI.shouldTreatMIMETypeAsText(mimeType)) { |
| await WI.FileUtilities.readText(file, async ({text}) => { |
| await callback({ |
| mimeType, |
| base64Encoded: false, |
| content: text, |
| }); |
| }); |
| } else { |
| await WI.FileUtilities.readData(file, async ({mimeType, base64Encoded, content}) => { |
| await callback({mimeType, base64Encoded, content}); |
| }); |
| } |
| } |
| |
| async _createAndShowLocalResourceOverride(type, {requestInitialContent} = {}) |
| { |
| let initialContent = requestInitialContent ? await this.requestLocalResourceOverrideInitialContent() : {}; |
| let localResourceOverride = await this._resource.createLocalResourceOverride(type, initialContent); |
| WI.networkManager.addLocalResourceOverride(localResourceOverride); |
| WI.showLocalResourceOverride(localResourceOverride, {overriddenResource: this._resource}); |
| } |
| |
| _populateCreateLocalResourceOverrideContextMenu(contextMenu, event) |
| { |
| if (!this._createLocalResourceOverrideButtonNavigationItem.enabled) |
| return; |
| |
| if (WI.NetworkManager.supportsOverridingRequests()) { |
| contextMenu.appendItem(WI.UIString("Create Request Local Override"), () => { |
| // Request overrides cannot be created from a file as files don't have network info. |
| this._createAndShowLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Request); |
| }); |
| } |
| |
| contextMenu.appendItem(WI.UIString("Create Response Local Override"), () => { |
| this._createAndShowLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Response, { |
| requestInitialContent: !event.shiftKey, |
| }); |
| }); |
| |
| if (WI.NetworkManager.supportsBlockingRequests()) { |
| contextMenu.appendItem(WI.UIString("Block Request URL"), async () => { |
| let localResourceOverride = await this._resource.createLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Block); |
| WI.networkManager.addLocalResourceOverride(localResourceOverride); |
| }); |
| } |
| } |
| |
| _handleCreateLocalResourceOverride(event) |
| { |
| let {nativeEvent} = event.data; |
| |
| this._createAndShowLocalResourceOverride(WI.LocalResourceOverride.InterceptType.Response, { |
| requestInitialContent: !nativeEvent.shiftKey, |
| }); |
| } |
| |
| _handleImportLocalResourceOverride(event) |
| { |
| let localResourceOverride = this.resource.localResourceOverride || WI.networkManager.localResourceOverridesForURL(this.resource.url)[0]; |
| console.assert(localResourceOverride); |
| |
| WI.FileUtilities.import(async (fileList) => { |
| console.assert(fileList.length === 1); |
| |
| await this._getContentForLocalResourceOverrideFromFile(fileList[0], ({mimeType, base64Encoded, content}) => { |
| let revision = localResourceOverride.localResource.editableRevision; |
| revision.updateRevisionContent(content, {base64Encoded, mimeType}); |
| }); |
| |
| if (!this._resource.localResourceOverride) |
| WI.showLocalResourceOverride(localResourceOverride, {overriddenResource: this._resource}); |
| }); |
| } |
| |
| _handleMapLocalResourceOverrideToFile(event) |
| { |
| WI.FileUtilities.import((files) => { |
| console.assert(files.length === 1, files); |
| |
| this.resource.mappedFilePath = files[0].getPath(); |
| }); |
| } |
| |
| _handleMappedFilePathChanged(event) |
| { |
| let mappedFilePath = this.resource.mappedFilePath; |
| |
| let mappedFilePathLink = document.createElement("a"); |
| mappedFilePathLink.href = "file://" + mappedFilePath; |
| mappedFilePathLink.textContent = mappedFilePath.insertWordBreakCharacters(); |
| mappedFilePathLink.addEventListener("click", (event) => { |
| event.stop(); |
| |
| InspectorFrontendHost.revealFileExternally(event.target.href); |
| }); |
| |
| let fragment = document.createDocumentFragment(); |
| String.format(WI.UIString("Mapped to \u201C%s\u201D"), [mappedFilePathLink], String.standardFormatters, fragment, (a, b) => { |
| a.append(b); |
| return a; |
| }); |
| this.showMessage(fragment); |
| |
| if (this._localResourceOverrideBannerView) { |
| this.element.insertBefore(this._localResourceOverrideBannerView.element, this.element.firstChild); |
| this.addSubview(this._localResourceOverrideBannerView); |
| } |
| } |
| |
| _handleRemoveLocalResourceOverride(event) |
| { |
| let localResourceOverride = this.resource.localResourceOverride || WI.networkManager.localResourceOverridesForURL(this._resource.url)[0]; |
| console.assert(localResourceOverride); |
| WI.networkManager.removeLocalResourceOverride(localResourceOverride); |
| } |
| |
| _handleLocalResourceOverrideChanged(event) |
| { |
| let {localResourceOverride} = event.data; |
| if (!localResourceOverride.matches(this._resource.url)) |
| return; |
| |
| if (this._createLocalResourceOverrideButtonNavigationItem) |
| this._createLocalResourceOverrideButtonNavigationItem.enabled = WI.networkManager.canBeOverridden(this._resource); |
| } |
| |
| _mouseWasClicked(event) |
| { |
| WI.handlePossibleLinkClick(event, {frame: this._resource.parentFrame}); |
| } |
| }; |
| |
| WI.ResourceContentView.Event = { |
| ContentError: "resource-content-view-content-error", |
| }; |