Title: [266514] trunk
Revision
266514
Author
akeer...@apple.com
Date
2020-09-03 07:40:06 -0700 (Thu, 03 Sep 2020)

Log Message

[macOS] Add disabled and readonly behaviors to date inputs
https://bugs.webkit.org/show_bug.cgi?id=216005

Reviewed by Devin Rousso.

Source/WebCore:

Added methods to EditControlOwner and FieldOwner so that
DateTimeEditElement and DateTimeFieldElement are aware of the readonly
and disabled states of their containing element.

Disabled inputs should not be focusable and should not respond to any
keyboard events. On the other hand, readonly inputs should respond to
some keyboard events (such as left arrow, right arrow, and the tab
key), but should not be editable.

* css/html.css: Updated to add GrayText color for disabled fields.
(input[disabled]::-webkit-datetime-edit-year-field,):
* html/BaseChooserOnlyDateAndTimeInputType.cpp:
(WebCore::BaseChooserOnlyDateAndTimeInputType::isEditControlOwnerDisabled const):
(WebCore::BaseChooserOnlyDateAndTimeInputType::isEditControlOwnerReadOnly const):
* html/BaseChooserOnlyDateAndTimeInputType.h:
* html/shadow/DateTimeEditElement.cpp:
(WebCore::DateTimeEditElement::isFieldOwnerDisabled const):
(WebCore::DateTimeEditElement::isFieldOwnerReadOnly const):
* html/shadow/DateTimeEditElement.h:
* html/shadow/DateTimeFieldElement.cpp:
(WebCore::DateTimeFieldElement::defaultEventHandler):

If the owning element is disabled or readonly, elide a call to
handleKeyboardEvent, which is implemented by subclasses to achieve
editability.

(WebCore::DateTimeFieldElement::defaultKeyboardEventHandler): Disabled inputs should not respond to any events.
(WebCore::DateTimeFieldElement::isFieldOwnerDisabled const):
(WebCore::DateTimeFieldElement::isFieldOwnerReadOnly const):
(WebCore::DateTimeFieldElement::isFocusable const): Disabled inputs should not be focusable.
* html/shadow/DateTimeFieldElement.h:

LayoutTests:

Added tests for disabled and readonly date inputs in existing test files.

* fast/forms/date/date-editable-components/date-editable-components-focus-and-blur-events-expected.txt:
* fast/forms/date/date-editable-components/date-editable-components-focus-and-blur-events.html:
* fast/forms/date/date-editable-components/date-editable-components-keyboard-events-expected.txt:
* fast/forms/date/date-editable-components/date-editable-components-keyboard-events.html:
* fast/forms/date/date-editable-components/date-editable-components-mouse-events-expected.txt:
* fast/forms/date/date-editable-components/date-editable-components-mouse-events.html:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (266513 => 266514)


--- trunk/LayoutTests/ChangeLog	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/LayoutTests/ChangeLog	2020-09-03 14:40:06 UTC (rev 266514)
@@ -1,3 +1,19 @@
+2020-09-03  Aditya Keerthi  <akeer...@apple.com>
+
+        [macOS] Add disabled and readonly behaviors to date inputs
+        https://bugs.webkit.org/show_bug.cgi?id=216005
+
+        Reviewed by Devin Rousso.
+
+        Added tests for disabled and readonly date inputs in existing test files.
+
+        * fast/forms/date/date-editable-components/date-editable-components-focus-and-blur-events-expected.txt:
+        * fast/forms/date/date-editable-components/date-editable-components-focus-and-blur-events.html:
+        * fast/forms/date/date-editable-components/date-editable-components-keyboard-events-expected.txt:
+        * fast/forms/date/date-editable-components/date-editable-components-keyboard-events.html:
+        * fast/forms/date/date-editable-components/date-editable-components-mouse-events-expected.txt:
+        * fast/forms/date/date-editable-components/date-editable-components-mouse-events.html:
+
 2020-09-03  Diego Pino Garcia  <dp...@igalia.com>
 
         [GTK] Unreviewed test gardening. Mark more convolver related tests as flaky crash.

Modified: trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-focus-and-blur-events-expected.txt (266513 => 266514)


--- trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-focus-and-blur-events-expected.txt	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-focus-and-blur-events-expected.txt	2020-09-03 14:40:06 UTC (rev 266514)
@@ -34,6 +34,27 @@
 PASS blurEventsFired is 1
 PASS focusEventsFired is 2
 PASS blurEventsFired is 2
