AutoTrader crashed while browsing search results
authordino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 31 May 2020 23:20:51 +0000 (23:20 +0000)
committerdino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 31 May 2020 23:20:51 +0000 (23:20 +0000)
https://bugs.webkit.org/show_bug.cgi?id=212461
rdar://60733185

Reviewed by Sam Weinig.

Source/WebCore:

On iOS, when using WebKit1 (UIWebView), CoreAnimation would
call WebGLLayer's display method from a thread that is not
the Web Thread. That method was performing some GL work using
ANGLE, causing a crash.

Since all the WebGLLayer's display method really needs to do
is swap buffers for compositing, the fix is to separate all
the GL operations into a method that can be called after
painting but before compositing. This should also have the added
benefit that by the time CoreAnimation comes to call display
on all the dirty layers, we will have already executed our
expensive GPU work. The total amount of work done on the GPU
is the same, but hopefully it is now all done in WebKit's
paint cycle, rather than when the Window Server is trying
to get CA to composite things.

Covered by a new API test: WebGLPrepareDisplayOnWebThread

* html/HTMLCanvasElement.h:
* html/HTMLCanvasElement.cpp:
(WebCore::HTMLCanvasElement::HTMLCanvasElement):
(WebCore::HTMLCanvasElement::~HTMLCanvasElement):
(WebCore::HTMLCanvasElement::didMoveToNewDocument):
(WebCore::HTMLCanvasElement::removedFromAncestor):
    Add or remove the document as a CanvasObserver.
(WebCore::HTMLCanvasElement::needsPreparationForDisplay):
    Signals whether this element is the type that needs preparation.
(WebCore::HTMLCanvasElement::prepareForDisplay):
    Tell the WebGLRenderingContext it must prepare.

* html/canvas/WebGLRenderingContextBase.h:
* html/canvas/WebGLRenderingContextBase.cpp:
(WebCore::WebGLRenderingContextBase::prepareForDisplay):
    The WebGLRenderingContext must forward the call
    to prepare down to the GraphicsContextGLOpenGL.

* platform/graphics/opengl/GraphicsContextGLOpenGL.h:
* platform/graphics/opengl/GraphicsContextGLOpenGL.cpp:
* platform/graphics/cocoa/GraphicsContextGLOpenGLCocoa.mm:
(WebCore::GraphicsContextGLOpenGL::prepareForDisplay):
    And the GraphicsContextGLOpenGL forwards the call
    into the WebGLLayer.

* platform/graphics/cocoa/WebGLLayer.h:
* platform/graphics/cocoa/WebGLLayer.mm:
(-[WebGLLayer prepareForDisplay]):
(-[WebGLLayer display]):
    Split the parts of the `display` method that deal
    with flushing the GL commands, preparing the framebuffer texture,
    and swapping the IOSurfaces into a new `prepareForDisplay`. This
    method is invoked at the end of the rendering/layout tasks, leaving
    the `display` method to only tell CoreAnimation about a new buffer
    to composite.

* dom/Document.cpp:
* dom/Document.h:
(WebCore::Document::prepareCanvasesForDisplayIfNeeded):
(WebCore::Document::canvasChanged):
(WebCore::Document::canvasDestroyed):
    Keep a set of HTMLCanvasElements that need to
    be prepared so we can tell them when they need to prepare.
    Do this by becoming a CanvasObserver, thus getting
    notified when a canvas has done something that
    would cause painting.

