diff --git chrome/browser/browser_about_handler.cc chrome/browser/browser_about_handler.cc
index 3285e422f76c1..5f35b91897b75 100644
--- chrome/browser/browser_about_handler.cc
+++ chrome/browser/browser_about_handler.cc
@@ -70,6 +70,9 @@ bool HandleNonNavigationAboutURL(const GURL& url) {
         FROM_HERE, base::BindOnce(&chrome::AttemptExit));
     return true;
   }
+  if (base::EqualsCaseInsensitiveASCII(spec, "chrome://ignore/")) {
+    return true;
+  }
 
   return false;
 }
diff --git chrome/browser/ui/BUILD.gn chrome/browser/ui/BUILD.gn
index 7612f95eed78e..e6da8824627b2 100644
--- chrome/browser/ui/BUILD.gn
+++ chrome/browser/ui/BUILD.gn
@@ -10,6 +10,7 @@ import("//build/config/compiler/compiler.gni")
 import("//build/config/features.gni")
 import("//build/config/ozone.gni")
 import("//build/config/ui.gni")
+import("//cef/libcef/features/features.gni")
 import("//chrome/browser/buildflags.gni")
 import("//chrome/common/features.gni")
 import("//chromeos/ash/components/assistant/assistant.gni")
@@ -362,6 +363,10 @@ static_library("ui") {
     "//build/config/compiler:wexit_time_destructors",
   ]
 
+  if (enable_cef) {
+    configs += [ "//cef/libcef/features:config" ]
+  }
+
   # Since browser and browser_ui actually depend on each other,
   # we must omit the dependency from browser_ui to browser.
   # However, this means browser_ui and browser should more or less
