Bug 1456833 - Remove nsIDateTimeInputArea interface draft
authorTimothy Guan-tin Chien <timdream@gmail.com>
Fri, 04 May 2018 13:06:05 -0700
changeset 795014 39462283aa177d29b0ff3fad07d6f8fdeb275909
parent 793238 76aad17f5b50a1b969eec8cbb5aa9875555640bc
push id109831
push usertimdream@gmail.com
push dateMon, 14 May 2018 20:55:23 +0000
bugs1456833
milestone62.0a1
Bug 1456833 - Remove nsIDateTimeInputArea interface This patch removes nsIDateTimeInputArea interface, implemented by the datetime bindings, with events dispatched on <datetimebox> or the input. It also removes two methods on HTMLInputElement that serves no purpose other than allow browser-content.js to talk to the datetime bindings (which are both in JavaScript). MozReview-Commit-ID: 9jBpRcVWvHc
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/moz.build
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
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2214,42 +2214,16 @@ void HTMLInputElement::GetDateTimeInputB
   if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
     return;
   }
 
   aValue = *mDateTimeInputBoxValue;
 }
 
 void
-HTMLInputElement::UpdateDateTimeInputBox(const DateTimeValue& aValue)
-{
-  if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
-    return;
-  }
-
-  nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
-  if (frame) {
-    frame->SetValueFromPicker(aValue);
-  }
-}
-
-void
-HTMLInputElement::SetDateTimePickerState(bool aOpen)
-{
-  if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
-    return;
-  }
-
-  nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
-  if (frame) {
-    frame->SetPickerState(aOpen);
-  }
-}
-
-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
@@ -855,18 +855,16 @@ public:
   void MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles);
   void MozSetDirectory(const nsAString& aDirectoryPath, ErrorResult& aRv);
 
   /*
    * 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);
 
   /*
    * 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/moz.build
+++ b/dom/html/moz.build
@@ -17,17 +17,16 @@ MOCHITEST_MANIFESTS += [
 MOCHITEST_CHROME_MANIFESTS += [
     'test/chrome.ini',
     'test/forms/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 XPIDL_SOURCES += [
-    'nsIDateTimeInputArea.idl',
     'nsIFormSubmitObserver.idl',
     'nsIImageDocument.idl',
     'nsIMenuBuilder.idl',
 ]
 
 XPIDL_MODULE = 'content_html'
 
 EXPORTS += [
deleted file mode 100644
--- a/dom/html/nsIDateTimeInputArea.idl
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * 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/. */
-
-#include "nsISupports.idl"
-
-[scriptable, uuid(465c0cc3-24cb-48ce-af1a-b18402326b05)]
-interface nsIDateTimeInputArea : nsISupports
-{
-  /**
-   * Called from DOM/Layout when input element value has changed.
-   */
-  void notifyInputElementValueChanged();
-
-  /**
-   * Called from DOM/Layout when input element min, max or step attribute has
-   * changed.
-   */
-  void notifyMinMaxStepAttrChanged();
-
-  /**
-   * Called when date/time picker value has changed.
-   */
-  void setValueFromPicker(in jsval value);
-
-  /**
-   * Called from DOM/Layout to set focus on inner text box.
-   */
-  void focusInnerTextBox();
-
-  /**
-   * Called from DOM/Layout to blur inner text box.
-   */
-  void blurInnerTextBox();
-
-  /**
-   * Called from DOM/Layout to know whether the current entered value is valid.
-   */
-  boolean hasBadInput();
-
-  /**
-   * Set the current state of the picker, true if it's opened, false otherwise.
-   */
-  void setPickerState(in boolean isOpen);
-
-  /**
-   * 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.
-   */
-  void removeEditAttribute(in DOMString name);
-};
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -236,22 +236,16 @@ dictionary DateTimeValue {
   long month;
   long day;
 };
 
 partial interface HTMLInputElement {
   [Pref="dom.forms.datetime", ChromeOnly]
   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,
    BinaryName="getMinimumAsDouble"]
   double getMinimum();
 
   [Pref="dom.forms.datetime", ChromeOnly,
    BinaryName="getMaximumAsDouble"]
   double getMaximum();
 
