Bug 1288591 - Implement the layout for <input type=time>. r=mconley, r=dholbert, r=smaug

This commit is contained in:
Jessica Jong 2016-10-06 00:17:00 -04:00
Родитель b59026eb98
Коммит b3014cc00b
47 изменённых файлов: 2474 добавлений и 40 удалений

Просмотреть файл

@ -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 &lt;input type='time'&gt;</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 &lt;input type='time'&gt;</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)