webkit在win32下的編譯規則(九)

webkit在win32下的編譯規則(七)中,曾經說到要專門介紹js binding這部分的,但後面逐漸忘記了,最近有網友提起,特加入js binding這部分的介紹。

首先來說是js engine,js engine是一個解釋器。解釋器比較直觀的就是命令行(cmd.exe),在命令行中,我們輸入一行腳本,然後cmd.exe就是解析這段腳本然後執行。同樣我們也可以將命令寫在bat文件中,然後讓cmd.exe執行。js engine與此類似,不同的是它執行js腳本。在命令行輸入腳本,相當於js engine裏面的執行標籤中內嵌的腳本,例如onClick=”javascript:window.alert(1);” ;執行bat腳本,相當於js engine裏面的解釋運行一個js文件。

爲了便於介紹,下面都已JavascriptCore這裏面的js engine(SquirrelFish or SquirrelFish Extreme)爲準,V8等引擎原理與此類似,只不過具體實現不一樣。

在JavascriptCore中,處於主線的函數是:

JSValue JSC::evaluate(ExecState* exec, ScopeChainNode* scopeChain, const SourceCode& source, JSValue thisValue, JSValue* returnedException)

這個函數完成javascript具體的解析和執行,如果不調用這個函數,js engine將什麼事也不會做。在webkit中,調用這個函數是webcore的JSMainThreadExecState::evaluate函數。當webkit下載完一個js文件,或者標籤中內嵌的js腳本被觸發時,JSMainThreadExecState::evaluate就會被調用,從而導致JSC::evaluate被調用。js engine是單線程的,一個頁面(或一個window對象)一般配一個js engine,如果一個頁面有多個frame,則會有多個js engine,這些js engine在不同的線程中運行。下面是對html進行parse時遇到js腳本後調用JSC::evaluate的堆棧:

     

WebKit_debug.dll!WebCore::JSMainThreadExecState::evaluate(JSC::ExecState * exec, JSC::ScopeChainNode * chain, const JSC::SourceCode & source, JSC::JSValue thisValue, JSC::JSValue * exception)  行 57    C++
>    WebKit_debug.dll!WebCore::ScriptController::evaluateInWorld(const WebCore::ScriptSourceCode & sourceCode, WebCore::DOMWrapperWorld * world)  行 144 + 0x33 字節    C++
     WebKit_debug.dll!WebCore::ScriptController::evaluate(const WebCore::ScriptSourceCode & sourceCode)  行 161 + 0x16 字節    C++
     WebKit_debug.dll!WebCore::ScriptElement::executeScript(const WebCore::ScriptSourceCode & sourceCode)  行 292 + 0x17 字節    C++
     WebKit_debug.dll!WebCore::HTMLScriptRunner::executePendingScriptAndDispatchEvent(WebCore::PendingScript & pendingScript)  行 140    C++
     WebKit_debug.dll!WebCore::HTMLScriptRunner::executeParsingBlockingScript()  行 119    C++
     WebKit_debug.dll!WebCore::HTMLScriptRunner::executeParsingBlockingScripts()  行 196    C++
     WebKit_debug.dll!WebCore::HTMLScriptRunner::executeScriptsWaitingForLoad(WebCore::CachedResource * cachedScript)  行 207    C++
     WebKit_debug.dll!WebCore::HTMLDocumentParser::notifyFinished(WebCore::CachedResource * cachedResource)  行 517 + 0x19 字節    C++
     WebKit_debug.dll!WebCore::CachedResource::checkNotify()  行 151 + 0x13 字節    C++
     WebKit_debug.dll!WebCore::CachedScript::data(WTF::PassRefPtr<WebCore::SharedBuffer> data, bool allDataReceived)  行 105    C++
     WebKit_debug.dll!WebCore::CachedResourceRequest::didFinishLoading(WebCore::SubresourceLoader * loader, double __formal)  行 170    C++
     WebKit_debug.dll!WebCore::SubresourceLoader::didFinishLoading(double finishTime)  行 196 + 0x28 字節    C++
     WebKit_debug.dll!WebCore::ResourceLoader::didFinishLoading(WebCore::ResourceHandle * __formal, double finishTime)  行 472 + 0x18 字節    C++
     WebKit_debug.dll!WebCore::ResourceHandleManager::downloadTimerCallback(WebCore::Timer<WebCore::ResourceHandleManager> * timer)  行 400 + 0x35 字節    C++
     WebKit_debug.dll!WebCore::Timer<WebCore::ResourceHandleManager>::fired()  行 100 + 0x23 字節    C++
     WebKit_debug.dll!WebCore::ThreadTimers::sharedTimerFiredInternal()  行 115 + 0xf 字節    C++
     WebKit_debug.dll!WebCore::ThreadTimers::sharedTimerFired()  行 94    C++
     WebKit_debug.dll!WebCore::TimerWindowWndProc(HWND__ * hWnd, unsigned int message, unsigned int wParam, long lParam)  行 103 + 0x8 字節    C++

