blob: 9a0d426234780429bb2a4211ab4287cbaccfec4a [file] [log] [blame]
// Copyright (c) 2012 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.
#include "chrome/browser/ui/views/extensions/extension_dialog.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_view_host.h"
#include "chrome/browser/extensions/extension_view_host_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/views/extensions/extension_dialog_observer.h"
#include "chrome/browser/ui/views/extensions/extension_view_views.h"
#include "components/constrained_window/constrained_window_views.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/base_window.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/views/background.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/public/cpp/tablet_mode.h"
#include "chromeos/ui/base/window_properties.h"
#include "ui/aura/window.h"
#endif
ExtensionDialog::InitParams::InitParams(gfx::Size size)
: size(std::move(size)) {}
ExtensionDialog::InitParams::InitParams(const InitParams& other) = default;
ExtensionDialog::InitParams::~InitParams() = default;
// static
ExtensionDialog* ExtensionDialog::Show(const GURL& url,
gfx::NativeWindow parent_window,
Profile* profile,
content::WebContents* web_contents,
ExtensionDialogObserver* observer,
const InitParams& init_params) {
DCHECK(parent_window);
std::unique_ptr<extensions::ExtensionViewHost> host =
extensions::ExtensionViewHostFactory::CreateDialogHost(url, profile);
if (!host)
return nullptr;
host->SetAssociatedWebContents(web_contents);
return new ExtensionDialog(std::move(host), observer, parent_window,
init_params);
}
void ExtensionDialog::ObserverDestroyed() {
observer_ = nullptr;
}
void ExtensionDialog::MaybeFocusRenderer() {
views::FocusManager* focus_manager = GetWidget()->GetFocusManager();
DCHECK(focus_manager);
// Already there's a focused view, so no need to switch the focus.
if (focus_manager->GetFocusedView())
return;
content::RenderWidgetHostView* view = host()->main_frame_host()->GetView();
if (!view)
return;
view->Focus();
}
void ExtensionDialog::SetMinimumContentsSize(int width, int height) {
extension_view_->SetPreferredSize(gfx::Size(width, height));
}
void ExtensionDialog::OnWindowClosing() {
if (observer_)
observer_->ExtensionDialogClosing(this);
}
void ExtensionDialog::HandleCloseExtensionHost(
extensions::ExtensionHost* host) {
DCHECK_EQ(host, host_.get());
GetWidget()->Close();
}
void ExtensionDialog::OnExtensionHostDidStopFirstLoad(
const extensions::ExtensionHost* host) {
DCHECK_EQ(host, host_.get());
// Avoid potential overdraw by removing the temporary background after
// the extension finishes loading.
extension_view_->SetBackground(nullptr);
// The render view is created during the LoadURL(), so we should
// set the focus to the view if nobody else takes the focus.
MaybeFocusRenderer();
}
void ExtensionDialog::OnExtensionProcessTerminated(
const extensions::Extension* extension) {
if (extension == host_->extension() && observer_)
observer_->ExtensionTerminated(this);
}
void ExtensionDialog::OnProcessManagerShutdown(
extensions::ProcessManager* manager) {
DCHECK(process_manager_observation_.IsObservingSource(manager));
process_manager_observation_.Reset();
}
ExtensionDialog::~ExtensionDialog() = default;
ExtensionDialog::ExtensionDialog(
std::unique_ptr<extensions::ExtensionViewHost> host,
ExtensionDialogObserver* observer,
gfx::NativeWindow parent_window,
const InitParams& init_params)
: host_(std::move(host)), observer_(observer) {
SetButtons(ui::DIALOG_BUTTON_NONE);
set_use_custom_frame(false);
AddRef();
RegisterDeleteDelegateCallback(
base::BindOnce(&ExtensionDialog::Release, base::Unretained(this)));
RegisterWindowClosingCallback(base::BindOnce(
&ExtensionDialog::OnWindowClosing, base::Unretained(this)));
extension_host_observation_.Observe(host_.get());
process_manager_observation_.Observe(
extensions::ProcessManager::Get(host_->browser_context()));
SetModalType(ui::MODAL_TYPE_WINDOW);
SetShowTitle(!init_params.title.empty());
SetTitle(init_params.title);
// The base::Unretained() below is safe because this object owns `host_`, so
// the callback will never fire if `this` is deleted.
host_->SetCloseHandler(base::BindOnce(
&ExtensionDialog::HandleCloseExtensionHost, base::Unretained(this)));
extension_view_ =
SetContentsView(std::make_unique<ExtensionViewViews>(host_.get()));
// Show a white background while the extension loads. This is prettier than
// flashing a black unfilled window frame.
extension_view_->SetBackground(
views::CreateThemedSolidBackground(kColorExtensionDialogBackground));
extension_view_->SetPreferredSize(init_params.size);
extension_view_->SetMinimumSize(init_params.min_size);
extension_view_->SetVisible(true);
bool can_resize = true;
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Prevent dialog resize mouse cursor in tablet mode, crbug.com/453634.
if (ash::TabletMode::IsInTabletMode())
can_resize = false;
#endif
SetCanResize(can_resize);
views::Widget* window =
init_params.is_modal
? constrained_window::CreateBrowserModalDialogViews(this,
parent_window)
: views::DialogDelegate::CreateDialogWidget(this, nullptr, nullptr);
// Center the window over the parent browser window or the screen.
gfx::Rect screen_rect = display::Screen::GetScreen()
->GetDisplayNearestWindow(parent_window)
.work_area();
gfx::Rect bounds = screen_rect;
if (parent_window) {
views::Widget* parent_widget =
views::Widget::GetWidgetForNativeWindow(parent_window);
if (parent_widget)
bounds = parent_widget->GetWindowBoundsInScreen();
}
bounds.ClampToCenteredSize(init_params.size);
// Make sure bounds is larger than {min_size}.
if (bounds.width() < init_params.min_size.width()) {
bounds.set_x(bounds.x() +
(bounds.width() - init_params.min_size.width()) / 2);
bounds.set_width(init_params.min_size.width());
}
if (bounds.height() < init_params.min_size.height()) {
bounds.set_y(bounds.y() +
(bounds.height() - init_params.min_size.height()) / 2);
bounds.set_height(init_params.min_size.height());
}
// Make sure bounds is still on screen.
bounds.AdjustToFit(screen_rect);
window->SetBounds(bounds);
#if BUILDFLAG(IS_CHROMEOS_ASH)
aura::Window* native_view = window->GetNativeWindow();
const bool should_track_default_frame_colors =
!(init_params.title_color || init_params.title_inactive_color);
native_view->SetProperty(chromeos::kTrackDefaultFrameColors,
should_track_default_frame_colors);
if (init_params.title_color) {
// Frame active color changes the title color when dialog is active.
native_view->SetProperty(chromeos::kFrameActiveColorKey,
init_params.title_color.value());
}
if (init_params.title_inactive_color) {
// Frame inactive color changes the title color when dialog is inactive.
native_view->SetProperty(chromeos::kFrameInactiveColorKey,
init_params.title_inactive_color.value());
}
#endif
window->Show();
// TODO(jamescook): Remove redundant call to Activate()?
window->Activate();
// Ensure the DOM JavaScript can respond immediately to keyboard shortcuts.
host_->host_contents()->Focus();
}