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