Skip to content

Commit

Permalink
[Navigation] Implement NavigationAPIMethodTracker
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=269930

Reviewed by Alex Christensen.

This creates a NavigationAPIMethodTracker struct per:
https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-api-method-tracker

As well as the maybeSetUpcomingNonTraversalTracker() method per:
https://html.spec.whatwg.org/multipage/nav-history-apis.html#maybe-set-the-upcoming-non-traverse-api-method-tracker

* Source/WebCore/page/Navigation.cpp:
(WebCore::Navigation::serializeState):
(WebCore::Navigation::maybeSetUpcomingNonTraversalTracker):
(WebCore::Navigation::reload):
(WebCore::Navigation::navigate):
* Source/WebCore/page/Navigation.h:
(WebCore::NavigationAPIMethodTracker::create):
(WebCore::NavigationAPIMethodTracker::NavigationAPIMethodTracker):

Canonical link: https://commits.webkit.org/275446@main
  • Loading branch information
TingPing committed Feb 28, 2024
1 parent 801286a commit 5592c3d
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 11 deletions.
58 changes: 47 additions & 11 deletions Source/WebCore/page/Navigation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,16 +127,49 @@ static Navigation::Result createErrorResult(Ref<DeferredPromise> committed, Ref<
return createErrorResult(committed, finished, Exception { exceptionCode, errorMessage });
}

ExceptionOr<RefPtr<SerializedScriptValue>> Navigation::serializeState(JSC::JSValue& state)
{
if (state.isUndefined())
return { nullptr };

Vector<RefPtr<MessagePort>> dummyPorts;
auto serializeResult = SerializedScriptValue::create(*protectedScriptExecutionContext()->globalObject(), state, { }, dummyPorts, SerializationForStorage::Yes);
if (serializeResult.hasException())
return serializeResult.releaseException();

return { serializeResult.releaseReturnValue().ptr() };
}

// https://html.spec.whatwg.org/multipage/nav-history-apis.html#maybe-set-the-upcoming-non-traverse-api-method-tracker
NavigationAPIMethodTracker Navigation::maybeSetUpcomingNonTraversalTracker(Ref<DeferredPromise>&& committed, Ref<DeferredPromise>&& finished, JSC::JSValue&& info, RefPtr<SerializedScriptValue>&& serializedState)
{
static uint64_t lastTrackerID;
auto apiMethodTracker = NavigationAPIMethodTracker(lastTrackerID++, WTFMove(committed), WTFMove(finished), WTFMove(info), WTFMove(serializedState));

// FIXME: apiMethodTracker.finishedPromise needs to be considered Handled

ASSERT(!m_upcomingNonTraverseMethodTracker);
if (!hasEntriesAndEventsDisabled())
m_upcomingNonTraverseMethodTracker = apiMethodTracker;

return apiMethodTracker;
}

// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-reload
Navigation::Result Navigation::reload(ReloadOptions&&, Ref<DeferredPromise>&& committed, Ref<DeferredPromise>&& finished)
Navigation::Result Navigation::reload(ReloadOptions&& options, Ref<DeferredPromise>&& committed, Ref<DeferredPromise>&& finished)
{
auto serializedState = serializeState(options.state);
if (serializedState.hasException())
return createErrorResult(committed, finished, serializedState.releaseException());

auto apiMethodTracker = maybeSetUpcomingNonTraversalTracker(WTFMove(committed), WTFMove(finished), WTFMove(options.info), serializedState.releaseReturnValue());

// FIXME: Only a stub to reload for testing.
window()->frame()->loader().reload();

// FIXME: keep track of promises to resolve later.
Ref entry = NavigationHistoryEntry::create(scriptExecutionContext(), { });
auto globalObject = committed->globalObject();
Navigation::Result result = { DOMPromise::create(*globalObject, *JSC::jsCast<JSC::JSPromise*>(committed->promise())), DOMPromise::create(*globalObject, *JSC::jsCast<JSC::JSPromise*>(finished->promise())) };
Navigation::Result result = { apiMethodTracker.committedPromise.ptr(), apiMethodTracker.finishedPromise.ptr() };
committed->resolve<IDLInterface<NavigationHistoryEntry>>(entry.get());
finished->resolve<IDLInterface<NavigationHistoryEntry>>(entry.get());
return result;
Expand All @@ -161,23 +194,26 @@ Navigation::Result Navigation::navigate(ScriptExecutionContext& scriptExecutionC
if (options.history == HistoryBehavior::Push && newURL.protocolIsJavaScript())
return createErrorResult(committed, finished, ExceptionCode::NotSupportedError, "A \"push\" navigation was explicitly requested, but only a \"replace\" navigation is possible when navigating to a javascript: URL."_s);

if (options.state) {
Vector<RefPtr<MessagePort>> dummyPorts;
auto serializeResult = SerializedScriptValue::create(*scriptExecutionContext.globalObject(), options.state, { }, dummyPorts, SerializationForStorage::Yes);
if (serializeResult.hasException())
return createErrorResult(committed, finished, serializeResult.releaseException());
}
auto serializedState = serializeState(options.state);
if (serializedState.hasException())
return createErrorResult(committed, finished, serializedState.releaseException());

if (!window()->frame() || !window()->frame()->document())
return createErrorResult(committed, finished, ExceptionCode::InvalidStateError, "Invalid state"_s);

auto apiMethodTracker = maybeSetUpcomingNonTraversalTracker(WTFMove(committed), WTFMove(finished), WTFMove(options.info), serializedState.releaseReturnValue());

// FIXME: This is not a proper Navigation API initiated traversal, just a simple load for now.
window()->frame()->loader().load(FrameLoadRequest(*window()->frame(), newURL));

if (m_upcomingNonTraverseMethodTracker && m_upcomingNonTraverseMethodTracker.value() == apiMethodTracker) {
// TODO: Once the frameloader properly promotes the upcoming tracker with the navigate event `m_upcomingNonTraverseMethodTracker` should be unset or this will throw.
m_upcomingNonTraverseMethodTracker = std::nullopt;
}

// FIXME: keep track of promises to resolve later.
Ref entry = NavigationHistoryEntry::create(&scriptExecutionContext, newURL);
auto globalObject = committed->globalObject();
Navigation::Result result = { DOMPromise::create(*globalObject, *JSC::jsCast<JSC::JSPromise*>(committed->promise())), DOMPromise::create(*globalObject, *JSC::jsCast<JSC::JSPromise*>(finished->promise())) };
Navigation::Result result = { apiMethodTracker.committedPromise.ptr(), apiMethodTracker.finishedPromise.ptr() };
committed->resolve<IDLInterface<NavigationHistoryEntry>>(entry.get());
finished->resolve<IDLInterface<NavigationHistoryEntry>>(entry.get());
return result;
Expand Down
38 changes: 38 additions & 0 deletions Source/WebCore/page/Navigation.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,42 @@
#include "NavigationHistoryEntry.h"
#include "NavigationTransition.h"
#include <JavaScriptCore/JSCJSValue.h>
#include <wtf/text/StringHash.h>

namespace WebCore {

class HistoryItem;
class SerializedScriptValue;

// https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-api-method-tracker
struct NavigationAPIMethodTracker {
WTF_MAKE_STRUCT_FAST_ALLOCATED;

NavigationAPIMethodTracker(uint64_t id, Ref<DeferredPromise>&& committed, Ref<DeferredPromise>&& finished, JSC::JSValue&& info, RefPtr<SerializedScriptValue>&& serializedState)
: info(info)
, serializedState(serializedState)
, committedPromise(DOMPromise::create(*committed->globalObject(), *JSC::jsCast<JSC::JSPromise*>(committed->promise())))
, finishedPromise(DOMPromise::create(*finished->globalObject(), *JSC::jsCast<JSC::JSPromise*>(finished->promise())))
, id(id)
{
};

bool operator==(const NavigationAPIMethodTracker& other) const
{
// key is optional so we manually identify each tracker.
return id == other.id;
};

String key;
JSC::JSValue info;
RefPtr<SerializedScriptValue> serializedState;
RefPtr<NavigationHistoryEntry> committedToEntry;
Ref<DOMPromise> committedPromise;
Ref<DOMPromise> finishedPromise;

private:
uint64_t id;
};

class Navigation final : public RefCounted<Navigation>, public EventTarget, public ContextDestructionObserver, public LocalDOMWindowProperty {
WTF_MAKE_ISO_ALLOCATED(Navigation);
Expand Down Expand Up @@ -104,10 +136,16 @@ class Navigation final : public RefCounted<Navigation>, public EventTarget, publ
bool hasEntriesAndEventsDisabled() const;
Result performTraversal(NavigationHistoryEntry&, Ref<DeferredPromise> committed, Ref<DeferredPromise> finished);
std::optional<Ref<NavigationHistoryEntry>> findEntryByKey(const String& key);
ExceptionOr<RefPtr<SerializedScriptValue>> serializeState(JSC::JSValue& state);
NavigationAPIMethodTracker maybeSetUpcomingNonTraversalTracker(Ref<DeferredPromise>&& committed, Ref<DeferredPromise>&& finished, JSC::JSValue&& info, RefPtr<SerializedScriptValue>&&);


std::optional<size_t> m_currentEntryIndex;
RefPtr<NavigationTransition> m_transition;
Vector<Ref<NavigationHistoryEntry>> m_entries;

std::optional<NavigationAPIMethodTracker> m_upcomingNonTraverseMethodTracker;
HashMap<String, NavigationAPIMethodTracker> m_upcomingTraverseMethodTrackers;
};

} // namespace WebCore

0 comments on commit 5592c3d

Please sign in to comment.