diff --git a/app/theme/brave_theme_resources.grd b/app/theme/brave_theme_resources.grd
index 58da4529ee29..d00e0d525ec3 100644
--- a/app/theme/brave_theme_resources.grd
+++ b/app/theme/brave_theme_resources.grd
@@ -21,6 +21,10 @@
+
+
+
+
diff --git a/app/theme/default_100_percent/common/brave_bookmark_folder_open-lin-dark.png b/app/theme/default_100_percent/common/brave_bookmark_folder_open-lin-dark.png
new file mode 100644
index 000000000000..bb9048be9ffe
Binary files /dev/null and b/app/theme/default_100_percent/common/brave_bookmark_folder_open-lin-dark.png differ
diff --git a/app/theme/default_100_percent/common/brave_bookmark_folder_open-lin-light.png b/app/theme/default_100_percent/common/brave_bookmark_folder_open-lin-light.png
new file mode 100644
index 000000000000..9a4541f0d717
Binary files /dev/null and b/app/theme/default_100_percent/common/brave_bookmark_folder_open-lin-light.png differ
diff --git a/app/theme/default_100_percent/common/brave_bookmark_folder_open-win-dark.png b/app/theme/default_100_percent/common/brave_bookmark_folder_open-win-dark.png
new file mode 100644
index 000000000000..4e38c4ea0919
Binary files /dev/null and b/app/theme/default_100_percent/common/brave_bookmark_folder_open-win-dark.png differ
diff --git a/app/theme/default_100_percent/common/brave_bookmark_folder_open-win-light.png b/app/theme/default_100_percent/common/brave_bookmark_folder_open-win-light.png
new file mode 100644
index 000000000000..4e38c4ea0919
Binary files /dev/null and b/app/theme/default_100_percent/common/brave_bookmark_folder_open-win-light.png differ
diff --git a/app/theme/default_200_percent/common/brave_bookmark_folder_open-lin-dark.png b/app/theme/default_200_percent/common/brave_bookmark_folder_open-lin-dark.png
new file mode 100644
index 000000000000..63c72f0453bc
Binary files /dev/null and b/app/theme/default_200_percent/common/brave_bookmark_folder_open-lin-dark.png differ
diff --git a/app/theme/default_200_percent/common/brave_bookmark_folder_open-lin-light.png b/app/theme/default_200_percent/common/brave_bookmark_folder_open-lin-light.png
new file mode 100644
index 000000000000..68fb716a17ae
Binary files /dev/null and b/app/theme/default_200_percent/common/brave_bookmark_folder_open-lin-light.png differ
diff --git a/app/theme/default_200_percent/common/brave_bookmark_folder_open-win-dark.png b/app/theme/default_200_percent/common/brave_bookmark_folder_open-win-dark.png
new file mode 100644
index 000000000000..38e3de4d6f08
Binary files /dev/null and b/app/theme/default_200_percent/common/brave_bookmark_folder_open-win-dark.png differ
diff --git a/app/theme/default_200_percent/common/brave_bookmark_folder_open-win-light.png b/app/theme/default_200_percent/common/brave_bookmark_folder_open-win-light.png
new file mode 100644
index 000000000000..38e3de4d6f08
Binary files /dev/null and b/app/theme/default_200_percent/common/brave_bookmark_folder_open-win-light.png differ
diff --git a/brave_paks.gni b/brave_paks.gni
index 93850571adc5..1c86ea2ac010 100644
--- a/brave_paks.gni
+++ b/brave_paks.gni
@@ -3,6 +3,7 @@
# You can obtain one at http://mozilla.org/MPL/2.0/.
import("//brave/components/brave_webtorrent/browser/buildflags/buildflags.gni")
+import("//brave/components/sidebar/buildflags/buildflags.gni")
import("//brave/components/tor/buildflags/buildflags.gni")
import("//build/config/locales.gni")
import("//chrome/common/features.gni")
@@ -83,6 +84,13 @@ template("brave_extra_paks") {
]
}
+ if (enable_sidebar) {
+ sources += [
+ "$root_gen_dir/brave/browser/resources/sidebar/sidebar_resources.pak",
+ ]
+ deps += [ "//brave/browser/resources/sidebar:resources" ]
+ }
+
if (enable_tor) {
sources +=
[ "$root_gen_dir/brave/components/tor/resources/tor_resources.pak" ]
diff --git a/browser/brave_content_browser_client.cc b/browser/brave_content_browser_client.cc
index 02c787250aed..cbf85f9cc640 100644
--- a/browser/brave_content_browser_client.cc
+++ b/browser/brave_content_browser_client.cc
@@ -61,6 +61,7 @@
#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/sidebar/buildflags/buildflags.h"
#include "brave/components/skus/common/skus_sdk.mojom.h"
#include "brave/components/speedreader/buildflags.h"
#include "brave/components/speedreader/speedreader_util.h"
@@ -176,6 +177,12 @@ using extensions::ChromeContentBrowserClientExtensionsPart;
#include "brave/browser/ethereum_remote_client/ethereum_remote_client_service_factory.h"
#endif
+#if BUILDFLAG(ENABLE_SIDEBAR)
+#include "brave/browser/ui/webui/sidebar/sidebar.mojom.h"
+#include "brave/browser/ui/webui/sidebar/sidebar_bookmarks_ui.h"
+#include "brave/components/sidebar/features.h"
+#endif
+
#if !defined(OS_ANDROID)
#include "brave/browser/new_tab/new_tab_shows_navigation_throttle.h"
#include "brave/browser/ui/webui/brave_shields/shields_panel_ui.h"
@@ -493,6 +500,13 @@ void BraveContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
}
#endif
+#if BUILDFLAG(ENABLE_SIDEBAR)
+ if (base::FeatureList::IsEnabled(sidebar::kSidebarFeature)) {
+ chrome::internal::RegisterWebUIControllerInterfaceBinder<
+ sidebar::mojom::BookmarksPageHandlerFactory, SidebarBookmarksUI>(map);
+ }
+#endif
+
// Brave News
#if !defined(OS_ANDROID)
if (base::FeatureList::IsEnabled(brave_today::features::kBraveNewsFeature)) {
diff --git a/browser/resources/resource_ids b/browser/resources/resource_ids
index 170b4e6d523b..5af5e35e3df8 100644
--- a/browser/resources/resource_ids
+++ b/browser/resources/resource_ids
@@ -1,4 +1,4 @@
-# Resource ids starting at 31000 are reserved for projects built on Chromium.
+# Resource ids starting at 38000 are reserved for projects built on Chromium.
{
"SRCDIR": "../../..",
"brave/common/extensions/api/brave_api_resources.grd": {
@@ -127,4 +127,7 @@
"<(ROOT_GEN_DIR)/brave/web-ui-trezor_bridge/trezor_bridge.grd": {
"includes": [51250]
},
+ "<(SHARED_INTERMEDIATE_DIR)/brave/browser/resources/sidebar/sidebar_resources.grd": {
+ "includes": [51500]
+ },
}
diff --git a/browser/resources/sidebar/BUILD.gn b/browser/resources/sidebar/BUILD.gn
new file mode 100644
index 000000000000..94a6ecb5a1f9
--- /dev/null
+++ b/browser/resources/sidebar/BUILD.gn
@@ -0,0 +1,34 @@
+# 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("//chrome/common/features.gni")
+import("//tools/grit/grit_rule.gni")
+import("//ui/webui/resources/tools/generate_grd.gni")
+
+grit("resources") {
+ defines = chrome_grit_defines
+ defines +=
+ [ "SHARED_INTERMEDIATE_DIR=" + rebase_path(root_gen_dir, root_build_dir) ]
+
+ enable_input_discovery_for_gn_analyze = false
+ source = "$target_gen_dir/sidebar_resources.grd"
+ deps = [ ":build_grd" ]
+
+ outputs = [
+ "grit/sidebar_resources.h",
+ "grit/sidebar_resources_map.cc",
+ "grit/sidebar_resources_map.h",
+ "sidebar_resources.pak",
+ ]
+ output_dir = "$root_gen_dir/brave/browser/resources/sidebar"
+ resource_ids = "//brave/browser/resources/resource_ids"
+}
+
+generate_grd("build_grd") {
+ grdp_files = [ "$target_gen_dir/bookmarks/resources.grdp" ]
+ deps = [ "bookmarks:build_grdp" ]
+ grd_prefix = "sidebar"
+ out_grd = "$target_gen_dir/${grd_prefix}_resources.grd"
+}
diff --git a/browser/resources/sidebar/bookmarks/BUILD.gn b/browser/resources/sidebar/bookmarks/BUILD.gn
new file mode 100644
index 000000000000..b227f7ebcec3
--- /dev/null
+++ b/browser/resources/sidebar/bookmarks/BUILD.gn
@@ -0,0 +1,85 @@
+# 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("//tools/grit/preprocess_if_expr.gni")
+import("//tools/polymer/html_to_js.gni")
+import("//tools/typescript/ts_library.gni")
+import("//ui/webui/resources/tools/generate_grd.gni")
+
+preprocess_folder =
+ "$root_gen_dir/brave/browser/resources/sidebar/bookmarks/preprocessed"
+
+generate_grd("build_grdp") {
+ grd_prefix = "sidebar_bookmarks"
+ out_grd = "$target_gen_dir/resources.grdp"
+ deps = [ ":build_ts" ]
+ manifest_files = [
+ "$root_gen_dir/brave/browser/resources/sidebar/bookmarks/tsconfig.manifest",
+ ]
+ input_files = [ "bookmarks.html" ]
+ input_files_base_dir = rebase_path(".", "//")
+}
+
+preprocess_if_expr("preprocess") {
+ in_folder = "./"
+ out_folder = preprocess_folder
+ in_files = [
+ "bookmarks_api_proxy.ts",
+ "bookmarks_drag_manager.ts",
+ ]
+}
+
+preprocess_if_expr("preprocess_generated") {
+ deps = [ ":web_components" ]
+ in_folder = target_gen_dir
+ out_folder = preprocess_folder
+ in_files = [
+ "bookmark_folder.ts",
+ "bookmarks_list.ts",
+ ]
+}
+
+preprocess_if_expr("preprocess_mojo") {
+ deps = [ "//brave/browser/ui/webui/sidebar:mojo_bindings_webui_js" ]
+ in_folder = "$root_gen_dir/mojom-webui/brave/browser/ui/webui/sidebar/"
+ out_folder = preprocess_folder
+ out_manifest = "$target_gen_dir/preprocessed_mojo_manifest.json"
+ in_files = [ "sidebar.mojom-webui.js" ]
+}
+
+html_to_js("web_components") {
+ js_files = [
+ "bookmark_folder.ts",
+ "bookmarks_list.ts",
+ ]
+}
+
+ts_library("build_ts") {
+ tsconfig_base = "tsconfig_base.json"
+ root_dir = "$target_gen_dir/preprocessed"
+ out_dir = "$target_gen_dir/tsc"
+ in_files = [
+ "bookmark_folder.ts",
+ "bookmarks_list.ts",
+ "bookmarks_api_proxy.ts",
+ "bookmarks_drag_manager.ts",
+ "sidebar.mojom-webui.js",
+ ]
+ definitions = [
+ "//tools/typescript/definitions/bookmark_manager_private.d.ts",
+ "//tools/typescript/definitions/bookmarks.d.ts",
+ "//tools/typescript/definitions/chrome_event.d.ts",
+ ]
+ deps = [
+ "//third_party/polymer/v3_0:library",
+ "//ui/webui/resources:library",
+ "//ui/webui/resources/mojo:library",
+ ]
+ extra_deps = [
+ ":preprocess",
+ ":preprocess_generated",
+ ":preprocess_mojo",
+ ]
+}
diff --git a/browser/resources/sidebar/bookmarks/bookmark_folder.html b/browser/resources/sidebar/bookmarks/bookmark_folder.html
new file mode 100644
index 000000000000..43d9ddc7d08a
--- /dev/null
+++ b/browser/resources/sidebar/bookmarks/bookmark_folder.html
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/browser/resources/sidebar/bookmarks/bookmark_folder.ts b/browser/resources/sidebar/bookmarks/bookmark_folder.ts
new file mode 100644
index 000000000000..bedae0319c1b
--- /dev/null
+++ b/browser/resources/sidebar/bookmarks/bookmark_folder.ts
@@ -0,0 +1,232 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import 'chrome://resources/cr_elements/mwb_element_shared_style.js';
+
+import {getFaviconForPageURL} from 'chrome://resources/js/icon.js';
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {BookmarksApiProxy} from './bookmarks_api_proxy.js';
+
+/** Event interface for dom-repeat. */
+interface RepeaterMouseEvent extends MouseEvent {
+ clientX: number;
+ clientY: number;
+ model: {
+ item: chrome.bookmarks.BookmarkTreeNode,
+ };
+}
+
+export interface BookmarkFolderElement {
+ $: {
+ children: HTMLElement,
+ };
+}
+
+// Event name for open state of a folder being changed.
+export const FOLDER_OPEN_CHANGED_EVENT = 'bookmark-folder-open-changed';
+
+export class BookmarkFolderElement extends PolymerElement {
+ static get is() {
+ return 'bookmark-folder';
+ }
+
+ static get template() {
+ return html`{__html_template__}`;
+ }
+
+ static get properties() {
+ return {
+ childDepth_: {
+ type: Number,
+ value: 1,
+ },
+
+ depth: {
+ type: Number,
+ observer: 'onDepthChanged_',
+ value: 0,
+ },
+
+ folder: Object,
+
+ open_: {
+ type: Boolean,
+ value: false,
+ },
+
+ openFolders: {
+ type: Array,
+ observer: 'onOpenFoldersChanged_',
+ },
+ };
+ }
+
+ private childDepth_: number;
+ depth: number;
+ folder: chrome.bookmarks.BookmarkTreeNode;
+ private open_: boolean;
+ openFolders: string[];
+ private bookmarksApi_: BookmarksApiProxy = BookmarksApiProxy.getInstance();
+
+ static get observers() {
+ return [
+ 'onChildrenLengthChanged_(folder.children.length)',
+ ];
+ }
+
+ private getAriaExpanded_(): string|undefined {
+ if (!this.folder.children || this.folder.children.length === 0) {
+ // Remove the attribute for empty folders that cannot be expanded.
+ return undefined;
+ }
+
+ return this.open_ ? 'true' : 'false';
+ }
+
+ private onBookmarkAuxClick_(event: RepeaterMouseEvent) {
+ if (event.button !== 1) {
+ // Not a middle click.
+ return;
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+ this.bookmarksApi_.openBookmark(event.model.item.url!, this.depth, {
+ middleButton: true,
+ altKey: event.altKey,
+ ctrlKey: event.ctrlKey,
+ metaKey: event.metaKey,
+ shiftKey: event.shiftKey,
+ });
+ }
+
+ private onBookmarkClick_(event: RepeaterMouseEvent) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.bookmarksApi_.openBookmark(event.model.item.url!, this.depth, {
+ middleButton: false,
+ altKey: event.altKey,
+ ctrlKey: event.ctrlKey,
+ metaKey: event.metaKey,
+ shiftKey: event.shiftKey,
+ });
+ }
+
+ private onBookmarkContextMenu_(event: RepeaterMouseEvent) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.bookmarksApi_.showContextMenu(
+ event.model.item.id, event.clientX, event.clientY);
+ }
+
+ private onFolderContextMenu_(event: MouseEvent) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.bookmarksApi_.showContextMenu(
+ this.folder.id, event.clientX, event.clientY);
+ }
+
+ private getBookmarkIcon_(url: string): string {
+ return getFaviconForPageURL(url, false);
+ }
+
+ private onChildrenLengthChanged_() {
+ if (this.folder.children) {
+ this.style.setProperty(
+ '--child-count', this.folder.children!.length.toString());
+ } else {
+ this.style.setProperty('--child-count', '0');
+ }
+ }
+
+ private onDepthChanged_() {
+ this.childDepth_ = this.depth + 1;
+ this.style.setProperty('--node-depth', `${this.depth}`);
+ this.style.setProperty('--child-depth', `${this.childDepth_}`);
+ }
+
+ private onFolderClick_(event: Event) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (!this.folder.children || this.folder.children.length === 0) {
+ // No reason to open if there are no children to show.
+ return;
+ }
+
+ this.open_ = !this.open_;
+ this.dispatchEvent(new CustomEvent(FOLDER_OPEN_CHANGED_EVENT, {
+ bubbles: true,
+ composed: true,
+ detail: {
+ id: this.folder.id,
+ open: this.open_,
+ }
+ }));
+ }
+
+ private onOpenFoldersChanged_() {
+ this.open_ =
+ Boolean(this.openFolders) && this.openFolders.includes(this.folder.id);
+ }
+
+ private getFocusableRows_(): HTMLElement[] {
+ return Array.from(
+ this.shadowRoot!.querySelectorAll('.row, bookmark-folder'));
+ }
+
+ moveFocus(delta: -1|1): boolean {
+ const currentFocus = this.shadowRoot!.activeElement;
+ if (currentFocus instanceof BookmarkFolderElement &&
+ currentFocus.moveFocus(delta)) {
+ // If focus is already inside a nested folder, delegate the focus to the
+ // nested folder and return early if successful.
+ return true;
+ }
+
+ let moveFocusTo = null;
+ const focusableRows = this.getFocusableRows_();
+ if (currentFocus) {
+ // If focus is in this folder, move focus to the next or previous
+ // focusable row.
+ const currentFocusIndex =
+ focusableRows.indexOf(currentFocus as HTMLElement);
+ moveFocusTo = focusableRows[currentFocusIndex + delta];
+ } else {
+ // If focus is not in this folder yet, move focus to either end.
+ moveFocusTo = delta === 1 ? focusableRows[0] :
+ focusableRows[focusableRows.length - 1];
+ }
+
+ if (moveFocusTo instanceof BookmarkFolderElement) {
+ return moveFocusTo.moveFocus(delta);
+ } else if (moveFocusTo) {
+ moveFocusTo.focus();
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+
+customElements.define(BookmarkFolderElement.is, BookmarkFolderElement);
+
+interface DraggableElement extends HTMLElement {
+ dataBookmark: chrome.bookmarks.BookmarkTreeNode;
+}
+
+export function getBookmarkFromElement(element: HTMLElement) {
+ return (element as DraggableElement).dataBookmark;
+}
+
+export function isValidDropTarget(element: HTMLElement) {
+ return element.id === 'folder' || element.classList.contains('bookmark');
+}
+
+export function isBookmarkFolderElement(element: HTMLElement): boolean {
+ return element.id === 'folder';
+}
\ No newline at end of file
diff --git a/browser/resources/sidebar/bookmarks/bookmarks.html b/browser/resources/sidebar/bookmarks/bookmarks.html
new file mode 100644
index 000000000000..f720ed28842a
--- /dev/null
+++ b/browser/resources/sidebar/bookmarks/bookmarks.html
@@ -0,0 +1,34 @@
+
+
+
+
+ $i18n{bookmarksTitle}
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/resources/sidebar/bookmarks/bookmarks_api_proxy.ts b/browser/resources/sidebar/bookmarks/bookmarks_api_proxy.ts
new file mode 100644
index 000000000000..545934ef6bf1
--- /dev/null
+++ b/browser/resources/sidebar/bookmarks/bookmarks_api_proxy.ts
@@ -0,0 +1,78 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
+import 'chrome://resources/mojo/url/mojom/url.mojom-lite.js';
+
+import {ChromeEvent} from '/tools/typescript/definitions/chrome_event.js';
+import {ClickModifiers} from 'chrome://resources/mojo/ui/base/mojom/window_open_disposition.mojom-webui.js';
+
+import {BookmarksPageHandlerFactory, BookmarksPageHandlerRemote} from './sidebar.mojom-webui.js';
+
+let instance: BookmarksApiProxy|null = null;
+
+export class BookmarksApiProxy {
+ callbackRouter: {[key: string]: ChromeEvent};
+ handler: BookmarksPageHandlerRemote;
+
+ constructor() {
+ this.callbackRouter = {
+ onChanged: chrome.bookmarks.onChanged,
+ onChildrenReordered: chrome.bookmarks.onChildrenReordered,
+ onCreated: chrome.bookmarks.onCreated,
+ onMoved: chrome.bookmarks.onMoved,
+ onRemoved: chrome.bookmarks.onRemoved,
+ };
+
+ this.handler = new BookmarksPageHandlerRemote();
+
+ const factory = BookmarksPageHandlerFactory.getRemote();
+ factory.createBookmarksPageHandler(
+ this.handler.$.bindNewPipeAndPassReceiver());
+ }
+
+ cutBookmark(id: string): Promise {
+ chrome.bookmarkManagerPrivate.cut([id]);
+ return Promise.resolve();
+ }
+
+ copyBookmark(id: string): Promise {
+ return new Promise(resolve => {
+ chrome.bookmarkManagerPrivate.copy([id], resolve);
+ });
+ }
+
+ getFolders(): Promise {
+ return new Promise(resolve => chrome.bookmarks.getTree(results => {
+ if (results[0] && results[0].children) {
+ resolve(results[0].children);
+ return;
+ }
+ resolve([]);
+ }));
+ }
+
+ openBookmark(url: string, depth: number, clickModifiers: ClickModifiers) {
+ this.handler.openBookmark({url}, depth, clickModifiers);
+ }
+
+ pasteToBookmark(parentId: string, destinationId?: string): Promise {
+ const destination = destinationId ? [destinationId] : [];
+ return new Promise(resolve => {
+ chrome.bookmarkManagerPrivate.paste(parentId, destination, resolve);
+ });
+ }
+
+ showContextMenu(id: string, x: number, y: number) {
+ this.handler.showContextMenu(id, {x, y});
+ }
+
+ static getInstance() {
+ return instance || (instance = new BookmarksApiProxy());
+ }
+
+ static setInstance(obj: BookmarksApiProxy) {
+ instance = obj;
+ }
+}
\ No newline at end of file
diff --git a/browser/resources/sidebar/bookmarks/bookmarks_drag_manager.ts b/browser/resources/sidebar/bookmarks/bookmarks_drag_manager.ts
new file mode 100644
index 000000000000..04cabff4f163
--- /dev/null
+++ b/browser/resources/sidebar/bookmarks/bookmarks_drag_manager.ts
@@ -0,0 +1,266 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import '../strings.m.js';
+
+import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+
+import {getBookmarkFromElement, isBookmarkFolderElement, isValidDropTarget} from './bookmark_folder.js';
+
+export const DROP_POSITION_ATTR = 'drop-position';
+
+const ROOT_FOLDER_ID = '0';
+
+// Ms to wait during a dragover to open closed folder.
+let folderOpenerTimeoutDelay = 1000;
+export function overrideFolderOpenerTimeoutDelay(ms: number) {
+ folderOpenerTimeoutDelay = ms;
+}
+
+export enum DropPosition {
+ ABOVE = 'above',
+ INTO = 'into',
+ BELOW = 'below',
+}
+
+interface BookmarksDragDelegate extends HTMLElement {
+ getAscendants(bookmarkId: string): string[];
+ getIndex(bookmark: chrome.bookmarks.BookmarkTreeNode): number;
+ isFolderOpen(bookmark: chrome.bookmarks.BookmarkTreeNode): boolean;
+ openFolder(folderId: string): void;
+}
+
+class DragSession {
+ private delegate_: BookmarksDragDelegate;
+ private dragData_: chrome.bookmarkManagerPrivate.DragData;
+ private lastDragOverElement_: HTMLElement|null = null;
+ private lastPointerWasTouch_ = false;
+ private folderOpenerTimeout_: number|null = null;
+
+ constructor(
+ delegate: BookmarksDragDelegate,
+ dragData: chrome.bookmarkManagerPrivate.DragData) {
+ this.delegate_ = delegate;
+ this.dragData_ = dragData;
+ }
+
+ start(e: DragEvent) {
+ chrome.bookmarkManagerPrivate.startDrag(
+ this.dragData_.elements!.map(bookmark => bookmark.id), 0,
+ this.lastPointerWasTouch_, e.clientX, e.clientY);
+ }
+
+ update(e: DragEvent) {
+ const dragOverElement = e.composedPath().find(target => {
+ return target instanceof HTMLElement && isValidDropTarget(target);
+ }) as HTMLElement;
+ if (!dragOverElement) {
+ return;
+ }
+
+ if (dragOverElement !== this.lastDragOverElement_) {
+ this.resetState_();
+ }
+
+ const dragOverBookmark = getBookmarkFromElement(dragOverElement);
+ const ascendants = this.delegate_.getAscendants(dragOverBookmark.id);
+ const isInvalidDragOverTarget = dragOverBookmark.unmodifiable ||
+ this.dragData_.elements &&
+ this.dragData_.elements.some(
+ element => ascendants.indexOf(element.id) !== -1);
+ if (isInvalidDragOverTarget) {
+ this.lastDragOverElement_ = null;
+ return;
+ }
+
+ const isDraggingOverFolder = isBookmarkFolderElement(dragOverElement);
+ const dragOverElRect = dragOverElement.getBoundingClientRect();
+ const dragOverYRatio =
+ (e.clientY - dragOverElRect.top) / dragOverElRect.height;
+
+ let dropPosition;
+ if (isDraggingOverFolder) {
+ const folderIsOpen = this.delegate_.isFolderOpen(dragOverBookmark);
+ if (dragOverBookmark.parentId === ROOT_FOLDER_ID) {
+ // Cannot drag above or below children of root folder.
+ dropPosition = DropPosition.INTO;
+ } else if (dragOverYRatio <= .25) {
+ dropPosition = DropPosition.ABOVE;
+ } else if (dragOverYRatio <= .75) {
+ dropPosition = DropPosition.INTO;
+ } else if (folderIsOpen) {
+ // If a folder is open, its child bookmarks appear immediately below it
+ // so it should not be possible to drop a bookmark right below an open
+ // folder.
+ dropPosition = DropPosition.INTO;
+ } else {
+ dropPosition = DropPosition.BELOW;
+ }
+ } else {
+ dropPosition =
+ dragOverYRatio <= .5 ? DropPosition.ABOVE : DropPosition.BELOW;
+ }
+ dragOverElement.setAttribute(DROP_POSITION_ATTR, dropPosition);
+
+ if (dropPosition === DropPosition.INTO &&
+ !this.delegate_.isFolderOpen(dragOverBookmark) &&
+ !this.folderOpenerTimeout_) {
+ // Queue a timeout to auto-open the dragged over folder.
+ this.folderOpenerTimeout_ = setTimeout(() => {
+ this.delegate_.openFolder(dragOverBookmark.id);
+ this.folderOpenerTimeout_ = null;
+ }, folderOpenerTimeoutDelay);
+ }
+
+ this.lastDragOverElement_ = dragOverElement;
+ }
+
+ cancel() {
+ this.resetState_();
+ this.lastDragOverElement_ = null;
+ }
+
+ finish() {
+ if (!this.lastDragOverElement_) {
+ return;
+ }
+
+ const dropTargetBookmark =
+ getBookmarkFromElement(this.lastDragOverElement_);
+ const dropPosition = this.lastDragOverElement_.getAttribute(
+ DROP_POSITION_ATTR) as DropPosition;
+ this.resetState_();
+
+ if (isBookmarkFolderElement(this.lastDragOverElement_) &&
+ dropPosition === DropPosition.INTO) {
+ chrome.bookmarkManagerPrivate.drop(
+ dropTargetBookmark.id, /* index */ undefined,
+ /* callback */ undefined);
+ return;
+ }
+
+ let toIndex = this.delegate_.getIndex(dropTargetBookmark);
+ toIndex += dropPosition === DropPosition.BELOW ? 1 : 0;
+ chrome.bookmarkManagerPrivate.drop(
+ dropTargetBookmark.parentId!, toIndex, /* callback */ undefined);
+ }
+
+ private resetState_() {
+ if (this.lastDragOverElement_) {
+ this.lastDragOverElement_.removeAttribute(DROP_POSITION_ATTR);
+ }
+
+ if (this.folderOpenerTimeout_ !== null) {
+ clearTimeout(this.folderOpenerTimeout_);
+ this.folderOpenerTimeout_ = null;
+ }
+ }
+
+ static createFromBookmark(
+ delegate: BookmarksDragDelegate,
+ bookmark: chrome.bookmarks.BookmarkTreeNode) {
+ return new DragSession(delegate, {
+ elements: [bookmark],
+ sameProfile: true,
+ });
+ }
+}
+
+export class BookmarksDragManager {
+ private delegate_: BookmarksDragDelegate;
+ private dragSession_: DragSession|null;
+ private eventTracker_: EventTracker = new EventTracker();
+
+ constructor(delegate: BookmarksDragDelegate) {
+ this.delegate_ = delegate;
+ }
+
+ startObserving() {
+ this.eventTracker_.add(
+ this.delegate_, 'dragstart', e => this.onDragStart_(e as DragEvent));
+ this.eventTracker_.add(
+ this.delegate_, 'dragover', e => this.onDragOver_(e as DragEvent));
+ this.eventTracker_.add(
+ this.delegate_, 'dragleave', () => this.onDragLeave_());
+ this.eventTracker_.add(this.delegate_, 'dragend', () => this.cancelDrag_());
+ this.eventTracker_.add(
+ this.delegate_, 'drop', e => this.onDrop_(e as DragEvent));
+
+ if (loadTimeData.getBoolean('bookmarksDragAndDropEnabled')) {
+ chrome.bookmarkManagerPrivate.onDragEnter.addListener(
+ (dragData: chrome.bookmarkManagerPrivate.DragData) =>
+ this.onChromeDragEnter_(dragData));
+ chrome.bookmarkManagerPrivate.onDragLeave.addListener(
+ () => this.cancelDrag_());
+ }
+ }
+
+ stopObserving() {
+ this.eventTracker_.removeAll();
+ }
+
+ private cancelDrag_() {
+ if (!this.dragSession_) {
+ return;
+ }
+ this.dragSession_.cancel();
+ this.dragSession_ = null;
+ }
+
+ private onChromeDragEnter_(dragData: chrome.bookmarkManagerPrivate.DragData) {
+ if (this.dragSession_) {
+ // A drag session is already in flight.
+ return;
+ }
+
+ this.dragSession_ = new DragSession(this.delegate_, dragData);
+ }
+
+ private onDragStart_(e: DragEvent) {
+ e.preventDefault();
+ if (!loadTimeData.getBoolean('bookmarksDragAndDropEnabled')) {
+ return;
+ }
+
+ const bookmark = getBookmarkFromElement(
+ e.composedPath().find(target => (target as HTMLElement).draggable) as
+ HTMLElement);
+ if (!bookmark ||
+ /* Cannot drag root's children. */ bookmark.parentId ===
+ ROOT_FOLDER_ID ||
+ bookmark.unmodifiable) {
+ return;
+ }
+
+ this.dragSession_ =
+ DragSession.createFromBookmark(this.delegate_, bookmark);
+ this.dragSession_.start(e);
+ }
+
+ private onDragOver_(e: DragEvent) {
+ e.preventDefault();
+ if (!this.dragSession_) {
+ return;
+ }
+ this.dragSession_.update(e);
+ }
+
+ private onDragLeave_() {
+ if (!this.dragSession_) {
+ return;
+ }
+
+ this.dragSession_.cancel();
+ }
+
+ private onDrop_(e: DragEvent) {
+ if (!this.dragSession_) {
+ return;
+ }
+
+ e.preventDefault();
+ this.dragSession_.finish();
+ }
+}
\ No newline at end of file
diff --git a/browser/resources/sidebar/bookmarks/bookmarks_list.html b/browser/resources/sidebar/bookmarks/bookmarks_list.html
new file mode 100644
index 000000000000..e24c1622e104
--- /dev/null
+++ b/browser/resources/sidebar/bookmarks/bookmarks_list.html
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/browser/resources/sidebar/bookmarks/bookmarks_list.ts b/browser/resources/sidebar/bookmarks/bookmarks_list.ts
new file mode 100644
index 000000000000..c012428c19a2
--- /dev/null
+++ b/browser/resources/sidebar/bookmarks/bookmarks_list.ts
@@ -0,0 +1,314 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {BookmarkFolderElement, FOLDER_OPEN_CHANGED_EVENT, getBookmarkFromElement, isBookmarkFolderElement} from './bookmark_folder.js';
+import {BookmarksApiProxy} from './bookmarks_api_proxy.js';
+import {BookmarksDragManager} from './bookmarks_drag_manager.js';
+
+// Key for localStorage object that refers to all the open folders.
+export const LOCAL_STORAGE_OPEN_FOLDERS_KEY = 'openFolders';
+
+export class BookmarksListElement extends PolymerElement {
+ static get is() {
+ return 'bookmarks-list';
+ }
+
+ static get template() {
+ return html`{__html_template__}`;
+ }
+
+ static get properties() {
+ return {
+ folders_: {
+ type: Array,
+ value: () => [],
+ },
+
+ openFolders_: {
+ type: Array,
+ value: () => [],
+ },
+ };
+ }
+
+ private bookmarksApi_: BookmarksApiProxy = BookmarksApiProxy.getInstance();
+ private bookmarksDragManager_: BookmarksDragManager =
+ new BookmarksDragManager(this);
+ private listeners_ = new Map();
+ private folders_: chrome.bookmarks.BookmarkTreeNode[];
+ private openFolders_: string[];
+
+ ready() {
+ super.ready();
+ this.addEventListener(
+ FOLDER_OPEN_CHANGED_EVENT,
+ e => this.onFolderOpenChanged_(
+ e as CustomEvent<{id: string, open: boolean}>));
+ this.addEventListener('keydown', e => this.onKeydown_(e));
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.setAttribute('role', 'tree');
+ this.bookmarksApi_.getFolders().then(folders => {
+ this.folders_ = folders;
+
+ this.addListener_(
+ 'onChildrenReordered',
+ (id: string, reorderedInfo: chrome.bookmarks.ReorderInfo) =>
+ this.onChildrenReordered_(id, reorderedInfo));
+ this.addListener_(
+ 'onChanged',
+ (id: string, changedInfo: chrome.bookmarks.ChangeInfo) =>
+ this.onChanged_(id, changedInfo));
+ this.addListener_(
+ 'onCreated',
+ (_id: string, node: chrome.bookmarks.BookmarkTreeNode) =>
+ this.onCreated_(node));
+ this.addListener_(
+ 'onMoved',
+ (_id: string, movedInfo: chrome.bookmarks.MoveInfo) =>
+ this.onMoved_(movedInfo));
+ this.addListener_('onRemoved', (id: string) => this.onRemoved_(id));
+
+ try {
+ const openFolders = window.localStorage[LOCAL_STORAGE_OPEN_FOLDERS_KEY];
+ this.openFolders_ = JSON.parse(openFolders);
+ } catch (error) {
+ this.openFolders_ = [this.folders_[0]!.id];
+ window.localStorage[LOCAL_STORAGE_OPEN_FOLDERS_KEY] =
+ JSON.stringify(this.openFolders_);
+ }
+
+ this.bookmarksDragManager_.startObserving();
+ });
+ }
+
+ disconnectedCallback() {
+ for (const [eventName, callback] of this.listeners_.entries()) {
+ this.bookmarksApi_.callbackRouter[eventName]!.removeListener(callback);
+ }
+ this.bookmarksDragManager_.stopObserving();
+ }
+
+ /** BookmarksDragDelegate */
+ getAscendants(bookmarkId: string): string[] {
+ const path = this.findPathToId_(bookmarkId);
+ return path.map(bookmark => bookmark.id);
+ }
+
+ /** BookmarksDragDelegate */
+ getIndex(bookmark: chrome.bookmarks.BookmarkTreeNode): number {
+ const path = this.findPathToId_(bookmark.id);
+ const parent = path[path.length - 2];
+ if (!parent || !parent.children) {
+ return -1;
+ }
+ return parent.children.findIndex((child) => child.id === bookmark.id);
+ }
+ /** BookmarksDragDelegate */
+ isFolderOpen(bookmark: chrome.bookmarks.BookmarkTreeNode): boolean {
+ return this.openFolders_.some(id => bookmark.id === id);
+ }
+
+ /** BookmarksDragDelegate */
+ openFolder(folderId: string) {
+ this.changeFolderOpenStatus_(folderId, true);
+ }
+
+ private addListener_(eventName: string, callback: Function): void {
+ this.bookmarksApi_.callbackRouter[eventName]!.addListener(callback);
+ this.listeners_.set(eventName, callback);
+ }
+
+ /**
+ * Finds the node within the nested array of folders and returns the path to
+ * the node in the tree.
+ */
+ private findPathToId_(id: string): chrome.bookmarks.BookmarkTreeNode[] {
+ const path: chrome.bookmarks.BookmarkTreeNode[] = [];
+
+ function findPathByIdInternal_(
+ id: string, node: chrome.bookmarks.BookmarkTreeNode) {
+ if (node.id === id) {
+ path.push(node);
+ return true;
+ }
+
+ if (!node.children) {
+ return false;
+ }
+
+ path.push(node);
+ const foundInChildren =
+ node.children.some(child => findPathByIdInternal_(id, child));
+ if (!foundInChildren) {
+ path.pop();
+ }
+
+ return foundInChildren;
+ }
+
+ this.folders_.some(folder => findPathByIdInternal_(id, folder));
+ return path;
+ }
+
+ /**
+ * Reduces an array of nodes to a string to notify Polymer of changes to the
+ * nested array.
+ */
+ private getPathString_(path: chrome.bookmarks.BookmarkTreeNode[]): string {
+ return path.reduce((reducedString, pathItem, index) => {
+ if (index === 0) {
+ return `folders_.${this.folders_.indexOf(pathItem)}`;
+ }
+
+ const parent = path[index - 1];
+ return `${reducedString}.children.${parent!.children!.indexOf(pathItem)}`;
+ }, '');
+ }
+
+ private onChanged_(id: string, changedInfo: chrome.bookmarks.ChangeInfo) {
+ const path = this.findPathToId_(id);
+ Object.assign(path[path.length - 1], changedInfo);
+
+ const pathString = this.getPathString_(path);
+ Object.keys(changedInfo)
+ .forEach(key => this.notifyPath(`${pathString}.${key}`));
+ }
+
+ private onChildrenReordered_(
+ id: string, reorderedInfo: chrome.bookmarks.ReorderInfo) {
+ const path = this.findPathToId_(id);
+ const parent = path[path.length - 1];
+ const childById = parent!.children!.reduce((map, node) => {
+ map.set(node.id, node);
+ return map;
+ }, new Map());
+ parent!.children = reorderedInfo.childIds.map(id => childById.get(id));
+ const pathString = this.getPathString_(path);
+ this.notifyPath(`${pathString}.children`);
+ }
+
+ private onCreated_(node: chrome.bookmarks.BookmarkTreeNode) {
+ const pathToParent = this.findPathToId_(node.parentId as string);
+ const pathToParentString = this.getPathString_(pathToParent);
+ const parent = pathToParent[pathToParent.length - 1];
+ if (parent && !parent.children) {
+ // Newly created folders in this session may not have an array of
+ // children yet, so create an empty one.
+ parent.children = [];
+ }
+ this.splice(`${pathToParentString}.children`, node.index!, 0, node);
+ }
+
+ private changeFolderOpenStatus_(id: string, open: boolean) {
+ const alreadyOpenIndex = this.openFolders_.indexOf(id);
+ if (open && alreadyOpenIndex === -1) {
+ this.openFolders_.push(id);
+ } else if (!open) {
+ this.openFolders_.splice(alreadyOpenIndex, 1);
+ }
+
+ // Assign to a new array so that listeners are triggered.
+ this.openFolders_ = [...this.openFolders_];
+ window.localStorage[LOCAL_STORAGE_OPEN_FOLDERS_KEY] =
+ JSON.stringify(this.openFolders_);
+ }
+
+ private onFolderOpenChanged_(event: CustomEvent) {
+ const {id, open} = event.detail;
+ this.changeFolderOpenStatus_(id, open);
+ }
+
+ private onKeydown_(event: KeyboardEvent) {
+ if (['ArrowDown', 'ArrowUp'].includes(event.key)) {
+ this.handleArrowKeyNavigation_(event);
+ return;
+ }
+
+ if (!event.ctrlKey && !event.metaKey) {
+ return;
+ }
+
+ event.preventDefault();
+ const eventTarget = event.composedPath()[0] as HTMLElement;
+ const bookmarkData = getBookmarkFromElement(eventTarget);
+ if (!bookmarkData) {
+ return;
+ }
+
+ if (event.key === 'x') {
+ this.bookmarksApi_.cutBookmark(bookmarkData.id);
+ } else if (event.key === 'c') {
+ this.bookmarksApi_.copyBookmark(bookmarkData.id);
+ } else if (event.key === 'v') {
+ if (isBookmarkFolderElement(eventTarget)) {
+ this.bookmarksApi_.pasteToBookmark(bookmarkData.id);
+ } else {
+ this.bookmarksApi_.pasteToBookmark(
+ bookmarkData.parentId!, bookmarkData.id);
+ }
+ }
+ }
+
+ private handleArrowKeyNavigation_(event: KeyboardEvent) {
+ if (!(this.shadowRoot!.activeElement instanceof BookmarkFolderElement)) {
+ // If the key event did not happen within a BookmarkFolderElement, do
+ // not do anything.
+ return;
+ }
+
+ // Prevent arrow keys from causing scroll.
+ event.preventDefault();
+
+ const allFolderElements: BookmarkFolderElement[] =
+ Array.from(this.shadowRoot!.querySelectorAll('bookmark-folder'));
+
+ const delta = event.key === 'ArrowUp' ? -1 : 1;
+ let currentIndex =
+ allFolderElements.indexOf(this.shadowRoot!.activeElement);
+ let focusHasMoved = false;
+ while (!focusHasMoved) {
+ focusHasMoved = allFolderElements[currentIndex]!.moveFocus(delta);
+ currentIndex = (currentIndex + delta + allFolderElements.length) %
+ allFolderElements.length;
+ }
+ }
+
+ private onMoved_(movedInfo: chrome.bookmarks.MoveInfo) {
+ // Get old path and remove node from oldParent at oldIndex.
+ const oldParentPath = this.findPathToId_(movedInfo.oldParentId);
+ const oldParentPathString = this.getPathString_(oldParentPath);
+ const oldParent = oldParentPath[oldParentPath.length - 1];
+ const movedNode = oldParent!.children![movedInfo.oldIndex];
+ Object.assign(
+ movedNode, {index: movedInfo.index, parentId: movedInfo.parentId});
+ this.splice(`${oldParentPathString}.children`, movedInfo.oldIndex, 1);
+
+ // Get new parent's path and add the node to the new parent at index.
+ const newParentPath = this.findPathToId_(movedInfo.parentId);
+ const newParentPathString = this.getPathString_(newParentPath);
+ const newParent = newParentPath[newParentPath.length - 1];
+ if (newParent && !newParent.children) {
+ newParent.children = [];
+ }
+ this.splice(
+ `${newParentPathString}.children`, movedInfo.index, 0, movedNode);
+ }
+
+ private onRemoved_(id: string) {
+ const oldPath = this.findPathToId_(id);
+ const removedNode = oldPath.pop()!;
+ const oldParent = oldPath[oldPath.length - 1]!;
+ const oldParentPathString = this.getPathString_(oldPath);
+ this.splice(
+ `${oldParentPathString}.children`,
+ oldParent.children!.indexOf(removedNode), 1);
+ }
+}
+
+customElements.define(BookmarksListElement.is, BookmarksListElement);
diff --git a/browser/resources/sidebar/bookmarks/tsconfig_base.json b/browser/resources/sidebar/bookmarks/tsconfig_base.json
new file mode 100644
index 000000000000..7ceb2c0b2e52
--- /dev/null
+++ b/browser/resources/sidebar/bookmarks/tsconfig_base.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../../../../tools/typescript/tsconfig_base.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "noPropertyAccessFromIndexSignature": false,
+ "noUnusedLocals": false,
+ "strictPropertyInitialization": false
+ }
+}
diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn
index 27fdc6cb4c83..74525f961b6a 100644
--- a/browser/ui/BUILD.gn
+++ b/browser/ui/BUILD.gn
@@ -357,8 +357,16 @@ source_set("ui") {
if (enable_sidebar) {
deps += [
+ "//brave/browser/resources/sidebar:resources",
"//brave/browser/ui/sidebar",
+ "//brave/browser/ui/webui/sidebar:mojo_bindings",
"//brave/components/sidebar",
+ "//chrome/app:generated_resources",
+ "//components/bookmarks/browser",
+ "//components/favicon_base",
+ "//mojo/public/cpp/bindings",
+ "//ui/base/mojom",
+ "//ui/webui",
]
sources += [
@@ -390,6 +398,10 @@ source_set("ui") {
"views/sidebar/sidebar_items_scroll_view.h",
"views/sidebar/sidebar_show_options_event_detect_widget.cc",
"views/sidebar/sidebar_show_options_event_detect_widget.h",
+ "webui/sidebar/sidebar_bookmarks_page_handler.cc",
+ "webui/sidebar/sidebar_bookmarks_page_handler.h",
+ "webui/sidebar/sidebar_bookmarks_ui.cc",
+ "webui/sidebar/sidebar_bookmarks_ui.h",
]
}
diff --git a/browser/ui/sidebar/sidebar.h b/browser/ui/sidebar/sidebar.h
index b30e1816dae8..36c4164e34d7 100644
--- a/browser/ui/sidebar/sidebar.h
+++ b/browser/ui/sidebar/sidebar.h
@@ -6,8 +6,18 @@
#ifndef BRAVE_BROWSER_UI_SIDEBAR_SIDEBAR_H_
#define BRAVE_BROWSER_UI_SIDEBAR_SIDEBAR_H_
+#include
+
#include "brave/components/sidebar/sidebar_service.h"
+namespace gfx {
+class Point;
+} // namespace gfx
+
+namespace ui {
+class MenuModel;
+} // namespace ui
+
namespace sidebar {
// Interact with UI layer.
@@ -17,6 +27,10 @@ class Sidebar {
SidebarService::ShowSidebarOption show_option) = 0;
// Update current sidebar UI.
virtual void UpdateSidebar() = 0;
+ virtual void ShowCustomContextMenu(
+ const gfx::Point& point,
+ std::unique_ptr menu_model) = 0;
+ virtual void HideCustomContextMenu() = 0;
protected:
virtual ~Sidebar() {}
diff --git a/browser/ui/views/sidebar/sidebar_container_view.cc b/browser/ui/views/sidebar/sidebar_container_view.cc
index b2d261576541..f2d8f2c91a42 100644
--- a/browser/ui/views/sidebar/sidebar_container_view.cc
+++ b/browser/ui/views/sidebar/sidebar_container_view.cc
@@ -5,6 +5,8 @@
#include "brave/browser/ui/views/sidebar/sidebar_container_view.h"
+#include
+
#include "base/bind.h"
#include "brave/browser/themes/theme_properties.h"
#include "brave/browser/ui/brave_browser.h"
@@ -18,11 +20,14 @@
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/models/menu_model.h"
#include "ui/base/theme_provider.h"
#include "ui/events/event_observer.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/border.h"
+#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/event_monitor.h"
#include "ui/views/widget/widget.h"
@@ -109,6 +114,33 @@ void SidebarContainerView::UpdateSidebar() {
sidebar_control_view_->Update();
}
+void SidebarContainerView::ShowCustomContextMenu(
+ const gfx::Point& point,
+ std::unique_ptr menu_model) {
+ // Show context menu at in screen coordinates.
+ gfx::Point screen_point = point;
+ ConvertPointToScreen(sidebar_panel_view_, &screen_point);
+ context_menu_model_ = std::move(menu_model);
+ context_menu_runner_ = std::make_unique(
+ context_menu_model_.get(),
+ views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU);
+ const int active_index = sidebar_model_->active_index();
+ if (active_index == -1) {
+ LOG(ERROR) << __func__
+ << " sidebar panel UI is loaded at non sidebar panel!";
+ return;
+ }
+ context_menu_runner_->RunMenuAt(
+ GetWidget(), nullptr, gfx::Rect(screen_point, gfx::Size()),
+ views::MenuAnchorPosition::kTopLeft, ui::MENU_SOURCE_MOUSE,
+ sidebar_model_->GetWebContentsAt(active_index)->GetContentNativeView());
+}
+
+void SidebarContainerView::HideCustomContextMenu() {
+ if (context_menu_runner_)
+ context_menu_runner_->Cancel();
+}
+
void SidebarContainerView::UpdateBackgroundAndBorder() {
if (const ui::ThemeProvider* theme_provider = GetThemeProvider()) {
constexpr int kBorderThickness = 1;
diff --git a/browser/ui/views/sidebar/sidebar_container_view.h b/browser/ui/views/sidebar/sidebar_container_view.h
index cf434bbbc27c..cce208015c1b 100644
--- a/browser/ui/views/sidebar/sidebar_container_view.h
+++ b/browser/ui/views/sidebar/sidebar_container_view.h
@@ -22,6 +22,14 @@ class EventMonitor;
class WebView;
} // namespace views
+namespace ui {
+class MenuModel;
+} // namespace ui
+
+namespace views {
+class MenuRunner;
+} // namespace views
+
class BraveBrowser;
class SidebarControlView;
@@ -46,6 +54,10 @@ class SidebarContainerView
void SetSidebarShowOption(
sidebar::SidebarService::ShowSidebarOption show_option) override;
void UpdateSidebar() override;
+ void ShowCustomContextMenu(
+ const gfx::Point& point,
+ std::unique_ptr menu_model) override;
+ void HideCustomContextMenu() override;
// views::View overrides:
void Layout() override;
@@ -100,6 +112,8 @@ class SidebarContainerView
base::ScopedObservation
observed_{this};
+ std::unique_ptr context_menu_runner_;
+ std::unique_ptr context_menu_model_;
};
#endif // BRAVE_BROWSER_UI_VIEWS_SIDEBAR_SIDEBAR_CONTAINER_VIEW_H_
diff --git a/browser/ui/webui/brave_web_ui_controller_factory.cc b/browser/ui/webui/brave_web_ui_controller_factory.cc
index 1fa1189992ec..0462fdabda46 100644
--- a/browser/ui/webui/brave_web_ui_controller_factory.cc
+++ b/browser/ui/webui/brave_web_ui_controller_factory.cc
@@ -20,6 +20,7 @@
#include "brave/common/webui_url_constants.h"
#include "brave/components/brave_vpn/buildflags/buildflags.h"
#include "brave/components/ipfs/buildflags/buildflags.h"
+#include "brave/components/sidebar/buildflags/buildflags.h"
#include "brave/components/tor/buildflags/buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
@@ -60,6 +61,12 @@
#include "brave/browser/ui/webui/tor_internals_ui.h"
#endif
+#if BUILDFLAG(ENABLE_SIDEBAR)
+#include "brave/browser/ui/webui/sidebar/sidebar_bookmarks_ui.h"
+#include "brave/components/sidebar/constants.h"
+#include "brave/components/sidebar/features.h"
+#endif
+
using content::WebUI;
using content::WebUIController;
@@ -127,6 +134,11 @@ WebUIController* NewWebUI(WebUI* web_ui, const GURL& url) {
#if BUILDFLAG(ENABLE_TOR)
} else if (host == kTorInternalsHost) {
return new TorInternalsUI(web_ui, url.host());
+#endif
+#if BUILDFLAG(ENABLE_SIDEBAR)
+ } else if (host == kSidebarBookmarksHost &&
+ base::FeatureList::IsEnabled(sidebar::kSidebarFeature)) {
+ return new SidebarBookmarksUI(web_ui);
#endif
}
return nullptr;
@@ -151,6 +163,10 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui,
#if BUILDFLAG(ENABLE_BRAVE_VPN) && !defined(OS_ANDROID)
(url.host_piece() == kVPNPanelHost && brave_vpn::IsBraveVPNEnabled()) ||
#endif
+#if BUILDFLAG(ENABLE_SIDEBAR)
+ (url.host_piece() == kSidebarBookmarksHost &&
+ base::FeatureList::IsEnabled(sidebar::kSidebarFeature)) ||
+#endif
#if !defined(OS_ANDROID)
url.host_piece() == kWalletPanelHost ||
url.host_piece() == kWalletPageHost ||
diff --git a/browser/ui/webui/sidebar/BUILD.gn b/browser/ui/webui/sidebar/BUILD.gn
new file mode 100644
index 000000000000..117cebd22a03
--- /dev/null
+++ b/browser/ui/webui/sidebar/BUILD.gn
@@ -0,0 +1,17 @@
+# 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("mojo_bindings") {
+ sources = [ "sidebar.mojom" ]
+ webui_module_path = "/"
+ public_deps = [
+ "//mojo/public/mojom/base",
+ "//ui/base/mojom",
+ "//ui/gfx/geometry/mojom",
+ "//url/mojom:url_mojom_gurl",
+ ]
+}
diff --git a/browser/ui/webui/sidebar/sidebar.mojom b/browser/ui/webui/sidebar/sidebar.mojom
new file mode 100644
index 000000000000..cff4cd6423ff
--- /dev/null
+++ b/browser/ui/webui/sidebar/sidebar.mojom
@@ -0,0 +1,30 @@
+// 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 sidebar.mojom;
+
+import "ui/base/mojom/window_open_disposition.mojom";
+import "ui/gfx/geometry/mojom/geometry.mojom";
+import "url/mojom/url.mojom";
+
+// Used by the sidebar's bookmarks WebUI page (for the side panel) to bootstrap
+// bidirectional communication.
+interface BookmarksPageHandlerFactory {
+ // The WebUI calls this method when the page is first initialized.
+ CreateBookmarksPageHandler(pending_receiver handler);
+};
+
+// Browser-side handler for requests from WebUI page.
+interface BookmarksPageHandler {
+ // Opens a bookmark by URL and passes the parent folder depth for metrics
+ // collection.
+ OpenBookmark(url.mojom.Url url, int32 parent_folder_depth,
+ ui.mojom.ClickModifiers click_modifiers);
+
+ // Opens a context menu for a bookmark node. The id parameter is internally
+ // an int64 but gets passed as a string from the chrome.bookmarks Extension
+ // API.
+ ShowContextMenu(string id, gfx.mojom.Point point);
+};
diff --git a/browser/ui/webui/sidebar/sidebar_bookmarks_page_handler.cc b/browser/ui/webui/sidebar/sidebar_bookmarks_page_handler.cc
new file mode 100644
index 000000000000..5b3d2babf01a
--- /dev/null
+++ b/browser/ui/webui/sidebar/sidebar_bookmarks_page_handler.cc
@@ -0,0 +1,153 @@
+/* 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/sidebar/sidebar_bookmarks_page_handler.h"
+
+#include
+#include
+
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "brave/browser/ui/brave_browser.h"
+#include "brave/browser/ui/sidebar/sidebar.h"
+#include "brave/browser/ui/sidebar/sidebar_controller.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/bookmarks/bookmark_model_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/bookmarks/bookmark_context_menu_controller.h"
+#include "chrome/browser/ui/bookmarks/bookmark_stats.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/bookmarks/browser/bookmark_utils.h"
+#include "ui/base/models/simple_menu_model.h"
+#include "ui/base/mojom/window_open_disposition.mojom.h"
+#include "ui/base/window_open_disposition.h"
+#include "url/gurl.h"
+
+namespace {
+
+class BookmarkContextMenu : public ui::SimpleMenuModel,
+ public ui::SimpleMenuModel::Delegate,
+ public BookmarkContextMenuControllerDelegate {
+ public:
+ explicit BookmarkContextMenu(Browser* browser,
+ sidebar::Sidebar* sidebar,
+ const bookmarks::BookmarkNode* bookmark)
+ : ui::SimpleMenuModel(this),
+ controller_(base::WrapUnique(new BookmarkContextMenuController(
+ browser->window()->GetNativeWindow(),
+ this,
+ browser,
+ browser->profile(),
+ base::BindRepeating(
+ [](content::PageNavigator* navigator) { return navigator; },
+ browser),
+ // Do we need our own histogram enum?
+ BOOKMARK_LAUNCH_LOCATION_SIDE_PANEL_CONTEXT_MENU,
+ bookmark->parent(),
+ {bookmark}))),
+ sidebar_(sidebar) {
+ AddItem(IDC_BOOKMARK_BAR_OPEN_ALL);
+ AddItem(IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW);
+ AddItem(IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO);
+ AddSeparator(ui::NORMAL_SEPARATOR);
+
+ AddItem(bookmark->is_folder() ? IDC_BOOKMARK_BAR_RENAME_FOLDER
+ : IDC_BOOKMARK_BAR_EDIT);
+ AddSeparator(ui::NORMAL_SEPARATOR);
+
+ AddItem(IDC_CUT);
+ AddItem(IDC_COPY);
+ AddItem(IDC_PASTE);
+ AddSeparator(ui::NORMAL_SEPARATOR);
+
+ AddItem(IDC_BOOKMARK_BAR_REMOVE);
+ AddSeparator(ui::NORMAL_SEPARATOR);
+
+ AddItem(IDC_BOOKMARK_BAR_ADD_NEW_BOOKMARK);
+ AddItem(IDC_BOOKMARK_BAR_NEW_FOLDER);
+ AddSeparator(ui::NORMAL_SEPARATOR);
+
+ AddItem(IDC_BOOKMARK_MANAGER);
+ }
+ ~BookmarkContextMenu() override = default;
+
+ void ExecuteCommand(int command_id, int event_flags) override {
+ controller_->ExecuteCommand(command_id, event_flags);
+ }
+
+ bool IsCommandIdEnabled(int command_id) const override {
+ return controller_->IsCommandIdEnabled(command_id);
+ }
+
+ bool IsCommandIdVisible(int command_id) const override {
+ return controller_->IsCommandIdVisible(command_id);
+ }
+
+ // BookmarkContextMenuControllerDelegate:
+ void CloseMenu() override { sidebar_->HideCustomContextMenu(); }
+
+ private:
+ void AddItem(int command_id) {
+ ui::SimpleMenuModel::AddItem(
+ command_id,
+ controller_->menu_model()->GetLabelAt(
+ controller_->menu_model()->GetIndexOfCommandId(command_id)));
+ }
+
+ std::unique_ptr controller_;
+ sidebar::Sidebar* sidebar_ = nullptr;
+};
+
+} // namespace
+
+SidebarBookmarksPageHandler::SidebarBookmarksPageHandler(
+ mojo::PendingReceiver receiver)
+ : receiver_(this, std::move(receiver)) {}
+
+SidebarBookmarksPageHandler::~SidebarBookmarksPageHandler() = default;
+
+void SidebarBookmarksPageHandler::OpenBookmark(
+ const GURL& url,
+ int32_t parent_folder_depth,
+ ui::mojom::ClickModifiersPtr click_modifiers) {
+ Browser* browser = chrome::FindLastActive();
+ if (!browser)
+ return;
+
+ WindowOpenDisposition open_location = ui::DispositionFromClick(
+ click_modifiers->middle_button, click_modifiers->alt_key,
+ click_modifiers->ctrl_key, click_modifiers->meta_key,
+ click_modifiers->shift_key);
+ content::OpenURLParams params(url, content::Referrer(), open_location,
+ ui::PAGE_TRANSITION_AUTO_BOOKMARK, false);
+ browser->OpenURL(params);
+}
+
+void SidebarBookmarksPageHandler::ShowContextMenu(const std::string& id_string,
+ const gfx::Point& point) {
+ int64_t id;
+ if (!base::StringToInt64(id_string, &id))
+ return;
+
+ Browser* browser = chrome::FindLastActive();
+ if (!browser)
+ return;
+
+ bookmarks::BookmarkModel* bookmark_model =
+ BookmarkModelFactory::GetForBrowserContext(browser->profile());
+ const bookmarks::BookmarkNode* bookmark =
+ bookmarks::GetBookmarkNodeByID(bookmark_model, id);
+ if (!bookmark)
+ return;
+
+ auto* sidebar =
+ static_cast(browser)->sidebar_controller()->sidebar();
+ sidebar->ShowCustomContextMenu(
+ point, std::make_unique(browser, sidebar, bookmark));
+}
diff --git a/browser/ui/webui/sidebar/sidebar_bookmarks_page_handler.h b/browser/ui/webui/sidebar/sidebar_bookmarks_page_handler.h
new file mode 100644
index 000000000000..0acd51ef2cab
--- /dev/null
+++ b/browser/ui/webui/sidebar/sidebar_bookmarks_page_handler.h
@@ -0,0 +1,37 @@
+/* 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_SIDEBAR_SIDEBAR_BOOKMARKS_PAGE_HANDLER_H_
+#define BRAVE_BROWSER_UI_WEBUI_SIDEBAR_SIDEBAR_BOOKMARKS_PAGE_HANDLER_H_
+
+#include
+
+#include "brave/browser/ui/webui/sidebar/sidebar.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+class GURL;
+
+class SidebarBookmarksPageHandler
+ : public sidebar::mojom::BookmarksPageHandler {
+ public:
+ explicit SidebarBookmarksPageHandler(
+ mojo::PendingReceiver receiver);
+ ~SidebarBookmarksPageHandler() override;
+ SidebarBookmarksPageHandler(const SidebarBookmarksPageHandler&) = delete;
+ SidebarBookmarksPageHandler& operator=(const SidebarBookmarksPageHandler&) =
+ delete;
+
+ private:
+ // sidebar::mojom::BookmarksPageHandler:
+ void OpenBookmark(const GURL& url,
+ int32_t parent_folder_depth,
+ ui::mojom::ClickModifiersPtr click_modifiers) override;
+ void ShowContextMenu(const std::string& id, const gfx::Point& point) override;
+
+ mojo::Receiver receiver_;
+};
+
+#endif // BRAVE_BROWSER_UI_WEBUI_SIDEBAR_SIDEBAR_BOOKMARKS_PAGE_HANDLER_H_
diff --git a/browser/ui/webui/sidebar/sidebar_bookmarks_ui.cc b/browser/ui/webui/sidebar/sidebar_bookmarks_ui.cc
new file mode 100644
index 000000000000..5b499f59688f
--- /dev/null
+++ b/browser/ui/webui/sidebar/sidebar_bookmarks_ui.cc
@@ -0,0 +1,70 @@
+// 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/sidebar/sidebar_bookmarks_ui.h"
+
+#include
+#include
+
+#include "brave/browser/resources/sidebar/grit/sidebar_resources.h"
+#include "brave/browser/resources/sidebar/grit/sidebar_resources_map.h"
+#include "brave/browser/ui/webui/sidebar/sidebar_bookmarks_page_handler.h"
+#include "brave/common/webui_url_constants.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/favicon_source.h"
+#include "chrome/browser/ui/webui/webui_util.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/bookmarks/common/bookmark_pref_names.h"
+#include "components/favicon_base/favicon_url_parser.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "ui/base/l10n/l10n_util.h"
+
+SidebarBookmarksUI::SidebarBookmarksUI(content::WebUI* web_ui)
+ : ui::MojoWebUIController(web_ui) {
+ content::WebUIDataSource* source =
+ content::WebUIDataSource::Create(kSidebarBookmarksHost);
+ static constexpr webui::LocalizedString kLocalizedStrings[] = {
+ {"bookmarksTitle", IDS_BOOKMARK_MANAGER_TITLE},
+ };
+ for (const auto& str : kLocalizedStrings) {
+ std::u16string l10n_str = l10n_util::GetStringUTF16(str.id);
+ source->AddString(str.name, l10n_str);
+ }
+
+ Profile* const profile = Profile::FromWebUI(web_ui);
+ PrefService* prefs = profile->GetPrefs();
+ source->AddBoolean(
+ "bookmarksDragAndDropEnabled",
+ prefs->GetBoolean(bookmarks::prefs::kEditBookmarksEnabled));
+
+ content::URLDataSource::Add(
+ profile, std::make_unique(
+ profile, chrome::FaviconUrlFormat::kFavicon2));
+ webui::SetupWebUIDataSource(
+ source, base::make_span(kSidebarResources, kSidebarResourcesSize),
+ IDR_SIDEBAR_BOOKMARKS_BOOKMARKS_HTML);
+ content::WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(),
+ source);
+}
+
+SidebarBookmarksUI::~SidebarBookmarksUI() = default;
+
+WEB_UI_CONTROLLER_TYPE_IMPL(SidebarBookmarksUI)
+
+void SidebarBookmarksUI::BindInterface(
+ mojo::PendingReceiver
+ receiver) {
+ bookmarks_page_factory_receiver_.reset();
+ bookmarks_page_factory_receiver_.Bind(std::move(receiver));
+}
+
+void SidebarBookmarksUI::CreateBookmarksPageHandler(
+ mojo::PendingReceiver receiver) {
+ bookmarks_page_handler_ =
+ std::make_unique(std::move(receiver));
+}
diff --git a/browser/ui/webui/sidebar/sidebar_bookmarks_ui.h b/browser/ui/webui/sidebar/sidebar_bookmarks_ui.h
new file mode 100644
index 000000000000..95b931c3b37e
--- /dev/null
+++ b/browser/ui/webui/sidebar/sidebar_bookmarks_ui.h
@@ -0,0 +1,44 @@
+// 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_SIDEBAR_SIDEBAR_BOOKMARKS_UI_H_
+#define BRAVE_BROWSER_UI_WEBUI_SIDEBAR_SIDEBAR_BOOKMARKS_UI_H_
+
+#include
+
+#include "brave/browser/ui/webui/sidebar/sidebar.mojom.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 "ui/webui/mojo_web_ui_controller.h"
+
+class SidebarBookmarksPageHandler;
+
+class SidebarBookmarksUI : public ui::MojoWebUIController,
+ public sidebar::mojom::BookmarksPageHandlerFactory {
+ public:
+ explicit SidebarBookmarksUI(content::WebUI* web_ui);
+ ~SidebarBookmarksUI() override;
+ SidebarBookmarksUI(const SidebarBookmarksUI&) = delete;
+ SidebarBookmarksUI& operator=(const SidebarBookmarksUI&) = delete;
+
+ void BindInterface(
+ mojo::PendingReceiver
+ receiver);
+
+ private:
+ // sidebar::mojom::BookmarksPageHandlerFactory
+ void CreateBookmarksPageHandler(
+ mojo::PendingReceiver receiver)
+ override;
+
+ std::unique_ptr bookmarks_page_handler_;
+ mojo::Receiver
+ bookmarks_page_factory_receiver_{this};
+
+ WEB_UI_CONTROLLER_TYPE_DECL();
+};
+
+#endif // BRAVE_BROWSER_UI_WEBUI_SIDEBAR_SIDEBAR_BOOKMARKS_UI_H_
diff --git a/chromium_src/python_modules/tools/__init__.py b/chromium_src/python_modules/tools/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/chromium_src/python_modules/tools/json_schema_compiler/__init__.py b/chromium_src/python_modules/tools/json_schema_compiler/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/chromium_src/python_modules/tools/json_schema_compiler/feature_compiler_helper.py b/chromium_src/python_modules/tools/json_schema_compiler/feature_compiler_helper.py
new file mode 100644
index 000000000000..ce7da7bbe1d1
--- /dev/null
+++ b/chromium_src/python_modules/tools/json_schema_compiler/feature_compiler_helper.py
@@ -0,0 +1,20 @@
+# 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/.
+
+# When update this method, feature_compiler.py should be touched to make
+# feature_compiler.py run. json_feature target doesn't have this in its
+# dependency.
+# below discard list comes from common/extensions/api/_api_features.json.
+def DiscardBraveOverridesFromDupes(dupes):
+ dupes.discard('topSites')
+ dupes.discard('extension.inIncognitoContext')
+ dupes.discard('bookmarkManagerPrivate')
+ dupes.discard('bookmarks')
+ dupes.discard('settingsPrivate')
+ dupes.discard('sockets')
+ dupes.discard('sockets.tcp')
+ dupes.discard('sockets.udp')
+ dupes.discard('sockets.tcpServer')
+ dupes.discard('tabs')
diff --git a/common/extensions/api/_api_features.json b/common/extensions/api/_api_features.json
index a289f97ee899..539ea3d8df5e 100644
--- a/common/extensions/api/_api_features.json
+++ b/common/extensions/api/_api_features.json
@@ -26,6 +26,19 @@
"chrome://newtab/*"
]
}],
+ "bookmarkManagerPrivate": [{
+ "dependencies": ["permission:bookmarkManagerPrivate"],
+ "contexts": ["blessed_extension"],
+ "default_parent": true
+ }, {
+ "channel": "stable",
+ "contexts": ["webui"],
+ "matches": [
+ "chrome://bookmarks/*",
+ "chrome://read-later.top-chrome/*",
+ "chrome://sidebar-bookmarks.top-chrome/*"
+ ]
+ }],
"bookmarks": [{
"dependencies": ["permission:bookmarks"],
"contexts": ["blessed_extension"],
@@ -35,7 +48,8 @@
"contexts": ["webui"],
"matches": [
"chrome://bookmarks/*",
- "chrome://read-later.top-chrome/*"
+ "chrome://read-later.top-chrome/*",
+ "chrome://sidebar-bookmarks.top-chrome/*"
],
"default_parent": true
}, {
diff --git a/common/webui_url_constants.cc b/common/webui_url_constants.cc
index 5e0c92315323..330be9692037 100644
--- a/common/webui_url_constants.cc
+++ b/common/webui_url_constants.cc
@@ -37,3 +37,4 @@ const char kUntrustedTrezorHost[] = "trezor-bridge";
const char kUntrustedTrezorURL[] = "chrome-untrusted://trezor-bridge/";
const char kShieldsPanelURL[] = "chrome://brave-shields.top-chrome";
const char kShieldsPanelHost[] = "brave-shields.top-chrome";
+const char kSidebarBookmarksHost[] = "sidebar-bookmarks.top-chrome";
diff --git a/common/webui_url_constants.h b/common/webui_url_constants.h
index 89222610143c..94d6133edbc3 100644
--- a/common/webui_url_constants.h
+++ b/common/webui_url_constants.h
@@ -39,5 +39,6 @@ extern const char kUntrustedTrezorHost[];
extern const char kUntrustedTrezorURL[];
extern const char kShieldsPanelURL[];
extern const char kShieldsPanelHost[];
+extern const char kSidebarBookmarksHost[];
#endif // BRAVE_COMMON_WEBUI_URL_CONSTANTS_H_
diff --git a/components/sidebar/constants.h b/components/sidebar/constants.h
index 8528199d14bd..9065a5d2108e 100644
--- a/components/sidebar/constants.h
+++ b/components/sidebar/constants.h
@@ -14,6 +14,11 @@ constexpr char kSidebarItemBuiltInItemTypeKey[] = "built_in_item_type";
constexpr char kSidebarItemTitleKey[] = "title";
constexpr char kSidebarItemOpenInPanelKey[] = "open_in_panel";
+// TODO(simonhong): Move this to //brave/common/webui_url_constants.h when
+// default builtin items list is provided from chrome layer.
+constexpr char kSidebarBookmarksURL[] =
+ "chrome://sidebar-bookmarks.top-chrome/";
+
} // namespace sidebar
#endif // BRAVE_COMPONENTS_SIDEBAR_CONSTANTS_H_
diff --git a/components/sidebar/sidebar_service.cc b/components/sidebar/sidebar_service.cc
index 3e6aff629caf..ab9d54c05c03 100644
--- a/components/sidebar/sidebar_service.cc
+++ b/components/sidebar/sidebar_service.cc
@@ -42,7 +42,7 @@ SidebarItem GetBuiltInItemForType(SidebarItem::BuiltInItemType type) {
break;
case SidebarItem::BuiltInItemType::kBookmarks:
return SidebarItem::Create(
- GURL("chrome://bookmarks/"),
+ GURL(kSidebarBookmarksURL),
l10n_util::GetStringUTF16(IDS_SIDEBAR_BOOKMARKS_ITEM_TITLE),
SidebarItem::Type::kTypeBuiltIn,
SidebarItem::BuiltInItemType::kBookmarks, true);
@@ -67,7 +67,7 @@ SidebarItem::BuiltInItemType GetBuiltInItemTypeForURL(const std::string& url) {
if (url == "chrome://wallet/")
return SidebarItem::BuiltInItemType::kWallet;
- if (url == "chrome://bookmarks/")
+ if (url == kSidebarBookmarksURL || url == "chrome://bookmarks/")
return SidebarItem::BuiltInItemType::kBookmarks;
if (url == "chrome://history/")
diff --git a/patches/tools-json_schema_compiler-feature_compiler.py.patch b/patches/tools-json_schema_compiler-feature_compiler.py.patch
index f0adb529c30a..186782059546 100644
--- a/patches/tools-json_schema_compiler-feature_compiler.py.patch
+++ b/patches/tools-json_schema_compiler-feature_compiler.py.patch
@@ -1,20 +1,12 @@
diff --git a/tools/json_schema_compiler/feature_compiler.py b/tools/json_schema_compiler/feature_compiler.py
-index b23dca5689042e1e6f0742ea79c3dcfcc65d168c..2c11ef5d4124c65d23cd5566cd886299fd5b5e05 100644
+index b23dca5689042e1e6f0742ea79c3dcfcc65d168c..026b32fa299ddf69f91fe5fb1020239d1b5184b2 100644
--- a/tools/json_schema_compiler/feature_compiler.py
+++ b/tools/json_schema_compiler/feature_compiler.py
-@@ -771,6 +771,15 @@ class FeatureCompiler(object):
+@@ -771,6 +771,7 @@ class FeatureCompiler(object):
abs_source_file)
raise
dupes = set(f_json) & set(self._json)
-+ dupes.discard('topSites')
-+ dupes.discard('extension.inIncognitoContext')
-+ dupes.discard('bookmarks')
-+ dupes.discard('settingsPrivate')
-+ dupes.discard('sockets')
-+ dupes.discard('sockets.tcp')
-+ dupes.discard('sockets.udp')
-+ dupes.discard('sockets.tcpServer')
-+ dupes.discard('tabs')
++ from tools.json_schema_compiler import feature_compiler_helper; feature_compiler_helper.DiscardBraveOverridesFromDupes(dupes)
assert not dupes, 'Duplicate keys found: %s' % list(dupes)
self._json.update(f_json)