blob: e4e50c9c73a7ccf85fa21074fb989d5535473b8c [file] [log] [blame] [edit]
/*
* Copyright (C) 2025 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"
#import "GetComputedStyleAfterIframeRemovalProtocol.h"
#import <WebKit/WKWebProcessPlugIn.h>
#import <WebKit/WKWebProcessPlugInBrowserContextControllerPrivate.h>
#import <WebKit/WKWebProcessPlugInFrame.h>
#import <WebKit/WKWebProcessPlugInLoadDelegate.h>
#import <WebKit/WKWebProcessPlugInScriptWorld.h>
#import <WebKit/_WKRemoteObjectInterface.h>
#import <WebKit/_WKRemoteObjectRegistry.h>
#import <wtf/RetainPtr.h>
#import <wtf/darwin/DispatchExtras.h>
// FIXME(rdar://171294437): Disabling the back/forward cache explicitly is (for some reason) not available
// on iOS, so we can only run this test on macOS at present.
#if PLATFORM(MAC)
// This plugin verifies the render tree cleanup invariant established by the fix in
// LocalFrame::frameWasDisconnectedFromOwner() (rdar://171020009).
//
// After an iframe is disconnected via HTMLFrameOwnerElement::disconnectContentFrame(),
// the iframe's render tree must be fully torn down before any code observes the
// post-disconnection state. The plugin uses didRemoveFrameFromHierarchy: to detect
// frame removal, then schedules a dispatch_async block to run after the full
// disconnectContentFrame() call stack (including disconnectOwnerElement()) unwinds.
// It then calls getComputedStyle on an element styled with a viewport unit (width:10vw).
// Because the render tree is gone, m_renderView is null, viewport factors resolve to
// zero, and the result is "0px" — confirming that the render tree is cleanly torn down.
@interface GetComputedStyleAfterIframeRemovalPlugIn : NSObject <WKWebProcessPlugIn, WKWebProcessPlugInLoadDelegate>
@end
@implementation GetComputedStyleAfterIframeRemovalPlugIn {
RetainPtr<WKWebProcessPlugInBrowserContextController> _browserContextController;
RetainPtr<id<GetComputedStyleAfterIframeRemovalProtocol>> _remoteObject;
RetainPtr<JSContext> _savedIframeContext;
BOOL _hasSavedIframeContext;
BOOL _didReport;
}
- (void)webProcessPlugIn:(WKWebProcessPlugInController *)plugInController didCreateBrowserContextController:(WKWebProcessPlugInBrowserContextController *)browserContextController
{
_browserContextController = browserContextController;
auto interface = retainPtr([_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(GetComputedStyleAfterIframeRemovalProtocol)]);
_remoteObject = [[browserContextController _remoteObjectRegistry] remoteObjectProxyWithInterface:interface.get()];
[browserContextController setLoadDelegate:self];
}
- (void)webProcessPlugInBrowserContextController:(WKWebProcessPlugInBrowserContextController *)controller didFinishDocumentLoadForFrame:(WKWebProcessPlugInFrame *)frame
{
// Save state only from the subframe (non-main frame) with the styled target element.
if (frame.isMainFrame || _hasSavedIframeContext)
return;
JSContext *context = [frame jsContextForWorld:WKWebProcessPlugInScriptWorld.normalWorld];
if (!context)
return;
// Check that the target element exists in this frame.
JSValue *element = [context evaluateScript:@"document.getElementById('target')"];
if (!element || [element isNull] || [element isUndefined])
return;
_savedIframeContext = context;
_hasSavedIframeContext = YES;
}
- (void)webProcessPlugInBrowserContextController:(WKWebProcessPlugInBrowserContextController *)controller didRemoveFrameFromHierarchy:(WKWebProcessPlugInFrame *)frame
{
if (frame.isMainFrame || !_hasSavedIframeContext || _didReport)
return;
_hasSavedIframeContext = NO;
_didReport = YES;
RetainPtr<JSContext> savedContext = _savedIframeContext;
RetainPtr<id<GetComputedStyleAfterIframeRemovalProtocol>> remoteObject = _remoteObject;
_savedIframeContext = nil;
// dispatch_async ensures this block runs after the entire disconnectContentFrame()
// call stack unwinds — including disconnectOwnerElement() -> frameWasDisconnectedFromOwner().
// At that point the render tree is fully torn down.
dispatch_async(mainDispatchQueueSingleton(), ^{
JSValue *result = [savedContext.get() evaluateScript:
@"(function() {"
"var el = document.getElementById('target');"
"if (!el) return '(element gone)';"
"try {"
" return window.getComputedStyle(el).width;"
"} catch(e) {"
" return '(exception: ' + e + ')';"
"}})()"];
NSString *resultString = (result && ![result isNull] && ![result isUndefined]) ? [result toString] : @"(null)";
[remoteObject.get() didNotCrashWithResult:resultString];
});
}
@end
#endif // PLATFORM(MAC)