// Copyright (c) 2012 The Chromium Embedded Framework Authors. // Portions 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 "libcef/browser/javascript_dialog_manager.h" #include #include "libcef/browser/browser_host_impl.h" #include "libcef/browser/thread_util.h" #include "base/bind.h" #include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "components/url_formatter/elide_url.h" namespace { class CefJSDialogCallbackImpl : public CefJSDialogCallback { public: using CallbackType = content::JavaScriptDialogManager::DialogClosedCallback; CefJSDialogCallbackImpl(CallbackType callback) : callback_(std::move(callback)) {} ~CefJSDialogCallbackImpl() override { if (!callback_.is_null()) { // The callback is still pending. Cancel it now. if (CEF_CURRENTLY_ON_UIT()) { CancelNow(std::move(callback_)); } else { CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefJSDialogCallbackImpl::CancelNow, std::move(callback_))); } } } void Continue(bool success, const CefString& user_input) override { if (CEF_CURRENTLY_ON_UIT()) { if (!callback_.is_null()) { std::move(callback_).Run(success, user_input); } } else { CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefJSDialogCallbackImpl::Continue, this, success, user_input)); } } CallbackType Disconnect() WARN_UNUSED_RESULT { return std::move(callback_); } private: static void CancelNow(CallbackType callback) { CEF_REQUIRE_UIT(); std::move(callback).Run(false, base::string16()); } CallbackType callback_; IMPLEMENT_REFCOUNTING(CefJSDialogCallbackImpl); }; } // namespace CefJavaScriptDialogManager::CefJavaScriptDialogManager( CefBrowserHostImpl* browser, std::unique_ptr runner) : browser_(browser), runner_(std::move(runner)), dialog_running_(false), weak_ptr_factory_(this) {} CefJavaScriptDialogManager::~CefJavaScriptDialogManager() {} void CefJavaScriptDialogManager::Destroy() { if (runner_.get()) { runner_.reset(nullptr); } } void CefJavaScriptDialogManager::RunJavaScriptDialog( content::WebContents* web_contents, content::RenderFrameHost* render_frame_host, content::JavaScriptDialogType message_type, const base::string16& message_text, const base::string16& default_prompt_text, DialogClosedCallback callback, bool* did_suppress_message) { const GURL& origin_url = render_frame_host->GetLastCommittedURL(); CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetJSDialogHandler(); if (handler.get()) { *did_suppress_message = false; CefRefPtr callbackPtr( new CefJSDialogCallbackImpl(std::move(callback))); // Execute the user callback. bool handled = handler->OnJSDialog( browser_, origin_url.spec(), static_cast(message_type), message_text, default_prompt_text, callbackPtr.get(), *did_suppress_message); if (handled) { // Invalid combination of values. Crash sooner rather than later. CHECK(!*did_suppress_message); return; } // |callback| may be null if the user executed it despite returning false. callback = callbackPtr->Disconnect(); if (callback.is_null() || *did_suppress_message) return; } } *did_suppress_message = false; if (!runner_.get() || dialog_running_) { // Suppress the dialog if there is no platform runner or if the dialog is // currently running. if (!runner_.get()) LOG(WARNING) << "No javascript dialog runner available for this platform"; *did_suppress_message = true; return; } dialog_running_ = true; const base::string16& display_url = url_formatter::FormatUrlForSecurityDisplay(origin_url); DCHECK(!callback.is_null()); runner_->Run( browser_, message_type, display_url, message_text, default_prompt_text, base::BindOnce(&CefJavaScriptDialogManager::DialogClosed, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } void CefJavaScriptDialogManager::RunBeforeUnloadDialog( content::WebContents* web_contents, content::RenderFrameHost* render_frame_host, bool is_reload, DialogClosedCallback callback) { if (browser_->destruction_state() >= CefBrowserHostImpl::DESTRUCTION_STATE_ACCEPTED) { // Currently destroying the browser. Accept the unload without showing // the prompt. std::move(callback).Run(true, base::string16()); return; } const base::string16& message_text = base::ASCIIToUTF16("Is it OK to leave/reload this page?"); CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetJSDialogHandler(); if (handler.get()) { CefRefPtr callbackPtr( new CefJSDialogCallbackImpl(std::move(callback))); // Execute the user callback. bool handled = handler->OnBeforeUnloadDialog( browser_, message_text, is_reload, callbackPtr.get()); if (handled) return; // |callback| may be null if the user executed it despite returning false. callback = callbackPtr->Disconnect(); if (callback.is_null()) return; } } if (!runner_.get() || dialog_running_) { if (!runner_.get()) LOG(WARNING) << "No javascript dialog runner available for this platform"; // Suppress the dialog if there is no platform runner or if the dialog is // currently running. std::move(callback).Run(true, base::string16()); return; } dialog_running_ = true; DCHECK(!callback.is_null()); runner_->Run( browser_, content::JAVASCRIPT_DIALOG_TYPE_CONFIRM, base::string16(), // display_url message_text, base::string16(), // default_prompt_text base::BindOnce(&CefJavaScriptDialogManager::DialogClosed, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } void CefJavaScriptDialogManager::CancelDialogs( content::WebContents* web_contents, bool reset_state) { CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetJSDialogHandler(); if (handler.get()) { // Execute the user callback. handler->OnResetDialogState(browser_); } } if (runner_.get() && dialog_running_) { runner_->Cancel(); dialog_running_ = false; } } void CefJavaScriptDialogManager::DialogClosed( DialogClosedCallback callback, bool success, const base::string16& user_input) { CefRefPtr client = browser_->GetClient(); if (client.get()) { CefRefPtr handler = client->GetJSDialogHandler(); if (handler.get()) handler->OnDialogClosed(browser_); } DCHECK(runner_.get()); DCHECK(dialog_running_); dialog_running_ = false; std::move(callback).Run(success, user_input); }