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