diff --git a/src/ng/location.js b/src/ng/location.js
index b5f102930365..727a0c2beddd 100644
--- a/src/ng/location.js
+++ b/src/ng/location.js
@@ -23,6 +23,10 @@ function encodePath(path) {
return segments.join('/');
}
+function stripHash(url) {
+ return url.split('#')[0];
+}
+
function matchUrl(url, obj) {
var match = URL_MATCH.exec(url);
@@ -102,19 +106,19 @@ function convertToHashbangUrl(url, basePath, hashPrefix) {
* @param {string} url HTML5 url
* @param {string} pathPrefix
*/
-function LocationUrl(url, pathPrefix) {
+function LocationUrl(url, pathPrefix, appBaseUrl) {
pathPrefix = pathPrefix || '';
/**
* Parse given html5 (regular) url string into properties
- * @param {string} url HTML5 url
+ * @param {string} newAbsoluteUrl HTML5 url
* @private
*/
- this.$$parse = function(url) {
- var match = matchUrl(url, this);
+ this.$$parse = function(newAbsoluteUrl) {
+ var match = matchUrl(newAbsoluteUrl, this);
if (match.path.indexOf(pathPrefix) !== 0) {
- throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !');
+ throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !');
}
this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length));
@@ -137,6 +141,14 @@ function LocationUrl(url, pathPrefix) {
pathPrefix + this.$$url;
};
+
+ this.$$rewriteAppUrl = function(absoluteLinkUrl) {
+ if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
+ return absoluteLinkUrl;
+ }
+ }
+
+
this.$$parse(url);
}
@@ -149,7 +161,7 @@ function LocationUrl(url, pathPrefix) {
* @param {string} url Legacy url
* @param {string} hashPrefix Prefix for hash part (containing path and search)
*/
-function LocationHashbangUrl(url, hashPrefix) {
+function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
var basePath;
/**
@@ -192,6 +204,13 @@ function LocationHashbangUrl(url, hashPrefix) {
basePath + (this.$$url ? '#' + hashPrefix + this.$$url : '');
};
+ this.$$rewriteAppUrl = function(absoluteLinkUrl) {
+ if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
+ return absoluteLinkUrl;
+ }
+ }
+
+
this.$$parse(url);
}
@@ -380,6 +399,19 @@ LocationUrl.prototype = {
LocationHashbangUrl.prototype = inherit(LocationUrl.prototype);
+function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) {
+ LocationHashbangUrl.apply(this, arguments);
+
+
+ this.$$rewriteAppUrl = function(absoluteLinkUrl) {
+ if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
+ return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length);
+ }
+ }
+}
+
+LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype);
+
function locationGetter(property) {
return function() {
return this[property];
@@ -479,26 +511,33 @@ function $LocationProvider(){
basePath,
pathPrefix,
initUrl = $browser.url(),
- absUrlPrefix;
+ initUrlParts = matchUrl(initUrl),
+ appBaseUrl;
if (html5Mode) {
basePath = $browser.baseHref() || '/';
pathPrefix = pathPrefixFromBase(basePath);
+ appBaseUrl =
+ composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
+ pathPrefix + '/';
+
if ($sniffer.history) {
$location = new LocationUrl(
convertToHtml5Url(initUrl, basePath, hashPrefix),
- pathPrefix);
+ pathPrefix, appBaseUrl);
} else {
- $location = new LocationHashbangUrl(
+ $location = new LocationHashbangInHtml5Url(
convertToHashbangUrl(initUrl, basePath, hashPrefix),
- hashPrefix);
+ hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1));
}
- // link rewriting
- absUrlPrefix = composeProtocolHostPort(
- $location.protocol(), $location.host(), $location.port()) + pathPrefix;
} else {
- $location = new LocationHashbangUrl(initUrl, hashPrefix);
- absUrlPrefix = $location.absUrl().split('#')[0];
+ appBaseUrl =
+ composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
+ (initUrlParts.path || '') +
+ (initUrlParts.search ? ('?' + initUrlParts.search) : '') +
+ '#' + hashPrefix + '/';
+
+ $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl);
}
$rootElement.bind('click', function(event) {
@@ -510,27 +549,22 @@ function $LocationProvider(){
var elm = jqLite(event.target);
// traverse the DOM up to find first A tag
- while (elm.length && lowercase(elm[0].nodeName) !== 'a') {
+ while (lowercase(elm[0].nodeName) !== 'a') {
+ if (elm[0] === $rootElement[0]) return;
elm = elm.parent();
}
var absHref = elm.prop('href'),
- href;
-
- if (!absHref ||
- elm.attr('target') ||
- absHref.indexOf(absUrlPrefix) !== 0) { // link to different domain or base path
- return;
+ rewrittenUrl = $location.$$rewriteAppUrl(absHref);
+
+ if (absHref && !elm.attr('target') && rewrittenUrl) {
+ // update location manually
+ $location.$$parse(rewrittenUrl);
+ $rootScope.$apply();
+ event.preventDefault();
+ // hack to work around FF6 bug 684208 when scenario runner clicks on links
+ window.angular['ff-684208-preventDefault'] = true;
}
-
- // update location with href without the prefix
- href = absHref.substr(absUrlPrefix.length);
- if (href.indexOf('#' + hashPrefix) == 0) href = href.substr(hashPrefix.length + 1);
- $location.url(href);
- $rootScope.$apply();
- event.preventDefault();
- // hack to work around FF6 bug 684208 when scenario runner clicks on links
- window.angular['ff-684208-preventDefault'] = true;
});
diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js
index 1e229214ecbd..9fec9710de28 100644
--- a/src/ngMock/angular-mocks.js
+++ b/src/ngMock/angular-mocks.js
@@ -39,7 +39,7 @@ angular.mock.$Browser = function() {
var self = this;
this.isMock = true;
- self.$$url = "http://server";
+ self.$$url = "http://server/";
self.$$lastUrl = self.$$url; // used by url polling fn
self.pollFns = [];
diff --git a/test/ng/locationSpec.js b/test/ng/locationSpec.js
index 91f3688c57fc..88747dff1cb1 100644
--- a/test/ng/locationSpec.js
+++ b/test/ng/locationSpec.js
@@ -1029,6 +1029,64 @@ describe('$location', function() {
expect($browser.url()).toEqual(base + '#!/view2');
});
});
+
+
+ it('should not intercept link clicks outside the app base url space', function() {
+ var base, clickHandler;
+ module(function($provide) {
+ $provide.value('$rootElement', {
+ bind: function(event, handler) {
+ expect(event).toEqual('click');
+ clickHandler = handler;
+ }
+ });
+ return function($browser) {
+ $browser.url(base = 'http://server/');
+ }
+ });
+ inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) {
+ // make IE happy
+ jqLite(window.document.body).html('link');
+
+ var event = {
+ target: jqLite(window.document.body).find('a')[0],
+ preventDefault: jasmine.createSpy('preventDefault')
+ };
+
+
+ clickHandler(event);
+ expect(event.preventDefault).not.toHaveBeenCalled();
+ });
+ });
+
+
+ it('should not intercept hash link clicks outside the app base url space', function() {
+ var base, clickHandler;
+ module(function($provide) {
+ $provide.value('$rootElement', {
+ bind: function(event, handler) {
+ expect(event).toEqual('click');
+ clickHandler = handler;
+ }
+ });
+ return function($browser) {
+ $browser.url(base = 'http://server/');
+ }
+ });
+ inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) {
+ // make IE happy
+ jqLite(window.document.body).html('link');
+
+ var event = {
+ target: jqLite(window.document.body).find('a')[0],
+ preventDefault: jasmine.createSpy('preventDefault')
+ };
+
+
+ clickHandler(event);
+ expect(event.preventDefault).not.toHaveBeenCalled();
+ });
+ });
});
@@ -1111,7 +1169,7 @@ describe('$location', function() {
);
- it('should listen on click events on href and prevent browser default in hasbang mode', function() {
+ it('should listen on click events on href and prevent browser default in hashbang mode', function() {
module(function() {
return function($rootElement, $compile, $rootScope) {
$rootElement.html('link');
@@ -1162,7 +1220,7 @@ describe('$location', function() {
log += '$locationChangeStart';
});
$rootScope.$on('$locationChangeSuccess', function() {
- throw new Error('after cancalation in html5 mode');
+ throw new Error('after cancelation in html5 mode');
});
browserTrigger(link, 'click');