WebCore:
authoradele@apple.com <adele@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 24 Oct 2008 21:12:45 +0000 (21:12 +0000)
committeradele@apple.com <adele@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 24 Oct 2008 21:12:45 +0000 (21:12 +0000)
2008-10-24  Adele Peterson  <adele@apple.com>

        Reviewed by Sam Weinig.

        WebCore part of fix for <rdar://problem/5839256> FILE CONTROL: multi-file upload.

        This change adds support for multiple file selection in an <input type="file"> control when the "multiple" attribute is used.
        This is consistent with the direction HTML5 will be going in the future.

        The initial implementation here will show "n files" as the text next to the control when multiple files are selected.  You can view
        the individual files in a tooltip for now.  Improvements to this control will come later.

        Web developers will be able to access the FileList from the HTMLInputElement element, where they can get a base name and a size for each file.
        These FileList Files can also be sent in an XMLHTTPRequest.

        * manual-tests/post-multi-file-upload.html: Added.
        * manual-tests/resources/multiFileResources: Added.
        * manual-tests/resources/multiFileResources/post-echo-and-notify-done.cgi: Added.
        * manual-tests/resources/multiFileResources/testFile1.html: Added.
        * manual-tests/resources/multiFileResources/testFile2.html: Added.
        * manual-tests/resources/multiFileResources/testFile3.html: Added.

        * WebCore.base.exp: Added support to export the new "chooseFilenames" method to be used in WebKit.

        * html/HTMLInputElement.cpp:
        (WebCore::HTMLInputElement::parseMappedAttribute): Add support for the multiple attribute.
        (WebCore::HTMLInputElement::value): Added comments.  The HTML5 spec says that value shouldn't apply for the file upload control,
        but we don't want to break the behavior for existing websites that may rely on this.
        (WebCore::HTMLInputElement::setValue): ditto.
        (WebCore::HTMLInputElement::setValueFromRenderer): This is no longer used for file upload controls. setFileListFromRenderer is used instead.
        (WebCore::HTMLInputElement::setFileListFromRenderer): Added.
        * html/HTMLInputElement.h:

        * page/Chrome.cpp: (WebCore::Chrome::setToolTip): Show a tooltip with the file name list for the multi-file upload control.

        * page/DragController.cpp: (WebCore::DragController::concludeDrag): Updated to support multiple files.

        * platform/FileChooser.cpp: Add support for maintaining a list of file paths that can be retrieved by the renderer.
        (WebCore::FileChooser::FileChooser):
        (WebCore::FileChooser::create):
        (WebCore::FileChooser::clear):
        (WebCore::FileChooser::chooseFile):
        (WebCore::FileChooser::chooseFiles):
        (WebCore::FileChooser::chooseIcon):
        * platform/FileChooser.h:
        (WebCore::FileChooser::filePaths):
        (WebCore::FileChooser::allowsMultipleFiles):

        * platform/graphics/Icon.h:
        * platform/graphics/mac/IconMac.mm: (WebCore::Icon::newIconForFiles): Returns a generic icon for multiple files.
        * platform/graphics/gtk/IconGtk.cpp: (WebCore::Icon::newIconForFiles): stubbed out.
        * platform/graphics/qt/IconQt.cpp: (WebCore::Icon::newIconForFiles): ditto.
        * platform/graphics/win/IconWin.cpp: (WebCore::Icon::newIconForFiles): ditto.

        * platform/gtk/FileChooserGtk.cpp: (WebCore::FileChooser::basenameForWidth):  Updated to deal with new filenames vector.
        * platform/mac/FileChooserMac.mm: (WebCore::FileChooser::basenameForWidth): ditto.
        * platform/qt/FileChooserQt.cpp:
        (WebCore::FileChooser::openFileChooser):
        (WebCore::FileChooser::basenameForWidth):
        * platform/win/FileChooserWin.cpp: (WebCore::FileChooser::basenameForWidth):

        * platform/network/mac/FormDataStreamMac.mm: (WebCore::disassociateStreamWithResourceHandle):
        Removed unnecessary assertion.  This can get hit when connectionDidFinishLoading and cancel
        both get called for the same ResourceHandle.  This getting called twice has no negative effect.

        * rendering/RenderFileUploadControl.cpp:
        (WebCore::RenderFileUploadControl::valueChanged):  Calls setFileListFromRenderer.
        (WebCore::RenderFileUploadControl::allowsMultipleFiles):  Added.
        (WebCore::RenderFileUploadControl::updateFromElement):  Uses the new filenames call from FileChooser.
        (WebCore::RenderFileUploadControl::receiveDroppedFiles):  Updated to support multiple files.
        * rendering/RenderFileUploadControl.h:

WebKit/mac:

