一些有趣的问题(2)——Chromium是如何处理history.pushState的?
采用Blink Master的代码进行分析。history.pushState的处理代码在blink\Source\core\frame\history.h中。
本地代码调试,调用到pushState时的调用栈如下:
webcore_shared.dll!blink::History::pushState(WTF::PassRefPtr<blink::SerializedScriptValue> data, const WTF::String & title, const WTF::String & url, const blink::StateOptions & options, blink::ExceptionState & exceptionState) Line 63 C++
webcore_shared.dll!blink::HistoryV8Internal::pushStateMethod(const v8::FunctionCallbackInfo<v8::Value> & info) Line 188 C++
webcore_shared.dll!blink::HistoryV8Internal::pushStateMethodCallback(const v8::FunctionCallbackInfo<v8::Value> & info) Line 198 C++
v8.dll!v8::internal::FunctionCallbackArguments::Call(void (const v8::FunctionCallbackInfo<v8::Value> &) * f) Line 34 C++
v8.dll!v8::internal::HandleApiCallHelper<0>(v8::internal::Isolate * isolate, v8::internal::`anonymous-namespace'::BuiltinArguments<1> & args) Line 1094 C++
v8.dll!v8::internal::Builtin_Impl_HandleApiCall(v8::internal::`anonymous-namespace'::BuiltinArguments<1> args, v8::internal::Isolate * isolate) Line 1116 C++
v8.dll!v8::internal::Builtin_HandleApiCall(int args_length, v8::internal::Object * * args_object, v8::internal::Isolate * isolate) Line 1111 C++
38f0bd3c() Unknown
[Frames below may be incorrect and/or missing]
38f3fb68() Unknown
38f2d541() Unknown
38f170df() Unknown
v8.dll!v8::internal::Invoke(bool is_construct, v8::internal::Handle<v8::internal::JSFunction> function, v8::internal::Handle<v8::internal::Object> receiver, int argc, v8::internal::Handle<v8::internal::Object> * args) Line 128 C++
v8.dll!v8::internal::Execution::Call(v8::internal::Isolate * isolate, v8::internal::Handle<v8::internal::Object> callable, v8::internal::Handle<v8::internal::Object> receiver, int argc, v8::internal::Handle<v8::internal::Object> * argv, bool convert_receiver) Line 179 C++
v8.dll!v8::Script::Run(v8::Local<v8::Context> context) Line 1671 C++
webcore_shared.dll!blink::V8ScriptRunner::runCompiledScript(v8::Isolate * isolate, v8::Local<v8::Script> script, blink::ExecutionContext * context) Line 391 C++
webcore_shared.dll!blink::ScriptController::executeScriptAndReturnValue(v8::Local<v8::Context> context, const blink::ScriptSourceCode & source, blink::AccessControlStatus accessControlStatus, double * compilationFinishTime) Line 186 C++
webcore_shared.dll!blink::ScriptController::evaluateScriptInMainWorld(const blink::ScriptSourceCode & sourceCode, blink::AccessControlStatus accessControlStatus, blink::ScriptController::ExecuteScriptPolicy policy, double * compilationFinishTime) Line 560 C++
webcore_shared.dll!blink::ScriptController::executeScriptInMainWorld(const blink::ScriptSourceCode & sourceCode, blink::AccessControlStatus accessControlStatus, double * compilationFinishTime) Line 533 C++
webcore_shared.dll!blink::ScriptLoader::executeScript(const blink::ScriptSourceCode & sourceCode, double * compilationFinishTime) Line 401 C++
webcore_shared.dll!blink::ScriptLoader::prepareScript(const WTF::TextPosition & scriptStartPosition, blink::ScriptLoader::LegacyTypeSupport supportLegacyTypes) Line 271 C++
webcore_shared.dll!blink::HTMLScriptRunner::runScript(blink::Element * script, const WTF::TextPosition & scriptStartPosition) Line 354 C++
webcore_shared.dll!blink::HTMLScriptRunner::execute(WTF::PassRefPtr<blink::Element> scriptElement, const WTF::TextPosition & scriptStartPosition) Line 216 C++
webcore_shared.dll!blink::HTMLDocumentParser::runScriptsForPausedTreeBuilder() Line 319 C++
webcore_shared.dll!blink::HTMLDocumentParser::processParsedChunkFromBackgroundParser(WTF::PassOwnPtr<blink::HTMLDocumentParser::ParsedChunk> popChunk) Line 503 C++
webcore_shared.dll!blink::HTMLDocumentParser::pumpPendingSpeculations() Line 563 C++
webcore_shared.dll!blink::HTMLDocumentParser::resumeParsingAfterYield() Line 308 C++
webcore_shared.dll!blink::HTMLParserScheduler::continueParsing() Line 166 C++
webcore_shared.dll!WTF::FunctionWrapper<void (__thiscall blink::HTMLParserScheduler::*)(void)>::operator()(blink::HTMLParserScheduler * c) Line 83 C++
webcore_shared.dll!WTF::PartBoundFunctionImpl<1,WTF::FunctionWrapper<void (__thiscall blink::HTMLParserScheduler::*)(void)>,void __cdecl(blink::HTMLParserScheduler *)>::operator()() Line 179 C++
blink_platform.dll!blink::CancellableTaskFactory::CancellableTask::run() Line 34 C++
scheduler.dll!scheduler::WebSchedulerImpl::runTask(scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> > task) Line 45 C++
scheduler.dll!base::internal::RunnableAdapter<void (__cdecl*)(scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> >)>::Run(scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> > <args_0>) Line 157 C++
scheduler.dll!base::internal::InvokeHelper<0,void,base::internal::RunnableAdapter<void (__cdecl*)(scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> >)>,base::internal::TypeList<scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> > > >::MakeItSo(base::internal::RunnableAdapter<void (__cdecl*)(scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> >)> runnable, scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> > <args_0>) Line 294 C++
scheduler.dll!base::internal::Invoker<base::IndexSequence<0>,base::internal::BindState<base::internal::RunnableAdapter<void (__cdecl*)(scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> >)>,void __cdecl(scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> >),base::internal::TypeList<base::internal::PassedWrapper<scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> > > > >,base::internal::TypeList<base::internal::UnwrapTraits<base::internal::PassedWrapper<scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> > > > >,base::internal::InvokeHelper<0,void,base::internal::RunnableAdapter<void (__cdecl*)(scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> >)>,base::internal::TypeList<scoped_ptr<blink::WebThread::Task,base::DefaultDeleter<blink::WebThread::Task> > > >,void __cdecl(void)>::Run(base::internal::BindStateBase * base) Line 346 C++
base.dll!base::Callback<void __cdecl(void)>::Run() Line 396 C++
base.dll!base::debug::TaskAnnotator::RunTask(const char * queue_function, const char * run_function, const base::PendingTask & pending_task) Line 64 C++
scheduler.dll!scheduler::TaskQueueManager::ProcessTaskFromWorkQueue(unsigned int queue_index, bool has_previous_task, base::PendingTask * previous_task) Line 738 C++
scheduler.dll!scheduler::TaskQueueManager::DoWork(bool posted_from_main_thread) Line 691 C++
scheduler.dll!base::internal::RunnableAdapter<void (__thiscall scheduler::TaskQueueManager::*)(bool)>::Run(scheduler::TaskQueueManager * object, const bool & <args_0>) Line 176 C++
scheduler.dll!base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__thiscall scheduler::TaskQueueManager::*)(bool)>,base::internal::TypeList<base::WeakPtr<scheduler::TaskQueueManager> const &,bool const &> >::MakeItSo(base::internal::RunnableAdapter<void (__thiscall scheduler::TaskQueueManager::*)(bool)> runnable, const base::WeakPtr<scheduler::TaskQueueManager> & weak_ptr, const bool & <args_0>) Line 304 C++
scheduler.dll!base::internal::Invoker<base::IndexSequence<0,1>,base::internal::BindState<base::internal::RunnableAdapter<void (__thiscall scheduler::TaskQueueManager::*)(bool)>,void __cdecl(scheduler::TaskQueueManager *,bool),base::internal::TypeList<base::WeakPtr<scheduler::TaskQueueManager>,bool> >,base::internal::TypeList<base::internal::UnwrapTraits<base::WeakPtr<scheduler::TaskQueueManager> >,base::internal::UnwrapTraits<bool> >,base::internal::InvokeHelper<1,void,base::internal::RunnableAdapter<void (__thiscall scheduler::TaskQueueManager::*)(bool)>,base::internal::TypeList<base::WeakPtr<scheduler::TaskQueueManager> const &,bool const &> >,void __cdecl(void)>::Run(base::internal::BindStateBase * base) Line 346 C++
base.dll!base::Callback<void __cdecl(void)>::Run() Line 396 C++
base.dll!base::debug::TaskAnnotator::RunTask(const char * queue_function, const char * run_function, const base::PendingTask & pending_task) Line 64 C++
base.dll!base::MessageLoop::RunTask(const base::PendingTask & pending_task) Line 481 C++
相关处理代码如下:
namespace blink {
....
class History final : public GarbageCollectedFinalized<History>, public ScriptWrappable, public DOMWindowProperty {
DEFINE_WRAPPERTYPEINFO();
WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(History);
public:
....
void pushState(PassRefPtr<SerializedScriptValue> data, const String& title, const String& url, ExceptionState& exceptionState)
{
stateObjectAdded(data, title, url, scrollRestorationInternal(), FrameLoadTypeStandard, exceptionState);
}
History::stateObjectAdded 代码如下:
void History::stateObjectAdded(PassRefPtr<SerializedScriptValue> data, const String& /* title */, const String& urlString, HistoryScrollRestorationType restorationType, FrameLoadType type, ExceptionState& exceptionState)
{
if (!m_frame || !m_frame->page() || !m_frame->loader().documentLoader())
return;
KURL fullURL = urlForState(urlString);
if (!fullURL.isValid() || !m_frame->document()->securityOrigin()->canRequest(fullURL)) {
// We can safely expose the URL to JavaScript, as a) no redirection takes place: JavaScript already had this URL, b) JavaScript can only access a same-origin History object.
exceptionState.throwSecurityError("A history state object with URL '" + fullURL.elidedString() + "' cannot be created in a document with origin '" + m_frame->document()->securityOrigin()->toString() + "'.");
return;
}
m_frame->loader().updateForSameDocumentNavigation(fullURL, SameDocumentNavigationHistoryApi, data, restorationType, type);
}
导航流程是:判断URL是否有效?——有效(Y)——重定向到新URL上。无效则抛出DOMError。m_frame->loader()将返回一个与当前页面关联的FrameLoader对象。
mutable FrameLoader m_loader;
inline FrameLoader& LocalFrame::loader() const
{
return m_loader;
}
FrameLoader::updateForSameDocumentNavigation代码如下:
void FrameLoader::updateForSameDocumentNavigation(const KURL& newURL, SameDocumentNavigationSource sameDocumentNavigationSource, PassRefPtr<SerializedScriptValue> data, HistoryScrollRestorationType scrollRestorationType, FrameLoadType type)
{
// Update the data source's request with the new URL to fake the URL change
m_frame->document()->setURL(newURL);
documentLoader()->setReplacesCurrentHistoryItem(type != FrameLoadTypeStandard);
documentLoader()->updateForSameDocumentNavigation(newURL, sameDocumentNavigationSource);
// Generate start and stop notifications only when loader is completed so that we
// don't fire them for fragment redirection that happens in window.onload handler.
// See https://bugs.webkit.org/show_bug.cgi?id=31838
if (m_frame->document()->loadEventFinished())
client()->didStartLoading(NavigationWithinSameDocument);
HistoryCommitType historyCommitType = loadTypeToCommitType(type);
if (!m_currentItem)
historyCommitType = HistoryInertCommit;
setHistoryItemStateForCommit(historyCommitType, sameDocumentNavigationSource == SameDocumentNavigationHistoryApi ? HistoryNavigationType::HistoryApi : HistoryNavigationType::Fragment);
if (sameDocumentNavigationSource == SameDocumentNavigationHistoryApi) {
m_currentItem->setStateObject(data);
m_currentItem->setScrollRestorationType(scrollRestorationType);
}
client()->dispatchDidNavigateWithinPage(m_currentItem.get(), historyCommitType);
client()->dispatchDidReceiveTitle(m_frame->document()->title());
if (m_frame->document()->loadEventFinished())
client()->didStopLoading();
}
FrameLoader::setHistoryItemStateForCommit中维护需要提交的HistoryItem,并放置在RefPtrWillBeMember<HistoryItem> m_currentItem;
中。
其中,DocumentLoader::updateForSameDocumentNavigation代码如下。
void DocumentLoader::updateForSameDocumentNavigation(const KURL& newURL, SameDocumentNavigationSource sameDocumentNavigationSource)
{
KURL oldURL = m_request.url();
m_originalRequest.setURL(newURL);
m_request.setURL(newURL);
if (sameDocumentNavigationSource == SameDocumentNavigationHistoryApi) {
m_request.setHTTPMethod("GET");
m_request.setHTTPBody(nullptr);
}
clearRedirectChain();
if (m_isClientRedirect)
appendRedirect(oldURL);
appendRedirect(newURL);
}
最终,添加History Entry处代码的调用栈如下:
> content.dll!content::RenderFrameImpl::didCommitProvisionalLoad(blink::WebLocalFrame * frame, const blink::WebHistoryItem & item, blink::WebHistoryCommitType commit_type) Line 2665 C++
content.dll!content::RenderFrameImpl::didNavigateWithinPage(blink::WebLocalFrame * frame, const blink::WebHistoryItem & item, blink::WebHistoryCommitType commit_type) Line 2950 C++
blink_web.dll!blink::FrameLoaderClientImpl::dispatchDidNavigateWithinPage(blink::HistoryItem * item, blink::HistoryCommitType commitType) Line 405 C++
webcore_shared.dll!blink::FrameLoader::updateForSameDocumentNavigation(const blink::KURL & newURL, blink::SameDocumentNavigationSource sameDocumentNavigationSource, WTF::PassRefPtr<blink::SerializedScriptValue> data, blink::HistoryScrollRestorationType scrollRestorationType, blink::FrameLoadType type) Line 651 C++
webcore_shared.dll!blink::History::stateObjectAdded(WTF::PassRefPtr<blink::SerializedScriptValue> data, const WTF::String & __formal, const WTF::String & urlString, const blink::StateOptions & options, blink::FrameLoadType type, blink::ExceptionState & exceptionState) Line 159 C++
webcore_shared.dll!blink::History::pushState(WTF::PassRefPtr<blink::SerializedScriptValue> data, const WTF::String & title, const WTF::String & url, const blink::StateOptions & options, blink::ExceptionState & exceptionState) Line 64
在didCommitProvisionalLoad中,我们可以看到render_view->UpdateSessionHistory(frame);的语句。在启动一个新导航前,Chrome会更新最后提交的Session History条目。这个操作是通过IPC消息来完成的。在一个重复循环的pushState操作中,这将在队列中产生大量的IPC消息,导致页面看起来产生了明显卡顿。
void RenderViewImpl::SendUpdateState(HistoryEntry* entry) {
if (!entry)
return;
// Don't send state updates for kSwappedOutURL.
if (entry->root().urlString() == WebString::fromUTF8(kSwappedOutURL))
return;
Send(new ViewHostMsg_UpdateState(
routing_id_, page_id_, HistoryEntryToPageState(entry)));
}
接着,Chrome使用render_view_->history_controller()->UpdateForCommit(this, item, commit_type, navigation_state->WasWithinSamePage());来提交历史项。Chrome通过CreateNewBackForwardItem(frame, item, navigation_within_page);来向历史列表添加一个后退项。
紧接着……
不要紧,继续分析,Chromium/Chrome中限制了history对象的数量为50。这是标准的做法。接下来的问题就和《1》一样了,内存占用飙升,完全是因为字符串自拼接导致的,这种长字符串最终会要求G级别的内存空间。
网页卡顿则是因为有大量IPC消息占用CPU(同时也有大量字符的拼接操作,也会占用CPU和内存)。在我的测试中,Chrome并没有崩溃。关闭Tab即可。所以这也是为什么Google不认为这是漏洞的原因。(确实也不是漏洞:))。
所以Chrome篇就这么结束了,有时间再看Safari。这块的处理估计只能看WebKit了。