Bug 1496242 - Part I, Simplify nsIDateTimeInputArea interface r=mconley,smaug

This patch simplifies the nsIDateTimeInputArea interface, implemented by the
datetimebox bindings, to a point that is easier to convert it to dispatch events,
by doing the following:

- hasBadInput() is re-implemented in C++ in nsIDateTimeControlFrame since
  C++ needs the return value synchronously.
- SetValueFromPicker() and SetPickerState() are avoided completed since they
  are simply called by HTMLInputElement methods exposed to the frame
  script. They are avoided by having the frame script access the NAC and call
  the nsIDateTimeInputArea methods directly.
- Merge setEditAttribute() and removeEditAttribute() to updateEditAttributes()
  which takes no arguments, and have the method access the attribute values by
  reading the values from <input>.

This patch is a scaled-down version of the patch proposed in bug 1456833.
The event approach is only usable in UA Widget version of datetimebox because
there is no way to avoid leaking events to the document without Shadow DOM.

Differential Revision: https://phabricator.services.mozilla.com/D9056

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Timothy Guan-tin Chien 2018-11-02 23:29:42 +00:00
Родитель f75b36a5b7
Коммит 3856b5e646
8 изменённых файлов: 73 добавлений и 220 удалений

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

@ -2220,30 +2220,13 @@ void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue)
aValue = *mDateTimeInputBoxValue;
}
void
HTMLInputElement::UpdateDateTimeInputBox(const DateTimeValue& aValue)
Element* HTMLInputElement::GetDateTimeBoxElement()
{
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);
return frame->GetInputAreaContent()->AsElement();
}
return nullptr;
}
void

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

@ -846,8 +846,12 @@ public:
* 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);
/*
* This allows chrome JavaScript to dispatch event to the inner datetimebox
* anonymous element or access nsIDateTimeInputArea implmentation.
*/
Element* GetDateTimeBoxElement();
/*
* The following functions are called from datetime input box XBL to control

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

@ -35,25 +35,15 @@ interface nsIDateTimeInputArea : nsISupports
*/
void blurInnerTextBox();
/**
* Called from DOM/Layout to know whether the current entered value is valid.
*/
boolean hasBadInput();
/**
* Set the current state of the picker, true if it's opened, false otherwise.
*/
void setPickerState(in boolean isOpen);
/**
* Set the attribute of the inner text boxes. Only "tabindex", "readonly",
* and "disabled" are allowed.
* Update the attribute of the inner text boxes by copying the attribute value
* from the input. Only values set to "tabindex", "readonly",
* and "disabled" attributes are copied.
*/
void setEditAttribute(in AString name, in AString value);
/**
* Remove the attribute of the inner text boxes. Only "tabindex", "readonly",
* and "disabled" are allowed.
*/
void removeEditAttribute(in AString name);
void updateEditAttributes();
};

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

