Bug 1890747 - Track Navigation history entries. r=dom-core,farre

Differential Revision: https://phabricator.services.mozilla.com/D219007
This commit is contained in:
Adam Vandolder
2025-02-13 03:11:20 +00:00
parent 739b849487
commit 23e9c9915a
30 changed files with 697 additions and 72 deletions

View File

@@ -5,20 +5,39 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/Navigation.h"
#include "mozilla/dom/NavigationBinding.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/Logging.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/FeaturePolicy.h"
#include "mozilla/dom/NavigationCurrentEntryChangeEvent.h"
#include "mozilla/dom/NavigationHistoryEntry.h"
#include "mozilla/dom/SessionHistoryEntry.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/NavigationCurrentEntryChangeEvent.h"
#include "mozilla/dom/NavigationHistoryEntry.h"
#include "mozilla/dom/SessionHistoryEntry.h"
#include "nsContentUtils.h"
#include "nsIXULRuntime.h"
#include "nsNetUtil.h"
mozilla::LazyLogModule gNavigationLog("Navigation");
namespace mozilla::dom {
NS_IMPL_CYCLE_COLLECTION_INHERITED(Navigation, DOMEventTargetHelper);
NS_IMPL_CYCLE_COLLECTION_INHERITED(Navigation, DOMEventTargetHelper, mEntries);
NS_IMPL_ADDREF_INHERITED(Navigation, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(Navigation, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Navigation)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
Navigation::Navigation(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) {
MOZ_ASSERT(aWindow);
}
JSObject* Navigation::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return Navigation_Binding::Wrap(aCx, this, aGivenProto);
@@ -30,4 +49,228 @@ bool Navigation::IsAPIEnabled(JSContext* /* unused */, JSObject* /* unused */) {
StaticPrefs::dom_navigation_webidl_enabled_DoNotUseDirectly();
}
void Navigation::Entries(
nsTArray<RefPtr<NavigationHistoryEntry>>& aResult) const {
aResult = mEntries.Clone();
}
already_AddRefed<NavigationHistoryEntry> Navigation::GetCurrentEntry() const {
if (HasEntriesAndEventsDisabled()) {
return nullptr;
}
if (!mCurrentEntryIndex) {
return nullptr;
}
MOZ_LOG(gNavigationLog, LogLevel::Debug,
("Current Entry: %d; Amount of Entries: %d", int(*mCurrentEntryIndex),
int(mEntries.Length())));
MOZ_ASSERT(*mCurrentEntryIndex < mEntries.Length());
RefPtr entry{mEntries[*mCurrentEntryIndex]};
return entry.forget();
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#dom-navigation-updatecurrententry
void Navigation::UpdateCurrentEntry(
JSContext* aCx, const NavigationUpdateCurrentEntryOptions& aOptions,
ErrorResult& aRv) {
RefPtr currentEntry(GetCurrentEntry());
if (!currentEntry) {
aRv.ThrowInvalidStateError(
"Can't call updateCurrentEntry without a valid entry.");
return;
}
JS::Rooted<JS::Value> state(aCx, aOptions.mState);
auto serializedState = MakeRefPtr<nsStructuredCloneContainer>();
nsresult rv = serializedState->InitFromJSVal(state, aCx);
if (NS_FAILED(rv)) {
aRv.ThrowDataCloneError(
"Failed to serialize value for updateCurrentEntry.");
return;
}
currentEntry->SetState(serializedState);
NavigationCurrentEntryChangeEventInit init;
init.mFrom = currentEntry;
// Leaving the navigation type unspecified means it will be initialized to
// null.
RefPtr event = NavigationCurrentEntryChangeEvent::Constructor(
this, u"currententrychange"_ns, init);
DispatchEvent(*event);
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#has-entries-and-events-disabled
bool Navigation::HasEntriesAndEventsDisabled() const {
Document* doc = mWindow->GetDoc();
return !doc->IsCurrentActiveDocument() ||
(NS_IsAboutBlank(doc->GetDocumentURI()) && doc->IsInitialDocument()) ||
doc->GetPrincipal()->GetIsNullPrincipal();
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#initialize-the-navigation-api-entries-for-a-new-document
void Navigation::InitializeHistoryEntries(
mozilla::Span<const SessionHistoryInfo> aNewSHInfos,
const SessionHistoryInfo* aInitialSHInfo) {
mEntries.Clear();
mCurrentEntryIndex.reset();
if (HasEntriesAndEventsDisabled()) {
return;
}
for (auto i = 0ul; i < aNewSHInfos.Length(); i++) {
mEntries.AppendElement(
MakeRefPtr<NavigationHistoryEntry>(mWindow, &aNewSHInfos[i], i));
if (aNewSHInfos[i].NavigationKey() == aInitialSHInfo->NavigationKey()) {
mCurrentEntryIndex = Some(i);
}
}
LogHistory();
nsID key = aInitialSHInfo->NavigationKey();
nsID id = aInitialSHInfo->NavigationId();
MOZ_LOG(
gNavigationLog, LogLevel::Debug,
("aInitialSHInfo: %s %s\n", key.ToString().get(), id.ToString().get()));
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#update-the-navigation-api-entries-for-a-same-document-navigation
void Navigation::UpdateEntriesForSameDocumentNavigation(
SessionHistoryInfo* aDestinationSHE, NavigationType aNavigationType) {
// Step 1.
if (HasEntriesAndEventsDisabled()) {
return;
}
MOZ_LOG(gNavigationLog, LogLevel::Debug,
("Updating entries for same-document navigation"));
// Steps 2-7.
RefPtr<NavigationHistoryEntry> oldCurrentEntry = GetCurrentEntry();
nsTArray<RefPtr<NavigationHistoryEntry>> disposedEntries;
switch (aNavigationType) {
case NavigationType::Traverse:
MOZ_LOG(gNavigationLog, LogLevel::Debug, ("Traverse navigation"));
mCurrentEntryIndex.reset();
for (auto i = 0ul; i < mEntries.Length(); i++) {
if (mEntries[i]->IsSameEntry(aDestinationSHE)) {
mCurrentEntryIndex = Some(i);
break;
}
}
MOZ_ASSERT(mCurrentEntryIndex);
break;
case NavigationType::Push:
MOZ_LOG(gNavigationLog, LogLevel::Debug, ("Push navigation"));
mCurrentEntryIndex =
Some(mCurrentEntryIndex ? *mCurrentEntryIndex + 1 : 0);
while (*mCurrentEntryIndex < mEntries.Length()) {
disposedEntries.AppendElement(mEntries.PopLastElement());
}
mEntries.AppendElement(MakeRefPtr<NavigationHistoryEntry>(
mWindow, aDestinationSHE, *mCurrentEntryIndex));
break;
case NavigationType::Replace:
MOZ_LOG(gNavigationLog, LogLevel::Debug, ("Replace navigation"));
disposedEntries.AppendElement(oldCurrentEntry);
aDestinationSHE->NavigationKey() = oldCurrentEntry->Key();
mEntries[*mCurrentEntryIndex] = MakeRefPtr<NavigationHistoryEntry>(
mWindow, aDestinationSHE, *mCurrentEntryIndex);
break;
case NavigationType::Reload:
break;
}
// TODO: Step 8.
// Steps 9-12.
{
nsAutoMicroTask mt;
AutoEntryScript aes(mWindow->AsGlobal(),
"UpdateEntriesForSameDocumentNavigation");
ScheduleEventsFromNavigation(aNavigationType, oldCurrentEntry,
std::move(disposedEntries));
}
}
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#update-the-navigation-api-entries-for-reactivation
void Navigation::UpdateForReactivation(SessionHistoryInfo* aReactivatedEntry) {
// NAV-TODO
}
void Navigation::ScheduleEventsFromNavigation(
NavigationType aType, const RefPtr<NavigationHistoryEntry>& aPreviousEntry,
nsTArray<RefPtr<NavigationHistoryEntry>>&& aDisposedEntries) {
nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
"mozilla::dom::Navigation::ScheduleEventsFromNavigation",
[self = RefPtr(this), previousEntry = RefPtr(aPreviousEntry),
disposedEntries = std::move(aDisposedEntries), aType]() {
if (previousEntry) {
NavigationCurrentEntryChangeEventInit init;
init.mFrom = previousEntry;
init.mNavigationType.SetValue(aType);
RefPtr event = NavigationCurrentEntryChangeEvent::Constructor(
self, u"currententrychange"_ns, init);
self->DispatchEvent(*event);
}
for (const auto& entry : disposedEntries) {
RefPtr<Event> event = NS_NewDOMEvent(entry, nullptr, nullptr);
event->InitEvent(u"dispose"_ns, false, false);
event->SetTrusted(true);
event->SetTarget(entry);
entry->DispatchEvent(*event);
}
}));
}
namespace {
void LogEntry(NavigationHistoryEntry* aEntry, uint64_t aIndex, uint64_t aTotal,
bool aIsCurrent) {
if (!aEntry) {
MOZ_LOG(gNavigationLog, LogLevel::Debug,
(" +- %d NHEntry null\n", int(aIndex)));
return;
}
nsString key, id;
aEntry->GetKey(key);
aEntry->GetId(id);
MOZ_LOG(gNavigationLog, LogLevel::Debug,
("%s+- %d NHEntry %p %s %s\n", aIsCurrent ? ">" : " ", int(aIndex),
aEntry, NS_ConvertUTF16toUTF8(key).get(),
NS_ConvertUTF16toUTF8(id).get()));
nsAutoString url;
aEntry->GetUrl(url);
MOZ_LOG(gNavigationLog, LogLevel::Debug,
(" URL = %s\n", NS_ConvertUTF16toUTF8(url).get()));
}
} // namespace
void Navigation::LogHistory() const {
if (!MOZ_LOG_TEST(gNavigationLog, LogLevel::Debug)) {
return;
}
MOZ_LOG(gNavigationLog, LogLevel::Debug,
("Navigation %p (current entry index: %d)\n", this,
mCurrentEntryIndex ? int(*mCurrentEntryIndex) : -1));
auto length = mEntries.Length();
for (uint64_t i = 0; i < length; i++) {
LogEntry(mEntries[i], i, length,
mCurrentEntryIndex && i == *mCurrentEntryIndex);
}
}
} // namespace mozilla::dom