зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1288591 - Implement the layout for <input type=time>. r=mconley, r=dholbert, r=smaug
This commit is contained in:
Родитель
b59026eb98
Коммит
b3014cc00b
|
@ -154,6 +154,12 @@
|
||||||
flip="none"
|
flip="none"
|
||||||
level="parent"/>
|
level="parent"/>
|
||||||
|
|
||||||
|
<panel id="DateTimePickerPanel"
|
||||||
|
hidden="true"
|
||||||
|
noautofocus="true"
|
||||||
|
consumeoutsideclicks="false"
|
||||||
|
level="parent"/>
|
||||||
|
|
||||||
<!-- for select dropdowns. The menupopup is what shows the list of options,
|
<!-- for select dropdowns. The menupopup is what shows the list of options,
|
||||||
and the popuponly menulist makes things like the menuactive attributes
|
and the popuponly menulist makes things like the menuactive attributes
|
||||||
work correctly on the menupopup. ContentSelectDropdown expects the
|
work correctly on the menupopup. ContentSelectDropdown expects the
|
||||||
|
@ -1056,7 +1062,8 @@
|
||||||
tabcontainer="tabbrowser-tabs"
|
tabcontainer="tabbrowser-tabs"
|
||||||
contentcontextmenu="contentAreaContextMenu"
|
contentcontextmenu="contentAreaContextMenu"
|
||||||
autocompletepopup="PopupAutoComplete"
|
autocompletepopup="PopupAutoComplete"
|
||||||
selectmenulist="ContentSelectDropdown"/>
|
selectmenulist="ContentSelectDropdown"
|
||||||
|
datetimepicker="DateTimePickerPanel"/>
|
||||||
</vbox>
|
</vbox>
|
||||||
<vbox id="browser-border-end" hidden="true" layer="true"/>
|
<vbox id="browser-border-end" hidden="true" layer="true"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<xul:vbox flex="1" class="browserContainer">
|
<xul:vbox flex="1" class="browserContainer">
|
||||||
<xul:stack flex="1" class="browserStack" anonid="browserStack">
|
<xul:stack flex="1" class="browserStack" anonid="browserStack">
|
||||||
<xul:browser anonid="initialBrowser" type="content-primary" message="true" messagemanagergroup="browsers"
|
<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:stack>
|
||||||
</xul:vbox>
|
</xul:vbox>
|
||||||
</xul:hbox>
|
</xul:hbox>
|
||||||
|
@ -1886,6 +1886,10 @@
|
||||||
if (this.hasAttribute("selectmenulist"))
|
if (this.hasAttribute("selectmenulist"))
|
||||||
b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
|
b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
|
||||||
|
|
||||||
|
if (this.hasAttribute("datetimepicker")) {
|
||||||
|
b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
|
||||||
|
}
|
||||||
|
|
||||||
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
|
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
|
||||||
|
|
||||||
if (aParams.relatedBrowser) {
|
if (aParams.relatedBrowser) {
|
||||||
|
|
|
@ -33,6 +33,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-s
|
||||||
["ContentClick", "resource:///modules/ContentClick.jsm"],
|
["ContentClick", "resource:///modules/ContentClick.jsm"],
|
||||||
["ContentPrefServiceParent", "resource://gre/modules/ContentPrefServiceParent.jsm"],
|
["ContentPrefServiceParent", "resource://gre/modules/ContentPrefServiceParent.jsm"],
|
||||||
["ContentSearch", "resource:///modules/ContentSearch.jsm"],
|
["ContentSearch", "resource:///modules/ContentSearch.jsm"],
|
||||||
|
["DateTimePickerHelper", "resource://gre/modules/DateTimePickerHelper.jsm"],
|
||||||
["DirectoryLinksProvider", "resource:///modules/DirectoryLinksProvider.jsm"],
|
["DirectoryLinksProvider", "resource:///modules/DirectoryLinksProvider.jsm"],
|
||||||
["Feeds", "resource:///modules/Feeds.jsm"],
|
["Feeds", "resource:///modules/Feeds.jsm"],
|
||||||
["FileUtils", "resource://gre/modules/FileUtils.jsm"],
|
["FileUtils", "resource://gre/modules/FileUtils.jsm"],
|
||||||
|
@ -1022,6 +1023,7 @@ BrowserGlue.prototype = {
|
||||||
CaptivePortalWatcher.init();
|
CaptivePortalWatcher.init();
|
||||||
|
|
||||||
AutoCompletePopup.init();
|
AutoCompletePopup.init();
|
||||||
|
DateTimePickerHelper.init();
|
||||||
|
|
||||||
this._firstWindowTelemetry(aWindow);
|
this._firstWindowTelemetry(aWindow);
|
||||||
this._firstWindowLoaded();
|
this._firstWindowLoaded();
|
||||||
|
@ -1053,6 +1055,7 @@ BrowserGlue.prototype = {
|
||||||
webrtcUI.uninit();
|
webrtcUI.uninit();
|
||||||
FormValidationHandler.uninit();
|
FormValidationHandler.uninit();
|
||||||
AutoCompletePopup.uninit();
|
AutoCompletePopup.uninit();
|
||||||
|
DateTimePickerHelper.uninit();
|
||||||
if (AppConstants.NIGHTLY_BUILD) {
|
if (AppConstants.NIGHTLY_BUILD) {
|
||||||
AddonWatcher.uninit();
|
AddonWatcher.uninit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -272,6 +272,7 @@ GK_ATOM(dataType, "data-type")
|
||||||
GK_ATOM(dateTime, "date-time")
|
GK_ATOM(dateTime, "date-time")
|
||||||
GK_ATOM(datasources, "datasources")
|
GK_ATOM(datasources, "datasources")
|
||||||
GK_ATOM(datetime, "datetime")
|
GK_ATOM(datetime, "datetime")
|
||||||
|
GK_ATOM(datetimebox, "datetimebox")
|
||||||
GK_ATOM(dblclick, "dblclick")
|
GK_ATOM(dblclick, "dblclick")
|
||||||
GK_ATOM(dd, "dd")
|
GK_ATOM(dd, "dd")
|
||||||
GK_ATOM(debug, "debug")
|
GK_ATOM(debug, "debug")
|
||||||
|
@ -1985,6 +1986,7 @@ GK_ATOM(colorControlFrame, "colorControlFrame")
|
||||||
GK_ATOM(columnSetFrame, "ColumnSetFrame")
|
GK_ATOM(columnSetFrame, "ColumnSetFrame")
|
||||||
GK_ATOM(comboboxControlFrame, "ComboboxControlFrame")
|
GK_ATOM(comboboxControlFrame, "ComboboxControlFrame")
|
||||||
GK_ATOM(comboboxDisplayFrame, "ComboboxDisplayFrame")
|
GK_ATOM(comboboxDisplayFrame, "ComboboxDisplayFrame")
|
||||||
|
GK_ATOM(dateTimeControlFrame, "DateTimeControlFrame")
|
||||||
GK_ATOM(deckFrame, "DeckFrame")
|
GK_ATOM(deckFrame, "DeckFrame")
|
||||||
GK_ATOM(detailsFrame, "DetailsFrame")
|
GK_ATOM(detailsFrame, "DetailsFrame")
|
||||||
GK_ATOM(fieldSetFrame, "FieldSetFrame")
|
GK_ATOM(fieldSetFrame, "FieldSetFrame")
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
#include "nsIIOService.h"
|
#include "nsIIOService.h"
|
||||||
#include "nsDocument.h"
|
#include "nsDocument.h"
|
||||||
#include "nsAttrValueOrString.h"
|
#include "nsAttrValueOrString.h"
|
||||||
|
#include "nsDateTimeControlFrame.h"
|
||||||
|
|
||||||
#include "nsPresState.h"
|
#include "nsPresState.h"
|
||||||
#include "nsIDOMEvent.h"
|
#include "nsIDOMEvent.h"
|
||||||
|
@ -2707,6 +2708,82 @@ HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
|
||||||
SetFilesOrDirectories(array, true);
|
SetFilesOrDirectories(array, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue)
|
||||||
|
{
|
||||||
|
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(),
|
||||||
|
static_cast<nsIDOMHTMLInputElement*>(this),
|
||||||
|
NS_LITERAL_STRING("MozOpenDateTimePicker"),
|
||||||
|
true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue)
|
||||||
|
{
|
||||||
|
if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mDateTimeInputBoxValue = new DateTimeValue(aValue);
|
||||||
|
nsContentUtils::DispatchChromeEvent(OwnerDoc(),
|
||||||
|
static_cast<nsIDOMHTMLInputElement*>(this),
|
||||||
|
NS_LITERAL_STRING("MozUpdateDateTimePicker"),
|
||||||
|
true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
HTMLInputElement::CloseDateTimePicker()
|
||||||
|
{
|
||||||
|
if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsContentUtils::DispatchChromeEvent(OwnerDoc(),
|
||||||
|
static_cast<nsIDOMHTMLInputElement*>(this),
|
||||||
|
NS_LITERAL_STRING("MozCloseDateTimePicker"),
|
||||||
|
true, true);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
HTMLInputElement::MozIsTextField(bool aExcludePassword)
|
HTMLInputElement::MozIsTextField(bool aExcludePassword)
|
||||||
{
|
{
|
||||||
|
@ -2733,6 +2810,28 @@ HTMLInputElement::GetOwnerNumberControl()
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HTMLInputElement*
|
||||||
|
HTMLInputElement::GetOwnerDateTimeControl()
|
||||||
|
{
|
||||||
|
if (IsInNativeAnonymousSubtree() &&
|
||||||
|
mType == NS_FORM_INPUT_TEXT &&
|
||||||
|
GetParent() &&
|
||||||
|
GetParent()->GetParent() &&
|
||||||
|
GetParent()->GetParent()->GetParent() &&
|
||||||
|
GetParent()->GetParent()->GetParent()->GetParent()) {
|
||||||
|
// Yes, this is very very deep.
|
||||||
|
HTMLInputElement* ownerDateTimeControl =
|
||||||
|
HTMLInputElement::FromContentOrNull(
|
||||||
|
GetParent()->GetParent()->GetParent()->GetParent());
|
||||||
|
if (ownerDateTimeControl &&
|
||||||
|
ownerDateTimeControl->mType == NS_FORM_INPUT_TIME) {
|
||||||
|
return ownerDateTimeControl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
|
HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
|
||||||
{
|
{
|
||||||
|
@ -2740,6 +2839,19 @@ HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
HTMLInputElement::SetUserInput(const nsAString& aInput,
|
||||||
|
const mozilla::Maybe<nsIPrincipal*>& aPrincipal) {
|
||||||
|
MOZ_ASSERT(aPrincipal.isSome());
|
||||||
|
|
||||||
|
if (mType == NS_FORM_INPUT_FILE &&
|
||||||
|
!nsContentUtils::IsSystemPrincipal(aPrincipal.value())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetUserInput(aInput);
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
HTMLInputElement::SetUserInput(const nsAString& aValue)
|
HTMLInputElement::SetUserInput(const nsAString& aValue)
|
||||||
{
|
{
|
||||||
|
@ -3164,6 +3276,12 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
|
||||||
if (frame) {
|
if (frame) {
|
||||||
frame->UpdateForValueChange();
|
frame->UpdateForValueChange();
|
||||||
}
|
}
|
||||||
|
} else if (mType == NS_FORM_INPUT_TIME &&
|
||||||
|
!IsExperimentalMobileType(mType)) {
|
||||||
|
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
|
||||||
|
if (frame) {
|
||||||
|
frame->UpdateInputBoxValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!mParserCreating) {
|
if (!mParserCreating) {
|
||||||
OnValueChanged(/* aNotify = */ true,
|
OnValueChanged(/* aNotify = */ true,
|
||||||
|
@ -3466,6 +3584,15 @@ HTMLInputElement::Blur(ErrorResult& aError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
|
||||||
|
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
|
||||||
|
if (frame) {
|
||||||
|
frame->HandleBlurEvent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nsGenericHTMLElement::Blur(aError);
|
nsGenericHTMLElement::Blur(aError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3485,6 +3612,14 @@ HTMLInputElement::Focus(ErrorResult& aError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
|
||||||
|
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
|
||||||
|
if (frame) {
|
||||||
|
frame->HandleFocusEvent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mType != NS_FORM_INPUT_FILE) {
|
if (mType != NS_FORM_INPUT_FILE) {
|
||||||
nsGenericHTMLElement::Focus(aError);
|
nsGenericHTMLElement::Focus(aError);
|
||||||
return;
|
return;
|
||||||
|
@ -3788,7 +3923,7 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
|
||||||
// Experimental mobile types rely on the system UI to prevent users to not
|
// Experimental mobile types rely on the system UI to prevent users to not
|
||||||
// set invalid values but we have to be extra-careful. Especially if the
|
// set invalid values but we have to be extra-careful. Especially if the
|
||||||
// option has been enabled on desktop.
|
// option has been enabled on desktop.
|
||||||
if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
|
if (IsExperimentalMobileType(mType)) {
|
||||||
nsAutoString aValue;
|
nsAutoString aValue;
|
||||||
GetValueInternal(aValue);
|
GetValueInternal(aValue);
|
||||||
nsresult rv =
|
nsresult rv =
|
||||||
|
@ -3809,6 +3944,18 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mType == NS_FORM_INPUT_TIME &&
|
||||||
|
!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 (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
|
if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
|
||||||
if (mNumberControlSpinnerIsSpinning) {
|
if (mNumberControlSpinnerIsSpinning) {
|
||||||
// If the timer is running the user has depressed the mouse on one of the
|
// If the timer is running the user has depressed the mouse on one of the
|
||||||
|
@ -6682,6 +6829,11 @@ HTMLInputElement::AddStates(EventStates aStates)
|
||||||
HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
|
HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
|
||||||
if (ownerNumberControl) {
|
if (ownerNumberControl) {
|
||||||
ownerNumberControl->AddStates(focusStates);
|
ownerNumberControl->AddStates(focusStates);
|
||||||
|
} else {
|
||||||
|
HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
|
||||||
|
if (ownerDateTimeControl) {
|
||||||
|
ownerDateTimeControl->AddStates(focusStates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6698,6 +6850,11 @@ HTMLInputElement::RemoveStates(EventStates aStates)
|
||||||
HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
|
HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
|
||||||
if (ownerNumberControl) {
|
if (ownerNumberControl) {
|
||||||
ownerNumberControl->RemoveStates(focusStates);
|
ownerNumberControl->RemoveStates(focusStates);
|
||||||
|
} else {
|
||||||
|
HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
|
||||||
|
if (ownerDateTimeControl) {
|
||||||
|
ownerDateTimeControl->RemoveStates(focusStates);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6875,12 +7032,14 @@ HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t*
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (mType == NS_FORM_INPUT_FILE ||
|
if (mType == NS_FORM_INPUT_FILE ||
|
||||||
mType == NS_FORM_INPUT_NUMBER) {
|
mType == NS_FORM_INPUT_NUMBER ||
|
||||||
|
mType == NS_FORM_INPUT_TIME) {
|
||||||
if (aTabIndex) {
|
if (aTabIndex) {
|
||||||
// We only want our native anonymous child to be tabable to, not ourself.
|
// We only want our native anonymous child to be tabable to, not ourself.
|
||||||
*aTabIndex = -1;
|
*aTabIndex = -1;
|
||||||
}
|
}
|
||||||
if (mType == NS_FORM_INPUT_NUMBER) {
|
if (mType == NS_FORM_INPUT_NUMBER ||
|
||||||
|
mType == NS_FORM_INPUT_TIME) {
|
||||||
*aIsFocusable = true;
|
*aIsFocusable = true;
|
||||||
} else {
|
} else {
|
||||||
*aIsFocusable = defaultFocusable;
|
*aIsFocusable = defaultFocusable;
|
||||||
|
|
|
@ -772,7 +772,24 @@ public:
|
||||||
void MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles);
|
void MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles);
|
||||||
void MozSetDirectory(const nsAString& aDirectoryPath, ErrorResult& aRv);
|
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();
|
||||||
|
|
||||||
HTMLInputElement* GetOwnerNumberControl();
|
HTMLInputElement* GetOwnerNumberControl();
|
||||||
|
HTMLInputElement* GetOwnerDateTimeControl();
|
||||||
|
|
||||||
void StartNumberControlSpinnerSpin();
|
void StartNumberControlSpinnerSpin();
|
||||||
enum SpinnerStopState {
|
enum SpinnerStopState {
|
||||||
|
@ -803,7 +820,8 @@ public:
|
||||||
|
|
||||||
nsIEditor* GetEditor();
|
nsIEditor* GetEditor();
|
||||||
|
|
||||||
// XPCOM SetUserInput() is OK
|
void SetUserInput(const nsAString& aInput,
|
||||||
|
const mozilla::Maybe<nsIPrincipal*>& aPrincipal);
|
||||||
|
|
||||||
// XPCOM GetPhonetic() is OK
|
// XPCOM GetPhonetic() is OK
|
||||||
|
|
||||||
|
@ -1471,6 +1489,12 @@ protected:
|
||||||
*/
|
*/
|
||||||
Decimal mRangeThumbDragStartValue;
|
Decimal mRangeThumbDragStartValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current value in the input box, in DateTimeValue dictionary format, see
|
||||||
|
* HTMLInputElement.webidl for details.
|
||||||
|
*/
|
||||||
|
nsAutoPtr<DateTimeValue> mDateTimeInputBoxValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The selection properties cache for number controls. This is needed because
|
* The selection properties cache for number controls. This is needed because
|
||||||
* the number controls don't recycle their text field, so the normal cache in
|
* the number controls don't recycle their text field, so the normal cache in
|
||||||
|
@ -1561,7 +1585,8 @@ private:
|
||||||
static bool MayFireChangeOnBlur(uint8_t aType) {
|
static bool MayFireChangeOnBlur(uint8_t aType) {
|
||||||
return IsSingleLineTextControl(false, aType) ||
|
return IsSingleLineTextControl(false, aType) ||
|
||||||
aType == NS_FORM_INPUT_RANGE ||
|
aType == NS_FORM_INPUT_RANGE ||
|
||||||
aType == NS_FORM_INPUT_NUMBER;
|
aType == NS_FORM_INPUT_NUMBER ||
|
||||||
|
aType == NS_FORM_INPUT_TIME;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct nsFilePickerFilter {
|
struct nsFilePickerFilter {
|
||||||
|
|
|
@ -18,6 +18,7 @@ MOCHITEST_CHROME_MANIFESTS += [
|
||||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||||
|
|
||||||
XPIDL_SOURCES += [
|
XPIDL_SOURCES += [
|
||||||
|
'nsIDateTimeInputArea.idl',
|
||||||
'nsIFormSubmitObserver.idl',
|
'nsIFormSubmitObserver.idl',
|
||||||
'nsIHTMLMenu.idl',
|
'nsIHTMLMenu.idl',
|
||||||
'nsIImageDocument.idl',
|
'nsIImageDocument.idl',
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* -*- 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 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();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current state of the picker, true if it's opened, false otherwise.
|
||||||
|
*/
|
||||||
|
void setPickerState(in boolean isOpen);
|
||||||
|
};
|
|
@ -266,8 +266,11 @@ nsIFormControl::IsSingleLineTextControl(bool aExcludePassword, uint32_t aType)
|
||||||
aType == NS_FORM_INPUT_TEL ||
|
aType == NS_FORM_INPUT_TEL ||
|
||||||
aType == NS_FORM_INPUT_URL ||
|
aType == NS_FORM_INPUT_URL ||
|
||||||
// TODO: those are temporary until bug 773205 is fixed.
|
// TODO: those are temporary until bug 773205 is fixed.
|
||||||
aType == NS_FORM_INPUT_DATE ||
|
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
|
||||||
|
// On Android/B2G, date/time input appears as a normal text box.
|
||||||
aType == NS_FORM_INPUT_TIME ||
|
aType == NS_FORM_INPUT_TIME ||
|
||||||
|
#endif
|
||||||
|
aType == NS_FORM_INPUT_DATE ||
|
||||||
aType == NS_FORM_INPUT_MONTH ||
|
aType == NS_FORM_INPUT_MONTH ||
|
||||||
aType == NS_FORM_INPUT_WEEK ||
|
aType == NS_FORM_INPUT_WEEK ||
|
||||||
(!aExcludePassword && aType == NS_FORM_INPUT_PASSWORD);
|
(!aExcludePassword && aType == NS_FORM_INPUT_PASSWORD);
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="reftest-wait">
|
||||||
|
<!-- In this case we're using reftest-wait to make sure the test doesn't
|
||||||
|
get snapshotted before it's been focused. We're not testing
|
||||||
|
invalidation so we don't need to listen for MozReftestInvalidate.
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<script>
|
||||||
|
function focusHandler() {
|
||||||
|
setTimeout(function() {
|
||||||
|
document.documentElement.removeAttribute("class");
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body onload="document.getElementById('t').focus();">
|
||||||
|
<input type="time" id="t" onfocus="focusHandler();"
|
||||||
|
style="-moz-appearance: none;">
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="reftest-wait">
|
||||||
|
<!-- In this case we're using reftest-wait to make sure the test doesn't
|
||||||
|
get snapshotted before it's been focused. We're not testing
|
||||||
|
invalidation so we don't need to listen for MozReftestInvalidate.
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<script>
|
||||||
|
function focusHandler() {
|
||||||
|
setTimeout(function() {
|
||||||
|
document.documentElement.removeAttribute("class");
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<input type="time" autofocus onfocus="focusHandler();"
|
||||||
|
style="-moz-appearance: none;">
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
default-preferences pref(dom.forms.number,true)
|
default-preferences pref(dom.forms.number,true) pref(dom.forms.datetime,true)
|
||||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-load.html input-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-load.html input-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-create.html input-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-create.html input-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-number.html input-number-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-number.html input-number-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||||
|
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == input-time.html input-time-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == button-load.html button-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == button-load.html button-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == button-create.html button-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == button-create.html button-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||||
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == textarea-load.html textarea-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
skip-if(B2G||Mulet) fuzzy-if(skiaContent,1,3) needs-focus == textarea-load.html textarea-ref.html # B2G timed out waiting for reftest-wait to be removed # Initial mulet triage: parity with B2G/B2G Desktop
|
||||||
|
|
|
@ -35,6 +35,10 @@ skip-if = buildapp == 'mulet'
|
||||||
skip-if = android_version == '18' # Android, bug 1147974
|
skip-if = android_version == '18' # Android, bug 1147974
|
||||||
[test_input_color_picker_update.html]
|
[test_input_color_picker_update.html]
|
||||||
skip-if = android_version == '18' # Android, bug 1147974
|
skip-if = android_version == '18' # Android, bug 1147974
|
||||||
|
[test_input_datetime_focus_blur.html]
|
||||||
|
skip-if = os == "android" || appname == "b2g"
|
||||||
|
[test_input_datetime_tabindex.html]
|
||||||
|
skip-if = os == "android" || appname == "b2g"
|
||||||
[test_input_defaultValue.html]
|
[test_input_defaultValue.html]
|
||||||
[test_input_email.html]
|
[test_input_email.html]
|
||||||
[test_input_event.html]
|
[test_input_event.html]
|
||||||
|
@ -64,6 +68,8 @@ skip-if = (toolkit == 'gonk' && debug) #debug-only failure; bug 926546
|
||||||
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
|
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
|
||||||
[test_input_sanitization.html]
|
[test_input_sanitization.html]
|
||||||
[test_input_textarea_set_value_no_scroll.html]
|
[test_input_textarea_set_value_no_scroll.html]
|
||||||
|
[test_input_time_key_events.html]
|
||||||
|
skip-if = os == "android" || appname == "b2g"
|
||||||
[test_input_types_pref.html]
|
[test_input_types_pref.html]
|
||||||
[test_input_typing_sanitization.html]
|
[test_input_typing_sanitization.html]
|
||||||
skip-if = buildapp == 'mulet'
|
skip-if = buildapp == 'mulet'
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<title>Test focus/blur behaviour for <input type='time'></title>
|
||||||
|
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=1288591">Mozilla Bug 1288591</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content">
|
||||||
|
<input id="input" type="time">
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
<script type="application/javascript">
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for Bug 1288591.
|
||||||
|
* This test checks whether date/time input types' .focus()/.blur() works
|
||||||
|
* correctly. This test also checks when focusing on an date/time input element,
|
||||||
|
* the focus is redirected to the anonymous text control, but the
|
||||||
|
* document.activeElement still returns date/time input element.
|
||||||
|
**/
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
SimpleTest.waitForFocus(function() {
|
||||||
|
test();
|
||||||
|
SimpleTest.finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
let time = document.getElementById("input");
|
||||||
|
time.focus();
|
||||||
|
|
||||||
|
// The active element returns the input type=time.
|
||||||
|
let activeElement = document.activeElement;
|
||||||
|
is(activeElement, time, "activeElement should be the time element");
|
||||||
|
is(activeElement.localName, "input", "activeElement should be an input element");
|
||||||
|
is(activeElement.type, "time", "activeElement should be of type time");
|
||||||
|
|
||||||
|
// Use FocusManager to check that the actual focus is on the anonymous
|
||||||
|
// text control.
|
||||||
|
let fm = SpecialPowers.Cc["@mozilla.org/focus-manager;1"]
|
||||||
|
.getService(SpecialPowers.Ci.nsIFocusManager);
|
||||||
|
let focusedElement = fm.focusedElement;
|
||||||
|
is(focusedElement.localName, "input", "focusedElement should be an input element");
|
||||||
|
is(focusedElement.type, "text", "focusedElement should be of type text");
|
||||||
|
|
||||||
|
time.blur();
|
||||||
|
isnot(document.activeElement, time, "activeElement should no longer be the time element");
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,72 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<title>Test tabindex attribute for <input type='time'></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=1288591">Mozilla Bug 1288591</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content">
|
||||||
|
<input id="time1" type="time" tabindex="0">
|
||||||
|
<input id="time2" type="time" tabindex="-1">
|
||||||
|
<input id="time3" type="time" tabindex="0">
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
<script type="application/javascript">
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for Bug 1288591.
|
||||||
|
* This test checks whether date/time input types' tabindex attribute works
|
||||||
|
* correctly.
|
||||||
|
**/
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
SimpleTest.waitForFocus(function() {
|
||||||
|
test();
|
||||||
|
SimpleTest.finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
let time1 = document.getElementById("time1");
|
||||||
|
let time2 = document.getElementById("time2");
|
||||||
|
let time3 = document.getElementById("time3");
|
||||||
|
|
||||||
|
time1.focus();
|
||||||
|
is(document.activeElement, time1,
|
||||||
|
"input element with tabindex=0 is focusable");
|
||||||
|
|
||||||
|
// Advance to time1 minute field
|
||||||
|
synthesizeKey("VK_TAB", {});
|
||||||
|
is(document.activeElement, time1,
|
||||||
|
"input element with tabindex=0 is tabbable");
|
||||||
|
|
||||||
|
// Advance to time1 AM/PM field
|
||||||
|
synthesizeKey("VK_TAB", {});
|
||||||
|
is(document.activeElement, time1,
|
||||||
|
"input element with tabindex=0 is tabbable");
|
||||||
|
|
||||||
|
// Advance to next element
|
||||||
|
synthesizeKey("VK_TAB", {});
|
||||||
|
is(document.activeElement, time3,
|
||||||
|
"input element with tabindex=-1 is not tabbable");
|
||||||
|
|
||||||
|
time2.focus();
|
||||||
|
is(document.activeElement, time2,
|
||||||
|
"input element with tabindex=-1 is still focusable");
|
||||||
|
|
||||||
|
// Changing the tabindex attribute dynamically.
|
||||||
|
time3.setAttribute("tabindex", "-1");
|
||||||
|
synthesizeKey("VK_TAB", {}); // need only one TAB since time2 is not tabbable
|
||||||
|
isnot(document.activeElement, time3,
|
||||||
|
"element with tabindex changed to -1 should not be tabbable");
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,197 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1288591
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<title>Test key events for time control</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"/>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1288591">Mozilla Bug 1288591</a>
|
||||||
|
<p id="display"></p>
|
||||||
|
<div id="content">
|
||||||
|
<input id="input" type="time">
|
||||||
|
</div>
|
||||||
|
<pre id="test">
|
||||||
|
<script type="application/javascript">
|
||||||
|
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
// Turn off Spatial Navigation because it hijacks arrow keydown events:
|
||||||
|
SimpleTest.waitForFocus(function() {
|
||||||
|
SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, function() {
|
||||||
|
test();
|
||||||
|
SimpleTest.finish();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var testData = [
|
||||||
|
/**
|
||||||
|
* keys: keys to send to the input element.
|
||||||
|
* initialVal: initial value set to the input element.
|
||||||
|
* expectedVal: expected value of the input element after sending the keys.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
// Type 1030 and select AM.
|
||||||
|
keys: ["1030", "VK_DOWN"],
|
||||||
|
initialVal: "",
|
||||||
|
expectedVal: "10:30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Type 3 in the hour field will automatically advance to the minute field.
|
||||||
|
keys: ["330", "VK_DOWN"],
|
||||||
|
initialVal: "",
|
||||||
|
expectedVal: "03:30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Type 5 in the hour field will automatically advance to the minute field.
|
||||||
|
// Type 7 in the minute field will automatically advance to the AM/PM field.
|
||||||
|
keys: ["57", "VK_DOWN"],
|
||||||
|
initialVal: "",
|
||||||
|
expectedVal: "05:07"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Advance to AM/PM field and change to PM.
|
||||||
|
keys: ["VK_TAB", "VK_TAB", "VK_DOWN"],
|
||||||
|
initialVal: "10:30",
|
||||||
|
expectedVal: "22:30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Right key should do the same thing as TAB key.
|
||||||
|
keys: ["VK_RIGHT", "VK_RIGHT", "VK_DOWN"],
|
||||||
|
initialVal: "10:30",
|
||||||
|
expectedVal: "22:30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Advance to minute field then back to hour field and decrement.
|
||||||
|
keys: ["VK_RIGHT", "VK_LEFT", "VK_DOWN"],
|
||||||
|
initialVal: "10:30",
|
||||||
|
expectedVal: "09:30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Focus starts on the first field, hour in this case, and increment.
|
||||||
|
keys: ["VK_UP"],
|
||||||
|
initialVal: "16:00",
|
||||||
|
expectedVal: "17:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Advance to minute field and decrement.
|
||||||
|
keys: ["VK_TAB", "VK_DOWN"],
|
||||||
|
initialVal: "16:00",
|
||||||
|
expectedVal: "16:59"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Advance to minute field and increment.
|
||||||
|
keys: ["VK_TAB", "VK_UP"],
|
||||||
|
initialVal: "16:59",
|
||||||
|
expectedVal: "16:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// PageUp on hour field increments hour by 3.
|
||||||
|
keys: ["VK_PAGE_UP"],
|
||||||
|
initialVal: "05:00",
|
||||||
|
expectedVal: "08:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// PageDown on hour field decrements hour by 3.
|
||||||
|
keys: ["VK_PAGE_DOWN"],
|
||||||
|
initialVal: "05:00",
|
||||||
|
expectedVal: "02:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// PageUp on minute field increments minute by 10.
|
||||||
|
keys: ["VK_TAB", "VK_PAGE_UP"],
|
||||||
|
initialVal: "14:00",
|
||||||
|
expectedVal: "14:10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// PageDown on minute field decrements minute by 10.
|
||||||
|
keys: ["VK_TAB", "VK_PAGE_DOWN"],
|
||||||
|
initialVal: "14:00",
|
||||||
|
expectedVal: "14:50"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Home key on hour field sets it to the minimum hour, which is 1 in 12-hour
|
||||||
|
// clock.
|
||||||
|
keys: ["VK_HOME"],
|
||||||
|
initialVal: "03:10",
|
||||||
|
expectedVal: "01:10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// End key on hour field sets it to the maximum hour, which is 12 in 12-hour
|
||||||
|
// clock.
|
||||||
|
keys: ["VK_END"],
|
||||||
|
initialVal: "03:10",
|
||||||
|
expectedVal: "00:10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Home key on minute field sets it to the minimum minute, which is 0.
|
||||||
|
keys: ["VK_TAB", "VK_HOME"],
|
||||||
|
initialVal: "19:30",
|
||||||
|
expectedVal: "19:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// End key on minute field sets it to the minimum minute, which is 59.
|
||||||
|
keys: ["VK_TAB", "VK_END"],
|
||||||
|
initialVal: "19:30",
|
||||||
|
expectedVal: "19:59"
|
||||||
|
},
|
||||||
|
// Second field will show up when needed.
|
||||||
|
{
|
||||||
|
// PageUp on second field increments second by 10.
|
||||||
|
keys: ["VK_TAB", "VK_TAB", "VK_PAGE_UP"],
|
||||||
|
initialVal: "08:10:10",
|
||||||
|
expectedVal: "08:10:20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// PageDown on second field increments second by 10.
|
||||||
|
keys: ["VK_TAB", "VK_TAB", "VK_PAGE_DOWN"],
|
||||||
|
initialVal: "08:10:10",
|
||||||
|
expectedVal: "08:10:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Home key on second field sets it to the minimum second, which is 0.
|
||||||
|
keys: ["VK_TAB", "VK_TAB", "VK_HOME"],
|
||||||
|
initialVal: "16:00:30",
|
||||||
|
expectedVal: "16:00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// End key on second field sets it to the minimum second, which is 59.
|
||||||
|
keys: ["VK_TAB", "VK_TAB", "VK_END"],
|
||||||
|
initialVal: "16:00:30",
|
||||||
|
expectedVal: "16:00:59"
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function sendKeys(aKeys) {
|
||||||
|
for (let i = 0; i < aKeys.length; i++) {
|
||||||
|
let key = aKeys[i];
|
||||||
|
if (key.startsWith("VK")) {
|
||||||
|
synthesizeKey(key, {});
|
||||||
|
} else {
|
||||||
|
sendString(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
var elem = document.getElementById("input");
|
||||||
|
|
||||||
|
for (let { keys, initialVal, expectedVal } of testData) {
|
||||||
|
elem.focus();
|
||||||
|
elem.value = initialVal;
|
||||||
|
sendKeys(keys);
|
||||||
|
elem.blur();
|
||||||
|
is(elem.value, expectedVal,
|
||||||
|
"Test with " + keys + ", result should be " + expectedVal);
|
||||||
|
elem.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -160,27 +160,6 @@ function runTest()
|
||||||
'1000-12-99',
|
'1000-12-99',
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'time',
|
|
||||||
validData: [
|
|
||||||
'00:00',
|
|
||||||
'09:09:00',
|
|
||||||
'08:23:23.1',
|
|
||||||
'21:43:56.12',
|
|
||||||
'23:12:45.100',
|
|
||||||
],
|
|
||||||
invalidData: [
|
|
||||||
'00:',
|
|
||||||
'00:00:',
|
|
||||||
'25:00',
|
|
||||||
'-00:00',
|
|
||||||
'00:00:00.',
|
|
||||||
'00:60',
|
|
||||||
'10:58:99',
|
|
||||||
':19:10',
|
|
||||||
'23:08:09.1012',
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'month',
|
type: 'month',
|
||||||
validData: [
|
validData: [
|
||||||
|
|
|
@ -196,7 +196,7 @@ partial interface HTMLInputElement {
|
||||||
// This is similar to set .value on nsIDOMInput/TextAreaElements, but handling
|
// This is similar to set .value on nsIDOMInput/TextAreaElements, but handling
|
||||||
// of the value change is closer to the normal user input, so 'change' event
|
// of the value change is closer to the normal user input, so 'change' event
|
||||||
// for example will be dispatched when focusing out the element.
|
// for example will be dispatched when focusing out the element.
|
||||||
[ChromeOnly]
|
[Func="IsChromeOrXBL", NeedsSubjectPrincipal]
|
||||||
void setUserInput(DOMString input);
|
void setUserInput(DOMString input);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -234,3 +234,28 @@ partial interface HTMLInputElement {
|
||||||
[Pref="dom.webkitBlink.dirPicker.enabled", BinaryName="WebkitDirectoryAttr", SetterThrows]
|
[Pref="dom.webkitBlink.dirPicker.enabled", BinaryName="WebkitDirectoryAttr", SetterThrows]
|
||||||
attribute boolean webkitdirectory;
|
attribute boolean webkitdirectory;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dictionary DateTimeValue {
|
||||||
|
long hour;
|
||||||
|
long minute;
|
||||||
|
};
|
||||||
|
|
||||||
|
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", Func="IsChromeOrXBL"]
|
||||||
|
void openDateTimePicker(optional DateTimeValue initialValue);
|
||||||
|
|
||||||
|
[Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
|
||||||
|
void updateDateTimePicker(optional DateTimeValue value);
|
||||||
|
|
||||||
|
[Pref="dom.forms.datetime", Func="IsChromeOrXBL"]
|
||||||
|
void closeDateTimePicker();
|
||||||
|
};
|
||||||
|
|
|
@ -861,7 +861,8 @@ nsXULElement::BindToTree(nsIDocument* aDocument,
|
||||||
// Note that add-ons may introduce bindings that cause this assertion to
|
// Note that add-ons may introduce bindings that cause this assertion to
|
||||||
// fire.
|
// fire.
|
||||||
NS_ASSERTION(IsInVideoControls(this) ||
|
NS_ASSERTION(IsInVideoControls(this) ||
|
||||||
IsInFeedSubscribeLine(this),
|
IsInFeedSubscribeLine(this) ||
|
||||||
|
IsXULElement(nsGkAtoms::datetimebox),
|
||||||
"Unexpected XUL element in non-XUL doc");
|
"Unexpected XUL element in non-XUL doc");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3654,8 +3654,12 @@ nsCSSFrameConstructor::FindInputData(Element* aElement,
|
||||||
SIMPLE_INT_CREATE(NS_FORM_INPUT_NUMBER, NS_NewNumberControlFrame),
|
SIMPLE_INT_CREATE(NS_FORM_INPUT_NUMBER, NS_NewNumberControlFrame),
|
||||||
// TODO: this is temporary until a frame is written: bug 888320.
|
// TODO: this is temporary until a frame is written: bug 888320.
|
||||||
SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame),
|
SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame),
|
||||||
// TODO: this is temporary until a frame is written: bug 888320
|
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
|
||||||
|
// On Android/B2G, date/time input appears as a normal text box.
|
||||||
SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewTextControlFrame),
|
SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewTextControlFrame),
|
||||||
|
#else
|
||||||
|
SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewDateTimeControlFrame),
|
||||||
|
#endif
|
||||||
// TODO: this is temporary until a frame is written: bug 888320
|
// TODO: this is temporary until a frame is written: bug 888320
|
||||||
SIMPLE_INT_CREATE(NS_FORM_INPUT_MONTH, NS_NewTextControlFrame),
|
SIMPLE_INT_CREATE(NS_FORM_INPUT_MONTH, NS_NewTextControlFrame),
|
||||||
// TODO: this is temporary until a frame is written: bug 888320
|
// TODO: this is temporary until a frame is written: bug 888320
|
||||||
|
|
|
@ -22,6 +22,7 @@ UNIFIED_SOURCES += [
|
||||||
'nsButtonFrameRenderer.cpp',
|
'nsButtonFrameRenderer.cpp',
|
||||||
'nsColorControlFrame.cpp',
|
'nsColorControlFrame.cpp',
|
||||||
'nsComboboxControlFrame.cpp',
|
'nsComboboxControlFrame.cpp',
|
||||||
|
'nsDateTimeControlFrame.cpp',
|
||||||
'nsFieldSetFrame.cpp',
|
'nsFieldSetFrame.cpp',
|
||||||
'nsFileControlFrame.cpp',
|
'nsFileControlFrame.cpp',
|
||||||
'nsFormControlFrame.cpp',
|
'nsFormControlFrame.cpp',
|
||||||
|
|
|
@ -0,0 +1,414 @@
|
||||||
|
/* -*- 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/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This frame type is used for input type=date, time, month, week, and
|
||||||
|
* datetime-local.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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"
|
||||||
|
#include "nsIDateTimeInputArea.h"
|
||||||
|
#include "nsIObserverService.h"
|
||||||
|
#include "nsIDOMHTMLInputElement.h"
|
||||||
|
#include "nsIDOMMutationEvent.h"
|
||||||
|
#include "jsapi.h"
|
||||||
|
#include "nsJSUtils.h"
|
||||||
|
#include "nsThreadUtils.h"
|
||||||
|
|
||||||
|
using namespace mozilla;
|
||||||
|
using namespace mozilla::dom;
|
||||||
|
|
||||||
|
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(&mInputAreaContent);
|
||||||
|
nsContainerFrame::DestroyFrom(aDestructRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsDateTimeControlFrame::UpdateInputBoxValue()
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
|
||||||
|
do_QueryInterface(mInputAreaContent);
|
||||||
|
if (inputAreaContent) {
|
||||||
|
inputAreaContent->NotifyInputElementValueChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsDateTimeControlFrame::HandleFocusEvent()
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
|
||||||
|
do_QueryInterface(mInputAreaContent);
|
||||||
|
if (inputAreaContent) {
|
||||||
|
inputAreaContent->FocusInnerTextBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsDateTimeControlFrame::HandleBlurEvent()
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
|
||||||
|
do_QueryInterface(mInputAreaContent);
|
||||||
|
if (inputAreaContent) {
|
||||||
|
inputAreaContent->BlurInnerTextBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
|
||||||
|
("enter nsDateTimeControlFrame::Reflow: availSize=%d,%d",
|
||||||
|
aReflowInput.AvailableWidth(),
|
||||||
|
aReflowInput.AvailableHeight()));
|
||||||
|
|
||||||
|
NS_ASSERTION(mInputAreaContent, "The input area content 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* inputAreaFrame = mFrames.FirstChild();
|
||||||
|
if (!inputAreaFrame) { // display:none?
|
||||||
|
if (contentBoxBSize == NS_INTRINSICSIZE) {
|
||||||
|
contentBoxBSize = 0;
|
||||||
|
borderBoxBSize =
|
||||||
|
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NS_ASSERTION(inputAreaFrame->GetContent() == mInputAreaContent,
|
||||||
|
"What is this child doing here?");
|
||||||
|
|
||||||
|
ReflowOutput childDesiredSize(aReflowInput);
|
||||||
|
|
||||||
|
WritingMode wm = inputAreaFrame->GetWritingMode();
|
||||||
|
LogicalSize availSize = aReflowInput.ComputedSize(wm);
|
||||||
|
availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
|
||||||
|
|
||||||
|
ReflowInput childReflowOuput(aPresContext, aReflowInput,
|
||||||
|
inputAreaFrame, availSize);
|
||||||
|
|
||||||
|
// Convert input area margin into my own writing-mode (in case it differs):
|
||||||
|
LogicalMargin childMargin =
|
||||||
|
childReflowOuput.ComputedLogicalMargin().ConvertTo(myWM, wm);
|
||||||
|
|
||||||
|
// offsets of input area frame within this frame:
|
||||||
|
LogicalPoint
|
||||||
|
childOffset(myWM,
|
||||||
|
aReflowInput.ComputedLogicalBorderPadding().IStart(myWM) +
|
||||||
|
childMargin.IStart(myWM),
|
||||||
|
aReflowInput.ComputedLogicalBorderPadding().BStart(myWM) +
|
||||||
|
childMargin.BStart(myWM));
|
||||||
|
|
||||||
|
nsReflowStatus childStatus;
|
||||||
|
// We initially reflow the child with a dummy containerSize; positioning
|
||||||
|
// will be fixed later.
|
||||||
|
const nsSize dummyContainerSize;
|
||||||
|
ReflowChild(inputAreaFrame, aPresContext, childDesiredSize,
|
||||||
|
childReflowOuput, myWM, childOffset, 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 childMarginBoxBSize =
|
||||||
|
childDesiredSize.BSize(myWM) + childMargin.BStartEnd(myWM);
|
||||||
|
|
||||||
|
if (contentBoxBSize == NS_INTRINSICSIZE) {
|
||||||
|
// We are intrinsically sized -- we should shrinkwrap the input area's
|
||||||
|
// block-size:
|
||||||
|
contentBoxBSize = childMarginBoxBSize;
|
||||||
|
|
||||||
|
// 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 - childMarginBoxBSize;
|
||||||
|
childOffset.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(inputAreaFrame, aPresContext, childDesiredSize,
|
||||||
|
&childReflowOuput, myWM, childOffset, borderBoxSize, 0);
|
||||||
|
|
||||||
|
nsSize contentBoxSize =
|
||||||
|
LogicalSize(myWM, contentBoxISize, contentBoxBSize).
|
||||||
|
GetPhysicalSize(myWM);
|
||||||
|
aDesiredSize.SetBlockStartAscent(
|
||||||
|
childDesiredSize.BlockStartAscent() +
|
||||||
|
inputAreaFrame->BStart(aReflowInput.GetWritingMode(),
|
||||||
|
contentBoxSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
|
||||||
|
aDesiredSize.SetSize(myWM, logicalDesiredSize);
|
||||||
|
|
||||||
|
aDesiredSize.SetOverflowAreasToDesiredBounds();
|
||||||
|
|
||||||
|
if (inputAreaFrame) {
|
||||||
|
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inputAreaFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 =
|
||||||
|
mContent->GetComposedDoc()->NodeInfoManager();
|
||||||
|
RefPtr<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(mInputAreaContent), nodeInfo.forget());
|
||||||
|
aElements.AppendElement(mInputAreaContent);
|
||||||
|
|
||||||
|
// Propogate our tabindex.
|
||||||
|
nsAutoString tabIndexStr;
|
||||||
|
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr)) {
|
||||||
|
mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex,
|
||||||
|
tabIndexStr, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate our readonly state.
|
||||||
|
nsAutoString readonly;
|
||||||
|
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) {
|
||||||
|
mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncDisabledState();
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsDateTimeControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
|
||||||
|
uint32_t aFilter)
|
||||||
|
{
|
||||||
|
if (mInputAreaContent) {
|
||||||
|
aElements.AppendElement(mInputAreaContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsDateTimeControlFrame::SyncDisabledState()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nsresult
|
||||||
|
nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID,
|
||||||
|
nsIAtom* aAttribute,
|
||||||
|
int32_t aModType)
|
||||||
|
{
|
||||||
|
NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
|
||||||
|
|
||||||
|
// nsGkAtoms::disabled is handled by SyncDisabledState
|
||||||
|
if (aNameSpaceID == kNameSpaceID_None) {
|
||||||
|
if (aAttribute == nsGkAtoms::value ||
|
||||||
|
aAttribute == nsGkAtoms::readonly ||
|
||||||
|
aAttribute == nsGkAtoms::tabindex) {
|
||||||
|
MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
|
||||||
|
auto contentAsInputElem = static_cast<dom::HTMLInputElement*>(mContent);
|
||||||
|
// 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->GetType() == NS_FORM_INPUT_TIME) {
|
||||||
|
if (aAttribute == nsGkAtoms::value) {
|
||||||
|
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
|
||||||
|
do_QueryInterface(mInputAreaContent);
|
||||||
|
if (inputAreaContent) {
|
||||||
|
nsContentUtils::AddScriptRunner(NewRunnableMethod(inputAreaContent,
|
||||||
|
&nsIDateTimeInputArea::NotifyInputElementValueChanged));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (aModType == nsIDOMMutationEvent::REMOVAL) {
|
||||||
|
mInputAreaContent->UnsetAttr(aNameSpaceID, aAttribute, true);
|
||||||
|
} else {
|
||||||
|
MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
|
||||||
|
aModType == nsIDOMMutationEvent::MODIFICATION);
|
||||||
|
nsAutoString value;
|
||||||
|
mContent->GetAttr(aNameSpaceID, aAttribute, value);
|
||||||
|
mInputAreaContent->SetAttr(aNameSpaceID, aAttribute, value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
|
||||||
|
aModType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsDateTimeControlFrame::ContentStatesChanged(EventStates aStates)
|
||||||
|
{
|
||||||
|
if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
|
||||||
|
nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nsIAtom*
|
||||||
|
nsDateTimeControlFrame::GetType() const
|
||||||
|
{
|
||||||
|
return nsGkAtoms::dateTimeControlFrame;
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
/* -*- 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/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This frame type is used for input type=date, time, month, week, and
|
||||||
|
* datetime-local.
|
||||||
|
*
|
||||||
|
* NOTE: some of the above-mentioned input types are still to-be-implemented.
|
||||||
|
* See nsCSSFrameConstructor::FindInputData, as well as bug 1286182 (date),
|
||||||
|
* bug 1306215 (month), bug 1306216 (week) and bug 1306217 (datetime-local).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef nsDateTimeControlFrame_h__
|
||||||
|
#define nsDateTimeControlFrame_h__
|
||||||
|
|
||||||
|
#include "mozilla/Attributes.h"
|
||||||
|
#include "nsContainerFrame.h"
|
||||||
|
#include "nsIAnonymousContentCreator.h"
|
||||||
|
#include "nsCOMPtr.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace dom {
|
||||||
|
struct DateTimeValue;
|
||||||
|
} // namespace dom
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
class nsDateTimeControlFrame final : public nsContainerFrame,
|
||||||
|
public nsIAnonymousContentCreator
|
||||||
|
{
|
||||||
|
typedef mozilla::dom::DateTimeValue DateTimeValue;
|
||||||
|
|
||||||
|
explicit nsDateTimeControlFrame(nsStyleContext* aContext);
|
||||||
|
|
||||||
|
public:
|
||||||
|
friend nsIFrame* NS_NewDateTimeControlFrame(nsIPresShell* aPresShell,
|
||||||
|
nsStyleContext* aContext);
|
||||||
|
|
||||||
|
void ContentStatesChanged(mozilla::EventStates aStates) override;
|
||||||
|
void DestroyFrom(nsIFrame* aDestructRoot) override;
|
||||||
|
|
||||||
|
NS_DECL_QUERYFRAME_TARGET(nsDateTimeControlFrame)
|
||||||
|
NS_DECL_QUERYFRAME
|
||||||
|
NS_DECL_FRAMEARENA_HELPERS
|
||||||
|
|
||||||
|
#ifdef DEBUG_FRAME_DUMP
|
||||||
|
nsresult GetFrameName(nsAString& aResult) const override {
|
||||||
|
return MakeFrameName(NS_LITERAL_STRING("DateTimeControl"), aResult);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
nsIAtom* GetType() const override;
|
||||||
|
|
||||||
|
bool IsFrameOfType(uint32_t aFlags) const override
|
||||||
|
{
|
||||||
|
return nsContainerFrame::IsFrameOfType(aFlags &
|
||||||
|
~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reflow
|
||||||
|
nscoord GetMinISize(nsRenderingContext* aRenderingContext) override;
|
||||||
|
|
||||||
|
nscoord GetPrefISize(nsRenderingContext* aRenderingContext) override;
|
||||||
|
|
||||||
|
void Reflow(nsPresContext* aPresContext,
|
||||||
|
ReflowOutput& aDesiredSize,
|
||||||
|
const ReflowInput& aReflowState,
|
||||||
|
nsReflowStatus& aStatus) override;
|
||||||
|
|
||||||
|
// nsIAnonymousContentCreator
|
||||||
|
nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
|
||||||
|
void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
|
||||||
|
uint32_t aFilter) override;
|
||||||
|
|
||||||
|
nsresult AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute,
|
||||||
|
int32_t aModType) override;
|
||||||
|
|
||||||
|
void UpdateInputBoxValue();
|
||||||
|
void SetValueFromPicker(const DateTimeValue& aValue);
|
||||||
|
void HandleFocusEvent();
|
||||||
|
void HandleBlurEvent();
|
||||||
|
void SetPickerState(bool aOpen);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class SyncDisabledStateEvent;
|
||||||
|
friend class SyncDisabledStateEvent;
|
||||||
|
class SyncDisabledStateEvent : public mozilla::Runnable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit SyncDisabledStateEvent(nsDateTimeControlFrame* aFrame)
|
||||||
|
: mFrame(aFrame)
|
||||||
|
{}
|
||||||
|
|
||||||
|
NS_IMETHOD Run() override
|
||||||
|
{
|
||||||
|
nsDateTimeControlFrame* frame =
|
||||||
|
static_cast<nsDateTimeControlFrame*>(mFrame.GetFrame());
|
||||||
|
NS_ENSURE_STATE(frame);
|
||||||
|
|
||||||
|
frame->SyncDisabledState();
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
nsWeakFrame 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.
|
||||||
|
nsCOMPtr<nsIContent> mInputAreaContent;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // nsDateTimeControlFrame_h__
|
|
@ -19,6 +19,7 @@ FRAME_ID(nsComboboxControlFrame)
|
||||||
FRAME_ID(nsComboboxDisplayFrame)
|
FRAME_ID(nsComboboxDisplayFrame)
|
||||||
FRAME_ID(nsContainerFrame)
|
FRAME_ID(nsContainerFrame)
|
||||||
FRAME_ID(nsContinuingTextFrame)
|
FRAME_ID(nsContinuingTextFrame)
|
||||||
|
FRAME_ID(nsDateTimeControlFrame)
|
||||||
FRAME_ID(nsDeckFrame)
|
FRAME_ID(nsDeckFrame)
|
||||||
FRAME_ID(nsDocElementBoxFrame)
|
FRAME_ID(nsDocElementBoxFrame)
|
||||||
FRAME_ID(nsFieldSetFrame)
|
FRAME_ID(nsFieldSetFrame)
|
||||||
|
|
|
@ -171,6 +171,8 @@ nsIFrame*
|
||||||
NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||||
nsIFrame*
|
nsIFrame*
|
||||||
NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||||
|
nsIFrame*
|
||||||
|
NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||||
nsBlockFrame*
|
nsBlockFrame*
|
||||||
NS_NewDetailsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
NS_NewDetailsFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<input type="checkbox" style="-moz-appearance:none;">
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="reftest-wait">
|
||||||
|
<!-- Test: when switching to another type, the input element should look
|
||||||
|
like that type (not like an input time element) -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
function setToCheckbox()
|
||||||
|
{
|
||||||
|
document.getElementById("i").type = "checkbox";
|
||||||
|
document.documentElement.className = "";
|
||||||
|
}
|
||||||
|
document.addEventListener("MozReftestInvalidate", setToCheckbox);
|
||||||
|
</script>
|
||||||
|
<body>
|
||||||
|
<input type="time" id="i" style="-moz-appearance:none;">
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,13 @@
|
||||||
|
default-preferences pref(dom.forms.datetime,true)
|
||||||
|
|
||||||
|
# not valid on Android/B2G where type=time looks like type=text
|
||||||
|
skip-if(Android||B2G||Mulet) != time-simple-unthemed.html time-simple-unthemed-ref.html
|
||||||
|
skip-if(Android||B2G||Mulet) != time-large-font.html time-basic.html
|
||||||
|
skip-if(Android||B2G||Mulet) != time-width-height.html time-basic.html
|
||||||
|
skip-if(Android||B2G||Mulet) != time-border.html time-basic.html
|
||||||
|
# only valid on Android/B2G where type=number looks the same as type=text
|
||||||
|
skip-if(!Android&&!B2G&&!Mulet) == time-simple-unthemed.html time-simple-unthemed-ref.html
|
||||||
|
|
||||||
|
# type change
|
||||||
|
skip-if(Android||B2G||Mulet) == to-time-from-other-type-unthemed.html time-simple-unthemed.html
|
||||||
|
skip-if(Android||B2G||Mulet) == from-time-to-other-type-unthemed.html from-time-to-other-type-unthemed-ref.html
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<input type="time" value="12:30">
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<input type="time" value="12:30" style="border:10px solid blue">
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<input type="time" value="12:30" style="font-size: 32px;">
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<input type="text" style="-moz-appearance:none;">
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<input type="time" style="-moz-appearance:none;">
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<input type="time" style="width:200px; height:50px">
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="reftest-wait">
|
||||||
|
<!-- Test: input element changed to time state doesn't look like checkbox state -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
function setToTime()
|
||||||
|
{
|
||||||
|
document.getElementById("i").type = "time";
|
||||||
|
document.documentElement.className = "";
|
||||||
|
}
|
||||||
|
document.addEventListener("MozReftestInvalidate", setToTime);
|
||||||
|
</script>
|
||||||
|
<body>
|
||||||
|
<input type="checkbox" id="i" style="-moz-appearance:none;">
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -11,3 +11,4 @@ include text/reftest.list
|
||||||
include percentage/reftest.list
|
include percentage/reftest.list
|
||||||
include hidden/reftest.list
|
include hidden/reftest.list
|
||||||
include color/reftest.list
|
include color/reftest.list
|
||||||
|
include datetime/reftest.list
|
||||||
|
|
|
@ -767,6 +767,13 @@ video > .caption-box {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* datetime elements */
|
||||||
|
|
||||||
|
input[type="time"] > xul|datetimebox {
|
||||||
|
display: flex;
|
||||||
|
-moz-binding: url("chrome://global/content/bindings/datetimebox.xml#time-input");
|
||||||
|
}
|
||||||
|
|
||||||
/* details & summary */
|
/* details & summary */
|
||||||
/* Need to revert Bug 1259889 Part 2 when removing details preference. */
|
/* Need to revert Bug 1259889 Part 2 when removing details preference. */
|
||||||
@supports -moz-bool-pref("dom.details_element.enabled") {
|
@supports -moz-bool-pref("dom.details_element.enabled") {
|
||||||
|
|
|
@ -167,7 +167,7 @@ function setupFormHistory(aCallback) {
|
||||||
{ op : "add", fieldname : "field9", value : "value" },
|
{ op : "add", fieldname : "field9", value : "value" },
|
||||||
{ op : "add", fieldname : "field10", value : "42" },
|
{ op : "add", fieldname : "field10", value : "42" },
|
||||||
{ op : "add", fieldname : "field11", value : "2010-10-10" },
|
{ op : "add", fieldname : "field11", value : "2010-10-10" },
|
||||||
{ op : "add", fieldname : "field12", value : "21:21" },
|
{ op : "add", fieldname : "field12", value : "21:21" }, // not used, since type=time doesn't have autocomplete currently
|
||||||
{ op : "add", fieldname : "field13", value : "32" }, // not used, since type=range doesn't have a drop down menu
|
{ op : "add", fieldname : "field13", value : "32" }, // not used, since type=range doesn't have a drop down menu
|
||||||
{ op : "add", fieldname : "field14", value : "#ffffff" }, // not used, since type=color doesn't have autocomplete currently
|
{ op : "add", fieldname : "field14", value : "#ffffff" }, // not used, since type=color doesn't have autocomplete currently
|
||||||
{ op : "add", fieldname : "field15", value : "2016-08" },
|
{ op : "add", fieldname : "field15", value : "2016-08" },
|
||||||
|
@ -913,15 +913,13 @@ function runTest() {
|
||||||
|
|
||||||
input = $_(15, "field12");
|
input = $_(15, "field12");
|
||||||
restoreForm();
|
restoreForm();
|
||||||
expectPopup();
|
waitForMenuChange(0);
|
||||||
doKey("down");
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 406:
|
case 406:
|
||||||
checkMenuEntries(["21:21"]);
|
checkMenuEntries([]); // type=time with it's own control frame does not
|
||||||
doKey("down");
|
// have a drop down menu for now
|
||||||
doKey("return");
|
checkForm("");
|
||||||
checkForm("21:21");
|
|
||||||
|
|
||||||
input = $_(16, "field13");
|
input = $_(16, "field13");
|
||||||
restoreForm();
|
restoreForm();
|
||||||
|
|
|
@ -1509,3 +1509,151 @@ let AutoCompletePopup = {
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoCompletePopup.init();
|
AutoCompletePopup.init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DateTimePickerListener is the communication channel between the input box
|
||||||
|
* (content) for date/time input types and its picker (chrome).
|
||||||
|
*/
|
||||||
|
let DateTimePickerListener = {
|
||||||
|
/**
|
||||||
|
* On init, just listen for the event to open the picker, once the picker is
|
||||||
|
* opened, we'll listen for update and close events.
|
||||||
|
*/
|
||||||
|
init: function() {
|
||||||
|
addEventListener("MozOpenDateTimePicker", this);
|
||||||
|
this._inputElement = null;
|
||||||
|
|
||||||
|
addEventListener("unload", () => {
|
||||||
|
this.uninit();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
uninit: function() {
|
||||||
|
removeEventListener("MozOpenDateTimePicker", this);
|
||||||
|
this._inputElement = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup function called when picker is closed.
|
||||||
|
*/
|
||||||
|
close: function() {
|
||||||
|
this.removeListeners();
|
||||||
|
this._inputElement.setDateTimePickerState(false);
|
||||||
|
this._inputElement = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after picker is opened to start listening for input box update
|
||||||
|
* events.
|
||||||
|
*/
|
||||||
|
addListeners: function() {
|
||||||
|
addEventListener("MozUpdateDateTimePicker", this);
|
||||||
|
addEventListener("MozCloseDateTimePicker", this);
|
||||||
|
addEventListener("pagehide", this);
|
||||||
|
|
||||||
|
addMessageListener("FormDateTime:PickerValueChanged", this);
|
||||||
|
addMessageListener("FormDateTime:PickerClosed", this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop listeneing for events when picker is closed.
|
||||||
|
*/
|
||||||
|
removeListeners: function() {
|
||||||
|
removeEventListener("MozUpdateDateTimePicker", this);
|
||||||
|
removeEventListener("MozCloseDateTimePicker", this);
|
||||||
|
removeEventListener("pagehide", this);
|
||||||
|
|
||||||
|
removeMessageListener("FormDateTime:PickerValueChanged", this);
|
||||||
|
removeMessageListener("FormDateTime:PickerClosed", this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function that returns the CSS direction property of the element.
|
||||||
|
*/
|
||||||
|
getComputedDirection: function(aElement) {
|
||||||
|
return aElement.ownerDocument.defaultView.getComputedStyle(aElement)
|
||||||
|
.getPropertyValue("direction");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function that returns the rect of the element, which is the position
|
||||||
|
* in "screen" coordinates.
|
||||||
|
*/
|
||||||
|
getBoundingContentRect: function(aElement) {
|
||||||
|
return BrowserUtils.getElementBoundingScreenRect(aElement);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nsIMessageListener.
|
||||||
|
*/
|
||||||
|
receiveMessage: function(aMessage) {
|
||||||
|
switch (aMessage.name) {
|
||||||
|
case "FormDateTime:PickerClosed": {
|
||||||
|
this.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "FormDateTime:PickerValueChanged": {
|
||||||
|
this._inputElement.updateDateTimeInputBox(aMessage.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nsIDOMEventListener, for chrome events sent by the input element and other
|
||||||
|
* DOM events.
|
||||||
|
*/
|
||||||
|
handleEvent: function(aEvent) {
|
||||||
|
switch (aEvent.type) {
|
||||||
|
case "MozOpenDateTimePicker": {
|
||||||
|
if (!(aEvent.originalTarget instanceof content.HTMLInputElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._inputElement = aEvent.originalTarget;
|
||||||
|
this._inputElement.setDateTimePickerState(true);
|
||||||
|
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: {
|
||||||
|
// Pass partial value if it's available, otherwise pass input
|
||||||
|
// element's value.
|
||||||
|
value: Object.keys(value).length > 0 ? value
|
||||||
|
: this._inputElement.value,
|
||||||
|
step: this._inputElement.step,
|
||||||
|
min: this._inputElement.min,
|
||||||
|
max: this._inputElement.max,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "MozUpdateDateTimePicker": {
|
||||||
|
let value = this._inputElement.getDateTimeInputBoxValue();
|
||||||
|
sendAsyncMessage("FormDateTime:UpdatePicker", { value });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "MozCloseDateTimePicker": {
|
||||||
|
sendAsyncMessage("FormDateTime:ClosePicker");
|
||||||
|
this.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "pagehide": {
|
||||||
|
if (this._inputElement &&
|
||||||
|
this._inputElement.ownerDocument == aEvent.target) {
|
||||||
|
sendAsyncMessage("FormDateTime:ClosePicker");
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimePickerListener.init();
|
||||||
|
|
|
@ -71,6 +71,8 @@ toolkit.jar:
|
||||||
content/global/bindings/checkbox.xml (widgets/checkbox.xml)
|
content/global/bindings/checkbox.xml (widgets/checkbox.xml)
|
||||||
content/global/bindings/colorpicker.xml (widgets/colorpicker.xml)
|
content/global/bindings/colorpicker.xml (widgets/colorpicker.xml)
|
||||||
content/global/bindings/datetimepicker.xml (widgets/datetimepicker.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/dialog.xml (widgets/dialog.xml)
|
||||||
content/global/bindings/editor.xml (widgets/editor.xml)
|
content/global/bindings/editor.xml (widgets/editor.xml)
|
||||||
content/global/bindings/expander.xml (widgets/expander.xml)
|
content/global/bindings/expander.xml (widgets/expander.xml)
|
||||||
|
|
|
@ -281,6 +281,10 @@
|
||||||
onget="return document.getElementById(this.getAttribute('autocompletepopup'))"
|
onget="return document.getElementById(this.getAttribute('autocompletepopup'))"
|
||||||
readonly="true"/>
|
readonly="true"/>
|
||||||
|
|
||||||
|
<property name="dateTimePicker"
|
||||||
|
onget="return document.getElementById(this.getAttribute('datetimepicker'))"
|
||||||
|
readonly="true"/>
|
||||||
|
|
||||||
<property name="docShellIsActive">
|
<property name="docShellIsActive">
|
||||||
<getter>
|
<getter>
|
||||||
<![CDATA[
|
<![CDATA[
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* 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: default;
|
||||||
|
background-color: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datetime-input {
|
||||||
|
-moz-appearance: none;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
ime-mode: disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datetime-separator {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datetime-input[readonly],
|
||||||
|
.datetime-input[disabled] {
|
||||||
|
color: GrayText;
|
||||||
|
-moz-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datetime-reset-button {
|
||||||
|
background-image: url(chrome://global/skin/icons/input-clear.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 12px, 12px;
|
||||||
|
border: none;
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
align-self: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
|
@ -0,0 +1,806 @@
|
||||||
|
<?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: Bug 1301312 - localization for input type=time input.
|
||||||
|
this.mHour12 = true;
|
||||||
|
this.mAMIndicator = "AM";
|
||||||
|
this.mPMIndicator = "PM";
|
||||||
|
this.mPlaceHolder = "--";
|
||||||
|
this.mSeparatorText = ":";
|
||||||
|
this.mMillisecSeparatorText = ".";
|
||||||
|
this.mMaxLength = 2;
|
||||||
|
this.mMillisecMaxLength = 3;
|
||||||
|
this.mDefaultStep = 60 * 1000; // in milliseconds
|
||||||
|
|
||||||
|
this.mMinHourInHour12 = 1;
|
||||||
|
this.mMaxHourInHour12 = 12;
|
||||||
|
this.mMinMinute = 0;
|
||||||
|
this.mMaxMinute = 59;
|
||||||
|
this.mMinSecond = 0;
|
||||||
|
this.mMaxSecond = 59;
|
||||||
|
this.mMinMillisecond = 0;
|
||||||
|
this.mMaxMillisecond = 999;
|
||||||
|
|
||||||
|
this.mHourPageUpDownInterval = 3;
|
||||||
|
this.mMinSecPageUpDownInterval = 10;
|
||||||
|
|
||||||
|
this.mHourField =
|
||||||
|
document.getAnonymousElementByAttribute(this, "anonid", "input-one");
|
||||||
|
this.mHourField.setAttribute("typeBuffer", "");
|
||||||
|
this.mMinuteField =
|
||||||
|
document.getAnonymousElementByAttribute(this, "anonid", "input-two");
|
||||||
|
this.mMinuteField.setAttribute("typeBuffer", "");
|
||||||
|
this.mDayPeriodField =
|
||||||
|
document.getAnonymousElementByAttribute(this, "anonid", "input-three");
|
||||||
|
this.mDayPeriodField.classList.remove("numeric");
|
||||||
|
|
||||||
|
this.mHourField.placeholder = this.mPlaceHolder;
|
||||||
|
this.mMinuteField.placeholder = this.mPlaceHolder;
|
||||||
|
this.mDayPeriodField.placeholder = this.mPlaceHolder;
|
||||||
|
|
||||||
|
this.mHourField.setAttribute("min", this.mMinHourInHour12);
|
||||||
|
this.mHourField.setAttribute("max", this.mMaxHourInHour12);
|
||||||
|
this.mMinuteField.setAttribute("min", this.mMinMinute);
|
||||||
|
this.mMinuteField.setAttribute("max", this.mMaxMinute);
|
||||||
|
|
||||||
|
this.mMinuteSeparator =
|
||||||
|
document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
|
||||||
|
this.mMinuteSeparator.textContent = this.mSeparatorText;
|
||||||
|
this.mSpaceSeparator =
|
||||||
|
document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
|
||||||
|
// space between time and am/pm field
|
||||||
|
this.mSpaceSeparator.textContent = " ";
|
||||||
|
|
||||||
|
this.mSecondSeparator = null;
|
||||||
|
this.mSecondField = null;
|
||||||
|
this.mMillisecSeparator = null;
|
||||||
|
this.mMillisecField = null;
|
||||||
|
|
||||||
|
if (this.mInputElement.value) {
|
||||||
|
this.setFieldsFromInputValue();
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</constructor>
|
||||||
|
|
||||||
|
<method name="insertSeparator">
|
||||||
|
<parameter name="aSeparatorText"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
let container = this.mHourField.parentNode;
|
||||||
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||||
|
|
||||||
|
let separator = document.createElementNS(HTML_NS, "span");
|
||||||
|
separator.textContent = aSeparatorText;
|
||||||
|
separator.setAttribute("class", "datetime-separator");
|
||||||
|
container.insertBefore(separator, this.mSpaceSeparator);
|
||||||
|
|
||||||
|
return separator;
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="insertAdditionalField">
|
||||||
|
<parameter name="aPlaceHolder"/>
|
||||||
|
<parameter name="aMin"/>
|
||||||
|
<parameter name="aMax"/>
|
||||||
|
<parameter name="aSize"/>
|
||||||
|
<parameter name="aMaxLength"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
let container = this.mHourField.parentNode;
|
||||||
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||||
|
|
||||||
|
let field = document.createElementNS(HTML_NS, "input");
|
||||||
|
field.classList.add("textbox-input", "datetime-input", "numeric");
|
||||||
|
field.setAttribute("size", aSize);
|
||||||
|
field.setAttribute("maxlength", aMaxLength);
|
||||||
|
field.setAttribute("min", aMin);
|
||||||
|
field.setAttribute("max", aMax);
|
||||||
|
field.setAttribute("typeBuffer", "");
|
||||||
|
field.disabled = this.mInputElement.disabled;
|
||||||
|
field.readOnly = this.mInputElement.readOnly;
|
||||||
|
field.tabIndex = this.mInputElement.tabIndex;
|
||||||
|
field.placeholder = aPlaceHolder;
|
||||||
|
container.insertBefore(field, this.mSpaceSeparator);
|
||||||
|
|
||||||
|
return field;
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="setFieldsFromInputValue">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
let value = this.mInputElement.value;
|
||||||
|
if (!value) {
|
||||||
|
this.clearInputFields(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log("setFieldsFromInputValue: " + value);
|
||||||
|
let [hour, minute, second] = value.split(':');
|
||||||
|
|
||||||
|
this.setFieldValue(this.mHourField, hour);
|
||||||
|
this.setFieldValue(this.mMinuteField, minute);
|
||||||
|
if (this.mHour12) {
|
||||||
|
this.mDayPeriodField.value = (hour >= this.mMaxHourInHour12) ?
|
||||||
|
this.mPMIndicator : this.mAMIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isEmpty(second)) {
|
||||||
|
let index = second.indexOf(".");
|
||||||
|
let millisecond;
|
||||||
|
if (index != -1) {
|
||||||
|
millisecond = second.substring(index + 1);
|
||||||
|
second = second.substring(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.mSecondField) {
|
||||||
|
this.mSecondSeparator = this.insertSeparator(this.mSeparatorText);
|
||||||
|
this.mSecondField = this.insertAdditionalField(this.mPlaceHolder,
|
||||||
|
this.mMinSecond, this.mMaxSecond, this.mMaxLength,
|
||||||
|
this.mMaxLength);
|
||||||
|
}
|
||||||
|
this.setFieldValue(this.mSecondField, second);
|
||||||
|
|
||||||
|
if (!this.isEmpty(millisecond)) {
|
||||||
|
if (!this.mMillisecField) {
|
||||||
|
this.mMillisecSeparator = this.insertSeparator(
|
||||||
|
this.mMillisecSeparatorText);
|
||||||
|
this.mMillisecField = this.insertAdditionalField(
|
||||||
|
this.mPlaceHolder, this.mMinMillisecond, this.mMaxMillisecond,
|
||||||
|
this.mMillisecMaxLength, this.mMillisecMaxLength);
|
||||||
|
}
|
||||||
|
this.setFieldValue(this.mMillisecField, millisecond);
|
||||||
|
} else if (this.mMillisecField) {
|
||||||
|
this.mMillisecField.remove();
|
||||||
|
this.mMillisecField = null;
|
||||||
|
|
||||||
|
this.mMillisecSeparator.remove();
|
||||||
|
this.mMillisecSeparator = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.mSecondField) {
|
||||||
|
this.mSecondField.remove();
|
||||||
|
this.mSecondField = null;
|
||||||
|
|
||||||
|
this.mSecondSeparator.remove();
|
||||||
|
this.mSecondSeparator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mMillisecField) {
|
||||||
|
this.mMillisecField.remove();
|
||||||
|
this.mMillisecField = null;
|
||||||
|
|
||||||
|
this.mMillisecSeparator.remove();
|
||||||
|
this.mMillisecSeparator = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.notifyPicker();
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="setInputValueFromFields">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
if (this.isEmpty(this.mHourField.value) ||
|
||||||
|
this.isEmpty(this.mMinuteField.value) ||
|
||||||
|
(this.mDayPeriodField && this.isEmpty(this.mDayPeriodField.value)) ||
|
||||||
|
(this.mSecondField && this.isEmpty(this.mSecondField.value))) {
|
||||||
|
// We still need to notify picker in case any of the field has
|
||||||
|
// changed. If we can set input element value, then notifyPicker
|
||||||
|
// will be called in setFieldsFromInputValue().
|
||||||
|
this.notifyPicker();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hour = Number(this.mHourField.value);
|
||||||
|
if (this.mHour12) {
|
||||||
|
let dayPeriod = this.mDayPeriodField.value;
|
||||||
|
if (dayPeriod == this.mPMIndicator &&
|
||||||
|
hour < this.mMaxHourInHour12) {
|
||||||
|
hour += this.mMaxHourInHour12;
|
||||||
|
} else if (dayPeriod == this.mAMIndicator &&
|
||||||
|
hour == this.mMaxHourInHour12) {
|
||||||
|
hour = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hour = (hour < 10) ? ("0" + hour) : hour;
|
||||||
|
|
||||||
|
let time = hour + ":" + this.mMinuteField.value;
|
||||||
|
if (this.mSecondField) {
|
||||||
|
time += ":" + this.mSecondField.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mMillisecField) {
|
||||||
|
time += "." + this.mMillisecField.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log("setInputValueFromFields: " + time);
|
||||||
|
this.mInputElement.setUserInput(time);
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="setFieldsFromPicker">
|
||||||
|
<parameter name="aValue"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
let hour = aValue.hour;
|
||||||
|
let minute = aValue.minute;
|
||||||
|
this.log("setFieldsFromPicker: " + hour + ":" + minute);
|
||||||
|
|
||||||
|
if (!this.isEmpty(hour)) {
|
||||||
|
this.setFieldValue(this.mHourField, hour);
|
||||||
|
if (this.mHour12) {
|
||||||
|
this.mDayPeriodField.value =
|
||||||
|
(hour >= this.mMaxHourInHour12) ? this.mPMIndicator
|
||||||
|
: this.mAMIndicator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isEmpty(minute)) {
|
||||||
|
this.setFieldValue(this.mMinuteField, minute);
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="clearInputFields">
|
||||||
|
<parameter name="aFromInputElement"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
this.log("clearInputFields");
|
||||||
|
|
||||||
|
if (this.isDisabled() || this.isReadonly()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mHourField && !this.mHourField.disabled &&
|
||||||
|
!this.mHourField.readOnly) {
|
||||||
|
this.mHourField.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mMinuteField && !this.mMinuteField.disabled &&
|
||||||
|
!this.mMinuteField.readOnly) {
|
||||||
|
this.mMinuteField.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mSecondField && !this.mSecondField.disabled &&
|
||||||
|
!this.mSecondField.readOnly) {
|
||||||
|
this.mSecondField.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mMillisecField && !this.mMillisecField.disabled &&
|
||||||
|
!this.mMillisecField.readOnly) {
|
||||||
|
this.mMillisecField.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mDayPeriodField && !this.mDayPeriodField.disabled &&
|
||||||
|
!this.mDayPeriodField.readOnly) {
|
||||||
|
this.mDayPeriodField.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!aFromInputElement) {
|
||||||
|
this.mInputElement.setUserInput("");
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="incrementFieldValue">
|
||||||
|
<parameter name="aTargetField"/>
|
||||||
|
<parameter name="aTimes"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
let value;
|
||||||
|
|
||||||
|
// Use current time if field is empty.
|
||||||
|
if (this.isEmpty(aTargetField.value)) {
|
||||||
|
let now = new Date();
|
||||||
|
|
||||||
|
if (aTargetField == this.mHourField) {
|
||||||
|
value = now.getHours() % this.mMaxHourInHour12 ||
|
||||||
|
this.mMaxHourInHour12;
|
||||||
|
} else if (aTargetField == this.mMinuteField) {
|
||||||
|
value = now.getMinutes();
|
||||||
|
} else if (aTargetField == this.mSecondField) {
|
||||||
|
value = now.getSeconds();
|
||||||
|
} else if (aTargetField == this.mMillisecondsField) {
|
||||||
|
value = now.getMilliseconds();
|
||||||
|
} else {
|
||||||
|
this.log("Field not supported in incrementFieldValue.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = Number(aTargetField.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
let min = aTargetField.getAttribute("min");
|
||||||
|
let max = aTargetField.getAttribute("max");
|
||||||
|
|
||||||
|
value += aTimes;
|
||||||
|
if (value > max) {
|
||||||
|
value -= (max - min + 1);
|
||||||
|
} else if (value < min) {
|
||||||
|
value += (max - min + 1);
|
||||||
|
}
|
||||||
|
this.setFieldValue(aTargetField, value);
|
||||||
|
aTargetField.select();
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="handleKeyboardNav">
|
||||||
|
<parameter name="aEvent"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
if (this.isDisabled() || this.isReadonly()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetField = aEvent.originalTarget;
|
||||||
|
let key = aEvent.key;
|
||||||
|
|
||||||
|
if (this.mDayPeriodField &&
|
||||||
|
targetField == this.mDayPeriodField) {
|
||||||
|
// Home/End key does nothing on AM/PM field.
|
||||||
|
if (key == "Home" || key == "End") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mDayPeriodField.value =
|
||||||
|
this.mDayPeriodField.value == this.mAMIndicator ?
|
||||||
|
this.mPMIndicator : this.mAMIndicator;
|
||||||
|
this.mDayPeriodField.select();
|
||||||
|
this.setInputValueFromFields();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case "ArrowUp":
|
||||||
|
this.incrementFieldValue(targetField, 1);
|
||||||
|
break;
|
||||||
|
case "ArrowDown":
|
||||||
|
this.incrementFieldValue(targetField, -1);
|
||||||
|
break;
|
||||||
|
case "PageUp":
|
||||||
|
this.incrementFieldValue(targetField,
|
||||||
|
targetField == this.mHourField ? this.mHourPageUpDownInterval
|
||||||
|
: this.mMinSecPageUpDownInterval);
|
||||||
|
break;
|
||||||
|
case "PageDown":
|
||||||
|
this.incrementFieldValue(targetField,
|
||||||
|
targetField == this.mHourField ? (0 - this.mHourPageUpDownInterval)
|
||||||
|
: (0 - this.mMinSecPageUpDownInterval));
|
||||||
|
break;
|
||||||
|
case "Home":
|
||||||
|
let min = targetField.getAttribute("min");
|
||||||
|
this.setFieldValue(targetField, min);
|
||||||
|
targetField.select();
|
||||||
|
break;
|
||||||
|
case "End":
|
||||||
|
let max = targetField.getAttribute("max");
|
||||||
|
this.setFieldValue(targetField, max);
|
||||||
|
targetField.select();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.setInputValueFromFields();
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="handleKeypress">
|
||||||
|
<parameter name="aEvent"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
if (this.isDisabled() || this.isReadonly()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetField = aEvent.originalTarget;
|
||||||
|
let key = aEvent.key;
|
||||||
|
|
||||||
|
if (this.mDayPeriodField &&
|
||||||
|
targetField == this.mDayPeriodField) {
|
||||||
|
if (key == "a" || key == "A") {
|
||||||
|
this.mDayPeriodField.value = this.mAMIndicator;
|
||||||
|
this.mDayPeriodField.select();
|
||||||
|
} else if (key == "p" || key == "P") {
|
||||||
|
this.mDayPeriodField.value = this.mPMIndicator;
|
||||||
|
this.mDayPeriodField.select();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) {
|
||||||
|
let buffer = targetField.getAttribute("typeBuffer") || "";
|
||||||
|
|
||||||
|
buffer = buffer.concat(key);
|
||||||
|
this.setFieldValue(targetField, buffer);
|
||||||
|
targetField.select();
|
||||||
|
|
||||||
|
let n = Number(buffer);
|
||||||
|
let max = targetField.getAttribute("max");
|
||||||
|
if (buffer.length >= targetField.maxLength || n * 10 > max) {
|
||||||
|
buffer = "";
|
||||||
|
this.advanceToNextField();
|
||||||
|
}
|
||||||
|
targetField.setAttribute("typeBuffer", buffer);
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="setFieldValue">
|
||||||
|
<parameter name="aField"/>
|
||||||
|
<parameter name="aValue"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
let value = Number(aValue);
|
||||||
|
if (isNaN(value)) {
|
||||||
|
this.log("NaN on setFieldValue!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aField.maxLength == this.mMaxLength) { // For hour, minute and second
|
||||||
|
if (aField == this.mHourField && this.mHour12) {
|
||||||
|
value = (value > this.mMaxHourInHour12) ?
|
||||||
|
value - this.mMaxHourInHour12 : value;
|
||||||
|
if (aValue == "00") {
|
||||||
|
value = this.mMaxHourInHour12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// prepend zero
|
||||||
|
if (value < 10) {
|
||||||
|
value = "0" + value;
|
||||||
|
}
|
||||||
|
} else if (aField.maxLength == this.mMillisecMaxLength) {
|
||||||
|
// prepend zeroes
|
||||||
|
if (value < 10) {
|
||||||
|
value = "00" + value;
|
||||||
|
} else if (value < 100) {
|
||||||
|
value = "0" + value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aField.value = value;
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="isValueAvailable">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
// Picker only cares about hour:minute.
|
||||||
|
return !this.isEmpty(this.mHourField.value) ||
|
||||||
|
!this.isEmpty(this.mMinuteField.value);
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="getCurrentValue">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
let hour;
|
||||||
|
if (!this.isEmpty(this.mHourField.value)) {
|
||||||
|
hour = Number(this.mHourField.value);
|
||||||
|
if (this.mHour12) {
|
||||||
|
let dayPeriod = this.mDayPeriodField.value;
|
||||||
|
if (dayPeriod == this.mPMIndicator &&
|
||||||
|
hour < this.mMaxHourInHour12) {
|
||||||
|
hour += this.mMaxHourInHour12;
|
||||||
|
} else if (dayPeriod == this.mAMIndicator &&
|
||||||
|
hour == this.mMaxHourInHour12) {
|
||||||
|
hour = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let minute;
|
||||||
|
if (!this.isEmpty(this.mMinuteField.value)) {
|
||||||
|
minute = Number(this.mMinuteField.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Picker only needs hour/minute.
|
||||||
|
let time = { hour, minute };
|
||||||
|
|
||||||
|
this.log("getCurrentValue: " + JSON.stringify(time));
|
||||||
|
return time;
|
||||||
|
]]>
|
||||||
|
</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 numeric"
|
||||||
|
size="2" maxlength="2"
|
||||||
|
xbl:inherits="disabled,readonly,tabindex"/>
|
||||||
|
<html:span anonid="sep-first" class="datetime-separator"></html:span>
|
||||||
|
<html:input anonid="input-two"
|
||||||
|
class="textbox-input datetime-input numeric"
|
||||||
|
size="2" maxlength="2"
|
||||||
|
xbl:inherits="disabled,readonly,tabindex"/>
|
||||||
|
<html:span anonid="sep-second" class="datetime-separator"></html:span>
|
||||||
|
<html:input anonid="input-three"
|
||||||
|
class="textbox-input datetime-input numeric"
|
||||||
|
size="2" maxlength="2"
|
||||||
|
xbl:inherits="disabled,readonly,tabindex"/>
|
||||||
|
</html:span>
|
||||||
|
|
||||||
|
<html:button class="datetime-reset-button" anoid="reset-button"
|
||||||
|
tabindex="-1" xbl:inherits="disabled"
|
||||||
|
onclick="document.getBindingParent(this).clearInputFields(false);"/>
|
||||||
|
</html:div>
|
||||||
|
</content>
|
||||||
|
|
||||||
|
<implementation implements="nsIDateTimeInputArea">
|
||||||
|
<constructor>
|
||||||
|
<![CDATA[
|
||||||
|
this.DEBUG = false;
|
||||||
|
this.mInputElement = this.parentNode;
|
||||||
|
|
||||||
|
this.mMin = this.mInputElement.min;
|
||||||
|
this.mMax = this.mInputElement.max;
|
||||||
|
this.mStep = this.mInputElement.step;
|
||||||
|
this.mIsPickerOpen = false;
|
||||||
|
]]>
|
||||||
|
</constructor>
|
||||||
|
|
||||||
|
<method name="log">
|
||||||
|
<parameter name="aMsg"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
if (this.DEBUG) {
|
||||||
|
dump("[DateTimeBox] " + aMsg + "\n");
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="focusInnerTextBox">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
this.log("focusInnerTextBox");
|
||||||
|
document.getAnonymousElementByAttribute(this, "anonid", "input-one").focus();
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="blurInnerTextBox">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
this.log("blurInnerTextBox");
|
||||||
|
if (this.mLastFocusedField) {
|
||||||
|
this.mLastFocusedField.blur();
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="notifyInputElementValueChanged">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
this.log("inputElementValueChanged");
|
||||||
|
this.setFieldsFromInputValue();
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="setValueFromPicker">
|
||||||
|
<parameter name="aValue"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
this.setFieldsFromPicker(aValue);
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="advanceToNextField">
|
||||||
|
<parameter name="aReverse"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
this.log("advanceToNextField");
|
||||||
|
|
||||||
|
let focusedInput = this.mLastFocusedField;
|
||||||
|
let next = aReverse ? focusedInput.previousElementSibling
|
||||||
|
: focusedInput.nextElementSibling;
|
||||||
|
if (!next && !aReverse) {
|
||||||
|
this.setInputValueFromFields();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (next) {
|
||||||
|
if (next.type == "text" && !next.disabled) {
|
||||||
|
next.focus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
next = aReverse ? next.previousElementSibling
|
||||||
|
: next.nextElementSibling;
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="setPickerState">
|
||||||
|
<parameter name="aIsOpen"/>
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
this.log("picker is now " + (aIsOpen ? "opened" : "closed"));
|
||||||
|
this.mIsPickerOpen = aIsOpen;
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="isEmpty">
|
||||||
|
<parameter name="aValue"/>
|
||||||
|
<body>
|
||||||
|
return (aValue == undefined || 0 === aValue.length);
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="clearInputFields">
|
||||||
|
<body>
|
||||||
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="setFieldsFromInputValue">
|
||||||
|
<body>
|
||||||
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="setInputValueFromFields">
|
||||||
|
<body>
|
||||||
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="setFieldsFromPicker">
|
||||||
|
<body>
|
||||||
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="handleKeypress">
|
||||||
|
<body>
|
||||||
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="handleKeyboardNav">
|
||||||
|
<body>
|
||||||
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="notifyPicker">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
if (this.mIsPickerOpen && this.isValueAvailable()) {
|
||||||
|
this.mInputElement.updateDateTimePicker(this.getCurrentValue());
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="isDisabled">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
return this.hasAttribute("disabled");
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="isReadonly">
|
||||||
|
<body>
|
||||||
|
<![CDATA[
|
||||||
|
return this.hasAttribute("readonly");
|
||||||
|
]]>
|
||||||
|
</body>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
</implementation>
|
||||||
|
|
||||||
|
<handlers>
|
||||||
|
<handler event="focus">
|
||||||
|
<![CDATA[
|
||||||
|
this.log("focus on: " + event.originalTarget);
|
||||||
|
|
||||||
|
let target = event.originalTarget;
|
||||||
|
if (target.type == "text") {
|
||||||
|
this.mLastFocusedField = target;
|
||||||
|
target.select();
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</handler>
|
||||||
|
|
||||||
|
<handler event="blur">
|
||||||
|
<![CDATA[
|
||||||
|
this.setInputValueFromFields();
|
||||||
|
]]>
|
||||||
|
</handler>
|
||||||
|
|
||||||
|
<handler event="click">
|
||||||
|
<![CDATA[
|
||||||
|
// XXX: .originalTarget is not expected.
|
||||||
|
// When clicking on one of the inner text boxes, the .originalTarget is
|
||||||
|
// a HTMLDivElement and when clicking on the reset button, it's a
|
||||||
|
// HTMLButtonElement but it's not equal to our reset-button.
|
||||||
|
this.log("click on: " + event.originalTarget);
|
||||||
|
if (event.defaultPrevented || this.isDisabled() || this.isReadonly()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(event.originalTarget instanceof HTMLButtonElement)) {
|
||||||
|
this.mInputElement.openDateTimePicker(this.getCurrentValue());
|
||||||
|
}
|
||||||
|
]]>
|
||||||
|
</handler>
|
||||||
|
|
||||||
|
<handler event="keypress" phase="capturing">
|
||||||
|
<![CDATA[
|
||||||
|
let key = event.key;
|
||||||
|
this.log("keypress: " + key);
|
||||||
|
|
||||||
|
if (key == "Backspace" || key == "Tab") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == "Enter" || key == " ") {
|
||||||
|
// Close picker on Enter and Space.
|
||||||
|
this.mInputElement.closeDateTimePicker();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == "ArrowUp" || key == "ArrowDown" ||
|
||||||
|
key == "PageUp" || key == "PageDown" ||
|
||||||
|
key == "Home" || key == "End") {
|
||||||
|
this.handleKeyboardNav(event);
|
||||||
|
} else if (key == "ArrowRight" || key == "ArrowLeft") {
|
||||||
|
this.advanceToNextField((key == "ArrowRight" ? false : true));
|
||||||
|
} else {
|
||||||
|
this.handleKeypress(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
]]>
|
||||||
|
</handler>
|
||||||
|
</handlers>
|
||||||
|
</binding>
|
||||||
|
|
||||||
|
</bindings>
|
|
@ -0,0 +1,165 @@
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
const DEBUG = false;
|
||||||
|
function debug(aStr) {
|
||||||
|
if (DEBUG) {
|
||||||
|
dump("-*- DateTimePickerHelper: " + aStr + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = [
|
||||||
|
"DateTimePickerHelper"
|
||||||
|
];
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DateTimePickerHelper receives message from content side (input box) and
|
||||||
|
* is reposible for opening, closing and updating the picker. Similary,
|
||||||
|
* DateTimePickerHelper listens for picker's events and notifies the content
|
||||||
|
* side (input box) about them.
|
||||||
|
*/
|
||||||
|
this.DateTimePickerHelper = {
|
||||||
|
picker: null,
|
||||||
|
weakBrowser: null,
|
||||||
|
|
||||||
|
MESSAGES: [
|
||||||
|
"FormDateTime:OpenPicker",
|
||||||
|
"FormDateTime:ClosePicker",
|
||||||
|
"FormDateTime:UpdatePicker"
|
||||||
|
],
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
for (let msg of this.MESSAGES) {
|
||||||
|
Services.mm.addMessageListener(msg, this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uninit: function() {
|
||||||
|
for (let msg of this.MESSAGES) {
|
||||||
|
Services.mm.removeMessageListener(msg, this);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// nsIMessageListener
|
||||||
|
receiveMessage: function(aMessage) {
|
||||||
|
debug("receiveMessage: " + aMessage.name);
|
||||||
|
switch (aMessage.name) {
|
||||||
|
case "FormDateTime:OpenPicker": {
|
||||||
|
this.showPicker(aMessage.target, aMessage.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "FormDateTime:ClosePicker": {
|
||||||
|
if (!this.picker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.picker.hidePopup();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "FormDateTime:UpdatePicker": {
|
||||||
|
let value = aMessage.data.value;
|
||||||
|
debug("Input box value is now: " + value.hour + ":" + value.minute);
|
||||||
|
// TODO: updating picker will be handled in Bug 1283384.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// nsIDOMEventListener
|
||||||
|
handleEvent: function(aEvent) {
|
||||||
|
debug("handleEvent: " + aEvent.type);
|
||||||
|
switch (aEvent.type) {
|
||||||
|
case "DateTimePickerValueChanged": {
|
||||||
|
this.updateInputBoxValue(aEvent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "popuphidden": {
|
||||||
|
let browser = this.weakBrowser ? this.weakBrowser.get() : null;
|
||||||
|
if (browser) {
|
||||||
|
browser.messageManager.sendAsyncMessage("FormDateTime:PickerClosed");
|
||||||
|
}
|
||||||
|
this.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Called when picker value has changed, notify input box about it.
|
||||||
|
updateInputBoxValue: function(aEvent) {
|
||||||
|
// TODO: parse data based on input type.
|
||||||
|
const { hour, minute } = aEvent.detail;
|
||||||
|
debug("hour: " + hour + ", minute: " + minute);
|
||||||
|
let browser = this.weakBrowser ? this.weakBrowser.get() : null;
|
||||||
|
if (browser) {
|
||||||
|
browser.messageManager.sendAsyncMessage(
|
||||||
|
"FormDateTime:PickerValueChanged", { hour, minute });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get picker from browser and show it anchored to the input box.
|
||||||
|
showPicker: function(aBrowser, aData) {
|
||||||
|
let rect = aData.rect;
|
||||||
|
let dir = aData.dir;
|
||||||
|
let type = aData.type;
|
||||||
|
let detail = aData.detail;
|
||||||
|
debug("Opening picker with details: " + JSON.stringify(detail));
|
||||||
|
|
||||||
|
let window = aBrowser.ownerDocument.defaultView;
|
||||||
|
let tabbrowser = window.gBrowser;
|
||||||
|
if (Services.focus.activeWindow != window ||
|
||||||
|
tabbrowser.selectedBrowser != aBrowser) {
|
||||||
|
// We were sent a message from a window or tab that went into the
|
||||||
|
// background, so we'll ignore it for now.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.weakBrowser = Cu.getWeakReference(aBrowser);
|
||||||
|
this.picker = aBrowser.dateTimePicker;
|
||||||
|
if (!this.picker) {
|
||||||
|
debug("aBrowser.dateTimePicker not found, exiting now.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.picker.hidden = false;
|
||||||
|
this.picker.openPopupAtScreenRect("after_start", rect.left, rect.top,
|
||||||
|
rect.width, rect.height, false, false);
|
||||||
|
this.addPickerListeners();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Picker is closed, do some cleanup.
|
||||||
|
close: function() {
|
||||||
|
this.removePickerListeners();
|
||||||
|
this.picker = null;
|
||||||
|
this.weakBrowser = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Listen to picker's event.
|
||||||
|
addPickerListeners: function() {
|
||||||
|
if (!this.picker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.picker.addEventListener("popuphidden", this);
|
||||||
|
this.picker.addEventListener("DateTimePickerValueChanged", this);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Stop listening to picker's event.
|
||||||
|
removePickerListeners: function() {
|
||||||
|
if (!this.picker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.picker.removeEventListener("popuphidden", this);
|
||||||
|
this.picker.removeEventListener("DateTimePickerValueChanged", this);
|
||||||
|
},
|
||||||
|
};
|
|
@ -35,6 +35,7 @@ EXTRA_JS_MODULES += [
|
||||||
'ClientID.jsm',
|
'ClientID.jsm',
|
||||||
'Color.jsm',
|
'Color.jsm',
|
||||||
'Console.jsm',
|
'Console.jsm',
|
||||||
|
'DateTimePickerHelper.jsm',
|
||||||
'debug.js',
|
'debug.js',
|
||||||
'DeferredTask.jsm',
|
'DeferredTask.jsm',
|
||||||
'Deprecated.jsm',
|
'Deprecated.jsm',
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 12 12" style="enable-background:new 0 0 12 12;" xml:space="preserve">
|
||||||
|
<path id="Combined-Shape" d="M6,12c3.3,0,6-2.7,6-6S9.3,0,6,0S0,2.7,0,6S2.7,12,6,12z M9,8.1L8.1,9L6,6.9L3.9,9L3,8.1L5.1,6L3,3.9
|
||||||
|
L3.9,3L6,5.1L8.1,3L9,3.9L6.9,6L9,8.1z"/>
|
||||||
|
</svg>
|
После Ширина: | Высота: | Размер: 521 B |
|
@ -23,6 +23,7 @@ toolkit.jar:
|
||||||
skin/classic/global/config.css (../../shared/config.css)
|
skin/classic/global/config.css (../../shared/config.css)
|
||||||
skin/classic/global/icons/find-arrows.svg (../../shared/icons/find-arrows.svg)
|
skin/classic/global/icons/find-arrows.svg (../../shared/icons/find-arrows.svg)
|
||||||
skin/classic/global/icons/info.svg (../../shared/incontent-icons/info.svg)
|
skin/classic/global/icons/info.svg (../../shared/incontent-icons/info.svg)
|
||||||
|
skin/classic/global/icons/input-clear.svg (../../shared/icons/input-clear.svg)
|
||||||
skin/classic/global/icons/loading.png (../../shared/icons/loading.png)
|
skin/classic/global/icons/loading.png (../../shared/icons/loading.png)
|
||||||
skin/classic/global/icons/loading@2x.png (../../shared/icons/loading@2x.png)
|
skin/classic/global/icons/loading@2x.png (../../shared/icons/loading@2x.png)
|
||||||
skin/classic/global/icons/warning.svg (../../shared/incontent-icons/warning.svg)
|
skin/classic/global/icons/warning.svg (../../shared/incontent-icons/warning.svg)
|
||||||
|
|
Загрузка…
Ссылка в новой задаче