зеркало из 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"
|
||||
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,
|
||||
and the popuponly menulist makes things like the menuactive attributes
|
||||
work correctly on the menupopup. ContentSelectDropdown expects the
|
||||
|
@ -1056,7 +1062,8 @@
|
|||
tabcontainer="tabbrowser-tabs"
|
||||
contentcontextmenu="contentAreaContextMenu"
|
||||
autocompletepopup="PopupAutoComplete"
|
||||
selectmenulist="ContentSelectDropdown"/>
|
||||
selectmenulist="ContentSelectDropdown"
|
||||
datetimepicker="DateTimePickerPanel"/>
|
||||
</vbox>
|
||||
<vbox id="browser-border-end" hidden="true" layer="true"/>
|
||||
</hbox>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<xul:vbox flex="1" class="browserContainer">
|
||||
<xul:stack flex="1" class="browserStack" anonid="browserStack">
|
||||
<xul:browser anonid="initialBrowser" type="content-primary" message="true" messagemanagergroup="browsers"
|
||||
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectmenulist"/>
|
||||
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectmenulist,datetimepicker"/>
|
||||
</xul:stack>
|
||||
</xul:vbox>
|
||||
</xul:hbox>
|
||||
|
@ -1886,6 +1886,10 @@
|
|||
if (this.hasAttribute("selectmenulist"))
|
||||
b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
|
||||
|
||||
if (this.hasAttribute("datetimepicker")) {
|
||||
b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
|
||||
}
|
||||
|
||||
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
|
||||
|
||||
if (aParams.relatedBrowser) {
|
||||
|
|
|
@ -33,6 +33,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-s
|
|||
["ContentClick", "resource:///modules/ContentClick.jsm"],
|
||||
["ContentPrefServiceParent", "resource://gre/modules/ContentPrefServiceParent.jsm"],
|
||||
["ContentSearch", "resource:///modules/ContentSearch.jsm"],
|
||||
["DateTimePickerHelper", "resource://gre/modules/DateTimePickerHelper.jsm"],
|
||||
["DirectoryLinksProvider", "resource:///modules/DirectoryLinksProvider.jsm"],
|
||||
["Feeds", "resource:///modules/Feeds.jsm"],
|
||||
["FileUtils", "resource://gre/modules/FileUtils.jsm"],
|
||||
|
@ -1022,6 +1023,7 @@ BrowserGlue.prototype = {
|
|||
CaptivePortalWatcher.init();
|
||||
|
||||
AutoCompletePopup.init();
|
||||
DateTimePickerHelper.init();
|
||||
|
||||
this._firstWindowTelemetry(aWindow);
|
||||
this._firstWindowLoaded();
|
||||
|
@ -1053,6 +1055,7 @@ BrowserGlue.prototype = {
|
|||
webrtcUI.uninit();
|
||||
FormValidationHandler.uninit();
|
||||
AutoCompletePopup.uninit();
|
||||
DateTimePickerHelper.uninit();
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
AddonWatcher.uninit();
|
||||
}
|
||||
|
|
|
@ -272,6 +272,7 @@ GK_ATOM(dataType, "data-type")
|
|||
GK_ATOM(dateTime, "date-time")
|
||||
GK_ATOM(datasources, "datasources")
|
||||
GK_ATOM(datetime, "datetime")
|
||||
GK_ATOM(datetimebox, "datetimebox")
|
||||
GK_ATOM(dblclick, "dblclick")
|
||||
GK_ATOM(dd, "dd")
|
||||
GK_ATOM(debug, "debug")
|
||||
|
@ -1985,6 +1986,7 @@ GK_ATOM(colorControlFrame, "colorControlFrame")
|
|||
GK_ATOM(columnSetFrame, "ColumnSetFrame")
|
||||
GK_ATOM(comboboxControlFrame, "ComboboxControlFrame")
|
||||
GK_ATOM(comboboxDisplayFrame, "ComboboxDisplayFrame")
|
||||
GK_ATOM(dateTimeControlFrame, "DateTimeControlFrame")
|
||||
GK_ATOM(deckFrame, "DeckFrame")
|
||||
GK_ATOM(detailsFrame, "DetailsFrame")
|
||||
GK_ATOM(fieldSetFrame, "FieldSetFrame")
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
#include "nsIIOService.h"
|
||||
#include "nsDocument.h"
|
||||
#include "nsAttrValueOrString.h"
|
||||
#include "nsDateTimeControlFrame.h"
|
||||
|
||||
#include "nsPresState.h"
|
||||
#include "nsIDOMEvent.h"
|
||||
|
@ -2707,6 +2708,82 @@ HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
|
|||
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
|
||||
HTMLInputElement::MozIsTextField(bool aExcludePassword)
|
||||
{
|
||||
|
@ -2733,6 +2810,28 @@ HTMLInputElement::GetOwnerNumberControl()
|
|||
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
|
||||
HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
|
||||
{
|
||||
|
@ -2740,6 +2839,19 @@ HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
|
|||
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
|
||||
HTMLInputElement::SetUserInput(const nsAString& aValue)
|
||||
{
|
||||
|
@ -3164,6 +3276,12 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
|
|||
if (frame) {
|
||||
frame->UpdateForValueChange();
|
||||
}
|
||||
} else if (mType == NS_FORM_INPUT_TIME &&
|
||||
!IsExperimentalMobileType(mType)) {
|
||||
nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (frame) {
|
||||
frame->UpdateInputBoxValue();
|
||||
}
|
||||
}
|
||||
if (!mParserCreating) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
nsGenericHTMLElement::Focus(aError);
|
||||
return;
|
||||
|
@ -3788,7 +3923,7 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
|
|||
// 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
|
||||
// option has been enabled on desktop.
|
||||
if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
|
||||
if (IsExperimentalMobileType(mType)) {
|
||||
nsAutoString aValue;
|
||||
GetValueInternal(aValue);
|
||||
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 (mNumberControlSpinnerIsSpinning) {
|
||||
// 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();
|
||||
if (ownerNumberControl) {
|
||||
ownerNumberControl->AddStates(focusStates);
|
||||
} else {
|
||||
HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
|
||||
if (ownerDateTimeControl) {
|
||||
ownerDateTimeControl->AddStates(focusStates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6698,6 +6850,11 @@ HTMLInputElement::RemoveStates(EventStates aStates)
|
|||
HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
|
||||
if (ownerNumberControl) {
|
||||
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
|
||||
|
||||
if (mType == NS_FORM_INPUT_FILE ||
|
||||
mType == NS_FORM_INPUT_NUMBER) {
|
||||
mType == NS_FORM_INPUT_NUMBER ||
|
||||
mType == NS_FORM_INPUT_TIME) {
|
||||
if (aTabIndex) {
|
||||
// We only want our native anonymous child to be tabable to, not ourself.
|
||||
*aTabIndex = -1;
|
||||
}
|
||||
if (mType == NS_FORM_INPUT_NUMBER) {
|
||||
if (mType == NS_FORM_INPUT_NUMBER ||
|
||||
mType == NS_FORM_INPUT_TIME) {
|
||||
*aIsFocusable = true;
|
||||
} else {
|
||||
*aIsFocusable = defaultFocusable;
|
||||
|
|
|
@ -772,7 +772,24 @@ public:
|
|||
void MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles);
|
||||
void MozSetDirectory(const nsAString& aDirectoryPath, ErrorResult& aRv);
|
||||
|
||||
/*
|
||||
* The following functions are called from datetime picker to let input box
|
||||
* know the current state of the picker or to update the input box on changes.
|
||||
*/
|
||||
void GetDateTimeInputBoxValue(DateTimeValue& aValue);
|
||||
void UpdateDateTimeInputBox(const DateTimeValue& aValue);
|
||||
void SetDateTimePickerState(bool aOpen);
|
||||
|
||||
/*
|
||||
* The following functions are called from datetime input box XBL to control
|
||||
* and update the picker.
|
||||
*/
|
||||
void OpenDateTimePicker(const DateTimeValue& aInitialValue);
|
||||
void UpdateDateTimePicker(const DateTimeValue& aValue);
|
||||
void CloseDateTimePicker();
|
||||
|
||||
HTMLInputElement* GetOwnerNumberControl();
|
||||
HTMLInputElement* GetOwnerDateTimeControl();
|
||||
|
||||
void StartNumberControlSpinnerSpin();
|
||||
enum SpinnerStopState {
|
||||
|
@ -803,7 +820,8 @@ public:
|
|||
|
||||
nsIEditor* GetEditor();
|
||||
|
||||
// XPCOM SetUserInput() is OK
|
||||
void SetUserInput(const nsAString& aInput,
|
||||
const mozilla::Maybe<nsIPrincipal*>& aPrincipal);
|
||||
|
||||
// XPCOM GetPhonetic() is OK
|
||||
|
||||
|
@ -1471,6 +1489,12 @@ protected:
|
|||
*/
|
||||
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 number controls don't recycle their text field, so the normal cache in
|
||||
|
@ -1561,7 +1585,8 @@ private:
|
|||
static bool MayFireChangeOnBlur(uint8_t aType) {
|
||||
return IsSingleLineTextControl(false, aType) ||
|
||||
aType == NS_FORM_INPUT_RANGE ||
|
||||
aType == NS_FORM_INPUT_NUMBER;
|
||||
aType == NS_FORM_INPUT_NUMBER ||
|
||||
aType == NS_FORM_INPUT_TIME;
|
||||
}
|
||||
|
||||
struct nsFilePickerFilter {
|
||||
|
|
|
@ -18,6 +18,7 @@ MOCHITEST_CHROME_MANIFESTS += [
|
|||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIDateTimeInputArea.idl',
|
||||
'nsIFormSubmitObserver.idl',
|
||||
'nsIHTMLMenu.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_URL ||
|
||||
// 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 ||
|
||||
#endif
|
||||
aType == NS_FORM_INPUT_DATE ||
|
||||
aType == NS_FORM_INPUT_MONTH ||
|
||||
aType == NS_FORM_INPUT_WEEK ||
|
||||
(!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-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-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-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
|
||||
|
|
|
@ -35,6 +35,10 @@ skip-if = buildapp == 'mulet'
|
|||
skip-if = android_version == '18' # Android, bug 1147974
|
||||
[test_input_color_picker_update.html]
|
||||
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_email.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
|
||||
[test_input_sanitization.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_typing_sanitization.html]
|
||||
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',
|
||||
]
|
||||
},
|
||||
{
|
||||
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',
|
||||
validData: [
|
||||
|
|
|
@ -196,7 +196,7 @@ partial interface HTMLInputElement {
|
|||
// 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
|
||||
// for example will be dispatched when focusing out the element.
|
||||
[ChromeOnly]
|
||||
[Func="IsChromeOrXBL", NeedsSubjectPrincipal]
|
||||
void setUserInput(DOMString input);
|
||||
};
|
||||
|
||||
|
@ -234,3 +234,28 @@ partial interface HTMLInputElement {
|
|||
[Pref="dom.webkitBlink.dirPicker.enabled", BinaryName="WebkitDirectoryAttr", SetterThrows]
|
||||
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
|
||||
// fire.
|
||||
NS_ASSERTION(IsInVideoControls(this) ||
|
||||
IsInFeedSubscribeLine(this),
|
||||
IsInFeedSubscribeLine(this) ||
|
||||
IsXULElement(nsGkAtoms::datetimebox),
|
||||
"Unexpected XUL element in non-XUL doc");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3654,8 +3654,12 @@ nsCSSFrameConstructor::FindInputData(Element* aElement,
|
|||
SIMPLE_INT_CREATE(NS_FORM_INPUT_NUMBER, NS_NewNumberControlFrame),
|
||||
// TODO: this is temporary until a frame is written: bug 888320.
|
||||
SIMPLE_INT_CREATE(NS_FORM_INPUT_DATE, NS_NewTextControlFrame),
|
||||
// TODO: this is temporary until a frame is written: bug 888320
|
||||
#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),
|
||||
#else
|
||||
SIMPLE_INT_CREATE(NS_FORM_INPUT_TIME, NS_NewDateTimeControlFrame),
|
||||
#endif
|
||||
// TODO: this is temporary until a frame is written: bug 888320
|
||||
SIMPLE_INT_CREATE(NS_FORM_INPUT_MONTH, NS_NewTextControlFrame),
|
||||
// TODO: this is temporary until a frame is written: bug 888320
|
||||
|
|
|
@ -22,6 +22,7 @@ UNIFIED_SOURCES += [
|
|||
'nsButtonFrameRenderer.cpp',
|
||||
'nsColorControlFrame.cpp',
|
||||
'nsComboboxControlFrame.cpp',
|
||||
'nsDateTimeControlFrame.cpp',
|
||||
'nsFieldSetFrame.cpp',
|
||||
'nsFileControlFrame.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(nsContainerFrame)
|
||||
FRAME_ID(nsContinuingTextFrame)
|
||||
FRAME_ID(nsDateTimeControlFrame)
|
||||
FRAME_ID(nsDeckFrame)
|
||||
FRAME_ID(nsDocElementBoxFrame)
|
||||
FRAME_ID(nsFieldSetFrame)
|
||||
|
|
|
@ -171,6 +171,8 @@ nsIFrame*
|
|||
NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||
nsIFrame*
|
||||
NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||
nsIFrame*
|
||||
NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
|
||||
nsBlockFrame*
|
||||
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 hidden/reftest.list
|
||||
include color/reftest.list
|
||||
include datetime/reftest.list
|
||||
|
|
|
@ -767,6 +767,13 @@ video > .caption-box {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* datetime elements */
|
||||
|
||||
input[type="time"] > xul|datetimebox {
|
||||
display: flex;
|
||||
-moz-binding: url("chrome://global/content/bindings/datetimebox.xml#time-input");
|
||||
}
|
||||
|
||||
/* details & summary */
|
||||
/* Need to revert Bug 1259889 Part 2 when removing details preference. */
|
||||
@supports -moz-bool-pref("dom.details_element.enabled") {
|
||||
|
|
|
@ -167,7 +167,7 @@ function setupFormHistory(aCallback) {
|
|||
{ op : "add", fieldname : "field9", value : "value" },
|
||||
{ op : "add", fieldname : "field10", value : "42" },
|
||||
{ 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 : "field14", value : "#ffffff" }, // not used, since type=color doesn't have autocomplete currently
|
||||
{ op : "add", fieldname : "field15", value : "2016-08" },
|
||||
|
@ -913,15 +913,13 @@ function runTest() {
|
|||
|
||||
input = $_(15, "field12");
|
||||
restoreForm();
|
||||
expectPopup();
|
||||
doKey("down");
|
||||
waitForMenuChange(0);
|
||||
break;
|
||||
|
||||
case 406:
|
||||
checkMenuEntries(["21:21"]);
|
||||
doKey("down");
|
||||
doKey("return");
|
||||
checkForm("21:21");
|
||||
checkMenuEntries([]); // type=time with it's own control frame does not
|
||||
// have a drop down menu for now
|
||||
checkForm("");
|
||||
|
||||
input = $_(16, "field13");
|
||||
restoreForm();
|
||||
|
|
|
@ -1509,3 +1509,151 @@ let AutoCompletePopup = {
|
|||
}
|
||||
|
||||
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/colorpicker.xml (widgets/colorpicker.xml)
|
||||
content/global/bindings/datetimepicker.xml (widgets/datetimepicker.xml)
|
||||
content/global/bindings/datetimebox.xml (widgets/datetimebox.xml)
|
||||
content/global/bindings/datetimebox.css (widgets/datetimebox.css)
|
||||
* content/global/bindings/dialog.xml (widgets/dialog.xml)
|
||||
content/global/bindings/editor.xml (widgets/editor.xml)
|
||||
content/global/bindings/expander.xml (widgets/expander.xml)
|
||||
|
|
|
@ -281,6 +281,10 @@
|
|||
onget="return document.getElementById(this.getAttribute('autocompletepopup'))"
|
||||
readonly="true"/>
|
||||
|
||||
<property name="dateTimePicker"
|
||||
onget="return document.getElementById(this.getAttribute('datetimepicker'))"
|
||||
readonly="true"/>
|
||||
|
||||
<property name="docShellIsActive">
|
||||
<getter>
|
||||
<![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',
|
||||
'Color.jsm',
|
||||
'Console.jsm',
|
||||
'DateTimePickerHelper.jsm',
|
||||
'debug.js',
|
||||
'DeferredTask.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/icons/find-arrows.svg (../../shared/icons/find-arrows.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@2x.png (../../shared/icons/loading@2x.png)
|
||||
skin/classic/global/icons/warning.svg (../../shared/incontent-icons/warning.svg)
|
||||
|
|
Загрузка…
Ссылка в новой задаче