Temporary patch for date/time input frame. draft
authorJessica Jong <jjong@mozilla.com>
Tue, 02 Aug 2016 14:24:40 +0800
changeset 403200 e4a45e2b57fb6a4e19d7e3f9a2398d8314a91226
parent 394995 ffac2798999c5b84f1b4605a1280994bb665a406
child 403201 0196e4465f404b68101cd5265112d93d902276da
push id26856
push userbmo:scwwu@mozilla.com
push dateFri, 19 Aug 2016 10:08:07 +0000
milestone51.0a1
Temporary patch for date/time input frame.
browser/base/content/browser.xul
browser/base/content/tabbrowser.xml
dom/base/nsGkAtomList.h
dom/html/HTMLInputElement.cpp
dom/html/nsIFormControl.h
dom/xul/nsXULElement.cpp
layout/base/nsCSSFrameConstructor.cpp
layout/forms/moz.build
layout/forms/nsDateTimeControlFrame.cpp
layout/forms/nsDateTimeControlFrame.h
layout/generic/nsFrameIdList.h
layout/generic/nsHTMLParts.h
layout/style/res/html.css
modules/libpref/init/all.js
toolkit/content/datetime-child.js
toolkit/content/jar.mn
toolkit/content/widgets/datetimebox.css
toolkit/content/widgets/datetimebox.xml
toolkit/content/widgets/remote-browser.xml
toolkit/modules/DateTimeContentHelper.jsm
toolkit/modules/DateTimeParentHelper.jsm
toolkit/modules/moz.build
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -156,16 +156,27 @@
 #ifdef NIGHTLY_BUILD
       <hbox id="urlbar-search-footer" flex="1" align="stretch" pack="end">
         <button id="urlbar-search-settings" label="&changeSearchSettings.button;"
                 oncommand="BrowserUITelemetry.countSearchSettingsEvent('urlbar'); openPreferences('paneSearch')"/>
       </hbox>
 #endif
     </panel>
 
+    <panel id="DateTimePickerPanel"
+           hidden="true"
+           noautofocus="true"
+           consumeoutsideclicks="true"
+           noautohide="false"
+          level="parent">
+      <vbox>
+        <iframe id="myFrame" width="200" height="200"/>
+      </vbox>
+    </panel>
+
     <!-- for select dropdowns. The menupopup is what shows the list of options,
          and the popuponly menulist makes things like the menuactive attributes
          work correctly on the menupopup. ContentSelectDropdown expects the
          popuponly menulist to be its immediate parent. -->
     <menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
       <menupopup rolluponmousewheel="true"
                  activateontab="true"
 #ifdef XP_WIN
@@ -1082,17 +1093,18 @@
       <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
       <vbox id="appcontent" flex="1">
         <notificationbox id="high-priority-global-notificationbox" notificationside="top"/>
         <tabbrowser id="content"
                     flex="1" contenttooltip="aHTMLTooltip"
                     tabcontainer="tabbrowser-tabs"
                     contentcontextmenu="contentAreaContextMenu"
                     autocompletepopup="PopupAutoComplete"
-                    selectmenulist="ContentSelectDropdown"/>
+                    selectmenulist="ContentSelectDropdown"
+                    datetimepicker="DateTimePickerPanel"/>
         <chatbar id="pinnedchats" layer="true" mousethrough="always" hidden="true"/>
       </vbox>
       <splitter id="social-sidebar-splitter"
                 class="chromeclass-extrachrome sidebar-splitter"
                 observes="socialSidebarBroadcaster"/>
       <vbox id="social-sidebar-box"
             class="chromeclass-extrachrome"
             observes="socialSidebarBroadcaster"
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -20,17 +20,17 @@
                   flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
                   onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
         <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
           <xul:notificationbox flex="1" notificationside="top">
             <xul:hbox flex="1" class="browserSidebarContainer">
               <xul:vbox flex="1" class="browserContainer">
                 <xul:stack flex="1" class="browserStack" anonid="browserStack">
                   <xul:browser anonid="initialBrowser" type="content-primary" message="true" messagemanagergroup="browsers"
-                               xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectmenulist"/>
+                               xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectmenulist,datetimepicker"/>
                 </xul:stack>
               </xul:vbox>
             </xul:hbox>
           </xul:notificationbox>
         </xul:tabpanels>
       </xul:tabbox>
       <children/>
     </content>
@@ -1791,16 +1791,19 @@
 
             if (!aParams.isPreloadBrowser && this.hasAttribute("autocompletepopup")) {
               b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
             }
 
             if (this.hasAttribute("selectmenulist"))
               b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
 
+            if (this.hasAttribute("datetimepicker"))
+              b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
+
             b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
 
             // Create the browserStack container
             var stack = document.createElementNS(NS_XUL, "stack");
             stack.className = "browserStack";
             stack.appendChild(b);
             stack.setAttribute("flex", "1");
 
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -266,16 +266,17 @@ GK_ATOM(curpos, "curpos")
 GK_ATOM(current, "current")
 GK_ATOM(cycler, "cycler")
 GK_ATOM(data, "data")
 GK_ATOM(datalist, "datalist")
 GK_ATOM(dataType, "data-type")
 GK_ATOM(dateTime, "date-time")
 GK_ATOM(datasources, "datasources")
 GK_ATOM(datetime, "datetime")
+GK_ATOM(datetimebox, "datetimebox")
 GK_ATOM(dblclick, "dblclick")
 GK_ATOM(dd, "dd")
 GK_ATOM(debug, "debug")
 GK_ATOM(decimalFormat, "decimal-format")
 GK_ATOM(decimalSeparator, "decimal-separator")
 GK_ATOM(deck, "deck")
 GK_ATOM(declare, "declare")
 GK_ATOM(decoderDoctor, "decoder-doctor")
