Bug 1514040 - Dispatch events to hidden datetimebox UA Widget r=smaug
authorTimothy Guan-tin Chien <timdream@gmail.com>
Fri, 04 Jan 2019 21:53:51 +0000
changeset 509688 6dd228164d427b7b247e59e7d9d0251206dea83d
parent 509687 8e900ef58b0f05fa6cacd29b719bbb14ad8c8e72
child 509722 e0a4fe89a7b0a36b246f79121d7e4fd70bd898b7
child 509723 763f3f9bc7c3d5eae05fe45639ece36a7495b8c0
child 509734 df5c4c4e6a7a6b29a61acdc18184c6a7708ba369
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1514040
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1514040 - Dispatch events to hidden datetimebox UA Widget r=smaug The XBL binding implementation relied on nsDateTimeControlFrame to call into its nsIDateTimeInputArea implementation. This is correct because the XBL binding is only constructed when the element has a frame. If the value is set while the element is hidden, the XBL binding will pick up the correct value during construction. That is not the case for UA Widget. As it is constructed when the DOM is attached, relying on nsDateTimeControlFrame to send an event when attributes change means the event won't be sent to the already constructed UA Widget. This patch fixes that by moving the event dispatching calls originating from HTMLInputElement out of nsDateTimeControlFrame, so they will behave correctly in the absence of the frame. I've also moved the gut of nsDateTimeControlFrame::HasBadInput() to DateTimeInputTypeBase::HasBadInput(). Content script should be allowed to validate the input without the frame. Sadly this means the XBL implementation and the UA Widget implementation have further diverged. The complexity should go away when we could finally remove the XBL implementation. nsDateTimeControlFrame still dispatches a few events to UA Widget, in AttributeChanged() and SyncDisabledState(), as they are originated from the layout. The name of the events in AttributeChanged() are incorrect though -- I am correcting that in this patch too. Differential Revision: https://phabricator.services.mozilla.com/D15601
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/input/DateTimeInputTypes.cpp
dom/html/test/forms/mochitest.ini
dom/html/test/forms/test_input_datetime_hidden.html
dom/html/test/forms/test_input_datetime_tabindex.html
layout/forms/nsDateTimeControlFrame.cpp
layout/forms/nsDateTimeControlFrame.h
toolkit/content/widgets/datetimebox.js
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2052,22 +2052,36 @@ void HTMLInputElement::MozSetDirectory(c
 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue) {
   if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
     return;
   }
 
   aValue = *mDateTimeInputBoxValue;
 }
 
+Element* HTMLInputElement::GetDateTimeBoxElementInUAWidget() {
+  if (GetShadowRoot()) {
+    // The datetimebox <div> is the only child of the UA Widget Shadow Root
+    // if it is present.
+    MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
+    MOZ_ASSERT(1 >= GetShadowRoot()->GetChildCount());
+    if (nsIContent* inputAreaContent = GetShadowRoot()->GetFirstChild()) {
+      return inputAreaContent->AsElement();
+    }
+  }
+
+  return nullptr;
+}
+
 Element* HTMLInputElement::GetDateTimeBoxElement() {
   nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
   if (frame && frame->GetInputAreaContent()) {
     return frame->GetInputAreaContent()->AsElement();
   }
-  return nullptr;
+  return GetDateTimeBoxElementInUAWidget();
 }
 
 void HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue) {
   if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
     return;
   }
 
   mDateTimeInputBoxValue = new DateTimeValue(aInitialValue);
@@ -2615,19 +2629,27 @@ nsresult HTMLInputElement::SetValueInter
           nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
           if (frame) {
             frame->UpdateForValueChange();
           }
         } else if ((mType == NS_FORM_INPUT_TIME ||
                     mType == NS_FORM_INPUT_DATE) &&
                    !IsExperimentalMobileType(mType) &&
                    !(aFlags & nsTextEditorState::eSetValue_BySetUserInput)) {
-          nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
-          if (frame) {
-            frame->OnValueChanged();
+          if (Element* dateTimeBoxElement = GetDateTimeBoxElementInUAWidget()) {
+            AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
+                dateTimeBoxElement,
+                NS_LITERAL_STRING("MozDateTimeValueChanged"), CanBubble::eNo,
+                ChromeOnlyDispatch::eNo);
+            dispatcher->RunDOMEventWhenSafe();
+          } else {
+            nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+            if (frame) {
+              frame->OnValueChanged();
+            }
           }
         }
         if (mDoneCreating) {
           OnValueChanged(/* aNotify = */ true,
                          /* aWasInteractiveUserChange = */ false);
         }
         // else DoneCreatingElement calls us again once mDoneCreating is true
       }
