blob: 0a9060e929faf465dc4e5694df22207b8b2f7022 [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 "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/renderer_preferences_util.h"
#include "chrome/browser/ui/blocked_content/popunder_preventer.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/webui/chrome_web_contents_handler.h"
#include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_delegate.h"
#include "ui/web_dialogs/web_dialog_delegate.h"
#include "ui/web_dialogs/web_dialog_ui.h"
namespace {
// WebContentsObserver that tracks the lifetime of the WebContents to avoid
// potential use after destruction.
class InitiatorWebContentsObserver
: public content::WebContentsObserver {
public:
explicit InitiatorWebContentsObserver(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents) {}
private:
DISALLOW_COPY_AND_ASSIGN(InitiatorWebContentsObserver);
};
gfx::Size RestrictToPlatformMinimumSize(const gfx::Size& min_size) {
#if defined(OS_MAC)
// http://crbug.com/78973 - MacOS does not handle zero-sized windows well.
gfx::Size adjusted_min_size(1, 1);
adjusted_min_size.SetToMax(min_size);
return adjusted_min_size;
#else
return min_size;
#endif
}
class ConstrainedWebDialogDelegateViews;
// The specialized WebView that lives in a constrained dialog.
class ConstrainedDialogWebView : public views::WebView,
public ConstrainedWebDialogDelegate,
public views::WidgetDelegate {
public:
METADATA_HEADER(ConstrainedDialogWebView);
ConstrainedDialogWebView(content::BrowserContext* browser_context,
std::unique_ptr<ui::WebDialogDelegate> delegate,
content::WebContents* web_contents,
const gfx::Size& min_size,
const gfx::Size& max_size);
ConstrainedDialogWebView(const ConstrainedDialogWebView&) = delete;
ConstrainedDialogWebView& operator=(const ConstrainedDialogWebView&) = delete;
~ConstrainedDialogWebView() override;
// ConstrainedWebDialogDelegate:
const ui::WebDialogDelegate* GetWebDialogDelegate() const override;
ui::WebDialogDelegate* GetWebDialogDelegate() override;
void OnDialogCloseFromWebUI() override;
std::unique_ptr<content::WebContents> ReleaseWebContents() override;
gfx::NativeWindow GetNativeDialog() override;
content::WebContents* GetWebContents() override;
gfx::Size GetConstrainedWebDialogPreferredSize() const override;
gfx::Size GetConstrainedWebDialogMinimumSize() const override;
gfx::Size GetConstrainedWebDialogMaximumSize() const override;
// views::WidgetDelegate:
views::View* GetInitiallyFocusedView() override;
void WindowClosing() override;
views::Widget* GetWidget() override;
const views::Widget* GetWidget() const override;
std::u16string GetWindowTitle() const override;
std::u16string GetAccessibleWindowTitle() const override;
views::View* GetContentsView() override;
std::unique_ptr<views::NonClientFrameView> CreateNonClientFrameView(
views::Widget* widget) override;
bool ShouldShowCloseButton() const override;
// views::WebView:
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
gfx::Size CalculatePreferredSize() const override;
gfx::Size GetMinimumSize() const override;
gfx::Size GetMaximumSize() const override;
void DocumentOnLoadCompletedInMainFrame(
content::RenderFrameHost* render_frame_host) override;
private:
InitiatorWebContentsObserver initiator_observer_;
// Showing a dialog should not activate, but on the Mac it does
// (https://crbug.com/1073587). Make sure it cannot be used to generate a
// popunder.
PopunderPreventer popunder_preventer_;
std::unique_ptr<ConstrainedWebDialogDelegateViews> impl_;
};
BEGIN_METADATA(ConstrainedDialogWebView, views::WebView)
END_METADATA
class WebDialogWebContentsDelegateViews
: public ui::WebDialogWebContentsDelegate {
public:
WebDialogWebContentsDelegateViews(content::BrowserContext* browser_context,
InitiatorWebContentsObserver* observer,
ConstrainedDialogWebView* web_view)
: ui::WebDialogWebContentsDelegate(
browser_context,
std::make_unique<ChromeWebContentsHandler>()),
initiator_observer_(observer),
web_view_(web_view) {}
~WebDialogWebContentsDelegateViews() override = default;
// ui::WebDialogWebContentsDelegate:
bool HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) override {
// Forward shortcut keys in dialog to our initiator's delegate.
// http://crbug.com/104586
if (!initiator_observer_->web_contents())
return false;
auto* delegate = initiator_observer_->web_contents()->GetDelegate();
if (!delegate)
return false;
return delegate->HandleKeyboardEvent(initiator_observer_->web_contents(),
event);
}
void ResizeDueToAutoResize(content::WebContents* source,
const gfx::Size& new_size) override {
if (source != web_view_->GetWebContents())
return;
if (!initiator_observer_->web_contents())
return;
// views::WebView is only a delegate for a WebContents it creates itself via
// views::WebView::GetWebContents(). ConstrainedDialogWebView's constructor
// sets its own WebContents (via an override of WebView::GetWebContents()).
// So forward this notification to views::WebView.
web_view_->ResizeDueToAutoResize(source, new_size);
content::WebContents* top_level_web_contents =
constrained_window::GetTopLevelWebContents(
initiator_observer_->web_contents());
if (top_level_web_contents) {
constrained_window::UpdateWebContentsModalDialogPosition(
web_view_->GetWidget(),
web_modal::WebContentsModalDialogManager::FromWebContents(
top_level_web_contents)
->delegate()
->GetWebContentsModalDialogHost());
}
}
private:
InitiatorWebContentsObserver* const initiator_observer_;
ConstrainedDialogWebView* web_view_;
DISALLOW_COPY_AND_ASSIGN(WebDialogWebContentsDelegateViews);
};
// Views implementation of ConstrainedWebDialogDelegate.
class ConstrainedWebDialogDelegateViews
: public ConstrainedWebDialogDelegate,
public content::WebContentsObserver,
public ui::WebDialogWebContentsDelegate {
public:
ConstrainedWebDialogDelegateViews(
content::BrowserContext* context,
std::unique_ptr<ui::WebDialogDelegate> delegate,
InitiatorWebContentsObserver* observer,
ConstrainedDialogWebView* view);
// |browser_context| must outlive |this| instance.
ConstrainedWebDialogDelegateViews(
content::BrowserContext* browser_context,
std::unique_ptr<ui::WebDialogDelegate> web_dialog_delegate,
std::unique_ptr<WebDialogWebContentsDelegate> tab_delegate);
~ConstrainedWebDialogDelegateViews() override;
bool closed_via_webui() const;
// ConstrainedWebDialogDelegate interface.
const ui::WebDialogDelegate* GetWebDialogDelegate() const override;
ui::WebDialogDelegate* GetWebDialogDelegate() override;
void OnDialogCloseFromWebUI() override;
std::unique_ptr<content::WebContents> ReleaseWebContents() override;
content::WebContents* GetWebContents() override;
gfx::Size GetConstrainedWebDialogMinimumSize() const override;
gfx::Size GetConstrainedWebDialogMaximumSize() const override;
gfx::Size GetConstrainedWebDialogPreferredSize() const override;
// WebContentsObserver interface
void WebContentsDestroyed() override;
// Resize the dialog to the given size.
virtual void ResizeToGivenSize(const gfx::Size size);
// ui::WebDialogWebContentsDelegate:
void CloseContents(content::WebContents* source) override {
view_->GetWidget()->Close();
}
// contents::WebContentsDelegate:
bool HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) override {
return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
event, view_->GetFocusManager());
}
// ConstrainedWebDialogDelegate:
gfx::NativeWindow GetNativeDialog() override {
return view_->GetWidget()->GetNativeWindow();
}
private:
std::unique_ptr<ui::WebDialogDelegate> web_dialog_delegate_;
// Holds the HTML to display in the constrained dialog.
std::unique_ptr<content::WebContents> web_contents_holder_;
// Pointer to the WebContents in |web_contents_holder_| for the lifetime of
// that object, even if ReleaseWebContents() gets called. If the WebContents
// gets destroyed, |web_contents_| will be set to a nullptr.
content::WebContents* web_contents_;
// Was the dialog closed from WebUI (in which case |web_dialog_delegate_|'s
// OnDialogClosed() method has already been called)?
bool closed_via_webui_;
views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
views::WebView* view_;
std::unique_ptr<WebDialogWebContentsDelegate> override_tab_delegate_;
DISALLOW_COPY_AND_ASSIGN(ConstrainedWebDialogDelegateViews);
};
using content::NativeWebKeyboardEvent;
using content::WebContents;
using ui::WebDialogDelegate;
using ui::WebDialogWebContentsDelegate;
ConstrainedWebDialogDelegateViews::ConstrainedWebDialogDelegateViews(
content::BrowserContext* browser_context,
std::unique_ptr<WebDialogDelegate> web_dialog_delegate,
InitiatorWebContentsObserver* observer,
ConstrainedDialogWebView* view)
: WebDialogWebContentsDelegate(
browser_context,
std::make_unique<ChromeWebContentsHandler>()),
web_dialog_delegate_(std::move(web_dialog_delegate)),
closed_via_webui_(false),
view_(view),
override_tab_delegate_(
std::make_unique<WebDialogWebContentsDelegateViews>(browser_context,
observer,
view)) {
chrome::RecordDialogCreation(chrome::DialogIdentifier::CONSTRAINED_WEB);
DCHECK(web_dialog_delegate_);
web_contents_holder_ =
WebContents::Create(WebContents::CreateParams(browser_context));
web_contents_ = web_contents_holder_.get();
WebContentsObserver::Observe(web_contents_);
zoom::ZoomController::CreateForWebContents(web_contents_);
web_contents_->SetDelegate(override_tab_delegate_.get());
blink::RendererPreferences* prefs = web_contents_->GetMutableRendererPrefs();
renderer_preferences_util::UpdateFromSystemSettings(
prefs, Profile::FromBrowserContext(browser_context));
web_contents_->SyncRendererPrefs();
// Set |this| as a delegate so the ConstrainedWebDialogUI can retrieve it.
ConstrainedWebDialogUI::SetConstrainedDelegate(web_contents_, this);
web_contents_->GetController().LoadURL(
web_dialog_delegate_->GetDialogContentURL(), content::Referrer(),
ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
}
ConstrainedWebDialogDelegateViews::~ConstrainedWebDialogDelegateViews() {
if (web_contents_) {
// Remove reference to |this| in the WebContent since it will becomes
// invalid and the lifetime of the WebContent may exceed the one of this
// object.
ConstrainedWebDialogUI::ClearConstrainedDelegate(web_contents_);
}
}
const WebDialogDelegate*
ConstrainedWebDialogDelegateViews::GetWebDialogDelegate() const {
return web_dialog_delegate_.get();
}
WebDialogDelegate* ConstrainedWebDialogDelegateViews::GetWebDialogDelegate() {
return web_dialog_delegate_.get();
}
void ConstrainedWebDialogDelegateViews::OnDialogCloseFromWebUI() {
closed_via_webui_ = true;
CloseContents(web_contents_);
}
bool ConstrainedWebDialogDelegateViews::closed_via_webui() const {
return closed_via_webui_;
}
std::unique_ptr<content::WebContents>
ConstrainedWebDialogDelegateViews::ReleaseWebContents() {
return std::move(web_contents_holder_);
}
WebContents* ConstrainedWebDialogDelegateViews::GetWebContents() {
return web_contents_;
}
gfx::Size
ConstrainedWebDialogDelegateViews::GetConstrainedWebDialogMinimumSize() const {
NOTREACHED();
return gfx::Size();
}
gfx::Size
ConstrainedWebDialogDelegateViews::GetConstrainedWebDialogMaximumSize() const {
NOTREACHED();
return gfx::Size();
}
gfx::Size
ConstrainedWebDialogDelegateViews::GetConstrainedWebDialogPreferredSize()
const {
NOTREACHED();
return gfx::Size();
}
void ConstrainedWebDialogDelegateViews::WebContentsDestroyed() {
web_contents_ = nullptr;
}
void ConstrainedWebDialogDelegateViews::ResizeToGivenSize(
const gfx::Size size) {
NOTREACHED();
}
ConstrainedDialogWebView::ConstrainedDialogWebView(
content::BrowserContext* browser_context,
std::unique_ptr<ui::WebDialogDelegate> delegate,
content::WebContents* web_contents,
const gfx::Size& min_size,
const gfx::Size& max_size)
: views::WebView(browser_context),
initiator_observer_(web_contents),
popunder_preventer_(web_contents),
impl_(std::make_unique<ConstrainedWebDialogDelegateViews>(
browser_context,
std::move(delegate),
&initiator_observer_,
this)) {
SetModalType(ui::MODAL_TYPE_CHILD);
SetWebContents(GetWebContents());
AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
if (!max_size.IsEmpty()) {
EnableSizingFromWebContents(RestrictToPlatformMinimumSize(min_size),
max_size);
}
}
ConstrainedDialogWebView::~ConstrainedDialogWebView() {}
const ui::WebDialogDelegate* ConstrainedDialogWebView::GetWebDialogDelegate()
const {
return impl_->GetWebDialogDelegate();
}
ui::WebDialogDelegate* ConstrainedDialogWebView::GetWebDialogDelegate() {
return impl_->GetWebDialogDelegate();
}
void ConstrainedDialogWebView::OnDialogCloseFromWebUI() {
return impl_->OnDialogCloseFromWebUI();
}
std::unique_ptr<content::WebContents>
ConstrainedDialogWebView::ReleaseWebContents() {
return impl_->ReleaseWebContents();
}
gfx::NativeWindow ConstrainedDialogWebView::GetNativeDialog() {
return impl_->GetNativeDialog();
}
content::WebContents* ConstrainedDialogWebView::GetWebContents() {
return impl_->GetWebContents();
}
gfx::Size ConstrainedDialogWebView::GetConstrainedWebDialogPreferredSize()
const {
return GetPreferredSize();
}
gfx::Size ConstrainedDialogWebView::GetConstrainedWebDialogMinimumSize() const {
return GetMinimumSize();
}
gfx::Size ConstrainedDialogWebView::GetConstrainedWebDialogMaximumSize() const {
return GetMaximumSize();
}
views::View* ConstrainedDialogWebView::GetInitiallyFocusedView() {
return this;
}
void ConstrainedDialogWebView::WindowClosing() {
if (!impl_->closed_via_webui())
GetWebDialogDelegate()->OnDialogClosed(std::string());
}
views::Widget* ConstrainedDialogWebView::GetWidget() {
return View::GetWidget();
}
const views::Widget* ConstrainedDialogWebView::GetWidget() const {
return View::GetWidget();
}
std::u16string ConstrainedDialogWebView::GetWindowTitle() const {
return impl_->closed_via_webui() ? std::u16string()
: GetWebDialogDelegate()->GetDialogTitle();
}
std::u16string ConstrainedDialogWebView::GetAccessibleWindowTitle() const {
return impl_->closed_via_webui()
? std::u16string()
: GetWebDialogDelegate()->GetAccessibleDialogTitle();
}
views::View* ConstrainedDialogWebView::GetContentsView() {
return this;
}
std::unique_ptr<views::NonClientFrameView>
ConstrainedDialogWebView::CreateNonClientFrameView(views::Widget* widget) {
return views::DialogDelegate::CreateDialogFrameView(widget);
}
bool ConstrainedDialogWebView::ShouldShowCloseButton() const {
// No close button if the dialog doesn't want a title bar.
return impl_->GetWebDialogDelegate()->ShouldShowDialogTitle();
}
bool ConstrainedDialogWebView::AcceleratorPressed(
const ui::Accelerator& accelerator) {
// Pressing ESC closes the dialog.
DCHECK_EQ(ui::VKEY_ESCAPE, accelerator.key_code());
GetWebDialogDelegate()->OnDialogClosingFromKeyEvent();
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kEscKeyPressed);
return true;
}
gfx::Size ConstrainedDialogWebView::CalculatePreferredSize() const {
if (impl_->closed_via_webui())
return gfx::Size();
// If auto-resizing is enabled and the dialog has been auto-resized,
// View::GetPreferredSize() won't try to calculate the size again, since a
// preferred size has been set explicitly from the renderer.
gfx::Size size = WebView::CalculatePreferredSize();
GetWebDialogDelegate()->GetDialogSize(&size);
return size;
}
gfx::Size ConstrainedDialogWebView::GetMinimumSize() const {
return min_size();
}
gfx::Size ConstrainedDialogWebView::GetMaximumSize() const {
return !max_size().IsEmpty() ? max_size() : WebView::GetMaximumSize();
}
void ConstrainedDialogWebView::DocumentOnLoadCompletedInMainFrame(
content::RenderFrameHost* render_frame_host) {
if (!max_size().IsEmpty() && initiator_observer_.web_contents()) {
content::WebContents* top_level_web_contents =
constrained_window::GetTopLevelWebContents(
initiator_observer_.web_contents());
if (top_level_web_contents) {
constrained_window::ShowModalDialog(GetWidget()->GetNativeWindow(),
top_level_web_contents);
}
}
}
} // namespace
ConstrainedWebDialogDelegate* ShowConstrainedWebDialog(
content::BrowserContext* browser_context,
std::unique_ptr<ui::WebDialogDelegate> delegate,
content::WebContents* web_contents) {
ConstrainedDialogWebView* dialog =
new ConstrainedDialogWebView(browser_context, std::move(delegate),
web_contents, gfx::Size(), gfx::Size());
constrained_window::ShowWebModalDialogViews(dialog, web_contents);
return dialog;
}
ConstrainedWebDialogDelegate* ShowConstrainedWebDialogWithAutoResize(
content::BrowserContext* browser_context,
std::unique_ptr<ui::WebDialogDelegate> delegate,
content::WebContents* web_contents,
const gfx::Size& min_size,
const gfx::Size& max_size) {
DCHECK(!min_size.IsEmpty());
DCHECK(!max_size.IsEmpty());
ConstrainedDialogWebView* dialog = new ConstrainedDialogWebView(
browser_context, std::move(delegate), web_contents, min_size, max_size);
// For embedded WebContents, use the embedder's WebContents for constrained
// window.
content::WebContents* top_level_web_contents =
constrained_window::GetTopLevelWebContents(web_contents);
DCHECK(top_level_web_contents);
constrained_window::CreateWebModalDialogViews(dialog, top_level_web_contents);
return dialog;
}