CCA: Fallback to inline buffer when BigBuffer fails

Add a fallback mechanism to use inline buffer when BigBuffer fails. Once
BigBuffer fails in a session, all subsequent operations will use inline
buffer.

Test: Manually
Bug: b:349015781
Change-Id: Ia36411abae145471e955a49f0458ac83a02c21da
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5678392
Commit-Queue: Chu-Hsuan Yang <chuhsuan@chromium.org>
Reviewed-by: Sean Li <seannli@google.com>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1323603}
diff --git a/ash/webui/camera_app_ui/camera_app_helper.mojom b/ash/webui/camera_app_ui/camera_app_helper.mojom
index 0e5bc83..37fdb98 100644
--- a/ash/webui/camera_app_ui/camera_app_helper.mojom
+++ b/ash/webui/camera_app_ui/camera_app_helper.mojom
@@ -325,6 +325,9 @@
   // Performs OCR on the image and returns the OCR result.
   PerformOcr(mojo_base.mojom.BigBuffer jpeg_data) => (OcrResult ocr_result);
 
+  // Same as `PerformOcr`, but using `array<uint8>`.
+  PerformOcrInline(array<uint8> jpeg_data) => (OcrResult ocr_result);
+
   // Creates a PDF builder.
   CreatePdfBuilder(pending_receiver<PdfBuilder> builder);
 };
diff --git a/ash/webui/camera_app_ui/camera_app_helper_impl.cc b/ash/webui/camera_app_ui/camera_app_helper_impl.cc
index 3be84ca..e982c5ae 100644
--- a/ash/webui/camera_app_ui/camera_app_helper_impl.cc
+++ b/ash/webui/camera_app_ui/camera_app_helper_impl.cc
@@ -623,6 +623,12 @@
   camera_app_ui_->delegate()->PerformOcr(jpeg_data, std::move(callback));
 }
 
+void CameraAppHelperImpl::PerformOcrInline(
+    const std::vector<uint8_t>& jpeg_data,
+    PerformOcrCallback callback) {
+  camera_app_ui_->delegate()->PerformOcr(jpeg_data, std::move(callback));
+}
+
 void CameraAppHelperImpl::CreatePdfBuilder(
     mojo::PendingReceiver<camera_app::mojom::PdfBuilder> receiver) {
   return camera_app_ui_->delegate()->CreatePdfBuilder(std::move(receiver));
diff --git a/ash/webui/camera_app_ui/camera_app_helper_impl.h b/ash/webui/camera_app_ui/camera_app_helper_impl.h
index 9cf347a2..fdea751 100644
--- a/ash/webui/camera_app_ui/camera_app_helper_impl.h
+++ b/ash/webui/camera_app_ui/camera_app_helper_impl.h
@@ -120,6 +120,8 @@
                        RenderPdfAsJpegCallback callback) override;
   void PerformOcr(mojo_base::BigBuffer jpeg_data,
                   PerformOcrCallback callback) override;
+  void PerformOcrInline(const std::vector<uint8_t>& jpeg_data,
+                        PerformOcrCallback callback) override;
   void CreatePdfBuilder(
       mojo::PendingReceiver<camera_app::mojom::PdfBuilder> receiver) override;
 
diff --git a/ash/webui/camera_app_ui/pdf_builder.mojom b/ash/webui/camera_app_ui/pdf_builder.mojom
index 00e32fb..5dfe13f 100644
--- a/ash/webui/camera_app_ui/pdf_builder.mojom
+++ b/ash/webui/camera_app_ui/pdf_builder.mojom
@@ -14,9 +14,13 @@
   // `page_index` is larger than PDF's current last index(L), the created page
   // index is the next available index(L+1).
   AddPage(mojo_base.mojom.BigBuffer jpeg, uint32 page_index);
+  // Same as `AddPage`, but using `array<uint8>` instead.
+  AddPageInline(array<uint8> jpeg, uint32 page_index);
   // Deletes the page of the PDF at `page_index` and shift the following pages
   // forward. Does nothing if the page at `page_index` doesn't exist.
   DeletePage(uint32 page_index);
   // Returns the PDF. It can be called multiple times at any time.
   Save() => (mojo_base.mojom.BigBuffer pdf);
+  // Same as `Save`, but using `array<uint8>` instead.
+  SaveInline() => (array<uint8> pdf);
 };
