Merge pull request #11613 from electron/safe-dialogs

Implement dialog (alert/confirm) blocking as a user switch after the first dialog
This commit is contained in:
Cheng Zhao 2018-03-06 14:28:53 +09:00 committed by GitHub
commit e73326a324
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 60 additions and 59 deletions

View file

@ -10,6 +10,7 @@
#include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/api/atom_api_web_contents.h"
#include "atom/browser/native_window.h" #include "atom/browser/native_window.h"
#include "atom/browser/ui/message_box.h" #include "atom/browser/ui/message_box.h"
#include "atom/browser/web_contents_preferences.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia.h"
@ -18,6 +19,12 @@ using content::JavaScriptDialogType;
namespace atom { namespace atom {
namespace {
constexpr int kUserWantsNoMoreDialogs = -1;
} // namespace
AtomJavaScriptDialogManager::AtomJavaScriptDialogManager( AtomJavaScriptDialogManager::AtomJavaScriptDialogManager(
api::WebContents* api_web_contents) api::WebContents* api_web_contents)
: api_web_contents_(api_web_contents) {} : api_web_contents_(api_web_contents) {}
@ -30,6 +37,11 @@ void AtomJavaScriptDialogManager::RunJavaScriptDialog(
const base::string16& default_prompt_text, const base::string16& default_prompt_text,
const DialogClosedCallback& callback, const DialogClosedCallback& callback,
bool* did_suppress_message) { bool* did_suppress_message) {
const std::string origin = origin_url.GetOrigin().spec();
if (origin_counts_[origin] == kUserWantsNoMoreDialogs) {
return callback.Run(false, base::string16());
}
if (dialog_type != JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_ALERT && if (dialog_type != JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_ALERT &&
dialog_type != JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_CONFIRM) { dialog_type != JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_CONFIRM) {
callback.Run(false, base::string16()); callback.Run(false, base::string16());
@ -41,12 +53,25 @@ void AtomJavaScriptDialogManager::RunJavaScriptDialog(
buttons.push_back("Cancel"); buttons.push_back("Cancel");
} }
atom::ShowMessageBox(NativeWindow::FromWebContents(web_contents), origin_counts_[origin]++;
atom::MessageBoxType::MESSAGE_BOX_TYPE_NONE, buttons, -1,
0, atom::MessageBoxOptions::MESSAGE_BOX_NONE, "", std::string checkbox_string;
base::UTF16ToUTF8(message_text), "", "", false, if (origin_counts_[origin] > 1 &&
gfx::ImageSkia(), WebContentsPreferences::IsPreferenceEnabled("safeDialogs",
base::Bind(&OnMessageBoxCallback, callback)); 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), "", checkbox_string,
false, gfx::ImageSkia(),
base::Bind(&AtomJavaScriptDialogManager::OnMessageBoxCallback,
base::Unretained(this), callback, origin));
} }
void AtomJavaScriptDialogManager::RunBeforeUnloadDialog( void AtomJavaScriptDialogManager::RunBeforeUnloadDialog(
@ -63,11 +88,13 @@ void AtomJavaScriptDialogManager::CancelDialogs(
bool reset_state) { bool reset_state) {
} }
// static
void AtomJavaScriptDialogManager::OnMessageBoxCallback( void AtomJavaScriptDialogManager::OnMessageBoxCallback(
const DialogClosedCallback& callback, const DialogClosedCallback& callback,
const std::string& origin,
int code, int code,
bool checkbox_checked) { bool checkbox_checked) {
if (checkbox_checked)
origin_counts_[origin] = kUserWantsNoMoreDialogs;
callback.Run(code == 0, base::string16()); callback.Run(code == 0, base::string16());
} }

View file

@ -5,6 +5,7 @@
#ifndef ATOM_BROWSER_ATOM_JAVASCRIPT_DIALOG_MANAGER_H_ #ifndef ATOM_BROWSER_ATOM_JAVASCRIPT_DIALOG_MANAGER_H_
#define ATOM_BROWSER_ATOM_JAVASCRIPT_DIALOG_MANAGER_H_ #define ATOM_BROWSER_ATOM_JAVASCRIPT_DIALOG_MANAGER_H_
#include <map>
#include <string> #include <string>
#include "content/public/browser/javascript_dialog_manager.h" #include "content/public/browser/javascript_dialog_manager.h"
@ -36,10 +37,13 @@ class AtomJavaScriptDialogManager : public content::JavaScriptDialogManager {
bool reset_state) override; bool reset_state) override;
private: private:
static void OnMessageBoxCallback(const DialogClosedCallback& callback, void OnMessageBoxCallback(const DialogClosedCallback& callback,
int code, const std::string& origin,
bool checkbox_checked); int code,
bool checkbox_checked);
api::WebContents* api_web_contents_; api::WebContents* api_web_contents_;
std::map<std::string, int> origin_counts_;
}; };
} // namespace atom } // namespace atom

