Title: [290463] trunk
Revision
290463
Author
rn...@webkit.org
Date
2022-02-24 14:32:36 -0800 (Thu, 24 Feb 2022)

Log Message

Explain visit children and opaque roots in Introduction.md
https://bugs.webkit.org/show_bug.cgi?id=236894

Reviewed by Saam Barati.

Added the descriptions of visit children and opaque roots
as well as how to add IDL files.

* Introduction.md:

Modified Paths

Diff

Modified: trunk/ChangeLog (290462 => 290463)


--- trunk/ChangeLog	2022-02-24 22:26:15 UTC (rev 290462)
+++ trunk/ChangeLog	2022-02-24 22:32:36 UTC (rev 290463)
@@ -1,3 +1,15 @@
+2022-02-24  Ryosuke Niwa  <rn...@webkit.org>
+
+        Explain visit children and opaque roots in Introduction.md
+        https://bugs.webkit.org/show_bug.cgi?id=236894
+
+        Reviewed by Saam Barati.
+
+        Added the descriptions of visit children and opaque roots
+        as well as how to add IDL files.
+
+        * Introduction.md:
+
 2022-02-23  Elliott Williams  <e...@apple.com>
 
         [XCBuild] Don't automatically use the ../Internal/WebKit workspace

Modified: trunk/Introduction.md (290462 => 290463)


--- trunk/Introduction.md	2022-02-24 22:26:15 UTC (rev 290462)
+++ trunk/Introduction.md	2022-02-24 22:32:36 UTC (rev 290463)
@@ -725,23 +725,32 @@
 multiple web pages across multiple tabs might be able to communicate with one another via _javascript_ API
 such as [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage).
 
-## _javascript_ Wrappers
+## _javascript_ Wrappers and IDL files
 
