File: developer_tools_policy_browsertest.cc

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; 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 (409 lines) | stat: -rw-r--r-- 18,180 bytes parent folder | download | duplicates (4)
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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/files/file_path.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/devtools/devtools_window_testing.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/scoped_test_mv2_enabler.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api/messaging/messaging_delegate.h"
#include "extensions/common/extension.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"

using extensions::mojom::ManifestLocation;

namespace policy {

namespace {

// Utility for waiting until the dev-mode controls are visible/hidden
// Uses a MutationObserver on the attributes of the DOM element.
void WaitForExtensionsDevModeControlsVisibility(
    content::WebContents* contents,
    const char* dev_controls_accessor_js,
    const char* dev_controls_visibility_check_js,
    bool expected_visible) {
  ASSERT_TRUE(content::ExecJs(
      contents,
      base::StringPrintf(
          "var screenElement = %s;"
          "new Promise(resolve => {"
          "  function SendReplyIfAsExpected() {"
          "    var is_visible = %s;"
          "    if (is_visible != %s)"
          "      return false;"
          "    observer.disconnect();"
          "    resolve(true);"
          "    return true;"
          "  }"
          "  var observer = new MutationObserver(SendReplyIfAsExpected);"
          "  if (!SendReplyIfAsExpected()) {"
          "    var options = { 'attributes': true };"
          "    observer.observe(screenElement, options);"
          "  }"
          "});",
          dev_controls_accessor_js, dev_controls_visibility_check_js,
          base::ToString(expected_visible))));
}

// Utility to get a PolicyMap for setting the DeveloperToolsAvailability policy
// to a given value.
PolicyMap MakeDeveloperToolsAvailabilityMap(int value) {
  PolicyMap policies;
  policies.Set(key::kDeveloperToolsAvailability, POLICY_LEVEL_MANDATORY,
               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(value),
               nullptr);
  return policies;
}

// Navigates the current tab of the browser to the given URL without any
// waiting after the navigation is triggered. Note: the
// ui_test_utils::BROWSER_TEST_NO_WAIT flag passed in results this returning
// right after the Browser::OpenURL() call without waiting for any load
// events.
void NavigateToURLNoWait(Browser* browser, const GURL& url) {
  ui_test_utils::NavigateToURLWithDisposition(
      browser, url, WindowOpenDisposition::CURRENT_TAB,
      ui_test_utils::BROWSER_TEST_NO_WAIT);
}

// Utility to navigate the current tab of the browser to the specified page and
// then kill it using chrome://kill, verifying that the page ends up crashed.
void VerifyPageAllowsKill(Browser* browser, const GURL& url) {
  SCOPED_TRACE(base::StringPrintf("Verifying url allows kill: '%s'",
                                  url.spec().c_str()));
  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser, url));
  {
    content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
    content::RenderProcessHostWatcher exit_observer(
        browser->tab_strip_model()
            ->GetActiveWebContents()
            ->GetPrimaryMainFrame()
            ->GetProcess(),
        content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser, GURL(blink::kChromeUIKillURL)));
    exit_observer.Wait();
    // The kill url will have left a hanging pending entry on the
    // NavigationController and the contents will be marked as having crashed.
    content::WebContents* web_contents =
        browser->tab_strip_model()->GetActiveWebContents();
    EXPECT_TRUE(web_contents->GetController().GetPendingEntry());
    EXPECT_TRUE(web_contents->IsCrashed());
    EXPECT_FALSE(exit_observer.did_exit_normally());
  }
}

