← All issues

Fast path for Array.prototype.indexOf on NodeList

cdd970b

Source/JavaScriptCore/runtime/ArrayPrototype.cpp

+ if (thisObject->type() == EmbedderArrayLikeType) {
+ Ref arrayLike = uncheckedDowncast<JSEmbedderArrayLike>(*thisObject).embeddedArrayLike();
+ int64_t fastResult = arrayLike->fastIndexOf(globalObject, searchElement, index, length);
+ RETURN_IF_EXCEPTION(scope, { });
+ return JSValue::encode(jsNumber(fastResult));
+ }

Source/JavaScriptCore/runtime/EmbedderArrayLike.h

+class EmbedderArrayLike : public RefCounted<EmbedderArrayLike> {
+public:
+ virtual ~EmbedderArrayLike() = default;
+ virtual int64_t fastIndexOf(JSGlobalObject*, JSValue searchElement, uint64_t startIndex, uint64_t length) = 0;
+};

WebKit's binding layer wraps native C++ DOM objects in JS objects (JSDOMWrapper) on demand. When Array.prototype.indexOf iterates an array-like object like NodeList, it calls [[Get]] on each index, triggering JSNodeList::item() which materializes a full JS wrapper for every Node encountered — just to perform a === comparison. This is pure overhead when the underlying operation is a pointer comparison.

This commit introduces EmbedderArrayLike, a new JSC abstraction that lets embedders implement a native fastIndexOf() bypassing JS wrapper creation. NodeList opts in via a new [EmbedderArrayLike] IDL attribute, so Array.prototype.indexOf on a NodeList now passes the search element directly into a C++ fast path that compares raw Node* pointers.

Before:                                    After:
indexOf(nodeList, searchEl)                indexOf(nodeList, searchEl)
  └─► for i in 0..length:                   ├─► JSType == EmbedderArrayLikeType?
        nodeList[i]                          │     yes: fastIndexOf(searchEl)
          → JSDOMWrapper::create(node_i)     │       └─► native Node* comparison
          → JSNode*  [heap alloc per elem]   │            [no alloc]
        JSValue(JSNode*) === searchEl        └─► no: original slow path

For large NodeLists, indexOf previously forced GC-pressure-inducing wrapper allocation for each element just to do a pointer comparison — the EmbedderArrayLike abstraction eliminates that entirely and is now open for other array-like DOM types to opt in.

🔒

New fast path bypasses normal JS semantics and widens the wrapper cache hierarchy — several edge cases are worth security investigation.

Subscribe to read more