Reland: [shapedetection] Upstream TextDetection tests to WPT

The original CL has been reverted because the test
external/wpt/shape-detection/idlharness.https.any.shareworker.html
is flaky on Mac. This reland CL disables this test on Mac.

Original change's description:
> [shapedetection] Upstream TextDetection tests to WPT
>
> This CL upstreams the reset of shapedetection tests(mainly
> TextDetection tests) to WPT.
>
> - Moves tests under
>   third_party/blink/web_tests/shapedetection/,
>   third_party/blink/web_tests/fast/shapedetection/,
>   third_party/blink/web_tests/http/tests/shapedetection/,
>   to third_party/blink/web_tests/external/wpt/shape-detection.
> - Moves third_party/blink/web_tests/shapedetection/resources/mock-textdetection.js
>   to third_party/blink/web_tests/external/wpt/resources/chromium/.
> - Removes third_party/blink/web_tests/fast/shapedetection/shapedetection-creation.html
>   as which should be covered in idlharness tests.
> - Introduces simulateClosedPipe() to MockBarcodeDetectionProvider to simulate
>   'no implementation available' case for
>   third_party/blink/web_tests/external/wpt/shape-detection/detection-getSupportedFormats.https.html
> - Improves third_party/blink/web_tests/external/wpt/shape-detection/detection-options.https.html
>   by using assert_throws_js BTW.
>
> Bug: 932382
> Change-Id: I2f795e4fa4f23b33c49b7924cdbcbccde5ac6ed4
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2131154
> Reviewed-by: Reilly Grant <reillyg@chromium.org>
> Commit-Queue: Wanming Lin <wanming.lin@intel.com>
> Cr-Commit-Position: refs/heads/master@{#756131}

Bug: 932382, 1067533
Change-Id: I85b5dcc841185e2edd20a76ed3aec8add7f08954
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2134924
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Commit-Queue: Wanming Lin <wanming.lin@intel.com>
Cr-Commit-Position: refs/heads/master@{#756647}
diff --git a/interfaces/text-detection-api.tentative.idl b/interfaces/text-detection-api.tentative.idl
new file mode 100644
index 0000000..8835c5f
--- /dev/null
+++ b/interfaces/text-detection-api.tentative.idl
@@ -0,0 +1,18 @@
+// Content was manually copied April 1 2020
+// Source: Accelerated Text Detection in Images (https://wicg.github.io/shape-detection-api/text.html)
+// This is currently a sister informative spec of Shape Detection API,
+// so it could not be merged into /interfaces/shape-detection-api.idl
+
+[
+    Constructor,
+    Exposed=(Window,Worker),
+    SecureContext
+] interface TextDetector {
+    Promise<sequence<DetectedText>> detect(ImageBitmapSource image);
+};
+
+dictionary DetectedText {
+  required DOMRectReadOnly boundingBox;
+  required DOMString rawValue;
+  required FrozenArray<Point2D> cornerPoints;
+};
\ No newline at end of file
diff --git a/resources/chromium/mock-barcodedetection.js b/resources/chromium/mock-barcodedetection.js
index 2558bbd..c11f879 100644
--- a/resources/chromium/mock-barcodedetection.js
+++ b/resources/chromium/mock-barcodedetection.js
@@ -10,9 +10,14 @@
 
       this.interceptor_ = new MojoInterfaceInterceptor(
           shapeDetection.mojom.BarcodeDetectionProvider.name);
-      this.interceptor_.oninterfacerequest =
-         e => this.bindingSet_.addBinding(this, e.handle);
+      this.interceptor_.oninterfacerequest = e => {
+        if (this.should_close_pipe_on_request_)
+          e.handle.close();
+        else
+          this.bindingSet_.addBinding(this, e.handle);
+      }
       this.interceptor_.start();
+      this.should_close_pipe_on_request_ = false;
     }
 
     createBarcodeDetection(request, options) {
@@ -39,9 +44,15 @@
 
     reset() {
       this.mockService_ = null;
+      this.should_close_pipe_on_request_ = false;
       this.bindingSet_.closeAllBindings();
       this.interceptor_.stop();
     }
+
+    // simulate a 'no implementation available' case
+    simulateNoImplementation() {
+      this.should_close_pipe_on_request_ = true;
+    }
   }
 
   // Class that mocks BarcodeDetection interface defined in
diff --git a/resources/chromium/mock-textdetection.js b/resources/chromium/mock-textdetection.js
new file mode 100644
index 0000000..427ce38
--- /dev/null
+++ b/resources/chromium/mock-textdetection.js
@@ -0,0 +1,93 @@
+"use strict";
+var TextDetectionTest = (() => {
+  // Class that mocks TextDetection interface defined in
+  // https://cs.chromium.org/chromium/src/services/shape_detection/public/mojom/textdetection.mojom
+  class MockTextDetection {
+    constructor() {
+      this.bindingSet_ =
+          new mojo.BindingSet(shapeDetection.mojom.TextDetection);
+
+      this.interceptor_ =
+          new MojoInterfaceInterceptor(shapeDetection.mojom.TextDetection.name);
+      this.interceptor_.oninterfacerequest =
+          e => this.bindingSet_.addBinding(this, e.handle);
+      this.interceptor_.start();
+    }
+
+    detect(bitmapData) {
+      this.bufferData_ =
+          new Uint32Array(getArrayBufferFromBigBuffer(bitmapData.pixelData));
+      return Promise.resolve({
+        results: [
+          {
+            rawValue : "cats",
+            boundingBox: { x: 1.0, y: 1.0, width: 100.0, height: 100.0 },
+            cornerPoints: [
+              { x: 1.0, y: 1.0 },
+              { x: 101.0, y: 1.0 },
+              { x: 101.0, y: 101.0 },
+              { x: 1.0, y: 101.0 }
+            ]
+          },
+          {
+            rawValue : "dogs",
+            boundingBox: { x: 2.0, y: 2.0, width: 50.0, height: 50.0 },
+            cornerPoints: [
+              { x: 2.0, y: 2.0 },
+              { x: 52.0, y: 2.0 },
+              { x: 52.0, y: 52.0 },
+              { x: 2.0, y: 52.0 }
+            ]
+          },
+        ],
+      });
+    }
+
+    getFrameData() {
+      return this.bufferData_;
+    }
+
+    reset() {
+      this.bindingSet_.closeAllBindings();
+      this.interceptor_.stop();
+    }
+
+  }
+
+  let testInternal = {
+    initialized: false,
+    MockTextDetection: null
+  }
+
+  class TextDetectionTestChromium {
+    constructor() {
+      Object.freeze(this); // Make it immutable.
+    }
+
+    initialize() {
+      if (testInternal.initialized)
+        throw new Error('Call reset() before initialize().');
+
+      testInternal.MockTextDetection = new MockTextDetection;
+      testInternal.initialized = true;
+    }
+
+    // Resets state of text detection mocks between test runs.
+    async reset() {
+      if (!testInternal.initialized)
+        throw new Error('Call initialize() before reset().');
+      testInternal.MockTextDetection.reset();
+      testInternal.MockTextDetection = null;
+      testInternal.initialized = false;
+
+      await new Promise(resolve => setTimeout(resolve, 0));
+    }
+
+    MockTextDetection() {
+      return testInternal.MockTextDetection;
+    }
+  }
+
+  return TextDetectionTestChromium;
+
+})();
diff --git a/resources/chromium/mock-textdetection.js.headers b/resources/chromium/mock-textdetection.js.headers
new file mode 100644
index 0000000..6c61a34
--- /dev/null
+++ b/resources/chromium/mock-textdetection.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
\ No newline at end of file
diff --git a/shape-detection/README.md b/shape-detection/README.md
index 556568b..624b021 100644
--- a/shape-detection/README.md
+++ b/shape-detection/README.md
@@ -1,6 +1,6 @@
 The `shapedetection-helpers.js` tests require implementations of
-the `FaceDetectionTest` and `BarcodeDetectionTest` interfaces, which
-should emulate platform shape detection backends.
+the `FaceDetectionTest`, `BarcodeDetectionTest` and `TextDetectionTest`
+interfaces, which should emulate platform shape detection backends.
 
 The `FaceDetectionTest` interface is defined as:
 
@@ -34,11 +34,30 @@
     async enumerateSupportedFormats(); //Enumerates supported formats
     getFrameData(); //Gets frame data of detection result.
     getFormats(); //Gets value of `formats` in `BarcodeDetector` constructor
+    simulateNoImplementation(); // simulate a 'no implementation available' case
   };
 ```
 
 The Chromium implementation of the `BarcodeDetectionTest` interface is located in
 [mock-barcodedetection.js](../resources/chromium/mock-barcodedetection.js).
 
+The `TextDetectionTest` interface is defined as:
+
+```
+  class TextDetectionTest {
+    async initialize();  // Sets up the testing environment.
+    async reset(); // Frees the resources.
+    MockTextDetection(); //Returns `MockTextDetection` interface.
+  };
+
+  class MockTextDetection {
+    getFrameData(); //Gets frame data of detection result.
+  };
+```
+
+The Chromium implementation of the `TextDetectionTest` interface is located in
+[mock-textdetection.js](../resources/chromium/mock-textdetection.js).
+
 Other browser vendors should provide their own implementations of
-the `FaceDetectionTest` and `BarcodeDetectionTest` interfaces.
+the `FaceDetectionTest`, `BarcodeDetectionTest` and `TextDetectionTest`
+interfaces.
diff --git a/shape-detection/detected-boundingBox-read-only.https.html b/shape-detection/detected-boundingBox-read-only.https.html
index 86b8889..dcf379b 100644
--- a/shape-detection/detected-boundingBox-read-only.https.html
+++ b/shape-detection/detected-boundingBox-read-only.https.html
@@ -4,7 +4,7 @@
 <script src="resources/shapedetection-helpers.js"></script>
 <script>
 
-// These tests verify that detected{Face, Barcode}'s boundingBox
+// These tests verify that detected{Face, Barcode, Text}'s boundingBox
 // should be DOMRectReadOnly.
 const imageDataTests =
     [
@@ -17,6 +17,11 @@
         createDetector: () => { return new BarcodeDetector(); },
         mockTestName: "BarcodeDetectionTest",
         name: "Barcode - detectedBarcode.boundingBox should be DOMRectReadOnly"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        mockTestName: "TextDetectionTest",
+        name: "Text - detectedText.boundingBox should be DOMRectReadOnly"
       }
     ];
 
diff --git a/shape-detection/detected-postMessage.https.html b/shape-detection/detected-postMessage.https.html
index 325aeee..8066984 100644
--- a/shape-detection/detected-postMessage.https.html
+++ b/shape-detection/detected-postMessage.https.html
@@ -4,7 +4,7 @@
 <script src="resources/shapedetection-helpers.js"></script>
 <script>
 
-// These tests verify that Detected{Face, Barcode} can be passed to
+// These tests verify that Detected{Face, Barcode, Text} can be passed to
 // postMessage().
 const postMessageTests =
     [
@@ -19,7 +19,13 @@
         mockTestName: "BarcodeDetectionTest",
         detectionResultTest: BarcodeDetectorDetectionResultTest,
         name: "Barcode - DetectedBarcode can be passed to postMessage()"
-      }
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        mockTestName: "TextDetectionTest",
+        detectionResultTest: TextDetectorDetectionResultTest,
+        name: "Text - DetectedText can be passed to postMessage()",
+      },
     ];
 
 for (let postMessageTest of postMessageTests) {
@@ -66,4 +72,19 @@
   assert_equals(detectionResult[1].format, "code_128", "barcode 2 format");
 }
 
+function TextDetectorDetectionResultTest(detectionResult) {
+  assert_equals(detectionResult.length, 2, "Number of textBlocks");
+  assert_equals(detectionResult[0].rawValue, "cats", "textBlock 1");
+  assert_equals(detectionResult[1].rawValue, "dogs", "textBlock 2");
+  for (let i = 0; i < detectionResult.length; i++) {
+    assert_equals(detectionResult[i].boundingBox.x, detectionResult[i].cornerPoints[0].x);
+    assert_equals(detectionResult[i].boundingBox.y, detectionResult[i].cornerPoints[0].y);
+    assert_equals(detectionResult[i].boundingBox.width,
+                  detectionResult[i].cornerPoints[2].x - detectionResult[i].cornerPoints[3].x);
+    assert_equals(detectionResult[i].boundingBox.height,
+                  detectionResult[i].cornerPoints[2].y - detectionResult[i].cornerPoints[1].y);
+  }
+
+}
+
 </script>
diff --git a/shape-detection/detection-HTMLCanvasElement.https.html b/shape-detection/detection-HTMLCanvasElement.https.html
index 4e9615a..e66b702 100644
--- a/shape-detection/detection-HTMLCanvasElement.https.html
+++ b/shape-detection/detection-HTMLCanvasElement.https.html
@@ -35,6 +35,20 @@
         mockTestName: "BarcodeDetectionTest",
         detectionResultTest: BarcodeDetectorDetectionResultTest,
         name: "Barcode - detect(OffscreenCanvas)"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        createCanvas: () => { return document.createElement("canvas"); },
+        mockTestName: "TextDetectionTest",
+        detectionResultTest: TextDetectorDetectionResultTest,
+        name: "Text - detect(HTMLCanvasElement)"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        createCanvas: () => { return new OffscreenCanvas(300, 150); },
+        mockTestName: "TextDetectionTest",
+        detectionResultTest: TextDetectorDetectionResultTest,
+        name: "Text - detect(OffscreenCanvas)"
       }
     ];
 
@@ -70,4 +84,10 @@
   assert_equals(detectionResult[1].format, "code_128", "barcode 2 format");
 }
 
+function TextDetectorDetectionResultTest(detectionResult, mockTest) {
+  assert_equals(detectionResult.length, 2, "Number of textBlocks");
+  assert_equals(detectionResult[0].rawValue, "cats", "textBlock 1");
+  assert_equals(detectionResult[1].rawValue, "dogs", "textBlock 2");
+}
+
 </script>
diff --git a/shape-detection/detection-HTMLImageElement.https.html b/shape-detection/detection-HTMLImageElement.https.html
index 979efab..f3b994c 100644
--- a/shape-detection/detection-HTMLImageElement.https.html
+++ b/shape-detection/detection-HTMLImageElement.https.html
@@ -21,6 +21,12 @@
         mockTestName: "BarcodeDetectionTest",
         detectionResultTest: BarcodeDetectorDetectionResultTest,
         name: "Barcode - detect(HTMLImageElement)",
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        mockTestName: "TextDetectionTest",
+        detectionResultTest: TextDetectorDetectionResultTest,
+        name: "Text - detect(HTMLImageElement)"
       }
     ];
 
@@ -62,4 +68,19 @@
   assert_equals(detectionResult[1].format, "code_128", "barcode 2 format");
 }
 
+function TextDetectorDetectionResultTest(detectionResult, mockTest) {
+  assert_equals(detectionResult.length, 2, "Number of textBlocks");
+  assert_equals(detectionResult[0].rawValue, "cats", "textBlock 1");
+  assert_equals(detectionResult[1].rawValue, "dogs", "textBlock 2");
+  for (let i = 0; i < detectionResult.length; i++) {
+    assert_equals(detectionResult[i].boundingBox.x, detectionResult[i].cornerPoints[0].x);
+    assert_equals(detectionResult[i].boundingBox.y, detectionResult[i].cornerPoints[0].y);
+    assert_equals(detectionResult[i].boundingBox.width,
+                  detectionResult[i].cornerPoints[2].x - detectionResult[i].cornerPoints[3].x);
+    assert_equals(detectionResult[i].boundingBox.height,
+                  detectionResult[i].cornerPoints[2].y - detectionResult[i].cornerPoints[1].y);
+  }
+
+}
+
 </script>
diff --git a/shape-detection/detection-HTMLVideoElement.https.html b/shape-detection/detection-HTMLVideoElement.https.html
index 7b3736d..2ce379b 100644
--- a/shape-detection/detection-HTMLVideoElement.https.html
+++ b/shape-detection/detection-HTMLVideoElement.https.html
@@ -17,7 +17,13 @@
         createDetector: () => { return new BarcodeDetector(); },
         mockTestName: "BarcodeDetectionTest",
         detectionResultTest: BarcodeDetectorDetectionResultTest,
-        name: "Barcode - detect(HTMLVideoElement)",
+        name: "Barcode - detect(HTMLVideoElement)"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        mockTestName: "TextDetectionTest",
+        detectionResultTest: TextDetectorDetectionResultTest,
+        name: "Text - detect(HTMLVideoElement)"
       }
     ];
 
@@ -52,4 +58,10 @@
   assert_equals(detectionResult[1].format, "code_128", "barcode 2 format");
 }
 
+function TextDetectorDetectionResultTest(detectionResult, mockTest) {
+  assert_equals(detectionResult.length, 2, "Number of textBlocks");
+  assert_equals(detectionResult[0].rawValue, "cats", "textBlock 1");
+  assert_equals(detectionResult[1].rawValue, "dogs", "textBlock 2");
+}
+
 </script>
diff --git a/shape-detection/detection-ImageBitmap.https.html b/shape-detection/detection-ImageBitmap.https.html
index a7157c0..b4302c4 100644
--- a/shape-detection/detection-ImageBitmap.https.html
+++ b/shape-detection/detection-ImageBitmap.https.html
@@ -17,7 +17,13 @@
         createDetector: () => { return new BarcodeDetector(); },
         mockTestName: "BarcodeDetectionTest",
         detectionResultTest: BarcodeDetectorDetectionResultTest,
-        name: "Barcode - detect(ImageBitmap)",
+        name: "Barcode - detect(ImageBitmap)"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        mockTestName: "TextDetectionTest",
+        detectionResultTest: TextDetectorDetectionResultTest,
+        name: "Text - detect(ImageBitmap)",
       }
     ];
 
@@ -51,4 +57,10 @@
   assert_equals(detectionResult[1].format, "code_128", "barcode 2 format");
 }
 
+function TextDetectorDetectionResultTest(detectionResult, mockTest) {
+  assert_equals(detectionResult.length, 2, "Number of textBlocks");
+  assert_equals(detectionResult[0].rawValue, "cats", "textBlock 1");
+  assert_equals(detectionResult[1].rawValue, "dogs", "textBlock 2");
+}
+
 </script>
diff --git a/shape-detection/detection-ImageData-detached.https.html b/shape-detection/detection-ImageData-detached.https.html
index 6b6060f..b4e31c8 100644
--- a/shape-detection/detection-ImageData-detached.https.html
+++ b/shape-detection/detection-ImageData-detached.https.html
@@ -31,4 +31,16 @@
   }
 }, 'BarcodeDetector.detect() rejects on a detached buffer');
 
+promise_test(async () => {
+  let data = new ImageData(1024, 1024);
+  detachBuffer(data.data.buffer);
+  let detector = new TextDetector();
+  try {
+    await detector.detect(data);
+    assert_unreached();
+  } catch (e) {
+    assert_equals(e.code, DOMException.INVALID_STATE_ERR);
+  }
+}, 'TextDetector.detect() rejects on a detached buffer');
+
 </script>
diff --git a/shape-detection/detection-ImageData.https.html b/shape-detection/detection-ImageData.https.html
index a74c2af..330239f 100644
--- a/shape-detection/detection-ImageData.https.html
+++ b/shape-detection/detection-ImageData.https.html
@@ -18,6 +18,12 @@
         mockTestName: "BarcodeDetectionTest",
         detectionResultTest: BarcodeDetectorDetectionResultTest,
         name: "Barcode - detect(ImageData)"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        mockTestName: "TextDetectionTest",
+        detectionResultTest: TextDetectorDetectionResultTest,
+        name: "Text - detect(ImageData)",
       }
     ];
 
@@ -53,4 +59,10 @@
   assert_equals(detectionResult[1].format, "code_128", "barcode 2 format");
 }
 
+function TextDetectorDetectionResultTest(detectionResult, mockTest) {
+  assert_equals(detectionResult.length, 2, "Number of textBlocks");
+  assert_equals(detectionResult[0].rawValue, "cats", "textBlock 1");
+  assert_equals(detectionResult[1].rawValue, "dogs", "textBlock 2");
+}
+
 </script>
diff --git a/shape-detection/detection-getSupportedFormats.https.html b/shape-detection/detection-getSupportedFormats.https.html
index 4ccb5ab..0b4b223 100644
--- a/shape-detection/detection-getSupportedFormats.https.html
+++ b/shape-detection/detection-getSupportedFormats.https.html
@@ -13,4 +13,13 @@
   assert_equals(result[2], 'qr_code', 'format 3');
 }, 'get supported barcode formats');
 
+detection_test('BarcodeDetectionTest', async (t, detectionTest) => {
+  // Disable built-in support for barcode detection to test fallback handling.
+  detectionTest.MockBarcodeDetectionProvider().simulateNoImplementation();
+
+  const result = await BarcodeDetector.getSupportedFormats();
+  assert_equals(result.length, 0, 'result.length');
+
+}, 'getSupportedFormats() resolves with empty list when unsupported');
+
 </script>
diff --git a/shape-detection/detection-on-worker.https.worker.js b/shape-detection/detection-on-worker.https.worker.js
index 6b440af..94b8e37 100644
--- a/shape-detection/detection-on-worker.https.worker.js
+++ b/shape-detection/detection-on-worker.https.worker.js
@@ -18,6 +18,12 @@
         mockTestName: "BarcodeDetectionTest",
         resultSize: 2, // Number of barcodes
         detectorType: "Barcode"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        mockTestName: "TextDetectionTest",
+        resultSize: 2, // Number of text blocks
+        detectorType: "Text"
       }
     ];
 
diff --git a/shape-detection/detection-options.https.html b/shape-detection/detection-options.https.html
index 704faf7..db870e8 100644
--- a/shape-detection/detection-options.https.html
+++ b/shape-detection/detection-options.https.html
@@ -40,27 +40,16 @@
        shapeDetection.mojom.BarcodeFormat.QR_CODE],
       "formats");
 
-  try {
-    new BarcodeDetector({formats: []});
-    assert_unreached("providing hint option that is empty should fail");
-  } catch (error) {
-    assert_equals(error.name, "TypeError");
-  }
+  const invalidFormats = [
+    [],
+    ["unknown"],
+    ["foo", "bar"]
+  ];
 
-  try {
-    new BarcodeDetector({formats: ["unknown"]});
-    assert_unreached("providing \"unknown\" as a hint option should fail");
-  } catch (error) {
-    assert_equals(error.name, "TypeError");
-  }
+  invalidFormats.forEach(invalidFormat => {
+    assert_throws_js(TypeError, () => new BarcodeDetector({formats: invalidFormat}));
+  });
 
-  try {
-    new BarcodeDetector({formats: ["foo", "bar"]});
-    assert_unreached(
-        "providing hint option with unrecognized formats should fail");
-  } catch (error) {
-    assert_equals(error.name, "TypeError");
-  }
 }, "Test that BarcodeDetectorOptions are correctly propagated");
 
 </script>
diff --git a/shape-detection/detection-security-test.https.html b/shape-detection/detection-security-test.https.html
index b3f458e..4d87238 100644
--- a/shape-detection/detection-security-test.https.html
+++ b/shape-detection/detection-security-test.https.html
@@ -13,7 +13,11 @@
       },
       {
         createDetector: () => { return new BarcodeDetector(); },
-        name: "Barcode - detect(broken image)",
+        name: "Barcode - detect(broken image)"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        name: "Text - detect(broken image)"
       }
     ];
 
@@ -38,6 +42,10 @@
       {
         createDetector: () => { return new BarcodeDetector(); },
         name: "Barcode - detect(broken video)"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        name: "Text - detect(broken video)"
       }
     ];
 
diff --git a/shape-detection/detector-same-object.https.html b/shape-detection/detector-same-object.https.html
index 5254027..bf7c068 100644
--- a/shape-detection/detector-same-object.https.html
+++ b/shape-detection/detector-same-object.https.html
@@ -4,7 +4,7 @@
 <script src="resources/shapedetection-helpers.js"></script>
 <script>
 
-// These tests verify that detect()ed Detected{Barcode,Face}'s individual
+// These tests verify that detect()ed Detected{Barcode,Face, Text}'s individual
 // fields are [SameObject].
 const imageDataTests =
     [
@@ -19,6 +19,12 @@
         mockTestName: "BarcodeDetectionTest",
         detectionResultTest: CheckDetectedBarcodesSameObjects,
         name: "Barcode - detect(ImageData), [SameObject]"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        mockTestName: "TextDetectionTest",
+        detectionResultTest: CheckDetectedTextBlocksSameObjects,
+        name: "Text - detect(ImageData), [SameObject]",
       }
     ];
 
@@ -52,4 +58,10 @@
   assert_equals(detectedBarcodes[0].cornerPoints, detectedBarcodes[0].cornerPoints);
 }
 
+function CheckDetectedTextBlocksSameObjects(detectedTextBlocks) {
+  assert_greater_than(detectedTextBlocks.length, 0, "Number of textBlocks");
+  assert_equals(detectedTextBlocks[0].rawValue, detectedTextBlocks[0].rawValue);
+  assert_equals(detectedTextBlocks[0].boundingBox, detectedTextBlocks[0].boundingBox);
+}
+
 </script>
diff --git a/shape-detection/idlharness.https.any.js b/shape-detection/idlharness.https.any.js
index da60edb..c3683f8 100644
--- a/shape-detection/idlharness.https.any.js
+++ b/shape-detection/idlharness.https.any.js
@@ -7,12 +7,13 @@
 'use strict';
 
 idl_test(
-  ['shape-detection-api'],
+  ['shape-detection-api', 'text-detection-api.tentative'],
   ['dom', 'geometry'],
   async idl_array => {
     idl_array.add_objects({
       FaceDetector: ['new FaceDetector()'],
       BarcodeDetector: ['new BarcodeDetector()'],
+      TextDetector: ['new TextDetector()'],
     });
   }
 );
diff --git a/shape-detection/resources/shapedetection-helpers.js b/shape-detection/resources/shapedetection-helpers.js
index eed4484..bdb0e85 100644
--- a/shape-detection/resources/shapedetection-helpers.js
+++ b/shape-detection/resources/shapedetection-helpers.js
@@ -28,8 +28,10 @@
     `${prefix}/barcodedetection_provider.mojom.js`,
     `${prefix}/facedetection.mojom.js`,
     `${prefix}/facedetection_provider.mojom.js`,
+    `${prefix}/textdetection.mojom.js`,
     '/resources/chromium/mock-barcodedetection.js',
     '/resources/chromium/mock-facedetection.js',
+    '/resources/chromium/mock-textdetection.js',
   ].forEach(path => {
     // Use importScripts for workers.
     if (typeof document === 'undefined') {
@@ -51,7 +53,8 @@
 /**
  * @param {String} detectionTestName
  * name of mock shape detection test interface,
- * must be the item of ["FaceDetectionTest", "BarcodeDetectionTest"]
+ * must be the item of ["FaceDetectionTest", "BarcodeDetectionTest",
+ * "TextDetectionTest"]
 */
 async function initialize_detection_tests(detectionTestName) {
   let detectionTest;
diff --git a/shape-detection/shapedetection-cross-origin.sub.https.html b/shape-detection/shapedetection-cross-origin.sub.https.html
index d20cc33..c4e3c3f 100644
--- a/shape-detection/shapedetection-cross-origin.sub.https.html
+++ b/shape-detection/shapedetection-cross-origin.sub.https.html
@@ -18,6 +18,10 @@
       {
         createDetector: () => { return new BarcodeDetector(); },
         detectorType: "BarcodeDetector"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        detectorType: "TextDetector"
       }
     ];
 
diff --git a/shape-detection/shapedetection-empty-input.https.html b/shape-detection/shapedetection-empty-input.https.html
index 601c992..a0d4490 100644
--- a/shape-detection/shapedetection-empty-input.https.html
+++ b/shape-detection/shapedetection-empty-input.https.html
@@ -14,6 +14,10 @@
       {
         createDetector: () => { return new BarcodeDetector(); },
         name: "Barcode - detect(empty)"
+      },
+      {
+        createDetector: () => { return new TextDetector(); },
+        name: "Text - detect(empty)"
       }
     ];