blob: 4470e813fa964cd3f68282dcf249a7dd9fbd50fe [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 "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/browser_dialogs.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_view_host.h"
#include "content/public/browser/render_widget_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 defined(OS_CHROMEOS)
#include "ash/public/cpp/tablet_mode.h"
#include "ash/public/cpp/window_properties.h"
#include "ui/aura/window.h"
#endif
using content::BrowserContext;
using content::WebContents;
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,
WebContents* web_contents,
ExtensionDialogObserver* observer,
const InitParams& init_params) {
std::unique_ptr<extensions::ExtensionViewHost> host =
extensions::ExtensionViewHostFactory::CreateDialogHost(url, profile);
if (!host)
return nullptr;
// Preferred size must be set before views::Widget::CreateWindowWithParent()
// is called because CreateWindowWithParent() references CanResize().
ExtensionViewViews* view = GetExtensionView(host.get());
view->SetPreferredSize(init_params.size);
view->set_minimum_size(init_params.min_size);
host->SetAssociatedWebContents(web_contents);
DCHECK(parent_window);
extensions::ExtensionViewHost* host_ptr = host.get();
ExtensionDialog* dialog = new ExtensionDialog(std::move(host), observer);
dialog->set_title(init_params.title);
dialog->InitWindow(parent_window, init_params);
// Show a white background while the extension loads. This is prettier than
// flashing a black unfilled window frame.
view->SetBackground(views::CreateSolidBackground(SK_ColorWHITE));
view->SetVisible(true);
// Ensure the DOM JavaScript can respond immediately to keyboard shortcuts.
host_ptr->host_contents()->Focus();
return dialog;
}
void ExtensionDialog::ObserverDestroyed() {
observer_ = NULL;
}
void ExtensionDialog::MaybeFocusRenderView() {
views::FocusManager* focus_manager = GetWidget()->GetFocusManager();
DCHECK(focus_manager != NULL);
// Already there's a focused view, so no need to switch the focus.
if (focus_manager->GetFocusedView())
return;
content::RenderWidgetHostView* view =
host()->render_view_host()->GetWidget()->GetView();
if (!view)
return;
view->Focus();
}
void ExtensionDialog::SetMinimumContentsSize(int width, int height) {
GetExtensionView()->SetPreferredSize(gfx::Size(width, height));
}
bool ExtensionDialog::CanResize() const {
#if defined(OS_CHROMEOS)
// Prevent dialog resize mouse cursor in tablet mode, crbug.com/453634.
if (ash::TabletMode::Get() && ash::TabletMode::Get()->InTabletMode())
return false;
#endif
// Can resize only if minimum contents size set.
return GetExtensionView()->GetPreferredSize() != gfx::Size();
}
ui::ModalType ExtensionDialog::GetModalType() const {
return ui::MODAL_TYPE_WINDOW;
}
bool ExtensionDialog::ShouldShowWindowTitle() const {
return !window_title_.empty();
}
base::string16 ExtensionDialog::GetWindowTitle() const {
return window_title_;
}
void ExtensionDialog::WindowClosing() {
if (observer_)
observer_->ExtensionDialogClosing(this);
}
void ExtensionDialog::DeleteDelegate() {
// The window has finished closing. Allow ourself to be deleted.
Release();
}
views::Widget* ExtensionDialog::GetWidget() {
return GetExtensionView()->GetWidget();
}
const views::Widget* ExtensionDialog::GetWidget() const {
return GetExtensionView()->GetWidget();
}
views::View* ExtensionDialog::GetContentsView() {
return GetExtensionView();
}
void ExtensionDialog::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case extensions::NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD:
// Avoid potential overdraw by removing the temporary background after
// the extension finishes loading.
GetExtensionView()->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.
if (content::Details<extensions::ExtensionHost>(host()) == details)
MaybeFocusRenderView();
break;
case extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE:
// If we aren't the host of the popup, then disregard the notification.
if (content::Details<extensions::ExtensionHost>(host()) != details)
return;
GetWidget()->Close();
break;
case extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED:
if (content::Details<extensions::ExtensionHost>(host()) != details)
return;
if (observer_)
observer_->ExtensionTerminated(this);
break;
default:
NOTREACHED() << "Received unexpected notification";
break;
}
}
ExtensionDialog::~ExtensionDialog() = default;
ExtensionDialog::ExtensionDialog(
std::unique_ptr<extensions::ExtensionViewHost> host,
ExtensionDialogObserver* observer)
: host_(std::move(host)), observer_(observer) {
DialogDelegate::SetButtons(ui::DIALOG_BUTTON_NONE);
DialogDelegate::set_use_custom_frame(false);
AddRef(); // Balanced in DeleteDelegate();
registrar_.Add(this,
extensions::NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD,
content::Source<BrowserContext>(host_->browser_context()));
// Listen for the containing view calling window.close();
registrar_.Add(this,
extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
content::Source<BrowserContext>(host_->browser_context()));
// Listen for a crash or other termination of the extension process.
registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
content::Source<BrowserContext>(host_->browser_context()));
chrome::RecordDialogCreation(chrome::DialogIdentifier::EXTENSION);
}
void ExtensionDialog::InitWindow(gfx::NativeWindow parent,
const InitParams& init_params) {
views::Widget* window =
init_params.is_modal
? constrained_window::CreateBrowserModalDialogViews(this, parent)
: views::DialogDelegate::CreateDialogWidget(
this, nullptr /* context */, nullptr /* parent */);
// Center the window over the parent browser window or the screen.
gfx::Rect screen_rect =
display::Screen::GetScreen()->GetDisplayNearestWindow(parent).work_area();
gfx::Rect bounds = parent ? views::Widget::GetWidgetForNativeWindow(parent)
->GetWindowBoundsInScreen()
: screen_rect;
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 defined(OS_CHROMEOS)
aura::Window* native_view = window->GetNativeWindow();
if (init_params.title_color) {
// Frame active color changes the title color when dialog is active.
native_view->SetProperty(ash::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(ash::kFrameInactiveColorKey,
init_params.title_inactive_color.value());
}
#endif
window->Show();
// TODO(jamescook): Remove redundant call to Activate()?
window->Activate();
}
ExtensionViewViews* ExtensionDialog::GetExtensionView() const {
return GetExtensionView(host_.get());
}
ExtensionViewViews* ExtensionDialog::GetExtensionView(
extensions::ExtensionViewHost* host) {
return static_cast<ExtensionViewViews*>(host->view());
}