2008-10-24  Adele Peterson  <adele@apple.com>

        Reviewed by Sam Weinig.

        WebKit part of fix for <rdar://problem/5839256> FILE CONTROL: multi-file upload.

        * WebCoreSupport/WebChromeClient.mm:
        (WebChromeClient::runOpenPanel):
        (-[WebOpenPanelResultListener chooseFilenames:]):
        * WebView/WebUIDelegate.h:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@37863 268f45cc-cd09-0410-ab3c-d52691b4dbfc

28 files changed:
WebCore/ChangeLog
WebCore/WebCore.base.exp
WebCore/html/HTMLInputElement.cpp
WebCore/html/HTMLInputElement.h
WebCore/manual-tests/post-multi-file-upload.html [new file with mode: 0644]
WebCore/manual-tests/resources/multiFileResources/post-echo-and-notify-done.cgi [new file with mode: 0755]
WebCore/manual-tests/resources/multiFileResources/testFile1.html [new file with mode: 0644]
WebCore/manual-tests/resources/multiFileResources/testFile2.html [new file with mode: 0644]
WebCore/manual-tests/resources/multiFileResources/testFile3.html [new file with mode: 0644]
WebCore/page/Chrome.cpp
WebCore/page/DragController.cpp
WebCore/platform/FileChooser.cpp
WebCore/platform/FileChooser.h
WebCore/platform/graphics/Icon.h
WebCore/platform/graphics/gtk/IconGtk.cpp
WebCore/platform/graphics/mac/IconMac.mm
WebCore/platform/graphics/qt/IconQt.cpp
WebCore/platform/graphics/win/IconWin.cpp
WebCore/platform/gtk/FileChooserGtk.cpp
WebCore/platform/mac/FileChooserMac.mm
WebCore/platform/network/mac/FormDataStreamMac.mm
WebCore/platform/qt/FileChooserQt.cpp
WebCore/platform/win/FileChooserWin.cpp
WebCore/rendering/RenderFileUploadControl.cpp
WebCore/rendering/RenderFileUploadControl.h
WebKit/mac/ChangeLog
WebKit/mac/WebCoreSupport/WebChromeClient.mm
WebKit/mac/WebView/WebUIDelegate.h

index ffb96b1..509faa0 100644 (file)
@@ -1,3 +1,75 @@
+2008-10-24  Adele Peterson  <adele@apple.com>
+
+        Reviewed by Sam Weinig.
+
+        WebCore part of fix for <rdar://problem/5839256> FILE CONTROL: multi-file upload.
+
+        This change adds support for multiple file selection in an <input type="file"> control when the "multiple" attribute is used.
+        This is consistent with the direction HTML5 will be going in the future.
+
+        The initial implementation here will show "n files" as the text next to the control when multiple files are selected.  You can view
+        the individual files in a tooltip for now.  Improvements to this control will come later.  
+
+        Web developers will be able to access the FileList from the HTMLInputElement element, where they can get a base name and a size for each file.  
+        These FileList Files can also be sent in an XMLHTTPRequest.
+
+        * manual-tests/post-multi-file-upload.html: Added.
+        * manual-tests/resources/multiFileResources: Added.
+        * manual-tests/resources/multiFileResources/post-echo-and-notify-done.cgi: Added.
+        * manual-tests/resources/multiFileResources/testFile1.html: Added.
+        * manual-tests/resources/multiFileResources/testFile2.html: Added.
+        * manual-tests/resources/multiFileResources/testFile3.html: Added.
+
+        * WebCore.base.exp: Added support to export the new "chooseFilenames" method to be used in WebKit.
+
+        * html/HTMLInputElement.cpp:
+        (WebCore::HTMLInputElement::parseMappedAttribute): Add support for the multiple attribute.
+        (WebCore::HTMLInputElement::value): Added comments.  The HTML5 spec says that value shouldn't apply for the file upload control,
+        but we don't want to break the behavior for existing websites that may rely on this.
+        (WebCore::HTMLInputElement::setValue): ditto.
+        (WebCore::HTMLInputElement::setValueFromRenderer): This is no longer used for file upload controls. setFileListFromRenderer is used instead.
+        (WebCore::HTMLInputElement::setFileListFromRenderer): Added. 
+        * html/HTMLInputElement.h:
+
+        * page/Chrome.cpp: (WebCore::Chrome::setToolTip): Show a tooltip with the file name list for the multi-file upload control.
+
+        * page/DragController.cpp: (WebCore::DragController::concludeDrag): Updated to support multiple files.
+
+        * platform/FileChooser.cpp: Add support for maintaining a list of file paths that can be retrieved by the renderer.
+        (WebCore::FileChooser::FileChooser):
+        (WebCore::FileChooser::create):
+        (WebCore::FileChooser::clear):
+        (WebCore::FileChooser::chooseFile):
+        (WebCore::FileChooser::chooseFiles):
+        (WebCore::FileChooser::chooseIcon):
+        * platform/FileChooser.h:
+        (WebCore::FileChooser::filePaths):
+        (WebCore::FileChooser::allowsMultipleFiles):
+
+        * platform/graphics/Icon.h:
+        * platform/graphics/mac/IconMac.mm: (WebCore::Icon::newIconForFiles): Returns a generic icon for multiple files.
+        * platform/graphics/gtk/IconGtk.cpp: (WebCore::Icon::newIconForFiles): stubbed out.
+        * platform/graphics/qt/IconQt.cpp: (WebCore::Icon::newIconForFiles): ditto.
+        * platform/graphics/win/IconWin.cpp: (WebCore::Icon::newIconForFiles): ditto.
+
+        * platform/gtk/FileChooserGtk.cpp: (WebCore::FileChooser::basenameForWidth):  Updated to deal with new filenames vector.
+        * platform/mac/FileChooserMac.mm: (WebCore::FileChooser::basenameForWidth): ditto.
+        * platform/qt/FileChooserQt.cpp:
+        (WebCore::FileChooser::openFileChooser):
+        (WebCore::FileChooser::basenameForWidth):
+        * platform/win/FileChooserWin.cpp: (WebCore::FileChooser::basenameForWidth):
+
+        * platform/network/mac/FormDataStreamMac.mm: (WebCore::disassociateStreamWithResourceHandle):
+        Removed unnecessary assertion.  This can get hit when connectionDidFinishLoading and cancel
+        both get called for the same ResourceHandle.  This getting called twice has no negative effect.
+
+        * rendering/RenderFileUploadControl.cpp:
+        (WebCore::RenderFileUploadControl::valueChanged):  Calls setFileListFromRenderer.
+        (WebCore::RenderFileUploadControl::allowsMultipleFiles):  Added.
+        (WebCore::RenderFileUploadControl::updateFromElement):  Uses the new filenames call from FileChooser.
+        (WebCore::RenderFileUploadControl::receiveDroppedFiles):  Updated to support multiple files.
+        * rendering/RenderFileUploadControl.h:
+
 2008-10-23  Peter Kasting  <pkasting@google.com>
 
         Reviewed by David Hyatt.
