Bug 37236

Summary: Cannot resubmit form after submission fails
Product: WebKit Reporter: Joe Mason <joenotcharles>
Component: FormsAssignee: Nobody <webkit-unassigned>
Status: NEW ---    
Severity: Normal CC: ap, dglazkov, jonlee, staikos, webkit.review.bot
Priority: P2    
Version: 528+ (Nightly build)   
Hardware: All   
OS: All   
Bug Depends on: 68185, 28633    
Bug Blocks:    
Attachments:
Description Flags
Manual test case
none
suggested patch
darin: review-, darin: commit-queue-
Attempt at layout test (not working)
none
updated patch
none
updated patch, with updated copyright
dglazkov: review-, dglazkov: commit-queue-
removed bogus comment darin: review-, darin: commit-queue-

Description Joe Mason 2010-04-07 15:27:11 PDT
Created attachment 52790 [details]
Manual test case

If a form submission fails due to a server error, the m_submittedUrl is not cleared properly and so clicking "submit" again does nothing.

Attached is a small html file that demonstrates this problem.  You'll need to use a client that opens a dialog on error instead of rendering an error page, so that it stays on the same page after the submission fails.  I used QtLauncher, edited to turn off ErrorPageExtension (

The criteria are:
- a form which loads in the current frame (no target attribute)
- the method is GET
- the action url returns an error (in the test file, it's a nonexistant file, for a file not found error)
- the client remains on the same page after the error is handled (eg. it displays the error in a dialog instead of an error page)

When this happens, clicking Submit the first time will display the error dialog, and after this clicking Submit again will do nothing, and not submit the form until the page is reloaded.

The reason is that FrameLoader::submitForm contains this code, to guard against submitting the form multiple times:

    if (m_frame->tree()->isDescendantOf(targetFrame)) {
        if (m_submittedFormURL == u)
            return;
        m_submittedFormURL = u;
    }

It's expected that after submitting the form, m_submittedFormURL is cleared.  But the code to do this on error, in FrameLoader::receivedMainResourceError, is:

        if (m_submittedFormURL == m_provisionalDocumentLoader->originalRequestCopy().url())
            m_submittedFormURL = KURL();

When the method is GET, the form data is added to the url as a query string, which is included in m_submittedFormURL.  But originalRequestCopy().url() is the basic url included in the action parameter, before the query string is added, so this test never passes and m_submittedFormURL is not reset.
Comment 1 Joe Mason 2010-04-07 15:31:23 PDT
I'm having a hard time making the attached file into a layout test.  It's easy to automatically submit the form after the page is loaded with javascript, but then the only way I can figure to wait for the response is to add a timeout, which is ugly and unreliable.  The best thing would be to catch some sort of "load failed" event and call form.submit again when it's received, but I can't find any such event.  The closest I could find was to set the form's target attribute to point to an iframe, and put an onload handler on the iframe - but that breaks the conditions of the bug, which only manifests when loading in the same frame.
Comment 2 Joe Mason 2010-04-07 15:43:39 PDT
Created attachment 52794 [details]
suggested patch

This patch will fix the problem.  It seems to me that if an error is received, always clearing m_submittedFormURL should be safe.  Under what circumstances would it NOT match the original URL for a FrameLoader?

If this is too invasive, an alternative is to compare only the base URL's but not the query parts.

I'll gladly add a layout test if someone can help me figure out how.
Comment 3 Darin Adler 2010-04-09 09:44:03 PDT
Comment on attachment 52794 [details]
suggested patch

Bug fixes require a test case. If you can't make an automated test case please include a manual one. But I think you can make an automated test case using http.

And I'm not sure it's safe to just clear multiple submission protection for failures that don't match the form URL. It might require more study of the patches that introduced this mechanism and some further thought about how a load of a different main resource might come through.
Comment 4 Joe Mason 2010-04-09 09:48:14 PDT
Comment on attachment 52790 [details]
Manual test case

This attachment IS a manual test case
Comment 5 Joe Mason 2010-04-09 09:57:13 PDT
A comment in FrameLoader.cpp says:

    // FIXME: Frame targeting is only one of the ways the submission could end
up doing something other
    // than replacing this frame's content, so this check is flawed. On the
other hand, the check is hardly
    // needed any more now that we reset m_submittedFormURL on each mouse or
key down event.

The root problem here is that, when manually submitting a form, you can click
on submit, wait for an error response, and then click on submit again and
nothing will happen.  The user would expect the form to be resubmitted in this
case.

But according to the comment above, since clicking on "submit" is a mouse press
or keydown, m_submittedFormURL should have been cleared by the user
interaction.  So now I think that's were the bug lies.

(Although the test in receivedMainResourceError is also incorrect for GET urls,
it shouldn't matter.)
Comment 6 Alexey Proskuryakov 2010-04-09 09:59:42 PDT
I'm also fairly confident that this can (and should) be tested automatically. There aren't a lot of forms tests in http/tests to serve as example currently, but there are tests with resources that change their state, e.g. returning 404 on one access, and a success on another.
Comment 7 Joe Mason 2010-04-09 12:06:24 PDT
The problem I'm having in creating an automated test is in deciding when to send the second submit.

It's easy to call form.submit on first load.  However:

- if we call form.submit again immediately, before FrameLoader::receivedMainResourceError has been called, m_submittedFormURL has not yet been reset so nothing happens - correctly, in this case
- we can't put an onload handler to submit again after the frame reloads, because it *doesn't* reload - we're testing an error condition
- we can't use an onerror handler as we would to test failure to load an image, because this has no meaning for forms
- we can't set the submit target to another frame and then watch the frame contents for an error message, because this bypasses the isDescendentOf check

The only thing I can think to do is to set a timeout after the first submit, and send the second submit after a reasonable time.  I can do that, I was just hoping somebody had a better suggestion
Comment 8 Alexey Proskuryakov 2010-04-09 12:14:39 PDT
You might be able poll the resource with XMLHttpRequest asking it if it has already received the failing submission. Although issues like bug 23933	could stand in the way.
Comment 9 Joe Mason 2010-05-11 15:41:52 PDT
Created attachment 55765 [details]
Attempt at layout test (not working)

Still unable to make a layout test for this.  I've attached my best attempt.  However, this code fails because after submitting a form with document.form.submit, the result replaces the current document even if it's an error code with no body.

This confuses me because it's not what happens when I run QtLauncher by hand, after editing WebPage::supportsExtension to turn off the ErrorPageExtension.  And LayoutTestControllerQt contains "m_handleErrorPages = false", so I think error pages are turned off for it as well.  So the error 500 I'm returning shouldn't cause a new page load - but it does.  After submitting postForm1, the output to the initial document ("Submitting postForm1"), "Waiting for postForm1") is cleared, and the final result is:

CONSOLE MESSAGE: line 68: TypeError: Result of expression 'form' [undefined] is not an object.
FAIL: Timed out waiting for notifyDone to be called
Got response: submitted=value1

Which tells me that it's gotten the response fine and gone on to submit the second form, which fails because "document.postForm2" is no longer an existing element: the document's been overwritten.

If anyone can help me get around this final hurdle I think this layout test will work.
Comment 10 Joe Mason 2010-05-11 15:46:52 PDT
Created attachment 55768 [details]
updated patch

This fixes the error in a safer way than the first patch.
Comment 11 Dimitri Glazkov (Google) 2010-05-11 15:50:13 PDT
Comment on attachment 55768 [details]
updated patch


> +    // (Nested FIXME: except we don't reset it on mouse events, only keydown.
> +    // This is important because part of the point is to prevent the user from
> +    // accidentally clicking twice on important forms like 1-click orders.)

But... we do reset it on mouse events. Look at EventHandlerMac.mm or WebKit/chromium/WebViewImpl.cpp.

Do you mean it's not done in Qt port?
Comment 12 WebKit Review Bot 2010-05-11 15:51:39 PDT
Attachment 55768 [details] did not pass style-queue:

Failed to run "['WebKitTools/Scripts/check-webkit-style', '--no-squash']" exit_code: 1
WebCore/loader/FrameLoader.cpp:520:  Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.  [readability/comparison_to_zero] [5]
Total errors found: 1 in 3 files


If any of these errors are false positives, please file a bug against check-webkit-style.
Comment 13 Joe Mason 2010-05-11 15:57:11 PDT
Created attachment 55771 [details]
updated patch, with updated copyright

Missed a copyright string in the last patch.
Comment 14 WebKit Review Bot 2010-05-11 15:58:58 PDT
Attachment 55771 [details] did not pass style-queue:

Failed to run "['WebKitTools/Scripts/check-webkit-style', '--no-squash']" exit_code: 1
WebCore/loader/FrameLoader.cpp:520:  Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons.  [readability/comparison_to_zero] [5]
Total errors found: 1 in 3 files


If any of these errors are false positives, please file a bug against check-webkit-style.
Comment 15 Dimitri Glazkov (Google) 2010-05-11 15:59:16 PDT
Comment on attachment 55771 [details]
updated patch, with updated copyright

Why not just resetMultipleFormSubmission protection on click, like other ports do?
Comment 16 Joe Mason 2010-05-11 16:04:19 PDT
Sigh. I only grepped WebCore for use of resetMultipleFormSubmission.  Why is it done in the cross-platform EventHandler.cpp for keyboard events, and in port-specific code for mouse events?
Comment 17 Dimitri Glazkov (Google) 2010-05-11 16:06:39 PDT
(In reply to comment #16)
> Sigh. I only grepped WebCore for use of resetMultipleFormSubmission.  Why is it done in the cross-platform EventHandler.cpp for keyboard events, and in port-specific code for mouse events?

That's a good question. The only reason I know is because I added this to Chromium port a short while ago.
Comment 18 Joe Mason 2010-05-11 16:12:53 PDT
Created attachment 55773 [details]
removed bogus comment

Here it is again, without the bogus "we don't clear it on mouse click" comment.  I also fixed the strcmp style error the stylebot was complaining about.

I think this is still a good patch even if the port does clear the multiple form submission on mouse click, because  it fixes a clear bug that's usually hidden by clearing the flag on click.
Comment 19 Darin Adler 2010-05-11 16:25:08 PDT
Comment on attachment 55773 [details]
removed bogus comment

Needs a regression test or an explanation of why one can't be created.
Comment 20 Joe Mason 2010-05-11 16:37:04 PDT
There's an explanation of why a regression test can't be created in comment #9.
Comment 21 Joe Mason 2011-09-14 09:16:55 PDT
resetMultipleFormSubmission was moved to core in bug 28633, which I think will affect this bug.
Comment 22 Joe Mason 2011-09-15 14:07:25 PDT
bug 68185, to move resetMultipleFormSubmission to another abstraction layer, will certainly affect this.