File: conditional_focus_interactive_uitest.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (393 lines) | stat: -rw-r--r-- 15,553 bytes parent folder | download | duplicates (6)
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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string>

#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "third_party/blink/public/common/switches.h"
#include "ui/gl/gl_switches.h"

namespace {

using content::WebContents;

// Capturing page.
const char kCapturingPage[] = "/webrtc/conditional_focus_capturing_page.html";
// Captured page.
const char kCapturedPage[] = "/webrtc/conditional_focus_captured_page.html";

// The tab to be captured is detected using this string, which is hard-coded
// in the HTML file.
const char kCapturedPageTitle[] = "Conditional Focus Test - Captured Page";

enum class FocusEnumValue {
  kNoValue,                    // ""
  kFocusCapturingApplication,  // "focus-capturing-application"
  kFocusCapturedSurface,       // "focus-captured-surface"
  kNoFocusChange               // "no-focus-change"
};

const char* ToString(FocusEnumValue focus_enum_value) {
  switch (focus_enum_value) {
    case FocusEnumValue::kNoValue:
      return "";
    case FocusEnumValue::kFocusCapturingApplication:
      return "focus-capturing-application";
    case FocusEnumValue::kFocusCapturedSurface:
      return "focus-captured-surface";
    case FocusEnumValue::kNoFocusChange:
      return "no-focus-change";
  }
  NOTREACHED();
}

enum class Tab { kUnknownTab, kCapturingTab, kCapturedTab };

}  // namespace

