From b9c328f475573cc911a6b9e347366803d98bad96 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 4 Jan 2024 10:19:54 +0100 Subject: [PATCH] feat(core polyfills): Add polyfill for navigation API. This polyfill adds support for the "navigate" event on the navigation object. We path "history.pushState" and "history.replaceState" to send the "navigate" event on the "window.navigation" object when the URL changes. This polyfill is for current Firefox and Safari. Chrome based browsers already support this. More information on: https://developer.mozilla.org/en-US/docs/Web/API/Navigation/navigate_event --- src/core/polyfills.js | 41 ++++++++++++++++++++++++++++++++++++++ src/core/polyfills.test.js | 34 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 src/core/polyfills.test.js diff --git a/src/core/polyfills.js b/src/core/polyfills.js index 2ef0afce6..3209cf7ae 100644 --- a/src/core/polyfills.js +++ b/src/core/polyfills.js @@ -38,3 +38,44 @@ true ); })(); + +// Navigation polyfill for Firefox and Safari, as of 2024-01-04 +// NOTE: this is a very basic polyfill, it only supports firing a `navigate` +// event on location change and even that without interception support, etc. +!(function () { + if (window.navigation == undefined) { + + class NavigateEvent extends CustomEvent { + constructor() { + super("navigate"); + this.destination = { url: undefined }; + } + } + + // Create a navigation object on the window + // We create a DOM element for the navigation object so that we can + // attach events on it. + window.navigation = document.createElement("div"); + + const create_event = (args) => { + const event = new NavigateEvent(); + event.destination.url = args[2]; + return event; + }; + + // Patch pushState to trigger an `navigate` event on the navigation + // object when the URL changes. + const pushState = window.history.pushState; + window.history.pushState = function () { + pushState.apply(window.history, arguments); + window.navigation.dispatchEvent(create_event(arguments)); + }; + + // Same with replaceState + const replaceState = window.history.replaceState; + window.history.replaceState = function () { + replaceState.apply(window.history, arguments); + window.navigation.dispatchEvent(create_event(arguments)); + }; + } +})(); diff --git a/src/core/polyfills.test.js b/src/core/polyfills.test.js new file mode 100644 index 000000000..9c9291aba --- /dev/null +++ b/src/core/polyfills.test.js @@ -0,0 +1,34 @@ +import "./polyfills"; + +describe("NavigateEvent tests", () => { + afterEach(() => { + document.body.innerHTML = ""; + }); + + it("should fire an event when history.pushState is called.", () => { + let destination_url; + + window.navigation.addEventListener("navigate", (event) => { + destination_url = event.destination.url; + }); + + const path = "foo/bar/baz.html"; + history.pushState(null, "", path); + + expect(destination_url).toBe(path); + }); + + + it("should fire an event when history.replaceState is called.", () => { + let destination_url; + + window.navigation.addEventListener("navigate", (event) => { + destination_url = event.destination.url; + }); + + const path = "foo/bar/baz.html"; + history.replaceState(null, "", path); + + expect(destination_url).toBe(path); + }); +});