Released June 29, 2026

CVE-2026-43700

CVSS 6.5 severity medium CWE-346 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N

Overview

Impact:

Description: Processing maliciously crafted web content may disclose sensitive user information

Researchers: Vitaly Simonovich, Muhamad Syaiful, Christian Meurer Xavier

Key insight. importExternalTexture enforced the cross-origin video taint check on only some paths; adding it to the reused-external-texture fast path and the main path closes the remaining SOP holes.

Patch Details

diff --git a/LayoutTests/fast/webgpu/regression/repro_315368-expected.txt b/LayoutTests/fast/webgpu/regression/repro_315368-expected.txt
new file mode 100644
index 000000000000..f3f8a6a97a34
--- /dev/null
+++ b/LayoutTests/fast/webgpu/regression/repro_315368-expected.txt
@@ -0,0 +1,2 @@
+CONSOLE MESSAGE: Pass
+
diff --git a/LayoutTests/fast/webgpu/regression/repro_315368.html b/LayoutTests/fast/webgpu/regression/repro_315368.html
new file mode 100644
index 000000000000..cecbedb144ce
--- /dev/null
+++ b/LayoutTests/fast/webgpu/regression/repro_315368.html
@@ -0,0 +1,29 @@
+<script>
+  globalThis.testRunner?.waitUntilDone();
+  const log = console.debug;
+
+  const veryBrightVideo = `data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAAvG1kYXQAAAAfTgEFGkdWStxcTEM/lO/FETzRQ6gD7gAA7gIAA3EYgAAAAEgoAa8iNjAkszOL+e58c//cEe//0TT//scp1n/381P/RWP/zOW4QtxorfVogeh8nQDbQAAAAwAQMCcWUTAAAAMAAAMAAAMA84AAAAAVAgHQAyu+KT35E7gAADFgAAADABLQAAAAEgIB4AiS76MTkNbgAAF3AAAPSAAAABICAeAEn8+hBOTXYAADUgAAHRAAAAPibW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAAKcAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAw10cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAAKcAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAABAAAAAQAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAACnAAAAAAABAAAAAAKFbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAABdwAAAD6BVxAAAAAAAMWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABDb3JlIE1lZGlhIFZpZGVvAAAAAixtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAHsc3RibAAAARxzdHNkAAAAAAAAAAEAAAEMaHZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAQABAASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAHVodmNDAQIgAAAAsAAAAAAAPPAA/P36+gAACwOgAAEAGEABDAH//wIgAAADALAAAAMAAAMAPBXAkKEAAQAmQgEBAiAAAAMAsAAAAwAAAwA8oBQgQcCTDLYgV7kWVYC1CRAJAICiAAEACUQBwChkuNBTJAAAAApmaWVsAQAAAAATY29scm5jbHgACQAQAAkAAAAAEHBhc3AAAAABAAAAAQAAABRidHJ0AAAAAAAALPwAACz8AAAAKHN0dHMAAAAAAAAAAwAAAAIAAAPoAAAAAQAAAAEAAAABAAAD6AAAABRzdHNzAAAAAAAAAAEAAAABAAAAEHNkdHAAAAAAIBAQGAAAAChjdHRzAAAAAAAAAAMAAAABAAAAAAAAAAEAAAfQAAAAAgAAAAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAQAAAABAAAAJHN0c3oAAAAAAAAAAAAAAAQAAABvAAAAGQAAABYAAAAWAAAAFHN0Y28AAAAAAAAAAQAAACwAAABhdWR0YQAAAFltZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAACxpbHN0AAAAJKl0b28AAAAcZGF0YQAAAAEAAAAATGF2ZjYwLjMuMTAw`;
+
+  // Tests the new texture path in importExternalTexture: first import of a same-origin video
+  // element exercises the code path that creates a new GPUExternalTexture (bug 315368).
+  onload = async () => {
+    let adapter = await navigator.gpu.requestAdapter({});
+    let device = await adapter.requestDevice({});
+
+    let video = document.createElement('video');
+    video.src = veryBrightVideo;
+    video.muted = true;
+    await new Promise(resolve => { video.onloadeddata = () => { video.play(); resolve(); }; });
+
+    video.requestVideoFrameCallback(() => {
+      try {
+        device.importExternalTexture({source: video});
+        log('Pass');
+      } catch (e) {
+        log('FAIL: ' + e.message);
+      }
+      globalThis.testRunner?.dumpAsText();
+      globalThis.testRunner?.notifyDone();
+    });
+  };
+</script>
diff --git a/LayoutTests/fast/webgpu/regression/repro_315368b-expected.txt b/LayoutTests/fast/webgpu/regression/repro_315368b-expected.txt
new file mode 100644
index 000000000000..f3f8a6a97a34
--- /dev/null
+++ b/LayoutTests/fast/webgpu/regression/repro_315368b-expected.txt
@@ -0,0 +1,2 @@
+CONSOLE MESSAGE: Pass
+
diff --git a/LayoutTests/fast/webgpu/regression/repro_315368b.html b/LayoutTests/fast/webgpu/regression/repro_315368b.html
new file mode 100644
index 000000000000..b5efaa958908
--- /dev/null
+++ b/LayoutTests/fast/webgpu/regression/repro_315368b.html
@@ -0,0 +1,31 @@
+<script>
+  globalThis.testRunner?.waitUntilDone();
+  const log = console.debug;
+
+  const veryBrightVideo = `data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAAvG1kYXQAAAAfTgEFGkdWStxcTEM/lO/FETzRQ6gD7gAA7gIAA3EYgAAAAEgoAa8iNjAkszOL+e58c//cEe//0TT//scp1n/381P/RWP/zOW4QtxorfVogeh8nQDbQAAAAwAQMCcWUTAAAAMAAAMAAAMA84AAAAAVAgHQAyu+KT35E7gAADFgAAADABLQAAAAEgIB4AiS76MTkNbgAAF3AAAPSAAAABICAeAEn8+hBOTXYAADUgAAHRAAAAPibW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAAKcAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAw10cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAAKcAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAABAAAAAQAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAACnAAAAAAABAAAAAAKFbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAABdwAAAD6BVxAAAAAAAMWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABDb3JlIE1lZGlhIFZpZGVvAAAAAixtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAHsc3RibAAAARxzdHNkAAAAAAAAAAEAAAEMaHZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAQABAASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAHVodmNDAQIgAAAAsAAAAAAAPPAA/P36+gAACwOgAAEAGEABDAH//wIgAAADALAAAAMAAAMAPBXAkKEAAQAmQgEBAiAAAAMAsAAAAwAAAwA8oBQgQcCTDLYgV7kWVYC1CRAJAICiAAEACUQBwChkuNBTJAAAAApmaWVsAQAAAAATY29scm5jbHgACQAQAAkAAAAAEHBhc3AAAAABAAAAAQAAABRidHJ0AAAAAAAALPwAACz8AAAAKHN0dHMAAAAAAAAAAwAAAAIAAAPoAAAAAQAAAAEAAAABAAAD6AAAABRzdHNzAAAAAAAAAAEAAAABAAAAEHNkdHAAAAAAIBAQGAAAAChjdHRzAAAAAAAAAAMAAAABAAAAAAAAAAEAAAfQAAAAAgAAAAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAQAAAABAAAAJHN0c3oAAAAAAAAAAAAAAAQAAABvAAAAGQAAABYAAAAWAAAAFHN0Y28AAAAAAAAAAQAAACwAAABhdWR0YQAAAFltZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAACxpbHN0AAAAJKl0b28AAAAcZGF0YQAAAAEAAAAATGF2ZjYwLjMuMTAw`;
+
+  // Tests the cached texture path in importExternalTexture: a second synchronous import of the
+  // same same-origin video element exercises the early-return path that reuses a cached
+  // GPUExternalTexture (bug 315368, PLATFORM(COCOA) only).
+  onload = async () => {
+    let adapter = await navigator.gpu.requestAdapter({});
+    let device = await adapter.requestDevice({});
+
+    let video = document.createElement('video');
+    video.src = veryBrightVideo;
+    video.muted = true;
+    await new Promise(resolve => { video.onloadeddata = () => { video.play(); resolve(); }; });
+
+    video.requestVideoFrameCallback(() => {
+      try {
+        let et1 = device.importExternalTexture({source: video}); // new texture path
+        let et2 = device.importExternalTexture({source: video}); // cached texture path (Cocoa)
+        log('Pass');
+      } catch (e) {
+        log('FAIL: ' + e.message);
+      }
+      globalThis.testRunner?.dumpAsText();
+      globalThis.testRunner?.notifyDone();
+    });
+  };
+</script>
diff --git a/Source/WebCore/Modules/WebGPU/GPUDevice.cpp b/Source/WebCore/Modules/WebGPU/GPUDevice.cpp
index add1e02b8303..3d03ba423a61 100644
--- a/Source/WebCore/Modules/WebGPU/GPUDevice.cpp
+++ b/Source/WebCore/Modules/WebGPU/GPUDevice.cpp
@@ -74,6 +74,7 @@
 #include "JSGPUUncapturedErrorEvent.h"
 #include "JSGPUValidationError.h"
 #include "RequestAnimationFrameCallback.h"