+
+Focus/blur on disabled input
+
+PASS focusEventsFired is 0
+PASS blurEventsFired is 0
+PASS document.activeElement.id is "after"
+PASS focusEventsFired is 0
+PASS blurEventsFired is 0
+PASS document.activeElement.id is "before"
+PASS focusEventsFired is 0
+PASS blurEventsFired is 0
+
+Focus/blur on readonly input
+
+PASS focusEventsFired is 1
+PASS blurEventsFired is 0
+PASS document.activeElement.id is "after"
+PASS focusEventsFired is 1
+PASS blurEventsFired is 1
+PASS focusEventsFired is 1
+PASS blurEventsFired is 1
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-focus-and-blur-events.html (266513 => 266514)


--- trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-focus-and-blur-events.html	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-focus-and-blur-events.html	2020-09-03 14:40:06 UTC (rev 266514)
@@ -114,7 +114,53 @@
 // Focus out.
 UIHelper.keyDown("\t", ["shiftKey"]);
 assertFocusAndBlurCount(2, 2);
+resetFocusAndBlurCount();
 
+debug("\nFocus/blur on disabled input\n")
+
+input.disabled = true;
+
+UIHelper.activateElement(before);
+// Tab to focus should skip disabled input.
+UIHelper.keyDown("\t");
+assertFocusAndBlurCount(0, 0);
+shouldBeEqualToString("document.activeElement.id", "after");
+// Shift+Tab should skip disabled input.
+UIHelper.keyDown("\t", ["shiftKey"]);
+assertFocusAndBlurCount(0, 0);
+shouldBeEqualToString("document.activeElement.id", "before");
+// Clicking on any part of the control should not focus/blur events.
+mouseClickOn(20, center);
+mouseClickOn(60, center);
+mouseClickOn(120, center);
+mouseClickOn(250, center);
+mouseClickOn(input.offsetWidth + 5, input.offsetHeight + 5);
+assertFocusAndBlurCount(0, 0);
+resetFocusAndBlurCount();
+
+debug("\nFocus/blur on readonly input\n")
+
+input.disabled = false;
+input.readOnly = true;
+
+UIHelper.activateElement(before);
+// Tab to focus should not skip readonly input.
+UIHelper.keyDown("\t");
+assertFocusAndBlurCount(1, 0);
+UIHelper.keyDown("\t");
+UIHelper.keyDown("\t");
+UIHelper.keyDown("\t");
+shouldBeEqualToString("document.activeElement.id", "after");
+assertFocusAndBlurCount(1, 1);
+// Clicking on any part of the control should fire the appropriate events.
+mouseClickOn(20, center);
+mouseClickOn(60, center);
+mouseClickOn(120, center);
+mouseClickOn(250, center);
+mouseClickOn(input.offsetWidth + 5, input.offsetHeight + 5);
+assertFocusAndBlurCount(1, 1);
+resetFocusAndBlurCount();
+
 </script>
 
 <script src=""

Modified: trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-keyboard-events-expected.txt (266513 => 266514)


--- trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-keyboard-events-expected.txt	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-keyboard-events-expected.txt	2020-09-03 14:40:06 UTC (rev 266514)
@@ -53,6 +53,14 @@
 PASS input.value is ""
 PASS changeEventsFired is 1
 PASS inputEventsFired is 1
+
+Disabled/readonly
+PASS input.value is "2020-09-01"
+PASS input.value is "2020-01-01"
+PASS input.value is "2020-01-01"
+PASS input.value is "2020-01-02"
+PASS changeEventsFired is 2
+PASS inputEventsFired is 2
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-keyboard-events.html (266513 => 266514)


--- trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-keyboard-events.html	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-keyboard-events.html	2020-09-03 14:40:06 UTC (rev 266514)
@@ -164,6 +164,24 @@
     shouldBe("changeEventsFired", "1");
     shouldBe("inputEventsFired", "1");
 
+    beginTest("Disabled/readonly", "2020-09-01");
+    input.disabled = true;
+    UIHelper.keyDown("1");
+    shouldBeEqualToString("input.value", "2020-09-01");
+    input.disabled = false;
+    input.focus();
+    UIHelper.keyDown("1");
+    shouldBeEqualToString("input.value", "2020-01-01");
+    input.readOnly = true;
+    UIHelper.keyDown("rightArrow");
+    UIHelper.keyDown("2");
+    shouldBeEqualToString("input.value", "2020-01-01");
+    input.readOnly = false;
+    UIHelper.keyDown("2");
+    shouldBeEqualToString("input.value", "2020-01-02");
+    shouldBe("changeEventsFired", "2");
+    shouldBe("inputEventsFired", "2");
+
     finishJSTest();
 });
 

