Skip to content

Adding a New WebIDL Interface

sideshowbarker edited this page May 23, 2024 · 1 revision

Note: For a real-world example, see the changeset which added the FragmentDirective interface.

To add a new WebIDL interface and its implementation to WebKit, follow these steps:

  1. Create all necessary files — e.g, Foo.idl, Foo.h, and Foo.cpp — in the appropriate directories.
  2. Find an existing .idl file that’s for a WebIDL interface similar to the one you’re adding.
    For the purposes of these steps, let’s call that file Bar.idl here.
  3. In your Source/WebCore directory, run a (rip)grep (or similar) search for \W(JS)?Bar\.(idl|cpp|h)|macro\(Bar\), to find config files with lines containing any of Bar.idl, Bar.h, Bar.cpp, JSBar.h, JSBar.cpp, or macro(Bar).
  4. For each matching Bar.idl, Bar.h, or Bar.cpp line you find, add a corresponding Foo.idl, Foo.h, or Foo.cpp line (for the files you created in step #1)
  5. For each matching JSBar.h or JSBar.cpp line you find, add a corresponding JSFoo.h or JSFoo.cpp line (for JS “derived sources” bindings files generated by the build).
  6. For any macro(Bar) line you find, add a corresponding macro(Foo)line.
  7. Run the build.
  8. If the build fails, repeat the above steps as needed.

Config Files

For step #4, the files in the Source/WebCore directory that you’ll likely need to add lines to are the following:

You may also need to make the following changes:

  • bindings/js/WebCoreBuiltinNames.h may need a macro(Foo) \ line
    (This is necessary when you’re using the EnabledBySetting annotation in any WebIDL files you’re adding)
  • WebCore.xcodeproj/project.pbxproj may need lines for the JSFoo.h and JSFoo.cpp derived sources
    [TODO: Figure when it’s actually necessary to add any JS* lines to this file]

Also for step #4: See https://docs.webkit.org/Deep%20Dive/Build/AddingNewFile.html for details on using Xcode to add new files to the project — which will modify the WebCore.xcodeproj/project.pbxproj file. But, for any JSFoo.h and JSFoo.cpp files, you might also need to make manual modifications to the WebCore.xcodeproj/project.pbxproj file — to assign each file a unique hash (fileref/UUID), and to adjust the paths for those files.

See also: https://docs.webkit.org/Deep%20Dive/Architecture/AddingNewJSApi.html


Implementation

Note: The examples in this section are based on the actual WebIDL for the document.fragmentDirective property and the FragmentDirective interface and its corresponding implementation in WebKit.

A minimal C++ implementation in WebKit for a WebIDL interface will look something like this:

class FragmentDirective : public RefCounted<FragmentDirective> {
public:
    static Ref<FragmentDirective> create() { return adoptRef(*new FragmentDirective()); }
    virtual ~FragmentDirective() = default;
private:
    FragmentDirective() = default;
};

Notes:

  • The corresponding C++ class for the interface needs to inherit from public RefCounted<FragmentDirective>.
  • The class must explicitly declare a virtual public destructor: virtual ~FragmentDirective() — unless the class is marked final, per Safer CPP Guidelines.
  • The class must explicitly declare a private constructor, per Safer CPP Guidelines — and note: in C++, if you didn’t explicitly declare it private, the compiler will generate an implicit constructor, and it will be public.

A minimal C++ implementation for a WebIDL readonly attribute added to an existing interface will look something like this:

Ref<FragmentDirective> fragmentDirective() { return m_fragmentDirective; }

…which corresponds to the following WebIDL:

partial interface Document {
    [SameObject] readonly attribute FragmentDirective fragmentDirective;
};

Note: While the WebIDL defines a (readonly) fragmentDirective attribute, the corresponding C++ code must define a fragmentDirective() function that’s a getter for a m_fragmentDirective data member; unless such a corresponding function is declared, a build with the derived sources produced by the bindings generator would fail during linking.

Resolving Name Conflicts: The ImplementedAs WebIDL Annotation

Sometimes you may find there’s a name conflict between the name of a WebIDL construct you’re adding, and the name of an existing C++ symbol in the WebKit code. In such cases, you’ll need to use the ImplementedAs WebIDL annotation, and specify an (arbitrary) alternate name for the corresponding C++ symbol in your implementation — like this:

WebIDL:

[SameObject, ImplementedAs=fragmentDirectiveForBindings]
readonly attribute FragmentDirective fragmentDirective;

C++

Ref<FragmentDirective> fragmentDirectiveForBindings() { return m_fragmentDirectiveForBindings; }

That is, for the WebIDL fragmentDirective attribute, we’ve given the corresponding C++ function the name fragmentDirectiveForBindings() — and added the ImplementedAs annotation to the WebIDL — so the bindings generator know where to find our function. (It would otherwise look for a function named fragmentDirective()).

Using the suffix forBindings is a common naming convention in the existing code — but using that exact suffix isn’t a requirement; instead the only requirement is that the C++ symbol name matches the value you specify for the ImplementedAs annotation in the WebIDL source.

Build Errors You May Encounter

If you’ve made a mistake during any of the steps outlined above, you might encounter some of these error messages:

Build error Cause
no member named 'deref' Your C++ class should inherit from public RefCounted<FragmentDirective>.
type 'Foo' does not provide a call operator A part of your C++ implementation should be implemented as a function (that is, callable).
Foo.h: No such file or directory The Foo.h header file should have its target membership set to Private in Xcode (that is, in the WebCore.xcodeproj/project.pbxproj config file).
Undefined symbols for architecture A config file is missing a necessary Foo.h or JSFoo.h line.

Clone this wiki locally