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