Skip to content
Permalink
Browse files
Implement cancel event on <input type=file>
https://bugs.webkit.org/show_bug.cgi?id=227799
rdar://80340803

Reviewed by Aditya Keerthi.

This PR implements the "Add a "cancel" event for when file upload selection is unchanged"
behavior.

The `cancel` event is now fired when:
- The file picker is dismissed
- The selected files are the same as those currently selected

Spec: https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable

* Source/WebCore/html/FileInputType.cpp:
(WebCore::FileInputType::setFiles):
(WebCore::FileInputType::fileChoosingCancelled):
* Source/WebCore/html/FileInputType.h:
* Source/WebCore/platform/FileChooser.cpp:
(WebCore::FileChooser::cancelFileChoosing):
* Source/WebCore/platform/FileChooser.h:
* Source/WebKit/WebProcess/WebPage/WebOpenPanelResultListener.cpp:
(WebKit::WebOpenPanelResultListener::didCancelFileChoosing):
* Source/WebKit/WebProcess/WebPage/WebOpenPanelResultListener.h:
* Source/WebKit/WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::didCancelForOpenPanel):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/RunOpenPanel.mm:
(-[FileInputTypeCancelEventUIDelegate webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:]):
(TestWebKitAPI::TEST):

Canonical link: https://commits.webkit.org/255394@main
  • Loading branch information
rr-codes committed Oct 11, 2022
1 parent fe38e39 commit 059f8069cb4a83b08a89b8b2ae02d9b82d8c5fd1
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 5 deletions.
@@ -400,7 +400,9 @@ void FileInputType::setFiles(RefPtr<FileList>&& files, RequestIcon shouldRequest
// input instance is safe since it is ref-counted.
protectedInputElement->dispatchInputEvent();
protectedInputElement->dispatchChangeEvent();
}
} else
protectedInputElement->dispatchCancelEvent();

protectedInputElement->setChangedSinceLastFormControlChangeEvent(false);
}

@@ -450,6 +452,14 @@ void FileInputType::filesChosen(const Vector<String>& paths, const Vector<String
filesChosen(WTFMove(files));
}

void FileInputType::fileChoosingCancelled()
{
ASSERT(element());
Ref<HTMLInputElement> protectedInputElement(*element());

protectedInputElement->dispatchCancelEvent();
}

void FileInputType::didCreateFileList(Ref<FileList>&& fileList, RefPtr<Icon>&& icon)
{
Ref protectedThis { *this };
@@ -86,6 +86,7 @@ class FileInputType final : public BaseClickableWithKeyInputType, private FileCh

void filesChosen(const Vector<FileChooserFileInfo>&, const String& displayString = { }, Icon* = nullptr) final;
void filesChosen(const Vector<String>& paths, const Vector<String>& replacementPaths = { });
void fileChoosingCancelled();

// FileIconLoaderClient implementation.
void iconLoaded(RefPtr<Icon>&&) final;
@@ -299,6 +299,11 @@ void HTMLFormControlElement::dispatchChangeEvent()
dispatchScopedEvent(Event::create(eventNames().changeEvent, Event::CanBubble::Yes, Event::IsCancelable::No));
}

void HTMLFormControlElement::dispatchCancelEvent()
{
dispatchScopedEvent(Event::create(eventNames().cancelEvent, Event::CanBubble::Yes, Event::IsCancelable::No));
}

