Skip to content

Commit 8399d53

Browse files
committed
feat: add MutationObserver support. (#508)
Fixed #405 https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver ---------
1 parent dcd299b commit 8399d53

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+3733
-103
lines changed

bridge/CMakeLists.txt

+12
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
248248
bindings/qjs/union_base.cc
249249
# Core sources
250250
core/executing_context.cc
251+
core/script_forbidden_scope.cc
251252
core/script_state.cc
252253
core/page.cc
253254
core/dart_methods.cc
@@ -287,6 +288,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
287288
core/binding_object.cc
288289
core/dom/node.cc
289290
core/dom/node_list.cc
291+
core/dom/static_node_list.cc
290292
core/dom/node_traversal.cc
291293
core/dom/live_node_list_base.cc
292294
core/dom/character_data.cc
@@ -305,6 +307,11 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
305307
core/dom/document_fragment.cc
306308
core/dom/child_node_list.cc
307309
core/dom/empty_node_list.cc
310+
core/dom/mutation_observer.cc
311+
core/dom/mutation_observer_registration.cc
312+
core/dom/mutation_observer_interest_group.cc
313+
core/dom/mutation_record.cc
314+
core/dom/child_list_mutation_scope.cc
308315
core/dom/container_node.cc
309316
core/html/custom/widget_element.cc
310317
core/events/error_event.cc
@@ -407,6 +414,10 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
407414
out/qjs_touch.cc
408415
out/qjs_touch_init.cc
409416
out/qjs_touch_list.cc
417+
out/qjs_mutation_record.cc
418+
out/qjs_mutation_observer.cc
419+
out/qjs_mutation_observer_init.cc
420+
out/qjs_mutation_observer_registration.cc
410421
out/qjs_touch_event.cc
411422
out/qjs_touch_event_init.cc
412423
out/qjs_pointer_event.cc
@@ -444,6 +455,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
444455
out/qjs_node_list.cc
445456
out/event_type_names.cc
446457
out/built_in_string.cc
458+
out/mutation_record_types.cc
447459
out/binding_call_methods.cc
448460
out/qjs_scroll_options.cc
449461
out/qjs_scroll_to_options.cc

bridge/bindings/qjs/binding_initializer.cc

+6
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161
#include "qjs_message_event.h"
6262
#include "qjs_module_manager.h"
6363
#include "qjs_mouse_event.h"
64+
#include "qjs_mutation_observer.h"
65+
#include "qjs_mutation_observer_registration.h"
66+
#include "qjs_mutation_record.h"
6467
#include "qjs_node.h"
6568
#include "qjs_node_list.h"
6669
#include "qjs_performance.h"
@@ -166,6 +169,9 @@ void InstallBindings(ExecutingContext* context) {
166169
QJSTouch::Install(context);
167170
QJSTouchList::Install(context);
168171
QJSDOMStringMap::Install(context);
172+
QJSMutationObserver::Install(context);
173+
QJSMutationRecord::Install(context);
174+
QJSMutationObserverRegistration::Install(context);
169175
QJSDOMTokenList::Install(context);
170176
QJSPerformance::Install(context);
171177
QJSPerformanceEntry::Install(context);

bridge/bindings/qjs/converter_impl.h

+12-1
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ struct Converter<IDLOptional<IDLDOMString>> : public ConverterBase<IDLDOMString>
237237
}
238238
static JSValue ToValue(JSContext* ctx, const std::string& str) { return Converter<IDLDOMString>::ToValue(ctx, str); }
239239
static JSValue ToValue(JSContext* ctx, typename Converter<IDLDOMString>::ImplType value) {
240+
if (value == AtomicString::Null()) {
241+
return JS_UNDEFINED;
242+
}
243+
240244
return Converter<IDLDOMString>::ToValue(ctx, std::move(value));
241245
}
242246
};
@@ -250,7 +254,12 @@ struct Converter<IDLNullable<IDLDOMString>> : public ConverterBase<IDLDOMString>
250254
}
251255

252256
static JSValue ToValue(JSContext* ctx, const std::string& value) { return AtomicString(ctx, value).ToQuickJS(ctx); }
253-
static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(ctx); }
257+
static JSValue ToValue(JSContext* ctx, const AtomicString& value) {
258+
if (value == AtomicString::Null()) {
259+
return JS_NULL;
260+
}
261+
return value.ToQuickJS(ctx);
262+
}
254263
};
255264

256265
template <>
@@ -347,6 +356,8 @@ struct Converter<IDLOptional<IDLSequence<T>>> : public ConverterBase<IDLSequence
347356

