JavaScript cannot be injected into iframes
<rdar://problem/54168946> and https://bugs.webkit.org/show_bug.cgi?id=213556

Reviewed by Geoff Garen.
Source/WebCore:

Covered by API tests.

* bindings/js/ExceptionDetails.h: Start a collection of "exception types" that will grow quickly,
  beginning with the specialized "missing frame" type.

Source/WebKit:

This adds a few mechanisms:
- Allows for WKUserScripts to have a target content world
- Allows "evaluateJavaScript" and "callAsyncJavaScript" to target a specific frame
- Allows for the completion handlers of those methods to be able to distinguish
  failure-due-to-missing-frame.

* Shared/WebCoreArgumentCoders.cpp:
(IPC::ArgumentCoder<ExceptionDetails>::encode):
(IPC::ArgumentCoder<ExceptionDetails>::decode):
* Shared/WebCoreArgumentCoders.h:

* UIProcess/API/Cocoa/WKError.h:
* UIProcess/API/Cocoa/WKError.mm:
(localizedDescriptionForErrorCode):

* UIProcess/API/Cocoa/WKUserScript.h:
* UIProcess/API/Cocoa/WKUserScript.mm:
(-[WKUserScript initWithSource:injectionTime:forMainFrameOnly:]):
(-[WKUserScript initWithSource:injectionTime:forMainFrameOnly:inContentWorld:]):
* UIProcess/API/Cocoa/WKWebView.h:

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView evaluateJavaScript:inFrame:inContentWorld:completionHandler:]):
(-[WKWebView callAsyncJavaScript:arguments:inFrame:inContentWorld:completionHandler:]):
(nsErrorFromExceptionDetails):
(-[WKWebView _evaluateJavaScript:asAsyncFunction:withSourceURL:withArguments:forceUserGesture:inFrame:inWorld:completionHandler:]):
(-[WKWebView callAsyncJavaScript:arguments:inContentWorld:completionHandler:]): Deleted.

* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::runJavaScript):

Tools:

* TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm:
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebKitCocoa/UserContentController.mm:
(TEST):
* TestWebKitAPI/Tests/WebKitCocoa/WKURLSchemeHandler-1.mm:
* TestWebKitAPI/Tests/WebKitCocoa/WKWebViewEvaluateJavaScript.mm:
(TEST):
(-[FramesMessageHandler userContentController:didReceiveScriptMessage:]):
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(-[WKWebView objectByCallingAsyncFunction:withArguments:error:]):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@263727 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index dc54f1f..8d6ab64 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,15 @@
+2020-06-29  Brady Eidson  <beidson@apple.com>
+
+        JavaScript cannot be injected into iframes
+        <rdar://problem/54168946> and https://bugs.webkit.org/show_bug.cgi?id=213556
+
+        Reviewed by Geoff Garen.
+
+        Covered by API tests.
+
+        * bindings/js/ExceptionDetails.h: Start a collection of "exception types" that will grow quickly,
+          beginning with the specialized "missing frame" type.
+
 2020-06-29  Sam Weinig  <weinig@apple.com>
 
         Convert AppCache manifest parser over to using StringParsingBuffer
diff --git a/Source/WebCore/bindings/js/ExceptionDetails.h b/Source/WebCore/bindings/js/ExceptionDetails.h
index 4924118..bd3bd73 100644
--- a/Source/WebCore/bindings/js/ExceptionDetails.h
+++ b/Source/WebCore/bindings/js/ExceptionDetails.h
@@ -30,9 +30,16 @@
 namespace WebCore {
 
 struct ExceptionDetails {
+    enum class Type : uint8_t {
+        Script,
+        InvalidTargetFrame,
+    };
+
     String message;
     int lineNumber { 0 };
     int columnNumber { 0 };
+    Type type { Type::Script };
+
     // This bizarre explicit initialization of String is because older compilers (like on High Sierra)
     // don't properly handle partial initialization lists unless every struct member has an explicit default value.
     // Once we stop building on those platforms we can remove this.
@@ -40,3 +47,13 @@
 };
 
 } // namespace WebCore
+
+namespace WTF {
+template<> struct EnumTraits<WebCore::ExceptionDetails::Type> {
+    using values = EnumValues<
+        WebCore::ExceptionDetails::Type,
+        WebCore::ExceptionDetails::Type::Script,
+        WebCore::ExceptionDetails::Type::InvalidTargetFrame
+    >;
+};
+}
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index 1c2f3a7..bc89f07 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,41 @@
+2020-06-29  Brady Eidson  <beidson@apple.com>
+
+        JavaScript cannot be injected into iframes
+        <rdar://problem/54168946> and https://bugs.webkit.org/show_bug.cgi?id=213556
+
+        Reviewed by Geoff Garen.
+        
+        This adds a few mechanisms:
+        - Allows for WKUserScripts to have a target content world
+        - Allows "evaluateJavaScript" and "callAsyncJavaScript" to target a specific frame
+        - Allows for the completion handlers of those methods to be able to distinguish 
+          failure-due-to-missing-frame.
+
+        * Shared/WebCoreArgumentCoders.cpp:
+        (IPC::ArgumentCoder<ExceptionDetails>::encode):
+        (IPC::ArgumentCoder<ExceptionDetails>::decode):
+        * Shared/WebCoreArgumentCoders.h:
+
+        * UIProcess/API/Cocoa/WKError.h:
+        * UIProcess/API/Cocoa/WKError.mm:
+        (localizedDescriptionForErrorCode):
+
+        * UIProcess/API/Cocoa/WKUserScript.h:
+        * UIProcess/API/Cocoa/WKUserScript.mm:
+        (-[WKUserScript initWithSource:injectionTime:forMainFrameOnly:]):
+        (-[WKUserScript initWithSource:injectionTime:forMainFrameOnly:inContentWorld:]):
+        * UIProcess/API/Cocoa/WKWebView.h:
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView evaluateJavaScript:inFrame:inContentWorld:completionHandler:]):
+        (-[WKWebView callAsyncJavaScript:arguments:inFrame:inContentWorld:completionHandler:]):
+        (nsErrorFromExceptionDetails):
+        (-[WKWebView _evaluateJavaScript:asAsyncFunction:withSourceURL:withArguments:forceUserGesture:inFrame:inWorld:completionHandler:]):
+        (-[WKWebView callAsyncJavaScript:arguments:inContentWorld:completionHandler:]): Deleted.
+
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::runJavaScript):
+
 2020-06-29  Alex Christensen  <achristensen@webkit.org>
 
         Make _WKWebsiteDataStoreConfiguration SPI for HSTS storage to replace _WKProcessPoolConfiguration.hstsStorageDirectory
diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.cpp b/Source/WebKit/Shared/WebCoreArgumentCoders.cpp
index 5447fca..3a0d4b8 100644
--- a/Source/WebKit/Shared/WebCoreArgumentCoders.cpp
+++ b/Source/WebKit/Shared/WebCoreArgumentCoders.cpp
@@ -2659,6 +2659,7 @@
     encoder << info.message;
     encoder << info.lineNumber;
     encoder << info.columnNumber;
