[cleanup] Use `ContentData` in `Workspace.FileManager.FileManager`.

Bug: 425280773
Change-Id: I0099f26a088452285ff88b80088e8c1a948f9798
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6651078
Commit-Queue: Benedikt Meurer <[email protected]>
Reviewed-by: Simon Zünd <[email protected]>
Auto-Submit: Benedikt Meurer <[email protected]>
Commit-Queue: Simon Zünd <[email protected]>
diff --git a/front_end/models/bindings/FileUtils.ts b/front_end/models/bindings/FileUtils.ts
index a6a55b5..8536f0a 100644
--- a/front_end/models/bindings/FileUtils.ts
+++ b/front_end/models/bindings/FileUtils.ts
@@ -30,6 +30,7 @@
 
 import type * as Common from '../../core/common/common.js';
 import type * as Platform from '../../core/platform/platform.js';
+import * as TextUtils from '../text_utils/text_utils.js';
 import * as Workspace from '../workspace/workspace.js';
 
 export interface ChunkedReader {
@@ -223,8 +224,8 @@
     this.#closed = false;
     this.#writeCallbacks = [];
     this.#fileName = fileName;
-    const saveResponse =
-        await Workspace.FileManager.FileManager.instance().save(this.#fileName, '', true, false /* isBase64 */);
+    const saveResponse = await Workspace.FileManager.FileManager.instance().save(
+        this.#fileName, TextUtils.ContentData.EMPTY_TEXT_CONTENT_DATA, /* forceSaveAs=*/ true);
     if (saveResponse) {
       Workspace.FileManager.FileManager.instance().addEventListener(
           Workspace.FileManager.Events.APPENDED_TO_URL, this.onAppendDone, this);
diff --git a/front_end/models/persistence/PersistenceAction.test.ts b/front_end/models/persistence/PersistenceAction.test.ts
index 4b33f53..9dab33e 100644
--- a/front_end/models/persistence/PersistenceAction.test.ts
+++ b/front_end/models/persistence/PersistenceAction.test.ts
@@ -32,13 +32,13 @@
     sinon.stub(event, 'target').value(document);
     const contextMenu = new UI.ContextMenu.ContextMenu(event);
     const menuProvider = new Persistence.PersistenceActions.ContextMenuProvider();
+    const contentData = new TextUtils.ContentData.ContentData('AGFzbQEAAAA=', true, 'image/webp');
     const contentProvider: TextUtils.ContentProvider.ContentProvider = {
       contentURL: () => urlString`https://example.com/sample.webp`,
       contentType: () => Common.ResourceType.resourceTypes
                              .Document,  // Navigating a tab to an image will result in a document type for images.
-      requestContent: () => Promise.resolve({isEncoded: true, content: 'AGFzbQEAAAA='}),
-      requestContentData: () =>
-          Promise.resolve(new TextUtils.ContentData.ContentData('AGFzbQEAAAA=', true, 'image/webp')),
+      requestContent: () => Promise.resolve({isEncoded: true, content: contentData.base64}),
+      requestContentData: () => Promise.resolve(contentData),
       searchInContent: () => assert.fail('Not implemented'),
     };
 
@@ -51,7 +51,9 @@
     contextMenu.invokeHandler(saveItem.id());
 
     assert.deepEqual(await expectCall(saveStub), [
-      urlString`https://example.com/sample.webp`, 'AGFzbQEAAAA=', true /* forceSaveAs */, true, /* isBase64 */
+      urlString`https://example.com/sample.webp`,
+      contentData,
+      /* forceSaveAs=*/ true,
     ]);
   });
 
@@ -83,8 +85,10 @@
 
     contextMenu.invokeHandler(saveItem.id());
 
-    assert.deepEqual(await expectCall(saveStub), [
-      urlString`https://example.com/sample.wasm`, 'AQIDBA==', true /* forceSaveAs */, true, /* isBase64 */
-    ]);
+    const args = await expectCall(saveStub);
+    assert.lengthOf(args, 3);
+    assert.strictEqual(args[0], urlString`https://example.com/sample.wasm`);
+    assert.strictEqual(args[1].base64, 'AQIDBA==');
+    assert.isTrue(args[2]);
   });
 });
