Web IDL in Blink
在做Chromium浏览器定制化需求时常常需要扩展JS对象以增加js的功能. 在javascript这门编程语言的概念里,一切皆为对象,变量,函数等等一切皆为对象,没有类的概念,javascript是一门动态语言,它的主要特点是对象的类型和内容是在运行时决定的,是可以不断变化的. 在javascript的世界里,根对象是global对象,所有的一切对象皆为global的子孙对象.在浏览器中,这个global对象的表现为window对象. 在webkit中已实现的对象称为javascript内部对象,内部对象又分为本地对象(由用户在js代码中使用new方法初化始对象之后才能使用)和内置对象(直接在blink初始化好的,如window对象的alert,confirm等).
一: 现在以扩展一个javascript Test本地对象为例子说明如何扩展webkit js对象:
实现目标: 以下js代码能够顺利执行
var test = new Test(“aaa”,”bbb”,”ccc”);
test.setValue(“value”);
alert(“ip = “ + test.address + “,mask = “ + test.mask + “,gateway =”+test.gateway + “,value = “ + test.value);
实现步骤:
1.在webkit core目录(浏览器根目录/src/wibkit/Source/core)相应的位置分别新建Test.idl,Test.h,Test.cpp文件,文件名和接口名一样,除非用扩展属性[ImplementedAs=...] 改变。
在.gni文件中包含进去,如 core_idl_files.gni并在core.gypi中相应的位置将.idl和c文件包含进去.
有三种idl:
core 或者 modules,决定在哪个子树;
main接口或者依赖接口:部分接口没有.h,.cpp产生。
test接口:不会进入bingding
构建时产生的接口有第四种,generated 或者非静态
idl接口位置:core的位于core_idl_files
variable or in the core_dependency_idl_files
variable
C++: core_files
variable or an appropriate core_*_files
variable, depending on directory, or core_testing_files
if a testing interface
2..idl文件是连接js对象和c++对象的媒介,首先根据你要设计的js对象的属性和方法来确定.idl对象,再根据.idl文件来写相应的.h 和 .cpp文件.
现在要新增一个 本地js对象,对象名称为Test,它的构造方法是传入三个字符串参数,它有三个可读写字符串属性adress,mask,gateway,一个只读字符串属性value,一个方法为setValue.
那么Test.idl文件的内容为.
[
Constructor(DOMString ip,DOMString mask,DOMString gateway),
] interface Test {
readonly attribute DOMString value;
attribute DOMString address;
attribute DOMString mask;
attribute DOMString gateway;
void setValue(DOMString value);
};
在window.idl中加上:
attribute TestConstructor Test; //注意这里的Test对应的javascript中的Test,如果这里是myTest,那么js代码就是 new myTest(...)
现在写Test 对应的c++类,它需要实现 static Test * create(String & str1,String &str2,String& str3)供外部调用. 只读属性 value 对应的方法 String value() const; 可读写属性对应的方法 String address() const; void setAddress(const String& ip); //(注意这里的函数名要与前面.idl文件声明的属性名称一致,设置属性的函数需要在前面加set并将第一个字母大写,如setAdress函数名一致),mask , gateway属性如上. .idl中声明的方法在c++类中的方法一致,只需要照旧声明一个方法 void setValue(String &value);就行. 好了,只需要把这些接口实现所有工作就完成了. 接下来就可以编译运行测试了.
类的实现代码如下所示:
#ifndef TEST_H
#define TEST_H
#include "wtf/PassRefPtr.h"
#include "core/CoreExport.h"
#include "bindings/core/v8/ScriptWrappable.h"
#include "wtf/RefCounted.h"
#include "wtf/text/WTFString.h"
namespace blink {
class Test : public RefCountedWillBeGarbageCollectedFinalized<Test>,public ScriptWrappable{
DEFINE_WRAPPERTYPEINFO();
public:
virtual ~Test();
static PassRefPtrWillBeRawPtr<Test> create(){
return create( String(""), String(""), String(""));
}
static PassRefPtrWillBeRawPtr<Test> create(String ip,String mask,String gateway );
String address() const;
String mask() const;
String gateway() const;
void setAddress(const String& ip){mIp=ip;}
void setMask(const String& mask){mMask=mask;}
void setGateway(const String& gateway){mGateWay=gateway;}
String value() const;
void setValue(const String& value){mValue=value;}
private:
explicit Test(String,String,String);
String mValue;
String mIp;
String mMask;
String mGateWay;
};
}
#endif
二 webkit扩展js内置对象的方法
还是以上面的 Test为例:
1.在Test.idl中将[
Constructor(DOMString ip,DOMString mask,DOMString gateway),
] 去除.
2. 在 window.idl 将原来的声明改为 [Replaceable] readonly attribute Test Test;
3. DomWindow.h中增加Test的虚函数构造方法声明 virtual Test* test() const {return NULL;}
4.在LocalDOMWindow.h中声明方法 Test* test() const override,新增变量mutable PersistentWillBeMember<Test> m_test;
5.在LocalDomWindow.cpp中对 test方法做实现
Test* LocalDOMWindow::test() const
{
if (!m_test)
m_test = Test::create();
return m_test.get();
}
三: 实现原理
Chromium编译系统会首先去解析test.idl,通过python脚本解析.idl文件生成相应的v8test.cpp,v8test.h. v8test对象 会调用到我们手动写的test对象. v8test.cpp对象绑定了js对象相应的属性和方法,所以在运行时V8解析javascript代码时能够找到找到关联的c++对象上. 运行Test 的js代码相应的调用步骤是: V8引擎解析js代码-> v8Test类->Test类.
Web IDL in Blink
Blink developers (non-bindings development): for general IDL use, see Web IDL interfaces; for configuring bindings, see Blink IDL Extended Attributes; for IDL dictionaries use, see IDL dictionaries in Blink.
OverviewWeb IDL is a language that defines how Blink interfaces are bound to V8. You need to write IDL files (e.g. xml_http_request.idl, element.idl, etc) to expose Blink interfaces to those external languages. When Blink is built, the IDL files are parsed, and the code to bind Blink implementations to V8 interfaces automatically generated. This document describes practical information about how the IDL bindings work and how you can write IDL files in Blink. The syntax of IDL files is fairly well documented in the Web IDL spec, but it is too formal to read :-) and there are several differences between the Web IDL spec and the Blink IDL due to implementation issues. For design docs on bindings generation, see IDL build and IDL compiler. For Blink developers, the main details of IDLs are the extended attributes, which control implementation-specific behavior: see Blink IDL Extended Attributes for extensive details. Our goal is to converge Blink's IDL and Web IDL. The grammar is almost identical; see below. Basics of IDLHere is an example of IDL files: [CustomToV8] interface Node { const unsigned short ELEMENT_NODE = 1; attribute Node parentNode; [TreatReturnedNullStringAs=Null] attribute DOMString nodeName; [Custom] Node appendChild(Node newChild); void addEventListener(DOMString type, EventListener listener, optional boolean useCapture); }; Let us introduce some terms:
The key points are as follows:
The valid extended attributes depend on where they attach: interfaces and methods have different extended attributes. A simple IDL file template looks like: interface INTERFACE_NAME { const unsigned long value = 12345; attribute Node node; void func(long argument, ...); }; With extended attributes, this looks like: [ EXTATTR, EXTATTR, ..., ] interface INTERFACE_NAME { const unsigned long value = 12345; [EXTATTR, EXTATTR, ...] attribute Node node; [EXTATTR, EXTATTR, ...] void func([EXTATTR, EXTATTR, ...] optional [EXTATTR] long argument, ...); }; SyntaxBlink IDL is a dialect of Web IDL. The lexical syntax is identical, but the phrase syntax is slightly different. Implementation-wise, the lexer and parser are written in PLY (Python lex-yacc), an implementation of lex and yacc for Python. A standard-compliant lexer is used (Chromium tools/idl_parser/idl_lexer.py). The parser (Blink bindings/scripts/blink_idl_parser.py) derives from a standard-compliant parser (Chromium tools/idl_parser/idl_parser.py). Blink deviations from the Web IDL standard can be seen as the BNF production rules in the derived parser. Style Style guidelines are to generally follow Blink style for C++, with a few points highlighted, addenda, and exceptions. These are not enforced by a pre-submit test, but do assist legibility:
[ A, B /* No trailing commas on the last extended attribute */ ] interface Foo { ... }; interface Bar { ... };
getter DOMString (unsigned long index); // Not: DOMString(unsigned long index)
// Indexed property operations getter DOMString (unsigned long index); setter DOMString (unsigned long index, DOMString value); deleter boolean (unsigned long index); // Named property operations getter DOMString (DOMString name); setter DOMString (DOMString name, DOMString value); deleter boolean (DOMString name); SemanticsWeb IDL exposes an interface to JavaScript, which is implemented in C++. Thus its semantics bridge these two languages, though it is not identical to either. Web IDL's semantics are much closer to C++ than to JavaScript – in practice, it is a relatively thin abstraction layer over C++. Thus C++ implementations are quite close to the IDL spec, though the resulting interface is somewhat unnatural from a JavaScript perspective: it behaves differently from normal JavaScript libraries.
TypesSee: Web IDL types.
Primitive types in Web IDL are very close to fundamental types in C++ (booleans, characters, integers, and floats), though note that there is no
int type in Web IDL (specs usually use long instead).undefined and nullJavaScript has two special values,
undefined and null , which are often confusing and do not fit easily into C++. Indeed, precise behavior of undefined in Web IDL has varied over time and is under discussion (see W3C Bug 23532 - Dealing with undefined).Behavior on
undefined and null MUST be tested in web tests, as these can be passed and are easy to get wrong. If these tests are omitted, there may be crashes (which will be caught by ClusterFuzz) or behavioral bugs (which will show up as web pages or JavaScript libraries breaking).For the purposes of Blink, behavior can be summarized as follows:
Function resolutionWeb IDL has required arguments and optional arguments. JavaScript does not: omitted arguments have Thus if you have the following Web IDL function declaration: interface A { void foo(long x); }; ...the JavaScript However, in JavaScript the corresponding function can be called without arguments: function foo(x) { return x } foo() // undefined Note that To get similar behavior in Web IDL, the argument can be explicitly specified as For example, given an optional argument such as: interface A { void foo(optional long x); }; This results in a = new A(); a.foo() being legal, and calling the underlying Blink C++ function implementing For overloaded operations, the situation is more complicated, and not currently implemented in Blink (Bug 293561). See the overload resolution algorithm in the spec for details. Pragmatically, passing Passing interface A { void foo(optional long x); void foo(Node x); }; This results in a = new A(); a.foo(undefined) resolving to the first Note that Blink code implementing a function can also check arguments, and similarly, JavaScript functions can check arguments, and access the number of arguments via Warning: File organizationThe Web IDL spec treats the Web API as a single API, spread across various IDL fragments. In practice these fragments are
.idl files, stored in the codebase alongside their implementation, with basename equal to the interface name. Thus for example the fragment defining the Node interface is written in node.idl , which is stored in the third_party/blink/renderer/core/dom directory, and is accompanied by node.h and node.cc in the same directory. In some cases the implementation has a different name, in which case there must be an [ImplementedAs=...] extended attribute in the IDL file, and the .h/.cc files have basename equal to the value of the [ImplementedAs=...] .For simplicity, each IDL file contains a single interface or dictionary, and contains all information needed for that definition, except for dependencies (below), notably any enumerations, implements statements, typedefs, and callback functions.
DependenciesIn principle (as a matter of the Web IDL spec) any IDL file can depend on any other IDL file, and thus changing one file can require rebuilding all the dependents. In practice there are 4 kinds of dependencies (since other required definitions, like enumerations and typedefs, are contained in the IDL file for the interface):
In practice, what happens is that, when compiling a given interfaces, its partial interfaces and the other interfaces it implements are merged into a single data structure, and that is compiled. There is a small amount of data recording where exactly a member came from (so the correct C++ class can be called), and a few other extended attributes for switching the partial/implemented interface on or off, but otherwise it is as if all members were specified in a single
interface statement. This is a deep dependency relationship: any change in the partial/implemented interface changes the bindings for the overall (merged) interface, since all the data is in fact used.Bindings for interfaces in general do not depend on their ancestors, beyond the name of their immediate parent. This is because the bindings just generate a class, which refers to the parent class, but otherwise is subject to information hiding. However, in a few cases bindings depend on whether the interface inherits from some other interface (notably EventHandler or Node), and in a few cases bindings depend on the extended attributes of ancestors (these extended attributes are "inherited"; the list is compute_dependencies.INHERITED_EXTENDED_ATTRIBUTES, and consists of extended attributes that affect memory management). There is thus a shallow dependency on ancestors, specifically only on the ancestor chain and on inherited extended attributes, not on the other contents of ancestors. On the other hand, the dependencies on used interfaces – so-called cross dependencies – are generally shallow dependency relationships: the using interface does not need to know much about the used interface (currently just the name of the implementing class, and whether the interface is a callback interface or not). Thus almost all changes in the used interface do not change the bindings for the using interface: the public information used by other bindings is minimal. There is one exception, namely the IDL extended attribute validatorTo avoid bugs caused by typos in extended attributes in IDL files, the extended attribute validator was introduced to the Blink build flow to check if all the extended attributes used in IDL files are implemented in the code generator. If you use an extended attribute not implemented in code generators, the extended attribute validator fails, and the Blink build fails. A list of IDL attributes implemented in code generators is described in IDLExtendedAttributes.txt. If you want to add a new IDL attribute, you need to
Note that the validator checks for known extended attributes and their arguments (if any), but does not enforce correct use of the the attributes. A warning will not be issued if, for example,
[Clamp] is specified on an interface.TestsReference tests (run-bindings-tests)third_party/blink/tools/run_bindings_tests.py tests the code generator, including its treatment of extended attributes. Specifically, run_bindings_tests.py compiles the IDL files in bindings/tests/idls, and then compares the results against reference files in bindings/tests/results. For example, run_bindings_tests.py reads test_object.idl, and then compares the generated results against v8_test_object.h and v8_test_object.cc, reporting any differences. If you change the behavior of the code generator or add a new extended attribute, please add suitable test cases, preferably reusing existing IDL files (this is to minimize size of diffs when making changes to overall generated bindings). You can reset the run-bindings-tests results using the --reset-results option: third_party/blink/tools/run_bindings_tests.py --reset-results run_bindings_tests.py is run in a presubmit script for any changes to Source/bindings: this requires you to update test results when you change the behavior of the code generator, and thus if test results get out of date, the presubmit test will fail: you won't be able to upload your patch via git-cl, and the CQ will refuse to process the patch. The objective of run-bindings-tests is to show you and reviewers how the code generation is changed by your patch. If you change the behavior of code generators, you need to update the results of run-bindings-tests. Despite these checks, sometimes the test results can get out of date; this is primarily due to dcommitting or changes in real IDL files (not in Source/bindings) that are used in test cases. If the results are out of date prior to your CL, please rebaseline them separately, before committing your CL, as otherwise it will be difficult to distinguish which changes are due to your CL and which are due to rebaselining due to older CLs. Note that using real interfaces in test IDL files means changes to real IDL files can break run-bindings-tests (e.g., Blink r174804/CL 292503006: Oilpan: add [WillBeGarbageCollected] for Node., since Node is inherited by test files). This is ok (we're not going to run run_bindings_tests.py on every IDL edit, and it's easy to fix), but something to be aware of. It is also possible for run_bindings_tests.py to break for other reasons, since it use the developer's local tree: it thus may pass locally but fail remotely, or conversely. For example, renaming Python files can result in outdated bytecode (.pyc files) being used locally and succeeding, even if run_bindings_tests.py is incompatible with current Python source (.py), as discussed and fixed in CL 301743008.
Behavior testsTo test behavior, use web tests, most simply actual interfaces that use the behavior you're implementing. If adding new behavior, it's preferable to make code generator changes and the first actual use case in the same CL, so that it is properly tested, and the changes appear in the context of an actual change. If this makes the CL too large, these can be split into a CG-only CL and an actual use CL, committed in sequence, but unused features should not be added to the CG.
For general behavior, like type conversions, there are some internal tests, like bindings/webidl-type-mapping.html, which uses testing/type_conversions.idl. There are also some other IDL files in testing, like testing/internals.idl.
Where is the bindings code generated?By reading this document you can learn how extended attributes work. However, the best practice to understand extended attributes is to try to use some and watch what kind of bindings code is generated. If you change an IDL file and rebuild (e.g., with ninja or Make), the bindings for that IDL file (and possibly others, if there are dependents) will be rebuilt. If the bindings have changed (in ninja), or even if they haven't (in other build systems), it will also recompile the bindings code. Regenerating bindings for a single IDL file is very fast, but regenerating all of them takes several minutes of CPU time. In case of xxx.idl in the Release build, the bindings code is generated in the following files ("Release" becomes "Debug" in the Debug build). out/Release/gen/third_party/blink/renderer/bindings/{core,modules}/v8_xxx.{h,cc} Limitations and improvementsA few parts of the Web IDL spec are not implemented; features are implemented on an as-needed basis. See component:Blink>Bindings for open bugs; please feel free to file bugs or contact bindings developers (members of blink-reviews-bindings, or bindings/OWNERS) if you have any questions, problems, or requests. Bindings generation can be controlled in many ways, generally by adding an extended attribute to specify the behavior, sometimes by special-casing a specific type, interface, or member. If the existing extended attributes are not sufficient (or buggy), please file a bug and contact bindings developers! Some commonly encountered limitations and suitable workarounds are listed below. Generally limitations can be worked around by using custom bindings, but these should be avoided if possible. If you need to work around a limitation, please put a
TODO with the bug number (as demonstrated below) in the IDL so that we can remove the hack when the feature is implemented.Syntax error causes infinite loopSome syntax errors cause the IDL parser to enter an infinite loop (Bug 363830). Until this is fixed, if the compiler hangs, please terminate the compiler and check your syntax.
Type checkingThe bindings do not do full type checking (Bug 321518). They do some type checking, but not all. Notably nullability is not strictly enforced. See Bindings developmentMailing ListIf working on bindings, you likely wish to join the blink-reviews-bindings mailing list.See also
|
Web IDL interfaces
To implement a new Web IDL interface in Blink:
IDL
See Blink IDL: Style for style guide.
IDL files contain two types of data:
Note that if Blink behavior differs from the spec, the Blink IDL file should reflect Blink behavior. This makes interface differences visible, rather than hiding them in the C++ implementation or bindings generator.
Also as a rule, nop data should not be included: if Blink (bindings generator) ignores an IDL keyword or extended attribute, do not include it, as it suggests a difference in behavior when there is none. If this results in a difference from the spec, this is good, as it makes the difference in behavior visible.
Nulls and non-finite numbersTwo points to be careful of, and which are often incorrect in specs, particularly older specs, are nullability and non-finite values (infinities and NaN). These are both to ensure correct type checking. If these are incorrect in the spec – for example, a prose specifying behavior on non-finite values, but the IDL not reflecting this – please file a spec bug upstream, and link to it in the IDL file.
If null values are valid (for attributes, argument types, or method return values), the type MUST be marked with a ? to indicate nullable, as in
attribute Foo? foo; Note that for arguments (but not attributes or method return values), optional is preferred to nullable (see Re: removeEventListener with only one passed parameter...).
Similarly, IEEE floating point allows non-finite numbers (infinities and NaN); if these are valid, the floating point type –
float or double – MUST be marked as unrestricted as in unrestricted float or unrestricted double – the bare float or double means finite floating point.Union typesMany older specs use overloading when a union type argument would be clearer. Please match spec, but file a spec bug for these and link to it. For example:
// FIXME: should be void bar((long or Foo) foo); https://www.w3.org/Bugs/Public/show_bug.cgi?id=123 void bar(long foo); void bar(Foo foo); Also, beware that you can't have multiple nullable arguments in the distinguishing position in an overload, as these are not distinguishing (what does
null resolve to?). This is best resolved by using a union type if possible; otherwise make sure to mark only one overload as having a nullable argument in that position.Don't do this:
void zork(Foo? x); void zork(Bar? x); // What does zork(null) resolve to? Instead do this:
void zork(Foo? x); void zork(Bar x); ...but preferably this:
void zork((Foo or Bar)? x); Extended attributesYou will often need to add Blink-specific extended attributes to specify implementation details.
Please comment extended attributes – why do you need special behavior?
BindingsSee Web IDL in Blink.
C++Bindings code assumes that a C++ class exists, with methods for each attribute or operation (with some exceptions). Attributes are implemented as properties, meaning that while in the JavaScript interface these are read and written as attributes, in C++ these are read and written by getter and setter methods.
For cases where an IDL attribute reflects a content attribute, you do not need to write boilerplate methods to call
getAttribute() and setAttribute(). Instead, use the [Reflect] extended attribute, and these calls will automatically be generated inline in the bindings code, with optimizations in some cases. However, if you wish to access these attributes from C++ code (say in another class), not just from JavaScript, you will need to write a getter and/or a setter, as necessary.NamesThe class and methods have default names, which can be overridden by the
[ImplementedAs] extended attribute; this is strongly discouraged, and method names should align with the spec unless there is very good reason for them to differ (this is sometimes necessary when there is a conflict, say when inheriting another interface).Given an IDL file Foo.idl:
interface Foo { attribute long a; attribute DOMString cssText; void f(); void f(long arg); void g(optional long arg); }; ...a minimal header file Foo.h illustrating this is:
class Foo { public: int a(); void setA(int); String cssText(); void setCSSText(const String&); void f(); void f(int); void g(); void g(int); // Alternatively, can use default arguments: // void f(int arg=0); };
Type information ("ScriptWrappable")Blink objects that are visible in JavaScript need type information, fundamentally because JavaScript is dynamically typed (so values have type), concretely because the bindings code uses type introspection for dynamic dispatch (function resolution of bindings functions): given a C++ object (representing the implementation of a JavaScript object), accessing it from V8 requires calling the correct C++ binding methods, which requires knowing its JavaScript type (i.e., the IDL interface type).
Blink does not use C++ run-time type information (RTTI), and thus the type information must be stored separately.
There are various ways this is done, most simply (for Blink developers) by the C++ class inheriting
ScriptWrappable and placing DEFINE_WRAPPERTYPEINFO in the class declaration. Stylistically ScriptWrappable should be the last class, or at least after more interesting classes, and should be directly inherited by the class (not indirectly from a more distant ancestor).Explicitly:
Foo.h:
#ifndef Foo_h #define Foo_h #include "bindings/v8/ScriptWrappable.h" namespace WebCore { class Foo FINAL : /* maybe others */ public ScriptWrappable { DEFINE_WRAPPERTYPEINFO();
// ... }; } // namespace WebCore #endif Foo_h In case of C++ inheritance, it's preferable to avoid inheriting ScriptWrappable indirectly, most simply because this creates overhead on a redundant write. In many cases this can be avoided by having an abstract base class that both concrete classes inherit. Stylistically, FIXME
However, in some cases – notably if both a base class and a derived class implement JS interface types (say, if there is IDL inheritance and the C++ inheritance is the same) – you will need to call
ScriptWrappable::init both in the base class and the derived class.Thus, to avoid this:
Foo.h:
class Foo FINAL : public Bar, public ScriptWrappable { /* ... */ };Bar.h:
class Bar : public ScriptWrappable { /* ... */ }; ...instead use an abstract base class, and have both concrete classes inherit
ScriptWrappable directly:Foo.h:
class Foo FINAL : public FooBarBase, public ScriptWrappable { /* ... */ };Bar.h:
class Bar FINAL : public FooBarBase, public ScriptWrappable { /* ... */ };FooBarBase.h:
class FooBarBase { /* ... */ }; History (ScriptWrappable)
Garbage CollectionBuildYou need to list the
.idl file and .h/.cpp files in the correct GN variable so that they will be built (bindings generated, Blink code compiled.) IDL files to be processed are listed in .gni (GN Include) files. For core files, this is core_idl_files.gni.There are 3 dichotomies in these
.idl files, which affect where you list them in the build:
For core interfaces, the IDL files are listed in the
core_idl_files variable or in the core_dependency_idl_files variable, if the IDL file is a partial interface or the target (right side of) an implements statement. This distinction is because partial interfaces and implemented interfaces do not have their own bindings generated, so these IDL files are not directly compiled.Testing files are listed in the
core_testing_idl_files variable instead; there are currently no core testing dependency files.The C++ files should be listed in the
core_files variable or an appropriate core_*_files variable, depending on directory, or core_testing_files if a testing interface.Modules files are analogous, and placed in modules_idl_files.gni. There are currently no modules testing interface files, but there are modules testing dependency files, which are listed in
modules_dependency_idl_files and modules_testing_files .TestsMake sure to test:
SubtypingThere are three mechanisms for subtyping in IDL:
The corresponding C++ implementations are as follows, here illustrated for
attribute T foo;
IDL files SHOULD agree with spec, and almost always MUST do so. It is not ok to change the kind of subtyping or move members between interfaces, and violations SHOULD or MUST be fixed:
Technical detailsWhile members of an interface definition, members of implemented interface, and members of partial interfaces are identical for JavaScript, partial interface members – and members of certain implemented interfaces, namely those with the
[TreatAsPartial] extended attribute – are treated differently internally in Blink (see below).Inheritance and implements are both interface inheritance. JavaScript has single inheritance, and IDL inheritance corresponds to JavaScript inheritance, while IDL
implements provides multiple inheritance in IDL, which does not correspond to inheritance in JavaScript.In both cases, by spec, members of the inherited or implemented interface must be implemented on the JavaScript object implementing the interface. Concretely, members of inherited interfaces are implemented as properties on the prototype object of the parent interface, while members of implemented interfaces are implemented as properties of the implementing interface.
In C++, members of an interface definition and members of implemented interfaces are implemented on the C++ object (referred to as the parameter or variable
impl ) implementing the JavaScript object. Specifically this is done in the Blink class corresponding to the IDL interface or a base class – the C++ hierarchy is invisible to both JavaScript and the bindings.Implementation-wise, inheritance and implements differ in two ways:
If (IDL) interface A inherits from interface B, then usually (C++) class A inherits from class B, meaning that:
interface A : B { /* ... */ }; class A : B { /* ... */ }; class A : C { /* ... */ }; class C : B { /* ... */ }; However, the bindings are agnostic about this, and simply set the prototype in the wrapper object to be the inherited interface (concretely, sets the parentClass attribute in the WrapperTypeInfo of the class's bindings). Dispatch is thus done in JavaScript.
"A implements B;"
should mean that members declared in (IDL) interface B
are members of (C++) classes implementing A.
impl.
Partial interfaces formally are type extension (external type extension, since specified in a separate place from the original definition), and in principle are simply part of the interface, just defined separately, as a convenience for spec authors. However in practice, members of partial interfaces are not assumed to be implemented on the C++ object (
impl ), and are not defined in the Blink class implementing the interface. Instead, they are implemented as static members of a separate class, which take impl as their first argument. This is done because in practice, partial interfaces are type extensions, which often only used in subtypes or are deactivated (via conditionals or as runtime enabled features), and we do not want to bloat the main Blink class to include these.Further, in some cases we must use type extension (static methods) for implemented interfaces as well. This is due to componentization in Blink (see Browser Components), currently
core versus modules. Code in core cannot inherit from code in modules, and thus if an interface in core implements an interface in modules, this must be implemented via type extension (static methods in modules ). This is an exceptional case, and indicates that Blink's internal layering (componentization) disagrees with the layering implied by the IDL specs, and formally should be resolved by moving the relevant interface from modules to core. This is not always possible or desirable (for internal implementation reasons), and thus static methods can be specified via the [TreatAsPartial] extended attribute on the implemented interface.Inheritance and code reuseIDL has single inheritance, which maps directly to JavaScript inheritance (prototype chain). C++ has multiple inheritance, and the two hierarchies need not be related.
FIXME: There are issues if a C++ class inherits from another C++ class that implements an IDL interface, as .
downcasting
IDL has 3 mechanisms for combining interfaces:
Sharing code with a legacy interface (unprefixing)...
Changing inheritance → implementsConverting a parent to the target of an implements
See alsoOther Blink interfaces, not standard Web IDL interfaces:
External linksFor reference, documentation by other projects.
|
Implementing a new extension API
Proposal
So you want to add the Widgets API. Let's call it widgets.
Defining the Interface
How will extensions declare their intent to use widgets?You need to decide this now. In other words, what will a user of widgets need to write in their manifest?Typically this will be either via a permission string or manifest entry. There is no need for both. By convention it should be called "widgets".
{"name": "Demo widget extension","widgets": {"foo": "bar","baz": "qux"}...}
{"name": "Demo widget extension","permissions": [..., "widgets", ...]...} There are exceptions:
Tell the extensions platform about widgetsFirstly decide, can your API be applied to any platform built on the web, or does it only make sense for Chrome? Examples of the former: storage, messaging. Examples of the latter: browserAction, bookmarks. A good clue is whether you need to #include anything from chrome.
From here, all files here are relative to either extensions/common/api or chrome/common/extensions/api:
First, add an entry in _api_features.json. This tells the extensions platform about when your API should be available (anywhere? only in extension processes?), and what they need to do to use it (do they need a permission? a manifest entry?).
Second, add an entry to either _manifest_feature.json or _permission_features.json. This tells the platform how to interpret "widgets" when it encounters it as either a manifest entry or a permission. What is it available to (extensions? apps? both?), and importantly what channel is it available in (dev? beta? stable?). New extension APIs MUST start in dev (although if they're unimplemented then trunk is advisable).
New extension APIs MUST start in dev (just repeating it).
Write a schema for widgets
Extension APIs can be defined in either IDL (widgets.idl) or JSON Schema (widgets.json). IDL is much more concise, but doesn't include some of the advanced features supported by JSON Schema.
You probably want IDL, though be warned IDL syntax errors occasionally cause the compiler to never terminate.
Fourth, list the schema in schemas.gypi, which tells the build system to generate a bunch of boilerplate for you in <build_dir>/gen/extensions/common/api or <build_dir>/gen/chrome/common/extensions/api: models for your API, and the glue to hook into your implementation.
Finally, add some documentation:
Adding documentationAdding documentation is very simple:
C++ implementation
The actual C++ implementation will typically live in extensions/browser/api/myapi or chrome/browser/extensions/api/myapi (as mentioned above, the magic glue is generated for you).
Functions
Extension APIs are implemented as subclasses of ExtensionFunction from extensions/browser/extension_function.h.
Model generation
Your C++ implementation must live in extensions/browser/api/myapi/myapi_api.h/cc or chrome/browser/extensions/api/myapi/myapi_api.h/cc (depending on where it was declared).This is so that the code generator can find the header file defining your extension function implementations. Remember to add your source files to chrome/chrome_browser_extensions.gypi.
In your header file, include extensions/common/api/myapi.h or chrome/common/extensions/api/myapi.h to use the generated model. This comes from a code-generated file that lives under e.g. out/Debug/gen/chrome/common/extensions/api. Let's say we have the following IDL (or equivalent JSON schema):
// High-level description of your API. This will appear in various places in the docs.
namespace myapi {
dictionary BazOptions {
// Describes what the id argument means. long id; // Describes what the s argument means. DOMString s; }; dictionary BazResult { long x; long y;
}; callback BazCallback = void (BazResult result); interface Functions { // An interesting comment describing what the baz operation is.
// Note that this function can take multiple input arguments, including things like
// long and DOMString, but they have been elided for simplicity.
static void doBaz(BazOptions options, BazCallback callback); }; }; A simple C++ implementation might look like this:
namespace extensions {
// You must follow a naming convention which is ApiNameFunctionNameFunction,
// in this case MyapiDoBazFunction. This is so that the generated code
// can find your implementation.
class MyapiDoBazFunction : public AsyncExtensionFunction {
public:
virtual ~MyapiDoBazFunction () {}
private:
// The MYAPI_DOBAZ entry is an enum you add right before ENUM_BOUNDARY
// in chrome/browser/extensions/extension_function_histogram_value.h
DECLARE_EXTENSION_FUNCTION("myapi.doBaz", MYAPI_DOBAZ);
virtual ResponseAction Run() OVERRIDE {
// Args are passed in via the args_ member as a base::ListValue.
// Use the convenience member of the glue class to easily parse it.
std::unique_ptr<api::myapi::DoBaz::Params> params(
api::myapi::DoBaz::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
api::myapi::BazResult result;
result.x = params->options.id;
base::StringToInt(params->options.s, &result.y);
// Responds to the caller right, but see comments on
// ExtensionFunction::Run() for other ways to respond to messages.
return RespondNow(ArgumentList(result.ToValue()));
}
};
} // namespace extensions
ExtensionFunction is refcounted and instantiated once per call to that extension function, so use base::Bind(this) to ensure it's kept alive (or use AddRef...Release if that's not possible for some reason).
Events
Use ExtensionEventRouter (on the UI thread) to dispatch events to extensions. Prefer the versions that allow you to pass in base::Value rather than a JSON serialized format. Event names are auto-generated in the API file (e.g. chrome/common/extensions/api/myapi.h). In the un-common case where an event is not defined in IDL or json, the corresponding event name should be defined in chrome/browser/extensions/event_names.h.
As with extension functions, it generates some C++ glue classes. Let's say we have the following IDL (or equivalent JSON Schema):
namespace myapi {
dictionary Foo {
// This comment should describe what the id parameter is for.
long id;
// This comment should describe what the bar parameter is for.
DOMString bar;
};
interface Events {
// Fired when something interesting has happened.
// |foo|: The details of the interesting event.
static void onInterestingEvent(Foo foo);
};};
To use the generated glue in C++:
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
api::myapi::Foo foo;
foo.id = 5;
foo.bar = "hello world";
ExtensionSystem::Get(profile)->event_router()->DispatchEventToExtension(
extension_id, api::myapi::OnInterestingEvent::kEventName,
*api::myapi::OnInterestingEvent::Create(foo),
profile, GURL()); Permissions
By default, extension APIs should require a permission named the same as the API namespace.
New permissions are added in ExtensionAPIPermissions::GetAllPermissions() in extensions/common/permissions/extensions_api_permissions.cc or in ChromeAPIPermissions::GetAllPermissions() in chrome/common/extensions/permissions/chrome_api_permissions.cc. You may also need to modify api_permission.h and chrome_permission_message_rules.cc in those directories; see how it's done for other permissions.
Advanced Extension Functionality
Custom Bindings
Custom JS bindings go in chrome/renderer/resources/extensions/*.js.
These are necessary for doing anything special: synchronous API functions, functions bound to types, anything renderer-side. (If it's a common enough operation, please send us patches to do it automatically as part of the bindings generation :).
New Manifest Sections
If your API requires a new manifest section:
The code which handles the externally_connectable manifest key is a good place to start.
Testing Your Implementation
Make sure it has tests. Like all of Chrome, we prefer unit tests to integration tests.
Iterating
Going to StableFollow the Going Live Phase instructions.
|
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2018-07-06 分布式链路跟踪 Sleuth 与 Zipkin【Finchley 版】
2015-07-06 ubuntu 12.04 server + OPENACS(TR069)安装配置日记
2015-07-06 windows搭建openacs编译环境