View file

@ -301,4 +301,13 @@ bool WebContentsPreferences::GetInteger(const std::string& attributeName,
return false; return false;
} }
bool WebContentsPreferences::GetString(const std::string& attribute_name,
std::string* string_value,
content::WebContents* web_contents) {
WebContentsPreferences* self = FromWebContents(web_contents);
if (!self)
return false;
return self->web_preferences()->GetString(attribute_name, string_value);
}
} // namespace atom } // namespace atom

View file

@ -40,6 +40,10 @@ class WebContentsPreferences
static bool IsPreferenceEnabled(const std::string& attribute_name, static bool IsPreferenceEnabled(const std::string& attribute_name,
content::WebContents* web_contents); content::WebContents* web_contents);
static bool GetString(const std::string& attribute_name,
std::string* string_value,
content::WebContents* web_contents);
// Modify the WebPreferences according to |web_contents|'s preferences. // Modify the WebPreferences according to |web_contents|'s preferences.
static void OverrideWebkitPrefs( static void OverrideWebkitPrefs(
content::WebContents* web_contents, content::WebPreferences* prefs); content::WebContents* web_contents, content::WebPreferences* prefs);

View file

@ -362,6 +362,12 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
* `additionArguments` String[] (optional) - A list of strings that will be appended * `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 to `process.argv` in the renderer process of this app. Useful for passing small
bits of data down to renderer process preload scripts. bits of data down to renderer process preload scripts.
* `safeDialogs` Boolean (optional) - Whether to enable browser style
consecutive dialog protection. Default is `false`.
* `safeDialogsMessage` String (optional) - The message to display when
consecutive dialog protection is triggered. If not defined the default
message would be used, note that currently the default message is in
English and not localized.
When setting minimum or maximum window size with `minWidth`/`maxWidth`/ When setting minimum or maximum window size with `minWidth`/`maxWidth`/
`minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from `minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from

View file

@ -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() // Implements window.close()
ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) { ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
const window = event.sender.getOwnerBrowserWindow() const window = event.sender.getOwnerBrowserWindow()

View file

@ -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(). // But we do not support prompt().
window.prompt = function () { window.prompt = function () {
throw new Error('prompt() is and will not be supported.') throw new Error('prompt() is and will not be supported.')

View file

@ -1088,10 +1088,6 @@ describe('chromium feature', () => {
assert.throws(() => { assert.throws(() => {
window.alert({toString: null}) window.alert({toString: null})
}, /Cannot convert object to primitive value/) }, /Cannot convert object to primitive value/)
assert.throws(() => {
window.alert('message', {toString: 3})
}, /Cannot convert object to primitive value/)
}) })
}) })
@ -1100,10 +1096,6 @@ describe('chromium feature', () => {
assert.throws(() => { assert.throws(() => {
window.confirm({toString: null}, 'title') window.confirm({toString: null}, 'title')
}, /Cannot convert object to primitive value/) }, /Cannot convert object to primitive value/)
assert.throws(() => {
window.confirm('message', {toString: 3})
}, /Cannot convert object to primitive value/)
}) })
}) })