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