Skip to content

Commit

Permalink
Mitigate crashes when removing KVO from NSWindow in -[WKWindowVisibil…
Browse files Browse the repository at this point in the history
…ityObserver stopObserving:]

https://bugs.webkit.org/show_bug.cgi?id=249103
rdar://102360839

Reviewed by Patrick Angle.

After the fix in 256334@main, Music sometimes crashes when destroying `NSWindow`, when
`WKWindowVisibilityObserver` attempts to remove key-value observers for "contentLayoutRect" and
"titlebarAppearsTransparent" from the window that were not added in the first place.

While I haven't been able to reproduce the crash locally or come up with a test case that (exactly)
replicates the crashing stack during `NSWindow` destruction, it should be possible to avoid it
altogether by guarding KVO registration and unregistration by using an associated object on the
`NSWindow` to indicate when `WKWindowVisibilityObserver` has key-value observers to the window. If
this flag is not set, then we avoid attempting to unregister KVO; similarly, if this flag is set,
then we avoid attempting to re-register KVO.

Test: WKWebView.PrepareForMoveToWindowShouldNotCrashWhenRemovingWindowObservers

* Source/WebKit/UIProcess/mac/WebViewImpl.mm:
(-[WKWindowVisibilityObserver startObserving:]):
(-[WKWindowVisibilityObserver stopObserving:]):

Use the `_impl` pointer as the context key.

* Tools/TestWebKitAPI/Tests/WebKitCocoa/PrepareForMoveToWindow.mm:

Add a (somewhat contrived) API test that exercises the mitigation.

Canonical link: https://commits.webkit.org/257736@main
  • Loading branch information
whsieh committed Dec 12, 2022
1 parent 19a30ab commit a9b66fa
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Source/WebKit/UIProcess/mac/WebViewImpl.mm
Expand Up @@ -332,6 +332,10 @@ - (void)startObserving:(NSWindow *)window
if (_shouldObserveFontPanel)
[self startObservingFontPanel];

if (objc_getAssociatedObject(window, _impl))
return;

objc_setAssociatedObject(window, _impl, @YES, OBJC_ASSOCIATION_COPY_NONATOMIC);
[window addObserver:self forKeyPath:@"contentLayoutRect" options:NSKeyValueObservingOptionInitial context:keyValueObservingContext];
[window addObserver:self forKeyPath:@"titlebarAppearsTransparent" options:NSKeyValueObservingOptionInitial context:keyValueObservingContext];
}
Expand Down Expand Up @@ -362,6 +366,10 @@ - (void)stopObserving:(NSWindow *)window
if (_shouldObserveFontPanel)
[[NSFontPanel sharedFontPanel] removeObserver:self forKeyPath:@"visible" context:keyValueObservingContext];

if (!objc_getAssociatedObject(window, _impl))
return;

objc_setAssociatedObject(window, _impl, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);
[window removeObserver:self forKeyPath:@"contentLayoutRect" context:keyValueObservingContext];
[window removeObserver:self forKeyPath:@"titlebarAppearsTransparent" context:keyValueObservingContext];
}
Expand Down
17 changes: 17 additions & 0 deletions Tools/TestWebKitAPI/Tests/WebKitCocoa/PrepareForMoveToWindow.mm
Expand Up @@ -70,6 +70,23 @@
TestWebKitAPI::Util::run(&done);
}

TEST(WKWebView, PrepareForMoveToWindowShouldNotCrashWhenRemovingWindowObservers)
{
auto window = adoptNS([NSWindow new]);
auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
[webView synchronouslyLoadTestPageNamed:@"simple"];

[[window contentView] addSubview:webView.get()];
[webView _prepareForMoveToWindow:nil completionHandler:^{ }];
[webView _prepareForMoveToWindow:window.get() completionHandler:^{ }];

__block bool done = false;
[webView _prepareForMoveToWindow:nil completionHandler:^{
done = true;
}];
TestWebKitAPI::Util::run(&done);
}

TEST(WKWebView, PrepareForMoveToWindowThenClose)
{
auto webView = adoptNS([[WKWebView alloc] init]);
Expand Down

0 comments on commit a9b66fa

Please sign in to comment.