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对象处于顶端一致了。

webkit在win32下的编译规则(九)_第1张图片

在上面的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.


你可能感兴趣的:(JavaScript,脚本,webkit,generator,attributes,structure)