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

Commit adf2802

Browse files
committed
fix($location): always resolve relative links in html5mode to <base> url
BREAKING CHANGE (since 1.2.0 and 1.3.0-beta.1): Angular now requires a `<base>` tag when html5 mode of `$location` is enabled. Reasoning: Using html5 mode without a `<base href="...">` tag makes relative links for images, links, ... relative to the current url if the browser supports the history API. However, if the browser does not support the history API Angular falls back to using the `#`, and then all links would be broken. BREAKING CHANGE (since 1.2.17 and 1.3.0-beta.10): In html5 mode without a `<base>` tag on older browser that don't support the history API relative paths were adding up. E.g. clicking on `<a href="page1">` and then on `<a href="page2">` would produce `$location.path()==='/page1/page2'. The code that introduced this behavior was removed and Angular now also requires a `<base>` tag to be present when using html5 mode. Closes #8172
1 parent 31931ce commit adf2802

File tree

3 files changed

+141
-124
lines changed

3 files changed

+141
-124
lines changed

src/ng/location.js

+52-55
Original file line numberDiff line numberDiff line change
@@ -127,21 +127,32 @@ function LocationHtml5Url(appBase, basePrefix) {
127127
this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
128128
};
129129

130-
this.$$rewrite = function(url) {
130+
this.$$parseLinkUrl = function(url, relHref) {
131+
if (relHref && relHref[0] === '#') {
132+
// special case for links to hash fragments:
133+
// keep the old url and only replace the hash fragment
134+
this.hash(relHref.slice(1));
135+
return true;
136+
}
131137
var appUrl, prevAppUrl;
138+
var rewrittenUrl;
132139

133140
if ( (appUrl = beginsWith(appBase, url)) !== undefined ) {
134141
prevAppUrl = appUrl;
135142
if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) {
136-
return appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
143+
rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
137144
} else {
138-
return appBase + prevAppUrl;
145+
rewrittenUrl = appBase + prevAppUrl;
139146
}
140147
} else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) {
141-
return appBaseNoFile + appUrl;
148+
rewrittenUrl = appBaseNoFile + appUrl;
142149
} else if (appBaseNoFile == url + '/') {
143-
return appBaseNoFile;
150+
rewrittenUrl = appBaseNoFile;
151+
}
152+
if (rewrittenUrl) {
153+
this.$$parse(rewrittenUrl);
144154
}
155+
return !!rewrittenUrl;
145156
};
146157
}
147158

@@ -231,10 +242,12 @@ function LocationHashbangUrl(appBase, hashPrefix) {
231242
this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : '');
232243
};
233244

234-
this.$$rewrite = function(url) {
245+
this.$$parseLinkUrl = function(url, relHref) {
235246
if(stripHash(appBase) == stripHash(url)) {
236-
return url;
247+
this.$$parse(url);
248+
return true;
237249
}
250+
return false;
238251
};
239252
}
240253

@@ -254,16 +267,28 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
254267

255268
var appBaseNoFile = stripFile(appBase);
256269

257-
this.$$rewrite = function(url) {
270+
this.$$parseLinkUrl = function(url, relHref) {
271+
if (relHref && relHref[0] === '#') {
272+
// special case for links to hash fragments:
273+
// keep the old url and only replace the hash fragment
274+
this.hash(relHref.slice(1));
275+
return true;
276+
}
277+
278+
var rewrittenUrl;
258279
var appUrl;
259280

260281
if ( appBase == stripHash(url) ) {
261-
return url;
282+
rewrittenUrl = url;
262283
} else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) {
263-
return appBase + hashPrefix + appUrl;
284+
rewrittenUrl = appBase + hashPrefix + appUrl;
264285
} else if ( appBaseNoFile === url + '/') {
265-
return appBaseNoFile;
286+
rewrittenUrl = appBaseNoFile;
266287
}
288+
if (rewrittenUrl) {
289+
this.$$parse(rewrittenUrl);
290+
}
291+
return !!rewrittenUrl;
267292
};
268293

