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