@@ -1966,16 +1967,17 @@ GK_ATOM(bcTableCellFrame, "BCTableCellFr
 GK_ATOM(blockFrame, "BlockFrame")
 GK_ATOM(boxFrame, "BoxFrame")
 GK_ATOM(brFrame, "BRFrame")
 GK_ATOM(bulletFrame, "BulletFrame")
 GK_ATOM(colorControlFrame, "colorControlFrame")
 GK_ATOM(columnSetFrame, "ColumnSetFrame")
 GK_ATOM(comboboxControlFrame, "ComboboxControlFrame")
 GK_ATOM(comboboxDisplayFrame, "ComboboxDisplayFrame")
+GK_ATOM(dateTimeControlFrame, "DateTimeControlFrame")
 GK_ATOM(deckFrame, "DeckFrame")
 GK_ATOM(detailsFrame, "DetailsFrame")
 GK_ATOM(fieldSetFrame, "FieldSetFrame")
 GK_ATOM(flexContainerFrame, "FlexContainerFrame")
 GK_ATOM(formControlFrame, "FormControlFrame") // radio or checkbox
 GK_ATOM(frameSetFrame, "FrameSetFrame")
 GK_ATOM(gfxButtonControlFrame, "gfxButtonControlFrame")
 GK_ATOM(gridContainerFrame, "GridContainerFrame")
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -48,16 +48,17 @@
 #include "nsIFrame.h"
 #include "nsRangeFrame.h"
 #include "nsIServiceManager.h"
 #include "nsError.h"
 #include "nsIEditor.h"
 #include "nsIIOService.h"
 #include "nsDocument.h"
 #include "nsAttrValueOrString.h"
+#include "nsDateTimeControlFrame.h"
 
 #include "nsPresState.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMNodeList.h"
 #include "nsIDOMHTMLCollection.h"
 #include "nsLinebreakConverter.h" //to strip out carriage returns
 #include "nsReadableUtils.h"
 #include "nsUnicharUtils.h"
@@ -3062,16 +3063,21 @@ HTMLInputElement::SetValueInternal(const
           if (numberControlFrame) {
             numberControlFrame->SetValueOfAnonTextControl(value);
           }
         } else if (mType == NS_FORM_INPUT_RANGE) {
           nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
           if (frame) {
             frame->UpdateForValueChange();
           }
+        } else if (mType == NS_FORM_INPUT_TIME) {
+          nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+          if (frame) {
+            frame->UpdateInputBoxValue(value);
+          }
         }
         if (!mParserCreating) {
           OnValueChanged(true);
         }
         // else DoneCreatingElement calls us again once mParserCreating is false
       }
 
       if (mType == NS_FORM_INPUT_COLOR) {
--- a/dom/html/nsIFormControl.h
+++ b/dom/html/nsIFormControl.h
@@ -261,17 +261,16 @@ nsIFormControl::IsSingleLineTextControl(
 {
   return aType == NS_FORM_INPUT_TEXT ||
          aType == NS_FORM_INPUT_EMAIL ||
          aType == NS_FORM_INPUT_SEARCH ||
          aType == NS_FORM_INPUT_TEL ||
          aType == NS_FORM_INPUT_URL ||
          // TODO: those are temporary until bug 773205 is fixed.
          aType == NS_FORM_INPUT_DATE ||
-         aType == NS_FORM_INPUT_TIME ||
          aType == NS_FORM_INPUT_MONTH ||
          (!aExcludePassword && aType == NS_FORM_INPUT_PASSWORD);
 }
 
 bool
 nsIFormControl::IsSubmittableControl() const
 {
   // TODO: keygen should be in that list, see bug 101019.
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -867,19 +867,19 @@ nsXULElement::BindToTree(nsIDocument* aD
       // for HTML we currently should only pull it in if the document contains
       // an <audio> or <video> element. This assertion is here to make sure
       // that we don't fail to notice if a change to bindings causes us to
       // start pulling in xul.css much more frequently. If this assertion
       // fails then we need to figure out why, and how we can continue to avoid
       // pulling in xul.css.
       // Note that add-ons may introduce bindings that cause this assertion to
       // fire.
-      NS_ASSERTION(IsInVideoControls(this) ||
-                   IsInFeedSubscribeLine(this),
-                   "Unexpected XUL element in non-XUL doc");
+      //NS_ASSERTION(IsInVideoControls(this) ||
+      //             IsInFeedSubscribeLine(this),
+      //             "Unexpected XUL element in non-XUL doc");
     }
   }
 
   if (aDocument) {
       NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
                    "Missing a script blocker!");
       // We're in a document now.  Kick off the frame load.
       LoadSrc();
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -3694,17 +3694,17 @@ nsCSSFrameConstructor::FindInputData(Ele
     { NS_FORM_INPUT_COLOR,
       FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewColorControlFrame,
                                  nsCSSAnonBoxes::buttonContent) },
     // TODO: this is temporary until a frame is written: bug 635240.
     SIMPLE_INT_CREATE(NS_FORM_INPUT_NUMBER, NS_NewNumberControlFrame),
     // TODO: this is temporary until a frame is written: bug 888320.
     SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame),
     // TODO: this is temporary until a frame is written: bug 888320
-    SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewTextControlFrame),
+    SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewDateTimeControlFrame),
     // TODO: this is temporary until a frame is written: bug 888320
     SIMPLE_INT_CREATE(NS_FORM_INPUT_MONTH, NS_NewTextControlFrame),
     { NS_FORM_INPUT_SUBMIT,
       FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewGfxButtonControlFrame,
                                  nsCSSAnonBoxes::buttonContent) },
     { NS_FORM_INPUT_RESET,
       FCDATA_WITH_WRAPPING_BLOCK(0, NS_NewGfxButtonControlFrame,
                                  nsCSSAnonBoxes::buttonContent) },
