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