/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=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 "UiaTextRange.h" #include "mozilla/a11y/HyperTextAccessibleBase.h" #include "nsAccUtils.h" #include "nsIAccessibleTypes.h" #include "TextLeafRange.h" #include #include #include namespace mozilla::a11y { template HRESULT GetAttribute(TEXTATTRIBUTEID aAttributeId, T const& aRangeOrPoint, VARIANT& aRetVal); static int CompareVariants(const VARIANT& aFirst, const VARIANT& aSecond) { // MinGW lacks support for VariantCompare, but does support converting to // PROPVARIANT and PropVariantCompareEx. Use this as a workaround for MinGW // builds, but avoid the extra work otherwise. See Bug 1944732. #if defined(__MINGW32__) || defined(__MINGW64__) || defined(__MINGW__) PROPVARIANT firstPropVar; PROPVARIANT secondPropVar; VariantToPropVariant(&aFirst, &firstPropVar); VariantToPropVariant(&aSecond, &secondPropVar); return PropVariantCompareEx(firstPropVar, secondPropVar, PVCU_DEFAULT, PVCHF_DEFAULT); #else return VariantCompare(aFirst, aSecond); #endif } // Used internally to safely get a UiaTextRange from a COM pointer provided // to us by a client. // {74B8E664-4578-4B52-9CBC-30A7A8271AE8} static const GUID IID_UiaTextRange = { 0x74b8e664, 0x4578, 0x4b52, {0x9c, 0xbc, 0x30, 0xa7, 0xa8, 0x27, 0x1a, 0xe8}}; // Helpers static TextLeafPoint GetEndpoint(TextLeafRange& aRange, enum TextPatternRangeEndpoint aEndpoint) { if (aEndpoint == TextPatternRangeEndpoint_Start) { return aRange.Start(); } return aRange.End(); } static void RemoveExcludedAccessiblesFromRange(TextLeafRange& aRange) { MOZ_ASSERT(aRange); TextLeafPoint start = aRange.Start(); TextLeafPoint end = aRange.End(); if (start == end) { // The range is collapsed. It doesn't include anything. return; } if (end.mOffset != 0) { // It is theoretically possible for start to be at the exclusive end of a // previous Accessible (i.e. mOffset is its length), so the range doesn't // really encompass that Accessible's text and we should thus exclude that // Accessible. However, that hasn't been seen in practice yet. If it does // occur and cause problems, we should adjust the start point here. return; } // end is at the start of its Accessible. This can happen because we always // search for the start of a character, word, etc. Since the end of a range // is exclusive, the range doesn't include anything in this Accessible. // Move the end back so that it doesn't touch this Accessible at all. This // is important when determining what Accessibles lie within this range // because otherwise, we'd incorrectly consider an Accessible which the range // doesn't actually cover. // Move to the previous character. end = end.FindBoundary(nsIAccessibleText::BOUNDARY_CHAR, eDirPrevious); // We want the position immediately after this character in the same // Accessible. ++end.mOffset; if (start <= end) { aRange.SetEnd(end); } } static bool IsUiaEmbeddedObject(const Accessible* aAcc) { // "For UI Automation, an embedded object is any element that has non-textual // boundaries such as an image, hyperlink, table, or document type" // https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-textpattern-and-embedded-objects-overview if (aAcc->IsText()) { return false; } switch (aAcc->Role()) { case roles::CONTENT_DELETION: case roles::CONTENT_INSERTION: case roles::EMPHASIS: case roles::LANDMARK: case roles::MARK: case roles::NAVIGATION: case roles::NOTE: case roles::PARAGRAPH: case roles::REGION: case roles::SECTION: case roles::STRONG: case roles::SUBSCRIPT: case roles::SUPERSCRIPT: case roles::TEXT: case roles::TEXT_CONTAINER: return false; default: break; } return true; } static NotNull GetSelectionContainer(TextLeafRange& aRange) { Accessible* acc = aRange.Start().mAcc; MOZ_ASSERT(acc); if (acc->IsTextLeaf()) { if (Accessible* parent = acc->Parent()) { acc = parent; } } if (acc->IsTextField()) { // Gecko uses an independent selection for and