メモ : History API
Reactを少しいじったときにRoutingでどうなっているだろうと気になって調べた。
React Routerは画面遷移(URLの書き換え)にHistory APIを利用している。
A <Router> that uses the HTML5 history API (pushState, replaceState and the popstate event) to keep your UI in sync with the URL.
historyというpackageでAPIをラップしているっぽい。
History APIはブラウザの履歴情報にアクセスするAPI。
pushStateやreplaceStateの第一引数のstateは、履歴スタックに状態をpushする際に合わせて保存しておきたいものを入れるっぽい。 MDNにはシリアライズできるものなら何でもOKと書いてあるので、ほんとになんでも良さそう。
一応HTMLの仕様も見てみる。 HistoryのInterface。
enum ScrollRestoration { "auto", "manual" }; [Exposed=Window] interface History { readonly attribute unsigned long length; attribute ScrollRestoration scrollRestoration; readonly attribute any state; undefined go(optional long delta = 0); undefined back(); undefined forward(); undefined pushState(any data, DOMString title, optional USVString? url = null); undefined replaceState(any data, DOMString title, optional USVString? url = null); };
折角なのでChromiumの実装を見てみる。
blinkの機能のハズなので、このコードがそうだろう。 WebIDLのinterfaceも同じディレクトリにあるし。
void History::pushState(v8::Isolate* isolate, const ScriptValue& data, const String& title, const String& url, ExceptionState& exception_state) { WebFrameLoadType load_type = WebFrameLoadType::kStandard; // Navigations in portal contexts do not create back/forward entries. if (DomWindow() && DomWindow()->GetFrame()->GetPage()->InsidePortal()) { DomWindow()->AddConsoleMessage( MakeGarbageCollected<ConsoleMessage>( mojom::ConsoleMessageSource::kJavaScript, mojom::ConsoleMessageLevel::kWarning, "Use of history.pushState in a portal context " "is treated as history.replaceState."), /* discard_duplicates */ true); load_type = WebFrameLoadType::kReplaceCurrentItem; } scoped_refptr<SerializedScriptValue> serialized_data = SerializedScriptValue::Serialize(isolate, data.V8Value(), SerializedScriptValue::SerializeOptions( SerializedScriptValue::kForStorage), exception_state); if (exception_state.HadException()) return; StateObjectAdded(std::move(serialized_data), title, url, ScrollRestorationInternal(), load_type, exception_state); }
第一引数のstateはほんとにシリアライズだけ行われているよう。
StateObjectAddedを読んでいくと、DocumentLoader::UpdateForSameDocumentNavigation
あたりで履歴スタックを保存しているっぽい。
pushした履歴スタックの復元はHistory::go
を見ればわかるかな。
void History::go(ScriptState* script_state, int delta, ExceptionState& exception_state) { if (!DomWindow()) { exception_state.ThrowSecurityError( "May not use a History object associated with a Document that is not " "fully active"); return; } DCHECK(IsMainThread()); auto* active_window = LocalDOMWindow::From(script_state); if (!active_window) return; if (!active_window->GetFrame() || !active_window->GetFrame()->CanNavigate(*DomWindow()->GetFrame()) || !active_window->GetFrame()->IsNavigationAllowed() || !DomWindow()->GetFrame()->IsNavigationAllowed()) { return; } if (!DomWindow()->GetFrame()->navigation_rate_limiter().CanProceed()) return; if (delta) { if (DomWindow()->GetFrame()->Client()->NavigateBackForward(delta)) { if (Page* page = DomWindow()->GetFrame()->GetPage()) page->HistoryNavigationVirtualTimePauser().PauseVirtualTime(); } } else { // We intentionally call reload() for the current frame if delta is zero. // Otherwise, navigation happens on the root frame. // This behavior is designed in the following spec. // https://html.spec.whatwg.org/C/#dom-history-go DomWindow()->GetFrame()->Reload(WebFrameLoadType::kReload); } }
細かいところは全然わからないが、移動したいページの位置(delta)?があればページを復元して、なければリロードしているっぽい。 復元がされたらpopstateイベントが発生するはずだけど、そのあたりは読めていない。。