Skip to content

Commit c15e88a

Browse files
committed
[Swift Testing] Convert more WKWebExtension* tests to use Swift Testing (part 4)
https://bugs.webkit.org/show_bug.cgi?id=326052 rdar://188988905 Reviewed by NOBODY (OOPS!). * TestExpectations/apitests: * Tools/Scripts/webkitpy/api_tests/allowlist.txt: * Tools/TestWebKitAPI/Helpers/cocoa/HTTPServer/HTTPServer.swift: (responseBehavior(_:)): (Connection.receiveRequestPath): (Connection.send(_:)): (Connection.terminate): * Tools/TestWebKitAPI/Helpers/cocoa/HTTPServer/HTTPServerConnection.swift: (NWConnection.terminate): * Tools/TestWebKitAPI/Helpers/cocoa/HTTPServer/HTTPServerCore.swift: (didReceive(_:)): * Tools/TestWebKitAPI/Helpers/cocoa/WebExtensionUtilities.h: * Tools/TestWebKitAPI/Helpers/cocoa/WebExtensionUtilities.mm: (testExceptionRaisedBySettingValue): (-[TestWebExtensionManager waitForTestMessage:completionHandler:]): (-[TestWebExtensionManager waitForContextErrorWithCompletionHandler:]): (-[TestWebExtensionManager done]): (-[TestWebExtensionManager _webExtensionController:receivedTestMessage:withArgument:andSourceURL:lineNumber:]): * Tools/TestWebKitAPI/PlatformCocoa.cmake: * Tools/TestWebKitAPI/SourcesCocoa.txt: * Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj: * Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionAPITest.mm: Removed. * Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionAPITest.swift: Added. (WKWebExtensionAPITestTests.testStartedEvent): (WKWebExtensionAPITestTests.testFinishedEvent): (WKWebExtensionAPITestTests.messageEvent): (WKWebExtensionAPITestTests.messageEventInWebPage): (WKWebExtensionAPITestTests.messageEventInContentScript): (WKWebExtensionAPITestTests.messageEventWithSendMessageReply): (WKWebExtensionAPITestTests.sendMessage): (WKWebExtensionAPITestTests.sendMessageMultipleTimes): (WKWebExtensionAPITestTests.sendMessageOutOfOrder): (WKWebExtensionAPITestTests.sendMessageBeforeListenerAdded): (WKWebExtensionAPITestTests.addAnonymousAsyncTest): (WKWebExtensionAPITestTests.addAsyncTestThatPasses): (WKWebExtensionAPITestTests.addAsyncTestThatFails): (WKWebExtensionAPITestTests.addAsyncTestThatThrows): (WKWebExtensionAPITestTests.addMultipleAsyncTestsThatPass): (WKWebExtensionAPITestTests.addMultipleAsyncTestsWithFailure): (WKWebExtensionAPITestTests.addAnonymousTest): (WKWebExtensionAPITestTests.addTestThatPasses): (WKWebExtensionAPITestTests.addTestThatFails): (WKWebExtensionAPITestTests.addTestThatThrows): (WKWebExtensionAPITestTests.addMultipleTestsThatPass): (WKWebExtensionAPITestTests.addMultipleTestsWithFailure): (WKWebExtensionAPITestTests.runAnonymousTests): (WKWebExtensionAPITestTests.runTestsThatPass): (WKWebExtensionAPITestTests.runTestsWithTestThatFails): (WKWebExtensionAPITestTests.runTestsWithAsyncTestThatFails): (WKWebExtensionAPITestTests.runTestsVerifyFailedTestAborts): * Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionAPIWebNavigation.mm: Removed. * Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionAPIWebNavigation.swift: Added. (WKWebExtensionAPIWebNavigationTests.eventListenerRegistration): (WKWebExtensionAPIWebNavigationTests.beforeNavigateEvent): (WKWebExtensionAPIWebNavigationTests.committedEvent): (WKWebExtensionAPIWebNavigationTests.domContentLoadedEvent): (WKWebExtensionAPIWebNavigationTests.completedEvent): (WKWebExtensionAPIWebNavigationTests.allowedFilter): (WKWebExtensionAPIWebNavigationTests.deniedFilter): (WKWebExtensionAPIWebNavigationTests.allEventsFired): (WKWebExtensionAPIWebNavigationTests.documentIdAcrossEvents): (WKWebExtensionAPIWebNavigationTests.removeListenerDuringEvent): (WKWebExtensionAPIWebNavigationTests.errorOccurredEventDuringProvisionalLoad): (WKWebExtensionAPIWebNavigationTests.errorOccurredEventDuringLoad): (WKWebExtensionAPIWebNavigationTests.getFrameWithMainFrame): (getFrameWithSubframe): (getAllFrames): (errorOccurred): (errors): (urlFilterTestMatchAllPredicates): (urlFilterMatchesOnePredicate): (emptyFilterMatchesEverything): (urlKeyTypeChecking): * Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionAPIWebRequest.mm: Removed. * Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionAPIWebRequest.swift: Added. (WKWebExtensionAPIWebRequestTests.configurationEnablingFeature(_:)): (WKWebExtensionAPIWebRequestTests.error): (WKWebExtensionAPIWebRequestTests.manifestV2Persistent): (WKWebExtensionAPIWebRequestTests.manifestV2NonPersistent): (WKWebExtensionAPIWebRequestTests.eventListenerRegistration): (WKWebExtensionAPIWebRequestTests.beforeRequestEvent): (WKWebExtensionAPIWebRequestTests.beforeRequestEventForSubresource): (WKWebExtensionAPIWebRequestTests.beforeRequestEventForSubframe): * Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionContext.mm: Removed. * Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionContext.swift: Added. (WKWebExtensionContextTests.defaultPermissionChecks): (WKWebExtensionContextTests.permissionGranting): (WKWebExtensionContextTests.contentScriptsParsing): (WKWebExtensionContextTests.optionsPageURLParsing): (WKWebExtensionContextTests.urlOverridesParsing): (WKWebExtensionContextTests.commandsParsing): (WKWebExtensionContextTests.loadNonExistentImage): (WKWebExtensionContextTests.topLevelThrowInModuleBackground): (WKWebExtensionContextTests.referenceErrorInBackground): (WKWebExtensionContextTests.callingMissingBrowserAPIInBackground): (WKWebExtensionContextTests.uncaughtScriptErrorInBackground): (WKWebExtensionContextTests.unhandledPromiseRejectionInBackground): (WKWebExtensionContextTests.uncaughtScriptErrorInServiceWorkerBackground): (WKWebExtensionContextTests.unhandledPromiseRejectionInServiceWorkerBackground): (WKWebExtensionContextTests.syntaxErrorInBackground): (WKWebExtensionContextTests.noErrorForCaughtExceptionsInBackground): (WKWebExtensionContextTests.uncaughtScriptErrorInContentScript): (WKWebExtensionContextTests.uncaughtScriptErrorInMainWorldContentScript): (WKWebExtensionContextTests.pageScriptErrorNotReportedToExtension): (WKWebExtensionContextTests.consoleErrorDoesNotEvaluateArgumentsTwice): (WKWebExtensionContextTests.uncaughtScriptErrorInEventListener): (WKWebExtensionContextTests.topLevelThrowInPopup): (WKWebExtensionContextTests.consoleErrorReportedNotLogOrWarn): (WKWebExtensionContextTests.consoleAssertWithMessage): (WKWebExtensionContextTests.consoleAssertWithoutMessage): (WKWebExtensionContextTests.cleanUpOldOriginDataAfterMigration): * Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionController.mm: Removed. * Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionController.swift: Added. (WKWebExtensionControllerTests.configuration): (WKWebExtensionControllerTests.loadingAndUnloadingContexts): (WKWebExtensionControllerTests.backgroundPageLoading): (WKWebExtensionControllerTests.backgroundPageWithModulesLoading): (WKWebExtensionControllerTests.backgroundWithServiceWorkerPreferredEnvironment): (WKWebExtensionControllerTests.backgroundWithPageDocumentPreferredEnvironment): (WKWebExtensionControllerTests.backgroundWithScriptsDocumentPreferredEnvironment): (WKWebExtensionControllerTests.backgroundWithMultipleDocumentModuleScripts): (WKWebExtensionControllerTests.backgroundWithMultipleServiceWorkerScripts): (WKWebExtensionControllerTests.backgroundWithMultipleServiceWorkerModuleScripts): (WKWebExtensionControllerTests.contentScriptLoading): (WKWebExtensionControllerTests.cssUserOrigin): (WKWebExtensionControllerTests.cssAuthorOrigin): (WKWebExtensionControllerTests.contentSecurityPolicyV2BlockingImageLoad): (WKWebExtensionControllerTests.contentSecurityPolicyV3BlockingImageLoad): (WKWebExtensionControllerTests.webAccessibleResources): (WKWebExtensionControllerTests.webAccessibleResourcesWithLeadingSlash): (WKWebExtensionControllerTests.webAccessibleResourceInSubframeFromAboutBlank): (WKWebExtensionControllerTests.webAccessibleResourcesV2):
1 parent b435774 commit c15e88a

