From 9b9a70267e5e55f63f753042b3cde2642fd6ebd1 Mon Sep 17 00:00:00 2001 From: "pengfei12.guo" Date: Wed, 16 Oct 2024 14:40:38 +0800 Subject: [PATCH 1/7] =?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; From 96870df15127cd1e5737a628245bfc38968d7fa7 Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 16 Oct 2024 22:01:22 +0800 Subject: [PATCH 2/7] fix: fix native intersection observer entry. --- bridge/core/dom/intersection_observer.cc | 38 +++++++++----- webf/lib/src/dom/intersection_observer.dart | 51 +++++++++---------- .../src/dom/intersection_observer_entry.dart | 28 +++------- 3 files changed, 53 insertions(+), 64 deletions(-) diff --git a/bridge/core/dom/intersection_observer.cc b/bridge/core/dom/intersection_observer.cc index 67270b2274..bb1e845793 100644 --- a/bridge/core/dom/intersection_observer.cc +++ b/bridge/core/dom/intersection_observer.cc @@ -9,17 +9,22 @@ #include #include -#include "core/dom/element.h" -#include #include "bindings/qjs/converter_impl.h" +#include "core/dom/element.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" +#include "foundation/native_value_converter.h" +#include "qjs_intersection_observer_init.h" namespace webf { +struct NativeIntersectionObserverEntry : public DartReadable { + int8_t is_intersecting; + NativeBindingObject* target; +}; + IntersectionObserver* IntersectionObserver::Create(ExecutingContext* context, const std::shared_ptr& function, ExceptionState& exception_state) { @@ -105,28 +110,33 @@ NativeValue IntersectionObserver::HandleCallFromDartSide(const AtomicString& met int32_t argc, const NativeValue* argv, Dart_Handle dart_object) { - if (!GetExecutingContext() || !GetExecutingContext()->IsContextValid()) { + if (!GetExecutingContext() || !GetExecutingContext()->IsContextValid()) { WEBF_LOG(ERROR) << "[IntersectionObserver]: HandleCallFromDartSide Context Valid" << std::endl; return Native_NewNull(); } + MemberMutationScope scope{GetExecutingContext()}; WEBF_LOG(DEBUG) << "[IntersectionObserver]: HandleCallFromDartSide NativeValueConverter" << std::endl; - NativeValue native_entry_list = argv[0]; - std::vector entries = - NativeValueConverter>>::FromNativeValue( - ctx(), native_entry_list); + NativeIntersectionObserverEntry* native_entry = + NativeValueConverter>::FromNativeValue(argv[0]); + size_t length = NativeValueConverter::FromNativeValue(argv[1]); - if (!entries.empty()) { + if (length > 0) { 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); + JSValue js_array = JS_NewArray(ctx()); + for (int i = 0; i < length; i++) { + auto* entry = MakeGarbageCollected( + GetExecutingContext(), native_entry[i].is_intersecting, + DynamicTo(BindingObject::From(native_entry[i].target))); + JS_SetPropertyUint32(ctx(), js_array, i, entry->ToQuickJS()); + } + ScriptValue arguments[] = {ScriptValue(ctx(), js_array), ToValue()}; WEBF_LOG(DEBUG) << "[IntersectionObserver]: HandleCallFromDartSide function_ Invoke" << std::endl; function_->Invoke(ctx(), ToValue(), 2, arguments); + + JS_FreeValue(ctx(), js_array); } else { WEBF_LOG(ERROR) << "[IntersectionObserver]: HandleCallFromDartSide entries is empty"; } diff --git a/webf/lib/src/dom/intersection_observer.dart b/webf/lib/src/dom/intersection_observer.dart index 2b18a172f2..c5f31f3443 100644 --- a/webf/lib/src/dom/intersection_observer.dart +++ b/webf/lib/src/dom/intersection_observer.dart @@ -19,6 +19,7 @@ class _IntersectionObserverDeliverContext { // Pointer method; Pointer allocatedNativeArguments; + //Pointer rawNativeEntries; WebFController controller; EvaluateOpItem? profileOp; @@ -33,7 +34,8 @@ class _IntersectionObserverDeliverContext { ); } -void _handleDeliverResult(_IntersectionObserverDeliverContext context, Pointer returnValue) { +void _handleDeliverResult(Object handle, Pointer returnValue) { + _IntersectionObserverDeliverContext context = handle as _IntersectionObserverDeliverContext; Pointer dispatchResult = fromNativeValue(context.controller.view, returnValue).cast(); @@ -99,35 +101,17 @@ class IntersectionObserver extends DynamicBindingObject { _entries.add(entry); } - List takeRecords() { + 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, - ); - }); + return entries; } Future deliver(WebFController controller) async { if (pointer == null) return; debugPrint('Dom.IntersectionObserver.deliver pointer:$pointer'); - List nativeEntries = takeRecords(); - if (nativeEntries.isNotEmpty) { + List entries = takeRecords(); + if (entries.isNotEmpty) { Completer completer = Completer(); EvaluateOpItem? currentProfileOp; @@ -139,16 +123,26 @@ class IntersectionObserver extends DynamicBindingObject { // 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(); } + // Allocate an chunk of memory for an list of NativeIntersectionObserverEntry + Pointer head = + malloc.allocate(sizeOf() * entries.length); + + // Write the native memory from dart objects. + for(int i = 0; i < entries.length; i ++) { + (head + i).ref.isIntersecting = entries[i].isIntersecting ? 1 : 0; + (head + i).ref.target = entries[i].element.pointer!; + } + + List dispatchEntryArguments = [ + head, + entries.length + ]; + Pointer allocatedNativeArguments = makeNativeValueArguments(bindingObject, dispatchEntryArguments); _IntersectionObserverDeliverContext context = _IntersectionObserverDeliverContext( @@ -167,6 +161,7 @@ class IntersectionObserver extends DynamicBindingObject { // Pointer> result_callback); f(pointer!, currentProfileOp?.hashCode ?? 0, nullptr, dispatchEntryArguments.length, allocatedNativeArguments, context, resultCallback); + malloc.free(head); }); return completer.future; diff --git a/webf/lib/src/dom/intersection_observer_entry.dart b/webf/lib/src/dom/intersection_observer_entry.dart index dc208cb4f9..ce4291cfcf 100644 --- a/webf/lib/src/dom/intersection_observer_entry.dart +++ b/webf/lib/src/dom/intersection_observer_entry.dart @@ -3,7 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import 'package:webf/foundation.dart'; +import 'dart:ffi'; +import 'package:webf/bridge.dart'; import 'element.dart'; class DartIntersectionObserverEntry { @@ -24,26 +25,9 @@ class DartIntersectionObserverEntry { } } -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); +class NativeIntersectionObserverEntry extends Struct { + @Int8() + external int isIntersecting; - @override - void initializeMethods(Map methods) { - // TODO: implement initializeMethods - } - - @override - void initializeProperties(Map properties) { - // TODO: implement initializeProperties - } + external Pointer target; } From 03cabce3ce9ee016ac61ab33d19fc06a168e646a Mon Sep 17 00:00:00 2001 From: "pengfei12.guo" Date: Thu, 17 Oct 2024 16:59:43 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E4=BC=98=E5=8C=96IntersectionObserver?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81thresholds=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bridge/core/binding_object.cc | 4 - bridge/core/binding_object.h | 1 - bridge/core/dom/dom_string_map.cc | 3 +- bridge/core/dom/intersection_observer.cc | 56 ++++---- bridge/core/dom/intersection_observer.d.ts | 3 - bridge/core/dom/intersection_observer.h | 7 +- .../core/dom/intersection_observer_entry.cc | 24 ++-- .../core/dom/intersection_observer_entry.d.ts | 19 +-- bridge/core/dom/intersection_observer_entry.h | 29 ++-- bridge/foundation/ui_command_strategy.cc | 7 +- webf/lib/src/bridge/binding.dart | 2 +- webf/lib/src/dom/element.dart | 36 +++-- webf/lib/src/dom/event.dart | 12 +- webf/lib/src/dom/intersection_observer.dart | 132 ++++++++---------- .../src/dom/intersection_observer_entry.dart | 11 +- webf/lib/src/html/img.dart | 3 +- .../src/rendering/intersection_observer.dart | 92 +++++++++--- 17 files changed, 249 insertions(+), 192 deletions(-) diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 88982642b8..2d52b75669 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -432,8 +432,4 @@ 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 fe99758857..4b67dfd7fb 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -139,7 +139,6 @@ 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/dom_string_map.cc b/bridge/core/dom/dom_string_map.cc index 1b3ec3a293..e7cc1901d1 100644 --- a/bridge/core/dom/dom_string_map.cc +++ b/bridge/core/dom/dom_string_map.cc @@ -150,7 +150,8 @@ bool DOMStringMap::SetItem(const webf::AtomicString& key, } auto attribute_name = AtomicString(ctx(), ConvertPropertyNameToAttributeName(key.ToStdString(ctx()))); - return owner_element_->attributes()->setAttribute(attribute_name, value, exception_state); + owner_element_->setAttribute(attribute_name, value, exception_state); + return true; } bool DOMStringMap::DeleteItem(const webf::AtomicString& key, webf::ExceptionState& exception_state) { diff --git a/bridge/core/dom/intersection_observer.cc b/bridge/core/dom/intersection_observer.cc index bb1e845793..e661ddafef 100644 --- a/bridge/core/dom/intersection_observer.cc +++ b/bridge/core/dom/intersection_observer.cc @@ -9,22 +9,17 @@ #include #include -#include "bindings/qjs/converter_impl.h" #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 "foundation/logging.h" -#include "foundation/native_value_converter.h" #include "qjs_intersection_observer_init.h" +#include "foundation/logging.h" namespace webf { -struct NativeIntersectionObserverEntry : public DartReadable { - int8_t is_intersecting; - NativeBindingObject* target; -}; - IntersectionObserver* IntersectionObserver::Create(ExecutingContext* context, const std::shared_ptr& function, ExceptionState& exception_state) { @@ -49,12 +44,22 @@ IntersectionObserver::IntersectionObserver(ExecutingContext* context, const std::shared_ptr& function, const std::shared_ptr& observer_init) : BindingObject(context->ctx()), function_(function) { - if (observer_init->hasRoot()) { + if (observer_init && observer_init->hasRoot()) { root_ = observer_init->root(); } + NativeValue arguments[1]; + if (observer_init && observer_init->hasThreshold()) { +#if ENABLE_LOG + WEBF_LOG(DEBUG) << "[IntersectionObserver]: Constructor threshold.size = " << observer_init->threshold().size() + << std::endl; +#endif + thresholds_ = std::move(observer_init->threshold()); + std::sort(thresholds_.begin(), thresholds_.end()); + arguments[0] = NativeValueConverter>::ToNativeValue(thresholds_); + } GetExecutingContext()->dartMethodPtr()->createBindingObject( GetExecutingContext()->isDedicated(), GetExecutingContext()->contextId(), bindingObject(), - CreateBindingObjectType::kCreateIntersectionObserver, nullptr, 0); + CreateBindingObjectType::kCreateIntersectionObserver, arguments, 1); } bool IntersectionObserver::RootIsValid() const { @@ -62,17 +67,23 @@ bool IntersectionObserver::RootIsValid() const { } void IntersectionObserver::observe(Element* target, ExceptionState& exception_state) { - if (!RootIsValid() || !target) + if (!RootIsValid() || !target) { + WEBF_LOG(ERROR) << "[IntersectionObserver]: observe valid:" << std::endl; return; + } - // TODO(pengfei12.guo@vipshop.com): 通知dart,注册IntersectionObserver +#if ENABLE_LOG + WEBF_LOG(DEBUG) << "[IntersectionObserver]: observe target=" << target << ",tagName=" << target->nodeName() << std::endl; +#endif GetExecutingContext()->uiCommandBuffer()->AddCommand(UICommand::kAddIntersectionObserver, nullptr, bindingObject(), target->bindingObject()); } void IntersectionObserver::unobserve(Element* target, ExceptionState& exception_state) { - if (!target) + if (!target) { + WEBF_LOG(ERROR) << "[IntersectionObserver]: unobserve valid:" << std::endl; return; + } GetExecutingContext()->uiCommandBuffer()->AddCommand(UICommand::kRemoveIntersectionObserver, nullptr, bindingObject(), target->bindingObject()); @@ -99,13 +110,6 @@ void IntersectionObserver::disconnect(ExceptionState& exception_state) { // 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, @@ -116,24 +120,27 @@ NativeValue IntersectionObserver::HandleCallFromDartSide(const AtomicString& met } MemberMutationScope scope{GetExecutingContext()}; - WEBF_LOG(DEBUG) << "[IntersectionObserver]: HandleCallFromDartSide NativeValueConverter" << std::endl; + NativeIntersectionObserverEntry* native_entry = NativeValueConverter>::FromNativeValue(argv[0]); size_t length = NativeValueConverter::FromNativeValue(argv[1]); if (length > 0) { assert(function_ != nullptr); - WEBF_LOG(DEBUG) << "[IntersectionObserver]: HandleCallFromDartSide To JSValue" << std::endl; JSValue js_array = JS_NewArray(ctx()); for (int i = 0; i < length; i++) { auto* entry = MakeGarbageCollected( - GetExecutingContext(), native_entry[i].is_intersecting, + GetExecutingContext(), native_entry[i].is_intersecting, native_entry[i].intersectionRatio, DynamicTo(BindingObject::From(native_entry[i].target))); JS_SetPropertyUint32(ctx(), js_array, i, entry->ToQuickJS()); } ScriptValue arguments[] = {ScriptValue(ctx(), js_array), ToValue()}; - WEBF_LOG(DEBUG) << "[IntersectionObserver]: HandleCallFromDartSide function_ Invoke" << std::endl; +#if ENABLE_LOG + WEBF_LOG(DEBUG) << "[IntersectionObserver]: HandleCallFromDartSide length=" << length << ",JS function_ Invoke" + << std::endl; +#endif + function_->Invoke(ctx(), ToValue(), 2, arguments); JS_FreeValue(ctx(), js_array); @@ -146,7 +153,6 @@ NativeValue IntersectionObserver::HandleCallFromDartSide(const AtomicString& met void IntersectionObserver::Trace(GCVisitor* visitor) const { BindingObject::Trace(visitor); - BindingObject::Trace(visitor); function_->Trace(visitor); } diff --git a/bridge/core/dom/intersection_observer.d.ts b/bridge/core/dom/intersection_observer.d.ts index 5478131dbd..acb226ff2a 100644 --- a/bridge/core/dom/intersection_observer.d.ts +++ b/bridge/core/dom/intersection_observer.d.ts @@ -11,10 +11,7 @@ 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; diff --git a/bridge/core/dom/intersection_observer.h b/bridge/core/dom/intersection_observer.h index a7acee43a2..a90629b284 100644 --- a/bridge/core/dom/intersection_observer.h +++ b/bridge/core/dom/intersection_observer.h @@ -125,13 +125,13 @@ class IntersectionObserver final : public BindingObject { // std::vector> takeRecords(ExceptionState&); // API attributes. - Node* root() const { return root_; } + [[nodiscard]] 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_; } + + [[nodiscard]] 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()) { @@ -192,6 +192,7 @@ class IntersectionObserver final : public BindingObject { // We use UntracedMember<> here to do custom weak processing. Node* root_; + std::vector thresholds_; // TODO(pengfei12.guo): not support // const std::vector margin_; diff --git a/bridge/core/dom/intersection_observer_entry.cc b/bridge/core/dom/intersection_observer_entry.cc index 226f811087..a57022a85a 100644 --- a/bridge/core/dom/intersection_observer_entry.cc +++ b/bridge/core/dom/intersection_observer_entry.cc @@ -9,22 +9,14 @@ 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())); -// } +IntersectionObserverEntry::IntersectionObserverEntry(ExecutingContext* context, + bool isIntersecting, + double intersectionRatio, + Element* target) + : ScriptWrappable(context->ctx()), + isIntersecting_(isIntersecting), + intersectionRatio_(intersectionRatio), + target_(target) {} void IntersectionObserverEntry::Trace(GCVisitor* visitor) const { visitor->TraceMember(target_); diff --git a/bridge/core/dom/intersection_observer_entry.d.ts b/bridge/core/dom/intersection_observer_entry.d.ts index eb7e95d54d..f42ae84c26 100644 --- a/bridge/core/dom/intersection_observer_entry.d.ts +++ b/bridge/core/dom/intersection_observer_entry.d.ts @@ -9,22 +9,17 @@ import {Element} from "./element"; export interface IntersectionObserverEntry { - // TODO(pengfei12.guo): DOMHighResTimeStamp not supported - //readonly time: DOMHighResTimeStamp; - - // TODO(pengfei12.guo): DOMRectReadOnly not supported + // TODO(pengfei12.guo): not supported + // readonly time: DOMHighResTimeStamp; // TODO(szager): |rootBounds| should not be nullable. - //readonly rootBounds: DOMRectReadOnly | null; // rootBounds 可以为 null - //readonly boundingClientRect: DOMRectReadOnly; - //readonly intersectionRect: DOMRectReadOnly; + // readonly rootBounds: DOMRectReadOnly | null; + // readonly boundingClientRect: DOMRectReadOnly; + // readonly intersectionRect: DOMRectReadOnly; + // readonly isVisible: boolean; readonly isIntersecting: boolean; - // TODO(pengfei12.guo): isVisible not supported - //readonly isVisible: boolean; - - // TODO(pengfei12.guo): intersectionRatio not supported - //readonly intersectionRatio: number; + readonly intersectionRatio: number; readonly target: Element; diff --git a/bridge/core/dom/intersection_observer_entry.h b/bridge/core/dom/intersection_observer_entry.h index 06e37edeb3..b344e883ca 100644 --- a/bridge/core/dom/intersection_observer_entry.h +++ b/bridge/core/dom/intersection_observer_entry.h @@ -8,25 +8,32 @@ #define WEBF_CORE_INTERSECTION_OBSERVER_INTERSECTION_OBSERVER_ENTRY_H_ #include "bindings/qjs/cppgc/member.h" -#include "core/binding_object.h" +#include "bindings/qjs/script_wrappable.h" namespace webf { class Element; -class IntersectionObserverEntry final : public BindingObject { +struct NativeIntersectionObserverEntry : public DartReadable { + int8_t is_intersecting; + double intersectionRatio; + NativeBindingObject* target; +}; + +class IntersectionObserverEntry final : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); public: - using ImplType = IntersectionObserverEntry*; + IntersectionObserverEntry() = delete; + explicit IntersectionObserverEntry(ExecutingContext* context, + bool isIntersecting, + double intersectionRatio, + Element* target); - 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(); - //} + double intersectionRatio() const { return intersectionRatio_; } // DOMRectReadOnly* boundingClientRect() const; // DOMRectReadOnly* rootBounds() const; // DOMRectReadOnly* intersectionRect() const; @@ -43,20 +50,14 @@ class IntersectionObserverEntry final : public BindingObject { // const IntersectionGeometry& GetGeometry() const { return geometry_; } void Trace(GCVisitor*) const override; - bool IsIntersectionObserverEntry() const override { return true; }; - private: // IntersectionGeometry geometry_; + double intersectionRatio_; 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/foundation/ui_command_strategy.cc b/bridge/foundation/ui_command_strategy.cc index 486d2a4ddc..274a02f621 100644 --- a/bridge/foundation/ui_command_strategy.cc +++ b/bridge/foundation/ui_command_strategy.cc @@ -84,7 +84,10 @@ void UICommandSyncStrategy::RecordUICommand(UICommand type, case UICommand::kSetAttribute: case UICommand::kRemoveEvent: case UICommand::kAddEvent: - case UICommand::kDisposeBindingObject: { + case UICommand::kDisposeBindingObject: + case UICommand::kAddIntersectionObserver: + case UICommand::kRemoveIntersectionObserver: + case UICommand::kDisconnectIntersectionObserver: { host_->waiting_buffer_->addCommand(type, std::move(args_01), native_binding_object, native_ptr2, request_ui_update); break; @@ -141,4 +144,4 @@ void UICommandSyncStrategy::RecordOperationForPointer(NativeBindingObject* ptr) SyncToReserveIfNecessary(); } -} // namespace webf \ No newline at end of file +} // namespace webf diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 071b715195..9d09253c2e 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -181,7 +181,7 @@ abstract class BindingBridge { return; } case CreateBindingObjectType.createIntersectionObserver: { - IntersectionObserver intersectionObserver = IntersectionObserver(BindingContext(controller.view, contextId, pointer)); + IntersectionObserver intersectionObserver = IntersectionObserver(BindingContext(controller.view, contextId, pointer), arguments); controller.view.setBindingObject(pointer, intersectionObserver); return; } diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 495b27d3f0..16d2c0d09c 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -113,6 +113,7 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin String tagName = UNKNOWN; final Set _intersectionObserverList = HashSet(); + List _thresholds = [0.0]; String? _id; @@ -407,6 +408,9 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin // Ensure that the event responder is bound. ensureEventResponderBound(); + + // Ensure IntersectionObserver when renderBoxModel change. + ensureAddIntersectionObserver(); } } @@ -1013,6 +1017,7 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin _beforeElement = null; _afterElement?.dispose(); _afterElement = null; + renderBoxModel?.removeIntersectionChangeListener(_handleIntersectionObserver); super.dispose(); } @@ -1984,36 +1989,47 @@ 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 + bool _handleIntersectionObserver(IntersectionObserverEntry entry) { + // If there are multiple IntersectionObservers, they cannot be distributed accurately for (var observer in _intersectionObserverList) { - observer.addEntry(DartIntersectionObserverEntry(entry.isIntersecting, this)); + observer.addEntry(DartIntersectionObserverEntry(entry.isIntersecting, entry.intersectionRatio, this)); } + + return _intersectionObserverList.isNotEmpty; } // IntersectionObserver 相关 - bool addIntersectionObserver(IntersectionObserver observer) { - debugPrint('Element.addIntersectionObserver observer=$observer,element=$this'); + bool addIntersectionObserver(IntersectionObserver observer, List thresholds) { if (_intersectionObserverList.contains(observer)) { + debugPrint('Element.addIntersectionObserver,element=$this duplicate'); return false; } - if (_intersectionObserverList.isEmpty) { - renderBoxModel?.addIntersectionChangeListener(_handleIntersectionObserver); - renderBoxModel?.markNeedsPaint();//markNeedsCompositingBitsUpdate + //debugPrint('Element.addIntersectionObserver,element=$this attached:${renderBoxModel?.attached ?? false}'); + if (renderBoxModel?.attached ?? false) { + renderBoxModel!.addIntersectionChangeListener(_handleIntersectionObserver, thresholds); + renderBoxModel!.markNeedsPaint(); //markNeedsCompositingBitsUpdate } _intersectionObserverList.add(observer); + _thresholds = thresholds; return true; } void removeIntersectionObserver(IntersectionObserver observer) { - debugPrint('Element.removeIntersectionObserver observer=$observer,element=$this'); + //debugPrint('Element.removeIntersectionObserver,element=$this'); _intersectionObserverList.remove(observer); if (_intersectionObserverList.isEmpty) { renderBoxModel?.removeIntersectionChangeListener(_handleIntersectionObserver); } } + + void ensureAddIntersectionObserver() { + if (_intersectionObserverList.isEmpty) { + return; + } + //debugPrint('Element.ensureAddIntersectionObserver,element=$this renderBoxModel:$renderBoxModel'); + renderBoxModel?.addIntersectionChangeListener(_handleIntersectionObserver, _thresholds); + } } // https://www.w3.org/TR/css-position-3/#def-cb diff --git a/webf/lib/src/dom/event.dart b/webf/lib/src/dom/event.dart index 55d27399d9..a034539fe6 100644 --- a/webf/lib/src/dom/event.dart +++ b/webf/lib/src/dom/event.dart @@ -85,7 +85,7 @@ mixin ElementEventMixin on ElementBase { // Make sure pointer responder bind. renderBox.getEventTarget = getEventTarget; - if (_hasIntersectionObserverEvent()) { + if (hasIntersectionObserverEvent()) { renderBox.addIntersectionChangeListener(handleIntersectionChange); // Mark the compositing state for this render object as dirty // cause it will create new layer. @@ -102,7 +102,7 @@ mixin ElementEventMixin on ElementBase { } } - bool _hasIntersectionObserverEvent() { + bool hasIntersectionObserverEvent() { return hasEventListener(EVENT_APPEAR) || hasEventListener(EVENT_DISAPPEAR) || hasEventListener(EVENT_INTERSECTION_CHANGE); @@ -148,13 +148,15 @@ mixin ElementEventMixin on ElementBase { dispatchEvent(DisappearEvent()); } - void handleIntersectionChange(IntersectionObserverEntry entry) { - dispatchEvent(IntersectionChangeEvent(entry.intersectionRatio)); - if (entry.intersectionRatio > 0) { + bool handleIntersectionChange(IntersectionObserverEntry entry) { + final double intersectionRatio = entry.intersectionRatio; + dispatchEvent(IntersectionChangeEvent(intersectionRatio)); + if (intersectionRatio > 0) { handleAppear(); } else { handleDisappear(); } + return false; } void handleResizeChange(ResizeObserverEntry entry) { diff --git a/webf/lib/src/dom/intersection_observer.dart b/webf/lib/src/dom/intersection_observer.dart index c5f31f3443..867055f49f 100644 --- a/webf/lib/src/dom/intersection_observer.dart +++ b/webf/lib/src/dom/intersection_observer.dart @@ -17,10 +17,8 @@ class _IntersectionObserverDeliverContext { Completer completer; Stopwatch? stopwatch; - // Pointer method; Pointer allocatedNativeArguments; - - //Pointer rawNativeEntries; + Pointer rawEntries; WebFController controller; EvaluateOpItem? profileOp; @@ -28,7 +26,7 @@ class _IntersectionObserverDeliverContext { this.completer, this.stopwatch, this.allocatedNativeArguments, - //this.rawNativeEntries, + this.rawEntries, this.controller, this.profileOp, ); @@ -36,8 +34,6 @@ class _IntersectionObserverDeliverContext { void _handleDeliverResult(Object handle, Pointer returnValue) { _IntersectionObserverDeliverContext context = handle as _IntersectionObserverDeliverContext; - Pointer dispatchResult = - fromNativeValue(context.controller.view, returnValue).cast(); if (enableWebFCommandLog && context.stopwatch != null) { debugPrint('deliver IntersectionObserverEntry to native side, time: ${context.stopwatch!.elapsedMicroseconds}us'); @@ -45,7 +41,7 @@ void _handleDeliverResult(Object handle, Pointer returnValue) { // Free the allocated arguments. malloc.free(context.allocatedNativeArguments); - malloc.free(dispatchResult); + malloc.free(context.rawEntries); malloc.free(returnValue); if (enableWebFProfileTracking) { @@ -56,7 +52,12 @@ void _handleDeliverResult(Object handle, Pointer returnValue) { } class IntersectionObserver extends DynamicBindingObject { - IntersectionObserver(BindingContext? context) : super(context); + IntersectionObserver(BindingContext? context, List thresholds_) : super(context) { + if (null != thresholds_) { + debugPrint('Dom.IntersectionObserver.Constructor thresholds_:$thresholds_'); + _thresholds = thresholds_.map((e) => e as double).toList(); + } + } @override void initializeMethods(Map methods) {} @@ -65,23 +66,17 @@ class IntersectionObserver extends DynamicBindingObject { void initializeProperties(Map properties) {} void observe(Element element) { - if (!element.addIntersectionObserver(this)) { + // debugPrint('Dom.IntersectionObserver.observe element:$element'); + if (!element.addIntersectionObserver(this, _thresholds)) { 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) { + // debugPrint('Dom.IntersectionObserver.unobserve'); _elementList.remove(element); element.removeIntersectionObserver(this); - debugPrint('Dom.IntersectionObserver.unobserve'); } void disconnect() { @@ -97,7 +92,6 @@ class IntersectionObserver extends DynamicBindingObject { } void addEntry(DartIntersectionObserverEntry entry) { - debugPrint('Dom.IntersectionObserver.addEntry entry:$entry'); _entries.add(entry); } @@ -109,65 +103,57 @@ class IntersectionObserver extends DynamicBindingObject { Future deliver(WebFController controller) async { if (pointer == null) return; - debugPrint('Dom.IntersectionObserver.deliver pointer:$pointer'); + List entries = takeRecords(); - if (entries.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(); - - Stopwatch? stopwatch; - if (enableWebFCommandLog) { - stopwatch = Stopwatch()..start(); - } - - // Allocate an chunk of memory for an list of NativeIntersectionObserverEntry - Pointer head = - malloc.allocate(sizeOf() * entries.length); - - // Write the native memory from dart objects. - for(int i = 0; i < entries.length; i ++) { - (head + i).ref.isIntersecting = entries[i].isIntersecting ? 1 : 0; - (head + i).ref.target = entries[i].element.pointer!; - } - - List dispatchEntryArguments = [ - head, - entries.length - ]; - - 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); - malloc.free(head); - }); - - return completer.future; + if (entries.isEmpty) { + return; + } + // debugPrint('Dom.IntersectionObserver.deliver size:${entries.length}'); + 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? invokeBindingMethodsFromDart = + pointer!.ref.invokeBindingMethodFromDart.asFunction(); + + Stopwatch? stopwatch; + if (enableWebFCommandLog) { + stopwatch = Stopwatch()..start(); + } + + // Allocate an chunk of memory for an list of NativeIntersectionObserverEntry + Pointer nativeEntries = + malloc.allocate(sizeOf() * entries.length); + + // Write the native memory from dart objects. + for (int i = 0; i < entries.length; i++) { + (nativeEntries + i).ref.isIntersecting = entries[i].isIntersecting ? 1 : 0; + (nativeEntries + i).ref.intersectionRatio = entries[i].intersectionRatio; + (nativeEntries + i).ref.element = entries[i].element.pointer!; } + + List dispatchEntryArguments = [nativeEntries, entries.length]; + Pointer allocatedNativeArguments = makeNativeValueArguments(bindingObject, dispatchEntryArguments); + + _IntersectionObserverDeliverContext context = _IntersectionObserverDeliverContext( + completer, stopwatch, allocatedNativeArguments, nativeEntries, controller, currentProfileOp); + + Pointer> resultCallback = Pointer.fromFunction(_handleDeliverResult); + + Future.microtask(() { + invokeBindingMethodsFromDart(pointer!, currentProfileOp?.hashCode ?? 0, nullptr, dispatchEntryArguments.length, + allocatedNativeArguments, context, resultCallback); + }); + // debugPrint('Dom.IntersectionObserver.deliver this:$pointer end'); + return completer.future; } final List _entries = []; final List _elementList = []; + List _thresholds = [0.0]; } diff --git a/webf/lib/src/dom/intersection_observer_entry.dart b/webf/lib/src/dom/intersection_observer_entry.dart index ce4291cfcf..1e2cb4b3bf 100644 --- a/webf/lib/src/dom/intersection_observer_entry.dart +++ b/webf/lib/src/dom/intersection_observer_entry.dart @@ -15,13 +15,13 @@ class DartIntersectionObserverEntry { final bool isIntersecting; //final bool isVisible; - //final double intersectionRatio; + final double intersectionRatio; final Element element; - DartIntersectionObserverEntry(this.isIntersecting, this.element); + DartIntersectionObserverEntry(this.isIntersecting, this.intersectionRatio, this.element); DartIntersectionObserverEntry copy() { - return DartIntersectionObserverEntry(isIntersecting, element); + return DartIntersectionObserverEntry(isIntersecting, intersectionRatio, element); } } @@ -29,5 +29,8 @@ class NativeIntersectionObserverEntry extends Struct { @Int8() external int isIntersecting; - external Pointer target; + @Double() + external double intersectionRatio; + + external Pointer element; } diff --git a/webf/lib/src/html/img.dart b/webf/lib/src/html/img.dart index e957468cfe..f43a98f07a 100644 --- a/webf/lib/src/html/img.dart +++ b/webf/lib/src/html/img.dart @@ -353,7 +353,7 @@ class ImageElement extends Element { // The getter must be called after image had loaded, otherwise will return 0. int naturalHeight = 0; - void _handleIntersectionChange(IntersectionObserverEntry entry) async { + bool _handleIntersectionChange(IntersectionObserverEntry entry) { // When appear if (entry.isIntersecting) { _updateImageDataLazyCompleter?.complete(); @@ -361,6 +361,7 @@ class ImageElement extends Element { } else { _stopListeningStream(keepStreamAlive: true); } + return false; } // To prevent trigger load event more than once. diff --git a/webf/lib/src/rendering/intersection_observer.dart b/webf/lib/src/rendering/intersection_observer.dart index 1e7214fd12..3f16c785cd 100644 --- a/webf/lib/src/rendering/intersection_observer.dart +++ b/webf/lib/src/rendering/intersection_observer.dart @@ -20,7 +20,8 @@ Iterable _getLayerChain(Layer start) { return layerChain.reversed; } -typedef IntersectionChangeCallback = void Function(IntersectionObserverEntry info); +// Call the callback function and decide whether to deliver IntersectionObserver based on its return value +typedef IntersectionChangeCallback = bool Function(IntersectionObserverEntry info); mixin RenderIntersectionObserverMixin on RenderBox { static copyTo(RenderIntersectionObserverMixin from, RenderIntersectionObserverMixin to) { @@ -40,6 +41,12 @@ mixin RenderIntersectionObserverMixin on RenderBox { /// A list of event handlers List? _listeners; + List _thresholds = [0.0]; + int _lastThresholdsIndex = 0; + bool _lastIsIntersecting = false; + + bool _clearIntersectionListeners = false; + void disposeIntersectionObserverLayer() { _intersectionObserverLayer.layer = null; } @@ -50,7 +57,7 @@ mixin RenderIntersectionObserverMixin on RenderBox { return _listeners?.isNotEmpty == true; } - void addIntersectionChangeListener(IntersectionChangeCallback callback) { + void addIntersectionChangeListener(IntersectionChangeCallback callback, [List thresholds = const [0.0]]) { // Init things if (_listeners == null) { _listeners = List.empty(growable: true); @@ -60,13 +67,22 @@ mixin RenderIntersectionObserverMixin on RenderBox { // Avoid same listener added twice. if (!_listeners!.contains(callback)) { _listeners!.add(callback); + _thresholds = thresholds; } } void clearIntersectionChangeListeners() { + _clearIntersectionListeners = true; + } + + void _clearIntersectionChangeListeners() { + debugPrint('RenderBox._clearIntersectionChangeListeners this:$this'); _listeners?.clear(); _listeners = null; _onIntersectionChange = null; + _clearIntersectionListeners = false; + _lastThresholdsIndex = 0; + _lastIsIntersecting = false; } void removeIntersectionChangeListener(IntersectionChangeCallback callback) { @@ -83,13 +99,30 @@ mixin RenderIntersectionObserverMixin on RenderBox { markNeedsPaint(); } - void _dispatchChange(IntersectionObserverEntry info) { - // Not use for-in, and not cache length, due to callback call stack may - // clear [_listeners], which case concurrent exception. - for (int i = 0; i < (_listeners == null ? 0 : _listeners!.length); i++) { - IntersectionChangeCallback callback = _listeners![i]; - callback(info); + bool _dispatchChange(IntersectionObserverEntry info) { + bool deliverIntersectionObserver = false; + // Limit frequency + int index = firstThresholdGreaterThan(info.intersectionRatio, _thresholds!); + bool isIntersecting = info.isIntersecting; + if (index != _lastThresholdsIndex || isIntersecting != _lastIsIntersecting) { + _lastThresholdsIndex = index; + _lastIsIntersecting = isIntersecting; + + // Not use for-in, and not cache length, due to callback call stack may + // clear [_listeners], which case concurrent exception. + for (int i = 0; i < (_listeners == null ? 0 : _listeners!.length); i++) { + IntersectionChangeCallback callback = _listeners![i]; + if (callback(info)) { + deliverIntersectionObserver = true; + } + } } + + if (_clearIntersectionListeners) { + _clearIntersectionChangeListeners(); + } + + return deliverIntersectionObserver; } void paintIntersectionObserver(PaintingContext context, Offset offset, PaintingContextCallback callback) { @@ -112,6 +145,14 @@ mixin RenderIntersectionObserverMixin on RenderBox { context.pushLayer(_intersectionObserverLayer.layer!, callback, offset); } + + int firstThresholdGreaterThan(double ratio, List thresholds) { + int result = 0; + while (result < thresholds.length && thresholds[result] <= ratio) { + result++; + } + return result; + } } class IntersectionObserverLayer extends ContainerLayer { @@ -139,6 +180,8 @@ class IntersectionObserverLayer extends ContainerLayer { /// 300ms delay compute layer offset static final Duration _updateInterval = Duration(milliseconds: 300); + static Timer? _timer; + /// Offset to the start of the element, in local coordinates. final Offset _elementOffset; @@ -239,10 +282,12 @@ class IntersectionObserverLayer extends ContainerLayer { final isUpdateScheduled = _updated.isNotEmpty; _updated[id] = this; - if (!isUpdateScheduled) { + if (!isUpdateScheduled && null == _timer) { // We use a normal [Timer] instead of a [RestartableTimer] so that changes // to the update duration will be picked up automatically. - Timer(_updateInterval, _handleUpdateTimer); + _timer = Timer.periodic(_updateInterval, (Timer timer) { + SchedulerBinding.instance.scheduleTask(_processCallbacks, Priority.touch); + }); } } @@ -297,17 +342,17 @@ class IntersectionObserverLayer extends ContainerLayer { /// Invokes the visibility callback if [IntersectionObserverEntry] hasn't meaningfully /// changed since the last time we invoked it. - void _fireCallback(IntersectionObserverEntry info) { + bool _fireCallback(IntersectionObserverEntry info) { final oldInfo = _lastIntersectionInfo; // If isIntersecting is true maybe not visible when element size is 0 final isIntersecting = info.isIntersecting; if (oldInfo == null) { if (!isIntersecting) { - return; + return false; } } else if (info.matchesIntersecting(oldInfo)) { - return; + return false; } if (isIntersecting) { @@ -317,7 +362,7 @@ class IntersectionObserverLayer extends ContainerLayer { _lastIntersectionInfo = null; } // Notify visibility changed event - onIntersectionChange!(info); + return onIntersectionChange!(info); } Rect? _rootBounds; @@ -326,10 +371,16 @@ class IntersectionObserverLayer extends ContainerLayer { /// Executes visibility callbacks for all updated. static void _processCallbacks() { + if (_updated.isEmpty) { + return; + } + + bool deliverIntersectionObserver = false; for (final layer in _updated.values) { - if (layer.onIntersectionChange == null) return; if (!layer.attached) { - layer._fireCallback(IntersectionObserverEntry(size: Size.zero)); + if (layer._fireCallback(IntersectionObserverEntry(size: Size.zero))) { + deliverIntersectionObserver = true; + } continue; } @@ -342,7 +393,14 @@ class IntersectionObserverLayer extends ContainerLayer { final info = IntersectionObserverEntry.fromRects( boundingClientRect: paddingAroundElementBounds, rootBounds: rootBounds); - layer._fireCallback(info); + if (layer._fireCallback(info)) { + deliverIntersectionObserver = true; + } + } + + // for dom IntersectionObserver + if (deliverIntersectionObserver) { + SchedulerBinding.instance.scheduleFrame(); } _updated.clear(); _layerTransformCache.clear(); From 200aedf83741a32004d535f63c7fde9643efbe71 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Wed, 11 Dec 2024 13:23:30 +0000 Subject: [PATCH 4/7] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 8 ++++---- bridge/core/binding_object.cc | 10 +++++----- bridge/core/binding_object.h | 5 +---- bridge/core/dart_isolate_context.cc | 3 +-- bridge/core/dom/events/event_target.cc | 3 ++- bridge/core/dom/intersection_observer.cc | 7 ++++--- bridge/core/dom/intersection_observer.h | 19 ++++++++---------- bridge/core/dom/node_test.cc | 20 +++++++++---------- .../core/events/hybrid_router_change_event.h | 20 +++++++++++-------- bridge/core/executing_context.cc | 2 +- bridge/core/frame/window.cc | 8 ++++---- bridge/foundation/ui_command_buffer.cc | 3 ++- 12 files changed, 54 insertions(+), 54 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index ae237c0eac..7a7c13b13a 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -53,10 +53,13 @@ #include "qjs_html_template_element.h" #include "qjs_html_textarea_element.h" #include "qjs_html_unknown_element.h" +#include "qjs_hybrid_router_change_event.h" #include "qjs_image.h" #include "qjs_inline_css_style_declaration.h" #include "qjs_input_event.h" #include "qjs_intersection_change_event.h" +#include "qjs_intersection_observer.h" +#include "qjs_intersection_observer_entry.h" #include "qjs_keyboard_event.h" #include "qjs_location.h" #include "qjs_message_event.h" @@ -92,15 +95,12 @@ #include "qjs_text.h" #include "qjs_touch.h" #include "qjs_touch_event.h" -#include "qjs_hybrid_router_change_event.h" #include "qjs_touch_list.h" #include "qjs_transition_event.h" #include "qjs_ui_event.h" #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 { @@ -202,7 +202,7 @@ void InstallBindings(ExecutingContext* context) { QJSSVGStyleElement::Install(context); QJSSVGLineElement::Install(context); - //IntersectionObserver + // IntersectionObserver QJSIntersectionObserver::Install(context); QJSIntersectionObserverEntry::Install(context); diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 2d52b75669..718286bc63 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -60,11 +60,11 @@ void NativeBindingObject::HandleCallFromDartSide(DartIsolateContext* dart_isolat dart_isolate_context->profiler()->StartTrackEvaluation(profile_id); - 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, ""); + 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(); diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h index 4b67dfd7fb..17279dffe8 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -70,10 +70,7 @@ enum BindingMethodCallOperations { kAsyncAnonymousFunction, }; -enum CreateBindingObjectType { - kCreateDOMMatrix = 0, - kCreateIntersectionObserver -}; +enum CreateBindingObjectType { kCreateDOMMatrix = 0, kCreateIntersectionObserver }; struct BindingObjectPromiseContext : public DartReadable { ExecutingContext* context; diff --git a/bridge/core/dart_isolate_context.cc b/bridge/core/dart_isolate_context.cc index 73dee33eae..3787b1e533 100644 --- a/bridge/core/dart_isolate_context.cc +++ b/bridge/core/dart_isolate_context.cc @@ -85,8 +85,7 @@ void DartIsolateContext::InitializeJSRuntime() { } void DartIsolateContext::FinalizeJSRuntime() { - if (running_dart_isolates > 0 || - runtime_ == nullptr) { + if (running_dart_isolates > 0 || runtime_ == nullptr) { return; } diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 1636b45b2d..a1b6b54fda 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -78,7 +78,8 @@ bool EventTarget::addEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, const std::shared_ptr& options, ExceptionState& exception_state) { - if (event_listener == nullptr) return false; + if (event_listener == nullptr) + return false; std::shared_ptr event_listener_options; if (options == nullptr) { event_listener_options = AddEventListenerOptions::Create(); diff --git a/bridge/core/dom/intersection_observer.cc b/bridge/core/dom/intersection_observer.cc index e661ddafef..e547514a07 100644 --- a/bridge/core/dom/intersection_observer.cc +++ b/bridge/core/dom/intersection_observer.cc @@ -9,14 +9,14 @@ #include #include -#include "core/dom/element.h" #include #include "bindings/qjs/converter_impl.h" +#include "core/dom/element.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" +#include "qjs_intersection_observer_init.h" namespace webf { @@ -73,7 +73,8 @@ void IntersectionObserver::observe(Element* target, ExceptionState& exception_st } #if ENABLE_LOG - WEBF_LOG(DEBUG) << "[IntersectionObserver]: observe target=" << target << ",tagName=" << target->nodeName() << std::endl; + WEBF_LOG(DEBUG) << "[IntersectionObserver]: observe target=" << target << ",tagName=" << target->nodeName() + << std::endl; #endif GetExecutingContext()->uiCommandBuffer()->AddCommand(UICommand::kAddIntersectionObserver, nullptr, bindingObject(), target->bindingObject()); diff --git a/bridge/core/dom/intersection_observer.h b/bridge/core/dom/intersection_observer.h index a90629b284..30ebeebb35 100644 --- a/bridge/core/dom/intersection_observer.h +++ b/bridge/core/dom/intersection_observer.h @@ -28,7 +28,6 @@ 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 @@ -45,7 +44,7 @@ class IntersectionObserver final : public BindingObject { // //////////////////// // //////////////////// // //////////////////// - //enum ThresholdInterpretation { kFractionOfTarget, kFractionOfRoot }; + // 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 @@ -58,7 +57,7 @@ class IntersectionObserver final : public BindingObject { // 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 }; + // 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 @@ -70,7 +69,7 @@ class IntersectionObserver final : public BindingObject { // // Note that the percentage margin is resolved against the root rect, even // when the margin is applied to the target. - //enum MarginTarget { kApplyMarginToRoot, kApplyMarginToTarget }; + // enum MarginTarget { kApplyMarginToRoot, kApplyMarginToTarget }; static IntersectionObserver* Create(ExecutingContext* context, const std::shared_ptr& function, @@ -113,9 +112,9 @@ class IntersectionObserver final : public BindingObject { // }; NativeValue HandleCallFromDartSide(const AtomicString& method, - int32_t argc, - const NativeValue* argv, - Dart_Handle dart_object) override; + int32_t argc, + const NativeValue* argv, + Dart_Handle dart_object) override; // API methods. void observe(Element*, ExceptionState&); @@ -127,9 +126,9 @@ class IntersectionObserver final : public BindingObject { // API attributes. [[nodiscard]] Node* root() const { return root_; } // TODO(pengfei12.guo): not supported - //AtomicString rootMargin() const; + // AtomicString rootMargin() const; // TODO(pengfei12.guo): not supported - //AtomicString scrollMargin() const; + // AtomicString scrollMargin() const; [[nodiscard]] const std::vector& thresholds() const { return thresholds_; } // TODO(pengfei12.guo): not supported @@ -185,11 +184,9 @@ class IntersectionObserver final : public BindingObject { // deleted; true otherwise. bool RootIsValid() const; - void Trace(GCVisitor*) const override; private: - // We use UntracedMember<> here to do custom weak processing. Node* root_; std::vector thresholds_; diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 8e208efce3..47a98b0f8f 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -75,12 +75,12 @@ Promise.resolve().then(() => { } 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"( + 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'); @@ -107,12 +107,12 @@ const char* code = R"( // Start observing the target element observer.observe(div); )"; -env->page()->evaluateScript(code, strlen(code), "vm://", 0); + env->page()->evaluateScript(code, strlen(code), "vm://", 0); -TEST_runLoop(context); + TEST_runLoop(context); -EXPECT_EQ(errorCalled, false); -EXPECT_EQ(logCalled, true); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); } TEST(Node, nodeName) { diff --git a/bridge/core/events/hybrid_router_change_event.h b/bridge/core/events/hybrid_router_change_event.h index f88a47e06e..8d7c7a5c21 100644 --- a/bridge/core/events/hybrid_router_change_event.h +++ b/bridge/core/events/hybrid_router_change_event.h @@ -21,18 +21,22 @@ class HybridRouterChangeEvent : public Event { static HybridRouterChangeEvent* Create(ExecutingContext* context, ExceptionState& exception_state); static HybridRouterChangeEvent* Create(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr& initializer, - ExceptionState& exception_state); + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); - explicit HybridRouterChangeEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + explicit HybridRouterChangeEvent(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state); explicit HybridRouterChangeEvent(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr& initializer, - ExceptionState& exception_state); + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); - explicit HybridRouterChangeEvent(ExecutingContext* context, const AtomicString& type, NativeHybridRouterChangeEvent* native_ui_event); + explicit HybridRouterChangeEvent(ExecutingContext* context, + const AtomicString& type, + NativeHybridRouterChangeEvent* native_ui_event); ScriptValue state() const; AtomicString kind() const; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index f1ce148f72..23e332a4fa 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -8,8 +8,8 @@ #include "bindings/qjs/converter_impl.h" #include "built_in_string.h" #include "core/dom/document.h" -#include "core/dom/mutation_observer.h" #include "core/dom/intersection_observer.h" +#include "core/dom/mutation_observer.h" #include "core/events/error_event.h" #include "core/events/promise_rejection_event.h" #include "event_type_names.h" diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index 5a83e1fa18..9d6a6e9160 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -38,8 +38,8 @@ AtomicString Window::btoa(const AtomicString& source, ExceptionState& exception_ std::string source_string = source.ToStdString(ctx()); - const size_t output_size = modp_b64_encode(reinterpret_cast(buffer.data()), - source_string.c_str(), source.length()); + const size_t output_size = + modp_b64_encode(reinterpret_cast(buffer.data()), source_string.c_str(), source.length()); const char* encode_str = buffer.data(); const size_t encode_str_len = strlen(encode_str); @@ -58,8 +58,8 @@ bool Base64DecodeRaw(JSContext* ctx, const AtomicString& in, std::vector(out.data()), - in_string.c_str(), in.length(), policy); + const size_t output_size = + modp_b64_decode(reinterpret_cast(out.data()), in_string.c_str(), in.length(), policy); if (output_size == MODP_B64_ERROR) return false; out.resize(output_size); diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 460caa99b6..70085188d8 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -129,7 +129,8 @@ bool UICommandBuffer::empty() { } void UICommandBuffer::clear() { - if (buffer_ == nullptr) return; + if (buffer_ == nullptr) + return; memset(buffer_, 0, sizeof(UICommandItem) * size_); size_ = 0; kind_flag = 0; From 17437c3cae07c21d9571c69f2d81a1c0ec7929d5 Mon Sep 17 00:00:00 2001 From: "pengfei12.guo" Date: Thu, 17 Oct 2024 16:59:43 +0800 Subject: [PATCH 5/7] =?UTF-8?q?resolve=20specification=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bridge/core/dom/intersection_observer.cc | 16 ------ bridge/core/dom/intersection_observer.h | 10 ++-- .../core/dom/intersection_observer_init.d.ts | 13 +++-- bridge/core/dom/node_test.cc | 6 +- .../assets/IntersectionObserver_test.html | 55 ------------------- webf/example/lib/main.dart | 2 +- webf/lib/src/dom/document.dart | 3 +- webf/lib/src/dom/element.dart | 9 +-- webf/lib/src/dom/intersection_observer.dart | 4 +- 9 files changed, 21 insertions(+), 97 deletions(-) delete mode 100644 webf/example/assets/IntersectionObserver_test.html diff --git a/bridge/core/dom/intersection_observer.cc b/bridge/core/dom/intersection_observer.cc index e547514a07..cd39f99a07 100644 --- a/bridge/core/dom/intersection_observer.cc +++ b/bridge/core/dom/intersection_observer.cc @@ -95,22 +95,6 @@ void IntersectionObserver::disconnect(ExceptionState& exception_state) { 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()); -// } - NativeValue IntersectionObserver::HandleCallFromDartSide(const AtomicString& method, int32_t argc, const NativeValue* argv, diff --git a/bridge/core/dom/intersection_observer.h b/bridge/core/dom/intersection_observer.h index 30ebeebb35..bc143e073c 100644 --- a/bridge/core/dom/intersection_observer.h +++ b/bridge/core/dom/intersection_observer.h @@ -146,24 +146,24 @@ class IntersectionObserver final : public BindingObject { // root just because root_ is null. Hence root_is_implicit_. [[nodiscard]] bool RootIsImplicit() const { // return root_is_implicit_; - // 如果没有指定 root 选项,默认情况下会使用视口作为根元素。 + // If the root option is not specified, the viewport is used as the root element by default. return root_ == nullptr; } - // TODO(pengfei12.guo@vipshop.com): TimeDelta not support + // TODO(pengfei12.guo): TimeDelta not support // base::TimeDelta GetEffectiveDelay() const; - // TODO(pengfei12.guo@vipshop.com): RootMargin not support + // TODO(pengfei12.guo): RootMargin not support // std::vector RootMargin() const { // return margin_target_ == kApplyMarginToRoot ? margin_ : Vector(); //} - // TODO(pengfei12.guo@vipshop.com): TargetMargin not support + // TODO(pengfei12.guo): TargetMargin not support // Vector TargetMargin() const { // return margin_target_ == kApplyMarginToTarget ? margin_ : Vector(); //} - // TODO(pengfei12.guo@vipshop.com): ScrollMargin not support + // TODO(pengfei12.guo): ScrollMargin not support // Vector ScrollMargin() const { return scroll_margin_; } // TODO(pengfei12.guo): ComputeIntersections impl by dart diff --git a/bridge/core/dom/intersection_observer_init.d.ts b/bridge/core/dom/intersection_observer_init.d.ts index 680bace87c..644858f3e4 100644 --- a/bridge/core/dom/intersection_observer_init.d.ts +++ b/bridge/core/dom/intersection_observer_init.d.ts @@ -11,10 +11,11 @@ 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; + root?: Node | null; + // TODO(pengfei12.guo): Just definition, no implementation. + rootMargin?: string; + threshold?: number[]; + // scrollMargin?: string; + // delay?: number; + // trackVisibility?: boolean; } diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 47a98b0f8f..645689d4f8 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -82,8 +82,8 @@ TEST(Node, IntersectionObserver) { auto context = env->page()->executingContext(); const char* code = R"( // Create the observed element - const div = document.createElement('div'); - + const container = document.createElement('div'); + document.body.appendChild(container); // Callback function to execute when mutations are observed const callback = (entries, observer) => { entries.forEach(entry => { @@ -105,7 +105,7 @@ TEST(Node, IntersectionObserver) { const observer = new IntersectionObserver(callback, options); // Start observing the target element - observer.observe(div); + observer.observe(container); )"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); diff --git a/webf/example/assets/IntersectionObserver_test.html b/webf/example/assets/IntersectionObserver_test.html deleted file mode 100644 index 280cf433bb..0000000000 --- a/webf/example/assets/IntersectionObserver_test.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - IntersectionObserver and MutationObserver Test - - - - -
-
- - - - diff --git a/webf/example/lib/main.dart b/webf/example/lib/main.dart index 5324e6c80d..dc3bd452ea 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/IntersectionObserver_test.html')); + controller.preload(WebFBundle.fromUrl('assets:assets/bundle.html')); } @override diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 5c79d5816e..f777c268cc 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -100,7 +100,7 @@ class Document extends ContainerNode { final Set _styleDirtyElements = {}; - final Set _intersectionObserverList = HashSet(); + final Set _intersectionObserverList = {}; void markElementStyleDirty(Element element) { _styleDirtyElements.add(element.pointer!.address); @@ -610,7 +610,6 @@ class Document extends ContainerNode { } void deliverIntersectionObserver() { - debugPrint('Document.deliverIntersectionObserver pointer:$pointer'); if (_intersectionObserverList.isEmpty) { return; } diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 16d2c0d09c..5e0a7aa1d6 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -112,7 +112,7 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin // Default to unknown, assign by [createElement], used by inspector. String tagName = UNKNOWN; - final Set _intersectionObserverList = HashSet(); + final Set _intersectionObserverList = {}; List _thresholds = [0.0]; String? _id; @@ -1998,16 +1998,13 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin return _intersectionObserverList.isNotEmpty; } - // IntersectionObserver 相关 bool addIntersectionObserver(IntersectionObserver observer, List thresholds) { if (_intersectionObserverList.contains(observer)) { - debugPrint('Element.addIntersectionObserver,element=$this duplicate'); return false; } - //debugPrint('Element.addIntersectionObserver,element=$this attached:${renderBoxModel?.attached ?? false}'); if (renderBoxModel?.attached ?? false) { renderBoxModel!.addIntersectionChangeListener(_handleIntersectionObserver, thresholds); - renderBoxModel!.markNeedsPaint(); //markNeedsCompositingBitsUpdate + renderBoxModel!.markNeedsPaint(); } _intersectionObserverList.add(observer); _thresholds = thresholds; @@ -2015,7 +2012,6 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin } void removeIntersectionObserver(IntersectionObserver observer) { - //debugPrint('Element.removeIntersectionObserver,element=$this'); _intersectionObserverList.remove(observer); if (_intersectionObserverList.isEmpty) { @@ -2027,7 +2023,6 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin if (_intersectionObserverList.isEmpty) { return; } - //debugPrint('Element.ensureAddIntersectionObserver,element=$this renderBoxModel:$renderBoxModel'); renderBoxModel?.addIntersectionChangeListener(_handleIntersectionObserver, _thresholds); } } diff --git a/webf/lib/src/dom/intersection_observer.dart b/webf/lib/src/dom/intersection_observer.dart index 867055f49f..f45b430652 100644 --- a/webf/lib/src/dom/intersection_observer.dart +++ b/webf/lib/src/dom/intersection_observer.dart @@ -7,8 +7,8 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:webf/foundation.dart'; -import '../../bridge.dart'; -import '../../launcher.dart'; +import 'package:webf/bridge.dart'; +import 'package:webf/launcher.dart'; import 'element.dart'; import 'intersection_observer_entry.dart'; import 'package:flutter/foundation.dart'; From 6c593e2be4c5a5f2f08826bdfd022d8ad1f9eaf8 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Thu, 12 Dec 2024 07:48:46 +0000 Subject: [PATCH 6/7] Committing clang-format changes --- bridge/foundation/ui_command_buffer.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 92b3b4c88c..8244ff5caf 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -21,8 +21,7 @@ enum UICommandKind : uint32_t { kAttributeUpdate = 1 << 5, kDisposeBindingObject = 1 << 6, kOperation = 1 << 7, - kIntersectionObserver = 1 << 8 - kUknownCommand = 1 << 9 + kIntersectionObserver = 1 << 8 kUknownCommand = 1 << 9 }; enum class UICommand { From f5e85b5ea111654d26aaa24c819541e367b5b7a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=80=86=E8=91=B5?= Date: Sun, 15 Dec 2024 22:49:46 +0800 Subject: [PATCH 7/7] test(intersectionobserver): add test case --- .../intersection-observer.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 integration_tests/specs/intersection-observer/intersection-observer.ts diff --git a/integration_tests/specs/intersection-observer/intersection-observer.ts b/integration_tests/specs/intersection-observer/intersection-observer.ts new file mode 100644 index 0000000000..47cc0d1d62 --- /dev/null +++ b/integration_tests/specs/intersection-observer/intersection-observer.ts @@ -0,0 +1,42 @@ +describe('IntersectionObserver', () => { + let container: HTMLElement; + let observed: HTMLElement; + + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + + const spacer = document.createElement('div'); + spacer.style.height = '100px'; + container.appendChild(spacer); + + observed = document.createElement('div'); + observed.id = 'observed'; + observed.style.width = '200px'; + observed.style.height = '200px'; + observed.style.backgroundColor = 'red'; + container.appendChild(observed); + }); + + afterEach(() => { + document.body.removeChild(container); + }); + + it('should trigger callback when element intersects', (done) => { + const intersectionCallback = (entries: IntersectionObserverEntry[]) => { + entries.forEach(entry => { + expect(entry.target).toBe(observed); + done(); + }); + }; + + const intersectionOptions = { + root: null, + rootMargin: "0px", + threshold: [0, 1] + }; + + const observer = new IntersectionObserver(intersectionCallback, intersectionOptions); + observer.observe(observed); + }); +});