--- a/layout/forms/nsDateTimeControlFrame.cpp
+++ b/layout/forms/nsDateTimeControlFrame.cpp
@@ -13,18 +13,18 @@
 
 #include "nsContentUtils.h"
 #include "nsCheckboxRadioFrame.h"
 #include "nsGkAtoms.h"
 #include "nsContentUtils.h"
 #include "nsContentCreatorFunctions.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/MutationEventBinding.h"
+#include "nsDOMTokenList.h"
 #include "nsNodeInfoManager.h"
-#include "nsIDateTimeInputArea.h"
 #include "nsIObserverService.h"
 #include "jsapi.h"
 #include "nsJSUtils.h"
 #include "nsThreadUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
@@ -48,109 +48,104 @@ nsDateTimeControlFrame::nsDateTimeContro
 
 void
 nsDateTimeControlFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
 {
   aPostDestroyData.AddAnonymousContent(mInputAreaContent.forget());
   nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
 }
 
+class DispatchDateTimeEvent : public Runnable
+{
+public:
+  explicit DispatchDateTimeEvent(nsIContent* aContent, nsString aEventName)
+    : mozilla::Runnable("DispatchDateTimeEvent")
+    , mContent(aContent)
+    , mEventName(aEventName)
+  {
+  }
+  NS_IMETHOD Run() override {
+    nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent,
+                                         mEventName,
+                                         false, false);
+    return NS_OK;
+  }
+  nsCOMPtr<nsIContent> mContent;
+  nsString mEventName;
+};
+
 void
 nsDateTimeControlFrame::OnValueChanged()
 {
-  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-    do_QueryInterface(mInputAreaContent);
-  if (inputAreaContent) {
-    inputAreaContent->NotifyInputElementValueChanged();
-  }
+  RefPtr<Runnable> event =
+    new DispatchDateTimeEvent(mInputAreaContent,
+                              NS_LITERAL_STRING("MozDateTimeValueChanged"));
+  nsContentUtils::AddScriptRunner(event);
 }
 
 void
 nsDateTimeControlFrame::OnMinMaxStepAttrChanged()
 {
-  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-    do_QueryInterface(mInputAreaContent);
-  if (inputAreaContent) {
-    inputAreaContent->NotifyMinMaxStepAttrChanged();
-  }
-}
-
-void
-nsDateTimeControlFrame::SetValueFromPicker(const DateTimeValue& aValue)
-{
-  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-    do_QueryInterface(mInputAreaContent);
-  if (inputAreaContent) {
-    AutoJSAPI api;
-    if (!api.Init(mContent->OwnerDoc()->GetScopeObject())) {
-      return;
-    }
-
-    JSObject* wrapper = mContent->GetWrapper();
-    if (!wrapper) {
-      return;
-    }
-
-    JSObject* scope = xpc::GetXBLScope(api.cx(), wrapper);
-    AutoJSAPI jsapi;
-    if (!scope || !jsapi.Init(scope)) {
-      return;
-    }
-
-    JS::Rooted<JS::Value> jsValue(jsapi.cx());
-    if (!ToJSValue(jsapi.cx(), aValue, &jsValue)) {
-      return;
-    }
-
-    inputAreaContent->SetValueFromPicker(jsValue);
-  }
-}
-
-void
-nsDateTimeControlFrame::SetPickerState(bool aOpen)
-{
-  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-    do_QueryInterface(mInputAreaContent);
-  if (inputAreaContent) {
-    inputAreaContent->SetPickerState(aOpen);
-  }
+  RefPtr<Runnable> event =
+    new DispatchDateTimeEvent(mInputAreaContent,
+                              NS_LITERAL_STRING("MozNotifyMinMaxStepAttrChanged"));
+  nsContentUtils::AddScriptRunner(event);
 }
 
 void
 nsDateTimeControlFrame::HandleFocusEvent()
 {
-  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-    do_QueryInterface(mInputAreaContent);
-  if (inputAreaContent) {
-    inputAreaContent->FocusInnerTextBox();
-  }
+  RefPtr<Runnable> event =
+    new DispatchDateTimeEvent(mInputAreaContent,
+                              NS_LITERAL_STRING("MozFocusInnerTextBox"));
+  nsContentUtils::AddScriptRunner(event);
 }
 
 void
 nsDateTimeControlFrame::HandleBlurEvent()
 {
-  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-    do_QueryInterface(mInputAreaContent);
-  if (inputAreaContent) {
-    inputAreaContent->BlurInnerTextBox();
-  }
+  RefPtr<Runnable> event =
+    new DispatchDateTimeEvent(mInputAreaContent,
+                              NS_LITERAL_STRING("MozBlurInnerTextBox"));
+  nsContentUtils::AddScriptRunner(event);
 }
 
 bool
 nsDateTimeControlFrame::HasBadInput()
 {
-  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-    do_QueryInterface(mInputAreaContent);
-
-  bool result = false;
-  if (inputAreaContent) {
-    inputAreaContent->HasBadInput(&result);
+  // Incomplete field does not imply bad input.
+  nsCOMPtr<nsINodeList> nodeList =
+    mInputAreaContent->GetComposedDoc()->GetAnonymousElementByAttribute(
+        mInputAreaContent,
+        nsGkAtoms::anonid,
+        NS_LITERAL_STRING("edit-wrapper"))->ChildNodes();
+  uint32_t length = nodeList->Length();
+  for (uint32_t i = 0; i < length; i++) {
+    if (!nodeList->Item(i)->IsElement()) {
+      continue;
+    }
+    Element* element = nodeList->Item(i)->AsElement();
+    if (element->ClassList()->Contains(NS_LITERAL_STRING("datetime-edit-field"))) {
+      nsAutoString value;
+      element->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
+      if (value.IsEmpty()) {
+        return false;
+      }
+    }
   }
 
-  return result;
+  // All fields are available but input element's value is empty implies
+  // it has been sanitized.
+  nsAutoString value;
+  HTMLInputElement::FromNode(mContent)->GetValue(value, CallerType::System);
+  if (value.IsEmpty()) {
+    return true;
+  }
+
+  return false;
 }
 
 nscoord
 nsDateTimeControlFrame::GetMinISize(gfxContext* aRenderingContext)
 {
   nscoord result;
   DISPLAY_MIN_WIDTH(this, result);
 
@@ -333,69 +328,42 @@ nsDateTimeControlFrame::CreateAnonymousC
   RefPtr<NodeInfo> nodeInfo =
     nodeInfoManager->GetNodeInfo(nsGkAtoms::datetimebox, nullptr,
                                  kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
   NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
 
   NS_TrustedNewXULElement(getter_AddRefs(mInputAreaContent), nodeInfo.forget());
   aElements.AppendElement(mInputAreaContent);
 
-  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-    do_QueryInterface(mInputAreaContent);
-  if (inputAreaContent) {
-    // Propogate our tabindex.
-    nsAutoString tabIndexStr;
-    if (mContent->AsElement()->GetAttr(kNameSpaceID_None,
-                                       nsGkAtoms::tabindex,
-                                       tabIndexStr)) {
-      inputAreaContent->SetEditAttribute(NS_LITERAL_STRING("tabindex"),
-                                         tabIndexStr);
-    }
-
-    // Propagate our readonly state.
-    nsAutoString readonly;
-    if (mContent->AsElement()->GetAttr(kNameSpaceID_None,
-                                       nsGkAtoms::readonly,
-                                       readonly)) {
-      inputAreaContent->SetEditAttribute(NS_LITERAL_STRING("readonly"),
-                                         readonly);
-    }
-
-    SyncDisabledState();
-  }
-
   return NS_OK;
 }
 
 void
 nsDateTimeControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
                                                  uint32_t aFilter)
 {
   if (mInputAreaContent) {
     aElements.AppendElement(mInputAreaContent);
   }
 }
 
 void
 nsDateTimeControlFrame::SyncDisabledState()
 {
-  NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
-  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-    do_QueryInterface(mInputAreaContent);
-  if (!inputAreaContent) {
-    return;
+  EventStates eventStates = mContent->AsElement()->State();
+  if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
+    mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, EmptyString(), true);
+  } else {
+    mInputAreaContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
   }
 
-  EventStates eventStates = mContent->AsElement()->State();
-  if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
-    inputAreaContent->SetEditAttribute(NS_LITERAL_STRING("disabled"),
-                                       EmptyString());
-  } else {
-    inputAreaContent->RemoveEditAttribute(NS_LITERAL_STRING("disabled"));
-  }
+  RefPtr<Runnable> event =
+    new DispatchDateTimeEvent(mInputAreaContent,
+                              NS_LITERAL_STRING("MozDateTimeAttributeChanged"));
+  nsContentUtils::AddScriptRunner(event);
 }
 
 nsresult
 nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID,
                                          nsAtom* aAttribute,
                                          int32_t aModType)
 {
   NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
@@ -406,49 +374,34 @@ nsDateTimeControlFrame::AttributeChanged
         aAttribute == nsGkAtoms::readonly ||
         aAttribute == nsGkAtoms::tabindex) {
       MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
       auto contentAsInputElem = static_cast<dom::HTMLInputElement*>(GetContent());
       // If script changed the <input>'s type before setting these attributes
       // then we don't need to do anything since we are going to be reframed.
       if (contentAsInputElem->ControlType() == NS_FORM_INPUT_TIME ||
           contentAsInputElem->ControlType() == NS_FORM_INPUT_DATE) {
-        nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-          do_QueryInterface(mInputAreaContent);
         if (aAttribute == nsGkAtoms::value) {
-          if (inputAreaContent) {
-            nsContentUtils::AddScriptRunner(NewRunnableMethod(
-              "nsIDateTimeInputArea::NotifyInputElementValueChanged",
-              inputAreaContent,
-              &nsIDateTimeInputArea::NotifyInputElementValueChanged));
-          }
+          RefPtr<Runnable> event =
+            new DispatchDateTimeEvent(mInputAreaContent,
+                                      NS_LITERAL_STRING("MozDateTimeValueChanged"));
+          nsContentUtils::AddScriptRunner(event);
         } else {
-          if (aModType == MutationEventBinding::REMOVAL) {
-            if (inputAreaContent) {
-              nsAtomString name(aAttribute);
-              inputAreaContent->RemoveEditAttribute(name);
-            }
-          } else {
-            MOZ_ASSERT(aModType == MutationEventBinding::ADDITION ||
-                       aModType == MutationEventBinding::MODIFICATION);
-            if (inputAreaContent) {
-              nsAtomString name(aAttribute);
-              nsAutoString value;
-              contentAsInputElem->GetAttr(aNameSpaceID, aAttribute, value);
-              inputAreaContent->SetEditAttribute(name, value);
-            }
-          }
+          RefPtr<Runnable> event =
+            new DispatchDateTimeEvent(mInputAreaContent,
+                                      NS_LITERAL_STRING("MozDateTimeAttributeChanged"));
+          nsContentUtils::AddScriptRunner(event);
         }
       }
     }
   }
 
   return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
                                             aModType);
 }
 
 void
 nsDateTimeControlFrame::ContentStatesChanged(EventStates aStates)
 {
   if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
-    nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
+    SyncDisabledState();
   }
 }