20 files changed

Lines changed: 7265 additions & 6456 deletions

‎TestExpectations/apitests‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ rdar://137268889 [ mac ] TestWebKitAPI.WKWebExtensionAPIDevTools.InspectedWindow
408408
rdar://137268889 [ mac ] TestWebKitAPI.WKWebExtensionAPIDevTools.PortMessagePassingToBackground [ Skip ]
409409
webkit.org/b/318821 [ sequoia ] TestWebKitAPI.WKWebExtensionAPINamespaceTests/noNotificationsObjectWithoutPermission() [ Pass Timeout ]
410410
webkit.org/b/318821 [ sequoia ] TestWebKitAPI.WKWebExtensionAPINamespaceTests/noWebNavigationObjectWithoutPermission() [ Pass Timeout ]
411-
webkit.org/b/318821 [ sequoia ] TestWebKitAPI.WKWebExtensionController.WebAccessibleResourceInSubframeFromAboutBlank [ Pass Timeout ]
411+
webkit.org/b/318821 [ sequoia ] TestWebKitAPI.WKWebExtensionControllerTests/webAccessibleResourceInSubframeFromAboutBlank() [ Pass Timeout ]
412412
rdar://167044676 [ mac ] TestWebKitAPI.WKWebExtensionDataRecord.GetDataRecords [ Skip ]
413413
rdar://147858640 TestWebKitAPI.WKWebExtensionDataRecord.GetDataRecordsForMultipleContexts [ Skip ]
414414
rdar://125926932 TestWebKitAPI.WKWebExtensionDataRecord.RemoveDataRecords [ Skip ]