diff --git a/ash/webui/camera_app_ui/resources/js/mojo/chrome_helper.ts b/ash/webui/camera_app_ui/resources/js/mojo/chrome_helper.ts
index 372d429..d0ad2a1 100644
--- a/ash/webui/camera_app_ui/resources/js/mojo/chrome_helper.ts
+++ b/ash/webui/camera_app_ui/resources/js/mojo/chrome_helper.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {assert, assertNotReached} from '../assert.js';
+import {assert, assertInstanceof, assertNotReached} from '../assert.js';
 import {reportError} from '../error.js';
 import {Point} from '../geometry.js';
 import * as localDev from '../local_dev.js';
@@ -80,10 +80,12 @@
   const size = bytes.byteLength;
 
   const sharedBuffer = Mojo.createSharedBuffer(size);
-  assert(sharedBuffer.result === Mojo.RESULT_OK);
+  assert(
+      sharedBuffer.result === Mojo.RESULT_OK,
+      'Failed to create shared buffer.');
 
   const mapBuffer = sharedBuffer.handle.mapBuffer(0, size);
-  assert(mapBuffer.result === Mojo.RESULT_OK);
+  assert(mapBuffer.result === Mojo.RESULT_OK, 'Failed to map buffer.');
 
   const uint8View = new Uint8Array(mapBuffer.buffer);
   uint8View.set(bytes);
@@ -103,8 +105,24 @@
   return bigBuffer;
 }
 
