Wire playlist webui
This commit is contained in:
+34
-1
@@ -23,6 +23,7 @@
|
||||
#include "brave/components/debounce/common/features.h"
|
||||
#include "brave/components/ipfs/buildflags/buildflags.h"
|
||||
#include "brave/components/ntp_background_images/browser/features.h"
|
||||
#include "brave/components/playlist/buildflags/buildflags.h"
|
||||
#include "brave/components/skus/browser/skus_utils.h"
|
||||
#include "brave/components/skus/common/features.h"
|
||||
#include "brave/components/speedreader/common/buildflags.h"
|
||||
@@ -43,6 +44,12 @@
|
||||
#include "brave/components/ipfs/features.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PLAYLIST)
|
||||
#include "brave/components/playlist/features.h"
|
||||
#include "chrome/common/channel_info.h"
|
||||
#include "components/version_info/version_info.h"
|
||||
#endif
|
||||
|
||||
using brave_shields::features::kBraveAdblockCnameUncloaking;
|
||||
using brave_shields::features::kBraveAdblockCollapseBlockedElements;
|
||||
using brave_shields::features::kBraveAdblockCookieListDefault;
|
||||
@@ -321,6 +328,9 @@ constexpr char kRestrictWebSocketsPoolName[] = "Restrict WebSockets pool";
|
||||
constexpr char kRestrictWebSocketsPoolDescription[] =
|
||||
"Limits simultaneous active WebSockets connections per eTLD+1";
|
||||
|
||||
constexpr char kPlaylistName[] = "Playlist";
|
||||
constexpr char kPlaylistDescription[] = "Enables Playlist";
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace flag_descriptions
|
||||
@@ -449,6 +459,28 @@ constexpr char kRestrictWebSocketsPoolDescription[] =
|
||||
#define BRAVE_TRANSLATE_GO_FEATURE_ENTRIES
|
||||
#endif // BUILDFLAG(ENABLE_BRAVE_TRANSLATE_GO)
|
||||
|
||||
#if BUILDFLAG(ENABLE_PLAYLIST)
|
||||
uint16_t AllowForDevVersion(uint16_t os) {
|
||||
if (auto channel = chrome::GetChannel();
|
||||
channel == version_info::Channel::STABLE ||
|
||||
channel == version_info::Channel::BETA) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
#define PLAYLIST_FEATURE_ENTRIES \
|
||||
{"playlist", \
|
||||
flag_descriptions::kPlaylistName, \
|
||||
flag_descriptions::kPlaylistDescription, \
|
||||
AllowForDevVersion( \
|
||||
flags_ui::kOsMac | flags_ui::kOsWin | flags_ui::kOsLinux), \
|
||||
FEATURE_VALUE_TYPE(playlist::features::kPlaylist)},
|
||||
#else
|
||||
#define PLAYLIST_FEATURE_ENTRIES
|
||||
#endif
|
||||
|
||||
#define BRAVE_ABOUT_FLAGS_FEATURE_ENTRIES \
|
||||
{"use-dev-updater-url", \
|
||||
flag_descriptions::kUseDevUpdaterUrlName, \
|
||||
@@ -582,4 +614,5 @@ constexpr char kRestrictWebSocketsPoolDescription[] =
|
||||
BRAVE_SKU_SDK_FEATURE_ENTRIES \
|
||||
SPEEDREADER_FEATURE_ENTRIES \
|
||||
BRAVE_TRANSLATE_GO_FEATURE_ENTRIES \
|
||||
BRAVE_FEDERATED_FEATURE_ENTRIES
|
||||
BRAVE_FEDERATED_FEATURE_ENTRIES \
|
||||
PLAYLIST_FEATURE_ENTRIES
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
#include "brave/components/ftx/browser/buildflags/buildflags.h"
|
||||
#include "brave/components/gemini/browser/buildflags/buildflags.h"
|
||||
#include "brave/components/ipfs/buildflags/buildflags.h"
|
||||
#include "brave/components/playlist/buildflags/buildflags.h"
|
||||
#include "brave/components/playlist/features.h"
|
||||
#include "brave/components/sidebar/buildflags/buildflags.h"
|
||||
#include "brave/components/skus/common/skus_sdk.mojom.h"
|
||||
#include "brave/components/speedreader/common/buildflags.h"
|
||||
@@ -213,6 +215,10 @@ using extensions::ChromeContentBrowserClientExtensionsPart;
|
||||
#include "brave/browser/brave_ads/brave_ads_host.h"
|
||||
#endif // BUILDFLAG(IS_ANDROID)
|
||||
|
||||
#if BUILDFLAG(ENABLE_PLAYLIST)
|
||||
#include "brave/browser/ui/webui/playlist_ui.h"
|
||||
#endif // BUILDFLAG(ENABLE_PLAYLIST)
|
||||
|
||||
namespace {
|
||||
|
||||
bool HandleURLReverseOverrideRewrite(GURL* url,
|
||||
@@ -592,6 +598,13 @@ void BraveContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
|
||||
}
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PLAYLIST)
|
||||
if (base::FeatureList::IsEnabled(playlist::features::kPlaylist)) {
|
||||
chrome::internal::RegisterWebUIControllerInterfaceBinder<
|
||||
playlist::mojom::PageHandlerFactory, playlist::PlaylistUI>(map);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !BUILDFLAG(IS_ANDROID)
|
||||
chrome::internal::RegisterWebUIControllerInterfaceBinder<
|
||||
brave_new_tab_page::mojom::PageHandlerFactory, BraveNewTabUI>(map);
|
||||
|
||||
@@ -166,7 +166,8 @@ void BraveShieldsWebContentsObserver::DispatchBlockedEventForWebContents(
|
||||
return;
|
||||
auto* shields_data_ctrlr =
|
||||
brave_shields::BraveShieldsDataController::FromWebContents(web_contents);
|
||||
DCHECK(shields_data_ctrlr);
|
||||
// |shields_data_ctrlr| can be null if the |web_contents| is generated in
|
||||
// component layer - We don't attach any tab helpers in this case.
|
||||
if (!shields_data_ctrlr)
|
||||
return;
|
||||
shields_data_ctrlr->HandleItemBlocked(block_type, subresource);
|
||||
|
||||
@@ -5,6 +5,7 @@ import("//brave/components/brave_wayback_machine/buildflags/buildflags.gni")
|
||||
import("//brave/components/ftx/browser/buildflags/buildflags.gni")
|
||||
import("//brave/components/ipfs/buildflags/buildflags.gni")
|
||||
import("//brave/components/ntp_background_images/buildflags/buildflags.gni")
|
||||
import("//brave/components/playlist/buildflags/buildflags.gni")
|
||||
import("//brave/components/sidebar/buildflags/buildflags.gni")
|
||||
import("//brave/components/speedreader/common/buildflags.gni")
|
||||
import("//brave/components/tor/buildflags/buildflags.gni")
|
||||
@@ -308,6 +309,8 @@ source_set("ui") {
|
||||
"//brave/components/ntp_background_images/common",
|
||||
"//brave/components/ntp_widget_utils/browser",
|
||||
"//brave/components/p3a:buildflags",
|
||||
"//brave/components/playlist/buildflags",
|
||||
"//brave/components/resources:static_resources",
|
||||
"//brave/components/sidebar/buildflags",
|
||||
"//brave/components/time_period_storage",
|
||||
"//brave/components/tor",
|
||||
@@ -658,6 +661,19 @@ source_set("ui") {
|
||||
deps += [ "//brave/components/ftx/common" ]
|
||||
}
|
||||
|
||||
if (enable_playlist) {
|
||||
sources += [
|
||||
"webui/playlist_page_handler.cc",
|
||||
"webui/playlist_page_handler.h",
|
||||
"webui/playlist_ui.cc",
|
||||
"webui/playlist_ui.h",
|
||||
]
|
||||
deps += [
|
||||
"//brave/components/playlist/mojom",
|
||||
"//brave/components/playlist/resources:generated_resources",
|
||||
]
|
||||
}
|
||||
|
||||
if (toolkit_views) {
|
||||
deps += [
|
||||
"//components/constrained_window",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "brave/components/constants/pref_names.h"
|
||||
#include "brave/components/constants/webui_url_constants.h"
|
||||
#include "brave/components/ipfs/buildflags/buildflags.h"
|
||||
#include "brave/components/playlist/buildflags/buildflags.h"
|
||||
#include "brave/components/sidebar/buildflags/buildflags.h"
|
||||
#include "brave/components/tor/buildflags/buildflags.h"
|
||||
#include "build/build_config.h"
|
||||
@@ -55,6 +56,10 @@
|
||||
#include "brave/components/ipfs/ipfs_utils.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PLAYLIST)
|
||||
#include "brave/browser/ui/webui/playlist_ui.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_TOR)
|
||||
#include "brave/browser/ui/webui/tor_internals_ui.h"
|
||||
#endif
|
||||
@@ -86,7 +91,7 @@ WebUIController* NewWebUI(WebUI* web_ui, const GURL& url) {
|
||||
} else if (host == kIPFSWebUIHost &&
|
||||
ipfs::IpfsServiceFactory::IsIpfsEnabled(profile)) {
|
||||
return new IPFSUI(web_ui, url.host());
|
||||
#endif // BUILDFLAG(ENABLE_IPFS)
|
||||
#endif
|
||||
#if !BUILDFLAG(IS_ANDROID)
|
||||
} else if (host == kWalletPageHost) {
|
||||
if (brave_wallet::IsNativeWalletEnabled()) {
|
||||
@@ -101,6 +106,10 @@ WebUIController* NewWebUI(WebUI* web_ui, const GURL& url) {
|
||||
} else if (host == kWalletPanelHost) {
|
||||
return new WalletPanelUI(web_ui);
|
||||
#endif // BUILDFLAG(OS_ANDROID)
|
||||
#if BUILDFLAG(ENABLE_PLAYLIST)
|
||||
} else if (host == kPlaylistHost) {
|
||||
return new playlist::PlaylistUI(web_ui, url.host());
|
||||
#endif // BUILDFLAG(PLAYLIST_ENABLED)
|
||||
} else if (host == kRewardsPageHost) {
|
||||
return new BraveRewardsPageUI(web_ui, url.host());
|
||||
} else if (host == kRewardsInternalsHost) {
|
||||
@@ -169,6 +178,9 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui, const GURL& url) {
|
||||
#endif
|
||||
#if BUILDFLAG(ENABLE_TOR)
|
||||
url.host_piece() == kTorInternalsHost ||
|
||||
#endif
|
||||
#if BUILDFLAG(ENABLE_PLAYLIST)
|
||||
url.host_piece() == kPlaylistHost ||
|
||||
#endif
|
||||
url.host_piece() == kWelcomeHost ||
|
||||
url.host_piece() == chrome::kChromeUIWelcomeURL ||
|
||||
@@ -213,6 +225,10 @@ WebUI::TypeID BraveWebUIControllerFactory::GetWebUIType(
|
||||
return WebUI::kNoWebUI;
|
||||
}
|
||||
#endif // BUILDFLAG(IS_ANDROID)
|
||||
#if BUILDFLAG(ENABLE_PLAYLIST)
|
||||
if (playlist::PlaylistUI::ShouldBlockPlaylistWebUI(browser_context, url))
|
||||
return WebUI::kNoWebUI;
|
||||
#endif
|
||||
WebUIFactoryFunction function = GetWebUIFactoryFunction(NULL, url);
|
||||
if (function) {
|
||||
return reinterpret_cast<WebUI::TypeID>(function);
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/browser/ui/webui/playlist_page_handler.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "brave/browser/playlist/playlist_service_factory.h"
|
||||
#include "brave/components/playlist/playlist_constants.h"
|
||||
#include "chrome/browser/profiles/profile.h"
|
||||
|
||||
playlist::PlaylistService* GetPlaylistService(Profile* profile) {
|
||||
return playlist::PlaylistServiceFactory::GetForBrowserContext(profile);
|
||||
}
|
||||
|
||||
PlaylistPageHandler::PlaylistPageHandler(
|
||||
Profile* profile,
|
||||
mojo::PendingReceiver<playlist::mojom::PageHandler> pending_page_handler,
|
||||
mojo::PendingRemote<playlist::mojom::Page> pending_page)
|
||||
: profile_(profile),
|
||||
page_(std::move(pending_page)),
|
||||
handler_(this, std::move(pending_page_handler)) {
|
||||
DCHECK(profile_);
|
||||
observation_.Observe(GetPlaylistService(profile_));
|
||||
}
|
||||
|
||||
PlaylistPageHandler::~PlaylistPageHandler() = default;
|
||||
|
||||
void PlaylistPageHandler::GetAllPlaylists(
|
||||
PlaylistPageHandler::GetAllPlaylistsCallback callback) {
|
||||
auto value = GetPlaylistService(profile_)->GetAllPlaylistItems();
|
||||
DCHECK(value.is_list());
|
||||
|
||||
std::vector<mojo::StructPtr<playlist::mojom::Playlist>> playlists;
|
||||
playlists.push_back(playlist::mojom::Playlist::New());
|
||||
auto& playlist = playlists.back();
|
||||
playlist->name = "default";
|
||||
for (const auto& item : value.GetList()) {
|
||||
auto* dict = item.GetIfDict();
|
||||
DCHECK(dict);
|
||||
DCHECK(dict->contains(playlist::kPlaylistItemIDKey));
|
||||
DCHECK(dict->contains(playlist::kPlaylistItemTitleKey));
|
||||
DCHECK(dict->contains(playlist::kPlaylistItemMediaFilePathKey));
|
||||
DCHECK(dict->contains(playlist::kPlaylistItemThumbnailPathKey));
|
||||
|
||||
playlist->items.push_back(playlist::mojom::PlaylistItem::New(
|
||||
*dict->FindString(playlist::kPlaylistItemIDKey) /* = id */,
|
||||
*dict->FindString(playlist::kPlaylistItemTitleKey) /* = name */,
|
||||
GURL(*dict->FindString(
|
||||
playlist::kPlaylistItemMediaFilePathKey)) /* = media_path */,
|
||||
GURL(*dict->FindString(
|
||||
playlist::
|
||||
kPlaylistItemThumbnailPathKey)) /* = ::GURL& thumbnail_path */,
|
||||
*dict->FindBool(playlist::kPlaylistItemReadyKey) /* = ready */));
|
||||
}
|
||||
|
||||
std::move(callback).Run(std::move(playlists));
|
||||
}
|
||||
|
||||
void PlaylistPageHandler::GetPlaylist(
|
||||
const std::string& id,
|
||||
PlaylistPageHandler::GetPlaylistCallback callback) {
|
||||
// TODO(sko) Get items for playlist with |id|.
|
||||
auto value = GetPlaylistService(profile_)->GetAllPlaylistItems();
|
||||
DCHECK(value.is_list());
|
||||
playlist::mojom::Playlist playlist;
|
||||
playlist.name = "default";
|
||||
for (const auto& item : value.GetList()) {
|
||||
auto* dict = item.GetIfDict();
|
||||
DCHECK(dict);
|
||||
DCHECK(dict->contains(playlist::kPlaylistItemIDKey));
|
||||
DCHECK(dict->contains(playlist::kPlaylistItemTitleKey));
|
||||
DCHECK(dict->contains(playlist::kPlaylistItemMediaFilePathKey));
|
||||
DCHECK(dict->contains(playlist::kPlaylistItemThumbnailPathKey));
|
||||
|
||||
playlist.items.push_back(playlist::mojom::PlaylistItem::New(
|
||||
*dict->FindString(playlist::kPlaylistItemIDKey) /* = id */,
|
||||
*dict->FindString(playlist::kPlaylistItemTitleKey) /* = name */,
|
||||
GURL(*dict->FindString(
|
||||
playlist::kPlaylistItemMediaFilePathKey)) /* = media_path */,
|
||||
GURL(*dict->FindString(
|
||||
playlist::
|
||||
kPlaylistItemThumbnailPathKey)) /* = ::GURL& thumbnail_path */,
|
||||
*dict->FindBool(playlist::kPlaylistItemReadyKey) /* = ready */));
|
||||
}
|
||||
|
||||
std::move(callback).Run(playlist.Clone());
|
||||
}
|
||||
|
||||
void PlaylistPageHandler::AddMediaFilesFromPageToPlaylist(const std::string& id,
|
||||
const GURL& url) {
|
||||
GetPlaylistService(profile_)->RequestDownloadMediaFilesFromPage(id,
|
||||
url.spec());
|
||||
}
|
||||
|
||||
void PlaylistPageHandler::RemoveItemFromPlaylist(const std::string& playlist_id,
|
||||
const std::string& item_id) {
|
||||
GetPlaylistService(profile_)->RemoveItemFromPlaylist(playlist_id, item_id);
|
||||
}
|
||||
|
||||
void PlaylistPageHandler::OnPlaylistItemStatusChanged(
|
||||
const playlist::PlaylistItemChangeParams& params) {
|
||||
page_->OnEvent(playlist::mojom::PlaylistEvent::kItemAdded);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_BROWSER_UI_WEBUI_PLAYLIST_PAGE_HANDLER_H_
|
||||
#define BRAVE_BROWSER_UI_WEBUI_PLAYLIST_PAGE_HANDLER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/scoped_observation.h"
|
||||
#include "brave/components/playlist/mojom/playlist.mojom.h"
|
||||
#include "brave/components/playlist/playlist_service.h"
|
||||
#include "brave/components/playlist/playlist_service_observer.h"
|
||||
#include "mojo/public/cpp/bindings/pending_receiver.h"
|
||||
#include "mojo/public/cpp/bindings/pending_remote.h"
|
||||
#include "mojo/public/cpp/bindings/receiver.h"
|
||||
#include "mojo/public/cpp/bindings/remote.h"
|
||||
|
||||
class Profile;
|
||||
|
||||
class PlaylistPageHandler : public playlist::mojom::PageHandler,
|
||||
public playlist::PlaylistServiceObserver {
|
||||
public:
|
||||
PlaylistPageHandler(
|
||||
Profile* profile,
|
||||
mojo::PendingReceiver<playlist::mojom::PageHandler> pending_page_handler,
|
||||
mojo::PendingRemote<playlist::mojom::Page> pending_page);
|
||||
~PlaylistPageHandler() override;
|
||||
|
||||
// playlist::mojom::PageHandler:
|
||||
void GetAllPlaylists(
|
||||
PlaylistPageHandler::GetAllPlaylistsCallback callback) override;
|
||||
void GetPlaylist(const std::string& id,
|
||||
PlaylistPageHandler::GetPlaylistCallback callback) override;
|
||||
void AddMediaFilesFromPageToPlaylist(const std::string& playlist_id,
|
||||
const GURL& url) override;
|
||||
void RemoveItemFromPlaylist(const std::string& playlist_id,
|
||||
const std::string& item_id) override;
|
||||
|
||||
// playlist::PlaylistServiceObserver
|
||||
void OnPlaylistItemStatusChanged(
|
||||
const playlist::PlaylistItemChangeParams& params) override;
|
||||
|
||||
private:
|
||||
raw_ptr<Profile> profile_ = nullptr;
|
||||
|
||||
mojo::Remote<playlist::mojom::Page> page_;
|
||||
mojo::Receiver<playlist::mojom::PageHandler> handler_;
|
||||
|
||||
base::ScopedObservation<playlist::PlaylistService,
|
||||
playlist::PlaylistServiceObserver>
|
||||
observation_{this};
|
||||
};
|
||||
|
||||
#endif // BRAVE_BROWSER_UI_WEBUI_PLAYLIST_PAGE_HANDLER_H_
|
||||
@@ -0,0 +1,60 @@
|
||||
/* Copyright (c) 2021 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,
|
||||
#include "chrome/browser/profiles/profile.h"
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/browser/ui/webui/playlist_ui.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "brave/browser/playlist/playlist_service_factory.h"
|
||||
#include "brave/browser/ui/webui/brave_webui_source.h"
|
||||
#include "brave/browser/ui/webui/playlist_page_handler.h"
|
||||
#include "brave/components/constants/webui_url_constants.h"
|
||||
#include "brave/components/playlist/resources/grit/playlist_generated_map.h"
|
||||
#include "chrome/browser/profiles/profile.h"
|
||||
#include "components/grit/brave_components_resources.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
namespace playlist {
|
||||
|
||||
// static
|
||||
bool PlaylistUI::ShouldBlockPlaylistWebUI(
|
||||
content::BrowserContext* browser_context,
|
||||
const GURL& url) {
|
||||
if (url.host_piece() != kPlaylistHost)
|
||||
return false;
|
||||
|
||||
return !PlaylistServiceFactory::IsPlaylistEnabled(browser_context);
|
||||
}
|
||||
|
||||
PlaylistUI::PlaylistUI(content::WebUI* web_ui, const std::string& name)
|
||||
: MojoWebUIController(web_ui) {
|
||||
CreateAndAddWebUIDataSource(web_ui, name, kPlaylistGenerated,
|
||||
kPlaylistGeneratedSize, IDR_PLAYLIST_HTML);
|
||||
}
|
||||
|
||||
PlaylistUI::~PlaylistUI() = default;
|
||||
|
||||
void PlaylistUI::BindInterface(
|
||||
mojo::PendingReceiver<playlist::mojom::PageHandlerFactory>
|
||||
pending_receiver) {
|
||||
if (page_factory_receiver_.is_bound())
|
||||
page_factory_receiver_.reset();
|
||||
|
||||
page_factory_receiver_.Bind(std::move(pending_receiver));
|
||||
}
|
||||
|
||||
void PlaylistUI::CreatePageHandler(
|
||||
mojo::PendingRemote<playlist::mojom::Page> pending_page,
|
||||
mojo::PendingReceiver<playlist::mojom::PageHandler> pending_page_handler) {
|
||||
DCHECK(pending_page.is_valid());
|
||||
page_handler_ = std::make_unique<PlaylistPageHandler>(
|
||||
Profile::FromWebUI(web_ui()), std::move(pending_page_handler),
|
||||
std::move(pending_page));
|
||||
}
|
||||
|
||||
WEB_UI_CONTROLLER_TYPE_IMPL(PlaylistUI)
|
||||
|
||||
} // namespace playlist
|
||||
@@ -0,0 +1,55 @@
|
||||
/* Copyright (c) 2021 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_BROWSER_UI_WEBUI_PLAYLIST_UI_H_
|
||||
#define BRAVE_BROWSER_UI_WEBUI_PLAYLIST_UI_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "brave/components/playlist/mojom/playlist.mojom.h"
|
||||
#include "ui/webui/mojo_web_ui_controller.h"
|
||||
|
||||
namespace content {
|
||||
class BrowserContext;
|
||||
} // namespace content
|
||||
|
||||
class GURL;
|
||||
class PlaylistPageHandler;
|
||||
|
||||
namespace playlist {
|
||||
|
||||
class PlaylistUI : public ui::MojoWebUIController,
|
||||
public playlist::mojom::PageHandlerFactory {
|
||||
public:
|
||||
static bool ShouldBlockPlaylistWebUI(content::BrowserContext* browser_context,
|
||||
const GURL& url);
|
||||
|
||||
PlaylistUI(content::WebUI* web_ui, const std::string& host);
|
||||
~PlaylistUI() override;
|
||||
PlaylistUI(const PlaylistUI&) = delete;
|
||||
PlaylistUI& operator=(const PlaylistUI&) = delete;
|
||||
|
||||
void BindInterface(mojo::PendingReceiver<playlist::mojom::PageHandlerFactory>
|
||||
pending_receiver);
|
||||
|
||||
// playlist::mojom::PageHandlerFactory:
|
||||
void CreatePageHandler(
|
||||
mojo::PendingRemote<playlist::mojom::Page> pending_page,
|
||||
mojo::PendingReceiver<playlist::mojom::PageHandler> pending_page_handler)
|
||||
override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<PlaylistPageHandler> page_handler_;
|
||||
|
||||
mojo::Receiver<playlist::mojom::PageHandlerFactory> page_factory_receiver_{
|
||||
this};
|
||||
|
||||
WEB_UI_CONTROLLER_TYPE_DECL();
|
||||
};
|
||||
|
||||
} // namespace playlist
|
||||
|
||||
#endif // BRAVE_BROWSER_UI_WEBUI_PLAYLIST_UI_H_
|
||||
@@ -162,7 +162,8 @@
|
||||
"chrome://wallet/*",
|
||||
"chrome://wallet-panel.top-chrome/*",
|
||||
"chrome://brave-shields.top-chrome/*",
|
||||
"chrome://rewards-panel.top-chrome/*"
|
||||
"chrome://rewards-panel.top-chrome/*",
|
||||
"chrome://playlist/*"
|
||||
]
|
||||
}],
|
||||
"greaselion": [{
|
||||
|
||||
@@ -11,6 +11,7 @@ brave_common_web_compile_inputs = [
|
||||
# see ../definitions/README.md.
|
||||
rebase_path("../definitions/adBlock.d.ts"),
|
||||
rebase_path("../definitions/ipfs.d.ts"),
|
||||
rebase_path("../definitions/playlist.d.ts"),
|
||||
rebase_path("../definitions/webcompatReporter.d.ts"),
|
||||
rebase_path("../definitions/chromel.d.ts"),
|
||||
rebase_path("../definitions/global.d.ts"),
|
||||
|
||||
@@ -43,3 +43,5 @@ const char kSidebarBookmarksHost[] = "sidebar-bookmarks.top-chrome";
|
||||
const char kFederatedInternalsURL[] = "brave://federated-internals";
|
||||
const char kFederatedInternalsHost[] = "federated-internals";
|
||||
const char kContentFiltersPath[] = "shields/filters";
|
||||
const char kPlaylistHost[] = "playlist";
|
||||
const char kPlaylistURL[] = "chrome://playlist/";
|
||||
|
||||
@@ -45,5 +45,7 @@ extern const char kSidebarBookmarksHost[];
|
||||
extern const char kFederatedInternalsURL[];
|
||||
extern const char kFederatedInternalsHost[];
|
||||
extern const char kContentFiltersPath[];
|
||||
extern const char kPlaylistHost[];
|
||||
extern const char kPlaylistURL[];
|
||||
|
||||
#endif // BRAVE_COMPONENTS_CONSTANTS_WEBUI_URL_CONSTANTS_H_
|
||||
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import * as PlaylistMojo from 'gen/brave/components/playlist/mojom/playlist.mojom.m.js'
|
||||
|
||||
declare namespace Playlist {
|
||||
export interface ApplicationState {
|
||||
playlistData: State|undefined
|
||||
}
|
||||
|
||||
export interface State {
|
||||
lists : PlaylistMojo.Playlist[]
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,59 @@ void MediaDetectorComponentManager::SetUseLocalScriptForTesting() {
|
||||
// https://github.com/brave/brave-ios/blob/development/Client/Frontend/UserContent/UserScripts/Playlist.js
|
||||
static const std::string kScript = R"-(
|
||||
(function() {
|
||||
function is_nan(value) {
|
||||
return typeof value === "number" && value !== value;
|
||||
}
|
||||
|
||||
function is_infinite(value) {
|
||||
return typeof value === "number" && (value === Infinity || value === -Infinity);
|
||||
}
|
||||
|
||||
function clamp_duration(value) {
|
||||
if (is_nan(value)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if (is_infinite(value)) {
|
||||
return Number.MAX_VALUE;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Algorithm:
|
||||
// Generate a random number from 0 to 256
|
||||
// Roll-Over clamp to the range [0, 15]
|
||||
// If the index is 13, set it to 4.
|
||||
// If the index is 17, clamp it to [0, 3]
|
||||
// Subtract that number from 15 (XOR) and convert the result to hex.
|
||||
function uuid_v4() {
|
||||
// X >> 2 = X / 4 (integer division)
|
||||
|
||||
// AND-ing (15 >> 0) roll-over clamps to 15
|
||||
// AND-ing (15 >> 2) roll-over clamps to 3
|
||||
// So '8' digit is clamped to 3 (inclusive) and all others clamped to 15 (inclusive).
|
||||
|
||||
// 0 XOR 15 = 15
|
||||
// 1 XOR 15 = 14
|
||||
// 8 XOR 15 = 7
|
||||
// So N XOR 15 = 15 - N
|
||||
|
||||
// UUID string format generated with array appending
|
||||
// Results in "10000000-1000-4000-8000-100000000000".replace(...)
|
||||
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (X) => {
|
||||
return (X ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (X >> 2)))).toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
function tagNode(node) {
|
||||
if (node) {
|
||||
if (!node.tagUUID) {
|
||||
node.tagUUID = uuid_v4();
|
||||
node.addEventListener('webkitpresentationmodechanged', (e) => e.stopPropagation(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getNodeSource(node, src, mimeType, thumbnail) {
|
||||
var name = node.title;
|
||||
if (name == null || typeof name == 'undefined' || name == "") {
|
||||
@@ -102,14 +155,14 @@ void MediaDetectorComponentManager::SetUseLocalScriptForTesting() {
|
||||
}
|
||||
|
||||
if (src && src !== "") {
|
||||
// tagNode(node);
|
||||
tagNode(node);
|
||||
return {
|
||||
"name": name,
|
||||
"src": src,
|
||||
"pageSrc": window.location.href,
|
||||
"pageTitle": document.title,
|
||||
"mimeType": mimeType,
|
||||
//"duration": clamp_duration(node.duration),
|
||||
"duration": clamp_duration(node.duration),
|
||||
"detected": true,
|
||||
"tagId": node.tagUUID,
|
||||
thumbnail
|
||||
@@ -119,14 +172,14 @@ void MediaDetectorComponentManager::SetUseLocalScriptForTesting() {
|
||||
document.querySelectorAll('source').forEach(function(node) {
|
||||
if (node.src !== "") {
|
||||
if (node.closest('video') === target) {
|
||||
// tagNode(target);
|
||||
tagNode(target);
|
||||
return {
|
||||
"name": name,
|
||||
"src": node.src,
|
||||
"pageSrc": window.location.href,
|
||||
"pageTitle": document.title,
|
||||
"mimeType": mimeType,
|
||||
//"duration": clamp_duration(target.duration),
|
||||
"duration": clamp_duration(target.duration),
|
||||
"detected": true,
|
||||
"tagId": target.tagUUID,
|
||||
thumbnail
|
||||
@@ -141,7 +194,7 @@ void MediaDetectorComponentManager::SetUseLocalScriptForTesting() {
|
||||
"pageSrc": window.location.href,
|
||||
"pageTitle": document.title,
|
||||
"mimeType": mimeType,
|
||||
//"duration": clamp_duration(target.duration),
|
||||
"duration": clamp_duration(target.duration),
|
||||
"detected": true,
|
||||
"tagId": target.tagUUID,
|
||||
thumbnail
|
||||
@@ -186,7 +239,6 @@ void MediaDetectorComponentManager::SetUseLocalScriptForTesting() {
|
||||
return medias;
|
||||
|
||||
return videoElements;
|
||||
|
||||
})();
|
||||
)-";
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import("//mojo/public/tools/bindings/mojom.gni")
|
||||
|
||||
mojom("mojom") {
|
||||
sources = [ "playlist.mojom" ]
|
||||
public_deps = [
|
||||
"//mojo/public/mojom/base",
|
||||
"//url/mojom:url_mojom_gurl",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/.
|
||||
|
||||
module playlist.mojom;
|
||||
|
||||
import "url/mojom/url.mojom";
|
||||
|
||||
enum PlaylistEvent {
|
||||
kItemAdded,
|
||||
kItemThumbnailReady,
|
||||
kItemThumbnailFailed,
|
||||
kItemReadyToPlay,
|
||||
kItemDeleted,
|
||||
kAllItemsDeleted,
|
||||
kItemAborted,
|
||||
};
|
||||
|
||||
struct Playlist {
|
||||
string? id;
|
||||
string name;
|
||||
array<PlaylistItem> items;
|
||||
};
|
||||
|
||||
struct PlaylistItem {
|
||||
string id;
|
||||
string name;
|
||||
|
||||
url.mojom.Url media_path;
|
||||
url.mojom.Url thumbnail_path;
|
||||
|
||||
// When it's |ready|, |media_path| and |thumbnail_path| will be
|
||||
// local path.
|
||||
bool ready;
|
||||
};
|
||||
|
||||
// Used by the WebUI page to bootstrap bidirectional communication.
|
||||
interface PageHandlerFactory {
|
||||
// The WebUI page's |BrowserProxy| singleton calls this method when the page
|
||||
// is first initialized.
|
||||
CreatePageHandler(pending_remote<Page> page,
|
||||
pending_receiver<PageHandler> handler);
|
||||
};
|
||||
|
||||
// Browser-side handler for requests from WebUI page.
|
||||
interface PageHandler {
|
||||
GetAllPlaylists() => (array<Playlist> playlists);
|
||||
|
||||
GetPlaylist(string id) => (Playlist? playlist);
|
||||
|
||||
AddMediaFilesFromPageToPlaylist(string playlist_id, url.mojom.Url url);
|
||||
|
||||
RemoveItemFromPlaylist(string playlist_id, string item_id);
|
||||
};
|
||||
|
||||
// WebUI-side handler for requests from the browser.
|
||||
interface Page {
|
||||
OnEvent(PlaylistEvent event);
|
||||
};
|
||||
|
||||
@@ -27,8 +27,10 @@ namespace {
|
||||
scoped_refptr<base::RefCountedMemory> ReadFileToString(
|
||||
const base::FilePath& path) {
|
||||
std::string contents;
|
||||
if (!base::ReadFileToString(path, &contents))
|
||||
if (!base::ReadFileToString(path, &contents)) {
|
||||
VLOG(2) << __FUNCTION__ << " Failed to read " << path;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return base::MakeRefCounted<base::RefCountedBytes>(
|
||||
reinterpret_cast<const unsigned char*>(contents.c_str()),
|
||||
@@ -43,7 +45,7 @@ PlaylistDataSource::PlaylistDataSource(PlaylistService* service)
|
||||
PlaylistDataSource::~PlaylistDataSource() {}
|
||||
|
||||
std::string PlaylistDataSource::GetSource() {
|
||||
return "playlist-image";
|
||||
return "playlist-data";
|
||||
}
|
||||
|
||||
void PlaylistDataSource::StartDataRequest(
|
||||
@@ -54,33 +56,78 @@ void PlaylistDataSource::StartDataRequest(
|
||||
std::move(got_data_callback).Run(nullptr);
|
||||
return;
|
||||
}
|
||||
base::FilePath thumbnail_path;
|
||||
if (!service_->GetThumbnailPath(URLDataSource::URLToRequestPath(url),
|
||||
&thumbnail_path)) {
|
||||
std::string path = URLDataSource::URLToRequestPath(url);
|
||||
std::string id;
|
||||
std::string type_string;
|
||||
if (auto pos = path.find("/"); pos != std::string::npos) {
|
||||
id = std::string(path.begin(), path.begin() + pos);
|
||||
type_string = std::string(path.begin() + pos, path.end());
|
||||
type_string.erase(std::remove(type_string.begin(), type_string.end(), '/'),
|
||||
type_string.end());
|
||||
} else {
|
||||
NOTREACHED() << "path is not in expected form: /id/{thumbnail,media}/ vs "
|
||||
<< path;
|
||||
std::move(got_data_callback).Run(nullptr);
|
||||
return;
|
||||
}
|
||||
GetThumbnailImageFile(thumbnail_path, std::move(got_data_callback));
|
||||
|
||||
base::FilePath data_path;
|
||||
if (type_string == "thumbnail") {
|
||||
if (!service_->GetThumbnailPath(id, &data_path)) {
|
||||
std::move(got_data_callback).Run(nullptr);
|
||||
return;
|
||||
}
|
||||
} else if (type_string == "media") {
|
||||
if (!service_->GetMediaPath(id, &data_path)) {
|
||||
std::move(got_data_callback).Run(nullptr);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
NOTREACHED() << "type is neither of {thumbnail,media}/ : " << type_string;
|
||||
std::move(got_data_callback).Run(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
GetDataFile(data_path, std::move(got_data_callback));
|
||||
}
|
||||
|
||||
void PlaylistDataSource::GetThumbnailImageFile(
|
||||
const base::FilePath& image_file_path,
|
||||
GotDataCallback got_data_callback) {
|
||||
void PlaylistDataSource::GetDataFile(const base::FilePath& data_path,
|
||||
GotDataCallback got_data_callback) {
|
||||
base::ThreadPool::PostTaskAndReplyWithResult(
|
||||
FROM_HERE, base::MayBlock(),
|
||||
base::BindOnce(&ReadFileToString, image_file_path),
|
||||
base::BindOnce(&PlaylistDataSource::OnGotThumbnailImageFile,
|
||||
FROM_HERE, base::MayBlock(), base::BindOnce(&ReadFileToString, data_path),
|
||||
base::BindOnce(&PlaylistDataSource::OnGotDataFile,
|
||||
weak_factory_.GetWeakPtr(), std::move(got_data_callback)));
|
||||
}
|
||||
|
||||
void PlaylistDataSource::OnGotThumbnailImageFile(
|
||||
void PlaylistDataSource::OnGotDataFile(
|
||||
GotDataCallback got_data_callback,
|
||||
scoped_refptr<base::RefCountedMemory> input) {
|
||||
std::move(got_data_callback).Run(input);
|
||||
}
|
||||
|
||||
std::string PlaylistDataSource::GetMimeType(const std::string&) {
|
||||
return "image/jpg";
|
||||
std::string PlaylistDataSource::GetMimeType(const std::string& path) {
|
||||
std::string id;
|
||||
std::string type_string;
|
||||
if (auto pos = path.find("/"); pos != std::string::npos) {
|
||||
id = std::string(path.begin(), path.begin() + pos);
|
||||
type_string = std::string(path.begin() + pos, path.end());
|
||||
type_string.erase(std::remove(type_string.begin(), type_string.end(), '/'),
|
||||
type_string.end());
|
||||
} else {
|
||||
NOTREACHED() << "path is not in expected form: /id/{thumbnail,media}/ vs "
|
||||
<< path;
|
||||
return std::string();
|
||||
}
|
||||
|
||||
if (type_string == "thumbnail")
|
||||
return "image/jpeg";
|
||||
|
||||
// TODO(sko) Decide mime type based on the file extension.
|
||||
if (type_string == "media")
|
||||
return "video/mp4";
|
||||
|
||||
NOTREACHED() << "type is neither of {thumbnail,media}/ : " << type_string;
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool PlaylistDataSource::AllowCaching() {
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace playlist {
|
||||
|
||||
class PlaylistService;
|
||||
|
||||
// A URL data source for chrome://playlist-image/<playlist-id>
|
||||
// A URL data source for chrome://playlist-data/<playlist-id>/{thumbnail,media}/
|
||||
// resources, for use in webui pages that want to display downloaded
|
||||
// playlist thumbnail images
|
||||
class PlaylistDataSource : public content::URLDataSource {
|
||||
@@ -37,14 +37,14 @@ class PlaylistDataSource : public content::URLDataSource {
|
||||
void StartDataRequest(const GURL& url,
|
||||
const content::WebContents::Getter& wc_getter,
|
||||
GotDataCallback got_data_callback) override;
|
||||
std::string GetMimeType(const std::string&) override;
|
||||
std::string GetMimeType(const std::string& path) override;
|
||||
bool AllowCaching() override;
|
||||
|
||||
private:
|
||||
void GetThumbnailImageFile(const base::FilePath& image_file_path,
|
||||
GotDataCallback got_data_callback);
|
||||
void OnGotThumbnailImageFile(GotDataCallback got_data_callback,
|
||||
scoped_refptr<base::RefCountedMemory> input);
|
||||
void GetDataFile(const base::FilePath& data_path,
|
||||
GotDataCallback got_data_callback);
|
||||
void OnGotDataFile(GotDataCallback got_data_callback,
|
||||
scoped_refptr<base::RefCountedMemory> input);
|
||||
|
||||
raw_ptr<PlaylistService> service_;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "content/public/common/isolated_world_ids.h"
|
||||
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
|
||||
#include "third_party/re2/src/re2/re2.h"
|
||||
#include "ui/base/page_transition_types.h"
|
||||
|
||||
namespace playlist {
|
||||
|
||||
@@ -68,6 +69,7 @@ void PlaylistDownloadRequestManager::CreateWebContents() {
|
||||
// |web_contents_| is created on demand.
|
||||
content::WebContents::CreateParams create_params(context_, nullptr);
|
||||
web_contents_ = content::WebContents::Create(create_params);
|
||||
|
||||
Observe(web_contents_.get());
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,9 @@ PlaylistMediaFileDownloadManager::PlaylistMediaFileDownloadManager(
|
||||
Delegate* delegate,
|
||||
const base::FilePath& base_dir)
|
||||
: base_dir_(base_dir), delegate_(delegate) {
|
||||
// TODO(pilgrim) dynamically set file extensions based on format
|
||||
// (may require changes to youtubedown parser)
|
||||
// TODO(pilgrim) dynamically set file extensions based on format.
|
||||
media_file_downloader_ = std::make_unique<PlaylistMediaFileDownloader>(
|
||||
this, context, FILE_PATH_LITERAL("media_file.mp4"));
|
||||
this, context, kMediaFileName);
|
||||
}
|
||||
|
||||
PlaylistMediaFileDownloadManager::~PlaylistMediaFileDownloadManager() = default;
|
||||
@@ -108,7 +107,7 @@ void PlaylistMediaFileDownloadManager::OnMediaFileReady(
|
||||
const std::string& media_file_path) {
|
||||
VLOG(2) << __func__ << ": " << id << " is ready.";
|
||||
|
||||
delegate_->OnMediaFileReady(id, current_item_->media_file_path);
|
||||
delegate_->OnMediaFileReady(id, media_file_path);
|
||||
|
||||
current_item_.reset();
|
||||
|
||||
|
||||
@@ -40,6 +40,9 @@ class PlaylistMediaFileDownloadManager
|
||||
virtual ~Delegate() {}
|
||||
};
|
||||
|
||||
static constexpr base::FilePath::CharType kMediaFileName[] =
|
||||
FILE_PATH_LITERAL("media_file.mp4");
|
||||
|
||||
PlaylistMediaFileDownloadManager(content::BrowserContext* context,
|
||||
Delegate* delegate,
|
||||
const base::FilePath& base_dir);
|
||||
|
||||
@@ -85,6 +85,13 @@ void PlaylistService::RequestDownloadMediaFilesFromPage(
|
||||
download_request_manager_->GetMediaFilesFromPage(url);
|
||||
}
|
||||
|
||||
void PlaylistService::RemoveItemFromPlaylist(const std::string& playlist_id,
|
||||
const std::string& item_id) {
|
||||
VLOG(2) << __func__ << " " << playlist_id << " " << item_id;
|
||||
// TODO(sko) consider |playlist_id|
|
||||
DeletePlaylistItem(item_id);
|
||||
}
|
||||
|
||||
void PlaylistService::OnPlaylistCreationParamsReady(
|
||||
const PlaylistItemInfo& params) {
|
||||
CreatePlaylistItem(params);
|
||||
@@ -293,7 +300,7 @@ void PlaylistService::RemoveObserver(PlaylistServiceObserver* observer) {
|
||||
|
||||
void PlaylistService::OnMediaFileReady(const std::string& id,
|
||||
const std::string& media_file_path) {
|
||||
VLOG(2) << __func__ << ": " << id;
|
||||
VLOG(2) << __func__ << ": " << id << " " << media_file_path;
|
||||
DCHECK(IsValidPlaylistItem(id));
|
||||
|
||||
const base::Value* item_value_ptr =
|
||||
@@ -369,6 +376,19 @@ bool PlaylistService::GetThumbnailPath(const std::string& id,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlaylistService::GetMediaPath(const std::string& id,
|
||||
base::FilePath* media_path) {
|
||||
// TODO(sko) The extension of the media file can differ.
|
||||
// We should iterate files in the directory and find the match.
|
||||
*media_path = GetPlaylistItemDirPath(id).Append(
|
||||
PlaylistMediaFileDownloadManager::kMediaFileName);
|
||||
if (media_path->ReferencesParent()) {
|
||||
media_path->clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
base::SequencedTaskRunner* PlaylistService::task_runner() {
|
||||
if (!task_runner_) {
|
||||
task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
|
||||
|
||||
@@ -71,10 +71,14 @@ class PlaylistService : public KeyedService,
|
||||
void RequestDownloadMediaFilesFromPage(const std::string& playlist_id,
|
||||
const std::string& url);
|
||||
|
||||
void RemoveItemFromPlaylist(const std::string& playlist_id,
|
||||
const std::string& item_id);
|
||||
|
||||
void AddObserver(PlaylistServiceObserver* observer);
|
||||
void RemoveObserver(PlaylistServiceObserver* observer);
|
||||
|
||||
bool GetThumbnailPath(const std::string& id, base::FilePath* thumbnail_path);
|
||||
bool GetMediaPath(const std::string& id, base::FilePath* media_path);
|
||||
|
||||
base::FilePath GetPlaylistItemDirPath(const std::string& id) const;
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Copyright (c) 2021 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 http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import("//brave/components/common/typescript.gni")
|
||||
|
||||
transpile_web_ui("playlist_ui") {
|
||||
entry_points = [ [
|
||||
"playlist",
|
||||
rebase_path("playlist.tsx"),
|
||||
] ]
|
||||
|
||||
deps = [
|
||||
"../mojom:mojom_js",
|
||||
"//mojo/public/mojom/base",
|
||||
]
|
||||
|
||||
resource_name = "playlist"
|
||||
}
|
||||
|
||||
pack_web_resources("generated_resources") {
|
||||
resource_name = "playlist"
|
||||
output_dir = "$root_gen_dir/brave/components/playlist/resources"
|
||||
deps = [ ":playlist_ui" ]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { action } from 'typesafe-actions'
|
||||
|
||||
import * as PlaylistMojo from 'gen/brave/components/playlist/mojom/playlist.mojom.m.js'
|
||||
|
||||
// Constants
|
||||
import { types } from '../constants/playlist_types'
|
||||
|
||||
export const playlistLoaded = (playlist: PlaylistMojo.Playlist[]) =>
|
||||
action(types.PLAYLIST_LOADED, playlist)
|
||||
@@ -0,0 +1,66 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import * as PlaylistMojo from 'gen/brave/components/playlist/mojom/playlist.mojom.m.js'
|
||||
import { Url } from 'gen/url/mojom/url.mojom.m.js'
|
||||
|
||||
type PlaylistEventListener = (event: PlaylistMojo.PlaylistEvent) => void
|
||||
|
||||
interface API {
|
||||
pageCallbackRouter: PlaylistMojo.PageCallbackRouter
|
||||
pageHandler: PlaylistMojo.PageHandlerRemote
|
||||
|
||||
getAllPlaylists: () => Promise<{playlists: PlaylistMojo.Playlist[]}>
|
||||
getPlaylist: (id: string) => Promise<{playlist?: PlaylistMojo.Playlist}>
|
||||
addMediaFilesFromPageToPlaylist: (playlistId: string, url: string) => void
|
||||
removeItemFromPlaylist: (playlistId: string, itemId: string) => void
|
||||
|
||||
addEventListener: (listener: PlaylistEventListener) => void
|
||||
}
|
||||
|
||||
let playlistAPIInstance: API
|
||||
|
||||
class PlaylistAPIInstance implements API {
|
||||
pageCallbackRouter = new PlaylistMojo.PageCallbackRouter()
|
||||
pageHandler = new PlaylistMojo.PageHandlerRemote()
|
||||
|
||||
constructor () {
|
||||
const factory = PlaylistMojo.PageHandlerFactory.getRemote()
|
||||
factory.createPageHandler(
|
||||
this.pageCallbackRouter.$.bindNewPipeAndPassRemote(),
|
||||
this.pageHandler.$.bindNewPipeAndPassReceiver())
|
||||
}
|
||||
|
||||
getAllPlaylists = async function ():
|
||||
Promise<{playlists: PlaylistMojo.Playlist[]}> {
|
||||
return this.pageHandler.getAllPlaylists()
|
||||
}
|
||||
|
||||
getPlaylist = async function (id: string):
|
||||
Promise<{playlist?: PlaylistMojo.Playlist}> {
|
||||
return this.pageHandler.getPlaylist(id)
|
||||
}
|
||||
|
||||
addEventListener (listener: PlaylistEventListener) {
|
||||
this.pageCallbackRouter.onEvent.addListener(listener)
|
||||
}
|
||||
|
||||
addMediaFilesFromPageToPlaylist (playlistId: string, url: string) {
|
||||
let mojoUrl = new Url()
|
||||
mojoUrl.url = url
|
||||
this.pageHandler.addMediaFilesFromPageToPlaylist(
|
||||
playlistId, mojoUrl)
|
||||
}
|
||||
|
||||
removeItemFromPlaylist (playlistId: string, itemId: string) {
|
||||
this.pageHandler.removeItemFromPlaylist(playlistId, itemId)
|
||||
}
|
||||
}
|
||||
|
||||
export function getPlaylistAPI (): API {
|
||||
if (!playlistAPIInstance) {
|
||||
playlistAPIInstance = new PlaylistAPIInstance()
|
||||
}
|
||||
return playlistAPIInstance
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import store from '../store'
|
||||
|
||||
import * as PlaylistActions from '../actions/playlist_action_creators'
|
||||
import { bindActionCreators, Dispatch } from 'redux'
|
||||
|
||||
type Action = typeof PlaylistActions
|
||||
|
||||
let actions: Action
|
||||
|
||||
// returns actions bound to dispatch.
|
||||
export function getAllActions () {
|
||||
actions = actions || getActionsForDispatch(store.dispatch.bind(store))
|
||||
return actions
|
||||
}
|
||||
|
||||
export function getActionsForDispatch (dispatch: Dispatch) {
|
||||
return bindActionCreators(PlaylistActions, dispatch)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import { getAllActions } from './api/getAllActions'
|
||||
import { getPlaylistAPI } from './api/api'
|
||||
|
||||
async function getInitialData () {
|
||||
return getPlaylistAPI().getAllPlaylists()
|
||||
}
|
||||
|
||||
export default function wireApiEventsToStore () {
|
||||
// Get initial data and dispatch to store
|
||||
getInitialData()
|
||||
.then((initialData) => {
|
||||
getAllActions().playlistLoaded(initialData.playlists)
|
||||
|
||||
// TODO: Add proper event listeners for changes to playlist
|
||||
getPlaylistAPI().addEventListener((e) => {
|
||||
getInitialData().then(data => { getAllActions().playlistLoaded(data.playlists) })
|
||||
})
|
||||
})
|
||||
.catch(e => { console.error('New Tab Page fatal error:', e) })
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import * as React from 'react'
|
||||
import { bindActionCreators, Dispatch } from 'redux'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { Playlist } from 'components/definitions/playlist'
|
||||
|
||||
// Components for playlist
|
||||
import { CloseCircleOIcon } from 'brave-ui/components/icons'
|
||||
import Table, { Cell, Row } from 'brave-ui/components/dataTables/table'
|
||||
import PlaylistItem from './playlistItem'
|
||||
|
||||
// Utils
|
||||
import * as playlistActions from '../actions/playlist_action_creators'
|
||||
import { getPlaylistAPI } from '../api/api'
|
||||
|
||||
import * as PlaylistMojo from 'gen/brave/components/playlist/mojom/playlist.mojom.m.js'
|
||||
|
||||
interface Props {
|
||||
actions: any
|
||||
playlistData: Playlist.State
|
||||
}
|
||||
|
||||
interface State {
|
||||
playlist?: PlaylistMojo.Playlist
|
||||
experimentalUrl: string
|
||||
}
|
||||
|
||||
export class PlaylistPage extends React.Component<Props, State> {
|
||||
constructor (props: Props) {
|
||||
super(props)
|
||||
this.state = { experimentalUrl: '' }
|
||||
this.onClickDownloadVideo = this.onClickDownloadVideo.bind(this)
|
||||
}
|
||||
|
||||
get actions () {
|
||||
return this.props.actions
|
||||
}
|
||||
|
||||
getCurrentPlaylist = () => {
|
||||
// TODO(sko): get current playlist index from store
|
||||
return this.props.playlistData.lists.at(0)
|
||||
}
|
||||
|
||||
getImgSrc = (itemId: string) => {
|
||||
return 'chrome://playlist-data/' + itemId + '/thumbnail/'
|
||||
}
|
||||
|
||||
getMediaSrc = (itemId: string) => {
|
||||
return 'chrome://playlist-data/' + itemId + '/media'
|
||||
}
|
||||
|
||||
get lazyButtonStyle () {
|
||||
const lazyButtonStyle: any = {
|
||||
alignItems: 'center',
|
||||
WebkitAppearance: 'none',
|
||||
width: '50px',
|
||||
height: '50px',
|
||||
display: 'flex',
|
||||
borderRadius: '4px'
|
||||
}
|
||||
return lazyButtonStyle
|
||||
}
|
||||
|
||||
getPlaylistHeader = (): Cell[] => {
|
||||
return [
|
||||
{ content: 'INDEX' },
|
||||
{ content: 'NAME' },
|
||||
{ content: 'STATUS' },
|
||||
{ content: 'REMOVE' }
|
||||
]
|
||||
}
|
||||
|
||||
getPlaylistRows = (playlist?: any): Row[] | undefined => {
|
||||
if (!playlist || !playlist.items) {
|
||||
return
|
||||
}
|
||||
|
||||
return playlist.items.map((item: PlaylistMojo.PlaylistItem, index: any): any => {
|
||||
const cell: Row = {
|
||||
content: [
|
||||
{ content: (<div style={{ textAlign: 'center' }}>{index + 1}</div>) },
|
||||
{
|
||||
content: (
|
||||
<PlaylistItem id={item.id} name={item.name} onClick={this.onClickItem.bind(this)}
|
||||
thumbnailUrl={item.thumbnailPath.url.startsWith('http') ? item.thumbnailPath.url : this.getImgSrc(item.id)}/>
|
||||
)
|
||||
},
|
||||
{ content: (<span>{item.ready ? 'Ready' : 'Downloading'}</span>) },
|
||||
{ content: (<button style={this.lazyButtonStyle} onClick={this.onClickRemoveItemButton.bind(this, item.id)}><CloseCircleOIcon /></button>) }
|
||||
]
|
||||
}
|
||||
return cell
|
||||
})
|
||||
}
|
||||
|
||||
onClickRemoveItemButton = (playlistItemId: string) => {
|
||||
getPlaylistAPI().removeItemFromPlaylist('', playlistItemId)
|
||||
}
|
||||
|
||||
get pageHasDownloadableVideo () {
|
||||
return this.state.experimentalUrl.startsWith('https://www.youtube.com/watch')
|
||||
}
|
||||
|
||||
onChangeExperimentalUrl = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
this.setState({ experimentalUrl: event.target.value })
|
||||
}
|
||||
|
||||
onClickDownloadVideo = () => {
|
||||
getPlaylistAPI().addMediaFilesFromPageToPlaylist('', this.state.experimentalUrl)
|
||||
}
|
||||
|
||||
onClickItem = (itemId: string) => {
|
||||
const item = this.getCurrentPlaylist()?.items.find((item: PlaylistMojo.PlaylistItem) => {
|
||||
return item.id === itemId
|
||||
})
|
||||
|
||||
if (!item || !item.ready) {
|
||||
return
|
||||
}
|
||||
|
||||
document.getElementById('player')?.setAttribute('src', this.getMediaSrc(itemId))
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div id='playlistPage'>
|
||||
<div style={{ minHeight: '600px', width: '1200px' }}>
|
||||
<Table header={this.getPlaylistHeader()} rows={this.getPlaylistRows(this.getCurrentPlaylist())}>
|
||||
YOUR PLAYLIST IS EMPTY
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<video id="player" controls autoPlay />
|
||||
|
||||
<div>
|
||||
<h1>Experimental</h1>
|
||||
<div>
|
||||
<textarea
|
||||
cols={100}
|
||||
rows={10}
|
||||
value={this.state.experimentalUrl}
|
||||
onChange={this.onChangeExperimentalUrl}
|
||||
/>
|
||||
<div>
|
||||
{
|
||||
this.pageHasDownloadableVideo
|
||||
? (
|
||||
<div>
|
||||
<h1>This page has a video you can download</h1>
|
||||
<button onClick={this.onClickDownloadVideo}>Click here to download</button>
|
||||
</div>
|
||||
) : (
|
||||
<h1>Nothing to see here. Put a proper YouTube link to see the magic</h1>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const mapStateToProps = (state: Playlist.ApplicationState) => ({
|
||||
playlistData: state.playlistData
|
||||
})
|
||||
|
||||
export const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
actions: bindActionCreators(playlistActions, dispatch)
|
||||
})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PlaylistPage)
|
||||
@@ -0,0 +1,31 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
name: string
|
||||
thumbnailUrl: string
|
||||
|
||||
onClick: (id: string) => void
|
||||
}
|
||||
|
||||
export default class PlaylistItem extends React.PureComponent<Props, {}> {
|
||||
render () {
|
||||
let { id, name, thumbnailUrl, onClick } = this.props
|
||||
return (
|
||||
<div>
|
||||
<h3>{name}</h3>
|
||||
<a href='#' onClick={() => { onClick(id) }}>
|
||||
<img
|
||||
style={{ maxWidth: '200px' }}
|
||||
data-id={id}
|
||||
src={thumbnailUrl}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
export const enum types {
|
||||
PLAYLIST_LOADED = '@@playlist/PLAYLIST_LOADED',
|
||||
|
||||
// TODO(sko) Need more actions for each events
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Brave Playlist</title>
|
||||
<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
|
||||
<link rel="import" href="chrome://resources/html/cr.html">
|
||||
<script src="chrome://resources/js/cr.js"></script>
|
||||
<script src="chrome://resources/js/load_time_data.js"></script>
|
||||
<script src="chrome://resources/js/util.js"></script>
|
||||
<script src="chrome://resources/js/i18n_template_no_process.js"></script>
|
||||
<script src="/playlist.bundle.js"></script>
|
||||
<script src="/strings.js"></script>
|
||||
<style>
|
||||
#root { height: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,43 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import * as React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
|
||||
// Components
|
||||
import App from './components/app'
|
||||
|
||||
// Theme
|
||||
import BraveCoreThemeProvider from '../../common/BraveCoreThemeProvider'
|
||||
import Theme from 'brave-ui/theme/brave-default'
|
||||
import DarkTheme from 'brave-ui/theme/brave-dark'
|
||||
import wireApiEventsToStore from './apiEventsToStore'
|
||||
|
||||
// Utils
|
||||
import store from './store'
|
||||
|
||||
function initialize () {
|
||||
new Promise(resolve => chrome.braveTheme.getBraveThemeType(resolve))
|
||||
.then((themeType: chrome.braveTheme.ThemeType) => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<BraveCoreThemeProvider
|
||||
initialThemeType={themeType}
|
||||
dark={DarkTheme}
|
||||
light={Theme}
|
||||
>
|
||||
<App />
|
||||
</BraveCoreThemeProvider>
|
||||
</Provider>,
|
||||
document.getElementById('root'))
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Problem mounting playlist', error)
|
||||
})
|
||||
}
|
||||
|
||||
wireApiEventsToStore()
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initialize)
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<grit-part>
|
||||
<if expr="enable_playlist">
|
||||
<include name="IDR_PLAYLIST_HTML" file="../playlist/resources/playlist.html" type="BINDATA" />
|
||||
</if>
|
||||
</grit-part>
|
||||
@@ -0,0 +1,13 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { combineReducers } from 'redux'
|
||||
|
||||
import { Playlist } from 'components/definitions/playlist'
|
||||
|
||||
// Utils
|
||||
import playlistReducer from './playlist_reducer'
|
||||
|
||||
export default combineReducers<Playlist.ApplicationState>(
|
||||
{ playlistData: playlistReducer })
|
||||
@@ -0,0 +1,27 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { Playlist } from 'components/definitions/playlist'
|
||||
import { Reducer } from 'redux'
|
||||
|
||||
import { types } from '../constants/playlist_types'
|
||||
|
||||
import * as PlaylistMojo from 'gen/brave/components/playlist/mojom/playlist.mojom.m.js'
|
||||
|
||||
const playlistReducer: Reducer<Playlist.State|undefined> =
|
||||
(state: Playlist.State|undefined, action) => {
|
||||
if (state === undefined) {
|
||||
state = { lists: [] }
|
||||
}
|
||||
|
||||
// TODO(sko) Handle each action properly.
|
||||
switch (action.type) {
|
||||
case types.PLAYLIST_LOADED:
|
||||
state = { ...state, lists: [...action.payload as PlaylistMojo.Playlist[]] }
|
||||
break
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
export default playlistReducer
|
||||
@@ -0,0 +1,10 @@
|
||||
/* 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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { createStore } from 'redux'
|
||||
|
||||
// Utils
|
||||
import reducers from './reducers'
|
||||
|
||||
export default createStore(reducers)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig",
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.d.ts",
|
||||
"../../definitions/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import("//brave/components/brave_vpn/buildflags/buildflags.gni")
|
||||
import("//brave/components/crypto_dot_com/browser/buildflags/buildflags.gni")
|
||||
import("//brave/components/ftx/browser/buildflags/buildflags.gni")
|
||||
import("//brave/components/ipfs/buildflags/buildflags.gni")
|
||||
import("//brave/components/playlist/buildflags/buildflags.gni")
|
||||
import("//brave/components/sidebar/buildflags/buildflags.gni")
|
||||
import("//brave/components/speedreader/common/buildflags.gni")
|
||||
import("//brave/components/tor/buildflags/buildflags.gni")
|
||||
@@ -37,11 +38,12 @@ brave_grit("static_resources") {
|
||||
}
|
||||
|
||||
defines = [
|
||||
"enable_speedreader=$enable_speedreader",
|
||||
"enable_ipfs=$enable_ipfs",
|
||||
"crypto_dot_com_enabled=$crypto_dot_com_enabled",
|
||||
"enable_ftx=$enable_ftx",
|
||||
"enable_brave_vpn=$enable_brave_vpn",
|
||||
"enable_ftx=$enable_ftx",
|
||||
"enable_ipfs=$enable_ipfs",
|
||||
"enable_playlist=$enable_playlist",
|
||||
"enable_speedreader=$enable_speedreader",
|
||||
]
|
||||
|
||||
output_dir = "$root_gen_dir/components"
|
||||
@@ -112,6 +114,11 @@ repack("resources") {
|
||||
}
|
||||
}
|
||||
|
||||
if (enable_playlist) {
|
||||
deps += [ "//brave/components/playlist/resources:generated_resources" ]
|
||||
sources += [ "$root_gen_dir/brave/components/playlist/resources/playlist_generated.pak" ]
|
||||
}
|
||||
|
||||
output = "$root_gen_dir/components/brave_components_resources.pak"
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<include name="IDR_BRAVE_PRIVATE_TAB_TOR_IMG" file="../img/newtab/private-window-tor.svg" type="BINDATA" />
|
||||
|
||||
<part file="../brave_perf_predictor/resources/brave_perf_predictor_resources.grdp" />
|
||||
<part file="../playlist/resources/playlist_resources.grdp" />
|
||||
<part file="brave_blank_page_resources.grdp" />
|
||||
<part file="brave_translate_resources.grdp" />
|
||||
<part file="speedreader_resources.grdp" />
|
||||
|
||||
@@ -192,4 +192,8 @@
|
||||
"META": {"sizes": {"includes": [20]}},
|
||||
"includes": [59000]
|
||||
},
|
||||
"<(SHARED_INTERMEDIATE_DIR)/brave/web-ui-playlist/playlist.grd": {
|
||||
"META": {"sizes": {"includes": [250]}},
|
||||
"includes": [59020]
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user