Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  GeckoTextMarker.mm   Sprache: unbekannt

 
/* clang-format off */
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* clang-format on */
/* 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/. */

#import "GeckoTextMarker.h"

#import "MacUtils.h"

#include "AccAttributes.h"
#include "DocAccessible.h"
#include "DocAccessibleParent.h"
#include "nsCocoaUtils.h"
#include "HyperTextAccessible.h"
#include "States.h"
#include "nsAccUtils.h"

namespace mozilla {
namespace a11y {

struct TextMarkerData {
  TextMarkerData(uintptr_t aDoc, uintptr_t aID, int32_t aOffset)
      : mDoc(aDoc), mID(aID), mOffset(aOffset) {}
  TextMarkerData() {}
  uintptr_t mDoc;
  uintptr_t mID;
  int32_t mOffset;
};

// GeckoTextMarker

GeckoTextMarker::GeckoTextMarker(Accessible* aAcc, int32_t aOffset) {
  HyperTextAccessibleBase* ht = aAcc->AsHyperTextBase();
  if (ht && aOffset != nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT &&
      aOffset <= static_cast<int32_t>(ht->CharacterCount())) {
    mPoint = aAcc->AsHyperTextBase()->ToTextLeafPoint(aOffset);
  } else {
    mPoint = TextLeafPoint(aAcc, aOffset);
  }
}

GeckoTextMarker GeckoTextMarker::MarkerFromAXTextMarker(
    Accessible* aDoc, AXTextMarkerRef aTextMarker) {
  MOZ_ASSERT(aDoc);
  if (!aTextMarker) {
    return GeckoTextMarker();
  }

  if (AXTextMarkerGetLength(aTextMarker) != sizeof(TextMarkerData)) {
    MOZ_ASSERT_UNREACHABLE("Malformed AXTextMarkerRef");
    return GeckoTextMarker();
  }

  TextMarkerData markerData;
  memcpy(&markerData, AXTextMarkerGetBytePtr(aTextMarker),
         sizeof(TextMarkerData));

  if (!utils::DocumentExists(aDoc, markerData.mDoc)) {
    return GeckoTextMarker();
  }

  Accessible* doc = reinterpret_cast<Accessible*>(markerData.mDoc);
  MOZ_ASSERT(doc->IsDoc());
  int32_t offset = markerData.mOffset;
  Accessible* acc = nullptr;
  if (doc->IsRemote()) {
    acc = doc->AsRemote()->AsDoc()->GetAccessible(markerData.mID);
  } else {
    acc = doc->AsLocal()->AsDoc()->GetAccessibleByUniqueID(
        reinterpret_cast<void*>(markerData.mID));
  }

  if (!acc) {
    return GeckoTextMarker();
  }

  return GeckoTextMarker(acc, offset);
}

GeckoTextMarker GeckoTextMarker::MarkerFromIndex(Accessible* aRoot,
                                                 int32_t aIndex) {
  TextLeafRange range(
      TextLeafPoint(aRoot, 0),
      TextLeafPoint(aRoot, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT));
  int32_t index = aIndex;
  // Iterate through all segments until we exhausted the index sum
  // so we can find the segment the index lives in.
  for (TextLeafRange segment : range) {
    if (segment.Start().mAcc->IsMenuPopup() &&
        (segment.Start().mAcc->State() & states::COLLAPSED)) {
      // XXX: Menu collapsed XUL menu popups are in our tree and we need to skip
      // them.
      continue;
    }

    if (segment.End().mAcc->Role() == roles::LISTITEM_MARKER) {
      // XXX: MacOS expects bullets to be in the range's text, but not in
      // the calculated length!
      continue;
    }

    index -= segment.End().mOffset - segment.Start().mOffset;
    if (index <= 0) {
      // The index is in the current segment.
      return GeckoTextMarker(segment.Start().mAcc,
                             segment.End().mOffset + index);
    }
  }

  return GeckoTextMarker();
}

AXTextMarkerRef GeckoTextMarker::CreateAXTextMarker() {
  if (!IsValid()) {
    return nil;
  }

  Accessible* doc = nsAccUtils::DocumentFor(mPoint.mAcc);
  TextMarkerData markerData(reinterpret_cast<uintptr_t>(doc), mPoint.mAcc->ID(),
                            mPoint.mOffset);
  AXTextMarkerRef cf_text_marker = AXTextMarkerCreate(
      kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&markerData),
      sizeof(TextMarkerData));

