Bug 1361950 - [WIP] Give month/year selectors visual focus when opened in date picker draft
authorScott Wu <scottcwwu@gmail.com>
Mon, 08 May 2017 17:51:12 +0800
changeset 575329 d753061fd254931d66382c75afb36514f26faac2
parent 575328 5ea00dc3d77a39ee5d984bb20584d2ce6bc7b7a5
child 627894 b3fe321a4cf3a39ebb04730385fe391605d04bec
push id58031
push userbmo:scwwu@mozilla.com
push dateWed, 10 May 2017 08:48:50 +0000
bugs1361950
milestone55.0a1
Bug 1361950 - [WIP] Give month/year selectors visual focus when opened in date picker MozReview-Commit-ID: 4eNnIns4PIm
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/nsIDateTimeInputArea.idl
dom/webidl/HTMLInputElement.webidl
layout/forms/nsDateTimeControlFrame.cpp
layout/forms/nsDateTimeControlFrame.h
toolkit/content/browser-content.js
toolkit/content/widgets/datetimebox.xml
toolkit/content/widgets/datetimepopup.xml
toolkit/modules/DateTimePickerHelper.jsm
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2716,16 +2716,29 @@ HTMLInputElement::SetDateTimePickerState
 
   nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
   if (frame) {
     frame->SetPickerState(aOpen);
   }
 }
 
 void
