Commit 8fb22b27 authored by Robert Knight's avatar Robert Knight

Implement "uriChanged" event in HTMLIntegration

Add an event that fires when the URI / URL of the current document, as
reported by the `uri` method, changes.
parent c5bfe0c9
...@@ -7,6 +7,7 @@ import { ...@@ -7,6 +7,7 @@ import {
guessMainContentArea, guessMainContentArea,
preserveScrollPosition, preserveScrollPosition,
} from './html-side-by-side'; } from './html-side-by-side';
import { NavigationObserver } from '../util/navigation-observer';
import { scrollElementIntoView } from '../util/scroll'; import { scrollElementIntoView } from '../util/scroll';
/** /**
...@@ -45,6 +46,7 @@ export class HTMLIntegration extends TinyEmitter { ...@@ -45,6 +46,7 @@ export class HTMLIntegration extends TinyEmitter {
this.describe = describe; this.describe = describe;
this._htmlMeta = new HTMLMetadata(); this._htmlMeta = new HTMLMetadata();
this._prevURI = this._htmlMeta.uri();
/** Whether to attempt to resize the document to fit alongside sidebar. */ /** Whether to attempt to resize the document to fit alongside sidebar. */
this._sideBySideEnabled = this.features.flagEnabled('html_side_by_side'); this._sideBySideEnabled = this.features.flagEnabled('html_side_by_side');
...@@ -58,6 +60,28 @@ export class HTMLIntegration extends TinyEmitter { ...@@ -58,6 +60,28 @@ export class HTMLIntegration extends TinyEmitter {
/** @type {SidebarLayout|null} */ /** @type {SidebarLayout|null} */
this._lastLayout = null; this._lastLayout = null;
// Watch for changes to `location.href`.
this._navObserver = new NavigationObserver(() => this._checkForURIChange());
// Watch for potential changes to location information in `<head>`, eg.
// `<link rel=canonical>`.
this._metaObserver = new MutationObserver(() => this._checkForURIChange());
this._metaObserver.observe(document.head, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: [
// Keys and values of <link> elements
'rel',
'href',
// Keys and values of <meta> elements
'name',
'content',
],
});
this._flagsChanged = () => { this._flagsChanged = () => {
const sideBySide = features.flagEnabled('html_side_by_side'); const sideBySide = features.flagEnabled('html_side_by_side');
if (sideBySide !== this._sideBySideEnabled) { if (sideBySide !== this._sideBySideEnabled) {
...@@ -73,11 +97,21 @@ export class HTMLIntegration extends TinyEmitter { ...@@ -73,11 +97,21 @@ export class HTMLIntegration extends TinyEmitter {
this.features.on('flagsChanged', this._flagsChanged); this.features.on('flagsChanged', this._flagsChanged);
} }
_checkForURIChange() {
const currentURI = this._htmlMeta.uri();
if (currentURI !== this._prevURI) {
this._prevURI = currentURI;
this.emit('uriChanged', currentURI);
}
}
canAnnotate() { canAnnotate() {
return true; return true;
} }
destroy() { destroy() {
this._navObserver.disconnect();
this._metaObserver.disconnect();
this.features.off('flagsChanged', this._flagsChanged); this.features.off('flagsChanged', this._flagsChanged);
} }
......
import { delay } from '../../../test-util/wait';
import { FeatureFlags } from '../../features'; import { FeatureFlags } from '../../features';
import { HTMLIntegration, $imports } from '../html'; import { HTMLIntegration, $imports } from '../html';
...@@ -8,6 +9,7 @@ describe('HTMLIntegration', () => { ...@@ -8,6 +9,7 @@ describe('HTMLIntegration', () => {
let fakeGuessMainContentArea; let fakeGuessMainContentArea;
let fakePreserveScrollPosition; let fakePreserveScrollPosition;
let fakeScrollElementIntoView; let fakeScrollElementIntoView;
let notifyNavigation;
beforeEach(() => { beforeEach(() => {
features = new FeatureFlags(); features = new FeatureFlags();
...@@ -22,6 +24,13 @@ describe('HTMLIntegration', () => { ...@@ -22,6 +24,13 @@ describe('HTMLIntegration', () => {
uri: sinon.stub().returns('https://example.com/'), uri: sinon.stub().returns('https://example.com/'),
}; };
class FakeNavigationObserver {
constructor(callback) {
notifyNavigation = callback;
this.disconnect = sinon.stub();
}
}
fakeScrollElementIntoView = sinon.stub().resolves(); fakeScrollElementIntoView = sinon.stub().resolves();
fakeGuessMainContentArea = sinon.stub().returns(null); fakeGuessMainContentArea = sinon.stub().returns(null);
...@@ -30,6 +39,9 @@ describe('HTMLIntegration', () => { ...@@ -30,6 +39,9 @@ describe('HTMLIntegration', () => {
const HTMLMetadata = sinon.stub().returns(fakeHTMLMetadata); const HTMLMetadata = sinon.stub().returns(fakeHTMLMetadata);
$imports.$mock({ $imports.$mock({
'../anchoring/html': fakeHTMLAnchoring, '../anchoring/html': fakeHTMLAnchoring,
'../util/navigation-observer': {
NavigationObserver: FakeNavigationObserver,
},
'../util/scroll': { '../util/scroll': {
scrollElementIntoView: fakeScrollElementIntoView, scrollElementIntoView: fakeScrollElementIntoView,
}, },
...@@ -354,4 +366,66 @@ describe('HTMLIntegration', () => { ...@@ -354,4 +366,66 @@ describe('HTMLIntegration', () => {
assert.deepEqual(await integration.uri(), 'https://example.com/'); assert.deepEqual(await integration.uri(), 'https://example.com/');
}); });
}); });
it('emits "uriChanged" event when URL changes after a navigation', () => {
const integration = createIntegration();
const onURIChanged = sinon.stub();
integration.on('uriChanged', onURIChanged);
fakeHTMLMetadata.uri.returns('https://example.com/new-url');
notifyNavigation();
assert.calledWith(onURIChanged, 'https://example.com/new-url');
});
it('emits "uriChanged" event when URL changes after a <head> change', async () => {
const linkEl = document.createElement('link');
linkEl.rel = 'dummy';
linkEl.href = 'https://example.com/dummy';
const integration = createIntegration();
const onURIChanged = sinon.stub();
integration.on('uriChanged', onURIChanged);
try {
document.head.append(linkEl);
fakeHTMLMetadata.uri.returns('https://example.com/new-url');
await delay(0); // Wait for MutationObserver
assert.calledWith(onURIChanged, 'https://example.com/new-url');
} finally {
linkEl.remove();
}
});
it('does not emit "uriChanged" if URL has not changed after a navigation', () => {
const integration = createIntegration();
const onURIChanged = sinon.stub();
integration.on('uriChanged', onURIChanged);
notifyNavigation();
assert.notCalled(onURIChanged);
});
it('does not emit "uriChanged" if URL has not changed after a <head> change', async () => {
const linkEl = document.createElement('link');
linkEl.rel = 'dummy';
linkEl.href = 'https://example.com/dummy';
const integration = createIntegration();
const onURIChanged = sinon.stub();
integration.on('uriChanged', onURIChanged);
try {
document.head.append(linkEl);
await delay(0); // Wait for MutationObserver
assert.notCalled(onURIChanged);
} finally {
linkEl.remove();
}
});
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment