Changeset 268097 in webkit


Ignore:
Timestamp:
Oct 6, 2020 5:21:02 PM (4 years ago)
Author:
Chris Dumez
Message:

Reloading a view in its processTerminationHandler does not work reliably when using related views
https://bugs.webkit.org/show_bug.cgi?id=217407

Reviewed by Geoff Garen.

Source/WebKit:

Related web views share the same WebContent process. When this process crashes, we iterate over
the list of WebPageProxy objects sharing this process and let them know that their process has
crashed. This causes the WebPageProxy to reset its state (so that it is aware it no longer has
a running process) and to notify the client application. Because we were notifying the client
application synchronously, the client could trigger a load in the view synchronously while we
are still iterating over the WebPageProxy objects. When triggering a load in a web view that
has no running process, we normally relaunch one. However, in the case of related web views,
the view uses its related webview's process, relaunching it if necessary. The 'relaunching
if necessary' part was not reliably happening here because the related web view may not have
been notified yet that its WebProcess has crashed (since we are still iterating over the
pages to notify them).

To address the issue, we now notify the client asynchronously of the process termination.

  • UIProcess/WebPageProxy.cpp:

(WebKit::WebPageProxy::dispatchProcessDidTerminate):

Tools:

Add API test coverage.

  • TestWebKitAPI/Tests/WebKitCocoa/WebContentProcessDidTerminate.mm:

(-[NavigationDelegateWithCrashHandlerThatLoadsAgain _webView:webContentProcessDidTerminateWithReason:]):
(-[NavigationDelegateWithCrashHandlerThatLoadsAgain webView:didFinishNavigation:]):
(TEST):

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit/ChangeLog

    r268095 r268097  
     12020-10-06  Chris Dumez  <cdumez@apple.com>
     2
     3        Reloading a view in its processTerminationHandler does not work reliably when using related views
     4        https://bugs.webkit.org/show_bug.cgi?id=217407
     5
     6        Reviewed by Geoff Garen.
     7
     8        Related web views share the same WebContent process. When this process crashes, we iterate over
     9        the list of WebPageProxy objects sharing this process and let them know that their process has
     10        crashed. This causes the WebPageProxy to reset its state (so that it is aware it no longer has
     11        a running process) and to notify the client application. Because we were notifying the client
     12        application synchronously, the client could trigger a load in the view synchronously while we
     13        are still iterating over the WebPageProxy objects. When triggering a load in a web view that
     14        has no running process, we normally relaunch one. However, in the case of related web views,
     15        the view uses its related webview's process, relaunching it if necessary. The 'relaunching
     16        if necessary' part was not reliably happening here because the related web view may not have
     17        been notified yet that its WebProcess has crashed (since we are still iterating over the
     18        pages to notify them).
     19
     20        To address the issue, we now notify the client asynchronously of the process termination.
     21
     22        * UIProcess/WebPageProxy.cpp:
     23        (WebKit::WebPageProxy::dispatchProcessDidTerminate):
     24
    1252020-10-06  Peng Liu  <peng.liu6@apple.com>
    226
  • trunk/Source/WebKit/UIProcess/WebPageProxy.cpp

    r268074 r268097  
    74657465    RELEASE_LOG_ERROR_IF_ALLOWED(Loading, "dispatchProcessDidTerminate: reason = %d", reason);
    74667466
    7467     bool handledByClient = false;
    7468     if (m_loaderClient)
    7469         handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this);
    7470     else
    7471         handledByClient = m_navigationClient->processDidTerminate(*this, reason);
    7472 
    7473     if (!handledByClient && shouldReloadAfterProcessTermination(reason))
    7474         tryReloadAfterProcessTermination();
     7467    // We notify the client asynchronously because several pages may share the same process
     7468    // and we want to make sure all pages are aware their process has crashed before the
     7469    // the client reacts to the process termination.
     7470    RunLoop::main().dispatch([this, weakThis = makeWeakPtr(*this), reason] {
     7471        if (!weakThis)
     7472            return;
     7473
     7474        bool handledByClient = false;
     7475        if (m_loaderClient)
     7476            handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this);
     7477        else
     7478            handledByClient = m_navigationClient->processDidTerminate(*this, reason);
     7479
     7480        if (!handledByClient && shouldReloadAfterProcessTermination(reason))
     7481            tryReloadAfterProcessTermination();
     7482    });
    74757483}
    74767484
  • trunk/Tools/ChangeLog

    r268086 r268097  
     12020-10-06  Chris Dumez  <cdumez@apple.com>
     2
     3        Reloading a view in its processTerminationHandler does not work reliably when using related views
     4        https://bugs.webkit.org/show_bug.cgi?id=217407
     5
     6        Reviewed by Geoff Garen.
     7
     8        Add API test coverage.
     9
     10        * TestWebKitAPI/Tests/WebKitCocoa/WebContentProcessDidTerminate.mm:
     11        (-[NavigationDelegateWithCrashHandlerThatLoadsAgain _webView:webContentProcessDidTerminateWithReason:]):
     12        (-[NavigationDelegateWithCrashHandlerThatLoadsAgain webView:didFinishNavigation:]):
     13        (TEST):
     14
    1152020-10-06  Devin Rousso  <drousso@apple.com>
    216
  • trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebContentProcessDidTerminate.mm

    r260366 r268097  
    3232#import <WebKit/WKProcessPoolPrivate.h>
    3333#import <WebKit/WKWebView.h>
     34#import <WebKit/WKWebViewConfigurationPrivate.h>
    3435#import <WebKit/WKWebViewPrivate.h>
    3536#import <wtf/RetainPtr.h>
     37#import <wtf/Vector.h>
    3638
    3739static bool didCrash;
     
    4345static bool calledAllCallbacks;
    4446static unsigned callbackCount;
     47static unsigned crashHandlerCount;
     48static unsigned loadCount;
     49static unsigned expectedLoadCount;
    4550
    4651static NSString *testHTML = @"<script>window.webkit.messageHandlers.testHandler.postMessage('LOADED');</script>";
     
    278283    EXPECT_EQ(6U, callbackCount);
    279284}
     285
     286@interface NavigationDelegateWithCrashHandlerThatLoadsAgain : NSObject <WKNavigationDelegate>
     287@end
     288
     289@implementation NavigationDelegateWithCrashHandlerThatLoadsAgain
     290
     291- (void)_webView:(WKWebView *)webView webContentProcessDidTerminateWithReason:(_WKProcessTerminationReason)reason
     292{
     293    ++crashHandlerCount;
     294
     295    // Attempt the load again synchronously.
     296    [webView loadHTMLString:@"foo" baseURL:nil];
     297}
     298
     299- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
     300{
     301    if (++loadCount == expectedLoadCount)
     302        finishedLoad = true;
     303}
     304
     305@end
     306
     307TEST(WKNavigation, ReloadRelatedViewsInProcessDidTerminate)
     308{
     309    const unsigned numberOfViews = 20;
     310    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
     311    auto webView1 = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100) configuration:configuration.get()]);
     312
     313    Vector<RetainPtr<WKWebView>> webViews;
     314    webViews.append(webView1);
     315
     316    configuration.get()._relatedWebView = webView1.get();
     317    for (unsigned i = 0; i < numberOfViews - 1; ++i) {
     318        auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100) configuration:configuration.get()]);
     319        webViews.append(webView);
     320    }
     321    auto delegate = adoptNS([[NavigationDelegateWithCrashHandlerThatLoadsAgain alloc] init]);
     322    for (auto& webView : webViews)
     323        [webView setNavigationDelegate:delegate.get()];
     324
     325    crashHandlerCount = 0;
     326    loadCount = 0;
     327    expectedLoadCount = numberOfViews;
     328    finishedLoad = false;
     329
     330    for (auto& webView : webViews)
     331        [webView loadHTMLString:@"foo" baseURL:nil];
     332
     333    TestWebKitAPI::Util::run(&finishedLoad);
     334    EXPECT_EQ(0U, crashHandlerCount);
     335
     336    auto pidBefore = [webView1 _webProcessIdentifier];
     337    EXPECT_TRUE(!!pidBefore);
     338    for (auto& webView : webViews)
     339        EXPECT_EQ(pidBefore, [webView _webProcessIdentifier]);
     340
     341    loadCount = 0;
     342    finishedLoad = false;
     343
     344    // Kill the WebContent process. The crash handler should reload all views.
     345    kill(pidBefore, 9);
     346
     347    TestWebKitAPI::Util::run(&finishedLoad);
     348    EXPECT_EQ(numberOfViews, crashHandlerCount);
     349
     350    auto pidAfter = [webView1 _webProcessIdentifier];
     351    EXPECT_TRUE(!!pidAfter);
     352    for (auto& webView : webViews)
     353        EXPECT_EQ(pidAfter, [webView _webProcessIdentifier]);
     354}
Note: See TracChangeset for help on using the changeset viewer.