-Each DOM node’s behavior is implemented as a C++ class in WebCore.
-_javascript_ API is primarily implemented using [Web IDL](https://heycam.github.io/webidl/),
-an [interface description language](https://en.wikipedia.org/wiki/Interface_description_language),
-from which various [JS DOM binding code](https://github.com/WebKit/WebKit/tree/main/Source/WebCore/bindings)
-is auto-generated by a [perl script](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm),
-for example, under `WebKitBuild/Debug/DerivedSources/WebCore/` for debug builds.
+In addition to typical C++ translation units (.cpp) and C++ header files (.cpp) along with some Objective-C and Objective-C++ files,
+[WebCore](https://github.com/WebKit/WebKit/tree/main/Source/WebCore) contains hundreds of [Web IDL](https://heycam.github.io/webidl/) (.idl) files.
+[Web IDL](https://heycam.github.io/webidl/) is an [interface description language](https://en.wikipedia.org/wiki/Interface_description_language)
+and it's used to define the shape and the behavior of _javascript_ API implemented in WebKit.
+
+When building WebKit, a [perl script](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm)
+generates appropriate C++ translation units and C++ header files corresponding to these IDL files under `WebKitBuild/Debug/DerivedSources/WebCore/`
+where `Debug` is the current build configuration (e.g. it could be `Release-iphonesimulator` for example).
+
+These auto-generated files along with manually written files [Source/WebCore/bindings](https://github.com/WebKit/WebKit/tree/main/Source/WebCore/bindings)
+are called **JS DOM binding code** and implements _javascript_ API for objects and concepts whose underlying shape and behaviors are written in C++.
+
 For example, C++ implementation of [Node](https://developer.mozilla.org/en-US/docs/Web/API/Node)
 is [Node class](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
-and its _javascript_ interface is implemented by JSNode class,
-most of which is auto-generated but has some custom bindings code in
-[JSNodeCustom](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/bindings/js/JSNodeCustom.cpp).
+and its _javascript_ interface is implemented by `JSNode` class.
+The class declartion and most of definitions are auto-generated
+at `WebKitBuild/Debug/DerivedSources/WebCore/JSNode.h` and `WebKitBuild/Debug/DerivedSources/WebCore/JSNode.cpp` for debug builds.
+It also has some custom, manually written, bindings code in
+[Source/WebCore/bindings/js/JSNodeCustom.cpp](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/bindings/js/JSNodeCustom.cpp).
 Similarly, C++ implementation of [Range interface](https://developer.mozilla.org/en-US/docs/Web/API/Range)
 is [Range class](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Range.h)
-whilst its _javascript_ API is implemented by the auto-generated JSRange class.
-We call instances of the latter JS* classes *JS wrappers*.
+whilst its _javascript_ API is implemented by the auto-generated JSRange class
+(located at `WebKitBuild/Debug/DerivedSources/WebCore/JSRange.h` and `WebKitBuild/Debug/DerivedSources/WebCore/JSRange.cpp` for debug builds)
+We call instances of these JSX classes *JS wrappers* of X.
 
 These JS wrappers exist in what we call a [`DOMWrapperWorld`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/bindings/js/DOMWrapperWorld.h).
 Each `DOMWrapperWorld` has its own JS wrapper for each C++ object.
@@ -759,19 +768,33 @@
 `toJS` function will find the appropriate JS wrapper in
 a [hash map](https://github.com/WebKit/WebKit/blob/ea1a56ee11a26f292f3d2baed2a3aea95fea40f1/Source/WebCore/bindings/js/DOMWrapperWorld.h#L74)
 of the given `DOMWrapperWorld`.
-Because a hash map lookup is expensive, some WebCore objects will inherit from
+Because a hash map lookup is expensive, some WebCore objects inherit from
 [ScriptWrappable](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/bindings/js/ScriptWrappable.h),
 which has an inline pointer to the JS wrapper for the main world if one was already created.
 
+### Adding new _javascript_ API
+
+To introduce a new _javascript_ API in [WebCore](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/), 
+first identify the directory under which to implement this new API, and introduce corresponding Web IDL files.
+New IDL files should be listed in [Source/WebCore/DerivedSources.make](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/DerivedSources.make)
+so that the aforementioned perl script can generate corresponding JS*.cpp and JS*.h filies.
+Add these newly generated JS*.cpp files to [Source/WebCore/Sources.txt](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/Sources.txt)
+in order for them to be compiled.
+Remember to add these files to [WebCore's Xcode project](https://github.com/WebKit/WebKit/tree/main/Source/WebCore/WebCore.xcodeproj) as well.
+
+For example, [this commit](https://github.com/WebKit/WebKit/commit/cbda68a29beb3da90d19855882c5340ce06f1546)
+introduced [`IdleDeadline.idl`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/IdleDeadline.idl)
+and added `JSIdleDeadline.cpp` to the list of derived sources to be compiled.
+
 ## JS Wrapper Lifecycle Management
 
-As a general rule, a JS wrapper object keeps its underlying C++ object alive by means of reference counting
+As a general rule, a JS wrapper keeps its underlying C++ object alive by means of reference counting
 in [JSDOMWrapper](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/bindings/js/JSDOMWrapper.h) temple class
 from which all JS wrappers in WebCore inherits.
 However, **C++ objects do not keep their corresponding JS wrapper in each world alive** by the virtue of them staying alive
 as such a circular dependency will result in a memory leak.
 
-There are two primary mechanisms to keep JS wrappers alive in WebCore:
+There are two primary mechanisms to keep JS wrappers alive in [WebCore](https://github.com/WebKit/WebKit/tree/main/Source/WebCore):
 
 * **Visit Children** - When _javascript_Core’s garbage collection visits some JS wrapper during
     the [marking phase](https://en.wikipedia.org/wiki/Tracing_garbage_collection#Basic_algorithm),
@@ -779,16 +802,210 @@
 * **Reachable from Opaque Roots** - Tell _javascript_Core’s garbage collection that a JS wrapper is reachable
     from an opaque root which was added to the set of opaque roots during marking phase.
 
-FIXME: Explain how to add new IDL files and where derived sources are generated.
-
 ### Visit Children
 
-FIXME: Explain how visit children works.
+*Visit Children* is the mechanism we use when a JS wrapper needs to keep another JS wrapper or
+[JS object](https://github.com/WebKit/WebKit/blob/main/Source/_javascript_Core/runtime/JSObject.h) alive.
 
+For example, [`ErrorEvent` object](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/ErrorEvent.idl)
+uses this method in
+[Source/WebCore/bindings/js/JSErrorEventCustom.cpp](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/bindings/js/JSErrorEventCustom.cpp)
+to keep its "error" IDL attribute as follows:
+
+```cpp
+template<typename Visitor>
+void JSErrorEvent::visitAdditionalChildren(Visitor& visitor)
+{
+    wrapped().originalError().visit(visitor);
+}
+
+DEFINE_VISIT_ADDITIONAL_CHILDREN(JSErrorEvent);
+```
+
+Here, `DEFINE_VISIT_ADDITIONAL_CHILDREN` macro generates template instances of visitAdditionalChildren
+which gets called by the _javascript_Core's garbage collector.
+When the garbage collector visits an instance `ErrorEvent` object,
+it also visits `wrapped().originalError()`, which is the _javascript_ value of "error" attribute:
+
+```cpp
+class ErrorEvent final : public Event {
+...
+    const JSValueInWrappedObject& originalError() const { return m_error; }
+    SerializedScriptValue* serializedError() const { return m_serializedError.get(); }
+...
+    JSValueInWrappedObject m_error;
+    RefPtr<SerializedScriptValue> m_serializedError;
+    bool m_triedToSerialize { false };
+};
+```
+
+Note that [`JSValueInWrappedObject`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/bindings/js/JSValueInWrappedObject.h)
+uses [`Weak`](https://github.com/WebKit/WebKit/blob/main/Source/_javascript_Core/heap/Weak.h),
+which does not keep the referenced object alive on its own.
+We can't use a reference type such as [`Strong`](https://github.com/WebKit/WebKit/blob/main/Source/_javascript_Core/heap/Strong.h)
+which keeps the referenced object alive on its own since the stored JS object may also have this `ErrorEvent` object stored as its property.
+Because the garbage collector has no way of knowing or clearing the `Strong` reference
+or the property to `ErrorEvent` in this hypothetical version of `ErrorEvent`,
+it would never be able to collect either object, resulting in a memory leak.
+
+To use this method of keeping a _javascript_ object or wrapper alive, add `JSCustomMarkFunction` to the IDL file,
+then introduce JS*Custom.cpp file under [Source/WebCore/bindings/js](https://github.com/WebKit/WebKit/tree/main/Source/WebCore/bindings/js)
+and implement `template<typename Visitor> void JS*Event::visitAdditionalChildren(Visitor& visitor)` as seen above for `ErrorEvent`.
+
+**visitAdditionalChildren is called concurrently** while the main thread is running.
+Any operation done in visitAdditionalChildren needs to be multi-thread safe.
+For example, it cannot increment or decrement the reference count of a `RefCounted` object
+or create a new `WeakPtr` from `CanMakeWeakPtr` since these WTF classes are not thread safe.
+
 ### Opaque Roots
 
-FIXME: Explain how opaque roots work.
+*Reachable from Opaque Roots* is the mechanism we use when we have an underlying C++ object and want to keep JS wrappers of other C++ objects alive.
 
+To see why, let's consider a [`StyleSheet` object](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/css/StyleSheet.idl).
+So long as this object is alive, we also need to keep the DOM node returned by the `ownerNode` attribute.
+Also, the object itself needs to be kept alive so long as the owner node is alive
+since this [`StyleSheet` object] can be accessed via [`sheet` IDL attribute](https://drafts.csswg.org/cssom/#the-linkstyle-interface)
+of the owner node.
+If we were to use the *visit children* mechanism,
+we need to visit every JS wrapper of the owner node whenever this `StyleSheet` object is visited by the garbage collector,
+and we need to visit every JS wrapper of the `StyleSheet` object whenever an owner node is visited by the garbage collector.
+But in order to do so, we need to query every `DOMWrapperWorld`'s wrapper map to see if there is a _javascript_ wrapper.
+This is an expensive operation that needs to happen all the time,
+and creates a tie coupling between `Node` and `StyleSheet` objects
+since each JS wrapper objects need to be  aware of other objects' existence. 
+
+*Opaque roots* solves these problems by letting the garbage collector know that a particular _javascript_ wrapper needs to be kept alive
+so long as the gargabe collector had encountered specific opaque root(s) this _javascript_ wrapper cares about
+even if the garbage collector didn't visit the _javascript_ wrapper directly.
+An opaque root is simply a `void*` identifier the garbage collector keeps track of during each marking phase,
+and it does not conform to a specific interface or behavior.
+It could have been an arbitrary integer value but `void*` is used out of convenience since pointer values of live objects are unique.
+
+In the case of a `StyleSheet` object, `StyleSheet`'s _javascript_ wrapper tells the garbage collector that it needs to be kept alive
+because an opaque root it cares about has been encountered whenever `onwerNode` is visited by the garbage collector.
+
+In the most simplistic model, the opaque root for this case will be the `onwerNode` itself.
+However, each `Node` object also has to keep its parent, siblings, and children alive.
+To this end, each `Node` designates the [root](https://dom.spec.whatwg.org/#concept-tree-root) node as its opaque root.
+Both `Node` and `StyleSheet` objects use this unique opaque root as a way of communicating with the gargage collector.
+
+For example, `StyleSheet` object informs the garbage collector of this opaque root when it's asked to visit its children in
+[JSStyleSheetCustom.cpp](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/bindings/js/JSStyleSheetCustom.cpp):
+
+```cpp
+template<typename Visitor>
+void JSStyleSheet::visitAdditionalChildren(Visitor& visitor)
+{
+    visitor.addOpaqueRoot(root(&wrapped()));
+}
+```
+
+Here, `void* root(StyleSheet*)` returns the opaque root of the `StyleSheet` object as follows:
+
+```cpp
+inline void* root(StyleSheet* styleSheet)
+{
+    if (CSSImportRule* ownerRule = styleSheet->ownerRule())
+        return root(ownerRule);
+    if (Node* ownerNode = styleSheet->ownerNode())
+        return root(ownerNode);
+    return styleSheet;
+}
+```
+
+And then in `JSStyleSheet.cpp` (located at `WebKitBuild/Debug/DerivedSources/WebCore/JSStyleSheet.cpp` for debug builds)
+`JSStyleSheetOwner` (a helper _javascript_ object to communicate with the garbage collector) tells the garbage collector
+that `JSStyleSheet` should be kept alive so long as the garbage collector had encountered this `StyleSheet`'s opaque root:
+
+```cpp
+bool JSStyleSheetOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, const char** reason)
+{
+    auto* jsStyleSheet = jsCast<JSStyleSheet*>(handle.slot()->asCell());
+    void* root = WebCore::root(&jsStyleSheet->wrapped());
+    if (UNLIKELY(reason))
+        *reason = "Reachable from jsStyleSheet";
+    return visitor.containsOpaqueRoot(root);
+}
+```
+
+Generally, using opaque roots as a way of keeping _javascript_ wrappers involve two steps:
+ 1. Add opaque roots in `visitAdditionalChildren`.
+ 2. Return true in `isReachableFromOpaqueRoots` when relevant opaque roots are found.
+
+The first step can be achieved by using the aforementioend `JSCustomMarkFunction` with `visitAdditionalChildren`.
+Alternatively and more preferably, `GenerateAddOpaqueRoot` can be added to the IDL interface to auto-generate this code.
+For example, [AbortController.idl](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/AbortController.idl)
+makes use of this IDL attribute as follows:
+
+```cpp
+[
+    Exposed=(Window,Worker),
+    GenerateAddOpaqueRoot=signal
+] interface AbortController {
+    [CallWith=ScriptExecutionContext] constructor();
+
+    [SameObject] readonly attribute AbortSignal signal;
+
+    [CallWith=GlobalObject] undefined abort(optional any reason);
+};
+```
+
+Here, `singal` is a public member function funtion of
+the [underlying C++ object](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/AbortController.h):
+
+```cpp
+class AbortController final : public ScriptWrappable, public RefCounted<AbortController> {
+    WTF_MAKE_ISO_ALLOCATED(AbortController);
+public:
+    static Ref<AbortController> create(ScriptExecutionContext&);
+    ~AbortController();
+
+    AbortSignal& signal();
+    void abort(JSDOMGlobalObject&, JSC::JSValue reason);
+
+private:
+    explicit AbortController(ScriptExecutionContext&);
+
+    Ref<AbortSignal> m_signal;
+};
+```
+
+When `GenerateAddOpaqueRoot` is specified without any value, it automatically calls `opaqueRoot()` instead.
+
+Like visitAdditionalChildren, **adding opaque roots happen concurrently** while the main thread is running.
+Any operation done in visitAdditionalChildren needs to be multi-thread safe.
+For example, it cannot increment or decrement the reference count of a `RefCounted` object
+or create a new `WeakPtr` from `CanMakeWeakPtr` since these WTF classes are not thread safe.
+
+The second step can be achived by adding `CustomIsReachable` to the IDL file and
+implementing `JS*Owner::isReachableFromOpaqueRoots` in JS*Custom.cpp file.
+Alternatively and more preferably, `GenerateIsReachable` can be added to IDL file to automatically generate this code
+with the following values:
+ * No value - Adds the result of calling `root(T*)` on the underlying C++ object of type T as the opaque root.
+ * `Impl` - Adds the underlying C++ object as the opaque root.
+ * `ReachableFromDOMWindow` - Adds a [`DOMWindow`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/page/DOMWindow.h)
+    returned by `window()` as the opaque root.
+ * `ReachableFromNavigator` - Adds a [`Navigator`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/page/Navigator.h)
+    returned by `navigator()` as the opaque root.
+ * `ImplDocument` - Adds a [`Document`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Document.h)
+    returned by `document()` as the opaque root.
+ * `ImplElementRoot` - Adds the root node of a [`Element`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Element.h)
+    returned by `element()` as the opaque root.
+ * `ImplOwnerNodeRoot` - Adds the root node of a [`Node`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/Node.h)
+    returned by `ownerNode()` as the opaque root.
+ * `ImplScriptExecutionContext` - Adds a [`ScriptExecutionContext`](https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/ScriptExecutionContext.h)
+    returned by `scriptExecutionContext()` as the opaque root.
+
+Similar to visiting children or adding opaque roots, **whether an opaque root is reachable or not is checked in parallel**.
+However, it happens **while the main thread is paused** unlike visiting children or adding opaque roots,
+which happen concurrently while the main thread is running.
+This means that any operation done in `JS*Owner::isReachableFromOpaqueRoots`
+or any function called by GenerateIsReachable cannot have thread unsafe side effects
+such as incrementing or decrementing the reference count of a `RefCounted` object
+or creating a new `WeakPtr` from `CanMakeWeakPtr` since these WTF classes' mutation operations are not thread safe.
+
+FIXME: Discuss Active DOM objects
+
 ## Inserting or Removing DOM Nodes 
 
 FIXME: Talk about how a node insertion or removal works.
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to