Repository: electron/electron
Pull request: fix: use proper PDF save approach

#### Description of Change

Refactors our existing PDF save approach to use the File System Access API and therefore match existing (deprecated) behavior in upstream. I've also upstreamed this at CL:5934369.

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: none


Changed files:
filename: patches/chromium/fix_crash_when_saving_edited_pdf_files.patch
patch: @@ -13,54 +13,128 @@ This patch can be removed should we choose to support chrome.fileSystem
 or support it enough to fix the crash.
 
 diff --git a/chrome/browser/resources/pdf/pdf_viewer.ts b/chrome/browser/resources/pdf/pdf_viewer.ts
-index 2e621b9a66b0443ad961d32a6bed47e1128035a8..5e7c0e238bfe6eb55a2e795681529679a35b8d03 100644
+index 2e621b9a66b0443ad961d32a6bed47e1128035a8..490f647b15b98fbd27dabeaa1349064044774cc5 100644
 --- a/chrome/browser/resources/pdf/pdf_viewer.ts
 +++ b/chrome/browser/resources/pdf/pdf_viewer.ts
-@@ -1012,7 +1012,15 @@ export class PdfViewerElement extends PdfViewerBaseElement {
+@@ -1012,28 +1012,27 @@ export class PdfViewerElement extends PdfViewerBaseElement {
        dataArray = [result.dataToSave];
      }
  
-+    const a = document.createElement('a');
-+    a.download = this.attachments_[index].name;
-     const blob = new Blob(dataArray);
-+    // <if expr="not _google_chrome">
-+    a.href = URL.createObjectURL(blob);
-+    a.click();
-+    URL.revokeObjectURL(a.href);
-+    // </if>
-+    // <if expr="_google_chrome">
+-    const blob = new Blob(dataArray);
      const fileName = this.attachments_[index].name;
-     chrome.fileSystem.chooseEntry(
-         {type: 'saveFile', suggestedName: fileName},
-@@ -1034,6 +1042,7 @@ export class PdfViewerElement extends PdfViewerBaseElement {
-             // </if>
-           });
-         });
-+    // </if>
+-    chrome.fileSystem.chooseEntry(
+-        {type: 'saveFile', suggestedName: fileName},
+-        (entry?: FileSystemFileEntry) => {
+-          if (chrome.runtime.lastError) {
+-            if (chrome.runtime.lastError.message !== 'User cancelled') {
+-              console.error(
+-                  'chrome.fileSystem.chooseEntry failed: ' +
+-                  chrome.runtime.lastError.message);
+-            }
+-            return;
+-          }
+-          entry!.createWriter((writer: FileWriter) => {
+-            writer.write(blob);
+-            // <if expr="enable_ink">
+-            // Unblock closing the window now that the user has saved
+-            // successfully.
+-            this.setShowBeforeUnloadDialog_(false);
+-            // </if>
+-          });
+-        });
++    const blob = new Blob(dataArray);
++
++    try {
++      const fileHandle = await window.showSaveFilePicker({
++        suggestedName: fileName,
++      });
++
++      const writable = await fileHandle.createWritable();
++      await writable.write(blob);
++      await writable.close();
++      // <if expr="enable_ink">
++      // Unblock closing the window now that the user has saved
++      // successfully.
++      this.setShowBeforeUnloadDialog_(false);
++      // </if>
++    } catch (error: any) {
++      if (error.name !== 'AbortError') {
++        console.error('window.showSaveFilePicker failed: ' + error);
++      }
++    }
    }
  
    /**
-@@ -1162,7 +1171,15 @@ export class PdfViewerElement extends PdfViewerBaseElement {
+@@ -1161,36 +1160,33 @@ export class PdfViewerElement extends PdfViewerBaseElement {
+       fileName = fileName + '.pdf';
      }
  
-     // Create blob before callback to avoid race condition.
-+    const a = document.createElement('a');
-+    a.download = fileName;
+-    // Create blob before callback to avoid race condition.
      const blob = new Blob([result.dataToSave], {type: 'application/pdf'});
-+    // <if expr="not _google_chrome">
-+    a.href = URL.createObjectURL(blob);
-+    a.click();
-+    URL.revokeObjectURL(a.href);
-+    // </if>
-+    // <if expr="_google_chrome">
-     chrome.fileSystem.chooseEntry(
-         {
-           type: 'saveFile',
-@@ -1191,6 +1208,7 @@ export class PdfViewerElement extends PdfViewerBaseElement {
-             // </if>
-           });
-         });
-+    // </if>
+-    chrome.fileSystem.chooseEntry(
+-        {
+-          type: 'saveFile',
+-          accepts: [{description: '*.pdf', extensions: ['pdf']}],
+-          suggestedName: fileName,
+-        },
+-        (entry?: FileSystemFileEntry) => {
+-          if (chrome.runtime.lastError) {
+-            if (chrome.runtime.lastError.message !== 'User cancelled') {
+-              console.error(
+-                  'chrome.fileSystem.chooseEntry failed: ' +
+-                  chrome.runtime.lastError.message);
+-            }
+-            return;
+-          }
+-          entry!.createWriter((writer: FileWriter) => {
+-            writer.write(blob);
+-            // <if expr="enable_ink or enable_pdf_ink2">
+-            // Unblock closing the window now that the user has saved
+-            // successfully.
+-            this.setShowBeforeUnloadDialog_(false);
+-            // </if>
+-            // <if expr="enable_pdf_ink2">
+-            this.hasSavedEdits_ =
+-                this.hasSavedEdits_ || requestType === SaveRequestType.EDITED;
+-            // </if>
+-          });
+-        });
++    try {
++      const fileHandle = await window.showSaveFilePicker({
++        suggestedName: fileName,
++        types: [{
++          description: 'PDF Files',
++          accept: { 'application/pdf': ['.pdf'] },
++        }],
++      });
++
++      const writable = await fileHandle.createWritable();
++      await writable.write(blob);
++      await writable.close();
++      // <if expr="enable_ink or enable_pdf_ink2">
++      // Unblock closing the window now that the user has saved
++      // successfully.
++      this.setShowBeforeUnloadDialog_(false);
++      // </if>
++      // <if expr="enable_pdf_ink2">
++      this.hasSavedEdits_ =
++        this.hasSavedEdits_ || requestType === SaveRequestType.EDITED;
++      // </if>
++    } catch (error: any) {
++      if (error.name !== 'AbortError') {
++        console.error('window.showSaveFilePicker failed: ' + error);
++      }
++    }
  
      // <if expr="enable_pdf_ink2">
      // Ink2 doesn't need to exit annotation mode after save.
+@@ -1300,6 +1296,9 @@ declare global {
+   interface HTMLElementTagNameMap {
+     'pdf-viewer': PdfViewerElement;
+   }
++  interface Window {
++    showSaveFilePicker(opts: unknown): Promise<FileSystemFileHandle>;
++  }
+ }
+ 
+ customElements.define(PdfViewerElement.is, PdfViewerElement);