webkit 中 javascript 与 WebCore DOM 的绑定

来源:互联网 发布:java小项目开发案例 编辑:程序博客网 时间:2024/05/24 06:12

转载请注明出处: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 是如何认识对象的,这个以后再讲。

参考:

http://blog.csdn.net/horkychen/article/details/7640052