%PDF- %PDF-
| Direktori : /proc/self/root/usr/share/nodejs/jsdom/lib/jsdom/living/window/ |
| Current File : //proc/self/root/usr/share/nodejs/jsdom/lib/jsdom/living/window/SessionHistory.js |
"use strict";
const whatwgURL = require("whatwg-url");
const HashChangeEvent = require("../generated/HashChangeEvent.js");
const PopStateEvent = require("../generated/PopStateEvent.js");
const notImplemented = require("../../browser/not-implemented.js");
const idlUtils = require("../generated/utils.js");
const { fireAnEvent } = require("../helpers/events");
// https://html.spec.whatwg.org/#session-history
class SessionHistory {
constructor(initialEntry, window) {
this._window = window;
this._windowImpl = idlUtils.implForWrapper(window);
this._historyTraversalQueue = new Set();
this._entries = [initialEntry];
this._currentIndex = 0;
}
_queueHistoryTraversalTask(fn) {
const timeoutId = this._window.setTimeout(() => {
this._historyTraversalQueue.delete(timeoutId);
fn();
}, 0);
this._historyTraversalQueue.add(timeoutId);
}
clearHistoryTraversalTasks() {
for (const timeoutId of this._historyTraversalQueue) {
this._window.clearTimeout(timeoutId);
}
this._historyTraversalQueue.clear();
}
get length() {
return this._entries.length;
}
get currentEntry() {
return this._entries[this._currentIndex];
}
// https://html.spec.whatwg.org/#dom-history-pushstate
removeAllEntriesAfterCurrentEntry() {
this._entries.splice(this._currentIndex + 1, Infinity);
}
// https://html.spec.whatwg.org/#traverse-the-history-by-a-delta
traverseByDelta(delta) {
this._queueHistoryTraversalTask(() => {
const newIndex = this._currentIndex + delta;
if (newIndex < 0 || newIndex >= this.length) {
return;
}
const specifiedEntry = this._entries[newIndex];
// Not implemented: unload a document guard
// Not clear that this should be queued. html/browsers/history/the-history-interface/004.html can be fixed
// by removing the queue, but doing so breaks some tests in history.js that also pass in browsers.
this._queueHistoryTraversalTask(() => {
// If there is an ongoing attempt to navigate specified browsing context that has not yet matured,
// then cancel that attempt to navigate the browsing context.
// Doing this seems to break tests involving navigating via push/pop state and via fragments. I think this
// is because these navigations should already count as having "matured" because the document is not changing.
// this.clearHistoryTraversalTasks();
if (specifiedEntry.document !== this.currentEntry.document) {
// TODO: unload the active document with the recycle parameter set to false
notImplemented("Traversing history in a way that would change the window", this._window);
}
this.traverseHistory(specifiedEntry);
});
});
}
// https://html.spec.whatwg.org/#traverse-the-history
traverseHistory(specifiedEntry, flags = {}) {
if (!specifiedEntry.document) {
// If entry no longer holds a Document object, then navigate the browsing context to entry's URL
// to perform an entry update of entry, and abort these steps
notImplemented("Traversing the history to an entry that no longer holds a Document object", this._window);
}
// Not spec compliant, just minimal. Lots of missing steps.
const nonBlockingEvents = Boolean(flags.nonBlockingEvents);
const document = idlUtils.implForWrapper(this._window._document);
const { currentEntry } = this;
// If the current entry's title was not set by the pushState() or replaceState() methods, then set its title
// to the value returned by the document.title IDL attribute.
if (currentEntry.title === undefined) {
currentEntry.title = document.title;
}
if (specifiedEntry.document !== currentEntry.document) {
// If entry has a different Document object than the current entry, then...
notImplemented("Traversing the history to an entry with a different Document", this._window);
}
document._URL = specifiedEntry.url;
const hashChanged =
specifiedEntry.url.fragment !== currentEntry.url.fragment && specifiedEntry.document === currentEntry.document;
let oldURL, newURL;
if (hashChanged) {
oldURL = currentEntry.url;
newURL = specifiedEntry.url;
}
if (flags.replacement) {
// If the traversal was initiated with replacement enabled, remove the entry immediately before the
// specified entry in the session history.
this._entries.splice(this._entries.indexOf(specifiedEntry) - 1, 1);
}
this.updateCurrentEntry(specifiedEntry);
const state = specifiedEntry.stateObject; // TODO structured clone
// arguably it's a bit odd that the state and latestEntry do not belong to the SessionHistory
// but the spec gives them to "History" and "Document" respecively.
document._history._state = state;
const stateChanged = specifiedEntry.document._latestEntry !== specifiedEntry;
specifiedEntry.document._latestEntry = specifiedEntry;
const fireEvents = () => this._fireEvents(stateChanged, hashChanged, state, oldURL, newURL);
if (nonBlockingEvents) {
this._window.setTimeout(fireEvents, 0);
} else {
fireEvents();
}
}
_fireEvents(stateChanged, hashChanged, state, oldURL, newURL) {
if (stateChanged) {
fireAnEvent("popstate", this._windowImpl, PopStateEvent, { state });
}
if (hashChanged) {
fireAnEvent("hashchange", this._windowImpl, HashChangeEvent, {
oldURL: whatwgURL.serializeURL(oldURL),
newURL: whatwgURL.serializeURL(newURL)
});
}
}
addEntryAfterCurrentEntry(entry) {
this._entries.splice(this._currentIndex + 1, 0, entry);
}
updateCurrentEntry(entry) {
this._currentIndex = this._entries.indexOf(entry);
}
}
module.exports = SessionHistory;