/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AccessibleCaret.h" #include "AccessibleCaretLogger.h" #include "mozilla/Assertions.h" #include "mozilla/ErrorResult.h" #include "mozilla/FloatingPoint.h" #include "mozilla/PresShell.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/ToString.h" #include "nsCanvasFrame.h" #include "nsCaret.h" #include "nsCSSFrameConstructor.h" #include "nsDOMTokenList.h" #include "nsGenericHTMLElement.h" #include "nsIFrame.h" #include "nsLayoutUtils.h" #include "nsPlaceholderFrame.h" namespace mozilla { using namespace dom; #undef AC_LOG #define AC_LOG(message, ...) \ AC_LOG_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__); #undef AC_LOGV #define AC_LOGV(message, ...) \ AC_LOGV_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__); NS_IMPL_ISUPPORTS(AccessibleCaret::DummyTouchListener, nsIDOMEventListener) static constexpr auto kTextOverlayElementId = u"text-overlay"_ns; static constexpr auto kCaretImageElementId = u"image"_ns; #define AC_PROCESS_ENUM_TO_STREAM(e) \ case (e): \ aStream << #e; \ break; std::ostream& operator<<(std::ostream& aStream, const AccessibleCaret::Appearance& aAppearance) { using Appearance = AccessibleCaret::Appearance; switch (aAppearance) { AC_PROCESS_ENUM_TO_STREAM(Appearance::None); AC_PROCESS_ENUM_TO_STREAM(Appearance::Normal); AC_PROCESS_ENUM_TO_STREAM(Appearance::NormalNotShown); AC_PROCESS_ENUM_TO_STREAM(Appearance::Left); AC_PROCESS_ENUM_TO_STREAM(Appearance::Right); } return aStream; } std::ostream& operator<<( std::ostream& aStream, const AccessibleCaret::PositionChangedResult& aResult) { using PositionChangedResult = AccessibleCaret::PositionChangedResult; switch (aResult) { AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::NotChanged); AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Position); AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Zoom); AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Invisible); } return aStream; } #undef AC_PROCESS_ENUM_TO_STREAM // ----------------------------------------------------------------------------- // Implementation of AccessibleCaret methods AccessibleCaret::AccessibleCaret(PresShell* aPresShell) : mPresShell(aPresShell) { // Check all resources required. if (mPresShell) { MOZ_ASSERT(mPresShell->GetDocument()); InjectCaretElement(mPresShell->GetDocument()); } } AccessibleCaret::~AccessibleCaret() { if (mPresShell) { RemoveCaretElement(mPresShell->GetDocument()); } } dom::Element* AccessibleCaret::TextOverlayElement() const { return mCaretElementHolder->Root()->GetElementById(kTextOverlayElementId); } dom::Element* AccessibleCaret::CaretImageElement() const { return mCaretElementHolder->Root()->GetElementById(kCaretImageElementId); } void AccessibleCaret::SetAppearance(Appearance aAppearance) { if (mAppearance == aAppearance) { return; } IgnoredErrorResult rv; CaretElement().ClassList()->Remove(AppearanceString(mAppearance), rv); MOZ_ASSERT(!rv.Failed(), "Remove old appearance failed!"); CaretElement().ClassList()->Add(AppearanceString(aAppearance), rv); MOZ_ASSERT(!rv.Failed(), "Add new appearance failed!"); AC_LOG("%s: %s -> %s", __FUNCTION__, ToString(mAppearance).c_str(), ToString(aAppearance).c_str()); mAppearance = aAppearance; // Need to reset rect since the cached rect will be compared in SetPosition. if (mAppearance == Appearance::None) { ClearCachedData(); } } /* static */ nsAutoString AccessibleCaret::AppearanceString(Appearance aAppearance) { nsAutoString string; switch (aAppearance) { case Appearance::None: string = u"none"_ns; break; case Appearance::NormalNotShown: string = u"hidden"_ns; break; case Appearance::Normal: string = u"normal"_ns; break; case Appearance::Right: string = u"right"_ns; break; case Appearance::Left: string = u"left"_ns; break; } return string; } bool AccessibleCaret::Intersects(const AccessibleCaret& aCaret) const { MOZ_ASSERT(mPresShell == aCaret.mPresShell); if (!IsVisuallyVisible() || !aCaret.IsVisuallyVisible()) { return false; } nsRect rect = nsLayoutUtils::GetRectRelativeToFrame(&CaretElement(), RootFrame()); nsRect rhsRect = nsLayoutUtils::GetRectRelativeToFrame(&aCaret.CaretElement(), RootFrame()); return rect.Intersects(rhsRect); } bool AccessibleCaret::Contains(const nsPoint& aPoint, TouchArea aTouchArea) const { if (!IsVisuallyVisible()) { return false; } nsRect textOverlayRect = nsLayoutUtils::GetRectRelativeToFrame(TextOverlayElement(), RootFrame()); nsRect caretImageRect = nsLayoutUtils::GetRectRelativeToFrame(CaretImageElement(), RootFrame()); if (aTouchArea == TouchArea::CaretImage) { return caretImageRect.Contains(aPoint); } MOZ_ASSERT(aTouchArea == TouchArea::Full, "Unexpected TouchArea type!"); return textOverlayRect.Contains(aPoint) || caretImageRect.Contains(aPoint); } void AccessibleCaret::EnsureApzAware() { // If the caret element was cloned, the listener might have been lost. So // if that's the case we register a dummy listener if there isn't one on // the element already. if (!CaretElement().IsApzAware()) { CaretElement().AddEventListener(u"touchstart"_ns, mDummyTouchListener, false); } } bool AccessibleCaret::IsInPositionFixedSubtree() const { return nsLayoutUtils::IsInPositionFixedSubtree( mImaginaryCaretReferenceFrame.GetFrame()); } void AccessibleCaret::InjectCaretElement(Document* aDocument) { mCaretElementHolder = aDocument->InsertAnonymousContent(/* aForce = */ false, IgnoreErrors()); MOZ_RELEASE_ASSERT(mCaretElementHolder, "We must have anonymous content!"); CreateCaretElement(); EnsureApzAware(); } void AccessibleCaret::CreateCaretElement() const { // Content structure of AccessibleCaret //