vendor: 4,833 bytes, lines 1-152
1(function (global, factory) { 2 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 typeof define === 'function' && define.amd ? define(factory) : 4 (global = global || self, global.dialogPolyfill = factory()); 5}(this, function () { 'use strict'; 6 7 // nb. This is for IE10 and lower _only_. 8 var supportCustomEvent = window.CustomEvent; 9 if (!supportCustomEvent || typeof supportCustomEvent === 'object') { 10 supportCustomEvent = function CustomEvent(event, x) { 11 x = x || {}; 12 var ev = document.createEvent('CustomEvent'); 13 ev.initCustomEvent(event, !!x.bubbles, !!x.cancelable, x.detail || null); 14 return ev; 15 }; 16 supportCustomEvent.prototype = window.Event.prototype; 17 } 18 19 /** 20 * @param {Element} el to check for stacking context 21 * @return {boolean} whether this el or its parents creates a stacking context 22 */ 23 function createsStackingContext(el) { 24 while (el && el !== document.body) { 25 var s = window.getComputedStyle(el); 26 var invalid = function(k, ok) { 27 return !(s[k] === undefined || s[k] === ok); 28 }; 29 30 if (s.opacity < 1 || 31 invalid('zIndex', 'auto') || 32 invalid('transform', 'none') || 33 invalid('mixBlendMode', 'normal') || 34 invalid('filter', 'none') || 35 invalid('perspective', 'none') || 36 s['isolation'] === 'isolate' || 37 s.position === 'fixed' || 38 s.webkitOverflowScrolling === 'touch') { 39 return true; 40 } 41 el = el.parentElement; 42 } 43 return false; 44 } 45 46 /** 47 * Finds the nearest <dialog> from the passed element. 48 * 49 * @param {Element} el to search from 50 * @return {HTMLDialogElement} dialog found 51 */ 52 function findNearestDialog(el) { 53 while (el) { 54 if (el.localName === 'dialog') { 55 return /** @type {HTMLDialogElement} */ (el); 56 } 57 el = el.parentElement; 58 } 59 return null; 60 } 61 62 /** 63 * Blur the specified element, as long as it's not the HTML body element. 64 * This works around an IE9/10 bug - blurring the body causes Windows to 65 * blur the whole application. 66 * 67 * @param {Element} el to blur 68 */ 69 function safeBlur(el) { 70 // Find the actual focused element when the active element is inside a shadow root 71 while (el && el.shadowRoot && el.shadowRoot.activeElement) { 72 el = el.shadowRoot.activeElement; 73 } 74 75 if (el && el.blur && el !== document.body) { 76 el.blur(); 77 } 78 } 79 80 /** 81 * @param {!NodeList} nodeList to search 82 * @param {Node} node to find 83 * @return {boolean} whether node is inside nodeList 84 */ 85 function inNodeList(nodeList, node) { 86 for (var i = 0; i < nodeList.length; ++i) { 87 if (nodeList[i] === node) { 88 return true; 89 } 90 } 91 return false; 92 } 93 94 /** 95 * @param {HTMLFormElement} el to check 96 * @return {boolean} whether this form has method="dialog" 97 */ 98 function isFormMethodDialog(el) { 99 if (!el || !el.hasAttribute('method')) { 100 return false; 101 } 102 return el.getAttribute('method').toLowerCase() === 'dialog'; 103 } 104 105 /** 106 * @param {!DocumentFragment|!Element} hostElement 107 * @return {?Element} 108 */ 109 function findFocusableElementWithin(hostElement) { 110 // Note that this is 'any focusable area'. This list is probably not exhaustive, but the 111 // alternative involves stepping through and trying to focus everything. 112 var opts = ['button', 'input', 'keygen', 'select', 'textarea']; 113 var query = opts.map(function(el) { 114 return el + ':not([disabled])'; 115 }); 116 // TODO(samthor): tabindex values that are not numeric are not focusable. 117 query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled 118 var target = hostElement.querySelector(query.join(', ')); 119 120 if (!target && 'attachShadow' in Element.prototype) { 121 // If we haven't found a focusable target, see if the host element contains an element 122 // which has a shadowRoot. 123 // Recursively search for the first focusable item in shadow roots. 124 var elems = hostElement.querySelectorAll('*'); 125 for (var i = 0; i < elems.length; i++) { 126 if (elems[i].tagName && elems[i].shadowRoot) { 127 target = findFocusableElementWithin(elems[i].shadowRoot); 128 if (target) { 129 break; 130 } 131 } 132 } 133 } 134 return target; 135 } 136 137 /** 138 * Determines if an element is attached to the DOM. 139 * @param {Element} element to check 140 * @return {Boolean} whether the element is in DOM 141 */ 142 function isConnected(element) { 143 return element.isConnected || document.body.contains(element); 144 } 145 146 /** 147 * @param {!HTMLDialogElement} dialog to upgrade 148 * @constructor 149 */ 150 function dialogPolyfillInfo(dialog) { 151 this.dialog_ = dialog; 152 this.replacedStyleTop_ = false;
vendor: 20,090 bytes, lines 153-721
153 this.openAsModal_ = false; 154 155 // Set a11y role. Browsers that support dialog implicitly know this already. 156 if (!dialog.hasAttribute('role')) { 157 dialog.setAttribute('role', 'dialog'); 158 } 159 160 dialog.show = this.show.bind(this); 161 dialog.showModal = this.showModal.bind(this); 162 dialog.close = this.close.bind(this); 163 164 if (!('returnValue' in dialog)) { 165 dialog.returnValue = ''; 166 } 167 168 if ('MutationObserver' in window) { 169 var mo = new MutationObserver(this.maybeHideModal.bind(this)); 170 mo.observe(dialog, {attributes: true, attributeFilter: ['open']}); 171 } else { 172 // IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also 173 // seem to fire even if the element was removed as part of a parent removal. Use the removed 174 // events to force downgrade (useful if removed/immediately added). 175 var removed = false; 176 var cb = function() { 177 removed ? this.downgradeModal() : this.maybeHideModal(); 178 removed = false; 179 }.bind(this); 180 var timeout; 181 var delayModel = function(ev) { 182 if (ev.target !== dialog) { return; } // not for a child element 183 var cand = 'DOMNodeRemoved'; 184 removed |= (ev.type.substr(0, cand.length) === cand); 185 window.clearTimeout(timeout); 186 timeout = window.setTimeout(cb, 0); 187 }; 188 ['DOMAttrModified', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument'].forEach(function(name) { 189 dialog.addEventListener(name, delayModel); 190 }); 191 } 192 // Note that the DOM is observed inside DialogManager while any dialog 193 // is being displayed as a modal, to catch modal removal from the DOM. 194 195 Object.defineProperty(dialog, 'open', { 196 set: this.setOpen.bind(this), 197 get: dialog.hasAttribute.bind(dialog, 'open') 198 }); 199 200 this.backdrop_ = document.createElement('div'); 201 this.backdrop_.className = 'backdrop'; 202 this.backdrop_.addEventListener('click', this.backdropClick_.bind(this)); 203 } 204 205 dialogPolyfillInfo.prototype = /** @type {HTMLDialogElement.prototype} */ ({ 206 207 get dialog() { 208 return this.dialog_; 209 }, 210 211 /** 212 * Maybe remove this dialog from the modal top layer. This is called when 213 * a modal dialog may no longer be tenable, e.g., when the dialog is no 214 * longer open or is no longer part of the DOM. 215 */ 216 maybeHideModal: function() { 217 if (this.dialog_.hasAttribute('open') && isConnected(this.dialog_)) { return; } 218 this.downgradeModal(); 219 }, 220 221 /** 222 * Remove this dialog from the modal top layer, leaving it as a non-modal. 223 */ 224 downgradeModal: function() { 225 if (!this.openAsModal_) { return; } 226 this.openAsModal_ = false; 227 this.dialog_.style.zIndex = ''; 228 229 // This won't match the native <dialog> exactly because if the user set top on a centered 230 // polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's 231 // possible to polyfill this perfectly. 232 if (this.replacedStyleTop_) { 233 this.dialog_.style.top = ''; 234 this.replacedStyleTop_ = false; 235 } 236 237 // Clear the backdrop and remove from the manager. 238 this.backdrop_.parentNode && this.backdrop_.parentNode.removeChild(this.backdrop_); 239 dialogPolyfill.dm.removeDialog(this); 240 }, 241 242 /** 243 * @param {boolean} value whether to open or close this dialog 244 */ 245 setOpen: function(value) { 246 if (value) { 247 this.dialog_.hasAttribute('open') || this.dialog_.setAttribute('open', ''); 248 } else { 249 this.dialog_.removeAttribute('open'); 250 this.maybeHideModal(); // nb. redundant with MutationObserver 251 } 252 }, 253 254 /** 255 * Handles clicks on the fake .backdrop element, redirecting them as if 256 * they were on the dialog itself. 257 * 258 * @param {!Event} e to redirect 259 */ 260 backdropClick_: function(e) { 261 if (!this.dialog_.hasAttribute('tabindex')) { 262 // Clicking on the backdrop should move the implicit cursor, even if dialog cannot be 263 // focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this 264 // would not be needed - clicks would move the implicit cursor there. 265 var fake = document.createElement('div'); 266 this.dialog_.insertBefore(fake, this.dialog_.firstChild); 267 fake.tabIndex = -1; 268 fake.focus(); 269 this.dialog_.removeChild(fake); 270 } else { 271 this.dialog_.focus(); 272 } 273 274 var redirectedEvent = document.createEvent('MouseEvents'); 275 redirectedEvent.initMouseEvent(e.type, e.bubbles, e.cancelable, window, 276 e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, 277 e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget); 278 this.dialog_.dispatchEvent(redirectedEvent); 279 e.stopPropagation(); 280 }, 281 282 /** 283 * Focuses on the first focusable element within the dialog. This will always blur the current 284 * focus, even if nothing within the dialog is found. 285 */ 286 focus_: function() { 287 // Find element with `autofocus` attribute, or fall back to the first form/tabindex control. 288 var target = this.dialog_.querySelector('[autofocus]:not([disabled])'); 289 if (!target && this.dialog_.tabIndex >= 0) { 290 target = this.dialog_; 291 } 292 if (!target) { 293 target = findFocusableElementWithin(this.dialog_); 294 } 295 safeBlur(document.activeElement); 296 target && target.focus(); 297 }, 298 299 /** 300 * Sets the zIndex for the backdrop and dialog. 301 * 302 * @param {number} dialogZ 303 * @param {number} backdropZ 304 */ 305 updateZIndex: function(dialogZ, backdropZ) { 306 if (dialogZ < backdropZ) { 307 throw new Error('dialogZ should never be < backdropZ'); 308 } 309 this.dialog_.style.zIndex = dialogZ; 310 this.backdrop_.style.zIndex = backdropZ; 311 }, 312 313 /** 314 * Shows the dialog. If the dialog is already open, this does nothing. 315 */ 316 show: function() { 317 if (!this.dialog_.open) { 318 this.setOpen(true); 319 this.focus_(); 320 } 321 }, 322 323 /** 324 * Show this dialog modally. 325 */ 326 showModal: function() { 327 if (this.dialog_.hasAttribute('open')) { 328 throw new Error('Failed to execute \'showModal\' on dialog: The element is already open, and therefore cannot be opened modally.'); 329 } 330 if (!isConnected(this.dialog_)) { 331 throw new Error('Failed to execute \'showModal\' on dialog: The element is not in a Document.'); 332 } 333 if (!dialogPolyfill.dm.pushDialog(this)) { 334 throw new Error('Failed to execute \'showModal\' on dialog: There are too many open modal dialogs.'); 335 } 336 337 if (createsStackingContext(this.dialog_.parentElement)) { 338 console.warn('A dialog is being shown inside a stacking context. ' + 339 'This may cause it to be unusable. For more information, see this link: ' + 340 'https://github.com/GoogleChrome/dialog-polyfill/#stacking-context'); 341 } 342 343 this.setOpen(true); 344 this.openAsModal_ = true; 345 346 // Optionally center vertically, relative to the current viewport. 347 if (dialogPolyfill.needsCentering(this.dialog_)) { 348 dialogPolyfill.reposition(this.dialog_); 349 this.replacedStyleTop_ = true; 350 } else { 351 this.replacedStyleTop_ = false; 352 } 353 354 // Insert backdrop. 355 this.dialog_.parentNode.insertBefore(this.backdrop_, this.dialog_.nextSibling); 356 357 // Focus on whatever inside the dialog. 358 this.focus_(); 359 }, 360 361 /** 362 * Closes this HTMLDialogElement. This is optional vs clearing the open 363 * attribute, however this fires a 'close' event. 364 * 365 * @param {string=} opt_returnValue to use as the returnValue 366 */ 367 close: function(opt_returnValue) { 368 if (!this.dialog_.hasAttribute('open')) { 369 throw new Error('Failed to execute \'close\' on dialog: The element does not have an \'open\' attribute, and therefore cannot be closed.'); 370 } 371 this.setOpen(false); 372 373 // Leave returnValue untouched in case it was set directly on the element 374 if (opt_returnValue !== undefined) { 375 this.dialog_.returnValue = opt_returnValue; 376 } 377 378 // Triggering "close" event for any attached listeners on the <dialog>. 379 var closeEvent = new supportCustomEvent('close', { 380 bubbles: false, 381 cancelable: false 382 }); 383 this.dialog_.dispatchEvent(closeEvent); 384 } 385 386 }); 387 388 var dialogPolyfill = {}; 389 390 dialogPolyfill.reposition = function(element) { 391 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 392 var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2; 393 element.style.top = Math.max(scrollTop, topValue) + 'px'; 394 }; 395 396 dialogPolyfill.isInlinePositionSetByStylesheet = function(element) { 397 for (var i = 0; i < document.styleSheets.length; ++i) { 398 var styleSheet = document.styleSheets[i]; 399 var cssRules = null; 400 // Some browsers throw on cssRules. 401 try { 402 cssRules = styleSheet.cssRules; 403 } catch (e) {} 404 if (!cssRules) { continue; } 405 for (var j = 0; j < cssRules.length; ++j) { 406 var rule = cssRules[j]; 407 var selectedNodes = null; 408 // Ignore errors on invalid selector texts. 409 try { 410 selectedNodes = document.querySelectorAll(rule.selectorText); 411 } catch(e) {} 412 if (!selectedNodes || !inNodeList(selectedNodes, element)) { 413 continue; 414 } 415 var cssTop = rule.style.getPropertyValue('top'); 416 var cssBottom = rule.style.getPropertyValue('bottom'); 417 if ((cssTop && cssTop !== 'auto') || (cssBottom && cssBottom !== 'auto')) { 418 return true; 419 } 420 } 421 } 422 return false; 423 }; 424 425 dialogPolyfill.needsCentering = function(dialog) { 426 var computedStyle = window.getComputedStyle(dialog); 427 if (computedStyle.position !== 'absolute') { 428 return false; 429 } 430 431 // We must determine whether the top/bottom specified value is non-auto. In 432 // WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but 433 // Firefox returns the used value. So we do this crazy thing instead: check 434 // the inline style and then go through CSS rules. 435 if ((dialog.style.top !== 'auto' && dialog.style.top !== '') || 436 (dialog.style.bottom !== 'auto' && dialog.style.bottom !== '')) { 437 return false; 438 } 439 return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog); 440 }; 441 442 /** 443 * @param {!Element} element to force upgrade 444 */ 445 dialogPolyfill.forceRegisterDialog = function(element) { 446 if (window.HTMLDialogElement || element.showModal) { 447 console.warn('This browser already supports <dialog>, the polyfill ' + 448 'may not work correctly', element); 449 } 450 if (element.localName !== 'dialog') { 451 throw new Error('Failed to register dialog: The element is not a dialog.'); 452 } 453 new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ (element)); 454 }; 455 456 /** 457 * @param {!Element} element to upgrade, if necessary 458 */ 459 dialogPolyfill.registerDialog = function(element) { 460 if (!element.showModal) { 461 dialogPolyfill.forceRegisterDialog(element); 462 } 463 }; 464 465 /** 466 * @constructor 467 */ 468 dialogPolyfill.DialogManager = function() { 469 /** @type {!Array<!dialogPolyfillInfo>} */ 470 this.pendingDialogStack = []; 471 472 var checkDOM = this.checkDOM_.bind(this); 473 474 // The overlay is used to simulate how a modal dialog blocks the document. 475 // The blocking dialog is positioned on top of the overlay, and the rest of 476 // the dialogs on the pending dialog stack are positioned below it. In the 477 // actual implementation, the modal dialog stacking is controlled by the 478 // top layer, where z-index has no effect. 479 this.overlay = document.createElement('div'); 480 this.overlay.className = '_dialog_overlay'; 481 this.overlay.addEventListener('click', function(e) { 482 this.forwardTab_ = undefined; 483 e.stopPropagation(); 484 checkDOM([]); // sanity-check DOM 485 }.bind(this)); 486 487 this.handleKey_ = this.handleKey_.bind(this); 488 this.handleFocus_ = this.handleFocus_.bind(this); 489 490 this.zIndexLow_ = 100000; 491 this.zIndexHigh_ = 100000 + 150; 492 493 this.forwardTab_ = undefined; 494 495 if ('MutationObserver' in window) { 496 this.mo_ = new MutationObserver(function(records) { 497 var removed = []; 498 records.forEach(function(rec) { 499 for (var i = 0, c; c = rec.removedNodes[i]; ++i) { 500 if (!(c instanceof Element)) { 501 continue; 502 } else if (c.localName === 'dialog') { 503 removed.push(c); 504 } 505 removed = removed.concat(c.querySelectorAll('dialog')); 506 } 507 }); 508 removed.length && checkDOM(removed); 509 }); 510 } 511 }; 512 513 /** 514 * Called on the first modal dialog being shown. Adds the overlay and related 515 * handlers. 516 */ 517 dialogPolyfill.DialogManager.prototype.blockDocument = function() { 518 document.documentElement.addEventListener('focus', this.handleFocus_, true); 519 document.addEventListener('keydown', this.handleKey_); 520 this.mo_ && this.mo_.observe(document, {childList: true, subtree: true}); 521 }; 522 523 /** 524 * Called on the first modal dialog being removed, i.e., when no more modal 525 * dialogs are visible. 526 */ 527 dialogPolyfill.DialogManager.prototype.unblockDocument = function() { 528 document.documentElement.removeEventListener('focus', this.handleFocus_, true); 529 document.removeEventListener('keydown', this.handleKey_); 530 this.mo_ && this.mo_.disconnect(); 531 }; 532 533 /** 534 * Updates the stacking of all known dialogs. 535 */ 536 dialogPolyfill.DialogManager.prototype.updateStacking = function() { 537 var zIndex = this.zIndexHigh_; 538 539 for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) { 540 dpi.updateZIndex(--zIndex, --zIndex); 541 if (i === 0) { 542 this.overlay.style.zIndex = --zIndex; 543 } 544 } 545 546 // Make the overlay a sibling of the dialog itself. 547 var last = this.pendingDialogStack[0]; 548 if (last) { 549 var p = last.dialog.parentNode || document.body; 550 p.appendChild(this.overlay); 551 } else if (this.overlay.parentNode) { 552 this.overlay.parentNode.removeChild(this.overlay); 553 } 554 }; 555 556 /** 557 * @param {Element} candidate to check if contained or is the top-most modal dialog 558 * @return {boolean} whether candidate is contained in top dialog 559 */ 560 dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function(candidate) { 561 while (candidate = findNearestDialog(candidate)) { 562 for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) { 563 if (dpi.dialog === candidate) { 564 return i === 0; // only valid if top-most 565 } 566 } 567 candidate = candidate.parentElement; 568 } 569 return false; 570 }; 571 572 dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) { 573 var target = event.composedPath ? event.composedPath()[0] : event.target; 574 575 if (this.containedByTopDialog_(target)) { return; } 576 577 if (document.activeElement === document.documentElement) { return; } 578 579 event.preventDefault(); 580 event.stopPropagation(); 581 safeBlur(/** @type {Element} */ (target)); 582 583 if (this.forwardTab_ === undefined) { return; } // move focus only from a tab key 584 585 var dpi = this.pendingDialogStack[0]; 586 var dialog = dpi.dialog; 587 var position = dialog.compareDocumentPosition(target); 588 if (position & Node.DOCUMENT_POSITION_PRECEDING) { 589 if (this.forwardTab_) { 590 // forward 591 dpi.focus_(); 592 } else if (target !== document.documentElement) { 593 // backwards if we're not already focused on <html> 594 document.documentElement.focus(); 595 } 596 } 597 598 return false; 599 }; 600 601 dialogPolyfill.DialogManager.prototype.handleKey_ = function(event) { 602 this.forwardTab_ = undefined; 603 if (event.keyCode === 27) { 604 event.preventDefault(); 605 event.stopPropagation(); 606 var cancelEvent = new supportCustomEvent('cancel', { 607 bubbles: false, 608 cancelable: true 609 }); 610 var dpi = this.pendingDialogStack[0]; 611 if (dpi && dpi.dialog.dispatchEvent(cancelEvent)) { 612 dpi.dialog.close(); 613 } 614 } else if (event.keyCode === 9) { 615 this.forwardTab_ = !event.shiftKey; 616 } 617 }; 618 619 /** 620 * Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are 621 * removed and immediately readded don't stay modal, they become normal. 622 * 623 * @param {!Array<!HTMLDialogElement>} removed that have definitely been removed 624 */ 625 dialogPolyfill.DialogManager.prototype.checkDOM_ = function(removed) { 626 // This operates on a clone because it may cause it to change. Each change also calls 627 // updateStacking, which only actually needs to happen once. But who removes many modal dialogs 628 // at a time?! 629 var clone = this.pendingDialogStack.slice(); 630 clone.forEach(function(dpi) { 631 if (removed.indexOf(dpi.dialog) !== -1) { 632 dpi.downgradeModal(); 633 } else { 634 dpi.maybeHideModal(); 635 } 636 }); 637 }; 638 639 /** 640 * @param {!dialogPolyfillInfo} dpi 641 * @return {boolean} whether the dialog was allowed 642 */ 643 dialogPolyfill.DialogManager.prototype.pushDialog = function(dpi) { 644 var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1; 645 if (this.pendingDialogStack.length >= allowed) { 646 return false; 647 } 648 if (this.pendingDialogStack.unshift(dpi) === 1) { 649 this.blockDocument(); 650 } 651 this.updateStacking(); 652 return true; 653 }; 654 655 /** 656 * @param {!dialogPolyfillInfo} dpi 657 */ 658 dialogPolyfill.DialogManager.prototype.removeDialog = function(dpi) { 659 var index = this.pendingDialogStack.indexOf(dpi); 660 if (index === -1) { return; } 661 662 this.pendingDialogStack.splice(index, 1); 663 if (this.pendingDialogStack.length === 0) { 664 this.unblockDocument(); 665 } 666 this.updateStacking(); 667 }; 668 669 dialogPolyfill.dm = new dialogPolyfill.DialogManager(); 670 dialogPolyfill.formSubmitter = null; 671 dialogPolyfill.useValue = null; 672 673 /** 674 * Installs global handlers, such as click listers and native method overrides. These are needed 675 * even if a no dialog is registered, as they deal with <form method="dialog">. 676 */ 677 if (window.HTMLDialogElement === undefined) { 678 679 /** 680 * If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with 681 * one that returns the correct value. 682 */ 683 var testForm = document.createElement('form'); 684 testForm.setAttribute('method', 'dialog'); 685 if (testForm.method !== 'dialog') { 686 var methodDescriptor = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, 'method'); 687 if (methodDescriptor) { 688 // nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything 689 // and don't bother to update the element. 690 var realGet = methodDescriptor.get; 691 methodDescriptor.get = function() { 692 if (isFormMethodDialog(this)) { 693 return 'dialog'; 694 } 695 return realGet.call(this); 696 }; 697 var realSet = methodDescriptor.set; 698 /** @this {HTMLElement} */ 699 methodDescriptor.set = function(v) { 700 if (typeof v === 'string' && v.toLowerCase() === 'dialog') { 701 return this.setAttribute('method', v); 702 } 703 return realSet.call(this, v); 704 }; 705 Object.defineProperty(HTMLFormElement.prototype, 'method', methodDescriptor); 706 } 707 } 708 709 /** 710 * Global 'click' handler, to capture the <input type="submit"> or <button> element which has 711 * submitted a <form method="dialog">. Needed as Safari and others don't report this inside 712 * document.activeElement. 713 */ 714 document.addEventListener('click', function(ev) { 715 dialogPolyfill.formSubmitter = null; 716 dialogPolyfill.useValue = null; 717 if (ev.defaultPrevented) { return; } // e.g. a submit which prevents default submission 718 719 var target = /** @type {Element} */ (ev.target); 720 if (!target || !isFormMethodDialog(target.form)) { return; } 721
722 var valid = (target.type === 'submit' && ['button', 'input'].indexOf(target.localName) > -1); 723 if (!valid) { 724 if (!(target.localName === 'input' && target.type === 'image')) { return; } 725 // this is a <input type="image">, which can submit forms 726 dialogPolyfill.useValue = ev.offsetX + ',' + ev.offsetY; 727 } 728 729 var dialog = findNearestDialog(target); 730 if (!dialog) { return; } 731 732 dialogPolyfill.formSubmitter = target; 733 734 }, false); 735 736 /** 737 * Replace the native HTMLFormElement.submit() method, as it won't fire the 738 * submit event and give us a chance to respond. 739 */ 740 var nativeFormSubmit = HTMLFormElement.prototype.submit; 741 var replacementFormSubmit = function () { 742 if (!isFormMethodDialog(this)) { 743 return nativeFormSubmit.call(this); 744 } 745 var dialog = findNearestDialog(this); 746 dialog && dialog.close(); 747 }; 748 HTMLFormElement.prototype.submit = replacementFormSubmit; 749 750 /** 751 * Global form 'dialog' method handler. Closes a dialog correctly on submit 752 * and possibly sets its return value. 753 */ 754 document.addEventListener('submit', function(ev) { 755 if (ev.defaultPrevented) { return; } // e.g. a submit which prevents default submission 756 757 var form = /** @type {HTMLFormElement} */ (ev.target); 758 if (!isFormMethodDialog(form)) { return; } 759 ev.preventDefault(); 760 761 var dialog = findNearestDialog(form); 762 if (!dialog) { return; } 763 764 // Forms can only be submitted via .submit() or a click (?), but anyway: sanity-check that 765 // the submitter is correct before using its value as .returnValue. 766 var s = dialogPolyfill.formSubmitter; 767 if (s && s.form === form) { 768 dialog.close(dialogPolyfill.useValue || s.value); 769 } else { 770 dialog.close(); 771 } 772 dialogPolyfill.formSubmitter = null; 773 774 }, false); 775 } 776 777 return dialogPolyfill; 778 779}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.