* page/Page.cpp:
(WebCore::Page::doAfterUpdateRendering):
    Add a new task that asks the Document to notify
    all relevant canvas objects that they should prepare
    for display.

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitLegacy/ios/WebGLPrepareDisplayOnWebThread.mm: Added.
(-[WebGLPrepareDisplayOnWebThreadDelegate webViewDidFinishLoad:]):
(-[WebGLPrepareDisplayOnWebThreadDelegate webView:shouldStartLoadWithRequest:navigationType:]):
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebKitLegacy/ios/webgl.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@262366 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/html/HTMLCanvasElement.cpp
Source/WebCore/html/HTMLCanvasElement.h
Source/WebCore/html/canvas/WebGLRenderingContextBase.cpp
Source/WebCore/html/canvas/WebGLRenderingContextBase.h
Source/WebCore/page/Page.cpp
Source/WebCore/platform/graphics/cocoa/GraphicsContextGLOpenGLCocoa.mm
Source/WebCore/platform/graphics/cocoa/WebGLLayer.h
Source/WebCore/platform/graphics/cocoa/WebGLLayer.mm
Source/WebCore/platform/graphics/opengl/GraphicsContextGLOpenGL.cpp
Source/WebCore/platform/graphics/opengl/GraphicsContextGLOpenGL.h
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitLegacy/ios/WebGLPrepareDisplayOnWebThread.mm [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKitLegacy/ios/webgl.html [new file with mode: 0644]

index 87b997f..6a02a18 100644 (file)
@@ -1,3 +1,82 @@
+2020-05-31  Dean Jackson  <dino@apple.com>
+
+        AutoTrader crashed while browsing search results
+        https://bugs.webkit.org/show_bug.cgi?id=212461
+        rdar://60733185
+
+        Reviewed by Sam Weinig.
+
+        On iOS, when using WebKit1 (UIWebView), CoreAnimation would
+        call WebGLLayer's display method from a thread that is not
+        the Web Thread. That method was performing some GL work using
+        ANGLE, causing a crash.
+
+        Since all the WebGLLayer's display method really needs to do
+        is swap buffers for compositing, the fix is to separate all
+        the GL operations into a method that can be called after
+        painting but before compositing. This should also have the added
+        benefit that by the time CoreAnimation comes to call display
+        on all the dirty layers, we will have already executed our
+        expensive GPU work. The total amount of work done on the GPU
+        is the same, but hopefully it is now all done in WebKit's
+        paint cycle, rather than when the Window Server is trying
+        to get CA to composite things.
+
+        Covered by a new API test: WebGLPrepareDisplayOnWebThread
+
+        * html/HTMLCanvasElement.h:
+        * html/HTMLCanvasElement.cpp:
+        (WebCore::HTMLCanvasElement::HTMLCanvasElement):
+        (WebCore::HTMLCanvasElement::~HTMLCanvasElement):
+        (WebCore::HTMLCanvasElement::didMoveToNewDocument):
+        (WebCore::HTMLCanvasElement::removedFromAncestor):
+            Add or remove the document as a CanvasObserver.
+        (WebCore::HTMLCanvasElement::needsPreparationForDisplay):
+            Signals whether this element is the type that needs preparation.
+        (WebCore::HTMLCanvasElement::prepareForDisplay):
+            Tell the WebGLRenderingContext it must prepare.
+
+        * html/canvas/WebGLRenderingContextBase.h:
+        * html/canvas/WebGLRenderingContextBase.cpp:
+        (WebCore::WebGLRenderingContextBase::prepareForDisplay):
+            The WebGLRenderingContext must forward the call
+            to prepare down to the GraphicsContextGLOpenGL.
+
+        * platform/graphics/opengl/GraphicsContextGLOpenGL.h:
+        * platform/graphics/opengl/GraphicsContextGLOpenGL.cpp:
+        * platform/graphics/cocoa/GraphicsContextGLOpenGLCocoa.mm:
+        (WebCore::GraphicsContextGLOpenGL::prepareForDisplay):
+            And the GraphicsContextGLOpenGL forwards the call
+            into the WebGLLayer.
+
+        * platform/graphics/cocoa/WebGLLayer.h:
+        * platform/graphics/cocoa/WebGLLayer.mm:
+        (-[WebGLLayer prepareForDisplay]):
+        (-[WebGLLayer display]):
+            Split the parts of the `display` method that deal
+            with flushing the GL commands, preparing the framebuffer texture,
+            and swapping the IOSurfaces into a new `prepareForDisplay`. This
+            method is invoked at the end of the rendering/layout tasks, leaving
+            the `display` method to only tell CoreAnimation about a new buffer
+            to composite.
+
+        * dom/Document.cpp:
+        * dom/Document.h:
+        (WebCore::Document::prepareCanvasesForDisplayIfNeeded):
+        (WebCore::Document::canvasChanged):
+        (WebCore::Document::canvasDestroyed):
+            Keep a set of HTMLCanvasElements that need to
+            be prepared so we can tell them when they need to prepare.
+            Do this by becoming a CanvasObserver, thus getting
+            notified when a canvas has done something that
+            would cause painting.
+
+        * page/Page.cpp:
+        (WebCore::Page::doAfterUpdateRendering):
+            Add a new task that asks the Document to notify
+            all relevant canvas objects that they should prepare
+            for display.
+
 2020-05-31  Jer Noble  <jer.noble@apple.com>
 
         [Cocoa] EME should return more helpful error code during key exchange
index cf6cae4..e311915 100644 (file)
@@ -8582,6 +8582,34 @@ LazyLoadImageObserver& Document::lazyLoadImageObserver()
     return *m_lazyLoadImageObserver;
 }
 