‎Tools/Scripts/webkitpy/api_tests/allowlist.txt‎

Lines changed: 129 additions & 86 deletions
Large diffs are not rendered by default.

‎Tools/TestWebKitAPI/Helpers/cocoa/HTTPServer/HTTPServer.swift‎

Lines changed: 135 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,24 @@ public import Network_SPI
5555
/// }
5656
/// ```
5757
public struct Route: Sendable {
58+
/// How a route responds to a request for its path.
59+
public enum ResponseBehavior: Sendable {
60+
/// Sends the route's response.
61+
case sendResponseNormally
62+
63+
/// Closes the connection as soon as the request arrives, without sending anything.
64+
case terminateConnectionAfterReceivingRequest
65+
66+
/// Leaves the request unanswered.
67+
case neverSendResponse
68+
}
69+
5870
fileprivate struct Storage: Sendable {
5971
let pathComponents: [String]
6072
let statusCode: Int
6173
let headerFields: [String: String]
6274
let response: String
75+
let behavior: ResponseBehavior
6376
}
6477

6578
fileprivate let children: [Storage]
@@ -69,7 +82,15 @@ public struct Route: Sendable {
6982
}
7083

7184
fileprivate init(path: String, statusCode: Int, headerFields: [String: String], response: String) {
72-
self.children = [Storage(pathComponents: [path], statusCode: statusCode, headerFields: headerFields, response: response)]
85+
self.children = [
86+
Storage(
87+
pathComponents: [path],
88+
statusCode: statusCode,
89+
headerFields: headerFields,
90+
response: response,
91+
behavior: .sendResponseNormally
92+
)
93+
]
7394
}
7495

7596
/// Creates a Route from a group of child Routes.
@@ -84,7 +105,8 @@ public struct Route: Sendable {
84105
pathComponents: [path] + $0.pathComponents,
85106
statusCode: $0.statusCode,
86107
headerFields: $0.headerFields,
87-
response: $0.response
108+
response: $0.response,
109+
behavior: $0.behavior
88110
)
89111
}
90112
}
@@ -95,10 +117,35 @@ public struct Route: Sendable {
95117
/// - path: The path of this route. If this value is non-empty, it must start with `/`.
96118
/// - statusCode: The status code of the response.
97119
/// - headerFields: The header fields of the response.
98-
/// - response: The response to be used.
99-
public init(_ path: String, statusCode: Int = 200, headerFields: [String: String] = [:], _ response: () -> String) {
120+
/// - response: The response to be used. Defaults to an empty body.
121+
public init(_ path: String, statusCode: Int = 200, headerFields: [String: String] = [:], _ response: () -> String = { "" }) {
100122
self.init(path: path, statusCode: statusCode, headerFields: headerFields, response: response())
101123
}
124+
125+
/// Changes how this route responds to requests for its path.
126+
///
127+
/// For example, a route can close the connection instead of responding:
128+
///
129+
/// ```swift
130+
/// Route("/dropped")
131+
/// .responseBehavior(.terminateConnectionAfterReceivingRequest)
132+
/// ```
133+
///
134+
/// - Parameter behavior: How the route responds. For a group of routes, this applies to every route in the group.
135+
/// - Returns: A copy of this route that responds with `behavior`.
136+
public func responseBehavior(_ behavior: ResponseBehavior) -> Route {
137+
Route(
138+
children: children.map { child in
139+
Storage(
140+
pathComponents: child.pathComponents,
141+
statusCode: child.statusCode,
142+
headerFields: child.headerFields,
143+
response: child.response,
144+
behavior: behavior
145+
)
146+
}
147+
)
148+
}
102149
}
103150

104151
/// A result builder used to create a ``Route``.
@@ -187,7 +234,8 @@ public struct HTTPServer: ~Copyable {
187234
let response = HTTPResponseData(
188235
statusCode: UInt(child.statusCode),
189236
headerFields: child.headerFields.map { (name: $0.key, value: $0.value) },
190-
body: Data(child.response.utf8)
237+
body: Data(child.response.utf8),
238+
behavior: .init(child.behavior)
191239
)
192240

193241
result[path] = response
@@ -197,6 +245,42 @@ public struct HTTPServer: ~Copyable {
197245
self.storage = try HTTPServerCore(protocol: .init(`protocol`), responses: responses)
198246
}
199247

248+
/// Create a server that hands each connection it accepts to a closure instead of responding from routes.
249+
///
250+
/// Use this when a test needs to control exactly what goes over the wire, such as a response that is cut off
251+
/// partway through its body:
252+
///
253+
/// ```swift
254+
/// var server = try HTTPServer(protocol: .http) { connection in
255+
/// guard try await connection.receiveRequestPath() == "/truncated" else {
256+
/// return
257+
/// }
258+
///
259+
/// try await connection.send("HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\nFewer than 100 bytes")
260+
/// await connection.terminate()
261+
/// }
262+
/// ```
263+
///
264+
/// - Parameters:
265+
/// - protocol: The HTTP protocol to use for this server.
266+
/// - connectionHandler: A closure called once for each connection the server accepts.
267+
/// - Throws: Any error that happens during creation of the server.
268+
public init(protocol: `Protocol`, connectionHandler: @escaping @MainActor (Connection) async throws -> Void) throws {
269+
self.protocol = `protocol`
270+
self.storage = try HTTPServerCore(protocol: .init(`protocol`)) { connection in
271+
Task {
272+
do {
273+
try await connectionHandler(Connection(connection: connection))
274+
} catch NWError.posix(.ECANCELED) {
275+
// `run(_:)` cancels every connection when the server shuts down, including any still sending a response.
276+
} catch {
277+
// FIXME: Handle errors better.
278+
fatalError("\(error)")
279+
}
280+
}
281+
}
282+
}
283+
200284
/// Calls the given closure after starting the server, and then closes the server once finished.
201285
///
202286
/// - Parameter body: A closure that will run while this server is active.
@@ -243,6 +327,17 @@ extension HTTPServer.`Protocol` {
243327
}
244328
}
245329

330+
extension HTTPResponseData.Behavior {
331+
fileprivate init(_ behavior: Route.ResponseBehavior) {
332+
self =
333+
switch behavior {
334+
case .sendResponseNormally: .sendResponseNormally
335+
case .terminateConnectionAfterReceivingRequest: .terminateConnectionAfterReceivingRequest
336+
case .neverSendResponse: .neverSendResponse
337+
}
338+
}
339+
}
340+
246341
extension HTTPServerCore.`Protocol` {
247342
fileprivate init(_ protocol: HTTPServer.`Protocol`) {
248343
self =
@@ -293,3 +388,38 @@ extension HTTPServer {
293388
}
294389
}
295390
}
391+
392+
extension HTTPServer {
393+
/// A connection accepted by a server created with ``init(protocol:connectionHandler:)``.
394+
@MainActor
395+
public struct Connection {
396+
fileprivate let connection: NWConnection
397+
398+
/// Waits for the client to send a request.
399+
///
400+
/// - Returns: The path of the request, or `nil` once the client has closed the connection.
401+
/// - Throws: An error if the request cannot be parsed.
402+
public func receiveRequestPath() async throws -> String? {
403+
let request = await connection.receiveHTTPRequest()
404+
guard !request.isEmpty else {
405+
return nil
406+
}
407+
408+
let parser = HTTPRequestComponentParser(request: request)
409+
return try parser.path
410+
}
411+
412+
/// Sends data to the client exactly as given, without adding any HTTP framing.
413+
///
414+
/// - Parameter data: The data to send, usually a complete or deliberately incomplete HTTP response.
415+
/// - Throws: An error if the data could not be sent.
416+
public func send(_ data: String) async throws {
417+
try await connection.send(Data(data.utf8))
418+
}
419+
420+
/// Closes the connection.
421+
public func terminate() async {
422+
await connection.terminate()
423+
}
424+
}
425+
}

‎Tools/TestWebKitAPI/Helpers/cocoa/HTTPServer/HTTPServerConnection.swift‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ extension NWConnection {
143143
}
144144

145145
func terminate() async {
146+
// A connection that was already cancelled, for instance by a test's own connection handler, never reports
147+
// .cancelled again, so waiting for it here would never finish.
148+
if case .cancelled = state {
149+
return
150+
}
151+
146152
await withCheckedContinuation { continuation in
147153
// Strong capture: the handler is stored on the connection itself, so a weak
148154
// capture could never outlive a strong one. The cycle is broken on .cancelled.

‎Tools/TestWebKitAPI/Helpers/cocoa/HTTPServer/HTTPServerCore.swift‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,18 +193,28 @@ final class HTTPServerCore {
193193
} else if `protocol` == .http2 || `protocol` == .http3 || `protocol` == .http2Proxy {
194194
#if HAVE_NETWORK_FRAMEWORK_HTTP_MESSAGING
195195
Task {
196-
// FIXME: Handle errors better.
197-
// swift-format-ignore: NeverUseForceTry
198-
try! await respondToHTTPMessagingRequests(on: connection)
196+
do {
197+
try await respondToHTTPMessagingRequests(on: connection)
198+
} catch NWError.posix(.ECANCELED) {
199+
// `cancel()` cancels every connection when the server shuts down, including any still sending a response.
200+
} catch {
201+
// FIXME: Handle errors better.
202+
fatalError("\(error)")
203+
}
199204
}
200205
#else
201206
fatalError("HTTP messaging is not available in this configuration")
202207
#endif // HAVE_NETWORK_FRAMEWORK_HTTP_MESSAGING
203208
} else {
204209
Task {
205-
// FIXME: Handle errors better.
206-
// swift-format-ignore: NeverUseForceTry
207-
try! await respondToRequests(on: connection)
210+
do {
211+
try await respondToRequests(on: connection)
212+
} catch NWError.posix(.ECANCELED) {
213+
// `cancel()` cancels every connection when the server shuts down, including any still sending a response.
214+
} catch {
215+
// FIXME: Handle errors better.
216+
fatalError("\(error)")
217+
}
208218
}
209219
}
210220
}

‎Tools/TestWebKitAPI/Helpers/cocoa/WebExtensionUtilities.h‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,12 @@ NS_SWIFT_UI_ACTOR
9696
- (void)run NS_SWIFT_UNAVAILABLE("Spins the run loop; use run() async instead.");
9797
- (void)runForTimeInterval:(NSTimeInterval)interval NS_SWIFT_UNAVAILABLE("Spins the run loop; add an async variant instead.");
9898
- (id)runUntilTestMessage:(NSString *)message NS_SWIFT_UNAVAILABLE("Spins the run loop; use waitForTestMessage(_:) async instead.");
99-
- (void)runUntilContextError NS_SWIFT_UNAVAILABLE("Spins the run loop; add an async variant instead.");
99+
- (void)runUntilContextError NS_SWIFT_UNAVAILABLE("Spins the run loop; use waitForContextError() async instead.");
100100

101101
- (void)runWithCompletionHandler:(void (^)(NSError * _Nullable error))completionHandler;
102-
- (void)waitForTestMessage:(NSString *)message completionHandler:(void (^)(NSError * _Nullable error))completionHandler NS_SWIFT_NAME(waitForTestMessage(_:completionHandler:));
102+
- (void)waitForTestMessage:(NSString *)message completionHandler:(void (^)(id _Nullable argument, NSError * _Nullable error))completionHandler NS_SWIFT_NAME(waitForTestMessage(_:completionHandler:));
103103
- (void)loadAndRunWithCompletionHandler:(void (^)(NSError * _Nullable error))completionHandler;
104+
- (void)waitForContextErrorWithCompletionHandler:(void (^)(NSError * _Nullable error))completionHandler NS_SWIFT_NAME(waitForContextError(completionHandler:));
104105

105106
- (void)done;
106107

@@ -186,6 +187,18 @@ NS_SWIFT_UI_ACTOR
186187

187188
@end
188189

190+
#ifdef __cplusplus
191+
extern "C" {
192+
#endif
193+
194+
// An Objective-C exception cannot unwind through a Swift frame, so a Swift test that expects a setter to raise one has
195+
// to call the setter from here.
196+
NSExceptionName _Nullable testExceptionRaisedBySettingValue(id, NSString *key, id _Nullable value) NS_SWIFT_NAME(exceptionRaised(setting:forKey:to:));
197+
198+
#ifdef __cplusplus
199+
}
200+
#endif
201+
189202
NS_HEADER_AUDIT_END(nullability, sendability)
190203

191204
#else // not __OBJC__

‎Tools/TestWebKitAPI/Helpers/cocoa/WebExtensionUtilities.mm‎

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ + (instancetype)testCachedPatternWithScheme:(NSString *)scheme host:(NSString *)
6969

7070
@end
7171

72+
NSExceptionName testExceptionRaisedBySettingValue(id object, NSString *key, id value)
73+
{
74+
@try {
75+
[object setValue:value forKey:key];
76+
} @catch (NSException *exception) {
77+
return exception.name;
78+
}
79+
80+
return nil;
81+
}
82+
7283
@implementation TestWebExtensionManager {
7384
bool _done;
7485
bool _receivedMessage;
@@ -78,7 +89,7 @@ @implementation TestWebExtensionManager {
7889
void (^_doneHandler)(NSError *);
7990
NSMutableArray<NSString *> *_collectedFailures;
8091
NSString *_pendingTestMessage;
81-
void (^_pendingTestMessageHandler)(NSError *);
92+
void (^_pendingTestMessageHandler)(id, NSError *);
8293
}
8394

8495
+ (BOOL)shouldEnableSiteIsolation
@@ -389,10 +400,10 @@ - (void)runWithCompletionHandler:(void (^)(NSError *))completionHandler
389400
_doneHandler = [completionHandler copy];
390401
}
391402

392-
- (void)waitForTestMessage:(NSString *)message completionHandler:(void (^)(NSError *))completionHandler
403+
- (void)waitForTestMessage:(NSString *)message completionHandler:(void (^)(id, NSError *))completionHandler
393404
{
394-
if ([self _takeTestMessage:message]) {
395-
completionHandler([self _collectedFailuresError]);
405+
if (id argument = [self _takeTestMessage:message]) {
406+
completionHandler(argument, [self _collectedFailuresError]);
396407
return;
397408
}
398409

@@ -412,6 +423,37 @@ - (void)loadAndRunWithCompletionHandler:(void (^)(NSError *))completionHandler
412423
[self runWithCompletionHandler:completionHandler];
413424
}
414425

426+
- (void)waitForContextErrorWithCompletionHandler:(void (^)(NSError *))completionHandler
427+
{
428+
if (_context.errors.count) {
429+
completionHandler([self _collectedFailuresError]);
430+
return;
431+
}
432+
433+
__block void (^handler)(NSError *) = [completionHandler copy];
434+
__block id observer;
435+
__weak TestWebExtensionManager *weakSelf = self;
436+
437+
auto finish = ^{
438+
if (!handler)
439+
return;
440+
441+
[NSNotificationCenter.defaultCenter removeObserver:observer];
442+
observer = nil;
443+
444+
auto completionHandler = handler;
445+
handler = nil;
446+
completionHandler([weakSelf _collectedFailuresError]);
447+
};
448+
449+
observer = [NSNotificationCenter.defaultCenter addObserverForName:WKWebExtensionContextErrorsDidUpdateNotification object:_context queue:nil usingBlock:^(NSNotification *) {
450+
finish();
451+
}];
452+
453+
// Like -runUntilContextError, stop waiting after 5 seconds, so that a test whose error never arrives fails its own expectations instead of hanging.
454+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), mainDispatchQueueSingleton(), finish);
455+
}
456+
415457
- (id)_takeTestMessage:(NSString *)message
416458
{
417459
NSMutableArray *messagesArray = _messages[message];
@@ -445,7 +487,7 @@ - (void)done
445487
_pendingTestMessageHandler = nil;
446488
_pendingTestMessage = nil;
447489

448-
handler([self _collectedFailuresError] ?: managerError([NSString stringWithFormat:@"The extension finished without sending the test message \"%@\".", message]));
490+
handler(nil, [self _collectedFailuresError] ?: managerError([NSString stringWithFormat:@"The extension finished without sending the test message \"%@\".", message]));
449491
return;
450492
}
451493

@@ -541,13 +583,14 @@ - (void)_webExtensionController:(WKWebExtensionController *)controller receivedT
541583
if (!_pendingTestMessageHandler)
542584
return;
543585

544-
if (![self _takeTestMessage:_pendingTestMessage])
586+
id pendingArgument = [self _takeTestMessage:_pendingTestMessage];
587+
if (!pendingArgument)
545588
return;
546589

547590
auto handler = _pendingTestMessageHandler;
548591
_pendingTestMessageHandler = nil;
549592
_pendingTestMessage = nil;
550-
handler([self _collectedFailuresError]);
593+
handler(pendingArgument, [self _collectedFailuresError]);
551594
}
552595

553596
- (void)_webExtensionController:(WKWebExtensionController *)controller recordTestAddedWithName:(NSString *)testName andSourceURL:(NSString *)sourceURL lineNumber:(unsigned)lineNumber

0 commit comments

Comments
 (0)