Back out 2 changesets (bug 1116385, bug 724353) for test_unknownContentType_delayedbutton.xul failures
authorPhil Ringnalda <philringnalda@gmail.com>
Mon, 12 Oct 2015 21:37:58 -0700
changeset 267323 9fce08491c5326a6b862622a8b8da6107c262a16
parent 267322 aaade64d3f284881e0eee9849b4bbba38a1c5a12
child 267324 4d1bdb825e4c3ecc5a2069af64bb1ee9af0b9513
push id15640
push userphilringnalda@gmail.com
push dateTue, 13 Oct 2015 04:38:10 +0000
treeherderfx-team@9fce08491c53 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1116385, 724353
milestone44.0a1
backs outaaade64d3f284881e0eee9849b4bbba38a1c5a12
dca478776c91b77006c20623cdff44894dc7ebde
Back out 2 changesets (bug 1116385, bug 724353) for test_unknownContentType_delayedbutton.xul failures Backed out changeset aaade64d3f28 (bug 724353) Backed out changeset dca478776c91 (bug 1116385)
toolkit/components/prompts/src/CommonDialog.jsm
toolkit/components/prompts/src/SharedPromptUtils.jsm
toolkit/mozapps/downloads/content/unknownContentType.xul
toolkit/mozapps/downloads/nsHelperAppDlg.js
toolkit/mozapps/downloads/tests/chrome/chrome.ini
toolkit/mozapps/downloads/tests/chrome/test_unknownContentType_delayedbutton.xul
toolkit/mozapps/handling/content/dialog.js
--- a/toolkit/components/prompts/src/CommonDialog.jsm
+++ b/toolkit/components/prompts/src/CommonDialog.jsm
@@ -5,19 +5,16 @@
 this.EXPORTED_SYMBOLS = ["CommonDialog"];
 
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cc = Components.classes;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "EnableDelayHelper",
-                                  "resource://gre/modules/SharedPromptUtils.jsm");
 
 
 this.CommonDialog = function CommonDialog(args, ui) {
     this.args = args;
     this.ui   = ui;
 }
 
 CommonDialog.prototype = {
@@ -159,21 +156,23 @@ CommonDialog.prototype = {
             xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b];
         else
             button.setAttribute("default", "true");
 
         // Set default focus / selection.
         this.setDefaultFocus(true);
 
         if (this.args.enableDelay) {
-            this.delayHelper = new EnableDelayHelper({
-                disableDialog: () => this.setButtonsEnabledState(false),
-                enableDialog: () => this.setButtonsEnabledState(true),
-                focusTarget: this.ui.focusTarget
-            });
+            this.setButtonsEnabledState(false);
+            // Use a longer, pref-controlled delay when the dialog is first opened.
+            let delayTime = Services.prefs.getIntPref("security.dialog_enable_delay");
+            this.startOnFocusDelay(delayTime);
+            let self = this;
+            this.ui.focusTarget.addEventListener("blur",  function(e) { self.onBlur(e);  }, false);
+            this.ui.focusTarget.addEventListener("focus", function(e) { self.onFocus(e); }, false);
         }
 
         // Play a sound (unless we're tab-modal -- don't want those to feel like OS prompts).
         try {
             if (xulDialog && this.soundID) {
                 Cc["@mozilla.org/sound;1"].
                 createInstance(Ci.nsISound).
                 playEventSound(this.soundID);
@@ -226,16 +225,54 @@ CommonDialog.prototype = {
 
     setButtonsEnabledState : function(enabled) {
         this.ui.button0.disabled = !enabled;
         // button1 (cancel) remains enabled.
         this.ui.button2.disabled = !enabled;
         this.ui.button3.disabled = !enabled;
     },
 
+    onBlur : function (aEvent) {
+        if (aEvent.target != this.ui.focusTarget)
+            return;
+        this.setButtonsEnabledState(false);
+
+        // If we blur while waiting to enable the buttons, just cancel the
+        // timer to ensure the delay doesn't fire while not focused.
+        if (this.focusTimer) {
+            this.focusTimer.cancel();
+            this.focusTimer = null;
+        }
+    },
+
+    onFocus : function (aEvent) {
+        if (aEvent.target != this.ui.focusTarget)
+            return;
+        this.startOnFocusDelay();
+    },
+
+    startOnFocusDelay : function(delayTime) {
+        // Shouldn't already have a timer, but just in case...
+        if (this.focusTimer)
+            return;
+        // If no delay specified, use 250ms. (This is the normal case for when
+        // after the dialog has been opened and focus shifts.)
+        if (!delayTime)
+            delayTime = 250;
+        let self = this;
+        this.focusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+        this.focusTimer.initWithCallback(function() { self.onFocusTimeout(); },
+                                         delayTime, Ci.nsITimer.TYPE_ONE_SHOT);
+    },
+
+    onFocusTimeout : function() {
+        this.focusTimer = null;
+        this.setButtonsEnabledState(true);
+    },
+
     setDefaultFocus : function(isInitialLoad) {
         let b = (this.args.defaultButtonNum || 0);
         let button = this.ui["button" + b];
 
         if (!this.hasInputField) {
             let isOSX = ("nsILocalFileMac" in Components.interfaces);
             if (isOSX)
                 this.ui.infoBody.focus();
--- a/toolkit/components/prompts/src/SharedPromptUtils.jsm
+++ b/toolkit/components/prompts/src/SharedPromptUtils.jsm
@@ -1,17 +1,15 @@
-this.EXPORTED_SYMBOLS = [ "PromptUtils", "EnableDelayHelper" ];
+this.EXPORTED_SYMBOLS = [ "PromptUtils" ];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
-Cu.import("resource://gre/modules/Services.jsm");
-
 this.PromptUtils = {
     // Fire a dialog open/close event. Used by tabbrowser to focus the
     // tab which is triggering a prompt.
     // For remote dialogs, we pass in a different DOM window and a separate
     // target. If the caller doesn't pass in the target, then we'll simply use
     // the passed-in DOM window.
     fireDialogEvent : function (domWin, eventName, maybeTarget) {
         let target = maybeTarget || domWin;
@@ -37,115 +35,8 @@ this.PromptUtils = {
         // Here we iterate over the object's original properties, not the bag
         // (ie, the prompt can't return more/different properties than were
         // passed in). This just helps ensure that the caller provides default
         // values, lest the prompt forget to set them.
         for (let propName in obj)
             obj[propName] = propBag.getProperty(propName);
     },
 };
-
-/**
- * This helper handles the enabling/disabling of dialogs that might
- * be subject to fast-clicking attacks. It handles the initial delayed
- * enabling of the dialog, as well as disabling it on blur and reapplying
- * the delay when the dialog regains focus.
- *
- * @param enableDialog   A custom function to be called when the dialog
- *                       is to be enabled.
- * @param diableDialog   A custom function to be called when the dialog
- *                       is to be disabled.
- * @param focusTarget    The window used to watch focus/blur events.
- */
-this.EnableDelayHelper = function({enableDialog, disableDialog, focusTarget}) {
-    this.enableDialog = makeSafe(enableDialog);
-    this.disableDialog = makeSafe(disableDialog);
-    this.focusTarget = focusTarget;
-
-    this.disableDialog();;
-
-    this.focusTarget.addEventListener("blur", this, false);
-    this.focusTarget.addEventListener("focus", this, false);
-    this.focusTarget.document.addEventListener("unload", this, false);
-
-    this.startOnFocusDelay();
-};
-
-this.EnableDelayHelper.prototype = {
-    get delayTime() {
-        return Services.prefs.getIntPref("security.dialog_enable_delay");
-    },
-
-    handleEvent : function(event) {
-        if (event.target != this.focusTarget &&
-            event.target != this.focusTarget.document)
-            return;
-
-        switch (event.type) {
-            case "blur":
-                this.onBlur();
-                break;
-
-            case "focus":
-                this.onFocus();
-                break;
-
-            case "unload":
-                this.onUnload();
-                break;
-        }
-    },
-
-    onBlur : function () {
-        this.disableDialog();
-        // If we blur while waiting to enable the buttons, just cancel the
-        // timer to ensure the delay doesn't fire while not focused.
-        if (this._focusTimer) {
-            this._focusTimer.cancel();
-            this._focusTimer = null;
-        }
-    },
-
-    onFocus : function () {
-        this.startOnFocusDelay();
-    },
-
-    onUnload: function() {
-        this.focusTarget.removeEventListener("blur", this, false);
-        this.focusTarget.removeEventListener("focus", this, false);
-        this.focusTarget.document.removeEventListener("unload", this, false);
-
-        if (this._focusTimer) {
-            this._focusTimer.cancel();
-            this._focusTimer = null;
-        }
-
-        this.focusTarget = this.enableDialog = this.disableDialog = null;
-    },
-
-    startOnFocusDelay : function() {
-        if (this._focusTimer)
-            return;
-
-        this._focusTimer = Cc["@mozilla.org/timer;1"]
-                             .createInstance(Ci.nsITimer);
-        this._focusTimer.initWithCallback(
-            () => { this.onFocusTimeout(); },
-            this.delayTime,
-            Ci.nsITimer.TYPE_ONE_SHOT
-        );
-    },
-
-    onFocusTimeout : function() {
-        this._focusTimer = null;
-        this.enableDialog();
-    },
-};
-
-function makeSafe(fn) {
-    return function () {
-        // The dialog could be gone by now (if the user closed it),
-        // which makes it likely that the given fn might throw.
-        try {
-            fn();
-        } catch (e) { }
-    };
-}
--- a/toolkit/mozapps/downloads/content/unknownContentType.xul
+++ b/toolkit/mozapps/downloads/content/unknownContentType.xul
@@ -14,16 +14,17 @@
   %uctDTD;
   <!ENTITY % scDTD SYSTEM "chrome://mozapps/locale/downloads/settingsChange.dtd" >
   %scDTD;
 ]>            
 
 <dialog id="unknownContentType"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         onload="dialog.initDialog();" onunload="if (dialog) dialog.onCancel();"
+        onblur="if (dialog) dialog.onBlur(event);" onfocus="dialog.onFocus(event);"
 #ifdef XP_WIN
         style="width: 36em;"
 #else
         style="width: 34em;"
 #endif
         screenX="" screenY=""
         persist="screenX screenY"
         aria-describedby="intro location whichIs type from source unknownPrompt"
--- a/toolkit/mozapps/downloads/nsHelperAppDlg.js
+++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js
@@ -3,19 +3,16 @@
 /*
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
 
 const {utils: Cu, interfaces: Ci, classes: Cc, results: Cr} = Components;
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "EnableDelayHelper",
-                                  "resource://gre/modules/SharedPromptUtils.jsm");
 
 ///////////////////////////////////////////////////////////////////////////////
 //// Helper Functions
 
 /**
  * Determines if a given directory is able to be used to download to.
  *
  * @param aDirectory
@@ -537,31 +534,35 @@ nsUnknownContentTypeDialog.prototype = {
       var openHandler = this.dialogElement("openHandler");
       openHandler.parentNode.removeChild(openHandler);
       var openHandlerBox = this.dialogElement("openHandlerBox");
       openHandlerBox.appendChild(openHandler);
     }
 
     this.mDialog.setTimeout("dialog.postShowCallback()", 0);
 
-    this.delayHelper = new EnableDelayHelper({
-      disableDialog: () => {
-        this.mDialog.document.documentElement.getButton("accept").disabled = true;
-      },
-      enableDialog: () => {
-        this.mDialog.document.documentElement.getButton("accept").disabled = false;
-      },
-      focusTarget: this.mDialog
-    });
+    let acceptDelay = Services.prefs.getIntPref("security.dialog_enable_delay");
+    this.mDialog.document.documentElement.getButton("accept").disabled = true;
+    this._showTimer = Components.classes["@mozilla.org/timer;1"]
+                                .createInstance(nsITimer);
+    this._showTimer.initWithCallback(this, acceptDelay, nsITimer.TYPE_ONE_SHOT);
   },
 
   notify: function (aTimer) {
     if (aTimer == this._showTimer) {
       if (!this.mDialog) {
         this.reallyShow();
+      } else {
+        // The user may have already canceled the dialog.
+        try {
+          if (!this._blurred) {
+            this.mDialog.document.documentElement.getButton("accept").disabled = false;
+          }
+        } catch (ex) {}
+        this._delayExpired = true;
       }
       // The timer won't release us, so we have to release it.
       this._showTimer = null;
     }
     else if (aTimer == this._saveToDiskTimer) {
       // Since saveToDisk may open a file picker and therefore block this routine,
       // we should only call it once the dialog is closed.
       this.mLauncher.saveToDisk(null, false);
@@ -635,16 +636,31 @@ nsUnknownContentTypeDialog.prototype = {
                        .getFormattedString("orderedFileSizeWithType", 
                                            [typeString, size, unit]);
     }
     else {
       type.value = typeString;
     }
   },
 
+  _blurred: false,
+  _delayExpired: false,
+  onBlur: function(aEvent) {
+    this._blurred = true;
+    this.mDialog.document.documentElement.getButton("accept").disabled = true;
+  },
+
+  onFocus: function(aEvent) {
+    this._blurred = false;
+    if (this._delayExpired) {
+      var script = "document.documentElement.getButton('accept').disabled = false";
+      this.mDialog.setTimeout(script, 250);
+    }
+  },
+
   // Returns true if opening the default application makes sense.
   openWithDefaultOK: function() {
     // The checking is different on Windows...
 #ifdef XP_WIN
     // Windows presents some special cases.
     // We need to prevent use of "system default" when the file is
     // executable (so the user doesn't launch nasty programs downloaded
     // from the web), and, enable use of "system default" if it isn't
--- a/toolkit/mozapps/downloads/tests/chrome/chrome.ini
+++ b/toolkit/mozapps/downloads/tests/chrome/chrome.ini
@@ -31,10 +31,9 @@ disabled = temporarily disabled test (bu
 [test_select_all.xul]
 [test_space_key_pauses_resumes.xul]
 [test_taskbarprogress_downloadstates.xul]
 skip-if = os != 'win' && toolkit != 'cocoa'
 [test_taskbarprogress_service.xul]
 # disabled for very frequent orange--bug 630567
 skip-if = os != 'win' || true
 [test_ui_stays_open_on_alert_clickback.xul]
-[test_unknownContentType_delayedbutton.xul]
 [test_unknownContentType_dialog_layout.xul]
deleted file mode 100644
--- a/toolkit/mozapps/downloads/tests/chrome/test_unknownContentType_delayedbutton.xul
+++ /dev/null
@@ -1,114 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/.  -->
-<!--
- * The unknownContentType popup can have two different layouts depending on
- * whether a helper application can be selected or not.
- * This tests that both layouts have correct collapsed elements.
--->
-
-<window title="Unknown Content Type Dialog Test"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        onload="doTest()">
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
-  <script type="application/javascript"
-          src="utils.js"/>
-
-  <script type="application/javascript"><![CDATA[
-    Components.utils.import("resource://gre/modules/Services.jsm");
-    Components.utils.import("resource://gre/modules/Task.jsm");
-    Components.utils.import("resource://gre/modules/Promise.jsm");
-
-    const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xul";
-    const LOAD_URI = "http://mochi.test:8888/chrome/toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.txt";
-
-    const DIALOG_DELAY = Services.prefs.getIntPref("security.dialog_enable_delay") + 100;
-
-    let UCTObserver = {
-      opened: Promise.defer(),
-      closed: Promise.defer(),
-
-      observe: function(aSubject, aTopic, aData) {
-        let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
-
-        switch (aTopic) {
-          case "domwindowopened":
-            win.addEventListener("load", function onLoad(event) {
-              win.removeEventListener("load", onLoad, false);
-
-              // Let the dialog initialize
-              SimpleTest.executeSoon(function() {
-                UCTObserver.opened.resolve(win);
-              });
-            }, false);
-            break;
-
-          case "domwindowclosed":
-            if (win.location == UCT_URI) {
-              this.closed.resolve();
-            }
-            break;
-        }
-      }
-    };
-
-    Services.ww.registerNotification(UCTObserver);
-    SimpleTest.waitForExplicitFinish();
-
-    function waitDelay(delay) {
-      return new Promise((resolve, reject) => {
-        window.setTimeout(resolve, delay);
-      });
-    }
-
-    function doTest() {
-      Task.spawn(function test_aboutCrashed() {
-        let frame = document.getElementById("testframe");
-        frame.setAttribute("src", LOAD_URI);
-
-        let uctWindow = yield UCTObserver.opened.promise;
-        let ok = uctWindow.document.documentElement.getButton("accept");
-
-        SimpleTest.is(ok.disabled, true, "button started disabled");
-
-        yield waitDelay(DIALOG_DELAY);
-
-        SimpleTest.is(ok.disabled, false, "button was enabled");
-
-        window.focus();
-        yield waitDelay(0);
-
-        SimpleTest.is(ok.disabled, true, "button was disabled");
-
-        uctWindow.focus();
-        yield waitDelay(0);
-
-        SimpleTest.is(ok.disabled, true, "button remained disabled");
-
-        yield waitDelay(DIALOG_DELAY);
-        SimpleTest.is(ok.disabled, false, "button re-enabled after delay");
-
-        uctWindow.document.documentElement.cancelDialog();
-        yield UCTObserver.closed.promise;
-
-        Services.ww.unregisterNotification(UCTObserver);
-        uctWindow = null;
-        UCTObserver = null;
-        SimpleTest.finish();
-      });
-    }
-  ]]></script>
-
-  <body xmlns="http://www.w3.org/1999/xhtml">
-    <p id="display"></p>
-    <div id="content" style="display:none;"></div>
-    <pre id="test"></pre>
-  </body>
-
-  <iframe xmlns="http://www.w3.org/1999/xhtml"
-          id="testframe">
-  </iframe>
-</window>
--- a/toolkit/mozapps/handling/content/dialog.js
+++ b/toolkit/mozapps/handling/content/dialog.js
@@ -26,31 +26,26 @@
  *   This is the nsIURI that we are being brought up for in the first place.
  * window.arguments[9]:
  *   The nsIInterfaceRequestor of the parent window; may be null
  */
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cr = Components.results;
-var Cu = Components.utils;
-
-Cu.import("resource://gre/modules/SharedPromptUtils.jsm");
-
 
 var dialog = {
   //////////////////////////////////////////////////////////////////////////////
   //// Member Variables
 
   _handlerInfo: null,
   _URI: null,
   _itemChoose: null,
   _okButton: null,
   _windowCtxt: null,
-  _buttonDisabled: true,
   
   //////////////////////////////////////////////////////////////////////////////
   //// Methods
 
  /**
   * This function initializes the content of the dialog.
   */
   initialize: function initialize()
@@ -58,16 +53,18 @@ var dialog = {
     this._handlerInfo = window.arguments[7].QueryInterface(Ci.nsIHandlerInfo);
     this._URI         = window.arguments[8].QueryInterface(Ci.nsIURI);
     this._windowCtxt  = window.arguments[9];
     if (this._windowCtxt)
       this._windowCtxt.QueryInterface(Ci.nsIInterfaceRequestor);
     this._itemChoose  = document.getElementById("item-choose");
     this._okButton    = document.documentElement.getButton("accept");
 
+    this.updateOKButton();
+
     var description = {
       image: document.getElementById("description-image"),
       text:  document.getElementById("description-text")
     };
     var options = document.getElementById("item-action-text");
     var checkbox = {
       desc: document.getElementById("remember"),
       text:  document.getElementById("remember-text")
@@ -83,28 +80,16 @@ var dialog = {
     checkbox.text.textContent    = window.arguments[6];
 
     // Hide stuff that needs to be hidden
     if (!checkbox.desc.label)
       checkbox.desc.hidden = true;
 
     // UI is ready, lets populate our list
     this.populateList();
-
-    this._delayHelper = new EnableDelayHelper({
-      disableDialog: () => {
-        this._buttonDisabled = true;
-        this.updateOKButton();
-      },
-      enableDialog: () => {
-        this._buttonDisabled = false;
-        this.updateOKButton();
-      },
-      focusTarget: window
-    });
   },
 
  /**
   * Populates the list that a user can choose from.
   */
   populateList: function populateList()
   {
     var items = document.getElementById("items");
@@ -232,18 +217,17 @@ var dialog = {
     return true;
   },
 
  /**
   * Determines if the OK button should be disabled or not
   */
   updateOKButton: function updateOKButton()
   {
-    this._okButton.disabled = this._itemChoose.selected ||
-                              this._buttonDisabled;
+    this._okButton.disabled = this._itemChoose.selected;
   },
 
  /**
   * Updates the UI based on the checkbox being checked or not.
   */
   onCheck: function onCheck()
   {
     if (document.getElementById("remember").checked)