index 208ff1f..6e7c75d 100644 (file)
@@ -139,6 +139,7 @@ __ZN7WebCore11ContextMenu22setPlatformDescriptionEP14NSMutableArray
 __ZN7WebCore11EditCommand7reapplyEv
 __ZN7WebCore11EditCommand7unapplyEv
 __ZN7WebCore11FileChooser10chooseFileERKNS_6StringE
+__ZN7WebCore11FileChooser11chooseFilesERKN3WTF6VectorINS_6StringELm0EEE
 __ZN7WebCore11FileChooserD1Ev
 __ZN7WebCore11FrameLoader11completeURLERKNS_6StringE
 __ZN7WebCore11FrameLoader11loadArchiveEN3WTF10PassRefPtrINS_7ArchiveEEE
@@ -874,6 +875,7 @@ __ZNK7WebCore9Selection23isContentRichlyEditableEv
 __ZNK7WebCore9Selection7toRangeEv
 __ZNK7WebCore9TimerBase8isActiveEv
 __ZTVN7WebCore12ChromeClientE
+__ZTVN7WebCore17FileChooserClientE
 _filenameByFixingIllegalCharacters
 _hasCaseInsensitiveSubstring
 _hasCaseInsensitiveSuffix
index 93791a2..b412736 100644 (file)
@@ -698,9 +698,10 @@ void HTMLInputElement::parseMappedAttribute(MappedAttribute *attr)
                attr->name() == incrementalAttr ||
                attr->name() == minAttr ||
                attr->name() == maxAttr ||
-               attr->name() == precisionAttr) {
+               attr->name() == multipleAttr ||
+               attr->name() == precisionAttr)
         setChanged();
-    else
+    else
         HTMLFormControlElementWithState::parseMappedAttribute(attr);
 }
 