+HTMLInputElement::SetDateTimePickerFocus(bool aFocus)
+{
+  if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+    return;
+  }
+
+  nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+  if (frame) {
+    frame->SetPickerFocus(aFocus);
+  }
+}
+
+void
 HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue)
 {
   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
     return;
   }
 
   mDateTimeInputBoxValue = new DateTimeValue(aInitialValue);
   nsContentUtils::DispatchChromeEvent(OwnerDoc(),
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -821,16 +821,17 @@ public:
 
   /*
    * The following functions are called from datetime picker to let input box
    * know the current state of the picker or to update the input box on changes.
    */
   void GetDateTimeInputBoxValue(DateTimeValue& aValue);
   void UpdateDateTimeInputBox(const DateTimeValue& aValue);
   void SetDateTimePickerState(bool aOpen);
+  void SetDateTimePickerFocus(bool aFocus);
 
   /*
    * The following functions are called from datetime input box XBL to control
    * and update the picker.
    */
   void OpenDateTimePicker(const DateTimeValue& aInitialValue);
   void UpdateDateTimePicker(const DateTimeValue& aValue);
   void CloseDateTimePicker();
--- a/dom/html/nsIDateTimeInputArea.idl
+++ b/dom/html/nsIDateTimeInputArea.idl
@@ -30,16 +30,21 @@ interface nsIDateTimeInputArea : nsISupp
   void blurInnerTextBox();
 
   /**
    * Set the current state of the picker, true if it's opened, false otherwise.
    */
   void setPickerState(in boolean isOpen);
 
   /**
+   * Set the focus state of the picker, true if it's focused, false otherwise.
+   */
+  void setPickerFocus(in boolean isFocus);
+
+  /**
    * Set the attribute of the inner text boxes. Only "tabindex", "readonly",
    * and "disabled" are allowed.
    */
   void setEditAttribute(in DOMString name, in DOMString value);
 
   /**
    * Remove the attribute of the inner text boxes. Only "tabindex", "readonly",
    * and "disabled" are allowed.
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -248,16 +248,19 @@ partial interface HTMLInputElement {
   DateTimeValue getDateTimeInputBoxValue();
 
   [Pref="dom.forms.datetime", ChromeOnly]
   void updateDateTimeInputBox(optional DateTimeValue value);
 
   [Pref="dom.forms.datetime", ChromeOnly]
   void setDateTimePickerState(boolean open);
 
+  [Pref="dom.forms.datetime", ChromeOnly]
+  void setDateTimePickerFocus(boolean focus);
+
   [Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
   void openDateTimePicker(optional DateTimeValue initialValue);
 
   [Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
   void updateDateTimePicker(optional DateTimeValue value);
 
   [Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
   void closeDateTimePicker();
--- a/layout/forms/nsDateTimeControlFrame.cpp
+++ b/layout/forms/nsDateTimeControlFrame.cpp
@@ -101,16 +101,26 @@ nsDateTimeControlFrame::SetPickerState(b
   nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
     do_QueryInterface(mInputAreaContent);
   if (inputAreaContent) {
     inputAreaContent->SetPickerState(aOpen);
   }
 }
 
 void
+nsDateTimeControlFrame::SetPickerFocus(bool aFocus)
+{
+  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+    do_QueryInterface(mInputAreaContent);
+  if (inputAreaContent) {
+    inputAreaContent->SetPickerFocus(aFocus);
+  }
+}
+
+void
 nsDateTimeControlFrame::HandleFocusEvent()
 {
   nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
     do_QueryInterface(mInputAreaContent);
   if (inputAreaContent) {
     inputAreaContent->FocusInnerTextBox();
   }
 }
--- a/layout/forms/nsDateTimeControlFrame.h
+++ b/layout/forms/nsDateTimeControlFrame.h
@@ -74,16 +74,17 @@ public:
   nsresult AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute,
                             int32_t aModType) override;
 
   void UpdateInputBoxValue();
   void SetValueFromPicker(const DateTimeValue& aValue);
   void HandleFocusEvent();
   void HandleBlurEvent();
   void SetPickerState(bool aOpen);
+  void SetPickerFocus(bool aFocus);
 
 private:
   class SyncDisabledStateEvent;
   friend class SyncDisabledStateEvent;
   class SyncDisabledStateEvent : public mozilla::Runnable
   {
   public:
     explicit SyncDisabledStateEvent(nsDateTimeControlFrame* aFrame)
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -1641,41 +1641,44 @@ let DateTimePickerListener = {
   },
 
   /**
    * Cleanup function called when picker is closed.
    */
   close() {
     this.removeListeners();
     this._inputElement.setDateTimePickerState(false);
+    this._inputElement.setDateTimePickerFocus(false);
     this._inputElement = null;
   },
 
   /**
    * Called after picker is opened to start listening for input box update
    * events.
    */
   addListeners() {
     addEventListener("MozUpdateDateTimePicker", this);
     addEventListener("MozCloseDateTimePicker", this);
     addEventListener("pagehide", this);
 
     addMessageListener("FormDateTime:PickerValueChanged", this);
+    addMessageListener("FormDateTime:PickerFocusChanged", this);
     addMessageListener("FormDateTime:PickerClosed", this);
   },
 
   /**
    * Stop listeneing for events when picker is closed.
    */
   removeListeners() {
     removeEventListener("MozUpdateDateTimePicker", this);
     removeEventListener("MozCloseDateTimePicker", this);
     removeEventListener("pagehide", this);
 
     removeMessageListener("FormDateTime:PickerValueChanged", this);
+    removeMessageListener("FormDateTime:PickerFocusChanged", this);
     removeMessageListener("FormDateTime:PickerClosed", this);
   },
 
   /**
    * Helper function that returns the CSS direction property of the element.
    */
   getComputedDirection(aElement) {
     return aElement.ownerGlobal.getComputedStyle(aElement)
@@ -1702,16 +1705,20 @@ let DateTimePickerListener = {
       case "FormDateTime:PickerClosed": {
         this.close();
         break;
       }
       case "FormDateTime:PickerValueChanged": {
         this._inputElement.updateDateTimeInputBox(aMessage.data);
         break;
       }
+      case "FormDateTime:PickerFocusChanged": {
+        this._inputElement.setDateTimePickerFocus(aMessage.data);
+        break;
+      }
       default:
         break;
     }
   },
 
   /**
    * nsIDOMEventListener, for chrome events sent by the input element and other
    * DOM events.
--- a/toolkit/content/widgets/datetimebox.xml
+++ b/toolkit/content/widgets/datetimebox.xml
@@ -1299,16 +1299,26 @@
         <parameter name="aValue"/>
         <body>
         <![CDATA[
           this.setFieldsFromPicker(aValue);
         ]]>
         </body>
       </method>
 
+      <method name="setPickerFocus">
+        <parameter name="aFocus"/>
+        <body>
+        <![CDATA[
+          this.log("pickerFocus: " + aFocus);
+          this.pickerFocus = aFocus;
+        ]]>
+        </body>
+      </method>
+
       <method name="advanceToNextField">
         <parameter name="aReverse"/>
         <body>
         <![CDATA[
           this.log("advanceToNextField");
 
           let focusedInput = this.mLastFocusedField;
           let next = aReverse ? focusedInput.previousElementSibling
@@ -1608,57 +1618,67 @@
           if (aEvent.target == this.mInputElement && this.mIsPickerOpen) {
             this.mInputElement.closeDateTimePicker();
           }
 
           let target = aEvent.originalTarget;
           target.setAttribute("typeBuffer", "");
           this.setInputValueFromFields();
           this.mInputElement.setFocusState(false);
+          this.pickerFocus = false;
         ]]>
         </body>
       </method>
 
       <method name="onKeyPress">
         <parameter name="aEvent"/>
         <body>
         <![CDATA[
           this.log("onKeyPress key: " + aEvent.key);
 
           switch (aEvent.key) {
             // Close picker on Enter, Escape or Space key.
             case "Enter":
             case "Escape":
             case " ": {
               if (this.mIsPickerOpen) {
+                this.pickerFocus = false;
                 this.mInputElement.closeDateTimePicker();
                 aEvent.preventDefault();
               }
               break;
             }
             case "Backspace": {
               let targetField = aEvent.originalTarget;
               this.clearFieldValue(targetField);
               this.setInputValueFromFields();
               aEvent.preventDefault();
               break;
             }
             case "ArrowRight":
             case "ArrowLeft": {
-              this.advanceToNextField(aEvent.key == "ArrowRight" ? false : true);
+              if (this.pickerFocus) {
+                // Send key to picker
+              } else {
+                this.advanceToNextField(aEvent.key == "ArrowRight" ? false : true);
+              }
               aEvent.preventDefault();
               break;
             }
             case "ArrowUp":
             case "ArrowDown":
             case "PageUp":
             case "PageDown":
             case "Home":
             case "End": {
-              this.handleKeyboardNav(aEvent);
+              if (this.pickerFocus) {
+                // Send key to picker
+              } else {
+                this.handleKeyboardNav(aEvent);
+              }
               aEvent.preventDefault();
               break;
             }
             default: {
               // printable characters
               if (aEvent.keyCode == 0 &&
                   !(aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey)) {
                 this.handleKeypress(aEvent);
--- a/toolkit/content/widgets/datetimepopup.xml
+++ b/toolkit/content/widgets/datetimepopup.xml
@@ -224,16 +224,24 @@
                   day: value.day
                 }
               }));
               break;
             }
           }
         ]]></body>
       </method>
+      <method name="sendPickerFocusChanged">
+        <parameter name="isFocus"/>
+        <body><![CDATA[
+          this.dispatchEvent(new CustomEvent("DateTimePickerFocusChanged", {
+            detail: isFocus,
+          }));
+        ]]></body>
+      </method>
       <method name="getCalendarInfo">
         <parameter name="locale"/>
         <body><![CDATA[
           const calendarInfo = this.mozIntl.getCalendarInfo(locale);
 
           // Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
           // so they need to be mapped to JavaScript convention with 0 as Sunday
           // and 6 as Saturday
--- a/toolkit/modules/DateTimePickerHelper.jsm
+++ b/toolkit/modules/DateTimePickerHelper.jsm
@@ -78,16 +78,20 @@ this.DateTimePickerHelper = {
   // nsIDOMEventListener
   handleEvent(aEvent) {
     debug("handleEvent: " + aEvent.type);
     switch (aEvent.type) {
       case "DateTimePickerValueChanged": {
         this.updateInputBoxValue(aEvent);
         break;
       }
+      case "DateTimePickerFocusChanged": {
+        this.setPickerFocus(aEvent);
+        break;
+      }
       case "popuphidden": {
         let browser = this.weakBrowser ? this.weakBrowser.get() : null;
         if (browser) {
           browser.messageManager.sendAsyncMessage("FormDateTime:PickerClosed");
         }
         this.close();
         break;
       }
@@ -100,16 +104,24 @@ this.DateTimePickerHelper = {
   updateInputBoxValue(aEvent) {
     let browser = this.weakBrowser ? this.weakBrowser.get() : null;
     if (browser) {
       browser.messageManager.sendAsyncMessage(
         "FormDateTime:PickerValueChanged", aEvent.detail);
     }
   },
 
+  setPickerFocus(aEvent) {
+    let browser = this.weakBrowser ? this.weakBrowser.get() : null;
+    if (browser) {
+      browser.messageManager.sendAsyncMessage(
+        "FormDateTime:PickerFocusChanged", aEvent.detail);
+    }
+  },
+
   // Get picker from browser and show it anchored to the input box.
   showPicker: Task.async(function* (aBrowser, aData) {
     let rect = aData.rect;
     let type = aData.type;
     let detail = aData.detail;
 
     this._anchor = aBrowser.ownerGlobal.gBrowser.popupAnchor;
     this._anchor.left = rect.left;
@@ -163,19 +175,21 @@ this.DateTimePickerHelper = {
 
   // Listen to picker's event.
   addPickerListeners() {
     if (!this.picker) {
       return;
     }
     this.picker.addEventListener("popuphidden", this);
     this.picker.addEventListener("DateTimePickerValueChanged", this);
+    this.picker.addEventListener("DateTimePickerFocusChanged", this);
   },
 
   // Stop listening to picker's event.
   removePickerListeners() {
     if (!this.picker) {
       return;
     }
     this.picker.removeEventListener("popuphidden", this);
     this.picker.removeEventListener("DateTimePickerValueChanged", this);
+    this.picker.removeEventListener("DateTimePickerFocusChanged", this);
   },
 };