@@ -2904,20 +2926,28 @@ void HTMLInputElement::Blur(ErrorResult&
         textControl->Blur(aError);
         return;
       }
     }
   }
 
   if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
       !IsExperimentalMobileType(mType)) {
-    nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
-    if (frame) {
-      frame->HandleBlurEvent();
+    if (Element* dateTimeBoxElement = GetDateTimeBoxElementInUAWidget()) {
+      AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
+          dateTimeBoxElement, NS_LITERAL_STRING("MozBlurInnerTextBox"),
+          CanBubble::eNo, ChromeOnlyDispatch::eNo);
+      dispatcher->RunDOMEventWhenSafe();
       return;
+    } else {
+      nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+      if (frame) {
+        frame->HandleBlurEvent();
+        return;
+      }
     }
   }
 
   nsGenericHTMLElement::Blur(aError);
 }
 
 void HTMLInputElement::Focus(ErrorResult& aError) {
   if (mType == NS_FORM_INPUT_NUMBER) {
@@ -2930,20 +2960,28 @@ void HTMLInputElement::Focus(ErrorResult
         textControl->Focus(aError);
         return;
       }
     }
   }
 
   if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
       !IsExperimentalMobileType(mType)) {
-    nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
-    if (frame) {
-      frame->HandleFocusEvent();
+    if (Element* dateTimeBoxElement = GetDateTimeBoxElementInUAWidget()) {
+      AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
+          dateTimeBoxElement, NS_LITERAL_STRING("MozFocusInnerTextBox"),
+          CanBubble::eNo, ChromeOnlyDispatch::eNo);
+      dispatcher->RunDOMEventWhenSafe();
       return;
+    } else {
+      nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+      if (frame) {
+        frame->HandleFocusEvent();
+        return;
+      }
     }
   }
 
   if (mType != NS_FORM_INPUT_FILE) {
     nsGenericHTMLElement::Focus(aError);
     return;
   }
 
