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.
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.
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.
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.
23 #include "core/html/forms/FileInputType.h"
25 #include "HTMLNames.h"
26 #include "InputTypeNames.h"
27 #include "RuntimeEnabledFeatures.h"
28 #include "bindings/v8/ExceptionStatePlaceholder.h"
29 #include "core/dom/shadow/ShadowRoot.h"
30 #include "core/events/Event.h"
31 #include "core/fileapi/File.h"
32 #include "core/fileapi/FileList.h"
33 #include "core/html/FormDataList.h"
34 #include "core/html/HTMLInputElement.h"
35 #include "core/html/forms/FormController.h"
36 #include "core/page/Chrome.h"
37 #include "core/page/DragData.h"
38 #include "core/rendering/RenderFileUploadControl.h"
39 #include "platform/FileMetadata.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"
48 using blink::WebLocalizedString;
49 using namespace HTMLNames;
51 inline FileInputType::FileInputType(HTMLInputElement& element)
52 : BaseClickableWithKeyInputType(element)
53 , m_fileList(FileList::create())
57 PassRefPtrWillBeRawPtr<InputType> FileInputType::create(HTMLInputElement& element)
59 return adoptRefWillBeNoop(new FileInputType(element));
62 void FileInputType::trace(Visitor* visitor)
64 visitor->trace(m_fileList);
65 BaseClickableWithKeyInputType::trace(visitor);
68 Vector<FileChooserFileInfo> FileInputType::filesFromFormControlState(const FormControlState& state)
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]));
75 files.append(FileChooserFileInfo(state[i]));
80 const AtomicString& FileInputType::formControlType() const
82 return InputTypeNames::file;
85 FormControlState FileInputType::saveFormControlState() const
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 state.append(m_fileList->item(i)->path());
93 state.append(m_fileList->item(i)->name());
98 void FileInputType::restoreFormControlState(const FormControlState& state)
100 if (state.valueSize() % 2)
102 filesChosen(filesFromFormControlState(state));
105 bool FileInputType::appendFormData(FormDataList& encoding, bool multipart) const
107 FileList* fileList = element().files();
108 unsigned numFiles = fileList->length();
110 // Send only the basenames.
111 // 4.10.16.4 and 4.10.16.6 sections in HTML5.
113 // Unlike the multipart case, we have no special handling for the empty
114 // fileList because Netscape doesn't support for non-multipart
115 // submission of file inputs, and Firefox doesn't add "name=" query
117 for (unsigned i = 0; i < numFiles; ++i)
118 encoding.appendData(element().name(), fileList->item(i)->name());
122 // If no filename at all is entered, return successful but empty.
123 // Null would be more logical, but Netscape posts an empty file. Argh.
125 encoding.appendBlob(element().name(), File::create(""));
129 for (unsigned i = 0; i < numFiles; ++i)
130 encoding.appendBlob(element().name(), fileList->item(i));
134 bool FileInputType::valueMissing(const String& value) const
136 return element().isRequired() && value.isEmpty();
139 String FileInputType::valueMissingText() const
141 return locale().queryString(element().multiple() ? WebLocalizedString::ValidationValueMissingForMultipleFile : WebLocalizedString::ValidationValueMissingForFile);
144 void FileInputType::handleDOMActivateEvent(Event* event)
146 if (element().isDisabledFormControl())
149 if (!UserGestureIndicator::processingUserGesture())
152 if (Chrome* chrome = this->chrome()) {
153 FileChooserSettings settings;
154 HTMLInputElement& input = element();
155 settings.allowsDirectoryUpload = input.fastHasAttribute(webkitdirectoryAttr);
156 settings.allowsMultipleFiles = settings.allowsDirectoryUpload || input.fastHasAttribute(multipleAttr);
157 settings.acceptMIMETypes = input.acceptMIMETypes();
158 settings.acceptFileExtensions = input.acceptFileExtensions();
159 settings.selectedFiles = m_fileList->paths();
160 #if ENABLE(MEDIA_CAPTURE)
161 settings.useMediaCapture = input.capture();
163 chrome->runOpenPanel(input.document().frame(), newFileChooser(settings));
165 event->setDefaultHandled();
168 RenderObject* FileInputType::createRenderer(RenderStyle*) const
170 return new RenderFileUploadControl(&element());
173 bool FileInputType::canSetStringValue() const
178 FileList* FileInputType::files()
180 return m_fileList.get();
183 bool FileInputType::canSetValue(const String& value)
185 // For security reasons, we don't allow setting the filename, but we do allow clearing it.
186 // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't
187 // applicable to the file upload control at all, but for now we are keeping this behavior
188 // to avoid breaking existing websites that may be relying on this.
189 return value.isEmpty();
192 bool FileInputType::getTypeSpecificValue(String& value)
194 if (m_fileList->isEmpty()) {
199 // HTML5 tells us that we're supposed to use this goofy value for
200 // file input controls. Historically, browsers revealed the real
201 // file path, but that's a privacy problem. Code on the web
202 // decided to try to parse the value by looking for backslashes
203 // (because that's what Windows file paths use). To be compatible
204 // with that code, we make up a fake path for the file.
205 value = "C:\\fakepath\\" + m_fileList->item(0)->name();
209 void FileInputType::setValue(const String&, bool valueChanged, TextFieldEventBehavior)
215 element().setNeedsStyleRecalc(SubtreeStyleChange);
216 element().setNeedsValidityCheck();
219 PassRefPtrWillBeRawPtr<FileList> FileInputType::createFileList(const Vector<FileChooserFileInfo>& files) const
221 RefPtrWillBeRawPtr<FileList> fileList(FileList::create());
222 size_t size = files.size();
224 // If a directory is being selected, the UI allows a directory to be chosen
225 // and the paths provided here share a root directory somewhere up the tree;
226 // we want to store only the relative paths from that point.
227 if (size && element().fastHasAttribute(webkitdirectoryAttr)) {
228 // Find the common root path.
229 String rootPath = directoryName(files[0].path);
230 for (size_t i = 1; i < size; i++) {
231 while (!files[i].path.startsWith(rootPath))
232 rootPath = directoryName(rootPath);
234 rootPath = directoryName(rootPath);
235 ASSERT(rootPath.length());
236 int rootLength = rootPath.length();
237 if (rootPath[rootLength - 1] != '\\' && rootPath[rootLength - 1] != '/')
239 for (size_t i = 0; i < size; i++) {
240 // Normalize backslashes to slashes before exposing the relative path to script.
241 String relativePath = files[i].path.substring(rootLength).replace('\\', '/');
242 fileList->append(File::createWithRelativePath(files[i].path, relativePath));
247 for (size_t i = 0; i < size; i++)
248 fileList->append(File::createWithName(files[i].path, files[i].displayName, File::AllContentTypes));
252 bool FileInputType::isFileUpload() const
257 void FileInputType::createShadowSubtree()
259 ASSERT(element().shadow());
260 RefPtr<HTMLInputElement> button = HTMLInputElement::create(element().document(), 0, false);
261 button->setType(InputTypeNames::button);
262 button->setAttribute(valueAttr, AtomicString(locale().queryString(element().multiple() ? WebLocalizedString::FileButtonChooseMultipleFilesLabel : WebLocalizedString::FileButtonChooseFileLabel)));
263 button->setShadowPseudoId(AtomicString("-webkit-file-upload-button", AtomicString::ConstructFromLiteral));
264 element().userAgentShadowRoot()->appendChild(button.release());
267 void FileInputType::disabledAttributeChanged()
269 ASSERT(element().shadow());
270 if (Element* button = toElement(element().userAgentShadowRoot()->firstChild()))
271 button->setBooleanAttribute(disabledAttr, element().isDisabledFormControl());
274 void FileInputType::multipleAttributeChanged()
276 ASSERT(element().shadow());
277 if (Element* button = toElement(element().userAgentShadowRoot()->firstChild()))
278 button->setAttribute(valueAttr, AtomicString(locale().queryString(element().multiple() ? WebLocalizedString::FileButtonChooseMultipleFilesLabel : WebLocalizedString::FileButtonChooseFileLabel)));
281 void FileInputType::setFiles(PassRefPtrWillBeRawPtr<FileList> files)
286 RefPtr<HTMLInputElement> input(element());
288 bool pathsChanged = false;
289 if (files->length() != m_fileList->length()) {
292 for (unsigned i = 0; i < files->length(); ++i) {
293 if (files->item(i)->path() != m_fileList->item(i)->path()) {
302 input->notifyFormStateChanged();
303 input->setNeedsValidityCheck();
305 if (input->renderer())
306 input->renderer()->repaint();
309 // This call may cause destruction of this instance.
310 // input instance is safe since it is ref-counted.
311 input->dispatchChangeEvent();
313 input->setChangedSinceLastFormControlChangeEvent(false);
316 void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& files)
318 setFiles(createFileList(files));
321 void FileInputType::receiveDropForDirectoryUpload(const Vector<String>& paths)
323 if (Chrome* chrome = this->chrome()) {
324 FileChooserSettings settings;
325 HTMLInputElement& input = element();
326 settings.allowsDirectoryUpload = true;
327 settings.allowsMultipleFiles = true;
328 settings.selectedFiles.append(paths[0]);
329 settings.acceptMIMETypes = input.acceptMIMETypes();
330 settings.acceptFileExtensions = input.acceptFileExtensions();
331 chrome->enumerateChosenDirectory(newFileChooser(settings));
335 bool FileInputType::receiveDroppedFiles(const DragData* dragData)
337 Vector<String> paths;
338 dragData->asFilenames(paths);
342 HTMLInputElement& input = element();
343 if (input.fastHasAttribute(webkitdirectoryAttr)) {
344 receiveDropForDirectoryUpload(paths);
348 m_droppedFileSystemId = dragData->droppedFileSystemId();
350 Vector<FileChooserFileInfo> files;
351 for (unsigned i = 0; i < paths.size(); ++i)
352 files.append(FileChooserFileInfo(paths[i]));
354 if (input.fastHasAttribute(multipleAttr)) {
357 Vector<FileChooserFileInfo> firstFileOnly;
358 firstFileOnly.append(files[0]);
359 filesChosen(firstFileOnly);
364 String FileInputType::droppedFileSystemId()
366 return m_droppedFileSystemId;
369 void FileInputType::copyNonAttributeProperties(const HTMLInputElement& sourceElement)
371 RefPtrWillBeRawPtr<FileList> fileList(FileList::create());
372 FileList* sourceFileList = sourceElement.files();
373 unsigned size = sourceFileList->length();
374 for (unsigned i = 0; i < size; ++i) {
375 File* file = sourceFileList->item(i);
376 fileList->append(File::createWithRelativePath(file->path(), file->webkitRelativePath()));
378 setFiles(fileList.release());
381 String FileInputType::defaultToolTip() const
383 FileList* fileList = m_fileList.get();
384 unsigned listSize = fileList->length();
386 return locale().queryString(WebLocalizedString::FileButtonNoFileSelectedLabel);
390 for (size_t i = 0; i < listSize; ++i) {
391 names.append(fileList->item(i)->name());
392 if (i != listSize - 1)
395 return names.toString();
398 } // namespace WebCore