root/Source/core/html/forms/FileInputType.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. m_fileList
  2. create
  3. filesFromFormControlState
  4. formControlType
  5. saveFormControlState
  6. restoreFormControlState
  7. appendFormData
  8. valueMissing
  9. valueMissingText
  10. handleDOMActivateEvent
  11. createRenderer
  12. canSetStringValue
  13. files
  14. canSetValue
  15. getTypeSpecificValue
  16. setValue
  17. createFileList
  18. isFileUpload
  19. createShadowSubtree
  20. disabledAttributeChanged
  21. multipleAttributeChanged
  22. setFiles
  23. filesChosen
  24. receiveDropForDirectoryUpload
  25. receiveDroppedFiles
  26. droppedFileSystemId
  27. defaultToolTip

/*
 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "core/html/forms/FileInputType.h"

#include "HTMLNames.h"
#include "InputTypeNames.h"
#include "RuntimeEnabledFeatures.h"
#include "bindings/v8/ExceptionStatePlaceholder.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/events/Event.h"
#include "core/fileapi/File.h"
#include "core/fileapi/FileList.h"
#include "core/html/FormDataList.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/forms/FormController.h"
#include "core/page/Chrome.h"
#include "core/page/DragData.h"
#include "core/rendering/RenderFileUploadControl.h"
#include "platform/FileMetadata.h"
#include "platform/UserGestureIndicator.h"
#include "platform/text/PlatformLocale.h"
#include "wtf/PassOwnPtr.h"
#include "wtf/text/StringBuilder.h"
#include "wtf/text/WTFString.h"

namespace WebCore {

using blink::WebLocalizedString;
using namespace HTMLNames;

inline FileInputType::FileInputType(HTMLInputElement& element)
    : BaseClickableWithKeyInputType(element)
    , m_fileList(FileList::create())
{
}

PassRefPtr<InputType> FileInputType::create(HTMLInputElement& element)
{
    return adoptRef(new FileInputType(element));
}

Vector<FileChooserFileInfo> FileInputType::filesFromFormControlState(const FormControlState& state)
{
    Vector<FileChooserFileInfo> files;
    for (size_t i = 0; i < state.valueSize(); i += 2) {
        if (!state[i + 1].isEmpty())
            files.append(FileChooserFileInfo(state[i], state[i + 1]));
        else
            files.append(FileChooserFileInfo(state[i]));
    }
    return files;
}

const AtomicString& FileInputType::formControlType() const
{
    return InputTypeNames::file;
}

FormControlState FileInputType::saveFormControlState() const
{
    if (m_fileList->isEmpty())
        return FormControlState();
    FormControlState state;
    unsigned numFiles = m_fileList->length();
    for (unsigned i = 0; i < numFiles; ++i) {
        state.append(m_fileList->item(i)->path());
        state.append(m_fileList->item(i)->name());
    }
    return state;
}

void FileInputType::restoreFormControlState(const FormControlState& state)
{
    if (state.valueSize() % 2)
        return;
    filesChosen(filesFromFormControlState(state));
}

bool FileInputType::appendFormData(FormDataList& encoding, bool multipart) const
{
    FileList* fileList = element().files();
    unsigned numFiles = fileList->length();
    if (!multipart) {
        // Send only the basenames.
        // 4.10.16.4 and 4.10.16.6 sections in HTML5.

        // Unlike the multipart case, we have no special handling for the empty
        // fileList because Netscape doesn't support for non-multipart
        // submission of file inputs, and Firefox doesn't add "name=" query
        // parameter.
        for (unsigned i = 0; i < numFiles; ++i)
            encoding.appendData(element().name(), fileList->item(i)->name());
        return true;
    }

    // If no filename at all is entered, return successful but empty.
    // Null would be more logical, but Netscape posts an empty file. Argh.
    if (!numFiles) {
        encoding.appendBlob(element().name(), File::create(""));
        return true;
    }

    for (unsigned i = 0; i < numFiles; ++i)
        encoding.appendBlob(element().name(), fileList->item(i));
    return true;
}

bool FileInputType::valueMissing(const String& value) const
{
    return element().isRequired() && value.isEmpty();
}

String FileInputType::valueMissingText() const
{
    return locale().queryString(element().multiple() ? WebLocalizedString::ValidationValueMissingForMultipleFile : WebLocalizedString::ValidationValueMissingForFile);
}

void FileInputType::handleDOMActivateEvent(Event* event)
{
    if (element().isDisabledFormControl())
        return;

    if (!UserGestureIndicator::processingUserGesture())
        return;

    if (Chrome* chrome = this->chrome()) {
        FileChooserSettings settings;
        HTMLInputElement& input = element();
        settings.allowsDirectoryUpload = input.fastHasAttribute(webkitdirectoryAttr);
        settings.allowsMultipleFiles = settings.allowsDirectoryUpload || input.fastHasAttribute(multipleAttr);
        settings.acceptMIMETypes = input.acceptMIMETypes();
        settings.acceptFileExtensions = input.acceptFileExtensions();
        settings.selectedFiles = m_fileList->paths();
#if ENABLE(MEDIA_CAPTURE)
        settings.useMediaCapture = input.capture();
#endif
        chrome->runOpenPanel(input.document().frame(), newFileChooser(settings));
    }
    event->setDefaultHandled();
}

RenderObject* FileInputType::createRenderer(RenderStyle*) const
{
    return new RenderFileUploadControl(&element());
}

bool FileInputType::canSetStringValue() const
{
    return false;
}

FileList* FileInputType::files()
{
    return m_fileList.get();
}

bool FileInputType::canSetValue(const String& value)
{
    // For security reasons, we don't allow setting the filename, but we do allow clearing it.
    // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't
    // applicable to the file upload control at all, but for now we are keeping this behavior
    // to avoid breaking existing websites that may be relying on this.
    return value.isEmpty();
}

bool FileInputType::getTypeSpecificValue(String& value)
{
    if (m_fileList->isEmpty()) {
        value = String();
        return true;
    }

    // HTML5 tells us that we're supposed to use this goofy value for
    // file input controls. Historically, browsers revealed the real
    // file path, but that's a privacy problem. Code on the web
    // decided to try to parse the value by looking for backslashes
    // (because that's what Windows file paths use). To be compatible
    // with that code, we make up a fake path for the file.
    value = "C:\\fakepath\\" + m_fileList->item(0)->name();
    return true;
}

void FileInputType::setValue(const String&, bool valueChanged, TextFieldEventBehavior)
{
    if (!valueChanged)
        return;

    m_fileList->clear();
    element().setNeedsStyleRecalc(SubtreeStyleChange);
    element().setNeedsValidityCheck();
}

PassRefPtrWillBeRawPtr<FileList> FileInputType::createFileList(const Vector<FileChooserFileInfo>& files) const
{
    RefPtrWillBeRawPtr<FileList> fileList(FileList::create());
    size_t size = files.size();

    // If a directory is being selected, the UI allows a directory to be chosen
    // and the paths provided here share a root directory somewhere up the tree;
    // we want to store only the relative paths from that point.
    if (size && element().fastHasAttribute(webkitdirectoryAttr)) {
        // Find the common root path.
        String rootPath = directoryName(files[0].path);
        for (size_t i = 1; i < size; i++) {
            while (!files[i].path.startsWith(rootPath))
                rootPath = directoryName(rootPath);
        }
        rootPath = directoryName(rootPath);
        ASSERT(rootPath.length());
        int rootLength = rootPath.length();
        if (rootPath[rootLength - 1] != '\\' && rootPath[rootLength - 1] != '/')
            rootLength += 1;
        for (size_t i = 0; i < size; i++) {
            // Normalize backslashes to slashes before exposing the relative path to script.
            String relativePath = files[i].path.substring(rootLength).replace('\\', '/');
            fileList->append(File::createWithRelativePath(files[i].path, relativePath));
        }
        return fileList;
    }

    for (size_t i = 0; i < size; i++)
        fileList->append(File::createWithName(files[i].path, files[i].displayName, File::AllContentTypes));
    return fileList;
}

bool FileInputType::isFileUpload() const
{
    return true;
}

void FileInputType::createShadowSubtree()
{
    ASSERT(element().shadow());
    RefPtr<HTMLInputElement> button = HTMLInputElement::create(element().document(), 0, false);
    button->setType(InputTypeNames::button);
    button->setAttribute(valueAttr, AtomicString(locale().queryString(element().multiple() ? WebLocalizedString::FileButtonChooseMultipleFilesLabel : WebLocalizedString::FileButtonChooseFileLabel)));
    button->setShadowPseudoId(AtomicString("-webkit-file-upload-button", AtomicString::ConstructFromLiteral));
    element().userAgentShadowRoot()->appendChild(button.release());
}

void FileInputType::disabledAttributeChanged()
{
    ASSERT(element().shadow());
    if (Element* button = toElement(element().userAgentShadowRoot()->firstChild()))
        button->setBooleanAttribute(disabledAttr, element().isDisabledFormControl());
}

void FileInputType::multipleAttributeChanged()
{
    ASSERT(element().shadow());
    if (Element* button = toElement(element().userAgentShadowRoot()->firstChild()))
        button->setAttribute(valueAttr, AtomicString(locale().queryString(element().multiple() ? WebLocalizedString::FileButtonChooseMultipleFilesLabel : WebLocalizedString::FileButtonChooseFileLabel)));
}

void FileInputType::setFiles(PassRefPtrWillBeRawPtr<FileList> files)
{
    if (!files)
        return;

    RefPtr<HTMLInputElement> input(element());

    bool pathsChanged = false;
    if (files->length() != m_fileList->length()) {
        pathsChanged = true;
    } else {
        for (unsigned i = 0; i < files->length(); ++i) {
            if (files->item(i)->path() != m_fileList->item(i)->path()) {
                pathsChanged = true;
                break;
            }
        }
    }

    m_fileList = files;

    input->setFormControlValueMatchesRenderer(true);
    input->notifyFormStateChanged();
    input->setNeedsValidityCheck();

    Vector<String> paths;
    for (unsigned i = 0; i < m_fileList->length(); ++i)
        paths.append(m_fileList->item(i)->path());

    if (input->renderer())
        input->renderer()->repaint();

    if (pathsChanged) {
        // This call may cause destruction of this instance.
        // input instance is safe since it is ref-counted.
        input->dispatchChangeEvent();
    }
    input->setChangedSinceLastFormControlChangeEvent(false);
}

void FileInputType::filesChosen(const Vector<FileChooserFileInfo>& files)
{
    setFiles(createFileList(files));
}

void FileInputType::receiveDropForDirectoryUpload(const Vector<String>& paths)
{
    if (Chrome* chrome = this->chrome()) {
        FileChooserSettings settings;
        HTMLInputElement& input = element();
        settings.allowsDirectoryUpload = true;
        settings.allowsMultipleFiles = true;
        settings.selectedFiles.append(paths[0]);
        settings.acceptMIMETypes = input.acceptMIMETypes();
        settings.acceptFileExtensions = input.acceptFileExtensions();
        chrome->enumerateChosenDirectory(newFileChooser(settings));
    }
}

bool FileInputType::receiveDroppedFiles(const DragData* dragData)
{
    Vector<String> paths;
    dragData->asFilenames(paths);
    if (paths.isEmpty())
        return false;

    HTMLInputElement& input = element();
    if (input.fastHasAttribute(webkitdirectoryAttr)) {
        receiveDropForDirectoryUpload(paths);
        return true;
    }

    m_droppedFileSystemId = dragData->droppedFileSystemId();

    Vector<FileChooserFileInfo> files;
    for (unsigned i = 0; i < paths.size(); ++i)
        files.append(FileChooserFileInfo(paths[i]));

    if (input.fastHasAttribute(multipleAttr)) {
        filesChosen(files);
    } else {
        Vector<FileChooserFileInfo> firstFileOnly;
        firstFileOnly.append(files[0]);
        filesChosen(firstFileOnly);
    }
    return true;
}

String FileInputType::droppedFileSystemId()
{
    return m_droppedFileSystemId;
}

String FileInputType::defaultToolTip() const
{
    FileList* fileList = m_fileList.get();
    unsigned listSize = fileList->length();
    if (!listSize) {
        return locale().queryString(WebLocalizedString::FileButtonNoFileSelectedLabel);
    }

    StringBuilder names;
    for (size_t i = 0; i < listSize; ++i) {
        names.append(fileList->item(i)->name());
        if (i != listSize - 1)
            names.append('\n');
    }
    return names.toString();
}

} // namespace WebCore

/* [<][>][^][v][top][bottom][index][help] */