Title: [290875] trunk
Revision
290875
Author
[email protected]
Date
2022-03-06 09:39:33 -0800 (Sun, 06 Mar 2022)

Log Message

[iOS] Layer tree can get indefinitely frozen if WKWebView is unparented underneath -[UIWindow dealloc]
https://bugs.webkit.org/show_bug.cgi?id=237505
rdar://85563958

Reviewed by Tim Horton.

Source/WebKit:

It's currently possible for the web page to get permanently stuck in frozen state, due to the
`BackgroundApplication` layer tree freeze reason; this occurs when the web view is unparented from the view
hierarchy underneath the scope of UIWindow's `-dealloc` method.

During `-[UIWindow dealloc]`, the backpointer underlying the implementation of `-[UIView window]` is set to `nil`
immediately before the subclassing method hook `-willMoveToWindow:` is invoked on the view hierarchy. This means
that when `-willMoveToWindow:` is invoked, `self.window` will return `nil`. This, in turn, puts
`WKApplicationStateTrackingView` in a bad state because we bail early before resetting `_applicationStateTracker`
in the early return below, since we (erroneously) believe that we've already been unparented from the view
hierarchy, so we don't need to do anything.

```
if (!self._contentView.window || newWindow)
    return;
```

As a result, if the same web view is eventually moved back into another visible window, `-didMoveToWindow` bails
before setting up the `_applicationStateTracker` again, since it already exists from when the previous window
was still active. This means `-_applicationWillEnterForeground` is never called when the web view is
reintroduced to the view hierarchy, so `LayerTreeFreezeReason::BackgroundApplication` is never lifted.

To address this, we simply remove the debug assertion for `_applicationStateTracker`, and instead check whether
the application state tracker exists or not for the logic of the early return. Doing so also makes the early
return in `-willMoveToWindow:` consistent with the logic in one in `-didMoveToWindow`, which already consults
`_applicationStateTracker`:

```
- (void)didMoveToWindow
{
    if (!self._contentView.window || _applicationStateTracker)
        return;
```

Test: ApplicationStateTracking.WindowDeallocDoesNotPermanentlyFreezeLayerTree

* UIProcess/ios/WKApplicationStateTrackingView.mm:
(-[WKApplicationStateTrackingView willMoveToWindow:]): See above.

Tools:

Add an API test to exercise the bug. This API test is comprised of the following series of steps:
1. Create the web view and add it under window #1.
2. Post a "did enter background" notification.
3. Deallocate window #1 (thereby unparenting the web view in the process).
4. Post a "will enter foreground" notification.
5. Add the web view under window #2.
6. Load some HTML content and wait for a presentation update.

Before the fix, this test times out because the layer tree is permanently frozen after step (3), due to the
`BackgroundApplication` reason, so the presentation update in step (6) never finishes.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/ios/ApplicationStateTracking.mm: Added.
(TestWebKitAPI::TEST):

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebKit/ChangeLog (290874 => 290875)


--- trunk/Source/WebKit/ChangeLog	2022-03-06 17:28:26 UTC (rev 290874)
+++ trunk/Source/WebKit/ChangeLog	2022-03-06 17:39:33 UTC (rev 290875)
@@ -1,3 +1,49 @@
+2022-03-06  Wenson Hsieh  <[email protected]>
+
+        [iOS] Layer tree can get indefinitely frozen if WKWebView is unparented underneath -[UIWindow dealloc]
+        https://bugs.webkit.org/show_bug.cgi?id=237505
+        rdar://85563958
+
+        Reviewed by Tim Horton.
+
+        It's currently possible for the web page to get permanently stuck in frozen state, due to the
+        `BackgroundApplication` layer tree freeze reason; this occurs when the web view is unparented from the view
+        hierarchy underneath the scope of UIWindow's `-dealloc` method.
+
+        During `-[UIWindow dealloc]`, the backpointer underlying the implementation of `-[UIView window]` is set to `nil`
+        immediately before the subclassing method hook `-willMoveToWindow:` is invoked on the view hierarchy. This means
+        that when `-willMoveToWindow:` is invoked, `self.window` will return `nil`. This, in turn, puts
+        `WKApplicationStateTrackingView` in a bad state because we bail early before resetting `_applicationStateTracker`
+        in the early return below, since we (erroneously) believe that we've already been unparented from the view
+        hierarchy, so we don't need to do anything.
+
+        ```
+        if (!self._contentView.window || newWindow)
+            return;
+        ```
+
+        As a result, if the same web view is eventually moved back into another visible window, `-didMoveToWindow` bails
+        before setting up the `_applicationStateTracker` again, since it already exists from when the previous window
+        was still active. This means `-_applicationWillEnterForeground` is never called when the web view is
+        reintroduced to the view hierarchy, so `LayerTreeFreezeReason::BackgroundApplication` is never lifted.
+
+        To address this, we simply remove the debug assertion for `_applicationStateTracker`, and instead check whether
+        the application state tracker exists or not for the logic of the early return. Doing so also makes the early
+        return in `-willMoveToWindow:` consistent with the logic in one in `-didMoveToWindow`, which already consults
+        `_applicationStateTracker`:
+
+        ```
+        - (void)didMoveToWindow
+        {
+            if (!self._contentView.window || _applicationStateTracker)
+                return;
+        ```
+
+        Test: ApplicationStateTracking.WindowDeallocDoesNotPermanentlyFreezeLayerTree
+
+        * UIProcess/ios/WKApplicationStateTrackingView.mm:
+        (-[WKApplicationStateTrackingView willMoveToWindow:]): See above.
+
 2022-03-06  Eric Carlson  <[email protected]>
 
         Push HTMLMediaElement renderer's contentBox rect to GPU process

