Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / forms / 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 "core/html/forms/FileInputType.h"
24
25 #include "HTMLNames.h"
26 #include "InputTypeNames.h"
27 #include "RuntimeEnabledFeatures.h"
28 #include "bindings/v8/ExceptionStatePlaceholder.h"
29 #include "core/dom/shadow/ShadowRoot.h"
30 #include "core/events/Event.h"
31 #include "core/fileapi/File.h"
32 #include "core/fileapi/FileList.h"
33 #include "core/html/FormDataList.h"
34 #include "core/html/HTMLInputElement.h"
35 #include "core/html/forms/FormController.h"
36 #include "core/page/Chrome.h"
37 #include "core/page/DragData.h"
38 #include "core/rendering/RenderFileUploadControl.h"
39 #include "platform/FileMetadata.h"
40 #include "platform/UserGestureIndicator.h"
41 #include "platform/text/PlatformLocale.h"
42 #include "wtf/PassOwnPtr.h"
43 #include "wtf/text/StringBuilder.h"
44 #include "wtf/text/WTFString.h"
45
46 namespace WebCore {
47
48 using blink::WebLocalizedString;
49 using namespace HTMLNames;
50
51 inline FileInputType::FileInputType(HTMLInputElement& element)
52     : BaseClickableWithKeyInputType(element)
53     , m_fileList(FileList::create())
54 {
55 }
56
57 PassRefPtrWillBeRawPtr<InputType> FileInputType::create(HTMLInputElement& element)
58 {
59     return adoptRefWillBeNoop(new FileInputType(element));
60 }
61
62 void FileInputType::trace(Visitor* visitor)
63 {
64     visitor->trace(m_fileList);
65     BaseClickableWithKeyInputType::trace(visitor);
66 }
67
68 Vector<FileChooserFileInfo> FileInputType::filesFromFormControlState(const FormControlState& state)
69 {
70     Vector<FileChooserFileInfo> files;
71     for (size_t i = 0; i < state.valueSize(); i += 2) {
72         if (!state[i + 1].isEmpty())
73             files.append(FileChooserFileInfo(state[i], state[i + 1]));
74         else
75             files.append(FileChooserFileInfo(state[i]));
76     }
77     return files;
78 }
79
80 const AtomicString& FileInputType::formControlType() const
81 {
82     return InputTypeNames::file;
83 }
84
85 FormControlState FileInputType::saveFormControlState() const
86 {
87     if (m_fileList->isEmpty())
88         return FormControlState();
89     FormControlState state;
90     unsigned numFiles = m_fileList->length();
91     for (unsigned i = 0; i < numFiles; ++i) {
92         state.append(m_fileList->item(i)->path());
93         state.append(m_fileList->item(i)->name());
94     }
95     return state;
96 }
97
98 void FileInputType::restoreFormControlState(const FormControlState& state)
99 {
100     if (state.valueSize() % 2)
101         return;
102     filesChosen(filesFromFormControlState(state));
103 }
104
105 bool FileInputType::appendFormData(FormDataList& encoding, bool multipart) const
106 {
107     FileList* fileList = element().files();
108     unsigned numFiles = fileList->length();
109     if (!multipart) {
110         // Send only the basenames.
111         // 4.10.16.4 and 4.10.16.6 sections in HTML5.
112
113         // Unlike the multipart case, we have no special handling for the empty
114         // fileList because Netscape doesn't support for non-multipart
115         // submission of file inputs, and Firefox doesn't add "name=" query
116         // parameter.
117         for (unsigned i = 0; i < numFiles; ++i)
118             encoding.appendData(element().name(), fileList->item(i)->name());
119         return true;
120     }
121
122     // If no filename at all is entered, return successful but empty.
123     // Null would be more logical, but Netscape posts an empty file. Argh.
124     if (!numFiles) {
125         encoding.appendBlob(element().name(), File::create(""));
126         return true;
127     }
128
129     for (unsigned i = 0; i < numFiles; ++i)
130         encoding.appendBlob(element().name(), fileList->item(i));
131     return true;
132 }
133
134 bool FileInputType::valueMissing(const String& value) const
135 {
136     return element().isRequired() && value.isEmpty();
137 }
138
139 String FileInputType::valueMissingText() const
140 {
141     return locale().queryString(element().multiple() ? WebLocalizedString::ValidationValueMissingForMultipleFile : WebLocalizedString::ValidationValueMissingForFile);
142 }
143
144 void FileInputType::handleDOMActivateEvent(Event* event)
145 {
146     if (element().isDisabledFormControl())
147         return;
148
149     if (!UserGestureIndicator::processingUserGesture())
150         return;
151
152     if (Chrome* chrome = this->chrome()) {
153         FileChooserSettings settings;
154         HTMLInputElement& input = element();
155         settings.allowsDirectoryUpload = input.fastHasAttribute(webkitdirectoryAttr);
156         settings.allowsMultipleFiles = settings.allowsDirectoryUpload || input.fastHasAttribute(multipleAttr);
157         settings.acceptMIMETypes = input.acceptMIMETypes();
158         settings.acceptFileExtensions = input.acceptFileExtensions();
159         settings.selectedFiles = m_fileList->paths();
160 #if ENABLE(MEDIA_CAPTURE)
161         settings.useMediaCapture = input.capture();
162 #endif
163         chrome->runOpenPanel(input.document().frame(), newFileChooser(settings));
164     }
165     event->setDefaultHandled();
166 }
167
168 RenderObject* FileInputType::createRenderer(RenderStyle*) const
169 {
170     return new RenderFileUploadControl(&element());
171 }
172
173 bool FileInputType::canSetStringValue() const
174 {
175     return false;
176 }
177
178 FileList* FileInputType::files()
179 {
180     return m_fileList.get();
181 }
182
183 bool FileInputType::canSetValue(const String& value)
184 {
185     // For security reasons, we don't allow setting the filename, but we do allow clearing it.
186     // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't
187     // applicable to the file upload control at all, but for now we are keeping this behavior
188     // to avoid breaking existing websites that may be relying on this.
189     return value.isEmpty();
190 }
191
192 bool FileInputType::getTypeSpecificValue(String& value)
193 {
194     if (m_fileList->isEmpty()) {
195         value = String();
196         return true;
197     }
198
199     // HTML5 tells us that we're supposed to use this goofy value for
200     // file input controls. Historically, browsers revealed the real
201     // file path, but that's a privacy problem. Code on the web
202     // decided to try to parse the value by looking for backslashes
203     // (because that's what Windows file paths use). To be compatible
204     // with that code, we make up a fake path for the file.
205     value = "C:\\fakepath\\" + m_fileList->item(0)->name();
206     return true;
207 }
208
209 void FileInputType::setValue(const String&, bool valueChanged, TextFieldEventBehavior)
210 {
211     if (!valueChanged)
212         return;
213
214     m_fileList->clear();
215     element().setNeedsStyleRecalc(SubtreeStyleChange);
216     element().setNeedsValidityCheck();
217 }
218
219 PassRefPtrWillBeRawPtr<FileList> FileInputType::createFileList(const Vector<FileChooserFileInfo>& files) const
220 {
221     RefPtrWillBeRawPtr<FileList> fileList(FileList::create());
222     size_t size = files.size();
223
224     // If a directory is being selected, the UI allows a directory to be chosen
225     // and the paths provided here share a root directory somewhere up the tree;
226     // we want to store only the relative paths from that point.
227     if (size && element().fastHasAttribute(webkitdirectoryAttr)) {
228         // Find the common root path.
229         String rootPath = directoryName(files[0].path);
230         for (size_t i = 1; i < size; i++) {
231             while (!files[i].path.startsWith(rootPath))
232                 rootPath = directoryName(rootPath);
233         }
234         rootPath = directoryName(rootPath);
235         ASSERT(rootPath.length());
236         int rootLength = rootPath.length();
237         if (rootPath[rootLength - 1] != '\\' && rootPath[rootLength - 1] != '/')
238             rootLength += 1;
239         for (size_t i = 0; i < size; i++) {
240             // Normalize backslashes to slashes before exposing the relative path to script.
241             String relativePath = files[i].path.substring(rootLength).replace('\\', '/');
242             fileList->append(File::createWithRelativePath(files[i].path, relativePath));
243         }
244         return fileList;
245     }
246
247     for (size_t i = 0; i < size; i++)
248         fileList->append(File::createWithName(files[i].path, files[i].displayName, File::AllContentTypes));
249     return fileList;
250 }
251
252 bool FileInputType::isFileUpload() const
253 {
254     return true;
255 }
256
257 void FileInputType::createShadowSubtree()
258 {
259     ASSERT(element().shadow());
260     RefPtr<HTMLInputElement> button = HTMLInputElement::create(element().document(), 0, false);
261     button->setType(InputTypeNames::button);
262     button->setAttribute(valueAttr, AtomicString(locale().queryString(element().multiple() ? WebLocalizedString::FileButtonChooseMultipleFilesLabel : WebLocalizedString::FileButtonChooseFileLabel)));
263     button->setShadowPseudoId(AtomicString("-webkit-file-upload-button", AtomicString::ConstructFromLiteral));
264     element().userAgentShadowRoot()->appendChild(button.release());
265 }
266
267 void FileInputType::disabledAttributeChanged()
268 {
269     ASSERT(element().shadow());
270     if (Element* button = toElement(element().userAgentShadowRoot()->firstChild()))
271         button->setBooleanAttribute(disabledAttr, element().isDisabledFormControl());
272 }
273
274 void FileInputType::multipleAttributeChanged()
275 {
276     ASSERT(element().shadow());
277     if (Element* button = toElement(element().userAgentShadowRoot()->firstChild()))
278         button->setAttribute(valueAttr, AtomicString(locale().queryString(element().multiple() ? WebLocalizedString::FileButtonChooseMultipleFilesLabel : WebLocalizedString::FileButtonChooseFileLabel)));
279 }
280
281 void FileInputType::setFiles(PassRefPtrWillBeRawPtr<FileList> files)
282 {
283     if (!files)
284         return;
285
286     RefPtr<HTMLInputElement> input(element());
287
288     bool pathsChanged = false;
289     if (files->length() != m_fileList->length()) {
290         pathsChanged = true;
291     } else {
292         for (unsigned i = 0; i < files->length(); ++i) {
293             if (files->item(i)->path() != m_fileList->item(i)->path()) {
294                 pathsChanged = true;
295                 break;
296             }
297         }
298     }
299
300     m_fileList = files;
301
302     input->notifyFormStateChanged();
303     input->setNeedsValidityCheck();
304
305     if (input->renderer())
306         input->renderer()->repaint();
307
308     if (pathsChanged) {
309         // This call may cause destruction of this instance.
310         // input instance is safe since it is ref-counted.
311         input->dispatchChangeEvent();
312     }
313     input->setChangedSinceLastFormControlChangeEvent(false);
314 }
315
316 void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& files)
317 {
318     setFiles(createFileList(files));
319 }
320
321 void FileInputType::receiveDropForDirectoryUpload(const Vector<String>& paths)
322 {
323     if (Chrome* chrome = this->chrome()) {
324         FileChooserSettings settings;
325         HTMLInputElement& input = element();
326         settings.allowsDirectoryUpload = true;
327         settings.allowsMultipleFiles = true;
328         settings.selectedFiles.append(paths[0]);
329         settings.acceptMIMETypes = input.acceptMIMETypes();
330         settings.acceptFileExtensions = input.acceptFileExtensions();
331         chrome->enumerateChosenDirectory(newFileChooser(settings));
332     }
333 }
334
335 bool FileInputType::receiveDroppedFiles(const DragData* dragData)
336 {
337     Vector<String> paths;
338     dragData->asFilenames(paths);
339     if (paths.isEmpty())
340         return false;
341
342     HTMLInputElement& input = element();
343     if (input.fastHasAttribute(webkitdirectoryAttr)) {
344         receiveDropForDirectoryUpload(paths);
345         return true;
346     }
347
348     m_droppedFileSystemId = dragData->droppedFileSystemId();
349
350     Vector<FileChooserFileInfo> files;
351     for (unsigned i = 0; i < paths.size(); ++i)
352         files.append(FileChooserFileInfo(paths[i]));
353
354     if (input.fastHasAttribute(multipleAttr)) {
355         filesChosen(files);
356     } else {
357         Vector<FileChooserFileInfo> firstFileOnly;
358         firstFileOnly.append(files[0]);
359         filesChosen(firstFileOnly);
360     }
361     return true;
362 }
363
364 String FileInputType::droppedFileSystemId()
365 {
366     return m_droppedFileSystemId;
367 }
368
369 void FileInputType::copyNonAttributeProperties(const HTMLInputElement& sourceElement)
370 {
371     RefPtrWillBeRawPtr<FileList> fileList(FileList::create());
372     FileList* sourceFileList = sourceElement.files();
373     unsigned size = sourceFileList->length();
374     for (unsigned i = 0; i < size; ++i) {
375         File* file = sourceFileList->item(i);
376         fileList->append(File::createWithRelativePath(file->path(), file->webkitRelativePath()));
377     }
378     setFiles(fileList.release());
379 }
380
381 String FileInputType::defaultToolTip() const
382 {
383     FileList* fileList = m_fileList.get();
384     unsigned listSize = fileList->length();
385     if (!listSize) {
386         return locale().queryString(WebLocalizedString::FileButtonNoFileSelectedLabel);
387     }
388
389     StringBuilder names;
390     for (size_t i = 0; i < listSize; ++i) {
391         names.append(fileList->item(i)->name());
392         if (i != listSize - 1)
393             names.append('\n');
394     }
395     return names.toString();
396 }
397
398 } // namespace WebCore