JSC::evaluate有5個參數:ExecState* exec表示js engine的運行整體環境,例如全局變量的值;ScopeChainNode* scopeChain表示當前運行Scope Chain,這個和js的閉包(Closure)有關;const SourceCode& source表示js腳本的內容;JSValue thisValue表示這段腳本的this對象;JSValue* returnedException表示腳本執行後的異常信息。

我們可以看一下ScriptController::evaluateInWorld的代碼:

ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world)
{
    const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
    String sourceURL = ustringToString(jsSourceCode.provider()->url());
    // evaluate code. Returns the JS return value or 0
    // if there was none, an error occurred or the type couldn't be converted.
    // inlineCode is true for <a href="javascript:doSomething()">
    // and false for <script>doSomething()</script>. Check if it has the
    // expected value in all cases.
    // See smart window.open policy for where this is used.
    JSDOMWindowShell* shell = windowShell(world);
    ExecState* exec = shell->window()->globalExec();
    const String* savedSourceURL = m_sourceURL;
    m_sourceURL = &sourceURL;
    JSLock lock(SilenceAssertionsOnly);
    RefPtr<Frame> protect = m_frame;
    InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine());
    JSValue evaluationException;
    exec->globalData().timeoutChecker.start();
    JSValue returnValue = JSMainThreadExecState::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, shell, &evaluationException);
    exec->globalData().timeoutChecker.stop();
    InspectorInstrumentation::didEvaluateScript(cookie);
    if (evaluationException) {
        reportException(exec, evaluationException);
        m_sourceURL = savedSourceURL;
        return ScriptValue();
    }
    m_sourceURL = savedSourceURL;
    return ScriptValue(exec->globalData(), returnValue);
}


從中我們可以看到,JSMainThreadExecState::evaluate的exec,scopeChain,thisValue都和JSDOMWindowShell這個對象有關。從JSDOMWindowShell這個名字可以看出,webkit是將js腳本的執行比喻成在shell中輸入命令。thisValue爲JSDOMWindowShell則表明js腳本最頂層的this爲window對象,這也於DOM模型的window對象處於頂端一致了。


在上面的JSDOMWindowShell* shell = windowShell(world);執行過程中,會調用JSDOMWindowShell::setWindow,調用堆棧如下:

     WebKit_debug.dll!WebCore::JSDOMWindowShell::setWindow(WTF::PassRefPtr<WebCore::DOMWindow> domWindow)  行 75    C++
     WebKit_debug.dll!WebCore::JSDOMWindowShell::finishCreation(JSC::JSGlobalData & globalData, WTF::PassRefPtr<WebCore::DOMWindow> window)  行 57    C++
     WebKit_debug.dll!WebCore::JSDOMWindowShell::create(WTF::PassRefPtr<WebCore::DOMWindow> window, JSC::Structure * structure, WebCore::DOMWrapperWorld * world)  行 62    C++
     WebKit_debug.dll!WebCore::ScriptController::createWindowShell(WebCore::DOMWrapperWorld * world)  行 111 + 0x21 字節    C++
     WebKit_debug.dll!WebCore::ScriptController::initScript(WebCore::DOMWrapperWorld * world)  行 213 + 0xc 字節    C++
     WebKit_debug.dll!WebCore::ScriptController::windowShell(WebCore::DOMWrapperWorld * world)  行 79 + 0x43 字節    C++