Modified: trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-mouse-events-expected.txt (266513 => 266514)


--- trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-mouse-events-expected.txt	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-mouse-events-expected.txt	2020-09-03 14:40:06 UTC (rev 266514)
@@ -3,6 +3,8 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
+Enabled Input
+
 PASS input.value is "2020-09-26"
 PASS input.value is "2020-09-12"
 PASS input.value is "3030-09-12"
@@ -9,6 +11,14 @@
 PASS input.value is "3030-05-12"
 PASS input.value is "3030-05-12"
 PASS clickEventsFired is 4
+
+Disabled Input
+
+PASS clickEventsFired is 0
+
+Readonly Input
+
+PASS clickEventsFired is 4
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-mouse-events.html (266513 => 266514)


--- trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-mouse-events.html	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/LayoutTests/fast/forms/date/date-editable-components/date-editable-components-mouse-events.html	2020-09-03 14:40:06 UTC (rev 266514)
@@ -49,6 +49,8 @@
 input.addEventListener("click", onClickEvent);
 const center = input.offsetHeight / 2;
 
+debug("Enabled Input\n");
+
 // Click on month field.
 mouseClickOn(20, center);
 UIHelper.keyDown("9");
@@ -80,6 +82,42 @@
 
 shouldBe("clickEventsFired", "4");
 
+debug("\nDisabled Input\n");
+clickEventsFired = 0;
+input.disabled = true;
+input.readOnly = false;
+
+// Click on month field.
+mouseClickOn(20, center);
+// Click on day field.
+mouseClickOn(60, center);
+// Click on year field.
+mouseClickOn(120, center);
+// Click on control, but not a specific field, defaults to first field.
+mouseClickOn(250, center);
+// Click outside control.
+mouseClickOn(input.offsetWidth + 5, input.offsetHeight + 5);
+
+shouldBe("clickEventsFired", "0");
+
+debug("\nReadonly Input\n");
+clickEventsFired = 0;
+input.disabled = false;
+input.readOnly = true;
+
+// Click on month field.
+mouseClickOn(20, center);
+// Click on day field.
+mouseClickOn(60, center);
+// Click on year field.
+mouseClickOn(120, center);
+// Click on control, but not a specific field, defaults to first field.
+mouseClickOn(250, center);
+// Click outside control.
+mouseClickOn(input.offsetWidth + 5, input.offsetHeight + 5);
+
+shouldBe("clickEventsFired", "4");
+
 </script>
 
 <script src=""

Modified: trunk/Source/WebCore/ChangeLog (266513 => 266514)


--- trunk/Source/WebCore/ChangeLog	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/Source/WebCore/ChangeLog	2020-09-03 14:40:06 UTC (rev 266514)
@@ -1,3 +1,42 @@
+2020-09-03  Aditya Keerthi  <akeer...@apple.com>
+
+        [macOS] Add disabled and readonly behaviors to date inputs
+        https://bugs.webkit.org/show_bug.cgi?id=216005
+
+        Reviewed by Devin Rousso.
+
+        Added methods to EditControlOwner and FieldOwner so that
+        DateTimeEditElement and DateTimeFieldElement are aware of the readonly
+        and disabled states of their containing element.
+
+        Disabled inputs should not be focusable and should not respond to any
+        keyboard events. On the other hand, readonly inputs should respond to
+        some keyboard events (such as left arrow, right arrow, and the tab
+        key), but should not be editable.
+
+        * css/html.css: Updated to add GrayText color for disabled fields.
+        (input[disabled]::-webkit-datetime-edit-year-field,):
+        * html/BaseChooserOnlyDateAndTimeInputType.cpp:
+        (WebCore::BaseChooserOnlyDateAndTimeInputType::isEditControlOwnerDisabled const):
+        (WebCore::BaseChooserOnlyDateAndTimeInputType::isEditControlOwnerReadOnly const):
+        * html/BaseChooserOnlyDateAndTimeInputType.h:
+        * html/shadow/DateTimeEditElement.cpp:
+        (WebCore::DateTimeEditElement::isFieldOwnerDisabled const):
+        (WebCore::DateTimeEditElement::isFieldOwnerReadOnly const):
+        * html/shadow/DateTimeEditElement.h:
+        * html/shadow/DateTimeFieldElement.cpp:
+        (WebCore::DateTimeFieldElement::defaultEventHandler):
+
+        If the owning element is disabled or readonly, elide a call to
+        handleKeyboardEvent, which is implemented by subclasses to achieve
+        editability.
+
+        (WebCore::DateTimeFieldElement::defaultKeyboardEventHandler): Disabled inputs should not respond to any events.
+        (WebCore::DateTimeFieldElement::isFieldOwnerDisabled const):
+        (WebCore::DateTimeFieldElement::isFieldOwnerReadOnly const):
+        (WebCore::DateTimeFieldElement::isFocusable const): Disabled inputs should not be focusable.
+        * html/shadow/DateTimeFieldElement.h:
+
 2020-09-03  Wenson Hsieh  <wenson_hs...@apple.com>
 
         Make TransformationMatrix::inverse() faster in the case of affine transformation matrices

