webkit 中 javascript 与 WebCore DOM 的绑定

转载请注明出处:http://blog.csdn.net/awebkit


由于工作中需要调试 JavaScript 的时候并不过,我对 WebKit 中 JavaScript 的了解并不深刻,我只能对 JavaScript 与 WebCore DOM 之间的接口进行一番解释。
如有错误,欢迎指正。

JavaScript 的基础知识

我们先来了解一下 JavaScript Engine 到底是什么。
通俗的讲,JavaScript Engine 就是一个脚本解释器,内置了一些对象如 date 。事实上, JavaScript Engine 只识别对象,可以说我们看到的所有变量都是对象。根据 ECMAScript 标准,JavaScript 提供的对象如下。


仔细看看,你会发现没有 window 对象。惊讶吗?没错,确实没有 window 对象。那么,对于 JavaScript:window.open ,JavaScript Engine 又是如何解释的呢?

这里就涉及到了 JavaScript Engine 与 DOM 的绑定。
 
总结一下就是 JavaScript Engine 作为一个 Script Engine ,只提供了很简单的一些功能,但是他又提供了一种扩展,可以把其他扩展对象加入到这个 Script Engine 里面,让这个 Script Engine 能解释这些新对象。其中,对 DOM 的操作就是通过扩展来实现的,又叫 JavaScript 与 DOM 的绑定。

DOM 绑定时机

DOM 是在什么时候绑定的呢?

我们知道 script 对应 frame 。frame 里面的 ScriptController 是运行 script 的关键类。在遇到 script 标签需要运行script 的时候,会调用 ScriptController 的 initScript ,从名字可以看出来,这是初始化的操作。那么,都做了哪些初始化操作呢?我们看一下代码
JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world)
{
    ASSERT(!m_windowShells.contains(world));

    JSLock lock(SilenceAssertionsOnly);

    JSDOMWindowShell* windowShell = createWindowShell(world);

    windowShell->window()->updateDocument();

    if (Page* page = m_frame->page()) {
        attachDebugger(windowShell, page->debugger());
        windowShell->window()->setProfileGroup(page->group().identifier());
    }   

    m_frame->loader()->dispatchDidClearWindowObjectInWorld(world);

    return windowShell;
}

先来分析 createWindowShell 
JSDOMWindowShell* ScriptController::createWindowShell(DOMWrapperWorld* world)
{
    ASSERT(!m_windowShells.contains(world));
    Global<JSDOMWindowShell> windowShell(*world->globalData(), new JSDOMWindowShell(m_frame->domWindow(), world));
    Global<JSDOMWindowShell> windowShell2(windowShell);
    m_windowShells.add(world, windowShell);
    world->didCreateWindowShell(this);
    return windowShell.get();
}
JSDOMWindowShell::JSDOMWindowShell(PassRefPtr<DOMWindow> window, DOMWrapperWorld* world)
    : Base(JSDOMWindowShell::createStructure(*world->globalData(), jsNull()))
    , m_world(world)
{
    ASSERT(inherits(&s_info));
    setWindow(window);
}
void JSDOMWindowShell::setWindow(PassRefPtr<DOMWindow> domWindow)
{
    // Explicitly protect the global object's prototype so it isn't collected
    // when we allocate the global object. (Once the global object is fully
    // constructed, it can mark its own prototype.)
    RefPtr<Structure> prototypeStructure = JSDOMWindowPrototype::createStructure(*JSDOMWindow::commonJSGlobalData(), jsNull());
    Global<JSDOMWindowPrototype> prototype(*JSDOMWindow::commonJSGlobalData(), new JSDOMWindowPrototype(0, prototypeStructure.release()));


    RefPtr<Structure> structure = JSDOMWindow::createStructure(*JSDOMWindow::commonJSGlobalData(), prototype.get());
    JSDOMWindow* jsDOMWindow = new (JSDOMWindow::commonJSGlobalData()) JSDOMWindow(structure.release(), domWindow, this);
    prototype->putAnonymousValue(*JSDOMWindow::commonJSGlobalData(), 0, jsDOMWindow);
    setWindow(*JSDOMWindow::commonJSGlobalData(), jsDOMWindow);
}