@ -242,10 +242,7 @@ partial interface HTMLInputElement {
DateTimeValue getDateTimeInputBoxValue();
[Pref="dom.forms.datetime", ChromeOnly]
void updateDateTimeInputBox(optional DateTimeValue value);
[Pref="dom.forms.datetime", ChromeOnly]
void setDateTimePickerState(boolean open);
readonly attribute Element? dateTimeBoxElement;
[Pref="dom.forms.datetime", ChromeOnly,
BinaryName="getMinimumAsDouble"]

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

@ -18,6 +18,7 @@
#include "nsContentCreatorFunctions.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "nsDOMTokenList.h"
#include "nsNodeInfoManager.h"
#include "nsIDateTimeInputArea.h"
#include "nsIObserverService.h"
@ -73,47 +74,6 @@ nsDateTimeControlFrame::OnMinMaxStepAttrChanged()
}
}
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()
{
@ -137,15 +97,26 @@ nsDateTimeControlFrame::HandleBlurEvent()
bool
nsDateTimeControlFrame::HasBadInput()
{
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
do_QueryInterface(mInputAreaContent);
// Incomplete field does not imply bad input.
Element* editWrapperElement = mInputAreaContent->GetComposedDoc()->
GetAnonymousElementByAttribute(mInputAreaContent,
nsGkAtoms::anonid, NS_LITERAL_STRING("edit-wrapper"));
bool result = false;
if (inputAreaContent) {
inputAreaContent->HasBadInput(&result);
for (Element* child = editWrapperElement->GetFirstElementChild(); child; child = child->GetNextElementSibling()) {
if (child->ClassList()->Contains(NS_LITERAL_STRING("datetime-edit-field"))) {
nsAutoString value;
child->GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
if (value.IsEmpty()) {
return false;
}
}
}
return result;
// All fields are available but input element's value is empty implies
// it has been sanitized.
nsAutoString value;
HTMLInputElement::FromNode(mContent)->GetValue(value, CallerType::System);
return value.IsEmpty();
}
nscoord
@ -338,30 +309,6 @@ nsDateTimeControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
NS_TrustedNewXULElement(getter_AddRefs(mInputAreaContent), nodeInfo.forget());
aElements.AppendElement(mInputAreaContent);
nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
do_QueryInterface(mInputAreaContent);
if (inputAreaContent) {
// Propogate our tabindex.
nsAutoString tabIndexStr;
if (mContent->AsElement()->GetAttr(kNameSpaceID_None,
nsGkAtoms::tabindex,
tabIndexStr)) {
inputAreaContent->SetEditAttribute(NS_LITERAL_STRING("tabindex"),
tabIndexStr);
}
// Propagate our readonly state.
nsAutoString readonly;
if (mContent->AsElement()->GetAttr(kNameSpaceID_None,
nsGkAtoms::readonly,
readonly)) {
inputAreaContent->SetEditAttribute(NS_LITERAL_STRING("readonly"),
readonly);
}
SyncDisabledState();
}
return NS_OK;
}
@ -383,14 +330,7 @@ nsDateTimeControlFrame::SyncDisabledState()
if (!inputAreaContent) {
return;
}
EventStates eventStates = mContent->AsElement()->State();
if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
inputAreaContent->SetEditAttribute(NS_LITERAL_STRING("disabled"),
EmptyString());
} else {
inputAreaContent->RemoveEditAttribute(NS_LITERAL_STRING("disabled"));
}
inputAreaContent->UpdateEditAttributes();
}
nsresult
@ -421,20 +361,8 @@ nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID,
&nsIDateTimeInputArea::NotifyInputElementValueChanged));
}
} else {
if (aModType == MutationEvent_Binding::REMOVAL) {
if (inputAreaContent) {
nsAtomString name(aAttribute);
inputAreaContent->RemoveEditAttribute(name);
}
} else {
MOZ_ASSERT(aModType == MutationEvent_Binding::ADDITION ||
aModType == MutationEvent_Binding::MODIFICATION);
if (inputAreaContent) {
nsAtomString name(aAttribute);
nsAutoString value;
contentAsInputElem->GetAttr(aNameSpaceID, aAttribute, value);
inputAreaContent->SetEditAttribute(name, value);
}
if (inputAreaContent) {
inputAreaContent->UpdateEditAttributes();
}
}
}

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

@ -74,12 +74,12 @@ public:
nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
int32_t aModType) override;
nsIContent* GetInputAreaContent() const { return mInputAreaContent; }
void OnValueChanged();
void OnMinMaxStepAttrChanged();
void SetValueFromPicker(const DateTimeValue& aValue);
void HandleFocusEvent();
void HandleBlurEvent();
void SetPickerState(bool aOpen);
bool HasBadInput();
private:

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

