File: chrome_browser_application_mac_browsertest.mm

package info (click to toggle)
chromium 138.0.7204.157-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 6,071,864 kB
  • sloc: cpp: 34,936,859; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,967; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (326 lines) | stat: -rw-r--r-- 10,727 bytes parent folder | download | duplicates (3)
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/chrome_browser_application_mac.h"

#import "base/apple/foundation_util.h"
#import "base/apple/scoped_objc_class_swizzler.h"
#import "base/mac/mac_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"

namespace {
BOOL g_voice_over_enabled = NO;
}

@interface NSWorkspace (Extras)
- (BOOL)voiceOverEnabled;
- (void)setVoiceOverEnabled:(BOOL)flag;
@end

@implementation NSWorkspace (Extras)

- (BOOL)voiceOverEnabled {
  return g_voice_over_enabled;
}

// It seems NSWorkspace notifies of changes to voiceOverEnabled via KVO,
// but doesn't implement this method. We add it so we can test our KVO
// monitoring code.
- (void)setVoiceOverEnabled:(BOOL)flag {
  g_voice_over_enabled = flag;
}

@end

@interface NSApplication (ChromeBrowserApplicationMacBrowserTestSwizzle)
@end

@implementation NSApplication (ChromeBrowserApplicationMacBrowserTestSwizzle)

- (void)testObserveValueForKeyPath:(NSString*)keyPath
                          ofObject:(id)object
                            change:
                                (NSDictionary<NSKeyValueChangeKey, id>*)change
                           context:(void*)context {
  if (context) {
    *static_cast<bool*>(context) = true;
  }
}

@end

class ChromeBrowserAppMacBrowserTest : public InProcessBrowserTest {
 public:
  ChromeBrowserAppMacBrowserTest() {
    scoped_feature_list_.InitAndEnableFeature(
        features::kSonomaAccessibilityActivationRefinements);

    br_cr_app_ = base::apple::ObjCCast<BrowserCrApplication>(NSApp);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SetVoiceOverEnabled(VoiceOverEnabledAtStartUp());
  }

  void SetUpOnMainThread() override {
    // Enable platform activation since that is what is begin tested here.
    content::BrowserAccessibilityState::GetInstance()
        ->SetActivationFromPlatformEnabled(
            /*enabled=*/true);
    InProcessBrowserTest::SetUpOnMainThread();
  }

  // Whether or not we simulate VoiceOver active before the test runs.
  virtual BOOL VoiceOverEnabledAtStartUp() { return NO; }

  BOOL VoiceOverEnabled() { return [br_cr_app_ voiceOverStateForTesting]; }

  // Simulates the user activating or deactivating VoiceOver.
  void SetVoiceOverEnabled(BOOL enabled) {
    NSString* kVoiceOverKVOKeyPath = @"voiceOverEnabled";
    [[NSWorkspace sharedWorkspace] setValue:[NSNumber numberWithBool:enabled]
                                     forKey:kVoiceOverKVOKeyPath];
  }

  void WaitThreeSeconds() {
    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&base::RunLoop::Quit, base::Unretained(&run_loop)),
        base::Seconds(3));
    run_loop.Run();
  }

  bool BrowserIsInAccessibilityMode(ui::AXMode mode) {
    content::BrowserAccessibilityState* accessibility_state =
        content::BrowserAccessibilityState::GetInstance();
    return accessibility_state->GetAccessibilityMode() == mode;
  }

  bool BrowserIsInCompleteAccessibilityMode() {
    return BrowserIsInAccessibilityMode(ui::kAXModeComplete |
                                        ui::AXMode::kScreenReader);
  }

  bool BrowserIsInBasicAccessibilityMode() {
    return BrowserIsInAccessibilityMode(ui::kAXModeBasic);
  }

  bool BrowserIsInNativeAPIAccessibilityMode() {
    return BrowserIsInAccessibilityMode(ui::AXMode::kNativeAPIs);
  }

  bool BrowserAccessibilityDisabled() {
    return BrowserIsInAccessibilityMode(ui::AXMode());
  }

  void RequestAppAccessibilityRole() { [br_cr_app_ accessibilityRole]; }

  void EnableEnhancedUserInterface(BOOL enable) {
    // We need to call -accessibilitySetValue:forAttribute: on br_cr_app_, but
    // the compiler complains it's deprecated API. It's right, but it's the API
    // BrowserCrApplication is currently using. Silence the error.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    [br_cr_app_ accessibilitySetValue:[NSNumber numberWithBool:enable]
                         forAttribute:@"AXEnhancedUserInterface"];
