Skip to content

Commit de78879

Browse files
committed
fix($location): update internal url with browser url if changed outside of Angular
$browser keeps an internal representation of the browser's url, and although $browser.url() would return the client's real current url, the $location service that was comparing that url with $location's representation had no way of knowing when the url had been changed outside of Angular. This commit makes browser and location a little bit smarter by setting a flag inside of $browser when it detects that a url changed outside of Angular, allowing $location to parse the new url and update its own internal representation of the url (and react appropriately). Fixes angular#6976
1 parent 3625803 commit de78879

File tree

3 files changed

+74
-3
lines changed

3 files changed

+74
-3
lines changed

src/ng/browser.js

+36-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ function Browser(window, document, $log, $sniffer) {
2929
history = window.history,
3030
setTimeout = window.setTimeout,
3131
clearTimeout = window.clearTimeout,
32-
pendingDeferIds = {};
32+
pendingDeferIds = {},
33+
urlChangedOutsideAngular = false;
3334

3435
self.isMock = false;
3536

@@ -146,6 +147,7 @@ function Browser(window, document, $log, $sniffer) {
146147
* @param {boolean=} replace Should new url replace current history record ?
147148
*/
148149
self.url = function(url, replace) {
150+
var currentHref;
149151
// Android Browser BFCache causes location, history reference to become stale.
150152
if (location !== window.location) location = window.location;
151153
if (history !== window.history) history = window.history;
@@ -175,7 +177,38 @@ function Browser(window, document, $log, $sniffer) {
175177
// - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
176178
// methods not updating location.href synchronously.
177179
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
178-
return newLocation || location.href.replace(/%27/g,"'");
180+
if (newLocation) {
181+
return newLocation;
182+
}
183+
if (lastBrowserUrl !== (currentHref = location.href.replace(/%27/g,"'"))) {
184+
urlChangedOutsideAngular = true;
185+
}
186+
return currentHref;
187+
}
188+
};
189+
190+
/**
191+
* @name $browser#urlChangedOutsideAngular
192+
*
193+
* @description
194+
* GETTER:
195+
* Without any argument, this method just returns current value of urlChangedOutsideAngular.
196+
*
197+
* SETTER:
198+
* With at least one argument, this method sets urlChangedOutsideAngular to new value.
199+
*
200+
* NOTE: this api is intended for use only by the $location service inside of $locationWatch.
201+
*
202+
* @param {boolean} val New value to set as urlChangedOutsideAngular,
203+
* typically used to reset value to false.
204+
*/
205+
self.urlChangedOutsideAngular = function(val) {
206+
if (isDefined(val)) {
207+
urlChangedOutsideAngular = val;
208+
return self;
209+
}
210+
else {
211+
return urlChangedOutsideAngular;
179212
}
180213
};
181214

@@ -184,6 +217,7 @@ function Browser(window, document, $log, $sniffer) {
184217

185218
function fireUrlChange() {
186219
newLocation = null;
220+
urlChangedOutsideAngular = false;
187221
if (lastBrowserUrl == self.url()) return;
188222

189223
lastBrowserUrl = self.url();

src/ng/location.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,21 @@ function $LocationProvider(){
738738
if (!changeCounter || oldUrl != $location.absUrl()) {
739739
changeCounter++;
740740
$rootScope.$evalAsync(function() {
741-
if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
741+
/**
742+
* $browser should detect if url was changed outside of Angular, in which
743+
* case $location should automatically update to the new url.
744+
*
745+
* NOTE: Detecting outside changes to location happens automatically in $browser via
746+
* window events (or polling if events aren't supported), but those methods are
747+
* async. For this reason, $browser.url() will perform a comparison with each call
748+
* to the method as a getter.
749+
* NOTE: Method does not exist on mock $browser
750+
*/
751+
if ($browser.urlChangedOutsideAngular && $browser.urlChangedOutsideAngular()) {
752+
$location.$$parse(oldUrl);
753+
$browser.urlChangedOutsideAngular(false);
754+
}
755+
else if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
742756
defaultPrevented) {
743757
$location.$$parse(oldUrl);
744758
} else {

test/ng/locationSpec.js

+23
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,29 @@ describe('$location', function() {
769769
});
770770

771771

772+
it('should update location when location changed outside of Angular', function() {
773+
module(function($windowProvider, $locationProvider, $browserProvider) {
774+
$locationProvider.html5Mode(true);
775+
$browserProvider.$get = function($document, $window, $log, $sniffer) {
776+
var b = new Browser($window, $document, $log, $sniffer);
777+
b.pollFns = [];
778+
return b;
779+
};
780+
});
781+
782+
inject(function($rootScope, $browser, $location, $sniffer){
783+
if ($sniffer.history) {
784+
window.history.replaceState(null, '', '/hello');
785+
// Verify that infinite digest reported in #6976 no longer occurs
786+
expect(function() {
787+
$rootScope.$digest();
788+
}).not.toThrow();
789+
expect($location.path()).toBe('/hello');
790+
}
791+
});
792+
});
793+
794+
772795
it('should rewrite when hashbang url given', function() {
773796
initService(true, '!', true);
774797
inject(

0 commit comments

Comments
 (0)