@@ -950,6 +951,8 @@ void HTMLInputElement::copyNonAttributeProperties(const Element *source)
 
 String HTMLInputElement::value() const
 {
+    // The HTML5 spec (as of the 10/24/08 working draft) says that the value attribute isn't applicable to the file upload control
+    // but we don't want to break existing websites, who may be relying on being able to get the file name as a value.
     if (inputType() == FILE) {
         if (!m_fileList->isEmpty())
             return m_fileList->item(0)->fileName();
@@ -999,6 +1002,8 @@ String HTMLInputElement::valueWithDefault() const
 void HTMLInputElement::setValue(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
+    // but we don't want to break existing websites, who may be relying on this method to clear things.
     if (inputType() == FILE && !value.isEmpty())
         return;
 
@@ -1036,21 +1041,19 @@ void HTMLInputElement::setValueFromRenderer(const String& value)
     // Renderer and our event handler are responsible for constraining values.
     ASSERT(value == constrainValue(value) || constrainValue(value).isEmpty());
 
+    // File upload controls will always use setFileListFromRenderer.
+    ASSERT (inputType() != FILE);
+
     if (isTextField())
         updatePlaceholderVisibility();
     
-    if (inputType() == FILE) {
-        m_fileList->clear();
-        m_fileList->append(File::create(value));
-    } else {
-        // Workaround for bug where trailing \n is included in the result of textContent.
-        // The assert macro above may also be simplified to:  value == constrainValue(value)
-        // http://bugs.webkit.org/show_bug.cgi?id=9661
-        if (value == "\n")
-            m_value = "";
-        else
-            m_value = value;
-    }
+    // Workaround for bug where trailing \n is included in the result of textContent.
+    // The assert macro above may also be simplified to:  value == constrainValue(value)
+    // http://bugs.webkit.org/show_bug.cgi?id=9661
+    if (value == "\n")
+        m_value = "";
+    else
+        m_value = value;
 
     setValueMatchesRenderer();
 
@@ -1058,6 +1061,16 @@ void HTMLInputElement::setValueFromRenderer(const String& value)
     dispatchEventForType(inputEvent, true, false);
 }
 
+void HTMLInputElement::setFileListFromRenderer(const Vector<String>& paths)
+{
+    m_fileList->clear();
+    int size = paths.size();
+    for (int i = 0; i < size; i++)
+        m_fileList->append(File::create(paths[i]));
+
+    setValueMatchesRenderer();
+}
+
 bool HTMLInputElement::storesValueSeparateFromAttribute() const
 {
     switch (inputType()) {
index 5ab9542..7f090a8 100644 (file)
@@ -108,6 +108,7 @@ public:
     String valueWithDefault() const;
 
     void setValueFromRenderer(const String&);
+    void setFileListFromRenderer(const Vector<String>&);
 
     virtual bool saveState(String& value) const;
     virtual void restoreState(const String&);
diff --git a/WebCore/manual-tests/post-multi-file-upload.html b/WebCore/manual-tests/post-multi-file-upload.html
new file mode 100644 (file)
index 0000000..18f3039
--- /dev/null
@@ -0,0 +1,35 @@
+<html>
+    <head>
+        <script type="text/javascript">
+            var submittedFiles = 0;
+            
+            function log(message)
+            {
+                document.getElementById('console').appendChild(document.createTextNode(message + "\n"));
+            }
+            
+            function verifyResults()
+            {
+                if (submittedFiles == 3) {
+                    var frame = document.getElementById('fr');
+                    frame.parentNode.removeChild(frame);
+                    log("Test Passed");
+                } else
+                    log("Test Failed");
+            }
+        </script>
+    </head>
+    <body onload="test()">
+        <p>
+        This tests that the multi-file upload control works correctly.  This test and the resources/multiFileResources must be placed on a web server so the test can be run manually.<br>
+        Choose "testFile1.html" "testFile2.html" and "testFile3.html" and submit the form. <br>
+        You should see a "Test Passed" or a "Test Failed" message after submitting the form.<br>
+        </p>
+        <form method="post" enctype="multipart/form-data" action="resources/multiFileResources/post-echo-and-notify-done.cgi" id="myForm" target="targetFrame">
+            <input type="file" id="target" multiple name="multiFileInput"></input>
+            <input type="submit" value="Submit">
+        </form>
+        <iframe name="targetFrame" id="fr"></iframe>
+        <pre id='console'></pre>
+    </body>
+</html>
diff --git a/WebCore/manual-tests/resources/multiFileResources/post-echo-and-notify-done.cgi b/WebCore/manual-tests/resources/multiFileResources/post-echo-and-notify-done.cgi
new file mode 100755 (executable)
index 0000000..2c523dc
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/perl -w
+
+print "Content-type: text/html\n\n"; 
+
+if ($ENV{'REQUEST_METHOD'} eq "POST") {
+    read(STDIN, $request, $ENV{'CONTENT_LENGTH'})
+                || die "Could not get query\n";
+    print "<pre>$request</pre>";
+    print "<script>if (window.layoutTestController) layoutTestController.notifyDone();</script>";
+} else {
+    print "Wrong method: " . $ENV{'REQUEST_METHOD'} . "\n";
+} 
diff --git a/WebCore/manual-tests/resources/multiFileResources/testFile1.html b/WebCore/manual-tests/resources/multiFileResources/testFile1.html
new file mode 100644 (file)
index 0000000..4313554
--- /dev/null
@@ -0,0 +1,4 @@
+<script>
+parent.log('test file 1 submitted');
+parent.submittedFiles++;
+</script>
\ No newline at end of file
diff --git a/WebCore/manual-tests/resources/multiFileResources/testFile2.html b/WebCore/manual-tests/resources/multiFileResources/testFile2.html
new file mode 100644 (file)
index 0000000..90167f7
--- /dev/null
@@ -0,0 +1,4 @@
+<script>
+parent.log('test file 2 submitted');
+parent.submittedFiles++;
+</script>
\ No newline at end of file
diff --git a/WebCore/manual-tests/resources/multiFileResources/testFile3.html b/WebCore/manual-tests/resources/multiFileResources/testFile3.html
new file mode 100644 (file)
index 0000000..f89ddf3
--- /dev/null
@@ -0,0 +1,5 @@
+<script>
+parent.log('test file 3 submitted');
+parent.submittedFiles++;
+parent.verifyResults();
+</script>
\ No newline at end of file
index ef11ca1..5fa015b 100644 (file)
@@ -24,6 +24,7 @@
 #include "ChromeClient.h"
 #include "DNS.h"
 #include "Document.h"
+#include "FileList.h"
 #include "FloatRect.h"
 #include "Frame.h"
 #include "FrameTree.h"
@@ -350,10 +351,32 @@ void Chrome::setToolTip(const HitTestResult& result)
             toolTip = result.absoluteLinkURL().string();
     }
 
-    // Lastly we'll consider a tooltip for element with "title" attribute
+    // Next we'll consider a tooltip for element with "title" attribute
     if (toolTip.isEmpty())
         toolTip = result.title();
 
+    // Lastly, for <input type="file"> that allow multiple files, we'll consider a tooltip for the selected filenames
+    if (toolTip.isEmpty()) {
+        if (Node* node = result.innerNonSharedNode()) {
+            if (node->hasTagName(inputTag)) {
+                HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
+                if (input->inputType() == HTMLInputElement::FILE) {
+                    FileList* files = input->files();
+                    unsigned listSize = files->length();
+                    if (files && listSize > 1) {
+                        Vector<UChar> names;
+                        for (size_t i = 0; i < listSize; ++i) {
+                            append(names, files->item(i)->fileName());
+                            if (i != listSize - 1)
+                                names.append('\n');
+                        }
+                        toolTip = String::adopt(names);
+                    }
+                }
+            }
+        }
+    }
+    
     m_client->setToolTip(toolTip);
 }
 
index 3dc5382..268397e 100644 (file)
@@ -380,8 +380,7 @@ bool DragController::concludeDrag(DragData* dragData, DragDestinationAction acti
         if (!renderer)
             return false;
         
-        // Only take the first filename as <input type="file" /> can only accept one
-        renderer->receiveDroppedFile(filenames[0]);
+        renderer->receiveDroppedFiles(filenames);
         return true;
     }
 
index 4172b4c..83f6e78 100644 (file)
@@ -35,9 +35,9 @@ namespace WebCore {
     
 inline FileChooser::FileChooser(FileChooserClient* client, const String& filename)
     : m_client(client)
-    , m_filename(filename)
     , m_icon(chooseIcon(filename))
 {
+    m_filenames.append(filename);
 }
 
 PassRefPtr<FileChooser> FileChooser::create(FileChooserClient* client, const String& filename)
@@ -51,23 +51,39 @@ FileChooser::~FileChooser()
 
 void FileChooser::clear()
 {
-    m_filename = String();
-    m_icon = chooseIcon(m_filename);
+    m_filenames.clear();
+    m_icon = 0;
 }
 
 void FileChooser::chooseFile(const String& filename)
 {
-    if (m_filename == filename)
+    if (m_filenames.size() == 1 && m_filenames[0] == filename)
         return;
-    m_filename = filename;
+    m_filenames.clear();
+    m_filenames.append(filename);
     m_icon = chooseIcon(filename);
     if (m_client)
         m_client->valueChanged();
 }
 
+void FileChooser::chooseFiles(const Vector<String>& filenames)
+{
+    m_filenames = filenames;
+    m_icon = chooseIcon(filenames);
+    if (m_client)
+        m_client->valueChanged();
+}
+
 PassRefPtr<Icon> FileChooser::chooseIcon(const String& filename)
 {
     return Icon::newIconForFile(filename);
 }
 
+PassRefPtr<Icon> FileChooser::chooseIcon(Vector<String> filenames)
+{
+    if (filenames.size() == 1)
+        return Icon::newIconForFile(filenames[0]);
+    return Icon::newIconForFiles(filenames);
+}
+
 }
index 53e06d7..c3cbc5a 100644 (file)
@@ -31,6 +31,7 @@
 #define FileChooser_h
 
 #include "PlatformString.h"
+#include <wtf/Vector.h>
 
 namespace WebCore {
 
@@ -42,6 +43,7 @@ class FileChooserClient {
 public:
     virtual ~FileChooserClient() { }
     virtual void valueChanged() = 0;
+    virtual bool allowsMultipleFiles() = 0;
 };
 
 class FileChooser : public RefCounted<FileChooser> {
@@ -59,21 +61,25 @@ public:
     // the Chrome class instead.
     void openFileChooser(Document*);
 
-    const String& filename() const { return m_filename; }
+    const Vector<String>& filenames() const { return m_filenames; }
     String basenameForWidth(const Font&, int width) const;
 
     Icon* icon() const { return m_icon.get(); }
 
     void clear(); // for use by client; does not call valueChanged
 
-    void chooseFile(const String& filename);
+    void chooseFile(const String& path);
+    void chooseFiles(const Vector<String>& paths);
+    
+    bool allowsMultipleFiles() const { return m_client ? m_client->allowsMultipleFiles() : false; }
 
 private:
-    FileChooser(FileChooserClient*, const String& initialFilename);
+    FileChooser(FileChooserClient*, const String& initialfilename);
     static PassRefPtr<Icon> chooseIcon(const String& filename);
+    static PassRefPtr<Icon> chooseIcon(Vector<String> filenames);
 
     FileChooserClient* m_client;
-    String m_filename;
+    Vector<String> m_filenames;
     RefPtr<Icon> m_icon;
 };
 
index f70b5b0..65d0135 100644 (file)
@@ -24,6 +24,7 @@
 #include <wtf/PassRefPtr.h>
 #include <wtf/RefCounted.h>
 #include <wtf/Forward.h>
+#include <wtf/Vector.h>
 
 #if PLATFORM(MAC)
 #include <wtf/RetainPtr.h>
@@ -51,6 +52,8 @@ class String;
 class Icon : public RefCounted<Icon> {
 public:
     static PassRefPtr<Icon> newIconForFile(const String& filename);
+    static PassRefPtr<Icon> newIconForFiles(const Vector<String>& filenames);
+
     ~Icon();
 
     void paint(GraphicsContext*, const IntRect&);
index e42414d..1b1c3fc 100644 (file)
@@ -103,6 +103,12 @@ PassRefPtr<Icon> Icon::newIconForFile(const String& filename)
     return icon.release();
 }
 
+PassRefPtr<Icon> Icon::newIconForFiles(const Vector<String>& filenames)
+{
+    //FIXME: Implement this
+    return 0;
+}
+
 void Icon::paint(GraphicsContext* context, const IntRect& rect)
 {
     if (context->paintingDisabled())
index a9eb524..19dffe2 100644 (file)
@@ -53,6 +53,18 @@ PassRefPtr<Icon> Icon::newIconForFile(const String& filename)
     return adoptRef(new Icon(image));
 }
 
+PassRefPtr<Icon> Icon::newIconForFiles(const Vector<String>& filenames)
+{
+    if (filenames.isEmpty())
+        return 0;
+
+    NSImage* image = [NSImage imageNamed:NSImageNameMultipleDocuments];
+    if (!image)
+        return 0;
+
+    return adoptRef(new Icon(image));
+}
+
 void Icon::paint(GraphicsContext* context, const IntRect& rect)
 {
     if (context->paintingDisabled())
index 734a4fb..84f18f7 100644 (file)
@@ -48,6 +48,12 @@ PassRefPtr<Icon> Icon::newIconForFile(const String& filename)
     return i.release();
 }
 
+PassRefPtr<Icon> Icon::newIconForFiles(const Vector<String>& filenames)
+{
+    //FIXME: Implement this
+    return 0;
+}
+
 void Icon::paint(GraphicsContext* ctx, const IntRect& rect)
 {
     QPixmap px = m_icon.pixmap(rect.size());
index 1c67bc7..d2b26e8 100644 (file)
@@ -50,6 +50,12 @@ PassRefPtr<Icon> Icon::newIconForFile(const String& filename)
     return adoptRef(new Icon(sfi.hIcon));
 }
 
+PassRefPtr<Icon> Icon::newIconForFiles(const Vector<String>& filenames)
+{
+    //FIXME: Implement this
+    return 0;
+}
+
 void Icon::paint(GraphicsContext* context, const IntRect& r)
 {
     if (context->paintingDisabled())
index 7a24bcd..4ee255d 100644 (file)
@@ -84,12 +84,13 @@ String FileChooser::basenameForWidth(const Font& font, int width) const
 
     String string = fileButtonNoFileSelectedLabel();
 
-    if (!m_filename.isEmpty()) {
-        gchar* systemFilename = filenameFromString(m_filename);
+    if (m_filenames.size() == 1) {
+        gchar* systemFilename = filenameFromString(m_filenames[0]);
         gchar* systemBasename = g_path_get_basename(systemFilename);
         g_free(systemFilename);
         stringByAdoptingFileSystemRepresentation(systemBasename, string);
-    }
+    } else if (m_filenames.size() > 1)
+        return StringTruncator::rightTruncate(String::number(m_filenames.size()) + " files", width, font, false);
 
     return StringTruncator::centerTruncate(string, width, font, false);
 }
index 0c3f577..429cd2f 100644 (file)
@@ -58,10 +58,12 @@ String FileChooser::basenameForWidth(const Font& font, int width) const
         return String();
 
     String strToTruncate;
-    if (m_filename.isEmpty())
+    if (m_filenames.isEmpty())
         strToTruncate = fileButtonNoFileSelectedLabel();
+    else if (m_filenames.size() == 1)
+        strToTruncate = [[NSFileManager defaultManager] displayNameAtPath:(m_filenames[0])];
     else
-        strToTruncate = [[NSFileManager defaultManager] displayNameAtPath:m_filename];
+        return StringTruncator::rightTruncate(String::number(m_filenames.size()) + " files", width, font, false);
 
     return StringTruncator::centerTruncate(strToTruncate, width, font, false);
 }
index c024da4..3ef224d 100644 (file)
@@ -83,7 +83,6 @@ void disassociateStreamWithResourceHandle(NSInputStream *stream)
     if (!stream)
         return;
 
-    ASSERT(getStreamResourceHandleMap().contains((CFReadStreamRef)stream));
     getStreamResourceHandleMap().remove((CFReadStreamRef)stream);
 }
 
index 6c95704..b664cf8 100644 (file)
@@ -42,7 +42,7 @@ void FileChooser::openFileChooser(Document* doc)
     if (!fl)
         return;
 
-    QString f = fl->chooseFile(m_filename);
+    QString f = fl->chooseFile(m_filenames[0]);
     if (!f.isEmpty())
         chooseFile(f);
 }
