--- title: Mozilla DOM Hacking Guide slug: Mozilla_DOM_Hacking_Guide translation_of: Mozilla/Mozilla_DOM_Hacking ---
Warning: this document has not yet been reviewed by the DOM gurus, it might contain some errors. It is also perhaps outdated in parts, because of recent changes in the DOMClassInfo code. If anyone wants to help, please let me know.
警告1: 这篇文档尚未经DOM大牛们审阅,它可能会包含某些错误。 由于DOMClassInfo代码最近的变化,本文某些部分可能已经过时。如果有人需要帮助,请告知我。
警告2: 这篇文档的翻译也未经大牛审阅,它可能会包含某些错误。。
Mozilla gives you the opportunity not only to use a very powerful and complete DOM support, but also to work on a world-class implementation of one of the greatest Internet technology ever created.
Mozilla提供了有力且完整的DOM支持,它的DOM实现是曾有的最伟大的网络技术之一,并且除了使用DOM支持以外,你也有机会在这个世界级的实现之上工作。
Mozilla's DOM is coded almost entirely in C++. Seriously hacking on it requires excellent knownledge of C++ and XPCOM, Mozilla's own component model. In this document I will try to outline the main aspects of the implementation, begining with the Class Info mechanism, which lies at the heart of the DOM, then with the description of various interfaces and classes. Since I am myself still learning how it works, don't expect this to be a complete reference quite yet. If you can contribute any time or knowledge to this document, it is greatly appreciated!
Mozilla 的DOM几乎全是用C++写的,真要修改它,你需要对C++,XPCOM,还有Mozilla自己的组件模型有出色的了解。本文试图对 DOM实现的主要方面进行勾勒,从DOM的核心部分,Class info开始,然后描述各种接口和类。因为我自己也还在学习它的工作原理,就别指望本文会是个完整的指导书了。如果你也愿意为这篇文档贡献时间精力,那就 太感谢了。
Target audience: People interested in learning how the DOM is implemented. Prior knowledge of C++ and XPCOM is assumed. If you don't know XPCOM yet, and would like to be able to read this document quickly, you can read the Introduction to XPCOM for the DOM. Otherwise, for more detailed XPCOM documentation, please see the XPCOM project page.
目标受众:对DOM如何实现感兴趣的人。本文假设你对C++和XPCOM有了解。如果你还不了解XPCOM,并且希望快速阅读本文,你可用先看看 introduction to XPCOM for the DOM.。不然,要得到更详细的XPCOM文档,请参考XPCOM project page。
Class Info is what gives the DOM classes their correct behavior when used through XPConnect. It lies at the heart of the famous "XPCDOM landing" that happened in May. We will talk alot about XPConnect in this document, since it is so important for the DOM. By "correct behavior", I mean "the intended behavior with respect to the specification or de facto standard". We will see that Class Info is mainly used to implement the DOM Level 0. The W3C DOM is mainly implemented in IDL. The goals of Class Info are twofolds: Interface flattening, and implementing behaviors that are not possible with IDL alone.
Class Info让DOM类在被通过XPConnect使用时,能够作出正确的行为。它是发生在五月的著名的"XPCDOM landing"的核心。在本文中,我们会大量地提到XPConnect,因为它对DOM很重要。提到“正确的行为”,我指的是规范或者标准所预期的行 为。我们会看到Class Info主要用来实现DOM Level 0。W3C DOM主要用IDL实现。Class Info的目标主要是两个:接口平坦化,还有实现不可能只用IDL实现的行为。
A brief introduction to JavaScript and XPConnect.
对JavaScript和XPConnect的简介
Before we begin the explanation of Class Info, I'd like to introduce quickly the JavaScript engine and XPConnect. In JavaScript, there is no knowledge of types, like there is in C++. A function for example can be represented by a JSFunction, a JSObject, a jsval, ... This means that when we use the DOM from JavaScript, we pass arguments that have no type. However, since the DOM is coded in C++, we expect to receive an argument of the correct type for our function. This is one of the jobs of XPConnect. XPConnect will "wrap" the argument in a wrapper that will be of the type expected by our C++ function. Similarly, then return type of the C++ function will be wrapped by XPConnect so that JavaScript can use it safely.
在我们开始解释Class Info之前,我想先快速地介绍一下JavaScript引擎和XPConnect。在JavaScript中,没有象C++那样类型的概念。比如说,一 个函数可以被表示成一个JSFunction,一个JSObject,一个jsval...这就意味着当我们从JavaScript中使用DOM是,我们 会传递没有类型的参数。然而,因为DOM是用C++编写的,我们会希望收到具有正确类型的参数。这就是XPConnect的一部分工作。 XPConnect会在参数外面"裹(wrap)"一层包装(wrapper),把它包装成C++函数所期望的类型。类似地,C++函数的返回值也会被 XPConnect包装成JavaScript可以安全使用的类型。
When, in JavaScript, a client tries to access a DOM object or a DOM method on a DOM object, the JS engine asks XPConnect to search for the relevant C++ method to call. For example, when we ask for |document.getElementById("myID");|, XPConnect will find that |document| is a property of the window object, so it will look on the interface nsIDOMWindow, and it will find the GetDocument() method. The return value of GetDocument() is a nsIDOMDocument. So XPConnect will then try to find a method named GetElementById() on the nsIDOMDocument interface. And indeed it will find it, and thus call it.
在JavaScript中,当客户端试图访问一个DOM对象或者一个DOM对象的一个DOM方法时,JS引 擎会向XPConnect要求寻找一个相关的C++函数来调用。比如,当我们要求|document.getElementById("myID");| 时,XPConnect会发现[document]
是window对象的一个属性,所以它会去找nsIDOMWindow接口,然后它会寻找GetDocument()方法。GetDocument()方 法的返回值是nsIDOMDocument。所以XPConnect会试图在nsIDOMDocument 接口中寻找叫GetElementById()的方法,它也确实找到了这个方法。所以它就调用了这个方法。
This is the schema used most of the time when using W3C DOM objects and methods. It is however different for some DOM Level 0 objects and methods. I'll take two very different examples. The first one is the window.location object (the same holds true for document.location, actually). We can change the URL of the current window by assigning window.location. In IDL, location is declared to be a readonly attribute. This is because, if we had a SetLocation() method, it would take an nsIDOMLocation parameter, and not a URL string. Instead, in the helper class for the window object (nsWindowSH, see the next Section), we define the GetProperty() member function. GetProperty() is a function used by XPConnect when we are setting an unknown property on the object (window, in our case). In GetProperty(), we check if the property being set is "location". If that is the case, we call nsIDOMLocation::SetHref(). In fact, when setting window.location, we really set window.location.href. This is all possible thanks to the magic of the interaction between XPConnect and the DOM.
这是使用W3C DOM对象和方法时最常用的场景。但是,对于有些DOM level 0的对象和方法来说有点不一样。我会举两个不同的例子。第一个是window.location对象(实际上document.location也是一 样)。我们可以通过给window.location赋值来改变当前窗口的URL。在IDL里,location被声明为一个只读属性。这是因为,如果我 们有个SetLocation()方法,它会接收一个nsIDOMLocation属性的参数,而不是一个URL字符串。与之相对地,在window对象 的helper类中,(nsWindowSH,见下章),我们定义了GetProperty()成员函数。GetProperty()是 XPConnect使用的函数,用来设置对象(在我们的例子中,是window)的一个未知变量。在GetProperty()里,我们检查要被设置的属 性是不是location。如果是的话,我们就调用nsIDOMLocation::SetHref()。实际上,当设置window.location 的时候,我们设置的是window.location.href。这些都归功于XPConnect和DOM之间的交互魔法。
The second example is the history object. Other browsers allow the history object to be used like an array, e.g. history{{ mediawiki.external(1) }}. The behavior "act as an array" cannot be reflected in the IDL itself. Fortunately, XPConnect provides us with a way to make our class available as an array in JavaScript. I'm talking about the "scriptable flags". The nsIXPCScriptable interface, implemented by the nsDOMClassInfo class (see Section) defines several flags, one of which is the WANT_GETPROPERTY flag. When set, it allows us to define a GetProperty() function on nsHistorySH (the helper class for the history object), which will handle the array behavior. Indeed, it will forward the call history{{ mediawiki.external(1) }} to history.item(1), which is defined in the IDL and easily coded. The relevant code is at {{ Source("dom/src/base/nsDOMClassInfo.cpp#4520", "nsDOMClassInfo.cpp") }}, around line 4520.
第二个例子是history对象。其他浏览器允许history对象被当作数组使用。比 如,history[1]。这种"象数组一样干活"的行为不可能在IDL本身中表现出来。幸运的是,XPConnect为我们提供了一种方式,让我们的类 在JavaScript中能被当作数组使用。我要说的是"scriptable标志"。由nsDOMClassInfo类实现的 nsIXPCScriptable接口定义了一些标志,其中一个是WANT_GETPROPERTY。当这个标志被设置,我们就能在 nsHistorySH(history对象的helper类)中定义一个GetProperty()方法。这个方法会处理数组行为。实际上,它将把 history[1]这样的调用翻译成history.item(1)。后者定义在IDL里,并且被容易地编码实现。对应的代码在 nsDOMClassInfo.cpp 里,大概在4520行左右。
These two examples demonstrate the power of the DOM combined with XPConnect and the JavaScript engine. The possibilities are endless. "What do you want to code today?" ;-)
这两个例子展示了DOM跟XPConnect以及JavaScript引擎结合的威力。可能性是无限的,“您今天想写些啥?”:)
All the DOM classes are listed in an enum defined in {{ Source("dom/public/nsIDOMClassInfo.h", "nsIDOMClassInfo.h") }}, nsDOMClassInfoID. There are classes for the DOM0, Core DOM, HTML, XML, XUL, XBL, range, css, events, etc...
所有的DOM类都在nsIDOMClassInfo.h中的一个枚举变量,nsDOMClassInfoID中被列出。这儿有DOM0的类,还有核心DOM,HTML, XML, XUL, XBL, range, css, events, 等等。
The array sClassInfoData, defined in {{ Source("dom/src/base/nsDOMClassInfo.cpp", "nsDOMClassInfo.cpp") }}, maps each DOM class to its helper class and to the interfaces that are exposed to JavaScript. It is an array of type nsDOMClassInfoData, which is a structure defined in {{ Source("dom/public/nsIDOMClassInfo.h", "nsIDOMClassInfo.h") }}. The array uses two macros to define its items: NS_DEFINE_CLASSINFO_DATA
and NS_DEFINE_CLASSINFO_DATA_WITH_NAME
. The first one calls the second one. The first argument passed to NS_DEFINE_CLASSINFO_DATA_WITH_NAME
, _class, is used for debug purposes. The second argument, _name, is the name that should appear in JavaScript. The third argument, _helper, is the name of the helper class for this DOM class. Helper classes are detailed in Section 1.3. The fourth and last argument, _flags, is a bitvector of nsIXPCScriptable flags. The macros for those flags are defined {{ Source("dom/src/base/nsDOMClassInfo.cpp", "nsDOMClassInfo.cpp") }}. The flags give special behavior through XPConnect. See also Section 1.9.
The nsDOMClassInfoData objects are created in the sClassInfoData array by explicitly initializing it. Here is the description of the structure:
nsDOMClassInfo.cpp中的sClassInfoData数组,将各个DOM类跟它的 helper类,还有将要暴露给JavaScript的接口对应起来。这是一个nsDOMClassInfoData类型的数 组,nsDOMClassInfoData是个结构体,定义在nsIDOMClassInfo.h里。这个数组使用
两个宏来定义它的子项:NS_DEFINE_CLASSINFO_DATA跟NS_DEFINE_CLASSINFO_DATA_WITH_NAME。前 者调用后者。传递给NS_DEFINE_CLASSINFO_DATA_WITH_NAME的第一个参数,_class,是用来调试的。第二个参 数,_name,是应该在JavaScript中使用的名字。第三个参数,_helper,是这个DOM类的helper类的名字。Helper类在 1.3节中详述。第四个也是最后一个参数,_flag,是一存放nsIXPCScriptable标志的比特向量。这些标志的宏定义在 nsDOMClassInfo.cpp里。这些标志通过XPConnect赋予特殊的行为。同见1.9节。
sClassInfoData数组里的nsDOMClassInfoData对象通过明确的初始化动作来创建。这是这个结构的描述:
const char *mName
: C-style string that is passed as second argument to the macro. It is the name of the JavaScript object that will be available in the browser through the DOM.union { nsDOMClassInfoConstructorFnc mConstructorFptr; nsDOMClassInfoExternalConstructorFnc mExternalConstructorFptr; } u;This union is a pointer to a function typedef'ed:
typedef nsIClassInfo* (*nsDOMClassInfoConstructorFnc)(nsDOMClassInfoID aID);
typedef nsIClassInfo* (*nsDOMClassInfoExternalConstructorFnc) (const char* aName);
nsIClassInfo *mCachedClassInfo
: mCachedClassInfo holds an nsIClassInfo pointer to an instance of the relevant helper class.const nsIID *mProtoChainInterface
: Pointer to the IID of the first interface available to JavaScript clients. This is used in global resolve functions, when XPConnect has to find the member function to call.const nsIID **mInterfaces
: Pointer to the first element of an array of pointers to all the interfaces available through JS for this class.PRUInt32 mScriptableFlags: 31;
: The fourth argument passed to NS_DEFINE_CLASSINFO_DATA_WITH_NAME.PRBool mHasClassInterface: 1;
: Help me?const char *mName
: C风格的字符串,被作为第二个参数传入这个宏,它是在browser中通过DOM使用的JavaScript对象的名字。union { nsDOMClassInfoConstructorFnc mConstructorFptr; nsDOMClassInfoExternalConstructorFnc mExternalConstructorFptr; } u;
这个union是个指向函数的指针,
nsDOMClassInfoConstructorFnc跟
nsDOMClassInfoExternalConstructorFnc都是typedef过的。
typedef nsIClassInfo* (*nsDOMClassInfoConstructorFnc)(nsDOMClassInfoID aID);
typedef nsIClassInfo* (*nsDOMClassInfoExternalConstructorFnc) (const char* aName);
nsIClassInfo *mCachedClassInfo
: mCachedClassInfo持有一个nsIClassInfo的指针,指向一个相关联的helper类的实例。const nsIID *mProtoChainInterface
: 指向JavaScript客户端可用的第一个接口的IID。当XPConnect需要寻找要调用的成员函数的时候,它被用来解析全局函数。
const nsIID **mInterfaces
: 指向一队指针中的第一个,这些指针指向类中可以通过JS调用的接口。PRUInt32 mScriptableFlags: 31;
: 传给NS_DEFINE_CLASSINFO_DATA_WITH_NAME的第四个参数。PRBool mHasClassInterface: 1;
:干啥的?帮帮我。mName and mConstructorFptr, mScriptableFlags and mHasInterface are initialized by NS_DEFINE_CLASSINFO_DATA_WITH_NAME. mCachedClassInfo, mProtoChainInterface and mInterfaces, however, are initialized in nsDOMClassInfo::Init(), described in Section 1.5.
mName和mConstructorFptr,mScriptableFlags,还有 mHasInterfaces通过NS_DEFINE_CLASSINFO_DATA_WITH_NAME初始化,mCachedClassInfo, mProtoChainInterface 还有 mInterfaces通过nsDOMClassInfo::Init()来初始化。我们在1.5节中有描述。
接口平坦化
One of the nicest -- and most important -- features of the XPConnect'ed DOM is the interface flattening. "Interface flattening is the ability to call methods on an object regardless of the interface it was defined on." For example, when we have the document object in JavaScript, we can call indistinctly document.getElementById(), or document.addEventListener(), although they are defined on two different interfaces ({{ Source("dom/public/idl/core/nsIDOMDocument.idl", "DOMDocument") }} and {{ Source("dom/public/idl/events/nsIDOMEventTarget.idl", "DOMEventTarget") }}. Needless to say this is critical for the use of the DOM in real-world content.
使用了XPConnect的DOM的功能中,最美妙也是最重要的一个是接口平坦化。“接口平坦化”是指调用 一个对象的方法而不必知道这个方法定义在哪个接口中。比如,如果我们在JavaScript中有个document对象,我们可以模糊地调用 document.getElementById(),或者document.addEventListener(),尽管它们定义在两个不同地接口里 (DOMDocument和DOMEventTarget。)不用说,这是在真实世界中使用DOM的最重要的东西。
In Mozilla interface flattening is obtained through the use of the nsIClassInfo interface. nsIClassInfo stores the interfaces available for an object and later on XPConnect uses those interfaces to lookup the right method to call.
在Mozilla中,接口平坦化是通过使用nsIClassInfo接口实现的。nsIClassInfo保存了一个对象可用的所有接口,随后XPConnect使用这些接口来寻找该调用的正确的方法。
The great thing is that one can easily see the interfaces available from JS through interface flattening by looking at the code. The interesting part is in {{ Source("dom/src/base/nsDOMClassInfo.cpp", "nsDOMClassInfo::Init()") }}. There we have a long list of macros. There is one set of macros per DOM class. There you can see, for each object, what interfaces are part of the "flattened" set. As an example, on the window object, we can call all the methods defined on the following interfaces: nsIDOMWindow, nsIDOMWindowInternal, nsIDOMJSWindow, nsIDOMEventReciever, nsIDOMEventTarget, nsIDOMViewCSS, and nsIDOMAbstractView. Again, without caring on what interface the method is defined. See Section 1.5 for more information about the Init() method.
好消息是人们可以通过看代码轻易地找到所有可以由JS调用的接口(拜接口平坦化所赐)。有趣的故事发生在拥 有一长列宏的nsDOMClassInfo::Init()中。每个DOM类都有一组宏。在这儿你可以看到,对于每个对象,它会列出一组接口,这就是“平 坦化”过的接口组合。比如,在window对象上,我们可以调用如下接口中的方法:nsIDOMWindow, nsIDOMWindowInternal, nsIDOMJSWindow, nsIDOMEventReciever, nsIDOMEventTarget, nsIDOMViewCSS, 还有nsIDOMAbstractView。同样,这种调用不用说明方法在哪个接口中定义。在1.5节中可以找到关于Init()方法的更多信息。
For the W3C DOM (Level 1, 2, 3) objects, for each object there is one "standards-compliant" interface, which is exactly the same as the W3C one, named nsIDOM<ObjectName>.idl, and a mozilla-specific extension interface, named nsIDOMNS<ObjectName>.idl, for compatibility with DOM Level 0. For example, the HTML "area" element has the following interfaces in its flattened set: nsIDOMHTMLAreaElement and nsIDOMNSHTMLAreaElement.
对于(1,2,3级)的W3C DOM对象,每个对象都有一个跟W3C完全一致的“符合标准”的接口,叫做nsIDOM<对象名>.idl,另外,为了跟DOM 0级别兼容,还有一个mozilla特有的扩展接口,叫做nsIDOMNS<对象名>.idl。举例来说,HTML"area"元素有下列的 平坦化接口:nsIDOMHTMLAreaElement和nsIDOMNSHTMLAreaElement。
nsDOMClassInfo.h defines several new classes. They all end in "SH", for "Scriptable Helper" e.g. nsWindowSH, nsElementSH, ... . We call these classes the "Helper Classes". All the helper classes inherit from the nsDOMClassInfo class. To demonstrate this, look in {{ Source("dom/src/base/nsDOMClassInfo.h", "nsDOMClassInfo.h") }}. We can see that the nsEventRecieverSH helper class inherits from nsDOMGenericSH:
nsDOMClassInfo.h定义了几个新类。它们都用"SH"结尾,意思是“Scriptable Helper(脚本化帮助类)”,比如,nsWindowSH,nsElementSH,...我们把这些类叫做帮助类。所有这些帮助类都继承自 nsDOMClassInfo类。为了展示这个,看看nsDOMClassInfo.h。我们会看到nsEventRecieverSH帮助类继承自 nsDOMGenericSH:
class nsEventRecieverSH : public nsDOMGenericSH
And nsDOMGenericSH is typedef'ed to nsDOMClassInfo:
nsDOMGenericSH是nsDOMClassInfo的别名:
typedef nsDOMClassInfo nsDOMGenericSH;
Another example is nsWindowSH, which inherits from nsEventReceiverSH, thus inheriting from nsDOMClassInfo.
另一个例子是nsWindowSH,它继承自nsEventReceiverSH,从而继承自nsDOMClassInfo。
Each DOM class is mapped to its helper class during the initialization of the sClassInfoData array.
每个DOM类通过sClassInfoData数组的初始化映射到它的帮助类。
Each helper class has a public doCreate member function that is called by GetClassInfoInstance (see also Section 1.6) to create a new instance of the class if needed. Remember that the doCreate member function is called through a pointer to a function named mConstructorFptr, a member of the nsDOMClassInfoData struct. An instance of a helper class is created the first time XPConnect needs access to the flattened set of interfaces of an object. The instance is then cached for further use.
每个帮助类都有一个公开的doCreate成员方法,它被GetClassInfoInstance调用 (见1.6节)来在需要的时候创建类实例。记住doCreate方法是通过一个叫做mConstructorFptr的函数指针来调用的。 mConstructorFptr是nsDOMClassInfoData结构的一个成员。一个帮助类的实例会在XPCOM第一次需要访问某对象的平坦化 的接口组时被创建。然后这个接口被缓存起来,以备后用。
Most of the helper classes implement one or more nsIXPCScriptable methods. Those methods are used by XPConnect when we require something from JavaScript that was not defined in IDL. For example, GetProperty() is used when retrieving an attribute that was not defined in IDL, and NewResolve() is used when resolving for the first time an attribute or method that was not previously resolved. Please see the {{ Source("js/src/xpconnect/idl/nsIXPCScriptable.idl", "nsIXPCScriptable interface") }} for more information.
这些帮助类的绝大部分实现了一个或多个nsIXPCScriptable接口中的方法。这些方法在我们需要 从JavaScript获取某些没有在IDL中定义的东西时会被XPConnect用到。比如,GetProperty()用来获取某个没有在IDL中定 义的属性。NewResolve()在第一次解析某个之前没有被解析过的属性或者方法时被调用。细节参见nsIXPCScriptable接口。
The heart of Class Info is the nsDOMClassInfo class, defined in {{ Source("dom/src/base/nsDOMClassInfo.h", "nsDOMClassInfo.h") }}. It implements two interfaces besides nsISupports: {{ Source("js/src/xpconnect/idl/nsIXPCScriptable.idl", "nsIXPCScriptable") }} and {{ Source("xpcom/components/nsIClassInfo.idl", "nsIClassInfo") }}.
Class Info的关键处在于nsDOMClassInfo类,它定义在nsDOMClassInfo.h中。除了nsISupports外,它还实现了两个接口:nsIXPCScriptable和nsIClassInfo。
We already know what nsIXPCScriptable is used for (see the previous Section).
我们已经知道nsIXPCScriptable是干嘛的了(见前节)
nsIClassInfo is an XPCOM interface, very well described by Mike Shaver in this overview of nsIClassInfo. Basically it contains convenient methods to find out about the interfaces an object promises to support. In our case, this list of interfaces will be populated by "Class Info". See also Section 1.5 on the Init() function and Section 1.2 on interface flattening.
nsIClassInfo是个XPCOM接口,在Mike Shaver 对nsIClassInfo的概述中已经描述得很清楚了。基本上来说,它包含了一些便利的方法,用来寻找一个对象承诺要支持的接口。在我们的情况下,这些 接口会通过“Class Info”导出。见1.5节对Init()方法的描述,还有1.2节对接口平坦化的描述。
We saw in Section 1.3 that nsDOMClassInfo is the base class for all the helper classes. Let's see what it's made of. Let's begin with the public interface.
在1.3节中,我们已经说过了nsDOMClassInfo是所有帮助类的基类。我们来看看它的组成,从公开接口开始。
static nsIClassInfo* GetClassInfoInstance(nsDOMClassInfoID aID)
:static nsIClassInfo* GetClassInfoInstance(nsDOMClassInfoData* aData);
:static void ShutDown()
:static nsIClassInfo* doCreate(nsDOMClassInfoData* aData)
:static nsresult WrapNative(...)
: XPConnect fu, not our problem.static nsresult ThrowJSException(JSContext *cx, nsresult aResult);
:static nsresult InitDOMJSClass(JSContext *cx, JSObject *obj);
:static JSClass sDOMJSClass;
:Protected section:保护函数 :
const nsDOMClassInfoData* mData;
: help me!static nsresult Init()
: Called only once, it is used to initialize the remaining members of the nsDOMClassInfoData structure, as mentioned above. Once called, Init() sets sIsInitialized to true, to remember that the initialization has been performed. The implementation is described in Section 1.5.static nsresult RegisterClassName(PRInt32 aDOMClassInfoID)
: help me!static nsresult RegisterClassProtos(PRInt32 aDOMClassInfoID)
: help me!static nsresult RegisterExternalClasses();
: help me!nsresult ResolveConstructor(JSContext *cx, JSObject *obj, JSObject **objp);
: help me!static PRInt32 GetArrayIndexFromId(JSContext *cx, jsval id, PRBool *aIsNumber =
nsnull)
:static inline PRBool IsReadonlyReplaceable(jsval id) { ... }
: help me!static inline PRBool IsWritableReplaceable(jsval id) { ... }
: help me!nsresult doCheckPropertyAccess(...)
: help me! (bug 90757)static JSClass sDOMConstructorProtoClass
: XPConnect fu to expose the DOM objects constructors to JavaScript.static JSFunctionSpec sDOMJSClass_methods[];
: help me! (bug 91557)static nsIXPConnect *sXPConnect
: Used to call nsIXPConnect methods that we need. Initialized in Init().static nsIScriptSecurityManager *sSecMan
: Used by the DOM security engine. Initialized in Init().static nsresult DefineStaticJSVals(JSContext *cx);
: Used to define all the static JSString data members of nsDOMClassInfo.static PRBool sIsInitialized
:static jsval *sX_id
: strings used in the global resolve methods for comparison with the passed in arguments. They represent special words for the DOM. Initialized by DefineStaticJSVals().static const JSClass *sObjectClass
: help me!static PRBool sDoSecurityCheckInAddProperty;
: help me!static PRInt32 GetArrayIndexFromId(JSContext *cx, jsval id, PRBool *aIsNumber =
nsnull)
:This method is to be called only once. Its purpose is, well, to initialize... It does a lot of different things: Fill the blanks in the sClassInfoData array, initialize the sXPConnect and sSecMan data members, create a new JavaScript Context, define the JSString data members, and register class names and class prototypes. Finally it sets sIsInitialized to true. The actions that concern the DOM are described below.
这个方法只调一次。它的目的是:好吧。。初始化。。。它干了很多不同的事,填写 sClassInfoData数组中的空白,初始化sXPConnect和sSecMan数据变量,创建一个新的JavaScript context,定义JSString数据成员,注册类名和类原型。最后,它把sIsInitialized设成true。跟DOM有关系的行为描述如 下:
First, the call to CallGetService() initializes sXPConnect. Then the Script Security Manager (sSecMan) is initialized. GetSafeJSContext() grabs us a cool JS context to run our JavaScript code in. The part about ComponentRegistrar is designed to allow external modules (in this case XPath) to be included in DOMClassInfo and as such benefit from the JavaScript benefits it provides. After that, we fill the blanks in the sClassInfoData array.
首先,它调用CallGetService()来初始化sXPConnect。然后脚本安全管理器 (sSecMan)被初始化。GetSafeJSContext()为我们获取一个挺不错的JS Context来跑我们的JavaScript代码。跟ComponentRegistrar有关的部分被设计成允许外部模块(在本例中是XPath)被 DOMClassInfo引用,以便从JavaScript提供的好处中得益。然后,我们填充sClassInfoData数组。
If you remember the discussion in the introduction to Class Info, there is the main array, sClassInfoData, filled with objects of type nsDOMClassInfoData. However when the array is created, three data members of the structure are left as null pointers: mCachedClassInfo, mProtoChainInterface, and mInterfaces. Init() uses a set of macros to fill the blanks: the DOM_CLASSINFO_MAP family. Each DOM class needs to use these macros, otherwise bad things will happen. I will use the example of the Window class to illustrate the use of the macros. Here is the relevant piece of code.
如果你记得在介绍Class Info时的讨论的话,这个主数组,sClassInfoData,填充了一些nsDOMClassInfoData类型的对象。然而当数组被创建的时 候,这个数据结构的三个数据成员被保留为空指针:mCachedClassInfo, mProtoChainInterface, 还有mInterfaces。Init()使用一组宏来填补空白:DOM_CLASSINFO_MAP家族。每个DOM类都要使用这些宏,否则就会遭遇不 测。我会使用Window类来演示这些宏的使用。以下是相关的代码片断。
DOM_CLASSINFO_MAP_BEGIN(Window, nsIDOMWindow) DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow) ... DOM_CLASSINFO_MAP_ENTRY(nsIDOMAbstractView) DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(_class, _interface) maps to _DOM_CLASSINFO_MAP_BEGIN(_class, &NS_GET_IID(_interface), PR_TRUE). NS_GET_IID is a macro that expands to the IID of the interface passed in. We pass the address of this nsIID object to the second macro.
DOM_CLASSINFO_MAP_BEGIN(_class, _interface) 被映射到_DOM_CLASSINFO_MAP_BEGIN(_class, &NS_GET_IID(_interface), PR_TRUE). NS_GET_IID是个宏,展开后,它表示被传入的接口的IID。我们把这个nsIID对象的地址传给第二个宏。
#define _DOM_CLASSINFO_MAP_BEGIN(_class, _ifptr, _has_class_if) { nsDOMClassInfoData &d = sClassInfoData[eDOMClassInfo_##_class##_id]; d.mProtoChainInterface = _ifptr; d.mHasClassInterface = _has_class_if; static const nsIID *interface_list[] = {
In this macro, |d| is a reference to the entry of the sClassInfoData array that corresponds to the class passed as an argument to the macro. The mProtoChainInterface member pointer is initialized to the address of the IID of the interface passed as an argument to DOM_CLASSINFO_MAP_BEGIN. A static array of pointers to objects of type nsIID is then declared. It is initialized explicitly with the DOM_CLASSINFO_MAP_ENTRY macro (see below).
在这个宏里面,[d]是个到sClassInfoData数组中某一项的引用。这个项是通过作为参数传给宏 的类来确定的。mProtoChainInterface成员指针被初始化成接口的IID的地址,这个接口被作为参数传递给 DOM_CLASSINFO_MAP_BEGIN宏。然后,声明了一个静态指针数组,指针指向nsIID类型的对象。它显式地用 DOM_CLASSINFO_MAP_ENTRY宏初始化。(见下)
There are two other similar macros:
下面是其他两个类似的宏:
#define DOM_CLASSINFO_MAP_BEGIN_NO_PRIMARY_INTERFACE(_class) _DOM_CLASSINFO_MAP_BEGIN(_class, nsnull, PR_TRUE)
This macro is used if the DOM class (for example XMLHTTPRequest) does not have any interface, yet you want the XMLHTTPRequest object to be available from JavaScript.
这个宏在DOM类(比如XMLHTTPRequest),没有任何接口时使用。如果你希望XMLHTTPRequest对象在JavaScript中可用的话。
#define DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(_class, _interface) _DOM_CLASSINFO_MAP_BEGIN(_class, &NS_GET_IID(_interface), PR_FALSE)
This macro should be used for DOM classes that have no "leaf" interface. For example, there is no HTMLSpanElement in the W3C DOM specification. Therefore, the first interface in the prototype chain for the span element is HTMLElement. However we do want to be able to access HTMLSpanElement to modify it. This macro allows you to do that. See {{ Bug(92071) }} for more information. Let's now see how to specify the interfaces available from JavaScript for a particular DOM class.
这个宏在DOM类没有"叶子"接口的时候使用。比如,在W3C DOM 中没有HTMLSpanElement的定义。因此在原型链中的第一个接口会是HTMLElement。但是,我们确实希望能够拿到 HTMLSpanElement并修改它。这个宏让你可以干这些。参见bug 92071。现在我们来看看如何为一个特定的DOM类声明它能够在JavaScript中使用的接口。
#define DOM_CLASSINFO_MAP_ENTRY(_if) &NS_GET_IID(_if),
The array of pointers interface_list is filled with the addresses of the IID's of all the interfaces passed as arguments to the macro. In our Window example, the interfaces are nsIDOMWindow, nsIDOMJSWindow, nsIDOMWindowInternal, nsIDOMEventReciever, nsIDOMEventTarget, nsIDOMViewCSS, and nsIDOMAbstractView. Please see Section 1.2 on interface flattening for an explanation of the use of these interfaces. The initialization for a class is finished by the DOM_CLASSINFO_MAP_END macro.
这个指针数组interface_list里面保存了所有作为参数传递给宏的接口的IID的地址。在我们的 Window范例中,这些接口是nsIDOMWindow, nsIDOMJSWindow, nsIDOMWindowInternal, nsIDOMEventReciever, nsIDOMEventTarget, nsIDOMViewCSS, 还有 nsIDOMAbstractView。参见1.2节(接口平坦化)中对这些接口使用的解释。对一个类的初始化工作以 DOM_CLASSINFO_MAP_END宏结束。
#define DOM_CLASSINFO_MAP_END nsnull }; d.mInterfaces = interface_list; }
The interface_list array is terminated by a null pointer. The line d.mInterfaces = interface_list assigns to mInterfaces the address of the first element of the interface_list array, which is itself a pointer. mInterfaces is thus correctly a pointer to a pointer to an object of type nsIID.
interface_list数组以空指针结尾。d.mInterfaces = interface_list这一行把interface_list数组的第一个元素的地址赋给mInterfaces。mInterfaces本身也是 一个指针。mInterfaces现在被正确地初始化成了一个指向一个nsIID类型的对象的指针的指针。
To define the jsvals, Init() simply calls DefineStaticJSVals(). To register the class names and class protos, Init() simply calls RegisterClassProtos and RegisterClassNames. This process might be described in a later document. Finally, sIsInitialized is set to true. Init() returns NS_OK if everything went fine.
为了定义jsvals,Init()简单地调用DefineStaticJSVals()。为了注册类名和 类原型。Init()简单地调用RegisterClassProtos和RegisterClassNames。这个流程会在稍后的文档中被描述。最 后,sIsInitialized被置真。Init()返回 NS_OK表示一切顺利。
There are two versions of this function. The first one takes an ID as argument, the second takes a Data struct as argument. This function is very important so let's take a closer look at it. Here is the function definition.
这个函数有两个版本。第一个接收一个ID作为参数,第二个接收一个数据结构作为参数。这个函数很重要,所以我们仔细看看它。这是函数定义:
nsIClassInfo* nsDOMClassInfo::GetClassInfoInstance(nsDOMClassInfoID aID) { if(!sIsInitialized) { nsresult rv = Init(); } if(!sClassInfoData[aID].mCachedClassInfo) { nsDOMClassInfoData &data = sClassInfoData[aID]; data.mCachedClassInfo = data.u.mConstructorFptr(&data); NS_ADDREF(data.mCachedClassInfo); } return sClassInfoData[aID].mCachedClassInfo; }
Here is the short explanation:
这是简短版解释:
This method returns the mCachedClassInfo member of the nsDOMClassInfoData structure that corresponds to aID in the sClassInfoData array, if it exists, i.e. if this method has been called before. If it is called for the first time however, mCachedClassInfo is still a null pointer, and the function will create a new instance of the relevant helper class, and cache it in the mCachedClassInfo pointer, then return it.
这个函数按照对应的aID,从sClassInfoData数组中取出一个 nsDOMClassInfoData结构的对象,然后返回这个结构的一个成员mCachedClassInfo。如果这是第一次调 用,mCachedClassInfo还是空指针,这个函数会创建一个对应的帮助类,然后将mCachedClassInfo指向这个缓存,然后返回它。
And for those interested, here the longer explanation.
对于那些感兴趣的人,这是加长版的解释:
The first time GetClassInfoInstance() is called, passing in an aID, mCachedClassInfo for that class will still be null. The body of the "if" clause is thus executed. We initialize "data" to be a reference to the nsDOMClassInfoData object that corresponds to the DOM class we want to "help". On the next line, there is a call to data.mConstructorFptr(aID), which, if you remember the introduction to Class Info, maps to the doCreate static member function of the relevant helper class. doCreate creates a new instance of the helper class, and returns a pointer to the nsIClassInfo interface, which is then assigned into mCachedClassInfo. mCachedClassInfo is AddRef'ed to keep it from being destroyed without our permission. Finally it is returned.
GetClassInfoInstance()第一次被调用的时候,一个aID被传进来,这个类的 mCachedClassInfo还是空的,if语句的本体被执行。我们把data初始化成一个nsDOMClassInfoData对象的引用。这个对 象对应于我们想要“帮助”的DOM类。下一行,我们调用了data.mConstructorFptr(aID),这一行,如果你记得Class Info的介绍的话,被映射成对应的帮助类的doCreate静态成员变量。doCreate创建帮助类的一个实例,然后返回一个到 nsIClassInfo接口的指针。这个指针被赋值给mCachedClassInfo。mCachedClassInfo被增加一个引用,以避免不经 我们的允许被删除。最后这个指针被返回。
On subsequent calls to this function with the same aID passed in, mCachedClassInfo will still be there, and thus the creation of a new helper class will not be necessary.
下次这个函数被调用(用同一个aID参数),mCachedClassInfo还在那儿,所以就不用再创建新的帮助类了。
"Where is GetClassInfoInstance used and why should I use it", would be an excellent question for now. The short answer is, "to implement the QueryInterface for nsIClassInfo". Indeed, a QueryInterface to nsIClassInfo cannot be implemented the same way as other interfaces. If you don't like macros, you can see the full QueryInterface implementation in {{ Source("content/xml/content/src/nsXMLElement.cpp", "nsXMLElement.cpp") }}. Two other GetClassInfoInstance member functions are defined in Mozilla, as member of class nsContentUtils and class nsDOMSOFactory. Both of these methods end up calling nsDOMClassInfo::GetClassInfoInstance so there is no real point in documenting them further. GetClassInfoInstance is used in the NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO macro, which is used to implement QueryInterface for the nsIClassInfo interface in {{ LXRSearch("ident", "i", "NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO", "most of the DOM classes") }}, and in the NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO macro, which is used to implement QueryInterface for the nsIClassInfo interface in most {{ LXRSearch("ident", "i", "NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO", "global object properties") }}.
"GetClassInfoInstance在哪儿用到,为什么我需要用它"现在可能是个好问题。简短的回 答是:“为nsIClassInfo实现QueryInterface”。实际上,nsIClassInfo的QueryInterface方法不能象其 他接口那样实现。如果你不喜欢宏,你可以看看nsXMLElement.cpp中QueryInterface的完整实现。Mozilla中定义了另外两 个GetClassInfoInstance成员函数,作为nsContentUtils和nsDOMSOFactory的成员。所有这些方法都不调 nsDOMClassInfo::GetClassInfoInstance了,所以现在讲这些也没啥意义了。GetClassInfoInstance 用在NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO宏里,在大部分DOM类中,这个宏用来为 nsIClassInfo接口实现QueryInterface方法。这个函数还用在 NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO 宏里,这个宏用来为大部分全局对象属性实现QueryInterface方法。
I think that's all there is to say about this function. If you think of something else don't hesitate to contact me, as usual.
我觉得这就是关于这个函数所有要讲的事了。如果你觉得还有什么要讲的话,请象平时一样,不要犹豫,过来找我。
This Section will describe in detail the absolutely horrific GlobalResolve() member function of the nsWindowSH helper, as an example of those functions. This is not for the faint of heart, and is not absolutely necessary, so you might want to skip this Section if you don't have too much time (and I suppose you don't).
作为这些功能的一个范例,这一节将讲到nsWindowSH帮助类的GlobalResolve()成员函数的极其可怕的细节。这不是为了惊吓你可怜的小心脏,也不是非常必要,所以如果你时间不够的话,也许你会希望跳过本节(当然我希望你不会)。
Warning: this document has not yet been reviewed by the DOM gurus, it might contain some errors. Specifically, due to some changes that happened around, April 2002, some things that were not possible before are now possible. I will try to update this guide as soon as possible. Please send any comment to Fabian Guisset.
什么时候DOMClassInfo会被用到
Example of functionality implemented using DOMClassInfo:
使用DOMClassInfo完成的功能:
如何为已有的DOM对象添加一个新的接口
For this Section, we will use the simple example of the DOMImplementation DOM object. This is a real-world case that was used to solve bug 33871 (the patch is not checked in yet, as of writing this document). The problem is the following: We have to add a new HTMLDOMImplementation interface to the DOMImplementation DOM object. The DOMImplementation object is used when one does, in JS, document.implementation
. This object already implements the DOMImplementation interface, but DOM2 HTML says it should also implement the HTMLDOMImplementation interface, so here we go. The C++ implementation is in nsDocument.cpp. The first step is of course to do the C++ implementation of the interface, which is described in the intro to XPCOM document.
本节中,我们会使用DOM对象DOMImplementation的一个简单的例子。这是一个真实的案例, 用来解决bug 33871(截止到写这篇文档的时候,patch还没有上传)。这个问题是这样的:我们必须为DOM对象DOMImplementation添加一个新的 HTMLDOMImplementation接口。DOMImplementation在人在JS中调用document.implementation 时被用到。这个对象已经实现了DOMImplementation接口,但是DOM2 HTML标准说它还应该实现HTMLDOMImplementation接口,这就是我们要干的活。它的C++实现是nsDocument.cpp,第一 步当然是象在XPCOM文档中的介绍那样,用C++实现这个接口。
Let's assume nsDOMImplementation now implements the nsIDOMHTMLDOMImplementation interface (look in bug 33871 if you want to know how to do that). We want to expose this interface to JavaScript (otherwise only XPCOM callers will be able to access this interface). To do that, we have to add it to the DOMClassInfo of the DOMImplementation DOM object.
不妨假设nsDOMImplementation现在实现了 nsIDOMHTMLDOMImplementation接口(如果你要知道怎么做到这一点的话,参见bug 33871),我们希望把这个接口暴露给JavaScript(否则只有XPCOM调用者才能访问到这个接口)。为了做到这一点,我们必须把它加到DOM 对象nsIDOMHTMLDOMImplementation的DOMClassInfo里面去。
document.implementation
object (the main goal) using the automatic interface flattening brought to you by nsDOMClassInfo and XPConnect.document.implementation instanceof HTMLDOMImplementation
will work (returns true)HTMLDOMImplementation.prototype
will be accessible and modifyable 好处
#include "nsIDOMHTMLDOMImplementation.h"
.1224 DOM_CLASSINFO_MAP_BEGIN(DOMImplementation, nsIDOMDOMImplementation)
1225 DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLDOMImplementation)
要做些什么:
1. 在nsDOMClassInfo.cpp加入新接口的定义:
#include "nsIDOMHTMLDOMImplementation.h".
把它放在你觉得最合适的地方
2. 找到所有被相关的DOM对象实现的接口被实现的地方。在 nsDOMClassInfo::Init()方法里。
3. 对于DOMImplementation来说, 它大概在1220行 (本文档写作时):
1224 DOM_CLASSINFO_MAP_BEGIN(DOMImplementation, nsIDOMDOMImplementation)
下面几行声明了DOMImplementation对实现了nsIDOMDOMImplementation接口。
4. 向DOMClassInfo定义添加新接口,对于我们来说,应该是:
1225 DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLDOMImplementation)
5. 把新接口添加到makefiles, manifests,等等。
6. 重新编译。
7. 手动修改components.reg,如果你打开了编译优化选项。
8. 惊叹DOMClassInfo的美妙之处。
如何向JavaScript暴露一个新的DOM对象
Let's now go a step further. Not only do we want to add a new interface to an object, but we also want to expose a completely new object to JavaScript. DOMClassInfo does almost everything for you, from prototypes to implementing a default ToString() method on your object.
现在让我们更进一步。现在我们不但想要向一个对象添加一个新的接口,我们还想向JavaScript暴露一个新对象。DOMClassInfo几乎为你完成了所有的事,从要实现的原型到为你的对象实现一个默认的ToString()方法。
We will again take the example of the DOMImplementation object. It is accessible using document.implementation
. It is defined in the W3C DOM Level 1 Core spec. The requirements include that the global constructor DOMImplementation
be accessible, that the ToString() method called on an instance of a DOMImplementation return "DOMImplementation", and that it implements the following methods: hasFeature() (DOM1), createDocumentType() and createDocument() (DOM2).
这次我们还用DOMImplementation对象为例。它可以调用 document.implementation获取。它定义在W3C DOM级别1核心规范中。对它的要求包括它的全局构造函数DOMImplementation必须是可访问的,对一个DOMImplementation 实例调用ToString()必须返回"DOMImplementation",它还必须实现如下方法:hasFeature() (DOM1), createDocumentType() 还有 createDocument() (DOM2).
我们要做什么:
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(dom_object_name)
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(DOMImplementation)
NS_DEFINE_CLASSINFO_DATA(dom_object_name, scriptable_helper_class, scriptable_flags)
For the DOMImplementation object, the lines would be:
NS_DEFINE_CLASSINFO_DATA(DOMImplementation, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS)The place where you have to add the DOMClassInfo in that array should be obvious. If it is not, ask Johnny Stenback.
DOM_CLASSINFO_MAP_BEGIN(dom_object_name, dom_object_main_interface) DOM_CLASSINFO_MAP_ENTRY(interface1) DOM_CLASSINFO_MAP_ENTRY(interface2) ... DOM_CLASSINFO_MAP_END
For the DOMImplementation object, the lines would be:
DOM_CLASSINFO_MAP_BEGIN(DOMImplementation, nsIDOMDOMImplementation) DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMImplementation) DOM_CLASSINFO_MAP_ENDThe
interface1
, interface2
, ... arguments are the name of the interfaces implemented by the DOM object AND exposed to JavaScript. The internal interfaces should NOT be a part of this list.1.用C++实现你的对象。这不在本文档的范围,你最好拷贝已经存在的代码。一个DOM对象是一个具有DOMClassInfo的简单的XPCOM对象。 在我们的例子中,实现类是nsDOMImplementation(在nsDocument.cpp)中。它实现了 nsIDOMDOMImplementation接口(它包含我们上面提到过的三个方法)。
2.2.修改你的XPCOM对象的QueryInterface实现,以便包含DOMClassInfo数据。在QueryInterface实现的末尾加入下面几行:
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(dom_object_name)
对于DOMImplementation对象,这行会是:
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(DOMImplementation)
What does it do? It's the QueryInterface implementation for the nsIClassInfo interface, which is requested internally by XPConnect. Basically it will create an instance of the scriptable helper class for this DOM object. More on this subject in the rest of this document.
这行是干嘛的?它是nsIClassInfo接口的QueryInterface实现,这个也是XPConnect内部要求的。基本上,它会为DOM对象创建一个Scriptable的帮助类。这个主题的更多内容见本文档的余下部分。
3. 把DOM对象DOMClassInfo加到sClassInfoData数组 (nsDOMClassInfo.cpp):
NS_DEFINE_CLASSINFO_DATA(dom_object_name, scriptable_helper_class,
scriptable_flags)
对于DOMImplementation对象, 这几行应该是:
NS_DEFINE_CLASSINFO_DATA(DOMImplementation, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
你应该很清楚应该把DOMClassInfo加到数组的哪个位置,如果搞不清的话,问Johnny Stenback。
4. 把DOM对象的DOMClassInfo加到nsDOMClassInfo::Init()方法里(nsDOMClassInfo.cpp):
DOM_CLASSINFO_MAP_BEGIN(dom_object_name, dom_object_main_interface)
DOM_CLASSINFO_MAP_ENTRY(interface1)
DOM_CLASSINFO_MAP_ENTRY(interface2)
...
DOM_CLASSINFO_MAP_END
对于DOMImplementation对象, 这几行应该是:
DOM_CLASSINFO_MAP_BEGIN(DOMImplementation, nsIDOMDOMImplementation)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMImplementation)
DOM_CLASSINFO_MAP_END
interface1, interface2, ... 参数是DOM对象实现的接口以及向JavaScript暴露的接口的并集。内部接口不应该包含在这个接口中。
5. 用#include包含相关文件让它能构建成功,修改makefiles,等等。 确保它能在所有平台下通过编译:-P
6. 如果你用的是个已有的scriptable帮助类的话,你要做的所有事就是构建,修改components.reg(如果你打开了编译优化选项),然后运行。一切都会运作良好。
7. 如果你需要一个新的scriptable帮助类,你就得实现它。
如何覆盖XPConnect对DOM对象的默认处理
XPConnect implements default behaviors for XPCOM objects in general, and for DOM objects in particular. DOMClassInfo allows the implementor to override this default behavior using the nsIXPCScriptable interface. Before we begin, please take a look at the nsIXPCScriptable.idl file. It defines a set of constants, called the "scriptable flags", and a set of functions, like NewResolve(), SetProperty(), ... Each flag corresponds to one function. For example, nsIXPCScriptable::WANT_NEWRESOLVE means that we want to implement the NewResolve() function. The important thing to grasp is that each function corresponds to an event in the life of the DOM object. For example, the SetProperty() function is called automatically by XPConnect when, in JS, the client tries to set a property on this DOM object. This is how we can override the default "set this property on this object" XPConnect behavior. For more information about each nsIXPCScriptable function, please see the nsIXPCScriptable documentation.
XPConnect为XPCOM对象实现默认行为,这种实现是一般化的。但对于DOM对象,这实现是特定的。DOMClassInfo允许用 nsIXPCScriptable接口的实现来覆盖默认行为。在我们开始前,请看看nsIXPCScriptable.idl文件。它定义了一组常量,叫 做“scriptable标志位”,还有一组函数,比如NewResolve(),SetProperty()...每个标志位对应一个函数。比 如,nsIXPCScriptable::WANT_NEWRESOLVE表示我们希望实现NewResolve()函数。重要的是,要清楚每个函数对应 着DOM对象声明周期中的一个事件。比如,当在js里,客户端试图给DOM对象赋值时,XPConnect会自动调用SetProperty()方法。所 以我们能覆盖默认的“给对象属性赋值”这个XPConnect行为。要获取每个nsIXPCScriptable函数的信息,请参考 nsIXPCScriptable文档。
To illustrate the use of nsIXPCScriptable and scriptable helper functions, we will take the example of the "location" property of the window object. window.location
is a DOM object of type "Location". However a common technique is to do window.location = "http://mozilla.org"
instead of the correct window.location.href = "http://mozilla.org"
. So, we have to override the default behavior of "setting the location property on the window object". The default behavior would be that XPConnect expects a nsIDOMLocation object. However it would be passed a JS string. A bad conversion exception would be thrown.
为了演示nsIXPCScriptable的用法和scriptable帮助类的功能,我们会举 window对象的location属性为例。window.location是个“Location”类型的DOM对象。但是,一个常用的技法是,使用 window.location = "http://mozilla.org",而不是正确的用法:window.location.href = "http://mozilla.org"。所以,我们必须覆盖默认的“设置window对象的location属性”行为。默认的行为 是,XPConnect期望得到一个nsIDOMLocation对象,但是,它会拿到一个JS字符串,一个错误转型的异常将被抛出。
Before we start looking at the implementation of the nsIXPCScriptable interface, the implementor needs the following information:
在我们开始看nsIXPCScriptable接口的实现前,实现者必须知道下列信息:
* 它关联到什么DOM object
* 什么行为希望被覆盖
* 应该发生什么
For our example, it is the window object. The action is setting a property. What should happen is that setting .location should set .location.href. With that information in hand, we can start coding.
对于我们的范例,关联的对象是window对象。行为是设置属性。希望发生的事情是对.location的设置应该被设置到.location.href上。这一信息在手,我们可以开始编码了。
我们需要做什么
1.在sClassInfoData数组中找到DOM对象的ClassInfo数据。在我们的例子中,是Window对象。如前所述,三个传递给宏的参数是DOM对象名称,scriptable帮助类,还有scriptable标志。
2.scriptable标志告诉你应该为DOM对象实现那些nsIXPCScriptable接口方法。如果你需要的标志位已经在那儿了,就跳到下一步,否则,把它加到标志列表中。
3。记住这个对象的scriptable帮助类的名字。对于大多数对象,会是nsDOMGenericSH类,那是nsDOMClassInfo类的一个 别名。如果你的DOM对象不需要特殊处理,那你的scriptable帮助类就会是nsDOMGenericSH,如果你需要特殊处理,翻到你的帮助类的 实现部分。在我们的例子中,是nsWindowSH类。
4.如果帮助类已经实现了你需要的nsIXPCScriptable函数,跳到下一步,否则,使用nsIXPCScriptable接口中描述的参数实现该函数。
5。现在到了有趣的部分了。不幸的是,不大可能描述scriptable帮助类的所有用法。你可能需要使用你的编程技巧或者/还有拷贝已有的代码。不管怎么说,我们会描述我们例子的实现,window.location属性。
window.location实现
Overriding the setter of a property requires two scriptable flags: WANT_NEWRESOLVE and WANT_SETPROPERTY. NewResolve() will define the property on the object using the JS API, the second one will map .location to .location.href. As of writing this document, the code in nsWindowSH::NewResolve() looks like this: (nsDOMClassInfo.cpp)
覆盖属性的获取方法需要两个scriptable标志:WANT_NEWRESOLVE 和 WANT_SETPROPERTY,NewResolve()会用JS API定义这个对象的属性。第二个会把.locaiton映射到.locaiton.href。截止到写这篇文档的时 候,nsWindowSH::NewResolve() 中的代码看起来是这样:(nsDOMClassInfo.cpp)
3553 if (flags & JSRESOLVE_ASSIGNING) { // Only define the property if we are setting it. 3554 if (str == sLocation_id) { // Setting the location property. 3555 nsCOMPtr<nsIDOMWindowInternal> window(do_QueryInterface(native)); 3556 NS_ENSURE_TRUE(window, NS_ERROR_UNEXPECTED); 3557 3558 nsCOMPtr<nsIDOMLocation> location; 3559 rv = window->GetLocation(getter_AddRefs(location)); 3560 NS_ENSURE_SUCCESS(rv, rv); // Use the DOM to get the Location object of the window object. 3561 3562 jsval v; 3563 3564 rv = WrapNative(cx, obj, location, NS_GET_IID(nsIDOMLocation), &v); // This XPConnect method creates a wrapper for the Location object on the // Window object. 3565 NS_ENSURE_SUCCESS(rv, rv); 3566 3567 if (!::JS_DefineUCProperty(cx, obj, ::JS_GetStringChars(str), 3568 ::JS_GetStringLength(str), v, nsnull, 3569 nsnull, 0)) { 3570 return NS_ERROR_FAILURE; 3571 } // This JS API call defines the "location" property on the window object, its // value being the XPConnect wrapper for the Location object. 3572 3573 *objp = obj; 3574 3575 return NS_OK; 3576 }
This is the first step. It is required to have the getter for .location work as well, but that's another story. The second step is to map .location to .location.href in nsWindowSH::SetProperty()
这个是第一步,另外还需要让.location的getter函数干活,但是那是另一回事了。第二步是在nsWindowSH::SetProperty() 里把.location映射到.location.href。
2894 if (str == sLocation_id) { // Setting the location property 2895 JSString *val = ::JS_ValueToString(cx, *vp); 2896 NS_ENSURE_TRUE(val, NS_ERROR_UNEXPECTED); // Convert the value assigned to location (i.e. the url) to a JSString. 2897 2898 nsCOMPtr<nsISupports> native; 2899 wrapper->GetNative(getter_AddRefs(native)); // Get the pointer to the content object that was wrapped. 2900 2901 nsCOMPtr<nsIDOMWindowInternal> window(do_QueryInterface(native)); 2902 NS_ENSURE_TRUE(window, NS_ERROR_UNEXPECTED); // QueryInterface to have a nsIDOMWindowInternal pointer to call // GetLocation() on it. 2903 2904 nsCOMPtr<nsIDOMLocation> location; 2905 nsresult rv = window->GetLocation(getter_AddRefs(location)); 2906 NS_ENSURE_SUCCESS(rv, rv); // Get the Location object for this window. 2907 2908 nsDependentString href(NS_REINTERPRET_CAST(PRUnichar *, 2909 ::JS_GetStringChars(val)), 2910 ::JS_GetStringLength(val)); // Convert the JSString to a string that can be passed to SetHref() 2911 2912 rv = location->SetHref(href); 2913 NS_ENSURE_SUCCESS(rv, rv); // After this, we effectively mapped .location to .location.href 2914 2915 return WrapNative(cx, obj, location, NS_GET_IID(nsIDOMLocation), vp); // Create a wrapper for the location object with vp (the url) as value. 2916 }
It's that simple. And the possibilities are endless.
就这么简单,但可能性是无限的。
This chapter has not been written yet. If you want to help please contact me!
This chapter has not been written yet. If you want to help please contact me!
{{ languages( { "ja": "ja/Mozilla_DOM_Hacking_Guide" } ) }}