代码很啰唆,可以只看我标记为黑体的部分,先理清一条线。
我们看到 createWindowShell 里面会创建 JSDOMWindow 。这是一个非常重要的类,对应了 DOMWindow的 JavaScript 类。我们看到所有的 JSXXX 对应的都是绑定JavaScript 与 XXX 关系的类,而且这部分类大都(除了bindings下面的 JSXXX 类)是根据 IDL 和脚本自动生成的(牛掰啊)。所以,我们可以看到 window.open 会先调用 JSDOMWindow ,然后调用到 DOMWindow 。JSDOMWindow 构造没什么好说的,继承于 JSDOMWindowBase ,我们看一下 JSDOMWindowBase 吧

JSDOMWindowBase::JSDOMWindowBase(NonNullPassRefPtr<Structure> structure, PassRefPtr<DOMWindow> window, JSDOMWindowShell* shell)
    : JSDOMGlobalObject(structure, shell->world(), shell)
    , m_impl(window)
    , m_shell(shell)
{
    ASSERT(inherits(&s_info));

    GlobalPropertyInfo staticGlobals[] = {
        GlobalPropertyInfo(Identifier(globalExec(), "document"), jsNull(), DontDelete | ReadOnly),
        GlobalPropertyInfo(Identifier(globalExec(), "window"), m_shell, DontDelete | ReadOnly)
    };

    addStaticGlobals(staticGlobals, WTF_ARRAY_LENGTH(staticGlobals));
}

addStaticGlobals
把 window 对象加入到了 JavaScript 执行环境中,对应 JSDOMWindowShell (是个 JSObject 对象),从此,window 再也不是未定义了。

关于 addStaticGlobals 的详细介绍以后再说。你只要了解这个类把一些 JSObject 对象添加到了 JavaScript 执行环境中就可以了。

再回到开始的地方,还有一个标记黑色的函数 updateDocument,代码如下
void JSDOMWindowBase::updateDocument()
{       
    ASSERT(m_impl->document());
    ExecState* exec = globalExec();
    symbolTablePutWithAttributes(exec->globalData(), Identifier(exec, "document"), toJS(exec, this, m_impl->document()), DontDelete | ReadOnly);
}

其实,就是更新了 document 对应的类。这样,DOM 的两个基本对象(window 和 document)就都有了。

总结一下,ScriptController 在 initScript 中把 window 对象,document 对象添加到了 JavaScript 执行环境中,对window 等全局对象的操作可以找到对应的类。

如何扩展 JavaScript 对象


上面我们讲了 WebKit 是如何在 JavaScript Engine 中扩展 window document 对象的,理解了整个流程,对于我们扩展自己对象就轻车熟路了。可以查看后面的参考。

如果每个对象都像参考里面说的需要修改源码,那就太麻烦了,对于浏览器开发者来说,需要给上层提供扩展对象的接口。就像 android 里面的 addJavaScriptInterface 。

那么,在哪里添加接口呢?

再回到刚开始的地方,我们看到还有一个标记黑体的函数 dispatchDidClearWindowObjectInWorld ,这个首先是 FrameLoader 的函数,然后会走到 FrameLoaderClient 的对应函数。

我们看一下 android 这个函数的实现
// This function is used to re-attach Javascript<->native code classes.
void FrameLoaderClientAndroid::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld* world)
{
    if (world != mainThreadNormalWorld())
        return;

    ASSERT(m_frame);
    LOGV("::WebCore:: windowObjectCleared called on frame %p for %s\n",
            m_frame, m_frame->loader()->url().string().ascii().data());
    m_webFrame->windowObjectCleared(m_frame);
}