// Utility to navigate the current tab of the browser to the specified page and
// then attempt to kill it using chrome://kill, verifying that the kill is
// blocked before any navigation is started.
void VerifyPageBlocksKill(Browser* browser, const GURL& url) {
  SCOPED_TRACE(base::StringPrintf("Verifying url blocks kill: '%s'",
                                  url.spec().c_str()));
  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser, url));
  // We expect the debug URL to be blocked synchronously, so we don't have to
  // wait for a load stop here. Afterwards we verify nothing has happened by
  // checking there is no pending entry on the NavigationController (indicating
  // no navigation was started) and that the contents has not crashed.
  NavigateToURLNoWait(browser, GURL(blink::kChromeUIKillURL));
  content::WebContents* web_contents =
      browser->tab_strip_model()->GetActiveWebContents();
  EXPECT_FALSE(web_contents->GetController().GetPendingEntry());
  EXPECT_FALSE(web_contents->IsCrashed());
}

// Utility to navigate the current tab of the browser to the specified page and
// return true if a javascript URL can be run on it, false otherwise.
bool PageAllowsJavascriptURL(Browser* browser, const GURL& url) {
  SCOPED_TRACE(base::StringPrintf("Checking url allows javascript URLs: '%s'",
                                  url.spec().c_str()));
  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser, url));

  content::WebContents* web_contents =
      browser->tab_strip_model()->GetActiveWebContents();
  const std::u16string original_title = web_contents->GetTitle();

  const GURL javascript_url("javascript:void(document.title='Modified Title')");
  NavigateToURLNoWait(browser, javascript_url);
  // Run another script we can wait on to ensure the javascript URL will have
  // processed if it was going to.
  EXPECT_EQ(true, content::EvalJs(web_contents, "true"));

  // Check if the title was changed and return true if so.
  if (web_contents->GetTitle() != original_title) {
    EXPECT_EQ(u"Modified Title", web_contents->GetTitle());
    return true;
  }
  EXPECT_NE(u"Modified Title", web_contents->GetTitle());
  return false;
}

}  // namespace

IN_PROC_BROWSER_TEST_F(PolicyTest, DeveloperToolsDisabledByLegacyPolicy) {
  // Verifies that access to the developer tools can be disabled by setting the
  // legacy DeveloperToolsDisabled policy.

  // Open devtools.
  EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  DevToolsWindow* devtools_window =
      DevToolsWindow::GetInstanceForInspectedWebContents(contents);
  EXPECT_TRUE(devtools_window);

  // Disable devtools via policy.
  PolicyMap policies;
  policies.Set(key::kDeveloperToolsDisabled, POLICY_LEVEL_MANDATORY,
               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(true),
               nullptr);
  content::WebContentsDestroyedWatcher close_observer(
      DevToolsWindowTesting::Get(devtools_window)->main_web_contents());
  UpdateProviderPolicy(policies);
  // wait for devtools close
  close_observer.Wait();
  // The existing devtools window should have closed.
  EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
  // And it's not possible to open it again.
  EXPECT_FALSE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
  EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
}

IN_PROC_BROWSER_TEST_F(PolicyTest,
                       DeveloperToolsDisabledByDeveloperToolsAvailability) {
  // Verifies that access to the developer tools can be disabled by setting the
  // DeveloperToolsAvailability policy.

  // Open devtools.
  EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  DevToolsWindow* devtools_window =
      DevToolsWindow::GetInstanceForInspectedWebContents(contents);
  EXPECT_TRUE(devtools_window);

  content::WebContentsDestroyedWatcher close_observer(
      DevToolsWindowTesting::Get(devtools_window)->main_web_contents());
  // Disable devtools via policy.
  UpdateProviderPolicy(
      MakeDeveloperToolsAvailabilityMap(2 /* DeveloperToolsDisallowed */));
  // wait for devtools close
  close_observer.Wait();
  // The existing devtools window should have closed.
  EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
  // And it's not possible to open it again.
  EXPECT_FALSE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
  EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
}

// Test for https://b/263040629
IN_PROC_BROWSER_TEST_F(PolicyTest, AvailabilityWins) {
  // DeveloperToolsDisabled is true, but DeveloperToolsAvailability wins.
  PolicyMap policies;
  policies.Set(key::kDeveloperToolsAvailability, POLICY_LEVEL_MANDATORY,
               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
               base::Value(1 /* DeveloperToolsAllowed */), nullptr);
  policies.Set(key::kDeveloperToolsDisabled, POLICY_LEVEL_MANDATORY,
               POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(true),
               nullptr);
  UpdateProviderPolicy(policies);

  EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  DevToolsWindow* devtools_window =
      DevToolsWindow::GetInstanceForInspectedWebContents(contents);
  EXPECT_TRUE(devtools_window);

  // Clearing DeveloperToolsAvailability leaves behind
  // DeveloperToolsDisabled, so the DevTools window gets closed.
  content::WebContentsDestroyedWatcher close_observer(
      DevToolsWindowTesting::Get(devtools_window)->main_web_contents());
  policies.Erase(key::kDeveloperToolsAvailability);
  UpdateProviderPolicy(policies);
  // wait for devtools close
  close_observer.Wait();
  // The existing devtools window should have closed.
  EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
  // And it's not possible to open it again.
  EXPECT_FALSE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
  EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
}

IN_PROC_BROWSER_TEST_F(PolicyTest,
                       ViewSourceDisabledByDeveloperToolsAvailability) {
  // Verifies that entry points to ViewSource can be disabled by setting the
  // DeveloperToolsAvailability policy.

  // Disable devtools via policy.
  UpdateProviderPolicy(
      MakeDeveloperToolsAvailabilityMap(2 /* DeveloperToolsDisallowed */));
  // Verify that it's not possible to ViewSource.
  EXPECT_FALSE(chrome::ExecuteCommand(browser(), IDC_VIEW_SOURCE));
}

IN_PROC_BROWSER_TEST_F(PolicyTest, DeveloperToolsDisabledExtensionsDevMode) {
  // Verifies that when DeveloperToolsDisabled policy is set, the "dev mode"
  // in chrome://extensions is actively turned off and the checkbox
  // is disabled.
  // Note: We don't test the indicator as it is tested in the policy pref test
  // for kDeveloperToolsDisabled and kDeveloperToolsAvailability.

  // This test depends on the following helper methods to locate the DOM
  // elements to be tested.
  const char define_helpers_js[] =
      R"(function getToolbar() {
           const manager = document.querySelector('extensions-manager');
           return manager.shadowRoot.querySelector('extensions-toolbar');
         }

         function getToggle() {
           return getToolbar().$.devMode;
         }

         function getControls() {
           return getToolbar().$.devDrawer;
         }
        )";

  const char toggle_dev_mode_accessor_js[] = "getToggle()";
  const char dev_controls_accessor_js[] = "getControls()";
  const char dev_controls_visibility_check_js[] =
      "getControls().hasAttribute('expanded')";

  // Navigate to the extensions frame and enabled "Developer mode"
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL(chrome::kChromeUIExtensionsURL)));

  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(content::ExecJs(contents, std::string(define_helpers_js)));

  EXPECT_TRUE(content::ExecJs(
      contents, base::StringPrintf("domAutomationController.send(%s.click());",
                                   toggle_dev_mode_accessor_js)));

  WaitForExtensionsDevModeControlsVisibility(contents, dev_controls_accessor_js,
                                             dev_controls_visibility_check_js,
                                             true);

  // Disable devtools via policy.
  UpdateProviderPolicy(
      MakeDeveloperToolsAvailabilityMap(2 /* DeveloperToolsDisallowed */));

  // Expect devcontrols to be hidden now...
  WaitForExtensionsDevModeControlsVisibility(contents, dev_controls_accessor_js,
                                             dev_controls_visibility_check_js,
                                             false);

  // ... and checkbox is disabled
  EXPECT_EQ(true, content::EvalJs(contents, base::StringPrintf(
                                                "%s.hasAttribute('disabled')",
                                                toggle_dev_mode_accessor_js)));
}

