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