@@ -50,7 +50,7 @@ void FileChooser::openFileChooser(Document* doc)
 String FileChooser::basenameForWidth(const Font& f, int width) const
 {
     QFontMetrics fm(f.font());
-    return fm.elidedText(m_filename, Qt::ElideLeft, width);
+    return fm.elidedText(m_filenames[0], Qt::ElideLeft, width);
 }
 
 }
index 76fd5bb..d1c83ea 100644 (file)
@@ -80,13 +80,14 @@ String FileChooser::basenameForWidth(const Font& font, int width) const
         return String();
 
     String string;
-    if (m_filename.isEmpty())
+    if (m_filenames.isEmpty())
         string = fileButtonNoFileSelectedLabel();
-    else {
-        String tmpFilename = m_filename;
+    else if (m_filenames.size() == 1) {
+        String tmpFilename = m_filenames[0];
         LPTSTR basename = PathFindFileName(tmpFilename.charactersWithNullTermination());
         string = String(basename);
-    }
+    } else
+        return StringTruncator::rightTruncate(String::number(m_filenames.size()) + " files", width, font, false);
 
     return StringTruncator::centerTruncate(string, width, font, false);
 }
index 40d8372..19318ec 100644 (file)
@@ -21,6 +21,7 @@
 #include "config.h"
 #include "RenderFileUploadControl.h"
 
