Skip to content

Commit

Permalink
fix($location): always resolve relative links in html5mode to `<base>…
Browse files Browse the repository at this point in the history
…` 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 angular#8172
  • Loading branch information
tbosch committed Aug 29, 2014
1 parent 31931ce commit adf2802
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 124 deletions.
107 changes: 52 additions & 55 deletions src/ng/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,21 +127,32 @@ function LocationHtml5Url(appBase, basePrefix) {
this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
};

this.$$rewrite = function(url) {
this.$$parseLinkUrl = function(url, relHref) {
if (relHref && relHref[0] === '#') {
// special case for links to hash fragments:
// keep the old url and only replace the hash fragment
this.hash(relHref.slice(1));
return true;
}
var appUrl, prevAppUrl;
var rewrittenUrl;

if ( (appUrl = beginsWith(appBase, url)) !== undefined ) {
prevAppUrl = appUrl;
if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) {
return appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
} else {
return appBase + prevAppUrl;
rewrittenUrl = appBase + prevAppUrl;
}
} else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) {
return appBaseNoFile + appUrl;
rewrittenUrl = appBaseNoFile + appUrl;
} else if (appBaseNoFile == url + '/') {
return appBaseNoFile;
rewrittenUrl = appBaseNoFile;
}
if (rewrittenUrl) {
this.$$parse(rewrittenUrl);
}
return !!rewrittenUrl;
};
}

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

this.$$rewrite = function(url) {
this.$$parseLinkUrl = function(url, relHref) {
if(stripHash(appBase) == stripHash(url)) {
return url;
this.$$parse(url);
return true;
}
return false;
};
}

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

var appBaseNoFile = stripFile(appBase);

this.$$rewrite = function(url) {
this.$$parseLinkUrl = function(url, relHref) {
if (relHref && relHref[0] === '#') {
// special case for links to hash fragments:
// keep the old url and only replace the hash fragment
this.hash(relHref.slice(1));
return true;
}

var rewrittenUrl;
var appUrl;

if ( appBase == stripHash(url) ) {
return url;
rewrittenUrl = url;
} else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) {
return appBase + hashPrefix + appUrl;
rewrittenUrl = appBase + hashPrefix + appUrl;
} else if ( appBaseNoFile === url + '/') {
return appBaseNoFile;
rewrittenUrl = appBaseNoFile;
}
if (rewrittenUrl) {
this.$$parse(rewrittenUrl);
}
return !!rewrittenUrl;
};

this.$$compose = function() {
Expand Down Expand Up @@ -626,14 +651,18 @@ function $LocationProvider(){
appBase;

if (html5Mode) {
if (!baseHref) {
throw $locationMinErr('nobase',
"$location in HTML5 mode requires a <base> tag to be present!");
}
appBase = serverBase(initialUrl) + (baseHref || '/');
LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url;
} else {
appBase = stripHash(initialUrl);
LocationMode = LocationHashbangUrl;
}
$location = new LocationMode(appBase, '#' + hashPrefix);
$location.$$parse($location.$$rewrite(initialUrl));
$location.$$parseLinkUrl(initialUrl, initialUrl);

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

Expand All @@ -652,6 +681,9 @@ function $LocationProvider(){
}

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

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

// Make relative links work in HTML5 mode for legacy browsers (or at least IE8 & 9)
// The href should be a regular url e.g. /link/somewhere or link/somewhere or ../somewhere or
// somewhere#anchor or http://example.com/somewhere
if (LocationMode === LocationHashbangInHtml5Url) {
// get the actual href attribute - see
// http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
var href = elm.attr('href') || elm.attr('xlink:href');

if (href && href.indexOf('://') < 0) { // Ignore absolute URLs
var prefix = '#' + hashPrefix;
if (href[0] == '/') {
// absolute path - replace old path
absHref = appBase + prefix + href;
} else if (href[0] == '#') {
// local anchor
absHref = appBase + prefix + ($location.path() || '/') + href;
} else {
// relative path - join with current path
var stack = $location.path().split("/"),
parts = href.split("/");
if (stack.length === 2 && !stack[1]) stack.length = 1;
for (var i=0; i<parts.length; i++) {
if (parts[i] == ".")
continue;
else if (parts[i] == "..")
stack.pop();
else if (parts[i].length)
stack.push(parts[i]);
}
absHref = appBase + prefix + stack.join('/');
}
}
}

var rewrittenUrl = $location.$$rewrite(absHref);

if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) {
event.preventDefault();
if (rewrittenUrl != $browser.url()) {
if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
if ($location.$$parseLinkUrl(absHref, relHref)) {
event.preventDefault();
// update location manually
$location.$$parse(rewrittenUrl);
$rootScope.$apply();
// hack to work around FF6 bug 684208 when scenario runner clicks on links
window.angular['ff-684208-preventDefault'] = true;
if ($location.absUrl() != $browser.url()) {
$rootScope.$apply();
// hack to work around FF6 bug 684208 when scenario runner clicks on links
window.angular['ff-684208-preventDefault'] = true;
}
}
}
});
Expand Down
4 changes: 4 additions & 0 deletions test/ng/anchorScrollSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ describe('$anchorScroll', function() {
return function($provide, $locationProvider) {
$provide.value('$sniffer', {history: config.historyApi});
$locationProvider.html5Mode(config.html5Mode);
$provide.decorator('$browser', function($delegate) {
$delegate.$$baseHref = '/';
return $delegate;
});
};
}

Expand Down
Loading

0 comments on commit adf2802

Please sign in to comment.