| /* |
| * 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> |
| |
| #if PLATFORM(MAC) || PLATFORM(MACCATALYST) |
| #import <OpenGL/OpenGL.h> |
| #elif PLATFORM(IOS) |
| #import <OpenGLES/EAGL.h> |
| #endif |
| |
| namespace { |
| bool didFinishLoad; |
| bool didFinishTest; |
| |
| #if PLATFORM(MAC) || PLATFORM(MACCATALYST) |
| class SetContextCGL { |
| SetContextCGL() |
| { |
| // TODO: implement properly. Skipped due to complicated instantiation. |
| // instead, just clobber the current context with nullptr. |
| CGLSetCurrentContext(nullptr); |
| } |
| ~SetContextCGL() |
| { |
| CGLSetCurrentContext(nullptr); |
| } |
| }; |
| #else |
| class SetContextCGL { |
| |
| }; |
| #endif |
| |
| #if PLATFORM(IOS) |
| class SetContextEAGL { |
| public: |
| SetContextEAGL() |
| { |
| m_eaglContext = adoptNS([[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES3]); |
| auto r = [EAGLContext setCurrentContext: m_eaglContext.get()]; |
| ASSERT(r); |
| UNUSED_VARIABLE(r); |
| } |
| ~SetContextEAGL() |
| { |
| [EAGLContext setCurrentContext: nil]; |
| } |
| |
| private: |
| RetainPtr<EAGLContext> m_eaglContext; |
| }; |
| #else |
| class SetContextEAGL { |
| |
| }; |
| #endif |
| |
| } |
| |
| @interface WebGLNoCrashOnOtherThreadAccessWebViewDelegate : NSObject <UIWebViewDelegate> |
| @end |
| |
| @implementation WebGLNoCrashOnOtherThreadAccessWebViewDelegate |
| |
| 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"]) { |
| if ([request.URL.resourceSpecifier isEqualToString:@"didFinishTest"]) |
| didFinishTest = true; |
| else |
| EXPECT_TRUE(false) << [request.URL.resourceSpecifier UTF8String]; |
| return NO; |
| } |
| |
| return YES; |
| } |
| IGNORE_WARNINGS_END |
| |
| @end |
| |
| namespace TestWebKitAPI { |
| |
| // Test that tests behavior of UIWebView API with regards to WebGL: |
| // 1) WebGL can be run on web thread. |
| // 2) WebGL can be run on client main thread. |
| // 3) WebGL run on client thread is not affected by client changing |
| // the EAGL/CGL context. |
| TEST(WebKitLegacy, WebGLNoCrashOnOtherThreadAccess) |
| { |
| const unsigned testIterations = 10; |
| |
| 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()]; |
| |
| ASSERT_TRUE(WebThreadIsEnabled()); |
| SetContextCGL clientContextCGL1; |
| SetContextEAGL clientContextEAGL1; |
| UNUSED_VARIABLE(clientContextCGL1); |
| UNUSED_VARIABLE(clientContextEAGL1); |
| |
| RetainPtr<WebGLNoCrashOnOtherThreadAccessWebViewDelegate> uiDelegate = |
| adoptNS([[WebGLNoCrashOnOtherThreadAccessWebViewDelegate alloc] init]); |
| uiWebView.get().delegate = uiDelegate.get(); |
| NSString* testHtml = @R"HTML(<body>NOLINT(readability/multiline_string) |
| <script> |
| function runTest() |
| { |
| let w = 100; |
| let h = 100; |
| let canvas = document.createElement("canvas"); |
| canvas.width = w; |
| canvas.height = h; |
| let gl = canvas.getContext("webgl"); |
| gl.clearColor(0, 0, 1, 1); |
| gl.clear(gl.COLOR_BUFFER_BIT); |
| let expected = 255 << 24 | 255 << 16; |
| let ps = new Uint8Array(w * h * 4); |
| gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, ps); |
| for (let i = 0; i < ps.length; i += 4) { |
| let got = ps[i] | ps[i+1] << 8 | ps[i+2] << 16 | ps[i+3] << 24; |
| if (got != expected) |
| return `${got} != ${expected}`; // NOLINT |
| } |
| return "didFinishTest"; |
| } |
| function runTestAsync() |
| { |
| setTimeout(() => { window.location = 'callback:' + runTest(); }, 10); // NOLINT |
| } |
| </script>)HTML"; // NOLINT(readability/multiline_string) |
| [uiWebView loadHTMLString:testHtml baseURL:nil]; |
| |
| Util::run(&didFinishLoad); |
| WebThreadLock(); |
| |
| RetainPtr<JSContext> jsContext = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; |
| RetainPtr<JSVirtualMachine> jsVM = [jsContext virtualMachine]; |
| EXPECT_TRUE([jsVM isWebThreadAware]); |
| |
| // Start to run WebGL on web thread. |
| [uiWebView stringByEvaluatingJavaScriptFromString:@"runTestAsync();"]; |
| EXPECT_FALSE(didFinishTest); // The pattern runs the test on web thread. |
| |
| for (unsigned i = 0; i < testIterations; i++) { |
| // Run WebGL on client main thread. |
| NSString* result = [[jsContext evaluateScript: @"runTest();"] toString]; |
| EXPECT_TRUE([@"didFinishTest" isEqualToString: result]); |
| |
| // Client clobbers EAGLContext between calls that run WebGL on main thread. |
| SetContextCGL clientContextCGL2; |
| SetContextEAGL clientContextEAGL2; |
| UNUSED_VARIABLE(clientContextCGL2); |
| UNUSED_VARIABLE(clientContextEAGL2); |
| |
| result = [[jsContext evaluateScript: @"runTest();"] toString]; |
| EXPECT_TRUE([@"didFinishTest" isEqualToString: result]); |
| |
| // Wait until the previous WebGL test run has finished. |
| Util::run(&didFinishTest); |
| didFinishTest = false; |
| // Start to run WebGL on web thread. |
| [uiWebView stringByEvaluatingJavaScriptFromString:@"runTestAsync();"]; |
| EXPECT_FALSE(didFinishTest); |
| } |
| |
| Util::run(&didFinishTest); |
| } |
| |
| } |
| |
| #endif |