Modified: trunk/Source/WebCore/css/html.css (266513 => 266514)


--- trunk/Source/WebCore/css/html.css	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/Source/WebCore/css/html.css	2020-09-03 14:40:06 UTC (rev 266514)
@@ -488,6 +488,8 @@
 #endif
 }
 
+/* FIXME: Styling inner elements of the date control should be specific to input[type="date"]. */
+
 input::-webkit-datetime-edit {
     display: inline-block;
     overflow: hidden;
@@ -517,6 +519,13 @@
     outline: none;
 }
 
+input[disabled]::-webkit-datetime-edit-year-field,
+input[disabled]::-webkit-datetime-edit-month-field,
+input[disabled]::-webkit-datetime-edit-day-field,
+input[disabled]::-webkit-datetime-edit-text {
+    color: GrayText;
+}
+
 input::-webkit-datetime-edit-text {
     display: inline;
 }

Modified: trunk/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.cpp (266513 => 266514)


--- trunk/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.cpp	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.cpp	2020-09-03 14:40:06 UTC (rev 266514)
@@ -320,6 +320,18 @@
         m_dateTimeChooser->showChooser(parameters);
 }
 
+bool BaseChooserOnlyDateAndTimeInputType::isEditControlOwnerDisabled() const
+{
+    ASSERT(element());
+    return element()->isDisabledFormControl();
+}
+
+bool BaseChooserOnlyDateAndTimeInputType::isEditControlOwnerReadOnly() const
+{
+    ASSERT(element());
+    return element()->isReadOnly();
+}
+
 AtomString BaseChooserOnlyDateAndTimeInputType::localeIdentifier() const
 {
     ASSERT(element());

Modified: trunk/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.h (266513 => 266514)


--- trunk/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.h	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/Source/WebCore/html/BaseChooserOnlyDateAndTimeInputType.h	2020-09-03 14:40:06 UTC (rev 266514)
@@ -65,6 +65,8 @@
     // DateTimeEditElement::EditControlOwner functions:
     void didBlurFromControl() final;
     void didChangeValueFromControl() final;
+    bool isEditControlOwnerDisabled() const final;
+    bool isEditControlOwnerReadOnly() const final;
     AtomString localeIdentifier() const final;
 
     // InputType functions:

Modified: trunk/Source/WebCore/html/shadow/DateTimeEditElement.cpp (266513 => 266514)


--- trunk/Source/WebCore/html/shadow/DateTimeEditElement.cpp	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/Source/WebCore/html/shadow/DateTimeEditElement.cpp	2020-09-03 14:40:06 UTC (rev 266514)
@@ -256,6 +256,16 @@
     return false;
 }
 
+bool DateTimeEditElement::isFieldOwnerDisabled() const
+{
+    return m_editControlOwner && m_editControlOwner->isEditControlOwnerDisabled();
+}
+
+bool DateTimeEditElement::isFieldOwnerReadOnly() const
+{
+    return m_editControlOwner && m_editControlOwner->isEditControlOwnerReadOnly();
+}
+
 AtomString DateTimeEditElement::localeIdentifier() const
 {
     return m_editControlOwner ? m_editControlOwner->localeIdentifier() : nullAtom();

Modified: trunk/Source/WebCore/html/shadow/DateTimeEditElement.h (266513 => 266514)


--- trunk/Source/WebCore/html/shadow/DateTimeEditElement.h	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/Source/WebCore/html/shadow/DateTimeEditElement.h	2020-09-03 14:40:06 UTC (rev 266514)
@@ -46,6 +46,8 @@
         virtual void didBlurFromControl() = 0;
         virtual void didChangeValueFromControl() = 0;
         virtual String formatDateTimeFieldsState(const DateTimeFieldsState&) const = 0;
+        virtual bool isEditControlOwnerDisabled() const = 0;
+        virtual bool isEditControlOwnerReadOnly() const = 0;
         virtual AtomString localeIdentifier() const = 0;
     };
 
