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