1 // Copyright (c) 2012 The Chromium Embedded Framework Authors.
2 // Portions copyright (c) 2012 The Chromium Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5
6 #include "libcef/browser/javascript_dialog_manager.h"
7
8 #include <utility>
9
10 #include "libcef/browser/alloy/alloy_browser_host_impl.h"
11 #include "libcef/browser/thread_util.h"
12
13 #include "base/bind.h"
14 #include "base/logging.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "components/url_formatter/elide_url.h"
17
18 namespace {
19
20 class CefJSDialogCallbackImpl : public CefJSDialogCallback {
21 public:
22 using CallbackType = content::JavaScriptDialogManager::DialogClosedCallback;
23
CefJSDialogCallbackImpl(CallbackType callback)24 CefJSDialogCallbackImpl(CallbackType callback)
25 : callback_(std::move(callback)) {}
~CefJSDialogCallbackImpl()26 ~CefJSDialogCallbackImpl() override {
27 if (!callback_.is_null()) {
28 // The callback is still pending. Cancel it now.
29 if (CEF_CURRENTLY_ON_UIT()) {
30 CancelNow(std::move(callback_));
31 } else {
32 CEF_POST_TASK(CEF_UIT,
33 base::BindOnce(&CefJSDialogCallbackImpl::CancelNow,
34 std::move(callback_)));
35 }
36 }
37 }
38
Continue(bool success,const CefString & user_input)39 void Continue(bool success, const CefString& user_input) override {
40 if (CEF_CURRENTLY_ON_UIT()) {
41 if (!callback_.is_null()) {
42 std::move(callback_).Run(success, user_input);
43 }
44 } else {
45 CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefJSDialogCallbackImpl::Continue,
46 this, success, user_input));
47 }
48 }
49
Disconnect()50 CallbackType Disconnect() WARN_UNUSED_RESULT { return std::move(callback_); }
51
52 private:
CancelNow(CallbackType callback)53 static void CancelNow(CallbackType callback) {
54 CEF_REQUIRE_UIT();
55 std::move(callback).Run(false, std::u16string());
56 }
57
58 CallbackType callback_;
59
60 IMPLEMENT_REFCOUNTING(CefJSDialogCallbackImpl);
61 };
62
63 } // namespace
64
CefJavaScriptDialogManager(AlloyBrowserHostImpl * browser,std::unique_ptr<CefJavaScriptDialogRunner> runner)65 CefJavaScriptDialogManager::CefJavaScriptDialogManager(
66 AlloyBrowserHostImpl* browser,
67 std::unique_ptr<CefJavaScriptDialogRunner> runner)
68 : browser_(browser),
69 runner_(std::move(runner)),
70 dialog_running_(false),
71 weak_ptr_factory_(this) {}
72
~CefJavaScriptDialogManager()73 CefJavaScriptDialogManager::~CefJavaScriptDialogManager() {}
74
Destroy()75 void CefJavaScriptDialogManager::Destroy() {
76 if (runner_.get()) {
77 runner_.reset(nullptr);
78 }
79 }
80
RunJavaScriptDialog(content::WebContents * web_contents,content::RenderFrameHost * render_frame_host,content::JavaScriptDialogType message_type,const std::u16string & message_text,const std::u16string & default_prompt_text,DialogClosedCallback callback,bool * did_suppress_message)81 void CefJavaScriptDialogManager::RunJavaScriptDialog(
82 content::WebContents* web_contents,
83 content::RenderFrameHost* render_frame_host,
84 content::JavaScriptDialogType message_type,
85 const std::u16string& message_text,
86 const std::u16string& default_prompt_text,
87 DialogClosedCallback callback,
88 bool* did_suppress_message) {
89 const GURL& origin_url = render_frame_host->GetLastCommittedURL();
90
91 CefRefPtr<CefClient> client = browser_->GetClient();
92 if (client.get()) {
93 CefRefPtr<CefJSDialogHandler> handler = client->GetJSDialogHandler();
94 if (handler.get()) {
95 *did_suppress_message = false;
96
97 CefRefPtr<CefJSDialogCallbackImpl> callbackPtr(
98 new CefJSDialogCallbackImpl(std::move(callback)));
99
100 // Execute the user callback.
101 bool handled = handler->OnJSDialog(
102 browser_, origin_url.spec(),
103 static_cast<cef_jsdialog_type_t>(message_type), message_text,
104 default_prompt_text, callbackPtr.get(), *did_suppress_message);
105 if (handled) {
106 // Invalid combination of values. Crash sooner rather than later.
107 CHECK(!*did_suppress_message);
108 return;
109 }
110
111 // |callback| may be null if the user executed it despite returning false.
112 callback = callbackPtr->Disconnect();
113 if (callback.is_null() || *did_suppress_message)
114 return;
115 }
116 }
117
118 *did_suppress_message = false;
119
120 if (!runner_.get() || dialog_running_) {
121 // Suppress the dialog if there is no platform runner or if the dialog is
122 // currently running.
123 if (!runner_.get())
124 LOG(WARNING) << "No javascript dialog runner available for this platform";
125 *did_suppress_message = true;
126 return;
127 }
128
129 dialog_running_ = true;
130
131 const std::u16string& display_url =
132 url_formatter::FormatUrlForSecurityDisplay(origin_url);
133
134 DCHECK(!callback.is_null());
135 runner_->Run(
136 browser_, message_type, display_url, message_text, default_prompt_text,
137 base::BindOnce(&CefJavaScriptDialogManager::DialogClosed,
138 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
139 }
140
RunBeforeUnloadDialog(content::WebContents * web_contents,content::RenderFrameHost * render_frame_host,bool is_reload,DialogClosedCallback callback)141 void CefJavaScriptDialogManager::RunBeforeUnloadDialog(
142 content::WebContents* web_contents,
143 content::RenderFrameHost* render_frame_host,
144 bool is_reload,
145 DialogClosedCallback callback) {
146 if (browser_->destruction_state() >=
147 AlloyBrowserHostImpl::DESTRUCTION_STATE_ACCEPTED) {
148 // Currently destroying the browser. Accept the unload without showing
149 // the prompt.
150 std::move(callback).Run(true, std::u16string());
151 return;
152 }
153
154 const std::u16string& message_text = u"Is it OK to leave/reload this page?";
155
156 CefRefPtr<CefClient> client = browser_->GetClient();
157 if (client.get()) {
158 CefRefPtr<CefJSDialogHandler> handler = client->GetJSDialogHandler();
159 if (handler.get()) {
160 CefRefPtr<CefJSDialogCallbackImpl> callbackPtr(
161 new CefJSDialogCallbackImpl(std::move(callback)));
162
163 // Execute the user callback.
164 bool handled = handler->OnBeforeUnloadDialog(
165 browser_, message_text, is_reload, callbackPtr.get());
166 if (handled)
167 return;
168
169 // |callback| may be null if the user executed it despite returning false.
170 callback = callbackPtr->Disconnect();
171 if (callback.is_null())
172 return;
173 }
174 }
175
176 if (!runner_.get() || dialog_running_) {
177 if (!runner_.get())
178 LOG(WARNING) << "No javascript dialog runner available for this platform";
179 // Suppress the dialog if there is no platform runner or if the dialog is
180 // currently running.
181 std::move(callback).Run(true, std::u16string());
182 return;
183 }
184
185 dialog_running_ = true;
186
187 DCHECK(!callback.is_null());
188 runner_->Run(
189 browser_, content::JAVASCRIPT_DIALOG_TYPE_CONFIRM,
190 std::u16string(), // display_url
191 message_text,
192 std::u16string(), // default_prompt_text
193 base::BindOnce(&CefJavaScriptDialogManager::DialogClosed,
194 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
195 }
196
CancelDialogs(content::WebContents * web_contents,bool reset_state)197 void CefJavaScriptDialogManager::CancelDialogs(
198 content::WebContents* web_contents,
199 bool reset_state) {
200 CefRefPtr<CefClient> client = browser_->GetClient();
201 if (client.get()) {
202 CefRefPtr<CefJSDialogHandler> handler = client->GetJSDialogHandler();
203 if (handler.get()) {
204 // Execute the user callback.
205 handler->OnResetDialogState(browser_);
206 }
207 }
208
209 if (runner_.get() && dialog_running_) {
210 runner_->Cancel();
211 dialog_running_ = false;
212 }
213 }
214
DialogClosed(DialogClosedCallback callback,bool success,const std::u16string & user_input)215 void CefJavaScriptDialogManager::DialogClosed(
216 DialogClosedCallback callback,
217 bool success,
218 const std::u16string& user_input) {
219 CefRefPtr<CefClient> client = browser_->GetClient();
220 if (client.get()) {
221 CefRefPtr<CefJSDialogHandler> handler = client->GetJSDialogHandler();
222 if (handler.get())
223 handler->OnDialogClosed(browser_);
224 }
225
226 DCHECK(runner_.get());
227 DCHECK(dialog_running_);
228
229 dialog_running_ = false;
230
231 std::move(callback).Run(success, user_input);
232 }
233