@@ -385,6 +390,7 @@ static_library("ui") {
     "//build:chromeos_buildflags",
     "//build/config/chromebox_for_meetings:buildflags",
     "//cc/paint",
+    "//cef/libcef/features",
     "//chrome:extra_resources",
     "//chrome:resources",
     "//chrome:strings",
@@ -5528,6 +5534,7 @@ static_library("ui") {
   if (enable_basic_printing) {
     deps += [
       "//components/printing/browser",
+      "//components/printing/common:mojo_interfaces",
       "//printing",
     ]
   }
diff --git chrome/browser/ui/browser.cc chrome/browser/ui/browser.cc
index 3aa1e336d8da2..2826a41485deb 100644
--- chrome/browser/ui/browser.cc
+++ chrome/browser/ui/browser.cc
@@ -264,6 +264,25 @@
 #include "components/captive_portal/content/captive_portal_tab_helper.h"
 #endif
 
+#if BUILDFLAG(ENABLE_CEF)
+#define CALL_CEF_DELEGATE(name, ...) \
+  if (cef_browser_delegate_) { \
+    cef_browser_delegate_->name(__VA_ARGS__); \
+  }
+#define CALL_CEF_DELEGATE_RETURN(name, ...) \
+  if (cef_browser_delegate_) { \
+    return cef_browser_delegate_->name(__VA_ARGS__); \
+  }
+#define CALL_CEF_DELEGATE_RESULT(name, result, ...) \
+  if (cef_browser_delegate_) { \
+    result = cef_browser_delegate_->name(__VA_ARGS__); \
+  }
+#else  // !BUILDFLAG(ENABLE_CEF)
+#define CALL_CEF_DELEGATE(name, ...)
+#define CALL_CEF_DELEGATE_RETURN(name, ...)
+#define CALL_CEF_DELEGATE_RESULT(name, result, ...)
+#endif
+
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #include "chrome/browser/extensions/extension_browser_window_helper.h"
 #endif
@@ -513,6 +532,13 @@ Browser::Browser(const CreateParams& params)
 
   tab_strip_model_->AddObserver(this);
 
+#if BUILDFLAG(ENABLE_CEF)
+  if (cef::IsChromeRuntimeEnabled()) {
+    cef_browser_delegate_ =
+        cef::BrowserDelegate::Create(this, params.cef_params);
+  }
+#endif
+
   location_bar_model_ = std::make_unique<LocationBarModelImpl>(
       location_bar_model_delegate_.get(), content::kMaxURLDisplayChars);
 
@@ -1342,6 +1368,14 @@ content::KeyboardEventProcessingResult Browser::PreHandleKeyboardEvent(
   if (exclusive_access_manager_->HandleUserKeyEvent(event))
     return content::KeyboardEventProcessingResult::HANDLED;
 
+#if BUILDFLAG(ENABLE_CEF)
+  if (cef_browser_delegate_) {
+    auto result = cef_browser_delegate_->PreHandleKeyboardEvent(source, event);
+    if (result != content::KeyboardEventProcessingResult::NOT_HANDLED)
+      return result;
+  }
+#endif
+
   return window()->PreHandleKeyboardEvent(event);
 }
 
@@ -1349,8 +1383,18 @@ bool Browser::HandleKeyboardEvent(content::WebContents* source,
                                   const NativeWebKeyboardEvent& event) {
   DevToolsWindow* devtools_window =
       DevToolsWindow::GetInstanceForInspectedWebContents(source);
-  return (devtools_window && devtools_window->ForwardKeyboardEvent(event)) ||
-         window()->HandleKeyboardEvent(event);
+  if (devtools_window && devtools_window->ForwardKeyboardEvent(event)) {
+    return true;
+  }
+
+#if BUILDFLAG(ENABLE_CEF)
+  if (cef_browser_delegate_ &&
+      cef_browser_delegate_->HandleKeyboardEvent(source, event)) {
+    return true;
+  }
+#endif
+
+  return window()->HandleKeyboardEvent(event);
 }
 
 bool Browser::TabsNeedBeforeUnloadFired() {
@@ -1564,6 +1608,14 @@ WebContents* Browser::OpenURLFromTab(WebContents* source,
     return window->OpenURLFromTab(source, params);
   }
 
+#if BUILDFLAG(ENABLE_CEF)
+  if (cef_browser_delegate_) {
+    auto web_contents = cef_browser_delegate_->OpenURLFromTab(source, params);
+    if (!web_contents)
+      return nullptr;
+  }
+#endif
+
   NavigateParams nav_params(this, params.url, params.transition);
   nav_params.FillNavigateParamsFromOpenURLParams(params);
   nav_params.source_contents = source;
@@ -1693,6 +1745,15 @@ void Browser::AddNewContents(WebContents* source,
     return;
   }
 
+#if BUILDFLAG(ENABLE_CEF)
+  if (cef_browser_delegate_) {
+    cef_browser_delegate_->AddNewContents(
+        source, std::move(new_contents), target_url, disposition, initial_rect,
+        user_gesture, was_blocked);
+    return;
+  }
+#endif
+
   chrome::AddWebContents(this, source, std::move(new_contents), target_url,
                          disposition, initial_rect, window_action);
 }
@@ -1711,6 +1772,8 @@ void Browser::LoadingStateChanged(WebContents* source,
                                   bool should_show_loading_ui) {
   ScheduleUIUpdate(source, content::INVALIDATE_TYPE_LOAD);
   UpdateWindowForLoadingStateChanged(source, should_show_loading_ui);
+
+  CALL_CEF_DELEGATE(LoadingStateChanged, source, should_show_loading_ui);
 }
 
 void Browser::CloseContents(WebContents* source) {
@@ -1738,6 +1801,8 @@ void Browser::SetContentsBounds(WebContents* source, const gfx::Rect& bounds) {
 }
 
 void Browser::UpdateTargetURL(WebContents* source, const GURL& url) {
+  CALL_CEF_DELEGATE(UpdateTargetURL, source, url);
+
   if (!GetStatusBubble())
     return;
 
@@ -1745,6 +1810,17 @@ void Browser::UpdateTargetURL(WebContents* source, const GURL& url) {
     GetStatusBubble()->SetURL(url);
 }
 
+bool Browser::DidAddMessageToConsole(
+    content::WebContents* source,
+    blink::mojom::ConsoleMessageLevel log_level,
+    const std::u16string& message,
+    int32_t line_no,
+    const std::u16string& source_id) {
+  CALL_CEF_DELEGATE_RETURN(DidAddMessageToConsole, source, log_level, message,
+                           line_no, source_id);
+  return false;
+}
+
 void Browser::ContentsMouseEvent(WebContents* source,
                                  bool motion,
                                  bool exited) {
@@ -1769,6 +1845,19 @@ bool Browser::TakeFocus(content::WebContents* source, bool reverse) {
   return false;
 }
 
+void Browser::CanDownload(const GURL& url,
+                          const std::string& request_method,
+                          base::OnceCallback<void(bool)> callback) {
+#if BUILDFLAG(ENABLE_CEF)
+  if (cef_browser_delegate_) {
+    cef_browser_delegate_->CanDownload(url, request_method,
+                                       std::move(callback));
+    return;
+  }
+#endif
+  std::move(callback).Run(true);
+}
+
 void Browser::BeforeUnloadFired(WebContents* web_contents,
                                 bool proceed,
                                 bool* proceed_to_fire_unload) {
@@ -1861,6 +1950,10 @@ void Browser::WebContentsCreated(WebContents* source_contents,
 
   // Make the tab show up in the task manager.
   task_manager::WebContentsTags::CreateForTabContents(new_contents);
+
+  CALL_CEF_DELEGATE(WebContentsCreated, source_contents,
+                    opener_render_process_id, opener_render_frame_id,
+                    frame_name, target_url, new_contents);
 }
 
 void Browser::PortalWebContentsCreated(WebContents* portal_web_contents) {
@@ -1905,6 +1998,8 @@ void Browser::RendererResponsive(
 void Browser::DidNavigatePrimaryMainFramePostCommit(WebContents* web_contents) {
   if (web_contents == tab_strip_model_->GetActiveWebContents())
     UpdateBookmarkBarState(BOOKMARK_BAR_STATE_CHANGE_TAB_STATE);
+
+  CALL_CEF_DELEGATE(DidNavigatePrimaryMainFramePostCommit, web_contents);
 }
 
 content::JavaScriptDialogManager* Browser::GetJavaScriptDialogManager(
@@ -1965,11 +2060,15 @@ void Browser::EnterFullscreenModeForTab(
     const blink::mojom::FullscreenOptions& options) {
   exclusive_access_manager_->fullscreen_controller()->EnterFullscreenModeForTab(
       requesting_frame, options.display_id);
+
+  CALL_CEF_DELEGATE(EnterFullscreenModeForTab, requesting_frame, options);
 }
 
 void Browser::ExitFullscreenModeForTab(WebContents* web_contents) {
   exclusive_access_manager_->fullscreen_controller()->ExitFullscreenModeForTab(
       web_contents);
+
+  CALL_CEF_DELEGATE(ExitFullscreenModeForTab, web_contents);
 }
 
 bool Browser::IsFullscreenForTabOrPending(const WebContents* web_contents) {
@@ -2154,6 +2253,15 @@ void Browser::RequestMediaAccessPermission(
     content::WebContents* web_contents,
     const content::MediaStreamRequest& request,
     content::MediaResponseCallback callback) {
+#if BUILDFLAG(ENABLE_CEF)
+  if (cef_browser_delegate_) {
+    callback = cef_browser_delegate_->RequestMediaAccessPermissionEx(
+        web_contents, request, std::move(callback));
+    if (callback.is_null())
+      return;
+  }
+#endif
+
   const extensions::Extension* extension =
       GetExtensionForOrigin(profile_, request.security_origin);
   MediaCaptureDevicesDispatcher::GetInstance()->ProcessMediaAccessRequest(
@@ -2691,13 +2799,20 @@ void Browser::RemoveScheduledUpdatesFor(WebContents* contents) {
 // Browser, Getters for UI (private):
 
 StatusBubble* Browser::GetStatusBubble() {
+  bool show_by_default = true;
+
   // In kiosk and exclusive app mode we want to always hide the status bubble.
   if (chrome::IsRunningInAppMode() ||
       (base::FeatureList::IsEnabled(features::kRemoveStatusBarInWebApps) &&
        web_app::AppBrowserController::IsWebApp(this))) {
-    return nullptr;
+    show_by_default = false;
   }
 
+  bool show = show_by_default;
+  CALL_CEF_DELEGATE_RESULT(ShowStatusBubble, show, show_by_default);
+  if (!show)
+    return nullptr;
+
   return window_ ? window_->GetStatusBubble() : nullptr;
 }
 
@@ -2824,6 +2939,8 @@ void Browser::SetAsDelegate(WebContents* web_contents, bool set_delegate) {
     content_translate_driver->RemoveTranslationObserver(this);
     BookmarkTabHelper::FromWebContents(web_contents)->RemoveObserver(this);
   }
+
+  CALL_CEF_DELEGATE(SetAsDelegate, web_contents, set_delegate);
 }
 
 void Browser::TabDetachedAtImpl(content::WebContents* contents,
diff --git chrome/browser/ui/browser.h chrome/browser/ui/browser.h
index c9e2f4c315d27..54cb88c98d72c 100644
--- chrome/browser/ui/browser.h
+++ chrome/browser/ui/browser.h
@@ -22,6 +22,7 @@
 #include "base/timer/elapsed_timer.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "cef/libcef/features/runtime.h"
 #include "chrome/browser/themes/theme_service_observer.h"
 #include "chrome/browser/ui/bookmarks/bookmark_bar.h"
 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper_observer.h"
@@ -47,6 +48,10 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/shell_dialogs/select_file_dialog.h"
 
+#if BUILDFLAG(ENABLE_CEF)
+#include "cef/libcef/browser/chrome/browser_delegate.h"
+#endif
+
 #if BUILDFLAG(IS_ANDROID)
 #error This file should only be included on desktop.
 #endif
@@ -320,6 +325,11 @@ class Browser : public TabStripModelObserver,
     float initial_aspect_ratio = 1.0f;
     bool lock_aspect_ratio = false;
 
+#if BUILDFLAG(ENABLE_CEF)
+    // Opaque CEF-specific configuration. Will be propagated to new Browsers.
+    scoped_refptr<cef::BrowserDelegate::CreateParams> cef_params;
+#endif
+
    private:
     friend class Browser;
     friend class WindowSizerChromeOSTest;
@@ -395,6 +405,13 @@ class Browser : public TabStripModelObserver,
     force_skip_warning_user_on_close_ = force_skip_warning_user_on_close;
   }
 
+  // Return true if CEF will expose the toolbar to the client. This value is
+  // used to selectively enable toolbar behaviors such as command processing
+  // and omnibox focus without also including the toolbar in BrowserView layout
+  // calculations.
+  void set_toolbar_overridden(bool val) { toolbar_overridden_ = val; }
+  bool toolbar_overridden() const { return toolbar_overridden_; }
+
   // Accessors ////////////////////////////////////////////////////////////////
 
   const CreateParams& create_params() const { return create_params_; }
@@ -468,6 +485,12 @@ class Browser : public TabStripModelObserver,
 
   base::WeakPtr<Browser> AsWeakPtr();
 
+#if BUILDFLAG(ENABLE_CEF)
+  cef::BrowserDelegate* cef_delegate() const {
+    return cef_browser_delegate_.get();
+  }
+#endif
+
   // Get the FindBarController for this browser, creating it if it does not
   // yet exist.
   FindBarController* GetFindBarController();
@@ -837,11 +860,19 @@ class Browser : public TabStripModelObserver,
   void SetContentsBounds(content::WebContents* source,
                          const gfx::Rect& bounds) override;
   void UpdateTargetURL(content::WebContents* source, const GURL& url) override;
+  bool DidAddMessageToConsole(content::WebContents* source,
+                              blink::mojom::ConsoleMessageLevel log_level,
+                              const std::u16string& message,
+                              int32_t line_no,
+                              const std::u16string& source_id) override;
   void ContentsMouseEvent(content::WebContents* source,
                           bool motion,
                           bool exited) override;
   void ContentsZoomChange(bool zoom_in) override;
   bool TakeFocus(content::WebContents* source, bool reverse) override;
+  void CanDownload(const GURL& url,
+                   const std::string& request_method,
+                   base::OnceCallback<void(bool)> callback) override;
   void BeforeUnloadFired(content::WebContents* source,
                          bool proceed,
                          bool* proceed_to_fire_unload) override;
@@ -1241,6 +1272,8 @@ class Browser : public TabStripModelObserver,
   const std::string initial_workspace_;
   bool initial_visible_on_all_workspaces_state_;
 
+  bool toolbar_overridden_ = false;
+
   CreationSource creation_source_ = CreationSource::kUnknown;
 
   UnloadController unload_controller_;
@@ -1312,6 +1345,10 @@ class Browser : public TabStripModelObserver,
   std::unique_ptr<screen_ai::AXScreenAIAnnotator> screen_ai_annotator_;
 #endif
 
+#if BUILDFLAG(ENABLE_CEF)
+  std::unique_ptr<cef::BrowserDelegate> cef_browser_delegate_;
+#endif
+
   const base::ElapsedTimer creation_timer_;
 
   // The following factory is used for chrome update coalescing.
diff --git chrome/browser/ui/browser_navigator.cc chrome/browser/ui/browser_navigator.cc
index 36244f0893bc6..3bbc1b68988f3 100644
--- chrome/browser/ui/browser_navigator.cc
+++ chrome/browser/ui/browser_navigator.cc
@@ -563,6 +563,13 @@ std::unique_ptr<content::WebContents> CreateTargetContents(
   std::unique_ptr<WebContents> target_contents =
       WebContents::Create(create_params);
 
+#if BUILDFLAG(ENABLE_CEF)
+  auto cef_delegate = params.browser->cef_delegate();
+  if (cef_delegate) {
+    cef_delegate->OnWebContentsCreated(target_contents.get());
+  }
+#endif
+
   // New tabs can have WebUI URLs that will make calls back to arbitrary
   // tab helpers, so the entire set of tab helpers needs to be set up
   // immediately.
diff --git chrome/browser/ui/browser_tabstrip.cc chrome/browser/ui/browser_tabstrip.cc
index fc41ea1501856..7e6ef189bbc51 100644
--- chrome/browser/ui/browser_tabstrip.cc
+++ chrome/browser/ui/browser_tabstrip.cc
@@ -31,9 +31,13 @@ void AddTabAt(Browser* browser,
   // Time new tab page creation time.  We keep track of the timing data in
   // WebContents, but we want to include the time it takes to create the
   // WebContents object too.
+  // For CEF use a PageTransition that matches
+  // CefFrameHostImpl::kPageTransitionExplicit.
   base::TimeTicks new_tab_start_time = base::TimeTicks::Now();
   NavigateParams params(browser, url.is_empty() ? browser->GetNewTabURL() : url,
-                        ui::PAGE_TRANSITION_TYPED);
+                        static_cast<ui::PageTransition>(
+                            ui::PAGE_TRANSITION_TYPED |
+                            ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
   params.disposition = foreground ? WindowOpenDisposition::NEW_FOREGROUND_TAB
                                   : WindowOpenDisposition::NEW_BACKGROUND_TAB;
   params.tabstrip_index = idx;