blob: f2a3fe25fe45f4f356b15f5e7ad0ce2f704d255c [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/devtools/devtools_ui_controller.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/views/frame/contents_container_view.h"
#include "chrome/browser/ui/views/frame/contents_web_view.h"
#include "chrome/browser/ui/views/frame/scrim_view.h"
#include "ui/views/focus/external_focus_tracker.h"
DEFINE_USER_DATA(DevtoolsUIController);
DevtoolsUIController::DevtoolsUIController(
BrowserWindowInterface* browser,
const std::vector<raw_ptr<ContentsContainerView, DanglingUntriaged>>&
contents_container_views)
: can_dock_devtools_(browser->GetType() ==
BrowserWindowInterface::Type::TYPE_NORMAL),
scoped_data_holder_(browser->GetUnownedUserDataHost(), *this) {
for (ContentsContainerView* contents_container_view :
contents_container_views) {
devtools_web_view_controllers_[contents_container_view] =
std::make_unique<DevtoolsWebViewController>(contents_container_view);
}
}
DevtoolsUIController::~DevtoolsUIController() = default;
DevtoolsUIController* DevtoolsUIController::From(
BrowserWindowInterface* browser) {
return Get(browser->GetUnownedUserDataHost());
}
void DevtoolsUIController::TearDown() {
devtools_web_view_controllers_.clear();
}
bool DevtoolsUIController::UpdateDevtools(
content::WebContents* web_contents,
bool update_devtools_web_contents) {
auto it = std::ranges::find_if(
devtools_web_view_controllers_, [&web_contents](const auto& entry) {
return entry.first->contents_view()->web_contents() == web_contents;
});
if (it == devtools_web_view_controllers_.end()) {
return false;
}
DevtoolsWebViewController* controller = it->second.get();
if (update_devtools_web_contents &&
!DevToolsWindow::GetInTabWebContents(web_contents, nullptr)) {
controller->RestoreFocus();
}
const bool requires_layout =
controller->UpdateDevtools(web_contents, update_devtools_web_contents);
if (requires_layout) {
ContentsContainerView* container_view = it->first;
container_view->InvalidateLayout();
}
return requires_layout;
}
bool DevtoolsUIController::CanDockDevtools() const {
return can_dock_devtools_;
}
void DevtoolsUIController::SetDevtoolsScrimVisibility(
content::WebContents* web_contents,
bool visible) {
auto it = std::ranges::find_if(
devtools_web_view_controllers_, [&web_contents](const auto& entry) {
return entry.first->contents_view()->web_contents() == web_contents;
});
if (it != devtools_web_view_controllers_.end()) {
ContentsContainerView* container_view = it->first;
container_view->devtools_scrim_view()->SetVisible(visible);
}
}
DevtoolsUIController::DevtoolsWebViewController::DevtoolsWebViewController(
ContentsContainerView* contents_container_view)
: contents_container_view_(contents_container_view) {
ContentsWebView* contents_web_view =
contents_container_view_->contents_view();
web_contents_attached_subscription_ =
contents_web_view->AddWebContentsAttachedCallback(
base::BindRepeating(&DevtoolsWebViewController::OnWebContentsAttached,
base::Unretained(this)));
web_contents_detached_subscription_ =
contents_web_view->AddWebContentsDetachedCallback(
base::BindRepeating(&DevtoolsWebViewController::OnWebContentsDetached,
base::Unretained(this)));
OnWebContentsAttached(contents_web_view);
}
DevtoolsUIController::DevtoolsWebViewController::~DevtoolsWebViewController() =
default;
void DevtoolsUIController::DevtoolsWebViewController::OnWebContentsAttached(
views::WebView* web_view) {
UpdateDevtools(contents_container_view_->contents_view()->web_contents(),
true);
}
void DevtoolsUIController::DevtoolsWebViewController::OnWebContentsDetached(
views::WebView* web_view) {
UpdateDevtools(nullptr, true);
}
bool DevtoolsUIController::DevtoolsWebViewController::UpdateDevtools(
content::WebContents* web_contents,
bool update_devtools_web_contents) {
ContentsWebView* contents_view = contents_container_view_->contents_view();
views::WebView* devtools_web_view =
contents_container_view_->devtools_web_view();
DevToolsContentsResizingStrategy strategy;
content::WebContents* devtools =
DevToolsWindow::GetInTabWebContents(web_contents, &strategy);
// Replace devtools WebContents.
if (devtools_web_view->web_contents() != devtools &&
update_devtools_web_contents) {
if (!devtools_web_view->web_contents() && devtools &&
!devtools_focus_tracker_.get()) {
// Install devtools focus tracker when dev tools window is shown for the
// first time.
devtools_focus_tracker_ = std::make_unique<views::ExternalFocusTracker>(
devtools_web_view, contents_container_view_->GetFocusManager());
}
// Reset the focus tracker when hiding devtools window.
if (devtools_web_view->web_contents() && !devtools &&
devtools_focus_tracker_.get()) {
devtools_focus_tracker_.reset();
}
devtools_web_view->SetWebContents(devtools);
}
bool devtools_layout_updated;
if (devtools) {
devtools_layout_updated =
!devtools_web_view->GetVisible() ||
!contents_container_view_->contents_resizing_strategy().Equals(
strategy);
devtools_web_view->SetVisible(true);
contents_container_view_->SetContentsResizingStrategy(strategy);
} else {
devtools_layout_updated =
devtools_web_view->GetVisible() ||
!contents_container_view_->contents_resizing_strategy().Equals(
DevToolsContentsResizingStrategy());
devtools_web_view->SetVisible(false);
contents_container_view_->SetContentsResizingStrategy(
DevToolsContentsResizingStrategy());
}
if (devtools) {
// When strategy.hide_inspected_contents() returns true, we are hiding the
// WebContents behind the devtools_web_view_. Otherwise, the WebContents
// should be right above the devtools_web_view_.
size_t devtools_index =
contents_container_view_->GetIndexOf(devtools_web_view).value();
size_t contents_index =
contents_container_view_->GetIndexOf(contents_view).value();
bool devtools_is_on_top = devtools_index > contents_index;
if (strategy.hide_inspected_contents() != devtools_is_on_top) {
contents_container_view_->ReorderChildView(contents_view, devtools_index);
}
}
return devtools_layout_updated;
}
void DevtoolsUIController::DevtoolsWebViewController::RestoreFocus() {
if (devtools_focus_tracker_.get()) {
devtools_focus_tracker_->FocusLastFocusedExternalView();
}
}