summaryrefslogtreecommitdiffstats
path: root/accessible/mac/MacUtils.mm
blob: 5534e8fcc8b659ccf56a11d6e2bbdf4e45b5ed2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/* 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 "MacUtils.h"
#include "mozAccessible.h"

#include "LocalAccessible.h"
#include "DocAccessible.h"
#include "DocAccessibleParent.h"
#include "nsCocoaUtils.h"
#include "mozilla/a11y/PDocAccessible.h"

namespace mozilla {
namespace a11y {
namespace utils {

/**
 * Get a localized string from the a11y string bundle.
 * Return nil if not found.
 */
NSString* LocalizedString(const nsString& aString) {
  nsString text;

  Accessible::TranslateString(aString, text);

  return text.IsEmpty() ? nil : nsCocoaUtils::ToNSString(text);
}

NSString* GetAccAttr(mozAccessible* aNativeAccessible, nsAtom* aAttrName) {
  nsAutoString result;
  Accessible* acc = [aNativeAccessible geckoAccessible];
  RefPtr<AccAttributes> attributes = acc->Attributes();

  if (!attributes) {
    return nil;
  }

  attributes->GetAttribute(aAttrName, result);

  if (!result.IsEmpty()) {
    return nsCocoaUtils::ToNSString(result);
  }

  return nil;
}

bool DocumentExists(Accessible* aDoc, uintptr_t aDocPtr) {
  if (reinterpret_cast<uintptr_t>(aDoc) == aDocPtr) {
    return true;
  }

  if (aDoc->IsLocal()) {
    DocAccessible* docAcc = aDoc->AsLocal()->AsDoc();
    uint32_t docCount = docAcc->ChildDocumentCount();
    for (uint32_t i = 0; i < docCount; i++) {
      if (DocumentExists(docAcc->GetChildDocumentAt(i), aDocPtr)) {
        return true;
      }
    }
  } else {
    DocAccessibleParent* docProxy = aDoc->AsRemote()->AsDoc();
    size_t docCount = docProxy->ChildDocCount();
    for (uint32_t i = 0; i < docCount; i++) {
      if (DocumentExists(docProxy->ChildDocAt(i), aDocPtr)) {
        return true;
      }
    }
  }

  return false;
}

static NSColor* ColorFromColor(const Color& aColor) {
  return [NSColor colorWithCalibratedRed:NS_GET_R(aColor.mValue) / 255.0
                                   green:NS_GET_G(aColor.mValue) / 255.0
                                    blue:NS_GET_B(aColor.mValue) / 255.0
                                   alpha:1.0];
}

NSDictionary* StringAttributesFromAccAttributes(AccAttributes* aAttributes,
                                                Accessible* aContainer) {
  if (!aAttributes) {
    if (mozAccessible* mozAcc = GetNativeFromGeckoAccessible(aContainer)) {
      // If we don't have attributes provided this is probably a control like
      // a button or empty entry. Just provide the accessible as an
      // AXAttachment.
      return @{@"AXAttachment" : mozAcc};
    }
    return @{};
  }

  NSMutableDictionary* attrDict =
      [NSMutableDictionary dictionaryWithCapacity:aAttributes->Count()];
  NSMutableDictionary* fontAttrDict = [[NSMutableDictionary alloc] init];
  [attrDict setObject:fontAttrDict forKey:@"AXFont"];
  for (auto iter : *aAttributes) {
    if (iter.Name() == nsGkAtoms::backgroundColor) {
      if (Maybe<Color> value = iter.Value<Color>()) {
        NSColor* color = ColorFromColor(*value);
        [attrDict setObject:(__bridge id)color.CGColor
                     forKey:@"AXBackgroundColor"];
      }
    } else if (iter.Name() == nsGkAtoms::color) {
      if (Maybe<Color> value = iter.Value<Color>()) {
        NSColor* color = ColorFromColor(*value);
        [attrDict setObject:(__bridge id)color.CGColor
                     forKey:@"AXForegroundColor"];
      }
    } else if (iter.Name() == nsGkAtoms::font_size) {
      if (Maybe<FontSize> pointSize = iter.Value<FontSize>()) {
        int32_t fontPixelSize = static_cast<int32_t>(pointSize->mValue * 4 / 3);
        [fontAttrDict setObject:@(fontPixelSize) forKey:@"AXFontSize"];
      }
    } else if (iter.Name() == nsGkAtoms::font_family) {
      nsAutoString fontFamily;
      iter.ValueAsString(fontFamily);
      [fontAttrDict setObject:nsCocoaUtils::ToNSString(fontFamily)
                       forKey:@"AXFontFamily"];
    } else if (iter.Name() == nsGkAtoms::textUnderlineColor) {
      [attrDict setObject:@1 forKey:@"AXUnderline"];
      if (Maybe<Color> value = iter.Value<Color>()) {
        NSColor* color = ColorFromColor(*value);
        [attrDict setObject:(__bridge id)color.CGColor
                     forKey:@"AXUnderlineColor"];
      }
    } else if (iter.Name() == nsGkAtoms::invalid) {
      // XXX: There is currently no attribute for grammar
      if (auto value = iter.Value<RefPtr<nsAtom>>()) {
        if (*value == nsGkAtoms::spelling) {
          [attrDict setObject:@YES
                       forKey:NSAccessibilityMarkedMisspelledTextAttribute];
        }
      }
    } else {
      nsAutoString valueStr;
      iter.ValueAsString(valueStr);
      nsAutoString keyStr;
      iter.NameAsString(keyStr);
      [attrDict setObject:nsCocoaUtils::ToNSString(valueStr)
                   forKey:nsCocoaUtils::ToNSString(keyStr)];
    }
  }

  mozAccessible* container = GetNativeFromGeckoAccessible(aContainer);
  id<MOXAccessible> link =
      [container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
        return [[moxAcc moxRole] isEqualToString:NSAccessibilityLinkRole];
      }];
  if (link) {
    [attrDict setObject:link forKey:@"AXLink"];
  }

  id<MOXAccessible> heading =
      [container moxFindAncestor:^BOOL(id<MOXAccessible> moxAcc, BOOL* stop) {
        return [[moxAcc moxRole] isEqualToString:@"AXHeading"];
      }];
  if (heading) {
    [attrDict setObject:[heading moxValue] forKey:@"AXHeadingLevel"];
  }

  return attrDict;
}
}  // namespace utils
}  // namespace a11y
}  // namespace mozilla