Modified: trunk/Source/WebKit/UIProcess/ios/WKApplicationStateTrackingView.mm (290874 => 290875)


--- trunk/Source/WebKit/UIProcess/ios/WKApplicationStateTrackingView.mm	2022-03-06 17:28:26 UTC (rev 290874)
+++ trunk/Source/WebKit/UIProcess/ios/WKApplicationStateTrackingView.mm	2022-03-06 17:39:33 UTC (rev 290875)
@@ -51,12 +51,11 @@
 
 - (void)willMoveToWindow:(UIWindow *)newWindow
 {
-    if (!self._contentView.window || newWindow)
+    if (!_applicationStateTracker || newWindow)
         return;
 
     auto page = [_webViewToTrack _page];
     RELEASE_LOG(ViewState, "%p - WKApplicationStateTrackingView: View with page [%p, pageProxyID=%" PRIu64 "] was removed from a window, _lastObservedStateWasBackground=%d", self, page.get(), page ? page->identifier().toUInt64() : 0, page ? page->lastObservedStateWasBackground() : false);
-    ASSERT(_applicationStateTracker);
     _applicationStateTracker = nullptr;
 }
 

Modified: trunk/Tools/ChangeLog (290874 => 290875)


--- trunk/Tools/ChangeLog	2022-03-06 17:28:26 UTC (rev 290874)
+++ trunk/Tools/ChangeLog	2022-03-06 17:39:33 UTC (rev 290875)
@@ -1,3 +1,26 @@
+2022-03-06  Wenson Hsieh  <[email protected]>
+
+        [iOS] Layer tree can get indefinitely frozen if WKWebView is unparented underneath -[UIWindow dealloc]
+        https://bugs.webkit.org/show_bug.cgi?id=237505
+        rdar://85563958
+
+        Reviewed by Tim Horton.
+
+        Add an API test to exercise the bug. This API test is comprised of the following series of steps:
+        1. Create the web view and add it under window #1.
+        2. Post a "did enter background" notification.
+        3. Deallocate window #1 (thereby unparenting the web view in the process).
+        4. Post a "will enter foreground" notification.
+        5. Add the web view under window #2.
+        6. Load some HTML content and wait for a presentation update.
+
+        Before the fix, this test times out because the layer tree is permanently frozen after step (3), due to the
+        `BackgroundApplication` reason, so the presentation update in step (6) never finishes.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/ios/ApplicationStateTracking.mm: Added.
+        (TestWebKitAPI::TEST):
+
 2022-03-04  Sihui Liu  <[email protected]>
 
         Add null check for path in makeAllDirectories

Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (290874 => 290875)


--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2022-03-06 17:28:26 UTC (rev 290874)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2022-03-06 17:39:33 UTC (rev 290875)
@@ -1086,6 +1086,7 @@
 		F46A095A1ED8A6E600D4AA55 /* apple.gif in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47D30EB1ED28619000482E1 /* apple.gif */; };
 		F46A095B1ED8A6E600D4AA55 /* gif-and-file-input.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47D30ED1ED28A6C000482E1 /* gif-and-file-input.html */; };
 		F46BD56924870643008282D6 /* dragstart-data.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F46BD5682487062D008282D6 /* dragstart-data.html */; };