  return (__bridge AXTextMarkerRef)[(__bridge id)(cf_text_marker)autorelease];
}

bool GeckoTextMarker::Next() {
  TextLeafPoint next =
      mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirNext,
                          TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);

  if (next && next != mPoint) {
    mPoint = next;
    return true;
  }

  return false;
}

bool GeckoTextMarker::Previous() {
  TextLeafPoint prev =
      mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
                          TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
  if (prev && mPoint != prev) {
    mPoint = prev;
    return true;
  }

  return false;
}

/**
 * Return true if the given point is inside editable content.
 */
static bool IsPointInEditable(const TextLeafPoint& aPoint) {
  if (aPoint.mAcc) {
    if (aPoint.mAcc->State() & states::EDITABLE) {
      return true;
    }

    Accessible* parent = aPoint.mAcc->Parent();
    if (parent && (parent->State() & states::EDITABLE)) {
      return true;
    }
  }

  return false;
}

GeckoTextMarkerRange GeckoTextMarker::LeftWordRange() const {
  bool includeCurrentInStart = !mPoint.IsParagraphStart(true);
  if (includeCurrentInStart) {
    TextLeafPoint prevChar =
        mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
    if (!prevChar.IsSpace()) {
      includeCurrentInStart = false;
    }
  }

  TextLeafPoint start = mPoint.FindBoundary(
      nsIAccessibleText::BOUNDARY_WORD_START, eDirPrevious,
      includeCurrentInStart
          ? (TextLeafPoint::BoundaryFlags::eIncludeOrigin |
             TextLeafPoint::BoundaryFlags::eStopInEditable |
             TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker)
          : (TextLeafPoint::BoundaryFlags::eStopInEditable |
             TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker));

  TextLeafPoint end;
  if (start == mPoint) {
    end = start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirPrevious,
                             TextLeafPoint::BoundaryFlags::eStopInEditable);
  }

  if (start != mPoint || end == start) {
    end = start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
                             TextLeafPoint::BoundaryFlags::eStopInEditable);
    if (end < mPoint && IsPointInEditable(end) && !IsPointInEditable(mPoint)) {
      start = end;
      end = mPoint;
    }
  }

  return GeckoTextMarkerRange(start < end ? start : end,
                              start < end ? end : start);
}

GeckoTextMarkerRange GeckoTextMarker::RightWordRange() const {
  TextLeafPoint prevChar =
      mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
                          TextLeafPoint::BoundaryFlags::eStopInEditable);

  if (prevChar != mPoint && mPoint.IsParagraphStart(true)) {
    return GeckoTextMarkerRange(mPoint, mPoint);
  }

  TextLeafPoint end =
      mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
                          TextLeafPoint::BoundaryFlags::eStopInEditable);

  if (end == mPoint) {
    // No word to the right of this point.
    return GeckoTextMarkerRange(mPoint, mPoint);
  }

  TextLeafPoint start =
      end.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_START, eDirPrevious,
                       TextLeafPoint::BoundaryFlags::eStopInEditable);

  if (start.FindBoundary(nsIAccessibleText::BOUNDARY_WORD_END, eDirNext,
                         TextLeafPoint::BoundaryFlags::eStopInEditable) <
      mPoint) {
    // Word end is inside of an input to the left of this.
    return GeckoTextMarkerRange(mPoint, mPoint);
  }

  if (mPoint < start) {
    end = start;
    start = mPoint;
  }

  return GeckoTextMarkerRange(start < end ? start : end,
                              start < end ? end : start);
}