@@ -96,6 +98,8 @@
     void fieldValueChanged() final;
     bool focusOnNextField(const DateTimeFieldElement&) final;
     bool focusOnPreviousField(const DateTimeFieldElement&) final;
+    bool isFieldOwnerDisabled() const final;
+    bool isFieldOwnerReadOnly() const final;
     AtomString localeIdentifier() const final;
 
     Vector<Ref<DateTimeFieldElement>, maximumNumberOfFields> m_fields;

Modified: trunk/Source/WebCore/html/shadow/DateTimeFieldElement.cpp (266513 => 266514)


--- trunk/Source/WebCore/html/shadow/DateTimeFieldElement.cpp	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/Source/WebCore/html/shadow/DateTimeFieldElement.cpp	2020-09-03 14:40:06 UTC (rev 266514)
@@ -61,9 +61,12 @@
 {
     if (is<KeyboardEvent>(event)) {
         auto& keyboardEvent = downcast<KeyboardEvent>(event);
-        handleKeyboardEvent(keyboardEvent);
-        if (keyboardEvent.defaultHandled())
-            return;
+        if (!isFieldOwnerDisabled() && !isFieldOwnerReadOnly()) {
+            handleKeyboardEvent(keyboardEvent);
+            if (keyboardEvent.defaultHandled())
+                return;
+        }
+
         defaultKeyboardEventHandler(keyboardEvent);
         if (keyboardEvent.defaultHandled())
             return;
@@ -74,6 +77,9 @@
 
 void DateTimeFieldElement::defaultKeyboardEventHandler(KeyboardEvent& keyboardEvent)
 {
+    if (isFieldOwnerDisabled())
+        return;
+
     if (keyboardEvent.type() != eventNames().keydownEvent)
         return;
 
@@ -89,6 +95,9 @@
         return;
     }
 
+    if (isFieldOwnerReadOnly())
+        return;
+
     // Clear value when pressing backspace or delete.
     if (key == "U+0008" || key == "U+007F") {
         keyboardEvent.setDefaultHandled();
@@ -97,6 +106,23 @@
     }
 }
 
+bool DateTimeFieldElement::isFieldOwnerDisabled() const
+{
+    return m_fieldOwner && m_fieldOwner->isFieldOwnerDisabled();
+}
+
+bool DateTimeFieldElement::isFieldOwnerReadOnly() const
+{
+    return m_fieldOwner && m_fieldOwner->isFieldOwnerReadOnly();
+}
+
+bool DateTimeFieldElement::isFocusable() const
+{
+    if (isFieldOwnerDisabled())
+        return false;
+    return HTMLElement::isFocusable();
+}
+
 void DateTimeFieldElement::dispatchBlurEvent(RefPtr<Element>&& newFocusedElement)
 {
     if (m_fieldOwner)

Modified: trunk/Source/WebCore/html/shadow/DateTimeFieldElement.h (266513 => 266514)


--- trunk/Source/WebCore/html/shadow/DateTimeFieldElement.h	2020-09-03 14:20:25 UTC (rev 266513)
+++ trunk/Source/WebCore/html/shadow/DateTimeFieldElement.h	2020-09-03 14:40:06 UTC (rev 266514)
@@ -49,11 +49,14 @@
         virtual void fieldValueChanged() = 0;
         virtual bool focusOnNextField(const DateTimeFieldElement&) = 0;
         virtual bool focusOnPreviousField(const DateTimeFieldElement&) = 0;
+        virtual bool isFieldOwnerDisabled() const = 0;
+        virtual bool isFieldOwnerReadOnly() const = 0;
         virtual AtomString localeIdentifier() const = 0;
     };
 
     void defaultEventHandler(Event&) override;
     void dispatchBlurEvent(RefPtr<Element>&& newFocusedElement) override;
+    bool isFocusable() const final;
 
     virtual bool hasValue() const = 0;
     virtual void populateDateTimeFieldsState(DateTimeFieldsState&) = 0;
@@ -78,6 +81,8 @@
     bool supportsFocus() const override;
 
     void defaultKeyboardEventHandler(KeyboardEvent&);
+    bool isFieldOwnerDisabled() const;
+    bool isFieldOwnerReadOnly() const;
 
     WeakPtr<FieldOwner> m_fieldOwner;
 };
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to