V8发动机之initContextIfNeeded(.)函数

V8引擎之initContextIfNeeded(...)函数

上篇说到一个很重要的函数initContextIfNeeded,这里专门来分析下这个函数:

// Create a new environment and setup the global object.
//
// The global object corresponds to a DOMWindow instance. However, to
// allow properties of the JS DOMWindow instance to be shadowed, we
// use a shadow object as the global object and use the JS DOMWindow
// instance as the prototype for that shadow object. The JS DOMWindow
// instance is undetectable from javascript code because the __proto__
// accessors skip that object.
//
// The shadow object and the DOMWindow instance are seen as one object
// from javascript. The javascript object that corresponds to a
// DOMWindow instance is the shadow object. When mapping a DOMWindow
// instance to a V8 object, we return the shadow object.
//
// To implement split-window, see
//   1) https://bugs.webkit.org/show_bug.cgi?id=17249
//   2) https://wiki.mozilla.org/Gecko:SplitWindow
//   3) https://bugzilla.mozilla.org/show_bug.cgi?id=296639
// we need to split the shadow object further into two objects:
// an outer window and an inner window. The inner window is the hidden
// prototype of the outer window. The inner window is the default
// global object of the context. A variable declared in the global
// scope is a property of the inner window.
//
// The outer window sticks to a Frame, it is exposed to JavaScript
// via window.window, window.self, window.parent, etc. The outer window
// has a security token which is the domain. The outer window cannot
// have its own properties. window.foo = 'x' is delegated to the
// inner window.
//
// When a frame navigates to a new page, the inner window is cut off
// the outer window, and the outer window identify is preserved for
// the frame. However, a new inner window is created for the new page.
// If there are JS code holds a closure to the old inner window,
// it won't be able to reach the outer window via its global object.
bool V8DOMWindowShell::initContextIfNeeded()
{
    // Bail out if the context has already been initialized.
    if (!m_context.IsEmpty())
        return false;

    // Create a handle scope for all local handles.
    v8::HandleScope handleScope;

    // Setup the security handlers and message listener. This only has
    // to be done once.
    static bool isV8Initialized = false;
    if (!isV8Initialized) {
        // Tells V8 not to call the default OOM handler, binding code
        // will handle it.
        v8::V8::IgnoreOutOfMemoryException();
        v8::V8::SetFatalErrorHandler(reportFatalErrorInV8);

        v8::V8::SetGlobalGCPrologueCallback(&V8GCController::gcPrologue);
        v8::V8::SetGlobalGCEpilogueCallback(&V8GCController::gcEpilogue);

        v8::V8::AddMessageListener(&v8UncaughtExceptionHandler);

        v8::V8::SetFailedAccessCheckCallbackFunction(reportUnsafeJavaScriptAccess);
#if ENABLE(JAVASCRIPT_DEBUGGER)
        ScriptProfiler::initialize();
#endif
        isV8Initialized = true;
    }

    m_context = createNewContext(m_global, 0);
    if (m_context.IsEmpty())
        return false;


    v8::Local<v8::Context> v8Context = v8::Local<v8::Context>::New(m_context);
    v8::Context::Scope contextScope(v8Context);

    // Store the first global object created so we can reuse it.
    if (m_global.IsEmpty()) {
        m_global = v8::Persistent<v8::Object>::New(v8Context->Global());
        // Bail out if allocation of the first global objects fails.
        if (m_global.IsEmpty()) {
            disposeContextHandles();
            return false;
        }
#ifndef NDEBUG
        V8GCController::registerGlobalHandle(PROXY, this, m_global);
#endif
    }

    if (!installHiddenObjectPrototype(v8Context)) {
        disposeContextHandles();
        return false;
    }

    if (!installDOMWindow(v8Context, m_frame->domWindow())) {
        disposeContextHandles();
        return false;
    }
    updateDocument();


    setSecurityToken();


    m_frame->loader()->client()->didCreateScriptContextForFrame();

    // FIXME: This is wrong. We should actually do this for the proper world once
    // we do isolated worlds the WebCore way.
    m_frame->loader()->dispatchDidClearWindowObjectInWorld(0);

    return true;
}

前面的英文注释很多,同时也说明了这个函数的作用,先把注释翻译一下,也方便自己日后查看:


创建一个新的环境和建立全局对象

//
JavaScript全局对象对应于一个domwindow实例。
然而为了让domWindow实例的属性被隐藏起来,我们使用一个影子对象作为全局对象和使用JS DomWindow实例作为影子对象的属性。
JS domwindow实例在js代码中不会被检测到,因为__proto__访问器略过了这个对象。

影子对象和DomWindow 实例在javascript中被看作同一对象。对应于一个DomWindow实例的javascript对象其实是影子对象。
当映射一个DomWindow实例到一个V8对象的时候,我们返回影子对象。

//

//
为了实现分离窗口,参考:
//   1) https://bugs.webkit.org/show_bug.cgi?id=17249
//   2) https://wiki.mozilla.org/Gecko:SplitWindow
//   3) https://bugzilla.mozilla.org/show_bug.cgi?id=296639


我们需要把隐蔽的对象分成两个对象:
一个外部的window和内部的window.内部的window是外部window的隐藏原型(prototype)。内部window是上下文默认的全局对象。在全局作用域的变量声明是内部window的一个属性。
//
外部window与一个frame相关,它通过window.window,window.self,window.parent等调用暴露给javascript。外部window有一个安全的范围字段。因此window.foo = 'x' 这种只代表内部window。
//
当一个frame导航到一个新页面时,内部window被从外部window切断,外部window标志用来保存frame。然而一个新的内部window被用于创建一个新的页面。如果有JS代码拥有一个以前内部window的闭包,那就不能通过全局对象访问到外部window。



在这个函数内部和之前说的那个有点类似,为所有的局部handle创建handleScope对象,创建上下文,关联上下文到作用域。
在这些步骤过后有一个  updateDocument();为新的document初始化js上下文。在updateDocument()中会调用initContextIfNeeded,为什么updateDocument这个函数会调用initContextIfNeeded呢?看注释:

 // There is an existing JavaScript wrapper for the global object
    // of this frame. JavaScript code in other frames might hold a
    // reference to this wrapper. We eagerly initialize the JavaScript
    // context for the new document to make property access on the
    // global object wrapper succeed.

存在这个frame的全局对象js外壳。在其他frame的js代码可能拥有这个外壳的引用。我们急切的为这个新document初始化js上下文,是为了能够成功的访问到全局对象的外壳。


在这个updateDocument中除了初始化上下文还要更新document的wraperCache,以及更新JS安全起点和安全字段。


如有问题,欢迎大家指出