Remove ENABLE(DIRECTORY_UPLOAD).
[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 "Event.h"
28 #include "File.h"
29 #include "FileList.h"
30 #include "FileSystem.h"
31 #include "FormController.h"
32 #include "FormDataList.h"
33 #include "Frame.h"
34 #include "HTMLInputElement.h"
35 #include "HTMLNames.h"
36 #include "Icon.h"
37 #include "InputTypeNames.h"
38 #include "LocalizedStrings.h"
39 #include "RenderFileUploadControl.h"
40 #include "ScriptController.h"
41 #include "ShadowRoot.h"
42 #include <wtf/text/StringBuilder.h>
43
44 namespace WebCore {
45
46 using namespace HTMLNames;
47
48 class UploadButtonElement : public HTMLInputElement {
49 public:
50     static PassRefPtr<UploadButtonElement> create(Document&);
51     static PassRefPtr<UploadButtonElement> createForMultiple(Document&);
52
53 private:
54     UploadButtonElement(Document&);
55
56     virtual const AtomicString& shadowPseudoId() const override;
57 };
58
59 PassRefPtr<UploadButtonElement> UploadButtonElement::create(Document& document)
60 {
61     RefPtr<UploadButtonElement> button = adoptRef(new UploadButtonElement(document));
62     button->setType("button");
63     button->setValue(fileButtonChooseFileLabel());
64     return button.release();
65 }
66
67 PassRefPtr<UploadButtonElement> UploadButtonElement::createForMultiple(Document& document)
68 {
69     RefPtr<UploadButtonElement> button = adoptRef(new UploadButtonElement(document));
70     button->setType("button");
71     button->setValue(fileButtonChooseMultipleFilesLabel());
72     return button.release();
73 }
74
75 UploadButtonElement::UploadButtonElement(Document& document)
76     : HTMLInputElement(inputTag, document, 0, false)
77 {
78 }
79
80 const AtomicString& UploadButtonElement::shadowPseudoId() const
81 {
82     DEFINE_STATIC_LOCAL(AtomicString, pseudoId, ("-webkit-file-upload-button", AtomicString::ConstructFromLiteral));
83     return pseudoId;
84 }
85
86 FileInputType::FileInputType(HTMLInputElement& element)
87     : BaseClickableWithKeyInputType(element)
88     , m_fileList(FileList::create())
89 {
90 }
91
92 FileInputType::~FileInputType()
93 {
94     if (m_fileChooser)
95         m_fileChooser->invalidate();
96
97 #if !PLATFORM(IOS)
98     // FIXME: Is this correct? Why don't we do this on iOS?
99     if (m_fileIconLoader)
100         m_fileIconLoader->invalidate();
101 #endif
102 }
103
104 Vector<FileChooserFileInfo> FileInputType::filesFromFormControlState(const FormControlState& state)
105 {
106     Vector<FileChooserFileInfo> files;
107     for (size_t i = 0; i < state.valueSize(); i += 2) {
108         if (!state[i + 1].isEmpty())
109             files.append(FileChooserFileInfo(state[i], state[i + 1]));
110         else
111             files.append(FileChooserFileInfo(state[i]));
112     }
113     return files;
114 }
115
116 const AtomicString& FileInputType::formControlType() const
117 {
118     return InputTypeNames::file();
119 }
120
121 FormControlState FileInputType::saveFormControlState() const
122 {
123     if (m_fileList->isEmpty())
124         return FormControlState();
125     FormControlState state;
126     unsigned numFiles = m_fileList->length();
127     for (unsigned i = 0; i < numFiles; ++i) {
128         state.append(m_fileList->item(i)->path());
129         state.append(m_fileList->item(i)->name());
130     }
131     return state;
132 }
133
134 void FileInputType::restoreFormControlState(const FormControlState& state)
135 {
136     if (state.valueSize() % 2)
137         return;
138     filesChosen(filesFromFormControlState(state));
139 }
140
141 bool FileInputType::appendFormData(FormDataList& encoding, bool multipart) const
142 {
143     FileList* fileList = element().files();
144     unsigned numFiles = fileList->length();
145     if (!multipart) {
146         // Send only the basenames.
147         // 4.10.16.4 and 4.10.16.6 sections in HTML5.
148
149         // Unlike the multipart case, we have no special handling for the empty
150         // fileList because Netscape doesn't support for non-multipart
151         // submission of file inputs, and Firefox doesn't add "name=" query
152         // parameter.
153         for (unsigned i = 0; i < numFiles; ++i)
154             encoding.appendData(element().name(), fileList->item(i)->name());
155         return true;
156     }
157
158     // If no filename at all is entered, return successful but empty.
159     // Null would be more logical, but Netscape posts an empty file. Argh.
160     if (!numFiles) {
161         encoding.appendBlob(element().name(), File::create(""));
162         return true;
163     }
164
165     for (unsigned i = 0; i < numFiles; ++i)
166         encoding.appendBlob(element().name(), fileList->item(i));
167     return true;
168 }
169
170 bool FileInputType::valueMissing(const String& value) const
171 {
172     return element().isRequired() && value.isEmpty();
173 }
174
175 String FileInputType::valueMissingText() const
176 {
177     return element().multiple() ? validationMessageValueMissingForMultipleFileText() : validationMessageValueMissingForFileText();
178 }
179
180 void FileInputType::handleDOMActivateEvent(Event* event)
181 {
182     if (element().isDisabledFormControl())
183         return;
184
185     if (!ScriptController::processingUserGesture())
186         return;
187
188     if (Chrome* chrome = this->chrome()) {
189         FileChooserSettings settings;
190         HTMLInputElement& input = element();
191         settings.allowsMultipleFiles = input.fastHasAttribute(multipleAttr);
192         settings.acceptMIMETypes = input.acceptMIMETypes();
193         settings.acceptFileExtensions = input.acceptFileExtensions();
194         settings.selectedFiles = m_fileList->paths();
195 #if ENABLE(MEDIA_CAPTURE)
196         settings.capture = input.capture();
197 #endif
198
199         applyFileChooserSettings(settings);
200         chrome->runOpenPanel(input.document().frame(), m_fileChooser);
201     }
202
203     event->setDefaultHandled();
204 }
205
206 RenderPtr<RenderElement> FileInputType::createInputRenderer(PassRef<RenderStyle> style)
207 {
208     return createRenderer<RenderFileUploadControl>(element(), std::move(style));
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     for (size_t i = 0; i < size; i++)
270         fileList->append(File::createWithName(files[i].path, files[i].displayName, File::AllContentTypes));
271     return fileList;
272 }
273
274 bool FileInputType::isFileUpload() const
275 {
276     return true;
277 }
278
279 void FileInputType::createShadowSubtree()
280 {
281     ASSERT(element().shadowRoot());
282     element().userAgentShadowRoot()->appendChild(element().multiple() ? UploadButtonElement::createForMultiple(element().document()): UploadButtonElement::create(element().document()), IGNORE_EXCEPTION);
283 }
284
285 void FileInputType::disabledAttributeChanged()
286 {
287     ASSERT(element().shadowRoot());
288     UploadButtonElement* button = static_cast<UploadButtonElement*>(element().userAgentShadowRoot()->firstChild());
289     if (button)
290         button->setBooleanAttribute(disabledAttr, element().isDisabledFormControl());
291 }
292
293 void FileInputType::multipleAttributeChanged()
294 {
295     ASSERT(element().shadowRoot());
296     UploadButtonElement* button = static_cast<UploadButtonElement*>(element().userAgentShadowRoot()->firstChild());
297     if (button)
298         button->setValue(element().multiple() ? fileButtonChooseMultipleFilesLabel() : fileButtonChooseFileLabel());
299 }
300
301 void FileInputType::requestIcon(const Vector<String>& paths)
302 {
303 #if PLATFORM(IOS)
304     UNUSED_PARAM(paths);
305 #else
306     if (!paths.size())
307         return;
308
309     Chrome* chrome = this->chrome();
310     if (!chrome)
311         return;
312
313     if (m_fileIconLoader)
314         m_fileIconLoader->invalidate();
315
316     m_fileIconLoader = std::make_unique<FileIconLoader>(static_cast<FileIconLoaderClient&>(*this));
317
318     chrome->loadIconForFiles(paths, m_fileIconLoader.get());
319 #endif
320 }
321
322 void FileInputType::applyFileChooserSettings(const FileChooserSettings& settings)
323 {
324     if (m_fileChooser)
325         m_fileChooser->invalidate();
326
327     m_fileChooser = FileChooser::create(this, settings);
328 }
329
330 void FileInputType::setFiles(PassRefPtr<FileList> files)
331 {
332     if (!files)
333         return;
334
335     Ref<HTMLInputElement> input(element());
336
337     bool pathsChanged = false;
338     if (files->length() != m_fileList->length())
339         pathsChanged = true;
340     else {
341         for (unsigned i = 0; i < files->length(); ++i) {
342             if (files->item(i)->path() != m_fileList->item(i)->path()) {
343                 pathsChanged = true;
344                 break;
345             }
346         }
347     }
348
349     m_fileList = files;
350
351     input->setFormControlValueMatchesRenderer(true);
352     input->notifyFormStateChanged();
353     input->setNeedsValidityCheck();
354
355     Vector<String> paths;
356     for (unsigned i = 0; i < m_fileList->length(); ++i)
357         paths.append(m_fileList->item(i)->path());
358     requestIcon(paths);
359
360     if (input->renderer())
361         input->renderer()->repaint();
362
363     if (pathsChanged) {
364         // This call may cause destruction of this instance.
365         // input instance is safe since it is ref-counted.
366         input->dispatchChangeEvent();
367     }
368     input->setChangedSinceLastFormControlChangeEvent(false);
369 }
370
371 #if PLATFORM(IOS)
372 void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& paths, const String& displayString, Icon* icon)
373 {
374     m_displayString = displayString;
375     filesChosen(paths);
376     updateRendering(icon);
377 }
378
379 String FileInputType::displayString() const
380 {
381     return m_displayString;
382 }
383 #endif
384
385 void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& files)
386 {
387     setFiles(createFileList(files));
388 }
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 #if ENABLE(DRAG_SUPPORT)
401 bool FileInputType::receiveDroppedFiles(const DragData& dragData)
402 {
403     Vector<String> paths;
404     dragData.asFilenames(paths);
405     if (paths.isEmpty())
406         return false;
407
408     HTMLInputElement* input = &element();
409
410     Vector<FileChooserFileInfo> files;
411     for (unsigned i = 0; i < paths.size(); ++i)
412         files.append(FileChooserFileInfo(paths[i]));
413
414     if (input->fastHasAttribute(multipleAttr))
415         filesChosen(files);
416     else {
417         Vector<FileChooserFileInfo> firstFileOnly;
418         firstFileOnly.append(files[0]);
419         filesChosen(firstFileOnly);
420     }
421     return true;
422 }
423 #endif // ENABLE(DRAG_SUPPORT)
424
425 Icon* FileInputType::icon() const
426 {
427     return m_icon.get();
428 }
429
430 String FileInputType::defaultToolTip() const
431 {
432     FileList* fileList = m_fileList.get();
433     unsigned listSize = fileList->length();
434     if (!listSize) {
435         if (element().multiple())
436             return fileButtonNoFilesSelectedLabel();
437         return fileButtonNoFileSelectedLabel();
438     }
439
440     StringBuilder names;
441     for (size_t i = 0; i < listSize; ++i) {
442         names.append(fileList->item(i)->name());
443         if (i != listSize - 1)
444             names.append('\n');
445     }
446     return names.toString();
447 }
448
449
450 } // namespace WebCore