+2017-04-18 Wenson Hsieh <wenson_hsieh@apple.com>
+
+ [WK2] Add infrastructure and unit tests for file uploads using data interaction
+ https://bugs.webkit.org/show_bug.cgi?id=170903
+ <rdar://problem/31314689>
+
+ Reviewed by Tim Horton.
+
+ Adds 5 new unit tests covering different cases of uploading files through data interaction, as well as
+ infrastructure for simulating UIItemProviders that load file data. Makes a few adjustments to the
+ DataInteractionSimulator along the way, detailed in the per-method annotations below. See
+ <https://bugs.webkit.org/show_bug.cgi?id=170880> for more details about the change this patch is testing.
+
+ New tests:
+ DataInteractionTests.ExternalSourceImageToFileInput
+ DataInteractionTests.ExternalSourceHTMLToUploadArea
+ DataInteractionTests.ExternalSourceImageAndHTMLToSingleFileInput
+ DataInteractionTests.ExternalSourceImageAndHTMLToMultipleFileInput
+ DataInteractionTests.ExternalSourceImageAndHTMLToUploadArea
+
+ * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
+ (testIconImage):
+ (temporaryURLForDataInteractionFileLoad):
+ (cleanUpDataInteractionTemporaryPath):
+
+ Creates and tears down temporary file directories for testing data interaction.
+
+ (-[UIItemProvider registerFileRepresentationForTypeIdentifier:withData:filename:]):
+ (TestWebKitAPI::TEST):
+ * TestWebKitAPI/ios/DataInteractionSimulator.h:
+ * TestWebKitAPI/ios/DataInteractionSimulator.mm:
+
+ Make necessary changes to be able to test what happens when data interaction ends over an element with no
+ operation. Previously, we would always simulate performing a data interaction operation when ending the
+ simulation, but this causes us to wait indefinitely for a data operation response to arrive in the UI process.
+ Instead, we need to note whether or not the content view is allowing data interaction, and only perform an
+ operation and wait for the -didPerform call if the operation was allowed. Otherwise, we immediately transition
+ the phase to Cancelled and end the run.
+
+ (-[DataInteractionSimulator _resetSimulatedState]):
+ (-[DataInteractionSimulator runFrom:to:]):
+ (-[DataInteractionSimulator _concludeDataInteractionAndPerformOperationIfNecessary]):
+ (-[DataInteractionSimulator _advanceProgress]):
+ (-[DataInteractionSimulator externalItemProviders]):
+ (-[DataInteractionSimulator setExternalItemProviders:]):
+
+ Previously, we hard-coded DataInteractionSimulator to only support a single external item provider. In order to
+ test the scenario where multiple files are being "data interacted" into a file-type input, we generalize this to
+ take multiple item providers.
+
+ (-[DataInteractionSimulator externalItemProvider]): Deleted.
+ (-[DataInteractionSimulator setExternalItemProvider:]): Deleted.
+
2017-04-18 John Wilander <wilander@apple.com>
Make WebCore::topPrivatelyControlledDomain() return "localhost" for localhost
#import <WebKit/WKWebViewConfigurationPrivate.h>
#import <WebKit/_WKProcessPoolConfiguration.h>
+typedef void (^FileLoadCompletionBlock)(NSURL *, BOOL, NSError *);
+
+static UIImage *testIconImage()
+{
+ return [UIImage imageNamed:@"TestWebKitAPI.resources/icon.png"];
+}
+
+static NSURL *temporaryURLForDataInteractionFileLoad(NSString *temporaryFileName)
+{
+ NSString *temporaryDirectoryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"data-interaction"];
+ if (![[NSFileManager defaultManager] fileExistsAtPath:temporaryDirectoryPath])
+ [[NSFileManager defaultManager] createDirectoryAtPath:temporaryDirectoryPath withIntermediateDirectories:YES attributes:nil error:nil];
+ return [NSURL fileURLWithPath:[temporaryDirectoryPath stringByAppendingPathComponent:temporaryFileName]];
+}
+
+static void cleanUpDataInteractionTemporaryPath()
+{
+ NSArray *temporaryDirectoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[NSURL fileURLWithPath:NSTemporaryDirectory()] includingPropertiesForKeys:nil options:0 error:nil];
+ for (NSURL *url in temporaryDirectoryContents) {
+ if ([url.lastPathComponent rangeOfString:@"data-interaction"].location != NSNotFound)
+ [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
+ }
+}
+
+@implementation UIItemProvider (DataInteractionTests)
+
+- (void)registerFileRepresentationForTypeIdentifier:(NSString *)typeIdentifier withData:(NSData *)data filename:(NSString *)filename
+{
+ RetainPtr<NSData> retainedData = data;
+ RetainPtr<NSURL> retainedTemporaryURL = temporaryURLForDataInteractionFileLoad(filename);
+ [self registerFileRepresentationForTypeIdentifier:typeIdentifier fileOptions:0 visibility:NSItemProviderRepresentationVisibilityAll loadHandler: [retainedData, retainedTemporaryURL] (FileLoadCompletionBlock block) -> NSProgress * {
+ [retainedData writeToFile:[retainedTemporaryURL path] atomically:YES];
+ dispatch_async(dispatch_get_main_queue(), [retainedTemporaryURL, capturedBlock = makeBlockPtr(block)] {
+ capturedBlock(retainedTemporaryURL.get(), NO, nil);
+ });
+ return nil;
+ }];
+}
+
+@end
+
@implementation TestWKWebView (DataInteractionTests)
- (BOOL)editorContainsImageElement
checkSelectionRectsWithLogging(@[ ], [dataInteractionSimulator finalSelectionRects]);
}
+TEST(DataInteractionTests, ExternalSourceImageToFileInput)
+{
+ RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+ [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
+
+ RetainPtr<UIItemProvider> simulatedImageItemProvider = adoptNS([[UIItemProvider alloc] init]);
+ NSData *imageData = UIImageJPEGRepresentation(testIconImage(), 0.5);
+ [simulatedImageItemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeJPEG withData:imageData filename:@"image.png"];
+
+ RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+ [dataInteractionSimulator setExternalItemProviders:@[ simulatedImageItemProvider.get() ]];
+ [dataInteractionSimulator runFrom:CGPointMake(200, 100) to:CGPointMake(100, 100)];
+
+ NSString *outputValue = [webView stringByEvaluatingJavaScript:@"output.value"];
+ EXPECT_WK_STREQ("image/jpeg", outputValue.UTF8String);
+
+ cleanUpDataInteractionTemporaryPath();
+}
+
+TEST(DataInteractionTests, ExternalSourceHTMLToUploadArea)
+{
+ RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+ [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
+
+ RetainPtr<UIItemProvider> simulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
+ NSData *htmlData = [@"<body contenteditable></body>" dataUsingEncoding:NSUTF8StringEncoding];
+ [simulatedHTMLItemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML withData:htmlData filename:@"index.html"];
+
+ RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+ [dataInteractionSimulator setExternalItemProviders:@[ simulatedHTMLItemProvider.get() ]];
+ [dataInteractionSimulator runFrom:CGPointMake(200, 300) to:CGPointMake(100, 300)];
+
+ NSString *outputValue = [webView stringByEvaluatingJavaScript:@"output.value"];
+ EXPECT_WK_STREQ("text/html", outputValue.UTF8String);
+
+ cleanUpDataInteractionTemporaryPath();
+}
+
+TEST(DataInteractionTests, ExternalSourceImageAndHTMLToSingleFileInput)
+{
+ RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+ [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
+
+ RetainPtr<UIItemProvider> simulatedImageItemProvider = adoptNS([[UIItemProvider alloc] init]);
+ NSData *imageData = UIImageJPEGRepresentation(testIconImage(), 0.5);
+ [simulatedImageItemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeJPEG withData:imageData filename:@"image.png"];
+
+ RetainPtr<UIItemProvider> simulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
+ NSData *htmlData = [@"<body contenteditable></body>" dataUsingEncoding:NSUTF8StringEncoding];
+ [simulatedHTMLItemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML withData:htmlData filename:@"index.html"];
+
+ RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+ [dataInteractionSimulator setExternalItemProviders:@[ simulatedHTMLItemProvider.get(), simulatedImageItemProvider.get() ]];
+ [dataInteractionSimulator runFrom:CGPointMake(200, 100) to:CGPointMake(100, 100)];
+
+ NSString *outputValue = [webView stringByEvaluatingJavaScript:@"output.value"];
+ EXPECT_WK_STREQ("", outputValue.UTF8String);
+
+ cleanUpDataInteractionTemporaryPath();
+}
+
+TEST(DataInteractionTests, ExternalSourceImageAndHTMLToMultipleFileInput)
+{
+ RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+ [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
+ [webView stringByEvaluatingJavaScript:@"input.setAttribute('multiple', '')"];
+
+ RetainPtr<UIItemProvider> simulatedImageItemProvider = adoptNS([[UIItemProvider alloc] init]);
+ NSData *imageData = UIImageJPEGRepresentation(testIconImage(), 0.5);
+ [simulatedImageItemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeJPEG withData:imageData filename:@"image.png"];
+
+ RetainPtr<UIItemProvider> simulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
+ NSData *htmlData = [@"<body contenteditable></body>" dataUsingEncoding:NSUTF8StringEncoding];
+ [simulatedHTMLItemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML withData:htmlData filename:@"index.html"];
+
+ RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+ [dataInteractionSimulator setExternalItemProviders:@[ simulatedHTMLItemProvider.get(), simulatedImageItemProvider.get() ]];
+ [dataInteractionSimulator runFrom:CGPointMake(200, 100) to:CGPointMake(100, 100)];
+
+ NSString *outputValue = [webView stringByEvaluatingJavaScript:@"output.value"];
+ EXPECT_WK_STREQ("image/jpeg, text/html", outputValue.UTF8String);
+
+ cleanUpDataInteractionTemporaryPath();
+}
+
+TEST(DataInteractionTests, ExternalSourceImageAndHTMLToUploadArea)
+{
+ RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+ [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
+
+ RetainPtr<UIItemProvider> simulatedImageItemProvider = adoptNS([[UIItemProvider alloc] init]);
+ NSData *imageData = UIImageJPEGRepresentation(testIconImage(), 0.5);
+ [simulatedImageItemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeJPEG withData:imageData filename:@"image.png"];
+
+ RetainPtr<UIItemProvider> firstSimulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
+ NSData *firstHTMLData = [@"<body contenteditable></body>" dataUsingEncoding:NSUTF8StringEncoding];
+ [firstSimulatedHTMLItemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML withData:firstHTMLData filename:@"index.html"];
+
+ RetainPtr<UIItemProvider> secondSimulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
+ NSData *secondHTMLData = [@"<html><body>hello world</body></html>" dataUsingEncoding:NSUTF8StringEncoding];
+ [secondSimulatedHTMLItemProvider registerFileRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML withData:secondHTMLData filename:@"index.html"];
+
+ RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+ [dataInteractionSimulator setExternalItemProviders:@[ simulatedImageItemProvider.get(), firstSimulatedHTMLItemProvider.get(), secondSimulatedHTMLItemProvider.get() ]];
+ [dataInteractionSimulator runFrom:CGPointMake(200, 300) to:CGPointMake(100, 300)];
+
+ NSString *outputValue = [webView stringByEvaluatingJavaScript:@"output.value"];
+ EXPECT_WK_STREQ("image/jpeg, text/html, text/html", outputValue.UTF8String);
+
+ cleanUpDataInteractionTemporaryPath();
+}
+
TEST(DataInteractionTests, ExternalSourceUTF8PlainTextOnly)
{
RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
completionBlock([textPayload dataUsingEncoding:NSUTF8StringEncoding], nil);
return [NSProgress discreteProgressWithTotalUnitCount:100];
}];
- [dataInteractionSimulator setExternalItemProvider:simulatedItemProvider.get()];
+ [dataInteractionSimulator setExternalItemProviders:@[ simulatedItemProvider.get() ]];
[dataInteractionSimulator runFrom:CGPointMake(300, 400) to:CGPointMake(100, 300)];
EXPECT_WK_STREQ(textPayload.UTF8String, [webView stringByEvaluatingJavaScript:@"editor.textContent"].UTF8String);
checkSelectionRectsWithLogging(@[ makeCGRectValue(1, 201, 1936, 227) ], [dataInteractionSimulator finalSelectionRects]);
RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
[webView synchronouslyLoadTestPageNamed:@"autofocus-contenteditable"];
- UIImage *testImage = [UIImage imageNamed:@"TestWebKitAPI.resources/icon.png"];
RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
RetainPtr<UIItemProvider> simulatedItemProvider = adoptNS([[UIItemProvider alloc] init]);
[simulatedItemProvider registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypeJPEG options:nil loadHandler:^NSProgress *(UIItemProviderDataLoadCompletionBlock completionBlock)
{
- completionBlock(UIImageJPEGRepresentation(testImage, 0.5), nil);
+ completionBlock(UIImageJPEGRepresentation(testIconImage(), 0.5), nil);
return [NSProgress discreteProgressWithTotalUnitCount:100];
}];
- [dataInteractionSimulator setExternalItemProvider:simulatedItemProvider.get()];
+ [dataInteractionSimulator setExternalItemProviders:@[ simulatedItemProvider.get() ]];
[dataInteractionSimulator runFrom:CGPointMake(300, 400) to:CGPointMake(100, 300)];
EXPECT_TRUE([webView editorContainsImageElement]);
checkSelectionRectsWithLogging(@[ makeCGRectValue(1, 201, 215, 174) ], [dataInteractionSimulator finalSelectionRects]);
_finalSelectionRects = @[ ];
_dataInteractionSession = nil;
_dataOperationSession = nil;
+ _shouldPerformOperation = NO;
}
- (NSArray *)observedEventNames
_startLocation = startLocation;
_endLocation = endLocation;
- if (self.externalItemProvider) {
- _dataOperationSession = adoptNS([[MockDataOperationSession alloc] initWithProvider:self.externalItemProvider location:_startLocation window:[_webView window]]);
+ if (self.externalItemProviders.count) {
+ _dataOperationSession = adoptNS([[MockDataOperationSession alloc] initWithProviders:self.externalItemProviders location:_startLocation window:[_webView window]]);
_phase = DataInteractionBegan;
[self _advanceProgress];
} else {
return _finalSelectionRects.get();
}
+- (void)_concludeDataInteractionAndPerformOperationIfNecessary
+{
+ if (_shouldPerformOperation) {
+ [_webView _simulateDataInteractionPerformOperation:_dataOperationSession.get()];
+ _phase = DataInteractionPerforming;
+ } else {
+ _isDoneWithCurrentRun = YES;
+ _phase = DataInteractionCancelled;
+ }
+
+ [_webView _simulateDataInteractionEnded:_dataOperationSession.get()];
+
+ if (_dataInteractionSession)
+ [_webView _simulateDataInteractionSessionDidEnd:_dataInteractionSession.get()];
+}
+
- (void)_advanceProgress
{
_currentProgress += progressIncrementStep;
[_dataOperationSession setMockLocationInWindow:locationInWindow];
if (_currentProgress >= 1) {
- [_webView _simulateDataInteractionPerformOperation:_dataOperationSession.get()];
- [_webView _simulateDataInteractionEnded:_dataOperationSession.get()];
- if (_dataInteractionSession)
- [_webView _simulateDataInteractionSessionDidEnd:_dataInteractionSession.get()];
-
- _phase = DataInteractionPerforming;
_currentProgress = 1;
+ [self _concludeDataInteractionAndPerformOperationIfNecessary];
return;
}
for (WKDataInteractionItem *item in items)
[itemProviders addObject:item.itemProvider];
- _dataOperationSession = adoptNS([[MockDataOperationSession alloc] initWithProvider:itemProviders.firstObject location:self._currentLocation window:[_webView window]]);
+ _dataOperationSession = adoptNS([[MockDataOperationSession alloc] initWithProviders:itemProviders location:self._currentLocation window:[_webView window]]);
[_dataInteractionSession setItems:items];
_sourceItemProviders = itemProviders;
if (self.showCustomActionSheetBlock) {
_phase = DataInteractionEntered;
break;
case DataInteractionEntered:
- [_webView _simulateDataInteractionUpdated:_dataOperationSession.get()];
+ _shouldPerformOperation = [_webView _simulateDataInteractionUpdated:_dataOperationSession.get()];
break;
default:
break;
return _sourceItemProviders.get();
}
-- (UIItemProvider *)externalItemProvider
+- (NSArray *)externalItemProviders
{
- return _externalItemProvider.get();
+ return _externalItemProviders.get();
}
-- (void)setExternalItemProvider:(UIItemProvider *)externalItemProvider
+- (void)setExternalItemProviders:(NSArray *)externalItemProviders
{
- _externalItemProvider = externalItemProvider;
+ _externalItemProviders = adoptNS([externalItemProviders copy]);
}
- (DataInteractionPhase)phase