6daac39c41afed8c4197d0106e4797e00fe4abf5
[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 "Event.h"
27 #include "File.h"
28 #include "FileList.h"
29 #include "FileSystem.h"
30 #include "FormDataList.h"
31 #include "Frame.h"
32 #include "HTMLInputElement.h"
33 #include "HTMLNames.h"
34 #include "Icon.h"
35 #include "LocalizedStrings.h"
36 #include "Page.h"
37 #include "RenderFileUploadControl.h"
38 #include "ScriptController.h"
39 #include "ShadowRoot.h"
40 #include <wtf/PassOwnPtr.h>
41 #include <wtf/text/WTFString.h>
42
43 namespace WebCore {
44
45 using namespace HTMLNames;
46
47 class UploadButtonElement : public HTMLInputElement {
48 public:
49     static PassRefPtr<UploadButtonElement> create(Document*);
50
51 private:
52     UploadButtonElement(Document*);
53
54     virtual const AtomicString& shadowPseudoId() const;
55 };
56
57 PassRefPtr<UploadButtonElement> UploadButtonElement::create(Document* document)
58 {
59     RefPtr<UploadButtonElement> button = adoptRef(new UploadButtonElement(document));
60     button->setType("button");
61     button->setValue(fileButtonChooseFileLabel());
62     return button.release();
63 }
64
65
66 UploadButtonElement::UploadButtonElement(Document* document)
67     : HTMLInputElement(inputTag, document, 0, false)
68 {
69 }
70
71 const AtomicString& UploadButtonElement::shadowPseudoId() const
72 {
73     DEFINE_STATIC_LOCAL(AtomicString, pseudoId, ("-webkit-file-upload-button"));
74     return pseudoId;
75 }
76
77 inline FileInputType::FileInputType(HTMLInputElement* element)
78     : BaseButtonInputType(element)
79     , m_fileList(FileList::create())
80 {
81 }
82
83 PassOwnPtr<InputType> FileInputType::create(HTMLInputElement* element)
84 {
85     return adoptPtr(new FileInputType(element));
86 }
87
88 const AtomicString& FileInputType::formControlType() const
89 {
90     return InputTypeNames::file();
91 }
92
93 bool FileInputType::appendFormData(FormDataList& encoding, bool multipart) const
94 {
95     FileList* fileList = element()->files();
96     unsigned numFiles = fileList->length();
97     if (!multipart) {
98         // Send only the basenames.
99         // 4.10.16.4 and 4.10.16.6 sections in HTML5.
100
101         // Unlike the multipart case, we have no special handling for the empty
102         // fileList because Netscape doesn't support for non-multipart
103         // submission of file inputs, and Firefox doesn't add "name=" query
104         // parameter.
105         for (unsigned i = 0; i < numFiles; ++i)
106             encoding.appendData(element()->name(), fileList->item(i)->fileName());
107         return true;
108     }
109
110     // If no filename at all is entered, return successful but empty.
111     // Null would be more logical, but Netscape posts an empty file. Argh.
112     if (!numFiles) {
113         encoding.appendBlob(element()->name(), File::create(""));
114         return true;
115     }
116
117     for (unsigned i = 0; i < numFiles; ++i)
118         encoding.appendBlob(element()->name(), fileList->item(i));
119     return true;
120 }
121
122 bool FileInputType::valueMissing(const String& value) const
123 {
124     return value.isEmpty();
125 }
126
127 String FileInputType::valueMissingText() const
128 {
129     return element()->multiple() ? validationMessageValueMissingForMultipleFileText() : validationMessageValueMissingForFileText();
130 }
131
132 void FileInputType::handleDOMActivateEvent(Event* event)
133 {
134     if (element()->disabled() || !element()->renderer())
135         return;
136
137     if (!ScriptController::processingUserGesture())
138         return;
139
140     if (Chrome* chrome = this->chrome()) {
141         FileChooserSettings settings;
142         HTMLInputElement* input = element();
143 #if ENABLE(DIRECTORY_UPLOAD)
144         settings.allowsDirectoryUpload = input->fastHasAttribute(webkitdirectoryAttr);
145         settings.allowsMultipleFiles = settings.allowsDirectoryUpload || input->fastHasAttribute(multipleAttr);
146 #else
147         settings.allowsMultipleFiles = input->fastHasAttribute(multipleAttr);
148 #endif
149         settings.acceptTypes = input->accept();
150         settings.selectedFiles = m_fileList->paths();
151         chrome->runOpenPanel(input->document()->frame(), newFileChooser(settings));
152     }
153     event->setDefaultHandled();
154 }
155
156 RenderObject* FileInputType::createRenderer(RenderArena* arena, RenderStyle*) const
157 {
158     return new (arena) RenderFileUploadControl(element());
159 }
160
161 bool FileInputType::canSetStringValue() const
162 {
163     return false;
164 }
165
166 bool FileInputType::canChangeFromAnotherType() const
167 {
168     // Don't allow the type to be changed to file after the first type change.
169     // In other engines this might mean a JavaScript programmer could set a text
170     // field's value to something like /etc/passwd and then change it to a file input.
171     // I don't think this would actually occur in WebKit, but this rule still may be
172     // important for compatibility.
173     return false;
174 }
175
176 FileList* FileInputType::files()
177 {
178     return m_fileList.get();
179 }
180
181 bool FileInputType::canSetValue(const String& value)
182 {
183     // For security reasons, we don't allow setting the filename, but we do allow clearing it.
184     // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't
185     // applicable to the file upload control at all, but for now we are keeping this behavior
186     // to avoid breaking existing websites that may be relying on this.
187     return value.isEmpty();
188 }
189
190 bool FileInputType::getTypeSpecificValue(String& value)
191 {
192     if (m_fileList->isEmpty()) {
193         value = String();
194         return true;
195     }
196
197     // HTML5 tells us that we're supposed to use this goofy value for
198     // file input controls. Historically, browsers revealed the real
199     // file path, but that's a privacy problem. Code on the web
200     // decided to try to parse the value by looking for backslashes
201     // (because that's what Windows file paths use). To be compatible
202     // with that code, we make up a fake path for the file.
203     value = "C:\\fakepath\\" + m_fileList->item(0)->fileName();
204     return true;
205 }
206
207 bool FileInputType::storesValueSeparateFromAttribute()
208 {
209     return true;
210 }
211
212 void FileInputType::setFileList(const Vector<String>& paths)
213 {
214     m_fileList->clear();
215     size_t size = paths.size();
216
217 #if ENABLE(DIRECTORY_UPLOAD)
218     // If a directory is being selected, the UI allows a directory to be chosen
219     // and the paths provided here share a root directory somewhere up the tree;
220     // we want to store only the relative paths from that point.
221     if (size && element()->fastHasAttribute(webkitdirectoryAttr)) {
222         // Find the common root path.
223         String rootPath = directoryName(paths[0]);
224         for (size_t i = 1; i < size; i++) {
225             while (!paths[i].startsWith(rootPath))
226                 rootPath = directoryName(rootPath);
227         }
228         rootPath = directoryName(rootPath);
229         ASSERT(rootPath.length());
230         for (size_t i = 0; i < size; i++) {
231             // Normalize backslashes to slashes before exposing the relative path to script.
232             String relativePath = paths[i].substring(1 + rootPath.length()).replace('\\', '/');
233             m_fileList->append(File::createWithRelativePath(paths[i], relativePath));
234         }
235         return;
236     }
237 #endif
238
239     for (size_t i = 0; i < size; i++)
240         m_fileList->append(File::create(paths[i]));
241 }
242
243 bool FileInputType::isFileUpload() const
244 {
245     return true;
246 }
247
248 void FileInputType::createShadowSubtree()
249 {
250     ExceptionCode ec = 0;
251     element()->ensureShadowRoot()->appendChild(UploadButtonElement::create(element()->document()), ec);
252 }
253
254 void FileInputType::requestIcon(const Vector<String>& paths)
255 {
256     if (!paths.size())
257         return;
258
259     if (Chrome* chrome = this->chrome())
260         chrome->loadIconForFiles(paths, newFileIconLoader());
261 }
262
263 void FileInputType::filesChosen(const Vector<String>& paths)
264 {
265     HTMLInputElement* input = element();
266     setFileList(paths);
267
268     input->setFormControlValueMatchesRenderer(true);
269     input->notifyFormStateChanged();
270     input->setNeedsValidityCheck();
271
272     requestIcon(paths);
273
274     if (input->renderer())
275         input->renderer()->repaint();
276     // This call may cause destruction of this instance and thus must always be last in the function.
277     input->dispatchFormControlChangeEvent();
278 }
279
280 #if ENABLE(DIRECTORY_UPLOAD)
281 void FileInputType::receiveDropForDirectoryUpload(const Vector<String>& paths)
282 {
283     if (Chrome* chrome = this->chrome()) {
284         FileChooserSettings settings;
285         settings.allowsDirectoryUpload = true;
286         settings.allowsMultipleFiles = true;
287         settings.selectedFiles.append(paths[0]);
288         chrome->enumerateChosenDirectory(newFileChooser(settings));
289     }
290 }
291 #endif
292
293 void FileInputType::updateRendering(PassRefPtr<Icon> icon)
294 {
295     if (m_icon == icon)
296         return;
297
298     m_icon = icon;
299     if (element()->renderer())
300         element()->renderer()->repaint();
301 }
302
303 Chrome* FileInputType::chrome() const
304 {
305     if (Page* page = element()->document()->page())
306         return page->chrome();
307     return 0;
308 }
309
310 void FileInputType::receiveDroppedFiles(const Vector<String>& paths)
311 {
312     HTMLInputElement* input = element();
313 #if ENABLE(DIRECTORY_UPLOAD)
314     if (input->fastHasAttribute(webkitdirectoryAttr)) {
315         receiveDropForDirectoryUpload(paths);
316         return;
317     }
318 #endif
319
320     if (input->fastHasAttribute(multipleAttr))
321         filesChosen(paths);
322     else {
323         Vector<String> firstPathOnly;
324         firstPathOnly.append(paths[0]);
325         filesChosen(firstPathOnly);
326     }
327 }
328
329 Icon* FileInputType::icon() const
330 {
331     return m_icon.get();
332 }
333
334 } // namespace WebCore