--- a/layout/forms/moz.build
+++ b/layout/forms/moz.build
@@ -17,16 +17,17 @@ EXPORTS += [
     'nsISelectControlFrame.h',
     'nsITextControlFrame.h',
 ]
 
 UNIFIED_SOURCES += [
     'nsButtonFrameRenderer.cpp',
     'nsColorControlFrame.cpp',
     'nsComboboxControlFrame.cpp',
+    'nsDateTimeControlFrame.cpp',
     'nsFieldSetFrame.cpp',
     'nsFileControlFrame.cpp',
     'nsFormControlFrame.cpp',
     'nsGfxButtonControlFrame.cpp',
     'nsGfxCheckboxControlFrame.cpp',
     'nsGfxRadioControlFrame.cpp',
     'nsHTMLButtonControlFrame.cpp',
     'nsImageControlFrame.cpp',
new file mode 100644
--- /dev/null
+++ b/layout/forms/nsDateTimeControlFrame.cpp
@@ -0,0 +1,332 @@
+/* -*- 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 "nsDateTimeControlFrame.h"
+
+#include "nsContentUtils.h"
+#include "nsFormControlFrame.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentList.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsNodeInfoManager.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class nsDateTimeListener : public nsIDOMEventListener
+{
+private:
+  virtual ~nsDateTimeListener() {}
+
+public:
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD HandleEvent(nsIDOMEvent*) override
+  {
+    mDateTimeControl->ShowPicker();
+    return NS_OK;
+  }
+
+  explicit nsDateTimeListener(nsDateTimeControlFrame* aDateTimeControl)
+  {
+    mDateTimeControl = aDateTimeControl;
+  }
+
+  nsDateTimeControlFrame* mDateTimeControl;
+};
+
+NS_IMPL_ISUPPORTS(nsDateTimeListener,
+                  nsIDOMEventListener)
+
+nsIFrame*
+NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+  return new (aPresShell) nsDateTimeControlFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsDateTimeControlFrame)
+
+NS_QUERYFRAME_HEAD(nsDateTimeControlFrame)
+  NS_QUERYFRAME_ENTRY(nsDateTimeControlFrame)
+  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+nsDateTimeControlFrame::nsDateTimeControlFrame(nsStyleContext* aContext)
+  : nsContainerFrame(aContext)
+{
+}
+
+void
+nsDateTimeControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+  nsContentUtils::DestroyAnonymousContent(&mDateTimeInputBox);
+  nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+nsDateTimeControlFrame::ShowPicker()
+{
+  if (XRE_IsContentProcess()) {
+    nsContentUtils::DispatchChromeEvent(mContent->OwnerDoc(), mContent,
+                                        NS_LITERAL_STRING("mozshowdatetimepicker"),
+                                        true, false);
+  }
+}
+
+class DispatchEventToDateTimeBox : public Runnable
+{
+public:
+  explicit DispatchEventToDateTimeBox(nsIContent* aContent,
+                                      const nsAString& aEventName)
+    : mContent(aContent),
+      mEventName(aEventName) {}
+  NS_IMETHOD Run() override {
+    printf_stderr("DispatchTrustedEvent: %s\n", NS_ConvertUTF16toUTF8(mEventName).get());
+    nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent,
+                                         mEventName,
+                                         false, false);
+    return NS_OK;
+  }
+  nsString mEventName;
+  nsCOMPtr<nsIContent> mContent;
+};
+
+void
+nsDateTimeControlFrame::UpdateInputBoxValue(const nsAString& aValue)
+{
+  printf_stderr("UpdateInputBoxValue\n");
+  RefPtr<Runnable> event = new DispatchEventToDateTimeBox(mDateTimeInputBox,
+    NS_LITERAL_STRING("datetime-value-changed"));
+  nsContentUtils::AddScriptRunner(event);
+}
+
+nscoord
+nsDateTimeControlFrame::GetMinISize(nsRenderingContext* aRenderingContext)
+{
+  nscoord result;
+  DISPLAY_MIN_WIDTH(this, result);
+
+  nsIFrame* kid = mFrames.FirstChild();
+  if (kid) { // display:none?
+    result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+                                                  kid,
+                                                  nsLayoutUtils::MIN_ISIZE);
+  } else {
+    result = 0;
+  }
+
+  return result;
+}
+
+nscoord
+nsDateTimeControlFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
+{
+  nscoord result;
+  DISPLAY_PREF_WIDTH(this, result);
+
+  nsIFrame* kid = mFrames.FirstChild();
+  if (kid) { // display:none?
+    result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
+                                                  kid,
+                                                  nsLayoutUtils::PREF_ISIZE);
+  } else {
+    result = 0;
+  }
+
+  return result;
+}
+
+void
+nsDateTimeControlFrame::Reflow(nsPresContext* aPresContext,
+                               ReflowOutput& aDesiredSize,
+                               const ReflowInput& aReflowInput,
+                               nsReflowStatus& aStatus)
+{
+  MarkInReflow();
+
+  DO_GLOBAL_REFLOW_COUNT("nsDateTimeControlFrame");
+  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+
+  NS_ASSERTION(mDateTimeInputBox, "DateTime box must exist!");
+
+  const WritingMode myWM = aReflowInput.GetWritingMode();
+
+  // The ISize of our content box, which is the available ISize
+  // for our anonymous content:
+  const nscoord contentBoxISize = aReflowInput.ComputedISize();
+  nscoord contentBoxBSize = aReflowInput.ComputedBSize();
+
+  // Figure out our border-box sizes as well (by adding borderPadding to
+  // content-box sizes):
+  const nscoord borderBoxISize = contentBoxISize +
+    aReflowInput.ComputedLogicalBorderPadding().IStartEnd(myWM);
+
+  nscoord borderBoxBSize;
+  if (contentBoxBSize != NS_INTRINSICSIZE) {
+    borderBoxBSize = contentBoxBSize +
+      aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+  } // else, we'll figure out borderBoxBSize after we resolve contentBoxBSize.
+
+  nsIFrame* outerWrapperFrame = mFrames.FirstChild();
+  if (outerWrapperFrame) {
+    NS_ASSERTION(outerWrapperFrame->GetContent() == mDateTimeInputBox,
+                 "What is this child doing here?");
+
+    ReflowOutput wrappersDesiredSize(aReflowInput);
+
+    WritingMode wrapperWM = outerWrapperFrame->GetWritingMode();
+    LogicalSize availSize = aReflowInput.ComputedSize(wrapperWM);
+    availSize.BSize(wrapperWM) = NS_UNCONSTRAINEDSIZE;
+
+    ReflowInput wrapperReflowInput(aPresContext, aReflowInput,
+                                   outerWrapperFrame, availSize);
+
+    // Convert wrapper margin into my own writing-mode (in case it differs):
+    LogicalMargin wrapperMargin =
+      wrapperReflowInput.ComputedLogicalMargin().ConvertTo(myWM, wrapperWM);
+
+    // offsets of wrapper frame within this frame:
+    LogicalPoint
+      wrapperOffset(myWM,
+                    aReflowInput.ComputedLogicalBorderPadding().IStart(myWM) +
+                    wrapperMargin.IStart(myWM),
+                    aReflowInput.ComputedLogicalBorderPadding().BStart(myWM) +
+                    wrapperMargin.BStart(myWM));
+
+    nsReflowStatus childStatus;
+    // We initially reflow the child with a dummy containerSize; positioning
+    // will be fixed later.
+    const nsSize dummyContainerSize;
+    ReflowChild(outerWrapperFrame, aPresContext, wrappersDesiredSize,
+                wrapperReflowInput, myWM, wrapperOffset, dummyContainerSize, 0,
+                childStatus);
+    MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus),
+               "We gave our child unconstrained available block-size, "
+               "so it should be complete");
+
+    nscoord wrappersMarginBoxBSize =
+      wrappersDesiredSize.BSize(myWM) + wrapperMargin.BStartEnd(myWM);
+
+    if (contentBoxBSize == NS_INTRINSICSIZE) {
+      // We are intrinsically sized -- we should shrinkwrap the outer wrapper's
+      // block-size:
+      contentBoxBSize = wrappersMarginBoxBSize;
+
+      // Make sure we obey min/max-bsize in the case when we're doing intrinsic
+      // sizing (we get it for free when we have a non-intrinsic
+      // aReflowInput.ComputedBSize()).  Note that we do this before
+      // adjusting for borderpadding, since ComputedMaxBSize and
+      // ComputedMinBSize are content heights.
+      contentBoxBSize =
+        NS_CSS_MINMAX(contentBoxBSize,
+                      aReflowInput.ComputedMinBSize(),
+                      aReflowInput.ComputedMaxBSize());
+
+      borderBoxBSize = contentBoxBSize +
+        aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
+    }
+
+    // Center child in block axis
+    nscoord extraSpace = contentBoxBSize - wrappersMarginBoxBSize;
+    wrapperOffset.B(myWM) += std::max(0, extraSpace / 2);
+
+    // Needed in FinishReflowChild, for logical-to-physical conversion:
+    nsSize borderBoxSize = LogicalSize(myWM, borderBoxISize, borderBoxBSize).
+                           GetPhysicalSize(myWM);
+
+    // Place the child
+    FinishReflowChild(outerWrapperFrame, aPresContext, wrappersDesiredSize,
+                      &wrapperReflowInput, myWM, wrapperOffset,
+                      borderBoxSize, 0);
+
+    nsSize contentBoxSize =
+      LogicalSize(myWM, contentBoxISize, contentBoxBSize).
+        GetPhysicalSize(myWM);
+    aDesiredSize.SetBlockStartAscent(
+       wrappersDesiredSize.BlockStartAscent() +
+       outerWrapperFrame->BStart(aReflowInput.GetWritingMode(),
+                             contentBoxSize));
+  }
+
+  LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
+  aDesiredSize.SetSize(myWM, logicalDesiredSize);
+
+  aDesiredSize.SetOverflowAreasToDesiredBounds();
+
+  if (outerWrapperFrame) {
+    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, outerWrapperFrame);
+  }
+
+  FinishAndStoreOverflow(&aDesiredSize);
+
+  aStatus = NS_FRAME_COMPLETE;
+
+  NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+                  ("exit nsDateTimeControlFrame::Reflow: size=%d,%d",
+                  aDesiredSize.Width(), aDesiredSize.Height()));
+  NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+nsresult
+nsDateTimeControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+  // Set up "datetimebox" XUL element which will be XBL-bound to the
+  // actual controls.
+  nsNodeInfoManager *nodeInfoManager = GetContent()->GetComposedDoc()->NodeInfoManager();
+  RefPtr<NodeInfo> nodeInfo;
+  nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::datetimebox, nullptr,
+                                             kNameSpaceID_XUL, nsIDOMNode::ELEMENT_NODE);
+  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
+
+  NS_TrustedNewXULElement(getter_AddRefs(mDateTimeInputBox), nodeInfo.forget());
+  if (!aElements.AppendElement(mDateTimeInputBox))
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  mDateTimeListener = new nsDateTimeListener(this);
+  mDateTimeInputBox->AddEventListener(NS_LITERAL_STRING("ShowDateTimePicker"), mDateTimeListener,
+                                      false, true);
+
+return NS_OK;
+}
+
+void
+nsDateTimeControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+                                                 uint32_t aFilter)
+{
+  if (mDateTimeInputBox) {
+    aElements.AppendElement(mDateTimeInputBox);
+  }
+}
+
+nsresult
+nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID,
+                                         nsIAtom* aAttribute,
+                                         int32_t aModType)
+{
+  // FIXME: This function is not getting called on attribute changed!?
+  NS_ASSERTION(mDateTimeInputBox, "The datetime box must exist!");
+
+  MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
+  bool typeIsDateTime = static_cast<dom::HTMLInputElement*>(mContent)->GetType() ==
+                          NS_FORM_INPUT_TIME;
+  // 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 (typeIsDateTime) {
+    RefPtr<Runnable> event = new DispatchEventToDateTimeBox(mDateTimeInputBox,
+      NS_LITERAL_STRING("datetime-value-changed"));
+    nsContentUtils::AddScriptRunner(event);
+  }
+
+  return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
+                                            aModType);
+}
+
+nsIAtom*
+nsDateTimeControlFrame::GetType() const
+{
+  return nsGkAtoms::dateTimeControlFrame;
+}
new file mode 100644
--- /dev/null
+++ b/layout/forms/nsDateTimeControlFrame.h
@@ -0,0 +1,72 @@
+/* -*- 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/. */
+
+#ifndef nsDateTimeControlFrame_h__
+#define nsDateTimeControlFrame_h__
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsCOMPtr.h"
+
+class nsDateTimeControlFrame final : public nsContainerFrame,
+                                     public nsIAnonymousContentCreator
+{
+  explicit nsDateTimeControlFrame(nsStyleContext* aContext);
+
+public:
+  friend nsIFrame* NS_NewDateTimeControlFrame(nsIPresShell* aPresShell,
+                                              nsStyleContext* aContext);
+
+  virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+  NS_DECL_QUERYFRAME_TARGET(nsDateTimeControlFrame)
+  NS_DECL_QUERYFRAME
+  NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG_FRAME_DUMP
+  virtual nsresult GetFrameName(nsAString& aResult) const override {
+    return MakeFrameName(NS_LITERAL_STRING("DateTimeControl"), aResult);
+  }
+#endif
+
+  virtual nsIAtom* GetType() const override;
+
+  virtual bool IsFrameOfType(uint32_t aFlags) const override
+  {
+    return nsContainerFrame::IsFrameOfType(aFlags &
+      ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
+  }
+
+  // Reflow
+  virtual nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
+
+  virtual nscoord GetPrefISize(nsRenderingContext* aRenderingContext) override;
+
+  virtual void Reflow(nsPresContext*           aPresContext,
+                      ReflowOutput&            aDesiredSize,
+                      const ReflowInput&       aReflowState,
+                      nsReflowStatus&          aStatus) override;
+
+  // nsIAnonymousContentCreator
+  virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+  virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+                                        uint32_t aFilter) override;
+
+  virtual nsresult AttributeChanged(int32_t  aNameSpaceID,
+                                    nsIAtom* aAttribute,
+                                    int32_t  aModType) override;
+
+  void ShowPicker();
+
+  void UpdateInputBoxValue(const nsAString& aValue);
+
+private:
+
+  nsCOMPtr<nsIDOMEventListener> mDateTimeListener;
+  nsCOMPtr<nsIContent> mDateTimeInputBox;
+};
+
+#endif // nsDateTimeControlFrame_h__
\ No newline at end of file
--- a/layout/generic/nsFrameIdList.h
+++ b/layout/generic/nsFrameIdList.h
@@ -14,16 +14,17 @@ FRAME_ID(nsBulletFrame)
 FRAME_ID(nsButtonBoxFrame)
 FRAME_ID(nsCanvasFrame)
 FRAME_ID(nsColorControlFrame)
 FRAME_ID(nsColumnSetFrame)
 FRAME_ID(nsComboboxControlFrame)
 FRAME_ID(nsComboboxDisplayFrame)
 FRAME_ID(nsContainerFrame)
 FRAME_ID(nsContinuingTextFrame)
+FRAME_ID(nsDateTimeControlFrame)
 FRAME_ID(nsDeckFrame)
 FRAME_ID(nsDocElementBoxFrame)
 FRAME_ID(nsFieldSetFrame)
 FRAME_ID(nsFileControlFrame)
 FRAME_ID(nsFirstLetterFrame)
 FRAME_ID(nsFirstLineFrame)
 FRAME_ID(nsFlexContainerFrame)
 FRAME_ID(nsFormControlFrame)
--- a/layout/generic/nsHTMLParts.h
+++ b/layout/generic/nsHTMLParts.h
@@ -170,16 +170,18 @@ NS_NewComboboxControlFrame(nsIPresShell*
 nsIFrame*
 NS_NewProgressFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewMeterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 nsIFrame*
 NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+nsIFrame*
+NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 class DetailsFrame;
 DetailsFrame*
 NS_NewDetailsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 
 // Table frame factories
 class nsTableWrapperFrame;
 nsTableWrapperFrame*
 NS_NewTableWrapperFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
--- a/layout/style/res/html.css
+++ b/layout/style/res/html.css
@@ -762,16 +762,23 @@ audio:not([controls]) {
   transform: translate(0) !important;
 }
 
 video > .caption-box {
   position: relative;
   overflow: hidden;
 }
 
+/* datetime elements */
+
+input[type="time"] > xul|datetimebox {
+  display: flex;
+  -moz-binding: url("chrome://global/content/bindings/datetimebox.xml#time-input");
+}
+
 /* details & summary */
 /* Need to revert Bug 1259889 Part 2 when removing details preference. */
 @supports -moz-bool-pref("dom.details_element.enabled") {
   details > summary:first-of-type,
   details > summary:-moz-native-anonymous {
     display: list-item;
     list-style: disclosure-closed inside;
   }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1130,16 +1130,19 @@ pref("dom.min_timeout_value", 4);
 pref("dom.min_background_timeout_value", 1000);
 
 // Don't use new input types
 pref("dom.experimental_forms", false);
 
 // Enable <input type=number>:
 pref("dom.forms.number", true);
 
+// Enable date/time input types
+pref("dom.forms.datetime", true);
+
 // Enable <input type=color> by default. It will be turned off for remaining
 // platforms which don't have a color picker implemented yet.
 pref("dom.forms.color", true);
 
 // Support for new @autocomplete values
 pref("dom.forms.autocomplete.experimental", false);
 
 // Enables requestAutocomplete DOM API on forms.
new file mode 100644
--- /dev/null
+++ b/toolkit/content/datetime-child.js
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DateTimeContentHelper",
+                                  "resource://gre/modules/DateTimeContentHelper.jsm");
+
+addEventListener("mozshowdatetimepicker", event => {
+  if (!event.isTrusted) {
+    return;
+  }
+
+  new DateTimeContentHelper(event.target, this);
+});
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -28,16 +28,17 @@ toolkit.jar:
    content/global/aboutwebrtc/aboutWebrtc.css   (aboutwebrtc/aboutWebrtc.css)
    content/global/aboutwebrtc/aboutWebrtc.js    (aboutwebrtc/aboutWebrtc.js)
    content/global/aboutwebrtc/aboutWebrtc.html (aboutwebrtc/aboutWebrtc.html)
    content/global/aboutSupport.js
 *  content/global/aboutSupport.xhtml
    content/global/aboutTelemetry.js
    content/global/aboutTelemetry.xhtml
    content/global/aboutTelemetry.css
+   content/global/datetime-child.js
    content/global/directionDetector.html
    content/global/plugins.html
    content/global/plugins.css
    content/global/browser-child.js
    content/global/browser-content.js
 *   content/global/buildconfig.html
    content/global/contentAreaUtils.js
 #ifndef MOZ_FENNEC
@@ -66,16 +67,18 @@ toolkit.jar:
    content/global/treeUtils.js
    content/global/viewZoomOverlay.js
    content/global/bindings/autocomplete.xml    (widgets/autocomplete.xml)
    content/global/bindings/browser.xml         (widgets/browser.xml)
    content/global/bindings/button.xml          (widgets/button.xml)
    content/global/bindings/checkbox.xml        (widgets/checkbox.xml)
    content/global/bindings/colorpicker.xml     (widgets/colorpicker.xml)
    content/global/bindings/datetimepicker.xml  (widgets/datetimepicker.xml)
+   content/global/bindings/datetimebox.xml     (widgets/datetimebox.xml)
+   content/global/bindings/datetimebox.css     (widgets/datetimebox.css)
 *  content/global/bindings/dialog.xml          (widgets/dialog.xml)
    content/global/bindings/editor.xml          (widgets/editor.xml)
    content/global/bindings/expander.xml        (widgets/expander.xml)
    content/global/bindings/filefield.xml       (widgets/filefield.xml)
 *  content/global/bindings/findbar.xml         (widgets/findbar.xml)
    content/global/bindings/general.xml         (widgets/general.xml)
    content/global/bindings/groupbox.xml        (widgets/groupbox.xml)
    content/global/bindings/listbox.xml         (widgets/listbox.xml)
new file mode 100644
--- /dev/null
+++ b/toolkit/content/widgets/datetimebox.css
@@ -0,0 +1,34 @@
+/* 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/. */
+
+@namespace url("http://www.w3.org/1999/xhtml");
+@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+.datetime-input-box-wrapper {
+  -moz-appearance: none;
+  display: inline-flex;
+  cursor: text;
+  padding: 0px;
+  background-color: -moz-Field;
+  color: -moz-FieldText;
+}
+
+.datetime-input {
+  text-align: center;
+}
+
+.datetime-separator {
+  margin: 0 !important;
+}
+
+.datetime-reset-button {
+  background-image: url(chrome://global/skin/icons/searchfield-cancel.svg);
+  background-repeat: no-repeat;
+  background-size: 12px, 12px;
+  border: none;
+  height: 12px;
+  width: 12px;
+  margin-left: 3px;
+  margin-top: 1px;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/content/widgets/datetimebox.xml
@@ -0,0 +1,373 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="datetimeboxBindings"
+   xmlns="http://www.mozilla.org/xbl"
+   xmlns:html="http://www.w3.org/1999/xhtml"
+   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+   xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <binding id="time-input"
+           extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base">
+    <resources>
+      <stylesheet src="chrome://global/content/textbox.css"/>
+      <stylesheet src="chrome://global/skin/textbox.css"/>
+      <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
+    </resources>
+
+    <implementation>
+      <constructor>
+      <![CDATA[
+        // TODO: localization
+        this.amIndicator = "AM";
+        this.pmIndicator = "PM";
+        this.placeHolder = "--";
+        this.separator = ":";
+
+        this.mHourField =
+          document.getAnonymousElementByAttribute(this, "anonid", "input-one");
+        this.mHourField.classList.add("numeric");
+        this.mMinuteField =
+          document.getAnonymousElementByAttribute(this, "anonid", "input-two");
+        this.mMinuteField.classList.add("numeric");
+        this.mAMPMField =
+          document.getAnonymousElementByAttribute(this, "anonid", "input-three");
+
+        this.mHourField.placeholder = this.placeHolder;
+        this.mMinuteField.placeholder = this.placeHolder;
+        this.mAMPMField.placeholder = this.placeHolder;
+
+        this.mHourSeparator =
+           document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
+        this.mHourSeparator.textContent = this.separator;
+        this.mSpaceSeparator =
+          document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
+        // space between time and am/pm field
+        this.mSpaceSeparator.textContent = " ";
+
+        this.mMinuteSeparator = null;
+        this.mSecondField = null;
+        this.mSecondSeparator = null;
+        this.mMillisecondsField = null;
+
+        if (this.mInputElement.value) {
+          this.setFieldsFromInputValue();
+        }
+        ]]>
+      </constructor>
+
+      <method name="insertSecondField">
+        <body>
+        <![CDATA[
+          if (this.mSecondField) {
+            return;
+          }
+
+          var container = this.mMinuteField.parentNode;
+
+          this.mMinuteSeparator =
+            document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+          this.mMinuteSeparator.textContent = this.separator;
+          this.mMinuteSeparator.setAttribute("class", "datetime-separator");
+          container.insertBefore(this.mMinuteSeparator, this.mSpaceSeparator);
+
+          this.mSecondField =
+            document.createElementNS("http://www.w3.org/1999/xhtml", "input");
+          this.mSecondField.classList.add("textbox-input");
+          this.mSecondField.classList.add("datetime-input");
+          this.mSecondField.classList.add("numeric");
+          this.mSecondField.setAttribute("size", "2");
+          this.mSecondField.setAttribute("maxlength", "2");
+          this.mSecondField.setAttribute("anonid", "input-four");
+
+          // Default to readonly to avoid step mismatch
+          this.mSecondField.setAttribute("readonly", "true");
+          this.mSecondField.placeholder = this.placeHolder;
+          container.insertBefore(this.mSecondField, this.mSpaceSeparator);
+        ]]>
+        </body>
+      </method>
+
+      <method name="setFieldsFromInputValue">
+        <body>
+        <![CDATA[
+          var value = this.mInputElement.value;
+          this.log("setFieldsFromInputValue: " + value);
+
+          if (!value) {
+            return;
+          }
+
+          var [hour, minute, second] = value.split(':');
+          if (hour >= 12) {
+            this.mAMPMField.value = this.pmIndicator;
+          } else {
+            this.mAMPMField.value = this.amIndicator;
+          }
+          hour = (hour % 12) || 12;
+          hour = (hour < 10) ? ("0" + hour) : hour;
+
+          this.mHourField.value = hour;
+          this.mMinuteField.value = minute;
+
+          // TODO: handle milliseconds.
+          if (second) {
+            this.insertSecondField();
+            this.mSecondField.value = second;
+          } else {
+            if (this.mSecondField) {
+              this.mSecondField.remove();
+              this.mSecondField = null;
+
+              this.mMinuteSeparator.remove();
+              this.mMinuteSeparator = null;
+            }
+          }
+        ]]>
+        </body>
+      </method>
+
+      <method name="setInputValueFromFields">
+       <body>
+       <![CDATA[
+         if (this.isEmpty(this.mHourField.value) ||
+             this.isEmpty(this.mMinuteField.value) ||
+             (this.mSecondField && this.isEmpty(this.mSecondField.value))) {
+          return;
+        }
+
+        var hour = Number(this.mHourField.value);
+        var ampm = this.mAMPMField.value;
+        if (ampm == this.pmIndicator && hour < 12) {
+          hour += 12;
+        } else if (ampm == this.amIndicator && hour == 12) {
+          hour = 0;
+        }
+
+        hour = (hour < 10) ? ("0" + hour) : hour;
+
+        var time = hour + ":" + this.mMinuteField.value;
+        if (this.mSecondField) {
+          time += ":" + this.mSecondField.value;
+        }
+
+        this.log("setInputValueFromFields: " + time);
+        this.mInputElement.value = time;
+      ]]>
+      </body>
+
+      </method>
+      <method name="clearInputFields">
+        <body>
+        <![CDATA[
+        this.mHourField.value = "";
+        this.mMinuteField.value = "";
+        this.mAMPMField.value = "";
+
+        // Remove it for now (as Chrome does).
+        if (this.mSecondField) {
+          this.mSecondField.remove();
+          this.mSecondField = null;
+
+          this.mMinuteSeparator.remove();
+          this.mMinuteSeparator = null;
+        }
+
+        // TODO: milliseconds
+        ]]>
+        </body>
+      </method>
+    </implementation>
+  </binding>
+
+  <binding id="datetime-input-base">
+    <resources>
+      <stylesheet src="chrome://global/content/textbox.css"/>
+      <stylesheet src="chrome://global/skin/textbox.css"/>
+      <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
+    </resources>
+
+    <content>
+      <html:div class="datetime-input-box-wrapper"
+                xbl:inherits="context,disabled,readonly">
+        <html:span>
+          <html:input anonid="input-one" class="textbox-input datetime-input"
+                      size="2" maxlength="2"
+                      xbl:inherits="disabled,readonly"/>
+          <html:span anonid="sep-first" class="datetime-separator"></html:span>
+          <html:input anonid="input-two" class="textbox-input datetime-input"
+                      size="2" maxlength="2"
+                      xbl:inherits="disabled,readonly"/>
+          <html:span anonid="sep-second" class="datetime-separator"></html:span>
+          <html:input anonid="input-three" class="textbox-input datetime-input"
+                      size="2" maxlength="2"
+                      xbl:inherits="disabled,readonly"/>
+        </html:span>
+
+        <html:button class="datetime-reset-button" anoid="reset-button"
+                     onclick="document.getBindingParent(this).clearInputFields();"/>
+      </html:div>
+    </content>
+
+    <implementation>
+      <constructor>
+      <![CDATA[
+        this.debug = true;
+        this.mInputElement = this.parentNode;
+        this.log("datetime-input-base ctor: this.mInputElement value: " +
+                 this.mInputElement.value);
+
+        this.mLastFocusedField = null;
+        this.advanceFocus = false;
+        this.mInputElement.addEventListener("focus", aEvent => {
+          if (aEvent.originalTarget == this.mInputElement) {
+            if (this.mLastFocusedField) {
+              this.mLastFocusedField.focus();
+            } else {
+              document.getAnonymousElementByAttribute(this, "anonid",
+                "input-one").focus();
+            }
+          }
+        }, true);
+
+        this.addEventListener("datetime-value-changed", () => {
+          this.setFieldsFromInputValue();
+        });
+      ]]>
+      </constructor>
+
+      <method name="log">
+        <parameter name="aMsg"/>
+        <body>
+        <![CDATA[
+          if (this.debug) {
+            dump("[DateTimeBox] " + aMsg + "\n");
+          }
+        ]]>
+        </body>
+      </method>
+
+      <method name="onFocus">
+        <parameter name="aEvent"/>
+        <body>
+        <![CDATA[
+          this.log("focus on: " + aEvent.originalTarget);
+
+          var target = aEvent.originalTarget;
+          if (target.type == "text") {
+            this.mLastFocusedField = target;
+            target.select();
+          }
+        ]]>
+        </body>
+      </method>
+
+      <method name="isEmpty">
+        <parameter name="aValue"/>
+        <body>
+          return (!aValue || 0 === aValue.length);
+        </body>
+      </method>
+
+      <method name="clearInputFields">
+        <body>
+          // Not implemented
+        </body>
+      </method>
+
+      <method name="setFieldsFromInputValue">
+        <body>
+          // Not implemented
+        </body>
+      </method>
+
+      <method name="setInputValueFromFields">
+        <body>
+          // Not implemented
+        </body>
+      </method>
+
+    </implementation>
+
+    <handlers>
+
+      <handler event="keyup">
+      <![CDATA[
+        if (!this.advanceFocus) {
+          return;
+        }
+
+        var focusedInput = this.mLastFocusedField;
+        if (focusedInput.value.length >= focusedInput.maxLength) {
+          var next = focusedInput.nextElementSibling;
+          while(next != null) {
+            if (next.type == "text") {
+              next.focus();
+              this.advanceFocus = false;
+              break;
+            }
+            next = next.nextElementSibling;
+          }
+        }
+      ]]>
+      </handler>
+
+      <handler event="focus" phase="capturing">
+        this.onFocus(event);
+      </handler>
+
+      <handler event="blur" phase="capturing">
+      <![CDATA[
+        this.log("blur");
+        this.setInputValueFromFields();
+      ]]>
+      </handler>
+
+      <handler event="click" phase="capturing">
+      <![CDATA[
+        this.log("click on: " + event.originalTarget);
+
+        if (event.originalTarget == document.getAnonymousElementByAttribute(this, "anonid", "reset-button")) {
+          this.log("reset-button");
+        }
+
+        if (!(event.originalTarget instanceof HTMLButtonElement)) {
+          this.dispatchEvent(new CustomEvent("ShowDateTimePicker"));
+        }
+      ]]>
+      </handler>
+
+      <handler event="keypress" phase="capturing">
+      <![CDATA[
+        var targetField = event.originalTarget;
+        var key = event.key;
+        if (key != "Tab" && key != "Backspace" &&
+            targetField.classList.contains("numeric") && !key.match(/[0-9]/)) {
+          event.preventDefault();
+          return false;
+        }
+
+        this.advanceFocus = (key == "Tab") ? false : true;
+      ]]>
+      </handler>
+
+      <handler event="keypress" keycode="VK_UP">
+      <![CDATA[
+        this.mInputElement.stepUp();
+        this.setFieldsFromInputValue();
+      ]]>
+      </handler>
+
+      <handler event="keypress" keycode="VK_DOWN">
+      <![CDATA[
+        this.mInputElement.stepDown();
+        this.setFieldsFromInputValue();
+      ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+</bindings>
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -389,16 +389,22 @@
           this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);
 
           if (this.hasAttribute("selectmenulist")) {
             this.messageManager.addMessageListener("Forms:ShowDropDown", this);
             this.messageManager.addMessageListener("Forms:HideDropDown", this);
             this.messageManager.loadFrameScript("chrome://global/content/select-child.js", true);
           }
 
+          if (this.hasAttribute("datetimepicker")) {
+            this.messageManager.addMessageListener("Forms:ShowDateTimePicker", this);
+            this.messageManager.addMessageListener("Forms:HideDateTimePicker", this);
+            this.messageManager.loadFrameScript("chrome://global/content/datetime-child.js", true);
+          }
+
           if (!this.hasAttribute("disablehistory")) {
             Services.obs.addObserver(this, "browser:purge-session-history", true);
           }
 
           let jsm = "resource://gre/modules/RemoteController.jsm";
           let RemoteController = Components.utils.import(jsm, {}).RemoteController;
           this._controller = new RemoteController(this);
           this.controllers.appendController(this._controller);
@@ -474,16 +480,28 @@
 
               let menulist = document.getElementById(this.getAttribute("selectmenulist"));
               menulist.menupopup.style.direction = data.direction;
               this._selectParentHelper.populate(menulist, data.options, data.selectedIndex, this._fullZoom);
               this._selectParentHelper.open(this, menulist, data.rect);
               break;
             }
 
+            case "Forms:ShowDateTimePicker": {
+              dump("Forms:ShowDateTimePicker\n");
+              if (!this._dateTimeParentHelper) {
+                this._dateTimeParentHelper =
+                  Cu.import("resource://gre/modules/DateTimeParentHelper.jsm", {}).DateTimeParentHelper;
+              }
+
+              let picker = document.getElementById(this.getAttribute("datetimepicker"));
+              this._dateTimeParentHelper.open(this, picker, data.rect);
+              break;
+            }
+
             case "FullZoomChange": {
               this._fullZoom = data.value;
               let event = document.createEvent("Events");
               event.initEvent("FullZoomChange", true, false);
               this.dispatchEvent(event);
               break;
             }
 
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/DateTimeContentHelper.jsm
@@ -0,0 +1,49 @@
+/* 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/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+                                  "resource://gre/modules/BrowserUtils.jsm");
+
+this.EXPORTED_SYMBOLS = [
+  "DateTimeContentHelper"
+];
+
+this.DateTimeContentHelper = function(aElement, aGlobal) {
+	
+  this.element = aElement;
+  this.global = aGlobal;
+  this.init();
+  this.showPicker();
+}
+
+this.DateTimeContentHelper.prototype = {
+  init: function() {
+    // TODO
+  },
+
+  showPicker: function() {
+    let rect = this._getBoundingContentRect();
+
+    this.global.sendAsyncMessage("Forms:ShowDateTimePicker", {
+      rect: rect,
+      direction: getComputedDirection(this.element)
+    });
+  },
+
+  _getBoundingContentRect: function() {
+    return BrowserUtils.getElementBoundingScreenRect(this.element);
+  },
+};
+
+function getComputedDirection(element) {
+  return element.ownerDocument.defaultView.getComputedStyle(element).getPropertyValue("direction");
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/DateTimeParentHelper.jsm
@@ -0,0 +1,21 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "DateTimeParentHelper"
+];
+
+var currentBrowser = null;
+
+this.DateTimeParentHelper = {
+  open: function(browser, panel, rect) {
+    panel.hidden = false;
+    currentBrowser = browser;
+
+    panel.openPopupAtScreenRect("after_start", rect.left, rect.top, rect.width,
+                                rect.height, false, false);
+  },
+};
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -30,16 +30,18 @@ EXTRA_JS_MODULES += [
     'BinarySearch.jsm',
     'BrowserUtils.jsm',
     'CanonicalJSON.jsm',
     'CertUtils.jsm',
     'CharsetMenu.jsm',
     'ClientID.jsm',
     'Color.jsm',
     'Console.jsm',
+    'DateTimeContentHelper.jsm',
+    'DateTimeParentHelper.jsm',
     'debug.js',
     'DeferredTask.jsm',
     'Deprecated.jsm',
     'FileUtils.jsm',
     'Finder.jsm',
     'FinderHighlighter.jsm',
     'FinderIterator.jsm',
     'Geometry.jsm',