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

Commit

Permalink
fix($location): revert erroneous logic and backport refactorings from…
Browse files Browse the repository at this point in the history
… master

Backport of 2294880 without enforcing the `<base>` tag and without the new handling for links that only contain hash fragments.

Related to #6162
Closes #8492
  • Loading branch information
tbosch committed Oct 4, 2014
1 parent 430082e commit 1ee9b4e
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 118 deletions.
90 changes: 35 additions & 55 deletions src/ng/location.js
Expand Up @@ -127,21 +127,26 @@ function LocationHtml5Url(appBase, basePrefix) {
this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/'
};

this.$$rewrite = function(url) {
this.$$parseLinkUrl = function(url, relHref) {
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 +236,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 +261,21 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {

var appBaseNoFile = stripFile(appBase);

this.$$rewrite = function(url) {
this.$$parseLinkUrl = function(url, relHref) {
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 @@ -636,7 +648,7 @@ function $LocationProvider(){
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 @@ -655,6 +667,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 @@ -665,50 +680,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
120 changes: 57 additions & 63 deletions test/ng/locationSpec.js
Expand Up @@ -858,12 +858,12 @@ describe('$location', function() {
module(function($provide, $locationProvider) {
attrs = attrs ? ' ' + attrs + ' ' : '';

// fake the base behavior
if (typeof linkHref === 'string') {
if (!relLink) {
if (linkHref[0] == '/') {
linkHref = 'http://host.com' + linkHref;
} else if(!linkHref.match(/:\/\//)) {
// fake the behavior of <base> tag
linkHref = 'http://host.com/base/' + linkHref;
}
}
Expand All @@ -888,8 +888,8 @@ describe('$location', function() {
}

function initBrowser() {
return function($browser){
$browser.url('http://host.com/base');
return function($browser, $document){
$browser.url('http://host.com/base/index.html');
$browser.$$baseHref = '/base/index.html';
};
}
Expand Down Expand Up @@ -1003,12 +1003,14 @@ describe('$location', function() {


it('should produce relative paths correctly when $location.path() is "/" when history enabled on old browser', function() {
configureService({linkHref: 'partial1', html5Mode: true, supportHist: false, relLink: true});
configureService({linkHref: 'partial1', html5Mode: true, supportHist: false});
inject(
initBrowser(),
initLocation(),
function($browser, $location) {
$location.path('/');
function($browser, $location, $rootScope) {
$rootScope.$apply(function() {
$location.path('/');
});
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/base/index.html#!/partial1');
}
Expand Down Expand Up @@ -1203,52 +1205,22 @@ describe('$location', function() {
);
});


it('should rewrite relative links relative to current path when history disabled', function() {
configureService({linkHref: 'link', html5Mode: true, supportHist: false, relLink: true});
inject(
initBrowser(),
initLocation(),
function($browser, $location) {
$location.path('/some');
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/base/index.html#!/some/link');
}
);
});


it('should replace current path when link begins with "/" and history disabled', function() {
configureService({linkHref: '/link', html5Mode: true, supportHist: false, relLink: true});
inject(
initBrowser(),
initLocation(),
function($browser, $location) {
$location.path('/some');
browserTrigger(link, 'click');
expectRewriteTo($browser, 'http://host.com/base/index.html#!/link');
}
);
});


it('should replace current hash fragment when link begins with "#" history disabled', function() {
configureService({linkHref: '#link', html5Mode: true, supportHist: false, relLink: true});
configureService({linkHref: '#link', html5Mode: true, supportHist: false, relLink: false});
inject(
initBrowser(),
initLocation(),
function($browser, $location) {
// Initialize browser URL
$location.path('/some');
$location.hash('foo');
function($browser, $location, $rootScope) {
$rootScope.$apply(function() {
$location.hash('foo');
});
browserTrigger(link, 'click');
expect($location.hash()).toBe('link');
expectRewriteTo($browser, 'http://host.com/base/index.html#!/some#link');
expectRewriteTo($browser, 'http://host.com/base/index.html#!#link');
}
);
});


// don't run next tests on IE<9, as browserTrigger does not simulate pressed keys
if (!msie || msie >= 9) {

Expand Down Expand Up @@ -1360,7 +1332,8 @@ describe('$location', function() {

var event = {
target: jqLite(window.document.body).find('a')[0],
preventDefault: jasmine.createSpy('preventDefault')
preventDefault: jasmine.createSpy('preventDefault'),
isDefaultPrevented: jasmine.createSpy().andReturn(false)
};


Expand Down Expand Up @@ -1390,7 +1363,8 @@ describe('$location', function() {

var event = {
target: jqLite(window.document.body).find('a')[0],
preventDefault: jasmine.createSpy('preventDefault')
preventDefault: jasmine.createSpy('preventDefault'),
isDefaultPrevented: jasmine.createSpy().andReturn(false)
};


Expand Down Expand Up @@ -1555,8 +1529,12 @@ describe('$location', function() {


it('should listen on click events on href and prevent browser default in html5 mode', function() {
module(function($locationProvider) {
module(function($locationProvider, $provide) {
$locationProvider.html5Mode(true);
$provide.decorator('$browser', function($delegate) {
$delegate.$$baseHref = '/';
return $delegate;
});
return function($rootElement, $compile, $rootScope) {
$rootElement.html('<a href="http://server/somePath">link</a>');
$compile($rootElement)($rootScope);
Expand Down Expand Up @@ -1613,6 +1591,13 @@ describe('$location', function() {
);
});

function parseLinkAndReturn(location, url, relHref) {
if (location.$$parseLinkUrl(url, relHref)) {
return location.absUrl();
}
return undefined;
}

describe('LocationHtml5Url', function() {
var location, locationIndex;

Expand All @@ -1622,28 +1607,36 @@ describe('$location', function() {
});

it('should rewrite URL', function() {
expect(location.$$rewrite('http://other')).toEqual(undefined);
expect(location.$$rewrite('http://server/pre')).toEqual('http://server/pre/');
expect(location.$$rewrite('http://server/pre/')).toEqual('http://server/pre/');
expect(location.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/otherPath');
expect(locationIndex.$$rewrite('http://server/pre')).toEqual('http://server/pre/');
expect(locationIndex.$$rewrite('http://server/pre/')).toEqual('http://server/pre/');
expect(locationIndex.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/otherPath');
expect(parseLinkAndReturn(location, 'http://other')).toEqual(undefined);
expect(parseLinkAndReturn(location, 'http://server/pre')).toEqual('http://server/pre/');
expect(parseLinkAndReturn(location, 'http://server/pre/')).toEqual('http://server/pre/');
expect(parseLinkAndReturn(location, 'http://server/pre/otherPath')).toEqual('http://server/pre/otherPath');

expect(parseLinkAndReturn(locationIndex, 'http://server/pre')).toEqual('http://server/pre/');
expect(parseLinkAndReturn(locationIndex, 'http://server/pre/')).toEqual('http://server/pre/');
expect(parseLinkAndReturn(locationIndex, 'http://server/pre/otherPath')).toEqual('http://server/pre/otherPath');
});
});


describe('LocationHashbangUrl', function() {
var location;

function parseLinkAndReturn(location, url, relHref) {
if (location.$$parseLinkUrl(url, relHref)) {
return location.absUrl();
}
return undefined;
}

it('should rewrite URL', function() {
/* jshint scripturl: true */
location = new LocationHashbangUrl('http://server/pre/', '#');

expect(location.$$rewrite('http://other')).toEqual(undefined);
expect(location.$$rewrite('http://server/pre/')).toEqual('http://server/pre/');
expect(location.$$rewrite('http://server/pre/#otherPath')).toEqual('http://server/pre/#otherPath');
expect(location.$$rewrite('javascript:void(0)')).toEqual(undefined);
expect(parseLinkAndReturn(location, 'http://other')).toEqual(undefined);
expect(parseLinkAndReturn(location, 'http://server/pre/')).toEqual('http://server/pre/');
expect(parseLinkAndReturn(location, 'http://server/pre/#otherPath')).toEqual('http://server/pre/#/otherPath');
expect(parseLinkAndReturn(location, 'javascript:void(0)')).toEqual(undefined);
});

it("should not set hash if one was not originally specified", function() {
Expand Down Expand Up @@ -1693,13 +1686,14 @@ describe('$location', function() {
});

it('should rewrite URL', function() {
expect(location.$$rewrite('http://other')).toEqual(undefined);
expect(location.$$rewrite('http://server/pre')).toEqual('http://server/pre/');
expect(location.$$rewrite('http://server/pre/')).toEqual('http://server/pre/');
expect(location.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/#!otherPath');
expect(locationIndex.$$rewrite('http://server/pre')).toEqual('http://server/pre/');
expect(locationIndex.$$rewrite('http://server/pre/')).toEqual(undefined);
expect(locationIndex.$$rewrite('http://server/pre/otherPath')).toEqual('http://server/pre/index.html#!otherPath');
expect(parseLinkAndReturn(location, 'http://other')).toEqual(undefined);
expect(parseLinkAndReturn(location, 'http://server/pre')).toEqual('http://server/pre/#!');
expect(parseLinkAndReturn(location, 'http://server/pre/')).toEqual('http://server/pre/#!');
expect(parseLinkAndReturn(location, 'http://server/pre/otherPath')).toEqual('http://server/pre/#!/otherPath');

expect(parseLinkAndReturn(locationIndex, 'http://server/pre')).toEqual('http://server/pre/index.html#!');
expect(parseLinkAndReturn(locationIndex, 'http://server/pre/')).toEqual(undefined);
expect(parseLinkAndReturn(locationIndex, 'http://server/pre/otherPath')).toEqual('http://server/pre/index.html#!/otherPath');
});
});
});

0 comments on commit 1ee9b4e

Please sign in to comment.