#pragma clang diagnostic pop
  }

 private:
  BrowserCrApplication* br_cr_app_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Ensures that overrides to the application's
// observeValueForKeyPath:ofObject:change:context: method call super on
// unrecognized key paths.
IN_PROC_BROWSER_TEST_F(ChromeBrowserAppMacBrowserTest,
                       KVOObservationCallsSuper) {
  base::apple::ScopedObjCClassSwizzler swizzler(
      [NSApplication class],
      @selector(observeValueForKeyPath:ofObject:change:context:),
      @selector(testObserveValueForKeyPath:ofObject:change:context:));

  bool super_was_called = false;

  [NSApp observeValueForKeyPath:@"testKeyPath"
                       ofObject:nil
                         change:nil
                        context:&super_was_called];

  EXPECT_TRUE(super_was_called);
}

// Tests how BrowserCrApplication responds to VoiceOver activations.
IN_PROC_BROWSER_TEST_F(ChromeBrowserAppMacBrowserTest,
                       RespondToVoiceOverStateChanges) {
  // We could perform this check in SetUp(), but in theory, this browser
  // test file will contain more that just these Sonoma tests.
  if (base::mac::MacOSVersion() < 14'00'00) {
    GTEST_SKIP();
  }

  EXPECT_FALSE(VoiceOverEnabled());
  EXPECT_TRUE(BrowserAccessibilityDisabled());

  SetVoiceOverEnabled(YES);
  EXPECT_TRUE(VoiceOverEnabled());
  EXPECT_TRUE(BrowserIsInCompleteAccessibilityMode());

  SetVoiceOverEnabled(NO);
  EXPECT_FALSE(VoiceOverEnabled());

  // Turning VoiceOver off disables accessibility support, but not immediately.
  // Chrome waits a couple seconds in case there's a fast-follow call to enable
  // it. Wait a bit for the change to take before proceeding.
  WaitThreeSeconds();

  EXPECT_TRUE(BrowserAccessibilityDisabled());
}

// Tests how BrowserCrApplication responds to AXEnhancedUserInterface requests
// from Assistive Technology (AT).
IN_PROC_BROWSER_TEST_F(ChromeBrowserAppMacBrowserTest,
                       RespondToAXEnhancedUserInterfaceRequests) {
  if (base::mac::MacOSVersion() < 14'00'00) {
    GTEST_SKIP();
  }

  EXPECT_TRUE(BrowserAccessibilityDisabled());

  // Requesting AX enhanced UI should have no immediate effect.
  EnableEnhancedUserInterface(YES);
  EXPECT_TRUE(BrowserAccessibilityDisabled());

  // If we suddenly turn off support and wait a bit, there should be no change
  // in accessibility support. We're ensuring that sudden on/off changes are
  // ignored.
  EnableEnhancedUserInterface(NO);
  WaitThreeSeconds();
  EXPECT_TRUE(BrowserAccessibilityDisabled());

  // If we turn it on and wait, the code should assume it's not a spurious
  // request.
  EnableEnhancedUserInterface(YES);
  WaitThreeSeconds();
  EXPECT_TRUE(BrowserIsInCompleteAccessibilityMode());

  // Turn it off (and wait a bit).
  EnableEnhancedUserInterface(NO);
  WaitThreeSeconds();
  EXPECT_TRUE(BrowserAccessibilityDisabled());
}