348357
return Converter<IDLSequence<T>>::FromValue(ctx, value, exception_state);
349358
}
359+
360+
static JSValue ToValue(JSContext* ctx, ImplType value) { return Converter<IDLSequence<T>>::ToValue(ctx, value); }
350361
};
351362

352363
template <typename T>

bridge/bindings/qjs/cppgc/member.h

+14-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "bindings/qjs/qjs_engine_patch.h"
1111
#include "bindings/qjs/script_value.h"
1212
#include "bindings/qjs/script_wrappable.h"
13+
#include "core/executing_context.h"
1314
#include "foundation/casting.h"
1415
#include "mutation_scope.h"
1516

@@ -25,24 +26,22 @@ class ScriptWrappable;
2526
template <typename T, typename = std::is_base_of<ScriptWrappable, T>>
2627
class Member {
2728
public:
29+
struct KeyHasher {
30+
std::size_t operator()(const Member& k) const { return reinterpret_cast<std::size_t>(k.raw_); }
31+
};
32+
2833
Member() = default;
2934
Member(T* ptr) { SetRaw(ptr); }
3035
Member(const Member<T>& other) {
3136
raw_ = other.raw_;
3237
runtime_ = other.runtime_;
3338
js_object_ptr_ = other.js_object_ptr_;
39+
((JSRefCountHeader*)other.js_object_ptr_)->ref_count++;
3440
}
3541
~Member() {
3642
if (raw_ != nullptr) {
3743
assert(runtime_ != nullptr);
38-
// There are two ways to free the member values:
39-
// One is by GC marking and sweep stage.
40-
// Two is by free directly when running out of function body.
41-
// We detect the GC phase to handle case two, and free our members by hand(call JS_FreeValueRT directly).
42-
JSGCPhaseEnum phase = JS_GetEnginePhase(runtime_);
43-
if (phase == JS_GC_PHASE_DECREF || phase == JS_GC_PHASE_REMOVE_CYCLES) {
44-
JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_));
45-
}
44+
JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_));
4645
}
4746
};
4847

@@ -70,6 +69,7 @@ class Member {
7069
raw_ = other.raw_;
7170
runtime_ = other.runtime_;
7271
js_object_ptr_ = other.js_object_ptr_;
72+
((JSRefCountHeader*)other.js_object_ptr_)->ref_count++;
7373
return *this;
7474
}
7575
// Move assignment.
@@ -94,6 +94,12 @@ class Member {
9494
T* operator->() const { return Get(); }
9595
T& operator*() const { return *Get(); }
9696

97+
T* Release() {
98+
T* result = Get();
99+
Clear();
100+
return result;
101+
}
102+
97103
private:
98104
void SetRaw(T* p) {
99105
if (p != nullptr) {

bridge/bindings/qjs/heap_vector.h

+10-2
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,27 @@ class HeapVector final {
1212
public:
1313
HeapVector() = default;
1414

15-
void Trace(GCVisitor* visitor) const;
15+
void TraceValue(GCVisitor* visitor) const;
16+
void TraceMember(GCVisitor* visitor) const;
1617

1718
private:
1819
std::vector<V> entries_;
1920
};
2021

2122
template <typename V>
22-
void HeapVector<V>::Trace(GCVisitor* visitor) const {
23+
void HeapVector<V>::TraceValue(GCVisitor* visitor) const {
2324
for (auto& item : entries_) {
2425
visitor->TraceValue(item);
2526
}
2627
}
2728

29+
template <typename V>
30+
void HeapVector<V>::TraceMember(GCVisitor* visitor) const {
31+
for (auto& item : entries_) {
32+
visitor->TraceMember(item);
33+
}
34+
}
35+
2836
} // namespace webf
2937

3038
#endif // BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_

bridge/bindings/qjs/qjs_function.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int
6060
JSValue returnValue = JS_Call(ctx, function_, this_val.QJSValue(), argc, argv);
6161

6262
ExecutingContext* context = ExecutingContext::From(ctx);
63-
context->DrainPendingPromiseJobs();
63+
context->DrainMicrotasks();
6464

6565
// Free the previous duplicated function.
6666
JS_FreeValue(ctx, function_);

bridge/bindings/qjs/script_promise_resolver.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) {
5454
JS_FreeValue(context_->ctx(), return_value);
5555
}
5656
}
57-
context_->DrainPendingPromiseJobs();
57+
context_->DrainMicrotasks();
5858
}
5959

6060
} // namespace webf

bridge/bindings/qjs/script_wrappable.h

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
#include <quickjs/quickjs.h>
1010
#include "bindings/qjs/cppgc/garbage_collected.h"
11-
#include "core/executing_context.h"
1211
#include "foundation/macros.h"
1312
#include "wrapper_type_info.h"
1413

