tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AccessibleCaret.cpp (12679B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "AccessibleCaret.h"
      8 
      9 #include "AccessibleCaretLogger.h"
     10 #include "mozilla/Assertions.h"
     11 #include "mozilla/BuiltInStyleSheets.h"
     12 #include "mozilla/ErrorResult.h"
     13 #include "mozilla/FloatingPoint.h"
     14 #include "mozilla/PresShell.h"
     15 #include "mozilla/StaticPrefs_layout.h"
     16 #include "mozilla/ToString.h"
     17 #include "mozilla/dom/Document.h"
     18 #include "mozilla/dom/ShadowRoot.h"
     19 #include "nsCSSFrameConstructor.h"
     20 #include "nsCanvasFrame.h"
     21 #include "nsCaret.h"
     22 #include "nsDOMTokenList.h"
     23 #include "nsGenericHTMLElement.h"
     24 #include "nsIFrame.h"
     25 #include "nsLayoutUtils.h"
     26 #include "nsPlaceholderFrame.h"
     27 
     28 namespace mozilla {
     29 using namespace dom;
     30 
     31 #undef AC_LOG
     32 #define AC_LOG(message, ...) \
     33  AC_LOG_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__);
     34 
     35 #undef AC_LOGV
     36 #define AC_LOGV(message, ...) \
     37  AC_LOGV_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__);
     38 
     39 NS_IMPL_ISUPPORTS(AccessibleCaret::DummyTouchListener, nsIDOMEventListener)
     40 
     41 static constexpr auto kTextOverlayElementId = u"text-overlay"_ns;
     42 static constexpr auto kCaretImageElementId = u"image"_ns;
     43 
     44 #define AC_PROCESS_ENUM_TO_STREAM(e) \
     45  case (e):                          \
     46    aStream << #e;                   \
     47    break;
     48 std::ostream& operator<<(std::ostream& aStream,
     49                         const AccessibleCaret::Appearance& aAppearance) {
     50  using Appearance = AccessibleCaret::Appearance;
     51  switch (aAppearance) {
     52    AC_PROCESS_ENUM_TO_STREAM(Appearance::None);
     53    AC_PROCESS_ENUM_TO_STREAM(Appearance::Normal);
     54    AC_PROCESS_ENUM_TO_STREAM(Appearance::NormalNotShown);
     55    AC_PROCESS_ENUM_TO_STREAM(Appearance::Left);
     56    AC_PROCESS_ENUM_TO_STREAM(Appearance::Right);
     57  }
     58  return aStream;
     59 }
     60 
     61 std::ostream& operator<<(
     62    std::ostream& aStream,
     63    const AccessibleCaret::PositionChangedResult& aResult) {
     64  using PositionChangedResult = AccessibleCaret::PositionChangedResult;
     65  switch (aResult) {
     66    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::NotChanged);
     67    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Position);
     68    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Zoom);
     69    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Invisible);
     70  }
     71  return aStream;
     72 }
     73 #undef AC_PROCESS_ENUM_TO_STREAM
     74 
     75 // -----------------------------------------------------------------------------
     76 // Implementation of AccessibleCaret methods
     77 
     78 AccessibleCaret::AccessibleCaret(PresShell* aPresShell)
     79    : mPresShell(aPresShell) {
     80  // Check all resources required.
     81  if (mPresShell) {
     82    MOZ_ASSERT(mPresShell->GetDocument());
     83    InjectCaretElement(mPresShell->GetDocument());
     84  }
     85 }
     86 
     87 AccessibleCaret::~AccessibleCaret() {
     88  if (mPresShell) {
     89    RemoveCaretElement(mPresShell->GetDocument());
     90  }
     91 }
     92 
     93 dom::Element* AccessibleCaret::TextOverlayElement() const {
     94  return mCaretElementHolder->Root()->GetElementById(kTextOverlayElementId);
     95 }
     96 
     97 dom::Element* AccessibleCaret::CaretImageElement() const {
     98  return mCaretElementHolder->Root()->GetElementById(kCaretImageElementId);
     99 }
    100 
    101 void AccessibleCaret::SetAppearance(Appearance aAppearance) {
    102  if (mAppearance == aAppearance) {
    103    return;
    104  }
    105 
    106  IgnoredErrorResult rv;
    107  CaretElement().ClassList()->Remove(AppearanceString(mAppearance), rv);
    108  MOZ_ASSERT(!rv.Failed(), "Remove old appearance failed!");
    109 
    110  CaretElement().ClassList()->Add(AppearanceString(aAppearance), rv);
    111  MOZ_ASSERT(!rv.Failed(), "Add new appearance failed!");
    112 
    113  AC_LOG("%s: %s -> %s", __FUNCTION__, ToString(mAppearance).c_str(),
    114         ToString(aAppearance).c_str());
    115 
    116  mAppearance = aAppearance;
    117 
    118  // Need to reset rect since the cached rect will be compared in SetPosition.
    119  if (mAppearance == Appearance::None) {
    120    ClearCachedData();
    121  }
    122 }
    123 
    124 /* static */
    125 nsAutoString AccessibleCaret::AppearanceString(Appearance aAppearance) {
    126  nsAutoString string;
    127  switch (aAppearance) {
    128    case Appearance::None:
    129      string = u"none"_ns;
    130      break;
    131    case Appearance::NormalNotShown:
    132      string = u"hidden"_ns;
    133      break;
    134    case Appearance::Normal:
    135      string = u"normal"_ns;
    136      break;
    137    case Appearance::Right:
    138      string = u"right"_ns;
    139      break;
    140    case Appearance::Left:
    141      string = u"left"_ns;
    142      break;
    143  }
    144  return string;
    145 }
    146 
    147 bool AccessibleCaret::Intersects(const AccessibleCaret& aCaret) const {
    148  MOZ_ASSERT(mPresShell == aCaret.mPresShell);
    149 
    150  if (!IsVisuallyVisible() || !aCaret.IsVisuallyVisible()) {
    151    return false;
    152  }
    153 
    154  nsRect rect =
    155      nsLayoutUtils::GetRectRelativeToFrame(&CaretElement(), RootFrame());
    156  nsRect rhsRect = nsLayoutUtils::GetRectRelativeToFrame(&aCaret.CaretElement(),
    157                                                         RootFrame());
    158  return rect.Intersects(rhsRect);
    159 }
    160 
    161 bool AccessibleCaret::Contains(const nsPoint& aPoint,
    162                               TouchArea aTouchArea) const {
    163  if (!IsVisuallyVisible()) {
    164    return false;
    165  }
    166 
    167  nsRect textOverlayRect =
    168      nsLayoutUtils::GetRectRelativeToFrame(TextOverlayElement(), RootFrame());
    169  nsRect caretImageRect =
    170      nsLayoutUtils::GetRectRelativeToFrame(CaretImageElement(), RootFrame());
    171 
    172  if (aTouchArea == TouchArea::CaretImage) {
    173    return caretImageRect.Contains(aPoint);
    174  }
    175 
    176  MOZ_ASSERT(aTouchArea == TouchArea::Full, "Unexpected TouchArea type!");
    177  return textOverlayRect.Contains(aPoint) || caretImageRect.Contains(aPoint);
    178 }
    179 
    180 void AccessibleCaret::EnsureApzAware() {
    181  // If the caret element was cloned, the listener might have been lost. So
    182  // if that's the case we register a dummy listener if there isn't one on
    183  // the element already.
    184  if (!CaretElement().IsApzAware()) {
    185    CaretElement().AddEventListener(u"touchstart"_ns, mDummyTouchListener,
    186                                    false);
    187  }
    188 }
    189 
    190 bool AccessibleCaret::IsInPositionFixedSubtree() const {
    191  return nsLayoutUtils::IsInPositionFixedSubtree(
    192      mImaginaryCaretReferenceFrame.GetFrame());
    193 }
    194 
    195 void AccessibleCaret::InjectCaretElement(Document* aDocument) {
    196  mCaretElementHolder = aDocument->InsertAnonymousContent(IgnoreErrors());
    197  MOZ_RELEASE_ASSERT(mCaretElementHolder, "We must have anonymous content!");
    198 
    199  CreateCaretElement();
    200  EnsureApzAware();
    201 }
    202 
    203 void AccessibleCaret::CreateCaretElement() const {
    204  // Content structure of AccessibleCaret
    205  // <div class="moz-accessiblecaret">  <- CaretElement()
    206  //   <#shadow-root>
    207  //     <div id="text-overlay">          <- TextOverlayElement()
    208  //     <div id="image">                 <- CaretImageElement()
    209 
    210  constexpr bool kNotify = false;
    211 
    212  Element& host = CaretElement();
    213  host.SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
    214               u"moz-accessiblecaret none"_ns, kNotify);
    215 
    216  ShadowRoot* root = mCaretElementHolder->Root();
    217  Document* doc = host.OwnerDoc();
    218  root->AppendBuiltInStyleSheet(BuiltInStyleSheet::AccessibleCaret);
    219 
    220  auto CreateAndAppendChildElement = [&](const nsLiteralString& aElementId) {
    221    RefPtr<Element> child = doc->CreateHTMLElement(nsGkAtoms::div);
    222    child->SetAttr(kNameSpaceID_None, nsGkAtoms::id, aElementId, kNotify);
    223    mCaretElementHolder->Root()->AppendChildTo(child, kNotify, IgnoreErrors());
    224  };
    225 
    226  CreateAndAppendChildElement(kTextOverlayElementId);
    227  CreateAndAppendChildElement(kCaretImageElementId);
    228 }
    229 
    230 void AccessibleCaret::RemoveCaretElement(Document* aDocument) {
    231  CaretElement().RemoveEventListener(u"touchstart"_ns, mDummyTouchListener,
    232                                     false);
    233 
    234  aDocument->RemoveAnonymousContent(*mCaretElementHolder);
    235 }
    236 
    237 void AccessibleCaret::ClearCachedData() {
    238  mImaginaryCaretRect = nsRect();
    239  mImaginaryCaretRectInContainerFrame = nsRect();
    240  mImaginaryCaretReferenceFrame = nullptr;
    241  mZoomLevel = 0.0f;
    242 }
    243 
    244 AccessibleCaret::PositionChangedResult AccessibleCaret::SetPosition(
    245    nsIFrame* aFrame, int32_t aOffset) {
    246  if (!CustomContentContainerFrame()) {
    247    return PositionChangedResult::NotChanged;
    248  }
    249 
    250  nsRect imaginaryCaretRectInFrame =
    251      nsCaret::GetGeometryForFrame(aFrame, aOffset, nullptr);
    252 
    253  imaginaryCaretRectInFrame =
    254      nsLayoutUtils::ClampRectToScrollFrames(aFrame, imaginaryCaretRectInFrame);
    255 
    256  if (imaginaryCaretRectInFrame.IsEmpty()) {
    257    // Don't bother to set the caret position since it's invisible.
    258    ClearCachedData();
    259    return PositionChangedResult::Invisible;
    260  }
    261 
    262  // SetCaretElementStyle() requires the input rect relative to the custom
    263  // content container frame.
    264  nsRect imaginaryCaretRectInContainerFrame = imaginaryCaretRectInFrame;
    265  nsLayoutUtils::TransformRect(aFrame, CustomContentContainerFrame(),
    266                               imaginaryCaretRectInContainerFrame);
    267  const float zoomLevel = GetZoomLevel();
    268  const bool isSamePosition = imaginaryCaretRectInContainerFrame.IsEqualEdges(
    269      mImaginaryCaretRectInContainerFrame);
    270  const bool isSameZoomLevel = FuzzyEqualsMultiplicative(zoomLevel, mZoomLevel);
    271 
    272  // Always update cached mImaginaryCaretRect (relative to the root frame)
    273  // because it can change when the caret is scrolled.
    274  mImaginaryCaretRect = imaginaryCaretRectInFrame;
    275  nsLayoutUtils::TransformRect(aFrame, RootFrame(), mImaginaryCaretRect);
    276 
    277  if (isSamePosition && isSameZoomLevel) {
    278    return PositionChangedResult::NotChanged;
    279  }
    280 
    281  mImaginaryCaretRectInContainerFrame = imaginaryCaretRectInContainerFrame;
    282  mImaginaryCaretReferenceFrame = aFrame;
    283  mZoomLevel = zoomLevel;
    284 
    285  SetCaretElementStyle(imaginaryCaretRectInContainerFrame, mZoomLevel);
    286 
    287  return isSamePosition ? PositionChangedResult::Zoom
    288                        : PositionChangedResult::Position;
    289 }
    290 
    291 nsIFrame* AccessibleCaret::RootFrame() const {
    292  return mPresShell->GetRootFrame();
    293 }
    294 
    295 nsIFrame* AccessibleCaret::CustomContentContainerFrame() const {
    296  Element* container = mPresShell->GetDocument()->GetCustomContentContainer();
    297  return container->GetPrimaryFrame();
    298 }
    299 
    300 void AccessibleCaret::SetCaretElementStyle(const nsRect& aRect,
    301                                           float aZoomLevel) {
    302  nsPoint position = CaretElementPosition(aRect);
    303  nsAutoString styleStr;
    304  // We can't use AppendPrintf here, because it does locale-specific
    305  // formatting of floating-point values.
    306  styleStr.AppendLiteral("left: ");
    307  styleStr.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(position.x));
    308  styleStr.AppendLiteral("px; top: ");
    309  styleStr.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(position.y));
    310  styleStr.AppendLiteral("px; width: ");
    311  styleStr.AppendFloat(StaticPrefs::layout_accessiblecaret_width() /
    312                       aZoomLevel);
    313  styleStr.AppendLiteral("px; margin-left: ");
    314  styleStr.AppendFloat(StaticPrefs::layout_accessiblecaret_margin_left() /
    315                       aZoomLevel);
    316  styleStr.AppendLiteral("px; transition-duration: ");
    317  styleStr.AppendFloat(
    318      StaticPrefs::layout_accessiblecaret_transition_duration());
    319  styleStr.AppendLiteral("ms");
    320 
    321  CaretElement().SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr, true);
    322  AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
    323 
    324  // Set style string for children.
    325  SetTextOverlayElementStyle(aRect, aZoomLevel);
    326  SetCaretImageElementStyle(aRect, aZoomLevel);
    327 }
    328 
    329 void AccessibleCaret::SetTextOverlayElementStyle(const nsRect& aRect,
    330                                                 float aZoomLevel) {
    331  nsAutoString styleStr;
    332  styleStr.AppendLiteral("height: ");
    333  styleStr.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
    334  styleStr.AppendLiteral("px;");
    335  TextOverlayElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
    336                                true);
    337  AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
    338 }
    339 
    340 void AccessibleCaret::SetCaretImageElementStyle(const nsRect& aRect,
    341                                                float aZoomLevel) {
    342  nsAutoString styleStr;
    343  styleStr.AppendLiteral("height: ");
    344  styleStr.AppendFloat(StaticPrefs::layout_accessiblecaret_height() /
    345                       aZoomLevel);
    346  styleStr.AppendLiteral("px;");
    347  CaretImageElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
    348                               true);
    349  AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
    350 }
    351 
    352 float AccessibleCaret::GetZoomLevel() {
    353  // Full zoom on desktop.
    354  float fullZoom = mPresShell->GetPresContext()->GetFullZoom();
    355 
    356  // Pinch-zoom on fennec.
    357  float resolution = mPresShell->GetCumulativeResolution();
    358 
    359  return fullZoom * resolution;
    360 }
    361 
    362 }  // namespace mozilla