GeckoTextMarkerRange GeckoTextMarker::LineRange() const {
  TextLeafPoint start = mPoint.FindBoundary(
      nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
      TextLeafPoint::BoundaryFlags::eStopInEditable |
          TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker |
          TextLeafPoint::BoundaryFlags::eIncludeOrigin);
  // If this is a blank line containing only a line feed, the start boundary
  // is the same as the end boundary. We do not want to walk to the end of the
  // next line.
  TextLeafPoint end =
      start.IsLineFeedChar()
          ? start
          : start.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
                               TextLeafPoint::BoundaryFlags::eStopInEditable);

  return GeckoTextMarkerRange(start, end);
}

GeckoTextMarkerRange GeckoTextMarker::LeftLineRange() const {
  TextLeafPoint start = mPoint.FindBoundary(
      nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
      TextLeafPoint::BoundaryFlags::eStopInEditable |
          TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
  TextLeafPoint end =
      start.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
                         TextLeafPoint::BoundaryFlags::eStopInEditable);

  return GeckoTextMarkerRange(start, end);
}

GeckoTextMarkerRange GeckoTextMarker::RightLineRange() const {
  TextLeafPoint end =
      mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
                          TextLeafPoint::BoundaryFlags::eStopInEditable);
  TextLeafPoint start =
      end.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
                       TextLeafPoint::BoundaryFlags::eStopInEditable);

  return GeckoTextMarkerRange(start, end);
}

GeckoTextMarkerRange GeckoTextMarker::ParagraphRange() const {
  // XXX: WebKit gets trapped in inputs. Maybe we shouldn't?
  TextLeafPoint end =
      mPoint.FindBoundary(nsIAccessibleText::BOUNDARY_PARAGRAPH, eDirNext,
                          TextLeafPoint::BoundaryFlags::eStopInEditable);
  TextLeafPoint start =
      end.FindBoundary(nsIAccessibleText::BOUNDARY_PARAGRAPH, eDirPrevious,
                       TextLeafPoint::BoundaryFlags::eStopInEditable);

  return GeckoTextMarkerRange(start, end);
}

