From 795447f61ab0c286f35e44204d5e94ca4a2461f7 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Wed, 10 Jan 2018 17:07:56 +1100 Subject: [PATCH] Implement dialog (alert/confirm) blocking as a user switch after the first dialog * This is to enable more browser-like behavior so that users who run third-party code will not be DOS'ed with alerts and confirms. This is already handled like this in most major browsers so this will greatly help these developers --- .../browser/atom_javascript_dialog_manager.cc | 28 ++++++++++++++-- atom/browser/atom_javascript_dialog_manager.h | 4 +++ atom/browser/web_contents_preferences.cc | 9 +++++ atom/browser/web_contents_preferences.h | 4 +++ docs/api/browser-window.md | 4 +++ lib/browser/rpc-server.js | 33 ------------------- lib/renderer/window-setup.js | 8 ----- 7 files changed, 47 insertions(+), 43 deletions(-) diff --git a/atom/browser/atom_javascript_dialog_manager.cc b/atom/browser/atom_javascript_dialog_manager.cc index c593b2bfba1..c209e700015 100644 --- a/atom/browser/atom_javascript_dialog_manager.cc +++ b/atom/browser/atom_javascript_dialog_manager.cc @@ -10,6 +10,7 @@ #include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/native_window.h" #include "atom/browser/ui/message_box.h" +#include "atom/browser/web_contents_preferences.h" #include "base/bind.h" #include "base/strings/utf_string_conversions.h" #include "ui/gfx/image/image_skia.h" @@ -30,6 +31,13 @@ void AtomJavaScriptDialogManager::RunJavaScriptDialog( const base::string16& default_prompt_text, const DialogClosedCallback& callback, bool* did_suppress_message) { + const std::string origin = origin_url.GetOrigin().spec(); + if (origin_counts_.find(origin) == origin_counts_.end()) { + origin_counts_[origin] = 0; + } + + if (origin_counts_[origin] == -1) return callback.Run(false, base::string16());; + if (dialog_type != JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_ALERT && dialog_type != JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_CONFIRM) { callback.Run(false, base::string16()); @@ -41,12 +49,23 @@ void AtomJavaScriptDialogManager::RunJavaScriptDialog( buttons.push_back("Cancel"); } + origin_counts_[origin]++; + + std::string checkbox_string; + if (origin_counts_[origin] > 1 && + WebContentsPreferences::IsPreferenceEnabled("safeDialogs", web_contents)) { + if (!WebContentsPreferences::GetString("safeDialogsMessage", + &checkbox_string, web_contents)) { + checkbox_string = "Prevent this app from creating additional dialogs"; + } + } atom::ShowMessageBox(NativeWindow::FromWebContents(web_contents), atom::MessageBoxType::MESSAGE_BOX_TYPE_NONE, buttons, -1, 0, atom::MessageBoxOptions::MESSAGE_BOX_NONE, "", - base::UTF16ToUTF8(message_text), "", "", false, + base::UTF16ToUTF8(message_text), "", checkbox_string, false, gfx::ImageSkia(), - base::Bind(&OnMessageBoxCallback, callback)); + base::Bind(&OnMessageBoxCallback, callback, origin, + &origin_counts_)); } void AtomJavaScriptDialogManager::RunBeforeUnloadDialog( @@ -66,8 +85,13 @@ void AtomJavaScriptDialogManager::CancelDialogs( // static void AtomJavaScriptDialogManager::OnMessageBoxCallback( const DialogClosedCallback& callback, + const std::string& origin, + std::map* origin_counts_, int code, bool checkbox_checked) { + if (checkbox_checked) { + (*origin_counts_)[origin] = -1; + } callback.Run(code == 0, base::string16()); } diff --git a/atom/browser/atom_javascript_dialog_manager.h b/atom/browser/atom_javascript_dialog_manager.h index d5e6c543361..b2af83d4cfa 100644 --- a/atom/browser/atom_javascript_dialog_manager.h +++ b/atom/browser/atom_javascript_dialog_manager.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_ATOM_JAVASCRIPT_DIALOG_MANAGER_H_ #include +#include #include "content/public/browser/javascript_dialog_manager.h" @@ -37,9 +38,12 @@ class AtomJavaScriptDialogManager : public content::JavaScriptDialogManager { private: static void OnMessageBoxCallback(const DialogClosedCallback& callback, + const std::string& origin, + std::map* origins_, int code, bool checkbox_checked); api::WebContents* api_web_contents_; + std::map origin_counts_; }; } // namespace atom diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 2cbf07be86a..932da8f4a86 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -301,4 +301,13 @@ bool WebContentsPreferences::GetInteger(const std::string& attributeName, return false; } +bool WebContentsPreferences::GetString(const std::string& attributeName, + std::string* stringValue, + content::WebContents* web_contents) { + WebContentsPreferences* self = FromWebContents(web_contents); + if (!self) + return false; + return self->web_preferences()->GetString(attributeName, stringValue); +} + } // namespace atom diff --git a/atom/browser/web_contents_preferences.h b/atom/browser/web_contents_preferences.h index 94dd8dc598b..299c088174e 100644 --- a/atom/browser/web_contents_preferences.h +++ b/atom/browser/web_contents_preferences.h @@ -40,6 +40,10 @@ class WebContentsPreferences static bool IsPreferenceEnabled(const std::string& attribute_name, content::WebContents* web_contents); + static bool GetString(const std::string& attributeName, + std::string* stringValue, + content::WebContents* web_contents); + // Modify the WebPreferences according to |web_contents|'s preferences. static void OverrideWebkitPrefs( content::WebContents* web_contents, content::WebPreferences* prefs); diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 2be2da59766..e739a659e4d 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -362,6 +362,10 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `additionArguments` String[] (optional) - A list of strings that will be appended to `process.argv` in the renderer process of this app. Useful for passing small bits of data down to renderer process preload scripts. + * `safeDialogs` Boolean (optional) - Whether to enable browser style + consecutive dialog protection. + * `safeDialogsMessage` String (optional) - The message to display when consecutive + dialog protection is triggered. When setting minimum or maximum window size with `minWidth`/`maxWidth`/ `minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index c0cd09dea42..633360db1e2 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -438,39 +438,6 @@ ipcMain.on('ELECTRON_BROWSER_SEND_TO', function (event, sendToAll, webContentsId } }) -// Implements window.alert(message, title) -ipcMain.on('ELECTRON_BROWSER_WINDOW_ALERT', function (event, message, title) { - if (message == null) message = '' - if (title == null) title = '' - - const dialogProperties = { - message: `${message}`, - title: `${title}`, - buttons: ['OK'] - } - event.returnValue = event.sender.isOffscreen() - ? electron.dialog.showMessageBox(dialogProperties) - : electron.dialog.showMessageBox( - event.sender.getOwnerBrowserWindow(), dialogProperties) -}) - -// Implements window.confirm(message, title) -ipcMain.on('ELECTRON_BROWSER_WINDOW_CONFIRM', function (event, message, title) { - if (message == null) message = '' - if (title == null) title = '' - - const dialogProperties = { - message: `${message}`, - title: `${title}`, - buttons: ['OK', 'Cancel'], - cancelId: 1 - } - event.returnValue = !(event.sender.isOffscreen() - ? electron.dialog.showMessageBox(dialogProperties) - : electron.dialog.showMessageBox( - event.sender.getOwnerBrowserWindow(), dialogProperties)) -}) - // Implements window.close() ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) { const window = event.sender.getOwnerBrowserWindow() diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js index b94eace5aec..ac329b43070 100644 --- a/lib/renderer/window-setup.js +++ b/lib/renderer/window-setup.js @@ -133,14 +133,6 @@ module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage, usesNative } } - window.alert = function (message, title) { - ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_ALERT', toString(message), toString(title)) - } - - window.confirm = function (message, title) { - return ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CONFIRM', toString(message), toString(title)) - } - // But we do not support prompt(). window.prompt = function () { throw new Error('prompt() is and will not be supported.')