Web Automation: support uploading non-local file paths
authorbburg@apple.com <bburg@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 21 Mar 2019 22:54:25 +0000 (22:54 +0000)
committerbburg@apple.com <bburg@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 21 Mar 2019 22:54:25 +0000 (22:54 +0000)
https://bugs.webkit.org/show_bug.cgi?id=196081
<rdar://problem/45819897>

Reviewed by Devin Rousso and Joseph Pecoraro.

To support cases where supplied file paths do not exist on the session host, add support for
receiving file contents via Automation.setFilesToSelectForFileUpload.

* UIProcess/Automation/Automation.json: Add new parameter.

* UIProcess/Automation/WebAutomationSession.h:
* UIProcess/Automation/WebAutomationSession.cpp:
(WebKit::WebAutomationSession::setFilesToSelectForFileUpload):
Add support for receiving and saving file contents to a temporary directory. Rewrite the used paths so
that WebCore knows to look at the revised paths where the file contents have been saved.

(WebKit::WebAutomationSession::platformGenerateLocalFilePathForRemoteFile):
Since WebKit does not have usable FileSystem implementations for all ports, shell out the actual
saving of base64-encoded file data. Provide a Cocoa implementation, since that's what I can test.

* UIProcess/Automation/cocoa/WebAutomationSessionCocoa.mm:
(WebKit::WebAutomationSession::platformGenerateLocalFilePathForRemoteFile):
Use WTF::FileSystem to create a temporary directory, and use Cocoa methods to actually write the file.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@243340 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit/ChangeLog
Source/WebKit/UIProcess/Automation/Automation.json
Source/WebKit/UIProcess/Automation/WebAutomationSession.cpp
Source/WebKit/UIProcess/Automation/WebAutomationSession.h
Source/WebKit/UIProcess/Automation/cocoa/WebAutomationSessionCocoa.mm

index cc173eb..a5a4ee3 100644 (file)
@@ -1,3 +1,30 @@
+2019-03-21  Brian Burg  <bburg@apple.com>
+
+        Web Automation: support uploading non-local file paths
+        https://bugs.webkit.org/show_bug.cgi?id=196081
+        <rdar://problem/45819897>
+
+        Reviewed by Devin Rousso and Joseph Pecoraro.
+
+        To support cases where supplied file paths do not exist on the session host, add support for
+        receiving file contents via Automation.setFilesToSelectForFileUpload.
+
+        * UIProcess/Automation/Automation.json: Add new parameter.
+
+        * UIProcess/Automation/WebAutomationSession.h:
+        * UIProcess/Automation/WebAutomationSession.cpp:
+        (WebKit::WebAutomationSession::setFilesToSelectForFileUpload):
+        Add support for receiving and saving file contents to a temporary directory. Rewrite the used paths so
+        that WebCore knows to look at the revised paths where the file contents have been saved.
+
+        (WebKit::WebAutomationSession::platformGenerateLocalFilePathForRemoteFile):
+        Since WebKit does not have usable FileSystem implementations for all ports, shell out the actual
+        saving of base64-encoded file data. Provide a Cocoa implementation, since that's what I can test.
+
+        * UIProcess/Automation/cocoa/WebAutomationSessionCocoa.mm:
+        (WebKit::WebAutomationSession::platformGenerateLocalFilePathForRemoteFile):
+        Use WTF::FileSystem to create a temporary directory, and use Cocoa methods to actually write the file.
+
 2019-03-21  Youenn Fablet  <youenn@apple.com>
 
         Cache API and IDB space usages should be initialized on first quota check
