一些有趣的问题(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);来向历史列表添加一个后退项。

紧接着……
QQ图片20160429181600.jpg

不要紧,继续分析,Chromium/Chrome中限制了history对象的数量为50。这是标准的做法。接下来的问题就和《1》一样了,内存占用飙升,完全是因为字符串自拼接导致的,这种长字符串最终会要求G级别的内存空间。

f1.png

网页卡顿则是因为有大量IPC消息占用CPU(同时也有大量字符的拼接操作,也会占用CPU和内存)。在我的测试中,Chrome并没有崩溃。关闭Tab即可。所以这也是为什么Google不认为这是漏洞的原因。(确实也不是漏洞:))。

所以Chrome篇就这么结束了,有时间再看Safari。这块的处理估计只能看WebKit了。

标签:none

添加新评论

captcha
请输入验证码