@@ -3240,21 +3278,28 @@ void HTMLInputElement::GetEventTargetPar
     if (frame) {
       frame->InvalidateFrameSubtree();
     }
   }
 
   if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
       !IsExperimentalMobileType(mType) && aVisitor.mEvent->mMessage == eFocus &&
       aVisitor.mEvent->mOriginalTarget == this) {
-    // If original target is this and not the anonymous text control, we should
-    // pass the focus to the anonymous text control.
-    nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
-    if (frame) {
-      frame->HandleFocusEvent();
+    // If original target is this and not the inner text control, we should
+    // pass the focus to the inner text control.
+    if (Element* dateTimeBoxElement = GetDateTimeBoxElementInUAWidget()) {
+      AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
+          dateTimeBoxElement, NS_LITERAL_STRING("MozFocusInnerTextBox"),
+          CanBubble::eNo, ChromeOnlyDispatch::eNo);
+      dispatcher->RunDOMEventWhenSafe();
+    } else {
+      nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+      if (frame) {
+        frame->HandleFocusEvent();
+      }
     }
   }
 
   if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
     if (mNumberControlSpinnerIsSpinning) {
       // If the timer is running the user has depressed the mouse on one of the
       // spin buttons. If the mouse exits the button we either want to reverse
       // the direction of spin if it has moved over the other button, or else
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -743,18 +743,26 @@ class HTMLInputElement final : public ns
 
   /*
    * 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);
 
   /*
+   * This locates the inner datetimebox UA Widget element and only the
+   * UA Widget
+   * element. This should fold into GetDateTimeBoxElement() when the XBL binding is removed.
+   */
+  Element* GetDateTimeBoxElementInUAWidget();
+
+  /*
    * This allows chrome JavaScript to dispatch event to the inner datetimebox
-   * anonymous element or access nsIDateTimeInputArea implmentation.
+   * anonymous or UA Widget element and access nsIDateTimeInputArea
+   * implementation.
    */
   Element* GetDateTimeBoxElement();
 
   /*
    * The following functions are called from datetime input box XBL to control
    * and update the picker.
    */
   void OpenDateTimePicker(const DateTimeValue& aInitialValue);
--- a/dom/html/input/DateTimeInputTypes.cpp
+++ b/dom/html/input/DateTimeInputTypes.cpp
@@ -2,25 +2,30 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "DateTimeInputTypes.h"
 
 #include "js/Date.h"
+#include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "nsDateTimeControlFrame.h"
+#include "nsDOMTokenList.h"
 
 const double DateTimeInputTypeBase::kMinimumYear = 1;
 const double DateTimeInputTypeBase::kMaximumYear = 275760;
 const double DateTimeInputTypeBase::kMaximumMonthInMaximumYear = 9;
 const double DateTimeInputTypeBase::kMaximumWeekInMaximumYear = 37;
 const double DateTimeInputTypeBase::kMsPerDay = 24 * 60 * 60 * 1000;
 
+using namespace mozilla;
+using namespace mozilla::dom;
+
 /* static */ bool DateTimeInputTypeBase::IsInputDateTimeEnabled() {
   static bool sDateTimeEnabled = false;
   static bool sDateTimePrefCached = false;
   if (!sDateTimePrefCached) {
     sDateTimePrefCached = true;
     mozilla::Preferences::AddBoolVarCache(&sDateTimeEnabled,
                                           "dom.forms.datetime", false);
   }
@@ -90,23 +95,52 @@ bool DateTimeInputTypeBase::HasStepMisma
     return false;
   }
 
   // Value has to be an integral multiple of step.
   return NS_floorModulo(value - GetStepBase(), step) != mozilla::Decimal(0);
 }
 
 bool DateTimeInputTypeBase::HasBadInput() const {
+  Element* editWrapperElement = nullptr;
   nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
-  if (!frame) {
+  if (frame && frame->GetInputAreaContent()) {
+    // edit-wrapper is inside an XBL binding
+    editWrapperElement =
+        mInputElement->GetComposedDoc()->GetAnonymousElementByAttribute(
+            frame->GetInputAreaContent(), nsGkAtoms::anonid,
+            NS_LITERAL_STRING("edit-wrapper"));
+  } else if (mInputElement->GetShadowRoot()) {
+    // edit-wrapper is inside an UA Widget Shadow DOM
+    editWrapperElement = mInputElement->GetShadowRoot()->GetElementById(
+        NS_LITERAL_STRING("edit-wrapper"));
+  }
+  if (!editWrapperElement) {
     return false;
   }
 
-  return frame->HasBadInput();
-  ;
+  // Incomplete field does not imply bad input.
+  for (Element* child = editWrapperElement->GetFirstElementChild(); child;
+       child = child->GetNextElementSibling()) {
+    if (child->ClassList()->Contains(
+            NS_LITERAL_STRING("datetime-edit-field"))) {
+      nsAutoString value;
+      child->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
+      if (value.IsEmpty()) {
+        return false;
+      }
+    }
+  }
+
+  // All fields are available but input element's value is empty implies
+  // it has been sanitized.
+  nsAutoString value;
+  mInputElement->GetValue(value, CallerType::System);
+
+  return value.IsEmpty();
 }
 
 nsresult DateTimeInputTypeBase::GetRangeOverflowMessage(nsAString& aMessage) {
   nsAutoString maxStr;
   mInputElement->GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
 
   const char16_t* params[] = {maxStr.get()};
   return nsContentUtils::FormatLocalizedString(
@@ -120,19 +154,27 @@ nsresult DateTimeInputTypeBase::GetRange
 
   const char16_t* params[] = {minStr.get()};
   return nsContentUtils::FormatLocalizedString(
       nsContentUtils::eDOM_PROPERTIES, "FormValidationDateTimeRangeUnderflow",
       params, aMessage);
 }
 
 nsresult DateTimeInputTypeBase::MinMaxStepAttrChanged() {
-  nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
-  if (frame) {
-    frame->OnMinMaxStepAttrChanged();
+  if (Element* dateTimeBoxElement =
+          mInputElement->GetDateTimeBoxElementInUAWidget()) {
+    AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
+        dateTimeBoxElement, NS_LITERAL_STRING("MozNotifyMinMaxStepAttrChanged"),
+        CanBubble::eNo, ChromeOnlyDispatch::eNo);
+    dispatcher->RunDOMEventWhenSafe();
+  } else {
+    nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+    if (frame) {
+      frame->OnMinMaxStepAttrChanged();
+    }
   }
 
   return NS_OK;
 }
 
 bool DateTimeInputTypeBase::GetTimeFromMs(double aValue, uint16_t* aHours,
                                           uint16_t* aMinutes,
                                           uint16_t* aSeconds,
--- a/dom/html/test/forms/mochitest.ini
+++ b/dom/html/test/forms/mochitest.ini
@@ -33,16 +33,17 @@ skip-if = android_version == '18' # Andr
 [test_input_color_picker_update.html]
 skip-if = android_version == '18' # Android, bug 1147974
 [test_input_date_bad_input.html]
 [test_input_date_key_events.html]
 [test_input_datetime_input_change_events.html]
 [test_input_datetime_focus_blur.html]
 [test_input_datetime_focus_blur_events.html]
 [test_input_datetime_focus_state.html]
+[test_input_datetime_hidden.html]
 [test_input_datetime_tabindex.html]
 [test_input_defaultValue.html]
 [test_input_email.html]
 [test_input_event.html]
 skip-if = android_version == '18' # bug 1147974
 [test_input_file_picker.html]
 [test_input_hasBeenTypePassword.html]
 [test_input_hasBeenTypePassword_navigation.html]
new file mode 100644
--- /dev/null
+++ b/dom/html/test/forms/test_input_datetime_hidden.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1514040
+-->
+<head>
+  <title>Test construction of hidden date input type</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1514040">Mozilla Bug 1514040</a>
+<p id="display"></p>
+<div id="content">
+  <input id="date" type="date" hidden value="1947-02-28">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+let el = document.getElementById("date");
+ok(el.hidden, "element is hidden");
+is(el.value, "1947-02-28", ".value is set correctly");
+let fieldElements = Array.from(SpecialPowers.wrap(el).openOrClosedShadowRoot.getElementsByClassName("datetime-edit-field"));
+is(fieldElements[0].textContent, "02", "month is set");
+is(fieldElements[1].textContent, "28", "day is set");
+is(fieldElements[2].textContent, "1947", "year is set");
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/html/test/forms/test_input_datetime_tabindex.html
+++ b/dom/html/test/forms/test_input_datetime_tabindex.html
@@ -29,16 +29,25 @@ https://bugzilla.mozilla.org/show_bug.cg
  * correctly.
  **/
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(function() {
   test();
   SimpleTest.finish();
 });
 
+function checkInnerTextboxTabindex(input, tabindex) {
+  let fields = SpecialPowers.wrap(input).openOrClosedShadowRoot.getElementsByClassName("datetime-edit-field");
+
+  for (let field of fields) {
+    is(field.tabIndex, tabindex, "tabIndex in the inner textbox should be correct");
+  }
+
+}
+
 function testTabindex(type) {
   let input1 = document.getElementById(type + "1");
   let input2 = document.getElementById(type + "2");
   let input3 = document.getElementById(type + "3");
 
   input1.focus();
   is(document.activeElement, input1,
      "input element with tabindex=0 is focusable");
@@ -57,21 +66,27 @@ function testTabindex(type) {
   synthesizeKey("KEY_Tab");
   is(document.activeElement, input3,
      "input element with tabindex=-1 is not tabbable");
 
   input2.focus();
   is(document.activeElement, input2,
      "input element with tabindex=-1 is still focusable");
 
+  checkInnerTextboxTabindex(input1, 0);
+  checkInnerTextboxTabindex(input2, -1);
+  checkInnerTextboxTabindex(input3, 0);
+
   // Changing the tabindex attribute dynamically.
   input3.setAttribute("tabindex", "-1");
   synthesizeKey("KEY_Tab"); // need only one TAB since input2 is not tabbable
   isnot(document.activeElement, input3,
         "element with tabindex changed to -1 should not be tabbable");
+
+  checkInnerTextboxTabindex(input3, -1);
 }
 
 function test() {
   let inputTypes = ["time", "date"];
 
   for (let i = 0; i < inputTypes.length; i++) {
     testTabindex(inputTypes[i]);
   }
--- a/layout/forms/nsDateTimeControlFrame.cpp
+++ b/layout/forms/nsDateTimeControlFrame.cpp
@@ -13,17 +13,16 @@
 
 #include "nsContentUtils.h"
 #include "nsCheckboxRadioFrame.h"
 #include "nsGkAtoms.h"
 #include "nsContentCreatorFunctions.h"
 #include "mozilla/AsyncEventDispatcher.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;
@@ -46,131 +45,57 @@ nsDateTimeControlFrame::nsDateTimeContro
 
 void nsDateTimeControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
                                          PostDestroyData& aPostDestroyData) {
   aPostDestroyData.AddAnonymousContent(mInputAreaContent.forget());
   nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
 }
 
 void nsDateTimeControlFrame::OnValueChanged() {
-  if (mInputAreaContent) {
-    nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-        do_QueryInterface(mInputAreaContent);
-    if (inputAreaContent) {
-      inputAreaContent->NotifyInputElementValueChanged();
-    }
-  } else {
-    Element* inputAreaContent = GetInputAreaContentAsElement();
-    if (!inputAreaContent) {
-      return;
-    }
-
-    AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
-        inputAreaContent, NS_LITERAL_STRING("MozDateTimeValueChanged"),
-        CanBubble::eNo, ChromeOnlyDispatch::eNo);
-    dispatcher->RunDOMEventWhenSafe();
+  if (!mInputAreaContent) {
+    return;
+  }
+  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+      do_QueryInterface(mInputAreaContent);
+  if (inputAreaContent) {
+    inputAreaContent->NotifyInputElementValueChanged();
   }
 }
 
 void nsDateTimeControlFrame::OnMinMaxStepAttrChanged() {
-  if (mInputAreaContent) {
-    nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-        do_QueryInterface(mInputAreaContent);
-    if (inputAreaContent) {
-      inputAreaContent->NotifyMinMaxStepAttrChanged();
-    }
-  } else {
-    Element* inputAreaContent = GetInputAreaContentAsElement();
-    if (!inputAreaContent) {
-      return;
-    }
-
-    AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
-        inputAreaContent, NS_LITERAL_STRING("MozNotifyMinMaxStepAttrChanged"),
-        CanBubble::eNo, ChromeOnlyDispatch::eNo);
-    dispatcher->RunDOMEventWhenSafe();
+  if (!mInputAreaContent) {
+    return;
+  }
+  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+      do_QueryInterface(mInputAreaContent);
+  if (inputAreaContent) {
+    inputAreaContent->NotifyMinMaxStepAttrChanged();
   }
 }
 
 void nsDateTimeControlFrame::HandleFocusEvent() {
-  if (mInputAreaContent) {
-    nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-        do_QueryInterface(mInputAreaContent);
-    if (inputAreaContent) {
-      inputAreaContent->FocusInnerTextBox();
-    }
-  } else {
-    Element* inputAreaContent = GetInputAreaContentAsElement();
-    if (!inputAreaContent) {
-      return;
-    }
-
-    AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
-        inputAreaContent, NS_LITERAL_STRING("MozFocusInnerTextBox"),
-        CanBubble::eNo, ChromeOnlyDispatch::eNo);
-    dispatcher->RunDOMEventWhenSafe();
+  if (!mInputAreaContent) {
+    return;
+  }
+  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+      do_QueryInterface(mInputAreaContent);
+  if (inputAreaContent) {
+    inputAreaContent->FocusInnerTextBox();
   }
 }
 
 void nsDateTimeControlFrame::HandleBlurEvent() {
-  if (mInputAreaContent) {
-    nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
-        do_QueryInterface(mInputAreaContent);
-    if (inputAreaContent) {
-      inputAreaContent->BlurInnerTextBox();
-    }
-  } else {
-    Element* inputAreaContent = GetInputAreaContentAsElement();
-    if (!inputAreaContent) {
-      return;
-    }
-
-    AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
-        inputAreaContent, NS_LITERAL_STRING("MozBlurInnerTextBox"),
-        CanBubble::eNo, ChromeOnlyDispatch::eNo);
-    dispatcher->RunDOMEventWhenSafe();
+  if (!mInputAreaContent) {
+    return;
   }
-}
-
-bool nsDateTimeControlFrame::HasBadInput() {
-  Element* editWrapperElement = nullptr;
-  if (mInputAreaContent) {
-    // edit-wrapper is inside an XBL binding
-    editWrapperElement =
-        mInputAreaContent->GetComposedDoc()->GetAnonymousElementByAttribute(
-            mInputAreaContent, nsGkAtoms::anonid,
-            NS_LITERAL_STRING("edit-wrapper"));
-  } else if (mContent->GetShadowRoot()) {
-    // edit-wrapper is inside an UA Widget Shadow DOM
-    editWrapperElement = mContent->GetShadowRoot()->GetElementById(
-        NS_LITERAL_STRING("edit-wrapper"));
-  }
-
-  if (!editWrapperElement) {
-    return false;
+  nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
+      do_QueryInterface(mInputAreaContent);
+  if (inputAreaContent) {
+    inputAreaContent->BlurInnerTextBox();
   }
-
-  // Incomplete field does not imply bad input.
-  for (Element* child = editWrapperElement->GetFirstElementChild(); child;
-       child = child->GetNextElementSibling()) {
-    if (child->ClassList()->Contains(
-            NS_LITERAL_STRING("datetime-edit-field"))) {
-      nsAutoString value;
-      child->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
-      if (value.IsEmpty()) {
-        return false;
-      }
-    }
-  }
-
-  // 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);
-  return value.IsEmpty();
 }
 
 nscoord nsDateTimeControlFrame::GetMinISize(gfxContext* aRenderingContext) {
   nscoord result;
   DISPLAY_MIN_INLINE_SIZE(this, result);
 
   nsIFrame* kid = mFrames.FirstChild();
   if (kid) {  // display:none?
@@ -379,23 +304,25 @@ void nsDateTimeControlFrame::SyncDisable
   if (mInputAreaContent) {
     nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
         do_QueryInterface(mInputAreaContent);
     if (!inputAreaContent) {
       return;
     }
     inputAreaContent->UpdateEditAttributes();
   } else {
-    Element* inputAreaContent = GetInputAreaContentAsElement();
-    if (!inputAreaContent) {
+    Element* dateTimeBoxElement =
+        static_cast<dom::HTMLInputElement*>(GetContent())
+            ->GetDateTimeBoxElementInUAWidget();
+    if (!dateTimeBoxElement) {
       return;
     }
 
     AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
-        inputAreaContent, NS_LITERAL_STRING("MozDateTimeAttributeChanged"),
+        dateTimeBoxElement, NS_LITERAL_STRING("MozDateTimeAttributeChanged"),
         CanBubble::eNo, ChromeOnlyDispatch::eNo);
     dispatcher->RunDOMEventWhenSafe();
   }
 }
 
 nsresult nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID,
                                                   nsAtom* aAttribute,
                                                   int32_t aModType) {
@@ -421,61 +348,45 @@ nsresult nsDateTimeControlFrame::Attribu
                   &nsIDateTimeInputArea::NotifyInputElementValueChanged));
             }
           } else {
             if (inputAreaContent) {
               inputAreaContent->UpdateEditAttributes();
             }
           }
         } else {
-          Element* inputAreaContent = GetInputAreaContentAsElement();
+          Element* dateTimeBoxElement =
+              static_cast<dom::HTMLInputElement*>(GetContent())
+                  ->GetDateTimeBoxElementInUAWidget();
           if (aAttribute == nsGkAtoms::value) {
-            if (inputAreaContent) {
+            if (dateTimeBoxElement) {
               AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
-                  inputAreaContent,
-                  NS_LITERAL_STRING("NotifyInputElementValueChanged"),
-                  CanBubble::eNo, ChromeOnlyDispatch::eNo);
+                  dateTimeBoxElement,
+                  NS_LITERAL_STRING("MozDateTimeValueChanged"), CanBubble::eNo,
+                  ChromeOnlyDispatch::eNo);
               dispatcher->RunDOMEventWhenSafe();
             }
           } else {
-            if (inputAreaContent) {
+            if (dateTimeBoxElement) {
               AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
-                  inputAreaContent,
-                  NS_LITERAL_STRING("MozDateTimeValueChanged"), CanBubble::eNo,
-                  ChromeOnlyDispatch::eNo);
+                  dateTimeBoxElement,
+                  NS_LITERAL_STRING("MozDateTimeAttributeChanged"),
+                  CanBubble::eNo, ChromeOnlyDispatch::eNo);
               dispatcher->RunDOMEventWhenSafe();
             }
           }
         }
       }
     }
   }
 
   return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
 }
 
 nsIContent* nsDateTimeControlFrame::GetInputAreaContent() {
-  if (mInputAreaContent) {
-    return mInputAreaContent;
-  }
-  if (mContent->GetShadowRoot()) {
-    // The datetimebox <div> is the only child of the UA Widget Shadow Root
-    // if it is present.
-    MOZ_ASSERT(mContent->GetShadowRoot()->IsUAWidget());
-    MOZ_ASSERT(1 >= mContent->GetShadowRoot()->GetChildCount());
-    return mContent->GetShadowRoot()->GetFirstChild();
-  }
-  return nullptr;
-}
-
-Element* nsDateTimeControlFrame::GetInputAreaContentAsElement() {
-  nsIContent* inputAreaContent = GetInputAreaContent();
-  if (inputAreaContent) {
-    return inputAreaContent->AsElement();
-  }
-  return nullptr;
+  return mInputAreaContent;
 }
 
 void nsDateTimeControlFrame::ContentStatesChanged(EventStates aStates) {
   if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
     nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
   }
 }
