Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix($location/$browser): prevent infinite digests on empty hash changes #9923

Closed

Conversation

petebacondarwin
Copy link
Contributor

Fixes #9635

@petebacondarwin
Copy link
Contributor Author

@tbosch is unavailable to to review.
@caitp - Can you take a look at this - I've managed to add unit tests.

@petebacondarwin petebacondarwin assigned lgalfaso and unassigned caitp Nov 6, 2014
@petebacondarwin
Copy link
Contributor Author

Actually @lgalfaso - here is one that you could review for me.

@lgalfaso
Copy link
Contributor

lgalfaso commented Nov 6, 2014

There is one thing that I do not fully like about this patch. Even when
there is no such a thing as empty fragment, browsers behave slightly
different when moving from some-url#foo to some-url and some-url#. In
the first case they stay at the page position that they are and in the
second they move to the top

This is, by adding a # at the end, we may end up with a situation that
the user is not expecting, this is that the page position moves to the top

@tbosch
Copy link
Contributor

tbosch commented Nov 6, 2014

AFAIK: moving from some-url#foo to some-url always does a page reload
On Thu, Nov 6, 2014 at 8:01 AM Lucas Galfasó [email protected]
wrote:

There is one thing that I do not fully like about this patch. Even when
there is no such a thing as empty fragment, browsers behave slightly
different when moving from some-url#foo to some-url and some-url#. In
the first case they stay at the page position that they are and in the
second they move to the top

This is, by adding a # at the end, we may end up with a situation that
the user is not expecting, this is that the page position moves to the top


Reply to this email directly or view it on GitHub
#9923 (comment).

@petebacondarwin
Copy link
Contributor Author

I believe that @tbosch is correct. This is the main cause of the problem. When we go from a URL with a hash to one without the browser does a page reload but doesn't update the location.href (since the page will be reloading anyway - it believes).

In non-HTML5 mode we always leave a # symbol in place so the page does not reload.

In this case, I don't think that this fix is a breaking change unless someone is trying to force a page reload by removing the hash from the URL. Previously removing the hash would cause a page reload and so the browser would reset to the top of the page anyway.

The workaround if you intended to reload the page would be to add an additional call to location.reload();

What do you think?

@lgalfaso
Copy link
Contributor

lgalfaso commented Nov 6, 2014

@petebacondarwin is this still the case when we do not really go to the page but just push the new url to the browser history?

One more question: Will people think that always adding a # to the end pollutes the URL?

@@ -68,6 +68,10 @@ function stripHash(url) {
return index == -1 ? url : url.substr(0, index);
}