bridge/bindings/qjs/wrapper_type_info.h

+3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ enum {
7070
JS_CLASS_HTML_LINK_ELEMENT,
7171
JS_CLASS_HTML_CANVAS_ELEMENT,
7272
JS_CLASS_IMAGE,
73+
JS_CLASS_MUTATION_OBSERVER,
74+
JS_CLASS_MUTATION_RECORD,
75+
JS_CLASS_MUTATION_OBSERVER_REGISTRATION,
7376
JS_CLASS_CANVAS_RENDERING_CONTEXT,
7477
JS_CLASS_CANVAS_RENDERING_CONTEXT_2_D,
7578
JS_CLASS_CANVAS_GRADIENT,

bridge/core/binding_object.cc

+12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "bindings/qjs/exception_state.h"
99
#include "bindings/qjs/script_promise_resolver.h"
1010
#include "core/dom/events/event_target.h"
11+
#include "core/dom/mutation_observer_interest_group.h"
1112
#include "core/executing_context.h"
1213
#include "foundation/native_string.h"
1314
#include "foundation/native_value_converter.h"
@@ -123,6 +124,17 @@ NativeValue BindingObject::SetBindingProperty(const AtomicString& prop,
123124
"Can not set binding property on BindingObject, dart binding object had been disposed");
124125
return Native_NewNull();
125126
}
127+
128+
if (auto element = const_cast<WidgetElement*>(DynamicTo<WidgetElement>(this))) {
129+
if (std::shared_ptr<MutationObserverInterestGroup> recipients =
130+
MutationObserverInterestGroup::CreateForAttributesMutation(*element, prop)) {
131+
NativeValue old_native_value = GetBindingProperty(prop, exception_state);
132+
ScriptValue old_value = ScriptValue(ctx(), old_native_value);
133+
recipients->EnqueueMutationRecord(
134+
MutationRecord::CreateAttributes(element, prop, AtomicString::Null(), old_value.ToString(ctx())));
135+
}
136+
}
137+
126138
GetExecutingContext()->FlushUICommand();
127139
const NativeValue argv[] = {Native_NewString(prop.ToNativeString(GetExecutingContext()->ctx()).release()), value};
128140
return InvokeBindingMethod(BindingMethodCallOperations::kSetProperty, 2, argv, exception_state);

bridge/core/css/inline_css_style_declaration.cc

+63-7
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
#include "inline_css_style_declaration.h"
66
#include <vector>
77
#include "core/dom/element.h"
8+
#include "core/dom/mutation_observer_interest_group.h"
89
#include "core/executing_context.h"
910
#include "core/html/parser/html_parser.h"
1011
#include "css_property_list.h"
12+
#include "element_namespace_uris.h"
13+
#include "html_names.h"
1114

1215
namespace webf {
1316

@@ -52,6 +55,27 @@ static std::string parseJavaScriptCSSPropertyName(std::string& propertyName) {
5255
return result;
5356
}
5457

58+
static std::string convertCamelCaseToKebabCase(const std::string& propertyName) {
59+
static std::unordered_map<std::string, std::string> propertyCache{};
60+
61+
if (propertyCache.count(propertyName) > 0) {
62+
return propertyCache[propertyName];
63+
}
64+
65+
std::string result;
66+
for (char c : propertyName) {
67+
if (std::isupper(c)) {
68+
result += '-';
69+
result += std::tolower(c);
70+
} else {
71+
result += c;
72+
}
73+
}
74+
75+
propertyCache[propertyName] = result;
76+
return result;
77+
}
78+
5579
InlineCssStyleDeclaration* InlineCssStyleDeclaration::Create(ExecutingContext* context,
5680
ExceptionState& exception_state) {
5781
exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor.");
@@ -79,7 +103,10 @@ bool InlineCssStyleDeclaration::SetItem(const AtomicString& key,
79103
}
80104

81105
std::string propertyName = key.ToStdString(ctx());
82-
return InternalSetProperty(propertyName, value.ToLegacyDOMString(ctx()));
106+
bool success = InternalSetProperty(propertyName, value.ToLegacyDOMString(ctx()));
107+
if (success)
108+
InlineStyleChanged();
109+
return success;
83110
}
84111

85112
bool InlineCssStyleDeclaration::DeleteItem(const webf::AtomicString& key, webf::ExceptionState& exception_state) {
@@ -90,6 +117,10 @@ int64_t InlineCssStyleDeclaration::length() const {
90117
return properties_.size();
91118
}
92119

120+
void InlineCssStyleDeclaration::Clear() {
121+
InternalClearProperty();
122+
}
123+
93124
AtomicString InlineCssStyleDeclaration::getPropertyValue(const AtomicString& key, ExceptionState& exception_state) {
94125
std::string propertyName = key.ToStdString(ctx());
95126
return InternalGetPropertyValue(propertyName);
@@ -99,7 +130,9 @@ void InlineCssStyleDeclaration::setProperty(const AtomicString& key,
99130
const ScriptValue& value,
100131
ExceptionState& exception_state) {
101132
std::string propertyName = key.ToStdString(ctx());
102-
InternalSetProperty(propertyName, value.ToLegacyDOMString(ctx()));
133+
bool success = InternalSetProperty(propertyName, value.ToLegacyDOMString(ctx()));
134+
if (success)
135+
InlineStyleChanged();
103136
}
104137

105138
AtomicString InlineCssStyleDeclaration::removeProperty(const AtomicString& key, ExceptionState& exception_state) {
@@ -117,7 +150,7 @@ AtomicString InlineCssStyleDeclaration::cssText() const {
117150
std::string result;
118151
size_t index = 0;
119152
for (auto& attr : properties_) {
120-
result += attr.first + ": " + attr.second.ToStdString(ctx()) + ";";
153+
result += convertCamelCaseToKebabCase(attr.first) + ": " + attr.second.ToStdString(ctx()) + ";";
121154
index++;
122155
if (index < properties_.size()) {
123156
result += " ";
@@ -127,11 +160,12 @@ AtomicString InlineCssStyleDeclaration::cssText() const {
127160
}
128161

129162
void InlineCssStyleDeclaration::setCssText(const webf::AtomicString& value, webf::ExceptionState& exception_state) {
130-
const std::string css_text = value.ToStdString(ctx());
131-
setCssText(css_text, exception_state);
163+
SetCSSTextInternal(value);
164+
InlineStyleChanged();
132165
}
133166

134-
void InlineCssStyleDeclaration::setCssText(const std::string& css_text, webf::ExceptionState& exception_state) {
167+
void InlineCssStyleDeclaration::SetCSSTextInternal(const AtomicString& value) {
168+
const std::string css_text = value.ToStdString(ctx());
135169
InternalClearProperty();
136170

137171
std::vector<std::string> styles;
@@ -173,6 +207,24 @@ std::string InlineCssStyleDeclaration::ToString() const {
173207
return s;
174208
}
175209

210+
void InlineCssStyleDeclaration::InlineStyleChanged() {
211+
assert(owner_element_->IsStyledElement());
212+
213+
owner_element_->InvalidateStyleAttribute();
214+
215+
if (std::shared_ptr<MutationObserverInterestGroup> recipients =
216+
MutationObserverInterestGroup::CreateForAttributesMutation(*owner_element_, html_names::kStyleAttr)) {
217+
AtomicString old_value = AtomicString::Null();
218+
if (owner_element_->attributes()->hasAttribute(html_names::kStyleAttr, ASSERT_NO_EXCEPTION())) {
219+
old_value = owner_element_->attributes()->getAttribute(html_names::kStyleAttr, ASSERT_NO_EXCEPTION());
220+
}
221+
222+
recipients->EnqueueMutationRecord(
223+
MutationRecord::CreateAttributes(owner_element_, html_names::kStyleAttr, AtomicString::Null(), old_value));
224+
owner_element_->SynchronizeStyleAttributeInternal();
225+
}
226+
}
227+
176228
bool InlineCssStyleDeclaration::NamedPropertyQuery(const AtomicString& key, ExceptionState&) {
177229
return cssPropertyList.count(key.ToStdString(ctx())) > 0;
178230
}
@@ -196,9 +248,11 @@ AtomicString InlineCssStyleDeclaration::InternalGetPropertyValue(std::string& na
196248
bool InlineCssStyleDeclaration::InternalSetProperty(std::string& name, const AtomicString& value) {
197249
name = parseJavaScriptCSSPropertyName(name);
198250
if (properties_[name] == value) {
199-
return true;
251+
return false;
200252
}
201253

254+
AtomicString old_value = properties_[name];
255+
202256
properties_[name] = value;
203257

204258
std::unique_ptr<SharedNativeString> args_01 = stringToNativeString(name);
@@ -218,6 +272,8 @@ AtomicString InlineCssStyleDeclaration::InternalRemoveProperty(std::string& name
218272
AtomicString return_value = properties_[name];
219273
properties_.erase(name);
220274

275+
InlineStyleChanged();
276+
221277
std::unique_ptr<SharedNativeString> args_01 = stringToNativeString(name);
222278
GetExecutingContext()->uiCommandBuffer()->addCommand(UICommand::kSetStyle, std::move(args_01),
223279
owner_element_->bindingObject(), nullptr);

0 commit comments

Comments
 (0)