index d475744..cad53c7 100644 (file)
             "description": "Sets the files to be selected when a file input type element becomes active in a browsing context.",
             "parameters": [
                 { "name": "browsingContextHandle", "$ref": "BrowsingContextHandle", "description": "The handle for the browsing context." },
-                { "name": "filenames", "type": "array", "items": { "$ref": "string" }, "description": "Absolute paths to the files that should be selected." }
+                { "name": "filenames", "type": "array", "items": { "type": "string" }, "description": "Absolute paths to the files that should be selected." },
+                { "name": "fileContents", "type": "array", "items": { "type" : "string" }, "optional": true, "description": "An array of Base64-encoded binary data for each file to be selected. If this property is provided, it is assumed that 'filenames' are not real file paths on the session host's filesystem, and this binary data will be used instead." }
             ]
         },
         {
index 5e0ca10..ac5d542 100644 (file)
@@ -1238,17 +1238,33 @@ void WebAutomationSession::setUserInputForCurrentJavaScriptPrompt(Inspector::Err
     m_client->setUserInputForCurrentJavaScriptPromptOnPage(*this, *page, promptValue);
 }
 
-void WebAutomationSession::setFilesToSelectForFileUpload(ErrorString& errorString, const String& browsingContextHandle, const JSON::Array& filenames)
+void WebAutomationSession::setFilesToSelectForFileUpload(ErrorString& errorString, const String& browsingContextHandle, const JSON::Array& filenames, const JSON::Array* fileContents)
 {
     Vector<String> newFileList;
     newFileList.reserveInitialCapacity(filenames.length());
 
-    for (const auto& item : filenames) {
+    if (fileContents && fileContents->length() != filenames.length())
+        SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The parameters 'filenames' and 'fileContents' must have equal length.");
+
+    for (size_t i = 0; i < filenames.length(); ++i) {
         String filename;
-        if (!item->asString(filename))
-            SYNC_FAIL_WITH_PREDEFINED_ERROR(InternalError);
+        if (!filenames.get(i)->asString(filename))
+            SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The parameter 'filenames' contains a non-string value.");
+
+        if (!fileContents) {
+            newFileList.append(filename);
+            continue;
+        }
+
+        String fileData;
+        if (!fileContents->get(i)->asString(fileData))
+            SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The parameter 'fileContents' contains a non-string value.");
+
+        Optional<String> localFilePath = platformGenerateLocalFilePathForRemoteFile(filename, fileData);
+        if (!localFilePath)
+            SYNC_FAIL_WITH_PREDEFINED_ERROR_AND_DETAILS(InternalError, "The remote file could not be saved to a local temporary directory.");
 
-        newFileList.append(filename);
+        newFileList.append(localFilePath.value());
     }
 
     m_filesToSelectForFileUpload.swap(newFileList);
@@ -2048,4 +2064,11 @@ Optional<String> WebAutomationSession::platformGetBase64EncodedPNGData(const Sha
 }
 #endif // !PLATFORM(COCOA) && !USE(CAIRO)
 
+#if !PLATFORM(COCOA)
+Optional<String> WebAutomationSession::platformGenerateLocalFilePathForRemoteFile(const String&, const String&)
+{
+    return WTF::nullopt;
+}
+#endif // !PLATFORM(COCOA)
+
 } // namespace WebKit
index 5dfdc18..0b8b86f 100644 (file)
@@ -186,7 +186,7 @@ public:
     void acceptCurrentJavaScriptDialog(Inspector::ErrorString&, const String& browsingContextHandle) override;
     void messageOfCurrentJavaScriptDialog(Inspector::ErrorString&, const String& browsingContextHandle, String* text) override;
     void setUserInputForCurrentJavaScriptPrompt(Inspector::ErrorString&, const String& browsingContextHandle, const String& text) override;
-    void setFilesToSelectForFileUpload(Inspector::ErrorString&, const String& browsingContextHandle, const JSON::Array& filenames) override;
+    void setFilesToSelectForFileUpload(Inspector::ErrorString&, const String& browsingContextHandle, const JSON::Array& filenames, const JSON::Array* optionalFileContents) override;
     void getAllCookies(const String& browsingContextHandle, Ref<GetAllCookiesCallback>&&) override;
     void deleteSingleCookie(const String& browsingContextHandle, const String& cookieName, Ref<DeleteSingleCookieCallback>&&) override;
     void addSingleCookie(const String& browsingContextHandle, const JSON::Object& cookie, Ref<AddSingleCookieCallback>&&) override;
@@ -260,9 +260,13 @@ private:
     void platformSimulateKeySequence(WebPageProxy&, const String&);
 #endif // ENABLE(WEBDRIVER_KEYBOARD_INTERACTIONS)
 
-    // Get base64 encoded PNG data from a bitmap.
+    // Get base64-encoded PNG data from a bitmap.
     Optional<String> platformGetBase64EncodedPNGData(const ShareableBitmap::Handle&);
 
+    // Save base64-encoded file contents to a local file path and return the path.
+    // This reuses the basename of the remote file path so that the filename exposed to DOM API remains the same.
+    Optional<String> platformGenerateLocalFilePathForRemoteFile(const String& remoteFilePath, const String& base64EncodedFileContents);
+
 #if PLATFORM(COCOA)
     // The type parameter of the NSArray argument is platform-dependent.
     void sendSynthesizedEventsToPage(WebPageProxy&, NSArray *eventsToSend);
index d347718..826556d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016, 2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2019 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -28,6 +28,8 @@
 
 #if PLATFORM(COCOA)
 
+#import <wtf/FileSystem.h>
+
 #if PLATFORM(IOS_FAMILY)
 #include <ImageIO/CGImageDestination.h>
 #include <MobileCoreServices/UTCoreTypes.h>
@@ -55,6 +57,28 @@ Optional<String> WebAutomationSession::platformGetBase64EncodedPNGData(const Sha
     return String([imageData base64EncodedStringWithOptions:0]);
 }
 
+Optional<String> WebAutomationSession::platformGenerateLocalFilePathForRemoteFile(const String& remoteFilePath, const String& base64EncodedFileContents)
+{
+    RetainPtr<NSData> fileContents = adoptNS([[NSData alloc] initWithBase64EncodedString:base64EncodedFileContents options:0]);
+    if (!fileContents) {
+        LOG_ERROR("WebAutomationSession: unable to decode base64-encoded file contents.");
+        return WTF::nullopt;
+    }
+
+    NSString *temporaryDirectory = FileSystem::createTemporaryDirectory(@"WebDriver");
+    NSURL *remoteFile = [NSURL fileURLWithPath:remoteFilePath isDirectory:NO];
+    NSString *localFilePath = [temporaryDirectory stringByAppendingPathComponent:remoteFile.lastPathComponent];
+
+    NSError *fileWriteError;
+    [fileContents.get() writeToFile:localFilePath options:NSDataWritingAtomic error:&fileWriteError];
+    if (fileWriteError) {
+        LOG_ERROR("WebAutomationSession: Error writing image data to temporary file: %@", fileWriteError);
+        return WTF::nullopt;
+    }
+
+    return String(localFilePath);
+}
+
 Optional<unichar> WebAutomationSession::charCodeForVirtualKey(Inspector::Protocol::Automation::VirtualKey key) const
 {
     switch (key) {