function trimEmptyHash(url) {
return url.replace(/#$/,'');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will not do the right thing for URLs of the form http://example.com/foo#bar# (check that there are two #)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Good point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although strictly unescaped # characters are not allowed inside fragments, I guess we have to deal with that case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that strictly speaking, this is not supported, but I tested this with Chrome and Safari and in both cases location.hash was unescaped

@petebacondarwin
Copy link
Contributor Author

One more question: Will people think that always adding a # to the end pollutes the URL?

Good question. I was wondering about this myself. I think we could try to be clever and only add the empty hash in if we are moving to a URL that has the same base and no hash, while the original did have a hash fragment?

@lgalfaso
Copy link
Contributor

lgalfaso commented Nov 6, 2014

@petebacondarwin if the # was added only when moving from a URL that already had a fragment, then that would be perfect

@petebacondarwin
Copy link
Contributor Author

I will try to implement that tomorrow but unfortunately I am running a workshop all day so I might miss the 1.3.2 cut-off

@NevilleS
Copy link
Contributor

NevilleS commented Nov 6, 2014

Hey there, I just wanted to mention that this seems to happen when you navigate to any URL with an empty hash, not just when making a change. It might already be covered by this fix, but I'm not sure - the test cases seem to test things like /path#123 --> /path#, whereas I can reproduce the issue by just navigating to /#. You can see this happening at https://docs.angularjs.org/api#, for example.

@petebacondarwin
Copy link
Contributor Author

This does appear to be fixed by this: https://ci.angularjs.org/job/angular.js-pete/711/artifact/build/docs/api#

@NevilleS
Copy link
Contributor

NevilleS commented Nov 6, 2014

OK good 😄

@petebacondarwin
Copy link
Contributor Author

if the # was added only when moving from a URL that already had a fragment, then that would be perfect

@lgalfaso - actually this is what the code already does!

Do we really need to cope with unescaped # chars in the fragment in HTML5 mode? It would only be a make a difference if moving from a/b/c#frag# to a/b/c#frag. The new algorithm would assume these to be the same URL and not update the browser URL or hash.

@caitp caitp modified the milestones: 1.3.2, 1.3.3 Nov 7, 2014
@Narretz
Copy link
Contributor

Narretz commented Nov 8, 2014

There is one case which is fixed by #9903, but not by this PR:

I don't know if the previous behavior was explicitly supported, but it made sense to me, since initial loading of a site works without !#.

@lgalfaso
Copy link
Contributor

lgalfaso commented Nov 8, 2014

Do we really need to cope with unescaped # chars in the fragment in HTML5 mode? It would only be a make a difference if moving from a/b/c#frag# to a/b/c#frag

The main issue is that the browser does not escape the second # (at least Chrome nor Safari do). The code will not be any less efficient as something like return url.replace(/(#.+)|#$/, '$1'); should make it

@petebacondarwin
Copy link
Contributor Author

@lgalfaso - OK, I will change the regex.
@Narretz - I think this is a bug in LocationHashbangUrl or LocationHashbangInHtml5Url highlighted by fixing $browser. In hashbang mode, we ought to catch changes to the $location that would cause the hashbang to be stripped unnecessarily and add it back in before it gets to the $browers.url method. What do you think?

By retaining a trailing `#` character in the URL we ensure that the browser
does not attempt a reload, which in turn allows us to read the correct
`location.href` value.

Closes angular#9635
The url is the same whether or not there is an empty `#` marker at the end.
This prevents unwanted calls to update the browser, since the browser is
automatically applying an empty hash if necessary to prevent page reloads.

Closes angular#9635
The test for not rewriting links was invalid and just happened to be
passing by chance (false-positive).
@petebacondarwin
Copy link
Contributor Author

  • in this route you link back with <a href="/">Home</a>

I believe that this link should be rewritten to <a href="/#!'>Home</a>??

In other words, is this test actually invalid: https://github.com/angular/angular.js/blob/master/test/ng/locationSpec.js#L1972-L1978

    it("should not set hash if one was not originally specified", function() {
      location = new LocationHashbangUrl('http://server/pre/index.html', '#');

      location.$$parse('http://server/pre/index.html');
      expect(location.url()).toBe('');
      expect(location.absUrl()).toBe('http://server/pre/index.html');
    });

@petebacondarwin
Copy link
Contributor Author

Actually, @Narretz - this PR doesn't actually have the problem you describe. See https://gist.github.com/petebacondarwin/3a3bab60cb00941874f4

@Narretz
Copy link
Contributor

Narretz commented Nov 10, 2014

@petebacondarwin Okay, I will test again and see what the difference between your gist and my test is.

@petebacondarwin
Copy link
Contributor Author

In my Gist, the browser actually does a reload - because the new URL without the hash bang is treated as outside the app.

Is this desired? It seems to me that this is about right. If you want to stay within the application you should include the hash bang. But this may not be what people expect.

@lgalfaso
Copy link
Contributor

In my Gist, the browser actually does a reload - because the new URL without the hash bang is treated as outside the app.

Is this also the case in html5 mode?

@petebacondarwin
Copy link
Contributor Author

@lgalfaso - so in HTML5 mode it is a little different as we have no hash-bang situation but it does appear that going from http://some/url# to http://some/url does cause a page reload. I am now confused as to whether this is expected or not.

@petebacondarwin
Copy link
Contributor Author

OK, so that reload does not happen in 1.2.20 so it should not happen here. It may be that a variant on #9903 is better

@lgalfaso
Copy link
Contributor

LGTM

@petebacondarwin
Copy link
Contributor Author

This doesn't actually fully solve the problem either. Check back on #9635 for further analysis

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

$location.hash('') causes infinite digest loop
6 participants