--- a/layout/forms/nsDateTimeControlFrame.h
+++ b/layout/forms/nsDateTimeControlFrame.h
@@ -71,47 +71,21 @@ public:
   void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
                                 uint32_t aFilter) override;
 
   nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
                             int32_t aModType) override;
 
   void OnValueChanged();
   void OnMinMaxStepAttrChanged();
-  void SetValueFromPicker(const DateTimeValue& aValue);
   void HandleFocusEvent();
   void HandleBlurEvent();
-  void SetPickerState(bool aOpen);
   bool HasBadInput();
 
 private:
-  class SyncDisabledStateEvent;
-  friend class SyncDisabledStateEvent;
-  class SyncDisabledStateEvent : public mozilla::Runnable
-  {
-  public:
-    explicit SyncDisabledStateEvent(nsDateTimeControlFrame* aFrame)
-      : mozilla::Runnable("nsDateTimeControlFrame::SyncDisabledStateEvent")
-      , mFrame(aFrame)
-    {}
-
-    NS_IMETHOD Run() override
-    {
-      nsDateTimeControlFrame* frame =
-        static_cast<nsDateTimeControlFrame*>(mFrame.GetFrame());
-      NS_ENSURE_STATE(frame);
-
-      frame->SyncDisabledState();
-      return NS_OK;
-    }
-
-  private:
-    WeakFrame mFrame;
-  };
-
   /**
    * Sync the disabled state of the anonymous children up with our content's.
    */
   void SyncDisabledState();
 
   // Anonymous child which is bound via XBL to an element that wraps the input
   // area and reset button.
   RefPtr<mozilla::dom::Element> mInputAreaContent;
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -1857,17 +1857,20 @@ let DateTimePickerListener = {
     this._inputElement = null;
   },
 
   /**
    * Cleanup function called when picker is closed.
    */
   close() {
     this.removeListeners();
-    this._inputElement.setDateTimePickerState(false);
+    let win = this._inputElement.ownerGlobal;
+    this._inputElement.dispatchEvent(
+      new win.CustomEvent("MozSetDateTimePickerState",
+        { detail: false }, win));
     this._inputElement = null;
   },
 
   /**
    * Called after picker is opened to start listening for input box update
    * events.
    */
   addListeners() {
@@ -1916,17 +1919,21 @@ let DateTimePickerListener = {
    */
   receiveMessage(aMessage) {
     switch (aMessage.name) {
       case "FormDateTime:PickerClosed": {
         this.close();
         break;
       }
       case "FormDateTime:PickerValueChanged": {
-        this._inputElement.updateDateTimeInputBox(aMessage.data);
+        let win = this._inputElement.ownerGlobal;
+
+        this._inputElement.dispatchEvent(
+          new win.CustomEvent("MozPickerValueChanged",
+            { detail: Cu.cloneInto(aMessage.data, win) }, win));
         break;
       }
       default:
         break;
     }
   },
 
   /**
@@ -1945,17 +1952,21 @@ let DateTimePickerListener = {
         if (this._inputElement) {
           // This happens when we're trying to open a picker when another picker
           // is still open. We ignore this request to let the first picker
           // close gracefully.
           return;
         }
 
         this._inputElement = aEvent.originalTarget;
-        this._inputElement.setDateTimePickerState(true);
+
+        let win = this._inputElement.ownerGlobal;
+        this._inputElement.dispatchEvent(
+          new win.CustomEvent("MozSetDateTimePickerState",
+            { detail: true }, win));
         this.addListeners();
 
         let value = this._inputElement.getDateTimeInputBoxValue();
         sendAsyncMessage("FormDateTime:OpenPicker", {
           rect: this.getBoundingContentRect(this._inputElement),
           dir: this.getComputedDirection(this._inputElement),
           type: this._inputElement.type,
           detail: {
--- a/toolkit/content/widgets/datetimebox.xml
+++ b/toolkit/content/widgets/datetimebox.xml
@@ -46,16 +46,17 @@
         this.mMaxYear = 275760;
         this.mMonthDayLength = 2;
         this.mYearLength = 4;
         this.mMonthPageUpDownInterval = 3;
         this.mDayPageUpDownInterval = 7;
         this.mYearPageUpDownInterval = 10;
 
         this.buildEditFields();
+        this.updateEditAttribute();
 
         if (this.mInputElement.value) {
           this.setFieldsFromInputValue();
         }
       ]]>
       </constructor>
 
       <method name="buildEditFields">
@@ -382,17 +383,17 @@
 
             if (value < min) {
               value = min;
             } else if (value > max) {
               value = max;
             }
           }
 
-          aField.setAttribute("rawValue", value);
+          aField.setAttribute("value", value);
 
           // Display formatted value based on locale.
           let minDigits = aField.getAttribute("mindigits");
           let formatted = value.toLocaleString(this.mLocales, {
             minimumIntegerDigits: minDigits,
             useGrouping: false
           });
 
@@ -1079,17 +1080,17 @@
               } else {
                 value = (value > this.mMaxHour) ? value % this.mMaxHour : value;
               }
             } else if (value > this.mMaxHour) {
               value = this.mMaxHour;
             }
           }
 
-          aField.setAttribute("rawValue", value);
+          aField.setAttribute("value", value);
 
           let minDigits = aField.getAttribute("mindigits");
           let formatted = value.toLocaleString(this.mLocales, {
             minimumIntegerDigits: minDigits,
             useGrouping: false
           });
 
           aField.textContent = formatted;
@@ -1119,16 +1120,17 @@
         <parameter name="aValue"/>
         <body>
         <![CDATA[
           if (!this.hasDayPeriodField()) {
             return;
           }
 
           this.mDayPeriodField.textContent = aValue;
+          this.mDayPeriodField.setAttribute("value", aValue);
           this.updateResetButtonVisibility();
         ]]>
         </body>
       </method>
 
       <method name="isAnyFieldAvailable">
         <parameter name="aForPicker"/>
         <body>
@@ -1214,17 +1216,17 @@
              - elements here -->
         </html:span>
 
         <html:button class="datetime-reset-button" anonid="reset-button"
                      tabindex="-1" xbl:inherits="disabled" aria-label="&datetime.reset.label;"/>
       </html:div>
     </content>
 
-    <implementation implements="nsIDateTimeInputArea">
+    <implementation>
       <constructor>
       <![CDATA[
         this.DEBUG = false;
         this.mInputElement = this.parentNode;
         this.mLocales = window.getRegionalPrefsLocales();
 
         this.mIsRTL = false;
         let intlUtils = window.intlUtils;
@@ -1257,16 +1259,24 @@
           capture: true,
           mozSystemGroup: true
         }, false);
         // This is to open the picker when input element is clicked (this
         // includes padding area).
         this.mInputElement.addEventListener("click", this,
                                             { mozSystemGroup: true },
                                             false);
+
+        this.mInputElement.addEventListener("MozPickerValueChanged", this,
+                                            { mozSystemGroup: true },
+                                            false);
+
+        this.mInputElement.addEventListener("MozSetDateTimePickerState", this,
+                                            { mozSystemGroup: true },
+                                            false);
       ]]>
       </constructor>
 
       <destructor>
       <![CDATA[
         this.EVENTS.forEach((eventName) => {
           this.removeEventListener(eventName, this, { mozSystemGroup: true });
         });
@@ -1279,17 +1289,20 @@
 
         this.mInputElement = null;
       ]]>
       </destructor>
 
       <property name="EVENTS" readonly="true">
         <getter>
         <![CDATA[
-          return ["focus", "blur", "copy", "cut", "paste", "mousedown"];
+          return ["focus", "blur", "copy", "cut", "paste", "mousedown",
+                  "MozDateTimeValueChanged", "MozNotifyMinMaxStepAttrChanged",
+                  "MozFocusInnerTextBox", "MozBlurInnerTextBox",
+                  "MozDateTimeAttributeChanged"];
         ]]>
         </getter>
       </property>
 
       <method name="log">
         <parameter name="aMsg"/>
         <body>
         <![CDATA[
@@ -1321,30 +1334,33 @@
 
           field.setAttribute("readonly", this.mInputElement.readOnly);
           field.setAttribute("disabled", this.mInputElement.disabled);
           // Set property as well for convenience.
           field.disabled = this.mInputElement.disabled;
           field.readOnly = this.mInputElement.readOnly;
           field.setAttribute("aria-label", aLabel);
 
+          // Used to store the non-formatted value, cleared when value is
+          // cleared.
+          // nsDateTimeControlFrame::HasBadInput() will read this to decide
+          // if the input has value.
+          field.setAttribute("value", "");
+
           if (aIsNumeric) {
             field.classList.add("numeric");
             // Maximum value allowed.
             field.setAttribute("min", aMinValue);
             // Minumim value allowed.
             field.setAttribute("max", aMaxValue);
             // Interval when pressing pageUp/pageDown key.
             field.setAttribute("pginterval", aPageUpDownInterval);
             // Used to store what the user has already typed in the field,
             // cleared when value is cleared and when field is blurred.
             field.setAttribute("typeBuffer", "");
-            // Used to store the non-formatted number, clered when value is
-            // cleared.
-            field.setAttribute("rawValue", "");
             // Minimum digits to display, padded with leading 0s.
             field.setAttribute("mindigits", aMinDigits);
             // Maximum length for the field, will be advance to the next field
             // automatically if exceeded.
             field.setAttribute("maxlength", aMaxLength);
             // Set spinbutton ARIA role
             field.setAttribute("role", "spinbutton");
 
@@ -1386,16 +1402,17 @@
           let editRoot =
             document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
 
           for (let child = editRoot.firstChild; child; child = child.nextSibling) {
             if ((child instanceof HTMLSpanElement) &&
                 child.classList.contains("datetime-edit-field")) {
               this.mLastFocusedField = child;
               child.focus();
+              this.log("focused");
               break;
             }
           }
         ]]>
         </body>
       </method>
 
       <method name="blurInnerTextBox">
@@ -1441,35 +1458,16 @@
         <parameter name="aValue"/>
         <body>
         <![CDATA[
           this.setFieldsFromPicker(aValue);
         ]]>
         </body>
       </method>
 
-      <method name="hasBadInput">
-        <body>
-        <![CDATA[
-          // Incomplete field does not imply bad input.
-          if (this.isAnyFieldEmpty()) {
-            return false;
-          }
-
-          // All fields are available but input element's value is empty implies
-          // it has been sanitized.
-          if (!this.mInputElement.value) {
-            return true;
-          }
-
-          return false;
-        ]]>
-        </body>
-      </method>
-
       <method name="advanceToNextField">
         <parameter name="aReverse"/>
         <body>
         <![CDATA[
           this.log("advanceToNextField");
 
           let focusedInput = this.mLastFocusedField;
           let next = aReverse ? focusedInput.previousElementSibling
@@ -1497,82 +1495,39 @@
         <body>
         <![CDATA[
           this.log("picker is now " + (aIsOpen ? "opened" : "closed"));
           this.mIsPickerOpen = aIsOpen;
         ]]>
         </body>
       </method>
 
-      <method name="setEditAttribute">
-        <parameter name="aName"/>
-        <parameter name="aValue"/>
+      <method name="updateEditAttribute">
         <body>
         <![CDATA[
-          this.log("setAttribute: " + aName + "=" + aValue);
-
-          if (aName != "tabindex" && aName != "disabled" &&
-              aName != "readonly") {
-            return;
-          }
+          this.log("updateEditAttribute");
 
           let editRoot =
             document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
 
           for (let child = editRoot.firstChild; child; child = child.nextSibling) {
             if ((child instanceof HTMLSpanElement) &&
                 child.classList.contains("datetime-edit-field")) {
-
-              switch (aName) {
-                case "tabindex":
-                  child.setAttribute(aName, aValue);
-                  break;
-                case "disabled": {
-                  let value = this.mInputElement.disabled;
-                  child.setAttribute("disabled", value);
-                  child.disabled = value;
-                  break;
-                }
-                case "readonly": {
-                  let value = this.mInputElement.readOnly;
-                  child.setAttribute("readonly", value);
-                  child.readOnly = value;
-                  break;
-                }
-              }
-            }
-          }
-        ]]>
-        </body>
-      </method>
+              // "disabled" and "readonly" must be set as attributes because they
+              // are not defined properties of HTMLSpanElement, and the stylesheet
+              // checks the literal string attribute values.
+              child.setAttribute("disabled", this.mInputElement.disabled);
+              child.setAttribute("readonly", this.mInputElement.readOnly);
 
-      <method name="removeEditAttribute">
-        <parameter name="aName"/>
-        <body>
-        <![CDATA[
-          this.log("removeAttribute: " + aName);
-
-          if (aName != "tabindex" && aName != "disabled" &&
-              aName != "readonly") {
-            return;
-          }
+              // Set property as well for convenience.
+              child.disabled = this.mInputElement.disabled;
+              child.readOnly = this.mInputElement.readOnly;
 
-          let editRoot =
-            document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
-
-          for (let child = editRoot.firstChild; child; child = child.nextSibling) {
-            if ((child instanceof HTMLSpanElement) &&
-                child.classList.contains("datetime-edit-field")) {
-              child.removeAttribute(aName);
-              // Update property as well.
-              if (aName == "readonly") {
-                child.readOnly = false;
-              } else if (aName == "disabled") {
-                child.disabled = false;
-              }
+              // tabIndex works on all elements
+              child.tabIndex = this.mInputElement.tabIndex;
             }
           }
         ]]>
         </body>
       </method>
 
       <method name="isEmpty">
         <parameter name="aValue"/>
@@ -1584,31 +1539,31 @@
       <method name="getFieldValue">
         <parameter name="aField"/>
         <body>
         <![CDATA[
           if (!aField || !aField.classList.contains("numeric")) {
             return undefined;
           }
 
-          let value = aField.getAttribute("rawValue");
+          let value = aField.getAttribute("value");
           // Avoid returning 0 when field is empty.
           return (this.isEmpty(value) ? undefined : Number(value));
         ]]>
         </body>
       </method>
 
       <method name="clearFieldValue">
         <parameter name="aField"/>
         <body>
         <![CDATA[
           aField.textContent = aField.placeholder;
+          aField.setAttribute("value", "");
           if (aField.classList.contains("numeric")) {
             aField.setAttribute("typeBuffer", "");
-            aField.setAttribute("rawValue", "");
           }
           this.updateResetButtonVisibility();
         ]]>
         </body>
       </method>
 
       <method name="setFieldValue">
         <body>
@@ -1692,16 +1647,44 @@
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body>
         <![CDATA[
           this.log("handleEvent: " + aEvent.type);
 
           switch (aEvent.type) {
+            case "MozDateTimeValueChanged": {
+              this.notifyInputElementValueChanged();
+              break;
+            }
+            case "MozNotifyMinMaxStepAttrChanged": {
+              this.notifyMinMaxStepAttrChanged();
+              break;
+            }
+            case "MozFocusInnerTextBox": {
+              this.focusInnerTextBox();
+              break;
+            }
+            case "MozBlurInnerTextBox": {
+              this.blurInnerTextBox();
+              break;
+            }
+            case "MozDateTimeAttributeChanged": {
+              this.updateEditAttribute();
+              break;
+            }
+            case "MozPickerValueChanged": {
+              this.setValueFromPicker(aEvent.detail);
+              break;
+            }
+            case "MozSetDateTimePickerState": {
+              this.setPickerState(aEvent.detail);
+              break;
+            }
             case "keypress": {
               this.onKeyPress(aEvent);
               break;
             }
             case "click": {
               this.onClick(aEvent);
               break;
             }