diff --git a/atom/atom_resources.grd b/atom/atom_resources.grd index 8b9b516c8b..11a80df735 100644 --- a/atom/atom_resources.grd +++ b/atom/atom_resources.grd @@ -17,8 +17,6 @@ - - diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index fde1e361f3..b874f8b66e 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -468,9 +468,16 @@ void WebContents::AddNewContents(content::WebContents* source, v8::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); + auto source_api_contents = CreateFrom(isolate(), source); auto new_api_contents = CreateFrom(isolate(), new_contents); + if (source_api_contents->GetType() == BACKGROUND_PAGE) { + user_gesture = true; + } + std::unique_ptr options(new base::DictionaryValue); + options->SetString("openerUrl", source_api_contents->GetURL().spec()); + options->SetInteger("openerTabId", source_api_contents->GetID()); options->SetBoolean("userGesture", user_gesture); auto url_params = new_api_contents->delayed_load_url_params_.get(); diff --git a/atom/browser/common_web_contents_delegate.cc b/atom/browser/common_web_contents_delegate.cc index 80e4d23972..f857d42791 100644 --- a/atom/browser/common_web_contents_delegate.cc +++ b/atom/browser/common_web_contents_delegate.cc @@ -9,7 +9,6 @@ #include #include "atom/browser/atom_browser_context.h" -#include "atom/browser/atom_javascript_dialog_manager.h" #include "atom/browser/atom_security_state_model_client.h" #include "atom/browser/browser.h" #include "atom/browser/native_window.h" @@ -19,6 +18,7 @@ #include "atom/common/atom_constants.h" #include "base/files/file_util.h" #include "base/strings/utf_string_conversions.h" +#include "brave/browser/brave_javascript_dialog_manager.h" #include "chrome/browser/custom_handlers/protocol_handler_registry.h" #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h" #include "chrome/browser/printing/print_preview_message_handler.h" @@ -427,10 +427,7 @@ bool CommonWebContentsDelegate::CanOverscrollContent() const { content::JavaScriptDialogManager* CommonWebContentsDelegate::GetJavaScriptDialogManager( content::WebContents* source) { - if (!dialog_manager_) - dialog_manager_.reset(new AtomJavaScriptDialogManager); - - return dialog_manager_.get(); + return brave::BraveJavaScriptDialogManager::GetInstance(); } content::ColorChooser* CommonWebContentsDelegate::OpenColorChooser( diff --git a/atom/browser/common_web_contents_delegate.h b/atom/browser/common_web_contents_delegate.h index 632db0ec10..4f3dd94cb0 100644 --- a/atom/browser/common_web_contents_delegate.h +++ b/atom/browser/common_web_contents_delegate.h @@ -20,7 +20,6 @@ using brightray::DevToolsFileSystemIndexer; namespace atom { class AtomBrowserContext; -class AtomJavaScriptDialogManager; class NativeWindow; class WebDialogHelper; @@ -163,7 +162,6 @@ class CommonWebContentsDelegate bool native_fullscreen_; std::unique_ptr web_dialog_helper_; - std::unique_ptr dialog_manager_; scoped_refptr devtools_file_system_indexer_; // Make sure BrowserContext is alwasys destroyed after WebContents. diff --git a/atom/common/api/event_emitter_caller.h b/atom/common/api/event_emitter_caller.h index a2567da9d1..2c08b9662e 100644 --- a/atom/common/api/event_emitter_caller.h +++ b/atom/common/api/event_emitter_caller.h @@ -7,6 +7,7 @@ #include +#include "gin/converter.h" #include "native_mate/converter.h" namespace mate { @@ -28,7 +29,7 @@ v8::Local EmitEvent(v8::Isolate* isolate, v8::Local obj, const StringType& name, const internal::ValueVector& args) { - internal::ValueVector concatenated_args = { StringToV8(isolate, name) }; + internal::ValueVector concatenated_args = { gin::StringToV8(isolate, name) }; concatenated_args.reserve(1 + args.size()); concatenated_args.insert(concatenated_args.end(), args.begin(), args.end()); return internal::CallEmitWithArgs(isolate, obj, &concatenated_args); @@ -42,7 +43,7 @@ v8::Local EmitEvent(v8::Isolate* isolate, const StringType& name, const Args&... args) { internal::ValueVector converted_args = { - StringToV8(isolate, name), + gin::StringToV8(isolate, name), ConvertToV8(isolate, args)..., }; return internal::CallEmitWithArgs(isolate, obj, &converted_args); diff --git a/atom/common/api/resources/ipc_bindings.js b/atom/common/api/resources/ipc_bindings.js deleted file mode 100644 index cec845f7cb..0000000000 --- a/atom/common/api/resources/ipc_bindings.js +++ /dev/null @@ -1,33 +0,0 @@ -const ipc = require('ipc_utils') - -exports.didCreateDocumentElement = function() { - // don't try to run if there is no window object - if (!window) - return - - window.alert = function(message, title) { - return ipc.send('window-alert', message, title) - } - - window.confirm = function(message, title) { - return ipc.sendSync('window-confirm', message, title) - } - - window.prompt = function(text, defaultText) { - return ipc.sendSync('window-prompt', text, defaultText) - } - - if (window.chrome && window.chrome.tabs && window.chrome.tabs.create) - window.open = function(url, name, specs, replace) { - if (name !== undefined) { - throw new Error('name of window.open is not supported yet') - } - if (specs !== undefined) { - throw new Error('specs of window.open is not supported yet') - } - if (replace !== undefined) { - throw new Error('replace of window.open is not supported yet') - } - return window.chrome.tabs.create({url: url}) - } -} diff --git a/atom/renderer/extensions/atom_extensions_dispatcher_delegate.cc b/atom/renderer/extensions/atom_extensions_dispatcher_delegate.cc index d5a5f5ad28..84003897cf 100644 --- a/atom/renderer/extensions/atom_extensions_dispatcher_delegate.cc +++ b/atom/renderer/extensions/atom_extensions_dispatcher_delegate.cc @@ -169,7 +169,6 @@ void AtomExtensionsDispatcherDelegate::PopulateSourceMap( source_map->RegisterSource("event_emitter", IDR_ATOM_EVENT_EMITTER_JS); source_map->RegisterSource("ipcRenderer", IDR_BRAVE_IPC_RENDERER_JS); source_map->RegisterSource("ipc_utils", IDR_ATOM_IPC_INTERNAL_JS); - source_map->RegisterSource("windowDialogs", IDR_ATOM_IPC_WINDOW_DIALOGS_JS); source_map->RegisterSource("webFrame", IDR_ATOM_WEB_FRAME_BINDINGS_JS); source_map->RegisterSource("remote", diff --git a/atom/renderer/extensions/atom_extensions_renderer_client.cc b/atom/renderer/extensions/atom_extensions_renderer_client.cc index 5ec8740064..714f7012ae 100644 --- a/atom/renderer/extensions/atom_extensions_renderer_client.cc +++ b/atom/renderer/extensions/atom_extensions_renderer_client.cc @@ -39,31 +39,6 @@ bool IsStandaloneExtensionProcess() { extensions::switches::kExtensionProcess); } -void DidCreateDocumentElement(content::RenderFrame* render_frame) { - v8::Isolate* isolate = blink::mainThreadIsolate(); - v8::HandleScope handle_scope(isolate); - - v8::Local context = - render_frame->GetWebFrame()->mainWorldScriptContext(); - v8::Context::Scope context_scope(context); - - auto script_context = - extensions::ScriptContextSet::GetContextByV8Context(context); - - if (script_context) { - extensions::ModuleSystem::NativesEnabledScope - natives_enabled(script_context->module_system()); - script_context->module_system() - ->CallModuleMethod("windowDialogs", "didCreateDocumentElement"); - } - - // reschedule the callback because a new render frame - // is not always created when navigating - extensions::ExtensionFrameHelper::Get(render_frame) - ->ScheduleAtDocumentStart(base::Bind(DidCreateDocumentElement, - render_frame)); -} - } // namespace namespace extensions { @@ -127,9 +102,6 @@ void AtomExtensionsRendererClient::RenderFrameCreated( new extensions::ExtensionFrameHelper(render_frame, extension_dispatcher_.get()); extension_dispatcher_->OnRenderFrameCreated(render_frame); - ExtensionFrameHelper::Get(render_frame) - ->ScheduleAtDocumentStart(base::Bind(DidCreateDocumentElement, - render_frame)); } void AtomExtensionsRendererClient::RenderViewCreated( diff --git a/brave/browser/BUILD.gn b/brave/browser/BUILD.gn index 24ba32dca4..afbda0577f 100644 --- a/brave/browser/BUILD.gn +++ b/brave/browser/BUILD.gn @@ -25,6 +25,8 @@ source_set("browser") { "brave_browser_context.cc", "brave_content_browser_client.h", "brave_content_browser_client.cc", + "brave_javascript_dialog_manager.h", + "brave_javascript_dialog_manager.cc", "brave_permission_manager.h", "brave_permission_manager.cc", "renderer_preferences_helper.h", diff --git a/brave/browser/brave_javascript_dialog_manager.cc b/brave/browser/brave_javascript_dialog_manager.cc new file mode 100644 index 0000000000..0dc892d589 --- /dev/null +++ b/brave/browser/brave_javascript_dialog_manager.cc @@ -0,0 +1,408 @@ +// Copyright 2016 The Brave 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 "brave/browser/brave_javascript_dialog_manager.h" + +#include +#include + +#include "base/bind.h" +#include "base/i18n/rtl.h" +#include "base/macros.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/utf_string_conversions.h" +#include "components/url_formatter/elide_url.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/javascript_message_type.h" +#include "gin/converter.h" +#include "gin/try_catch.h" +#include "grit/components_strings.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/font_list.h" + +#include "atom/common/api/event_emitter_caller.h" +#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/native_mate_converters/content_converter.h" +#include "atom/common/native_mate_converters/string16_converter.h" +#include "atom/common/node_includes.h" +#include "native_mate/converter.h" +#include "native_mate/dictionary.h" + +namespace mate { + +template<> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + brave::JavaScriptDialogExtraData* val) { + if (!val) + return v8::Null(isolate); + + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.Set("suppressJavascriptMessages", val->suppress_javascript_messages_); + dict.Set("dialogCount", val->dialog_count_); + dict.Set("suppressedDialogCount", val->suppressed_dialog_count_); + return dict.GetHandle(); + } +}; + +} + +namespace brave { + +namespace { + +// Keep in sync with kDefaultMessageWidth, but allow some space for the rest of +// the text. +const int kUrlElideWidth = 350; + +bool ShouldDisplaySuppressCheckbox( + JavaScriptDialogExtraData* extra_data) { + return extra_data->dialog_count_ > 0; +} + +void LogUMAMessageLengthStats(const base::string16& message) { + UMA_HISTOGRAM_COUNTS("JSDialogs.CountOfJSDialogMessageCharacters", + static_cast(message.length())); + + int32_t newline_count = + std::count_if(message.begin(), message.end(), + [](const base::char16& c) { return c == '\n'; }); + UMA_HISTOGRAM_COUNTS("JSDialogs.CountOfJSDialogMessageNewlines", + newline_count); +} + +} // namespace + +JavaScriptDialogExtraData::JavaScriptDialogExtraData() + : dialog_count_(0), + suppress_javascript_messages_(false), + suppressed_dialog_count_(0) {} + +//////////////////////////////////////////////////////////////////////////////// +// BraveJavaScriptDialogManager, public: + +// static +BraveJavaScriptDialogManager* BraveJavaScriptDialogManager::GetInstance() { + return base::Singleton::get(); +} + +//////////////////////////////////////////////////////////////////////////////// +// BraveJavaScriptDialogManager, private: + +BraveJavaScriptDialogManager::BraveJavaScriptDialogManager() { +} + +BraveJavaScriptDialogManager::~BraveJavaScriptDialogManager() { +} + +void BraveJavaScriptDialogManager::RunJavaScriptDialog( + content::WebContents* web_contents, + const GURL& origin_url, + content::JavaScriptMessageType message_type, + const base::string16& message_text, + const base::string16& default_prompt_text, + const DialogClosedCallback& callback, + bool* did_suppress_message) { + *did_suppress_message = false; + + JavaScriptDialogExtraData* extra_data = + &javascript_dialog_extra_data_[web_contents]; + + if (extra_data->suppress_javascript_messages_) { + // If a page tries to open dialogs in a tight loop, the number of + // suppressions logged can grow out of control. Arbitrarily cap the number + // logged at 100. That many suppressed dialogs is enough to indicate the + // page is doing something very hinky. + if (extra_data->suppressed_dialog_count_ < 100) { + // Log a suppressed dialog as one that opens and then closes immediately. + UMA_HISTOGRAM_MEDIUM_TIMES( + "JSDialogs.FineTiming.TimeBetweenDialogCreatedAndSameDialogClosed", + base::TimeDelta()); + + // Only increment the count if it's not already at the limit, to prevent + // overflow. + extra_data->suppressed_dialog_count_++; + } + + *did_suppress_message = true; + return; + } + + base::TimeTicks now = base::TimeTicks::Now(); + if (!last_creation_time_.is_null()) { + // A new dialog has been created: log the time since the last one was + // created. + UMA_HISTOGRAM_MEDIUM_TIMES( + "JSDialogs.FineTiming.TimeBetweenDialogCreatedAndNextDialogCreated", + now - last_creation_time_); + } + last_creation_time_ = now; + + // Also log the time since a dialog was closed, but only if this is the first + // dialog that was opened since the closing. + if (!last_close_time_.is_null()) { + UMA_HISTOGRAM_MEDIUM_TIMES( + "JSDialogs.FineTiming.TimeBetweenDialogClosedAndNextDialogCreated", + now - last_close_time_); + last_close_time_ = base::TimeTicks(); + } + + bool is_alert = message_type == content::JAVASCRIPT_MESSAGE_TYPE_ALERT; + base::string16 dialog_title = GetTitle(web_contents, origin_url, is_alert); + + LogUMAMessageLengthStats(message_text); + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (!isolate) { + *did_suppress_message = true; + return; + } + + node::Environment* env = node::Environment::GetCurrent(isolate); + if (!env) { + *did_suppress_message = true; + return; + } + + gin::TryCatch try_catch(isolate); + mate::EmitEvent(isolate, + env->process_object(), + GetEventName(message_type), + web_contents, + extra_data, + dialog_title, + message_text, + default_prompt_text, + ShouldDisplaySuppressCheckbox(extra_data), + false, // is_before_unload_dialog + false, // is_reload + base::Bind(&BraveJavaScriptDialogManager::OnDialogClosed, + base::Unretained(this), web_contents, callback)); + if (try_catch.HasCaught()) { + callback.Run(false, base::string16()); + LOG(ERROR) << "Uncaught exception: " << try_catch.GetStackTrace(); + } else { + if (extra_data->dialog_count_ < 100) { + extra_data->dialog_count_++; + } + } +} + +void BraveJavaScriptDialogManager::RunBeforeUnloadDialog( + content::WebContents* web_contents, + bool is_reload, + const DialogClosedCallback& callback) { + JavaScriptDialogExtraData* extra_data = + &javascript_dialog_extra_data_[web_contents]; + + if (extra_data->suppress_javascript_messages_) { + // If a site harassed the user enough for them to put it on mute, then it + // lost its privilege to deny unloading. + callback.Run(true, base::string16()); + return; + } + + // Build the dialog message. We explicitly do _not_ allow the webpage to + // specify the contents of this dialog, because most of the time nowadays it's + // used for scams. + // + // This does not violate the spec. Per + // https://html.spec.whatwg.org/#prompt-to-unload-a-document, step 7: + // + // "The prompt shown by the user agent may include the string of the + // returnValue attribute, or some leading subset thereof." + // + // The prompt MAY include the string. It doesn't any more. Scam web page + // authors have abused this, so we're taking away the toys from everyone. This + // is why we can't have nice things. + const base::string16 title = l10n_util::GetStringUTF16(is_reload ? + IDS_BEFORERELOAD_MESSAGEBOX_TITLE : IDS_BEFOREUNLOAD_MESSAGEBOX_TITLE); + const base::string16 message = + l10n_util::GetStringUTF16(IDS_BEFOREUNLOAD_MESSAGEBOX_MESSAGE); + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (!isolate) { + callback.Run(true, base::string16()); + return; + } + + node::Environment* env = node::Environment::GetCurrent(isolate); + if (!env) { + callback.Run(true, base::string16()); + return; + } + + gin::TryCatch try_catch(isolate); + mate::EmitEvent(isolate, + env->process_object(), + GetEventName(content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM), + web_contents, + extra_data, + title, + message, + base::string16(), // default_prompt_text + ShouldDisplaySuppressCheckbox(extra_data), + true, // is_before_unload_dialog + is_reload, + base::Bind(&BraveJavaScriptDialogManager::OnBeforeUnloadDialogClosed, + base::Unretained(this), web_contents, callback)); + + if (try_catch.HasCaught()) { + callback.Run(true, base::string16()); + LOG(ERROR) << "Uncaught exception: " << try_catch.GetStackTrace(); + } else { + if (extra_data->dialog_count_ < 100) { + extra_data->dialog_count_++; + } + } +} + +bool BraveJavaScriptDialogManager::HandleJavaScriptDialog( + content::WebContents* web_contents, + bool accept, + const base::string16* prompt_override) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (!isolate) + return false; + + node::Environment* env = node::Environment::GetCurrent(isolate); + if (!env) + return false; + + gin::TryCatch try_catch(isolate); + mate::EmitEvent(isolate, + env->process_object(), + "handle-javascript-dialog", + web_contents, + accept, + *prompt_override); + + if (try_catch.HasCaught()) { + LOG(ERROR) << "Uncaught exception: " << try_catch.GetStackTrace(); + return false; + } else { + return true; + } +} + +void BraveJavaScriptDialogManager::ResetDialogState( + content::WebContents* web_contents) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (!isolate) + return; + + node::Environment* env = node::Environment::GetCurrent(isolate); + if (!env) + return; + + gin::TryCatch try_catch(isolate); + mate::EmitEvent(isolate, + env->process_object(), + "reset-javascript-dialog", + web_contents); + if (try_catch.HasCaught()) { + LOG(ERROR) << "Uncaught exception: " << try_catch.GetStackTrace(); + } + + javascript_dialog_extra_data_.erase(web_contents); +} + +std::string BraveJavaScriptDialogManager::GetEventName(content::JavaScriptMessageType message_type) { + switch (message_type) { + case content::JAVASCRIPT_MESSAGE_TYPE_ALERT: return "window-alert"; + case content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM: return "window-confirm"; + case content::JAVASCRIPT_MESSAGE_TYPE_PROMPT: return "window-prompt"; + default: return "unknown"; + } +} + +base::string16 BraveJavaScriptDialogManager::GetTitle( + content::WebContents* web_contents, + const GURL& origin_url, + bool is_alert) { + // For extensions, show the extension name, but only if the origin of + // the alert matches the top-level WebContents. + std::string name; + // if (extensions_client_->GetExtensionName(web_contents, origin_url, &name)) + // return base::UTF8ToUTF16(name); + + // Otherwise, return the formatted URL. For non-standard URLs such as |data:|, + // just say "This page". + bool is_same_origin_as_main_frame = + (web_contents->GetURL().GetOrigin() == origin_url.GetOrigin()); + if (origin_url.IsStandard() && !origin_url.SchemeIsFile() && + !origin_url.SchemeIsFileSystem()) { + base::string16 url_string = + url_formatter::ElideHost(origin_url, gfx::FontList(), kUrlElideWidth); + return l10n_util::GetStringFUTF16( + is_same_origin_as_main_frame ? IDS_JAVASCRIPT_MESSAGEBOX_TITLE + : IDS_JAVASCRIPT_MESSAGEBOX_TITLE_IFRAME, + base::i18n::GetDisplayStringInLTRDirectionality(url_string)); + } + return l10n_util::GetStringUTF16( + is_same_origin_as_main_frame + ? IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL + : IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL_IFRAME); +} + +void BraveJavaScriptDialogManager::CancelActiveAndPendingDialogs( + content::WebContents* web_contents) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + if (!isolate) + return; + + node::Environment* env = node::Environment::GetCurrent(isolate); + if (!env) + return; + + gin::TryCatch try_catch(isolate); + mate::EmitEvent(isolate, + env->process_object(), + "cancel-all-javascript-dialogs", + web_contents); + if (try_catch.HasCaught()) { + LOG(ERROR) << "Uncaught exception: " << try_catch.GetStackTrace(); + } +} + +void BraveJavaScriptDialogManager::OnBeforeUnloadDialogClosed( + content::WebContents* web_contents, + DialogClosedCallback callback, + bool success, + const base::string16& user_input, + bool suppress_javascript_messages = false) { + enum class StayVsLeave { + STAY = 0, + LEAVE = 1, + MAX, + }; + UMA_HISTOGRAM_ENUMERATION( + "JSDialogs.OnBeforeUnloadStayVsLeave", + static_cast(success ? StayVsLeave::LEAVE : StayVsLeave::STAY), + static_cast(StayVsLeave::MAX)); + + OnDialogClosed(web_contents, callback, success, user_input, + suppress_javascript_messages); +} + +void BraveJavaScriptDialogManager::OnDialogClosed( + content::WebContents* web_contents, + DialogClosedCallback callback, + bool success, + const base::string16& user_input, + bool suppress_javascript_messages = false) { + JavaScriptDialogExtraData* extra_data = + &javascript_dialog_extra_data_[web_contents]; + extra_data->suppress_javascript_messages_ = suppress_javascript_messages; + // If an extension opened this dialog then the extension may shut down its + // lazy background page after the dialog closes. (Dialogs are closed before + // their WebContents is destroyed so |web_contents| is still valid here.) + // extensions_client_->OnDialogClosed(web_contents); + + last_close_time_ = base::TimeTicks::Now(); + + callback.Run(success, user_input); +} + +} // namespace brave diff --git a/brave/browser/brave_javascript_dialog_manager.h b/brave/browser/brave_javascript_dialog_manager.h new file mode 100644 index 0000000000..cbc2f6161f --- /dev/null +++ b/brave/browser/brave_javascript_dialog_manager.h @@ -0,0 +1,96 @@ +// Copyright 2016 The Brave Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BRAVE_BROWSER_BRAVE_JAVASCRIPT_DIALOG_MANAGER +#define BRAVE_BROWSER_BRAVE_JAVASCRIPT_DIALOG_MANAGER + +#include +#include + +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "base/time/time.h" +#include "content/public/browser/javascript_dialog_manager.h" + +namespace brave { + +class JavaScriptDialogExtraData { + public: + JavaScriptDialogExtraData(); + + // The number of dialogs that the webContents has opened + bool dialog_count_; + + // True if the user has decided to block future JavaScript dialogs. + bool suppress_javascript_messages_; + + // Number of dialogs from the origin that were suppressed. + int suppressed_dialog_count_; +}; + +class BraveJavaScriptDialogManager : public content::JavaScriptDialogManager { + public: + typedef std::map ExtraDataMap; + typedef content::JavaScriptDialogManager::DialogClosedCallback DialogClosedCallback; + static BraveJavaScriptDialogManager* GetInstance(); + + private: + friend struct base::DefaultSingletonTraits; + + BraveJavaScriptDialogManager(); + ~BraveJavaScriptDialogManager() override; + + // JavaScriptDialogManager: + void RunJavaScriptDialog(content::WebContents* web_contents, + const GURL& origin_url, + content::JavaScriptMessageType message_type, + const base::string16& message_text, + const base::string16& default_prompt_text, + const DialogClosedCallback& callback, + bool* did_suppress_message) override; + void RunBeforeUnloadDialog(content::WebContents* web_contents, + bool is_reload, + const DialogClosedCallback& callback) override; + bool HandleJavaScriptDialog(content::WebContents* web_contents, + bool accept, + const base::string16* prompt_override) override; + void CancelActiveAndPendingDialogs( + content::WebContents* web_contents) override; + void ResetDialogState(content::WebContents* web_contents) override; + + std::string GetEventName(content::JavaScriptMessageType message_type); + base::string16 GetTitle(content::WebContents* web_contents, + const GURL& origin_url, + bool is_alert); + + // Wrapper around OnDialogClosed; logs UMA stats before continuing on. + void OnBeforeUnloadDialogClosed(content::WebContents* web_contents, + DialogClosedCallback callback, + bool success, + const base::string16& user_input, + bool suppress_javascript_messages); + + // Wrapper around a DialogClosedCallback so that we can intercept it before + // passing it onto the original callback. + void OnDialogClosed(content::WebContents* web_contents, + DialogClosedCallback callback, + bool success, + const base::string16& user_input, + bool suppress_javascript_messages); + + // Mapping between the WebContents and their extra data. The key + // is a void* because the pointer is just a cookie and is never dereferenced. + ExtraDataMap javascript_dialog_extra_data_; + + // Record a single create and close timestamp to track the time between + // dialogs. + base::TimeTicks last_close_time_; + base::TimeTicks last_creation_time_; + + DISALLOW_COPY_AND_ASSIGN(BraveJavaScriptDialogManager); +}; + +} // namespace brave + +#endif // BRAVE_BROWSER_BRAVE_JAVASCRIPT_DIALOG_MANAGER