+void Document::prepareCanvasesForDisplayIfNeeded()
+{
+    // Some canvas contexts need to do work when rendering has finished but
+    // before their content is composited.
+    for (auto* canvas : m_canvasesNeedingDisplayPreparation) {
+        auto refCountedCanvas = makeRefPtr(canvas);
+        refCountedCanvas->prepareForDisplay();
+    }
+    m_canvasesNeedingDisplayPreparation.clear();
+}
+
+void Document::canvasChanged(CanvasBase& canvasBase, const FloatRect&)
+{
+    if (is<HTMLCanvasElement>(canvasBase)) {
+        auto* canvas = downcast<HTMLCanvasElement>(&canvasBase);
+        if (canvas->needsPreparationForDisplay())
+            m_canvasesNeedingDisplayPreparation.add(canvas);
+    }
+}
+
+void Document::canvasDestroyed(CanvasBase& canvasBase)
+{
+    if (is<HTMLCanvasElement>(canvasBase)) {
+        auto* canvas = downcast<HTMLCanvasElement>(&canvasBase);
+        m_canvasesNeedingDisplayPreparation.remove(canvas);
+    }
+}
+
 } // namespace WebCore
 
 #undef RELEASE_LOG_IF_ALLOWED
index f2629d2..b5c4260 100644 (file)
@@ -28,6 +28,7 @@
 #pragma once
 
 #include "CSSRegisteredCustomProperty.h"
+#include "CanvasBase.h"
 #include "Color.h"
 #include "ContainerNode.h"
 #include "DisabledAdaptations.h"
@@ -357,7 +358,8 @@ class Document
     , public FontSelectorClient
     , public FrameDestructionObserver
     , public Supplementable<Document>
