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