>    WebKit_debug.dll!WebCore::ScriptController::evaluateInWorld(const WebCore::ScriptSourceCode & sourceCode, WebCore::DOMWrapperWorld * world)  行 130 + 0xc 字節    C++


JSDOMWindowShell::setWindow會調用JSDOMWindow::create創建真正的window對象:

JSDOMWindow* jsDOMWindow = JSDOMWindow::create(*JSDOMWindow::commonJSGlobalData(), structure, domWindow, this);


而後會調用兩個參數的setWindow將jsDOMWindow賦值給JSDOMWindowShell的成員變量m_window。

void setWindow(JSC::JSGlobalData& globalData, JSDOMWindow* window)
        {
            ASSERT_ARG(window, window);
            m_window.set(globalData, this, window);
            setPrototype(globalData, window->prototype());
        }


接下面我們看JSDOMWindowShell的定義,如下:

class JSDOMWindowShell : public JSC::JSNonFinalObject

JSDOMWindowShell繼承於JSC::JSNonFinalObject,JSC::JSNonFinalObject繼承於JSObject,總之JSDOMWindowShell是一個JSObject。JSObject有如下函數是可以被重載的(多態):

        virtual bool getOwnPropertySlot(JSC::ExecState*, const JSC::Identifier& propertyName, JSC::PropertySlot&);
        virtual bool getOwnPropertyDescriptor(JSC::ExecState*, const JSC::Identifier& propertyName, JSC::PropertyDescriptor&);
        virtual void put(JSC::ExecState*, const JSC::Identifier& propertyName, JSC::JSValue, JSC::PutPropertySlot&);
        virtual void putWithAttributes(JSC::ExecState*, const JSC::Identifier& propertyName, JSC::JSValue, unsigned attributes);
        virtual bool deleteProperty(JSC::ExecState*, const JSC::Identifier& propertyName);
        virtual void getPropertyNames(JSC::ExecState*, JSC::PropertyNameArray&, JSC::EnumerationMode mode = JSC::ExcludeDontEnumProperties);
        virtual void getOwnPropertyNames(JSC::ExecState*, JSC::PropertyNameArray&, JSC::EnumerationMode mode = JSC::ExcludeDontEnumProperties);
        virtual void defineGetter(JSC::ExecState*, const JSC::Identifier& propertyName, JSC::JSObject* getterFunction, unsigned attributes);
        virtual void defineSetter(JSC::ExecState*, const JSC::Identifier& propertyName, JSC::JSObject* setterFunction, unsigned attributes);
        virtual bool defineOwnProperty(JSC::ExecState*, const JSC::Identifier& propertyName, JSC::PropertyDescriptor&, bool shouldThrow);
        virtual JSC::JSValue lookupGetter(JSC::ExecState*, const JSC::Identifier& propertyName);
        virtual JSC::JSValue lookupSetter(JSC::ExecState*, const JSC::Identifier& propertyName);


JSDOMWindowShell重載了這些函數,所以當執行js的相關函數時就會調用這些函數,例如執行

window.onload= function() {}

js engine就會調用JSDOMWindowShell::put函數,JSDOMWindowShell::put接着會調用JSDOMWindow::put

void JSDOMWindowShell::put(ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot)
{
    m_window->put(exec, propertyName, value, slot);
}


JSDOMWindow::put會lookupPut在s_info查找onload對應的函數並執行:

