Bug 545955 - mozmill windows orange bustage fix; assumption is windows tinderbox resolution is limited to 1024x768 or the like. work-around this by collapsing the folder pane so we get enough space. a=bustage-fix
authorAndrew Sutherland <asutherland@asutherland.org>
Thu, 22 Apr 2010 20:07:41 -0700
changeset 5519 2974e8b4fb57419b249c8c64a88a9e0728bf1d7b
parent 5518 9759a8e7417fb65bde3430e1f260ec154f04d5de
child 5520 43670c7cbad1943f897080fc6fbe20ba012f8226
push idunknown
push userunknown
push dateunknown
reviewersbustage-fix
bugs545955
Bug 545955 - mozmill windows orange bustage fix; assumption is windows tinderbox resolution is limited to 1024x768 or the like. work-around this by collapsing the folder pane so we get enough space. a=bustage-fix
mail/base/modules/quickFilterManager.js
mail/test/mozmill/quick-filter-bar/test-display-issues.js
mail/test/mozmill/quick-filter-bar/test-keyboard-interface.js
--- a/mail/base/modules/quickFilterManager.js
+++ b/mail/base/modules/quickFilterManager.js
@@ -163,20 +163,23 @@ QuickFilterState.prototype = {
    *  applied filters, change our constraints.  First press clears the last
    *  added constraint (if any), second press (or if no last constraint) clears
    *  the state entirely.
    *
    * @return true if we relaxed the state, false if there was nothing to relax.
    */
   userHitEscape: function MFS_userHitEscape() {
     if (this._lastFilterAttr) {
-      QuickFilterManager.clearFilterValue(this._lastFilterAttr,
-                                            this.filterValues);
-      this._lastFilterAttr = null;
-      return true;
+      // it's possible the UI state the last attribute has already been cleared,
+      //  in which case we want to fall through...
+      if (QuickFilterManager.clearFilterValue(this._lastFilterAttr,
+                                              this.filterValues)) {
+        this._lastFilterAttr = null;
+        return true;
+      }
     }
 
     return QuickFilterManager.clearAllFilterValues(this.filterValues);
   },
 
   /**
    * Clear the state without going through any undo-ish steps like
    *  |userHitEscape| tries to do.
@@ -361,17 +364,17 @@ let QuickFilterManager = {
    *     undefined was set).  If you have nothing to add, then don't do
    *     anything.  If you do add terms, the first term you add needs to have
    *     the booleanAnd flag set to true.  You may optionally return a listener
    *     that complies with the documentation on QuickFilterSearchListener if
    *     you want to process all of the messages returned by the filter; doing
    *     so is not cheap, so don't do that lightly.  (Tag faceting uses this.)
    * @param {function()} [aFilterDef.getDefaults] Function that returns the
    *     default state for the filter.  If the function is not defined or the
-   *     returned value is === undefined, no state is set.
+   *     returned value is == undefined/null, no state is set.
    * @param {function(aTemplState, aSticky)} [aFilterDef.propagateState] A
    *     function that takes the state from another QuickFilterState instance
    *     for this definition and propagates it to a new state which it returns.
    *     You would use this to keep the 'sticky' bits of state that you want to
    *     persist between folder changes and when new tabs are opened.  The
    *     aSticky argument tells you if the user wants all the filters still
    *     applied or not.  When false, the idea is you might keep things like
    *     which text fields to filter on, but not the text to filter.  When true,
@@ -403,17 +406,17 @@ let QuickFilterManager = {
    *     update the search] as its result.
    * @param {function(aDomNode, aFilterValue, aDoc, aMuxer)}
    *     [aFilterDef.reflectInDOM]
    *     If omitted, we assume the widget referenced by domId has a checked
    *     attribute and assign the filter value coerced to a boolean to the
    *     checked attribute.  Otherwise we call your function and it's up to you
    *     to reflect your state.  aDomNode is the node referred to by domId.
    *     This function will be called when the tab changes, folder changes, or
-   *     if we called postFilterProcess and you returned a value !== undefined.
+   *     if we called postFilterProcess and you returned a value != undefined.
    * @param {function(aState, aViewWrapper, aFiltering)}
    *     [aFilterDef.postFilterProcess]
    *     Invoked after all of the message headers for the view have been
    *     displayed, allowing your code to perform some kind of faceting or other
    *     clever logic.  Return a tuple of [new state, should call reflectInDOM,
    *     should treat as if the user modified the state].  We call this _even
    *     when there is no filter_ applied.  We tell you what's happening via
    *     aFiltering; true means we have applied some terms, false means not.
@@ -453,17 +456,17 @@ let QuickFilterManager = {
     let values = {};
     let sticky = ("sticky" in aTemplValues) ? aTemplValues.sticky : false;
 
     for each (let [, filterDef] in Iterator(this.filterDefs)) {
       if ("propagateState" in filterDef) {
         let curValue = (filterDef.name in aTemplValues) ?
                          aTemplValues[filterDef.name] : undefined;
         let newValue = filterDef.propagateState(curValue, sticky);
-        if (newValue !== undefined)
+        if (newValue != null)
           values[filterDef.name] = newValue;
       }
       // always propagate the value if sticky and there was no handler
       else if (sticky) {
         if (filterDef.name in aTemplValues)
           values[filterDef.name] = aTemplValues[filterDef.name];
       }
     }
@@ -475,17 +478,17 @@ let QuickFilterManager = {
    *
    * @return Thew new filterValues state.
    */
   getDefaultValues: function MFM_getDefaultValues() {
     let values = {};
     for each (let [, filterDef] in Iterator(this.filterDefs)) {
       if ("getDefaults" in filterDef) {
         let newValue = filterDef.getDefaults();
-        if (newValue !== undefined)
+        if (newValue != null)
           values[filterDef.name] = newValue;
       }
     }
     return values;
   },
 
   /**
    * Reset the state of a single filter given the provided values.
@@ -502,17 +505,17 @@ let QuickFilterManager = {
       }
       return false;
     }
 
     let curValue = (aFilterName in aValues) ?
                      aValues[aFilterName] : undefined;
     // Yes, we want to call it to clear its state even if it has no state.
     let [newValue, didClear] = filterDef.clearState(curValue);
-    if (newValue !== undefined)
+    if (newValue != null)
       aValues[aFilterName] = newValue;
     else
       delete aValues[aFilterName];
     return didClear;
   },
 
   /**
    * Reset the state of all filters given the provided values.
@@ -1060,17 +1063,17 @@ let MessageTextFilter = {
   },
   propagateState: function(aOld, aSticky) {
     return {
       text: aSticky ? aOld.text : null,
       states: shallowObjCopy(aOld.states),
     };
   },
   clearState: function(aState) {
-    let hadState = (aState.text && aState.text != "");
+    let hadState = Boolean(aState.text);
     aState.text = null;
     return [aState, hadState];
   },
 
   /**
    * We need to create and bind our expando-bar toggle buttons.  We also need to
    *  add a special down keypress handler that escapes the textbox into the
    *  thread pane.
@@ -1303,16 +1306,32 @@ ResultsLabelFolderDisplayListener.protot
 /**
  * The results label says whether there were any matches and, if so, how many.
  */
 QuickFilterManager.defineFilter({
   name: "results",
   domId: "qfb-results-label",
   appendTerms: function(aTermCreator, aTerms, aFilterValue) {
   },
+
+  /**
+   * Our state is meaningless; we implement this to avoid clearState ever
+   *  thinking we were a facet.
+   */
+  clearState: function(aState) {
+    return [null, false];
+  },
+
+  /**
+   * We never have any state to propagate!
+   */
+  propagateState: function(aOld, aSticky) {
+    return null;
+  },
+
   /**
    * Hook us up as a folder display listener so we can get information on when
    * the counts change.
    */
   domBindExtra: function MessageTextFilter_domBind(aDocument, aMuxer, aNode) {
     aDocument.defaultView.FolderDisplayListenerManager.registerListener(
       new ResultsLabelFolderDisplayListener(aMuxer));
   },
--- a/mail/test/mozmill/quick-filter-bar/test-display-issues.js
+++ b/mail/test/mozmill/quick-filter-bar/test-display-issues.js
@@ -71,16 +71,26 @@ function test_buttons_collapse_and_expan
   assert_quick_filter_bar_visible(true); // precondition
 
   try {
     let qfbCollapsy = mc.e("quick-filter-bar-collapsible-buttons");
     let qfbExemplarButton = mc.e("qfb-unread"); // (arbitrary labeled button)
     let qfbExemplarLabel = mc.window
                              .document.getAnonymousNodes(qfbExemplarButton)[1];
 
+    function logState(aWhen) {
+      dump("\n\n*********** " + aWhen + "\n");
+      dump("Current window location: " + mc.window.screenX + ", " +
+           mc.window.screenY + "\n");
+      dump("Current window dimensions: " + mc.window.outerWidth + ", " +
+           mc.window.outerHeight + "\n");
+      dump("Collapsy bar width: " + qfbCollapsy.clientWidth + "\n");
+      dump("***********\n\n");
+    }
+
     function assertCollapsed() {
       // The bar should be shrunken and the button should be the same size as its
       // image!
       if (qfbCollapsy.getAttribute("shrink") != "true")
         throw new Error("The collapsy bar should be shrunk!");
       if (qfbExemplarLabel.clientWidth != 0)
         throw new Error("The exemplar label should be collapsed!");
     }
@@ -88,37 +98,42 @@ function test_buttons_collapse_and_expan
       // The bar should not be shrunken and the button should be smaller than its
       // label!
       if (qfbCollapsy.hasAttribute("shrink"))
         throw new Error("The collapsy bar should not be shrunk!");
       if (qfbExemplarLabel.clientWidth == 0)
         throw new Error("The exemplar label should not be collapsed!");
     }
 
+    logState("entry");
+
     // -- GIANT!
     mc.window.resizeTo(1200, 600);
     // Right, so resizeTo caps us at the display size limit, so we may end up
     // smaller than we want.  So let's turn off the folder pane too.
-    mc.e("folderpane_splitter").collapsed = true;
+    mc.e("folderpane_splitter").setAttribute("state", "collapsed");
     // spin the event loop once
     mc.sleep(0);
+    logState("giant");
     assertExpanded();
 
     // -- tiny.
-    mc.e("folderpane_splitter").collapsed = false;
+    mc.e("folderpane_splitter").setAttribute("state", "open");
     mc.window.resizeTo(600, 600);
     // spin the event loop once
     mc.sleep(0);
+    logState("tiny");
     assertCollapsed();
 
     // -- GIANT again!
     mc.window.resizeTo(1200, 600);
-    mc.e("folderpane_splitter").collapsed = true;
+    mc.e("folderpane_splitter").setAttribute("state", "collapsed");
     // spin the event loop once
     mc.sleep(0);
+    logState("giant again!");
     assertExpanded();
   }
   finally {
     // restore window to nominal dimensions; saving was not working out
     mc.window.resizeTo(1024, 768);
-    mc.e("folderpane_splitter").collapsed = false;
+    mc.e("folderpane_splitter").setAttribute("state", "open");
   }
 }
--- a/mail/test/mozmill/quick-filter-bar/test-keyboard-interface.js
+++ b/mail/test/mozmill/quick-filter-bar/test-keyboard-interface.js
@@ -44,17 +44,16 @@
 var MODULE_NAME = 'test-keyboard-interface';
 
 const RELATIVE_ROOT = '../shared-modules';
 
 var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers',
                        'quick-filter-bar-helper'];
 
 var folder;
