Refactoring: Replace Element::disabled and isEnabledFormControl with isDisabledFormCo...
[WebKit-https.git] / Source / WebCore / html / FileInputType.cpp
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 "FileInputType.h"
24
25 #include "Chrome.h"
26 #include "DragData.h"
27 #include "ElementShadow.h"
28 #include "Event.h"
29 #include "File.h"
30 #include "FileList.h"
31 #include "FileSystem.h"
32 #include "FormController.h"
33 #include "FormDataList.h"
34 #include "Frame.h"
35 #include "HTMLInputElement.h"
36 #include "HTMLNames.h"
37 #include "Icon.h"
38 #include "InputTypeNames.h"
39 #include "LocalizedStrings.h"
40 #include "RenderFileUploadControl.h"
41 #include "ScriptController.h"
42 #include "ShadowRoot.h"
43 #include <wtf/PassOwnPtr.h>
44 #include <wtf/text/StringBuilder.h>
45 #include <wtf/text/WTFString.h>
46
47 namespace WebCore {
48
49 using namespace HTMLNames;
50
51 class UploadButtonElement : public HTMLInputElement {
52 public:
53     static PassRefPtr<UploadButtonElement> create(Document*);
54     static PassRefPtr<UploadButtonElement> createForMultiple(Document*);
55
56 private:
57     UploadButtonElement(Document*);
58
59     virtual const AtomicString& shadowPseudoId() const;
60 };
61
62 PassRefPtr<UploadButtonElement> UploadButtonElement::create(Document* document)
63 {
64     RefPtr<UploadButtonElement> button = adoptRef(new UploadButtonElement(document));
65     button->setType("button");
66     button->setValue(fileButtonChooseFileLabel());
67     return button.release();
68 }
69
70 PassRefPtr<UploadButtonElement> UploadButtonElement::createForMultiple(Document* document)
71 {
72     RefPtr<UploadButtonElement> button = adoptRef(new UploadButtonElement(document));
73     button->setType("button");
74     button->setValue(fileButtonChooseMultipleFilesLabel());
75     return button.release();
76 }
77
78 UploadButtonElement::UploadButtonElement(Document* document)
79     : HTMLInputElement(inputTag, document, 0, false)
80 {
81 }
82
83 const AtomicString& UploadButtonElement::shadowPseudoId() const
84 {
85     DEFINE_STATIC_LOCAL(AtomicString, pseudoId, ("-webkit-file-upload-button", AtomicString::ConstructFromLiteral));
86     return pseudoId;
87 }
88
89 inline FileInputType::FileInputType(HTMLInputElement* element)
90     : BaseClickableWithKeyInputType(element)
91     , m_fileList(FileList::create())
92 {
93 }
94
95 PassOwnPtr<InputType> FileInputType::create(HTMLInputElement* element)
96 {
97     return adoptPtr(new FileInputType(element));
98 }
99
100 Vector<FileChooserFileInfo> FileInputType::filesFromFormControlState(const FormControlState& state)
101 {
102     Vector<FileChooserFileInfo> files;
103     for (size_t i = 0; i < state.valueSize(); i += 2) {
104         if (!state[i + 1].isEmpty())
105             files.append(FileChooserFileInfo(state[i], state[i + 1]));
106         else
107             files.append(FileChooserFileInfo(state[i]));
108     }
109     return files;
110 }
111
112 const AtomicString& FileInputType::formControlType() const
113 {
114     return InputTypeNames::file();
115 }
116
117 FormControlState FileInputType::saveFormControlState() const
118 {
119     if (m_fileList->isEmpty())
120         return FormControlState();
121     FormControlState state;
122     unsigned numFiles = m_fileList->length();
123     for (unsigned i = 0; i < numFiles; ++i) {
124         state.append(m_fileList->item(i)->path());
125         state.append(m_fileList->item(i)->name());
126     }
127     return state;
128 }
129
130 void FileInputType::restoreFormControlState(const FormControlState& state)
131 {
132     if (state.valueSize() % 2)
133         return;
134     filesChosen(filesFromFormControlState(state));
135 }
136
137 bool FileInputType::appendFormData(FormDataList& encoding, bool multipart) const
138 {
139     FileList* fileList = element()->files();
140     unsigned numFiles = fileList->length();
141     if (!multipart) {
142         // Send only the basenames.
143         // 4.10.16.4 and 4.10.16.6 sections in HTML5.
144
145         // Unlike the multipart case, we have no special handling for the empty
146         // fileList because Netscape doesn't support for non-multipart
147         // submission of file inputs, and Firefox doesn't add "name=" query
148         // parameter.
149         for (unsigned i = 0; i < numFiles; ++i)
150             encoding.appendData(element()->name(), fileList->item(i)->name());
151         return true;
152     }
153
154     // If no filename at all is entered, return successful but empty.
155     // Null would be more logical, but Netscape posts an empty file. Argh.
156     if (!numFiles) {
157         encoding.appendBlob(element()->name(), File::create(""));
158         return true;
159     }
160
161     for (unsigned i = 0; i < numFiles; ++i)
162         encoding.appendBlob(element()->name(), fileList->item(i));
163     return true;
164 }
165
166 bool FileInputType::valueMissing(const String& value) const
167 {
168     return element()->isRequired() && value.isEmpty();
169 }
170
171 String FileInputType::valueMissingText() const
172 {
173     return element()->multiple() ? validationMessageValueMissingForMultipleFileText() : validationMessageValueMissingForFileText();
174 }
175
176 void FileInputType::handleDOMActivateEvent(Event* event)
177 {
178     if (element()->isDisabledFormControl())
179         return;
180
181     if (!ScriptController::processingUserGesture())
182         return;
183
184     if (Chrome* chrome = this->chrome()) {
185         FileChooserSettings settings;
186         HTMLInputElement* input = element();
187 #if ENABLE(DIRECTORY_UPLOAD)
188         settings.allowsDirectoryUpload = input->fastHasAttribute(webkitdirectoryAttr);
189         settings.allowsMultipleFiles = settings.allowsDirectoryUpload || input->fastHasAttribute(multipleAttr);
190 #else
191         settings.allowsMultipleFiles = input->fastHasAttribute(multipleAttr);
192 #endif
193         settings.acceptMIMETypes = input->acceptMIMETypes();
194         settings.acceptFileExtensions = input->acceptFileExtensions();
195         settings.selectedFiles = m_fileList->paths();
196 #if ENABLE(MEDIA_CAPTURE)
197         settings.capture = input->capture();
198 #endif
199         chrome->runOpenPanel(input->document()->frame(), newFileChooser(settings));
200     }
201     event->setDefaultHandled();
202 }
203
204 RenderObject* FileInputType::createRenderer(RenderArena* arena, RenderStyle*) const
205 {
206     return new (arena) RenderFileUploadControl(element());
207 }
208
209 bool FileInputType::canSetStringValue() const
210 {
211     return false;
212 }
213
214 bool FileInputType::canChangeFromAnotherType() const
215 {
216     // Don't allow the type to be changed to file after the first type change.
217     // In other engines this might mean a JavaScript programmer could set a text
218     // field's value to something like /etc/passwd and then change it to a file input.
219     // I don't think this would actually occur in WebKit, but this rule still may be
220     // important for compatibility.
221     return false;
222 }
223
224 FileList* FileInputType::files()
225 {
226     return m_fileList.get();
227 }
228
229 bool FileInputType::canSetValue(const String& value)
230 {
231     // For security reasons, we don't allow setting the filename, but we do allow clearing it.
232     // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't
233     // applicable to the file upload control at all, but for now we are keeping this behavior
234     // to avoid breaking existing websites that may be relying on this.
235     return value.isEmpty();
236 }
237
238 bool FileInputType::getTypeSpecificValue(String& value)
239 {
240     if (m_fileList->isEmpty()) {
241         value = String();
242         return true;
243     }
244
245     // HTML5 tells us that we're supposed to use this goofy value for
246     // file input controls. Historically, browsers revealed the real
247     // file path, but that's a privacy problem. Code on the web
248     // decided to try to parse the value by looking for backslashes
249     // (because that's what Windows file paths use). To be compatible
250     // with that code, we make up a fake path for the file.
251     value = "C:\\fakepath\\" + m_fileList->item(0)->name();
252     return true;
253 }
254
255 void FileInputType::setValue(const String&, bool, TextFieldEventBehavior)
256 {
257     m_fileList->clear();
258     m_icon.clear();
259     element()->setNeedsStyleRecalc();
260 }
261
262 PassRefPtr<FileList> FileInputType::createFileList(const Vector<FileChooserFileInfo>& files) const
263 {
264     RefPtr<FileList> fileList(FileList::create());
265     size_t size = files.size();
266
267 #if ENABLE(DIRECTORY_UPLOAD)
268     // If a directory is being selected, the UI allows a directory to be chosen
269     // and the paths provided here share a root directory somewhere up the tree;
270     // we want to store only the relative paths from that point.
271     if (size && element()->fastHasAttribute(webkitdirectoryAttr)) {
272         // Find the common root path.
273         String rootPath = directoryName(files[0].path);
274         for (size_t i = 1; i < size; i++) {
275             while (!files[i].path.startsWith(rootPath))
276                 rootPath = directoryName(rootPath);
277         }
278         rootPath = directoryName(rootPath);
279         ASSERT(rootPath.length());
280         int rootLength = rootPath.length();
281         if (rootPath[rootLength - 1] != '\\' && rootPath[rootLength - 1] != '/')
282             rootLength += 1;
283         for (size_t i = 0; i < size; i++) {
284             // Normalize backslashes to slashes before exposing the relative path to script.
285             String relativePath = files[i].path.substring(rootLength).replace('\\', '/');
286             fileList->append(File::createWithRelativePath(files[i].path, relativePath));
287         }
288         return fileList;
289     }
290 #endif
291
292     for (size_t i = 0; i < size; i++)
293         fileList->append(File::createWithName(files[i].path, files[i].displayName, File::AllContentTypes));
294     return fileList;
295 }
296
297 bool FileInputType::isFileUpload() const
298 {
299     return true;
300 }
301
302 void FileInputType::createShadowSubtree()
303 {
304     ASSERT(element()->shadow());
305     element()->userAgentShadowRoot()->appendChild(element()->multiple() ? UploadButtonElement::createForMultiple(element()->document()): UploadButtonElement::create(element()->document()), IGNORE_EXCEPTION);
306 }
307
308 void FileInputType::disabledAttributeChanged()
309 {
310     ASSERT(element()->shadow());
311     UploadButtonElement* button = static_cast<UploadButtonElement*>(element()->userAgentShadowRoot()->firstChild());
312     if (button)
313         button->setBooleanAttribute(disabledAttr, element()->isDisabledFormControl());
314 }
315
316 void FileInputType::multipleAttributeChanged()
317 {
318     ASSERT(element()->shadow());
319     UploadButtonElement* button = static_cast<UploadButtonElement*>(element()->userAgentShadowRoot()->firstChild());
320     if (button)
321         button->setValue(element()->multiple() ? fileButtonChooseMultipleFilesLabel() : fileButtonChooseFileLabel());
322 }
323
324 void FileInputType::requestIcon(const Vector<String>& paths)
325 {
326     if (!paths.size())
327         return;
328
329     if (Chrome* chrome = this->chrome())
330         chrome->loadIconForFiles(paths, newFileIconLoader());
331 }
332
333 void FileInputType::setFiles(PassRefPtr<FileList> files)
334 {
335     if (!files)
336         return;
337
338     RefPtr<HTMLInputElement> input = element();
339
340     bool pathsChanged = false;
341     if (files->length() != m_fileList->length())
342         pathsChanged = true;
343     else {
344         for (unsigned i = 0; i < files->length(); ++i) {
345             if (files->item(i)->path() != m_fileList->item(i)->path()) {
346                 pathsChanged = true;
347                 break;
348             }
349         }
350     }
351
352     m_fileList = files;
353
354     input->setFormControlValueMatchesRenderer(true);
355     input->notifyFormStateChanged();
356     input->setNeedsValidityCheck();
357
358     Vector<String> paths;
359     for (unsigned i = 0; i < m_fileList->length(); ++i)
360         paths.append(m_fileList->item(i)->path());
361     requestIcon(paths);
362
363     if (input->renderer())
364         input->renderer()->repaint();
365
366     if (pathsChanged) {
367         // This call may cause destruction of this instance.
368         // input instance is safe since it is ref-counted.
369         input->HTMLElement::dispatchChangeEvent();
370     }
371     input->setChangedSinceLastFormControlChangeEvent(false);
372 }
373
374 void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& files)
375 {
376     setFiles(createFileList(files));
377 }
378
379 #if ENABLE(DIRECTORY_UPLOAD)
380 void FileInputType::receiveDropForDirectoryUpload(const Vector<String>& paths)
381 {
382     if (Chrome* chrome = this->chrome()) {
383         FileChooserSettings settings;
384         HTMLInputElement* input = element();
385         settings.allowsDirectoryUpload = true;
386         settings.allowsMultipleFiles = true;
387         settings.selectedFiles.append(paths[0]);
388         settings.acceptMIMETypes = input->acceptMIMETypes();
389         settings.acceptFileExtensions = input->acceptFileExtensions();
390         chrome->enumerateChosenDirectory(newFileChooser(settings));
391     }
392 }
393 #endif
394
395 void FileInputType::updateRendering(PassRefPtr<Icon> icon)
396 {
397     if (m_icon == icon)
398         return;
399
400     m_icon = icon;
401     if (element()->renderer())
402         element()->renderer()->repaint();
403 }
404
405 bool FileInputType::receiveDroppedFiles(const DragData* dragData)
406 {
407     Vector<String> paths;
408     dragData->asFilenames(paths);
409     if (paths.isEmpty())
410         return false;
411
412     HTMLInputElement* input = element();
413 #if ENABLE(DIRECTORY_UPLOAD)
414     if (input->fastHasAttribute(webkitdirectoryAttr)) {
415         receiveDropForDirectoryUpload(paths);
416         return true;
417     }
418 #endif
419
420 #if ENABLE(FILE_SYSTEM)
421     m_droppedFileSystemId = dragData->droppedFileSystemId();
422 #endif
423
424     Vector<FileChooserFileInfo> files;
425     for (unsigned i = 0; i < paths.size(); ++i)
426         files.append(FileChooserFileInfo(paths[i]));
427
428     if (input->fastHasAttribute(multipleAttr))
429         filesChosen(files);
430     else {
431         Vector<FileChooserFileInfo> firstFileOnly;
432         firstFileOnly.append(files[0]);
433         filesChosen(firstFileOnly);
434     }
435     return true;
436 }
437
438 #if ENABLE(FILE_SYSTEM)
439 String FileInputType::droppedFileSystemId()
440 {
441     return m_droppedFileSystemId;
442 }
443 #endif
444
445 Icon* FileInputType::icon() const
446 {
447     return m_icon.get();
448 }
449
450 String FileInputType::defaultToolTip() const
451 {
452     FileList* fileList = m_fileList.get();
453     unsigned listSize = fileList->length();
454     if (!listSize) {
455         if (element()->multiple())
456             return fileButtonNoFilesSelectedLabel();
457         return fileButtonNoFileSelectedLabel();
458     }
459
460     StringBuilder names;
461     for (size_t i = 0; i < listSize; ++i) {
462         names.append(fileList->item(i)->name());
463         if (i != listSize - 1)
464             names.append('\n');
465     }
466     return names.toString();
467 }
468
469 } // namespace WebCore