[NavigationScheduler] history.back/forward/go(n) calls don't coalesce per spec when queued synchronously
NavigationScheduler는 WebCore에서 frame당 하나씩 존재하는 pending navigation 슬롯으로, m_redirect에 ScheduledNavigation 하나를 보관하다가 다음 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
이 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 의미론을 보존하기 위해 명시적으로 제외되었습니다.
Significance
이 변경은 WebKit이 cross-frame history navigation을 처리하고 합치는 방식 자체를 재구성합니다. 해당 경로는 어떤 document가 로드되는지, load event가 언제 발생하는지, back-forward list가 어떻게 진행되는지를 결정하는 privileged path입니다. iframe scheduler에서 top frame으로의 새로운 전달 경로, 그리고 same-document 이동을 둘러싼 step 누적 경계 조건은 security에 민감한 control plane에 도입된 새로운 로직입니다.
Audit directions
전달(forwarding) 전에 originating frame의 scheduler가 취소되고 loader가 완료 처리됩니다. cancel()이 부분적으로 초기화된 상태를 남길 가능성이 있는지 점검이 필요합니다. 아울러 accumulateHistorySteps에 전달된 originating frame pointer를 통해, top frame의 fire()가 해당 상태에 접근하는 상황이 가능한지도 확인해야 합니다.
Same-document 검사는 NotHandled를 반환하여 hash/pushState 이동이 schedule()/cancel()로 떨어집니다. 다만 이 검사는 forwarding 이후에도 호출 frame의 scope에서 실행됩니다. iframe의 cross-document 이동이 main frame 관점에서 same-document로 보이는 경우, 어떤 동작이 발생하는지 살펴볼 필요가 있습니다.
adjustPendingHistoryNavigationForNewBackForwardEntry 역시 이제 top frame으로 전달됩니다. scheduler가 이미 forwarding을 완료하여 m_redirect가 null인 상태에서, 해당 함수의 early-return 경로가 안전한지 점검해야 합니다.
마지막으로 이 commit은 cross-process top frame(Site Isolation) 케이스를 명시적으로 처리하지 않습니다. 공격자가 제어하는 cross-site iframe에서는 여전히 기존의 덮어쓰기 동작이 관찰됩니다. 반면 same-site iframe은 합산(coalescing) 동작을 보게 됩니다. 이 비대칭은 UIProcess 큐 후속 작업이 완료될 때까지 관찰 가능한 timing channel로 남습니다.