| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_ANNOTATION_ANNOTATION_AGENT_IMPL_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_CORE_ANNOTATION_ANNOTATION_AGENT_IMPL_H_ |
| |
| #include "base/types/pass_key.h" |
| #include "third_party/blink/public/mojom/annotation/annotation.mojom-blink.h" |
| #include "third_party/blink/public/mojom/scroll/scroll_enums.mojom-blink-forward.h" |
| #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-blink-forward.h" |
| #include "third_party/blink/renderer/core/core_export.h" |
| #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/dom_node_id.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| #include "third_party/blink/renderer/platform/heap/member.h" |
| #include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h" |
| #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" |
| |
| namespace blink { |
| |
| class AnnotationAgentContainerImpl; |
| class AnnotationAgentImplTest; |
| class AnnotationSelector; |
| class HitTestResult; |
| class RangeInFlatTree; |
| |
| // This class represents an instantiation of an annotation in a Document. It is |
| // always owned and stored in an AnnotationAgentContainerImpl. Removing it from |
| // the container will remove any visible effects on content and disconnect mojo |
| // bindings. Once removed, an agent cannot be reused. |
| // |
| // AnnotationAgentImpl allows its client to provide a selector that it uses to |
| // "attach" to a particular Range in the Document. Once attached, the |
| // annotation adds a visible marker to the content and enables the client to |
| // scroll the attached content into view. An agent is considered attached if it |
| // has found a Range and that Range is valid and uncollapsed. The range may |
| // become invalid in response to changes in a Document's DOM. |
| // TODO(bokan): Changes in the DOM affecting an annotation should be signaled |
| // via the AnnotationAgentHost interface. |
| // |
| // This class is the renderer end of an annotation. It can be instantiated |
| // directly from Blink as well in which case it need not be bound to a host on |
| // the browser side. If bound to a corresponding AnnotationAgentHost in the |
| // browser, it will notify the host of relevant events (e.g. attachment |
| // succeeded/failed) and self-remove itself from the container if the mojo |
| // bindings become disconnected (i.e. if the browser closes the connection, the |
| // AnnotationAgentImpl will be removed). |
| class CORE_EXPORT AnnotationAgentImpl final |
| : public GarbageCollected<AnnotationAgentImpl>, |
| public mojom::blink::AnnotationAgent { |
| public: |
| using PassKey = base::PassKey<AnnotationAgentImpl>; |
| |
| // Agents can only be created via AnnotationAgentContainerImpl. |
| AnnotationAgentImpl(AnnotationAgentContainerImpl& owning_container, |
| mojom::blink::AnnotationType annotation_type, |
| AnnotationSelector& selector, |
| std::optional<DOMNodeId> search_range_start_node_id, |
| base::PassKey<AnnotationAgentContainerImpl>); |
| ~AnnotationAgentImpl() override = default; |
| |
| // Non-copyable or moveable |
| AnnotationAgentImpl(const AnnotationAgentImpl&) = delete; |
| AnnotationAgentImpl& operator=(const AnnotationAgentImpl&) = delete; |
| |
| void Trace(Visitor* visitor) const; |
| |
| // Binds this agent to a host. |
| void Bind( |
| mojo::PendingRemote<mojom::blink::AnnotationAgentHost> host_remote, |
| mojo::PendingReceiver<mojom::blink::AnnotationAgent> agent_receiver); |
| |
| // Attempts to find a Range of DOM matching the search criteria of the |
| // AnnotationSelector passed in the constructor. The DOM search is performed |
| // synchronously but if that match is in a hidden subtree |
| // (content-visibility: auto, <details>, hidden=until-found) that can be |
| // shown, attachment will complete asynchronously once the subtree is made |
| // visible. This is only called by the AnnotationAgentContainer immediately |
| // after layout is completed (clients should use SetNeedsAttachment to |
| // request an attachment if the initial one failed). |
| void Attach(base::PassKey<AnnotationAgentContainerImpl>); |
| |
| AnnotationAgentContainerImpl* OwningContainer() { return owning_container_; } |
| |
| // Clients can request an attachment (after the automatic first one has |
| // failed) to occur using this setter. |
| void SetNeedsAttachment() { needs_attachment_ = true; } |
| bool NeedsAttachment() const { return needs_attachment_; } |
| |
| // Returns true if the agent has performed attachment and resulted in a valid |
| // DOM Range. Note that Range is relocated, meaning that it will update in |
| // response to changes in DOM. Hence, an AnnotationAgent that IsAttached may |
| // become detached due to changes in the Document. |
| bool IsAttached() const; |
| |
| // Returns true if the agent has found the requested range but is waiting |
| // on DOM mutations before attaching. For example, if the range is in a hidden |
| // <details> element, the agent will be in a pending state until the <details> |
| // is opened in the next animation frame. |
| bool IsAttachmentPending() const; |
| |
| // Returns true if this agent is bound to a host. |
| bool IsBoundForTesting() const; |
| |
| // Prepares the agent to be removed from its container by clearing all its |
| // state, mojo bindings, and any visual indications in the document. Should |
| // only be called by its container. |
| void Reset(base::PassKey<AnnotationAgentContainerImpl>); |
| |
| // mojom::blink::AnnotationAgent |
| void ScrollIntoView(bool applies_focus) override { |
| const_cast<const AnnotationAgentImpl*>(this)->ScrollIntoView(applies_focus); |
| } |
| void ScrollIntoView(bool applies_focus) const; |
| |
| const RangeInFlatTree& GetAttachedRange() const { |
| CHECK(attached_range_.Get()); |
| return *attached_range_.Get(); |
| } |
| |
| const AnnotationSelector* GetSelector() const { return selector_.Get(); } |
| |
| mojom::blink::AnnotationType GetType() const { return type_; } |
| |
| bool IsScrollOnly() const { |
| return type_ == mojom::blink::AnnotationType::kScrollOnly; |
| } |
| |
| // Determine if `result` represents a click on an existing annotation, and |
| // returns the type of the annotation if so (or std::nullopt if not). |
| // Note: It is possible for the click to be above multiple annotations, in |
| // which case we only return the type of what we consider to be the "topmost" |
| // (see implementation). |
| static std::optional<mojom::blink::AnnotationType> IsOverAnnotation( |
| const HitTestResult& result); |
| |
| private: |
| friend AnnotationAgentImplTest; |
| |
| // Callback for when AnnotationSelector::FindRange finishes. If needed, this |
| // may post a task to perform DOM mutation for hidden=until-found and similar |
| // "activate on find" DOM features before calling ProcessAttachmentFinished. |
| // Otherwise, ProcessAttachmentFinished is called synchronously. |
| void DidFinishFindRange(const RangeInFlatTree* range); |
| |
| bool NeedsDOMMutationToAttach() const; |
| void PerformPreAttachDOMMutation(); |
| |
| // This will add the highlight marker and respond to the host or callback as |
| // needed. |
| void ProcessAttachmentFinished(); |
| |
| bool IsRemoved() const; |
| |
| mojom::blink::ScrollBehavior ComputeScrollIntoViewBehavior( |
| const PhysicalRect& bounding_box, |
| const mojom::blink::ScrollIntoViewParams& params) const; |
| |
| // Mojo bindings to the remote host and this' remote. These are always |
| // connected as a pair and disconnecting one will cause the other to be |
| // disconnected as well. |
| HeapMojoRemote<mojom::blink::AnnotationAgentHost> agent_host_; |
| HeapMojoReceiver<mojom::blink::AnnotationAgent, AnnotationAgentImpl> |
| receiver_; |
| |
| // These will be cleared when the agent is removed from its container. |
| Member<AnnotationAgentContainerImpl> owning_container_; |
| Member<AnnotationSelector> selector_; |
| |
| // The attached_range_ is null until the agent performs a successful |
| // Attach(). |
| Member<RangeInFlatTree> attached_range_; |
| |
| // In some cases attachment may be asynchronous, e.g. while waiting on a new |
| // compositor frame to expand a hidden section. If text was found but is |
| // waiting for such a "PreAttachDOMMutation", the range will be stored here |
| // before being "attached" by transfer to `attached_range_`. At most one of |
| // `attached_range_` or `pending_range_` will be non-null |
| // TODO(bokan): This doesn't need to be const but is due to the |
| // TextFragmentFinder::Client interface. |
| Member<const RangeInFlatTree> pending_range_; |
| |
| // The start node id of the search range within which the agent will attempt |
| // to match the selector in. |
| std::optional<DOMNodeId> search_range_start_node_id_; |
| |
| // TODO(bokan): Once we have more of this implemented we'll use the type to |
| // determine styling and context menu behavior. |
| mojom::blink::AnnotationType type_; |
| |
| // Attachment is expensive so it's called only once from |
| // PerformInitialAttachments. Clients can reset this value to try again (e.g. |
| // to try and attach to newly added content). |
| bool needs_attachment_ = true; |
| }; |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_CORE_ANNOTATION_ANNOTATION_AGENT_IMPL_H_ |