diff options
| -rw-r--r-- | src/ng/location.js | 96 | ||||
| -rw-r--r-- | src/ngMock/angular-mocks.js | 2 | ||||
| -rw-r--r-- | test/ng/locationSpec.js | 62 | 
3 files changed, 126 insertions, 34 deletions
| diff --git a/src/ng/location.js b/src/ng/location.js index b5f10293..727a0c2b 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 1e229214..9fec9710 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 91f3688c..88747dff 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('<a href="http://server/test.html">link</a>'); + +        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('<a href="http://server/index.html#test">link</a>'); + +        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('<a href="http://server/#/somePath">link</a>'); @@ -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'); | 
