Released June 29, 2026

CVE-2026-43720

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

Overview

Impact:

Description: Processing maliciously crafted web content may lead to an unexpected Safari crash

Researchers: Josef Korbel, Gia Bui (@yabeow) from Calif.io

Key insight. getContext cached m_context’s nullness across a WebIDL dictionary conversion that can run script and re-entrantly set it, then created a second context and clobbered the first — re-checking m_context (and detach state) after every script-running step closes it.

Patch Details

diff --git a/LayoutTests/fast/canvas/canvas-getContext-reentrant-expected.txt b/LayoutTests/fast/canvas/canvas-getContext-reentrant-expected.txt
new file mode 100644
index 000000000000..d801ccb9fae7
--- /dev/null
+++ b/LayoutTests/fast/canvas/canvas-getContext-reentrant-expected.txt
@@ -0,0 +1,33 @@
+getContext("2d") with reentrant getContext("2d"): outer=CanvasRenderingContext2D inner=CanvasRenderingContext2D
+getContext("2d") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+getContext("2d") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+getContext("2d") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+getContext("bitmaprenderer") with reentrant getContext("2d"): outer=null inner=CanvasRenderingContext2D
+getContext("bitmaprenderer") with reentrant getContext("bitmaprenderer"): outer=ImageBitmapRenderingContext inner=ImageBitmapRenderingContext
+getContext("bitmaprenderer") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+getContext("bitmaprenderer") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+getContext("webgl") with reentrant getContext("2d"): outer=null inner=CanvasRenderingContext2D
+getContext("webgl") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+getContext("webgl") with reentrant getContext("webgl"): outer=WebGLRenderingContext inner=WebGLRenderingContext
+getContext("webgl") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+getContext("webgl2") with reentrant getContext("2d"): outer=null inner=CanvasRenderingContext2D
+getContext("webgl2") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+getContext("webgl2") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+getContext("webgl2") with reentrant getContext("webgl2"): outer=WebGL2RenderingContext inner=WebGL2RenderingContext
+OffscreenCanvas getContext("2d") with reentrant getContext("2d"): outer=OffscreenCanvasRenderingContext2D inner=OffscreenCanvasRenderingContext2D
+OffscreenCanvas getContext("2d") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+OffscreenCanvas getContext("2d") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+OffscreenCanvas getContext("2d") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+OffscreenCanvas getContext("bitmaprenderer") with reentrant getContext("2d"): outer=null inner=OffscreenCanvasRenderingContext2D
+OffscreenCanvas getContext("bitmaprenderer") with reentrant getContext("bitmaprenderer"): outer=ImageBitmapRenderingContext inner=ImageBitmapRenderingContext
+OffscreenCanvas getContext("bitmaprenderer") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+OffscreenCanvas getContext("bitmaprenderer") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+OffscreenCanvas getContext("webgl") with reentrant getContext("2d"): outer=null inner=OffscreenCanvasRenderingContext2D
+OffscreenCanvas getContext("webgl") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+OffscreenCanvas getContext("webgl") with reentrant getContext("webgl"): outer=WebGLRenderingContext inner=WebGLRenderingContext
+OffscreenCanvas getContext("webgl") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+OffscreenCanvas getContext("webgl2") with reentrant getContext("2d"): outer=null inner=OffscreenCanvasRenderingContext2D
+OffscreenCanvas getContext("webgl2") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+OffscreenCanvas getContext("webgl2") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+OffscreenCanvas getContext("webgl2") with reentrant getContext("webgl2"): outer=WebGL2RenderingContext inner=WebGL2RenderingContext
+PASS if no crash.
diff --git a/LayoutTests/fast/canvas/canvas-getContext-reentrant.html b/LayoutTests/fast/canvas/canvas-getContext-reentrant.html
new file mode 100644
index 000000000000..e46e9d41f37d
--- /dev/null
+++ b/LayoutTests/fast/canvas/canvas-getContext-reentrant.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<script>
+if (window.testRunner)
+    testRunner.dumpAsText();
+
+function gc() {
+    if (window.GCController)
+        return GCController.collect();
+    for (let i = 0; i < 10000; i++)
+        new String("abc");
+}
+
+function reenter(outer, inner) {
+    let canvas = document.createElement("canvas");
+    let innerCtx;
+    let outerCtx = canvas.getContext(outer, {
+        get colorSpace() { innerCtx = canvas.getContext(inner); return "srgb"; },
+        get alpha() { innerCtx = canvas.getContext(inner); return true; },
+    });
+    document.write(`getContext("${outer}") with reentrant getContext("${inner}"): outer=${name(outerCtx)} inner=${name(innerCtx)}<br>`);
+}
+
+function reenterOffscreen(outer, inner) {
+    let canvas = new OffscreenCanvas(10, 10);
+    let innerCtx;
+    let outerCtx = canvas.getContext(outer, {
+        get colorSpace() { innerCtx = canvas.getContext(inner); return "srgb"; },
+        get alpha() { innerCtx = canvas.getContext(inner); return true; },
+    });
+    document.write(`OffscreenCanvas getContext("${outer}") with reentrant getContext("${inner}"): outer=${name(outerCtx)} inner=${name(innerCtx)}<br>`);
+}
+
+function name(ctx) {
+    return ctx ? ctx.constructor.name : "null";
+}
+
+for (let outer of ["2d", "bitmaprenderer", "webgl", "webgl2"])
+    for (let inner of ["2d", "bitmaprenderer", "webgl", "webgl2"])
+        reenter(outer, inner);
+
+for (let outer of ["2d", "bitmaprenderer", "webgl", "webgl2"])
+    for (let inner of ["2d", "bitmaprenderer", "webgl", "webgl2"])
+        reenterOffscreen(outer, inner);
+
+gc();
+document.write("PASS if no crash.");
+</script>
diff --git a/Source/WebCore/html/HTMLCanvasElement.cpp b/Source/WebCore/html/HTMLCanvasElement.cpp
index c280824242a4..2bc4b33962b8 100644
--- a/Source/WebCore/html/HTMLCanvasElement.cpp
+++ b/Source/WebCore/html/HTMLCanvasElement.cpp
@@ -226,7 +226,8 @@ void HTMLCanvasElement::setSizeForControllingContext(IntSize newSize)
 
 ExceptionOr<std::optional<RenderingContext>> HTMLCanvasElement::getContext(JSC::JSGlobalObject& state, const String& contextId, FixedVector<JSC::Strong<JSC::Unknown>>&& arguments)
 {
-    if (m_context) {
+    auto getExistingContext = [&]() -> ExceptionOr<std::optional<RenderingContext>> {
+        ASSERT(m_context);
         if (m_context->isPlaceholder())
             return Exception { ExceptionCode::InvalidStateError };
 
@@ -263,7 +264,10 @@ ExceptionOr<std::optional<RenderingContext>> HTMLCanvasElement::getContext(JSC::
 
         ASSERT_NOT_REACHED();
         return std::optional<RenderingContext> { std::nullopt };
-    }
+    };
+
+    if (m_context)
+        return getExistingContext();
 
     if (is2dType(contextId)) {
         Ref vm = state.vm();
@@ -273,6 +277,10 @@ ExceptionOr<std::optional<RenderingContext>> HTMLCanvasElement::getContext(JSC::
         if (settings.hasException(scope)) [[unlikely]]
             return Exception { ExceptionCode::ExistingExceptionError };
 
+        // Dictionary conversion may run script, which may have set m_context via a re-entrant getContext().
+        if (m_context) [[unlikely]]
+            return getExistingContext();
+
         RefPtr context = createContext2d(contextId, settings.releaseReturnValue());
         if (!context)
             return std::optional<RenderingContext> { std::nullopt };
@@ -287,6 +295,10 @@ ExceptionOr<std::optional<RenderingContext>> HTMLCanvasElement::getContext(JSC::
         if (settings.hasException(scope)) [[unlikely]]
             return Exception { ExceptionCode::ExistingExceptionError };
 
+        // Dictionary conversion may run script, which may have set m_context via a re-entrant getContext().
+        if (m_context) [[unlikely]]
+            return getExistingContext();
+
         RefPtr context = createContextBitmapRenderer(contextId, settings.releaseReturnValue());
         if (!context)
             return std::optional<RenderingContext> { std::nullopt };
@@ -302,6 +314,10 @@ ExceptionOr<std::optional<RenderingContext>> HTMLCanvasElement::getContext(JSC::
         if (attributes.hasException(scope)) [[unlikely]]
             return Exception { ExceptionCode::ExistingExceptionError };
 
+        // Dictionary conversion may run script, which may have set m_context via a re-entrant getContext().
+        if (m_context) [[unlikely]]
+            return getExistingContext();
+
         RefPtr context = createContextWebGL(toWebGLVersion(contextId), attributes.releaseReturnValue());
         if (!context)
             return std::optional<RenderingContext> { std::nullopt };
@@ -355,7 +371,7 @@ bool HTMLCanvasElement::is2dType(const String& type)
 CanvasRenderingContext2D* HTMLCanvasElement::createContext2d(const String& type, CanvasRenderingContext2DSettings&& settings)
 {
     ASSERT_UNUSED(HTMLCanvasElement::is2dType(type), type);
-    ASSERT(!m_context);
+    ASSERT_WITH_SECURITY_IMPLICATION(!m_context);
 
     m_context = CanvasRenderingContext2D::create(*this, WTF::move(settings), document().inQuirksMode());
     if (!m_context)
@@ -403,7 +419,7 @@ WebGLVersion HTMLCanvasElement::toWebGLVersion(const String& type)
 
 WebGLRenderingContextBase* HTMLCanvasElement::createContextWebGL(WebGLVersion type, WebGLContextAttributes&& attrs)
 {
-    ASSERT(!m_context);
+    ASSERT_WITH_SECURITY_IMPLICATION(!m_context);
     if (!document().settings().webGLEnabled())
         return nullptr;
 
@@ -459,7 +475,7 @@ bool HTMLCanvasElement::isBitmapRendererType(const String& type)
 ImageBitmapRenderingContext* HTMLCanvasElement::createContextBitmapRenderer(const String& type, ImageBitmapRenderingContextSettings&& settings)
 {
     ASSERT_UNUSED(type, HTMLCanvasElement::isBitmapRendererType(type));
-    ASSERT(!m_context);
+    ASSERT_WITH_SECURITY_IMPLICATION(!m_context);
 
     auto context = ImageBitmapRenderingContext::create(*this, WTF::move(settings));
     WeakPtr weakContext = *context;
@@ -491,7 +507,7 @@ bool HTMLCanvasElement::isWebGPUType(const String& type)
 GPUCanvasContext* HTMLCanvasElement::createContextWebGPU(const String& type, GPU* gpu)
 {
     ASSERT_UNUSED(type, HTMLCanvasElement::isWebGPUType(type));
-    ASSERT(!m_context);
+    ASSERT_WITH_SECURITY_IMPLICATION(!m_context);
 
     if (!document().settings().webGPUEnabled() || !gpu)
         return nullptr;
diff --git a/Source/WebCore/html/OffscreenCanvas.cpp b/Source/WebCore/html/OffscreenCanvas.cpp
index f67caa13c15d..5adbd49dc2d5 100644
--- a/Source/WebCore/html/OffscreenCanvas.cpp
+++ b/Source/WebCore/html/OffscreenCanvas.cpp
@@ -183,6 +183,13 @@ ExceptionOr<std::optional<OffscreenRenderingContext>> OffscreenCanvas::getContex
     if (m_detached)
         return Exception { ExceptionCode::InvalidStateError };
 
+    // Dictionary conversion may run script, which may have detached this canvas.
+    auto shouldThrowForDetachedCanvas = [&]() -> ExceptionOr<void> {
+        if (m_detached) [[unlikely]]
+            return Exception { ExceptionCode::InvalidStateError };
+        return { };

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2026-43720
Bugzilla 313175
Component WebCore
Bug Class UAF
Severity medium
Commit 040ef6e21ffac03b…
Advisory Apple Advisory

Terminology

  • Canvas rendering context (m_context) — A canvas lazily creates and caches one rendering context (2d/webgl/bitmap/webgpu) in m_context on the first getContext call.
  • WebIDL dictionary conversion — Converting a JS argument to a WebIDL dictionary reads its properties, which can invoke author-defined getters and thus run script mid-call.
  • Re-entrancy — Script run during argument conversion can call back into getContext on the same canvas before the outer call finishes.
  • ASSERT_WITH_SECURITY_IMPLICATION — A stronger assertion marking an invariant whose violation is security-relevant, enabled in security test/debug builds.

Root Cause Analysis

HTMLCanvasElement::getContext (and OffscreenCanvas::getContext) convert the JS arguments into a WebIDL settings/attributes dictionary before creating the rendering context, and that dictionary conversion can run author script through getters/valueOf on the supplied object. The pre-patch code checked m_context once at entry, performed the conversion, then unconditionally created a new context and assigned m_context = createContext...(). If script executed during the conversion re-entrantly called getContext() and set m_context, the outer call would overwrite m_context with a second, freshly-created context — dropping the context the inner call had already created (and possibly already returned to script), leaving dangling references to a now-freed context object: a use-after-free / memory corruption. The fix factors the existing-context path into a getExistingContext lambda and, after every dictionary conversion that can run script, re-checks if (m_context) return getExistingContext(); and guards creation with if (!m_context), so a context established by re-entrancy is honored rather than clobbered. For OffscreenCanvas, the conversion can additionally detach the canvas, so a shouldThrowForDetachedCanvas() re-check is added after each conversion and creation is likewise guarded by if (!m_context). The ASSERT(!m_context) invariants in createContext2d/createContextWebGL/createContextBitmapRenderer/createContextWebGPU were strengthened to ASSERT_WITH_SECURITY_IMPLICATION(!m_context) to catch a double-create. The restored invariant is that m_context is re-validated after any step that may run script, and a canvas creates exactly one rendering context exactly once.

Attack Path

1. Call getContext with a script hook

Call canvas.getContext(type, settings) where settings is an object whose getter (or a coercible member) runs attacker script during WebIDL dictionary conversion.

2. Re-enter getContext

Inside the getter, call canvas.getContext(type, …) again; the inner call creates and assigns m_context and returns a context object to script.

3. Clobber the context

The outer call resumes after conversion and, pre-patch, creates a second context and overwrites m_context, dropping the inner context.

4. Free and dangle

The replaced context is destroyed (helped by gc()) while script still references it — use-after-free / memory corruption in WebContent.

Impact Assessment

A script-controlled re-entrancy UAF / double-create of a canvas rendering context. The attacker fully controls the re-entrant call and its timing via the settings-object getter, but the object and window are constrained, so the realistic outcome is a controlled crash with potential for memory corruption after heap grooming. Confined to the WebContent process (GPU-backed contexts are still brokered from WebContent). Rated medium (CVSS 6.5).

Audit Directions

Other getContext-style factories

grep for a member read, then releaseReturnValue()/dictionary conversion, then an unconditional member assignment without re-checking the member — the exact clobber shape fixed here.

Assumptions across argument coercion

Audit DOM methods that assume a member stays null/unchanged across convert<IDLDictionary>/argument coercion, since coercion can run script and re-enter.

OffscreenCanvas detach/transfer

Check transferToImageBitmap and other OffscreenCanvas paths for state (m_detached/m_context) that can change via script during conversion.

Changed Functions

Function File Change Note
HTMLCanvasElement::getContext Source/WebCore/html/HTMLCanvasElement.cpp modified Extracts a getExistingContext() lambda and, after each 2d/bitmap/webgl dictionary conversion, re-checks if (m_context) return getExistingContext(); so a re-entrantly-created context is not overwritten.
HTMLCanvasElement::createContext2d / createContextWebGL / createContextBitmapRenderer / createContextWebGPU Source/WebCore/html/HTMLCanvasElement.cpp modified ASSERT(!m_context) upgraded to ASSERT_WITH_SECURITY_IMPLICATION(!m_context) to catch a double-create as a security-relevant invariant.
OffscreenCanvas::getContext Source/WebCore/html/OffscreenCanvas.cpp modified Adds shouldThrowForDetachedCanvas() re-checks after conversions (script may detach the canvas) and guards each context creation with if (!m_context) against re-entrant creation.

Files Changed

Source Files

  • Source/WebCore/html/HTMLCanvasElement.cpp
  • Source/WebCore/html/OffscreenCanvas.cpp

Test Files

  • LayoutTests/fast/canvas/canvas-getContext-reentrant-expected.txt
  • LayoutTests/fast/canvas/canvas-getContext-reentrant.html

Regression Test

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

diff --git a/LayoutTests/fast/canvas/canvas-getContext-reentrant-expected.txt b/LayoutTests/fast/canvas/canvas-getContext-reentrant-expected.txt
new file mode 100644
index 000000000000..d801ccb9fae7
--- /dev/null
+++ b/LayoutTests/fast/canvas/canvas-getContext-reentrant-expected.txt
@@ -0,0 +1,33 @@
+getContext("2d") with reentrant getContext("2d"): outer=CanvasRenderingContext2D inner=CanvasRenderingContext2D
+getContext("2d") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+getContext("2d") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+getContext("2d") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+getContext("bitmaprenderer") with reentrant getContext("2d"): outer=null inner=CanvasRenderingContext2D
+getContext("bitmaprenderer") with reentrant getContext("bitmaprenderer"): outer=ImageBitmapRenderingContext inner=ImageBitmapRenderingContext
+getContext("bitmaprenderer") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+getContext("bitmaprenderer") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+getContext("webgl") with reentrant getContext("2d"): outer=null inner=CanvasRenderingContext2D
+getContext("webgl") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+getContext("webgl") with reentrant getContext("webgl"): outer=WebGLRenderingContext inner=WebGLRenderingContext
+getContext("webgl") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+getContext("webgl2") with reentrant getContext("2d"): outer=null inner=CanvasRenderingContext2D
+getContext("webgl2") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+getContext("webgl2") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+getContext("webgl2") with reentrant getContext("webgl2"): outer=WebGL2RenderingContext inner=WebGL2RenderingContext
+OffscreenCanvas getContext("2d") with reentrant getContext("2d"): outer=OffscreenCanvasRenderingContext2D inner=OffscreenCanvasRenderingContext2D
+OffscreenCanvas getContext("2d") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+OffscreenCanvas getContext("2d") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+OffscreenCanvas getContext("2d") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+OffscreenCanvas getContext("bitmaprenderer") with reentrant getContext("2d"): outer=null inner=OffscreenCanvasRenderingContext2D
+OffscreenCanvas getContext("bitmaprenderer") with reentrant getContext("bitmaprenderer"): outer=ImageBitmapRenderingContext inner=ImageBitmapRenderingContext
+OffscreenCanvas getContext("bitmaprenderer") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+OffscreenCanvas getContext("bitmaprenderer") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+OffscreenCanvas getContext("webgl") with reentrant getContext("2d"): outer=null inner=OffscreenCanvasRenderingContext2D
+OffscreenCanvas getContext("webgl") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+OffscreenCanvas getContext("webgl") with reentrant getContext("webgl"): outer=WebGLRenderingContext inner=WebGLRenderingContext
+OffscreenCanvas getContext("webgl") with reentrant getContext("webgl2"): outer=null inner=WebGL2RenderingContext
+OffscreenCanvas getContext("webgl2") with reentrant getContext("2d"): outer=null inner=OffscreenCanvasRenderingContext2D
+OffscreenCanvas getContext("webgl2") with reentrant getContext("bitmaprenderer"): outer=null inner=ImageBitmapRenderingContext
+OffscreenCanvas getContext("webgl2") with reentrant getContext("webgl"): outer=null inner=WebGLRenderingContext
+OffscreenCanvas getContext("webgl2") with reentrant getContext("webgl2"): outer=WebGL2RenderingContext inner=WebGL2RenderingContext
+PASS if no crash.
diff --git a/LayoutTests/fast/canvas/canvas-getContext-reentrant.html b/LayoutTests/fast/canvas/canvas-getContext-reentrant.html
new file mode 100644
index 000000000000..e46e9d41f37d
--- /dev/null
+++ b/LayoutTests/fast/canvas/canvas-getContext-reentrant.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<script>
+if (window.testRunner)
+    testRunner.dumpAsText();
+
+function gc() {
+    if (window.GCController)
+        return GCController.collect();
+    for (let i = 0; i < 10000; i++)
+        new String("abc");
+}
+
+function reenter(outer, inner) {
+    let canvas = document.createElement("canvas");
+    let innerCtx;
+    let outerCtx = canvas.getContext(outer, {
+        get colorSpace() { innerCtx = canvas.getContext(inner); return "srgb"; },
+        get alpha() { innerCtx = canvas.getContext(inner); return true; },
+    });
+    document.write(`getContext("${outer}") with reentrant getContext("${inner}"): outer=${name(outerCtx)} inner=${name(innerCtx)}<br>`);
+}
+
+function reenterOffscreen(outer, inner) {
+    let canvas = new OffscreenCanvas(10, 10);
+    let innerCtx;
+    let outerCtx = canvas.getContext(outer, {
+        get colorSpace() { innerCtx = canvas.getContext(inner); return "srgb"; },
+        get alpha() { innerCtx = canvas.getContext(inner); return true; },
+    });
+    document.write(`OffscreenCanvas getContext("${outer}") with reentrant getContext("${inner}"): outer=${name(outerCtx)} inner=${name(innerCtx)}<br>`);
+}
+
+function name(ctx) {
+    return ctx ? ctx.constructor.name : "null";
+}
+
+for (let outer of ["2d", "bitmaprenderer", "webgl", "webgl2"])
+    for (let inner of ["2d", "bitmaprenderer", "webgl", "webgl2"])
+        reenter(outer, inner);
+
+for (let outer of ["2d", "bitmaprenderer", "webgl", "webgl2"])
+    for (let inner of ["2d", "bitmaprenderer", "webgl", "webgl2"])
+        reenterOffscreen(outer, inner);
+
+gc();
+document.write("PASS if no crash.");
+</script>