+		F46C45C427D42A2F00ECED2C /* ApplicationStateTracking.mm in Sources */ = {isa = PBXBuildFile; fileRef = F46C45C327D42A2F00ECED2C /* ApplicationStateTracking.mm */; };
 		F46D43AB26D7092800969E5E /* test.jpg in Copy Resources */ = {isa = PBXBuildFile; fileRef = F46D43AA26D7090300969E5E /* test.jpg */; };
 		F472874727816BCE003EBE7F /* NSResponderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F43C3823278133190099ABCE /* NSResponderTests.mm */; };
 		F47728991E4AE3C1007ABF6A /* full-page-contenteditable.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */; };
@@ -3103,6 +3104,7 @@
 		F46849BF1EEF5EDC00B937FE /* rich-and-plain-text.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "rich-and-plain-text.html"; sourceTree = "<group>"; };
 		F469FB231F01803500401539 /* contenteditable-and-target.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "contenteditable-and-target.html"; sourceTree = "<group>"; };
 		F46BD5682487062D008282D6 /* dragstart-data.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "dragstart-data.html"; sourceTree = "<group>"; };
+		F46C45C327D42A2F00ECED2C /* ApplicationStateTracking.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ApplicationStateTracking.mm; sourceTree = "<group>"; };
 		F46D43AA26D7090300969E5E /* test.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = test.jpg; sourceTree = "<group>"; };
 		F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "full-page-contenteditable.html"; sourceTree = "<group>"; };
 		F47D30EB1ED28619000482E1 /* apple.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = apple.gif; sourceTree = "<group>"; };
@@ -3957,6 +3959,7 @@
 				A1C4FB6F1BACCEFA003742D0 /* Resources */,
 				2E205BA31F527746005952DD /* AccessibilityTestsIOS.mm */,
 				F45B63FC1F19D410009D38B9 /* ActionSheetTests.mm */,
+				F46C45C327D42A2F00ECED2C /* ApplicationStateTracking.mm */,
 				F42D634322A1729F00D2FB3A /* AutocorrectionTestsIOS.mm */,
 				44077BB0231449D200179E2D /* DataDetectorsTestIOS.mm */,
 				F4D4F3B71E4E36E400BB2767 /* DragAndDropTestsIOS.mm */,
@@ -5522,6 +5525,7 @@
 				712E4BC125DDC52A0007201C /* AnimationFrameRate.cpp in Sources */,
 				57152B5E21CC2045000C37CA /* ApduTest.cpp in Sources */,
 				6354F4D11F7C3AB500D89DF3 /* ApplicationManifestParser.cpp in Sources */,
+				F46C45C427D42A2F00ECED2C /* ApplicationStateTracking.mm in Sources */,
 				DF6580942722168900B3F1C1 /* ASN1Utilities.cpp in Sources */,
 				7CCE7EB41A411A7E00447C4C /* AttributedString.mm in Sources */,
 				F4FA2A4F24D1F05700618A46 /* AttributedSubstringForProposedRange.mm in Sources */,

Added: trunk/Tools/TestWebKitAPI/Tests/ios/ApplicationStateTracking.mm (0 => 290875)


--- trunk/Tools/TestWebKitAPI/Tests/ios/ApplicationStateTracking.mm	                        (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/ios/ApplicationStateTracking.mm	2022-03-06 17:39:33 UTC (rev 290875)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 PLATFORM(IOS_FAMILY)
+
+#import "TestWKWebView.h"
+
+namespace TestWebKitAPI {
+
+TEST(ApplicationStateTracking, WindowDeallocDoesNotPermanentlyFreezeLayerTree)
+{
+    auto configuration = adoptNS([WKWebViewConfiguration new]);
+
+    RetainPtr<TestWKWebView> webView;
+    RetainPtr<UIWindow> window;
+
+    auto postWithScene = [&](NSNotificationName name) {
+        [NSNotificationCenter.defaultCenter postNotificationName:name object:[window windowScene] userInfo:nil];
+    };
+
+    @autoreleasepool {
+        webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 568) configuration:configuration.get() addToWindow:NO]);
+        window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 320, 568)]);
+        [window addSubview:webView.get()];
+        [webView synchronouslyLoadHTMLString:@"<body>Foo</body>"];
+        [webView waitForNextPresentationUpdate];
+        postWithScene(UISceneDidEnterBackgroundNotification);
+        window = nil;
+    }
+
+    window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 320, 568)]);
+    postWithScene(UISceneWillEnterForegroundNotification);
+    [window addSubview:webView.get()];
+    [webView synchronouslyLoadHTMLString:@"<body>Bar</body>"];
+    [webView waitForNextPresentationUpdate];
+}
+
+} // namespace TestWebKitAPI
+
+#endif // PLATFORM(IOS_FAMILY)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to