diff --git a/front_end/models/persistence/PersistenceActions.ts b/front_end/models/persistence/PersistenceActions.ts
index 390e900..bde8512 100644
--- a/front_end/models/persistence/PersistenceActions.ts
+++ b/front_end/models/persistence/PersistenceActions.ts
@@ -9,7 +9,7 @@
 import * as SDK from '../../core/sdk/sdk.js';
 import * as UI from '../../ui/legacy/legacy.js';
 import * as Bindings from '../bindings/bindings.js';
-import type * as TextUtils from '../text_utils/text_utils.js';
+import * as TextUtils from '../text_utils/text_utils.js';
 import * as Workspace from '../workspace/workspace.js';
 
 import {NetworkPersistenceManager} from './NetworkPersistenceManager.js';
@@ -48,6 +48,11 @@
   overrideSourceMappedFileExplanation: '‘{PH1}’ is a source mapped file and cannot be overridden.',
   /**
    * @description An error message shown in the DevTools console after the user clicked "Save as" in
+   * the context menu of a page resource.
+   */
+  saveFailed: 'Failed to save file to disk.',
+  /**
+   * @description An error message shown in the DevTools console after the user clicked "Save as" in
    * the context menu of a WebAssembly file.
    */
   saveWasmFailed: 'Unable to save WASM module to disk. Most likely the module is too large.',
@@ -66,22 +71,27 @@
         (contentProvider).commitWorkingCopy();
       }
       const url = contentProvider.contentURL();
-      let content: TextUtils.ContentProvider.DeferredContent;
+      let contentData: TextUtils.ContentData.ContentData;
       const maybeScript = getScript(contentProvider);
       if (maybeScript?.isWasm()) {
         try {
-          const byteCode = await maybeScript.getWasmBytecode();
-          const base64 = await Common.Base64.encode(byteCode);
-          content = {isEncoded: true, content: base64};
+          const base64 = await maybeScript.getWasmBytecode().then(Common.Base64.encode);
+          contentData = new TextUtils.ContentData.ContentData(base64, /* isBase64=*/ true, 'application/wasm');
         } catch (e) {
           console.error(`Unable to convert WASM byte code for ${url} to base64. Not saving to disk`, e.stack);
           Common.Console.Console.instance().error(i18nString(UIStrings.saveWasmFailed), /* show=*/ false);
           return;
         }
       } else {
-        content = await contentProvider.requestContent();
+        const contentDataOrError = await contentProvider.requestContentData();
+        if (TextUtils.ContentData.ContentData.isError(contentDataOrError)) {
+          console.error(`Failed to retrieve content for ${url}: ${contentDataOrError}`);
+          Common.Console.Console.instance().error(i18nString(UIStrings.saveFailed), /* show=*/ false);
+          return;
+        }
+        contentData = contentDataOrError;
       }
-      await Workspace.FileManager.FileManager.instance().save(url, content.content ?? '', true, content.isEncoded);
+      await Workspace.FileManager.FileManager.instance().save(url, contentData, /* forceSaveAs=*/ true);
       Workspace.FileManager.FileManager.instance().close(url);
     }
 
diff --git a/front_end/models/workspace/FileManager.ts b/front_end/models/workspace/FileManager.ts
index 5333f95..f09bcc6 100644
--- a/front_end/models/workspace/FileManager.ts
+++ b/front_end/models/workspace/FileManager.ts
@@ -31,6 +31,7 @@
 import * as Common from '../../core/common/common.js';
 import * as Host from '../../core/host/host.js';
 import type * as Platform from '../../core/platform/platform.js';
+import type * as TextUtils from '../text_utils/text_utils.js';
 
 let fileManagerInstance: FileManager|null;
 
@@ -65,13 +66,14 @@
    */
   save(
       url: Platform.DevToolsPath.RawPathString|Platform.DevToolsPath.UrlString,
-      content: string,
+      contentData: TextUtils.ContentData.ContentData,
       forceSaveAs: boolean,
-      isBase64: boolean,
       ): Promise<SaveCallbackParam|null> {
     // Remove this url from the saved URLs while it is being saved.
     const result = new Promise<SaveCallbackParam|null>(resolve => this.#saveCallbacks.set(url, resolve));
-    Host.InspectorFrontendHost.InspectorFrontendHostInstance.save(url, content, forceSaveAs, isBase64);
+    const {isTextContent} = contentData;
+    const content = isTextContent ? contentData.text : contentData.base64;
+    Host.InspectorFrontendHost.InspectorFrontendHostInstance.save(url, content, forceSaveAs, !isTextContent);
     return result;
   }