+    encoder << info.type;
     encoder << info.sourceURL;
 }
 
@@ -2673,6 +2674,9 @@
     if (!decoder.decode(result.columnNumber))
         return false;
 
+    if (!decoder.decode(result.type))
+        return false;
+
     if (!decoder.decode(result.sourceURL))
         return false;
 
diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKError.h b/Source/WebKit/UIProcess/API/Cocoa/WKError.h
index 140d66f..81a6691 100644
--- a/Source/WebKit/UIProcess/API/Cocoa/WKError.h
+++ b/Source/WebKit/UIProcess/API/Cocoa/WKError.h
@@ -58,6 +58,7 @@
     WKErrorContentRuleListStoreVersionMismatch WK_API_AVAILABLE(macos(10.13), ios(11.0)),
     WKErrorAttributedStringContentFailedToLoad WK_API_AVAILABLE(macos(10.15), ios(13.0)),
     WKErrorAttributedStringContentLoadTimedOut WK_API_AVAILABLE(macos(10.15), ios(13.0)),
+    WKErrorJavaScriptInvalidFrameTarget WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA)),
 } WK_API_AVAILABLE(macos(10.10), ios(8.0));
 
 NS_ASSUME_NONNULL_END
diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKError.mm b/Source/WebKit/UIProcess/API/Cocoa/WKError.mm
index 36e1f4b..eec76da 100644
--- a/Source/WebKit/UIProcess/API/Cocoa/WKError.mm
+++ b/Source/WebKit/UIProcess/API/Cocoa/WKError.mm
@@ -73,6 +73,9 @@
 
     case WKErrorAttributedStringContentLoadTimedOut:
         return WEB_UI_STRING("Timed out while loading attributed string content", "WKErrorAttributedStringContentLoadTimedOut description");
+
+    case WKErrorJavaScriptInvalidFrameTarget:
+        return WEB_UI_STRING("JavaScript execution targeted an invalid frame", "WKErrorJavaScriptInvalidFrameTarget description");
     }
 }
 
diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKUserScript.h b/Source/WebKit/UIProcess/API/Cocoa/WKUserScript.h
index 4012f88..0c5c6dc 100644
--- a/Source/WebKit/UIProcess/API/Cocoa/WKUserScript.h
+++ b/Source/WebKit/UIProcess/API/Cocoa/WKUserScript.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2014-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
@@ -57,9 +57,18 @@
  @param source The script source.
  @param injectionTime When the script should be injected.
  @param forMainFrameOnly Whether the script should be injected into all frames or just the main frame.
