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