@ -30,7 +30,9 @@ class DateTimePickerChild extends ActorChild {
*/
close() {
this.removeListeners();
this._inputElement.setDateTimePickerState(false);
if (this._inputElement.dateTimeBoxElement instanceof Ci.nsIDateTimeInputArea) {
this._inputElement.dateTimeBoxElement.setPickerState(false);
}
this._inputElement = null;
}
@ -89,7 +91,9 @@ class DateTimePickerChild extends ActorChild {
break;
}
case "FormDateTime:PickerValueChanged": {
this._inputElement.updateDateTimeInputBox(aMessage.data);
if (this._inputElement.dateTimeBoxElement instanceof Ci.nsIDateTimeInputArea) {
this._inputElement.dateTimeBoxElement.setValueFromPicker(aMessage.data);
}
break;
}
default:
@ -118,7 +122,11 @@ class DateTimePickerChild extends ActorChild {
}
this._inputElement = aEvent.originalTarget;
this._inputElement.setDateTimePickerState(true);
if (this._inputElement.dateTimeBoxElement instanceof Ci.nsIDateTimeInputArea) {
this._inputElement.dateTimeBoxElement.setPickerState(true);
}
this.addListeners();
let value = this._inputElement.getDateTimeInputBoxValue();

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

@ -49,6 +49,7 @@
this.mYearPageUpDownInterval = 10;
this.buildEditFields();
this.updateEditAttributes();
if (this.mInputElement.value) {
this.setFieldsFromInputValue();
@ -385,7 +386,7 @@
}
}
aField.setAttribute("rawValue", value);
aField.setAttribute("value", value);
// Display formatted value based on locale.
let minDigits = aField.getAttribute("mindigits");
@ -1080,7 +1081,7 @@
}
}
aField.setAttribute("rawValue", value);
aField.setAttribute("value", value);
let minDigits = aField.getAttribute("mindigits");
let formatted = value.toLocaleString(this.mLocales, {
@ -1120,6 +1121,7 @@
}
this.mDayPeriodField.textContent = aValue;
this.mDayPeriodField.setAttribute("value", aValue);
this.updateResetButtonVisibility();
]]>
</body>
@ -1274,7 +1276,6 @@
});
this.mInputElement.removeEventListener("click", this,
{ mozSystemGroup: true });
this.mInputElement = null;
]]>
</destructor>
@ -1324,6 +1325,12 @@
field.readOnly = this.mInputElement.readOnly;
field.setAttribute("aria-label", aLabel);
// Used to store the non-formatted value, cleared when value is
// cleared.
// nsDateTimeControlFrame::HasBadInput() will read this to decide
// if the input has value.
field.setAttribute("value", "");
if (aIsNumeric) {
field.classList.add("numeric");
// Maximum value allowed.
@ -1335,9 +1342,6 @@
// Used to store what the user has already typed in the field,
// cleared when value is cleared and when field is blurred.
field.setAttribute("typeBuffer", "");
// Used to store the non-formatted number, clered when value is
// cleared.
field.setAttribute("rawValue", "");
// Minimum digits to display, padded with leading 0s.
field.setAttribute("mindigits", aMinDigits);
// Maximum length for the field, will be advance to the next field
@ -1389,6 +1393,7 @@
child.classList.contains("datetime-edit-field")) {
this.mLastFocusedField = child;
child.focus();
this.log("focused");
break;
}
}
@ -1444,25 +1449,6 @@
</body>
</method>
<method name="hasBadInput">
<body>
<![CDATA[
// Incomplete field does not imply bad input.
if (this.isAnyFieldEmpty()) {
return false;
}
// All fields are available but input element's value is empty implies
// it has been sanitized.
if (!this.mInputElement.value) {
return true;
}
return false;
]]>
</body>
</method>
<method name="advanceToNextField">
<parameter name="aReverse"/>
<body>
@ -1500,17 +1486,10 @@
</body>
</method>
<method name="setEditAttribute">
<parameter name="aName"/>
<parameter name="aValue"/>
<method name="updateEditAttributes">
<body>
<![CDATA[
this.log("setAttribute: " + aName + "=" + aValue);
if (aName != "tabindex" && aName != "disabled" &&
aName != "readonly") {
return;
}
this.log("updateEditAttributes");
let editRoot =
document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
@ -1518,54 +1497,18 @@
for (let child = editRoot.firstChild; child; child = child.nextSibling) {
if ((child instanceof HTMLSpanElement) &&
child.classList.contains("datetime-edit-field")) {
// "disabled" and "readonly" must be set as attributes because they
// are not defined properties of HTMLSpanElement, and the stylesheet
// checks the literal string attribute values.
child.setAttribute("disabled", this.mInputElement.disabled);
child.setAttribute("readonly", this.mInputElement.readOnly);
switch (aName) {
case "tabindex":
child.setAttribute(aName, aValue);
break;
case "disabled": {
let value = this.mInputElement.disabled;
child.setAttribute("disabled", value);
child.disabled = value;
break;
}
case "readonly": {
let value = this.mInputElement.readOnly;
child.setAttribute("readonly", value);
child.readOnly = value;
break;
}
}
}
}
]]>
</body>
</method>
// Set property as well for convenience.
child.disabled = this.mInputElement.disabled;
child.readOnly = this.mInputElement.readOnly;
<method name="removeEditAttribute">
<parameter name="aName"/>
<body>
<![CDATA[
this.log("removeAttribute: " + aName);
if (aName != "tabindex" && aName != "disabled" &&
aName != "readonly") {
return;
}
let editRoot =
document.getAnonymousElementByAttribute(this, "anonid", "edit-wrapper");
for (let child = editRoot.firstChild; child; child = child.nextSibling) {
if ((child instanceof HTMLSpanElement) &&
child.classList.contains("datetime-edit-field")) {
child.removeAttribute(aName);
// Update property as well.
if (aName == "readonly") {
child.readOnly = false;
} else if (aName == "disabled") {
child.disabled = false;
}
// tabIndex works on all elements
child.tabIndex = this.mInputElement.tabIndex;
}
}
]]>
@ -1587,7 +1530,7 @@
return undefined;
}
let value = aField.getAttribute("rawValue");
let value = aField.getAttribute("value");
// Avoid returning 0 when field is empty.
return (this.isEmpty(value) ? undefined : Number(value));
]]>
@ -1599,9 +1542,9 @@
<body>
<![CDATA[
aField.textContent = aField.placeholder;
aField.setAttribute("value", "");
if (aField.classList.contains("numeric")) {
aField.setAttribute("typeBuffer", "");
aField.setAttribute("rawValue", "");
}
this.updateResetButtonVisibility();
]]>