- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / select_file_dialog_extension_browsertest.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/views/select_file_dialog_extension.h"
6
7 #include "base/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/path_service.h"
12 #include "base/strings/utf_string_conversions.h"  // ASCIIToUTF16
13 #include "base/threading/platform_thread.h"
14 #include "build/build_config.h"
15 #include "chrome/browser/extensions/component_loader.h"
16 #include "chrome/browser/extensions/extension_browsertest.h"
17 #include "chrome/browser/extensions/extension_test_message_listener.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/browser_navigator.h"
21 #include "chrome/browser/ui/browser_window.h"
22 #include "chrome/common/chrome_paths.h"
23 #include "content/public/browser/browser_context.h"
24 #include "content/public/browser/notification_service.h"
25 #include "content/public/browser/notification_types.h"
26 #include "content/public/browser/render_view_host.h"
27 #include "content/public/browser/storage_partition.h"
28 #include "content/public/test/test_utils.h"
29 #include "ui/shell_dialogs/select_file_dialog.h"
30 #include "ui/shell_dialogs/selected_file_info.h"
31 #include "webkit/browser/fileapi/external_mount_points.h"
32
33 using content::BrowserContext;
34
35 // Mock listener used by test below.
36 class MockSelectFileDialogListener : public ui::SelectFileDialog::Listener {
37  public:
38   MockSelectFileDialogListener()
39     : file_selected_(false),
40       canceled_(false),
41       params_(NULL) {
42   }
43
44   bool file_selected() const { return file_selected_; }
45   bool canceled() const { return canceled_; }
46   base::FilePath path() const { return path_; }
47   void* params() const { return params_; }
48
49   // ui::SelectFileDialog::Listener implementation.
50   virtual void FileSelected(const base::FilePath& path,
51                             int index,
52                             void* params) OVERRIDE {
53     file_selected_ = true;
54     path_ = path;
55     params_ = params;
56   }
57   virtual void FileSelectedWithExtraInfo(
58       const ui::SelectedFileInfo& selected_file_info,
59       int index,
60       void* params) OVERRIDE {
61     FileSelected(selected_file_info.local_path, index, params);
62   }
63   virtual void MultiFilesSelected(
64       const std::vector<base::FilePath>& files, void* params) OVERRIDE {}
65   virtual void FileSelectionCanceled(void* params) OVERRIDE {
66     canceled_ = true;
67     params_ = params;
68   }
69
70  private:
71   bool file_selected_;
72   bool canceled_;
73   base::FilePath path_;
74   void* params_;
75
76   DISALLOW_COPY_AND_ASSIGN(MockSelectFileDialogListener);
77 };
78
79 class SelectFileDialogExtensionBrowserTest : public ExtensionBrowserTest {
80  public:
81   enum DialogButtonType {
82     DIALOG_BTN_OK,
83     DIALOG_BTN_CANCEL
84   };
85
86   virtual void SetUp() OVERRIDE {
87     extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
88
89     // Create the dialog wrapper object, but don't show it yet.
90     listener_.reset(new MockSelectFileDialogListener());
91     dialog_ = new SelectFileDialogExtension(listener_.get(), NULL);
92
93     // We have to provide at least one mount point.
94     // File manager looks for "Downloads" mount point, so use this name.
95     base::FilePath tmp_path;
96     PathService::Get(base::DIR_TEMP, &tmp_path);
97     ASSERT_TRUE(tmp_dir_.CreateUniqueTempDirUnderPath(tmp_path));
98     downloads_dir_ = tmp_dir_.path().Append("Downloads");
99     file_util::CreateDirectory(downloads_dir_);
100
101     // Must run after our setup because it actually runs the test.
102     ExtensionBrowserTest::SetUp();
103   }
104
105   virtual void TearDown() OVERRIDE {
106     ExtensionBrowserTest::TearDown();
107
108     // Delete the dialog first, as it holds a pointer to the listener.
109     dialog_ = NULL;
110     listener_.reset();
111
112     second_dialog_ = NULL;
113     second_listener_.reset();
114   }
115
116   // Creates a file system mount point for a directory.
117   void AddMountPoint(const base::FilePath& path) {
118     std::string mount_point_name = path.BaseName().AsUTF8Unsafe();
119     fileapi::ExternalMountPoints* mount_points =
120         BrowserContext::GetMountPoints(browser()->profile());
121     // The Downloads mount point already exists so it must be removed before
122     // adding the test mount point (which will also be mapped as Downloads).
123     mount_points->RevokeFileSystem(mount_point_name);
124     EXPECT_TRUE(mount_points->RegisterFileSystem(
125         mount_point_name, fileapi::kFileSystemTypeNativeLocal, path));
126   }
127
128   void CheckJavascriptErrors() {
129     content::RenderViewHost* host = dialog_->GetRenderViewHost();
130     scoped_ptr<base::Value> value =
131         content::ExecuteScriptAndGetValue(host, "window.JSErrorCount");
132     int js_error_count = 0;
133     ASSERT_TRUE(value->GetAsInteger(&js_error_count));
134     ASSERT_EQ(0, js_error_count);
135   }
136
137   void OpenDialog(ui::SelectFileDialog::Type dialog_type,
138                   const base::FilePath& file_path,
139                   const gfx::NativeWindow& owning_window,
140                   const std::string& additional_message) {
141     // Spawn a dialog to open a file.  The dialog will signal that it is ready
142     // via chrome.test.sendMessage() in the extension JavaScript.
143     ExtensionTestMessageListener init_listener("worker-initialized",
144                                                false /* will_reply */);
145
146     scoped_ptr<ExtensionTestMessageListener> additional_listener;
147     if (!additional_message.empty()) {
148       additional_listener.reset(
149           new ExtensionTestMessageListener(additional_message, false));
150     }
151
152     dialog_->SelectFile(dialog_type,
153                         string16() /* title */,
154                         file_path,
155                         NULL /* file_types */,
156                          0 /* file_type_index */,
157                         FILE_PATH_LITERAL("") /* default_extension */,
158                         owning_window,
159                         this /* params */);
160
161     LOG(INFO) << "Waiting for JavaScript ready message.";
162     ASSERT_TRUE(init_listener.WaitUntilSatisfied());
163
164     if (additional_listener.get()) {
165       LOG(INFO) << "Waiting for JavaScript " << additional_message
166                 << " message.";
167       ASSERT_TRUE(additional_listener->WaitUntilSatisfied());
168     }
169
170     // Dialog should be running now.
171     ASSERT_TRUE(dialog_->IsRunning(owning_window));
172
173     ASSERT_NO_FATAL_FAILURE(CheckJavascriptErrors());
174   }
175
176   void TryOpeningSecondDialog(const gfx::NativeWindow& owning_window) {
177     second_listener_.reset(new MockSelectFileDialogListener());
178     second_dialog_ = new SelectFileDialogExtension(second_listener_.get(),
179                                                    NULL);
180
181     // At the moment we don't really care about dialog type, but we have to put
182     // some dialog type.
183     second_dialog_->SelectFile(ui::SelectFileDialog::SELECT_OPEN_FILE,
184                                string16() /* title */,
185                                base::FilePath() /* default_path */,
186                                NULL /* file_types */,
187                                0 /* file_type_index */,
188                                FILE_PATH_LITERAL("") /* default_extension */,
189                                owning_window,
190                                this /* params */);
191   }
192
193   void CloseDialog(DialogButtonType button_type,
194                    const gfx::NativeWindow& owning_window) {
195     // Inject JavaScript to click the cancel button and wait for notification
196     // that the window has closed.
197     content::WindowedNotificationObserver host_destroyed(
198         content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
199         content::NotificationService::AllSources());
200     content::RenderViewHost* host = dialog_->GetRenderViewHost();
201     string16 main_frame;
202     std::string button_class =
203         (button_type == DIALOG_BTN_OK) ? ".button-panel .ok" :
204                                          ".button-panel .cancel";
205     string16 script = ASCIIToUTF16(
206         "console.log(\'Test JavaScript injected.\');"
207         "document.querySelector(\'" + button_class + "\').click();");
208     // The file selection handler closes the dialog and does not return control
209     // to JavaScript, so do not wait for return values.
210     host->ExecuteJavascriptInWebFrame(main_frame, script);
211     LOG(INFO) << "Waiting for window close notification.";
212     host_destroyed.Wait();
213
214     // Dialog no longer believes it is running.
215     ASSERT_FALSE(dialog_->IsRunning(owning_window));
216   }
217
218   scoped_ptr<MockSelectFileDialogListener> listener_;
219   scoped_refptr<SelectFileDialogExtension> dialog_;
220
221   scoped_ptr<MockSelectFileDialogListener> second_listener_;
222   scoped_refptr<SelectFileDialogExtension> second_dialog_;
223
224   base::ScopedTempDir tmp_dir_;
225   base::FilePath downloads_dir_;
226 };
227
228 IN_PROC_BROWSER_TEST_F(SelectFileDialogExtensionBrowserTest, CreateAndDestroy) {
229   // Browser window must be up for us to test dialog window parent.
230   gfx::NativeWindow native_window = browser()->window()->GetNativeWindow();
231   ASSERT_TRUE(native_window != NULL);
232
233   // Before we call SelectFile, dialog is not running/visible.
234   ASSERT_FALSE(dialog_->IsRunning(native_window));
235 }
236
237 IN_PROC_BROWSER_TEST_F(SelectFileDialogExtensionBrowserTest, DestroyListener) {
238   // Some users of SelectFileDialog destroy their listener before cleaning
239   // up the dialog.  Make sure we don't crash.
240   dialog_->ListenerDestroyed();
241   listener_.reset();
242 }
243
244 // TODO(jamescook): Add a test for selecting a file for an <input type='file'/>
245 // page element, as that uses different memory management pathways.
246 // crbug.com/98791
247
248 IN_PROC_BROWSER_TEST_F(SelectFileDialogExtensionBrowserTest,
249                        SelectFileAndCancel) {
250   AddMountPoint(downloads_dir_);
251
252   gfx::NativeWindow owning_window = browser()->window()->GetNativeWindow();
253
254   // base::FilePath() for default path.
255   ASSERT_NO_FATAL_FAILURE(OpenDialog(ui::SelectFileDialog::SELECT_OPEN_FILE,
256                                      base::FilePath(), owning_window, ""));
257
258   // Press cancel button.
259   CloseDialog(DIALOG_BTN_CANCEL, owning_window);
260
261   // Listener should have been informed of the cancellation.
262   ASSERT_FALSE(listener_->file_selected());
263   ASSERT_TRUE(listener_->canceled());
264   ASSERT_EQ(this, listener_->params());
265 }
266
267 IN_PROC_BROWSER_TEST_F(SelectFileDialogExtensionBrowserTest,
268                        SelectFileAndOpen) {
269   AddMountPoint(downloads_dir_);
270
271   base::FilePath test_file =
272       downloads_dir_.AppendASCII("file_manager_test.html");
273
274   // Create an empty file to give us something to select.
275   FILE* fp = file_util::OpenFile(test_file, "w");
276   ASSERT_TRUE(fp != NULL);
277   ASSERT_TRUE(file_util::CloseFile(fp));
278
279   gfx::NativeWindow owning_window = browser()->window()->GetNativeWindow();
280
281   // Spawn a dialog to open a file.  Provide the path to the file so the dialog
282   // will automatically select it.  Ensure that the OK button is enabled by
283   // waiting for chrome.test.sendMessage('selection-change-complete').
284   // The extension starts a Web Worker to read file metadata, so it may send
285   // 'selection-change-complete' before 'worker-initialized'.  This is OK.
286   ASSERT_NO_FATAL_FAILURE(OpenDialog(ui::SelectFileDialog::SELECT_OPEN_FILE,
287                                      test_file, owning_window,
288                                      "selection-change-complete"));
289
290   // Click open button.
291   CloseDialog(DIALOG_BTN_OK, owning_window);
292
293   // Listener should have been informed that the file was opened.
294   ASSERT_TRUE(listener_->file_selected());
295   ASSERT_FALSE(listener_->canceled());
296   ASSERT_EQ(test_file, listener_->path());
297   ASSERT_EQ(this, listener_->params());
298 }
299
300 IN_PROC_BROWSER_TEST_F(SelectFileDialogExtensionBrowserTest,
301                        SelectFileAndSave) {
302   AddMountPoint(downloads_dir_);
303
304   base::FilePath test_file =
305       downloads_dir_.AppendASCII("file_manager_test.html");
306
307   gfx::NativeWindow owning_window = browser()->window()->GetNativeWindow();
308
309   // Spawn a dialog to save a file, providing a suggested path.
310   // Ensure "Save" button is enabled by waiting for notification from
311   // chrome.test.sendMessage().
312   // The extension starts a Web Worker to read file metadata, so it may send
313   // 'directory-change-complete' before 'worker-initialized'.  This is OK.
314   ASSERT_NO_FATAL_FAILURE(OpenDialog(ui::SelectFileDialog::SELECT_SAVEAS_FILE,
315                                      test_file, owning_window,
316                                      "directory-change-complete"));
317
318   // Click save button.
319   CloseDialog(DIALOG_BTN_OK, owning_window);
320
321   // Listener should have been informed that the file was selected.
322   ASSERT_TRUE(listener_->file_selected());
323   ASSERT_FALSE(listener_->canceled());
324   ASSERT_EQ(test_file, listener_->path());
325   ASSERT_EQ(this, listener_->params());
326 }
327
328 IN_PROC_BROWSER_TEST_F(SelectFileDialogExtensionBrowserTest,
329                        OpenSingletonTabAndCancel) {
330   AddMountPoint(downloads_dir_);
331
332   gfx::NativeWindow owning_window = browser()->window()->GetNativeWindow();
333
334   ASSERT_NO_FATAL_FAILURE(OpenDialog(ui::SelectFileDialog::SELECT_OPEN_FILE,
335                                      base::FilePath(), owning_window, ""));
336
337   // Open a singleton tab in background.
338   chrome::NavigateParams p(browser(), GURL("www.google.com"),
339                            content::PAGE_TRANSITION_LINK);
340   p.window_action = chrome::NavigateParams::SHOW_WINDOW;
341   p.disposition = SINGLETON_TAB;
342   chrome::Navigate(&p);
343
344   // Press cancel button.
345   CloseDialog(DIALOG_BTN_CANCEL, owning_window);
346
347   // Listener should have been informed of the cancellation.
348   ASSERT_FALSE(listener_->file_selected());
349   ASSERT_TRUE(listener_->canceled());
350   ASSERT_EQ(this, listener_->params());
351 }
352
353 IN_PROC_BROWSER_TEST_F(SelectFileDialogExtensionBrowserTest,
354                        OpenTwoDialogs) {
355   AddMountPoint(downloads_dir_);
356
357   gfx::NativeWindow owning_window = browser()->window()->GetNativeWindow();
358
359   ASSERT_NO_FATAL_FAILURE(OpenDialog(ui::SelectFileDialog::SELECT_OPEN_FILE,
360                                      base::FilePath(), owning_window, ""));
361
362   TryOpeningSecondDialog(owning_window);
363
364   // Second dialog should not be running.
365   ASSERT_FALSE(second_dialog_->IsRunning(owning_window));
366
367   // Click cancel button.
368   CloseDialog(DIALOG_BTN_CANCEL, owning_window);
369
370   // Listener should have been informed of the cancellation.
371   ASSERT_FALSE(listener_->file_selected());
372   ASSERT_TRUE(listener_->canceled());
373   ASSERT_EQ(this, listener_->params());
374 }