Browse Source

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
Samuel Attard 7 years ago
parent
commit
795447f61a

+ 26 - 2
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<std::string, int>* origin_counts_,
     int code,
     bool checkbox_checked) {
+  if (checkbox_checked) {
+    (*origin_counts_)[origin] = -1;
+  }
   callback.Run(code == 0, base::string16());
 }
 

+ 4 - 0
atom/browser/atom_javascript_dialog_manager.h

@@ -6,6 +6,7 @@
 #define ATOM_BROWSER_ATOM_JAVASCRIPT_DIALOG_MANAGER_H_
 
 #include <string>
+#include <map>
 
 #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<std::string, int>* origins_,
                                    int code,
                                    bool checkbox_checked);
   api::WebContents* api_web_contents_;
+  std::map<std::string, int> origin_counts_;
 };
 
 }  // namespace atom

+ 9 - 0
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

+ 4 - 0
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);

+ 4 - 0
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

+ 0 - 33
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()

+ 0 - 8
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.')