From 9b9a70267e5e55f63f753042b3cde2642fd6ebd1 Mon Sep 17 00:00:00 2001 From: "pengfei12.guo" Date: Wed, 16 Oct 2024 14:40:38 +0800 Subject: [PATCH] =?UTF-8?q?add=20IntersectionObserver=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bridge/CMakeLists.txt | 9 + bridge/bindings/qjs/binding_initializer.cc | 6 + bridge/bindings/qjs/wrapper_type_info.h | 4 + bridge/core/binding_object.cc | 12 +- bridge/core/binding_object.h | 6 +- bridge/core/dom/intersection_observer.cc | 144 ++++++++++++ bridge/core/dom/intersection_observer.d.ts | 31 +++ bridge/core/dom/intersection_observer.h | 211 ++++++++++++++++++ .../core/dom/intersection_observer_entry.cc | 33 +++ .../core/dom/intersection_observer_entry.d.ts | 34 +++ bridge/core/dom/intersection_observer_entry.h | 62 +++++ .../core/dom/intersection_observer_init.d.ts | 20 ++ bridge/core/dom/node_test.cc | 43 +++- bridge/core/executing_context.cc | 1 + bridge/foundation/ui_command_buffer.cc | 4 + bridge/foundation/ui_command_buffer.h | 6 +- .../templates/idl_templates/dictionary.h.tpl | 1 + bridge/third_party/quickjs/vendor/mimalloc | 2 +- .../assets/IntersectionObserver_test.html | 55 +++++ webf/example/lib/main.dart | 2 +- webf/lib/src/bridge/binding.dart | 9 +- webf/lib/src/bridge/to_native.dart | 4 + webf/lib/src/bridge/ui_command.dart | 33 +++ webf/lib/src/dom/document.dart | 30 +++ webf/lib/src/dom/element.dart | 36 +++ webf/lib/src/dom/intersection_observer.dart | 178 +++++++++++++++ .../src/dom/intersection_observer_entry.dart | 49 ++++ webf/lib/src/launcher/controller.dart | 46 ++++ 28 files changed, 1062 insertions(+), 9 deletions(-) create mode 100644 bridge/core/dom/intersection_observer.cc create mode 100644 bridge/core/dom/intersection_observer.d.ts create mode 100644 bridge/core/dom/intersection_observer.h create mode 100644 bridge/core/dom/intersection_observer_entry.cc create mode 100644 bridge/core/dom/intersection_observer_entry.d.ts create mode 100644 bridge/core/dom/intersection_observer_entry.h create mode 100644 bridge/core/dom/intersection_observer_init.d.ts create mode 100644 webf/example/assets/IntersectionObserver_test.html create mode 100644 webf/lib/src/dom/intersection_observer.dart create mode 100644 webf/lib/src/dom/intersection_observer_entry.dart diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 85d9171f2f..b234fb07cf 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -401,6 +401,10 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/dom/legacy/bounding_client_rect.cc core/input/touch.cc core/input/touch_list.cc + + #IntersectionObserver + core/dom/intersection_observer.cc + core/dom/intersection_observer_entry.cc ) # Gen sources. @@ -534,6 +538,11 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/element_attribute_names.cc out/element_namespace_uris.cc + # IntersectionObserver + out/qjs_intersection_observer.cc + out/qjs_intersection_observer_entry.cc + out/qjs_intersection_observer_init.cc + # SVG generated out/svg_names.cc out/svg_element_factory.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 3a25fbc33f..ae237c0eac 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -99,6 +99,8 @@ #include "qjs_widget_element.h" #include "qjs_window.h" #include "qjs_window_or_worker_global_scope.h" +#include "qjs_intersection_observer.h" +#include "qjs_intersection_observer_entry.h" namespace webf { @@ -200,6 +202,10 @@ void InstallBindings(ExecutingContext* context) { QJSSVGStyleElement::Install(context); QJSSVGLineElement::Install(context); + //IntersectionObserver + QJSIntersectionObserver::Install(context); + QJSIntersectionObserverEntry::Install(context); + // Legacy bindings, not standard. QJSElementAttributes::Install(context); } diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 3aa7c313ad..b594b83b08 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -115,6 +115,10 @@ enum { JS_CLASS_SVG_LENGTH, JS_CLASS_SVG_ANIMATED_LENGTH, + // IntersectionObserver + JS_CLASS_INTERSECTION_OBSERVER, + JS_CLASS_INTERSECTION_OBSERVER_ENTRY, + JS_CLASS_CUSTOM_CLASS_INIT_COUNT /* last entry for predefined classes */ }; diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index b138e01fa5..88982642b8 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -60,9 +60,11 @@ void NativeBindingObject::HandleCallFromDartSide(DartIsolateContext* dart_isolat dart_isolate_context->profiler()->StartTrackEvaluation(profile_id); - AtomicString method = AtomicString( - binding_object->binding_target_->ctx(), - std::unique_ptr(reinterpret_cast(native_method->u.ptr))); + auto context = binding_object->binding_target_->ctx(); + AtomicString method = native_method != nullptr + ? AtomicString(context, std::unique_ptr( + reinterpret_cast(native_method->u.ptr))) + : AtomicString(context, ""); NativeValue result = binding_object->binding_target_->HandleCallFromDartSide(method, argc, argv, dart_object); auto* return_value = new NativeValue(); @@ -430,4 +432,8 @@ bool BindingObject::IsCanvasGradient() const { return false; } +bool BindingObject::IsIntersectionObserverEntry() const { + return false; +} + } // namespace webf diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h index ec0d328cd1..fe99758857 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -70,7 +70,10 @@ enum BindingMethodCallOperations { kAsyncAnonymousFunction, }; -enum CreateBindingObjectType { kCreateDOMMatrix = 0 }; +enum CreateBindingObjectType { + kCreateDOMMatrix = 0, + kCreateIntersectionObserver +}; struct BindingObjectPromiseContext : public DartReadable { ExecutingContext* context; @@ -136,6 +139,7 @@ class BindingObject : public ScriptWrappable { virtual bool IsTouchList() const; virtual bool IsComputedCssStyleDeclaration() const; virtual bool IsCanvasGradient() const; + virtual bool IsIntersectionObserverEntry() const; protected: void TrackPendingPromiseBindingContext(BindingObjectPromiseContext* binding_object_promise_context); diff --git a/bridge/core/dom/intersection_observer.cc b/bridge/core/dom/intersection_observer.cc new file mode 100644 index 0000000000..67270b2274 --- /dev/null +++ b/bridge/core/dom/intersection_observer.cc @@ -0,0 +1,144 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2024-present The WebF authors. All rights reserved. + +#include "core/dom/intersection_observer.h" + +#include +#include + +#include "core/dom/element.h" +#include +#include "bindings/qjs/converter_impl.h" +#include "core/dom/intersection_observer_entry.h" +#include "core/dom/node.h" +#include "core/executing_context.h" +#include "qjs_intersection_observer_init.h" +#include "foundation/logging.h" + +namespace webf { + +IntersectionObserver* IntersectionObserver::Create(ExecutingContext* context, + const std::shared_ptr& function, + ExceptionState& exception_state) { + return MakeGarbageCollected(context, function); +} + +IntersectionObserver* IntersectionObserver::Create(ExecutingContext* context, + const std::shared_ptr& function, + const std::shared_ptr& observer_init, + ExceptionState& exception_state) { + return MakeGarbageCollected(context, function, observer_init); +} + +IntersectionObserver::IntersectionObserver(ExecutingContext* context, const std::shared_ptr& function) + : BindingObject(context->ctx()), function_(function) { + GetExecutingContext()->dartMethodPtr()->createBindingObject( + GetExecutingContext()->isDedicated(), GetExecutingContext()->contextId(), bindingObject(), + CreateBindingObjectType::kCreateIntersectionObserver, nullptr, 0); +} + +IntersectionObserver::IntersectionObserver(ExecutingContext* context, + const std::shared_ptr& function, + const std::shared_ptr& observer_init) + : BindingObject(context->ctx()), function_(function) { + if (observer_init->hasRoot()) { + root_ = observer_init->root(); + } + GetExecutingContext()->dartMethodPtr()->createBindingObject( + GetExecutingContext()->isDedicated(), GetExecutingContext()->contextId(), bindingObject(), + CreateBindingObjectType::kCreateIntersectionObserver, nullptr, 0); +} + +bool IntersectionObserver::RootIsValid() const { + return RootIsImplicit() || root(); +} + +void IntersectionObserver::observe(Element* target, ExceptionState& exception_state) { + if (!RootIsValid() || !target) + return; + + // TODO(pengfei12.guo@vipshop.com): 通知dart,注册IntersectionObserver + GetExecutingContext()->uiCommandBuffer()->AddCommand(UICommand::kAddIntersectionObserver, nullptr, bindingObject(), + target->bindingObject()); +} + +void IntersectionObserver::unobserve(Element* target, ExceptionState& exception_state) { + if (!target) + return; + + GetExecutingContext()->uiCommandBuffer()->AddCommand(UICommand::kRemoveIntersectionObserver, nullptr, bindingObject(), + target->bindingObject()); +} + +void IntersectionObserver::disconnect(ExceptionState& exception_state) { + GetExecutingContext()->uiCommandBuffer()->AddCommand(UICommand::kDisconnectIntersectionObserver, nullptr, + bindingObject(), nullptr); +} + +// std::vector> IntersectionObserver::takeRecords(ExceptionState& exception_state) { +// std::vector> entries; +// for (auto& observation : observations_) +// observation->TakeRecords(entries); +// active_observations_.clear(); +// return entries; +// } + +// AtomicString IntersectionObserver::rootMargin() const { +// return StringifyMargin(RootMargin()); +// } + +// AtomicString IntersectionObserver::scrollMargin() const { +// return StringifyMargin(ScrollMargin()); +// } + +// using InvokeBindingMethodsFromDart = void (*)(NativeBindingObject* binding_object, +// int64_t profile_id, +// NativeValue* method, +// int32_t argc, +// NativeValue* argv, +// Dart_Handle dart_object, +// DartInvokeResultCallback result_callback); +NativeValue IntersectionObserver::HandleCallFromDartSide(const AtomicString& method, + int32_t argc, + const NativeValue* argv, + Dart_Handle dart_object) { + if (!GetExecutingContext() || !GetExecutingContext()->IsContextValid()) { + WEBF_LOG(ERROR) << "[IntersectionObserver]: HandleCallFromDartSide Context Valid" << std::endl; + return Native_NewNull(); + } + + WEBF_LOG(DEBUG) << "[IntersectionObserver]: HandleCallFromDartSide NativeValueConverter" << std::endl; + NativeValue native_entry_list = argv[0]; + std::vector entries = + NativeValueConverter>>::FromNativeValue( + ctx(), native_entry_list); + + if (!entries.empty()) { + assert(function_ != nullptr); + + WEBF_LOG(DEBUG) << "[IntersectionObserver]: HandleCallFromDartSide To JSValue" << std::endl; + JSValue v = Converter>::ToValue(ctx(), entries); + ScriptValue arguments[] = {ScriptValue(ctx(), v), ToValue()}; + + JS_FreeValue(ctx(), v); + + WEBF_LOG(DEBUG) << "[IntersectionObserver]: HandleCallFromDartSide function_ Invoke" << std::endl; + function_->Invoke(ctx(), ToValue(), 2, arguments); + } else { + WEBF_LOG(ERROR) << "[IntersectionObserver]: HandleCallFromDartSide entries is empty"; + } + + return Native_NewNull(); +} + +void IntersectionObserver::Trace(GCVisitor* visitor) const { + BindingObject::Trace(visitor); + BindingObject::Trace(visitor); + + function_->Trace(visitor); +} + +} // namespace webf diff --git a/bridge/core/dom/intersection_observer.d.ts b/bridge/core/dom/intersection_observer.d.ts new file mode 100644 index 0000000000..5478131dbd --- /dev/null +++ b/bridge/core/dom/intersection_observer.d.ts @@ -0,0 +1,31 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://www.w3.org/TR/intersection-observer/#intersection-observer-interface + +// Copyright (C) 2024-present The WebF authors. All rights reserved. + +import {IntersectionObserverInit} from "./intersection_observer_init"; +import {IntersectionObserverEntry} from "./intersection_observer_entry"; +import {Node} from "./node"; +import {Element} from "./element"; + +//type IntersectionObserverCallback = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) + +interface IntersectionObserver { + //new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver; + new(callback: Function, options?: IntersectionObserverInit): IntersectionObserver; + + //readonly root: Node | null; + //readonly rootMargin: string; + //readonly scrollMargin: string; + //readonly thresholds: number[]; + //readonly delay: number; + //readonly trackVisibility: boolean; + + observe(target: Element): void; + unobserve(target: Element): void; + disconnect(): void; + //takeRecords(): IntersectionObserverEntry[]; +} diff --git a/bridge/core/dom/intersection_observer.h b/bridge/core/dom/intersection_observer.h new file mode 100644 index 0000000000..a7acee43a2 --- /dev/null +++ b/bridge/core/dom/intersection_observer.h @@ -0,0 +1,211 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2024-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_INTERSECTION_OBSERVER_INTERSECTION_OBSERVER_H_ +#define WEBF_CORE_INTERSECTION_OBSERVER_INTERSECTION_OBSERVER_H_ + +#include +#include +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/exception_state.h" +#include "core/binding_object.h" +#include "out/qjs_intersection_observer_init.h" + +namespace webf { + +class Element; +class ExceptionState; +class IntersectionObserverEntry; +class Node; +class ScriptState; + +class IntersectionObserver final : public BindingObject { + DEFINE_WRAPPERTYPEINFO(); + + public: + + // The IntersectionObserver can be configured to notify based on changes to + // how much of the target element's area intersects with the root, or based on + // changes to how much of the root element's area intersects with the + // target. Examples illustrating the distinction: + // + // 1.0 of target, 0.5 of target, 1.0 of target, + // 0.25 of root 0.5 of root 1.0 of root + // +------------------+ +------------------+ *~~~~~~~~~~~~~~~~~~* + // | ////////// | | | ;//////////////////; + // | ////////// | | | ;//////////////////; + // | ////////// | ;//////////////////; ;//////////////////; + // | | ;//////////////////; ;//////////////////; + // +------------------+ *~~~~~~~~~~~~~~~~~~* *~~~~~~~~~~~~~~~~~~* + // //////////////////// + // //////////////////// + // //////////////////// + //enum ThresholdInterpretation { kFractionOfTarget, kFractionOfRoot }; + + // This value can be used to detect transitions between non-intersecting or + // edge-adjacent (i.e., zero area) state, and intersecting by any non-zero + // number of pixels. + // static constexpr float kMinimumThreshold = + // IntersectionGeometry::kMinimumThreshold; + + // Used to specify when callbacks should be invoked with new notifications. + // Blink-internal users of IntersectionObserver will have their callbacks + // invoked synchronously either at the end of a lifecycle update or in the + // middle of the lifecycle post layout. Javascript observers will PostTask to + // invoke their callbacks. + //enum DeliveryBehavior { kDeliverDuringPostLayoutSteps, kDeliverDuringPostLifecycleSteps, kPostTaskToDeliver }; + + // Used to specify whether the margins apply to the root element or the source + // element. The effect of the root element margins is that intermediate + // scrollers clip content by its bounding box without considering margins. + // That is, margins only apply to the last scroller (root). The effect of + // source element margins is that the margins apply to the first / deepest + // clipper, but do not apply to any other clippers. Note that in a case of a + // single clipper, the two approaches are equivalent. + // + // Note that the percentage margin is resolved against the root rect, even + // when the margin is applied to the target. + //enum MarginTarget { kApplyMarginToRoot, kApplyMarginToTarget }; + + static IntersectionObserver* Create(ExecutingContext* context, + const std::shared_ptr& function, + ExceptionState& exception_state); + + static IntersectionObserver* Create(ExecutingContext* context, + const std::shared_ptr& function, + const std::shared_ptr& observer_init, + ExceptionState& exception_state); + + IntersectionObserver(ExecutingContext* context, const std::shared_ptr& function); + IntersectionObserver(ExecutingContext* context, + const std::shared_ptr& function, + const std::shared_ptr& observer_init); + + // TODO(pengfei12.guo): Params not supported + // struct Params { + // WEBF_STACK_ALLOCATED(); + // + // public: + // Node* root; + // Vector margin; + // MarginTarget margin_target = kApplyMarginToRoot; + // Vector scroll_margin; + // + // // Elements should be in the range [0,1], and are interpreted according to + // // the given `semantics`. + // Vector thresholds; + // ThresholdInterpretation semantics = kFractionOfTarget; + // + // DeliveryBehavior behavior = kDeliverDuringPostLifecycleSteps; + // // Specifies the minimum period between change notifications. + // base::TimeDelta delay; + // bool track_visibility = false; + // bool always_report_root_bounds = false; + // // Indicates whether the overflow clip edge should be used instead of the + // // bounding box if appropriate. + // bool use_overflow_clip_edge = false; + // bool needs_initial_observation_with_detached_target = true; + // }; + + NativeValue HandleCallFromDartSide(const AtomicString& method, + int32_t argc, + const NativeValue* argv, + Dart_Handle dart_object) override; + + // API methods. + void observe(Element*, ExceptionState&); + void unobserve(Element*, ExceptionState&); + void disconnect(ExceptionState&); + // TODO(pengfei12.guo): not supported + // std::vector> takeRecords(ExceptionState&); + + // API attributes. + Node* root() const { return root_; } + // TODO(pengfei12.guo): not supported + //AtomicString rootMargin() const; + // TODO(pengfei12.guo): not supported + //AtomicString scrollMargin() const; + // TODO(pengfei12.guo): not supported + //const std::vector& thresholds() const { return thresholds_; } + // TODO(pengfei12.guo): not supported + // DOMHighResTimeStamp delay() const { + // if (delay_ != std::numeric_limits::min() && delay_ != std::numeric_limits::max()) { + // return delay_ / 1000; + // } + // return (delay_ < 0) ? std::numeric_limits::min() : std::numeric_limits::max(); + // } + + // An observer can either track intersections with an explicit root Node, + // or with the the top-level frame's viewport (the "implicit root"). When + // tracking the implicit root, root_ will be null, but because root_ is a + // weak pointer, we cannot surmise that this observer tracks the implicit + // root just because root_ is null. Hence root_is_implicit_. + [[nodiscard]] bool RootIsImplicit() const { + // return root_is_implicit_; + // 如果没有指定 root 选项,默认情况下会使用视口作为根元素。 + return root_ == nullptr; + } + + // TODO(pengfei12.guo@vipshop.com): TimeDelta not support + // base::TimeDelta GetEffectiveDelay() const; + + // TODO(pengfei12.guo@vipshop.com): RootMargin not support + // std::vector RootMargin() const { + // return margin_target_ == kApplyMarginToRoot ? margin_ : Vector(); + //} + + // TODO(pengfei12.guo@vipshop.com): TargetMargin not support + // Vector TargetMargin() const { + // return margin_target_ == kApplyMarginToTarget ? margin_ : Vector(); + //} + + // TODO(pengfei12.guo@vipshop.com): ScrollMargin not support + // Vector ScrollMargin() const { return scroll_margin_; } + + // TODO(pengfei12.guo): ComputeIntersections impl by dart + // Returns the number of IntersectionObservations that recomputed geometry. + // int64_t ComputeIntersections(unsigned flags, ComputeIntersectionsContext&); + + // TODO(pengfei12.guo): GetUkmMetricId not support + // bool IsInternal() const; + + // TODO(pengfei12.guo): GetUkmMetricId not support + // The metric id for tracking update time via UpdateTime metrics, or null for + // internal intersection observers without explicit metrics. + // std::optional GetUkmMetricId() const { + // return ukm_metric_id_; + //} + + // Returns false if this observer has an explicit root node which has been + // deleted; true otherwise. + bool RootIsValid() const; + + + void Trace(GCVisitor*) const override; + + private: + + // We use UntracedMember<> here to do custom weak processing. + Node* root_; + + // TODO(pengfei12.guo): not support + // const std::vector margin_; + // const std::vector scroll_margin_; + // const MarginTarget margin_target_; + // const unsigned root_is_implicit_ : 1; + // const unsigned track_visibility_ : 1; + // const unsigned track_fraction_of_root_ : 1; + // const unsigned always_report_root_bounds_ : 1; + // const unsigned use_overflow_clip_edge_ : 1; + + std::shared_ptr function_; +}; + +} // namespace webf + +#endif // WEBF_CORE_INTERSECTION_OBSERVER_INTERSECTION_OBSERVER_H_ diff --git a/bridge/core/dom/intersection_observer_entry.cc b/bridge/core/dom/intersection_observer_entry.cc new file mode 100644 index 0000000000..226f811087 --- /dev/null +++ b/bridge/core/dom/intersection_observer_entry.cc @@ -0,0 +1,33 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2024-present The WebF authors. All rights reserved. + +#include "core/dom/intersection_observer_entry.h" +#include "core/dom/element.h" + +namespace webf { + +IntersectionObserverEntry::IntersectionObserverEntry(ExecutingContext* context, bool isIntersecting, Element* target) + : BindingObject(context->ctx()), isIntersecting_(isIntersecting), target_(target) {} + +// DOMRectReadOnly* IntersectionObserverEntry::boundingClientRect() const { +// return DOMRectReadOnly::FromRectF(gfx::RectF(geometry_.TargetRect())); +// } +// +// DOMRectReadOnly* IntersectionObserverEntry::rootBounds() const { +// if (geometry_.ShouldReportRootBounds()) +// return DOMRectReadOnly::FromRectF(gfx::RectF(geometry_.RootRect())); +// return nullptr; +// } +// +// DOMRectReadOnly* IntersectionObserverEntry::intersectionRect() const { +// return DOMRectReadOnly::FromRectF(gfx::RectF(geometry_.IntersectionRect())); +// } + +void IntersectionObserverEntry::Trace(GCVisitor* visitor) const { + visitor->TraceMember(target_); +} + +} // namespace webf diff --git a/bridge/core/dom/intersection_observer_entry.d.ts b/bridge/core/dom/intersection_observer_entry.d.ts new file mode 100644 index 0000000000..eb7e95d54d --- /dev/null +++ b/bridge/core/dom/intersection_observer_entry.d.ts @@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://wicg.github.io/IntersectionObserver/#intersection-observer-entry + +// Copyright (C) 2024-present The WebF authors. All rights reserved. + +import {Element} from "./element"; + +export interface IntersectionObserverEntry { + // TODO(pengfei12.guo): DOMHighResTimeStamp not supported + //readonly time: DOMHighResTimeStamp; + + // TODO(pengfei12.guo): DOMRectReadOnly not supported + // TODO(szager): |rootBounds| should not be nullable. + //readonly rootBounds: DOMRectReadOnly | null; // rootBounds 可以为 null + //readonly boundingClientRect: DOMRectReadOnly; + //readonly intersectionRect: DOMRectReadOnly; + + readonly isIntersecting: boolean; + + // TODO(pengfei12.guo): isVisible not supported + //readonly isVisible: boolean; + + // TODO(pengfei12.guo): intersectionRatio not supported + //readonly intersectionRatio: number; + + readonly target: Element; + + new(): void; +} + + diff --git a/bridge/core/dom/intersection_observer_entry.h b/bridge/core/dom/intersection_observer_entry.h new file mode 100644 index 0000000000..06e37edeb3 --- /dev/null +++ b/bridge/core/dom/intersection_observer_entry.h @@ -0,0 +1,62 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Copyright (C) 2024-present The WebF authors. All rights reserved. + +#ifndef WEBF_CORE_INTERSECTION_OBSERVER_INTERSECTION_OBSERVER_ENTRY_H_ +#define WEBF_CORE_INTERSECTION_OBSERVER_INTERSECTION_OBSERVER_ENTRY_H_ + +#include "bindings/qjs/cppgc/member.h" +#include "core/binding_object.h" + +namespace webf { + +class Element; + +class IntersectionObserverEntry final : public BindingObject { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = IntersectionObserverEntry*; + + IntersectionObserverEntry(ExecutingContext* context, bool isIntersecting, Element* target); + // TODO(pengfei12.guo): not supported + // IDL interface + // double time() const { return time_; } + // double intersectionRatio() const { + // return geometry_.IntersectionRatio(); + //} + // DOMRectReadOnly* boundingClientRect() const; + // DOMRectReadOnly* rootBounds() const; + // DOMRectReadOnly* intersectionRect() const; + // bool isVisible() const { + // return geometry_.IsVisible(); + //} + + bool isIntersecting() const { return isIntersecting_; } + + Element* target() const { return target_.Get(); } + + // TODO(pengfei12.guo): IntersectionGeometry not supported + // blink-internal interface + // const IntersectionGeometry& GetGeometry() const { return geometry_; } + void Trace(GCVisitor*) const override; + + bool IsIntersectionObserverEntry() const override { return true; }; + + private: + // IntersectionGeometry geometry_; + bool isIntersecting_; + // DOMHighResTimeStamp time_; + Member target_; +}; + +template <> +struct DowncastTraits { + static bool AllowFrom(const BindingObject& binding_object) { return binding_object.IsIntersectionObserverEntry(); } +}; + +} // namespace webf + +#endif // WEBF_CORE_INTERSECTION_OBSERVER_INTERSECTION_OBSERVER_ENTRY_H_ diff --git a/bridge/core/dom/intersection_observer_init.d.ts b/bridge/core/dom/intersection_observer_init.d.ts new file mode 100644 index 0000000000..680bace87c --- /dev/null +++ b/bridge/core/dom/intersection_observer_init.d.ts @@ -0,0 +1,20 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://wicg.github.io/IntersectionObserver/#intersection-observer-init + +// Copyright (C) 2024-present The WebF authors. All rights reserved. + +import {Node} from "./node"; + +// @ts-ignore +@Dictionary() +export interface IntersectionObserverInit { + root?: Node | null; // 指定根(root)元素,用于检查目标的可见性。必须是目标元素的父级元素。 + rootMargin?: string; // 根(root)元素的外边距,用作 root 元素和 target 发生交集时候的计算交集的区域范围 + //scrollMargin?: string; + threshold?: number[]; // 数组,该值为 1.0 含义是当 target 完全出现在 root 元素中时候回调才会被执行 + //delay?: number; + //trackVisibility?: boolean; +} diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 83ef523614..8e208efce3 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -74,6 +74,47 @@ Promise.resolve().then(() => { EXPECT_EQ(logCalled, true); } +TEST(Node, IntersectionObserver) { +bool static errorCalled = false; +bool static logCalled = false; +webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; +auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); +auto context = env->page()->executingContext(); +const char* code = R"( + // Create the observed element + const div = document.createElement('div'); + + // Callback function to execute when mutations are observed + const callback = (entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { // Element is visible + console.log('Element is intersecting'); + } else { // Element is not visible + console.log('Element is not intersecting'); + } + }); + }; + + const options = { + root: null, // Use the viewport as the root + rootMargin: "0px", + threshold: [0, 1], // Trigger callback at 0% and 100% visibility + }; + + // Create an observer instance linked to the callback function + const observer = new IntersectionObserver(callback, options); + + // Start observing the target element + observer.observe(div); + )"; +env->page()->evaluateScript(code, strlen(code), "vm://", 0); + +TEST_runLoop(context); + +EXPECT_EQ(errorCalled, false); +EXPECT_EQ(logCalled, true); +} + TEST(Node, nodeName) { bool static errorCalled = false; bool static logCalled = false; @@ -367,4 +408,4 @@ console.assert(el.isConnected == false); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, false); -} \ No newline at end of file +} diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 14c4aff2a1..f1ce148f72 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -9,6 +9,7 @@ #include "built_in_string.h" #include "core/dom/document.h" #include "core/dom/mutation_observer.h" +#include "core/dom/intersection_observer.h" #include "core/events/error_event.h" #include "core/events/promise_rejection_event.h" #include "event_type_names.h" diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 3066ae3717..460caa99b6 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -40,6 +40,10 @@ UICommandKind GetKindFromUICommand(UICommand command) { case UICommand::kStartRecordingCommand: case UICommand::kFinishRecordingCommand: return UICommandKind::kOperation; + case UICommand::kAddIntersectionObserver: + case UICommand::kRemoveIntersectionObserver: + case UICommand::kDisconnectIntersectionObserver: + return UICommandKind::kIntersectionObserver; } } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index d8eea269c3..8e4f23e907 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -20,7 +20,8 @@ enum UICommandKind : uint32_t { kEvent = 1 << 4, kAttributeUpdate = 1 << 5, kDisposeBindingObject = 1 << 6, - kOperation = 1 << 7 + kOperation = 1 << 7, + kIntersectionObserver = 1 << 8 }; enum class UICommand { @@ -44,6 +45,9 @@ enum class UICommand { kCreateSVGElement, kCreateElementNS, kFinishRecordingCommand, + kAddIntersectionObserver, + kRemoveIntersectionObserver, + kDisconnectIntersectionObserver }; #define MAXIMUM_UI_COMMAND_SIZE 2048 diff --git a/bridge/scripts/code_generator/templates/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/dictionary.h.tpl index 15fd3d6a3d..ed193727df 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/dictionary.h.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/dictionary.h.tpl @@ -7,6 +7,7 @@ namespace webf { class ExecutingContext; class ExceptionState; +class Node; class <%= className %> : public <%= object.parent ? object.parent : 'DictionaryBase' %> { public: diff --git a/bridge/third_party/quickjs/vendor/mimalloc b/bridge/third_party/quickjs/vendor/mimalloc index db3d8485d2..43ce4bd7fd 160000 --- a/bridge/third_party/quickjs/vendor/mimalloc +++ b/bridge/third_party/quickjs/vendor/mimalloc @@ -1 +1 @@ -Subproject commit db3d8485d2f45a6f179d784f602f9eff4f60795c +Subproject commit 43ce4bd7fd34bcc730c1c7471c99995597415488 diff --git a/webf/example/assets/IntersectionObserver_test.html b/webf/example/assets/IntersectionObserver_test.html new file mode 100644 index 0000000000..280cf433bb --- /dev/null +++ b/webf/example/assets/IntersectionObserver_test.html @@ -0,0 +1,55 @@ + + + + + + + IntersectionObserver and MutationObserver Test + + + + +
+
+ + + + diff --git a/webf/example/lib/main.dart b/webf/example/lib/main.dart index dc3bd452ea..5324e6c80d 100644 --- a/webf/example/lib/main.dart +++ b/webf/example/lib/main.dart @@ -44,7 +44,7 @@ class FirstPageState extends State { context, devToolsService: ChromeDevToolsService(), ); - controller.preload(WebFBundle.fromUrl('assets:assets/bundle.html')); + controller.preload(WebFBundle.fromUrl('assets:assets/IntersectionObserver_test.html')); } @override diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index bb047a1527..071b715195 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -14,6 +14,7 @@ import 'package:webf/dom.dart'; import 'package:webf/geometry.dart'; import 'package:webf/foundation.dart'; import 'package:webf/launcher.dart'; +import '../dom/intersection_observer.dart'; // We have some integrated built-in behavior starting with string prefix reuse the callNativeMethod implements. enum BindingMethodCallOperations { @@ -157,7 +158,8 @@ Future _dispatchEventToNative(Event event, bool isCapture) async { } enum CreateBindingObjectType { - createDOMMatrix + createDOMMatrix, + createIntersectionObserver } abstract class BindingBridge { @@ -178,6 +180,11 @@ abstract class BindingBridge { controller.view.setBindingObject(pointer, domMatrix); return; } + case CreateBindingObjectType.createIntersectionObserver: { + IntersectionObserver intersectionObserver = IntersectionObserver(BindingContext(controller.view, contextId, pointer)); + controller.view.setBindingObject(pointer, intersectionObserver); + return; + } } } diff --git a/webf/lib/src/bridge/to_native.dart b/webf/lib/src/bridge/to_native.dart index 609f0490f0..02942abe6a 100644 --- a/webf/lib/src/bridge/to_native.dart +++ b/webf/lib/src/bridge/to_native.dart @@ -688,6 +688,10 @@ enum UICommandType { createSVGElement, createElementNS, finishRecordingCommand, + // IntersectionObserver + addIntersectionObserver, + removeIntersectionObserver, + disconnectIntersectionObserver, } class UICommandItem extends Struct { diff --git a/webf/lib/src/bridge/ui_command.dart b/webf/lib/src/bridge/ui_command.dart index 98e93b6dc3..6708604d72 100644 --- a/webf/lib/src/bridge/ui_command.dart +++ b/webf/lib/src/bridge/ui_command.dart @@ -312,6 +312,39 @@ void execUICommands(WebFViewController view, List commands) { WebFProfiler.instance.finishTrackUICommandStep(); } break; + case UICommandType.addIntersectionObserver: + if (enableWebFProfileTracking) { + WebFProfiler.instance.startTrackUICommandStep('FlushUICommand.addIntersectionObserver'); + } + + view.addIntersectionObserver( + nativePtr.cast(), command.nativePtr2.cast()); + if (enableWebFProfileTracking) { + WebFProfiler.instance.finishTrackUICommandStep(); + } + + break; + case UICommandType.removeIntersectionObserver: + if (enableWebFProfileTracking) { + WebFProfiler.instance.startTrackUICommandStep('FlushUICommand.removeIntersectionObserver'); + } + + view.removeIntersectionObserver( + nativePtr.cast(), command.nativePtr2.cast()); + if (enableWebFProfileTracking) { + WebFProfiler.instance.finishTrackUICommandStep(); + } + break; + case UICommandType.disconnectIntersectionObserver: + if (enableWebFProfileTracking) { + WebFProfiler.instance.startTrackUICommandStep('FlushUICommand.disconnectIntersectionObserver'); + } + + view.disconnectIntersectionObserver(nativePtr.cast()); + if (enableWebFProfileTracking) { + WebFProfiler.instance.finishTrackUICommandStep(); + } + break; default: break; } diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 5f52b61af9..5c79d5816e 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -18,6 +18,7 @@ import 'package:webf/rendering.dart'; import 'package:webf/src/css/query_selector.dart' as QuerySelector; import 'package:webf/src/dom/element_registry.dart' as element_registry; import 'package:webf/src/foundation/cookie_jar.dart'; +import 'package:webf/src/dom/intersection_observer.dart'; /// In the document tree, there may contains WidgetElement which connected to a Flutter Elements. /// And these flutter element will be unmounted in the end of this frame and their renderObject will call dispose() too. @@ -99,6 +100,8 @@ class Document extends ContainerNode { final Set _styleDirtyElements = {}; + final Set _intersectionObserverList = HashSet(); + void markElementStyleDirty(Element element) { _styleDirtyElements.add(element.pointer!.address); } @@ -588,4 +591,31 @@ class Document extends ContainerNode { pendingPreloadingScriptCallbacks.clear(); super.dispose(); } + + void addIntersectionObserver(IntersectionObserver observer, Element element) { + observer.observe(element); + _intersectionObserverList.add(observer); + } + + void removeIntersectionObserver(IntersectionObserver observer, Element element) { + observer.unobserve(element); + if (!observer.HasObservations()) { + _intersectionObserverList.remove(observer); + } + } + + void disconnectIntersectionObserver(IntersectionObserver observer) { + observer.disconnect(); + _intersectionObserverList.remove(observer); + } + + void deliverIntersectionObserver() { + debugPrint('Document.deliverIntersectionObserver pointer:$pointer'); + if (_intersectionObserverList.isEmpty) { + return; + } + for (var observer in _intersectionObserverList) { + observer.deliver(controller); + } + } } diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index c44fe714be..495b27d3f0 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:ui'; +import 'dart:collection'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; @@ -19,6 +20,8 @@ import 'package:webf/src/svg/rendering/container.dart'; import 'package:webf/svg.dart'; import 'package:webf/widget.dart'; import 'package:webf/src/css/query_selector.dart' as QuerySelector; +import 'intersection_observer.dart'; +import 'intersection_observer_entry.dart'; final RegExp classNameSplitRegExp = RegExp(r'\s+'); const String _ONE_SPACE = ' '; @@ -109,6 +112,8 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin // Default to unknown, assign by [createElement], used by inspector. String tagName = UNKNOWN; + final Set _intersectionObserverList = HashSet(); + String? _id; String? get id => _id; @@ -1978,6 +1983,37 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin } return style; } + + void _handleIntersectionObserver(IntersectionObserverEntry entry) async { + debugPrint('Element._handleIntersectionObserver observer=$entry,element=$this'); + // TODO(pengfei12.guo): 若存在多个IntersectionObserver,无法区分IntersectionObserver + for (var observer in _intersectionObserverList) { + observer.addEntry(DartIntersectionObserverEntry(entry.isIntersecting, this)); + } + } + + // IntersectionObserver 相关 + bool addIntersectionObserver(IntersectionObserver observer) { + debugPrint('Element.addIntersectionObserver observer=$observer,element=$this'); + if (_intersectionObserverList.contains(observer)) { + return false; + } + if (_intersectionObserverList.isEmpty) { + renderBoxModel?.addIntersectionChangeListener(_handleIntersectionObserver); + renderBoxModel?.markNeedsPaint();//markNeedsCompositingBitsUpdate + } + _intersectionObserverList.add(observer); + return true; + } + + void removeIntersectionObserver(IntersectionObserver observer) { + debugPrint('Element.removeIntersectionObserver observer=$observer,element=$this'); + _intersectionObserverList.remove(observer); + + if (_intersectionObserverList.isEmpty) { + renderBoxModel?.removeIntersectionChangeListener(_handleIntersectionObserver); + } + } } // https://www.w3.org/TR/css-position-3/#def-cb diff --git a/webf/lib/src/dom/intersection_observer.dart b/webf/lib/src/dom/intersection_observer.dart new file mode 100644 index 0000000000..2b18a172f2 --- /dev/null +++ b/webf/lib/src/dom/intersection_observer.dart @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +import 'dart:async'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; +import 'package:webf/foundation.dart'; +import '../../bridge.dart'; +import '../../launcher.dart'; +import 'element.dart'; +import 'intersection_observer_entry.dart'; +import 'package:flutter/foundation.dart'; + +class _IntersectionObserverDeliverContext { + Completer completer; + Stopwatch? stopwatch; + + // Pointer method; + Pointer allocatedNativeArguments; + //Pointer rawNativeEntries; + WebFController controller; + EvaluateOpItem? profileOp; + + _IntersectionObserverDeliverContext( + this.completer, + this.stopwatch, + this.allocatedNativeArguments, + //this.rawNativeEntries, + this.controller, + this.profileOp, + ); +} + +void _handleDeliverResult(_IntersectionObserverDeliverContext context, Pointer returnValue) { + Pointer dispatchResult = + fromNativeValue(context.controller.view, returnValue).cast(); + + if (enableWebFCommandLog && context.stopwatch != null) { + debugPrint('deliver IntersectionObserverEntry to native side, time: ${context.stopwatch!.elapsedMicroseconds}us'); + } + + // Free the allocated arguments. + malloc.free(context.allocatedNativeArguments); + malloc.free(dispatchResult); + malloc.free(returnValue); + + if (enableWebFProfileTracking) { + WebFProfiler.instance.finishTrackEvaluate(context.profileOp!); + } + + context.completer.complete(); +} + +class IntersectionObserver extends DynamicBindingObject { + IntersectionObserver(BindingContext? context) : super(context); + + @override + void initializeMethods(Map methods) {} + + @override + void initializeProperties(Map properties) {} + + void observe(Element element) { + if (!element.addIntersectionObserver(this)) { + return; + } + _elementList.add(element); + debugPrint('Dom.IntersectionObserver.observe'); + + // TODO(pengfei12.guo): test deliver + Future.delayed(Duration(milliseconds: 1000), () async { + addEntry(DartIntersectionObserverEntry(true, element)); + await deliver(element.ownerView.rootController); + }); + } + + void unobserve(Element element) { + _elementList.remove(element); + element.removeIntersectionObserver(this); + debugPrint('Dom.IntersectionObserver.unobserve'); + } + + void disconnect() { + if (_elementList.isEmpty) return; + for (var element in _elementList) { + element!.removeIntersectionObserver(this); + } + _elementList.clear(); + } + + bool HasObservations() { + return _elementList.isNotEmpty; + } + + void addEntry(DartIntersectionObserverEntry entry) { + debugPrint('Dom.IntersectionObserver.addEntry entry:$entry'); + _entries.add(entry); + } + + List takeRecords() { + List entries = _entries.map((entry) => entry.copy()).toList(); + _entries.clear(); + return toNativeEntries(entries); + } + + List toNativeEntries(List entries) { + if (entries.isEmpty) { + return []; + } + + return List.generate(entries.length, (i) { + return NativeIntersectionObserverEntry( + BindingContext( + entries[i].element.ownerView, + entries[i].element.ownerView.contextId, + allocateNewBindingObject(), + ), + entries[i].isIntersecting, + entries[i].element, + ); + }); + } + + Future deliver(WebFController controller) async { + if (pointer == null) return; + debugPrint('Dom.IntersectionObserver.deliver pointer:$pointer'); + List nativeEntries = takeRecords(); + if (nativeEntries.isNotEmpty) { + Completer completer = Completer(); + + EvaluateOpItem? currentProfileOp; + if (enableWebFProfileTracking) { + currentProfileOp = WebFProfiler.instance.startTrackEvaluate('_dispatchEventToNative'); + } + + BindingObject bindingObject = controller.view.getBindingObject(pointer!); + // Call methods implements at C++ side. + DartInvokeBindingMethodsFromDart? f = pointer!.ref.invokeBindingMethodFromDart.asFunction(); + + // Pointer method = malloc.allocate(sizeOf()); + // toNativeValue(method, 'deliver'); + + List dispatchEntryArguments = [nativeEntries]; + + Stopwatch? stopwatch; + if (enableWebFCommandLog) { + stopwatch = Stopwatch()..start(); + } + + Pointer allocatedNativeArguments = makeNativeValueArguments(bindingObject, dispatchEntryArguments); + + _IntersectionObserverDeliverContext context = _IntersectionObserverDeliverContext( + completer, stopwatch, allocatedNativeArguments, controller, currentProfileOp); + + Pointer> resultCallback = Pointer.fromFunction(_handleDeliverResult); + + Future.microtask(() { +// typedef DartInvokeBindingMethodsFromDart = void Function( +// Pointer binding_object, +// int profileId, +// Pointer method, +// int argc, +// Pointer argv, +// Object bindingDartObject, +// Pointer> result_callback); + f(pointer!, currentProfileOp?.hashCode ?? 0, nullptr, dispatchEntryArguments.length, allocatedNativeArguments, + context, resultCallback); + }); + + return completer.future; + } + } + + final List _entries = []; + final List _elementList = []; +} diff --git a/webf/lib/src/dom/intersection_observer_entry.dart b/webf/lib/src/dom/intersection_observer_entry.dart new file mode 100644 index 0000000000..dc208cb4f9 --- /dev/null +++ b/webf/lib/src/dom/intersection_observer_entry.dart @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'package:webf/foundation.dart'; +import 'element.dart'; + +class DartIntersectionObserverEntry { + //final DOMHighResTimeStamp time; + //final DOMRectReadOnly? rootBounds; + //final DOMRectReadOnly boundingClientRect; + //final DOMRectReadOnly intersectionRect; + final bool isIntersecting; + + //final bool isVisible; + //final double intersectionRatio; + final Element element; + + DartIntersectionObserverEntry(this.isIntersecting, this.element); + + DartIntersectionObserverEntry copy() { + return DartIntersectionObserverEntry(isIntersecting, element); + } +} + +class NativeIntersectionObserverEntry extends DynamicBindingObject { + //final DOMHighResTimeStamp time; + //final DOMRectReadOnly? rootBounds; + //final DOMRectReadOnly boundingClientRect; + //final DOMRectReadOnly intersectionRect; + final bool isIntersecting; + + //final bool isVisible; + //final double intersectionRatio; + final Element target; + + NativeIntersectionObserverEntry(BindingContext context, this.isIntersecting, this.target) : super(context); + + @override + void initializeMethods(Map methods) { + // TODO: implement initializeMethods + } + + @override + void initializeProperties(Map properties) { + // TODO: implement initializeProperties + } +} diff --git a/webf/lib/src/launcher/controller.dart b/webf/lib/src/launcher/controller.dart index bb564ea8a3..06e86e7812 100644 --- a/webf/lib/src/launcher/controller.dart +++ b/webf/lib/src/launcher/controller.dart @@ -26,6 +26,7 @@ import 'package:webf/gesture.dart'; import 'package:webf/rendering.dart'; import 'package:webf/devtools.dart'; import 'package:webf/webf.dart'; +import 'package:webf/src/dom/intersection_observer.dart'; // Error handler when load bundle failed. typedef LoadErrorHandler = void Function(FlutterError error, StackTrace stack); @@ -223,6 +224,7 @@ class WebFViewController implements WidgetsBindingObserver { if (disposed && _isFrameBindingAttached) return; _isFrameBindingAttached = true; flushUICommand(this, window.pointer!); + deliverIntersectionObserver(); SchedulerBinding.instance.addPostFrameCallback((_) => flushPendingCommandsPerFrame()); } @@ -499,6 +501,50 @@ class WebFViewController implements WidgetsBindingObserver { document.createDocumentFragment(BindingContext(document.controller.view, _contextId, nativePtr)); } + void addIntersectionObserver( + Pointer observerPointer, Pointer elementPointer) { + debugPrint('Dom.IntersectionObserver.observe'); + assert(hasBindingObject(observerPointer), 'observer: $observerPointer'); + assert(hasBindingObject(elementPointer), 'element: $elementPointer'); + + IntersectionObserver? observer = getBindingObject(observerPointer); + Element? element = getBindingObject(elementPointer); + if (nullptr == observer || nullptr == element) { + return; + } + + document.addIntersectionObserver(observer!, element!); + } + + void removeIntersectionObserver( + Pointer observerPointer, Pointer elementPointer) { + assert(hasBindingObject(observerPointer), 'observer: $observerPointer'); + assert(hasBindingObject(elementPointer), 'element: $elementPointer'); + + IntersectionObserver? observer = getBindingObject(observerPointer); + Element? element = getBindingObject(elementPointer); + if (nullptr == observer || nullptr == element) { + return; + } + + document.removeIntersectionObserver(observer!, element!); + } + + void disconnectIntersectionObserver(Pointer observerPointer) { + assert(hasBindingObject(observerPointer), 'observer: $observerPointer'); + + IntersectionObserver? observer = getBindingObject(observerPointer); + if (nullptr == observer) { + return; + } + + document.disconnectIntersectionObserver(observer!); + } + + void deliverIntersectionObserver() { + document.deliverIntersectionObserver(); + } + void addEvent(Pointer nativePtr, String eventType, {Pointer? addEventListenerOptions}) { if (!hasBindingObject(nativePtr)) return;