blob: 52aac3f78943957756b6eba9f3c92afaa1daca8d [file] [log] [blame]
/*
* 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(() => {
// 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]);
}
this._showingLocalResourceOverride = false;
if (WI.NetworkManager.supportsLocalResourceOverrides()) {
if (resource.isLocalResourceOverride) {
this._showingLocalResourceOverride = true;
this._localResourceOverrideBannerView = new WI.LocalResourceOverrideLabelView(resource);
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);
this._removeLocalResourceOverrideButtonNavigationItem = new WI.ButtonNavigationItem("remove-local-resource-override", WI.UIString("Remove 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.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateLocalResourceOverride, this);
this._createLocalResourceOverrideButtonNavigationItem.enabled = false; // Enabled when the content is available.
this._createLocalResourceOverrideButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
}
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 showingLocalResourceOverride() { return this._showingLocalResourceOverride; }
get navigationItems()
{
let items = [];
if (this._importLocalResourceOverrideButtonNavigationItem)
items.push(this._importLocalResourceOverrideButtonNavigationItem, 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 saveData()
{
return {url: this._resource.url, content: this._resource.content};
}
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(callback)
{
// Overridden by subclasses if needed.
WI.FileUtilities.import(async (fileList) => {
console.assert(fileList.length === 1);
this._getContentForLocalResourceOverrideFromFile(fileList[0], ({mimeType, base64Encoded, content}) => {
callback({
initialMIMEType: mimeType,
initialBase64Encoded: base64Encoded,
initialContent: content,
});
});
});
}
showGenericNoContentMessage()
{
this.showMessage(WI.UIString("Resource has no content"));
this.dispatchEventToListeners(WI.ResourceContentView.Event.ContentError);
}
showGenericErrorMessage()
{
this._contentError(WI.UIString("An error occurred trying to load the resource."));
}
showMessage(message)
{
this.element.removeChildren();
this.element.appendChild(WI.createMessageTextView(message));
}
addIssue(issue)
{
// This generically shows only the last issue, subclasses can override for better handling.
this.element.removeChildren();
this.element.appendChild(WI.createMessageTextView(issue.text, issue.level === WI.IssueMessage.Level.Error));
}
closed()
{
super.closed();
if (WI.NetworkManager.supportsLocalResourceOverrides())
WI.networkManager.removeEventListener(null, null, this);
if (!this.managesOwnIssues)
WI.consoleManager.removeEventListener(null, null, 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) {
this._contentError(parameters.error);
return;
}
if (parameters.message) {
this.showMessage(parameters.message);
return;
}
// The view maybe populated with inline scripts content by the time resource
// content arrives. SourceCodeTextEditor will handle that.
if (this._hasContent())
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 initialMIMEType = file.type || WI.mimeTypeForFileExtension(WI.fileExtensionForFilename(file.name));
if (WI.shouldTreatMIMETypeAsText(initialMIMEType)) {
await WI.FileUtilities.readText(file, async ({text}) => {
await callback({
mimeType: initialMIMEType,
base64Encoded: false,
content: text,
});
});
} else {
await WI.FileUtilities.readData(file, async ({mimeType, base64Encoded, content}) => {
await callback({mimeType, base64Encoded, content});
});
}
}
_handleCreateLocalResourceOverride(event)
{
let {nativeEvent} = event.data;
let createLocalResourceOverride = async (initialContent) => {
let localResourceOverride = await this._resource.createLocalResourceOverride(initialContent);
WI.networkManager.addLocalResourceOverride(localResourceOverride);
WI.showLocalResourceOverride(localResourceOverride);
};
if (nativeEvent.shiftKey)
createLocalResourceOverride({});
else
this.requestLocalResourceOverrideInitialContent(createLocalResourceOverride);
}
_handleImportLocalResourceOverride(event)
{
console.assert(this._showingLocalResourceOverride);
WI.FileUtilities.import(async (fileList) => {
console.assert(fileList.length === 1);
let localResourceOverride = WI.networkManager.localResourceOverrideForURL(this.resource.url);
console.assert(localResourceOverride);
await this._getContentForLocalResourceOverrideFromFile(fileList[0], ({mimeType, base64Encoded, content}) => {
let revision = localResourceOverride.localResource.editableRevision;
revision.updateRevisionContent(content, {base64Encoded, mimeType});
});
if (!this.showingLocalResourceOverride)
WI.showLocalResourceOverride(localResourceOverride);
});
}
_handleRemoveLocalResourceOverride(event)
{
console.assert(this._showingLocalResourceOverride);
let localResourceOverride = WI.networkManager.localResourceOverrideForURL(this._resource.url);
WI.networkManager.removeLocalResourceOverride(localResourceOverride);
}
_handleLocalResourceOverrideChanged(event)
{
if (this._resource.url !== event.data.localResourceOverride.url)
return;
if (this._createLocalResourceOverrideButtonNavigationItem)
this._createLocalResourceOverrideButtonNavigationItem.enabled = WI.networkManager.canBeOverridden(this._resource);
}
_mouseWasClicked(event)
{
WI.handlePossibleLinkClick(event, this._resource.parentFrame);
}
};
WI.ResourceContentView.Event = {
ContentError: "resource-content-view-content-error",
};