+#include "SecurityOrigin.h"
 #include "WebGPUXRBinding.h"
 #include "XRGPUBinding.h"
 #include <wtf/TZoneMallocInlines.h>
@@ -394,6 +395,15 @@ private:
 
 ExceptionOr<Ref<GPUExternalTexture>> GPUDevice::importExternalTexture(GPUExternalTextureDescriptor&& externalTextureDescriptor)
 {
+#if ENABLE(VIDEO)
+    auto checkVideoElementOriginTaint = [this](const HTMLVideoElement& videoElement) -> std::optional<Exception> {
+        RefPtr context = scriptExecutionContext();
+        if (RefPtr securityOrigin = context ? context->securityOrigin() : nullptr; securityOrigin && videoElement.taintsOrigin(*securityOrigin))
+            return Exception { ExceptionCode::SecurityError, "GPUDevice.importExternalTexture: Cross origin external videos are not allowed in WebGPU"_s };
+        return std::nullopt;
+    };
+#endif
+
 #if ENABLE(VIDEO) && PLATFORM(COCOA)
     if (RefPtr externalTexture = externalTextureForDescriptor(externalTextureDescriptor)) {
         externalTexture->undestroy();
@@ -402,6 +412,9 @@ ExceptionOr<Ref<GPUExternalTexture>> GPUDevice::importExternalTexture(GPUExterna
 #else
         Ref videoElementRef = externalTextureDescriptor.source;
 #endif
+        if (auto exception = checkVideoElementOriginTaint(videoElementRef))
+            return WTF::move(*exception);
+
         m_videoElementToExternalTextureMap.remove(videoElementRef);
         if (auto optionalMediaIdentifier = externalTextureDescriptor.mediaIdentifier()) {
             m_backing->updateExternalTexture(externalTexture->backing(), *optionalMediaIdentifier);
@@ -425,6 +438,9 @@ ExceptionOr<Ref<GPUExternalTexture>> GPUDevice::importExternalTexture(GPUExterna
 #else
     Ref videoElementRef = externalTextureDescriptor.source;
 #endif
+    if (auto exception = checkVideoElementOriginTaint(videoElementRef))
+        return WTF::move(*exception);
+
     WeakPtr videoElementPtr = videoElementRef.ptr();
     m_videoElementToExternalTextureMap.set(videoElementRef, externalTexture.get());
     m_previouslyImportedExternalTexture.first = videoElementRef.ptr();

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2026-43700
Bugzilla 315368
Component WebCore
Bug Class CrossOrigin
Severity medium
Commit 67b563b85f480c70…
Advisory Apple Advisory

Terminology

  • importExternalTexture — A WebGPU API ingesting HTMLVideoElement frames as a texture that shaders can sample and read back.
  • Origin taint / taintsOrigin — Cross-origin non-CORS media is tainted; its pixels must not be readable, as canvas enforces.
  • External-texture reuse path — A Cocoa fast path that undestroys/reuses a previously-created external texture; it also needs the taint check.

Root Cause Analysis

This closes remaining cross-origin video paths in WebGPU’s importExternalTexture that were not covered by the origin-taint check, defeating the Same-Origin Policy for video pixels. GPUDevice::importExternalTexture ingests an HTMLVideoElement’s frames as a WebGPU external texture that a shader can sample and read back. To respect the SOP (as canvas does by tainting), an origin-taint check must reject a cross-origin, non-CORS video before it enters the pipeline. Pre-patch, importExternalTexture had paths that imported the video WITHOUT that check — notably the Cocoa fast path that reuses/undestroys a previously-created external texture (externalTextureForDescriptor) and the main import path. The fix adds a checkVideoElementOriginTaint lambda that fetches the caller’s security origin from scriptExecutionContext()->securityOrigin() and, if videoElement.taintsOrigin(securityOrigin), returns a SecurityError (‘Cross origin external videos are not allowed in WebGPU’); it invokes this check on BOTH the reused-external-texture path and the main path before importing. The restored invariant is that a cross-origin tainted video cannot enter (or be reused in) a WebGPU external texture through any importExternalTexture path, so its pixels cannot be sampled and exfiltrated. This is an information-disclosure fix, not memory corruption.

Attack Path

1. Load a cross-origin video

Load a cross-origin video without CORS so it is origin-tainted.

2. Import it as an external texture

Call GPUDevice.importExternalTexture on the tainted video, hitting a path that lacked the taint check (e.g. the reused-texture fast path).

3. Sample and read back

Sample the external texture in a shader and read the result into a GPU buffer readable by script.

4. Exfiltrate cross-origin pixels

Recover the cross-origin video’s pixels, defeating the Same-Origin Policy.

Impact Assessment

A cross-origin information disclosure in the WebContent process defeating the Same-Origin Policy for video: uncovered importExternalTexture paths let a page read pixels of a cross-origin video. No memory corruption; the risk is exfiltration of cross-origin visual content (e.g. authenticated video), reliable and stealthy.

Audit Directions

All media-ingestion paths

Audit every importExternalTexture / external-texture reuse and update path (and WebGL video uploads) for a consistent taintsOrigin check.

Origin retrieval

Confirm each path obtains the caller’s security origin (scriptExecutionContext()->securityOrigin()) before importing media.

Changed Functions

Function File Change Note
GPUDevice::importExternalTexture Source/WebCore/Modules/WebGPU/GPUDevice.cpp modified Adds checkVideoElementOriginTaint (using scriptExecutionContext()->securityOrigin() and videoElement.taintsOrigin) and applies it on both the reused/undestroyed external-texture path and the main import path, rejecting cross-origin tainted video with a SecurityError.

Files Changed

Source Files

  • Source/WebCore/Modules/WebGPU/GPUDevice.cpp

Test Files

  • LayoutTests/fast/webgpu/regression/repro_315368-expected.txt
  • LayoutTests/fast/webgpu/regression/repro_315368.html
  • LayoutTests/fast/webgpu/regression/repro_315368b-expected.txt
  • LayoutTests/fast/webgpu/regression/repro_315368b.html

Regression Test

The fix ships this proof-of-concept / regression test:

diff --git a/LayoutTests/fast/webgpu/regression/repro_315368-expected.txt b/LayoutTests/fast/webgpu/regression/repro_315368-expected.txt
new file mode 100644
index 000000000000..f3f8a6a97a34
--- /dev/null
+++ b/LayoutTests/fast/webgpu/regression/repro_315368-expected.txt
@@ -0,0 +1,2 @@
+CONSOLE MESSAGE: Pass
+
diff --git a/LayoutTests/fast/webgpu/regression/repro_315368.html b/LayoutTests/fast/webgpu/regression/repro_315368.html
new file mode 100644
index 000000000000..cecbedb144ce
--- /dev/null
+++ b/LayoutTests/fast/webgpu/regression/repro_315368.html
@@ -0,0 +1,29 @@
+<script>
+  globalThis.testRunner?.waitUntilDone();
+  const log = console.debug;
+
+  const veryBrightVideo = `data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAAvG1kYXQAAAAfTgEFGkdWStxcTEM/lO/FETzRQ6gD7gAA7gIAA3EYgAAAAEgoAa8iNjAkszOL+e58c//cEe//0TT//scp1n/381P/RWP/zOW4QtxorfVogeh8nQDbQAAAAwAQMCcWUTAAAAMAAAMAAAMA84AAAAAVAgHQAyu+KT35E7gAADFgAAADABLQAAAAEgIB4AiS76MTkNbgAAF3AAAPSAAAABICAeAEn8+hBOTXYAADUgAAHRAAAAPibW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAAKcAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAw10cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAAKcAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAABAAAAAQAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAACnAAAAAAABAAAAAAKFbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAABdwAAAD6BVxAAAAAAAMWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABDb3JlIE1lZGlhIFZpZGVvAAAAAixtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAHsc3RibAAAARxzdHNkAAAAAAAAAAEAAAEMaHZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAQABAASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAHVodmNDAQIgAAAAsAAAAAAAPPAA/P36+gAACwOgAAEAGEABDAH//wIgAAADALAAAAMAAAMAPBXAkKEAAQAmQgEBAiAAAAMAsAAAAwAAAwA8oBQgQcCTDLYgV7kWVYC1CRAJAICiAAEACUQBwChkuNBTJAAAAApmaWVsAQAAAAATY29scm5jbHgACQAQAAkAAAAAEHBhc3AAAAABAAAAAQAAABRidHJ0AAAAAAAALPwAACz8AAAAKHN0dHMAAAAAAAAAAwAAAAIAAAPoAAAAAQAAAAEAAAABAAAD6AAAABRzdHNzAAAAAAAAAAEAAAABAAAAEHNkdHAAAAAAIBAQGAAAAChjdHRzAAAAAAAAAAMAAAABAAAAAAAAAAEAAAfQAAAAAgAAAAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAQAAAABAAAAJHN0c3oAAAAAAAAAAAAAAAQAAABvAAAAGQAAABYAAAAWAAAAFHN0Y28AAAAAAAAAAQAAACwAAABhdWR0YQAAAFltZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAACxpbHN0AAAAJKl0b28AAAAcZGF0YQAAAAEAAAAATGF2ZjYwLjMuMTAw`;
+
+  // Tests the new texture path in importExternalTexture: first import of a same-origin video
+  // element exercises the code path that creates a new GPUExternalTexture (bug 315368).
+  onload = async () => {
+    let adapter = await navigator.gpu.requestAdapter({});
+    let device = await adapter.requestDevice({});
+
+    let video = document.createElement('video');
+    video.src = veryBrightVideo;
+    video.muted = true;
+    await new Promise(resolve => { video.onloadeddata = () => { video.play(); resolve(); }; });
+
+    video.requestVideoFrameCallback(() => {
+      try {
+        device.importExternalTexture({source: video});
+        log('Pass');
+      } catch (e) {
+        log('FAIL: ' + e.message);
+      }
+      globalThis.testRunner?.dumpAsText();
+      globalThis.testRunner?.notifyDone();
+    });
+  };
+</script>
diff --git a/LayoutTests/fast/webgpu/regression/repro_315368b-expected.txt b/LayoutTests/fast/webgpu/regression/repro_315368b-expected.txt
new file mode 100644
index 000000000000..f3f8a6a97a34
--- /dev/null
+++ b/LayoutTests/fast/webgpu/regression/repro_315368b-expected.txt
@@ -0,0 +1,2 @@
+CONSOLE MESSAGE: Pass
+
diff --git a/LayoutTests/fast/webgpu/regression/repro_315368b.html b/LayoutTests/fast/webgpu/regression/repro_315368b.html
new file mode 100644
index 000000000000..b5efaa958908
--- /dev/null
+++ b/LayoutTests/fast/webgpu/regression/repro_315368b.html
@@ -0,0 +1,31 @@
+<script>
+  globalThis.testRunner?.waitUntilDone();
+  const log = console.debug;
+
+  const veryBrightVideo = `data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAAvG1kYXQAAAAfTgEFGkdWStxcTEM/lO/FETzRQ6gD7gAA7gIAA3EYgAAAAEgoAa8iNjAkszOL+e58c//cEe//0TT//scp1n/381P/RWP/zOW4QtxorfVogeh8nQDbQAAAAwAQMCcWUTAAAAMAAAMAAAMA84AAAAAVAgHQAyu+KT35E7gAADFgAAADABLQAAAAEgIB4AiS76MTkNbgAAF3AAAPSAAAABICAeAEn8+hBOTXYAADUgAAHRAAAAPibW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAAKcAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAw10cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAAKcAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAABAAAAAQAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAACnAAAAAAABAAAAAAKFbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAABdwAAAD6BVxAAAAAAAMWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABDb3JlIE1lZGlhIFZpZGVvAAAAAixtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAHsc3RibAAAARxzdHNkAAAAAAAAAAEAAAEMaHZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAQABAASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAHVodmNDAQIgAAAAsAAAAAAAPPAA/P36+gAACwOgAAEAGEABDAH//wIgAAADALAAAAMAAAMAPBXAkKEAAQAmQgEBAiAAAAMAsAAAAwAAAwA8oBQgQcCTDLYgV7kWVYC1CRAJAICiAAEACUQBwChkuNBTJAAAAApmaWVsAQAAAAATY29scm5jbHgACQAQAAkAAAAAEHBhc3AAAAABAAAAAQAAABRidHJ0AAAAAAAALPwAACz8AAAAKHN0dHMAAAAAAAAAAwAAAAIAAAPoAAAAAQAAAAEAAAABAAAD6AAAABRzdHNzAAAAAAAAAAEAAAABAAAAEHNkdHAAAAAAIBAQGAAAAChjdHRzAAAAAAAAAAMAAAABAAAAAAAAAAEAAAfQAAAAAgAAAAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAQAAAABAAAAJHN0c3oAAAAAAAAAAAAAAAQAAABvAAAAGQAAABYAAAAWAAAAFHN0Y28AAAAAAAAAAQAAACwAAABhdWR0YQAAAFltZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAACxpbHN0AAAAJKl0b28AAAAcZGF0YQAAAAEAAAAATGF2ZjYwLjMuMTAw`;
+
+  // Tests the cached texture path in importExternalTexture: a second synchronous import of the
+  // same same-origin video element exercises the early-return path that reuses a cached
+  // GPUExternalTexture (bug 315368, PLATFORM(COCOA) only).
+  onload = async () => {
+    let adapter = await navigator.gpu.requestAdapter({});
+    let device = await adapter.requestDevice({});
+
+    let video = document.createElement('video');
+    video.src = veryBrightVideo;
+    video.muted = true;
+    await new Promise(resolve => { video.onloadeddata = () => { video.play(); resolve(); }; });
+
+    video.requestVideoFrameCallback(() => {
+      try {
+        let et1 = device.importExternalTexture({source: video}); // new texture path
+        let et2 = device.importExternalTexture({source: video}); // cached texture path (Cocoa)
+        log('Pass');
+      } catch (e) {
+        log('FAIL: ' + e.message);
+      }
+      globalThis.testRunner?.dumpAsText();
+      globalThis.testRunner?.notifyDone();
+    });
+  };
+</script>