1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
content / browser / file_system_access / file_system_access_clipboard_browsertest.cc [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/common/features_generated.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/clipboard/file_info.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/clipboard/test/test_clipboard.h"
namespace content {
// End-to-end tests for File System Access via clipboard, or more specifically,
// DataTransferItem::getAsFileSystemHandle().
class FileSystemAccessClipboardBrowserTest : public ContentBrowserTest {
public:
FileSystemAccessClipboardBrowserTest() {
scoped_features_.InitAndEnableFeature(
blink::features::kFileSystemAccessLocal);
}
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(embedded_test_server()->Start());
ui::TestClipboard::CreateForCurrentThread();
ContentBrowserTest::SetUp();
}
void TearDown() override {
ContentBrowserTest::TearDown();
ASSERT_TRUE(temp_dir_.Delete());
}
base::FilePath CreateTestFileInDirectory(const base::FilePath& directory_path,
const std::string& contents) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath result;
EXPECT_TRUE(base::CreateTemporaryFileInDir(directory_path, &result));
EXPECT_TRUE(base::WriteFile(result, contents));
return result;
}
base::FilePath CreateTestDir() {
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath result;
EXPECT_TRUE(base::CreateTemporaryDirInDir(
temp_dir_.GetPath(), FILE_PATH_LITERAL("test"), &result));
return result;
}
protected:
base::ScopedTempDir temp_dir_;
base::test::ScopedFeatureList scoped_features_;
};
IN_PROC_BROWSER_TEST_F(FileSystemAccessClipboardBrowserTest, File) {
ASSERT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
// Create a promise that will resolve on paste with the text from the file on
// the clipboard. The text is retrieved using the FileSystemAccess
// getAsFileSystemHandle() function. The promise will reject if zero/multiple
// items are on the clipboard or if the item is not a file.
ASSERT_TRUE(
ExecJs(shell(),
"var p = new Promise((resolve, reject) => {"
" window.document.onpaste = async (event) => {"
" if (event.clipboardData.files.length !== 1) {"
" reject('There were ' + event.clipboardData.files.length +"
" ' clipboard files. Expected 1.');"
" }"
" if (event.clipboardData.items.length !== 1) {"
" reject('There were ' + event.clipboardData.items.length +"
" ' clipboard items. Expected 1.');"
" }"
" if (event.clipboardData.items[0].kind != 'file') {"
" reject('The clipboard item was of kind: ' +"
" event.clipboardData.items[0].kind + '. Expected ' +"
" 'file.');"
" }"
" const fileItem = event.clipboardData.items[0];"
" const fileHandle = await fileItem.getAsFileSystemHandle();"
" if (fileHandle.kind !== 'file') {"
" reject('The clipboard item was a directory, expected ' +"
" ' it to be a file.');"
" }"
" const file = await fileHandle.getFile();"
" const text = await file.text();"
" resolve(text);"
" };"
"});"));
// Create a test file with contents `test_contents` to put on clipboard.
std::string test_contents = "Deleted code is debugged code.";
base::FilePath test_file_path =
CreateTestFileInDirectory(temp_dir_.GetPath(), test_contents);
{
ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste);
writer.WriteFilenames(ui::FileInfosToURIList(
{ui::FileInfo(test_file_path, base::FilePath())}));
}
// Send paste event and wait for JS promise to resolve with `test_contents`.
shell()->web_contents()->Paste();
EXPECT_EQ(test_contents, EvalJs(shell(), "p"));
}
IN_PROC_BROWSER_TEST_F(FileSystemAccessClipboardBrowserTest, Directory) {
ASSERT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
// Create a promise that will resolve on paste with the name of the child
// directory of the clipboard directory. The promise rejects if there are
// zero/multiple items on the clipboard or if the item is not a directory with
// a single child.
ASSERT_TRUE(
ExecJs(shell(),
"var p = new Promise((resolve, reject) => {"
" window.document.onpaste = async (event) => {"
" if (event.clipboardData.items.length !== 1) {"
" reject('There were ' + event.clipboardData.items.length +"
" ' clipboard items. Expected 1.');"
" }"
" if (event.clipboardData.items[0].kind != 'file') {"
" reject('The drag item was of kind: ' +"
" event.clipboardData.items[0].kind + '. Expected ' +"
" ' file.');"
" }"
" const fileItem = event.clipboardData.items[0];"
" directoryHandle = await fileItem.getAsFileSystemHandle();"
" if (directoryHandle.kind !== 'directory') {"
" reject('The dragged item was a file, expected it to be ' +"
" 'a directory.');"
" }"
" let directoryContents = [];"
" for await (let fileName of directoryHandle.keys()) {"
" directoryContents.push(fileName);"
" };"
" if (directoryContents.length !== 1) {"
" reject('There were ', directoryContents.length,"
" ' files in the clipboard directory. Expected 1.');"
" }"
" resolve(directoryContents.pop());"
" };"
"});"));
// Create a directory with a file inside and place on the clipboard.
const base::FilePath test_dir_path = CreateTestDir();
std::string contents = "Irrelevant contents.";
const base::FilePath file_inside_dir =
CreateTestFileInDirectory(test_dir_path, contents);
{
ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste);
writer.WriteFilenames(ui::FileInfosToURIList(
{ui::FileInfo(test_dir_path, base::FilePath())}));
}
// Send paste event and wait for promise to resolve and expect the directory
// to have child with name matching the base name of `file_inside_dir`.
shell()->web_contents()->Paste();
EXPECT_EQ(file_inside_dir.BaseName().AsUTF8Unsafe(), EvalJs(shell(), "p"));
}
} // namespace content