← All issues

[NavigationScheduler] history.back/forward/go(n) calls don't coalesce per spec when queued synchronously

04c8ceb

NavigationScheduler는 WebCore에서 frame당 하나씩 존재하는 pending navigation 슬롯으로, m_redirectScheduledNavigation 하나를 보관하다가 다음 task 경계에서 실행합니다. HTML 스펙은 history 이동을 top-level traversable 기준의 단일 session-history-traversal 큐에서 처리되는 task로 정의하며, 동기적으로 queue된 history.back/forward/go 호출은 어느 frame에서 발생하든 실제 navigation 전에 하나의 net delta로 합쳐져야 합니다. 그러나 WebKit에는 이런 큐가 존재하지 않았습니다. scheduleHistoryNavigation()이 호출될 때마다 schedule()이 실행되었고, 내부에서 cancel()로 기존 항목을 제거한 뒤 m_redirect를 교체하는 방식이었습니다. 두 번째 호출이 첫 번째 호출을 조용히 덮어쓰는 구조였습니다.

Source/WebCore/loader/NavigationScheduler.cpp

ScheduledNavigation::AccumulateResult ScheduledHistoryNavigation::accumulateHistorySteps(int additionalSteps)
{
if (!additionalSteps) // go(0)은 reload이며, delta가 아닙니다
return AccumulateResult::NotHandled;
m_steps += additionalSteps;
m_historyItem = nullptr; // fire() 시점에 target을 재산출합니다
return AccumulateResult::Handled;
}
 
void NavigationScheduler::scheduleHistoryNavigation(Frame& originatingFrame, int steps)
{
if (auto* topLocalFrame = topLevelLocalFrame(); topLocalFrame != &m_frame) {
originatingFrame.loader().completed();
cancel();
topLocalFrame->navigationScheduler().scheduleHistoryNavigation(originatingFrame, steps);
return;
}
if (m_redirect) {
auto result = m_redirect->accumulateHistorySteps(steps);
if (result == ScheduledNavigation::AccumulateResult::Handled) {
if (!m_redirect->steps()) // net delta == 0: 이동 없음
cancel();
return;
}
}
schedule(makeUnique<ScheduledHistoryNavigation>(steps));
}

이 fix는 accumulateHistorySteps virtual hook을 추가했습니다. 기존처럼 ScheduledHistoryNavigation을 교체하는 대신, 이미 pending 상태인 항목에 step 수를 누적하는 방식으로 변경된 셈입니다. 또한 subframe의 history 이동은 top-level LocalFrame의 scheduler로 전달되어, iframe과 main frame의 호출이 올바른 scope에서 하나로 합쳐지게 됩니다. 이제 back();forward()는 net delta가 0이 되므로 spurious navigation 없이 cancel()로 처리됩니다. back();back()-2로 누적되어 중간 항목을 건너뛰고 한 번만 실행되며, go(0)은 reload 의미론을 보존하기 위해 명시적으로 제외되었습니다.

이 변경은 WebKit이 cross-frame history navigation을 처리하고 합치는 방식 자체를 재구성합니다. 해당 경로는 어떤 document가 로드되는지, load event가 언제 발생하는지, back-forward list가 어떻게 진행되는지를 결정하는 privileged path입니다. iframe scheduler에서 top frame으로의 새로운 전달 경로, 그리고 same-document 이동을 둘러싼 step 누적 경계 조건은 security에 민감한 control plane에 도입된 새로운 로직입니다.

🔒

New cross-frame forwarding and step-accumulation paths introduce several boundary conditions across the same-document/cross-document divide — audit directions included.

더 확인하려면 구독해 주세요