// Verifies debug URLs, specifically chrome://kill and javascript URLs, are
// blocked or allowed for different pages depending on the
// DeveloperToolsAvailability policy setting. Note: javascript URLs are always
// blocked on extension schemes, regardless of the policy setting.
// TODO(crbug.com/40064953): The loading of a force installed extension in this
// test runs into an issue on branded Windows builders.
#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_WIN)
#define MAYBE_DebugURLsDisabledByDeveloperToolsAvailability \
  DISABLED_DebugURLsDisabledByDeveloperToolsAvailability
#else
#define MAYBE_DebugURLsDisabledByDeveloperToolsAvailability \
  DebugURLsDisabledByDeveloperToolsAvailability
#endif
IN_PROC_BROWSER_TEST_F(PolicyTest,
                       MAYBE_DebugURLsDisabledByDeveloperToolsAvailability) {
  // TODO(https://crbug.com/40804030): Remove this when updated to use MV3.
  extensions::ScopedTestMV2Enabler mv2_enabler;

  // Get a url for a standard web page.
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL tab_url(embedded_test_server()->GetURL("/empty.html"));

  // Get a url for a force installed extension.
  base::FilePath crx_path(ui_test_utils::GetTestFilePath(
      base::FilePath().AppendASCII("devtools").AppendASCII("extensions"),
      base::FilePath().AppendASCII("options.crx")));
  extensions::ChromeTestExtensionLoader loader(browser()->profile());
  // TODO(crbug.com/40269105): We shouldn't need to ignore manifest warnings
  // here, but there's an issue related to the _metadata folder added for
  // content verification when force-installing an off-store crx in a branded
  // build, which produces an install warning.
  loader.set_ignore_manifest_warnings(true);
  loader.set_location(ManifestLocation::kExternalPolicyDownload);
  scoped_refptr<const extensions::Extension> extension =
      loader.LoadExtension(crx_path);
  ASSERT_TRUE(extension);
  GURL extension_url("chrome-extension://" + extension->id() + "/options.html");

  // The default for DeveloperToolsAvailability is to disallow for force
  // installed extensions. Even though that is already the value, we set it here
  // to be explicit about what the value currently is.
  // With this setting force installed extension should block the debug and
  // javascript URLs and but normal pages should allow them.
  UpdateProviderPolicy(MakeDeveloperToolsAvailabilityMap(
      0 /* DeveloperToolsDisallowedForForceInstalledExtensions */));
  {
    SCOPED_TRACE(
        "Testing DeveloperToolsDisallowedForForceInstalledExtensions policy "
        "setting");
    VerifyPageAllowsKill(browser(), tab_url);
    EXPECT_TRUE(PageAllowsJavascriptURL(browser(), tab_url));
    VerifyPageBlocksKill(browser(), extension_url);
    EXPECT_FALSE(PageAllowsJavascriptURL(browser(), extension_url));
  }

  // When the policy is set to always allow Devtools all the pages should allow
  // debug URLs to be used, but javascript URLs will still be blocked on any
  // extension schemes.
  UpdateProviderPolicy(
      MakeDeveloperToolsAvailabilityMap(1 /* DeveloperToolsAllowed */));
  {
    SCOPED_TRACE("Testing DeveloperToolsAllowed policy setting");
    VerifyPageAllowsKill(browser(), tab_url);
    EXPECT_TRUE(PageAllowsJavascriptURL(browser(), tab_url));
    VerifyPageAllowsKill(browser(), extension_url);
    EXPECT_FALSE(PageAllowsJavascriptURL(browser(), extension_url));
  }

  // When the policy is set to always disallow Devtools all the pages should
  // block debug and javascript URLs.
  UpdateProviderPolicy(
      MakeDeveloperToolsAvailabilityMap(2 /* DeveloperToolsDisallowed */));
  {
    SCOPED_TRACE("Testing DeveloperToolsDisallowed policy setting");
    VerifyPageBlocksKill(browser(), tab_url);
    EXPECT_FALSE(PageAllowsJavascriptURL(browser(), tab_url));
    VerifyPageBlocksKill(browser(), extension_url);
    EXPECT_FALSE(PageAllowsJavascriptURL(browser(), extension_url));
  }
}

}  // namespace policy