+/**
+ * Creates a number array from `blob` for Mojo's `array<uint8>`.
+ */
+export async function createNumArrayFromBlob(blob: Blob): Promise<number[]> {
+  const buffer = await blob.arrayBuffer();
+  return castToNumberArray(new Uint8Array(buffer));
+}
+
 export abstract class ChromeHelper {
   /**
+   * TODO(b/349015781): A flag to determine if we should use BigBuffer. It
+   * will be turned off when something went wrong when using BigBuffer. In the
+   * future, we want to monitor the error metrics to see if this flag is still
+   * needed.
+   */
+  static useBigBuffer = true;
+
+  /**
    * Starts monitoring tablet mode state of device.
    *
    * @param onChange Callback called each time when tablet mode state of device
@@ -283,6 +301,15 @@
     }
     return instance;
   }
+
+  static handleBigBufferError(e: unknown): void {
+    ChromeHelper.useBigBuffer = false;
+    reportError(
+        ErrorType.BIG_BUFFER_FAILURE,
+        ErrorLevel.WARNING,
+        assertInstanceof(e, Error),
+    );
+  }
 }
 
 export const getInstanceImpl =
@@ -527,8 +554,17 @@
   }
 
   override async performOcr(jpeg: Blob): Promise<OcrResult> {
-    const bigBuffer = await createBigBufferFromBlob(jpeg);
-    const {ocrResult} = await this.remote.performOcr(bigBuffer);
+    try {
+      if (ChromeHelper.useBigBuffer) {
+        const bigBuffer = await createBigBufferFromBlob(jpeg);
+        const {ocrResult} = await this.remote.performOcr(bigBuffer);
+        return ocrResult;
+      }
+    } catch (e) {
+      ChromeHelper.handleBigBufferError(e);
+    }
+    const numArray = await createNumArrayFromBlob(jpeg);
+    const {ocrResult} = await this.remote.performOcrInline(numArray);
     return ocrResult;
   }
 
diff --git a/ash/webui/camera_app_ui/resources/js/type.ts b/ash/webui/camera_app_ui/resources/js/type.ts
index 8a4f5d86..b69da19 100644
--- a/ash/webui/camera_app_ui/resources/js/type.ts
+++ b/ash/webui/camera_app_ui/resources/js/type.ts
@@ -373,6 +373,7 @@
  * Types of error used in ERROR metrics.
  */
 export enum ErrorType {
+  BIG_BUFFER_FAILURE = 'big-buffer-failure',
   BROKEN_THUMBNAIL = 'broken-thumbnail',
   CHECK_COVER_FAILURE = 'check-cover-failed',
   DEVICE_INFO_UPDATE_FAILURE = 'device-info-update-failure',
diff --git a/ash/webui/camera_app_ui/resources/js/views/document_review.ts b/ash/webui/camera_app_ui/resources/js/views/document_review.ts
index ff2f848..226e93d 100644
--- a/ash/webui/camera_app_ui/resources/js/views/document_review.ts
+++ b/ash/webui/camera_app_ui/resources/js/views/document_review.ts
@@ -21,7 +21,11 @@
 import {Filenamer} from '../models/file_namer.js';
 import {getI18nMessage} from '../models/load_time_data.js';
 import {ResultSaver} from '../models/result_saver.js';
-import {ChromeHelper, createBigBufferFromBlob} from '../mojo/chrome_helper.js';
+import {
+  ChromeHelper,
+  createBigBufferFromBlob,
+  createNumArrayFromBlob,
+} from '../mojo/chrome_helper.js';
 import {
   BigBuffer,
   PdfBuilderRemote,
@@ -643,8 +647,17 @@
    */
   async addPage(jpg: Blob, index: number): Promise<void> {
     assert(this.builder !== null);
-    const bigBuffer = await createBigBufferFromBlob(jpg);
-    this.builder.addPage(bigBuffer, index);
+    try {
+      if (ChromeHelper.useBigBuffer) {
+        const bigBuffer = await createBigBufferFromBlob(jpg);
+        this.builder.addPage(bigBuffer, index);
+        return;
+      }
+    } catch (e) {
+      ChromeHelper.handleBigBufferError(e);
+    }
+    const numArray = await createNumArrayFromBlob(jpg);
+    this.builder.addPageInline(numArray, index);
   }
 
   /**
@@ -660,8 +673,16 @@
    */
   async save(): Promise<Blob> {
     assert(this.builder !== null);
-    const {pdf} = await this.builder.save();
-    return this.createPdfBlob(pdf);
+    try {
+      if (ChromeHelper.useBigBuffer) {
+        const {pdf} = await this.builder.save();
+        return this.createPdfBlob(pdf);
+      }
+    } catch (e) {
+      ChromeHelper.handleBigBufferError(e);
+    }
+    const {pdf} = await this.builder.saveInline();
+    return new Blob([new Uint8Array(pdf)], {type: MimeType.PDF});
   }
 
   /**
diff --git a/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.cc b/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.cc
index 4ffbc7d..abc6033 100644
--- a/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.cc
+++ b/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.cc
@@ -386,6 +386,16 @@
 void ChromeCameraAppUIDelegate::PdfServiceManager::ProgressivePdf::AddPage(
     mojo_base::BigBuffer jpg,
     uint32_t index) {
+  AddPageInternal(jpg, index);
+}
+
+void ChromeCameraAppUIDelegate::PdfServiceManager::ProgressivePdf::
+    AddPageInline(const std::vector<uint8_t>& jpg, uint32_t index) {
+  AddPageInternal(jpg, index);
+}
+
+void ChromeCameraAppUIDelegate::PdfServiceManager::ProgressivePdf::
+    AddPageInternal(base::span<const uint8_t> jpg, uint32_t index) {
   if (!pdf_searchifier_) {
     LOG(ERROR) << "Failed to add new page to PDF";
     return;
@@ -406,6 +416,15 @@
 
 void ChromeCameraAppUIDelegate::PdfServiceManager::ProgressivePdf::Save(
     SaveCallback callback) {
+  SaveInline(base::BindOnce(
+      [](SaveCallback callback, const std::vector<uint8_t>& pdf) {
+        std::move(callback).Run(pdf);
+      },
+      std::move(callback)));
+}
+
+void ChromeCameraAppUIDelegate::PdfServiceManager::ProgressivePdf::SaveInline(
+    SaveInlineCallback callback) {
   if (!pdf_searchifier_) {
     LOG(ERROR) << "Failed to save PDF";
     std::move(callback).Run({});
diff --git a/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.h b/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.h
index 9c56336..d91a760 100644
--- a/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.h
+++ b/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.h
@@ -146,13 +146,17 @@
 
       // ash::camera_app::mojom::PdfBuilder
       void AddPage(mojo_base::BigBuffer jpg, uint32_t index) override;
+      void AddPageInline(const std::vector<uint8_t>& jpg,
+                         uint32_t index) override;
       void DeletePage(uint32_t index) override;
       void Save(SaveCallback callback) override;
+      void SaveInline(SaveInlineCallback callback) override;
 
      private:
+      void AddPageInternal(base::span<const uint8_t> jpg, uint32_t index);
       void ConsumeSaveCallback(const std::vector<uint8_t>& searchified_pdf);
 
-      SaveCallback save_callback_;
+      SaveInlineCallback save_callback_;
       mojo::Remote<pdf::mojom::PdfService> pdf_service_;
       mojo::Remote<pdf::mojom::PdfProgressiveSearchifier> pdf_searchifier_;
       base::WeakPtrFactory<ProgressivePdf> weak_factory_{this};