--- a/layout/forms/nsDateTimeControlFrame.h
+++ b/layout/forms/nsDateTimeControlFrame.h
@@ -75,17 +75,16 @@ class nsDateTimeControlFrame final : pub
                             int32_t aModType) override;
 
   nsIContent* GetInputAreaContent();
 
   void OnValueChanged();
   void OnMinMaxStepAttrChanged();
   void HandleFocusEvent();
   void HandleBlurEvent();
-  bool HasBadInput();
 
  private:
   class SyncDisabledStateEvent;
   friend class SyncDisabledStateEvent;
   class SyncDisabledStateEvent : public mozilla::Runnable {
    public:
     explicit SyncDisabledStateEvent(nsDateTimeControlFrame* aFrame)
         : mozilla::Runnable("nsDateTimeControlFrame::SyncDisabledStateEvent"),
@@ -104,16 +103,14 @@ class nsDateTimeControlFrame final : pub
     WeakFrame mFrame;
   };
 
   /**
    * Sync the disabled state of the anonymous children up with our content's.
    */
   void SyncDisabledState();
 
-  mozilla::dom::Element* GetInputAreaContentAsElement();
-
   // Anonymous child which is bound via XBL to an element that wraps the input
   // area and reset button.
   RefPtr<mozilla::dom::Element> mInputAreaContent;
 };
 
 #endif  // nsDateTimeControlFrame_h__
--- a/toolkit/content/widgets/datetimebox.js
+++ b/toolkit/content/widgets/datetimebox.js
@@ -262,17 +262,17 @@ this.DateTimeInputBaseImplWidget = class
     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
+    // DateTimeInputTypeBase::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.