Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebKit / UIProcess / API / glib / WebKitFileChooserRequest.cpp
1 /*
2  * Copyright (C) 2012 Igalia S.L.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21 #include "WebKitFileChooserRequest.h"
22
23 #include "APIArray.h"
24 #include "APIOpenPanelParameters.h"
25 #include "APIString.h"
26 #include "WebKitFileChooserRequestPrivate.h"
27 #include "WebOpenPanelResultListenerProxy.h"
28 #include <WebCore/FileSystem.h>
29 #include <WebCore/TextEncoding.h>
30 #include <glib/gi18n-lib.h>
31 #include <wtf/URL.h>
32 #include <wtf/glib/GRefPtr.h>
33 #include <wtf/glib/GUniquePtr.h>
34 #include <wtf/glib/WTFGType.h>
35 #include <wtf/text/CString.h>
36
37 using namespace WebKit;
38 using namespace WebCore;
39
40 /**
41  * SECTION: WebKitFileChooserRequest
42  * @Short_description: A request to open a file chooser
43  * @Title: WebKitFileChooserRequest
44  * @See_also: #WebKitWebView
45  *
46  * Whenever the user interacts with an &lt;input type='file' /&gt;
47  * HTML element, WebKit will need to show a dialog to choose one or
48  * more files to be uploaded to the server along with the rest of the
49  * form data. For that to happen in a general way, instead of just
50  * opening a #GtkFileChooserDialog (which might be not desirable in
51  * some cases, which could prefer to use their own file chooser
52  * dialog), WebKit will fire the #WebKitWebView::run-file-chooser
53  * signal with a #WebKitFileChooserRequest object, which will allow
54  * the client application to specify the files to be selected, to
55  * inspect the details of the request (e.g. if multiple selection
56  * should be allowed) and to cancel the request, in case nothing was
57  * selected.
58  *
59  * In case the client application does not wish to handle this signal,
60  * WebKit will provide a default handler which will asynchronously run
61  * a regular #GtkFileChooserDialog for the user to interact with.
62  */
63
64 struct _WebKitFileChooserRequestPrivate {
65     RefPtr<API::OpenPanelParameters> parameters;
66     RefPtr<WebOpenPanelResultListenerProxy> listener;
67 #if PLATFORM(GTK)
68     GRefPtr<GtkFileFilter> filter;
69 #endif
70     GRefPtr<GPtrArray> mimeTypes;
71     GRefPtr<GPtrArray> selectedFiles;
72     bool handledRequest;
73 };
74
75 WEBKIT_DEFINE_TYPE(WebKitFileChooserRequest, webkit_file_chooser_request, G_TYPE_OBJECT)
76
77 enum {
78     PROP_0,
79 #if PLATFORM(GTK)
80     PROP_FILTER,
81 #endif
82     PROP_MIME_TYPES,
83     PROP_SELECT_MULTIPLE,
84     PROP_SELECTED_FILES,
85 };
86
87 static void webkitFileChooserRequestDispose(GObject* object)
88 {
89     WebKitFileChooserRequest* request = WEBKIT_FILE_CHOOSER_REQUEST(object);
90
91     // Make sure the request is always handled before finalizing.
92     if (!request->priv->handledRequest)
93         webkit_file_chooser_request_cancel(request);
94
95     G_OBJECT_CLASS(webkit_file_chooser_request_parent_class)->dispose(object);
96 }
97
98 static void webkitFileChooserRequestGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec)
99 {
100     WebKitFileChooserRequest* request = WEBKIT_FILE_CHOOSER_REQUEST(object);
101     switch (propId) {
102 #if PLATFORM(GTK)
103     case PROP_FILTER:
104         g_value_set_object(value, webkit_file_chooser_request_get_mime_types_filter(request));
105         break;
106 #endif
107     case PROP_MIME_TYPES:
108         g_value_set_boxed(value, webkit_file_chooser_request_get_mime_types(request));
109         break;
110     case PROP_SELECT_MULTIPLE:
111         g_value_set_boolean(value, webkit_file_chooser_request_get_select_multiple(request));
112         break;
113     case PROP_SELECTED_FILES:
114         g_value_set_boxed(value, webkit_file_chooser_request_get_selected_files(request));
115         break;
116     default:
117         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec);
118         break;
119     }
120 }
121
122 static void webkit_file_chooser_request_class_init(WebKitFileChooserRequestClass* requestClass)
123 {
124     GObjectClass* objectClass = G_OBJECT_CLASS(requestClass);
125     objectClass->dispose = webkitFileChooserRequestDispose;
126     objectClass->get_property = webkitFileChooserRequestGetProperty;
127
128 #if PLATFORM(GTK)
129     /**
130      * WebKitFileChooserRequest:filter:
131      *
132      * The filter currently associated with the request. See
133      * webkit_file_chooser_request_get_mime_types_filter() for more
134      * details.
135      */
136     g_object_class_install_property(objectClass,
137                                     PROP_FILTER,
138                                     g_param_spec_object("filter",
139                                                       _("MIME types filter"),
140                                                       _("The filter currently associated with the request"),
141                                                       GTK_TYPE_FILE_FILTER,
142                                                       WEBKIT_PARAM_READABLE));
143 #endif
144
145     /**
146      * WebKitFileChooserRequest:mime-types:
147      *
148      * A %NULL-terminated array of strings containing the list of MIME
149      * types the file chooser dialog should handle. See
150      * webkit_file_chooser_request_get_mime_types() for more details.
151      */
152     g_object_class_install_property(objectClass,
153                                     PROP_MIME_TYPES,
154                                     g_param_spec_boxed("mime-types",
155                                                       _("MIME types"),
156                                                       _("The list of MIME types associated with the request"),
157                                                       G_TYPE_STRV,
158                                                       WEBKIT_PARAM_READABLE));
159     /**
160      * WebKitFileChooserRequest:select-multiple:
161      *
162      * Whether the file chooser should allow selecting multiple
163      * files. See
164      * webkit_file_chooser_request_get_select_multiple() for
165      * more details.
166      */
167     g_object_class_install_property(objectClass,
168                                     PROP_SELECT_MULTIPLE,
169                                     g_param_spec_boolean("select-multiple",
170                                                        _("Select multiple files"),
171                                                        _("Whether the file chooser should allow selecting multiple files"),
172                                                        FALSE,
173                                                        WEBKIT_PARAM_READABLE));
174     /**
175      * WebKitFileChooserRequest:selected-files:
176      *
177      * A %NULL-terminated array of strings containing the list of
178      * selected files associated to the current request. See
179      * webkit_file_chooser_request_get_selected_files() for more details.
180      */
181     g_object_class_install_property(objectClass,
182                                     PROP_SELECTED_FILES,
183                                     g_param_spec_boxed("selected-files",
184                                                       _("Selected files"),
185                                                       _("The list of selected files associated with the request"),
186                                                       G_TYPE_STRV,
187                                                       WEBKIT_PARAM_READABLE));
188 }
189
190 WebKitFileChooserRequest* webkitFileChooserRequestCreate(API::OpenPanelParameters* parameters, WebOpenPanelResultListenerProxy* listener)
191 {
192     WebKitFileChooserRequest* request = WEBKIT_FILE_CHOOSER_REQUEST(g_object_new(WEBKIT_TYPE_FILE_CHOOSER_REQUEST, NULL));
193     request->priv->parameters = parameters;
194     request->priv->listener = listener;
195     return request;
196 }
197
198 /**
199  * webkit_file_chooser_request_get_mime_types:
200  * @request: a #WebKitFileChooserRequest
201  *
202  * Get the list of MIME types the file chooser dialog should handle,
203  * in the format specified in RFC 2046 for "media types". Its contents
204  * depend on the value of the 'accept' attribute for HTML input
205  * elements. This function should normally be called before presenting
206  * the file chooser dialog to the user, to decide whether to allow the
207  * user to select multiple files at once or only one.
208  *
209  * Returns: (array zero-terminated=1) (transfer none): a
210  * %NULL-terminated array of strings if a list of accepted MIME types
211  * is defined or %NULL otherwise, meaning that any MIME type should be
212  * accepted. This array and its contents are owned by WebKit and
213  * should not be modified or freed.
214  */
215 const gchar* const* webkit_file_chooser_request_get_mime_types(WebKitFileChooserRequest* request)
216 {
217     g_return_val_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request), 0);
218     if (request->priv->mimeTypes)
219         return reinterpret_cast<gchar**>(request->priv->mimeTypes->pdata);
220
221     Ref<API::Array> mimeTypes = request->priv->parameters->acceptMIMETypes();
222     size_t numOfMimeTypes = mimeTypes->size();
223     if (!numOfMimeTypes)
224         return 0;
225
226     request->priv->mimeTypes = adoptGRef(g_ptr_array_new_with_free_func(g_free));
227     for (size_t i = 0; i < numOfMimeTypes; ++i) {
228         API::String* webMimeType = static_cast<API::String*>(mimeTypes->at(i));
229         String mimeTypeString = webMimeType->string();
230         if (mimeTypeString.isEmpty())
231             continue;
232         g_ptr_array_add(request->priv->mimeTypes.get(), g_strdup(mimeTypeString.utf8().data()));
233     }
234     g_ptr_array_add(request->priv->mimeTypes.get(), 0);
235
236     return reinterpret_cast<gchar**>(request->priv->mimeTypes->pdata);
237 }
238
239 #if PLATFORM(GTK)
240 /**
241  * webkit_file_chooser_request_get_mime_types_filter:
242  * @request: a #WebKitFileChooserRequest
243  *
244  * Get the filter currently associated with the request, ready to be
245  * used by #GtkFileChooser. This function should normally be called
246  * before presenting the file chooser dialog to the user, to decide
247  * whether to apply a filter so the user would not be allowed to
248  * select files with other MIME types.
249  *
250  * See webkit_file_chooser_request_get_mime_types() if you are
251  * interested in getting the list of accepted MIME types.
252  *
253  * Returns: (transfer none): a #GtkFileFilter if a list of accepted
254  * MIME types is defined or %NULL otherwise. The returned object is
255  * owned by WebKit should not be modified or freed.
256  */
257 GtkFileFilter* webkit_file_chooser_request_get_mime_types_filter(WebKitFileChooserRequest* request)
258 {
259     g_return_val_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request), 0);
260     if (request->priv->filter)
261         return request->priv->filter.get();
262
263     Ref<API::Array> mimeTypes = request->priv->parameters->acceptMIMETypes();
264     size_t numOfMimeTypes = mimeTypes->size();
265     if (!numOfMimeTypes)
266         return 0;
267
268     // Do not use adoptGRef here, since we want to sink the floating
269     // reference for the new instance of GtkFileFilter, so we make
270     // sure we keep the ownership during the lifetime of the request.
271     request->priv->filter = gtk_file_filter_new();
272     for (size_t i = 0; i < numOfMimeTypes; ++i) {
273         API::String* webMimeType = static_cast<API::String*>(mimeTypes->at(i));
274         String mimeTypeString = webMimeType->string();
275         if (mimeTypeString.isEmpty())
276             continue;
277         gtk_file_filter_add_mime_type(request->priv->filter.get(), mimeTypeString.utf8().data());
278     }
279
280     return request->priv->filter.get();
281 }
282 #endif // PLATFORM(GTK)
283
284 /**
285  * webkit_file_chooser_request_get_select_multiple:
286  * @request: a #WebKitFileChooserRequest
287  *
288  * Determine whether the file chooser associated to this
289  * #WebKitFileChooserRequest should allow selecting multiple files,
290  * which depends on the HTML input element having a 'multiple'
291  * attribute defined.
292  *
293  * Returns: %TRUE if the file chooser should allow selecting multiple files or %FALSE otherwise.
294  */
295 gboolean webkit_file_chooser_request_get_select_multiple(WebKitFileChooserRequest* request)
296 {
297     g_return_val_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request), FALSE);
298     return request->priv->parameters->allowMultipleFiles();
299 }
300
301 /**
302  * webkit_file_chooser_request_select_files:
303  * @request: a #WebKitFileChooserRequest
304  * @files: (array zero-terminated=1) (transfer none): a
305  * %NULL-terminated array of strings, containing paths to local files.
306  *
307  * Ask WebKit to select local files for upload and complete the
308  * request.
309  */
310 void webkit_file_chooser_request_select_files(WebKitFileChooserRequest* request, const gchar* const* files)
311 {
312     g_return_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request));
313     g_return_if_fail(files);
314
315     GRefPtr<GPtrArray> selectedFiles = adoptGRef(g_ptr_array_new_with_free_func(g_free));
316     Vector<String> chosenFiles;
317     for (int i = 0; files[i]; i++) {
318         chosenFiles.append(WebCore::decodeURLEscapeSequences(String::fromUTF8(files[i])));
319         g_ptr_array_add(selectedFiles.get(), g_strdup(files[i]));
320     }
321     g_ptr_array_add(selectedFiles.get(), nullptr);
322
323     // Select the files in WebCore and update local private attributes.
324     request->priv->listener->chooseFiles(chosenFiles);
325     request->priv->selectedFiles = selectedFiles;
326     request->priv->handledRequest = true;
327 }
328
329 /**
330  * webkit_file_chooser_request_get_selected_files:
331  * @request: a #WebKitFileChooserRequest
332  *
333  * Get the list of selected files currently associated to the
334  * request. Initially, the return value of this method contains any
335  * files selected in previous file chooser requests for this HTML
336  * input element. Once webkit_file_chooser_request_select_files, the
337  * value will reflect whatever files are given.
338  *
339  * This function should normally be called only before presenting the
340  * file chooser dialog to the user, to decide whether to perform some
341  * extra action, like pre-selecting the files from a previous request.
342  *
343  * Returns: (array zero-terminated=1) (transfer none): a
344  * %NULL-terminated array of strings if there are selected files
345  * associated with the request or %NULL otherwise. This array and its
346  * contents are owned by WebKit and should not be modified or
347  * freed.
348  */
349 const gchar* const* webkit_file_chooser_request_get_selected_files(WebKitFileChooserRequest* request)
350 {
351     g_return_val_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request), 0);
352     if (request->priv->selectedFiles)
353         return reinterpret_cast<gchar**>(request->priv->selectedFiles->pdata);
354
355     RefPtr<API::Array> selectedFileNames = request->priv->parameters->selectedFileNames();
356     size_t numOfFiles = selectedFileNames->size();
357     if (!numOfFiles)
358         return 0;
359
360     request->priv->selectedFiles = adoptGRef(g_ptr_array_new_with_free_func(g_free));
361     for (size_t i = 0; i < numOfFiles; ++i) {
362         API::String* webFileName = static_cast<API::String*>(selectedFileNames->at(i));
363         if (webFileName->stringView().isEmpty())
364             continue;
365         CString filename = FileSystem::fileSystemRepresentation(webFileName->string());
366         g_ptr_array_add(request->priv->selectedFiles.get(), g_strdup(filename.data()));
367     }
368     g_ptr_array_add(request->priv->selectedFiles.get(), 0);
369
370     return reinterpret_cast<gchar**>(request->priv->selectedFiles->pdata);
371 }
372
373 /**
374  * webkit_file_chooser_request_cancel:
375  * @request: a #WebKitFileChooserRequest
376  *
377  * Ask WebKit to cancel the request. It's important to do this in case
378  * no selection has been made in the client, otherwise the request
379  * won't be properly completed and the browser will keep the request
380  * pending forever, which might cause the browser to hang.
381  */
382 void webkit_file_chooser_request_cancel(WebKitFileChooserRequest* request)
383 {
384     g_return_if_fail(WEBKIT_IS_FILE_CHOOSER_REQUEST(request));
385     request->priv->listener->cancel();
386     request->priv->handledRequest = true;
387 }