third_party: update dialog polyfill
For golang/go#49556
For golang/go#49455
Change-Id: I6404c1b6075ac24fd7e0c7706a71ff10fdb2c7f8
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/378095
Trust: Jamal Carvalho <jamal@golang.org>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/third_party/dialog-polyfill/dialog-polyfill.css b/third_party/dialog-polyfill/dialog-polyfill.css
index 6b38bf0..60589b5 100644
--- a/third_party/dialog-polyfill/dialog-polyfill.css
+++ b/third_party/dialog-polyfill/dialog-polyfill.css
@@ -34,4 +34,4 @@
position: fixed;
top: 50%;
transform: translate(0, -50%);
-}
\ No newline at end of file
+}
diff --git a/third_party/dialog-polyfill/dialog-polyfill.esm.js b/third_party/dialog-polyfill/dialog-polyfill.esm.js
index 50e8773..da774bc 100644
--- a/third_party/dialog-polyfill/dialog-polyfill.esm.js
+++ b/third_party/dialog-polyfill/dialog-polyfill.esm.js
@@ -11,6 +11,22 @@
}
/**
+ * Dispatches the passed event to both an "on<type>" handler as well as via the
+ * normal dispatch operation. Does not bubble.
+ *
+ * @param {!EventTarget} target
+ * @param {!Event} event
+ * @return {boolean}
+ */
+function safeDispatchEvent(target, event) {
+ var check = 'on' + event.type.toLowerCase();
+ if (typeof target[check] === 'function') {
+ target[check](event);
+ }
+ return target.dispatchEvent(event);
+}
+
+/**
* @param {Element} el to check for stacking context
* @return {boolean} whether this el or its parents creates a stacking context
*/
@@ -48,7 +64,13 @@
if (el.localName === 'dialog') {
return /** @type {HTMLDialogElement} */ (el);
}
- el = el.parentElement;
+ if (el.parentElement) {
+ el = el.parentElement;
+ } else if (el.parentNode) {
+ el = el.parentNode.host;
+ } else {
+ el = null;
+ }
}
return null;
}
@@ -61,6 +83,11 @@
* @param {Element} el to blur
*/
function safeBlur(el) {
+ // Find the actual focused element when the active element is inside a shadow root
+ while (el && el.shadowRoot && el.shadowRoot.activeElement) {
+ el = el.shadowRoot.activeElement;
+ }
+
if (el && el.blur && el !== document.body) {
el.blur();
}
@@ -92,6 +119,112 @@
}
/**
+ * @param {!DocumentFragment|!Element} hostElement
+ * @return {?Element}
+ */
+function findFocusableElementWithin(hostElement) {
+ // Note that this is 'any focusable area'. This list is probably not exhaustive, but the
+ // alternative involves stepping through and trying to focus everything.
+ var opts = ['button', 'input', 'keygen', 'select', 'textarea'];
+ var query = opts.map(function(el) {
+ return el + ':not([disabled])';
+ });
+ // TODO(samthor): tabindex values that are not numeric are not focusable.
+ query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled
+ var target = hostElement.querySelector(query.join(', '));
+
+ if (!target && 'attachShadow' in Element.prototype) {
+ // If we haven't found a focusable target, see if the host element contains an element
+ // which has a shadowRoot.
+ // Recursively search for the first focusable item in shadow roots.
+ var elems = hostElement.querySelectorAll('*');
+ for (var i = 0; i < elems.length; i++) {
+ if (elems[i].tagName && elems[i].shadowRoot) {
+ target = findFocusableElementWithin(elems[i].shadowRoot);
+ if (target) {
+ break;
+ }
+ }
+ }
+ }
+ return target;
+}
+
+/**
+ * Determines if an element is attached to the DOM.
+ * @param {Element} element to check
+ * @return {boolean} whether the element is in DOM
+ */
+function isConnected(element) {
+ return element.isConnected || document.body.contains(element);
+}
+
+/**
+ * @param {!Event} event
+ * @return {?Element}
+ */
+function findFormSubmitter(event) {
+ if (event.submitter) {
+ return event.submitter;
+ }
+
+ var form = event.target;
+ if (!(form instanceof HTMLFormElement)) {
+ return null;
+ }
+
+ var submitter = dialogPolyfill.formSubmitter;
+ if (!submitter) {
+ var target = event.target;
+ var root = ('getRootNode' in target && target.getRootNode() || document);
+ submitter = root.activeElement;
+ }
+
+ if (!submitter || submitter.form !== form) {
+ return null;
+ }
+ return submitter;
+}
+
+/**
+ * @param {!Event} event
+ */
+function maybeHandleSubmit(event) {
+ if (event.defaultPrevented) {
+ return;
+ }
+ var form = /** @type {!HTMLFormElement} */ (event.target);
+
+ // We'd have a value if we clicked on an imagemap.
+ var value = dialogPolyfill.imagemapUseValue;
+ var submitter = findFormSubmitter(event);
+ if (value === null && submitter) {
+ value = submitter.value;
+ }
+
+ // There should always be a dialog as this handler is added specifically on them, but check just
+ // in case.
+ var dialog = findNearestDialog(form);
+ if (!dialog) {
+ return;
+ }
+
+ // Prefer formmethod on the button.
+ var formmethod = submitter && submitter.getAttribute('formmethod') || form.getAttribute('method');
+ if (formmethod !== 'dialog') {
+ return;
+ }
+ event.preventDefault();
+
+ if (value != null) {
+ // nb. we explicitly check against null/undefined
+ dialog.close(value);
+ } else {
+ dialog.close();
+ }
+}
+
+/**
* @param {!HTMLDialogElement} dialog to upgrade
* @constructor
*/
@@ -109,6 +242,8 @@
dialog.showModal = this.showModal.bind(this);
dialog.close = this.close.bind(this);
+ dialog.addEventListener('submit', maybeHandleSubmit, false);
+
if (!('returnValue' in dialog)) {
dialog.returnValue = '';
}
@@ -147,10 +282,12 @@
this.backdrop_ = document.createElement('div');
this.backdrop_.className = 'backdrop';
- this.backdrop_.addEventListener('click', this.backdropClick_.bind(this));
+ this.backdrop_.addEventListener('mouseup' , this.backdropMouseEvent_.bind(this));
+ this.backdrop_.addEventListener('mousedown', this.backdropMouseEvent_.bind(this));
+ this.backdrop_.addEventListener('click' , this.backdropMouseEvent_.bind(this));
}
-dialogPolyfillInfo.prototype = {
+dialogPolyfillInfo.prototype = /** @type {HTMLDialogElement.prototype} */ ({
get dialog() {
return this.dialog_;
@@ -162,7 +299,7 @@
* longer open or is no longer part of the DOM.
*/
maybeHideModal: function() {
- if (this.dialog_.hasAttribute('open') && document.body.contains(this.dialog_)) { return; }
+ if (this.dialog_.hasAttribute('open') && isConnected(this.dialog_)) { return; }
this.downgradeModal();
},
@@ -200,12 +337,12 @@
},
/**
- * Handles clicks on the fake .backdrop element, redirecting them as if
+ * Handles mouse events ('mouseup', 'mousedown', 'click') on the fake .backdrop element, redirecting them as if
* they were on the dialog itself.
*
* @param {!Event} e to redirect
*/
- backdropClick_: function(e) {
+ backdropMouseEvent_: function(e) {
if (!this.dialog_.hasAttribute('tabindex')) {
// Clicking on the backdrop should move the implicit cursor, even if dialog cannot be
// focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this
@@ -238,15 +375,7 @@
target = this.dialog_;
}
if (!target) {
- // Note that this is 'any focusable area'. This list is probably not exhaustive, but the
- // alternative involves stepping through and trying to focus everything.
- var opts = ['button', 'input', 'keygen', 'select', 'textarea'];
- var query = opts.map(function(el) {
- return el + ':not([disabled])';
- });
- // TODO(samthor): tabindex values that are not numeric are not focusable.
- query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled
- target = this.dialog_.querySelector(query.join(', '));
+ target = findFocusableElementWithin(this.dialog_);
}
safeBlur(document.activeElement);
target && target.focus();
@@ -283,7 +412,7 @@
if (this.dialog_.hasAttribute('open')) {
throw new Error('Failed to execute \'showModal\' on dialog: The element is already open, and therefore cannot be opened modally.');
}
- if (!document.body.contains(this.dialog_)) {
+ if (!isConnected(this.dialog_)) {
throw new Error('Failed to execute \'showModal\' on dialog: The element is not in a Document.');
}
if (!dialogPolyfill.dm.pushDialog(this)) {
@@ -336,10 +465,10 @@
bubbles: false,
cancelable: false
});
- this.dialog_.dispatchEvent(closeEvent);
+ safeDispatchEvent(this.dialog_, closeEvent);
}
-};
+});
var dialogPolyfill = {};
@@ -526,24 +655,26 @@
};
dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) {
- if (this.containedByTopDialog_(event.target)) { return; }
+ var target = event.composedPath ? event.composedPath()[0] : event.target;
+
+ if (this.containedByTopDialog_(target)) { return; }
if (document.activeElement === document.documentElement) { return; }
event.preventDefault();
event.stopPropagation();
- safeBlur(/** @type {Element} */ (event.target));
+ safeBlur(/** @type {Element} */ (target));
if (this.forwardTab_ === undefined) { return; } // move focus only from a tab key
var dpi = this.pendingDialogStack[0];
var dialog = dpi.dialog;
- var position = dialog.compareDocumentPosition(event.target);
+ var position = dialog.compareDocumentPosition(target);
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
if (this.forwardTab_) {
// forward
dpi.focus_();
- } else if (event.target !== document.documentElement) {
+ } else if (target !== document.documentElement) {
// backwards if we're not already focused on <html>
document.documentElement.focus();
}
@@ -562,7 +693,7 @@
cancelable: true
});
var dpi = this.pendingDialogStack[0];
- if (dpi && dpi.dialog.dispatchEvent(cancelEvent)) {
+ if (dpi && safeDispatchEvent(dpi.dialog, cancelEvent)) {
dpi.dialog.close();
}
} else if (event.keyCode === 9) {
@@ -622,7 +753,7 @@
dialogPolyfill.dm = new dialogPolyfill.DialogManager();
dialogPolyfill.formSubmitter = null;
-dialogPolyfill.useValue = null;
+dialogPolyfill.imagemapUseValue = null;
/**
* Installs global handlers, such as click listers and native method overrides. These are needed
@@ -649,6 +780,7 @@
return realGet.call(this);
};
var realSet = methodDescriptor.set;
+ /** @this {HTMLElement} */
methodDescriptor.set = function(v) {
if (typeof v === 'string' && v.toLowerCase() === 'dialog') {
return this.setAttribute('method', v);
@@ -666,17 +798,21 @@
*/
document.addEventListener('click', function(ev) {
dialogPolyfill.formSubmitter = null;
- dialogPolyfill.useValue = null;
+ dialogPolyfill.imagemapUseValue = null;
if (ev.defaultPrevented) { return; } // e.g. a submit which prevents default submission
var target = /** @type {Element} */ (ev.target);
+ if ('composedPath' in ev) {
+ var path = ev.composedPath();
+ target = path.shift() || target;
+ }
if (!target || !isFormMethodDialog(target.form)) { return; }
var valid = (target.type === 'submit' && ['button', 'input'].indexOf(target.localName) > -1);
if (!valid) {
if (!(target.localName === 'input' && target.type === 'image')) { return; }
// this is a <input type="image">, which can submit forms
- dialogPolyfill.useValue = ev.offsetX + ',' + ev.offsetY;
+ dialogPolyfill.imagemapUseValue = ev.offsetX + ',' + ev.offsetY;
}
var dialog = findNearestDialog(target);
@@ -687,6 +823,24 @@
}, false);
/**
+ * Global 'submit' handler. This handles submits of `method="dialog"` which are invalid, i.e.,
+ * outside a dialog. They get prevented.
+ */
+ document.addEventListener('submit', function(ev) {
+ var form = ev.target;
+ var dialog = findNearestDialog(form);
+ if (dialog) {
+ return; // ignore, handle there
+ }
+
+ var submitter = findFormSubmitter(ev);
+ var formmethod = submitter && submitter.getAttribute('formmethod') || form.getAttribute('method');
+ if (formmethod === 'dialog') {
+ ev.preventDefault();
+ }
+ });
+
+ /**
* Replace the native HTMLFormElement.submit() method, as it won't fire the
* submit event and give us a chance to respond.
*/
@@ -699,32 +853,6 @@
dialog && dialog.close();
};
HTMLFormElement.prototype.submit = replacementFormSubmit;
-
- /**
- * Global form 'dialog' method handler. Closes a dialog correctly on submit
- * and possibly sets its return value.
- */
- document.addEventListener('submit', function(ev) {
- if (ev.defaultPrevented) { return; } // e.g. a submit which prevents default submission
-
- var form = /** @type {HTMLFormElement} */ (ev.target);
- if (!isFormMethodDialog(form)) { return; }
- ev.preventDefault();
-
- var dialog = findNearestDialog(form);
- if (!dialog) { return; }
-
- // Forms can only be submitted via .submit() or a click (?), but anyway: sanity-check that
- // the submitter is correct before using its value as .returnValue.
- var s = dialogPolyfill.formSubmitter;
- if (s && s.form === form) {
- dialog.close(dialogPolyfill.useValue || s.value);
- } else {
- dialog.close();
- }
- dialogPolyfill.formSubmitter = null;
-
- }, false);
}
export default dialogPolyfill;
diff --git a/third_party/dialog-polyfill/dialog-polyfill.js b/third_party/dialog-polyfill/dialog-polyfill.js
index cee158c..aee7d96 100644
--- a/third_party/dialog-polyfill/dialog-polyfill.js
+++ b/third_party/dialog-polyfill/dialog-polyfill.js
@@ -17,6 +17,22 @@
}
/**
+ * Dispatches the passed event to both an "on<type>" handler as well as via the
+ * normal dispatch operation. Does not bubble.
+ *
+ * @param {!EventTarget} target
+ * @param {!Event} event
+ * @return {boolean}
+ */
+ function safeDispatchEvent(target, event) {
+ var check = 'on' + event.type.toLowerCase();
+ if (typeof target[check] === 'function') {
+ target[check](event);
+ }
+ return target.dispatchEvent(event);
+ }
+
+ /**
* @param {Element} el to check for stacking context
* @return {boolean} whether this el or its parents creates a stacking context
*/
@@ -54,7 +70,13 @@
if (el.localName === 'dialog') {
return /** @type {HTMLDialogElement} */ (el);
}
- el = el.parentElement;
+ if (el.parentElement) {
+ el = el.parentElement;
+ } else if (el.parentNode) {
+ el = el.parentNode.host;
+ } else {
+ el = null;
+ }
}
return null;
}
@@ -67,6 +89,11 @@
* @param {Element} el to blur
*/
function safeBlur(el) {
+ // Find the actual focused element when the active element is inside a shadow root
+ while (el && el.shadowRoot && el.shadowRoot.activeElement) {
+ el = el.shadowRoot.activeElement;
+ }
+
if (el && el.blur && el !== document.body) {
el.blur();
}
@@ -98,6 +125,112 @@
}
/**
+ * @param {!DocumentFragment|!Element} hostElement
+ * @return {?Element}
+ */
+ function findFocusableElementWithin(hostElement) {
+ // Note that this is 'any focusable area'. This list is probably not exhaustive, but the
+ // alternative involves stepping through and trying to focus everything.
+ var opts = ['button', 'input', 'keygen', 'select', 'textarea'];
+ var query = opts.map(function(el) {
+ return el + ':not([disabled])';
+ });
+ // TODO(samthor): tabindex values that are not numeric are not focusable.
+ query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled
+ var target = hostElement.querySelector(query.join(', '));
+
+ if (!target && 'attachShadow' in Element.prototype) {
+ // If we haven't found a focusable target, see if the host element contains an element
+ // which has a shadowRoot.
+ // Recursively search for the first focusable item in shadow roots.
+ var elems = hostElement.querySelectorAll('*');
+ for (var i = 0; i < elems.length; i++) {
+ if (elems[i].tagName && elems[i].shadowRoot) {
+ target = findFocusableElementWithin(elems[i].shadowRoot);
+ if (target) {
+ break;
+ }
+ }
+ }
+ }
+ return target;
+ }
+
+ /**
+ * Determines if an element is attached to the DOM.
+ * @param {Element} element to check
+ * @return {boolean} whether the element is in DOM
+ */
+ function isConnected(element) {
+ return element.isConnected || document.body.contains(element);
+ }
+
+ /**
+ * @param {!Event} event
+ * @return {?Element}
+ */
+ function findFormSubmitter(event) {
+ if (event.submitter) {
+ return event.submitter;
+ }
+
+ var form = event.target;
+ if (!(form instanceof HTMLFormElement)) {
+ return null;
+ }
+
+ var submitter = dialogPolyfill.formSubmitter;
+ if (!submitter) {
+ var target = event.target;
+ var root = ('getRootNode' in target && target.getRootNode() || document);
+ submitter = root.activeElement;
+ }
+
+ if (!submitter || submitter.form !== form) {
+ return null;
+ }
+ return submitter;
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ function maybeHandleSubmit(event) {
+ if (event.defaultPrevented) {
+ return;
+ }
+ var form = /** @type {!HTMLFormElement} */ (event.target);
+
+ // We'd have a value if we clicked on an imagemap.
+ var value = dialogPolyfill.imagemapUseValue;
+ var submitter = findFormSubmitter(event);
+ if (value === null && submitter) {
+ value = submitter.value;
+ }
+
+ // There should always be a dialog as this handler is added specifically on them, but check just
+ // in case.
+ var dialog = findNearestDialog(form);
+ if (!dialog) {
+ return;
+ }
+
+ // Prefer formmethod on the button.
+ var formmethod = submitter && submitter.getAttribute('formmethod') || form.getAttribute('method');
+ if (formmethod !== 'dialog') {
+ return;
+ }
+ event.preventDefault();
+
+ if (value != null) {
+ // nb. we explicitly check against null/undefined
+ dialog.close(value);
+ } else {
+ dialog.close();
+ }
+ }
+
+ /**
* @param {!HTMLDialogElement} dialog to upgrade
* @constructor
*/
@@ -115,6 +248,8 @@
dialog.showModal = this.showModal.bind(this);
dialog.close = this.close.bind(this);
+ dialog.addEventListener('submit', maybeHandleSubmit, false);
+
if (!('returnValue' in dialog)) {
dialog.returnValue = '';
}
@@ -153,10 +288,12 @@
this.backdrop_ = document.createElement('div');
this.backdrop_.className = 'backdrop';
- this.backdrop_.addEventListener('click', this.backdropClick_.bind(this));
+ this.backdrop_.addEventListener('mouseup' , this.backdropMouseEvent_.bind(this));
+ this.backdrop_.addEventListener('mousedown', this.backdropMouseEvent_.bind(this));
+ this.backdrop_.addEventListener('click' , this.backdropMouseEvent_.bind(this));
}
- dialogPolyfillInfo.prototype = {
+ dialogPolyfillInfo.prototype = /** @type {HTMLDialogElement.prototype} */ ({
get dialog() {
return this.dialog_;
@@ -168,7 +305,7 @@
* longer open or is no longer part of the DOM.
*/
maybeHideModal: function() {
- if (this.dialog_.hasAttribute('open') && document.body.contains(this.dialog_)) { return; }
+ if (this.dialog_.hasAttribute('open') && isConnected(this.dialog_)) { return; }
this.downgradeModal();
},
@@ -206,12 +343,12 @@
},
/**
- * Handles clicks on the fake .backdrop element, redirecting them as if
+ * Handles mouse events ('mouseup', 'mousedown', 'click') on the fake .backdrop element, redirecting them as if
* they were on the dialog itself.
*
* @param {!Event} e to redirect
*/
- backdropClick_: function(e) {
+ backdropMouseEvent_: function(e) {
if (!this.dialog_.hasAttribute('tabindex')) {
// Clicking on the backdrop should move the implicit cursor, even if dialog cannot be
// focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this
@@ -244,15 +381,7 @@
target = this.dialog_;
}
if (!target) {
- // Note that this is 'any focusable area'. This list is probably not exhaustive, but the
- // alternative involves stepping through and trying to focus everything.
- var opts = ['button', 'input', 'keygen', 'select', 'textarea'];
- var query = opts.map(function(el) {
- return el + ':not([disabled])';
- });
- // TODO(samthor): tabindex values that are not numeric are not focusable.
- query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled
- target = this.dialog_.querySelector(query.join(', '));
+ target = findFocusableElementWithin(this.dialog_);
}
safeBlur(document.activeElement);
target && target.focus();
@@ -289,7 +418,7 @@
if (this.dialog_.hasAttribute('open')) {
throw new Error('Failed to execute \'showModal\' on dialog: The element is already open, and therefore cannot be opened modally.');
}
- if (!document.body.contains(this.dialog_)) {
+ if (!isConnected(this.dialog_)) {
throw new Error('Failed to execute \'showModal\' on dialog: The element is not in a Document.');
}
if (!dialogPolyfill.dm.pushDialog(this)) {
@@ -342,18 +471,10 @@
bubbles: false,
cancelable: false
});
-
- // If we have an onclose handler assigned and it's a function, call it
- if(this.dialog_.onclose instanceof Function) {
- this.dialog_.onclose(closeEvent);
- }
-
- // Dispatch the event as normal
- this.dialog_.dispatchEvent(closeEvent);
-
+ safeDispatchEvent(this.dialog_, closeEvent);
}
- };
+ });
var dialogPolyfill = {};
@@ -540,24 +661,26 @@
};
dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) {
- if (this.containedByTopDialog_(event.target)) { return; }
+ var target = event.composedPath ? event.composedPath()[0] : event.target;
+
+ if (this.containedByTopDialog_(target)) { return; }
if (document.activeElement === document.documentElement) { return; }
event.preventDefault();
event.stopPropagation();
- safeBlur(/** @type {Element} */ (event.target));
+ safeBlur(/** @type {Element} */ (target));
if (this.forwardTab_ === undefined) { return; } // move focus only from a tab key
var dpi = this.pendingDialogStack[0];
var dialog = dpi.dialog;
- var position = dialog.compareDocumentPosition(event.target);
+ var position = dialog.compareDocumentPosition(target);
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
if (this.forwardTab_) {
// forward
dpi.focus_();
- } else if (event.target !== document.documentElement) {
+ } else if (target !== document.documentElement) {
// backwards if we're not already focused on <html>
document.documentElement.focus();
}
@@ -576,7 +699,7 @@
cancelable: true
});
var dpi = this.pendingDialogStack[0];
- if (dpi && dpi.dialog.dispatchEvent(cancelEvent)) {
+ if (dpi && safeDispatchEvent(dpi.dialog, cancelEvent)) {
dpi.dialog.close();
}
} else if (event.keyCode === 9) {
@@ -636,7 +759,7 @@
dialogPolyfill.dm = new dialogPolyfill.DialogManager();
dialogPolyfill.formSubmitter = null;
- dialogPolyfill.useValue = null;
+ dialogPolyfill.imagemapUseValue = null;
/**
* Installs global handlers, such as click listers and native method overrides. These are needed
@@ -663,6 +786,7 @@
return realGet.call(this);
};
var realSet = methodDescriptor.set;
+ /** @this {HTMLElement} */
methodDescriptor.set = function(v) {
if (typeof v === 'string' && v.toLowerCase() === 'dialog') {
return this.setAttribute('method', v);
@@ -680,17 +804,21 @@
*/
document.addEventListener('click', function(ev) {
dialogPolyfill.formSubmitter = null;
- dialogPolyfill.useValue = null;
+ dialogPolyfill.imagemapUseValue = null;
if (ev.defaultPrevented) { return; } // e.g. a submit which prevents default submission
var target = /** @type {Element} */ (ev.target);
+ if ('composedPath' in ev) {
+ var path = ev.composedPath();
+ target = path.shift() || target;
+ }
if (!target || !isFormMethodDialog(target.form)) { return; }
var valid = (target.type === 'submit' && ['button', 'input'].indexOf(target.localName) > -1);
if (!valid) {
if (!(target.localName === 'input' && target.type === 'image')) { return; }
// this is a <input type="image">, which can submit forms
- dialogPolyfill.useValue = ev.offsetX + ',' + ev.offsetY;
+ dialogPolyfill.imagemapUseValue = ev.offsetX + ',' + ev.offsetY;
}
var dialog = findNearestDialog(target);
@@ -701,6 +829,24 @@
}, false);
/**
+ * Global 'submit' handler. This handles submits of `method="dialog"` which are invalid, i.e.,
+ * outside a dialog. They get prevented.
+ */
+ document.addEventListener('submit', function(ev) {
+ var form = ev.target;
+ var dialog = findNearestDialog(form);
+ if (dialog) {
+ return; // ignore, handle there
+ }
+
+ var submitter = findFormSubmitter(ev);
+ var formmethod = submitter && submitter.getAttribute('formmethod') || form.getAttribute('method');
+ if (formmethod === 'dialog') {
+ ev.preventDefault();
+ }
+ });
+
+ /**
* Replace the native HTMLFormElement.submit() method, as it won't fire the
* submit event and give us a chance to respond.
*/
@@ -713,32 +859,6 @@
dialog && dialog.close();
};
HTMLFormElement.prototype.submit = replacementFormSubmit;
-
- /**
- * Global form 'dialog' method handler. Closes a dialog correctly on submit
- * and possibly sets its return value.
- */
- document.addEventListener('submit', function(ev) {
- if (ev.defaultPrevented) { return; } // e.g. a submit which prevents default submission
-
- var form = /** @type {HTMLFormElement} */ (ev.target);
- if (!isFormMethodDialog(form)) { return; }
- ev.preventDefault();
-
- var dialog = findNearestDialog(form);
- if (!dialog) { return; }
-
- // Forms can only be submitted via .submit() or a click (?), but anyway: sanity-check that
- // the submitter is correct before using its value as .returnValue.
- var s = dialogPolyfill.formSubmitter;
- if (s && s.form === form) {
- dialog.close(dialogPolyfill.useValue || s.value);
- } else {
- dialog.close();
- }
- dialogPolyfill.formSubmitter = null;
-
- }, false);
}
return dialogPolyfill;