void JSDOMWindow::put(ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot)
{
    if (!impl()->frame())
        return;
    // Optimization: access JavaScript global variables directly before involving the DOM.
    if (JSGlobalObject::hasOwnPropertyForWrite(exec, propertyName)) {
        if (allowsAccessFrom(exec))
            JSGlobalObject::put(exec, propertyName, value, slot);
        return;
    }
    if (lookupPut<JSDOMWindow>(exec, propertyName, value, s_info.propHashTable(exec), this))
        return;
    if (allowsAccessFrom(exec))
        Base::put(exec, propertyName, value, slot);
}


我們看一下JSDOMWindow::s_info定義:

const ClassInfo JSDOMWindow::s_info = { "DOMWindow", &JSDOMWindowBase::s_info, &JSDOMWindowTable, 0 };


其中s_info的parentClass爲JSDOMWindowBase::s_info,staticPropHashTable爲JSDOMWindowTable。JSDOMWindowTable的定義在D:\project\WebKit\WebKitBuild\Debug_Cairo_CFLite\obj\WebCore\DerivedSources\JSDOMWindow.cpp,JSDOMWindow.cpp是由DOMWindow.idl通過generate-bindings.pl生成的,這個在webkit在win32下的編譯規則(七)中已經解釋過了:

static JSC_CONST_HASHTABLE HashTable JSDOMWindowTable = { 1125, 1023, JSDOMWindowTableValues, 0 };


它是一個HashTable,其值在JSDOMWindowTableValues中。下面截取了JSDOMWindowTableValues的一部分:

static const HashTableValue JSDOMWindowTableValues[] =
{
    { "screen", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowScreen), (intptr_t)setJSDOMWindowScreen THUNK_GENERATOR(0) },
    { "history", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowHistory), (intptr_t)setJSDOMWindowHistory THUNK_GENERATOR(0) },
    { "locationbar", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowLocationbar), (intptr_t)setJSDOMWindowLocationbar THUNK_GENERATOR(0) },
    { "menubar", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowMenubar), (intptr_t)setJSDOMWindowMenubar THUNK_GENERATOR(0) },
    { "personalbar", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowPersonalbar), (intptr_t)setJSDOMWindowPersonalbar THUNK_GENERATOR(0) },
    { "scrollbars", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowScrollbars), (intptr_t)setJSDOMWindowScrollbars THUNK_GENERATOR(0) },
    { "statusbar", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowStatusbar), (intptr_t)setJSDOMWindowStatusbar THUNK_GENERATOR(0) },
    { "toolbar", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowToolbar), (intptr_t)setJSDOMWindowToolbar THUNK_GENERATOR(0) },
    { "navigator", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowNavigator), (intptr_t)setJSDOMWindowNavigator THUNK_GENERATOR(0) },
    { "clientInformation", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowClientInformation), (intptr_t)setJSDOMWindowClientInformation THUNK_GENERATOR(0) },
…………… 
    { "document", DontDelete | ReadOnly, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowDocument), (intptr_t)0 THUNK_GENERATOR(0) },
   …………… 
    { "console", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowConsole), (intptr_t)setJSDOMWindowConsole THUNK_GENERATOR(0) },
    { "onabort", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOnabort), (intptr_t)setJSDOMWindowOnabort THUNK_GENERATOR(0) },
    { "onbeforeunload", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOnbeforeunload), (intptr_t)setJSDOMWindowOnbeforeunload THUNK_GENERATOR(0) },
    { "onblur", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOnblur), (intptr_t)setJSDOMWindowOnblur THUNK_GENERATOR(0) },
    { "oncanplay", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOncanplay), (intptr_t)setJSDOMWindowOncanplay THUNK_GENERATOR(0) },
    { "oncanplaythrough", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOncanplaythrough), (intptr_t)setJSDOMWindowOncanplaythrough THUNK_GENERATOR(0) },
    { "onchange", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOnchange), (intptr_t)setJSDOMWindowOnchange THUNK_GENERATOR(0) },
    { "onclick", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOnclick), (intptr_t)setJSDOMWindowOnclick THUNK_GENERATOR(0) },
    { "oncontextmenu", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOncontextmenu), (intptr_t)setJSDOMWindowOncontextmenu THUNK_GENERATOR(0) },
    { "ondblclick", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOndblclick), (intptr_t)setJSDOMWindowOndblclick THUNK_GENERATOR(0) },
   …………… 
    { "onkeypress", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOnkeypress), (intptr_t)setJSDOMWindowOnkeypress THUNK_GENERATOR(0) },
    { "onkeyup", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOnkeyup), (intptr_t)setJSDOMWindowOnkeyup THUNK_GENERATOR(0) },
    { "onload", DontDelete, (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowOnload), (intptr_t)setJSDOMWindowOnload THUNK_GENERATOR(0) },