-var setUnstarred, setStarred;
 
 function setupModule(module) {
   let fdh = collector.getModule('folder-display-helpers');
   fdh.installInto(module);
   let wh = collector.getModule('window-helpers');
   wh.installInto(module);
   let qfb = collector.getModule('quick-filter-bar-helper');
   qfb.installInto(module);
@@ -107,16 +106,34 @@ function test_escape_rules() {
 
   // 1) focus in the thread pane
   mc.e("threadTree").focus();
   legwork();
 
   // 2) focus in the text box
   mc.e("qfb-qs-textbox").focus();
   legwork();
+
+  // 3) focus in the text box and pretend to type stuff...
+  mc.e("qfb-qs-textbox").focus();
+  set_filter_text("qxqxqxqx");
+
+  // Escape should clear the text constraint but the bar should still be
+  //  visible.  The trick here is that escape is clearing the text widget
+  //  and is not falling through to the cmd_stop case so we end up with a
+  //  situation where the _lastFilterAttr is the textbox but the textbox
+  //  does not actually have any active filter.
+  mc.keypress(null, "VK_ESCAPE", {});
+  assert_quick_filter_bar_visible(true);
+  assert_constraints_expressed({});
+  assert_filter_text("");
+
+  // Next escape should close the box
+  mc.keypress(null, "VK_ESCAPE", {});
+  assert_quick_filter_bar_visible(false);
 }
 
 /**
  * It's fairly important that the gloda search widget eats escape when people
  * press escape in there.  Because gloda is disabled by default, we need to
  * viciously uncollapse it ourselves and then cleanup afterwards...
  */
 function test_escape_does_not_reach_us_from_gloda_search() {