Blame layout/base/AccessibleCaret.cpp

Packit f0b94e
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
Packit f0b94e
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
Packit f0b94e
/* This Source Code Form is subject to the terms of the Mozilla Public
Packit f0b94e
 * License, v. 2.0. If a copy of the MPL was not distributed with this
Packit f0b94e
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Packit f0b94e
Packit f0b94e
#include "AccessibleCaret.h"
Packit f0b94e
Packit f0b94e
#include "AccessibleCaretLogger.h"
Packit f0b94e
#include "mozilla/FloatingPoint.h"
Packit f0b94e
#include "mozilla/Preferences.h"
Packit f0b94e
#include "mozilla/ToString.h"
Packit f0b94e
#include "nsCanvasFrame.h"
Packit f0b94e
#include "nsCaret.h"
Packit f0b94e
#include "nsCSSFrameConstructor.h"
Packit f0b94e
#include "nsDOMTokenList.h"
Packit f0b94e
#include "nsIFrame.h"
Packit f0b94e
#include "nsPlaceholderFrame.h"
Packit f0b94e
Packit f0b94e
namespace mozilla {
Packit f0b94e
using namespace dom;
Packit f0b94e
Packit f0b94e
#undef AC_LOG
Packit f0b94e
#define AC_LOG(message, ...) \
Packit f0b94e
  AC_LOG_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__);
Packit f0b94e
Packit f0b94e
#undef AC_LOGV
Packit f0b94e
#define AC_LOGV(message, ...) \
Packit f0b94e
  AC_LOGV_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__);
Packit f0b94e
Packit f0b94e
NS_IMPL_ISUPPORTS(AccessibleCaret::DummyTouchListener, nsIDOMEventListener)
Packit f0b94e
Packit f0b94e
float AccessibleCaret::sWidth = 0.0f;
Packit f0b94e
float AccessibleCaret::sHeight = 0.0f;
Packit f0b94e
float AccessibleCaret::sMarginLeft = 0.0f;
Packit f0b94e
float AccessibleCaret::sBarWidth = 0.0f;
Packit f0b94e
Packit f0b94e
NS_NAMED_LITERAL_STRING(AccessibleCaret::sTextOverlayElementId, "text-overlay");
Packit f0b94e
NS_NAMED_LITERAL_STRING(AccessibleCaret::sCaretImageElementId, "image");
Packit f0b94e
NS_NAMED_LITERAL_STRING(AccessibleCaret::sSelectionBarElementId, "bar");
Packit f0b94e
Packit f0b94e
#define AC_PROCESS_ENUM_TO_STREAM(e) \
Packit f0b94e
  case (e):                          \
Packit f0b94e
    aStream << #e;                   \
Packit f0b94e
    break;
Packit f0b94e
std::ostream& operator<<(std::ostream& aStream,
Packit f0b94e
                         const AccessibleCaret::Appearance& aAppearance) {
Packit f0b94e
  using Appearance = AccessibleCaret::Appearance;
Packit f0b94e
  switch (aAppearance) {
Packit f0b94e
    AC_PROCESS_ENUM_TO_STREAM(Appearance::None);
Packit f0b94e
    AC_PROCESS_ENUM_TO_STREAM(Appearance::Normal);
Packit f0b94e
    AC_PROCESS_ENUM_TO_STREAM(Appearance::NormalNotShown);
Packit f0b94e
    AC_PROCESS_ENUM_TO_STREAM(Appearance::Left);
Packit f0b94e
    AC_PROCESS_ENUM_TO_STREAM(Appearance::Right);
Packit f0b94e
  }
Packit f0b94e
  return aStream;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
std::ostream& operator<<(
Packit f0b94e
    std::ostream& aStream,
Packit f0b94e
    const AccessibleCaret::PositionChangedResult& aResult) {
Packit f0b94e
  using PositionChangedResult = AccessibleCaret::PositionChangedResult;
Packit f0b94e
  switch (aResult) {
Packit f0b94e
    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::NotChanged);
Packit f0b94e
    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Changed);
Packit f0b94e
    AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Invisible);
Packit f0b94e
  }
Packit f0b94e
  return aStream;
Packit f0b94e
}
Packit f0b94e
#undef AC_PROCESS_ENUM_TO_STREAM
Packit f0b94e
Packit f0b94e
// -----------------------------------------------------------------------------
Packit f0b94e
// Implementation of AccessibleCaret methods
Packit f0b94e
Packit f0b94e
AccessibleCaret::AccessibleCaret(nsIPresShell* aPresShell)
Packit f0b94e
    : mPresShell(aPresShell) {
Packit f0b94e
  // Check all resources required.
Packit f0b94e
  if (mPresShell) {
Packit f0b94e
    MOZ_ASSERT(RootFrame());
Packit f0b94e
    MOZ_ASSERT(mPresShell->GetDocument());
Packit f0b94e
    MOZ_ASSERT(mPresShell->GetCanvasFrame());
Packit f0b94e
    MOZ_ASSERT(mPresShell->GetCanvasFrame()->GetCustomContentContainer());
Packit f0b94e
Packit f0b94e
    InjectCaretElement(mPresShell->GetDocument());
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  static bool prefsAdded = false;
Packit f0b94e
  if (!prefsAdded) {
Packit f0b94e
    Preferences::AddFloatVarCache(&sWidth, "layout.accessiblecaret.width");
Packit f0b94e
    Preferences::AddFloatVarCache(&sHeight, "layout.accessiblecaret.height");
Packit f0b94e
    Preferences::AddFloatVarCache(&sMarginLeft,
Packit f0b94e
                                  "layout.accessiblecaret.margin-left");
Packit f0b94e
    Preferences::AddFloatVarCache(&sBarWidth,
Packit f0b94e
                                  "layout.accessiblecaret.bar.width");
Packit f0b94e
    prefsAdded = true;
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
AccessibleCaret::~AccessibleCaret() {
Packit f0b94e
  if (mPresShell) {
Packit f0b94e
    RemoveCaretElement(mPresShell->GetDocument());
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void AccessibleCaret::SetAppearance(Appearance aAppearance) {
Packit f0b94e
  if (mAppearance == aAppearance) {
Packit f0b94e
    return;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  ErrorResult rv;
Packit f0b94e
  CaretElement()->ClassList()->Remove(AppearanceString(mAppearance), rv);
Packit f0b94e
  MOZ_ASSERT(!rv.Failed(), "Remove old appearance failed!");
Packit f0b94e
Packit f0b94e
  CaretElement()->ClassList()->Add(AppearanceString(aAppearance), rv);
Packit f0b94e
  MOZ_ASSERT(!rv.Failed(), "Add new appearance failed!");
Packit f0b94e
Packit f0b94e
  AC_LOG("%s: %s -> %s", __FUNCTION__, ToString(mAppearance).c_str(),
Packit f0b94e
         ToString(aAppearance).c_str());
Packit f0b94e
Packit f0b94e
  mAppearance = aAppearance;
Packit f0b94e
Packit f0b94e
  // Need to reset rect since the cached rect will be compared in SetPosition.
Packit f0b94e
  if (mAppearance == Appearance::None) {
Packit f0b94e
    mImaginaryCaretRect = nsRect();
Packit f0b94e
    mZoomLevel = 0.0f;
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void AccessibleCaret::SetSelectionBarEnabled(bool aEnabled) {
Packit f0b94e
  if (mSelectionBarEnabled == aEnabled) {
Packit f0b94e
    return;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  AC_LOG("Set selection bar %s", aEnabled ? "Enabled" : "Disabled");
Packit f0b94e
Packit f0b94e
  ErrorResult rv;
Packit f0b94e
  CaretElement()->ClassList()->Toggle(NS_LITERAL_STRING("no-bar"),
Packit f0b94e
                                      Optional<bool>(!aEnabled), rv);
Packit f0b94e
  MOZ_ASSERT(!rv.Failed());
Packit f0b94e
Packit f0b94e
  mSelectionBarEnabled = aEnabled;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
/* static */ nsAutoString AccessibleCaret::AppearanceString(
Packit f0b94e
    Appearance aAppearance) {
Packit f0b94e
  nsAutoString string;
Packit f0b94e
  switch (aAppearance) {
Packit f0b94e
    case Appearance::None:
Packit f0b94e
    case Appearance::NormalNotShown:
Packit f0b94e
      string = NS_LITERAL_STRING("none");
Packit f0b94e
      break;
Packit f0b94e
    case Appearance::Normal:
Packit f0b94e
      string = NS_LITERAL_STRING("normal");
Packit f0b94e
      break;
Packit f0b94e
    case Appearance::Right:
Packit f0b94e
      string = NS_LITERAL_STRING("right");
Packit f0b94e
      break;
Packit f0b94e
    case Appearance::Left:
Packit f0b94e
      string = NS_LITERAL_STRING("left");
Packit f0b94e
      break;
Packit f0b94e
  }
Packit f0b94e
  return string;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
bool AccessibleCaret::Intersects(const AccessibleCaret& aCaret) const {
Packit f0b94e
  MOZ_ASSERT(mPresShell == aCaret.mPresShell);
Packit f0b94e
Packit f0b94e
  if (!IsVisuallyVisible() || !aCaret.IsVisuallyVisible()) {
Packit f0b94e
    return false;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  nsRect rect =
Packit f0b94e
      nsLayoutUtils::GetRectRelativeToFrame(CaretElement(), RootFrame());
Packit f0b94e
  nsRect rhsRect =
Packit f0b94e
      nsLayoutUtils::GetRectRelativeToFrame(aCaret.CaretElement(), RootFrame());
Packit f0b94e
  return rect.Intersects(rhsRect);
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
bool AccessibleCaret::Contains(const nsPoint& aPoint,
Packit f0b94e
                               TouchArea aTouchArea) const {
Packit f0b94e
  if (!IsVisuallyVisible()) {
Packit f0b94e
    return false;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  nsRect textOverlayRect =
Packit f0b94e
      nsLayoutUtils::GetRectRelativeToFrame(TextOverlayElement(), RootFrame());
Packit f0b94e
  nsRect caretImageRect =
Packit f0b94e
      nsLayoutUtils::GetRectRelativeToFrame(CaretImageElement(), RootFrame());
Packit f0b94e
Packit f0b94e
  if (aTouchArea == TouchArea::CaretImage) {
Packit f0b94e
    return caretImageRect.Contains(aPoint);
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  MOZ_ASSERT(aTouchArea == TouchArea::Full, "Unexpected TouchArea type!");
Packit f0b94e
  return textOverlayRect.Contains(aPoint) || caretImageRect.Contains(aPoint);
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void AccessibleCaret::EnsureApzAware() {
Packit f0b94e
  // If the caret element was cloned, the listener might have been lost. So
Packit f0b94e
  // if that's the case we register a dummy listener if there isn't one on
Packit f0b94e
  // the element already.
Packit f0b94e
  if (!CaretElement()->IsApzAware()) {
Packit f0b94e
    CaretElement()->AddEventListener(NS_LITERAL_STRING("touchstart"),
Packit f0b94e
                                     mDummyTouchListener, false);
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void AccessibleCaret::InjectCaretElement(nsIDocument* aDocument) {
Packit f0b94e
  ErrorResult rv;
Packit f0b94e
  nsCOMPtr<Element> element = CreateCaretElement(aDocument);
Packit f0b94e
  mCaretElementHolder = aDocument->InsertAnonymousContent(*element, rv);
Packit f0b94e
Packit f0b94e
  MOZ_ASSERT(!rv.Failed(), "Insert anonymous content should not fail!");
Packit f0b94e
  MOZ_ASSERT(mCaretElementHolder.get(), "We must have anonymous content!");
Packit f0b94e
Packit f0b94e
  // InsertAnonymousContent will clone the element to make an AnonymousContent.
Packit f0b94e
  // Since event listeners are not being cloned when cloning a node, we need to
Packit f0b94e
  // add the listener here.
Packit f0b94e
  EnsureApzAware();
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
already_AddRefed<Element> AccessibleCaret::CreateCaretElement(
Packit f0b94e
    nsIDocument* aDocument) const {
Packit f0b94e
  // Content structure of AccessibleCaret
Packit f0b94e
  // 
<- CaretElement()
Packit f0b94e
  //   
Packit f0b94e
  //   
<- CaretImageElement()
Packit f0b94e
  //   
<- SelectionBarElement()
Packit f0b94e
Packit f0b94e
  ErrorResult rv;
Packit f0b94e
  nsCOMPtr<Element> parent = aDocument->CreateHTMLElement(nsGkAtoms::div);
Packit f0b94e
  parent->ClassList()->Add(NS_LITERAL_STRING("moz-accessiblecaret"), rv);
Packit f0b94e
  parent->ClassList()->Add(NS_LITERAL_STRING("none"), rv);
Packit f0b94e
  parent->ClassList()->Add(NS_LITERAL_STRING("no-bar"), rv);
Packit f0b94e
Packit f0b94e
  auto CreateAndAppendChildElement =
Packit f0b94e
      [aDocument, &parent](const nsLiteralString& aElementId) {
Packit f0b94e
        nsCOMPtr<Element> child = aDocument->CreateHTMLElement(nsGkAtoms::div);
Packit f0b94e
        child->SetAttr(kNameSpaceID_None, nsGkAtoms::id, aElementId, true);
Packit f0b94e
        parent->AppendChildTo(child, false);
Packit f0b94e
      };
Packit f0b94e
Packit f0b94e
  CreateAndAppendChildElement(sTextOverlayElementId);
Packit f0b94e
  CreateAndAppendChildElement(sCaretImageElementId);
Packit f0b94e
  CreateAndAppendChildElement(sSelectionBarElementId);
Packit f0b94e
Packit f0b94e
  return parent.forget();
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void AccessibleCaret::RemoveCaretElement(nsIDocument* aDocument) {
Packit f0b94e
  CaretElement()->RemoveEventListener(NS_LITERAL_STRING("touchstart"),
Packit f0b94e
                                      mDummyTouchListener, false);
Packit f0b94e
Packit f0b94e
  if (nsIFrame* frame = CaretElement()->GetPrimaryFrame()) {
Packit f0b94e
    if (frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
Packit f0b94e
      frame = frame->GetPlaceholderFrame();
Packit f0b94e
    }
Packit f0b94e
    nsAutoScriptBlocker scriptBlocker;
Packit f0b94e
    frame->GetParent()->RemoveFrame(nsIFrame::kPrincipalList, frame);
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  ErrorResult rv;
Packit f0b94e
  aDocument->RemoveAnonymousContent(*mCaretElementHolder, rv);
Packit f0b94e
  // It's OK rv is failed since nsCanvasFrame might not exists now.
Packit f0b94e
  rv.SuppressException();
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
AccessibleCaret::PositionChangedResult AccessibleCaret::SetPosition(
Packit f0b94e
    nsIFrame* aFrame, int32_t aOffset) {
Packit f0b94e
  if (!CustomContentContainerFrame()) {
Packit f0b94e
    return PositionChangedResult::NotChanged;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  nsRect imaginaryCaretRectInFrame =
Packit f0b94e
      nsCaret::GetGeometryForFrame(aFrame, aOffset, nullptr);
Packit f0b94e
Packit f0b94e
  imaginaryCaretRectInFrame =
Packit f0b94e
      nsLayoutUtils::ClampRectToScrollFrames(aFrame, imaginaryCaretRectInFrame);
Packit f0b94e
Packit f0b94e
  if (imaginaryCaretRectInFrame.IsEmpty()) {
Packit f0b94e
    // Don't bother to set the caret position since it's invisible.
Packit f0b94e
    mImaginaryCaretRect = nsRect();
Packit f0b94e
    mZoomLevel = 0.0f;
Packit f0b94e
    return PositionChangedResult::Invisible;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  nsRect imaginaryCaretRect = imaginaryCaretRectInFrame;
Packit f0b94e
  nsLayoutUtils::TransformRect(aFrame, RootFrame(), imaginaryCaretRect);
Packit f0b94e
  float zoomLevel = GetZoomLevel();
Packit f0b94e
Packit f0b94e
  if (imaginaryCaretRect.IsEqualEdges(mImaginaryCaretRect) &&
Packit f0b94e
      FuzzyEqualsMultiplicative(zoomLevel, mZoomLevel)) {
Packit f0b94e
    return PositionChangedResult::NotChanged;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  mImaginaryCaretRect = imaginaryCaretRect;
Packit f0b94e
  mZoomLevel = zoomLevel;
Packit f0b94e
Packit f0b94e
  // SetCaretElementStyle() requires the input rect relative to container frame.
Packit f0b94e
  nsRect imaginaryCaretRectInContainerFrame = imaginaryCaretRectInFrame;
Packit f0b94e
  nsLayoutUtils::TransformRect(aFrame, CustomContentContainerFrame(),
Packit f0b94e
                               imaginaryCaretRectInContainerFrame);
Packit f0b94e
  SetCaretElementStyle(imaginaryCaretRectInContainerFrame, mZoomLevel);
Packit f0b94e
Packit f0b94e
  return PositionChangedResult::Changed;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
nsIFrame* AccessibleCaret::CustomContentContainerFrame() const {
Packit f0b94e
  nsCanvasFrame* canvasFrame = mPresShell->GetCanvasFrame();
Packit f0b94e
  Element* container = canvasFrame->GetCustomContentContainer();
Packit f0b94e
  nsIFrame* containerFrame = container->GetPrimaryFrame();
Packit f0b94e
  return containerFrame;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void AccessibleCaret::SetCaretElementStyle(const nsRect& aRect,
Packit f0b94e
                                           float aZoomLevel) {
Packit f0b94e
  nsPoint position = CaretElementPosition(aRect);
Packit f0b94e
  nsAutoString styleStr;
Packit f0b94e
  styleStr.AppendPrintf(
Packit f0b94e
      "left: %dpx; top: %dpx; "
Packit f0b94e
      "width: ",
Packit f0b94e
      nsPresContext::AppUnitsToIntCSSPixels(position.x),
Packit f0b94e
      nsPresContext::AppUnitsToIntCSSPixels(position.y));
Packit f0b94e
  // We can't use AppendPrintf here, because it does locale-specific
Packit f0b94e
  // formatting of floating-point values.
Packit f0b94e
  styleStr.AppendFloat(sWidth / aZoomLevel);
Packit f0b94e
  styleStr.AppendLiteral("px; height: ");
Packit f0b94e
  styleStr.AppendFloat(sHeight / aZoomLevel);
Packit f0b94e
  styleStr.AppendLiteral("px; margin-left: ");
Packit f0b94e
  styleStr.AppendFloat(sMarginLeft / aZoomLevel);
Packit f0b94e
  styleStr.AppendLiteral("px");
Packit f0b94e
Packit f0b94e
  CaretElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr, true);
Packit f0b94e
  AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
Packit f0b94e
Packit f0b94e
  // Set style string for children.
Packit f0b94e
  SetTextOverlayElementStyle(aRect, aZoomLevel);
Packit f0b94e
  SetCaretImageElementStyle(aRect, aZoomLevel);
Packit f0b94e
  SetSelectionBarElementStyle(aRect, aZoomLevel);
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void AccessibleCaret::SetTextOverlayElementStyle(const nsRect& aRect,
Packit f0b94e
                                                 float aZoomLevel) {
Packit f0b94e
  nsAutoString styleStr;
Packit f0b94e
  styleStr.AppendPrintf("height: %dpx;",
Packit f0b94e
                        nsPresContext::AppUnitsToIntCSSPixels(aRect.height));
Packit f0b94e
  TextOverlayElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
Packit f0b94e
                                true);
Packit f0b94e
  AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void AccessibleCaret::SetCaretImageElementStyle(const nsRect& aRect,
Packit f0b94e
                                                float aZoomLevel) {
Packit f0b94e
  nsAutoString styleStr;
Packit f0b94e
  styleStr.AppendPrintf("margin-top: %dpx;",
Packit f0b94e
                        nsPresContext::AppUnitsToIntCSSPixels(aRect.height));
Packit f0b94e
  CaretImageElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
Packit f0b94e
                               true);
Packit f0b94e
  AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void AccessibleCaret::SetSelectionBarElementStyle(const nsRect& aRect,
Packit f0b94e
                                                  float aZoomLevel) {
Packit f0b94e
  nsAutoString styleStr;
Packit f0b94e
  styleStr.AppendPrintf("height: %dpx; width: ",
Packit f0b94e
                        nsPresContext::AppUnitsToIntCSSPixels(aRect.height));
Packit f0b94e
  // We can't use AppendPrintf here, because it does locale-specific
Packit f0b94e
  // formatting of floating-point values.
Packit f0b94e
  styleStr.AppendFloat(sBarWidth / aZoomLevel);
Packit f0b94e
  styleStr.AppendLiteral("px");
Packit f0b94e
Packit f0b94e
  SelectionBarElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
Packit f0b94e
                                 true);
Packit f0b94e
  AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
float AccessibleCaret::GetZoomLevel() {
Packit f0b94e
  // Full zoom on desktop.
Packit f0b94e
  float fullZoom = mPresShell->GetPresContext()->GetFullZoom();
Packit f0b94e
Packit f0b94e
  // Pinch-zoom on fennec.
Packit f0b94e
  float resolution = mPresShell->GetCumulativeResolution();
Packit f0b94e
Packit f0b94e
  return fullZoom * resolution;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
}  // namespace mozilla