451 lines
18 KiB
JavaScript
451 lines
18 KiB
JavaScript
/*
|
|
* 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",
|
|
};
|