经过 JNI 代码,调用到 java 部分 BrowserFrame 的代码。(请注意解释)
    /*   
     * This method is called by WebCore to inform the frame that
     * the Javascript window object has been cleared.
     * We should re-attach any attached js interfaces.
     */
    private void windowObjectCleared(int nativeFramePointer) {
        Iterator<String> iter = mJavaScriptObjects.keySet().iterator();
        while (iter.hasNext())  {
            String interfaceName = iter.next();
            Object object = mJavaScriptObjects.get(interfaceName);
            if (object != null) {
                nativeAddJavascriptInterface(nativeFramePointer,
                        mJavaScriptObjects.get(interfaceName), interfaceName);
            }    
        }    
        mRemovedJavaScriptObjects.clear();

        stringByEvaluatingJavaScriptFromString(SearchBoxImpl.JS_BRIDGE);
    }   

nativeAddJavascriptInterface 经过 JNI ,又会调用到 WebCoreFrameBridge::AddJavascriptInterface 函数。这个函数把新扩展的对象加入到 window 对象中。
static void AddJavascriptInterface(JNIEnv *env, jobject obj, jint nativeFramePointer,
        jobject javascriptObj, jstring interfaceName)
{
#ifdef ANDROID_INSTRUMENT
    TimeCounterAuto counter(TimeCounter::NativeCallbackTimeCounter);
#endif
    WebCore::Frame* pFrame = 0;
    if (nativeFramePointer == 0)
        pFrame = GET_NATIVE_FRAME(env, obj);
    else
        pFrame = (WebCore::Frame*)nativeFramePointer;
    LOG_ASSERT(pFrame, "nativeAddJavascriptInterface must take a valid frame pointer!");

    JavaVM* vm;
    env->GetJavaVM(&vm);
    const char* myname = getCharactersFromJStringInEnv(env, interfaceName);
    DBG_NAV_LOGD("::WebCore:: addJSInterface: %p, js %s", pFrame, myname);

#if USE(JSC)
    // Copied from qwebframe.cpp
    JSC::JSLock lock(JSC::SilenceAssertionsOnly);
    WebCore::JSDOMWindow *window = WebCore::toJSDOMWindow(pFrame, mainThreadNormalWorld());
    if (window) {
        RootObject *root = pFrame->script()->bindingRootObject();
        setJavaVM(vm);
        // Add the binding to JS environment
        JSC::ExecState* exec = window->globalExec();
        JSC::JSObject* addedObject = WeakJavaInstance::create(javascriptObj,
                root)->createRuntimeObject(exec);
        const jchar* s = env->GetStringChars(interfaceName, NULL);
        if (s) {

            // Add the binding name to the window's table of child objects.
            JSC::PutPropertySlot slot;
            window->put(exec, JSC::Identifier(exec, (const UChar *)s,
                    env->GetStringLength(interfaceName)), addedObject, slot);
            env->ReleaseStringChars(interfaceName, s);
            checkException(env);
        }
    }
#elif USE(V8)
    if (pFrame) {
        RefPtr<JavaInstance> addedObject = WeakJavaInstance::create(javascriptObj);
        const char* name = getCharactersFromJStringInEnv(env, interfaceName);
        // Pass ownership of the added object to bindToWindowObject.
        NPObject* npObject = JavaInstanceToNPObject(addedObject.get());
        pFrame->script()->bindToWindowObject(pFrame, name, npObject);
        // bindToWindowObject calls NPN_RetainObject on the
        // returned one (see createV8ObjectForNPObject in V8NPObject.cpp).
        // bindToWindowObject also increases obj's ref count and decreases
        // the ref count when the object is not reachable from JavaScript
        // side. Code here must release the reference count increased by
        // bindToWindowObject.

        // Note that while this function is declared in WebCore/bridge/npruntime.h, for V8 builds
        // we use WebCore/bindings/v8/npruntime.cpp (rather than
        // WebCore/bridge/npruntime.cpp), so the function is implemented there.
        // TODO: Combine the two versions of these NPAPI files.
        NPN_ReleaseObject(npObject);
        releaseCharactersForJString(interfaceName, name);
    }
#endif

}

上面我讲了 android 平台如何扩展 JavaScript 对象的,当我们作浏览器开发的时候,可以照猫画虎。

但上面只是讲的流程,如果需要扩展 JavaScript 对象,必须对扩展的对象 JSObject 熟悉,知道 JavaScript 是如何认识对象的,这个以后再讲。

参考:




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值