………
}


從JSDOMWindowTableValues我們看到了我們所熟悉的history,document,navigator等屬性,也看到了onload,onclick等event。

每一個JSDOMWindowTableValues的項由5部分組成:"document"->屬性的名稱;DontDelete | ReadOnly->屬性的相關flag; (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsDOMWindowDocument)->getter函數;(intptr_t)0->setter函數; THUNK_GENERATOR(0) 用於 jit。執行window.document.xxx就會觸發getter函數;執行window.document = 123;就會觸發setter函數,由於document 的setter函數爲null,所以這句js腳本會拋出異常。

具體的setter和getter的實現與具體功能相關,這裏就不專門解釋了。例如window.navigator最終調用:

JSValue jsDOMWindowNavigator(ExecState* exec, JSValue slotBase, const Identifier&)
{
    JSDOMWindow* castedThis = static_cast<JSDOMWindow*>(asObject(slotBase));
    if (!castedThis->allowsAccessFrom(exec))
        return jsUndefined();
    UNUSED_PARAM(exec);
    DOMWindow* imp = static_cast<DOMWindow*>(castedThis->impl());
    JSValue result = toJS(exec, castedThis->globalObject(), WTF::getPtr(imp->navigator()));
    return result;
}


而其中的imp->navigator()則是調用D:\project\WebKit\Source\WebCore\page\DOMWindow.cpp中的:

Navigator* DOMWindow::navigator() const
{
    if (!m_navigator)
        m_navigator = Navigator::create(m_frame);
    return m_navigator.get();
}


另外,很多人有一個疑問是:調用相關方法或獲取相關屬性時,爲什麼寫不寫window都可以,例如下面腳本是可以正常執行的:

<html>
<head>
<title>test1234</title>
<script>
onload = function() {
    alert(1234);
}
</script>
</head>
<body>
dsfdsf
</body>
</html>


答案是上面的腳本其實是最直接的方式,因爲js腳本執行時的thisValue就爲JSDOMWindowShell,也即window對象,而JSDOMWindow有一個屬性叫window,它返回的就是當前的JSDOMWindowShell,所以js腳本中的window.onload其實是thisValue.window.onload.


在DOMWindow.idl中,有一些註釋是// DOM Level 0,// DOM Level 2 AbstractView Interface等,相關的解釋可以參考wiki(http://en.wikipedia.org/wiki/Document_Object_Model#Levels_of_DOM):

After the release of ECMAScript, W3C began work on a standardized DOM. The initial DOM standard, known as "DOM Level 1," was recommended by W3C in late 1998. About the same time, Internet Explorer 5.0 shipped with limited support for DOM Level 1. DOM Level 1 provided a complete model for an entire HTML or XML document, including means to change any portion of the document. Non-conformant browsers such as Internet Explorer 4.x and Netscape 4.x were still widely used as late as 2000.

DOM Level 2 was published in late 2000. It introduced the "getElementById" function as well as an event model and support for XML namespaces and CSS. DOM Level 3, the current release of the DOM specification, published in April 2004, added support for XPath and keyboard event handling, as well as an interface for serializing documents as XML.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章