-    , public Logger::Observer {
+    , public Logger::Observer
+    , public CanvasObserver {
     WTF_MAKE_ISO_ALLOCATED(Document);
 public:
     static Ref<Document> create(const URL&);
@@ -1593,6 +1595,11 @@ public:
     FrameSelection& selection() { return m_selection; }
     const FrameSelection& selection() const { return m_selection; }
 
+    void prepareCanvasesForDisplayIfNeeded();
+    void canvasChanged(CanvasBase&, const FloatRect&) final;
+    void canvasResized(CanvasBase&) final { };
+    void canvasDestroyed(CanvasBase&) final;
+
 protected:
     enum ConstructionFlags { Synthesized = 1, NonRenderedPlaceholder = 1 << 1 };
     Document(Frame*, const URL&, unsigned = DefaultDocumentClass, unsigned constructionFlags = 0);
@@ -1811,6 +1818,11 @@ private:
     std::unique_ptr<SVGDocumentExtensions> m_svgExtensions;
     HashSet<SVGUseElement*> m_svgUseElements;
 
+    // Collection of canvas objects that need to do work after they've
+    // rendered but before compositing, for the next frame. The set is
+    // cleared after they've been called.
+    HashSet<HTMLCanvasElement*> m_canvasesNeedingDisplayPreparation;
+
 #if ENABLE(DARK_MODE_CSS)
     OptionSet<ColorScheme> m_colorScheme;
     bool m_allowsColorSchemeTransformations { true };
index 0ad20b1..473fa95 100644 (file)
@@ -124,6 +124,7 @@ HTMLCanvasElement::HTMLCanvasElement(const QualifiedName& tagName, Document& doc
     , ActiveDOMObject(document)
 {
     ASSERT(hasTagName(canvasTag));
+    addObserver(document);
 }
 
 Ref<HTMLCanvasElement> HTMLCanvasElement::create(Document& document)
@@ -145,6 +146,7 @@ HTMLCanvasElement::~HTMLCanvasElement()
     // FIXME: This has to be called here because CSSCanvasValue::CanvasObserverProxy::canvasDestroyed()
     // downcasts the CanvasBase object to HTMLCanvasElement. That invokes virtual methods, which should be
     // avoided in destructors, but works as long as it's done before HTMLCanvasElement destructs completely.
+    // This will also cause the document to remove itself as an observer.
     notifyObserversCanvasDestroyed();
 
     m_context = nullptr; // Ensure this goes away before the ImageBuffer.
@@ -997,4 +999,33 @@ void HTMLCanvasElement::eventListenersDidChange()
 #endif
 }
 
+void HTMLCanvasElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
+{
+    removeObserver(oldDocument);
+    addObserver(newDocument);
+
+    HTMLElement::didMoveToNewDocument(oldDocument, newDocument);
+}
+
+void HTMLCanvasElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
+{
+    if (removalType.disconnectedFromDocument)
+        removeObserver(oldParentOfRemovedTree.document());
+
+    HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
+}
+
+bool HTMLCanvasElement::needsPreparationForDisplay()
+{
+    return is<WebGLRenderingContextBase>(m_context.get());
+}
+
+void HTMLCanvasElement::prepareForDisplay()
+{
+    ASSERT(needsPreparationForDisplay());
+
+    if (is<WebGLRenderingContextBase>(m_context.get()))
+        downcast<WebGLRenderingContextBase>(m_context.get())->prepareForDisplay();
+}
+
 }
index 50e7c2c..2105ce6 100644 (file)
@@ -129,6 +129,9 @@ public:
 
     WEBCORE_EXPORT static void setMaxPixelMemoryForTesting(size_t);
 
+    bool needsPreparationForDisplay();
+    void prepareForDisplay();
+
 private:
     HTMLCanvasElement(const QualifiedName&, Document&);
 
@@ -165,6 +168,9 @@ private:
 
     ScriptExecutionContext* canvasBaseScriptExecutionContext() const final { return HTMLElement::scriptExecutionContext(); }
 
+    void didMoveToNewDocument(Document& oldDocument, Document& newDocument) final;
+    void removedFromAncestor(RemovalType, ContainerNode& oldParentOfRemovedTree) final;
+
     FloatRect m_dirtyRect;
 
     bool m_ignoreReset { false };
index ca274de..eaebab4 100644 (file)
@@ -7526,6 +7526,13 @@ void WebGLRenderingContextBase::dispatchContextChangedNotification()
     queueTaskToDispatchEvent(*canvas, TaskSource::WebGL, WebGLContextEvent::create(eventNames().webglcontextchangedEvent, Event::CanBubble::No, Event::IsCancelable::Yes, emptyString()));
 }
 
+void WebGLRenderingContextBase::prepareForDisplay()
+{
+    if (!m_context)
+        return;
+
+    m_context->prepareForDisplay();
+}
 
 } // namespace WebCore
 
index 91cf0b9..1c8b7eb 100644 (file)
@@ -382,6 +382,8 @@ public:
     // Used for testing only, from Internals.
     WEBCORE_EXPORT void setFailNextGPUStatusCheck();
 
+    void prepareForDisplay();
+
     // GraphicsContextGL::Client
     void didComposite() override;
     void forceContextLost() override;
index df8e30e..2f0f549 100644 (file)
@@ -1463,6 +1463,10 @@ void Page::doAfterUpdateRendering()
     if (UNLIKELY(isMonitoringWheelEvents()))
         wheelEventTestMonitor()->checkShouldFireCallbacks();
 
+    forEachDocument([] (Document& document) {
+        document.prepareCanvasesForDisplayIfNeeded();
+    });
+
 #if ASSERT_ENABLED
     for (Frame* child = mainFrame().tree().firstRenderedChild(); child; child = child->tree().traverseNextRendered()) {
         auto* frameView = child->view();
index 641406d..6be0126 100644 (file)
@@ -867,6 +867,11 @@ void GraphicsContextGLOpenGL::screenDidChange(PlatformDisplayID displayID)
 }
 #endif // !PLATFORM(MAC)
 
+void GraphicsContextGLOpenGL::prepareForDisplay()
+{
+    [m_webGLLayer prepareForDisplay];
+}
+
 }
 
 #endif // ENABLE(GRAPHICS_CONTEXT_GL)
index 652ef54..5501566 100644 (file)
@@ -62,6 +62,7 @@ ALLOW_DEPRECATED_DECLARATIONS_BEGIN
     void* _sparePbuffer;
     void* _latchedPbuffer;
 #endif