void HTMLFormControlElement::dispatchFormControlChangeEvent()
{
dispatchChangeEvent();
@@ -70,6 +70,7 @@ class HTMLFormControlElement : public LabelableElement, public FormAssociatedEle

virtual void dispatchFormControlChangeEvent();
void dispatchChangeEvent();
void dispatchCancelEvent();
void dispatchFormControlInputEvent();

bool isDisabledFormControl() const final { return m_disabled || m_disabledByAncestorFieldset; }
@@ -68,6 +68,14 @@ void FileChooser::chooseFiles(const Vector<String>& filenames, const Vector<Stri
m_client->filesChosen(WTFMove(files));
}

void FileChooser::cancelFileChoosing()
{
if (!m_client)
return;

m_client->fileChoosingCancelled();
}

#if PLATFORM(IOS_FAMILY)

// FIXME: This function is almost identical to FileChooser::chooseFiles(). We should merge this function
@@ -87,10 +95,6 @@ void FileChooser::chooseMediaFiles(const Vector<String>& filenames, const String

void FileChooser::chooseFiles(const Vector<FileChooserFileInfo>& files)
{
auto paths = files.map([](auto& file) {
return file.path;
});

if (m_client)
m_client->filesChosen(files);
}
@@ -70,6 +70,8 @@ class FileChooserClient {
virtual ~FileChooserClient() = default;

virtual void filesChosen(const Vector<FileChooserFileInfo>&, const String& displayString = { }, Icon* = nullptr) = 0;

virtual void fileChoosingCancelled() = 0;
};

class FileChooser : public RefCounted<FileChooser> {
@@ -81,6 +83,7 @@ class FileChooser : public RefCounted<FileChooser> {

WEBCORE_EXPORT void chooseFile(const String& path);
WEBCORE_EXPORT void chooseFiles(const Vector<String>& paths, const Vector<String>& replacementPaths = { });
WEBCORE_EXPORT void cancelFileChoosing();
#if PLATFORM(IOS_FAMILY)
// FIXME: This function is almost identical to FileChooser::chooseFiles(). We should merge this
// function with FileChooser::chooseFiles() and hence remove the PLATFORM(IOS_FAMILY)-guard.
@@ -51,6 +51,11 @@ void WebOpenPanelResultListener::didChooseFiles(const Vector<String>& files, con
m_fileChooser->chooseFiles(files, replacementFiles);
}

void WebOpenPanelResultListener::didCancelFileChoosing()
{
m_fileChooser->cancelFileChoosing();
}

#if PLATFORM(IOS_FAMILY)
void WebOpenPanelResultListener::didChooseFilesWithDisplayStringAndIcon(const Vector<String>& files, const String& displayString, WebCore::Icon* displayIcon)
{
@@ -45,6 +45,7 @@ class WebOpenPanelResultListener : public RefCounted<WebOpenPanelResultListener>

void disconnectFromPage() { m_page = 0; }
void didChooseFiles(const Vector<String>& files, const Vector<String>& replacementFiles);
void didCancelFileChoosing();
#if PLATFORM(IOS_FAMILY)
void didChooseFilesWithDisplayStringAndIcon(const Vector<String>&, const String& displayString, WebCore::Icon*);
#endif
@@ -5094,6 +5094,10 @@ void WebPage::didChooseFilesForOpenPanel(const Vector<String>& files, const Vect

void WebPage::didCancelForOpenPanel()
{
if (!m_activeOpenPanelResultListener)
return;

m_activeOpenPanelResultListener->didCancelFileChoosing();
m_activeOpenPanelResultListener = nullptr;
}

@@ -29,9 +29,11 @@

#import "PlatformUtilities.h"
#import "TestNavigationDelegate.h"
#import "TestWKWebView.h"
#import "Utilities.h"
#import <AppKit/AppKit.h>
#import <WebKit/WKOpenPanelParametersPrivate.h>
#import <WebKit/WKUIDelegatePrivate.h>
#import <WebKit/WebKit.h>
#import <wtf/RetainPtr.h>

@@ -55,6 +57,18 @@ - (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelPara

@end

@interface FileInputTypeCancelEventUIDelegate : NSObject <WKUIDelegatePrivate>
@end

@implementation FileInputTypeCancelEventUIDelegate

- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * URLs))completionHandler
{
completionHandler(nil);
}

@end

namespace TestWebKitAPI {

TEST(WebKit, RunOpenPanelNonLatin1)
@@ -79,6 +93,41 @@ - (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelPara
}];
Util::run(&testFinished);
}

TEST(WebKit, FileInputTypeCancelEvent)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]);
auto uiDelegate = adoptNS([[FileInputTypeCancelEventUIDelegate alloc] init]);
[webView setUIDelegate:uiDelegate.get()];

NSString *markup = @""
"<script>"
" function loaded() {"
" setTimeout(() => {"
" const $file = document.getElementById('file');"
" $file.addEventListener('cancel', () => {"
" webkit.messageHandlers.testHandler.postMessage('cancel');"
" }, false);"
" }, 0);"
" }"
"</script>"
"<body style='width: 100vw; height: 100vh;' onload='loaded()'>"
" <input style='width: 100vw; height: 100vh;' type='file' id='file' name='file'>"
"</body>";

[webView loadHTMLString:markup baseURL:nil];
[webView _test_waitForDidFinishNavigation];

__block bool done = false;
[webView performAfterReceivingMessage:@"cancel" action:^{
done = true;
}];

NSPoint clickPoint = NSMakePoint(50, 50);
[webView sendClickAtPoint:clickPoint];

Util::run(&done);
}

} // namespace TestWebKitAPI

0 comments on commit 059f806

Please sign in to comment.