Open Bug 1922677 Opened 1 year ago Updated 2 days ago

Prevent new navigations triggered from abort handlers while an ongoing navigation is in progress

Categories

(Core :: DOM: Navigation, defect, P2)

defect
Points:
3

Tracking

()

ASSIGNED

People

(Reporter: jrmuizel, Assigned: farre, NeedInfo)

References

(Blocks 3 open bugs)

Details

(Keywords: webcompat:platform-bug, Whiteboard: [necko-triaged])

User Story

user-impact-score:2040

Attachments

(6 files, 1 obsolete file)

Attached file server.js

The attached server will wait 1 second before responding to any requests. If you make a status file containing some text and serve the attached index.html it will give different results in different browsers during refresh.

  • Firefox calls onabort on the XHR
  • Chrome seems to silently drop the XHR
  • Safari calls onerror on the XHR

This behaviour difference was observable on battle.net in bug 1919308. If you refresh the 2fa page in Firefox it will sometime redirect to the login page because onabort calls the jquery error handler in the following code:


	checkStatus: function() {
		$.ajax({
			method: 'GET',
			timeout: 30000,
			url: '/login/authenticator/status?check-' + (Math.random() + '') * 100000000000000000,
			beforeSend: function (){
				history.replaceState
					? history.replaceState(null, null, window.location.href.split('#')[0])
					: window.location.hash = '';
			},
			success: function (data) {
				var state = data.two_factor_state;

				if (state == "ACCEPTED" || state == "REJECTED") {
					pushButtonAuthenticator.isStatusCheckComplete = true;
					// Unschedule statusTimer on a successful response that will end up redirecting away from this view.
					clearTimeout(pushButtonAuthenticator.statusTimer);
					var queryString = '?';
					if (window.location.href.indexOf('?') !== -1) {
						queryString = '&';
					}
					window.location.href = window.location.href + queryString + (Math.random() + '') * 100000000000000000;
				}  else {
					pushButtonAuthenticator.statusTimer = setTimeout(function() {
						return pushButtonAuthenticator.checkStatus();
					}, pushButtonAuthenticator.statusFrequency);
				}
			},
			error: function() {
				// Prevent late responses from causing a redirect back to login after accepting/rejecting
				if (pushButtonAuthenticator.isStatusCheckComplete) {
					return;
				}

				// Only redirect if the user agent is still on the authenticator view.
				if (window.location.pathname.indexOf("/authenticator") > -1) {
					window.location.href =
						"/login/?app=" + pushButtonAuthenticator.getParam('app') +
						"&ref=" + pushButtonAuthenticator.getParam('ref');
				}
			}
		})
	},
Attached file index.html
See Also: → 1919308

This impacts a major site webcompat issue

Severity: -- → S3
Priority: -- → P2
Whiteboard: [necko-triaged][necko-priority-new]
Points: --- → 3
Rank: 2
Whiteboard: [necko-triaged][necko-priority-new] → [necko-triaged][necko-priority-next]
Whiteboard: [necko-triaged][necko-priority-next] → [necko-triaged][necko-priority-queue]
Assignee: nobody → valentin.gosu

From my research I think Firefox is doing the right thing by calling onabort when the XHR is cancelled due to the page navigating/refreshing.
However, what doesn't make a lot of sense is for the onabort handler to be able to override the location from the handler - in bug 1919308 comment 3 - if I'm understanding the problem correctly.

Attached file index.html

Running this testcase allong with the server in attachment 9428936 [details] and refreshing the page will have different behaviour in Chrome vs Firefox.
Chrome will refresh and end up on the same URL.
Firefox calls onabort and ends up navigating to http://example.com
Safari calls onerror and ends up navigating to http://example.org

Olli, any feedback regarding attachment 9496667 [details] ?
Not entirely sure this is the correct approach.
try auto looks green https://treeherder.mozilla.org/jobs?repo=try&revision=813425d96e3b97e98063d6b7c053b280291f3a7b
Anne also said this is problematic (onunload not being allowed to navigate, but onabort being called during unload is allowed to), and he'd appreciate some WPTs.
I'll try to post those too.

Component: DOM: Networking → DOM: Navigation
Flags: needinfo?(smaug)
Whiteboard: [necko-triaged][necko-priority-queue] → [necko-triaged]

How does the patch work if one just calls window.stop() during pageload?
But otherwise, looks quite reasonable.

Flags: needinfo?(smaug)
See Also: → 1505389
See Also: → 2004615
Blocks: 2004615
User Story: (updated)
Attachment #9496667 - Attachment is obsolete: true

Unfortunately my simple patch didn't really solve the issue, but I also found that the problem isn't limited to just abort handlers.

IMO when we navigate the docshell, we should make it ignore any other LoadURI calls that don't come from the browser or from user interaction.
I've found in attachment 9531993 [details] that the page handlers can even override user navigations via the URL bar which I don't think is right.
Not only can beforeunload navigate the page to a different location than expected, but even when some navigations are blocked (like in the unload event handler), one can still use setTimeout(0) to change the location after the restriction is lifted.

I think this seeds a more hollistic fix, and I'm probably not the best one to come up with it. Olli, could you take a look?

Assignee: valentin.gosu → nobody
Flags: needinfo?(smaug)
See Also: → 1671309
Blocks: 2011368
User Story: (updated)

Hi folks, would it be possible to prioritize this bug?
It seems we're seeing more and more web-compat issues steming from it, and it would be great to avoid them.

Flags: needinfo?(smaug)
Flags: needinfo?(afarre)
Summary: Different behaviour for outstanding XHRs when refreshing → Prevent new navigations triggered from abort handlers while an ongoing navigation is in progress

I'll take it to the next DOM Core meeting. It would be nice if we could do what smaug mentions in comment 13, and I think that we can shoe-horn this in to be a part of the work we need to do to fix replace loads for iframes. Or maybe not, but it would still be nice to handle ongoing navigation tracking as spec does.

Flags: needinfo?(afarre)
Blocks: 2021531
User Story: (updated)
Assignee: nobody → afarre
Status: NEW → ASSIGNED

I'm looking at the test in comment 11. Chrome does exactly what we do insofar that it gets into a reload loop. How they manager to break it I don't know, but we hit our rate limit in BrowsingContext::Navigate, which makes the last one throw and the second to last finish.

@zcorpan, @smaug: has it been the rate limiter all along?

Flags: needinfo?(smaug) → needinfo?(zcorpan)

I'm trying to see if rate limiting everything works, but removing the exception if limit is hit. This would align us with Chrome.

OK, I think this is actually two bugs:

  1. Rate limiting throwing with Gecko. Chromium were willing to switch to doing this, but never followed through, and as long as the situation is this way we'll misbehave on poorly constructed web-pages. This corresponds to bug 2011368 and bug 2021531. Poorly, in the way that what has been constructed is a navigation trigger loop:
1) Trigger navigation
2) On navigation triggered goto 1
  1. onAbort firing when navigating away. This is bug 2004615.

More try

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: