Skip to content

Commit

Permalink
feat: add MutationObserver support. (#508)
Browse files Browse the repository at this point in the history
  • Loading branch information
andycall committed Nov 22, 2023
1 parent dcd299b commit 8399d53
Show file tree
Hide file tree
Showing 62 changed files with 3,733 additions and 103 deletions.
12 changes: 12 additions & 0 deletions bridge/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
bindings/qjs/union_base.cc
# Core sources
core/executing_context.cc
core/script_forbidden_scope.cc
core/script_state.cc
core/page.cc
core/dart_methods.cc
Expand Down Expand Up @@ -287,6 +288,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
core/binding_object.cc
core/dom/node.cc
core/dom/node_list.cc
core/dom/static_node_list.cc
core/dom/node_traversal.cc
core/dom/live_node_list_base.cc
core/dom/character_data.cc
Expand All @@ -305,6 +307,11 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
core/dom/document_fragment.cc
core/dom/child_node_list.cc
core/dom/empty_node_list.cc
core/dom/mutation_observer.cc
core/dom/mutation_observer_registration.cc
core/dom/mutation_observer_interest_group.cc
core/dom/mutation_record.cc
core/dom/child_list_mutation_scope.cc
core/dom/container_node.cc
core/html/custom/widget_element.cc
core/events/error_event.cc
Expand Down Expand Up @@ -407,6 +414,10 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
out/qjs_touch.cc
out/qjs_touch_init.cc
out/qjs_touch_list.cc
out/qjs_mutation_record.cc
out/qjs_mutation_observer.cc
out/qjs_mutation_observer_init.cc
out/qjs_mutation_observer_registration.cc
out/qjs_touch_event.cc
out/qjs_touch_event_init.cc
out/qjs_pointer_event.cc
Expand Down Expand Up @@ -444,6 +455,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs")
out/qjs_node_list.cc
out/event_type_names.cc
out/built_in_string.cc
out/mutation_record_types.cc
out/binding_call_methods.cc
out/qjs_scroll_options.cc
out/qjs_scroll_to_options.cc
Expand Down
6 changes: 6 additions & 0 deletions bridge/bindings/qjs/binding_initializer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
#include "qjs_message_event.h"
#include "qjs_module_manager.h"
#include "qjs_mouse_event.h"
#include "qjs_mutation_observer.h"
#include "qjs_mutation_observer_registration.h"
#include "qjs_mutation_record.h"
#include "qjs_node.h"
#include "qjs_node_list.h"
#include "qjs_performance.h"
Expand Down Expand Up @@ -166,6 +169,9 @@ void InstallBindings(ExecutingContext* context) {
QJSTouch::Install(context);
QJSTouchList::Install(context);
QJSDOMStringMap::Install(context);
QJSMutationObserver::Install(context);
QJSMutationRecord::Install(context);
QJSMutationObserverRegistration::Install(context);
QJSDOMTokenList::Install(context);
QJSPerformance::Install(context);
QJSPerformanceEntry::Install(context);
Expand Down
13 changes: 12 additions & 1 deletion bridge/bindings/qjs/converter_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ struct Converter<IDLOptional<IDLDOMString>> : public ConverterBase<IDLDOMString>
}
static JSValue ToValue(JSContext* ctx, const std::string& str) { return Converter<IDLDOMString>::ToValue(ctx, str); }
static JSValue ToValue(JSContext* ctx, typename Converter<IDLDOMString>::ImplType value) {
if (value == AtomicString::Null()) {
return JS_UNDEFINED;
}

return Converter<IDLDOMString>::ToValue(ctx, std::move(value));
}
};
Expand All @@ -250,7 +254,12 @@ struct Converter<IDLNullable<IDLDOMString>> : public ConverterBase<IDLDOMString>
}

static JSValue ToValue(JSContext* ctx, const std::string& value) { return AtomicString(ctx, value).ToQuickJS(ctx); }
static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(ctx); }
static JSValue ToValue(JSContext* ctx, const AtomicString& value) {
if (value == AtomicString::Null()) {
return JS_NULL;
}
return value.ToQuickJS(ctx);
}
};

template <>
Expand Down Expand Up @@ -347,6 +356,8 @@ struct Converter<IDLOptional<IDLSequence<T>>> : public ConverterBase<IDLSequence

return Converter<IDLSequence<T>>::FromValue(ctx, value, exception_state);
}

static JSValue ToValue(JSContext* ctx, ImplType value) { return Converter<IDLSequence<T>>::ToValue(ctx, value); }
};

template <typename T>
Expand Down
22 changes: 14 additions & 8 deletions bridge/bindings/qjs/cppgc/member.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "bindings/qjs/qjs_engine_patch.h"
#include "bindings/qjs/script_value.h"
#include "bindings/qjs/script_wrappable.h"
#include "core/executing_context.h"
#include "foundation/casting.h"
#include "mutation_scope.h"

Expand All @@ -25,24 +26,22 @@ class ScriptWrappable;
template <typename T, typename = std::is_base_of<ScriptWrappable, T>>
class Member {
public:
struct KeyHasher {
std::size_t operator()(const Member& k) const { return reinterpret_cast<std::size_t>(k.raw_); }
};

Member() = default;
Member(T* ptr) { SetRaw(ptr); }
Member(const Member<T>& other) {
raw_ = other.raw_;
runtime_ = other.runtime_;
js_object_ptr_ = other.js_object_ptr_;
((JSRefCountHeader*)other.js_object_ptr_)->ref_count++;
}
~Member() {
if (raw_ != nullptr) {
assert(runtime_ != nullptr);
// There are two ways to free the member values:
// One is by GC marking and sweep stage.
// Two is by free directly when running out of function body.
// We detect the GC phase to handle case two, and free our members by hand(call JS_FreeValueRT directly).
JSGCPhaseEnum phase = JS_GetEnginePhase(runtime_);
if (phase == JS_GC_PHASE_DECREF || phase == JS_GC_PHASE_REMOVE_CYCLES) {
JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_));
}
JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_));
}
};

Expand Down Expand Up @@ -70,6 +69,7 @@ class Member {
raw_ = other.raw_;
runtime_ = other.runtime_;
js_object_ptr_ = other.js_object_ptr_;
((JSRefCountHeader*)other.js_object_ptr_)->ref_count++;
return *this;
}
// Move assignment.
Expand All @@ -94,6 +94,12 @@ class Member {
T* operator->() const { return Get(); }
T& operator*() const { return *Get(); }

T* Release() {
T* result = Get();
Clear();
return result;
}

private:
void SetRaw(T* p) {
if (p != nullptr) {
Expand Down
12 changes: 10 additions & 2 deletions bridge/bindings/qjs/heap_vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,27 @@ class HeapVector final {
public:
HeapVector() = default;

void Trace(GCVisitor* visitor) const;
void TraceValue(GCVisitor* visitor) const;
void TraceMember(GCVisitor* visitor) const;

private:
std::vector<V> entries_;
};

template <typename V>
void HeapVector<V>::Trace(GCVisitor* visitor) const {
void HeapVector<V>::TraceValue(GCVisitor* visitor) const {
for (auto& item : entries_) {
visitor->TraceValue(item);
}
}

template <typename V>
void HeapVector<V>::TraceMember(GCVisitor* visitor) const {
for (auto& item : entries_) {
visitor->TraceMember(item);
}
}

} // namespace webf

#endif // BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_
2 changes: 1 addition & 1 deletion bridge/bindings/qjs/qjs_function.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int
JSValue returnValue = JS_Call(ctx, function_, this_val.QJSValue(), argc, argv);

ExecutingContext* context = ExecutingContext::From(ctx);
context->DrainPendingPromiseJobs();
context->DrainMicrotasks();

// Free the previous duplicated function.
JS_FreeValue(ctx, function_);
Expand Down
2 changes: 1 addition & 1 deletion bridge/bindings/qjs/script_promise_resolver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) {
JS_FreeValue(context_->ctx(), return_value);
}
}
context_->DrainPendingPromiseJobs();
context_->DrainMicrotasks();
}

} // namespace webf
1 change: 0 additions & 1 deletion bridge/bindings/qjs/script_wrappable.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

#include <quickjs/quickjs.h>
#include "bindings/qjs/cppgc/garbage_collected.h"
#include "core/executing_context.h"
#include "foundation/macros.h"
#include "wrapper_type_info.h"

Expand Down
3 changes: 3 additions & 0 deletions bridge/bindings/qjs/wrapper_type_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ enum {
JS_CLASS_HTML_LINK_ELEMENT,
JS_CLASS_HTML_CANVAS_ELEMENT,
JS_CLASS_IMAGE,
JS_CLASS_MUTATION_OBSERVER,
JS_CLASS_MUTATION_RECORD,
JS_CLASS_MUTATION_OBSERVER_REGISTRATION,
JS_CLASS_CANVAS_RENDERING_CONTEXT,
JS_CLASS_CANVAS_RENDERING_CONTEXT_2_D,
JS_CLASS_CANVAS_GRADIENT,
Expand Down
12 changes: 12 additions & 0 deletions bridge/core/binding_object.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "bindings/qjs/exception_state.h"
#include "bindings/qjs/script_promise_resolver.h"
#include "core/dom/events/event_target.h"
#include "core/dom/mutation_observer_interest_group.h"
#include "core/executing_context.h"
#include "foundation/native_string.h"
#include "foundation/native_value_converter.h"
Expand Down Expand Up @@ -123,6 +124,17 @@ NativeValue BindingObject::SetBindingProperty(const AtomicString& prop,
"Can not set binding property on BindingObject, dart binding object had been disposed");
return Native_NewNull();
}

if (auto element = const_cast<WidgetElement*>(DynamicTo<WidgetElement>(this))) {
if (std::shared_ptr<MutationObserverInterestGroup> recipients =
MutationObserverInterestGroup::CreateForAttributesMutation(*element, prop)) {
NativeValue old_native_value = GetBindingProperty(prop, exception_state);
ScriptValue old_value = ScriptValue(ctx(), old_native_value);
recipients->EnqueueMutationRecord(
MutationRecord::CreateAttributes(element, prop, AtomicString::Null(), old_value.ToString(ctx())));
}
}

GetExecutingContext()->FlushUICommand();
const NativeValue argv[] = {Native_NewString(prop.ToNativeString(GetExecutingContext()->ctx()).release()), value};
return InvokeBindingMethod(BindingMethodCallOperations::kSetProperty, 2, argv, exception_state);
Expand Down
70 changes: 63 additions & 7 deletions bridge/core/css/inline_css_style_declaration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
#include "inline_css_style_declaration.h"
#include <vector>
#include "core/dom/element.h"
#include "core/dom/mutation_observer_interest_group.h"
#include "core/executing_context.h"
#include "core/html/parser/html_parser.h"
#include "css_property_list.h"
#include "element_namespace_uris.h"
#include "html_names.h"

namespace webf {

Expand Down Expand Up @@ -52,6 +55,27 @@ static std::string parseJavaScriptCSSPropertyName(std::string& propertyName) {
return result;
}

static std::string convertCamelCaseToKebabCase(const std::string& propertyName) {
static std::unordered_map<std::string, std::string> propertyCache{};

if (propertyCache.count(propertyName) > 0) {
return propertyCache[propertyName];
}

std::string result;
for (char c : propertyName) {
if (std::isupper(c)) {
result += '-';
result += std::tolower(c);
} else {
result += c;
}
}

propertyCache[propertyName] = result;
return result;
}

InlineCssStyleDeclaration* InlineCssStyleDeclaration::Create(ExecutingContext* context,
ExceptionState& exception_state) {
exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor.");
Expand Down Expand Up @@ -79,7 +103,10 @@ bool InlineCssStyleDeclaration::SetItem(const AtomicString& key,
}

std::string propertyName = key.ToStdString(ctx());
return InternalSetProperty(propertyName, value.ToLegacyDOMString(ctx()));
bool success = InternalSetProperty(propertyName, value.ToLegacyDOMString(ctx()));
if (success)
InlineStyleChanged();
return success;
}

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

void InlineCssStyleDeclaration::Clear() {
InternalClearProperty();
}

AtomicString InlineCssStyleDeclaration::getPropertyValue(const AtomicString& key, ExceptionState& exception_state) {
std::string propertyName = key.ToStdString(ctx());
return InternalGetPropertyValue(propertyName);
Expand All @@ -99,7 +130,9 @@ void InlineCssStyleDeclaration::setProperty(const AtomicString& key,
const ScriptValue& value,
ExceptionState& exception_state) {
std::string propertyName = key.ToStdString(ctx());
InternalSetProperty(propertyName, value.ToLegacyDOMString(ctx()));
bool success = InternalSetProperty(propertyName, value.ToLegacyDOMString(ctx()));
if (success)
InlineStyleChanged();
}

AtomicString InlineCssStyleDeclaration::removeProperty(const AtomicString& key, ExceptionState& exception_state) {
Expand All @@ -117,7 +150,7 @@ AtomicString InlineCssStyleDeclaration::cssText() const {
std::string result;
size_t index = 0;
for (auto& attr : properties_) {
result += attr.first + ": " + attr.second.ToStdString(ctx()) + ";";
result += convertCamelCaseToKebabCase(attr.first) + ": " + attr.second.ToStdString(ctx()) + ";";
index++;
if (index < properties_.size()) {
result += " ";
Expand All @@ -127,11 +160,12 @@ AtomicString InlineCssStyleDeclaration::cssText() const {
}

void InlineCssStyleDeclaration::setCssText(const webf::AtomicString& value, webf::ExceptionState& exception_state) {
const std::string css_text = value.ToStdString(ctx());
setCssText(css_text, exception_state);
SetCSSTextInternal(value);
InlineStyleChanged();
}

void InlineCssStyleDeclaration::setCssText(const std::string& css_text, webf::ExceptionState& exception_state) {
void InlineCssStyleDeclaration::SetCSSTextInternal(const AtomicString& value) {
const std::string css_text = value.ToStdString(ctx());
InternalClearProperty();

std::vector<std::string> styles;
Expand Down Expand Up @@ -173,6 +207,24 @@ std::string InlineCssStyleDeclaration::ToString() const {
return s;
}

void InlineCssStyleDeclaration::InlineStyleChanged() {
assert(owner_element_->IsStyledElement());

owner_element_->InvalidateStyleAttribute();

if (std::shared_ptr<MutationObserverInterestGroup> recipients =
MutationObserverInterestGroup::CreateForAttributesMutation(*owner_element_, html_names::kStyleAttr)) {
AtomicString old_value = AtomicString::Null();
if (owner_element_->attributes()->hasAttribute(html_names::kStyleAttr, ASSERT_NO_EXCEPTION())) {
old_value = owner_element_->attributes()->getAttribute(html_names::kStyleAttr, ASSERT_NO_EXCEPTION());
}

recipients->EnqueueMutationRecord(
MutationRecord::CreateAttributes(owner_element_, html_names::kStyleAttr, AtomicString::Null(), old_value));
owner_element_->SynchronizeStyleAttributeInternal();
}
}

bool InlineCssStyleDeclaration::NamedPropertyQuery(const AtomicString& key, ExceptionState&) {
return cssPropertyList.count(key.ToStdString(ctx())) > 0;
}
Expand All @@ -196,9 +248,11 @@ AtomicString InlineCssStyleDeclaration::InternalGetPropertyValue(std::string& na
bool InlineCssStyleDeclaration::InternalSetProperty(std::string& name, const AtomicString& value) {
name = parseJavaScriptCSSPropertyName(name);
if (properties_[name] == value) {
return true;
return false;
}

AtomicString old_value = properties_[name];

properties_[name] = value;

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

InlineStyleChanged();

std::unique_ptr<SharedNativeString> args_01 = stringToNativeString(name);
GetExecutingContext()->uiCommandBuffer()->addCommand(UICommand::kSetStyle, std::move(args_01),
owner_element_->bindingObject(), nullptr);
Expand Down
Loading

0 comments on commit 8399d53

Please sign in to comment.