+#include "FileList.h"
 #include "FrameView.h"
 #include "GraphicsContext.h"
 #include "HTMLInputElement.h"
@@ -86,7 +87,7 @@ void RenderFileUploadControl::valueChanged()
     RefPtr<FileChooser> fileChooser = m_fileChooser;
 
     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
-    inputElement->setValueFromRenderer(fileChooser->filename());
+    inputElement->setFileListFromRenderer(fileChooser->filenames());
     inputElement->onChange();
  
     // only repaint if it doesn't seem we have been destroyed
@@ -94,6 +95,12 @@ void RenderFileUploadControl::valueChanged()
         repaint();
 }
 
+bool RenderFileUploadControl::allowsMultipleFiles()
+{
+    HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
+    return !input->getAttribute(multipleAttr).isNull();
+}
+
 void RenderFileUploadControl::click()
 {
      m_fileChooser->openFileChooser(node()->document());
@@ -102,7 +109,8 @@ void RenderFileUploadControl::click()
 void RenderFileUploadControl::updateFromElement()
 {
     HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(node());
-
+    ASSERT(inputElement->inputType() == HTMLInputElement::FILE);
+    
     if (!m_button) {
         m_button = new HTMLFileUploadInnerButtonElement(document(), inputElement);
         m_button->setInputType("button");
@@ -120,9 +128,11 @@ void RenderFileUploadControl::updateFromElement()
 
     m_button->setDisabled(!theme()->isEnabled(this));
 
-    // This only supports clearing out the filename, but that's OK because for
+    // This only supports clearing out the files, but that's OK because for
     // security reasons that's the only change the DOM is allowed to make.
-    if (inputElement->value().isEmpty() && !m_fileChooser->filename().isEmpty()) {
+    FileList* files = inputElement->files();
+    ASSERT(files);
+    if (files && files->isEmpty() && !m_fileChooser->filenames().isEmpty()) {
         m_fileChooser->clear();
         repaint();
     }
@@ -251,9 +261,12 @@ void RenderFileUploadControl::calcPrefWidths()
     setPrefWidthsDirty(false);
 }
 
-void RenderFileUploadControl::receiveDroppedFile(const String& filename)
+void RenderFileUploadControl::receiveDroppedFiles(const Vector<String>& paths)
 {
-    m_fileChooser->chooseFile(filename);
+    if (allowsMultipleFiles())
+        m_fileChooser->chooseFiles(paths);
+    else
+        m_fileChooser->chooseFile(paths[0]);
 }
 
 String RenderFileUploadControl::buttonValue()
index d909b6e..60e7a7b 100644 (file)
@@ -47,10 +47,12 @@ public:
 
     void valueChanged();
     
-    void receiveDroppedFile(const String&);
+    void receiveDroppedFiles(const Vector<String>&);
 
     String buttonValue();
     String fileTextValue();
+    
+    bool allowsMultipleFiles();
 
 protected:
     virtual void styleDidChange(RenderStyle::Diff, const RenderStyle* oldStyle);
index 9bb46ed..4421f0f 100644 (file)
@@ -1,3 +1,14 @@
+2008-10-24  Adele Peterson  <adele@apple.com>
+
+        Reviewed by Sam Weinig.
+
+        WebKit part of fix for <rdar://problem/5839256> FILE CONTROL: multi-file upload.
+
+        * WebCoreSupport/WebChromeClient.mm:
+        (WebChromeClient::runOpenPanel):
+        (-[WebOpenPanelResultListener chooseFilenames:]):
+        * WebView/WebUIDelegate.h:
+
 2008-10-24  Timothy Hatcher  <timothy@apple.com>
 
         Implement new InspectorClient methods to work with Settings.
index 3ef7b46..5649817 100644 (file)
@@ -58,6 +58,7 @@
 #import <WebCore/Widget.h>
 #import <WebCore/WindowFeatures.h>
 #import <wtf/PassRefPtr.h>
+#import <wtf/Vector.h>
 
 @interface NSView (WebNSViewDetails)
 - (NSView *)_findLastViewInKeyViewLoop;
@@ -546,8 +547,13 @@ void WebChromeClient::paintCustomHighlight(Node* node, const AtomicString& type,
 void WebChromeClient::runOpenPanel(PassRefPtr<FileChooser> chooser)
 {
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
+    BOOL allowMultipleFiles = chooser->allowsMultipleFiles();
     WebOpenPanelResultListener *listener = [[WebOpenPanelResultListener alloc] initWithChooser:chooser];
-    CallUIDelegate(m_webView, @selector(webView:runOpenPanelForFileButtonWithResultListener:), listener);
+    id delegate = [m_webView UIDelegate];
+    if ([delegate respondsToSelector:@selector(webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:)])
+        CallUIDelegate(m_webView, @selector(webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:), listener, allowMultipleFiles);
+    else
+        CallUIDelegate(m_webView, @selector(webView:runOpenPanelForFileButtonWithResultListener:), listener);
     [listener release];
     END_BLOCK_OBJC_EXCEPTIONS;
 }
@@ -658,4 +664,18 @@ void WebChromeClient::enableSuddenTermination()
     _chooser = 0;
 }
 
+- (void)chooseFilenames:(NSArray *)filenames
+{
+    ASSERT(_chooser);
+    if (!_chooser)
+        return;
+    int count = [filenames count]; 
+    Vector<String> names(count);
+    for (int i = 0; i < count; i++)
+        names[i] = [filenames objectAtIndex:i];
+    _chooser->chooseFiles(names);
+    _chooser->deref();
+    _chooser = 0;
+}
+
 @end
index 5bacf47..c9b97ca 100644 (file)
@@ -129,6 +129,13 @@ typedef enum {
 - (void)chooseFilename:(NSString *)fileName;
 
 /*!
+    @method chooseFilenames:
+    @abstract Call this method to return an array of filenames from the file open panel.
+    @param fileNames
+*/
+- (void)chooseFilenames:(NSArray *)fileNames AVAILABLE_AFTER_WEBKIT_VERSION_3_1;
+
+/*!
     @method cancel
     @abstract Call this method to indicate that the file open panel was cancelled.
 */
@@ -404,6 +411,17 @@ typedef enum {
 - (void)webView:(WebView *)sender runOpenPanelForFileButtonWithResultListener:(id<WebOpenPanelResultListener>)resultListener;
 
 /*!
+    @method webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles
+    @abstract Display a file open panel for a file input control that may allow multiple files to be selected.
+    @param sender The WebView sending the delegate method.
+    @param resultListener The object to call back with the results.
+    @param allowMultipleFiles YES if the open panel should allow myltiple files to be selected, NO if not.
+    @discussion This method is passed a callback object instead of giving a return
+    value so that it can be handled with a sheet.
+*/
+- (void)webView:(WebView *)sender runOpenPanelForFileButtonWithResultListener:(id<WebOpenPanelResultListener>)resultListener allowMultipleFiles:(BOOL)allowMultipleFiles WEBKIT_OBJC_METHOD_ANNOTATION(AVAILABLE_AFTER_WEBKIT_VERSION_3_1);
+
+/*!
     @method webView:mouseDidMoveOverElement:modifierFlags:
     @abstract Update the window's feedback for mousing over links to reflect a new item the mouse is over
     or new modifier flags.