1 /* 2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. 3 * Copyright (C) 2010 Google Inc. All rights reserved. 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Library General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Library General Public License for more details. 14 * 15 * You should have received a copy of the GNU Library General Public License 16 * along with this library; see the file COPYING.LIB. If not, write to 17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 * Boston, MA 02110-1301, USA. 19 * 20 */ 21 22 #include "config.h" 23 #include "core/html/forms/FileInputType.h" 24 25 #include "bindings/core/v8/ExceptionStatePlaceholder.h" 26 #include "core/HTMLNames.h" 27 #include "core/InputTypeNames.h" 28 #include "core/dom/shadow/ShadowRoot.h" 29 #include "core/events/Event.h" 30 #include "core/fileapi/File.h" 31 #include "core/fileapi/FileList.h" 32 #include "core/html/FormDataList.h" 33 #include "core/html/HTMLInputElement.h" 34 #include "core/html/forms/FormController.h" 35 #include "core/page/Chrome.h" 36 #include "core/page/DragData.h" 37 #include "core/rendering/RenderFileUploadControl.h" 38 #include "platform/FileMetadata.h" 39 #include "platform/RuntimeEnabledFeatures.h" 40 #include "platform/UserGestureIndicator.h" 41 #include "platform/text/PlatformLocale.h" 42 #include "wtf/PassOwnPtr.h" 43 #include "wtf/text/StringBuilder.h" 44 #include "wtf/text/WTFString.h" 45 46 namespace blink { 47 48 using blink::WebLocalizedString; 49 using namespace HTMLNames; 50 51 inline FileInputType::FileInputType(HTMLInputElement& element) 52 : BaseClickableWithKeyInputType(element) 53 , m_fileList(FileList::create()) 54 { 55 } 56 57 PassRefPtrWillBeRawPtr<InputType> FileInputType::create(HTMLInputElement& element) 58 { 59 return adoptRefWillBeNoop(new FileInputType(element)); 60 } 61 62 void FileInputType::trace(Visitor* visitor) 63 { 64 visitor->trace(m_fileList); 65 BaseClickableWithKeyInputType::trace(visitor); 66 } 67 68 Vector<FileChooserFileInfo> FileInputType::filesFromFormControlState(const FormControlState& state) 69 { 70 Vector<FileChooserFileInfo> files; 71 for (size_t i = 0; i < state.valueSize(); i += 2) { 72 if (!state[i + 1].isEmpty()) 73 files.append(FileChooserFileInfo(state[i], state[i + 1])); 74 else 75 files.append(FileChooserFileInfo(state[i])); 76 } 77 return files; 78 } 79 80 const AtomicString& FileInputType::formControlType() const 81 { 82 return InputTypeNames::file; 83 } 84 85 FormControlState FileInputType::saveFormControlState() const 86 { 87 if (m_fileList->isEmpty()) 88 return FormControlState(); 89 FormControlState state; 90 unsigned numFiles = m_fileList->length(); 91 for (unsigned i = 0; i < numFiles; ++i) { 92 if (m_fileList->item(i)->hasBackingFile()) { 93 state.append(m_fileList->item(i)->path()); 94 state.append(m_fileList->item(i)->name()); 95 } 96 // FIXME: handle Blob-backed File instances, see http://crbug.com/394948 97 } 98 return state; 99 } 100 101 void FileInputType::restoreFormControlState(const FormControlState& state) 102 { 103 if (state.valueSize() % 2) 104 return; 105 filesChosen(filesFromFormControlState(state)); 106 } 107 108 bool FileInputType::appendFormData(FormDataList& encoding, bool multipart) const 109 { 110 FileList* fileList = element().files(); 111 unsigned numFiles = fileList->length(); 112 if (!multipart) { 113 // Send only the basenames. 114 // 4.10.16.4 and 4.10.16.6 sections in HTML5. 115 116 // Unlike the multipart case, we have no special handling for the empty 117 // fileList because Netscape doesn't support for non-multipart 118 // submission of file inputs, and Firefox doesn't add "name=" query 119 // parameter. 120 for (unsigned i = 0; i < numFiles; ++i) 121 encoding.appendData(element().name(), fileList->item(i)->name()); 122 return true; 123 } 124 125 // If no filename at all is entered, return successful but empty. 126 // Null would be more logical, but Netscape posts an empty file. Argh. 127 if (!numFiles) { 128 encoding.appendBlob(element().name(), File::create("")); 129 return true; 130 } 131 132 for (unsigned i = 0; i < numFiles; ++i) 133 encoding.appendBlob(element().name(), fileList->item(i)); 134 return true; 135 } 136 137 bool FileInputType::valueMissing(const String& value) const 138 { 139 return element().isRequired() && value.isEmpty(); 140 } 141 142 String FileInputType::valueMissingText() const 143 { 144 return locale().queryString(element().multiple() ? WebLocalizedString::ValidationValueMissingForMultipleFile : WebLocalizedString::ValidationValueMissingForFile); 145 } 146 147 void FileInputType::handleDOMActivateEvent(Event* event) 148 { 149 if (element().isDisabledFormControl()) 150 return; 151 152 if (!UserGestureIndicator::processingUserGesture()) 153 return; 154 155 if (Chrome* chrome = this->chrome()) { 156 FileChooserSettings settings; 157 HTMLInputElement& input = element(); 158 settings.allowsDirectoryUpload = input.fastHasAttribute(webkitdirectoryAttr); 159 settings.allowsMultipleFiles = settings.allowsDirectoryUpload || input.fastHasAttribute(multipleAttr); 160 settings.acceptMIMETypes = input.acceptMIMETypes(); 161 settings.acceptFileExtensions = input.acceptFileExtensions(); 162 settings.selectedFiles = m_fileList->pathsForUserVisibleFiles(); 163 settings.useMediaCapture = RuntimeEnabledFeatures::mediaCaptureEnabled() && input.fastHasAttribute(captureAttr); 164 chrome->runOpenPanel(input.document().frame(), newFileChooser(settings)); 165 } 166 event->setDefaultHandled(); 167 } 168 169 RenderObject* FileInputType::createRenderer(RenderStyle*) const 170 { 171 return new RenderFileUploadControl(&element()); 172 } 173 174 bool FileInputType::canSetStringValue() const 175 { 176 return false; 177 } 178 179 FileList* FileInputType::files() 180 { 181 return m_fileList.get(); 182 } 183 184 bool FileInputType::canSetValue(const String& value) 185 { 186 // For security reasons, we don't allow setting the filename, but we do allow clearing it. 187 // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't 188 // applicable to the file upload control at all, but for now we are keeping this behavior 189 // to avoid breaking existing websites that may be relying on this. 190 return value.isEmpty(); 191 } 192 193 bool FileInputType::getTypeSpecificValue(String& value) 194 { 195 if (m_fileList->isEmpty()) { 196 value = String(); 197 return true; 198 } 199 200 // HTML5 tells us that we're supposed to use this goofy value for 201 // file input controls. Historically, browsers revealed the real 202 // file path, but that's a privacy problem. Code on the web 203 // decided to try to parse the value by looking for backslashes 204 // (because that's what Windows file paths use). To be compatible 205 // with that code, we make up a fake path for the file. 206 value = "C:\\fakepath\\" + m_fileList->item(0)->name(); 207 return true; 208 } 209 210 void FileInputType::setValue(const String&, bool valueChanged, TextFieldEventBehavior) 211 { 212 if (!valueChanged) 213 return; 214 215 m_fileList->clear(); 216 element().setNeedsStyleRecalc(SubtreeStyleChange); 217 element().setNeedsValidityCheck(); 218 } 219 220 PassRefPtrWillBeRawPtr<FileList> FileInputType::createFileList(const Vector<FileChooserFileInfo>& files) const 221 { 222 RefPtrWillBeRawPtr<FileList> fileList(FileList::create()); 223 size_t size = files.size(); 224 225 // If a directory is being selected, the UI allows a directory to be chosen 226 // and the paths provided here share a root directory somewhere up the tree; 227 // we want to store only the relative paths from that point. 228 if (size && element().fastHasAttribute(webkitdirectoryAttr)) { 229 // Find the common root path. 230 String rootPath = directoryName(files[0].path); 231 for (size_t i = 1; i < size; i++) { 232 while (!files[i].path.startsWith(rootPath)) 233 rootPath = directoryName(rootPath); 234 } 235 rootPath = directoryName(rootPath); 236 ASSERT(rootPath.length()); 237 int rootLength = rootPath.length(); 238 if (rootPath[rootLength - 1] != '\\' && rootPath[rootLength - 1] != '/') 239 rootLength += 1; 240 for (size_t i = 0; i < size; i++) { 241 // Normalize backslashes to slashes before exposing the relative path to script. 242 String relativePath = files[i].path.substring(rootLength).replace('\\', '/'); 243 fileList->append(File::createWithRelativePath(files[i].path, relativePath)); 244 } 245 return fileList; 246 } 247 248 for (size_t i = 0; i < size; i++) 249 fileList->append(File::createForUserProvidedFile(files[i].path, files[i].displayName)); 250 return fileList; 251 } 252 253 void FileInputType::createShadowSubtree() 254 { 255 ASSERT(element().shadow()); 256 RefPtrWillBeRawPtr<HTMLInputElement> button = HTMLInputElement::create(element().document(), 0, false); 257 button->setType(InputTypeNames::button); 258 button->setAttribute(valueAttr, AtomicString(locale().queryString(element().multiple() ? WebLocalizedString::FileButtonChooseMultipleFilesLabel : WebLocalizedString::FileButtonChooseFileLabel))); 259 button->setShadowPseudoId(AtomicString("-webkit-file-upload-button", AtomicString::ConstructFromLiteral)); 260 element().userAgentShadowRoot()->appendChild(button.release()); 261 } 262 263 void FileInputType::disabledAttributeChanged() 264 { 265 ASSERT(element().shadow()); 266 if (Element* button = toElement(element().userAgentShadowRoot()->firstChild())) 267 button->setBooleanAttribute(disabledAttr, element().isDisabledFormControl()); 268 } 269 270 void FileInputType::multipleAttributeChanged() 271 { 272 ASSERT(element().shadow()); 273 if (Element* button = toElement(element().userAgentShadowRoot()->firstChild())) 274 button->setAttribute(valueAttr, AtomicString(locale().queryString(element().multiple() ? WebLocalizedString::FileButtonChooseMultipleFilesLabel : WebLocalizedString::FileButtonChooseFileLabel))); 275 } 276 277 void FileInputType::setFiles(PassRefPtrWillBeRawPtr<FileList> files) 278 { 279 if (!files) 280 return; 281 282 RefPtrWillBeRawPtr<HTMLInputElement> input(element()); 283 284 bool pathsChanged = false; 285 if (files->length() != m_fileList->length()) { 286 pathsChanged = true; 287 } else { 288 for (unsigned i = 0; i < files->length(); ++i) { 289 if (files->item(i)->path() != m_fileList->item(i)->path()) { 290 pathsChanged = true; 291 break; 292 } 293 } 294 } 295 296 m_fileList = files; 297 298 input->notifyFormStateChanged(); 299 input->setNeedsValidityCheck(); 300 301 if (input->renderer()) 302 input->renderer()->setShouldDoFullPaintInvalidation(true); 303 304 if (pathsChanged) { 305 // This call may cause destruction of this instance. 306 // input instance is safe since it is ref-counted. 307 input->dispatchChangeEvent(); 308 } 309 input->setChangedSinceLastFormControlChangeEvent(false); 310 } 311 312 void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& files) 313 { 314 setFiles(createFileList(files)); 315 } 316 317 void FileInputType::receiveDropForDirectoryUpload(const Vector<String>& paths) 318 { 319 if (Chrome* chrome = this->chrome()) { 320 FileChooserSettings settings; 321 HTMLInputElement& input = element(); 322 settings.allowsDirectoryUpload = true; 323 settings.allowsMultipleFiles = true; 324 settings.selectedFiles.append(paths[0]); 325 settings.acceptMIMETypes = input.acceptMIMETypes(); 326 settings.acceptFileExtensions = input.acceptFileExtensions(); 327 chrome->enumerateChosenDirectory(newFileChooser(settings)); 328 } 329 } 330 331 bool FileInputType::receiveDroppedFiles(const DragData* dragData) 332 { 333 Vector<String> paths; 334 dragData->asFilenames(paths); 335 if (paths.isEmpty()) 336 return false; 337 338 HTMLInputElement& input = element(); 339 if (input.fastHasAttribute(webkitdirectoryAttr)) { 340 receiveDropForDirectoryUpload(paths); 341 return true; 342 } 343 344 m_droppedFileSystemId = dragData->droppedFileSystemId(); 345 346 Vector<FileChooserFileInfo> files; 347 for (unsigned i = 0; i < paths.size(); ++i) 348 files.append(FileChooserFileInfo(paths[i])); 349 350 if (input.fastHasAttribute(multipleAttr)) { 351 filesChosen(files); 352 } else { 353 Vector<FileChooserFileInfo> firstFileOnly; 354 firstFileOnly.append(files[0]); 355 filesChosen(firstFileOnly); 356 } 357 return true; 358 } 359 360 String FileInputType::droppedFileSystemId() 361 { 362 return m_droppedFileSystemId; 363 } 364 365 String FileInputType::defaultToolTip() const 366 { 367 FileList* fileList = m_fileList.get(); 368 unsigned listSize = fileList->length(); 369 if (!listSize) { 370 return locale().queryString(WebLocalizedString::FileButtonNoFileSelectedLabel); 371 } 372 373 StringBuilder names; 374 for (size_t i = 0; i < listSize; ++i) { 375 names.append(fileList->item(i)->name()); 376 if (i != listSize - 1) 377 names.append('\n'); 378 } 379 return names.toString(); 380 } 381 382 } // namespace blink 383