// Tests how BrowserCrApplication responds to mismatched
// AXEnhancedUserInterface requests.
IN_PROC_BROWSER_TEST_F(ChromeBrowserAppMacBrowserTest,
                       HandleMismatchedAXEnhancedUserInterfaceRequests) {
  if (base::mac::MacOSVersion() < 14'00'00) {
    GTEST_SKIP();
  }

  EXPECT_TRUE(BrowserAccessibilityDisabled());

  // The code uses a counter to track requests. Ensure that it can't go
  // negative.
  EnableEnhancedUserInterface(YES);
  EnableEnhancedUserInterface(NO);
  EnableEnhancedUserInterface(NO);
  EnableEnhancedUserInterface(NO);

  WaitThreeSeconds();

  EnableEnhancedUserInterface(YES);
  WaitThreeSeconds();
  EXPECT_TRUE(BrowserIsInCompleteAccessibilityMode());
}

// Tests that BrowserCrApplication ignores requests from ATs to disable AX
// support if VoiceOver is active.
IN_PROC_BROWSER_TEST_F(ChromeBrowserAppMacBrowserTest,
                       IgnoreAXEnhancedUserInterfaceDisableRequests) {
  if (base::mac::MacOSVersion() < 14'00'00) {
    GTEST_SKIP();
  }

  EXPECT_TRUE(BrowserAccessibilityDisabled());

  // Simulate an AT requesting AX enhanced UI.
  EnableEnhancedUserInterface(YES);

  WaitThreeSeconds();

  // The user activates VoiceOver.
  SetVoiceOverEnabled(YES);
  EXPECT_TRUE(BrowserIsInCompleteAccessibilityMode());

  // When the AT is done, make sure it can't disable AX support (VoiceOver is
  // using it).
  EnableEnhancedUserInterface(NO);
  WaitThreeSeconds();
  EXPECT_FALSE(BrowserAccessibilityDisabled());
}

// Tests that accessibility role requests to the application enable native
// accessibility support.
IN_PROC_BROWSER_TEST_F(ChromeBrowserAppMacBrowserTest,
                       RespondToAccessibilityRoleRequests) {
  if (base::mac::MacOSVersion() < 14'00'00) {
    GTEST_SKIP();
  }

  EXPECT_TRUE(BrowserAccessibilityDisabled());

  RequestAppAccessibilityRole();
  EXPECT_TRUE(BrowserIsInNativeAPIAccessibilityMode());

  // The user activates VoiceOver.
  SetVoiceOverEnabled(YES);
  // Requests for AccessibilityRole when VoiceOver is active should not
  // downgrade the AX level.
  RequestAppAccessibilityRole();
  EXPECT_TRUE(BrowserIsInCompleteAccessibilityMode());

  // After VoiceOver is deactivated, the AXMode is returned to its
  // previous value.
  SetVoiceOverEnabled(NO);
  WaitThreeSeconds();
  EXPECT_TRUE(BrowserIsInNativeAPIAccessibilityMode());

  EnableEnhancedUserInterface(YES);
  WaitThreeSeconds();

  // Requests for AccessibilityRole when the AX mode is complete should not
  // downgrade the AX level.
  RequestAppAccessibilityRole();
  EXPECT_TRUE(BrowserIsInCompleteAccessibilityMode());
}

// A test class where VoiceOver is "enabled" when its tests start.
class ChromeBrowserAppMacBrowserMacVoiceOverEnabledTest
    : public ChromeBrowserAppMacBrowserTest {
 public:
  BOOL VoiceOverEnabledAtStartUp() override { return YES; }
};

IN_PROC_BROWSER_TEST_F(ChromeBrowserAppMacBrowserMacVoiceOverEnabledTest,
                       DetectVoiceOverStateOnStartUp) {
  if (base::mac::MacOSVersion() < 14'00'00) {
    GTEST_SKIP();
  }

  content::BrowserAccessibilityState* accessibility_state =
      content::BrowserAccessibilityState::GetInstance();

  // Enable VoiceOver.
  EXPECT_TRUE(VoiceOverEnabled());
  EXPECT_EQ(accessibility_state->GetAccessibilityMode(),
            ui::kAXModeComplete | ui::AXMode::kScreenReader);
}