Repository: brave/brave-core
Pull request #28593: Inject JS script to restore picture in picture behavior
Author: login: simoarpe
Labels: name: CI/skip-ios
description: Do not run CI builds for iOS
name: needs-security-review
name: CI/skip-windows-x64
description: Do not run CI builds for Windows x64
name: CI/skip-macos-arm64
description: Do not run CI builds for macOS arm64
Created: 2025-04-10T13:51:12Z | Merged: 2025-05-06T13:14:16Z

## Resolves https://github.com/brave/brave-browser/issues/44933

## Releated to https://github.com/brave/brave-core/pull/26355

# 🧐 Reviewer's Notes

1. **The approach is wrong as we should be doing this through a new component, downloading the scripts from a CRX package.**

**Answer:** Implementing the new architecture will require time and multiple PRs. The solution adopted here should be intended as a temporary (and quick) fix to restore the PIP behavior and limit bad user reviews. The solution is also based on the architecture we currently have in production now, so even if is not a state-of-the-art architecture it proved to work correctly and it's battle tested. The solution is based on a minimal set of JS instructions. Instructions that will thoroughly go over a security review to be extra safe.

2. **This solution may stop working again in case of further modification on YouTube website and without the mechanics of a CRX package we cannot propagate a fix in production.**

**Answer:** Correct, this is a possibility we should not exclude. The JS script though was implemented keeping in mind the main priority: zero side effects. In case of breaking changes, the script becomes a no op. On top of that the script will be loaded only when the YouTube background playback option is enabled. Even if suboptimal, this solution can guarantee an extra safety net in case of issues: disabling YouTube background playback option will completely skip the injection offering a pristine version of the YouTube page.

3. **Why the script is not injected in an isolated world ID?**