269294
this.$$compose = function() {
@@ -626,14 +651,18 @@ function $LocationProvider(){
626651
appBase;
627652

628653
if (html5Mode) {
654+
if (!baseHref) {
655+
throw $locationMinErr('nobase',
656+
"$location in HTML5 mode requires a <base> tag to be present!");
657+
}
629658
appBase = serverBase(initialUrl) + (baseHref || '/');
630659
LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
631660
} else {
632661
appBase = stripHash(initialUrl);
633662
LocationMode = LocationHashbangUrl;
634663
}
635664
$location = new LocationMode(appBase, '#' + hashPrefix);
636-
$location.$$parse($location.$$rewrite(initialUrl));
665+
$location.$$parseLinkUrl(initialUrl, initialUrl);
637666

638667
var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
639668

@@ -652,6 +681,9 @@ function $LocationProvider(){
652681
}
653682

654683
var absHref = elm.prop('href');
684+
// get the actual href attribute - see
685+
// http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
686+
var relHref = elm.attr('href') || elm.attr('xlink:href');
655687

656688
if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') {
657689
// SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
@@ -662,50 +694,15 @@ function $LocationProvider(){
662694
// Ignore when url is started with javascript: or mailto:
663695
if (IGNORE_URI_REGEXP.test(absHref)) return;
664696

665-
// Make relative links work in HTML5 mode for legacy browsers (or at least IE8 & 9)
666-
// The href should be a regular url e.g. /link/somewhere or link/somewhere or ../somewhere or
667-
// somewhere#anchor or http://example.com/somewhere
668-
if (LocationMode === LocationHashbangInHtml5Url) {
669-
// get the actual href attribute - see
670-
// http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
671-
var href = elm.attr('href') || elm.attr('xlink:href');
672-
673-
if (href && href.indexOf('://') < 0) { // Ignore absolute URLs
674-
var prefix = '#' + hashPrefix;
675-
if (href[0] == '/') {
676-
// absolute path - replace old path
677-
absHref = appBase + prefix + href;
678-
} else if (href[0] == '#') {
679-
// local anchor
680-
absHref = appBase + prefix + ($location.path() || '/') + href;
681-
} else {
682-
// relative path - join with current path
683-
var stack = $location.path().split("/"),
684-
parts = href.split("/");
685-
if (stack.length === 2 && !stack[1]) stack.length = 1;
686-
for (var i=0; i<parts.length; i++) {
687-
if (parts[i] == ".")
688-
continue;
689-
else if (parts[i] == "..")
690-
stack.pop();
691-
else if (parts[i].length)
692-
stack.push(parts[i]);
693-
}
694-
absHref = appBase + prefix + stack.join('/');
695-
}
696-
}
697-
}
698-
699-
var rewrittenUrl = $location.$$rewrite(absHref);
700-
701-
if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) {
702-
event.preventDefault();
703-
if (rewrittenUrl != $browser.url()) {
697+
if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
698+
if ($location.$$parseLinkUrl(absHref, relHref)) {
699+
event.preventDefault();
704700
// update location manually
705-
$location.$$parse(rewrittenUrl);
706-
$rootScope.$apply();
707-
// hack to work around FF6 bug 684208 when scenario runner clicks on links
708-
window.angular['ff-684208-preventDefault'] = true;
701+
if ($location.absUrl() != $browser.url()) {
702+
$rootScope.$apply();
703+
// hack to work around FF6 bug 684208 when scenario runner clicks on links
704+
window.angular['ff-684208-preventDefault'] = true;
705+
}
709706
}
710707
}
711708
});

test/ng/anchorScrollSpec.js

+4
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ describe('$anchorScroll', function() {
110110
return function($provide, $locationProvider) {
111111
$provide.value('$sniffer', {history: config.historyApi});
112112
$locationProvider.html5Mode(config.html5Mode);
113+
$provide.decorator('$browser', function($delegate) {
114+
$delegate.$$baseHref = '/';
115+
return $delegate;
116+
});
113117
};
114118
}
115119

0 commit comments

Comments
 (0)