GeckoTextMarkerRange GeckoTextMarker::StyleRange() const {
  if (mPoint.mOffset == 0) {
    // If the marker is on the boundary between two leafs, MacOS expects the
    // previous leaf.
    TextLeafPoint prev = mPoint.FindBoundary(
        nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
        TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
    if (prev != mPoint) {
      return GeckoTextMarker(prev).StyleRange();
    }
  }

  TextLeafPoint start(mPoint.mAcc, 0);
  TextLeafPoint end(mPoint.mAcc, nsAccUtils::TextLength(mPoint.mAcc));
  return GeckoTextMarkerRange(start, end);
}

Accessible* GeckoTextMarker::Leaf() {
  MOZ_ASSERT(mPoint.mAcc);
  Accessible* acc = mPoint.mAcc;
  if (mPoint.mOffset == 0) {
    // If the marker is on the boundary between two leafs, MacOS expects the
    // previous leaf.
    TextLeafPoint prev = mPoint.FindBoundary(
        nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious,
        TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker);
    acc = prev.mAcc;
  }

  Accessible* parent = acc->Parent();
  return parent && nsAccUtils::MustPrune(parent) ? parent : acc;
}

// GeckoTextMarkerRange

GeckoTextMarkerRange::GeckoTextMarkerRange(Accessible* aAccessible) {
  mRange = TextLeafRange(
      TextLeafPoint(aAccessible, 0),
      TextLeafPoint(aAccessible, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT));
}

GeckoTextMarkerRange GeckoTextMarkerRange::MarkerRangeFromAXTextMarkerRange(
    Accessible* aDoc, AXTextMarkerRangeRef aTextMarkerRange) {
  if (!aTextMarkerRange ||
      CFGetTypeID(aTextMarkerRange) != AXTextMarkerRangeGetTypeID()) {
    return GeckoTextMarkerRange();
  }

  AXTextMarkerRef start_marker(
      AXTextMarkerRangeCopyStartMarker(aTextMarkerRange));
  AXTextMarkerRef end_marker(AXTextMarkerRangeCopyEndMarker(aTextMarkerRange));

  GeckoTextMarker start =
      GeckoTextMarker::MarkerFromAXTextMarker(aDoc, start_marker);
  GeckoTextMarker end =
      GeckoTextMarker::MarkerFromAXTextMarker(aDoc, end_marker);

  CFRelease(start_marker);
  CFRelease(end_marker);

  return GeckoTextMarkerRange(start, end);
}

AXTextMarkerRangeRef GeckoTextMarkerRange::CreateAXTextMarkerRange() {
  if (!IsValid()) {
    return nil;
  }

  GeckoTextMarker start = GeckoTextMarker(mRange.Start());
  GeckoTextMarker end = GeckoTextMarker(mRange.End());

  AXTextMarkerRangeRef cf_text_marker_range =
      AXTextMarkerRangeCreate(kCFAllocatorDefault, start.CreateAXTextMarker(),
                              end.CreateAXTextMarker());

  return (__bridge AXTextMarkerRangeRef)[(__bridge id)(
      cf_text_marker_range)autorelease];
}

NSString* GeckoTextMarkerRange::Text() const {
  if (mRange.Start() == mRange.End()) {
    return @"";
  }

  if ((mRange.Start().mAcc == mRange.End().mAcc) &&
      (mRange.Start().mAcc->ChildCount() == 0) &&
      (mRange.Start().mAcc->State() & states::EDITABLE)) {
    return @"";
  }

  nsAutoString text;
  TextLeafPoint prev = mRange.Start().FindBoundary(
      nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
  TextLeafRange range =
      prev != mRange.Start() && prev.mAcc->Role() == roles::LISTITEM_MARKER
          ? TextLeafRange(TextLeafPoint(prev.mAcc, 0), mRange.End())
          : mRange;

  for (TextLeafRange segment : range) {
    TextLeafPoint start = segment.Start();
    if (start.mAcc->IsMenuPopup() &&
        (start.mAcc->State() & states::COLLAPSED)) {
      // XXX: Menu collapsed XUL menu popups are in our tree and we need to skip
      // them.
      continue;
    }
    if (start.mAcc->IsTextField() && start.mAcc->ChildCount() == 0) {
      continue;
    }

    start.mAcc->AppendTextTo(text, start.mOffset,
                             segment.End().mOffset - start.mOffset);
  }

  return nsCocoaUtils::ToNSString(text);
}

static void AppendTextToAttributedString(
    NSMutableAttributedString* aAttributedString, Accessible* aAccessible,
    const nsString& aString, AccAttributes* aAttributes) {
  NSAttributedString* substr = [[[NSAttributedString alloc]
      initWithString:nsCocoaUtils::ToNSString(aString)
          attributes:utils::StringAttributesFromAccAttributes(
                         aAttributes, aAccessible)] autorelease];

  [aAttributedString appendAttributedString:substr];
}

static RefPtr<AccAttributes> GetTextAttributes(TextLeafPoint aPoint) {
  RefPtr<AccAttributes> attrs = aPoint.GetTextAttributes();
  // Mac expects some object properties to be exposed as text attributes. We
  // add these here rather than in utils::StringAttributesFromAccAttributes so
  // we can use AccAttributes::Equal to determine whether we need to start a new
  // run, rather than needing additional special case comparisons.
  for (Accessible* ancestor = aPoint.mAcc->Parent();
       ancestor && !ancestor->IsDoc(); ancestor = ancestor->Parent()) {
    if (ancestor->Role() == roles::MARK) {
      attrs->SetAttribute(nsGkAtoms::mark, true);
    }
  }
  return attrs;
}

NSAttributedString* GeckoTextMarkerRange::AttributedText() const {
  NSMutableAttributedString* str =
      [[[NSMutableAttributedString alloc] init] autorelease];

  if (mRange.Start() == mRange.End()) {
    return str;
  }

  if ((mRange.Start().mAcc == mRange.End().mAcc) &&
      (mRange.Start().mAcc->ChildCount() == 0) &&
      (mRange.Start().mAcc->IsTextField())) {
    return str;
  }

  TextLeafPoint prev = mRange.Start().FindBoundary(
      nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious);
  TextLeafRange range =
      prev != mRange.Start() && prev.mAcc->Role() == roles::LISTITEM_MARKER
          ? TextLeafRange(TextLeafPoint(prev.mAcc, 0), mRange.End())
          : mRange;

  nsAutoString text;
  RefPtr<AccAttributes> currentRun = GetTextAttributes(range.Start());
  Accessible* runAcc = range.Start().mAcc;
  for (TextLeafRange segment : range) {
    TextLeafPoint start = segment.Start();
    TextLeafPoint attributesNext;
    if (start.mAcc->IsMenuPopup() &&
        (start.mAcc->State() & states::COLLAPSED)) {
      // XXX: Menu collapsed XUL menu popups are in our tree and we need to skip
      // them.
      continue;
    }
    do {
      if (start.mAcc->IsText()) {
        attributesNext = start.FindTextAttrsStart(eDirNext, false);
      } else {
        // If this segment isn't a text leaf, but another kind of inline element
        // like a control, just consider this full segment one "attributes run".
        attributesNext = segment.End();
      }
      if (attributesNext == start) {
        // XXX: FindTextAttrsStart should not return the same point.
        break;
      }
      RefPtr<AccAttributes> attributes = GetTextAttributes(start);
      if (!currentRun || !attributes || !attributes->Equal(currentRun)) {
        // If currentRun is null this is a non-text control and we will
        // append a run with no text or attributes, just an AXAttachment
        // referencing this accessible.
        AppendTextToAttributedString(str, runAcc, text, currentRun);
        text.Truncate();
        currentRun = attributes;
        runAcc = start.mAcc;
      }
      TextLeafPoint end =
          attributesNext < segment.End() ? attributesNext : segment.End();
      start.mAcc->AppendTextTo(text, start.mOffset,
                               end.mOffset - start.mOffset);
      start = attributesNext;

    } while (attributesNext < segment.End());
  }

  if (!text.IsEmpty()) {
    AppendTextToAttributedString(str, runAcc, text, currentRun);
  }

  return str;
}

int32_t GeckoTextMarkerRange::Length() const {
  int32_t length = 0;
  for (TextLeafRange segment : mRange) {
    if (segment.End().mAcc->Role() == roles::LISTITEM_MARKER) {
      // XXX: MacOS expects bullets to be in the range's text, but not in
      // the calculated length!
      continue;
    }
    length += segment.End().mOffset - segment.Start().mOffset;
  }

  return length;
}

NSValue* GeckoTextMarkerRange::Bounds() const {
  LayoutDeviceIntRect rect = mRange ? mRange.Bounds() : LayoutDeviceIntRect();

  NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
  CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
  NSRect r =
      NSMakeRect(static_cast<CGFloat>(rect.x) / scaleFactor,
                 [mainView frame].size.height -
                     static_cast<CGFloat>(rect.y + rect.height) / scaleFactor,
                 static_cast<CGFloat>(rect.width) / scaleFactor,
                 static_cast<CGFloat>(rect.height) / scaleFactor);

  return [NSValue valueWithRect:r];
}

void GeckoTextMarkerRange::Select() const { mRange.SetSelection(0); }

}  // namespace a11y
}  // namespace mozilla

[ Dauer der Verarbeitung: 0.14 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge