appium-webdriveragent 16.2.2 → 16.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/PrivateHeaders/XCTest/XCSynthesizedEventRecord.h +3 -3
- package/PrivateHeaders/XCTest/XCUIApplication.h +1 -1
- package/PrivateHeaders/XCTest/XCUIElement.h +1 -1
- package/Scripts/build.sh +6 -0
- package/WebDriverAgent.xcodeproj/project.pbxproj +1482 -2
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationApp_watchOS.xcscheme +76 -0
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationTests_watchOS.xcscheme +86 -0
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentLib_watchOS.xcscheme +85 -0
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner.xcscheme +1 -1
- package/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner_watchOS.xcscheme +128 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.m +5 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +2 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +1 -1
- package/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +4 -4
- package/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.h +1 -1
- package/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.m +1 -1
- package/WebDriverAgentLib/Categories/XCUIElement+FBPickerWheel.m +1 -1
- package/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m +11 -0
- package/WebDriverAgentLib/Commands/FBCustomCommands.m +19 -4
- package/WebDriverAgentLib/Commands/FBElementCommands.m +6 -5
- package/WebDriverAgentLib/Commands/FBOrientationCommands.m +1 -1
- package/WebDriverAgentLib/Commands/FBSessionCommands.m +29 -5
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/FBTCPSocket.h +70 -0
- package/WebDriverAgentLib/Routing/FBTCPSocket.m +199 -0
- package/WebDriverAgentLib/Routing/FBWebServer.m +33 -3
- package/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.h +68 -0
- package/WebDriverAgentLib/Routing/WatchOS/FBWatchHTTPServer.m +376 -0
- package/WebDriverAgentLib/Routing/WatchOS/RouteRequest.h +30 -0
- package/WebDriverAgentLib/Routing/WatchOS/RouteRequest.m +25 -0
- package/WebDriverAgentLib/Routing/WatchOS/RouteResponse.h +30 -0
- package/WebDriverAgentLib/Routing/WatchOS/RouteResponse.m +54 -0
- package/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.m +8 -0
- package/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.h +1 -1
- package/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.m +1 -1
- package/WebDriverAgentLib/Utilities/FBConfiguration.h +1 -1
- package/WebDriverAgentLib/Utilities/FBConfiguration.m +3 -3
- package/WebDriverAgentLib/Utilities/FBImageProcessor.m +7 -2
- package/WebDriverAgentLib/Utilities/FBMacros.h +14 -5
- package/WebDriverAgentLib/Utilities/FBMathUtils.h +1 -1
- package/WebDriverAgentLib/Utilities/FBMathUtils.m +1 -1
- package/WebDriverAgentLib/Utilities/FBPasteboard.h +1 -1
- package/WebDriverAgentLib/Utilities/FBPasteboard.m +1 -1
- package/WebDriverAgentLib/Utilities/FBSettingsHandler.m +2 -2
- package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.h +1 -1
- package/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +1 -1
- package/WebDriverAgentTests/IntegrationApp_watchOS/ContentView.swift +31 -0
- package/WebDriverAgentTests/IntegrationApp_watchOS/WatchSpikeApp.swift +18 -0
- package/WebDriverAgentTests/IntegrationTests/FBForceTouchTests.m +3 -0
- package/WebDriverAgentTests/IntegrationTests/FBPasteboardTests.m +3 -2
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAlertIntegrationTests.swift +23 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAAppLifecycleIntegrationTests.swift +57 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAClickIntegrationTests.swift +31 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDADeviceIntegrationTests.swift +50 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAElementAttributeIntegrationTests.swift +57 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAFindIntegrationTests.swift +126 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAScreenshotAndSourceIntegrationTests.swift +52 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDASessionIntegrationTests.swift +72 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDATypingIntegrationTests.swift +57 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAUnknownCommandIntegrationTests.swift +24 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchHTTPClient.swift +95 -0
- package/WebDriverAgentTests/IntegrationTests_watchOS/WDAWatchIntegrationTestCase.swift +61 -0
- package/package.json +1 -1
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import "FBWatchHTTPServer.h"
|
|
10
|
+
|
|
11
|
+
#import "FBConfiguration.h"
|
|
12
|
+
#import "FBTCPSocket.h"
|
|
13
|
+
|
|
14
|
+
static NSData *FBCRLFCRLFData(void)
|
|
15
|
+
{
|
|
16
|
+
static NSData *data;
|
|
17
|
+
static dispatch_once_t onceToken;
|
|
18
|
+
dispatch_once(&onceToken, ^{
|
|
19
|
+
data = [@"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
|
|
20
|
+
});
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// -dataUsingEncoding:NSUTF8StringEncoding never actually returns nil; this just keeps the cast
|
|
25
|
+
// out of every call site below.
|
|
26
|
+
static NSData * _Nonnull FBUTF8Data(NSString *string)
|
|
27
|
+
{
|
|
28
|
+
return (NSData * _Nonnull)[string dataUsingEncoding:NSUTF8StringEncoding];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@interface FBWatchHTTPRoute : NSObject
|
|
32
|
+
@property (nonatomic, copy) NSString *verb;
|
|
33
|
+
@property (nonatomic, strong) NSRegularExpression *regex;
|
|
34
|
+
@property (nonatomic, copy, nullable) NSArray<NSString *> *keys;
|
|
35
|
+
@property (nonatomic, copy) void (^block)(RouteRequest *request, RouteResponse *response);
|
|
36
|
+
@end
|
|
37
|
+
|
|
38
|
+
@implementation FBWatchHTTPRoute
|
|
39
|
+
@end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@interface FBWatchHTTPServer () <FBTCPSocketDelegate>
|
|
43
|
+
|
|
44
|
+
@property (nonatomic, nullable, strong) FBTCPSocket *socket;
|
|
45
|
+
@property (nonatomic, strong) NSMutableArray<FBWatchHTTPRoute *> *routes;
|
|
46
|
+
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSString *> *defaultHeaders;
|
|
47
|
+
@property (nonatomic, nullable) dispatch_queue_t routeQueue;
|
|
48
|
+
// nw_connection_t isn't NSCopying, so it can't be an NSDictionary key - use NSMapTable instead.
|
|
49
|
+
@property (nonatomic, strong) NSMapTable<id, NSMutableData *> *connectionBuffers;
|
|
50
|
+
|
|
51
|
+
@end
|
|
52
|
+
|
|
53
|
+
@implementation FBWatchHTTPServer
|
|
54
|
+
|
|
55
|
+
- (instancetype)init
|
|
56
|
+
{
|
|
57
|
+
if ((self = [super init])) {
|
|
58
|
+
_routes = [NSMutableArray array];
|
|
59
|
+
_defaultHeaders = [NSMutableDictionary dictionary];
|
|
60
|
+
_connectionBuffers = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsOptions)(NSMapTableObjectPointerPersonality | NSMapTableStrongMemory)
|
|
61
|
+
valueOptions:(NSPointerFunctionsOptions)NSMapTableStrongMemory];
|
|
62
|
+
}
|
|
63
|
+
return self;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
- (void)setRouteQueue:(nullable dispatch_queue_t)queue
|
|
67
|
+
{
|
|
68
|
+
_routeQueue = queue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
- (void)setDefaultHeader:(NSString *)field value:(NSString *)value
|
|
72
|
+
{
|
|
73
|
+
self.defaultHeaders[field] = value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#pragma mark - Route registration
|
|
77
|
+
|
|
78
|
+
- (FBWatchHTTPRoute *)compiledRouteWithPath:(NSString *)path
|
|
79
|
+
{
|
|
80
|
+
FBWatchHTTPRoute *route = [FBWatchHTTPRoute new];
|
|
81
|
+
NSMutableArray<NSString *> *keys = [NSMutableArray array];
|
|
82
|
+
|
|
83
|
+
// Escape regex-significant characters before substituting :param placeholders, like
|
|
84
|
+
// RoutingHTTPServer.m does.
|
|
85
|
+
NSRegularExpression *escapeRegex = [NSRegularExpression regularExpressionWithPattern:@"[.+()]"
|
|
86
|
+
options:(NSRegularExpressionOptions)0
|
|
87
|
+
error:nil];
|
|
88
|
+
NSString *escapedPath = [escapeRegex stringByReplacingMatchesInString:path
|
|
89
|
+
options:(NSMatchingOptions)0
|
|
90
|
+
range:NSMakeRange(0, path.length)
|
|
91
|
+
withTemplate:@"\\\\$0"];
|
|
92
|
+
|
|
93
|
+
NSRegularExpression *paramRegex = [NSRegularExpression regularExpressionWithPattern:@"(:(\\w+)|\\*)"
|
|
94
|
+
options:(NSRegularExpressionOptions)0
|
|
95
|
+
error:nil];
|
|
96
|
+
NSMutableString *regexPath = [NSMutableString stringWithString:escapedPath];
|
|
97
|
+
__block NSInteger diff = 0;
|
|
98
|
+
[paramRegex enumerateMatchesInString:escapedPath
|
|
99
|
+
options:(NSMatchingOptions)0
|
|
100
|
+
range:NSMakeRange(0, escapedPath.length)
|
|
101
|
+
usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
|
|
102
|
+
NSRange replacementRange = NSMakeRange(diff + result.range.location, result.range.length);
|
|
103
|
+
NSString *capturedString = [escapedPath substringWithRange:result.range];
|
|
104
|
+
NSString *replacementString;
|
|
105
|
+
if ([capturedString isEqualToString:@"*"]) {
|
|
106
|
+
[keys addObject:@"wildcards"];
|
|
107
|
+
replacementString = @"(.*?)";
|
|
108
|
+
} else {
|
|
109
|
+
NSString *keyString = [escapedPath substringWithRange:[result rangeAtIndex:2]];
|
|
110
|
+
[keys addObject:keyString];
|
|
111
|
+
replacementString = @"([^/]+)";
|
|
112
|
+
}
|
|
113
|
+
[regexPath replaceCharactersInRange:replacementRange withString:replacementString];
|
|
114
|
+
diff += replacementString.length - result.range.length;
|
|
115
|
+
}];
|
|
116
|
+
|
|
117
|
+
NSString *anchoredPattern = [NSString stringWithFormat:@"^%@$", regexPath];
|
|
118
|
+
route.regex = [NSRegularExpression regularExpressionWithPattern:anchoredPattern
|
|
119
|
+
options:NSRegularExpressionCaseInsensitive
|
|
120
|
+
error:nil];
|
|
121
|
+
route.keys = keys.count > 0 ? keys.copy : nil;
|
|
122
|
+
return route;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
- (void)handleMethod:(NSString *)method
|
|
126
|
+
withPath:(NSString *)path
|
|
127
|
+
block:(void (^)(RouteRequest *request, RouteResponse *response))block
|
|
128
|
+
{
|
|
129
|
+
FBWatchHTTPRoute *route = [self compiledRouteWithPath:path];
|
|
130
|
+
route.verb = method.uppercaseString;
|
|
131
|
+
route.block = block;
|
|
132
|
+
[self.routes addObject:route];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
- (void)get:(NSString *)path withBlock:(void (^)(RouteRequest *request, RouteResponse *response))block
|
|
136
|
+
{
|
|
137
|
+
[self handleMethod:@"GET" withPath:path block:block];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#pragma mark - Lifecycle
|
|
141
|
+
|
|
142
|
+
- (BOOL)start:(NSError **)error
|
|
143
|
+
{
|
|
144
|
+
FBTCPSocket *socket = [[FBTCPSocket alloc] initWithPort:self.port];
|
|
145
|
+
socket.delegate = self;
|
|
146
|
+
if (![socket startWithError:error]) {
|
|
147
|
+
return NO;
|
|
148
|
+
}
|
|
149
|
+
self.socket = socket;
|
|
150
|
+
_isRunning = YES;
|
|
151
|
+
return YES;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
- (void)stop:(BOOL)immediately
|
|
155
|
+
{
|
|
156
|
+
[self.socket stop];
|
|
157
|
+
self.socket = nil;
|
|
158
|
+
@synchronized (self.connectionBuffers) {
|
|
159
|
+
[self.connectionBuffers removeAllObjects];
|
|
160
|
+
}
|
|
161
|
+
_isRunning = NO;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#pragma mark - FBTCPSocketDelegate
|
|
165
|
+
|
|
166
|
+
- (void)didClientConnect:(nw_connection_t)newClient
|
|
167
|
+
{
|
|
168
|
+
@synchronized (self.connectionBuffers) {
|
|
169
|
+
[self.connectionBuffers setObject:[NSMutableData data] forKey:newClient];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
- (void)didClientDisconnect:(nw_connection_t)client
|
|
174
|
+
{
|
|
175
|
+
@synchronized (self.connectionBuffers) {
|
|
176
|
+
[self.connectionBuffers removeObjectForKey:client];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
- (void)client:(nw_connection_t)client didReceiveData:(NSData *)data
|
|
181
|
+
{
|
|
182
|
+
NSMutableData *buffer;
|
|
183
|
+
@synchronized (self.connectionBuffers) {
|
|
184
|
+
buffer = [self.connectionBuffers objectForKey:client];
|
|
185
|
+
if (nil == buffer) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
[buffer appendData:data];
|
|
189
|
+
}
|
|
190
|
+
[self processBufferForClient:client];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#pragma mark - HTTP parsing
|
|
194
|
+
|
|
195
|
+
- (void)processBufferForClient:(nw_connection_t)client
|
|
196
|
+
{
|
|
197
|
+
while (YES) {
|
|
198
|
+
NSMutableData *buffer;
|
|
199
|
+
@synchronized (self.connectionBuffers) {
|
|
200
|
+
buffer = [self.connectionBuffers objectForKey:client];
|
|
201
|
+
}
|
|
202
|
+
if (nil == buffer) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
NSRange headerEndRange = [buffer rangeOfData:FBCRLFCRLFData() options:(NSDataSearchOptions)0 range:NSMakeRange(0, buffer.length)];
|
|
207
|
+
if (NSNotFound == headerEndRange.location) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
NSData *headerData = [buffer subdataWithRange:NSMakeRange(0, headerEndRange.location)];
|
|
212
|
+
NSString *headerString = [[NSString alloc] initWithData:headerData encoding:NSUTF8StringEncoding];
|
|
213
|
+
NSArray<NSString *> *lines = [headerString componentsSeparatedByString:@"\r\n"];
|
|
214
|
+
if (lines.count < 1) {
|
|
215
|
+
[self closeClient:client];
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
NSArray<NSString *> *requestLineParts = [lines.firstObject componentsSeparatedByString:@" "];
|
|
220
|
+
if (requestLineParts.count < 2) {
|
|
221
|
+
[self closeClient:client];
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
NSString *method = requestLineParts[0].uppercaseString;
|
|
225
|
+
NSString *pathAndQuery = requestLineParts[1];
|
|
226
|
+
|
|
227
|
+
NSMutableDictionary<NSString *, NSString *> *requestHeaders = [NSMutableDictionary dictionary];
|
|
228
|
+
for (NSUInteger i = 1; i < lines.count; i++) {
|
|
229
|
+
NSString *line = lines[i];
|
|
230
|
+
NSRange colonRange = [line rangeOfString:@":"];
|
|
231
|
+
if (NSNotFound == colonRange.location) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
NSString *name = [line substringToIndex:colonRange.location];
|
|
235
|
+
NSString *value = [[line substringFromIndex:colonRange.location + 1]
|
|
236
|
+
stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
|
|
237
|
+
requestHeaders[name.lowercaseString] = value;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
NSUInteger contentLength = (NSUInteger)requestHeaders[@"content-length"].integerValue;
|
|
241
|
+
if (contentLength > FBConfiguration.sharedInstance.httpRequestBodySizeLimit) {
|
|
242
|
+
// Mirrors FBHTTPConnection's maxRequestBodySize enforcement on iOS/tvOS. Closes the
|
|
243
|
+
// connection after responding, since the rest of the oversized body is still incoming.
|
|
244
|
+
RouteResponse *tooLarge = [RouteResponse new];
|
|
245
|
+
tooLarge.statusCode = kHTTPStatusCodeRequestEntityTooLarge;
|
|
246
|
+
[tooLarge respondWithString:@"Request Entity Too Large"];
|
|
247
|
+
[self writeResponse:tooLarge toClient:client thenCloseConnection:YES];
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
NSUInteger bodyStart = headerEndRange.location + headerEndRange.length;
|
|
251
|
+
NSUInteger totalRequestLength = bodyStart + contentLength;
|
|
252
|
+
if (buffer.length < totalRequestLength) {
|
|
253
|
+
// Wait for the rest of the body to arrive.
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
NSData *body = contentLength > 0 ? [buffer subdataWithRange:NSMakeRange(bodyStart, contentLength)] : [NSData data];
|
|
258
|
+
|
|
259
|
+
@synchronized (self.connectionBuffers) {
|
|
260
|
+
[buffer replaceBytesInRange:NSMakeRange(0, totalRequestLength) withBytes:NULL length:0];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
[self dispatchMethod:method pathAndQuery:pathAndQuery body:body client:client];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
- (void)dispatchMethod:(NSString *)method pathAndQuery:(NSString *)pathAndQuery body:(NSData *)body client:(nw_connection_t)client
|
|
268
|
+
{
|
|
269
|
+
NSURLComponents *requestTarget = [NSURLComponents componentsWithString:pathAndQuery];
|
|
270
|
+
NSString *path = requestTarget.path ?: pathAndQuery;
|
|
271
|
+
|
|
272
|
+
for (FBWatchHTTPRoute *route in self.routes) {
|
|
273
|
+
if (![route.verb isEqualToString:method]) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
NSTextCheckingResult *result = [route.regex firstMatchInString:path options:(NSMatchingOptions)0 range:NSMakeRange(0, path.length)];
|
|
277
|
+
if (nil == result) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
NSMutableDictionary<NSString *, NSString *> *params = [NSMutableDictionary dictionary];
|
|
282
|
+
for (NSURLQueryItem *queryItem in requestTarget.queryItems) {
|
|
283
|
+
params[queryItem.name] = queryItem.value ?: @"";
|
|
284
|
+
}
|
|
285
|
+
if (route.keys.count > 0 && result.numberOfRanges == route.keys.count + 1) {
|
|
286
|
+
NSUInteger index = 1;
|
|
287
|
+
for (NSString *key in route.keys) {
|
|
288
|
+
params[key] = [path substringWithRange:[result rangeAtIndex:index]];
|
|
289
|
+
index++;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
NSURL *url = [NSURL URLWithString:path] ?: [NSURL URLWithString:@"/"];
|
|
294
|
+
RouteRequest *request = [[RouteRequest alloc] initWithURL:url params:params.copy body:body];
|
|
295
|
+
RouteResponse *response = [RouteResponse new];
|
|
296
|
+
[self.defaultHeaders enumerateKeysAndObjectsUsingBlock:^(NSString *field, NSString *value, BOOL *stop) {
|
|
297
|
+
[response setHeader:field value:value];
|
|
298
|
+
}];
|
|
299
|
+
|
|
300
|
+
void (^invoke)(void) = ^{
|
|
301
|
+
route.block(request, response);
|
|
302
|
+
[self writeResponse:response toClient:client];
|
|
303
|
+
};
|
|
304
|
+
dispatch_queue_t routeQueue = self.routeQueue;
|
|
305
|
+
if (routeQueue) {
|
|
306
|
+
dispatch_async((dispatch_queue_t _Nonnull)routeQueue, invoke);
|
|
307
|
+
} else {
|
|
308
|
+
invoke();
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
RouteResponse *notFound = [RouteResponse new];
|
|
314
|
+
notFound.statusCode = kHTTPStatusCodeNotFound;
|
|
315
|
+
[notFound respondWithString:@"Not Found"];
|
|
316
|
+
[self writeResponse:notFound toClient:client];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
- (void)writeResponse:(RouteResponse *)response toClient:(nw_connection_t)client
|
|
320
|
+
{
|
|
321
|
+
[self writeResponse:response toClient:client thenCloseConnection:NO];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
- (void)writeResponse:(RouteResponse *)response toClient:(nw_connection_t)client thenCloseConnection:(BOOL)shouldClose
|
|
325
|
+
{
|
|
326
|
+
NSMutableData *payload = [NSMutableData data];
|
|
327
|
+
NSString *statusLine = [NSString stringWithFormat:@"HTTP/1.1 %ld %@\r\n",
|
|
328
|
+
(long)response.statusCode, [self reasonPhraseForStatusCode:response.statusCode]];
|
|
329
|
+
[payload appendData:FBUTF8Data(statusLine)];
|
|
330
|
+
|
|
331
|
+
NSData *body = response.responseData ?: [NSData data];
|
|
332
|
+
NSMutableDictionary<NSString *, NSString *> *headers = response.headers.mutableCopy;
|
|
333
|
+
if (nil == headers[@"Content-Length"]) {
|
|
334
|
+
headers[@"Content-Length"] = [NSString stringWithFormat:@"%lu", (unsigned long)body.length];
|
|
335
|
+
}
|
|
336
|
+
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *field, NSString *value, BOOL *stop) {
|
|
337
|
+
NSString *headerLine = [NSString stringWithFormat:@"%@: %@\r\n", field, value];
|
|
338
|
+
[payload appendData:FBUTF8Data(headerLine)];
|
|
339
|
+
}];
|
|
340
|
+
[payload appendData:FBUTF8Data(@"\r\n")];
|
|
341
|
+
[payload appendData:body];
|
|
342
|
+
|
|
343
|
+
if (shouldClose) {
|
|
344
|
+
__weak typeof(self) weakSelf = self;
|
|
345
|
+
[self.socket writeData:payload toClient:client completion:^{
|
|
346
|
+
[weakSelf closeClient:client];
|
|
347
|
+
}];
|
|
348
|
+
} else {
|
|
349
|
+
[self.socket writeData:payload toClient:client];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
- (void)closeClient:(nw_connection_t)client
|
|
354
|
+
{
|
|
355
|
+
@synchronized (self.connectionBuffers) {
|
|
356
|
+
[self.connectionBuffers removeObjectForKey:client];
|
|
357
|
+
}
|
|
358
|
+
nw_connection_cancel(client);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
- (NSString *)reasonPhraseForStatusCode:(HTTPStatusCode)statusCode
|
|
362
|
+
{
|
|
363
|
+
// if/else, not switch, to avoid having to list all ~90 HTTPStatusCode cases for -Wswitch-enum.
|
|
364
|
+
if (kHTTPStatusCodeOK == statusCode) {
|
|
365
|
+
return @"OK";
|
|
366
|
+
} else if (kHTTPStatusCodeBadRequest == statusCode) {
|
|
367
|
+
return @"Bad Request";
|
|
368
|
+
} else if (kHTTPStatusCodeNotFound == statusCode) {
|
|
369
|
+
return @"Not Found";
|
|
370
|
+
} else if (kHTTPStatusCodeInternalServerError == statusCode) {
|
|
371
|
+
return @"Internal Server Error";
|
|
372
|
+
}
|
|
373
|
+
return @"Status";
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Minimal, watchOS-only stand-in for Vendor/RoutingHTTPServer/RouteRequest.h, which is not
|
|
10
|
+
// available on watchOS because RoutingHTTPServer/CocoaHTTPServer/CocoaAsyncSocket cannot be
|
|
11
|
+
// built there (see FBWatchHTTPServer.h). Exposes just the surface FBWebServer's route blocks
|
|
12
|
+
// and FBRoute.decorateRequest: read.
|
|
13
|
+
|
|
14
|
+
@import Foundation;
|
|
15
|
+
|
|
16
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
17
|
+
|
|
18
|
+
@interface RouteRequest : NSObject
|
|
19
|
+
|
|
20
|
+
@property (nonatomic, copy, readonly) NSURL *url;
|
|
21
|
+
@property (nonatomic, copy, readonly) NSDictionary<NSString *, NSString *> *params;
|
|
22
|
+
@property (nonatomic, copy, readonly) NSData *body;
|
|
23
|
+
|
|
24
|
+
- (instancetype)initWithURL:(NSURL *)url
|
|
25
|
+
params:(NSDictionary<NSString *, NSString *> *)params
|
|
26
|
+
body:(NSData *)body;
|
|
27
|
+
|
|
28
|
+
@end
|
|
29
|
+
|
|
30
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import "RouteRequest.h"
|
|
10
|
+
|
|
11
|
+
@implementation RouteRequest
|
|
12
|
+
|
|
13
|
+
- (instancetype)initWithURL:(NSURL *)url
|
|
14
|
+
params:(NSDictionary<NSString *, NSString *> *)params
|
|
15
|
+
body:(NSData *)body
|
|
16
|
+
{
|
|
17
|
+
if ((self = [super init])) {
|
|
18
|
+
_url = url.copy;
|
|
19
|
+
_params = params.copy;
|
|
20
|
+
_body = body.copy;
|
|
21
|
+
}
|
|
22
|
+
return self;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Minimal, watchOS-only stand-in for Vendor/RoutingHTTPServer/RouteResponse.h, reproducing just
|
|
10
|
+
// what FBRoute.m/FBResponseJSONPayload.m call on it. See FBWatchHTTPServer.h.
|
|
11
|
+
|
|
12
|
+
@import Foundation;
|
|
13
|
+
#import <WebDriverAgentLib/FBHTTPStatusCodes.h>
|
|
14
|
+
|
|
15
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
16
|
+
|
|
17
|
+
@interface RouteResponse : NSObject
|
|
18
|
+
|
|
19
|
+
@property (nonatomic) HTTPStatusCode statusCode;
|
|
20
|
+
@property (nonatomic, copy, readonly) NSDictionary<NSString *, NSString *> *headers;
|
|
21
|
+
@property (nonatomic, copy, readonly, nullable) NSData *responseData;
|
|
22
|
+
|
|
23
|
+
- (void)setHeader:(NSString *)field value:(NSString *)value;
|
|
24
|
+
- (void)respondWithString:(NSString *)string;
|
|
25
|
+
- (void)respondWithString:(NSString *)string encoding:(NSStringEncoding)encoding;
|
|
26
|
+
- (void)respondWithData:(NSData *)data;
|
|
27
|
+
|
|
28
|
+
@end
|
|
29
|
+
|
|
30
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2015-present, Facebook, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the BSD-style license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#import "RouteResponse.h"
|
|
10
|
+
|
|
11
|
+
@interface RouteResponse ()
|
|
12
|
+
@property (nonatomic, copy) NSMutableDictionary<NSString *, NSString *> *mutableHeaders;
|
|
13
|
+
@end
|
|
14
|
+
|
|
15
|
+
@implementation RouteResponse
|
|
16
|
+
|
|
17
|
+
- (instancetype)init
|
|
18
|
+
{
|
|
19
|
+
if ((self = [super init])) {
|
|
20
|
+
_statusCode = kHTTPStatusCodeOK;
|
|
21
|
+
_mutableHeaders = [NSMutableDictionary dictionary];
|
|
22
|
+
}
|
|
23
|
+
return self;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- (NSDictionary<NSString *, NSString *> *)headers
|
|
27
|
+
{
|
|
28
|
+
return self.mutableHeaders.copy;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
- (void)setHeader:(NSString *)field value:(NSString *)value
|
|
32
|
+
{
|
|
33
|
+
self.mutableHeaders[field] = value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
- (void)respondWithData:(NSData *)data
|
|
37
|
+
{
|
|
38
|
+
_responseData = data.copy;
|
|
39
|
+
if (nil == self.mutableHeaders[@"Content-Length"]) {
|
|
40
|
+
self.mutableHeaders[@"Content-Length"] = [NSString stringWithFormat:@"%lu", (unsigned long)data.length];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (void)respondWithString:(NSString *)string
|
|
45
|
+
{
|
|
46
|
+
[self respondWithString:string encoding:NSUTF8StringEncoding];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
- (void)respondWithString:(NSString *)string encoding:(NSStringEncoding)encoding
|
|
50
|
+
{
|
|
51
|
+
[self respondWithData:[string dataUsingEncoding:encoding] ?: [NSData data]];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@end
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
#import "FBActiveAppDetectionPoint.h"
|
|
10
10
|
|
|
11
|
+
#if TARGET_OS_WATCH
|
|
12
|
+
@import WatchKit;
|
|
13
|
+
#endif
|
|
14
|
+
|
|
11
15
|
#import "FBErrorBuilder.h"
|
|
12
16
|
#import "FBLogger.h"
|
|
13
17
|
#import "FBXCTestDaemonsProxy.h"
|
|
@@ -17,7 +21,11 @@
|
|
|
17
21
|
|
|
18
22
|
- (instancetype)init {
|
|
19
23
|
if ((self = [super init])) {
|
|
24
|
+
#if TARGET_OS_WATCH
|
|
25
|
+
CGSize screenSize = WKInterfaceDevice.currentDevice.screenBounds.size;
|
|
26
|
+
#else
|
|
20
27
|
CGSize screenSize = [UIScreen mainScreen].bounds.size;
|
|
28
|
+
#endif
|
|
21
29
|
// Consider the element, which is located close to the top left corner of the screen the on-screen one.
|
|
22
30
|
CGFloat pointDistance = MIN(screenSize.width, screenSize.height) * (CGFloat) 0.2;
|
|
23
31
|
_coordinates = CGPointMake(pointDistance, pointDistance);
|
|
@@ -37,7 +37,7 @@ static NSString *const axSettingsClassName = @"AXSettings";
|
|
|
37
37
|
@interface FBConfiguration ()
|
|
38
38
|
|
|
39
39
|
@property (atomic, strong) NSNumber *maxTypingFrequencyOverride;
|
|
40
|
-
#if !TARGET_OS_TV
|
|
40
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
41
41
|
@property (atomic, assign) UIInterfaceOrientation screenshotOrientationStorage;
|
|
42
42
|
#endif
|
|
43
43
|
|
|
@@ -292,7 +292,7 @@ static NSString *const axSettingsClassName = @"AXSettings";
|
|
|
292
292
|
return [FBGetCustomParameterForElementSnapshot(FBSnapshotMaxChildrenKey) intValue];
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
#if !TARGET_OS_TV
|
|
295
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
296
296
|
- (BOOL)setScreenshotOrientation:(NSString *)orientation error:(NSError **)error
|
|
297
297
|
{
|
|
298
298
|
// Only UIInterfaceOrientationUnknown is over iOS 8. Others are over iOS 2.
|
|
@@ -359,7 +359,7 @@ static NSString *const axSettingsClassName = @"AXSettings";
|
|
|
359
359
|
FBSetCustomParameterForElementSnapshot(FBSnapshotMaxChildrenKey, @INT_MAX);
|
|
360
360
|
self.useClearTextShortcut = YES;
|
|
361
361
|
self.limitXpathContextScope = YES;
|
|
362
|
-
#if !TARGET_OS_TV
|
|
362
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
363
363
|
self.screenshotOrientationStorage = UIInterfaceOrientationUnknown;
|
|
364
364
|
#endif
|
|
365
365
|
}
|
|
@@ -109,8 +109,12 @@ const CGFloat FBMaxCompressionQuality = 1.0f;
|
|
|
109
109
|
desiredOrientation:(nullable NSNumber *)orientation
|
|
110
110
|
{
|
|
111
111
|
scalingFactor = MAX(FBMinScalingFactor, MIN(FBMaxScalingFactor, scalingFactor));
|
|
112
|
-
BOOL usesScaling = scalingFactor > 0.0 && scalingFactor < FBMaxScalingFactor;
|
|
113
112
|
@autoreleasepool {
|
|
113
|
+
#if TARGET_OS_WATCH
|
|
114
|
+
// UIGraphicsImageRenderer is unavailable and the watch screen doesn't rotate, so skip both.
|
|
115
|
+
return [uti conformsToType:UTTypePNG] ? FBToPngData(imageData) : FBToJpegData(imageData, compressionQuality);
|
|
116
|
+
#else
|
|
117
|
+
BOOL usesScaling = scalingFactor > 0.0 && scalingFactor < FBMaxScalingFactor;
|
|
114
118
|
if (!usesScaling && !fixOrientation) {
|
|
115
119
|
return [uti conformsToType:UTTypePNG] ? FBToPngData(imageData) : FBToJpegData(imageData, compressionQuality);
|
|
116
120
|
}
|
|
@@ -157,6 +161,7 @@ const CGFloat FBMaxCompressionQuality = 1.0f;
|
|
|
157
161
|
actions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
|
|
158
162
|
[uiImage drawInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height)];
|
|
159
163
|
}];
|
|
164
|
+
#endif
|
|
160
165
|
}
|
|
161
166
|
}
|
|
162
167
|
|
|
@@ -167,7 +172,7 @@ const CGFloat FBMaxCompressionQuality = 1.0f;
|
|
|
167
172
|
error:(NSError **)error
|
|
168
173
|
{
|
|
169
174
|
NSNumber *orientation = nil;
|
|
170
|
-
#if !TARGET_OS_TV
|
|
175
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
171
176
|
if (FBConfiguration.sharedInstance.screenshotOrientation == UIInterfaceOrientationPortrait) {
|
|
172
177
|
orientation = @(UIImageOrientationUp);
|
|
173
178
|
} else if (FBConfiguration.sharedInstance.screenshotOrientation == UIInterfaceOrientationPortraitUpsideDown) {
|
|
@@ -42,12 +42,21 @@
|
|
|
42
42
|
typeof(arg) arg = wda_weak_##arg \
|
|
43
43
|
_Pragma("clang diagnostic pop")
|
|
44
44
|
|
|
45
|
+
/*! Current OS version as a dotted string, e.g. "17.4". UIDevice.systemVersion is unavailable on
|
|
46
|
+
watchOS, so this is sourced from NSProcessInfo, which works identically on every platform. */
|
|
47
|
+
NS_INLINE NSString *FBSystemVersion(void) {
|
|
48
|
+
NSOperatingSystemVersion v = NSProcessInfo.processInfo.operatingSystemVersion;
|
|
49
|
+
return 0 == v.patchVersion
|
|
50
|
+
? [NSString stringWithFormat:@"%ld.%ld", (long)v.majorVersion, (long)v.minorVersion]
|
|
51
|
+
: [NSString stringWithFormat:@"%ld.%ld.%ld", (long)v.majorVersion, (long)v.minorVersion, (long)v.patchVersion];
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
/*! Returns YES if current system version satisfies the given codition */
|
|
46
|
-
#define SYSTEM_VERSION_EQUAL_TO(v) ([
|
|
47
|
-
#define SYSTEM_VERSION_GREATER_THAN(v) ([
|
|
48
|
-
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([
|
|
49
|
-
#define SYSTEM_VERSION_LESS_THAN(v) ([
|
|
50
|
-
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([
|
|
55
|
+
#define SYSTEM_VERSION_EQUAL_TO(v) ([FBSystemVersion() compare:v options:NSNumericSearch] == NSOrderedSame)
|
|
56
|
+
#define SYSTEM_VERSION_GREATER_THAN(v) ([FBSystemVersion() compare:v options:NSNumericSearch] == NSOrderedDescending)
|
|
57
|
+
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([FBSystemVersion() compare:v options:NSNumericSearch] != NSOrderedAscending)
|
|
58
|
+
#define SYSTEM_VERSION_LESS_THAN(v) ([FBSystemVersion() compare:v options:NSNumericSearch] == NSOrderedAscending)
|
|
59
|
+
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([FBSystemVersion() compare:v options:NSNumericSearch] != NSOrderedDescending)
|
|
51
60
|
|
|
52
61
|
/*! Converts the given number of milliseconds into seconds */
|
|
53
62
|
#define FBMillisToSeconds(ms) ((ms) / 1000.0)
|
|
@@ -30,7 +30,7 @@ BOOL FBSizeFuzzyEqualToSize(CGSize size1, CGSize size2, CGFloat threshold);
|
|
|
30
30
|
/*! Returns whether rect are equal within given threshold */
|
|
31
31
|
BOOL FBRectFuzzyEqualToRect(CGRect rect1, CGRect rect2, CGFloat threshold);
|
|
32
32
|
|
|
33
|
-
#if !TARGET_OS_TV
|
|
33
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
34
34
|
/*! Inverts size if necessary to match current screen orientation */
|
|
35
35
|
CGSize FBAdjustDimensionsForApplication(CGSize actualSize, UIInterfaceOrientation orientation);
|
|
36
36
|
#endif
|
|
@@ -44,7 +44,7 @@ BOOL FBRectFuzzyEqualToRect(CGRect rect1, CGRect rect2, CGFloat threshold)
|
|
|
44
44
|
FBSizeFuzzyEqualToSize(rect1.size, rect2.size, threshold);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
#if !TARGET_OS_TV
|
|
47
|
+
#if !TARGET_OS_TV && !TARGET_OS_WATCH
|
|
48
48
|
|
|
49
49
|
CGSize FBAdjustDimensionsForApplication(CGSize actualSize, UIInterfaceOrientation orientation)
|
|
50
50
|
{
|