**Answer:** For similar reasons presented [here](https://github.com/brave/brave-browser/issues/36443), the script needs access to the main YT JS bundle, so main world access is required.

# Description

This PR implements a solution that brings back the PIP behavior.
The PR introduces a very minimal set of JS instructions to inject within a YouTube page.

## Security Review GH issue: https://github.com/brave/reviews/issues/1916

# Update May 6, 2025

Including a small update for posterity in order to recap what has been done and what will be addressed in follow up issues.
This PR implements a strategy to bring back picture in picture support for YouTube videos. Test coverage has been extensively discussed and introduced to make sure:
* We don't have race conditions when injecting the JS script
* We don't inject the script multiple times on same page navigation
* We don't create side effects in case of unexpected changes, and/or serialzed flags don't match
* We inject/modify the serialized flags in case of partial match

After careful considerations we opted to protect the picture in picture feature behind a flag.

The playback video injection has been left as is, and not modified on purpose as we didn't want to mix these two features in this PR. Any improvement and test coverage related to playback video support will be implemented in a follow up PR.

Security & privacy review: ✅ 
Performance review: ✅

## Demo

https://github.com/user-attachments/assets/00136119-0a78-4283-ab27-28b110ee85c7

## Test Plan

- Open Brave Browser and navigate to youtube.com
- Search for a random video and play it
- Tap on fullscreen icon within the YT player
- Navigate to the home screen using the swiping gestures or tapping on the home button
- Observe the activity enters picture in picture mode
- Observe the video keeps playing correctly

---

<!-- CI-related labels that can be applied to this PR:
* CI/run-audit-deps (1) - check for known npm/cargo vulnerabilities (audit_deps)
* CI/run-network-audit (1) - run network-audit
* CI/run-upstream-tests - run Chromium unit and browser tests on Linux and Windows (otherwise only on Linux)
* CI/run-linux-arm64, CI/run-macos-x64, CI/run-windows-arm64, CI/run-windows-x86 - run builds that would otherwise be skipped
* CI/skip - do not run CI builds (except noplatform)
* CI/skip-linux-x64, CI/skip-android, CI/skip-macos-arm64, CI/skip-ios, CI/skip-windows-x64 - skip CI builds for specific platforms
* CI/skip-upstream-tests - do not run Chromium unit, or browser tests (otherwise only on Linux)
* CI/skip-all-linters - do not run presubmit and lint checks
* CI/storybook-url (1) - deploy storybook and provide a unique URL for each build

(1) applied automatically when some files are changed (see: https://github.com/brave/brave-core/blob/master/.github/labeler.yml)
-->

<!--
## Checklist:

- Review design docs
  [Browser design principles](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/chrome_browser_design_principles.md)
  [Style guide](https://chromium.googlesource.com/chromium/src/+/main/styleguide/c++/c++.md)
  [Core principles](https://www.chromium.org/developers/core-principles/)
- Ensure there are (tests)[https://www.chromium.org/developers/testing/]. Unit test as much as possible (including edge cases), but also include browser tests covering high level functionality.
- Ensure that there are comments explaining what classes/methods are/do. The "why" is often more important than the "what" in comments. Also update any relevant docs (moving docs from wiki to brave-core if necessary).
- Request security or other review (third-party libraries, rust code, etc...) if applicable [security/privacy review is needed](https://github.com/brave/brave-browser/wiki/Security-reviews) [other review](https://github.com/brave/reviews/issues/new/choose)
  Also see [adding third-party libraries](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/adding_to_third_party.md) for general guidelines on using third party code
- Maks sure there is a [ticket](https://github.com/brave/brave-browser/issues) for your issue
- Use Github [auto-closing keywords](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) in the PR description above
- Write a good [PR/commit description](https://google.github.io/eng-practices/review/developer/cl-descriptions.html)
- Squash any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
- Add appropriate labels (`QA/Yes` or `QA/No`; `release-notes/include` or `release-notes/exclude`; `OS/...`) to the associated issue
- Checked the PR locally:
  * `npm run test -- brave_browser_tests`, `npm run test -- brave_unit_tests` [wiki](https://github.com/brave/brave-browser/wiki/Tests)
  * `npm run presubmit` [wiki](https://github.com/brave/brave-browser/wiki/Presubmit-checks), `npm run gn_check`, `npm run tslint`
- Run `git rebase master` (if needed)
-->



Diff:
diff --git a/browser/android/background_video/BUILD.gn b/browser/android/background_video/BUILD.gn
index 6dfba1a4755f..dcfd08eb3832 100644
--- a/browser/android/background_video/BUILD.gn
+++ b/browser/android/background_video/BUILD.gn
@@ -33,3 +33,20 @@ source_set("features") {
 
   public_deps = [ "//base" ]
 }
+
+source_set("browser_tests") {
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+  testonly = true
+  sources = [ "background_video_playback_tab_helper_browsertest.cc" ]
+
+  deps = [
+    ":background_video",
+    ":features",
+    "//base",
+    "//brave/components/constants",
+    "//chrome/test:test_support",
+    "//net:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/browser/android/background_video/background_video_playback_tab_helper.cc b/browser/android/background_video/background_video_playback_tab_helper.cc
index 0f47f8419984..3c4f500c3813 100644
--- a/browser/android/background_video/background_video_playback_tab_helper.cc
+++ b/browser/android/background_video/background_video_playback_tab_helper.cc
@@ -19,17 +19,66 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlayback[] =
+    uR"(
+(function() {
+  if (document._addEventListener === undefined) {
+    document._addEventListener = document.addEventListener;
+    document.addEventListener = function(a, b, c) {
+      if (a != 'visibilitychange') {
+        document._addEventListener(a, b, c);
+      }
+    };
+  }
+}());
+)";
+
+constexpr char16_t kYoutubePictureInPictureSupport[] =
+    uR"(
+(function() {
+  // Function to modify the flags if the target object exists.
+  function modifyYtcfgFlags() {
+    const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")
+      ?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+    if (config && config.serializedExperimentFlags && typeof config
+      .serializedExperimentFlags === 'string') {
+      let flags = config.serializedExperimentFlags;
+
+      // Replace target flags.
+      flags = flags
+        .replace(
+          "html5_picture_in_picture_blocking_ontimeupdate=true",
+          "html5_picture_in_picture_blocking_ontimeupdate=false")
+        .replace("html5_picture_in_picture_blocking_onresize=true",
+          "html5_picture_in_picture_blocking_onresize=false")
+        .replace(
+          "html5_picture_in_picture_blocking_document_fullscreen=true",
+          "html5_picture_in_picture_blocking_document_fullscreen=false"
+        )
+        .replace(
+          "html5_picture_in_picture_blocking_standard_api=true",
+          "html5_picture_in_picture_blocking_standard_api=false")
+        .replace("html5_picture_in_picture_logging_onresize=true",
+          "html5_picture_in_picture_logging_onresize=false");
+
+      // Assign updated flags back to config.
+      config.serializedExperimentFlags = flags;
+    }
+  }
+
+  if (window.ytcfg) {
+    modifyYtcfgFlags();
+  } else {
+    document.addEventListener('load', (event) => {
+      const target = event.target;
+      if (target.tagName === 'SCRIPT' && window.ytcfg) {
+        // Check and modify flags when a new script is added.
+        modifyYtcfgFlags();
+      }
+    }, true);
+  }
+}());
+)";
 
 bool IsYouTubeDomain(const GURL& url) {
   if (net::registry_controlled_domains::SameDomainOrHost(
@@ -51,10 +100,9 @@ bool IsBackgroundVideoPlaybackEnabled(content::WebContents* contents) {
     return false;
   }
 
-  content::RenderFrameHost::AllowInjectingJavaScript();
-
   return true;
 }
+
 }  // namespace
 
 BackgroundVideoPlaybackTabHelper::BackgroundVideoPlaybackTabHelper(
@@ -65,15 +113,21 @@ BackgroundVideoPlaybackTabHelper::BackgroundVideoPlaybackTabHelper(
 
 BackgroundVideoPlaybackTabHelper::~BackgroundVideoPlaybackTabHelper() {}
 
-void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
+void BackgroundVideoPlaybackTabHelper::PrimaryMainDocumentElementAvailable() {
+  content::WebContents* contents = web_contents();
   // Filter only YT domain here
-  if (!IsYouTubeDomain(web_contents()->GetLastCommittedURL())) {
+  if (!IsYouTubeDomain(contents->GetLastCommittedURL())) {
     return;
   }
-  if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
-    web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+  content::RenderFrameHost::AllowInjectingJavaScript();
+  if (IsBackgroundVideoPlaybackEnabled(contents)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
+        kYoutubeBackgroundPlayback, base::NullCallback());
+  }
+  if (base::FeatureList::IsEnabled(
+          ::preferences::features::kBravePictureInPictureForYouTubeVideos)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
+        kYoutubePictureInPictureSupport, base::NullCallback());
   }
 }
 
diff --git a/browser/android/background_video/background_video_playback_tab_helper.h b/browser/android/background_video/background_video_playback_tab_helper.h
index dd867e1724cf..0f3c5ffb2699 100644
--- a/browser/android/background_video/background_video_playback_tab_helper.h
+++ b/browser/android/background_video/background_video_playback_tab_helper.h
@@ -21,8 +21,7 @@ class BackgroundVideoPlaybackTabHelper
   ~BackgroundVideoPlaybackTabHelper() override;
 
   // content::WebContentsObserver overrides:
-  void DidFinishNavigation(
-      content::NavigationHandle* navigation_handle) override;
+  void PrimaryMainDocumentElementAvailable() override;
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };
diff --git a/browser/android/background_video/background_video_playback_tab_helper_browsertest.cc b/browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
new file mode 100644
index 000000000000..5a463041d5f4
--- /dev/null
+++ b/browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
@@ -0,0 +1,389 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       ReplaceExperimentalFlagValues) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       ReplaceExperimentalFlagValuesInjectedBeforeOnLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       DontReplaceExperimentalFlagValuesForSamePageNavigation) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Navigate to "#section1".
+  const GURL url_link1(url.spec() + "#section1");
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url_link1,
+                                                      1, true);
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Navigate to "#section2".
+  const GURL url_link2(url.spec() + "#section2");
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url_link2,
+                                                      1, true);
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even with multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       OnlyReplaceExperimentalFlagValuesForYouTubeDomains) {
+  const GURL url = https_server_.GetURL("youtub.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify nothing is injected when domain does not match.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       NoOpIfSerializedExperimentFlagsIsMissing) {
+  const GURL url =
+      https_server_.GetURL("youtube.com", "/ytcfg_mock_no_flags.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Assert that `serializedExperimentFlags` is not present.
+  ASSERT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "typeof "
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags === 'undefined'")
+          .ExtractBool());
+  // Verify that nothing is injected if `serializedExperimentFlags` is not
+  // present.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       NoOpIfYtcfgIsMissing) {
+  const GURL url = https_server_.GetURL("youtube.com", "/no_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Assert that `ytcfg` is not present
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "typeof window.ytcfg === 'undefined'")
+          .ExtractBool());
+  // Verify that nothing is injected if `ytcfg` is not present.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       IgnoreMissingSerializedExperimentalFlags) {
+  const GURL url =
+      https_server_.GetURL("youtube.com", "/ytcfg_mock_reduced_flags.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Assert that some flags were missing by checking
+  // `serializedExperimentFlags.includes` returns `false`.
+  ASSERT_FALSE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen\")")
+          .ExtractBool());
+  ASSERT_FALSE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api\")")
+          .ExtractBool());
+  ASSERT_FALSE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize\")")
+          .ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify the remaining flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
diff --git a/browser/android/background_video/features.cc b/browser/android/background_video/features.cc
index 505e03f80e41..ffead2a51e39 100644
--- a/browser/android/background_video/features.cc
+++ b/browser/android/background_video/features.cc
@@ -15,5 +15,9 @@ BASE_FEATURE(kBraveBackgroundVideoPlayback,
              "BraveBackgroundVideoPlayback",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kBravePictureInPictureForYouTubeVideos,
+             "BravePictureInPictureForYouTubeVideos",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 }  // namespace features
 }  // namespace preferences
diff --git a/browser/android/background_video/features.h b/browser/android/background_video/features.h
index a09fe9ad076c..8c3884596958 100644
--- a/browser/android/background_video/features.h
+++ b/browser/android/background_video/features.h
@@ -12,6 +12,7 @@ namespace preferences {
 namespace features {
 
 BASE_DECLARE_FEATURE(kBraveBackgroundVideoPlayback);
+BASE_DECLARE_FEATURE(kBravePictureInPictureForYouTubeVideos);
 
 }  // namespace features
 }  // namespace preferences
diff --git a/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc b/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc
index 838fe0573cb4..853b533ebd2a 100644
--- a/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc
@@ -46,6 +46,7 @@
     &playlist::features::kPlaylist,                                     \
     &download::features::kParallelDownloading,                          \
     &preferences::features::kBraveBackgroundVideoPlayback,              \
+    &preferences::features::kBravePictureInPictureForYouTubeVideos,     \
     &brave_rewards::features::kNewRewardsUIFeature,                     \
     &request_otr::features::kBraveRequestOTRTab,                        \
     &safe_browsing::features::kBraveAndroidSafeBrowsing,                \
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 9ff9cc4dc106..7ba77cd7e6af 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -1231,6 +1231,8 @@ test("brave_browser_tests") {
       "//brave/components/brave_perf_predictor/resources/entities-httparchive-nostats.json",
     ]
 
+    # android only tests
+    deps += [ "//brave/browser/android/background_video:browser_tests" ]
     deps -= android_test_exception_deps
     deps += android_only_test_deps
     sources -= android_test_exception_sources
diff --git a/test/data/load_ytcfg_mock.html b/test/data/load_ytcfg_mock.html
new file mode 100644
index 000000000000..d71f4658a66f
--- /dev/null
+++ b/test/data/load_ytcfg_mock.html
@@ -0,0 +1,61 @@
+<html>
+<head>
+    <title>OK</title>
+    <script>
+    // Backup the original `replace` method.
+    const originalReplace = String.prototype.replace;
+
+    // Add a counter to track the number of times `replace` is called.
+    let replaceCallCount = 0;
+
+    // Override `String.prototype.replace`.
+    String.prototype.replace = function (...args) {
+      // Increment the counter.
+      replaceCallCount++;
+
+      // Call the original `replace` method and return the result.
+      return originalReplace.apply(this, args);
+    };
+
+    // Function to get the current replace call count.
+    window.getReplaceCallCount = function () {
+      return replaceCallCount;
+    };
+
+    // `window.ytcfg` object is not created immediately
+    // as it will be injected from tests.
+    window.simulateScriptLoadEvent = function () {
+      const script = document.createElement('script');
+      script.tagName = 'SCRIPT'; // Emulate SCRIPT tag behavior.
+      script.textContent = `
+      // Mock window.ytcfg object here.
+      window.ytcfg = {
+        data_: {
+          WEB_PLAYER_CONTEXT_CONFIGS: {
+            WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH: {
+              serializedExperimentFlags: "html5_picture_in_picture_blocking_ontimeupdate=true&"
+              + "html5_picture_in_picture_blocking_onresize=true&"
+              + "html5_picture_in_picture_blocking_document_fullscreen=true&"
+              + "html5_picture_in_picture_blocking_standard_api=true&"
+              + "html5_picture_in_picture_logging_onresize=true&"
+              + "another_flag_for_testing=true"
+            }
+          }
+        },
+        get(key) {
+          // Allow access to keys via a simple getter.
+          return this.data_[key];
+        }
+      };
+  `;
+      document.head.appendChild(script);
+      const loadEvent = new Event('load', { bubbles: true, cancelable: false });
+      script.dispatchEvent(loadEvent);
+    };
+    </script>
+</head>
+<body>
+  <a href="#section1" id="link1">Go to Section 1</a>
+  <a href="#section2" id="link2">Go to Section 2</a>
+</body>
+</html>
diff --git a/test/data/no_ytcfg_mock.html b/test/data/no_ytcfg_mock.html
new file mode 100644
index 000000000000..f3ae99916836
--- /dev/null
+++ b/test/data/no_ytcfg_mock.html
@@ -0,0 +1,27 @@
+<html>
+<head>
+    <title>OK</title>
+    <script>
+    // Backup the original `replace` method.
+    const originalReplace = String.prototype.replace;
+
+    // Add a counter to track the number of times `replace` is called.
+    let replaceCallCount = 0;
+
+    // Override `String.prototype.replace`.
+    String.prototype.replace = function (...args) {
+      // Increment the counter.
+      replaceCallCount++;
+
+      // Call the original `replace` method and return the result.
+      return originalReplace.apply(this, args);
+    };
+
+    // Function to get the current replace call count.
+    window.getReplaceCallCount = function () {
+      return replaceCallCount;
+    };
+    </script>
+</head>
+<body></body>
+</html>
diff --git a/test/data/ytcfg_mock.html b/test/data/ytcfg_mock.html
new file mode 100644
index 000000000000..89be97fa2dbb
--- /dev/null
+++ b/test/data/ytcfg_mock.html
@@ -0,0 +1,52 @@
+<html>
+<head>
+    <title>OK</title>
+    <script>
+    // Backup the original `replace` method.
+    const originalReplace = String.prototype.replace;
+
+    // Add a counter to track the number of times `replace` is called.
+    let replaceCallCount = 0;
+
+    // Override `String.prototype.replace`.
+    String.prototype.replace = function (...args) {
+      // Increment the counter.
+      replaceCallCount++;
+
+      // Call the original `replace` method and return the result.
+      return originalReplace.apply(this, args);
+    };
+
+    // Function to get the current replace call count.
+    window.getReplaceCallCount = function () {
+      return replaceCallCount;
+    };
+
+    // Mock `window.ytcfg` object.
+    window.ytcfg = {
+      data_: {
+        WEB_PLAYER_CONTEXT_CONFIGS: {
+          WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH: {
+            serializedExperimentFlags: `
+              html5_picture_in_picture_blocking_ontimeupdate=true&
+              html5_picture_in_picture_blocking_onresize=true&
+              html5_picture_in_picture_blocking_document_fullscreen=true&
+              html5_picture_in_picture_blocking_standard_api=true&
+              html5_picture_in_picture_logging_onresize=true&
+              another_flag_for_testing=true
+            `
+          }
+        }
+      },
+      get(key) {
+        // Allow access to keys via a simple getter.
+        return this.data_[key];
+      }
+    };
+    </script>
+</head>
+<body>
+  <a href="#section1" id="link1">Go to Section 1</a>
+  <a href="#section2" id="link2">Go to Section 2</a>
+</body>
+</html>
diff --git a/test/data/ytcfg_mock_no_flags.html b/test/data/ytcfg_mock_no_flags.html
new file mode 100644
index 000000000000..2b93d5a474b7
--- /dev/null
+++ b/test/data/ytcfg_mock_no_flags.html
@@ -0,0 +1,41 @@
+<html>
+<head>
+    <title>OK</title>
+    <script>
+    // Backup the original `replace` method.
+    const originalReplace = String.prototype.replace;
+
+    // Add a counter to track the number of times `replace` is called.
+    let replaceCallCount = 0;
+
+    // Override `String.prototype.replace`.
+    String.prototype.replace = function (...args) {
+      // Increment the counter.
+      replaceCallCount++;
+
+      // Call the original `replace` method and return the result.
+      return originalReplace.apply(this, args);
+    };
+
+    // Function to get the current replace call count.
+    window.getReplaceCallCount = function () {
+      return replaceCallCount;
+    };
+
+    // Mock `window.ytcfg` object.
+    window.ytcfg = {
+      data_: {
+        WEB_PLAYER_CONTEXT_CONFIGS: {
+          WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH: {
+          }
+        }
+      },
+      get(key) {
+        // Allow access to keys via a simple getter.
+        return this.data_[key];
+      }
+    };
+    </script>
+</head>
+<body></body>
+</html>
diff --git a/test/data/ytcfg_mock_reduced_flags.html b/test/data/ytcfg_mock_reduced_flags.html
new file mode 100644
index 000000000000..5947a54f87f9
--- /dev/null
+++ b/test/data/ytcfg_mock_reduced_flags.html
@@ -0,0 +1,49 @@
+<html>
+<head>
+    <title>OK</title>
+    <script>
+    // Backup the original `replace` method.
+    const originalReplace = String.prototype.replace;
+
+    // Add a counter to track the number of times `replace` is called.
+    let replaceCallCount = 0;
+
+    // Override `String.prototype.replace`.
+    String.prototype.replace = function (...args) {
+      // Increment the counter.
+      replaceCallCount++;
+
+      // Call the original `replace` method and return the result.
+      return originalReplace.apply(this, args);
+    };
+
+    // Function to get the current replace call count.
+    window.getReplaceCallCount = function () {
+      return replaceCallCount;
+    };
+
+    // Mock `window.ytcfg` object without the following flags
+    // html5_picture_in_picture_blocking_document_fullscreen=true&
+    // html5_picture_in_picture_blocking_standard_api=true&
+    // html5_picture_in_picture_logging_onresize=true&
+    window.ytcfg = {
+      data_: {
+        WEB_PLAYER_CONTEXT_CONFIGS: {
+          WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH: {
+            serializedExperimentFlags: `
+              html5_picture_in_picture_blocking_ontimeupdate=true&
+              html5_picture_in_picture_blocking_onresize=true&
+              another_flag_for_testing=true
+            `
+          }
+        }
+      },
+      get(key) {
+        // Allow access to keys via a simple getter.
+        return this.data_[key];
+      }
+    };
+    </script>
+</head>
+<body></body>
+</html>


Reviews:
user: login: github-actions[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T14:00:42Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T14:13:36Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T20:58:21Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T20:59:12Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T21:00:31Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T21:07:26Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T09:58:49Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T10:18:17Z
user: login: atuchin-m
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T10:58:05Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T11:10:21Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T12:04:53Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T14:17:43Z
user: login: atuchin-m
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T15:01:10Z
user: login: atuchin-m
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T18:07:49Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T18:25:41Z
user: login: atuchin-m
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T14:58:58Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T15:47:15Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T15:53:02Z
user: login: atuchin-m
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T15:58:20Z
user: login: atuchin-m
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T15:58:55Z
user: login: atuchin-m
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T16:03:26Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T08:29:33Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T09:12:45Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T09:13:11Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T09:13:46Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T09:42:48Z
user: login: atuchin-m
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T12:16:26Z
user: login: atuchin-m
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T12:18:19Z
user: login: atuchin-m
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T12:25:11Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T18:33:01Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T18:37:02Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T18:46:45Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T18:53:41Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T18:54:03Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T18:55:51Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T18:58:29Z
user: login: diracdeltas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T20:02:21Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T11:59:25Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T12:36:14Z
user: login: diracdeltas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T16:00:39Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T18:37:19Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T18:39:19Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T18:47:59Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T18:57:27Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T18:59:44Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T19:01:08Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T19:02:44Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T20:02:02Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T11:38:17Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T11:47:01Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-01T13:00:28Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T23:59:13Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:03:50Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:04:09Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:07:54Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:10:38Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:13:40Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:17:53Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:19:40Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:22:31Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:24:00Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:24:44Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:26:50Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:28:50Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:30:22Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:31:42Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:33:03Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:33:30Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:34:48Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:39:38Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T00:41:47Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T01:16:41Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T09:22:30Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T11:43:04Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T11:46:33Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T11:47:13Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T11:47:52Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T13:55:47Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T13:56:02Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T13:57:40Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T13:59:26Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:01:37Z
user: login: bridiver
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:02:07Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:13:17Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:17:24Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:22:27Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:22:31Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:22:35Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:22:42Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:22:47Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:22:51Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:22:54Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T14:24:13Z
user: login: simoarpe
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T15:45:53Z
user: login: github-actions[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T15:51:05Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T16:32:52Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T16:33:48Z
user: login: bridiver
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-05T16:41:32Z
user: login: github-actions[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T16:45:24Z
user: login: stoletheminerals
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T17:08:13Z

Review comments:
diff_hunk: @@ -73,7 +112,7 @@ void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
   }
   if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
     web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+        kYoutubeBackgroundPlaybackAndPipScript, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: github-actions[bot]
body: <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>[semgrep] ExecuteJavaScript usages should be vet by the security-team.<br/><br/>References:<br/>- https://github.com/brave/brave-browser/wiki/Security-reviews (point 12)<br/><br><br>Source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-execute-script.yaml<br><br><!-- Category: security --><br>Cc @thypon @diracdeltas @bridiver
<!-- __reviewdog__:ChA4NmJmZGE1ZWQ4YjU2NWI2 -->

created_at: 2025-04-10T14:00:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -73,7 +112,7 @@ void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
   }
   if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
     web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+        kYoutubeBackgroundPlaybackAndPipScript, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: Sec review requested here: https://github.com/brave/reviews/issues/1916
created_at: 2025-04-10T14:13:36Z
author_association: COLLABORATOR
diff_hunk: @@ -73,7 +112,7 @@ void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
   }
   if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
     web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+        kYoutubeBackgroundPlaybackAndPipScript, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: `DidFinishNavigation` is the wrong method to be using for this because it only indicates that the network request has started and the page may not even be available yet. Separately it's also missing a check for `IsInPrimaryMainFrame`. `PrimaryMainDocumentElementAvailable` is the first indication that the dom is available and it's safe to run scripts
created_at: 2025-04-14T20:58:21Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,56 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  if (!window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+    if (observer) {
+      observer.disconnect();
+    }
+  }
+}
+const observer = new MutationObserver((mutations) => {
+  for (const mutation of mutations) {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: MutationObservers can have a significant impact on performance, has this been reviewed by @brave/perf-team ?
created_at: 2025-04-14T20:59:12Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,56 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  if (!window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: what happens if these values do not exist in serializedExperimentFlags?
created_at: 2025-04-14T21:00:31Z
author_association: COLLABORATOR
diff_hunk: @@ -73,7 +112,7 @@ void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
   }
   if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
     web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+        kYoutubeBackgroundPlaybackAndPipScript, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: if we also need to run this for same page navigations then we will need to run this here for only same page navigations
created_at: 2025-04-14T21:07:26Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,56 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  if (!window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+    if (observer) {
+      observer.disconnect();
+    }
+  }
+}
+const observer = new MutationObserver((mutations) => {
+  for (const mutation of mutations) {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: https://bravesoftware.slack.com/archives/C3T9S9WKD/p1744711045992409

Performance review team has been pinged on Slack! 
created_at: 2025-04-15T09:58:49Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,56 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  if (!window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: The way `String.replace()` would behave in case of no match (a value does not exist in `serializedExperimentFlags`) would be to simply return the original string. 

The entire code was implemented keeping in mind potential unexpected changes. Making sure it does not create  side effects or unwanted operations, and replacing only the flags that match.

I've explained the details in the PR description, under the section `Reviewer's Notes`:

`
The JS script was implemented keeping in mind the main priority: zero side effects. In case of breaking changes, the script becomes a no op. On top of that the script will be loaded only when the YouTube background playback option is enabled. Even if suboptimal, this solution can guarantee an extra safety net in case of issues: disabling YouTube background playback option will completely skip the injection offering a pristine version of the YouTube page.
`

created_at: 2025-04-15T10:18:17Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,56 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  if (!window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+    if (observer) {
+      observer.disconnect();
+    }
+  }
+}
+const observer = new MutationObserver((mutations) => {
+  for (const mutation of mutations) {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: atuchin-m
body: The problem with MutationObserver performance happens when we have a huge number of the observer callback hits. This can slow down the page. Usually we should make the filter the specific as possible.
Looks like here there no way to make filter specific, so we have to count and estimate the maximum number of `mutations` we will process.

WDYT about tracking `onload` event instead, does it work for you?
```
document.addEventListener('load', (event) => {
    const target = event.target;
    if (target.tagName === 'SCRIPT') {
      console.log('added', target.getAttribute('src'));
    }
  }, true);
  ```

P.S. the current impl with MutationObserver is likely to miss a case when added `node` isn't `<script>`, but has `<script>` in the subtree.
created_at: 2025-04-15T10:58:05Z
author_association: COLLABORATOR
diff_hunk: @@ -73,7 +112,7 @@ void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
   }
   if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
     web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+        kYoutubeBackgroundPlaybackAndPipScript, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: I was not able to fully comprehend why we picked `DidFinishNavigation` in the first place. I assumed pre-existing code had already been reviewed, vetted, and approved so I left it untouched. If you check my original PR, my choice falls on `PrimaryMainDocumentElementAvailable` method.

https://github.com/brave/brave-core/blob/d7c438a66fb8dd6a1a68780f24250cb94964caba/components/youtube_script_injector/browser/content/youtube_tab_feature.cc#L96-L129

I thought a lot about this, and because I didn't want to introduce any side effects to the playback behavior I've relied on the same method. In the `Reviewer's note` as well I've stated that this is the wrong approach:

<img width="856" alt="Inject_JS_script_to_restore_picture_in_picture_behavior_by_simoarpe_·_Pull_Request__28593_·_brave_brave-core" src="https://github.com/user-attachments/assets/f55cf652-92f2-4ef4-9df7-e8f16e708a22" />

We had several options here:
- (Solution 1) Reusing the `DidFinishNavigation` method that [has been in production for almost 6 years](https://github.com/brave/brave-core/commit/fd418da227e63642a277c43e971af1958c40708a) and (as far as I know) didn't give us any issues.
- (Solution 2) Changing the implementation adopting the right callback `PrimaryMainDocumentElementAvailable` opening the possibiliy this might alter the playback behavior.
- (Solution 3) Split the logic, leaving the playback implementation as is, and introducing the PIP script injection in `PrimaryMainDocumentElementAvailable`.

Considering that this PR was intended as a quick temporary fix to address the lack of a feature many users were demanding I opted for the first solution. Solution that would probably last in production for a couple of months, and that will be replaced by proper component.

I'm happy to consider other solutions: shall I give it a try at solution 3? 
Or maybe do you have a better one to suggest @bridiver? WDYT?
created_at: 2025-04-15T11:10:20Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,56 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  if (!window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+    if (observer) {
+      observer.disconnect();
+    }
+  }
+}
+const observer = new MutationObserver((mutations) => {
+  for (const mutation of mutations) {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: Thank you @atuchin-m, the suggested solution works good! I'm going to commit the new changes.
created_at: 2025-04-15T12:04:53Z
author_association: COLLABORATOR
diff_hunk: @@ -73,7 +112,7 @@ void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
   }
   if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
     web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+        kYoutubeBackgroundPlaybackAndPipScript, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: **Update:** Discussed in DMs - I'm going to implement the following callbacks: `PrimaryMainDocumentElementAvailable` that covers the initial load and `DidFinishNavigation` with `IsSameDocument() == true` that covers same page navigations.
created_at: 2025-04-15T14:17:42Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,56 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  if (!window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+    if (observer) {
+      observer.disconnect();
+    }
+  }
+}
+const observer = new MutationObserver((mutations) => {
+  for (const mutation of mutations) {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: atuchin-m
body: the updated version looks good to me in terms of perf.
created_at: 2025-04-15T15:01:10Z
author_association: COLLABORATOR
diff_hunk: @@ -73,7 +112,7 @@ void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
   }
   if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
     web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+        kYoutubeBackgroundPlaybackAndPipScript, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: atuchin-m
body: @simoarpe 
BTW, do we really need to cover `IsSameDocument() == true`? Such navigations sure preserve the same domain => the script will be injected.

If we need to do it, please implement protection against making multiple subscriptions to `document.addEventListener('load',..)`
created_at: 2025-04-15T18:07:49Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,48 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  if (!window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+document.addEventListener('load', (event) => {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: this creates a race condition on first load. It needs to first check to see if `window.ytcfg` and if it doesn't, then it can wait for it to load. 

Also looking at this again I don't know why it's running for same document navigations in the current code because `document` is not going to change
created_at: 2025-04-15T18:25:40Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,57 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags(checkYtcfgInstance) {
+  if (checkYtcfgInstance && !window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
+  if (window.ytcfg) {
+    // Pass `false` as there's no need to check again for `window.ytcfg`.
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: atuchin-m
body: nit: IMHO it actually makes the code harder to read, but doesn't save anything. 
Could we always check for `window.ytcfg` in`modifyYtcfgFlags()`?
Or just check it externally (i.e. `if (target.tagName === 'SCRIPT' && window.ytcfg) { ... }`?
created_at: 2025-04-16T14:58:58Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,57 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags(checkYtcfgInstance) {
+  if (checkYtcfgInstance && !window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
+  if (window.ytcfg) {
+    // Pass `false` as there's no need to check again for `window.ytcfg`.
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: You're right! Fixing this now...
created_at: 2025-04-16T15:47:15Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,57 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags(checkYtcfgInstance) {
+  if (checkYtcfgInstance && !window.ytcfg) {
+    return;
+  }
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
+  if (window.ytcfg) {
+    // Pass `false` as there's no need to check again for `window.ytcfg`.
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: Feedback addressed by commit  [`0e00680` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/0e0068029e891324ae010dc0348f6f02506e2d8a)
created_at: 2025-04-16T15:53:02Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: atuchin-m
body: this API doesn't wait anything except the navigation: it could be unblocked before the injected script is called.
What about using `WaitForLoadStop()`?
https://source.chromium.org/chromium/chromium/src/+/main:content/public/test/browser_test_utils.h;l=299;bpv=1;bpt=1?q=WaitForLoadStop&ss=chromium%2Fchromium%2Fsrc
created_at: 2025-04-16T15:58:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                        true);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+  base::test::ScopedFeatureList feature_list_;
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: atuchin-m
body: leftover?
created_at: 2025-04-16T15:58:55Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: atuchin-m
body: do we need this override?
created_at: 2025-04-16T15:59:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                        true);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+  base::test::ScopedFeatureList feature_list_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidYouTubeScriptInjectorBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/simple.html");
+
+  NavigateToURL(url);
+  std::string command = "window._pipScriptInjected === true";
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: atuchin-m
body: nit: make this a constant in `namespace {}`
created_at: 2025-04-16T16:00:50Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: atuchin-m
body: Look like we don't need this include.
Let's leave only what we actually use.
created_at: 2025-04-16T16:02:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                        true);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+  base::test::ScopedFeatureList feature_list_;
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: simoarpe
body: Yes! Time to remove it.
created_at: 2025-04-17T08:29:32Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: simoarpe
body: No, it was from my initial copy-pasted skelethon.
created_at: 2025-04-17T09:12:45Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                        true);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+  base::test::ScopedFeatureList feature_list_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidYouTubeScriptInjectorBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/simple.html");
+
+  NavigateToURL(url);
+  std::string command = "window._pipScriptInjected === true";
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: simoarpe
body: 👍
created_at: 2025-04-17T09:13:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: simoarpe
body: Is there a check we can run to remove unused include?
created_at: 2025-04-17T09:13:45Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: simoarpe
body: So the navigation can potantially complete but the script injection still ongoing?

I've added `WaitForLoadStop` method in commit [`24f26c3` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/24f26c34fa94c47c52cea0f50149f98532947f3e)
created_at: 2025-04-17T09:42:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: atuchin-m
body: If you use clangd plugin it has ability to highlight unused includes.
Although, I don't know about any tool to check it automatically.
created_at: 2025-04-17T12:16:25Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,98 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+std::string command = "window._pipScriptInjected === true";
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: atuchin-m
body: nit : this should be `const char*` or string_view
non trivial objects shouldn't be constructed globally.
created_at: 2025-04-17T12:18:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: atuchin-m
body: Very basically, the navigation is completed when you got a first byte of the main document.
Although, PrimaryMainDocumentElementAvailable is called after parsing is done (including running inlined scripts):
```
  // This method is invoked once the window.document element of the primary main
  // frame's current document (i.e., |render_frame_host|) is ready. This happens
  // when the document's main HTML resource has finished parsing
```  
[link](https://source.chromium.org/chromium/chromium/src/+/main:content/public/browser/web_contents_observer.h;l=420-426?q=PrimaryMainDocumentElementAvailable)
  
P.S. it's the possible race I mentioned in DM. There is no way to inject JS earlier than `PrimaryMainDocumentElementAvailable` from the browser process. So the script should work under this limitations.
created_at: 2025-04-17T12:25:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: bridiver
body: @atuchin-m this is actually not needed as the default for `TestNavigationObserver` is `kLoadStopped` which waits for `OnDidStopLoading`
created_at: 2025-04-22T18:33:01Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,98 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+const char* command = "window._pipScriptInjected === true";
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: bridiver
body: `constexpr` and should be kCheckScriptInjected or something https://google.github.io/styleguide/cppguide.html#Constant_Names
created_at: 2025-04-22T18:37:02Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: we shouldn't be adding anything to `window` as this can cause webcompat and other issues (unlikely here, but as a general rule we don't do this). `document._addEventListener` is also not great here, but it was pre-existing. 

Somehow I overlooked this in DM when you mentioned `window._pipScriptInjected`, but we don't need an explicit guard in the script anyway. What we need here is what I suggested in DM which is to have a mock `ytcfg` script that will fail if we try to change the flags more than once. The mock script is also needed to make sure this code works correctly (including cases where `serializedExperimentFlags` doesn't exist and also where it does exist, but one or more of the values we replace are missing.
created_at: 2025-04-22T18:46:45Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: there should also be a browser test here, but there's a potential race condition here if the tab is backgrounded while it's still loading. We should only suppress this event if `document.hidden = false`.
created_at: 2025-04-22T18:53:41Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: we should also have a test for this functionality in general, but both of these can be done in a follow-up
created_at: 2025-04-22T18:54:02Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: bridiver
body: it doesn't hurt anything, but I'd prefer not to have these `NavigateToURL` methods because they are all a little bit different and can be confused with `ui_test_utils::NavigateToURL`
created_at: 2025-04-22T18:55:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: bridiver
body: also even though this method isn't needed and should be removed, do not put assertions in helper methods because it obscures where the error originated. Return a bool and assert in the test where you call the method
created_at: 2025-04-22T18:58:29Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: diracdeltas
body: this should probably have writable/configurable set to false if it's needed at all https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
created_at: 2025-04-22T20:02:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,101 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/browser/brave_content_browser_client.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownOnMainThread() override {
+    PlatformBrowserTest::TearDownOnMainThread();
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  // ui_test_utils::NavigateToURL isn't available on Android
+  void NavigateToURL(const GURL& url) {
+    content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: simoarpe
body: Feedback addressed by commit 👉 [`98d9fea` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/98d9fea74fbe56d639ceb12bd892c0ff33f5c6a2)
created_at: 2025-04-23T11:59:25Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: > we shouldn't be adding anything to window as this can cause webcompat and other issues (unlikely here, but as a general rule we don't do this). document._addEventListener is also not great here, but it was pre-existing.

@bridiver, is there a document or a wiki where I can learn more about not adding extra variables to `window`?
If it was an unwritten rule, it was hard to infer it from the current implementation as `document._addEventListener` does exactly the same. Or maybe because `document` != `window` there are different considerations to make? Also, which kind of webcompat issues may they create? Sorry for all the questions, I'm genuinely interested in knowing more. We can pick this up here, or via DMs they both work great for me.

> Somehow I overlooked this in DM when you mentioned window._pipScriptInjected, but we don't need an explicit guard in the script anyway. What we need here is what I suggested in DM which is to have a mock ytcfg script that will fail if we try to change the flags more than once.

The flag has been used also in the tests to verify the injection, so removing this now implies reworking the tests. Not the end of the world, but because time is of the essence I feel the best approach is to work towards a solution that we have thoroughly discussed in order to make this quick. So I'm going to ask you to elaborate a bit more about mocking the `ytcfg` script. I'm not entirely sure I follow, so I prefer to ask: the way I see this would introduce a lot of boilerplate.

> The mock script is also needed to make sure this code works correctly (including cases where serializedExperimentFlags doesn't exist and also where it does exist, but one or more of the values we replace are missing.

So they way this would work in tests would be a mock + a tailored test page that would check the behavior in case `window.ytcfg` exists without `serializedExperimentFlags`. Is that correct?
Instead, testing the behavior of the `replace` method feels a bit overkill to me, I struggle to see how it could be beneficial. One day the PIP behavior may break even if the replace method works, because further checks in the YT backend. Still open to do it anyway if that's what you want.

> this should probably have writable/configurable set to false if it's needed at all [developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)

I was not aware or `defineProperty()`, thank you for the tip @diracdeltas. I'll make sure to use it if required.
created_at: 2025-04-23T12:36:14Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: diracdeltas
body: @simoarpe see https://github.com/brave/internal/wiki/Pull-request-security-audit-checklist#window-api-injections and formerly https://docs.google.com/document/d/1BfJbLCvwToPqXHzsKqQgDuluwVDtGhzFR1BclqVzqaA/edit?tab=t.0
created_at: 2025-04-23T16:00:39Z
author_association: MEMBER
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: > is to work towards a solution that we have thoroughly discussed in order to make this quick

the fix that I am proposing here is exactly what we discussed in DM. Having a mock of the yt script that is used to verify the behavior. I'm only stating that I missed seeing the window thing which is a problem in itself.
created_at: 2025-04-23T18:37:19Z
author_association: COLLABORATOR
diff_hunk: @@ -73,7 +112,7 @@ void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
   }
   if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
     web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+        kYoutubeBackgroundPlaybackAndPipScript, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: @atuchin-m nothing should happen for `IsSameDocument() == true` navigations. The script has already been injected and should not run again. 
created_at: 2025-04-23T18:39:19Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: in regards to the urgency of the fix, this is a change that will impact all Brave android users on yt so we need to make sure we have sufficient testing. Also given that we can't update this right now without updating Brave, I think we should put this new addition to the script behind a default enabled feature flag. That will give us the ability to switch this off if it ends up causing problems in release. 
created_at: 2025-04-23T18:47:59Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,95 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kPipScriptInjected[] = "window._pipScriptInjected === true";
+}  // namespace
+
+class AndroidYouTubeScriptInjectorBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidYouTubeScriptInjectorBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidYouTubeScriptInjectorBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidYouTubeScriptInjectorBrowserTest,
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: bridiver
body: in addition to the tests to check that the property overrides work correctly, this is also missing the same page navigation test that we discussed. 
created_at: 2025-04-23T18:57:27Z
author_association: COLLABORATOR
diff_hunk: @@ -100,6 +100,7 @@ android_test_exception_deps = [
 
 # Tests that run only on Android
 android_only_test_sources = [
+  "//brave/browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc",
path: android/android_browser_tests.gni
user: login: bridiver
body: please create a new target for this test and add to `android_only_test_deps`
created_at: 2025-04-23T18:59:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,95 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: bridiver
body: there is no existing component called `youtube_script_injector` so this path doesn't really make sense. This is testing the background_video_playback_tab_helper so the test path/name should reflect that
created_at: 2025-04-23T19:01:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,95 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: bridiver
body: fyi you can put the test in the same directory as the tab helper and use the existing BUILD.gn for a test target per above https://github.com/brave/brave-core/pull/28593/files#r2056717531
created_at: 2025-04-23T19:02:44Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: the existing test also doesn't verify that it is only injected/run once, it only verifies that `_pipScriptInjected` is set at least once
created_at: 2025-04-23T20:02:02Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
+  const config = window.ytcfg.get("WEB_PLAYER_CONTEXT_CONFIGS")?.WEB_PLAYER_CONTEXT_CONFIG_ID_MWEB_WATCH
+  if (config && config.serializedExperimentFlags) {
+    let flags = config.serializedExperimentFlags;
+
+    // Replace target flags.
+    flags = flags
+      .replace("html5_picture_in_picture_blocking_ontimeupdate=true", "html5_picture_in_picture_blocking_ontimeupdate=false")
+      .replace("html5_picture_in_picture_blocking_onresize=true", "html5_picture_in_picture_blocking_onresize=false")
+      .replace("html5_picture_in_picture_blocking_document_fullscreen=true", "html5_picture_in_picture_blocking_document_fullscreen=false")
+      .replace("html5_picture_in_picture_blocking_standard_api=true", "html5_picture_in_picture_blocking_standard_api=false")
+      .replace("html5_picture_in_picture_logging_onresize=true", "html5_picture_in_picture_logging_onresize=false");
+
+    // Assign updated flags back to config.
+    config.serializedExperimentFlags = flags;
+  }
+}
+
+// Define a guard flag to track the script injection.
+if (!window._pipScriptInjected) {
+  window._pipScriptInjected = true;
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: @bridiver, I agree on the point about testing: we should improve it. I also agree on testing the injection times: we should make sure it runs once and only once. About testing the `replace` method behavior instead I have mixed feelings. 

About the mock itself, I'm happy to implement it: we have discussed that in DMs you're right, sorry for all the back and forth and thank you for answering all my questions.

Some actionable points I'm going to implement:
* Remove _pipScriptInjected
* Create a YT lib mock in several test pages to cover these tests:
    * Script injection runs once and only once by counting how many times the mock itself gets called
    * Script injection does not create errors or side effects in case `ytcfg` doesn't exist
    * Script injection does not create errors or side effects in case `serializedExperimentFlags` doesn't exist
    * Script injection does not create errors or side effects in case `serializedExperimentFlags` **does exist**, but one or more values we replace are missing. (?)


> in regards to the urgency of the fix, this is a change that will impact all Brave android users on yt so we need to make sure we have sufficient testing. Also given that we can't update this right now without updating Brave, I think we should put this new addition to the script behind a default enabled feature flag. That will give us the ability to switch this off if it ends up causing problems in release.

About the feature flag instead, here is what I have suggested in the `Reviewer's notes` section 👇

<img width="902" alt="Screenshot 2025-04-24 at 11 30 12 AM" src="https://github.com/user-attachments/assets/81242bd3-0ed1-45ee-a7f4-4ef5193c488f" />

To me it feels like a good tradeoff between the urgency or releasing this PR and the safety net of disabling the PIP injection in case of issues.

I'm going to move the Reviewer's Note at the top of the PR description, I feel it didn't get enough attention, maybe because I've included it at the bottom after the video.


created_at: 2025-04-24T11:38:17Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,53 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: Filed a new GitHub issue to make sure this does not fall through the cracks 👉 https://github.com/brave/brave-browser/issues/45668
created_at: 2025-04-24T11:47:01Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,95 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
path: browser/youtube_script_injector/android_youtube_script_injector_browsertest.cc
user: login: simoarpe
body: 👍 
Feedback addressed by commit [`438d084` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/438d084f086fa38e8837101daa4d7dd99425600a)
created_at: 2025-05-01T13:00:28Z
author_association: COLLABORATOR
diff_hunk: @@ -33,3 +33,21 @@ source_set("features") {
 
   public_deps = [ "//base" ]
 }
+
+source_set("test_support") {
path: browser/android/background_video/BUILD.gn
user: login: bridiver
body: `test_support` is actually for test helpers and similar. This should be `browser_tests`
created_at: 2025-05-02T23:59:13Z
author_association: COLLABORATOR
diff_hunk: @@ -108,6 +108,7 @@ android_only_test_sources = [
 android_only_test_deps = [
   ":test_support",
   "//android_webview/common",
+  "//brave/browser/android/background_video:test_support",
path: android/android_browser_tests.gni
user: login: bridiver
body: I'm actually not sure why `android_only_test_deps` even exists, it doesn't really make sense and these deps should just do directly in the `is_android` block in `brave_browser_tests` and this can be added directly there as well. `android_browser_tests.gni` is really about tests that should run on android, but currently don't because they have to be updated. 
created_at: 2025-05-03T00:03:50Z
author_association: COLLABORATOR
diff_hunk: @@ -108,6 +108,7 @@ android_only_test_sources = [
 android_only_test_deps = [
   ":test_support",
   "//android_webview/common",
+  "//brave/browser/android/background_video:test_support",
path: android/android_browser_tests.gni
user: login: bridiver
body: I'm going to put `android_only_test_deps` on my list of things to fix and add some better docs
created_at: 2025-05-03T00:04:09Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,49 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: I just noticed that for some reason this is not part of the function wrapper. It's not critical here because it's still scoped inside `()`, but it makes it a bit more confusing
created_at: 2025-05-03T00:07:54Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: this method doesn't seem to click anything? Also there's no navigation to wait for here
created_at: 2025-05-03T00:10:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: why are you using this instead of `EvalJs`? 
created_at: 2025-05-03T00:13:40Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,41 @@
+<html>
+<head>
+    <title>OK</title>
+    <script>
+    // Backup the original `replace` method.
+    const originalReplace = String.prototype.replace;
path: test/data/ytcfg_mock_no_flags.html
user: login: bridiver
body: there's a lot of duplication across these test files, I think it would be better to put them all in a shared js file (except for window.ytcfg). Can be fixed in a follow-up
created_at: 2025-05-03T00:17:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: the names of these tests don't really help me understand what you are testing. Something like `ReplaceExperimentalFlagValues`?
created_at: 2025-05-03T00:19:40Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: per above `ReplaceExperimentalFlagValuesInjectedBeforeOnLoad`
created_at: 2025-05-03T00:22:31Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: `DontReplaceExperimentalFlagValuesAgainForSamePageNavigation`
created_at: 2025-05-03T00:24:00Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: these assertions aren't necessary
created_at: 2025-05-03T00:24:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: I guess you are passing a script to click a same page navigation, but I would just remove this helper. In cases like this it's easier to read and understand the test if these are inline instead of helper methods and this helper method is confusing because it's only `ClickAndWaitForNavigation` if you happen to pass in a script that clicks a link. Also the whole thing is more complicated that necessary because you could just call `NavigateToURLBlockUntilNavigationsComplete` with an anchor tag and achieve the same effect
created_at: 2025-05-03T00:26:50Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: we should check these before and after same page navigation
created_at: 2025-05-03T00:28:50Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoMatch) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: OnlyReplaceExperimentalFlagValuesForYoutubeDomains
created_at: 2025-05-03T00:30:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoMatch) {
+  const GURL url = https_server_.GetURL("youtub.com", "/ytcfg_mock.html");
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: in a follow-up there should be a unit test for `IsBackgroundVideoPlaybackEnabled` to check edge cases, but this single check is ok for the browser test
created_at: 2025-05-03T00:31:41Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoMatch) {
+  const GURL url = https_server_.GetURL("youtub.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify nothing is injected when domain does not match.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoSerializedExperimentFlags) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: NoOpIfSerializedExperimentalFlagsIsMissing
created_at: 2025-05-03T00:33:03Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoMatch) {
+  const GURL url = https_server_.GetURL("youtub.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify nothing is injected when domain does not match.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoSerializedExperimentFlags) {
+  const GURL url =
+      https_server_.GetURL("youtube.com", "/ytcfg_mock_no_flags.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Assert that `serializedExperimentFlags` is not present.
+  ASSERT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "typeof "
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags === 'undefined'")
+          .ExtractBool());
+  // Verify that nothing is injected if `serializedExperimentFlags` is not
+  // present.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoYtcfg) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: `NoOpIfYtcfgIsMissing`
created_at: 2025-05-03T00:33:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoMatch) {
+  const GURL url = https_server_.GetURL("youtub.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify nothing is injected when domain does not match.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoSerializedExperimentFlags) {
+  const GURL url =
+      https_server_.GetURL("youtube.com", "/ytcfg_mock_no_flags.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Assert that `serializedExperimentFlags` is not present.
+  ASSERT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "typeof "
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags === 'undefined'")
+          .ExtractBool());
+  // Verify that nothing is injected if `serializedExperimentFlags` is not
+  // present.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoYtcfg) {
+  const GURL url = https_server_.GetURL("youtube.com", "/no_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Assert that `ytcfg` is not present
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "typeof window.ytcfg === 'undefined'")
+          .ExtractBool());
+  // Verify that nothing is injected if `ytcfg` is not present.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionReducedFlags) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: IgnoreMissingSerializedExperimentalFlags
created_at: 2025-05-03T00:34:48Z
author_association: COLLABORATOR
diff_hunk: @@ -65,15 +97,15 @@ BackgroundVideoPlaybackTabHelper::BackgroundVideoPlaybackTabHelper(
 
 BackgroundVideoPlaybackTabHelper::~BackgroundVideoPlaybackTabHelper() {}
 
-void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
+void BackgroundVideoPlaybackTabHelper::PrimaryMainDocumentElementAvailable() {
+  content::WebContents* contents = web_contents();
   // Filter only YT domain here
-  if (!IsYouTubeDomain(web_contents()->GetLastCommittedURL())) {
+  if (!IsYouTubeDomain(contents->GetLastCommittedURL())) {
     return;
   }
-  if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
-    web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+  if (IsBackgroundVideoPlaybackEnabled(contents)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: we shouldn't be using `ExecuteJavaScript` here, it's only meant to be used in chrome:// and devtools urls. In a follow-up we should change this to use script injector component
created_at: 2025-05-03T00:39:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: bridiver
body: these can be cleaned up with helpers (that return true/false so the EXPECT_TRUE stays inside the test block) and allow passing in the flag name (use constants for the known flag names) in a follow-up
created_at: 2025-05-03T00:41:46Z
author_association: COLLABORATOR
diff_hunk: @@ -65,15 +97,15 @@ BackgroundVideoPlaybackTabHelper::BackgroundVideoPlaybackTabHelper(
 
 BackgroundVideoPlaybackTabHelper::~BackgroundVideoPlaybackTabHelper() {}
 
-void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
+void BackgroundVideoPlaybackTabHelper::PrimaryMainDocumentElementAvailable() {
+  content::WebContents* contents = web_contents();
   // Filter only YT domain here
-  if (!IsYouTubeDomain(web_contents()->GetLastCommittedURL())) {
+  if (!IsYouTubeDomain(contents->GetLastCommittedURL())) {
     return;
   }
-  if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
-    web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+  if (IsBackgroundVideoPlaybackEnabled(contents)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: I may just do a follow-up to fix all of them
created_at: 2025-05-05T01:16:41Z
author_association: COLLABORATOR
diff_hunk: @@ -33,3 +33,21 @@ source_set("features") {
 
   public_deps = [ "//base" ]
 }
+
+source_set("test_support") {
path: browser/android/background_video/BUILD.gn
user: login: simoarpe
body: Source set renamed 👉 [`80d6a08` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/80d6a082d506fb77e1e14c95361df66306dfeebc)
created_at: 2025-05-05T09:22:29Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,49 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: Why does it make it confusing? 

I didn't wanto to mix the PIP injection with the playback injection, but the use of the IIFE here is completely unneeded. They should be used to global pollution or accidental conflicts right? But here the IIFE seems unnecessary as it overrides something that already exists in the global scope.
created_at: 2025-05-05T11:43:04Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,49 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: <img width="208" alt="image" src="https://github.com/user-attachments/assets/d77303ca-5081-4210-b8fe-edbde400a1db" />

created_at: 2025-05-05T11:46:33Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,49 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: they are both scoped to a block (and have to be to avoid global namespace pollution), but they are scoped in different ways which is confusing and also the indentation makes it look like the new code is not scoped
created_at: 2025-05-05T11:47:13Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,49 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+    if (document._addEventListener === undefined) {
+        document._addEventListener = document.addEventListener;
+        document.addEventListener = function(a,b,c) {
+            if(a != 'visibilitychange') {
+                document._addEventListener(a,b,c);
+            }
+        };
+    }
+}());
+// Function to modify the flags if the target object exists.
+function modifyYtcfgFlags() {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: oops, missed a little bit in my screenshot, `test` was defined without wrapping in `()`
created_at: 2025-05-05T11:47:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: simoarpe
body: Feedback addressed by commit 👉 [`4294b46` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/4294b46fd66c8bb6ea5032734337b3375bdbec27)
created_at: 2025-05-05T13:55:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: simoarpe
body: Feedback addressed by commit 👉 [`4294b46` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/4294b46fd66c8bb6ea5032734337b3375bdbec27)
created_at: 2025-05-05T13:56:02Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,61 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: we can remove this extra enclosing `()` as well
created_at: 2025-05-05T13:57:40Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,61 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: nvm, I see now that it is part of the text delimiter
created_at: 2025-05-05T13:59:26Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,61 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: Yes!

```cc
auto str = R"(This is a "raw string"!)";
std::cout << str;
// Output: This is a "raw string"!
```
created_at: 2025-05-05T14:01:37Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,61 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+  if (document._addEventListener === undefined) {
+    document._addEventListener = document.addEventListener;
+    document.addEventListener = function(a, b, c) {
+      if (a != 'visibilitychange') {
+        document._addEventListener(a, b, c);
+      }
+    };
+  }
+
+  // Function to modify the flags if the target object exists.
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: let's add a feature flag for this as dicussed
created_at: 2025-05-05T14:13:17Z
author_association: COLLABORATOR
diff_hunk: @@ -33,3 +33,21 @@ source_set("features") {
 
   public_deps = [ "//base" ]
 }
+
+source_set("browser_tests") {
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+  testonly = true
+  sources = [ "background_video_playback_tab_helper_browsertest.cc" ]
+
+  deps = [
+    ":background_video",
+    ":features",
+    "//brave/components/constants",
+    "//chrome/test:test_support",
+    "//net:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+
+  public_deps = [ "//base" ]
path: browser/android/background_video/BUILD.gn
user: login: bridiver
body: just noticed this, why did you make `base` a public dep? Normally you'd make something a public dep if there is an include for it in a header file, but seems odd here.
created_at: 2025-05-05T14:17:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: simoarpe
body: Test names improved by commit 👉 [`4ec1f0c` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/4ec1f0c4e58f5985a309bbcc632488c155004ab9)
created_at: 2025-05-05T14:22:27Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: simoarpe
body: Test names improved by commit 👉 [`4ec1f0c` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/4ec1f0c4e58f5985a309bbcc632488c155004ab9)
created_at: 2025-05-05T14:22:31Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: simoarpe
body: Test names improved by commit 👉 [`4ec1f0c` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/4ec1f0c4e58f5985a309bbcc632488c155004ab9)
created_at: 2025-05-05T14:22:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoMatch) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: simoarpe
body: Test names improved by commit 👉 [`4ec1f0c` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/4ec1f0c4e58f5985a309bbcc632488c155004ab9)
created_at: 2025-05-05T14:22:41Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoMatch) {
+  const GURL url = https_server_.GetURL("youtub.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify nothing is injected when domain does not match.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoSerializedExperimentFlags) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: simoarpe
body: Test names improved by commit 👉 [`4ec1f0c` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/4ec1f0c4e58f5985a309bbcc632488c155004ab9)
created_at: 2025-05-05T14:22:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoMatch) {
+  const GURL url = https_server_.GetURL("youtub.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify nothing is injected when domain does not match.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoSerializedExperimentFlags) {
+  const GURL url =
+      https_server_.GetURL("youtube.com", "/ytcfg_mock_no_flags.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Assert that `serializedExperimentFlags` is not present.
+  ASSERT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "typeof "
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags === 'undefined'")
+          .ExtractBool());
+  // Verify that nothing is injected if `serializedExperimentFlags` is not
+  // present.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoYtcfg) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: simoarpe
body: Test names improved by commit 👉 [`4ec1f0c` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/4ec1f0c4e58f5985a309bbcc632488c155004ab9)
created_at: 2025-05-05T14:22:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,388 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * 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 https://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "base/path_service.h"
+#include "brave/components/constants/brave_paths.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_mock_cert_verifier.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace {
+constexpr char kReplaceCallCount[] = "window.getReplaceCallCount()";
+
+}  // namespace
+
+class AndroidBackgroundVideoPlaybackBrowserTest : public PlatformBrowserTest {
+ public:
+  AndroidBackgroundVideoPlaybackBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  ~AndroidBackgroundVideoPlaybackBrowserTest() override = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    PlatformBrowserTest::SetUpCommandLine(command_line);
+    mock_cert_verifier_.SetUpCommandLine(command_line);
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    PlatformBrowserTest::SetUpInProcessBrowserTestFixture();
+    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    PlatformBrowserTest::SetUpOnMainThread();
+    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    base::FilePath test_data_dir = GetTestDataDir();
+
+    https_server_.ServeFilesFromDirectory(test_data_dir);
+    content::SetupCrossSiteRedirector(&https_server_);
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  base::FilePath GetTestDataDir() {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    return base::PathService::CheckedGet(brave::DIR_TEST_DATA);
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
+    PlatformBrowserTest::TearDownInProcessBrowserTestFixture();
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  void ClickAndWaitForNavigation(const std::u16string& script) {
+    content::TestNavigationObserver observer(web_contents());
+    InjectScript(script);
+    observer.WaitForNavigationFinished();
+  }
+
+  void InjectScript(const std::u16string& script) {
+    content::RenderFrameHost* frame = web_contents()->GetPrimaryMainFrame();
+    frame->ExecuteJavaScriptForTests(script, base::NullCallback(),
+                                     content::ISOLATED_WORLD_ID_GLOBAL);
+  }
+
+ protected:
+  // Must use HTTPS because `youtube.com` is in Chromium's HSTS preload list.
+  net::EmbeddedTestServer https_server_;
+
+ private:
+  content::ContentMockCertVerifier mock_cert_verifier_;
+};
+
+// TESTS
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatch) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionMatchLoad) {
+  const GURL url = https_server_.GetURL("youtube.com", "/load_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  InjectScript(u"window.simulateScriptLoadEvent();");
+
+  EXPECT_EQ(true,
+            content::EvalJs(web_contents(), "!!window.ytcfg").ExtractBool());
+
+  // Verify the replace method was called exactly 5 times.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestSamePageNavigationInjection) {
+  const GURL url = https_server_.GetURL("youtube.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link1')")
+          .ExtractBool());
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "!!document.getElementById('link2')")
+          .ExtractBool());
+
+  // Click the first anchor link with `id=link1` to navigate to "#section1".
+  ClickAndWaitForNavigation(u"document.getElementById('link1').click();");
+
+  // Verify navigation to the same page with the appropriate hash.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section1");
+
+  // Click the second anchor link with `id=link2` to navigate to "#section2".
+  ClickAndWaitForNavigation(u"document.getElementById('link2').click();");
+
+  // Verify navigation.
+  EXPECT_EQ(web_contents()->GetVisibleURL().ref(), "section2");
+
+  // Verify the replace method was called exactly 5 times
+  // even if multiple same page navigations.
+  EXPECT_EQ(5, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+
+  // Verify all the flags were properly set to `false`.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_ontimeupdate=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_onresize=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_document_fullscreen=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_blocking_standard_api=false\")")
+          .ExtractBool());
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"html5_"
+          "picture_in_picture_logging_onresize=false\")")
+          .ExtractBool());
+
+  // Verify the other flags were not modified.
+  EXPECT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags.includes(\"another_"
+          "flag_for_testing=true\")")
+          .ExtractBool());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoMatch) {
+  const GURL url = https_server_.GetURL("youtub.com", "/ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Verify nothing is injected when domain does not match.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoSerializedExperimentFlags) {
+  const GURL url =
+      https_server_.GetURL("youtube.com", "/ytcfg_mock_no_flags.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Assert that `serializedExperimentFlags` is not present.
+  ASSERT_TRUE(
+      content::EvalJs(
+          web_contents(),
+          "typeof "
+          "window.ytcfg.get(\"WEB_PLAYER_CONTEXT_CONFIGS\").WEB_PLAYER_CONTEXT_"
+          "CONFIG_ID_MWEB_WATCH.serializedExperimentFlags === 'undefined'")
+          .ExtractBool());
+  // Verify that nothing is injected if `serializedExperimentFlags` is not
+  // present.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionNoYtcfg) {
+  const GURL url = https_server_.GetURL("youtube.com", "/no_ytcfg_mock.html");
+
+  content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1,
+                                                      true);
+  // Assert that `ytcfg` is not present
+  ASSERT_TRUE(
+      content::EvalJs(web_contents(), "typeof window.ytcfg === 'undefined'")
+          .ExtractBool());
+  // Verify that nothing is injected if `ytcfg` is not present.
+  EXPECT_EQ(0, content::EvalJs(web_contents(), kReplaceCallCount).ExtractInt());
+}
+
+IN_PROC_BROWSER_TEST_F(AndroidBackgroundVideoPlaybackBrowserTest,
+                       TestInjectionReducedFlags) {
path: browser/android/background_video/background_video_playback_tab_helper_browsertest.cc
user: login: simoarpe
body: Test names improved by commit 👉 [`4ec1f0c` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/4ec1f0c4e58f5985a309bbcc632488c155004ab9)
created_at: 2025-05-05T14:22:54Z
author_association: COLLABORATOR
diff_hunk: @@ -33,3 +33,21 @@ source_set("features") {
 
   public_deps = [ "//base" ]
 }
+
+source_set("browser_tests") {
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+  testonly = true
+  sources = [ "background_video_playback_tab_helper_browsertest.cc" ]
+
+  deps = [
+    ":background_video",
+    ":features",
+    "//brave/components/constants",
+    "//chrome/test:test_support",
+    "//net:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+
+  public_deps = [ "//base" ]
path: browser/android/background_video/BUILD.gn
user: login: simoarpe
body: Probably some bad copy pasta, I'm going to move it to deps.
created_at: 2025-05-05T14:24:13Z
author_association: COLLABORATOR
diff_hunk: @@ -19,17 +19,61 @@
 #include "url/gurl.h"
 
 namespace {
-constexpr char16_t kYoutubeBackgroundPlaybackScript[] =
-    u"(function() {"
-    "    if (document._addEventListener === undefined) {"
-    "        document._addEventListener = document.addEventListener;"
-    "        document.addEventListener = function(a,b,c) {"
-    "            if(a != 'visibilitychange') {"
-    "                document._addEventListener(a,b,c);"
-    "            }"
-    "        };"
-    "    }"
-    "}());";
+constexpr char16_t kYoutubeBackgroundPlaybackAndPipScript[] =
+    uR"(
+(function() {
+  if (document._addEventListener === undefined) {
+    document._addEventListener = document.addEventListener;
+    document.addEventListener = function(a, b, c) {
+      if (a != 'visibilitychange') {
+        document._addEventListener(a, b, c);
+      }
+    };
+  }
+
+  // Function to modify the flags if the target object exists.
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: simoarpe
body: Implemented new feature flag enabled by default 👉 [`5516694` (#28593)](https://github.com/brave/brave-core/pull/28593/commits/551669498548441c23481ef9ff786a01fc8f3bfd)
created_at: 2025-05-05T15:45:52Z
author_association: COLLABORATOR
diff_hunk: @@ -65,15 +125,19 @@ BackgroundVideoPlaybackTabHelper::BackgroundVideoPlaybackTabHelper(
 
 BackgroundVideoPlaybackTabHelper::~BackgroundVideoPlaybackTabHelper() {}
 
-void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
+void BackgroundVideoPlaybackTabHelper::PrimaryMainDocumentElementAvailable() {
+  content::WebContents* contents = web_contents();
   // Filter only YT domain here
-  if (!IsYouTubeDomain(web_contents()->GetLastCommittedURL())) {
+  if (!IsYouTubeDomain(contents->GetLastCommittedURL())) {
     return;
   }
-  if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
-    web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+  if (IsBackgroundVideoPlaybackEnabled(contents)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
+        kYoutubeBackgroundPlayback, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: github-actions[bot]
body: <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>[semgrep] ExecuteJavaScript usages should be vet by the security-team.<br/><br/>References:<br/>- https://github.com/brave/brave-browser/wiki/Security-reviews (point 12)<br/><br><br>Source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-execute-script.yaml<br><br><!-- Category: security --><br>Cc @thypon @diracdeltas @bridiver
<!-- __reviewdog__:ChA3ZGEyZDY0OTMxNTk2Y2Fj -->

created_at: 2025-05-05T15:51:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -65,15 +125,19 @@
 
 BackgroundVideoPlaybackTabHelper::~BackgroundVideoPlaybackTabHelper() {}
 
-void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
+void BackgroundVideoPlaybackTabHelper::PrimaryMainDocumentElementAvailable() {
+  content::WebContents* contents = web_contents();
   // Filter only YT domain here
-  if (!IsYouTubeDomain(web_contents()->GetLastCommittedURL())) {
+  if (!IsYouTubeDomain(contents->GetLastCommittedURL())) {
     return;
   }
-  if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
-    web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+  if (IsBackgroundVideoPlaybackEnabled(contents)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
+        kYoutubeBackgroundPlayback, base::NullCallback());
+  }
+  if (IsPictureInPictureForYouTubeVideosEnabled(contents)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
+        kYoutubePictureInPictureSupport, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: github-actions[bot]
body: <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>[semgrep] ExecuteJavaScript usages should be vet by the security-team.<br/><br/>References:<br/>- https://github.com/brave/brave-browser/wiki/Security-reviews (point 12)<br/><br><br>Source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-execute-script.yaml<br><br><!-- Category: security --><br>Cc @thypon @diracdeltas @bridiver
<!-- __reviewdog__:ChA4OTIxN2VhODI3ODkzY2Jk -->

created_at: 2025-05-05T15:51:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -55,6 +104,17 @@ bool IsBackgroundVideoPlaybackEnabled(content::WebContents* contents) {
 
   return true;
 }
+
+bool IsPictureInPictureForYouTubeVideosEnabled(content::WebContents* contents) {
+  if (!base::FeatureList::IsEnabled(
+          ::preferences::features::kBravePictureInPictureForYouTubeVideos)) {
+    return false;
+  }
+
+  content::RenderFrameHost::AllowInjectingJavaScript();
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: I don't know why `IsBackgroundVideoPlaybackEnabled` calls this internally, it only needs to be called once and can just happen in `PrimaryMainDocumentElementAvailable`
created_at: 2025-05-05T16:32:52Z
author_association: COLLABORATOR
diff_hunk: @@ -55,6 +104,17 @@ bool IsBackgroundVideoPlaybackEnabled(content::WebContents* contents) {
 
   return true;
 }
+
+bool IsPictureInPictureForYouTubeVideosEnabled(content::WebContents* contents) {
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: is this distinct from `IsBackgroundVideoPlaybackEnabled`? Would we inject this even if `IsBackgroundVideoPlaybackEnabled` returns false?
created_at: 2025-05-05T16:33:47Z
author_association: COLLABORATOR
diff_hunk: @@ -65,15 +113,21 @@ BackgroundVideoPlaybackTabHelper::BackgroundVideoPlaybackTabHelper(
 
 BackgroundVideoPlaybackTabHelper::~BackgroundVideoPlaybackTabHelper() {}
 
-void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
+void BackgroundVideoPlaybackTabHelper::PrimaryMainDocumentElementAvailable() {
+  content::WebContents* contents = web_contents();
   // Filter only YT domain here
-  if (!IsYouTubeDomain(web_contents()->GetLastCommittedURL())) {
+  if (!IsYouTubeDomain(contents->GetLastCommittedURL())) {
     return;
   }
-  if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
-    web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+  content::RenderFrameHost::AllowInjectingJavaScript();
+  if (IsBackgroundVideoPlaybackEnabled(contents)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
+        kYoutubeBackgroundPlayback, base::NullCallback());
+  }
+  if (base::FeatureList::IsEnabled(
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: bridiver
body: in a follow-up we need to update the tests to check that the script doesn't get injected when the feature is disabled
created_at: 2025-05-05T16:41:32Z
author_association: COLLABORATOR
diff_hunk: @@ -65,15 +113,21 @@ BackgroundVideoPlaybackTabHelper::BackgroundVideoPlaybackTabHelper(
 
 BackgroundVideoPlaybackTabHelper::~BackgroundVideoPlaybackTabHelper() {}
 
-void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
+void BackgroundVideoPlaybackTabHelper::PrimaryMainDocumentElementAvailable() {
+  content::WebContents* contents = web_contents();
   // Filter only YT domain here
-  if (!IsYouTubeDomain(web_contents()->GetLastCommittedURL())) {
+  if (!IsYouTubeDomain(contents->GetLastCommittedURL())) {
     return;
   }
-  if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
-    web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+  content::RenderFrameHost::AllowInjectingJavaScript();
+  if (IsBackgroundVideoPlaybackEnabled(contents)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
+        kYoutubeBackgroundPlayback, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: github-actions[bot]
body: <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>[semgrep] ExecuteJavaScript usages should be vet by the security-team.<br/><br/>References:<br/>- https://github.com/brave/brave-browser/wiki/Security-reviews (point 12)<br/><br><br>Source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-execute-script.yaml<br><br><!-- Category: security --><br>Cc @thypon @diracdeltas @bridiver
<!-- __reviewdog__:ChBhNTVhZjY2MDRhMWI3ZTRi -->

created_at: 2025-05-05T16:45:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -65,15 +113,21 @@
 
 BackgroundVideoPlaybackTabHelper::~BackgroundVideoPlaybackTabHelper() {}
 
-void BackgroundVideoPlaybackTabHelper::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
+void BackgroundVideoPlaybackTabHelper::PrimaryMainDocumentElementAvailable() {
+  content::WebContents* contents = web_contents();
   // Filter only YT domain here
-  if (!IsYouTubeDomain(web_contents()->GetLastCommittedURL())) {
+  if (!IsYouTubeDomain(contents->GetLastCommittedURL())) {
     return;
   }
-  if (IsBackgroundVideoPlaybackEnabled(web_contents())) {
-    web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(
-        kYoutubeBackgroundPlaybackScript, base::NullCallback());
+  content::RenderFrameHost::AllowInjectingJavaScript();
+  if (IsBackgroundVideoPlaybackEnabled(contents)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
+        kYoutubeBackgroundPlayback, base::NullCallback());
+  }
+  if (base::FeatureList::IsEnabled(
+          ::preferences::features::kBravePictureInPictureForYouTubeVideos)) {
+    contents->GetPrimaryMainFrame()->ExecuteJavaScript(
+        kYoutubePictureInPictureSupport, base::NullCallback());
path: browser/android/background_video/background_video_playback_tab_helper.cc
user: login: github-actions[bot]
body: <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>[semgrep] ExecuteJavaScript usages should be vet by the security-team.<br/><br/>References:<br/>- https://github.com/brave/brave-browser/wiki/Security-reviews (point 12)<br/><br><br>Source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-execute-script.yaml<br><br><!-- Category: security --><br>Cc @thypon @diracdeltas @bridiver
<!-- __reviewdog__:ChAyNGVkZTE2NzQ4ZTlmODg4 -->

created_at: 2025-05-05T16:45:24Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-16T17:17:53Z
body: Chromium major version is behind target branch (135.0.7049.84 vs 136.0.7103.25). Please rebase.
author_association: CONTRIBUTOR
user: login: simoarpe
created_at: 2025-04-17T09:43:20Z
body: @atuchin-m, feedback addressed. Ready for another review round.
author_association: COLLABORATOR
user: login: brave-builds
created_at: 2025-05-05T15:50:29Z
body: > [!WARNING]
> You have got a presubmit warning. Please address it if possible.

```
-------------------------
[Warning] chromium_src:
  Ignoring symbol BRAVE_AI_CHAT_FLAGS:
  Symbol is used internally in chromium_src/chrome/browser/flags/android/chrome_feature_list.cc
  Symbol is NOT found in /home/ubuntu/workspace/ore-build-pr-noplatform_PR-28593/src/chrome/browser/flags/android/chrome_feature_list.cc.
```

<!-- presubmit-hash=b45e6acf6bc76cd35a1209f5a550ba91858e3be2c5ca94538d32406b8278f722 -->
author_association: COLLABORATOR
user: login: brave-builds
created_at: 2025-05-05T15:50:30Z
body: > [!WARNING]
> You have got a presubmit warning. Please address it if possible.

```
-------------------------
[Warning] chromium_src:
  Ignoring symbol BRAVE_WEB_DISCOVERY_FLAG:
  Symbol is used internally in chromium_src/chrome/browser/flags/android/chrome_feature_list.cc
  Symbol is NOT found in /home/ubuntu/workspace/ore-build-pr-noplatform_PR-28593/src/chrome/browser/flags/android/chrome_feature_list.cc.
```

<!-- presubmit-hash=8c3c9d110e4333a6734d45398713b25e08e1f6e4ba0b62d790e20b6c9ff898fc -->
author_association: COLLABORATOR
user: login: brave-builds
created_at: 2025-05-05T15:50:30Z
body: > [!WARNING]
> You have got a presubmit warning. Please address it if possible.

```
-------------------------
[Warning] chromium_src:
  Ignoring symbol BRAVE_WEB_DISCOVERY_FLAG:
  Symbol is used internally in chromium_src/chrome/browser/flags/android/chrome_feature_list.cc
  Symbol is NOT found in /home/ubuntu/workspace/ore-build-pr-noplatform_PR-28593/src/chrome/browser/flags/android/chrome_feature_list.cc.
```

<!-- presubmit-hash=8c3c9d110e4333a6734d45398713b25e08e1f6e4ba0b62d790e20b6c9ff898fc -->
author_association: COLLABORATOR
user: login: brave-builds
created_at: 2025-05-06T16:10:15Z
body: Released in [v1.80.38](https://github.com/brave/brave-browser/releases/tag/v1.80.38)
author_association: COLLABORATOR
user: login: kjozwiak
created_at: 2025-05-13T20:14:48Z
body: Verification **PASSED** on `Pixel 6` running `Android 15` using the following build(s):

```
Brave | 1.80.56 Chromium: 136.0.7103.93 (Official Build) canary (64-bit)
--- | ---
Revision | 791ece49974c52e646063ef3b8d955f16f411fc2
OS | Android 15; Build/AP3A.240905.015.A2; 35; REL
```

Using the STR/Cases outlined via https://github.com/brave/brave-core/pull/28593#issue-2985777408, ensured that `PiP` was working on YT as per the following:

https://github.com/user-attachments/assets/9792f240-943e-414c-bbbe-a542b71fb435
author_association: MEMBER