+    BOOL _prepared;
 }
 
 @property (nonatomic) NakedPtr<WebCore::GraphicsContextGLOpenGL> context;
@@ -70,6 +71,8 @@ ALLOW_DEPRECATED_DECLARATIONS_BEGIN
 
 - (CGImageRef)copyImageSnapshotWithColorSpace:(CGColorSpaceRef)colorSpace;
 
+- (void)prepareForDisplay;
+
 #if USE(OPENGL) || USE(ANGLE)
 - (void)allocateIOSurfaceBackingStoreWithSize:(WebCore::IntSize)size usingAlpha:(BOOL)usingAlpha;
 - (void)bindFramebufferToNextAvailableSurface;
index 8f0245f..2868517 100644 (file)
@@ -163,21 +163,21 @@ static void freeData(void *, const void *data, size_t /* size */)
 #endif
 }
 
-- (void)display
+- (void)prepareForDisplay
 {
     if (!_context)
         return;
 
+    // To avoid running any OpenGL code in `display`, this method should be called
+    // at the end of the rendering task. We will flush all painting commands
+    // leaving the buffers ready to composite.
+
 #if USE(OPENGL)
     _context->prepareTexture();
     if (_drawingBuffer) {
         std::swap(_contentsBuffer, _drawingBuffer);
-        self.contents = _contentsBuffer->asLayerContents();
-        [self reloadValueForKeyPath:@"contents"];
         [self bindFramebufferToNextAvailableSurface];
     }
-#elif USE(OPENGL_ES)
-    _context->presentRenderbuffer();
 #elif USE(ANGLE)
     if (!_context->makeContextCurrent()) {
         // Context is likely being torn down.
@@ -198,18 +198,40 @@ static void freeData(void *, const void *data, size_t /* size */)
             }
             _latchedPbuffer = nullptr;
         }
+
         std::swap(_contentsBuffer, _drawingBuffer);
         std::swap(_contentsPbuffer, _drawingPbuffer);
+        [self bindFramebufferToNextAvailableSurface];
+    }
+#endif
+
+    _prepared = YES;
+}
+
+- (void)display
+{
+    if (!_context)
+        return;
+
+    // At this point we've painted into the _drawingBuffer and swapped it with the old _contentsBuffer,
+    // so all we need to do here is tickle the CALayer to let it know it has new contents.
+    // This avoids running any OpenGL code in this method.
+
+#if USE(OPENGL) || USE(ANGLE)
+    if (_contentsBuffer && _prepared) {
         self.contents = _contentsBuffer->asLayerContents();
         [self reloadValueForKeyPath:@"contents"];
-        [self bindFramebufferToNextAvailableSurface];
     }
+#elif USE(OPENGL_ES)
+    _context->presentRenderbuffer();
 #endif
 
     _context->markLayerComposited();
     auto layer = WebCore::PlatformCALayer::platformCALayerForLayer((__bridge void*)self);
     if (layer && layer->owner())
         layer->owner()->platformCALayerLayerDidDisplay(layer.get());
+
+    _prepared = NO;
 }
 
 #if USE(ANGLE)
index 8b9e9e5..33db98b 100644 (file)
@@ -782,6 +782,10 @@ void GraphicsContextGLOpenGL::setContextVisibility(bool)
 void GraphicsContextGLOpenGL::simulateContextChanged()
 {
 }
+
+void GraphicsContextGLOpenGL::prepareForDisplay()
+{
+}
 #endif
 
 } // namespace WebCore
index bb52eb3..49e1069 100644 (file)
@@ -661,6 +661,8 @@ public:
     void screenDidChange(PlatformDisplayID);
 #endif
 
+    void prepareForDisplay();
+
 private:
     GraphicsContextGLOpenGL(GraphicsContextGLAttributes, HostWindow*, Destination = Destination::Offscreen, GraphicsContextGLOpenGL* sharedContext = nullptr);
 
