Home | History | Annotate | Download | only in forms
      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 PassRefPtr<InputType> FileInputType::create(HTMLInputElement& element)
     58 {
     59     return adoptRef(new FileInputType(element));
     60 }
     61 
     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 
     74 const AtomicString& FileInputType::formControlType() const
     75 {
     76     return InputTypeNames::file;
     77 }
     78 
     79 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 
     92 void FileInputType::restoreFormControlState(const FormControlState& state)
     93 {
     94     if (state.valueSize() % 2)
     95         return;
     96     filesChosen(filesFromFormControlState(state));
     97 }
     98 
     99 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 
    128 bool FileInputType::valueMissing(const String& value) const
    129 {
    130     return element().isRequired() && value.isEmpty();
    131 }
    132 
    133 String FileInputType::valueMissingText() const
    134 {
    135     return locale().queryString(element().multiple() ? WebLocalizedString::ValidationValueMissingForMultipleFile : WebLocalizedString::ValidationValueMissingForFile);
    136 }
    137 
    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 
    162 RenderObject* FileInputType::createRenderer(RenderStyle*) const
    163 {
    164     return new RenderFileUploadControl(&element());
    165 }
    166 
    167 bool FileInputType::canSetStringValue() const
    168 {
    169     return false;
    170 }
    171 
    172 FileList* FileInputType::files()
    173 {
    174     return m_fileList.get();
    175 }
    176 
    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 
    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 
    203 void FileInputType::setValue(const String&, bool, TextFieldEventBehavior)
    204 {
    205     m_fileList->clear();
    206     element().setNeedsStyleRecalc();
    207 }
    208 
    209 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 
    242 bool FileInputType::isFileUpload() const
    243 {
    244     return true;
    245 }
    246 
    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 
    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 
    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 
    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 
    311 void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& files)
    312 {
    313     setFiles(createFileList(files));
    314 }
    315 
    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 
    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 
    359 String FileInputType::droppedFileSystemId()
    360 {
    361     return m_droppedFileSystemId;
    362 }
    363 
    364 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