Eliminates roundtrips between browser and renderer

This commit is contained in:
Szilard Szaloki
2024-02-28 15:21:45 -05:00
parent 3360882b75
commit ed1ffc55f2
29 changed files with 460 additions and 441 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ deps = {
'condition': 'checkout_android',
'dep_type': 'cipd',
},
"third_party/playlist_component/src": "https://github.com/brave/playlist-component.git@565d3ef2b31d8c7eb0d19b5d046f56b1d9e1fe01",
"third_party/playlist_component/src": "https://github.com/brave/playlist-component.git@673d40f017a1559bb685a15cf608ad1d4a94f8fb",
"third_party/rust/challenge_bypass_ristretto/v1/crate": "https://github.com/brave-intl/challenge-bypass-ristretto.git@48910ab1e0c6a52cd757abdc234aa4d6db214c09",
"third_party/rust/futures_retry/v0_5/crate": "https://github.com/brave-intl/futures-retry.git@2aaaafbc3d394661534d4dbd14159d164243c20e",
"third_party/macholib": {
@@ -136,6 +136,7 @@ public class PlaylistServiceObserverImpl implements PlaylistServiceObserver {
@Override
public void onMediaFilesUpdated(Url pageUrl, PlaylistItem[] items) {
if (items.length == 0) return;
if (mDelegate == null) return;
mDelegate.onMediaFilesUpdated(pageUrl, items);
}
@@ -1638,6 +1638,7 @@ public abstract class BraveToolbarLayoutImpl extends ToolbarLayout
@Override
public void onMediaFilesUpdated(Url pageUrl, PlaylistItem[] items) {
if (items.length == 0) return;
Tab currentTab = getToolbarDataProvider().getTab();
if (currentTab == null || !pageUrl.url.equals(currentTab.getUrl().getSpec())) {
return;
+1 -4
View File
@@ -267,10 +267,7 @@ void PlaylistServiceFactory::RegisterProfilePrefs(
PlaylistServiceFactory::PlaylistServiceFactory()
: BrowserContextKeyedServiceFactory(
"PlaylistService",
BrowserContextDependencyManager::GetInstance()) {
PlaylistDownloadRequestManager::SetPlaylistJavaScriptWorldId(
ISOLATED_WORLD_ID_BRAVE_INTERNAL);
}
BrowserContextDependencyManager::GetInstance()) {}
PlaylistServiceFactory::~PlaylistServiceFactory() = default;
@@ -20,7 +20,6 @@
#include "chrome/test/base/chrome_test_utils.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/schemeful_site.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
@@ -41,8 +40,6 @@ class PlaylistDownloadRequestManagerBrowserTest : public PlatformBrowserTest {
};
PlaylistDownloadRequestManagerBrowserTest() {
playlist::PlaylistDownloadRequestManager::SetPlaylistJavaScriptWorldId(
ISOLATED_WORLD_ID_BRAVE_INTERNAL);
scoped_feature_list_.InitAndEnableFeature(playlist::features::kPlaylist);
}
@@ -62,38 +59,25 @@ class PlaylistDownloadRequestManagerBrowserTest : public PlatformBrowserTest {
return item;
}
GURL LoadHTML(const std::string& html, GURL url = GURL()) {
GURL set_up_https_server(const std::string& html, GURL url = GURL()) {
const auto* test_info =
testing::UnitTest::GetInstance()->current_test_info();
VLOG(2) << __FUNCTION__ << test_info->name() << ": " << __func__;
VLOG(2) << test_info->name() << ": " << __FUNCTION__;
// Start server: ASSER_TRUE() has its own return type internally, thus
// encloses with lambda.
([&]() {
// ASSERT_*() only work in void functions
([&] {
if (https_server()->Started()) {
ASSERT_TRUE(https_server()->ShutdownAndWaitUntilComplete());
}
https_server()->RegisterRequestHandler(
base::BindRepeating(&PlaylistDownloadRequestManagerBrowserTest::Serve,
https_server(), html));
ASSERT_TRUE(https_server()->Start());
})();
// Load given |html| contents: ASSER_TRUE() has its own return type
// internally, thus encloesg with lambda.
GURL destination_url = url;
([&]() {
auto* active_web_contents = chrome_test_utils::GetActiveWebContents(this);
if (destination_url.is_valid()) {
destination_url = https_server()->GetURL(destination_url.host(),
destination_url.path());
} else {
destination_url = https_server()->GetURL("/test");
}
ASSERT_TRUE(content::NavigateToURL(active_web_contents, destination_url));
})();
return destination_url;
return url.is_valid() ? https_server()->GetURL(url.host(), url.path())
: https_server()->GetURL("/test");
}
void LoadHTMLAndCheckResult(const std::string& html,
@@ -103,20 +87,29 @@ class PlaylistDownloadRequestManagerBrowserTest : public PlatformBrowserTest {
testing::UnitTest::GetInstance()->current_test_info();
VLOG(2) << __FUNCTION__ << test_info->name() << ": " << __func__;
auto destination_url = LoadHTML(html, url);
auto* playlist_service =
playlist::PlaylistServiceFactory::GetForBrowserContext(
chrome_test_utils::GetProfile(this));
ASSERT_TRUE(playlist_service);
testing::NiceMock<MockPlaylistServiceObserver> observer;
playlist_service->AddObserver(observer.GetRemote());
const auto destination_url = set_up_https_server(html, url);
base::RunLoop run_loop;
EXPECT_CALL(observer, OnMediaFilesUpdated(testing::_, testing::_))
.WillOnce(
[&](const GURL&,
std::vector<playlist::mojom::PlaylistItemPtr> actual_items) {
OnGetMedia(test_info->name(), items,
url.is_valid() ? url.host() : destination_url.host(),
std::move(actual_items));
run_loop.Quit();
});
auto* active_web_contents = chrome_test_utils::GetActiveWebContents(this);
// Run script and find media files
ASSERT_FALSE(component_manager_->GetMediaDetectorScript({}).empty());
request_manager_->GetMedia(
active_web_contents,
base::BindOnce(&PlaylistDownloadRequestManagerBrowserTest::OnGetMedia,
base::Unretained(this), test_info->name(), items,
url.is_valid() ? url.host() : destination_url.host()));
// Block until result is received from OnGetMedia().
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
ASSERT_TRUE(content::NavigateToURL(active_web_contents, destination_url));
run_loop.Run();
}
protected:
@@ -214,9 +207,6 @@ class PlaylistDownloadRequestManagerBrowserTest : public PlatformBrowserTest {
a->thumbnail_path == b->thumbnail_path;
};
EXPECT_TRUE(base::ranges::equal(actual_items, expected_items, equal));
ASSERT_TRUE(run_loop_);
run_loop_->Quit();
}
base::test::ScopedFeatureList scoped_feature_list_;
@@ -224,8 +214,6 @@ class PlaylistDownloadRequestManagerBrowserTest : public PlatformBrowserTest {
raw_ptr<playlist::MediaDetectorComponentManager> component_manager_;
raw_ptr<playlist::PlaylistDownloadRequestManager> request_manager_;
std::unique_ptr<base::RunLoop> run_loop_;
content::ContentMockCertVerifier mock_cert_verifier_;
std::unique_ptr<net::EmbeddedTestServer> https_server_;
};
@@ -289,8 +277,14 @@ IN_PROC_BROWSER_TEST_F(PlaylistDownloadRequestManagerBrowserTest,
{});
}
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_YouTubeSpecificRetriever YouTubeSpecificRetriever
#else
#define MAYBE_YouTubeSpecificRetriever DISABLED_YouTubeSpecificRetriever
#endif
IN_PROC_BROWSER_TEST_F(PlaylistDownloadRequestManagerBrowserTest,
YouTubeSpecificRetriever) {
MAYBE_YouTubeSpecificRetriever) {
// Pre-conditions to decide site specific script
ASSERT_EQ(net::SchemefulSite(GURL("https://m.youtube.com")),
net::SchemefulSite(GURL("https://youtube.com")));
@@ -299,9 +293,6 @@ IN_PROC_BROWSER_TEST_F(PlaylistDownloadRequestManagerBrowserTest,
ASSERT_NE(net::SchemefulSite(GURL("http://m.youtube.com")),
net::SchemefulSite(GURL("https://m.youtube.com")));
// Getting JavaScript object requires to access the main world.
request_manager()->SetRunScriptOnMainWorldForTest();
// Check if we can retrieve metadata from youtube specific script.
LoadHTMLAndCheckResult(
R"html(
@@ -419,14 +410,14 @@ IN_PROC_BROWSER_TEST_F(PlaylistDownloadRequestManagerBrowserTest,
IN_PROC_BROWSER_TEST_F(PlaylistDownloadRequestManagerBrowserTest,
DynamicallyAddedMedia) {
testing::NiceMock<MockPlaylistServiceObserver> observer;
auto* playlist_service =
playlist::PlaylistServiceFactory::GetForBrowserContext(
chrome_test_utils::GetProfile(this));
ASSERT_TRUE(playlist_service);
testing::NiceMock<MockPlaylistServiceObserver> observer;
playlist_service->AddObserver(observer.GetRemote());
const auto& url = LoadHTML(
const auto url = set_up_https_server(
R"html(
<html>
<meta property="og:image" content="/img.jpg">
@@ -445,42 +436,20 @@ IN_PROC_BROWSER_TEST_F(PlaylistDownloadRequestManagerBrowserTest,
});
</script>
</html>
)html",
GURL("https://youtube.com") // For now, detecting dynamically added media
// files is done only for Background web
// contents. So we should pretend to be
// Youtube.
);
// At the first execution, we don't get any media files.
// And after that, we should find dynamically added media files and
// notify observers.
using testing::_;
base::MockOnceCallback<void(const GURL&,
std::vector<playlist::mojom::PlaylistItemPtr>)>
callback;
// Unfortunately, testing::ElementsAreArray does't seem to work with
// non-copyable When we we write
// EXPECT_CALL(callback, Run(url,
// testing::ElementsAreArray(
// empty_result.begin(), empty_result.empty())));
// It fails to compile. So as a workaround, checks it in an Action.
EXPECT_CALL(callback, Run(url, _))
.WillOnce([](const GURL& url,
std::vector<playlist::mojom::PlaylistItemPtr> items) {
EXPECT_TRUE(items.empty());
});
// This returns cached items that are found so far. They should be empty.
playlist_service->FindMediaFilesFromActiveTab(callback.Get());
)html");
base::RunLoop run_loop;
EXPECT_CALL(observer, OnMediaFilesUpdated(url, _))
.WillOnce([&](const GURL& page_url,
std::vector<playlist::mojom::PlaylistItemPtr> items) {
EXPECT_FALSE(items.empty());
run_loop.Quit();
});
testing::InSequence in_sequence;
EXPECT_CALL(observer, OnMediaFilesUpdated(url, testing::IsEmpty()))
.Times(testing::AtLeast(1));
EXPECT_CALL(observer,
OnMediaFilesUpdated(url, testing::Not(testing::IsEmpty())))
.WillOnce(
[&](const GURL&, std::vector<playlist::mojom::PlaylistItemPtr>) {
run_loop.Quit();
});
auto* active_web_contents = chrome_test_utils::GetActiveWebContents(this);
ASSERT_TRUE(content::NavigateToURL(active_web_contents, url));
run_loop.Run();
}
@@ -23,9 +23,9 @@ class PlaylistDownloadRequestManagerUnitTest : public testing::Test {
return *download_request_manager_;
}
std::vector<mojom::PlaylistItemPtr> ProcessFoundMedia(base::Value value) {
return manager().ProcessFoundMedia(std::move(value),
GURL("https://example.com"));
std::vector<mojom::PlaylistItemPtr> GetPlaylistItems(base::Value value) {
return manager().GetPlaylistItems(std::move(value),
GURL("https://example.com"));
}
// testing::Test:
@@ -100,20 +100,20 @@ TEST_F(PlaylistDownloadRequestManagerUnitTest,
}
TEST_F(PlaylistDownloadRequestManagerUnitTest,
ProcessFoundMedia_NoMediaDetected) {
GetPlaylistItems_NoMediaDetected) {
// When media detection script doesn't find any media, it returns an empty
// dict.
EXPECT_TRUE(ProcessFoundMedia(base::Value(base::Value::Type::DICT)).empty());
EXPECT_TRUE(GetPlaylistItems(base::Value(base::Value::Type::DICT)).empty());
}
TEST_F(PlaylistDownloadRequestManagerUnitTest, ProcessFoundMedia_InvalidValue) {
// ProcessFoundMedia only takes either list or dict
BASE_EXPECT_DEATH(ProcessFoundMedia(base::Value(base::Value::Type::BOOLEAN)),
TEST_F(PlaylistDownloadRequestManagerUnitTest, GetPlaylistItems_InvalidValue) {
// GetPlaylistItems only takes either list or dict
BASE_EXPECT_DEATH(GetPlaylistItems(base::Value(base::Value::Type::BOOLEAN)),
CHECK_WILL_STREAM() ? "Check failed: value\\.is_list" : "");
}
TEST_F(PlaylistDownloadRequestManagerUnitTest,
ProcessFoundMedia_RequiredProperties) {
GetPlaylistItems_RequiredProperties) {
base::Value value(base::Value::Dict()
.Set("name", "Video 1")
.Set("pageTitle", "Example page")
@@ -123,7 +123,7 @@ TEST_F(PlaylistDownloadRequestManagerUnitTest,
.Set("srcIsMediaSourceObjectURL", false));
std::vector<mojom::PlaylistItemPtr> result =
ProcessFoundMedia(base::Value(base::Value::List().Append(value.Clone())));
GetPlaylistItems(base::Value(base::Value::List().Append(value.Clone())));
EXPECT_EQ(result.size(), 1u);
EXPECT_FALSE(result[0]->id.empty());
EXPECT_EQ(result[0]->name, "Video 1");
@@ -139,14 +139,14 @@ TEST_F(PlaylistDownloadRequestManagerUnitTest,
"src", "srcIsMediaSourceObjectURL"}) {
auto invalid_value = value.Clone();
invalid_value.GetDict().Set(required_property, base::Value());
EXPECT_TRUE(ProcessFoundMedia(base::Value(base::Value::List().Append(
std::move(invalid_value))))
EXPECT_TRUE(GetPlaylistItems(base::Value(base::Value::List().Append(
std::move(invalid_value))))
.empty());
}
}
TEST_F(PlaylistDownloadRequestManagerUnitTest,
ProcessFoundMedia_OptionalProperties) {
GetPlaylistItems_OptionalProperties) {
base::Value value(base::Value::Dict()
.Set("name", "Video 1")
.Set("pageTitle", "Example page")
@@ -159,7 +159,7 @@ TEST_F(PlaylistDownloadRequestManagerUnitTest,
.Set("duration", 1234.0));
std::vector<mojom::PlaylistItemPtr> result =
ProcessFoundMedia(base::Value(base::Value::List().Append(value.Clone())));
GetPlaylistItems(base::Value(base::Value::List().Append(value.Clone())));
EXPECT_EQ(result.size(), 1u);
EXPECT_FALSE(result[0]->id.empty());
EXPECT_EQ(result[0]->author, "Me");
@@ -172,7 +172,7 @@ TEST_F(PlaylistDownloadRequestManagerUnitTest,
}
TEST_F(PlaylistDownloadRequestManagerUnitTest,
ProcessFoundMedia_MediaSourceScheme) {
GetPlaylistItems_MediaSourceScheme) {
base::Value value(base::Value::Dict()
.Set("name", "Video 1")
.Set("pageTitle", "Example page")
@@ -182,32 +182,32 @@ TEST_F(PlaylistDownloadRequestManagerUnitTest,
.Set("srcIsMediaSourceObjectURL", false));
EXPECT_FALSE(
ProcessFoundMedia(base::Value(base::Value::List().Append(value.Clone())))
GetPlaylistItems(base::Value(base::Value::List().Append(value.Clone())))
.empty());
// http:// scheme is not allowed.
value.GetDict().Set("src", "http://example.com/12345");
EXPECT_TRUE(
ProcessFoundMedia(base::Value(base::Value::List().Append(value.Clone())))
GetPlaylistItems(base::Value(base::Value::List().Append(value.Clone())))
.empty());
// blob: that's not backed by MediaSource is not allowed
value.GetDict().Set("src", "blob:https://example.com/12345");
EXPECT_TRUE(
ProcessFoundMedia(base::Value(base::Value::List().Append(value.Clone())))
GetPlaylistItems(base::Value(base::Value::List().Append(value.Clone())))
.empty());
// blob: that's backed by MediaSource but from unknown source is not allowed
value.GetDict().Set("src", "blob:https://example.com/12345");
value.GetDict().Set("srcIsMediaSourceObjectURL", true);
EXPECT_TRUE(
ProcessFoundMedia(base::Value(base::Value::List().Append(value.Clone())))
GetPlaylistItems(base::Value(base::Value::List().Append(value.Clone())))
.empty());
// blob: that's backed by MediaSource and from known source is allowed
value.GetDict().Set("src", "blob:https://youtube.com/12345");
EXPECT_FALSE(
ProcessFoundMedia(base::Value(base::Value::List().Append(value.Clone())))
GetPlaylistItems(base::Value(base::Value::List().Append(value.Clone())))
.empty());
}
@@ -23,8 +23,6 @@ bool StructTraits<blink::mojom::WebPreferencesDataView,
return false;
}
out->force_cosmetic_filtering = data.force_cosmetic_filtering();
out->hide_media_src_api = data.hide_media_src_api();
out->should_detect_media_files = data.should_detect_media_files();
return true;
}
@@ -26,8 +26,6 @@ struct BLINK_COMMON_EXPORT WebPreferences : public WebPreferences_ChromiumImpl {
WebPreferences& operator=(WebPreferences&& other);
bool force_cosmetic_filtering = false;
bool hide_media_src_api = false;
bool should_detect_media_files = false;
};
} // namespace web_pref
@@ -27,15 +27,6 @@ struct BLINK_COMMON_EXPORT StructTraits<blink::mojom::WebPreferencesDataView,
return r.force_cosmetic_filtering;
}
static bool hide_media_src_api(const blink::web_pref::WebPreferences& r) {
return r.hide_media_src_api;
}
static bool should_detect_media_files(
const blink::web_pref::WebPreferences& r) {
return r.should_detect_media_files;
}
static bool Read(blink::mojom::WebPreferencesDataView r,
blink::web_pref::WebPreferences* out);
};
@@ -11,14 +11,4 @@ struct WebPreferences {
// regardless of contents settings. This is used for Playlist so that it can
// remove ads from the page.
bool force_cosmetic_filtering;
// If |hide_media_src_api| is true, PlaylistRenderFrameObserver will hide
// the media source API from the page. This is used by Playlist so that
// it can get downloadable media urls instead of blob url.
bool hide_media_src_api;
// If |should_detect_media_files| is true, PlaylistRenderFrameObserver will
// try to inject JavaScript to detect <video> and <audio> tag, that are
// dynamically attached.
bool should_detect_media_files;
};
+2 -1
View File
@@ -16,6 +16,8 @@ static_library("browser") {
"media_detector_component_installer.h",
"media_detector_component_manager.cc",
"media_detector_component_manager.h",
"playlist_background_webcontents_helper.cc",
"playlist_background_webcontents_helper.h",
"playlist_constants.h",
"playlist_download_request_manager.cc",
"playlist_download_request_manager.h",
@@ -50,7 +52,6 @@ static_library("browser") {
"//brave/components/p3a_utils",
"//brave/components/resources:static_resources",
"//brave/components/resources:strings",
"//brave/components/script_injector/common/mojom",
"//components/component_updater",
"//components/download/public/common:public",
"//components/keyed_service/core",
@@ -25,24 +25,33 @@ using ScriptName = base::FilePath::StringType;
using ScriptToSchemefulSiteMap = base::flat_map<ScriptName, net::SchemefulSite>;
using ScriptToResourceIdMap = base::flat_map<ScriptName, int>;
const base::FilePath::StringType& GetMediaSourceAPISuppressorScriptName() {
static const base::NoDestructor kMediaSourceApiSuppressor(
ScriptName(FILE_PATH_LITERAL("media_source_api_suppressor.js")));
return *kMediaSourceApiSuppressor;
}
const base::FilePath::StringType& GetBaseScriptName() {
static const base::NoDestructor base_script(
static const base::NoDestructor kBaseScript(
ScriptName(FILE_PATH_LITERAL("index.js")));
return *base_script;
return *kBaseScript;
}
const ScriptToSchemefulSiteMap& GetScriptNameToSchemefulSiteMap() {
static const base::NoDestructor script_name_to_schemeful_sites(
static const base::NoDestructor kScriptNameToSchemefulSites(
ScriptToSchemefulSiteMap{
{FILE_PATH_LITERAL("youtube.com.js"),
net::SchemefulSite(GURL("https://youtube.com"))}});
return *script_name_to_schemeful_sites;
return *kScriptNameToSchemefulSites;
}
base::flat_map<ScriptName, std::string> GetLocalScriptMap() {
const auto& rb = ui::ResourceBundle::GetSharedInstance();
return {
{GetMediaSourceAPISuppressorScriptName(),
std::string(rb.LoadDataResourceString(
IDR_PLAYLIST_MEDIA_SOURCE_API_SUPPRESSOR_JS))},
{GetBaseScriptName(),
std::string(rb.LoadDataResourceString(IDR_PLAYLIST_MEDIA_DETECTOR_JS))},
{FILE_PATH_LITERAL("youtube.com.js"),
@@ -119,7 +128,8 @@ void MediaDetectorComponentManager::RegisterIfNeeded() {
void MediaDetectorComponentManager::OnComponentReady(
const base::FilePath& install_path) {
base::flat_set<base::FilePath> files(
{install_path.Append(GetBaseScriptName())});
{install_path.Append(GetMediaSourceAPISuppressorScriptName()),
install_path.Append(GetBaseScriptName())});
for (const auto& [file, _] : GetScriptNameToSchemefulSiteMap()) {
files.insert(install_path.Append(file));
}
@@ -138,7 +148,11 @@ void MediaDetectorComponentManager::OnGetScripts(
return;
}
DCHECK(script_map.count(GetBaseScriptName()));
CHECK(script_map.contains(GetMediaSourceAPISuppressorScriptName()));
media_source_api_suppressor_ =
script_map.at(GetMediaSourceAPISuppressorScriptName());
CHECK(script_map.contains(GetBaseScriptName()));
base_script_ = script_map.at(GetBaseScriptName());
// This could have been filled when we've used media detector script before
@@ -147,7 +161,7 @@ void MediaDetectorComponentManager::OnGetScripts(
const auto& schemeful_site_map = GetScriptNameToSchemefulSiteMap();
for (const auto& [script_name, script] : script_map) {
if (schemeful_site_map.count(script_name)) {
if (schemeful_site_map.contains(script_name)) {
site_specific_detectors_[schemeful_site_map.at(script_name)] = script;
}
}
@@ -172,6 +186,13 @@ bool MediaDetectorComponentManager::ShouldHideMediaSrcAPI(
});
}
const std::string&
MediaDetectorComponentManager::GetMediaSourceAPISuppressorScript() {
MaybeInitScripts();
CHECK(!media_source_api_suppressor_.empty());
return media_source_api_suppressor_;
}
std::string MediaDetectorComponentManager::GetMediaDetectorScript(
const GURL& url) {
MaybeInitScripts();
@@ -45,6 +45,8 @@ class MediaDetectorComponentManager {
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
const std::string& GetMediaSourceAPISuppressorScript();
// Returns a script to get media from page. If the script isn't fetched
// from component yet, will return a local script.
std::string GetMediaDetectorScript(const GURL& url);
@@ -72,6 +74,7 @@ class MediaDetectorComponentManager {
bool register_requested_ = false;
raw_ptr<component_updater::ComponentUpdateService> component_update_service_;
std::string media_source_api_suppressor_;
std::string base_script_;
std::vector<net::SchemefulSite> sites_to_hide_media_src_api_;
@@ -0,0 +1,55 @@
/* Copyright (c) 2024 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 "brave/components/playlist/browser/playlist_background_webcontents_helper.h"
#include "base/logging.h"
#include "brave/components/playlist/common/mojom/playlist.mojom.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
namespace playlist {
PlaylistBackgroundWebContentsHelper::~PlaylistBackgroundWebContentsHelper() =
default;
void PlaylistBackgroundWebContentsHelper::ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) {
DVLOG(2) << __FUNCTION__;
DCHECK(navigation_handle);
if (!navigation_handle->IsInPrimaryMainFrame()) {
return;
}
const GURL url = navigation_handle->GetURL();
if (!url.SchemeIsHTTPOrHTTPS()) {
return;
}
mojo::AssociatedRemote<mojom::PlaylistRenderFrameObserverConfigurator>
frame_observer_config;
navigation_handle->GetRenderFrameHost()
->GetRemoteAssociatedInterfaces()
->GetInterface(&frame_observer_config);
frame_observer_config->AddMediaSourceAPISuppressor(
media_source_api_suppressor_);
frame_observer_config->AddMediaDetector(media_detector_);
}
PlaylistBackgroundWebContentsHelper::PlaylistBackgroundWebContentsHelper(
content::WebContents* web_contents,
const std::string& media_source_api_suppressor,
const std::string& media_detector)
: content::WebContentsUserData<PlaylistBackgroundWebContentsHelper>(
*web_contents),
content::WebContentsObserver(web_contents),
media_source_api_suppressor_(media_source_api_suppressor),
media_detector_(media_detector) {}
WEB_CONTENTS_USER_DATA_KEY_IMPL(PlaylistBackgroundWebContentsHelper);
} // namespace playlist
@@ -0,0 +1,52 @@
/* Copyright (c) 2024 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/. */
#ifndef BRAVE_COMPONENTS_PLAYLIST_BROWSER_PLAYLIST_BACKGROUND_WEBCONTENTS_HELPER_H_
#define BRAVE_COMPONENTS_PLAYLIST_BROWSER_PLAYLIST_BACKGROUND_WEBCONTENTS_HELPER_H_
#include <string>
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
namespace content {
class NavigationHandle;
class WebContents;
} // namespace content
namespace playlist {
class PlaylistBackgroundWebContentsHelper final
: public content::WebContentsUserData<PlaylistBackgroundWebContentsHelper>,
public content::WebContentsObserver {
public:
PlaylistBackgroundWebContentsHelper(
const PlaylistBackgroundWebContentsHelper&) = delete;
PlaylistBackgroundWebContentsHelper& operator=(
const PlaylistBackgroundWebContentsHelper&) = delete;
~PlaylistBackgroundWebContentsHelper() override;
// content::WebContentsObserver:
void ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) override;
private:
friend class content::WebContentsUserData<
PlaylistBackgroundWebContentsHelper>;
PlaylistBackgroundWebContentsHelper(
content::WebContents* web_contents,
const std::string& media_source_api_suppressor,
const std::string& media_detector);
std::string media_source_api_suppressor_;
std::string media_detector_;
WEB_CONTENTS_USER_DATA_KEY_DECL();
};
} // namespace playlist
#endif // BRAVE_COMPONENTS_PLAYLIST_BROWSER_PLAYLIST_BACKGROUND_WEBCONTENTS_HELPER_H_
@@ -14,6 +14,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "brave/components/playlist/browser/playlist_background_webcontents_helper.h"
#include "brave/components/playlist/browser/playlist_service.h"
#include "brave/components/playlist/browser/playlist_tab_helper.h"
#include "brave/components/playlist/common/features.h"
@@ -31,19 +32,6 @@
namespace playlist {
namespace {
constexpr int32_t kInvalidWorldID = -1;
int32_t g_playlist_javascript_world_id = kInvalidWorldID;
bool g_run_script_on_main_world = false;
bool PlaylistJavaScriptWorldIdIsSet() {
return g_playlist_javascript_world_id != kInvalidWorldID;
}
} // namespace
PlaylistDownloadRequestManager::Request::Request() = default;
PlaylistDownloadRequestManager::Request&
PlaylistDownloadRequestManager::Request::operator=(
@@ -52,24 +40,6 @@ PlaylistDownloadRequestManager::Request::Request(
PlaylistDownloadRequestManager::Request&&) noexcept = default;
PlaylistDownloadRequestManager::Request::~Request() = default;
// static
void PlaylistDownloadRequestManager::SetPlaylistJavaScriptWorldId(
const int32_t id) {
// Never allow running in main world (0).
CHECK(id > content::ISOLATED_WORLD_ID_CONTENT_END);
// Only allow ID to be set once.
if (PlaylistJavaScriptWorldIdIsSet()) {
CHECK_IS_TEST();
}
g_playlist_javascript_world_id = id;
}
// static
void PlaylistDownloadRequestManager::SetRunScriptOnMainWorldForTest() {
CHECK_IS_TEST();
g_run_script_on_main_world = true;
}
PlaylistDownloadRequestManager::PlaylistDownloadRequestManager(
PlaylistService* service,
content::BrowserContext* context,
@@ -80,15 +50,16 @@ PlaylistDownloadRequestManager::PlaylistDownloadRequestManager(
PlaylistDownloadRequestManager::~PlaylistDownloadRequestManager() = default;
void PlaylistDownloadRequestManager::CreateWebContents(
bool should_force_fake_ua) {
void PlaylistDownloadRequestManager::CreateWebContents(const Request& request) {
content::WebContents::CreateParams create_params(context_, nullptr);
create_params.is_never_visible = true;
web_contents_ = content::WebContents::Create(create_params);
web_contents_->SetAudioMuted(true);
PlaylistTabHelper::MaybeCreateForWebContents(web_contents_.get(),
service_.get());
if (should_force_fake_ua ||
PlaylistBackgroundWebContentsHelper::CreateForWebContents(
web_contents_.get(),
media_detector_component_manager_->GetMediaSourceAPISuppressorScript(),
media_detector_component_manager_->GetMediaDetectorScript(request.url));
if (request.should_force_fake_ua ||
base::FeatureList::IsEnabled(features::kPlaylistFakeUA)) {
DVLOG(2) << __func__ << " Faked UA to detect media files";
blink::UserAgentOverride user_agent(
@@ -136,7 +107,6 @@ void PlaylistDownloadRequestManager::FetchPendingRequest() {
void PlaylistDownloadRequestManager::RunMediaDetector(Request request) {
DVLOG(2) << __func__;
CHECK(PlaylistJavaScriptWorldIdIsSet());
DCHECK_GE(in_progress_urls_count_, 0);
in_progress_urls_count_++;
@@ -150,7 +120,7 @@ void PlaylistDownloadRequestManager::RunMediaDetector(Request request) {
// Start to request on clean slate, so that result won't be affected by
// previous page.
CreateWebContents(request.should_force_fake_ua);
CreateWebContents(request);
DCHECK(request.url.is_valid());
DCHECK(web_contents_);
@@ -177,54 +147,14 @@ bool PlaylistDownloadRequestManager::ReadyToRunMediaDetectorScript() const {
return in_progress_urls_count_ == 0;
}
void PlaylistDownloadRequestManager::GetMedia(
content::WebContents* contents,
base::OnceCallback<void(std::vector<mojom::PlaylistItemPtr>)> cb) {
void PlaylistDownloadRequestManager::
MaybeResetBackgroundWebContentsAndFetchNextRequest(
const std::vector<mojom::PlaylistItemPtr>& items,
content::WebContents* contents) {
DVLOG(2) << __func__;
CHECK(contents && contents->GetPrimaryMainFrame());
CHECK(contents);
const auto& media_detector_script =
media_detector_component_manager_->GetMediaDetectorScript(
contents->GetLastCommittedURL());
DCHECK(!media_detector_script.empty());
auto callback = base::BindOnce(
&PlaylistDownloadRequestManager::OnGetMedia, weak_factory_.GetWeakPtr(),
contents->GetWeakPtr(), contents->GetLastCommittedURL(), std::move(cb));
#if BUILDFLAG(IS_ANDROID)
content::RenderFrameHost::AllowInjectingJavaScript();
PlaylistTabHelper::FromWebContents(contents)->RequestAsyncExecuteScript(
content::ISOLATED_WORLD_ID_GLOBAL /* main_world*/,
base::UTF8ToUTF16(media_detector_script), std::move(callback));
#else
if (g_run_script_on_main_world) {
PlaylistTabHelper::FromWebContents(contents)->RequestAsyncExecuteScript(
content::ISOLATED_WORLD_ID_GLOBAL /* main_world*/,
base::UTF8ToUTF16(media_detector_script), std::move(callback));
} else {
CHECK(PlaylistJavaScriptWorldIdIsSet());
PlaylistTabHelper::FromWebContents(contents)->RequestAsyncExecuteScript(
g_playlist_javascript_world_id,
base::UTF8ToUTF16(media_detector_script), std::move(callback));
}
#endif
}
void PlaylistDownloadRequestManager::OnGetMedia(
base::WeakPtr<content::WebContents> contents,
GURL url,
base::OnceCallback<void(std::vector<mojom::PlaylistItemPtr>)> cb,
base::Value value) {
if (!contents) {
return;
}
DVLOG(2) << __func__;
auto items = ProcessFoundMedia(std::move(value), url);
if (contents.get() == background_contents() && items.size()) {
if (contents == background_contents() && items.size()) {
CHECK(!callback_for_current_request_.is_null()) << " callback already ran";
auto callback = std::move(callback_for_current_request_);
@@ -239,14 +169,12 @@ void PlaylistDownloadRequestManager::OnGetMedia(
web_contents_.reset();
}
std::move(cb).Run(std::move(items));
FetchPendingRequest();
}
std::vector<mojom::PlaylistItemPtr>
PlaylistDownloadRequestManager::ProcessFoundMedia(base::Value value,
GURL page_url) {
PlaylistDownloadRequestManager::GetPlaylistItems(base::Value value,
GURL page_url) {
/* Expected output:
[
{
@@ -395,22 +323,6 @@ bool PlaylistDownloadRequestManager::
<< "CanCacheMedia() should be true when this method is called";
}
void PlaylistDownloadRequestManager::ConfigureWebPrefsForBackgroundWebContents(
content::WebContents* web_contents,
blink::web_pref::WebPreferences* web_prefs) {
if (!service_->playlist_enabled()) {
return;
}
web_prefs->should_detect_media_files = true;
if (web_contents_ && web_contents_.get() == web_contents) {
// Background web contents.
web_prefs->force_cosmetic_filtering = true;
web_prefs->hide_media_src_api = true;
}
}
void PlaylistDownloadRequestManager::ResetRequests() {
if (web_contents_) {
web_contents_.reset();
@@ -425,7 +337,7 @@ void PlaylistDownloadRequestManager::ResetRequests() {
content::WebContents*
PlaylistDownloadRequestManager::GetBackgroundWebContentsForTesting() {
if (!web_contents_) {
CreateWebContents(false);
CreateWebContents();
}
return web_contents_.get();
@@ -23,10 +23,6 @@ namespace base {
class Value;
} // namespace base
namespace blink::web_pref {
struct WebPreferences;
} // namespace blink::web_pref
namespace content {
class BrowserContext;
} // namespace content
@@ -39,9 +35,6 @@ class PlaylistService;
// by injecting media detector script to dedicated WebContents.
class PlaylistDownloadRequestManager {
public:
static void SetRunScriptOnMainWorldForTest();
static void SetPlaylistJavaScriptWorldId(const int32_t id);
struct Request {
using Callback =
base::OnceCallback<void(std::vector<mojom::PlaylistItemPtr>)>;
@@ -72,10 +65,6 @@ class PlaylistDownloadRequestManager {
// Request::callback will be called with generated param.
virtual void GetMediaFilesFromPage(Request request);
// Update |web_prefs| if we want for |web_contents|.
void ConfigureWebPrefsForBackgroundWebContents(
content::WebContents* web_contents,
blink::web_pref::WebPreferences* web_prefs);
void ResetRequests();
const content::WebContents* background_contents() const {
@@ -93,9 +82,11 @@ class PlaylistDownloadRequestManager {
return media_detector_component_manager_;
}
void GetMedia(
content::WebContents* contents,
base::OnceCallback<void(std::vector<mojom::PlaylistItemPtr>)> cb);
std::vector<mojom::PlaylistItemPtr> GetPlaylistItems(base::Value value,
GURL page_url);
void MaybeResetBackgroundWebContentsAndFetchNextRequest(
const std::vector<mojom::PlaylistItemPtr>& items,
content::WebContents* contents);
bool CanCacheMedia(const mojom::PlaylistItemPtr& item) const;
bool ShouldExtractMediaFromBackgroundWebContents(
@@ -110,14 +101,7 @@ class PlaylistDownloadRequestManager {
void RunMediaDetector(Request request);
bool ReadyToRunMediaDetectorScript() const;
void CreateWebContents(bool should_force_fake_ua);
void OnGetMedia(
base::WeakPtr<content::WebContents> contents,
GURL url,
base::OnceCallback<void(std::vector<mojom::PlaylistItemPtr>)> cb,
base::Value value);
std::vector<mojom::PlaylistItemPtr> ProcessFoundMedia(base::Value value,
GURL page_url);
void CreateWebContents(const Request& request = {});
// Pop a task from queue and detect media from the page if any.
void FetchPendingRequest();
@@ -5,6 +5,8 @@
#include "brave/components/playlist/browser/playlist_media_handler.h"
#include <utility>
#include "brave/components/playlist/browser/playlist_service.h"
namespace playlist {
@@ -20,8 +22,13 @@ PlaylistMediaHandler::~PlaylistMediaHandler() {
DVLOG(2) << __FUNCTION__ << " " << frame_id_;
}
void PlaylistMediaHandler::OnMediaUpdatedFromRenderFrame() {
void PlaylistMediaHandler::OnMediaDetected(base::Value media) {
DVLOG(2) << __FUNCTION__ << " " << frame_id_;
if (!service_) {
return;
}
auto* render_frame_host = content::RenderFrameHost::FromID(frame_id_);
if (!render_frame_host) {
return;
@@ -33,9 +40,7 @@ void PlaylistMediaHandler::OnMediaUpdatedFromRenderFrame() {
return;
}
if (service_) {
service_->OnMediaUpdatedFromContents(web_contents);
}
service_->OnMediaDetected(std::move(media), web_contents);
}
} // namespace playlist
@@ -7,6 +7,7 @@
#define BRAVE_COMPONENTS_PLAYLIST_BROWSER_PLAYLIST_MEDIA_HANDLER_H_
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "brave/components/playlist/common/mojom/playlist.mojom.h"
#include "content/public/browser/global_routing_id.h"
@@ -20,7 +21,7 @@ class PlaylistMediaHandler : public mojom::PlaylistMediaHandler {
~PlaylistMediaHandler() override;
// mojom::PlaylistMediaHandler:
void OnMediaUpdatedFromRenderFrame() override;
void OnMediaDetected(base::Value media) override;
private:
content::GlobalRenderFrameHostId frame_id_;
+20 -14
View File
@@ -18,6 +18,7 @@
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/task/thread_pool.h"
#include "brave/components/playlist/browser/playlist_background_webcontents_helper.h"
#include "brave/components/playlist/browser/playlist_constants.h"
#include "brave/components/playlist/browser/playlist_tab_helper.h"
#include "brave/components/playlist/browser/pref_names.h"
@@ -28,6 +29,7 @@
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_context.h"
#include "net/base/filename_util.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
namespace playlist {
namespace {
@@ -364,11 +366,7 @@ void PlaylistService::NotifyPlaylistChanged(mojom::PlaylistEvent playlist_event,
void PlaylistService::NotifyMediaFilesUpdated(
const GURL& url,
std::vector<mojom::PlaylistItemPtr> items) {
if (items.empty()) {
return;
}
const std::vector<mojom::PlaylistItemPtr>& items) {
DVLOG(2) << __FUNCTION__ << " Media files from " << url.spec()
<< " were updated: count =>" << items.size();
@@ -417,12 +415,11 @@ void PlaylistService::ConfigureWebPrefsForBackgroundWebContents(
return;
}
if (!PlaylistTabHelper::FromWebContents(web_contents)) {
if (!PlaylistBackgroundWebContentsHelper::FromWebContents(web_contents)) {
return;
}
download_request_manager_->ConfigureWebPrefsForBackgroundWebContents(
web_contents, web_prefs);
web_prefs->force_cosmetic_filtering = true;
}
base::WeakPtr<PlaylistService> PlaylistService::GetWeakPtr() {
@@ -532,6 +529,11 @@ bool PlaylistService::ShouldExtractMediaFromBackgroundWebContents(
});
}
std::string PlaylistService::GetMediaDetectorScript(const GURL& url) const {
return download_request_manager_->media_detector_component_manager()
->GetMediaDetectorScript(url);
}
void PlaylistService::AddMediaFilesFromActiveTabToPlaylist(
const std::string& playlist_id,
bool can_cache,
@@ -1198,16 +1200,20 @@ void PlaylistService::AddObserver(
observers_.Add(std::move(observer));
}
void PlaylistService::OnMediaUpdatedFromContents(
content::WebContents* contents) {
void PlaylistService::OnMediaDetected(base::Value media,
content::WebContents* contents) {
if (!*enabled_pref_) {
return;
}
download_request_manager_->GetMedia(
contents, base::BindOnce(&PlaylistService::NotifyMediaFilesUpdated,
weak_factory_.GetWeakPtr(),
contents->GetLastCommittedURL()));
CHECK(contents);
const GURL url = contents->GetLastCommittedURL();
const auto items =
download_request_manager_->GetPlaylistItems(std::move(media), url);
NotifyMediaFilesUpdated(url, items);
download_request_manager_->MaybeResetBackgroundWebContentsAndFetchNextRequest(
items, contents);
}
void PlaylistService::OnMediaFileDownloadProgressed(
@@ -217,7 +217,7 @@ class PlaylistService : public KeyedService,
void AddObserver(
mojo::PendingRemote<mojom::PlaylistServiceObserver> observer) override;
void OnMediaUpdatedFromContents(content::WebContents* contents);
void OnMediaDetected(base::Value media, content::WebContents* contents);
bool HasPlaylistItem(const std::string& id) const;
@@ -239,6 +239,8 @@ class PlaylistService : public KeyedService,
bool playlist_enabled() const { return *enabled_pref_; }
std::string GetMediaDetectorScript(const GURL& url) const;
private:
friend class ::CosmeticFilteringPlaylistFlagEnabledTest;
friend class ::PlaylistBrowserTest;
@@ -309,8 +311,9 @@ class PlaylistService : public KeyedService,
// https://github.com/brave/brave-browser/issues/30735
void NotifyPlaylistChanged(mojom::PlaylistEvent playlist_event,
const std::string& playlist_id);
void NotifyMediaFilesUpdated(const GURL& url,
std::vector<mojom::PlaylistItemPtr> items);
void NotifyMediaFilesUpdated(
const GURL& url,
const std::vector<mojom::PlaylistItemPtr>& items);
void UpdatePlaylistItemValue(const std::string& id, base::Value value);
void RemovePlaylistItemValue(const std::string& id);
@@ -19,6 +19,7 @@
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/base/l10n/l10n_util.h"
@@ -182,14 +183,26 @@ void PlaylistTabHelper::ExtractMediaFromBackgroundWebContents(
ExtractMediaFromBackgroundContents();
}
void PlaylistTabHelper::RequestAsyncExecuteScript(
int32_t world_id,
const std::u16string& script,
base::OnceCallback<void(base::Value)> cb) {
GetRemote(web_contents()->GetPrimaryMainFrame())
->RequestAsyncExecuteScript(
world_id, script, blink::mojom::UserActivationOption::kActivate,
blink::mojom::PromiseResultOption::kAwait, std::move(cb));
void PlaylistTabHelper::ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) {
DVLOG(2) << __FUNCTION__;
if (!navigation_handle->IsInPrimaryMainFrame()) {
return;
}
const GURL url = navigation_handle->GetURL();
if (!url.SchemeIsHTTPOrHTTPS()) {
return;
}
mojo::AssociatedRemote<mojom::PlaylistRenderFrameObserverConfigurator>
frame_observer_config;
navigation_handle->GetRenderFrameHost()
->GetRemoteAssociatedInterfaces()
->GetInterface(&frame_observer_config);
frame_observer_config->AddMediaDetector(
service_->GetMediaDetectorScript(url));
}
void PlaylistTabHelper::PrimaryPageChanged(content::Page& page) {
@@ -279,6 +292,10 @@ void PlaylistTabHelper::OnItemLocalDataDeleted(const std::string& id) {
void PlaylistTabHelper::OnMediaFilesUpdated(
const GURL& url,
std::vector<mojom::PlaylistItemPtr> items) {
if (items.empty()) {
return;
}
OnFoundMediaFromContents(url, std::move(items));
}
@@ -480,19 +497,6 @@ void PlaylistTabHelper::OnPlaylistEnabledPrefChanged() {
}
}
mojo::AssociatedRemote<script_injector::mojom::ScriptInjector>&
PlaylistTabHelper::GetRemote(content::RenderFrameHost* rfh) {
if (rfh != script_injector_rfh_ || !script_injector_remote_.is_bound()) {
script_injector_rfh_ = rfh;
if (script_injector_remote_.is_bound()) {
script_injector_remote_.reset();
}
rfh->GetRemoteAssociatedInterfaces()->GetInterface(
&script_injector_remote_);
}
return script_injector_remote_;
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(PlaylistTabHelper);
} // namespace playlist
@@ -12,11 +12,9 @@
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "brave/components/playlist/common/mojom/playlist.mojom.h"
#include "brave/components/script_injector/common/mojom/script_injector.mojom.h"
#include "components/prefs/pref_member.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace playlist {
@@ -69,11 +67,9 @@ class PlaylistTabHelper
void ExtractMediaFromBackgroundWebContents(
base::OnceCallback<void(bool)> extracted_callback);
void RequestAsyncExecuteScript(int32_t world_id,
const std::u16string& script,
base::OnceCallback<void(base::Value)> cb);
// content::WebContentsObserver:
void ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) override;
void PrimaryPageChanged(content::Page& page) override;
// mojom::PlaylistServiceObserver:
@@ -119,9 +115,6 @@ class PlaylistTabHelper
void OnPlaylistEnabledPrefChanged();
mojo::AssociatedRemote<script_injector::mojom::ScriptInjector>& GetRemote(
content::RenderFrameHost* rfh);
raw_ptr<PlaylistService> service_;
GURL target_url_;
@@ -143,10 +136,6 @@ class PlaylistTabHelper
BooleanPrefMember playlist_enabled_pref_;
mojo::AssociatedRemote<script_injector::mojom::ScriptInjector>
script_injector_remote_;
raw_ptr<content::RenderFrameHost> script_injector_rfh_ = nullptr;
base::WeakPtrFactory<PlaylistTabHelper> weak_ptr_factory_{this};
};
@@ -5,6 +5,7 @@
<include name="IDR_PLAYLIST_HTML" file="../playlist/browser/resources/playlist.html" type="BINDATA" />
<include name="IDR_PLAYLIST_PLAYER_HTML" file="../playlist/browser/resources/video_frame_contents.html" type="BINDATA" />
</if>
<include name="IDR_PLAYLIST_MEDIA_SOURCE_API_SUPPRESSOR_JS" file="../../third_party/playlist_component/src/media_source_api_suppressor.js" type="BINDATA" />
<include name="IDR_PLAYLIST_MEDIA_DETECTOR_JS" file="../../third_party/playlist_component/src/index.js" type="BINDATA" />
<include name="IDR_PLAYLIST_MEDIA_DETECTOR_YOUTUBE_JS" file="../../third_party/playlist_component/src/youtube.com.js" type="BINDATA" />
</if>
@@ -192,7 +192,7 @@ interface PlaylistServiceObserver {
// Interface that is used to notify that found media from renderer process to
// browser process.
interface PlaylistMediaHandler {
OnMediaUpdatedFromRenderFrame();
OnMediaDetected(mojo_base.mojom.Value media);
};
interface PlaylistStreamingObserver {
@@ -200,3 +200,8 @@ interface PlaylistStreamingObserver {
OnDataReceived(array<uint8> data_received);
OnDataCompleted();
};
interface PlaylistRenderFrameObserverConfigurator {
AddMediaSourceAPISuppressor(string media_source_api_suppressor);
AddMediaDetector(string media_detector);
};
@@ -5,28 +5,59 @@
#include "brave/components/playlist/renderer/playlist_render_frame_observer.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/values.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/v8_value_converter.h"
#include "gin/converter.h"
#include "gin/function_template.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_script_source.h"
#include "url/gurl.h"
#include "v8/include/v8.h"
namespace gin {
template <>
struct Converter<base::Value> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> v8_value,
base::Value* out) {
if (v8_value.IsEmpty()) {
return false;
}
std::unique_ptr<base::Value> base_value =
content::V8ValueConverter::Create()->FromV8Value(
v8_value, isolate->GetCurrentContext());
if (!base_value) {
return false;
}
*out = std::move(*base_value);
return true;
}
};
} // namespace gin
namespace playlist {
PlaylistRenderFrameObserver::PlaylistRenderFrameObserver(
content::RenderFrame* render_frame,
content::RenderFrame* frame,
int32_t isolated_world_id)
: RenderFrameObserver(render_frame),
RenderFrameObserverTracker<PlaylistRenderFrameObserver>(render_frame),
: RenderFrameObserver(frame),
RenderFrameObserverTracker<PlaylistRenderFrameObserver>(frame),
isolated_world_id_(isolated_world_id) {
EnsureConnectedToMediaHandler();
render_frame()
->GetAssociatedInterfaceRegistry()
->AddInterface<mojom::PlaylistRenderFrameObserverConfigurator>(
base::BindRepeating(&PlaylistRenderFrameObserver::BindConfigurator,
weak_ptr_factory_.GetWeakPtr()));
}
PlaylistRenderFrameObserver::~PlaylistRenderFrameObserver() = default;
@@ -35,137 +66,100 @@ void PlaylistRenderFrameObserver::OnDestruct() {
delete this;
}
bool PlaylistRenderFrameObserver::EnsureConnectedToMediaHandler() {
if (!media_handler_.is_bound()) {
render_frame()->GetBrowserInterfaceBroker()->GetInterface(
media_handler_.BindNewPipeAndPassReceiver());
media_handler_.set_disconnect_handler(
base::BindOnce(&PlaylistRenderFrameObserver::OnMediaHandlerDisconnect,
weak_ptr_factory_.GetWeakPtr()));
}
void PlaylistRenderFrameObserver::AddMediaSourceAPISuppressor(
const std::string& media_source_api_suppressor) {
DVLOG(2) << __FUNCTION__;
return media_handler_.is_bound();
media_source_api_suppressor_ = media_source_api_suppressor;
CHECK(!media_source_api_suppressor_->empty());
}
void PlaylistRenderFrameObserver::OnMediaHandlerDisconnect() {
media_handler_.reset();
EnsureConnectedToMediaHandler();
void PlaylistRenderFrameObserver::AddMediaDetector(
const std::string& media_detector) {
DVLOG(2) << __FUNCTION__;
media_detector_ = media_detector;
CHECK(!media_detector_->empty());
}
void PlaylistRenderFrameObserver::BindConfigurator(
mojo::PendingAssociatedReceiver<
mojom::PlaylistRenderFrameObserverConfigurator> receiver) {
configurator_receiver_.reset();
configurator_receiver_.Bind(std::move(receiver));
}
const mojo::Remote<playlist::mojom::PlaylistMediaHandler>&
PlaylistRenderFrameObserver::GetMediaHandler() {
if (!media_handler_) {
render_frame()->GetBrowserInterfaceBroker()->GetInterface(
media_handler_.BindNewPipeAndPassReceiver());
media_handler_.reset_on_disconnect();
}
return media_handler_;
}
void PlaylistRenderFrameObserver::RunScriptsAtDocumentStart() {
if (render_frame()->GetWebFrame()->IsProvisional()) {
return;
}
if (media_source_api_suppressor_) {
v8::Isolate* isolate = blink::MainThreadIsolate();
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
const auto& blink_preferences = render_frame()->GetBlinkPreferences();
if (blink_preferences.hide_media_src_api) {
HideMediaSourceAPI();
}
if (blink_preferences.should_detect_media_files) {
InstallMediaDetector();
Inject(*media_source_api_suppressor_,
render_frame()->GetWebFrame()->MainWorldScriptContext());
}
}
// Disables the MediaSource API in hope of the page switching to
// network-fetchable HTTPS URLs. This script is from
// https://github.com/brave/brave-ios/blob/development/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/Scripts/Paged/PlaylistSwizzlerScript.js
void PlaylistRenderFrameObserver::HideMediaSourceAPI() const {
DVLOG(2) << __FUNCTION__;
void PlaylistRenderFrameObserver::RunScriptsAtDocumentEnd() {
if (media_detector_) {
v8::Isolate* isolate = blink::MainThreadIsolate();
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
render_frame()->GetWebFrame()->ExecuteScript(
blink::WebScriptSource(blink::WebString::FromASCII(R"(
(function() {
if (
window.MediaSource ||
window.WebKitMediaSource ||
window.HTMLMediaElement && HTMLMediaElement.prototype.webkitSourceAddId
) {
delete window.MediaSource;
delete window.WebKitMediaSource;
}
})();)")));
v8::Local<v8::Context> context =
#if !BUILDFLAG(IS_ANDROID)
render_frame()->GetWebFrame()->GetScriptContextFromWorldId(
isolate, isolated_world_id_);
#else
render_frame()->GetWebFrame()->MainWorldScriptContext();
#endif
v8::Local<v8::Function> on_media_detected =
gin::CreateFunctionTemplate(
isolate,
base::BindRepeating(&PlaylistRenderFrameObserver::OnMediaDetected,
weak_ptr_factory_.GetWeakPtr()))
->GetFunction(context)
.ToLocalChecked();
Inject(*media_detector_, context, {on_media_detected.As<v8::Value>()});
}
}
void PlaylistRenderFrameObserver::InstallMediaDetector() {
void PlaylistRenderFrameObserver::Inject(
const std::string& script_text,
v8::Local<v8::Context> context,
std::vector<v8::Local<v8::Value>> args) const {
DVLOG(2) << __FUNCTION__;
static const char kScript[] = R"(
(function(onMediaUpdated) {
// Firstly, we try to get find all <video> or <audio> tags periodically,
// for a a while from the start up. If we find them, then we attach
// MutationObservers to them to detect source URL.
// After a given amount of time, we do this in requestIdleCallback().
// Note that there's a global object named |pl_worker|. This worker is
// created and bound by PlaylistJSHandler.
const mutationSources = new Set();
const mutationObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => { onMediaUpdated(window.location.href); })
});
const findNewMediaAndObserveMutation = () => {
return document.querySelectorAll('video, audio').forEach(
(mediaNode) => {
if (mutationSources.has(mediaNode)) return
mutationSources.add(mediaNode)
onMediaUpdated(window.location.href)
mutationObserver.observe(mediaNode, { attributeFilter: ['src'] })
});
}
const pollingIntervalId = window.setInterval(
findNewMediaAndObserveMutation, 1000);
window.setTimeout(() => {
window.clearInterval(pollingIntervalId)
window.requestIdleCallback(findNewMediaAndObserveMutation)
// TODO(sko) We might want to check if idle callback is waiting too
// long. In that case, we should get back to the polling style. And
// also, this time could be too long for production.
}, 20000)
// Try getting media after page was restored or navigated back.
window.addEventListener(
'pageshow', () => { onMediaUpdated(window.location.href); });
})
)";
v8::Isolate* isolate = blink::MainThreadIsolate();
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context =
render_frame()->GetWebFrame()->GetScriptContextFromWorldId(
isolate, isolated_world_id_);
v8::Context::Scope context_scope(context);
v8::MicrotasksScope microtasks_scope(
context, v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::Local<v8::Script> script =
v8::Script::Compile(context, gin::StringToV8(isolate, kScript))
v8::Script::Compile(context,
gin::StringToV8(context->GetIsolate(), script_text))
.ToLocalChecked();
v8::Local<v8::Function> function =
v8::Local<v8::Function>::Cast(script->Run(context).ToLocalChecked());
v8::Local<v8::Function> on_media_updated =
gin::CreateFunctionTemplate(
context->GetIsolate(),
base::BindRepeating(&PlaylistRenderFrameObserver::OnMediaUpdated,
weak_ptr_factory_.GetWeakPtr()))
->GetFunction(context)
.ToLocalChecked();
v8::Local<v8::Value> arg = on_media_updated.As<v8::Value>();
std::ignore = function->Call(context, context->Global(), 1, &arg);
std::ignore = function->Call(context, context->Global(), args.size(),
args.empty() ? nullptr : args.data());
}
void PlaylistRenderFrameObserver::OnMediaUpdated(const std::string& page_url) {
if (!GURL(page_url).SchemeIsHTTPOrHTTPS()) {
return;
}
void PlaylistRenderFrameObserver::OnMediaDetected(base::Value media) {
DVLOG(2) << __FUNCTION__;
DVLOG(2) << __FUNCTION__ << " " << page_url;
media_handler_->OnMediaUpdatedFromRenderFrame();
GetMediaHandler()->OnMediaDetected(std::move(media));
}
} // namespace playlist
@@ -6,41 +6,64 @@
#ifndef BRAVE_COMPONENTS_PLAYLIST_RENDERER_PLAYLIST_RENDER_FRAME_OBSERVER_H_
#define BRAVE_COMPONENTS_PLAYLIST_RENDERER_PLAYLIST_RENDER_FRAME_OBSERVER_H_
#include <optional>
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "brave/components/playlist/common/mojom/playlist.mojom.h"
#include "content/public/renderer/render_frame_observer.h"
#include "content/public/renderer/render_frame_observer_tracker.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace playlist {
class PlaylistRenderFrameObserver final
: public content::RenderFrameObserver,
public content::RenderFrameObserverTracker<PlaylistRenderFrameObserver> {
public content::RenderFrameObserverTracker<PlaylistRenderFrameObserver>,
public mojom::PlaylistRenderFrameObserverConfigurator {
public:
PlaylistRenderFrameObserver(content::RenderFrame* render_frame,
PlaylistRenderFrameObserver(content::RenderFrame* frame,
int32_t isolated_world_id);
PlaylistRenderFrameObserver(const PlaylistRenderFrameObserver&) = delete;
PlaylistRenderFrameObserver& operator=(const PlaylistRenderFrameObserver&) =
delete;
void RunScriptsAtDocumentStart();
void RunScriptsAtDocumentEnd();
private:
// RenderFrameObserver:
~PlaylistRenderFrameObserver() override;
// RenderFrameObserver:
void OnDestruct() override;
bool EnsureConnectedToMediaHandler();
void OnMediaHandlerDisconnect();
// mojom::PlaylistRenderFrameObserverConfigurator
void AddMediaSourceAPISuppressor(
const std::string& media_source_api_suppressor) override;
void AddMediaDetector(const std::string& media_detector) override;
void HideMediaSourceAPI() const;
void InstallMediaDetector();
void BindConfigurator(
mojo::PendingAssociatedReceiver<
mojom::PlaylistRenderFrameObserverConfigurator> receiver);
void OnMediaUpdated(const std::string& page_url);
const mojo::Remote<playlist::mojom::PlaylistMediaHandler>& GetMediaHandler();
void Inject(const std::string& script_text,
v8::Local<v8::Context> context,
std::vector<v8::Local<v8::Value>> args = {}) const;
void OnMediaDetected(base::Value media);
private:
int32_t isolated_world_id_;
mojo::AssociatedReceiver<mojom::PlaylistRenderFrameObserverConfigurator>
configurator_receiver_{this};
mojo::Remote<playlist::mojom::PlaylistMediaHandler> media_handler_;
std::optional<std::string> media_source_api_suppressor_;
std::optional<std::string> media_detector_;
base::WeakPtrFactory<PlaylistRenderFrameObserver> weak_ptr_factory_{this};
};
+14
View File
@@ -215,6 +215,20 @@ void BraveContentRendererClient::RunScriptsAtDocumentStart(
ChromeContentRendererClient::RunScriptsAtDocumentStart(render_frame);
}
void BraveContentRendererClient::RunScriptsAtDocumentEnd(
content::RenderFrame* render_frame) {
#if BUILDFLAG(ENABLE_PLAYLIST)
if (base::FeatureList::IsEnabled(playlist::features::kPlaylist)) {
if (auto* playlist_observer =
playlist::PlaylistRenderFrameObserver::Get(render_frame)) {
playlist_observer->RunScriptsAtDocumentEnd();
}
}
#endif
ChromeContentRendererClient::RunScriptsAtDocumentEnd(render_frame);
}
void BraveContentRendererClient::WillEvaluateServiceWorkerOnWorkerThread(
blink::WebServiceWorkerContextProxy* context_proxy,
v8::Local<v8::Context> v8_context,
+1
View File
@@ -32,6 +32,7 @@ class BraveContentRendererClient : public ChromeContentRendererClient {
void RenderFrameCreated(content::RenderFrame* render_frame) override;
void GetSupportedKeySystems(media::GetSupportedKeySystemsCB cb) override;
void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
void RunScriptsAtDocumentEnd(content::RenderFrame* render_frame) override;
void WillEvaluateServiceWorkerOnWorkerThread(
blink::WebServiceWorkerContextProxy* context_proxy,
v8::Local<v8::Context> v8_context,