index fb38f00..bf50e68 100644 (file)
@@ -1,3 +1,18 @@
+2020-05-31  Dean Jackson  <dino@apple.com>
+
+        AutoTrader crashed while browsing search results
+        https://bugs.webkit.org/show_bug.cgi?id=212461
+        rdar://60733185
+
+        Reviewed by Sam Weinig.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitLegacy/ios/WebGLPrepareDisplayOnWebThread.mm: Added.
+        (-[WebGLPrepareDisplayOnWebThreadDelegate webViewDidFinishLoad:]):
+        (-[WebGLPrepareDisplayOnWebThreadDelegate webView:shouldStartLoadWithRequest:navigationType:]):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/Tests/WebKitLegacy/ios/webgl.html: Added.
+
 2020-05-31  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         Unreviewed. Fix GTK4 build with current GTK
index 511706c..b7d67b2 100644 (file)
                315118101DB1AE4000176304 /* ExtendedColorTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3151180F1DB1ADD500176304 /* ExtendedColorTests.cpp */; };
                31B76E4323298E2C007FED2C /* SystemPreview.mm in Sources */ = {isa = PBXBuildFile; fileRef = 31B76E4223298E2B007FED2C /* SystemPreview.mm */; };
                31B76E4523299BDC007FED2C /* system-preview-trigger.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 31B76E4423299BA3007FED2C /* system-preview-trigger.html */; };
+               31E9BDA1247F4C62002E51A2 /* WebGLPrepareDisplayOnWebThread.mm in Sources */ = {isa = PBXBuildFile; fileRef = 31E9BDA0247F4C62002E51A2 /* WebGLPrepareDisplayOnWebThread.mm */; };
+               31E9BDA3247F5729002E51A2 /* webgl.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 31E9BDA2247F4DD0002E51A2 /* webgl.html */; };
                33BE5AF9137B5AAE00705813 /* MouseMoveAfterCrash_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33BE5AF8137B5AAE00705813 /* MouseMoveAfterCrash_Bundle.cpp */; };
                33DC8912141955FE00747EF7 /* simple-iframe.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 33DC890E1419539300747EF7 /* simple-iframe.html */; };
                33DC89141419579F00747EF7 /* LoadCanceledNoServerRedirectCallback_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 33DC89131419579F00747EF7 /* LoadCanceledNoServerRedirectCallback_Bundle.cpp */; };
                                57EDFC5C245A1A3F00959521 /* web-authentication-make-credential-la-no-mock.html in Copy Resources */,
                                5742178E2400D2DF002B303D /* web-authentication-make-credential-la.html in Copy Resources */,
                                1C2B81861C89259D00A5529F /* webfont.html in Copy Resources */,
+                               31E9BDA3247F5729002E51A2 /* webgl.html in Copy Resources */,
                                51714EB41CF8C78C004723C4 /* WebProcessKillIDBCleanup-1.html in Copy Resources */,
                                51714EB51CF8C78C004723C4 /* WebProcessKillIDBCleanup-2.html in Copy Resources */,
                                536770361CC81B6100D425B1 /* WebScriptObjectDescription.html in Copy Resources */,
                3151180F1DB1ADD500176304 /* ExtendedColorTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ExtendedColorTests.cpp; sourceTree = "<group>"; };
                31B76E4223298E2B007FED2C /* SystemPreview.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemPreview.mm; sourceTree = "<group>"; };
                31B76E4423299BA3007FED2C /* system-preview-trigger.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "system-preview-trigger.html"; sourceTree = "<group>"; };
+               31E9BDA0247F4C62002E51A2 /* WebGLPrepareDisplayOnWebThread.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = WebGLPrepareDisplayOnWebThread.mm; sourceTree = "<group>"; };
+               31E9BDA2247F4DD0002E51A2 /* webgl.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = webgl.html; sourceTree = "<group>"; };
                333B9CE11277F23100FEFCE3 /* PreventEmptyUserAgent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PreventEmptyUserAgent.cpp; sourceTree = "<group>"; };
                33BE5AF4137B5A6C00705813 /* MouseMoveAfterCrash.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MouseMoveAfterCrash.cpp; sourceTree = "<group>"; };
                33BE5AF8137B5AAE00705813 /* MouseMoveAfterCrash_Bundle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MouseMoveAfterCrash_Bundle.cpp; sourceTree = "<group>"; };
                                CDC0932A21C872C10030C4B0 /* ScrollingDoesNotPauseMedia.mm */,
                                F434CA1922E65BCA005DDB26 /* ScrollToRevealSelection.mm */,
                                0F4FFA9D1ED3AA8500F7111F /* SnapshotViaRenderInContext.mm */,