// Essentially depends on InProcessBrowserTest, but WebRtcTestBase provides
// detection of JS errors.
class ConditionalFocusInteractiveUiTest : public WebRtcTestBase {
 public:
  void SetUpInProcessBrowserTestFixture() override {
    WebRtcTestBase::SetUpInProcessBrowserTestFixture();
    DetectErrorsInJavaScript();
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  void SetUpOnMainThread() override {
    WebRtcTestBase::SetUpOnMainThread();

    base::RunLoop run_loop;
    content::GetIOThreadTaskRunner({})->PostTaskAndReply(
        FROM_HERE, base::BindOnce([]() {
          content::SetConditionalFocusWindowForTesting(base::Seconds(5));
        }),
        run_loop.QuitClosure());
    run_loop.Run();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
    command_line->AppendSwitchASCII(
        switches::kAutoSelectTabCaptureSourceByTitle, kCapturedPageTitle);
    // MSan and GL do not get along so avoid using the GPU with MSan.
    // TODO(crbug.com/40260482): Remove this after fixing feature
    // detection in 0c tab capture path as it'll no longer be needed.
#if !BUILDFLAG(IS_CHROMEOS) && !defined(MEMORY_SANITIZER)
    command_line->AppendSwitch(switches::kUseGpuInTests);
#endif
  }

  WebContents* OpenTestPageInNewTab(const std::string& test_url) {
    chrome::AddTabAt(browser(), GURL(url::kAboutBlankURL), -1, true);
    GURL url = embedded_test_server()->GetURL(test_url);
    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  void SetUpTestTabs() {
    captured_tab_ = OpenTestPageInNewTab(kCapturedPage);
    capturing_tab_ = OpenTestPageInNewTab(kCapturingPage);

    TabStripModel* const tab_strip_model = browser()->tab_strip_model();

    EXPECT_EQ(tab_strip_model->GetWebContentsAt(1), captured_tab_);
    EXPECT_EQ(tab_strip_model->GetWebContentsAt(2), capturing_tab_);
    EXPECT_EQ(tab_strip_model->GetActiveWebContents(), capturing_tab_);
  }

  Tab ActiveTab() const {
    WebContents* const active_web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    return active_web_contents == capturing_tab_  ? Tab::kCapturingTab
           : active_web_contents == captured_tab_ ? Tab::kCapturedTab
                                                  : Tab::kUnknownTab;
  }

  // Performs the following actions, in order:
  // 1. Calls navigator.mediaDevices.getDisplayMedia(). The test fixture
  //    is set to simulate the user selecting |captured_tab_|. Expects success.
  // 2. getDisplayMedia() returns a Promise<MediaStream> which resolves in
  //    a microtask. We hold up the main thread for |busy_wait_ms| so as to
  //    simulate either (a) an application which performs some non-trivial
  //    computation on that task, (b) intentional delay by the app or
  //    (c) random CPU delays.
  // 3. Avoids calling setFocusBehavior() or does so with the appropriate
  //    value, depending on |focus_enum_value|.
  // If !on_correct_microtask, calling setFocusBehavior() is done
  // from a task that is scheduled to be executed later.
  void Capture(int busy_wait_ms,
               FocusEnumValue focus_enum_value,
               bool on_correct_microtask = true,
               const std::string& expected_result = "capture-success") {
    std::string script_result;
    EXPECT_EQ(content::EvalJs(
                  capturing_tab_->GetPrimaryMainFrame(),
                  base::StringPrintf("captureOtherTab(%d, '%s', %s);",
                                     busy_wait_ms, ToString(focus_enum_value),
                                     base::ToString(on_correct_microtask))),
              expected_result);
  }

  void Wait(base::TimeDelta timeout) {
    base::RunLoop run_loop;
    base::OneShotTimer timer;
    timer.Start(FROM_HERE, timeout, run_loop.QuitClosure());
    run_loop.Run();
    timer.Stop();
  }

  // Repeatedly polls until a focus-switch to the captured tab is detected.
  // If a switch is detected within 10s, returns true; otherwise, returns false.
  bool WaitForFocusSwitchToCapturedTab() {
    for (int i = 0; i < 20; ++i) {
      if (ActiveTab() == Tab::kCapturedTab) {
        return true;
      }
      Wait(base::Milliseconds(500));
    }
    return (ActiveTab() == Tab::kCapturedTab);
  }

  void CallFocusAndExpectError(const std::string& expected_error) {
    EXPECT_EQ(content::EvalJs(capturing_tab_->GetPrimaryMainFrame(),
                              "callFocusAndExpectError();"),
              expected_error);
  }

  void CallSetFocusBehaviorBeforeCapture(
      FocusEnumValue focus_enum_value_before_capture,
      FocusEnumValue focus_enum_value_after_capture = FocusEnumValue::kNoValue,
      const std::string& expected_result = "capture-success") {
    EXPECT_EQ(
        content::EvalJs(
            capturing_tab_->GetPrimaryMainFrame(),
            base::StringPrintf("callSetFocusBehaviorBeforeCapture('%s', '%s');",
                               ToString(focus_enum_value_before_capture),
                               ToString(focus_enum_value_after_capture))),
        expected_result);
  }

 protected:
  raw_ptr<WebContents, AcrossTasksDanglingUntriaged> captured_tab_ = nullptr;
  raw_ptr<WebContents, AcrossTasksDanglingUntriaged> capturing_tab_ = nullptr;
};

// Flaky on Win bots and on linux release bots http://crbug.com/1264744
#if BUILDFLAG(IS_WIN) || (BUILDFLAG(IS_LINUX) && defined(NDEBUG))
#define MAYBE_CapturedTabFocusedIfNoExplicitCallToFocus \
  DISABLED_CapturedTabFocusedIfNoExplicitCallToFocus
#else
#define MAYBE_CapturedTabFocusedIfNoExplicitCallToFocus \
  CapturedTabFocusedIfNoExplicitCallToFocus
#endif
IN_PROC_BROWSER_TEST_F(ConditionalFocusInteractiveUiTest,
                       MAYBE_CapturedTabFocusedIfNoExplicitCallToFocus) {
  SetUpTestTabs();
  Capture(0, FocusEnumValue::kNoValue);
  EXPECT_TRUE(WaitForFocusSwitchToCapturedTab());
}

// Flaky on Win bots and on linux release bots http://crbug.com/1264744
#if BUILDFLAG(IS_WIN) || (BUILDFLAG(IS_LINUX) && defined(NDEBUG))
#define MAYBE_CapturedTabFocusedIfExplicitlyCallingFocus \
  DISABLED_CapturedTabFocusedIfExplicitlyCallingFocus
#else
#define MAYBE_CapturedTabFocusedIfExplicitlyCallingFocus \
  CapturedTabFocusedIfExplicitlyCallingFocus
#endif
IN_PROC_BROWSER_TEST_F(ConditionalFocusInteractiveUiTest,
                       MAYBE_CapturedTabFocusedIfExplicitlyCallingFocus) {
  SetUpTestTabs();
  Capture(0, FocusEnumValue::kFocusCapturedSurface);
  EXPECT_TRUE(WaitForFocusSwitchToCapturedTab());
}

// This class only uses the values of FocusEnumValue that lead to the capturing
// application keeping focus.
class ConditionalFocusInteractiveUiTestWithFocusCapturingApplication
    : public ConditionalFocusInteractiveUiTest,
      public testing::WithParamInterface<FocusEnumValue> {
 public:
  ConditionalFocusInteractiveUiTestWithFocusCapturingApplication()
      : focus_behavior_(GetParam()) {}

  ~ConditionalFocusInteractiveUiTestWithFocusCapturingApplication() override =
      default;

 protected:
  const FocusEnumValue focus_behavior_;
};

INSTANTIATE_TEST_SUITE_P(
    _,
    ConditionalFocusInteractiveUiTestWithFocusCapturingApplication,
    testing::Values(FocusEnumValue::kFocusCapturingApplication,
                    FocusEnumValue::kNoFocusChange));

// TODO(crbug.com/40913269): Flaky on a TSan bot.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_CapturedTabNotFocusedIfExplicitlyCallingNoFocus \
  DISABLED_CapturedTabNotFocusedIfExplicitlyCallingNoFocus
#else
#define MAYBE_CapturedTabNotFocusedIfExplicitlyCallingNoFocus \
  CapturedTabNotFocusedIfExplicitlyCallingNoFocus
#endif
IN_PROC_BROWSER_TEST_P(
    ConditionalFocusInteractiveUiTestWithFocusCapturingApplication,
    MAYBE_CapturedTabNotFocusedIfExplicitlyCallingNoFocus) {
  SetUpTestTabs();
  Capture(0, focus_behavior_);
  // Whereas calls to Wait() in previous tests served to minimize flakiness,
  // this one is to prove no false-positives. Namely, we allow enough time
  // for the focus-change, yet it does not occur.
  Wait(base::Seconds(10));
  EXPECT_EQ(ActiveTab(), Tab::kCapturingTab);
}

// TODO(crbug.com/40913269): Flaky on a TSan bot.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_CapturedTabFocusedIfAppWaitsTooLongBeforeCallingFocus \
  DISABLED_CapturedTabFocusedIfAppWaitsTooLongBeforeCallingFocus
#else
#define MAYBE_CapturedTabFocusedIfAppWaitsTooLongBeforeCallingFocus \
  CapturedTabFocusedIfAppWaitsTooLongBeforeCallingFocus
#endif
IN_PROC_BROWSER_TEST_P(
    ConditionalFocusInteractiveUiTestWithFocusCapturingApplication,
    MAYBE_CapturedTabFocusedIfAppWaitsTooLongBeforeCallingFocus) {
  SetUpTestTabs();
  Capture(15000, focus_behavior_);
  EXPECT_TRUE(WaitForFocusSwitchToCapturedTab());
}

// This ensures that we don't have to wait for the entire duration of the
// conditional focus window before focus occurs. Rather, that is just the
// hard-limit that is employed in case the application attempts abuse by
// blocking the main thread for too long.
IN_PROC_BROWSER_TEST_F(ConditionalFocusInteractiveUiTest,
                       FocusTriggeredByMicrotask) {
  SetUpTestTabs();
  Capture(0, FocusEnumValue::kNoValue);
  // Recall that the test fixture set the conditional focus window to 5s.
  Wait(base::Seconds(2));
  // Focus-change occurs before 5s elapse.
  EXPECT_EQ(ActiveTab(), Tab::kCapturedTab);
}

IN_PROC_BROWSER_TEST_F(ConditionalFocusInteractiveUiTest,
                       UserFocusChangeSuppressesFocusDecision) {
  SetUpTestTabs();

  // Recall that tab 0 is neither the capturing nor captured tab,
  // and it is initially inactive.
  ASSERT_NE(browser()->tab_strip_model()->GetWebContentsAt(0), captured_tab_);
  ASSERT_NE(browser()->tab_strip_model()->GetWebContentsAt(0), capturing_tab_);

  // Start capturing, but give some time for a manual focus-switch by the user.
  Capture(2500, FocusEnumValue::kFocusCapturedSurface);

  // Simulated manual user change of active tab.
  browser()->tab_strip_model()->ActivateTabAt(0);

  // No additional focus-change - user activity has suppressed that.
  Wait(base::Milliseconds(7500));
  EXPECT_EQ(browser()->tab_strip_model()->GetActiveWebContents(),
            browser()->tab_strip_model()->GetWebContentsAt(0));
}

IN_PROC_BROWSER_TEST_F(ConditionalFocusInteractiveUiTest,
                       ExceptionRaisedIfFocusCalledAfterMicrotaskExecutes) {
  // Setup.
  SetUpTestTabs();
  Capture(0, FocusEnumValue::kFocusCapturedSurface,
          /*on_correct_microtask=*/false,
          /*expected_result=*/
          "InvalidStateError: Failed to execute 'setFocusBehavior' on "
          "'CaptureController': The window of opportunity for focus-decision "
          "is closed.");
}

IN_PROC_BROWSER_TEST_F(ConditionalFocusInteractiveUiTest, FocusBeforeCapture) {
  // Setup.
  SetUpTestTabs();
  CallSetFocusBehaviorBeforeCapture(FocusEnumValue::kFocusCapturedSurface);
  EXPECT_TRUE(WaitForFocusSwitchToCapturedTab());
}

// TODO(crbug.com/40913269): Flaky on a TSan bot.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_NoFocusBeforeCapture DISABLED_NoFocusBeforeCapture
#else
#define MAYBE_NoFocusBeforeCapture NoFocusBeforeCapture
#endif
IN_PROC_BROWSER_TEST_P(
    ConditionalFocusInteractiveUiTestWithFocusCapturingApplication,
    MAYBE_NoFocusBeforeCapture) {
  // Setup.
  SetUpTestTabs();
  CallSetFocusBehaviorBeforeCapture(focus_behavior_);
  // Whereas calls to Wait() in previous tests served to minimize flakiness,
  // this one is to prove no false-positives. Namely, we allow enough time
  // for the focus-change, yet it does not occur.
  Wait(base::Seconds(10));
  EXPECT_EQ(ActiveTab(), Tab::kCapturingTab);
}

// TODO(crbug.com/40913269): Flaky on a TSan bot.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_NoFocusAfterCaptureOverrideFocusBeforeCapture \
  DISABLED_NoFocusAfterCaptureOverrideFocusBeforeCapture
#else
#define MAYBE_NoFocusAfterCaptureOverrideFocusBeforeCapture \
  NoFocusAfterCaptureOverrideFocusBeforeCapture
#endif
IN_PROC_BROWSER_TEST_P(
    ConditionalFocusInteractiveUiTestWithFocusCapturingApplication,
    MAYBE_NoFocusAfterCaptureOverrideFocusBeforeCapture) {
  // Setup.
  SetUpTestTabs();
  CallSetFocusBehaviorBeforeCapture(FocusEnumValue::kFocusCapturedSurface,
                                    focus_behavior_);
  // Whereas calls to Wait() in previous tests served to minimize flakiness,
  // this one is to prove no false-positives. Namely, we allow enough time
  // for the focus-change, yet it does not occur.
  Wait(base::Seconds(10));
  EXPECT_EQ(ActiveTab(), Tab::kCapturingTab);
}

// TODO(crbug.com/40913269): Flaky on a TSan bot.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_FocusAfterCaptureOverrideNoFocusBeforeCapture \
  DISABLED_FocusAfterCaptureOverrideNoFocusBeforeCapture
#else
#define MAYBE_FocusAfterCaptureOverrideNoFocusBeforeCapture \
  FocusAfterCaptureOverrideNoFocusBeforeCapture
#endif
IN_PROC_BROWSER_TEST_P(
    ConditionalFocusInteractiveUiTestWithFocusCapturingApplication,
    MAYBE_FocusAfterCaptureOverrideNoFocusBeforeCapture) {
  // Setup.
  SetUpTestTabs();
  CallSetFocusBehaviorBeforeCapture(focus_behavior_,
                                    FocusEnumValue::kFocusCapturedSurface);
  EXPECT_TRUE(WaitForFocusSwitchToCapturedTab());
}