blob: 70b64deb7145e7bbd40346fd8c296f039fae2298 [file]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/javascript_dialogs/app_modal_dialog_controller.h"
#include <utility>
#include "base/check_op.h"
#include "build/build_config.h"
#include "components/javascript_dialogs/app_modal_dialog_manager.h"
#include "components/javascript_dialogs/app_modal_dialog_queue.h"
#include "components/javascript_dialogs/app_modal_dialog_view.h"
#include "ui/gfx/text_elider.h"
namespace javascript_dialogs {
namespace {
AppModalDialogObserver* app_modal_dialog_observer = nullptr;
// Control maximum sizes of various texts passed to us from javascript.
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
// Two-dimensional eliding. Reformat the text of the message dialog
// inserting line breaks because otherwise a single long line can overflow
// the message dialog (and crash/hang the GTK, depending on the version).
const int kMessageTextMaxRows = 32;
const int kMessageTextMaxCols = 132;
const int kDefaultPromptMaxRows = 24;
const int kDefaultPromptMaxCols = 132;
std::u16string EnforceMaxTextSize(const std::u16string& in_string) {
std::u16string out_string;
gfx::ElideRectangleString(in_string, kMessageTextMaxRows, kMessageTextMaxCols,
false, &out_string);
return out_string;
}
std::u16string EnforceMaxPromptSize(const std::u16string& in_string) {
std::u16string out_string;
gfx::ElideRectangleString(in_string, kDefaultPromptMaxRows,
kDefaultPromptMaxCols, false, &out_string);
return out_string;
}
#else
// One-dimensional eliding. Trust the window system to break the string
// appropriately, but limit its overall length to something reasonable.
const size_t kMessageTextMaxSize = 2000;
const size_t kDefaultPromptMaxSize = 2000;
std::u16string EnforceMaxTextSize(const std::u16string& in_string) {
std::u16string out_string;
gfx::ElideString(in_string, kMessageTextMaxSize, &out_string);
return out_string;
}
std::u16string EnforceMaxPromptSize(const std::u16string& in_string) {
std::u16string out_string;
gfx::ElideString(in_string, kDefaultPromptMaxSize, &out_string);
return out_string;
}
#endif
} // namespace
ChromeJavaScriptDialogExtraData::ChromeJavaScriptDialogExtraData() = default;
AppModalDialogController::AppModalDialogController(
content::WebContents* web_contents,
ExtraDataMap* extra_data_map,
const std::u16string& title,
content::JavaScriptDialogType javascript_dialog_type,
const std::u16string& message_text,
const std::u16string& default_prompt_text,
bool display_suppress_checkbox,
bool is_before_unload_dialog,
bool is_reload,
content::JavaScriptDialogManager::DialogClosedCallback callback)
: content::WebContentsObserver(web_contents),
title_(title),
extra_data_map_(extra_data_map),
javascript_dialog_type_(javascript_dialog_type),
message_text_(EnforceMaxTextSize(message_text)),
default_prompt_text_(EnforceMaxPromptSize(default_prompt_text)),
display_suppress_checkbox_(display_suppress_checkbox),
is_before_unload_dialog_(is_before_unload_dialog),
is_reload_(is_reload),
callback_(std::move(callback)) {}
AppModalDialogController::~AppModalDialogController() {
// `CompleteDialog()` below can synchronously run more dialog code (it shows
// the next dialog in the queue). Invalidate weak pointers first so that any
// such code, as well as any stack frame holding a weak pointer to `this`,
// sees this dialog as already gone instead of as a half-destroyed object.
weak_ptr_factory_.InvalidateWeakPtrs();
CompleteDialog();
}
void AppModalDialogController::ShowModalDialog(
std::unique_ptr<AppModalDialogController> controller) {
CHECK_EQ(this, controller.get());
// Showing the dialog can synchronously close it again, which destroys the
// view and with it `this`.
base::WeakPtr<AppModalDialogController> weak_this =
weak_ptr_factory_.GetWeakPtr();
view_ = AppModalDialogManager::GetInstance()->view_factory()->Run(
std::move(controller));
view_->ShowAppModalDialog();
if (weak_this && app_modal_dialog_observer) {
app_modal_dialog_observer->Notify(this);
}
}
void AppModalDialogController::ActivateModalDialog() {
DCHECK(view_);
view_->ActivateAppModalDialog();
}
void AppModalDialogController::CloseModalDialog() {
DCHECK(view_);
view_->CloseAppModalDialog();
}
void AppModalDialogController::CompleteDialog() {
// If |view_| is non-null, then |this| is the active dialog and the next one
// should be shown. Otherwise, |this| was never shown.
if (view_) {
view_ = nullptr;
AppModalDialogQueue::GetInstance()->ShowNextDialog();
} else {
DCHECK(!valid_);
}
}
bool AppModalDialogController::IsValid() {
return valid_;
}
void AppModalDialogController::Invalidate() {
if (!valid_) {
return;
}
valid_ = false;
// The closed callback can synchronously re-enter the dialog machinery: for
// example, closing a beforeunload dialog can resume a deferred prerender
// activation, which cancels dialogs again. That eventually reaches
// `AppModalDialogQueue::GetNextDialog()`, which destroys every invalidated
// queued dialog - including `this`, whose `Invalidate()` is still on the
// stack. See https://crbug.com/560439699.
base::WeakPtr<AppModalDialogController> weak_this =
weak_ptr_factory_.GetWeakPtr();
CallDialogClosedCallback(false, std::u16string());
if (!weak_this) {
return;
}
if (view_) {
CloseModalDialog();
}
}
void AppModalDialogController::OnCancel(bool suppress_js_messages) {
// We need to do this before WM_DESTROY (WindowClosing()) as any parent frame
// will receive its activation messages before this dialog receives
// WM_DESTROY. The parent frame would then try to activate any modal dialogs
// that were still open in the ModalDialogQueue, which would send activation
// back to this one. The framework should be improved to handle this, so this
// is a temporary workaround.
//
// `CompleteDialog()` shows the next dialog in the queue, which can run
// arbitrary code that destroys `this`.
base::WeakPtr<AppModalDialogController> weak_this =
weak_ptr_factory_.GetWeakPtr();
CompleteDialog();
if (!weak_this) {
return;
}
NotifyDelegate(/*success=*/false, std::u16string(), suppress_js_messages);
}
void AppModalDialogController::OnAccept(const std::u16string& prompt_text,
bool suppress_js_messages) {
// See the comment in `OnCancel()`: `CompleteDialog()` can destroy `this`.
base::WeakPtr<AppModalDialogController> weak_this =
weak_ptr_factory_.GetWeakPtr();
CompleteDialog();
if (!weak_this) {
return;
}
NotifyDelegate(/*success=*/true, override_prompt_text_.value_or(prompt_text),
suppress_js_messages);
}
void AppModalDialogController::OnClose() {
NotifyDelegate(false, std::u16string(), false);
}
void AppModalDialogController::SetOverridePromptText(
const std::u16string& override_prompt_text) {
override_prompt_text_ = override_prompt_text;
}
void AppModalDialogController::WebContentsDestroyed() {
Invalidate();
}
void AppModalDialogController::NotifyDelegate(bool success,
const std::u16string& user_input,
bool suppress_js_messages) {
if (!valid_) {
return;
}
// Mark invalid immediately so that any re-entrant calls (such as OnClose()
// from Views during window destruction, or Invalidate() from navigation)
// return early. On Views, we can also end up coming through this code path a
// second time asynchronously (see https://crbug.com/40085084).
valid_ = false;
// The close callback can destroy `this`, e.g. by closing the dialog and
// letting the dialog queue delete it. See https://crbug.com/560439699.
base::WeakPtr<AppModalDialogController> weak_this =
weak_ptr_factory_.GetWeakPtr();
CallDialogClosedCallback(success, user_input);
if (!weak_this) {
return;
}
// The close callback above may delete web_contents_, thus removing the extra
// data from the map owned by ::AppModalDialogManager. Make sure
// to only use the data if still present. https://crbug.com/41009870
if (auto* contents = web_contents()) {
auto extra_data = extra_data_map_->find(contents);
if (extra_data != extra_data_map_->end()) {
extra_data->second.has_already_shown_a_dialog_ = true;
extra_data->second.suppress_javascript_messages_ = suppress_js_messages;
}
}
}
void AppModalDialogController::CallDialogClosedCallback(
bool success,
const std::u16string& user_input) {
if (!callback_.is_null()) {
std::move(callback_).Run(success, user_input);
}
}
AppModalDialogObserver::AppModalDialogObserver() {
DCHECK(!app_modal_dialog_observer);
app_modal_dialog_observer = this;
}
AppModalDialogObserver::~AppModalDialogObserver() {
DCHECK(app_modal_dialog_observer);
app_modal_dialog_observer = nullptr;
}
} // namespace javascript_dialogs