+                               31E9BDA0247F4C62002E51A2 /* WebGLPrepareDisplayOnWebThread.mm */,
                        );
                        path = ios;
                        sourceTree = "<group>";
                                CD758A6E20572D540071834A /* video-with-paused-audio-and-playing-muted.html */,
                                CDC8E48B1BC5C96200594FEC /* video-without-audio.html */,
                                CDC8E48C1BC5C96200594FEC /* video-without-audio.mp4 */,
+                               31E9BDA2247F4DD0002E51A2 /* webgl.html */,
                        );
                        name = Resources;
                        sourceTree = "<group>";
                                57A79857224AB34E00A7F6F1 /* WebCryptoMasterKey.mm in Sources */,
                                C1FF9EDB244644F000839AE4 /* WebFilter.mm in Sources */,
                                5C973F5C1F58EF8B00359C27 /* WebGLPolicy.mm in Sources */,
+                               31E9BDA1247F4C62002E51A2 /* WebGLPrepareDisplayOnWebThread.mm in Sources */,
                                7CCE7EAB1A411A2400447C4C /* WebKitAgnosticTest.mm in Sources */,
                                51714EB81CF8CA17004723C4 /* WebProcessKillIDBCleanup.mm in Sources */,
                                536770341CC8022800D425B1 /* WebScriptObjectDescription.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitLegacy/ios/WebGLPrepareDisplayOnWebThread.mm b/Tools/TestWebKitAPI/Tests/WebKitLegacy/ios/WebGLPrepareDisplayOnWebThread.mm
new file mode 100644 (file)
index 0000000..1c4dae6
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+
+#if HAVE(UIWEBVIEW)
+
+#import "PlatformUtilities.h"
+#import <JavaScriptCore/JSVirtualMachine.h>
+#import <JavaScriptCore/JSVirtualMachineInternal.h>
+#import <UIKit/UIKit.h>
+#import <WebCore/WebCoreThread.h>
+#import <stdlib.h>
+#import <wtf/RetainPtr.h>
+
+@interface WebGLPrepareDisplayOnWebThreadDelegate : NSObject <UIWebViewDelegate> {
+}
+@end
+
+static bool didFinishLoad = false;
+static bool didFinishPainting = false;
+static bool isReady = false;
+
+@implementation WebGLPrepareDisplayOnWebThreadDelegate
+
+IGNORE_WARNINGS_BEGIN("deprecated-implementations")
+- (void)webViewDidFinishLoad:(UIWebView *)webView
+{
+    didFinishLoad = true;
+}
+
+- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
+{
+    if ([request.URL.scheme isEqualToString:@"callback"] && [request.URL.resourceSpecifier isEqualToString:@"didFinishPainting"]) {
+        didFinishPainting = true;
+        return NO;
+    }
+
+    if ([request.URL.scheme isEqualToString:@"callback"] && [request.URL.resourceSpecifier isEqualToString:@"isReady"]) {
+        isReady = true;
+        return NO;
+    }
+
+    return YES;
+}
+IGNORE_WARNINGS_END
+
+@end
+
+namespace TestWebKitAPI {
+
+TEST(WebKitLegacy, WebGLPrepareDisplayOnWebThread)
+{
+    RetainPtr<UIWindow> uiWindow = adoptNS([[UIWindow alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
+    RetainPtr<UIWebView> uiWebView = adoptNS([[UIWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
+    [uiWindow addSubview:uiWebView.get()];
+
+    RetainPtr<WebGLPrepareDisplayOnWebThreadDelegate> uiDelegate = adoptNS([[WebGLPrepareDisplayOnWebThreadDelegate alloc] init]);
+    uiWebView.get().delegate = uiDelegate.get();
+
+    NSURL *url = [[NSBundle mainBundle] URLForResource:@"webgl" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+    NSLog(@"Loading %@", url);
+    [uiWebView loadRequest:[NSURLRequest requestWithURL:url]];
+
+    Util::run(&didFinishLoad);
+    Util::run(&isReady);
+
+    RetainPtr<JSContext> jsContext = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
+
+    EXPECT_TRUE(!!jsContext.get());
+
+    RetainPtr<JSVirtualMachine> jsVM = [jsContext virtualMachine];
+    EXPECT_TRUE([jsVM isWebThreadAware]);
+
+    // Release WebThread lock.
+    Util::spinRunLoop(1);
+
+    EXPECT_FALSE(WebThreadIsLocked());
+
+    [jsContext evaluateScript:@"loadColorIntoTexture(128, 128, 255, 255)"];
+    [jsContext evaluateScript:@"draw()"];
+    Util::run(&didFinishPainting);
+
+}
+
+}
+
+#endif
diff --git a/Tools/TestWebKitAPI/Tests/WebKitLegacy/ios/webgl.html b/Tools/TestWebKitAPI/Tests/WebKitLegacy/ios/webgl.html
new file mode 100644 (file)
index 0000000..36a40f4
--- /dev/null
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<head>
+<title>WebGL drawing</title>
+<style>
+canvas {
+    width: 100px;
+    height: 100px;
+}
+</style>
+</head>
+<script id="vertexShaderSource" type="text/glsl">
+attribute vec4 a_position;
+varying vec2 v_texturePosition;
+
+void main() {
+    v_texturePosition = vec2((a_position.x + 1.0) / 2.0, (a_position.y + 1.0) / 2.0);
+    gl_Position = a_position;
+}
+</script>
+<script id="fragmentShaderSource" type="text/glsl">
+precision mediump float;
+
+varying vec2 v_texturePosition;
+
+uniform sampler2D texture;
+
+void main() {
+  gl_FragColor = texture2D(texture, v_texturePosition);
+}
+</script>
+<script>
+let texture;
+let gl;
+let textureUniform;
+let NUMBER_OF_PAINTS_REMAINING = 60;
+
+function signalReady() {
+    window.location = "callback:isReady";
+}
+
+function signalFinished() {
+    window.location = "callback:didFinishPainting";
+}
+
+function loadColorIntoTexture(r, g, b, a) {
+    gl.bindTexture(gl.TEXTURE_2D, texture);
+    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([r, g, b, a]));
+}
+
+function draw() {
+    gl.clear(gl.COLOR_BUFFER_BIT);
+
+    gl.activeTexture(gl.TEXTURE0);
+    gl.bindTexture(gl.TEXTURE_2D, texture);
+
+    gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+    if (NUMBER_OF_PAINTS_REMAINING--) {
+        window.requestAnimationFrame(draw);
+    } else {
+        signalFinished();
+    }
+};
+
+function run() {
+
+    let canvas = document.querySelector("canvas");
+    canvas.width = 100;
+    canvas.height = 100;
+
+    gl = canvas.getContext("webgl");
+
+    gl.clearColor(0, 0, 1, 1);
+
+    let vertexShader = gl.createShader(gl.VERTEX_SHADER);
+
+    gl.shaderSource(vertexShader, document.getElementById("vertexShaderSource").textContent);
+
+    gl.compileShader(vertexShader);
+    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
+        // We failed to compile. Output to the console and quit.
+        console.error("Vertex Shader failed to compile.");
+        console.log(gl.getShaderInfoLog(vertexShader));
+        return;
+    }
+
+    let fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
+    gl.shaderSource(fragmentShader, document.getElementById("fragmentShaderSource").textContent);
+    gl.compileShader(fragmentShader);
+    if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
+        console.error("Fragment Shader failed to compile.");
+        console.log(gl.getShaderInfoLog(fragmentShader));
+        return;
+    }
+
+    let program = gl.createProgram();
+    gl.attachShader(program, vertexShader);
+    gl.attachShader(program, fragmentShader);
+    gl.linkProgram(program);
+
+    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
+        console.error("Unable to link shaders into program.");
+        return;
+    }
+
+    gl.useProgram(program);
+
+    let textureUniform = gl.getUniformLocation(program, "texture");
+
+    let positionAttribute = gl.getAttribLocation(program, "a_position");
+    gl.enableVertexAttribArray(positionAttribute);
+
+    let vertices = new Float32Array([
+       -1, -1,
+       1, -1,
+       1, 1,
+       1, 1,
+       -1, 1,
+       -1, -1
+    ]);
+
+    let quadBuffer = gl.createBuffer();
+
+    gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
+    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
+
+    texture = gl.createTexture();
+    loadColorIntoTexture(0, 128, 255, 255);
+
+    gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
+    gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 0, 0);
+
+    gl.uniform1i(textureUniform, 0);
+
+    signalReady();
+}
+
+window.addEventListener("load", run, false);
+</script>
+<body>
+    <canvas></canvas>
+</body>