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;