+ @discussion Calling this method is the same as calling `initWithSource:injectionTime:forMainFrameOnly:inContentWorld:` with a `contentWorld` value of `WKContentWorld.pageWorld`
  */
 - (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;
 
+/*! @abstract Returns an initialized user script that can be added to a @link WKUserContentController @/link.
+ @param source The script source.
+ @param injectionTime When the script should be injected.
+ @param forMainFrameOnly Whether the script should be injected into all frames or just the main frame.
+ @param contentWorld The WKContentWorld in which to inject the script.
+ */
+- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly inContentWorld:(WKContentWorld *)contentWorld WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKUserScript.mm b/Source/WebKit/UIProcess/API/Cocoa/WKUserScript.mm
index 846f929..03eb0c0 100644
--- a/Source/WebKit/UIProcess/API/Cocoa/WKUserScript.mm
+++ b/Source/WebKit/UIProcess/API/Cocoa/WKUserScript.mm
@@ -33,10 +33,15 @@
 
 - (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly
 {
+    return [self initWithSource:source injectionTime:injectionTime forMainFrameOnly:forMainFrameOnly inContentWorld:WKContentWorld.pageWorld];
+}
+
+- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly inContentWorld:(WKContentWorld *)contentWorld
+{
     if (!(self = [super init]))
         return nil;
 
-    API::Object::constructInWrapper<API::UserScript>(self, WebCore::UserScript { source, API::UserScript::generateUniqueURL(), { }, { }, API::toWebCoreUserScriptInjectionTime(injectionTime), forMainFrameOnly ? WebCore::UserContentInjectedFrames::InjectInTopFrameOnly : WebCore::UserContentInjectedFrames::InjectInAllFrames, WebCore::WaitForNotificationBeforeInjecting::No }, API::ContentWorld::pageContentWorld());
+    API::Object::constructInWrapper<API::UserScript>(self, WebCore::UserScript { source, API::UserScript::generateUniqueURL(), { }, { }, API::toWebCoreUserScriptInjectionTime(injectionTime), forMainFrameOnly ? WebCore::UserContentInjectedFrames::InjectInTopFrameOnly : WebCore::UserContentInjectedFrames::InjectInAllFrames, WebCore::WaitForNotificationBeforeInjecting::No }, *contentWorld->_contentWorld);
 
     return self;
 }
diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.h b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.h
index 6c82a68..4659bf3 100644
--- a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.h
+++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.h
@@ -38,6 +38,7 @@
 @class WKContentWorld;
 @class WKFindConfiguration;
 @class WKFindResult;
+@class WKFrameInfo;
 @class WKNavigation;
 @class WKPDFConfiguration;
 @class WKSnapshotConfiguration;
@@ -220,14 +221,25 @@
  @param javaScriptString The JavaScript string to evaluate.
  @param completionHandler A block to invoke when script evaluation completes or fails.
  @discussion The completionHandler is passed the result of the script evaluation or an error.
+ Calling this method is equivalent to calling `evaluateJavaScript:inFrame:inContentWorld:completionHandler:` with:
+   - A `frame` value of `nil` to represent the main frame
+   - A `contentWorld` value of `WKContentWorld.pageWorld`
 */
 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
 
 /* @abstract Evaluates the given JavaScript string.
  @param javaScriptString The JavaScript string to evaluate.
+ @param frame A WKFrameInfo identifying the frame in which to evaluate the JavaScript string.
  @param contentWorld The WKContentWorld in which to evaluate the JavaScript string.
  @param completionHandler A block to invoke when script evaluation completes or fails.
  @discussion The completionHandler is passed the result of the script evaluation or an error.
+
+ Passing nil is equivalent to targeting the main frame.
+ If the frame argument no longer represents a valid frame by the time WebKit attempts to call the JavaScript function your completion handler will be called with a WKErrorJavaScriptInvalidFrameTarget error.
+ This might happen for a number of reasons, including but not limited to:
+     - The target frame has been removed from the DOM via JavaScript
+     - A parent frame has navigated, destroying all of its previous child frames
+
  No matter which WKContentWorld you use to evaluate your JavaScript string, you can make changes to the underlying web content. (e.g. the Document and its DOM structure)
  Such changes will be visible to script executing in all WKContentWorlds.
  Evaluating your JavaScript string can leave behind other changes to global state visibile to JavaScript. (e.g. `window.myVariable = 1;`)
@@ -235,11 +247,12 @@
  evaluateJavaScript: is a great way to set up global state for future JavaScript execution in a given world. (e.g. Importing libraries/utilities that future JavaScript execution will rely on)
  Once your global state is set up, consider using callAsyncJavaScript: for more flexible interaction with the JavaScript programming model.
 */
-- (void)evaluateJavaScript:(NSString *)javaScriptString inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler NS_REFINED_FOR_SWIFT WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)evaluateJavaScript:(NSString *)javaScriptString inFrame:(nullable WKFrameInfo *)frame inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler NS_REFINED_FOR_SWIFT WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 /* @abstract Calls the given JavaScript string as an async JavaScript function, passing the given named arguments to that function.
  @param functionBody The JavaScript string to use as the function body.
  @param arguments A dictionary representing the arguments to be passed to the function call.
+ @param frame A WKFrameInfo identifying the frame in which to call the JavaScript function.
  @param contentWorld The WKContentWorld in which to call the JavaScript function.
  @param completionHandler A block to invoke with the return value of the function call, or with the asynchronous resolution of the function's return value.
  @discussion The functionBody string is treated as an anonymous JavaScript function body that can be called with named arguments.
@@ -271,6 +284,12 @@
  NSNumber, NSString, NSDate, NSArray, NSDictionary, and NSNull.
  Any NSArray or NSDictionary containers can only contain objects of those types.
 
+ Passing nil is equivalent to targeting the main frame.
+ If the frame argument no longer represents a valid frame by the time WebKit attempts to call the JavaScript function your completion handler will be called with a WKErrorJavaScriptInvalidFrameTarget error.
+ This might happen for a number of reasons, including but not limited to:
+     - The target frame has been removed from the DOM via JavaScript
+     - A parent frame has navigated, destroying all of its previous child frames
+
  No matter which WKContentWorld you use to call your JavaScript function, you can make changes to the underlying web content. (e.g. the Document and its DOM structure)
  Such changes will be visible to script executing in all WKContentWorlds.
  Calling your JavaScript function can leave behind other changes to global state visibile to JavaScript. (e.g. `window.myVariable = 1;`)
@@ -300,7 +319,7 @@
 
  The above function text will create a promise that will fulfull with the value 42 after a one second delay, wait for it to resolve, then return the fulfillment value of 42.
 */
-- (void)callAsyncJavaScript:(NSString *)functionBody arguments:(nullable NSDictionary<NSString *, id> *)arguments inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler NS_REFINED_FOR_SWIFT WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)callAsyncJavaScript:(NSString *)functionBody arguments:(nullable NSDictionary<NSString *, id> *)arguments inFrame:(nullable WKFrameInfo *)frame inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler NS_REFINED_FOR_SWIFT WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 /*! @abstract Get a snapshot for the visible viewport of WKWebView.
  @param snapshotConfiguration An object that specifies how the snapshot is configured.
diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
index 1457b38..4a338ec 100644
--- a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
+++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
@@ -820,14 +820,14 @@
     [self _evaluateJavaScript:javaScriptString asAsyncFunction:NO withSourceURL:nil withArguments:nil forceUserGesture:YES inFrame:nil inWorld:WKContentWorld.pageWorld completionHandler:completionHandler];
 }
 
-- (void)evaluateJavaScript:(NSString *)javaScriptString inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^)(id, NSError *))completionHandler
+- (void)evaluateJavaScript:(NSString *)javaScriptString inFrame:(WKFrameInfo *)frame inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^)(id, NSError *))completionHandler
 {
-    [self _evaluateJavaScript:javaScriptString asAsyncFunction:NO withSourceURL:nil withArguments:nil forceUserGesture:YES inFrame:nil inWorld:contentWorld completionHandler:completionHandler];
+    [self _evaluateJavaScript:javaScriptString asAsyncFunction:NO withSourceURL:nil withArguments:nil forceUserGesture:YES inFrame:frame inWorld:contentWorld completionHandler:completionHandler];
 }
 
-- (void)callAsyncJavaScript:(NSString *)javaScriptString arguments:(NSDictionary<NSString *, id> *)arguments inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^)(id, NSError *error))completionHandler
+- (void)callAsyncJavaScript:(NSString *)javaScriptString arguments:(NSDictionary<NSString *, id> *)arguments inFrame:(WKFrameInfo *)frame inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^)(id, NSError *error))completionHandler
 {
-    [self _evaluateJavaScript:javaScriptString asAsyncFunction:YES withSourceURL:nil withArguments:arguments forceUserGesture:YES inFrame:nil inWorld:contentWorld completionHandler:completionHandler];
+    [self _evaluateJavaScript:javaScriptString asAsyncFunction:YES withSourceURL:nil withArguments:arguments forceUserGesture:YES inFrame:frame inWorld:contentWorld completionHandler:completionHandler];
 }
 
 static bool validateArgument(id argument)
@@ -864,6 +864,31 @@
     return false;
 }
 
+static RetainPtr<NSError> nsErrorFromExceptionDetails(const WebCore::ExceptionDetails& details)
+{
+    auto userInfo = adoptNS([[NSMutableDictionary alloc] init]);
+
+    WKErrorCode errorCode;
+    switch (details.type) {
+    case WebCore::ExceptionDetails::Type::InvalidTargetFrame:
+        errorCode = WKErrorJavaScriptInvalidFrameTarget;
+        break;
+    case WebCore::ExceptionDetails::Type::Script:
+        errorCode = WKErrorJavaScriptExceptionOccurred;
+        break;
+    }
+
+    [userInfo setObject:localizedDescriptionForErrorCode(errorCode) forKey:NSLocalizedDescriptionKey];
+    [userInfo setObject:details.message forKey:_WKJavaScriptExceptionMessageErrorKey];
+    [userInfo setObject:@(details.lineNumber) forKey:_WKJavaScriptExceptionLineNumberErrorKey];
+    [userInfo setObject:@(details.columnNumber) forKey:_WKJavaScriptExceptionColumnNumberErrorKey];
+
+    if (!details.sourceURL.isEmpty())
+        [userInfo setObject:[NSURL _web_URLWithWTFString:details.sourceURL] forKey:_WKJavaScriptExceptionSourceURLErrorKey];
+
+    return adoptNS([[NSError alloc] initWithDomain:WKErrorDomain code:errorCode userInfo:userInfo.get()]);
+}
+
 - (void)_evaluateJavaScript:(NSString *)javaScriptString asAsyncFunction:(BOOL)asAsyncFunction withSourceURL:(NSURL *)sourceURL withArguments:(NSDictionary<NSString *, id> *)arguments forceUserGesture:(BOOL)forceUserGesture inFrame:(WKFrameInfo *)frame inWorld:(WKContentWorld *)world completionHandler:(void (^)(id, NSError *))completionHandler
 {
     auto handler = adoptNS([completionHandler copy]);
@@ -930,18 +955,7 @@
         auto rawHandler = (void (^)(id, NSError *))handler.get();
         if (details) {
             ASSERT(!serializedScriptValue);
-
-            RetainPtr<NSMutableDictionary> userInfo = adoptNS([[NSMutableDictionary alloc] init]);
-
-            [userInfo setObject:localizedDescriptionForErrorCode(WKErrorJavaScriptExceptionOccurred) forKey:NSLocalizedDescriptionKey];
-            [userInfo setObject:static_cast<NSString *>(details->message) forKey:_WKJavaScriptExceptionMessageErrorKey];
-            [userInfo setObject:@(details->lineNumber) forKey:_WKJavaScriptExceptionLineNumberErrorKey];
-            [userInfo setObject:@(details->columnNumber) forKey:_WKJavaScriptExceptionColumnNumberErrorKey];
-
-            if (!details->sourceURL.isEmpty())
-                [userInfo setObject:[NSURL _web_URLWithWTFString:details->sourceURL] forKey:_WKJavaScriptExceptionSourceURLErrorKey];
-
-            rawHandler(nil, adoptNS([[NSError alloc] initWithDomain:WKErrorDomain code:WKErrorJavaScriptExceptionOccurred userInfo:userInfo.get()]).get());
+            rawHandler(nil, nsErrorFromExceptionDetails(*details).get());
             return;
         }
 
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp
index 422646d..593e722 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp
@@ -3430,7 +3430,7 @@
     // disappear during script execution.
 
     if (!frame || !frame->coreFrame()) {
-        send(Messages::WebPageProxy::ScriptValueCallback({ }, ExceptionDetails { "Unable to execute JavaScript: Page is in invalid state"_s }, callbackID));
+        send(Messages::WebPageProxy::ScriptValueCallback({ }, ExceptionDetails { "Unable to execute JavaScript: Target frame could not be found in the page"_s, 0, 0, ExceptionDetails::Type::InvalidTargetFrame }, callbackID));
         return;
     }
 
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index d010c53..1de8ca8 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,21 @@
+2020-06-29  Brady Eidson  <beidson@apple.com>
+
+        JavaScript cannot be injected into iframes
+        <rdar://problem/54168946> and https://bugs.webkit.org/show_bug.cgi?id=213556
+
+        Reviewed by Geoff Garen.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm:
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/Tests/WebKitCocoa/UserContentController.mm:
+        (TEST):
+        * TestWebKitAPI/Tests/WebKitCocoa/WKURLSchemeHandler-1.mm:
+        * TestWebKitAPI/Tests/WebKitCocoa/WKWebViewEvaluateJavaScript.mm:
+        (TEST):
+        (-[FramesMessageHandler userContentController:didReceiveScriptMessage:]):
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (-[WKWebView objectByCallingAsyncFunction:withArguments:error:]):
+
 2020-06-29  Jonathan Bedard  <jbedard@apple.com>
 
         [TestExpectations] Remove --csv option
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm
index ebb5b85e..a492757 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/AsyncFunction.mm
@@ -183,12 +183,12 @@
 {
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]);
 
-    [webView callAsyncJavaScript:@"shouldn't crash" arguments:@{ @"invalidparameter" : webView.get() } inContentWorld:WKContentWorld.pageWorld completionHandler:nil];
+    [webView callAsyncJavaScript:@"shouldn't crash" arguments:@{ @"invalidparameter" : webView.get() } inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:nil];
 
     NSString *functionBody = @"return new Promise(function(resolve, reject) { setTimeout(function(){ resolve(42) }, 0); })";
 
     bool done = false;
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isKindOfClass:[NSNumber class]]);
         EXPECT_TRUE([result isEqualToNumber:@42]);
@@ -200,7 +200,7 @@
     functionBody = @"return new Promise(function(resolve, reject) { setTimeout(function(){ reject('Rejected!') }, 0); })";
 
     done = false;
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_TRUE(error != nil);
         EXPECT_TRUE([[error description] containsString:@"Rejected!"]);
@@ -211,7 +211,7 @@
     functionBody = @"let p = new Proxy(function(resolve, reject) { setTimeout(function() { resolve(42); }, 0); }, { }); return { then: p };";
 
     done = false;
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isKindOfClass:[NSNumber class]]);
         EXPECT_TRUE([result isEqualToNumber:@42]);
@@ -222,7 +222,7 @@
     functionBody = @"let p = new Proxy(function(resolve, reject) { setTimeout(function() { reject('Rejected!'); }, 0); }, { }); return { then: p };";
 
     done = false;
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_TRUE(error != nil);
         EXPECT_TRUE([[error description] containsString:@"Rejected!"]);
@@ -234,7 +234,7 @@
     functionBody = @"var r = 0; var p = new Promise(function(fulfill, reject) { setTimeout(function(){ r = 42; fulfill(); }, 5);}); await p; return r;";
 
     done = false;
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isKindOfClass:[NSNumber class]]);
         EXPECT_TRUE([result isEqualToNumber:@42]);
@@ -246,7 +246,7 @@
     functionBody = @"var p = new Promise(function(fulfill, reject) { setTimeout(function(){ fulfill('Fulfilled!') }, 5);}); await p; return p;";
 
     done = false;
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
         EXPECT_TRUE([result isEqualToString:@"Fulfilled!"]);
@@ -258,7 +258,7 @@
     functionBody = @"var p = new Promise(function (r) { r(new Promise(function (r) { r(42); })); }); await p; return 'Done';";
 
     done = false;
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
         EXPECT_TRUE([result isEqualToString:@"Done"]);
@@ -270,7 +270,7 @@
     done = false;
     functionBody = @"return new Promise(function(resolve, reject) { })";
     for (int i = 0; i < 500; ++i) {
-        [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+        [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
             EXPECT_NULL(result);
             EXPECT_TRUE(error != nil);
             EXPECT_TRUE([[error description] containsString:@"no longer reachable"]);
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/UserContentController.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/UserContentController.mm
index 4237e12..15a93da 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/UserContentController.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/UserContentController.mm
@@ -221,7 +221,7 @@
 
     __block bool done = false;
     // Test that we throw an exception if you try to use a message handler that has been removed.
-    [webView callAsyncJavaScript:@"return handlerToRemove.postMessage('FAIL')" arguments:nil inContentWorld:[WKContentWorld pageWorld] completionHandler:^ (id value, NSError * error) {
+    [webView callAsyncJavaScript:@"return handlerToRemove.postMessage('FAIL')" arguments:nil inFrame:nil inContentWorld:[WKContentWorld pageWorld] completionHandler:^ (id value, NSError * error) {
         EXPECT_NULL(value);
         EXPECT_NOT_NULL(error);
         EXPECT_TRUE([[error description] containsString:@"InvalidAccessError"]);
@@ -1012,7 +1012,7 @@
     NSString *functionBody = @"var p = window.webkit.messageHandlers[handler].postMessage(arg); await p; return p;";
 
     // pageWorld is where testhandler1 lives
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler1", @"arg" : @1 } inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler1", @"arg" : @1 } inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isEqualToNumber:@1]);
         done = true;
@@ -1021,7 +1021,7 @@
     done = false;
 
     // Trying to find testHandler1 in the defaultClientWorld should fail
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler1", @"arg" : @1 } inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler1", @"arg" : @1 } inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_NOT_NULL(error);
         done = true;
@@ -1030,7 +1030,7 @@
     done = false;
 
     // defaultClientWorld is where testhandler2 lives
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler2", @"arg" : @1 } inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler2", @"arg" : @1 } inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isEqualToNumber:@1]);
         done = true;
@@ -1040,7 +1040,7 @@
 
     // But if we remvoe it, it should no longer live there, and using it should cause an error.
     [[configuration userContentController] removeScriptMessageHandlerForName:@"testHandler2" contentWorld:WKContentWorld.defaultClientWorld];
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler2", @"arg" : @1 } inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler2", @"arg" : @1 } inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_NOT_NULL(error);
         done = true;
@@ -1049,15 +1049,15 @@
     done = false;
 
     // Verify handlers 3, 4, and 5 are all in the custom world.
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler3", @"arg" : @1 } inContentWorld:world completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler3", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isEqualToNumber:@1]);
     }];
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler4", @"arg" : @1 } inContentWorld:world completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler4", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isEqualToNumber:@1]);
     }];
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler5", @"arg" : @1 } inContentWorld:world completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler5", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isEqualToNumber:@1]);
         done = true;
@@ -1067,7 +1067,7 @@
 
     // Remove 3 from the wrong world, verify it is still there in the custom world.
     [[configuration userContentController] removeScriptMessageHandlerForName:@"testHandler3" contentWorld:WKContentWorld.defaultClientWorld];
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler3", @"arg" : @1 } inContentWorld:world completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler3", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isEqualToNumber:@1]);
         done = true;
@@ -1077,15 +1077,15 @@
 
     // Remove 3 from the correct world, verify it is gone, but 4 and 5 are still there.
     [[configuration userContentController] removeScriptMessageHandlerForName:@"testHandler3" contentWorld:world];
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler3", @"arg" : @1 } inContentWorld:world completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler3", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_NOT_NULL(error);
     }];
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler4", @"arg" : @1 } inContentWorld:world completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler4", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isEqualToNumber:@1]);
     }];
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler5", @"arg" : @1 } inContentWorld:world completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler5", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isEqualToNumber:@1]);
         done = true;
@@ -1095,11 +1095,11 @@
 
     // Remove "all" in the custom world, verify 4 and 5 are now gone.
     [[configuration userContentController] removeAllScriptMessageHandlersFromContentWorld:world];
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler4", @"arg" : @1 } inContentWorld:world completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler4", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_NOT_NULL(error);
     }];
-    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler5", @"arg" : @1 } inContentWorld:world completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:@{ @"handler" : @"testHandler5", @"arg" : @1 } inFrame:nil inContentWorld:world completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_NOT_NULL(error);
         done = true;
@@ -1118,7 +1118,7 @@
 
     bool done = false;
     NSString *functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage('Fulfill'); await p; return p;";
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
         EXPECT_TRUE([result isEqualToString:@"Fulfilled!"]);
@@ -1129,7 +1129,7 @@
 
     done = false;
     functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage('Reject'); await p; return p;";
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_TRUE(!!error);
         EXPECT_TRUE([[error description] containsString:@"Rejected!"]);
@@ -1141,7 +1141,7 @@
 
     done = false;
     functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage('Undefined'); var result = await p; return result == undefined ? 'Yes' : 'No'";
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
         EXPECT_TRUE([result isEqualToString:@"Yes"]);
@@ -1153,7 +1153,7 @@
 
     done = false;
     functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage('Do nothing'); await p; return p;";
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_TRUE(!!error);
         EXPECT_TRUE([[error description] containsString:@"did not respond to this postMessage"]);
@@ -1165,7 +1165,7 @@
 
     done = false;
     functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage('Invalid reply'); await p; return p;";
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_TRUE(!!error);
         EXPECT_TRUE([[error description] containsString:@"unable to be serialized"]);
@@ -1187,7 +1187,7 @@
 
     // Set a variable in the world.
     bool done = false;
-    [webView evaluateJavaScript:@"var foo = 'bar'" inContentWorld:world.get() completionHandler:[&] (id result, NSError *error) {
+    [webView evaluateJavaScript:@"var foo = 'bar'" inFrame:nil inContentWorld:world.get() completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         done = true;
     }];
@@ -1196,7 +1196,7 @@
 
     // Have the message handler bounce back that value.
     NSString *functionBody = @"var p = window.webkit.messageHandlers.testHandler.postMessage(foo); await p; return p;";
-    [webView callAsyncJavaScript:functionBody arguments:nil inContentWorld:world.get() completionHandler:[&] (id result, NSError *error) {
+    [webView callAsyncJavaScript:functionBody arguments:nil inFrame:nil inContentWorld:world.get() completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
         EXPECT_TRUE([result isEqualToString:@"bar"]);
@@ -1208,7 +1208,7 @@
     // Remove the message handler, which used to cause the world to be destroyed in the web process.
     // But by evaluating JS make sure the value is still there.
     [[configuration userContentController] removeAllScriptMessageHandlersFromContentWorld:world.get()];
-    [webView evaluateJavaScript:@"foo" inContentWorld:world.get() completionHandler:[&] (id result, NSError *error) {
+    [webView evaluateJavaScript:@"foo" inFrame:nil inContentWorld:world.get() completionHandler:[&] (id result, NSError *error) {
         EXPECT_NULL(error);
         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
         EXPECT_TRUE([result isEqualToString:@"bar"]);
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewEvaluateJavaScript.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewEvaluateJavaScript.mm
index 7a297cf..d2303e6 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewEvaluateJavaScript.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewEvaluateJavaScript.mm
@@ -158,7 +158,7 @@
     isDone = false;
 
     // Verify that value is visible when evaluating in the pageWorld
-    [webView evaluateJavaScript:@"foo" inContentWorld:WKContentWorld.pageWorld completionHandler:^(id result, NSError *error) {
+    [webView evaluateJavaScript:@"foo" inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:^(id result, NSError *error) {
         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
         EXPECT_TRUE([result isEqualToString:@"bar"]);
         isDone = true;
@@ -168,7 +168,7 @@
     isDone = false;
 
     // Verify that value is not visible when evaluating in the defaultClientWorld
-    [webView evaluateJavaScript:@"foo" inContentWorld:WKContentWorld.defaultClientWorld completionHandler:^(id result, NSError *error) {
+    [webView evaluateJavaScript:@"foo" inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:^(id result, NSError *error) {
         EXPECT_NULL(result);
         isDone = true;
         testsPassed++;
@@ -177,7 +177,7 @@
     isDone = false;
 
     // Verify that value is visible when calling a function in the pageWorld
-    [webView callAsyncJavaScript:@"return foo" arguments:nil inContentWorld:WKContentWorld.pageWorld completionHandler:^(id result, NSError *error) {
+    [webView callAsyncJavaScript:@"return foo" arguments:nil inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:^(id result, NSError *error) {
         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
         EXPECT_TRUE([result isEqualToString:@"bar"]);
         isDone = true;
@@ -187,7 +187,7 @@
     isDone = false;
 
     // Verify that value is not visible when calling a function in the defaultClientWorld
-    [webView callAsyncJavaScript:@"return foo" arguments:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:^(id result, NSError *error) {
+    [webView callAsyncJavaScript:@"return foo" arguments:nil inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:^(id result, NSError *error) {
         EXPECT_NULL(result);
         isDone = true;
         testsPassed++;
@@ -201,7 +201,7 @@
     [webView.get().configuration.userContentController _addScriptMessageHandler:handler name:@"testHandlerName" userContentWorld:namedWorld.get()._userContentWorld];
 
     // Set a variable value in that named world.
-    [webView evaluateJavaScript:@"var bar = 'baz'" inContentWorld:namedWorld.get() completionHandler:^(id result, NSError *error) {
+    [webView evaluateJavaScript:@"var bar = 'baz'" inFrame:nil inContentWorld:namedWorld.get() completionHandler:^(id result, NSError *error) {
         EXPECT_NULL(result);
         isDone = true;
         testsPassed++;
@@ -210,7 +210,7 @@
     isDone = false;
 
     // Set a global variable value in that named world via a function call.
-    [webView callAsyncJavaScript:@"window.baz = 'bat'" arguments:nil inContentWorld:namedWorld.get() completionHandler:^(id result, NSError *error) {
+    [webView callAsyncJavaScript:@"window.baz = 'bat'" arguments:nil inFrame:nil inContentWorld:namedWorld.get() completionHandler:^(id result, NSError *error) {
         EXPECT_NULL(result);
         EXPECT_NULL(error);
         isDone = true;
@@ -223,7 +223,7 @@
     [webView.get().configuration.userContentController _removeScriptMessageHandlerForName:@"testHandlerName" userContentWorld:namedWorld.get()._userContentWorld];
 
     // Verify the variables we set are there in that named world.
-    [webView evaluateJavaScript:@"bar" inContentWorld:namedWorld.get() completionHandler:^(id result, NSError *error) {
+    [webView evaluateJavaScript:@"bar" inFrame:nil inContentWorld:namedWorld.get() completionHandler:^(id result, NSError *error) {
         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
         EXPECT_TRUE([result isEqualToString:@"baz"]);
         isDone = true;
@@ -232,7 +232,7 @@
     TestWebKitAPI::Util::run(&isDone);
     isDone = false;
 
-    [webView evaluateJavaScript:@"window.baz" inContentWorld:namedWorld.get() completionHandler:^(id result, NSError *error) {
+    [webView evaluateJavaScript:@"window.baz" inFrame:nil inContentWorld:namedWorld.get() completionHandler:^(id result, NSError *error) {
         EXPECT_TRUE([result isKindOfClass:[NSString class]]);
         EXPECT_TRUE([result isEqualToString:@"bat"]);
         isDone = true;
@@ -242,7 +242,7 @@
     isDone = false;
 
     // Verify they aren't there in the defaultClientWorld.
-    [webView evaluateJavaScript:@"bar" inContentWorld:WKContentWorld.defaultClientWorld completionHandler:^(id result, NSError *error) {
+    [webView evaluateJavaScript:@"bar" inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:^(id result, NSError *error) {
         EXPECT_NULL(result);
         isDone = true;
         testsPassed++;
@@ -250,7 +250,7 @@
     TestWebKitAPI::Util::run(&isDone);
     isDone = false;
 
-    [webView evaluateJavaScript:@"window.baz" inContentWorld:WKContentWorld.defaultClientWorld completionHandler:^(id result, NSError *error) {
+    [webView evaluateJavaScript:@"window.baz" inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:^(id result, NSError *error) {
         EXPECT_NULL(result);
         isDone = true;
         testsPassed++;
@@ -259,7 +259,7 @@
     isDone = false;
 
     // Verify they aren't there in the pageWorld.
-    [webView evaluateJavaScript:@"bar" inContentWorld:WKContentWorld.pageWorld completionHandler:^(id result, NSError *error) {
+    [webView evaluateJavaScript:@"bar" inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:^(id result, NSError *error) {
         EXPECT_NULL(result);
         isDone = true;
         testsPassed++;
@@ -267,7 +267,7 @@
     TestWebKitAPI::Util::run(&isDone);
     isDone = false;
 
-    [webView evaluateJavaScript:@"window.baz" inContentWorld:WKContentWorld.pageWorld completionHandler:^(id result, NSError *error) {
+    [webView evaluateJavaScript:@"window.baz" inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:^(id result, NSError *error) {
         EXPECT_NULL(result);
         isDone = true;
         testsPassed++;
@@ -285,7 +285,7 @@
     [webView synchronouslyLoadHTMLString:@"<html></html>"];
 
     __block bool done = false;
-    [webView evaluateJavaScript:@"window.worldName" inContentWorld:[WKContentWorld worldWithName:@"testName"] completionHandler:^(id result, NSError *error) {
+    [webView evaluateJavaScript:@"window.worldName" inFrame:nil inContentWorld:[WKContentWorld worldWithName:@"testName"] completionHandler:^(id result, NSError *error) {
         EXPECT_WK_STREQ(result, "testName");
         done = true;
     }];
@@ -472,3 +472,337 @@
     TestWebKitAPI::Util::run(&done);
 
 }
+
+static NSMutableSet<WKFrameInfo *> *allFrames;
+@interface FramesMessageHandler : NSObject <WKScriptMessageHandler>
+@end
+
+@implementation FramesMessageHandler
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+    EXPECT_TRUE(message.world == WKContentWorld.defaultClientWorld);
+    [allFrames addObject:message.frameInfo];
+}
+@end
+
+static const char* framesMainResource = R"FRAMESRESOURCE(
+Hello world<br>
+<iframe id='theFrame' src='otherprotocol://test/index.html'></iframe>
+)FRAMESRESOURCE";
+
+static NSString *userScriptSource = @"window.webkit.messageHandlers.framesTester.postMessage('hi');";
+
+// This test loads a document under one protocol.
+// That document embeds an iframe under a different protocol
+// (ensuring they cannot access each other under the cross-origin rules of the web security model)
+// It uses a WKUserScript to collect all of the frames on the page, and then uses new forms of evaluateJavaScript
+// and callAsyncJavaScript to confirm that it can execute JS directly in each of those frames.
+TEST(EvaluateJavaScript, JavaScriptInFramesFromPostMessage)
+{
+    allFrames = [[NSMutableSet<WKFrameInfo *> alloc] init];
+    auto messageHandler = adoptNS([[FramesMessageHandler alloc] init]);
+    auto userScript = adoptNS([[WKUserScript alloc] initWithSource:userScriptSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO inContentWorld:WKContentWorld.defaultClientWorld]);
+
+    WKWebViewConfiguration *configuration = [[[WKWebViewConfiguration alloc] init] autorelease];
+    [configuration.userContentController addUserScript:userScript.get()];
+    [configuration.userContentController addScriptMessageHandler:messageHandler.get() contentWorld:WKContentWorld.defaultClientWorld name:@"framesTester"];
+
+    auto handler = adoptNS([[TestURLSchemeHandler alloc] init]);
+    [handler setStartURLSchemeTaskHandler:[&](WKWebView *, id<WKURLSchemeTask> task) {
+        if ([task.request.URL.absoluteString isEqualToString:@"framestest://test/index.html"]) {
+            NSData *data = [[NSString stringWithFormat:@"%s", framesMainResource] dataUsingEncoding:NSUTF8StringEncoding];
+            [task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:data.length textEncodingName:nil] autorelease]];
+            [task didReceiveData:data];
+            [task didFinish];
+        } else if ([task.request.URL.absoluteString isEqualToString:@"otherprotocol://test/index.html"]) {
+            [task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil] autorelease]];
+            [task didFinish];
+        } else
+            ASSERT_NOT_REACHED();
+    }];
+
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"framestest"];
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"otherprotocol"];
+
+    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
+    [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"framestest://test/index.html"]]];
+
+    EXPECT_EQ(allFrames.count, 2u);
+
+    static size_t finishedFrames = 0;
+    static bool isDone = false;
+
+    for (WKFrameInfo *frame in allFrames) {
+        bool isMainFrame = frame.isMainFrame;
+        [webView callAsyncJavaScript:@"return location.href;" arguments:nil inFrame:frame inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[isMainFrame] (id result, NSError *error) {
+            EXPECT_NULL(error);
+            EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+
+            if (isMainFrame)
+                EXPECT_TRUE([result isEqualToString:@"framestest://test/index.html"]);
+            else
+                EXPECT_TRUE([result isEqualToString:@"otherprotocol://test/index.html"]);
+
+            if (++finishedFrames == allFrames.count * 2)
+                isDone = true;
+        }];
+
+
+        [webView evaluateJavaScript:@"location.href;" inFrame:frame inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[isMainFrame] (id result, NSError *error) {
+            EXPECT_NULL(error);
+            EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+
+            if (isMainFrame)
+                EXPECT_TRUE([result isEqualToString:@"framestest://test/index.html"]);
+            else
+                EXPECT_TRUE([result isEqualToString:@"otherprotocol://test/index.html"]);
+
+            if (++finishedFrames == allFrames.count * 2)
+                isDone = true;
+        }];
+    }
+
+    TestWebKitAPI::Util::run(&isDone);
+}
+
+// This test loads a document under one protocol.
+// That document embeds an iframe under a different protocol
+// (ensuring they cannot access each other under the cross-origin rules of the web security model)
+// It collects all the frames seen during navigation delegate callbacks, and then uses new forms of evaluateJavaScript
+// and callAsyncJavaScript to confirm that it can execute JS directly in each of those frames.
+TEST(EvaluateJavaScript, JavaScriptInFramesFromNavigationDelegate)
+{
+    allFrames = [[NSMutableSet<WKFrameInfo *> alloc] init];
+
+    auto handler = adoptNS([[TestURLSchemeHandler alloc] init]);
+    [handler setStartURLSchemeTaskHandler:[&](WKWebView *, id<WKURLSchemeTask> task) {
+        if ([task.request.URL.absoluteString isEqualToString:@"framestest://test/index.html"]) {
+            NSData *data = [[NSString stringWithFormat:@"%s", framesMainResource] dataUsingEncoding:NSUTF8StringEncoding];
+            [task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:data.length textEncodingName:nil] autorelease]];
+            [task didReceiveData:data];
+            [task didFinish];
+        } else if ([task.request.URL.absoluteString isEqualToString:@"otherprotocol://test/index.html"]) {
+            NSData *data = [@"FooBarBaz" dataUsingEncoding:NSUTF8StringEncoding];
+            [task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil] autorelease]];
+            [task didReceiveData:data];
+            [task didFinish];
+        } else
+            ASSERT_NOT_REACHED();
+    }];
+
+    WKWebViewConfiguration *configuration = [[[WKWebViewConfiguration alloc] init] autorelease];
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"framestest"];
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"otherprotocol"];
+
+    RetainPtr<WKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
+
+    auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
+
+    __block bool didFinishNavigation = false;
+    [navigationDelegate setDidFinishNavigation:^(WKWebView *, WKNavigation *) {
+        didFinishNavigation = true;
+    }];
+
+    [navigationDelegate setDecidePolicyForNavigationAction:[&] (WKNavigationAction *action, void (^decisionHandler)(WKNavigationActionPolicy)) {
+        if (action.targetFrame)
+            [allFrames addObject:action.targetFrame];
+
+        decisionHandler(WKNavigationActionPolicyAllow);
+    }];
+
+    [webView setNavigationDelegate:navigationDelegate.get()];
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"framestest://test/index.html"]]];
+
+    TestWebKitAPI::Util::run(&didFinishNavigation);
+
+    EXPECT_EQ(allFrames.count, 2u);
+
+    static size_t finishedFrames = 0;
+    static bool isDone = false;
+
+    for (WKFrameInfo *frame in allFrames) {
+        bool isMainFrame = frame.isMainFrame;
+        [webView callAsyncJavaScript:@"return location.href;" arguments:nil inFrame:frame inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[isMainFrame] (id result, NSError *error) {
+            EXPECT_NULL(error);
+            EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+
+            if (isMainFrame)
+                EXPECT_TRUE([result isEqualToString:@"framestest://test/index.html"]);
+            else
+                EXPECT_TRUE([result isEqualToString:@"otherprotocol://test/index.html"]);
+
+            if (++finishedFrames == allFrames.count * 2)
+                isDone = true;
+        }];
+
+
+        [webView evaluateJavaScript:@"location.href;" inFrame:frame inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[isMainFrame] (id result, NSError *error) {
+            EXPECT_NULL(error);
+            EXPECT_TRUE([result isKindOfClass:[NSString class]]);
+
+            if (isMainFrame)
+                EXPECT_TRUE([result isEqualToString:@"framestest://test/index.html"]);
+            else
+                EXPECT_TRUE([result isEqualToString:@"otherprotocol://test/index.html"]);
+
+            if (++finishedFrames == allFrames.count * 2)
+                isDone = true;
+        }];
+    }
+
+    TestWebKitAPI::Util::run(&isDone);
+}
+
+// This test verifies that evaluating JavaScript in a frame that has since gone missing
+// due to removal from the DOM results in an appropriate error
+TEST(EvaluateJavaScript, JavaScriptInMissingFrameError)
+{
+    allFrames = [[NSMutableSet<WKFrameInfo *> alloc] init];
+
+    auto handler = adoptNS([[TestURLSchemeHandler alloc] init]);
+    [handler setStartURLSchemeTaskHandler:[&](WKWebView *, id<WKURLSchemeTask> task) {
+        if ([task.request.URL.absoluteString isEqualToString:@"framestest://test/index.html"]) {
+            NSData *data = [[NSString stringWithFormat:@"%s", framesMainResource] dataUsingEncoding:NSUTF8StringEncoding];
+            [task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:data.length textEncodingName:nil] autorelease]];
+            [task didReceiveData:data];
+            [task didFinish];
+        } else if ([task.request.URL.absoluteString isEqualToString:@"otherprotocol://test/index.html"]) {
+            NSData *data = [@"FooBarBaz" dataUsingEncoding:NSUTF8StringEncoding];
+            [task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil] autorelease]];
+            [task didReceiveData:data];
+            [task didFinish];
+        } else
+            ASSERT_NOT_REACHED();
+    }];
+
+    WKWebViewConfiguration *configuration = [[[WKWebViewConfiguration alloc] init] autorelease];
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"framestest"];
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"otherprotocol"];
+
+    RetainPtr<WKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
+
+    auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
+
+    __block bool didFinishNavigation = false;
+    [navigationDelegate setDidFinishNavigation:^(WKWebView *, WKNavigation *) {
+        didFinishNavigation = true;
+    }];
+
+    [navigationDelegate setDecidePolicyForNavigationAction:[&] (WKNavigationAction *action, void (^decisionHandler)(WKNavigationActionPolicy)) {
+        if (action.targetFrame)
+            [allFrames addObject:action.targetFrame];
+
+        decisionHandler(WKNavigationActionPolicyAllow);
+    }];
+
+    [webView setNavigationDelegate:navigationDelegate.get()];
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"framestest://test/index.html"]]];
+
+    TestWebKitAPI::Util::run(&didFinishNavigation);
+
+    EXPECT_EQ(allFrames.count, 2u);
+
+    static bool isDone = false;
+    [webView evaluateJavaScript:@"var frame = document.getElementById('theFrame'); frame.parentNode.removeChild(frame);" inFrame:nil inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[] (id result, NSError *error) {
+        isDone = true;
+    }];
+
+    TestWebKitAPI::Util::run(&isDone);
+    isDone = false;
+
+    for (WKFrameInfo *frame in allFrames) {
+        if (frame.isMainFrame)
+            continue;
+
+        [webView callAsyncJavaScript:@"return location.href;" arguments:nil inFrame:frame inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[] (id result, NSError *error) {
+            EXPECT_FALSE(error == nil);
+            EXPECT_TRUE(error.domain == WKErrorDomain);
+            EXPECT_TRUE(error.code == WKErrorJavaScriptInvalidFrameTarget);
+            isDone = true;
+        }];
+    }
+
+    TestWebKitAPI::Util::run(&isDone);
+}
+
+// This test verifies that evaluating JavaScript in a frame from the previous main navigation results in an error
+TEST(EvaluateJavaScript, JavaScriptInMissingFrameAfterNavigationError)
+{
+    allFrames = [[NSMutableSet<WKFrameInfo *> alloc] init];
+
+    auto handler = adoptNS([[TestURLSchemeHandler alloc] init]);
+    [handler setStartURLSchemeTaskHandler:[&](WKWebView *, id<WKURLSchemeTask> task) {
+        if ([task.request.URL.absoluteString isEqualToString:@"framestest://test/index.html"]) {
+            NSData *data = [[NSString stringWithFormat:@"%s", framesMainResource] dataUsingEncoding:NSUTF8StringEncoding];
+            [task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:data.length textEncodingName:nil] autorelease]];
+            [task didReceiveData:data];
+            [task didFinish];
+        } else if ([task.request.URL.absoluteString isEqualToString:@"otherprotocol://test/index.html"]) {
+            NSData *data = [@"FooBarBaz" dataUsingEncoding:NSUTF8StringEncoding];
+            [task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil] autorelease]];
+            [task didReceiveData:data];
+            [task didFinish];
+        } else if ([task.request.URL.absoluteString isEqualToString:@"framestest://index2.html"]) {
+            NSData *data = [@"Hi" dataUsingEncoding:NSUTF8StringEncoding];
+            [task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil] autorelease]];
+            [task didReceiveData:data];
+            [task didFinish];
+        } else
+            ASSERT_NOT_REACHED();
+    }];
+
+    WKWebViewConfiguration *configuration = [[[WKWebViewConfiguration alloc] init] autorelease];
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"framestest"];
+    [configuration setURLSchemeHandler:handler.get() forURLScheme:@"otherprotocol"];
+
+    RetainPtr<WKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration]);
+
+    auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
+
+    __block bool didFinishNavigation = false;
+    [navigationDelegate setDidFinishNavigation:^(WKWebView *, WKNavigation *) {
+        didFinishNavigation = true;
+    }];
+
+    [navigationDelegate setDecidePolicyForNavigationAction:[&] (WKNavigationAction *action, void (^decisionHandler)(WKNavigationActionPolicy)) {
+        if (action.targetFrame)
+            [allFrames addObject:action.targetFrame];
+
+        decisionHandler(WKNavigationActionPolicyAllow);
+    }];
+
+    [webView setNavigationDelegate:navigationDelegate.get()];
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"framestest://test/index.html"]]];
+
+    TestWebKitAPI::Util::run(&didFinishNavigation);
+    didFinishNavigation = false;
+
+    EXPECT_EQ(allFrames.count, 2u);
+
+    RetainPtr<WKFrameInfo> iframe;
+    for (WKFrameInfo *frame in allFrames) {
+        if (frame.isMainFrame)
+            continue;
+        iframe = frame;
+        break;
+    }
+
+    EXPECT_NOT_NULL(iframe);
+
+    // Get rid of the frame by navigating
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"framestest://index2.html"]]];
+    TestWebKitAPI::Util::run(&didFinishNavigation);
+    didFinishNavigation = false;
+
+    static bool isDone = false;
+
+    [webView callAsyncJavaScript:@"return location.href;" arguments:nil inFrame:iframe.get() inContentWorld:WKContentWorld.defaultClientWorld completionHandler:[] (id result, NSError *error) {
+        EXPECT_FALSE(error == nil);
+        EXPECT_TRUE(error.domain == WKErrorDomain);
+        EXPECT_TRUE(error.code == WKErrorJavaScriptInvalidFrameTarget);
+        isDone = true;
+    }];
+
+    TestWebKitAPI::Util::run(&isDone);
+    isDone = false;
+}
diff --git a/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm b/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm
index e2357a9..5888918 100644
--- a/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm
+++ b/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm
@@ -198,7 +198,7 @@
         *errorOut = nil;
 
     RetainPtr<id> evalResult;
-    [self callAsyncJavaScript:script arguments:arguments inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
+    [self callAsyncJavaScript:script arguments:arguments inFrame:nil inContentWorld:WKContentWorld.pageWorld completionHandler:[&] (id result, NSError *error) {
         evalResult = result;
         if (errorOut)
             *errorOut = [error retain];