1 |
|
2 |
|
3 |
|
4 |
|
5 | import * as Common from '../../core/common/common.js';
|
6 | import * as Platform from '../../core/platform/platform.js';
|
7 | import * as SDK from '../../core/sdk/sdk.js';
|
8 | import * as Protocol from '../../generated/protocol.js';
|
9 | import * as Workspace from '../workspace/workspace.js';
|
10 |
|
11 | import type {FileSystem} from './FileSystemWorkspaceBinding.js';
|
12 | import {FileSystemWorkspaceBinding} from './FileSystemWorkspaceBinding.js';
|
13 | import {PersistenceBinding, PersistenceImpl} from './PersistenceImpl.js';
|
14 |
|
15 | let networkPersistenceManagerInstance: NetworkPersistenceManager|null;
|
16 |
|
17 | export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes> implements
|
18 | SDK.TargetManager.Observer {
|
19 | private bindings: WeakMap<Workspace.UISourceCode.UISourceCode, PersistenceBinding>;
|
20 | private readonly originalResponseContentPromises: WeakMap<Workspace.UISourceCode.UISourceCode, Promise<string|null>>;
|
21 | private savingForOverrides: WeakSet<Workspace.UISourceCode.UISourceCode>;
|
22 | private readonly savingSymbol: symbol;
|
23 | private enabledSetting: Common.Settings.Setting<boolean>;
|
24 | private readonly workspace: Workspace.Workspace.WorkspaceImpl;
|
25 | private readonly networkUISourceCodeForEncodedPath: Map<string, Workspace.UISourceCode.UISourceCode>;
|
26 | private readonly interceptionHandlerBound:
|
27 | (interceptedRequest: SDK.NetworkManager.InterceptedRequest) => Promise<void>;
|
28 | private readonly updateInterceptionThrottler: Common.Throttler.Throttler;
|
29 | private projectInternal: Workspace.Workspace.Project|null;
|
30 | private readonly activeProject: Workspace.Workspace.Project|null;
|
31 | private activeInternal: boolean;
|
32 | private enabled: boolean;
|
33 | private eventDescriptors: Common.EventTarget.EventDescriptor[];
|
34 |
|
35 | private constructor(workspace: Workspace.Workspace.WorkspaceImpl) {
|
36 | super();
|
37 | this.bindings = new WeakMap();
|
38 | this.originalResponseContentPromises = new WeakMap();
|
39 | this.savingForOverrides = new WeakSet();
|
40 | this.savingSymbol = Symbol('SavingForOverrides');
|
41 |
|
42 | this.enabledSetting = Common.Settings.Settings.instance().moduleSetting('persistenceNetworkOverridesEnabled');
|
43 | this.enabledSetting.addChangeListener(this.enabledChanged, this);
|
44 |
|
45 | this.workspace = workspace;
|
46 |
|
47 | this.networkUISourceCodeForEncodedPath = new Map();
|
48 | this.interceptionHandlerBound = this.interceptionHandler.bind(this);
|
49 | this.updateInterceptionThrottler = new Common.Throttler.Throttler(50);
|
50 |
|
51 | this.projectInternal = null;
|
52 | this.activeProject = null;
|
53 |
|
54 | this.activeInternal = false;
|
55 | this.enabled = false;
|
56 |
|
57 | this.workspace.addEventListener(Workspace.Workspace.Events.ProjectAdded, event => {
|
58 | this.onProjectAdded(event.data);
|
59 | });
|
60 | this.workspace.addEventListener(Workspace.Workspace.Events.ProjectRemoved, event => {
|
61 | this.onProjectRemoved(event.data);
|
62 | });
|
63 |
|
64 | PersistenceImpl.instance().addNetworkInterceptor(this.canHandleNetworkUISourceCode.bind(this));
|
65 |
|
66 | this.eventDescriptors = [];
|
67 | this.enabledChanged();
|
68 |
|
69 | SDK.TargetManager.TargetManager.instance().observeTargets(this);
|
70 | }
|
71 |
|
72 | targetAdded(): void {
|
73 | this.updateActiveProject();
|
74 | }
|
75 | targetRemoved(): void {
|
76 | this.updateActiveProject();
|
77 | }
|
78 |
|
79 | static instance(opts: {
|
80 | forceNew: boolean|null,
|
81 | workspace: Workspace.Workspace.WorkspaceImpl|null,
|
82 | } = {forceNew: null, workspace: null}): NetworkPersistenceManager {
|
83 | const {forceNew, workspace} = opts;
|
84 | if (!networkPersistenceManagerInstance || forceNew) {
|
85 | if (!workspace) {
|
86 | throw new Error('Missing workspace for NetworkPersistenceManager');
|
87 | }
|
88 | networkPersistenceManagerInstance = new NetworkPersistenceManager(workspace);
|
89 | }
|
90 |
|
91 | return networkPersistenceManagerInstance;
|
92 | }
|
93 |
|
94 | active(): boolean {
|
95 | return this.activeInternal;
|
96 | }
|
97 |
|
98 | project(): Workspace.Workspace.Project|null {
|
99 | return this.projectInternal;
|
100 | }
|
101 |
|
102 | originalContentForUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<string|null>|null {
|
103 | const binding = this.bindings.get(uiSourceCode);
|
104 | if (!binding) {
|
105 | return null;
|
106 | }
|
107 | const fileSystemUISourceCode = binding.fileSystem;
|
108 | return this.originalResponseContentPromises.get(fileSystemUISourceCode) || null;
|
109 | }
|
110 |
|
111 | private async enabledChanged(): Promise<void> {
|
112 | if (this.enabled === this.enabledSetting.get()) {
|
113 | return;
|
114 | }
|
115 | this.enabled = this.enabledSetting.get();
|
116 | if (this.enabled) {
|
117 | this.eventDescriptors = [
|
118 | Workspace.Workspace.WorkspaceImpl.instance().addEventListener(
|
119 | Workspace.Workspace.Events.UISourceCodeRenamed,
|
120 | event => {
|
121 | this.uiSourceCodeRenamedListener(event);
|
122 | }),
|
123 | Workspace.Workspace.WorkspaceImpl.instance().addEventListener(
|
124 | Workspace.Workspace.Events.UISourceCodeAdded,
|
125 | event => {
|
126 | this.uiSourceCodeAdded(event);
|
127 | }),
|
128 | Workspace.Workspace.WorkspaceImpl.instance().addEventListener(
|
129 | Workspace.Workspace.Events.UISourceCodeRemoved,
|
130 | event => {
|
131 | this.uiSourceCodeRemovedListener(event);
|
132 | }),
|
133 | Workspace.Workspace.WorkspaceImpl.instance().addEventListener(
|
134 | Workspace.Workspace.Events.WorkingCopyCommitted,
|
135 | event => this.onUISourceCodeWorkingCopyCommitted(event.data.uiSourceCode)),
|
136 | ];
|
137 | await this.updateActiveProject();
|
138 | } else {
|
139 | Common.EventTarget.removeEventListeners(this.eventDescriptors);
|
140 | await this.updateActiveProject();
|
141 | }
|
142 | }
|
143 |
|
144 | private async uiSourceCodeRenamedListener(
|
145 | event: Common.EventTarget.EventTargetEvent<Workspace.Workspace.UISourceCodeRenamedEvent>): Promise<void> {
|
146 | const uiSourceCode = event.data.uiSourceCode;
|
147 | await this.onUISourceCodeRemoved(uiSourceCode);
|
148 | await this.onUISourceCodeAdded(uiSourceCode);
|
149 | }
|
150 |
|
151 | private async uiSourceCodeRemovedListener(
|
152 | event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): Promise<void> {
|
153 | await this.onUISourceCodeRemoved(event.data);
|
154 | }
|
155 |
|
156 | private async uiSourceCodeAdded(event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>):
|
157 | Promise<void> {
|
158 | await this.onUISourceCodeAdded(event.data);
|
159 | }
|
160 |
|
161 | private async updateActiveProject(): Promise<void> {
|
162 | const wasActive = this.activeInternal;
|
163 | this.activeInternal = Boolean(
|
164 | this.enabledSetting.get() && SDK.TargetManager.TargetManager.instance().mainTarget() && this.projectInternal);
|
165 | if (this.activeInternal === wasActive) {
|
166 | return;
|
167 | }
|
168 |
|
169 | if (this.activeInternal && this.projectInternal) {
|
170 | await Promise.all(
|
171 | this.projectInternal.uiSourceCodes().map(uiSourceCode => this.filesystemUISourceCodeAdded(uiSourceCode)));
|
172 |
|
173 | const networkProjects = this.workspace.projectsForType(Workspace.Workspace.projectTypes.Network);
|
174 | for (const networkProject of networkProjects) {
|
175 | await Promise.all(
|
176 | networkProject.uiSourceCodes().map(uiSourceCode => this.networkUISourceCodeAdded(uiSourceCode)));
|
177 | }
|
178 | } else if (this.projectInternal) {
|
179 | await Promise.all(
|
180 | this.projectInternal.uiSourceCodes().map(uiSourceCode => this.filesystemUISourceCodeRemoved(uiSourceCode)));
|
181 | this.networkUISourceCodeForEncodedPath.clear();
|
182 | }
|
183 | PersistenceImpl.instance().refreshAutomapping();
|
184 | }
|
185 |
|
186 | private encodedPathFromUrl(url: string): string {
|
187 | if (!this.activeInternal || !this.projectInternal) {
|
188 | return '';
|
189 | }
|
190 | let urlPath = Common.ParsedURL.ParsedURL.urlWithoutHash(url.replace(/^https?:\/\//, ''));
|
191 | if (urlPath.endsWith('/') && urlPath.indexOf('?') === -1) {
|
192 | urlPath = urlPath + 'index.html';
|
193 | }
|
194 | let encodedPathParts = encodeUrlPathToLocalPathParts(urlPath);
|
195 | const projectPath = FileSystemWorkspaceBinding.fileSystemPath(this.projectInternal.id());
|
196 | const encodedPath = encodedPathParts.join('/');
|
197 | if (projectPath.length + encodedPath.length > 200) {
|
198 | const domain = encodedPathParts[0];
|
199 | const encodedFileName = encodedPathParts[encodedPathParts.length - 1];
|
200 | const shortFileName = encodedFileName ? encodedFileName.substr(0, 10) + '-' : '';
|
201 | const extension = Common.ParsedURL.ParsedURL.extractExtension(urlPath);
|
202 | const extensionPart = extension ? '.' + extension.substr(0, 10) : '';
|
203 | encodedPathParts = [
|
204 | domain,
|
205 | 'longurls',
|
206 | shortFileName + Platform.StringUtilities.hashCode(encodedPath).toString(16) + extensionPart,
|
207 | ];
|
208 | }
|
209 | return encodedPathParts.join('/');
|
210 |
|
211 | function encodeUrlPathToLocalPathParts(urlPath: string): string[] {
|
212 | const encodedParts = [];
|
213 | for (const pathPart of fileNamePartsFromUrlPath(urlPath)) {
|
214 | if (!pathPart) {
|
215 | continue;
|
216 | }
|
217 |
|
218 | let encodedName = encodeURI(pathPart).replace(/[\/:\?\*]/g, match => '%' + match[0].charCodeAt(0).toString(16));
|
219 |
|
220 | if (RESERVED_FILENAMES.has(encodedName.toLowerCase())) {
|
221 | encodedName = encodedName.split('').map(char => '%' + char.charCodeAt(0).toString(16)).join('');
|
222 | }
|
223 |
|
224 | const lastChar = encodedName.charAt(encodedName.length - 1);
|
225 | if (lastChar === '.') {
|
226 | encodedName = encodedName.substr(0, encodedName.length - 1) + '%2e';
|
227 | }
|
228 | encodedParts.push(encodedName);
|
229 | }
|
230 | return encodedParts;
|
231 | }
|
232 |
|
233 | function fileNamePartsFromUrlPath(urlPath: string): string[] {
|
234 | urlPath = Common.ParsedURL.ParsedURL.urlWithoutHash(urlPath);
|
235 | const queryIndex = urlPath.indexOf('?');
|
236 | if (queryIndex === -1) {
|
237 | return urlPath.split('/');
|
238 | }
|
239 | if (queryIndex === 0) {
|
240 | return [urlPath];
|
241 | }
|
242 | const endSection = urlPath.substr(queryIndex);
|
243 | const parts = urlPath.substr(0, urlPath.length - endSection.length).split('/');
|
244 | parts[parts.length - 1] += endSection;
|
245 | return parts;
|
246 | }
|
247 | }
|
248 |
|
249 | private decodeLocalPathToUrlPath(path: string): string {
|
250 | try {
|
251 | return unescape(path);
|
252 | } catch (e) {
|
253 | console.error(e);
|
254 | }
|
255 | return path;
|
256 | }
|
257 |
|
258 | private async unbind(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
259 | const binding = this.bindings.get(uiSourceCode);
|
260 | if (binding) {
|
261 | this.bindings.delete(binding.network);
|
262 | this.bindings.delete(binding.fileSystem);
|
263 | await PersistenceImpl.instance().removeBinding(binding);
|
264 | }
|
265 | }
|
266 |
|
267 | private async bind(
|
268 | networkUISourceCode: Workspace.UISourceCode.UISourceCode,
|
269 | fileSystemUISourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
270 | if (this.bindings.has(networkUISourceCode)) {
|
271 | await this.unbind(networkUISourceCode);
|
272 | }
|
273 | if (this.bindings.has(fileSystemUISourceCode)) {
|
274 | await this.unbind(fileSystemUISourceCode);
|
275 | }
|
276 | const binding = new PersistenceBinding(networkUISourceCode, fileSystemUISourceCode);
|
277 | this.bindings.set(networkUISourceCode, binding);
|
278 | this.bindings.set(fileSystemUISourceCode, binding);
|
279 | await PersistenceImpl.instance().addBinding(binding);
|
280 | const uiSourceCodeOfTruth =
|
281 | this.savingForOverrides.has(networkUISourceCode) ? networkUISourceCode : fileSystemUISourceCode;
|
282 | const [{content}, encoded] =
|
283 | await Promise.all([uiSourceCodeOfTruth.requestContent(), uiSourceCodeOfTruth.contentEncoded()]);
|
284 | PersistenceImpl.instance().syncContent(uiSourceCodeOfTruth, content || '', encoded);
|
285 | }
|
286 |
|
287 | private onUISourceCodeWorkingCopyCommitted(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
|
288 | this.saveUISourceCodeForOverrides(uiSourceCode);
|
289 | }
|
290 |
|
291 | canSaveUISourceCodeForOverrides(uiSourceCode: Workspace.UISourceCode.UISourceCode): boolean {
|
292 | return this.activeInternal && uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Network &&
|
293 | !this.bindings.has(uiSourceCode) && !this.savingForOverrides.has(uiSourceCode);
|
294 | }
|
295 |
|
296 | async saveUISourceCodeForOverrides(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
297 | if (!this.canSaveUISourceCodeForOverrides(uiSourceCode)) {
|
298 | return;
|
299 | }
|
300 | this.savingForOverrides.add(uiSourceCode);
|
301 | let encodedPath = this.encodedPathFromUrl(uiSourceCode.url());
|
302 | const content = (await uiSourceCode.requestContent()).content || '';
|
303 | const encoded = await uiSourceCode.contentEncoded();
|
304 | const lastIndexOfSlash = encodedPath.lastIndexOf('/');
|
305 | const encodedFileName = encodedPath.substr(lastIndexOfSlash + 1);
|
306 | encodedPath = encodedPath.substr(0, lastIndexOfSlash);
|
307 | if (this.projectInternal) {
|
308 | await this.projectInternal.createFile(encodedPath, encodedFileName, content, encoded);
|
309 | }
|
310 | this.fileCreatedForTest(encodedPath, encodedFileName);
|
311 | this.savingForOverrides.delete(uiSourceCode);
|
312 | }
|
313 |
|
314 | private fileCreatedForTest(_path: string, _fileName: string): void {
|
315 | }
|
316 |
|
317 | private patternForFileSystemUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): string {
|
318 | const relativePathParts = FileSystemWorkspaceBinding.relativePath(uiSourceCode);
|
319 | if (relativePathParts.length < 2) {
|
320 | return '';
|
321 | }
|
322 | if (relativePathParts[1] === 'longurls' && relativePathParts.length !== 2) {
|
323 | return 'http?://' + relativePathParts[0] + '/*';
|
324 | }
|
325 | return 'http?://' + this.decodeLocalPathToUrlPath(relativePathParts.join('/'));
|
326 | }
|
327 |
|
328 | private async onUISourceCodeAdded(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
329 | await this.networkUISourceCodeAdded(uiSourceCode);
|
330 | await this.filesystemUISourceCodeAdded(uiSourceCode);
|
331 | }
|
332 |
|
333 | private canHandleNetworkUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): boolean {
|
334 | return this.activeInternal && !uiSourceCode.url().startsWith('snippet://');
|
335 | }
|
336 |
|
337 | private async networkUISourceCodeAdded(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
338 | if (uiSourceCode.project().type() !== Workspace.Workspace.projectTypes.Network ||
|
339 | !this.canHandleNetworkUISourceCode(uiSourceCode)) {
|
340 | return;
|
341 | }
|
342 | const url = Common.ParsedURL.ParsedURL.urlWithoutHash(uiSourceCode.url());
|
343 | this.networkUISourceCodeForEncodedPath.set(this.encodedPathFromUrl(url), uiSourceCode);
|
344 |
|
345 | const project = this.projectInternal as FileSystem;
|
346 | const fileSystemUISourceCode =
|
347 | project.uiSourceCodeForURL(project.fileSystemPath() + '/' + this.encodedPathFromUrl(url));
|
348 | if (fileSystemUISourceCode) {
|
349 | await this.bind(uiSourceCode, fileSystemUISourceCode);
|
350 | }
|
351 | }
|
352 |
|
353 | private async filesystemUISourceCodeAdded(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
354 | if (!this.activeInternal || uiSourceCode.project() !== this.projectInternal) {
|
355 | return;
|
356 | }
|
357 | this.updateInterceptionPatterns();
|
358 |
|
359 | const relativePath = FileSystemWorkspaceBinding.relativePath(uiSourceCode);
|
360 | const networkUISourceCode = this.networkUISourceCodeForEncodedPath.get(relativePath.join('/'));
|
361 | if (networkUISourceCode) {
|
362 | await this.bind(networkUISourceCode, uiSourceCode);
|
363 | }
|
364 | }
|
365 |
|
366 | private updateInterceptionPatterns(): void {
|
367 | this.updateInterceptionThrottler.schedule(innerUpdateInterceptionPatterns.bind(this));
|
368 |
|
369 | function innerUpdateInterceptionPatterns(this: NetworkPersistenceManager): Promise<void> {
|
370 | if (!this.activeInternal || !this.projectInternal) {
|
371 | return SDK.NetworkManager.MultitargetNetworkManager.instance().setInterceptionHandlerForPatterns(
|
372 | [], this.interceptionHandlerBound);
|
373 | }
|
374 | const patterns = new Set<string>();
|
375 | const indexFileName = 'index.html';
|
376 | for (const uiSourceCode of this.projectInternal.uiSourceCodes()) {
|
377 | const pattern = this.patternForFileSystemUISourceCode(uiSourceCode);
|
378 | patterns.add(pattern);
|
379 | if (pattern.endsWith('/' + indexFileName)) {
|
380 | patterns.add(pattern.substr(0, pattern.length - indexFileName.length));
|
381 | }
|
382 | }
|
383 |
|
384 | return SDK.NetworkManager.MultitargetNetworkManager.instance().setInterceptionHandlerForPatterns(
|
385 | Array.from(patterns).map(
|
386 | pattern =>
|
387 | ({urlPattern: pattern, interceptionStage: Protocol.Network.InterceptionStage.HeadersReceived})),
|
388 | this.interceptionHandlerBound);
|
389 | }
|
390 | }
|
391 |
|
392 | private async onUISourceCodeRemoved(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
393 | await this.networkUISourceCodeRemoved(uiSourceCode);
|
394 | await this.filesystemUISourceCodeRemoved(uiSourceCode);
|
395 | }
|
396 |
|
397 | private async networkUISourceCodeRemoved(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
398 | if (uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Network) {
|
399 | await this.unbind(uiSourceCode);
|
400 | this.networkUISourceCodeForEncodedPath.delete(this.encodedPathFromUrl(uiSourceCode.url()));
|
401 | }
|
402 | }
|
403 |
|
404 | private async filesystemUISourceCodeRemoved(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
405 | if (uiSourceCode.project() !== this.projectInternal) {
|
406 | return;
|
407 | }
|
408 | this.updateInterceptionPatterns();
|
409 | this.originalResponseContentPromises.delete(uiSourceCode);
|
410 | await this.unbind(uiSourceCode);
|
411 | }
|
412 |
|
413 | private async setProject(project: Workspace.Workspace.Project|null): Promise<void> {
|
414 | if (project === this.projectInternal) {
|
415 | return;
|
416 | }
|
417 |
|
418 | if (this.projectInternal) {
|
419 | await Promise.all(
|
420 | this.projectInternal.uiSourceCodes().map(uiSourceCode => this.filesystemUISourceCodeRemoved(uiSourceCode)));
|
421 | }
|
422 |
|
423 | this.projectInternal = project;
|
424 |
|
425 | if (this.projectInternal) {
|
426 | await Promise.all(
|
427 | this.projectInternal.uiSourceCodes().map(uiSourceCode => this.filesystemUISourceCodeAdded(uiSourceCode)));
|
428 | }
|
429 |
|
430 | await this.updateActiveProject();
|
431 | this.dispatchEventToListeners(Events.ProjectChanged, this.projectInternal);
|
432 | }
|
433 |
|
434 | private async onProjectAdded(project: Workspace.Workspace.Project): Promise<void> {
|
435 | if (project.type() !== Workspace.Workspace.projectTypes.FileSystem ||
|
436 | FileSystemWorkspaceBinding.fileSystemType(project) !== 'overrides') {
|
437 | return;
|
438 | }
|
439 | const fileSystemPath = FileSystemWorkspaceBinding.fileSystemPath(project.id());
|
440 | if (!fileSystemPath) {
|
441 | return;
|
442 | }
|
443 | if (this.projectInternal) {
|
444 | this.projectInternal.remove();
|
445 | }
|
446 |
|
447 | await this.setProject(project);
|
448 | }
|
449 |
|
450 | private async onProjectRemoved(project: Workspace.Workspace.Project): Promise<void> {
|
451 | if (project === this.projectInternal) {
|
452 | await this.setProject(null);
|
453 | }
|
454 | }
|
455 |
|
456 | private async interceptionHandler(interceptedRequest: SDK.NetworkManager.InterceptedRequest): Promise<void> {
|
457 | const method = interceptedRequest.request.method;
|
458 | if (!this.activeInternal || (method !== 'GET' && method !== 'POST')) {
|
459 | return;
|
460 | }
|
461 | const proj = this.projectInternal as FileSystem;
|
462 | const path = proj.fileSystemPath() + '/' + this.encodedPathFromUrl(interceptedRequest.request.url);
|
463 | const fileSystemUISourceCode = proj.uiSourceCodeForURL(path);
|
464 | if (!fileSystemUISourceCode) {
|
465 | return;
|
466 | }
|
467 |
|
468 | let mimeType = '';
|
469 | if (interceptedRequest.responseHeaders) {
|
470 | const responseHeaders = SDK.NetworkManager.NetworkManager.lowercaseHeaders(interceptedRequest.responseHeaders);
|
471 | mimeType = responseHeaders['content-type'];
|
472 | }
|
473 |
|
474 | if (!mimeType) {
|
475 | const expectedResourceType =
|
476 | Common.ResourceType.resourceTypes[interceptedRequest.resourceType] || Common.ResourceType.resourceTypes.Other;
|
477 | mimeType = fileSystemUISourceCode.mimeType();
|
478 | if (Common.ResourceType.ResourceType.fromMimeType(mimeType) !== expectedResourceType) {
|
479 | mimeType = expectedResourceType.canonicalMimeType();
|
480 | }
|
481 | }
|
482 | const project = fileSystemUISourceCode.project() as FileSystem;
|
483 |
|
484 | this.originalResponseContentPromises.set(
|
485 | fileSystemUISourceCode, interceptedRequest.responseBody().then(response => {
|
486 | if (response.error || response.content === null) {
|
487 | return null;
|
488 | }
|
489 | if (response.encoded) {
|
490 | const text = atob(response.content);
|
491 | const data = new Uint8Array(text.length);
|
492 | for (let i = 0; i < text.length; ++i) {
|
493 | data[i] = text.charCodeAt(i);
|
494 | }
|
495 | return new TextDecoder('utf-8').decode(data);
|
496 | }
|
497 | return response.content;
|
498 | }));
|
499 |
|
500 | const blob = await project.requestFileBlob(fileSystemUISourceCode);
|
501 | if (blob) {
|
502 | interceptedRequest.continueRequestWithContent(new Blob([blob], {type: mimeType}));
|
503 | }
|
504 | }
|
505 | }
|
506 |
|
507 | const RESERVED_FILENAMES = new Set<string>([
|
508 | 'con', 'prn', 'aux', 'nul', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7',
|
509 | 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
|
510 | ]);
|
511 |
|
512 |
|
513 |
|
514 | export enum Events {
|
515 | ProjectChanged = 'ProjectChanged',
|
516 | }
|
517 |
|
518 | export type EventTypes = {
|
519 | [Events.ProjectChanged]: Workspace.Workspace.Project|null,
|
520 | };
|