| /* |
| * Copyright (C) 2013 Google 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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. |
| */ |
| |
| import * as Common from '../../core/common/common.js'; |
| import * as Host from '../../core/host/host.js'; |
| import * as i18n from '../../core/i18n/i18n.js'; |
| import * as Platform from '../../core/platform/platform.js'; |
| |
| import type * as TextUtils from '../text_utils/text_utils.js'; |
| |
| import type {IsolatedFileSystemManager} from './IsolatedFileSystemManager.js'; |
| import {Events} from './IsolatedFileSystemManager.js'; |
| import {PlatformFileSystem} from './PlatformFileSystem.js'; |
| |
| const UIStrings = { |
| /** |
| *@description Text in Isolated File System of the Workspace settings in Settings |
| *@example {folder does not exist} PH1 |
| */ |
| fileSystemErrorS: 'File system error: {PH1}', |
| /** |
| *@description Error message when reading a remote blob |
| */ |
| blobCouldNotBeLoaded: 'Blob could not be loaded.', |
| /** |
| *@description Error message when reading a file. |
| *@example {c:\dir\file.js} PH1 |
| *@example {Underlying error} PH2 |
| */ |
| cantReadFileSS: 'Can\'t read file: {PH1}: {PH2}', |
| /** |
| *@description Error message when failing to load a file |
| *@example {c:\dir\file.js} PH1 |
| */ |
| unknownErrorReadingFileS: 'Unknown error reading file: {PH1}', |
| /** |
| *@description Text to show something is linked to another |
| *@example {example.url} PH1 |
| */ |
| linkedToS: 'Linked to {PH1}', |
| }; |
| const str_ = i18n.i18n.registerUIStrings('models/persistence/IsolatedFileSystem.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| export class IsolatedFileSystem extends PlatformFileSystem { |
| private readonly manager: IsolatedFileSystemManager; |
| private readonly embedderPathInternal: string; |
| private readonly domFileSystem: FileSystem; |
| private readonly excludedFoldersSetting: Common.Settings.Setting<{[path: string]: string[]}>; |
| private excludedFoldersInternal: Set<string>; |
| private readonly excludedEmbedderFolders: string[]; |
| private readonly initialFilePathsInternal: Set<Platform.DevToolsPath.EncodedPathString>; |
| private readonly initialGitFoldersInternal: Set<Platform.DevToolsPath.EncodedPathString>; |
| private readonly fileLocks: Map<string, Promise<void>>; |
| |
| constructor( |
| manager: IsolatedFileSystemManager, path: string, embedderPath: string, domFileSystem: FileSystem, type: string) { |
| super(path, type); |
| this.manager = manager; |
| this.embedderPathInternal = embedderPath; |
| this.domFileSystem = domFileSystem; |
| this.excludedFoldersSetting = |
| Common.Settings.Settings.instance().createLocalSetting('workspaceExcludedFolders', {}); |
| this.excludedFoldersInternal = new Set(this.excludedFoldersSetting.get()[path] || []); |
| this.excludedEmbedderFolders = []; |
| |
| this.initialFilePathsInternal = new Set(); |
| this.initialGitFoldersInternal = new Set(); |
| this.fileLocks = new Map(); |
| } |
| |
| static async create( |
| manager: IsolatedFileSystemManager, path: string, embedderPath: string, type: string, name: string, |
| rootURL: string): Promise<IsolatedFileSystem|null> { |
| const domFileSystem = Host.InspectorFrontendHost.InspectorFrontendHostInstance.isolatedFileSystem(name, rootURL); |
| if (!domFileSystem) { |
| return null as IsolatedFileSystem | null; |
| } |
| |
| const fileSystem = new IsolatedFileSystem(manager, path, embedderPath, domFileSystem, type); |
| return fileSystem.initializeFilePaths().then(() => fileSystem).catch(error => { |
| console.error(error); |
| return null; |
| }); |
| } |
| |
| static errorMessage(error: DOMError): string { |
| // @ts-ignore TODO(crbug.com/1172300) Properly type this after jsdoc to ts migration |
| return i18nString(UIStrings.fileSystemErrorS, {PH1: error.message}); |
| } |
| |
| private serializedFileOperation<T>(path: string, operation: () => Promise<T>): Promise<T> { |
| const promise = Promise.resolve(this.fileLocks.get(path)).then(() => operation.call(null)); |
| this.fileLocks.set(path, promise as unknown as Promise<void>); |
| return promise; |
| } |
| |
| getMetadata(path: string): Promise<Metadata|null> { |
| let fulfill: (arg0: Metadata|null) => void; |
| const promise = new Promise<Metadata|null>(f => { |
| fulfill = f; |
| }); |
| this.domFileSystem.root.getFile(decodeURIComponent(path), undefined, fileEntryLoaded, errorHandler); |
| return promise; |
| |
| function fileEntryLoaded(entry: FileEntry): void { |
| entry.getMetadata(fulfill, errorHandler); |
| } |
| |
| function errorHandler(error: DOMError): void { |
| const errorMessage = IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + ' when getting file metadata \'' + path); |
| fulfill(null); |
| } |
| } |
| |
| initialFilePaths(): Platform.DevToolsPath.EncodedPathString[] { |
| return [...this.initialFilePathsInternal]; |
| } |
| |
| initialGitFolders(): Platform.DevToolsPath.EncodedPathString[] { |
| return [...this.initialGitFoldersInternal]; |
| } |
| |
| embedderPath(): string { |
| return this.embedderPathInternal; |
| } |
| |
| private initializeFilePaths(): Promise<void> { |
| return new Promise(fulfill => { |
| let pendingRequests = 1; |
| const boundInnerCallback = innerCallback.bind(this); |
| this.requestEntries('', boundInnerCallback); |
| |
| function innerCallback(this: IsolatedFileSystem, entries: FileEntry[]): void { |
| for (let i = 0; i < entries.length; ++i) { |
| const entry = entries[i]; |
| if (!entry.isDirectory) { |
| if (this.isFileExcluded(entry.fullPath)) { |
| continue; |
| } |
| this.initialFilePathsInternal.add(Common.ParsedURL.ParsedURL.rawPathToEncodedPathString( |
| Common.ParsedURL.ParsedURL.substr(entry.fullPath as Platform.DevToolsPath.RawPathString, 1))); |
| } else { |
| if (entry.fullPath.endsWith('/.git')) { |
| const lastSlash = entry.fullPath.lastIndexOf('/'); |
| const parentFolder = Common.ParsedURL.ParsedURL.substr( |
| entry.fullPath as Platform.DevToolsPath.RawPathString, 1, lastSlash); |
| this.initialGitFoldersInternal.add(Common.ParsedURL.ParsedURL.rawPathToEncodedPathString(parentFolder)); |
| } |
| if (this.isFileExcluded(entry.fullPath + '/')) { |
| const url = Common.ParsedURL.ParsedURL.concatenate(this.path(), entry.fullPath); |
| this.excludedEmbedderFolders.push( |
| Common.ParsedURL.ParsedURL.urlToRawPathString(url, Host.Platform.isWin())); |
| continue; |
| } |
| ++pendingRequests; |
| this.requestEntries(entry.fullPath, boundInnerCallback); |
| } |
| } |
| if ((--pendingRequests === 0)) { |
| fulfill(); |
| } |
| } |
| }); |
| } |
| |
| private async createFoldersIfNotExist(folderPath: string): Promise<DirectoryEntry|null> { |
| // Fast-path. If parent directory already exists we return it immidiatly. |
| let dirEntry = await new Promise<DirectoryEntry|null>( |
| resolve => this.domFileSystem.root.getDirectory(folderPath, undefined, resolve, () => resolve(null))); |
| if (dirEntry) { |
| return dirEntry; |
| } |
| const paths = folderPath.split('/'); |
| let activePath = ''; |
| for (const path of paths) { |
| activePath = activePath + '/' + path; |
| dirEntry = await this.innerCreateFolderIfNeeded(activePath); |
| if (!dirEntry) { |
| return null; |
| } |
| } |
| return dirEntry; |
| } |
| |
| private innerCreateFolderIfNeeded(path: string): Promise<DirectoryEntry|null> { |
| return new Promise(resolve => { |
| this.domFileSystem.root.getDirectory(path, {create: true}, dirEntry => resolve(dirEntry), error => { |
| const errorMessage = IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + ' trying to create directory \'' + path + '\''); |
| resolve(null); |
| }); |
| }); |
| } |
| |
| async createFile(path: string, name: Platform.DevToolsPath.RawPathString|null): Promise<string|null> { |
| const dirEntry = await this.createFoldersIfNotExist(decodeURIComponent(path)); |
| if (!dirEntry) { |
| return null; |
| } |
| const fileEntry = |
| await this.serializedFileOperation( |
| path, createFileCandidate.bind(this, name || 'NewFile' as Platform.DevToolsPath.RawPathString)) as |
| FileEntry | |
| null; |
| if (!fileEntry) { |
| return null; |
| } |
| return Common.ParsedURL.ParsedURL.rawPathToEncodedPathString( |
| Common.ParsedURL.ParsedURL.substr(fileEntry.fullPath as Platform.DevToolsPath.RawPathString, 1)); |
| |
| function createFileCandidate( |
| this: IsolatedFileSystem, name: Platform.DevToolsPath.RawPathString, |
| newFileIndex?: number): Promise<FileEntry|null> { |
| return new Promise(resolve => { |
| const nameCandidate = Common.ParsedURL.ParsedURL.concatenate(name, (newFileIndex || '').toString()); |
| (dirEntry as DirectoryEntry).getFile(nameCandidate, {create: true, exclusive: true}, resolve, error => { |
| if (error.name === 'InvalidModificationError') { |
| resolve(createFileCandidate.call(this, name, (newFileIndex ? newFileIndex + 1 : 1))); |
| return; |
| } |
| const errorMessage = IsolatedFileSystem.errorMessage(error); |
| console.error( |
| errorMessage + ' when testing if file exists \'' + (this.path() + '/' + path + '/' + nameCandidate) + |
| '\''); |
| resolve(null); |
| }); |
| }); |
| } |
| } |
| |
| deleteFile(path: Platform.DevToolsPath.EncodedPathString): Promise<boolean> { |
| let resolveCallback: (arg0: boolean) => void; |
| const promise = new Promise<boolean>(resolve => { |
| resolveCallback = resolve; |
| }); |
| this.domFileSystem.root.getFile( |
| decodeURIComponent(path), undefined, fileEntryLoaded.bind(this), errorHandler.bind(this)); |
| return promise; |
| |
| function fileEntryLoaded(this: IsolatedFileSystem, fileEntry: FileEntry): void { |
| fileEntry.remove(fileEntryRemoved, errorHandler.bind(this)); |
| } |
| |
| function fileEntryRemoved(): void { |
| resolveCallback(true); |
| } |
| |
| /** |
| * TODO(jsbell): Update externs replacing DOMError with DOMException. https://crbug.com/496901 |
| */ |
| function errorHandler(this: IsolatedFileSystem, error: DOMError): void { |
| const errorMessage = IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + ' when deleting file \'' + (this.path() + '/' + path) + '\''); |
| resolveCallback(false); |
| } |
| } |
| |
| requestFileBlob(path: Platform.DevToolsPath.EncodedPathString): Promise<Blob|null> { |
| return new Promise(resolve => { |
| this.domFileSystem.root.getFile(decodeURIComponent(path), undefined, entry => { |
| entry.file(resolve, errorHandler.bind(this)); |
| }, errorHandler.bind(this)); |
| |
| function errorHandler(this: IsolatedFileSystem, error: DOMError): void { |
| if (error.name === 'NotFoundError') { |
| resolve(null); |
| return; |
| } |
| |
| const errorMessage = IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + ' when getting content for file \'' + (this.path() + '/' + path) + '\''); |
| resolve(null); |
| } |
| }); |
| } |
| |
| requestFileContent(path: Platform.DevToolsPath.EncodedPathString): |
| Promise<TextUtils.ContentProvider.DeferredContent> { |
| return this.serializedFileOperation(path, () => this.innerRequestFileContent(path)); |
| } |
| |
| private async innerRequestFileContent(path: Platform.DevToolsPath.EncodedPathString): |
| Promise<TextUtils.ContentProvider.DeferredContent> { |
| const blob = await this.requestFileBlob(path); |
| if (!blob) { |
| return {content: null, error: i18nString(UIStrings.blobCouldNotBeLoaded), isEncoded: false}; |
| } |
| |
| const reader = new FileReader(); |
| const extension = Common.ParsedURL.ParsedURL.extractExtension(path); |
| const encoded = BinaryExtensions.has(extension); |
| const readPromise = new Promise(x => { |
| reader.onloadend = x; |
| }); |
| if (encoded) { |
| reader.readAsBinaryString(blob); |
| } else { |
| reader.readAsText(blob); |
| } |
| await readPromise; |
| if (reader.error) { |
| const error = i18nString(UIStrings.cantReadFileSS, {PH1: path, PH2: reader.error.toString()}); |
| console.error(error); |
| return {content: null, isEncoded: false, error}; |
| } |
| let result: string|null = null; |
| let error: Common.UIString.LocalizedString|null = null; |
| try { |
| result = reader.result as string; |
| } catch (e) { |
| result = null; |
| error = i18nString(UIStrings.cantReadFileSS, {PH1: path, PH2: e.message}); |
| } |
| if (result === undefined || result === null) { |
| error = error || i18nString(UIStrings.unknownErrorReadingFileS, {PH1: path}); |
| console.error(error); |
| return {content: null, isEncoded: false, error}; |
| } |
| return {isEncoded: encoded, content: encoded ? btoa(result) : result}; |
| } |
| |
| async setFileContent(path: Platform.DevToolsPath.EncodedPathString, content: string, isBase64: boolean): |
| Promise<void> { |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.FileSavedInWorkspace); |
| let callback: (event?: ProgressEvent<EventTarget>) => void; |
| const innerSetFileContent = (): Promise<ProgressEvent<EventTarget>> => { |
| const promise = new Promise<ProgressEvent<EventTarget>>(x => { |
| // @ts-ignore TODO(crbug.com/1172300) Properly type this after jsdoc to ts migration |
| callback = x; |
| }); |
| this.domFileSystem.root.getFile( |
| decodeURIComponent(path), {create: true}, fileEntryLoaded.bind(this), errorHandler.bind(this)); |
| return promise; |
| }; |
| |
| void this.serializedFileOperation(path, innerSetFileContent); |
| |
| function fileEntryLoaded(this: IsolatedFileSystem, entry: FileEntry): void { |
| entry.createWriter(fileWriterCreated.bind(this), errorHandler.bind(this)); |
| } |
| |
| async function fileWriterCreated(this: IsolatedFileSystem, fileWriter: FileWriter): Promise<void> { |
| fileWriter.onerror = errorHandler.bind(this); |
| fileWriter.onwriteend = fileWritten; |
| let blob: Blob; |
| if (isBase64) { |
| blob = await (await fetch(`data:application/octet-stream;base64,${content}`)).blob(); |
| } else { |
| blob = new Blob([content], {type: 'text/plain'}); |
| } |
| fileWriter.write(blob); |
| |
| function fileWritten(): void { |
| fileWriter.onwriteend = callback; |
| fileWriter.truncate(blob.size); |
| } |
| } |
| |
| function errorHandler(this: IsolatedFileSystem, error: DOMError|ProgressEvent<EventTarget>): void { |
| // @ts-ignore TODO(crbug.com/1172300) Properly type this after jsdoc to ts migration |
| const errorMessage = IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + ' when setting content for file \'' + (this.path() + '/' + path) + '\''); |
| callback(undefined); |
| } |
| } |
| |
| renameFile( |
| path: Platform.DevToolsPath.EncodedPathString, newName: Platform.DevToolsPath.RawPathString, |
| callback: (arg0: boolean, arg1?: string|undefined) => void): void { |
| newName = newName ? newName.trim() as Platform.DevToolsPath.RawPathString : newName; |
| if (!newName || newName.indexOf('/') !== -1) { |
| callback(false); |
| return; |
| } |
| let fileEntry: FileEntry; |
| let dirEntry: DirectoryEntry; |
| |
| this.domFileSystem.root.getFile( |
| decodeURIComponent(path), undefined, fileEntryLoaded.bind(this), errorHandler.bind(this)); |
| |
| function fileEntryLoaded(this: IsolatedFileSystem, entry: FileEntry): void { |
| if (entry.name === newName) { |
| callback(false); |
| return; |
| } |
| |
| fileEntry = entry; |
| fileEntry.getParent(dirEntryLoaded.bind(this), errorHandler.bind(this)); |
| } |
| |
| function dirEntryLoaded(this: IsolatedFileSystem, entry: Entry): void { |
| dirEntry = entry as DirectoryEntry; |
| dirEntry.getFile(newName, undefined, newFileEntryLoaded, newFileEntryLoadErrorHandler.bind(this)); |
| } |
| |
| function newFileEntryLoaded(_entry: FileEntry): void { |
| callback(false); |
| } |
| |
| function newFileEntryLoadErrorHandler(this: IsolatedFileSystem, error: DOMError): void { |
| if (error.name !== 'NotFoundError') { |
| callback(false); |
| return; |
| } |
| fileEntry.moveTo(dirEntry, newName, fileRenamed, errorHandler.bind(this)); |
| } |
| |
| function fileRenamed(entry: Entry): void { |
| callback(true, entry.name); |
| } |
| |
| function errorHandler(this: IsolatedFileSystem, error: DOMError): void { |
| const errorMessage = IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + ' when renaming file \'' + (this.path() + '/' + path) + '\' to \'' + newName + '\''); |
| callback(false); |
| } |
| } |
| |
| private readDirectory(dirEntry: DirectoryEntry, callback: (arg0: Array<FileEntry>) => void): void { |
| const dirReader = dirEntry.createReader(); |
| let entries: FileEntry[] = []; |
| |
| function innerCallback(results: Entry[]): void { |
| if (!results.length) { |
| callback(entries.sort()); |
| } else { |
| entries = entries.concat(toArray(results)); |
| dirReader.readEntries(innerCallback, errorHandler); |
| } |
| } |
| |
| function toArray(list: Entry[]): FileEntry[] { |
| return Array.prototype.slice.call(list || [], 0); |
| } |
| |
| dirReader.readEntries(innerCallback, errorHandler); |
| |
| function errorHandler(error: DOMError): void { |
| const errorMessage = IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + ' when reading directory \'' + dirEntry.fullPath + '\''); |
| callback([]); |
| } |
| } |
| |
| private requestEntries(path: string, callback: (arg0: Array<FileEntry>) => void): void { |
| this.domFileSystem.root.getDirectory(decodeURIComponent(path), undefined, innerCallback.bind(this), errorHandler); |
| |
| function innerCallback(this: IsolatedFileSystem, dirEntry: DirectoryEntry): void { |
| this.readDirectory(dirEntry, callback); |
| } |
| |
| function errorHandler(error: DOMError): void { |
| const errorMessage = IsolatedFileSystem.errorMessage(error); |
| console.error(errorMessage + ' when requesting entry \'' + path + '\''); |
| callback([]); |
| } |
| } |
| |
| private saveExcludedFolders(): void { |
| const settingValue = this.excludedFoldersSetting.get(); |
| settingValue[this.path()] = [...this.excludedFoldersInternal]; |
| this.excludedFoldersSetting.set(settingValue); |
| } |
| |
| addExcludedFolder(path: string): void { |
| this.excludedFoldersInternal.add(path); |
| this.saveExcludedFolders(); |
| this.manager.dispatchEventToListeners(Events.ExcludedFolderAdded, path); |
| } |
| |
| removeExcludedFolder(path: string): void { |
| this.excludedFoldersInternal.delete(path); |
| this.saveExcludedFolders(); |
| this.manager.dispatchEventToListeners(Events.ExcludedFolderRemoved, path); |
| } |
| |
| fileSystemRemoved(): void { |
| const settingValue = this.excludedFoldersSetting.get(); |
| delete settingValue[this.path()]; |
| this.excludedFoldersSetting.set(settingValue); |
| } |
| |
| isFileExcluded(folderPath: string): boolean { |
| if (this.excludedFoldersInternal.has(folderPath)) { |
| return true; |
| } |
| const regex = (this.manager.workspaceFolderExcludePatternSetting() as Common.Settings.RegExpSetting).asRegExp(); |
| return Boolean(regex && regex.test(folderPath)); |
| } |
| |
| excludedFolders(): Set<string> { |
| return this.excludedFoldersInternal; |
| } |
| |
| searchInPath(query: string, progress: Common.Progress.Progress): Promise<string[]> { |
| return new Promise(resolve => { |
| const requestId = this.manager.registerCallback(innerCallback); |
| Host.InspectorFrontendHost.InspectorFrontendHostInstance.searchInPath( |
| requestId, this.embedderPathInternal, query); |
| |
| function innerCallback(files: Platform.DevToolsPath.RawPathString[]): void { |
| resolve(files.map(path => Common.ParsedURL.ParsedURL.rawPathToUrlString(path))); |
| progress.incrementWorked(1); |
| } |
| }); |
| } |
| |
| indexContent(progress: Common.Progress.Progress): void { |
| progress.setTotalWork(1); |
| const requestId = this.manager.registerProgress(progress); |
| Host.InspectorFrontendHost.InspectorFrontendHostInstance.indexPath( |
| requestId, this.embedderPathInternal, JSON.stringify(this.excludedEmbedderFolders)); |
| } |
| |
| mimeFromPath(path: string): string { |
| return Common.ResourceType.ResourceType.mimeFromURL(path) || 'text/plain'; |
| } |
| |
| canExcludeFolder(path: string): boolean { |
| return Boolean(path) && this.type() !== 'overrides'; |
| } |
| |
| contentType(path: string): Common.ResourceType.ResourceType { |
| const extension = Common.ParsedURL.ParsedURL.extractExtension(path); |
| if (STYLE_SHEET_EXTENSIONS.has(extension)) { |
| return Common.ResourceType.resourceTypes.Stylesheet; |
| } |
| if (DOCUMENT_EXTENSIONS.has(extension)) { |
| return Common.ResourceType.resourceTypes.Document; |
| } |
| if (IMAGE_EXTENSIONS.has(extension)) { |
| return Common.ResourceType.resourceTypes.Image; |
| } |
| if (SCRIPT_EXTENSIONS.has(extension)) { |
| return Common.ResourceType.resourceTypes.Script; |
| } |
| return BinaryExtensions.has(extension) ? Common.ResourceType.resourceTypes.Other : |
| Common.ResourceType.resourceTypes.Document; |
| } |
| |
| tooltipForURL(url: Platform.DevToolsPath.UrlString): string { |
| const path = Platform.StringUtilities.trimMiddle( |
| Common.ParsedURL.ParsedURL.urlToRawPathString(url, Host.Platform.isWin()), 150); |
| return i18nString(UIStrings.linkedToS, {PH1: path}); |
| } |
| |
| supportsAutomapping(): boolean { |
| return this.type() !== 'overrides'; |
| } |
| } |
| |
| const STYLE_SHEET_EXTENSIONS = new Set<string>(['css', 'scss', 'sass', 'less']); |
| const DOCUMENT_EXTENSIONS = new Set<string>(['htm', 'html', 'asp', 'aspx', 'phtml', 'jsp']); |
| |
| const SCRIPT_EXTENSIONS = new Set<string>([ |
| 'asp', 'aspx', 'c', 'cc', 'cljs', 'coffee', 'cpp', 'cs', 'dart', 'java', 'js', |
| 'jsp', 'jsx', 'h', 'm', 'mjs', 'mm', 'py', 'sh', 'ts', 'tsx', 'ls', |
| ]); |
| |
| const IMAGE_EXTENSIONS = new Set<string>(['jpeg', 'jpg', 'svg', 'gif', 'webp', 'png', 'ico', 'tiff', 'tif', 'bmp']); |
| |
| export const BinaryExtensions = new Set<string>([ |
| // Executable extensions, roughly taken from https://en.wikipedia.org/wiki/Comparison_of_executable_file_formats |
| 'cmd', |
| 'com', |
| 'exe', |
| // Archive extensions, roughly taken from https://en.wikipedia.org/wiki/List_of_archive_formats |
| 'a', |
| 'ar', |
| 'iso', |
| 'tar', |
| 'bz2', |
| 'gz', |
| 'lz', |
| 'lzma', |
| 'z', |
| '7z', |
| 'apk', |
| 'arc', |
| 'cab', |
| 'dmg', |
| 'jar', |
| 'pak', |
| 'rar', |
| 'zip', |
| // Audio file extensions, roughly taken from https://en.wikipedia.org/wiki/Audio_file_format#List_of_formats |
| '3gp', |
| 'aac', |
| 'aiff', |
| 'flac', |
| 'm4a', |
| 'mmf', |
| 'mp3', |
| 'ogg', |
| 'oga', |
| 'raw', |
| 'sln', |
| 'wav', |
| 'wma', |
| 'webm', |
| // Video file extensions, roughly taken from https://en.wikipedia.org/wiki/Video_file_format |
| 'mkv', |
| 'flv', |
| 'vob', |
| 'ogv', |
| 'gifv', |
| 'avi', |
| 'mov', |
| 'qt', |
| 'mp4', |
| 'm4p', |
| 'm4v', |
| 'mpg', |
| 'mpeg', |
| // Image file extensions |
| 'jpeg', |
| 'jpg', |
| 'gif', |
| 'webp', |
| 'png', |
| 'ico', |
| 'tiff', |
| 'tif', |
| 'bmp', |
| ]); |