Skip to content

Commit

Permalink
Track unique history location states
Browse files Browse the repository at this point in the history
This PR will allow the HistoryLocation API to keep track of unique
state IDs for each state that is pushed/replaced. This change should not
break backwards compatibility and can allow for other libraries to use
this information to set proper scroll position.

The path alone does not provide enough information. For example, if you
visit page A, scroll down, then click on a link to page B, then click on
a link back to page A. Your actual browser history stack is [A, B, A].
Each of those nodes in the history should have their own unique scroll
position. In order to record this position we need an ID that is unique
for each node in the history.

Moved from stateCounter to uuid

Set under Feature Flag
  • Loading branch information
bcardarella committed Jan 21, 2017
1 parent 2706cea commit 6ab3170
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 32 deletions.
3 changes: 2 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"ember-testing-resume-test": null,
"ember-factory-for": true,
"ember-no-double-extend": null,
"ember-routing-router-service": null
"ember-routing-router-service": null,
"ember-unique-location-history-state": null
}
}
27 changes: 26 additions & 1 deletion packages/ember-routing/lib/location/history_location.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
get,
set
set,
isFeatureEnabled
} from 'ember-metal';

import { Object as EmberObject } from 'ember-runtime';
Expand All @@ -13,6 +14,19 @@ import EmberLocation from './api';

let popstateFired = false;

let _uuid;

if (isFeatureEnabled('ember-unique-location-history-state')) {
_uuid = function _uuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r, v;
r = Math.random() * 16 | 0;
v = c === 'x' ? r : r & 3 | 8;
return v.toString(16);
});
}
}

/**
Ember.HistoryLocation implements the location API using the browser's
history.pushState API.
Expand Down Expand Up @@ -134,6 +148,10 @@ export default EmberObject.extend({
from getState may be null if an iframe has changed a window's
history.
The object returned will contain a `path` for the given state as well
as a unique state `id`. The state index will allow the app to distinguish
between two states with similar paths but should be unique from one another.
@private
@method getState
@return state {Object}
Expand All @@ -155,6 +173,9 @@ export default EmberObject.extend({
*/
pushState(path) {
let state = { path };
if (isFeatureEnabled('ember-unique-location-history-state')) {
state.uuid = _uuid();
}

get(this, 'history').pushState(state, null, path);

Expand All @@ -173,6 +194,10 @@ export default EmberObject.extend({
*/
replaceState(path) {
let state = { path };
if (isFeatureEnabled('ember-unique-location-history-state')) {
state.uuid = _uuid();
}

get(this, 'history').replaceState(state, null, path);

this._historyState = state;
Expand Down
112 changes: 82 additions & 30 deletions packages/ember-routing/tests/location/history_location_test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { set, run } from 'ember-metal';
import {
isFeatureEnabled,
set,
run
} from 'ember-metal';
import HistoryLocation from '../../location/history_location';

let FakeHistory, HistoryTestLocation, location;
Expand Down Expand Up @@ -110,48 +114,96 @@ QUnit.test('base URL is removed when retrieving the current pathname', function(
location.initState();
});

QUnit.test('base URL is preserved when moving around', function() {
expect(1);
if (isFeatureEnabled('ember-unique-location-history-state')) {
QUnit.test('base URL is preserved when moving around', function() {
expect(2);

HistoryTestLocation.reopen({
init() {
this._super(...arguments);
HistoryTestLocation.reopen({
init() {
this._super(...arguments);

set(this, 'location', mockBrowserLocation('/base/foo/bar'));
set(this, 'baseURL', '/base/');
}
set(this, 'location', mockBrowserLocation('/base/foo/bar'));
set(this, 'baseURL', '/base/');
}
});

createLocation();
location.initState();
location.setURL('/one/two');

equal(location._historyState.path, '/base/one/two');
ok(location._historyState.uuid);
});

createLocation();
location.initState();
location.setURL('/one/two');
QUnit.test('setURL continues to set even with a null state (iframes may set this)', function() {
expect(2);

equal(location._historyState.path, '/base/one/two');
});
createLocation();
location.initState();

QUnit.test('setURL continues to set even with a null state (iframes may set this)', function() {
expect(1);
FakeHistory.pushState(null);
location.setURL('/three/four');

createLocation();
location.initState();
equal(location._historyState.path, '/three/four');
ok(location._historyState.uuid);
});

FakeHistory.pushState(null);
location.setURL('/three/four');
QUnit.test('replaceURL continues to set even with a null state (iframes may set this)', function() {
expect(2);

equal(location._historyState.path, '/three/four');
});
createLocation();
location.initState();

QUnit.test('replaceURL continues to set even with a null state (iframes may set this)', function() {
expect(1);
FakeHistory.pushState(null);
location.replaceURL('/three/four');

createLocation();
location.initState();
equal(location._historyState.path, '/three/four');
ok(location._historyState.uuid);
});
} else {
QUnit.test('base URL is preserved when moving around', function() {
expect(1);

FakeHistory.pushState(null);
location.replaceURL('/three/four');
HistoryTestLocation.reopen({
init() {
this._super(...arguments);

equal(location._historyState.path, '/three/four');
});
set(this, 'location', mockBrowserLocation('/base/foo/bar'));
set(this, 'baseURL', '/base/');
}
});

createLocation();
location.initState();
location.setURL('/one/two');

equal(location._historyState.path, '/base/one/two');
});

QUnit.test('setURL continues to set even with a null state (iframes may set this)', function() {
expect(1);

createLocation();
location.initState();

FakeHistory.pushState(null);
location.setURL('/three/four');

equal(location._historyState.path, '/three/four');
});

QUnit.test('replaceURL continues to set even with a null state (iframes may set this)', function() {
expect(1);

createLocation();
location.initState();

FakeHistory.pushState(null);
location.replaceURL('/three/four');

equal(location._historyState.path, '/three/four');
});
}

QUnit.test('HistoryLocation.getURL() returns the current url, excluding both rootURL and baseURL', function() {
expect(1);
Expand Down

0 comments on commit 6ab3170

Please sign in to comment.