Implement OCSP stapling in Windows BoringSSL port.
[chromium-blink-merge.git] / third_party / polymer / components-chromium / core-overlay / core-overlay-extracted.js
blob589952944df063799f6dfd2902cdd58730875ac4
2 (function() {
4 Polymer('core-overlay', {
6 publish: {
7 /**
8 * The target element that will be shown when the overlay is
9 * opened. If unspecified, the core-overlay itself is the target.
11 * @attribute target
12 * @type Object
13 * @default the overlay element
15 target: null,
18 /**
19 * A `core-overlay`'s size is guaranteed to be
20 * constrained to the window size. To achieve this, the sizingElement
21 * is sized with a max-height/width. By default this element is the
22 * target element, but it can be specifically set to a specific element
23 * inside the target if that is more appropriate. This is useful, for
24 * example, when a region inside the overlay should scroll if needed.
26 * @attribute sizingTarget
27 * @type Object
28 * @default the target element
30 sizingTarget: null,
32 /**
33 * Set opened to true to show an overlay and to false to hide it.
34 * A `core-overlay` may be made initially opened by setting its
35 * `opened` attribute.
36 * @attribute opened
37 * @type boolean
38 * @default false
40 opened: false,
42 /**
43 * If true, the overlay has a backdrop darkening the rest of the screen.
44 * The backdrop element is attached to the document body and may be styled
45 * with the class `core-overlay-backdrop`. When opened the `core-opened`
46 * class is applied.
48 * @attribute backdrop
49 * @type boolean
50 * @default false
51 */
52 backdrop: false,
54 /**
55 * If true, the overlay is guaranteed to display above page content.
57 * @attribute layered
58 * @type boolean
59 * @default false
61 layered: false,
63 /**
64 * By default an overlay will close automatically if the user
65 * taps outside it or presses the escape key. Disable this
66 * behavior by setting the `autoCloseDisabled` property to true.
67 * @attribute autoCloseDisabled
68 * @type boolean
69 * @default false
71 autoCloseDisabled: false,
73 /**
74 * This property specifies an attribute on elements that should
75 * close the overlay on tap. Should not set `closeSelector` if this
76 * is set.
78 * @attribute closeAttribute
79 * @type string
80 * @default "core-overlay-toggle"
82 closeAttribute: 'core-overlay-toggle',
84 /**
85 * This property specifies a selector matching elements that should
86 * close the overlay on tap. Should not set `closeAttribute` if this
87 * is set.
89 * @attribute closeSelector
90 * @type string
91 * @default ""
93 closeSelector: '',
95 /**
96 * A `core-overlay` target's size is constrained to the window size.
97 * The `margin` property specifies a pixel amount around the overlay
98 * that will be reserved. It's useful for ensuring that, for example,
99 * a shadow displayed outside the target will always be visible.
101 * @attribute margin
102 * @type number
103 * @default 0
105 margin: 0,
108 * The transition property specifies a string which identifies a
109 * <a href="../core-transition/">`core-transition`</a> element that
110 * will be used to help the overlay open and close. The default
111 * `core-transition-fade` will cause the overlay to fade in and out.
113 * @attribute transition
114 * @type string
115 * @default 'core-transition-fade'
117 transition: 'core-transition-fade'
121 captureEventName: 'tap',
122 targetListeners: {
123 'tap': 'tapHandler',
124 'keydown': 'keydownHandler',
125 'core-transitionend': 'transitionend'
128 registerCallback: function(element) {
129 this.layer = document.createElement('core-overlay-layer');
130 this.keyHelper = document.createElement('core-key-helper');
131 this.meta = document.createElement('core-transition');
132 this.scrim = document.createElement('div');
133 this.scrim.className = 'core-overlay-backdrop';
136 ready: function() {
137 this.target = this.target || this;
138 // flush to ensure styles are installed before paint
139 Platform.flush();
142 /**
143 * Toggle the opened state of the overlay.
144 * @method toggle
146 toggle: function() {
147 this.opened = !this.opened;
150 /**
151 * Open the overlay. This is equivalent to setting the `opened`
152 * property to true.
153 * @method open
155 open: function() {
156 this.opened = true;
159 /**
160 * Close the overlay. This is equivalent to setting the `opened`
161 * property to false.
162 * @method close
164 close: function() {
165 this.opened = false;
168 domReady: function() {
169 this.ensureTargetSetup();
172 targetChanged: function(old) {
173 if (this.target) {
174 // really make sure tabIndex is set
175 if (this.target.tabIndex < 0) {
176 this.target.tabIndex = -1;
178 this.addElementListenerList(this.target, this.targetListeners);
179 this.target.style.display = 'none';
181 if (old) {
182 this.removeElementListenerList(old, this.targetListeners);
183 var transition = this.getTransition();
184 if (transition) {
185 transition.teardown(old);
186 } else {
187 old.style.position = '';
188 old.style.outline = '';
190 old.style.display = '';
194 // NOTE: wait to call this until we're as sure as possible that target
195 // is styled.
196 ensureTargetSetup: function() {
197 if (!this.target || this.target.__overlaySetup) {
198 return;
200 this.target.__overlaySetup = true;
201 this.target.style.display = '';
202 var transition = this.getTransition();
203 if (transition) {
204 transition.setup(this.target);
206 var computed = getComputedStyle(this.target);
207 this.targetStyle = {
208 position: computed.position === 'static' ? 'fixed' :
209 computed.position
211 if (!transition) {
212 this.target.style.position = this.targetStyle.position;
213 this.target.style.outline = 'none';
215 this.target.style.display = 'none';
218 openedChanged: function() {
219 this.transitioning = true;
220 this.ensureTargetSetup();
221 this.prepareRenderOpened();
222 // continue styling after delay so display state can change
223 // without aborting transitions
224 // note: we wait a full frame so that transition changes executed
225 // during measuring do not cause transition
226 this.async(function() {
227 this.target.style.display = '';
228 this.async('renderOpened');
230 this.fire('core-overlay-open', this.opened);
233 // tasks which must occur before opening; e.g. making the element visible
234 prepareRenderOpened: function() {
235 if (this.opened) {
236 addOverlay(this);
238 this.prepareBackdrop();
239 // async so we don't auto-close immediately via a click.
240 this.async(function() {
241 if (!this.autoCloseDisabled) {
242 this.enableElementListener(this.opened, document,
243 this.captureEventName, 'captureHandler', true);
246 this.enableElementListener(this.opened, window, 'resize',
247 'resizeHandler');
249 if (this.opened) {
250 // TODO(sorvell): force SD Polyfill to render
251 forcePolyfillRender(this.target);
252 if (!this._shouldPosition) {
253 this.target.style.position = 'absolute';
254 var computed = getComputedStyle(this.target);
255 var t = (computed.top === 'auto' && computed.bottom === 'auto');
256 var l = (computed.left === 'auto' && computed.right === 'auto');
257 this.target.style.position = this.targetStyle.position;
258 this._shouldPosition = {top: t, left: l};
260 // if we are showing, then take care when measuring
261 this.prepareMeasure(this.target);
262 this.updateTargetDimensions();
263 this.finishMeasure(this.target);
264 if (this.layered) {
265 this.layer.addElement(this.target);
266 this.layer.opened = this.opened;
271 // tasks which cause the overlay to actually open; typically play an
272 // animation
273 renderOpened: function() {
274 var transition = this.getTransition();
275 if (transition) {
276 transition.go(this.target, {opened: this.opened});
277 } else {
278 this.transitionend();
280 this.renderBackdropOpened();
283 // finishing tasks; typically called via a transition
284 transitionend: function(e) {
285 // make sure this is our transition event.
286 if (e && e.target !== this.target) {
287 return;
289 this.transitioning = false;
290 if (!this.opened) {
291 this.resetTargetDimensions();
292 this.target.style.display = 'none';
293 this.completeBackdrop();
294 removeOverlay(this);
295 if (this.layered) {
296 if (!currentOverlay()) {
297 this.layer.opened = this.opened;
299 this.layer.removeElement(this.target);
302 this.applyFocus();
305 prepareBackdrop: function() {
306 if (this.backdrop && this.opened) {
307 if (!this.scrim.parentNode) {
308 document.body.appendChild(this.scrim);
309 this.scrim.style.zIndex = currentOverlayZ() - 1;
311 trackBackdrop(this);
315 renderBackdropOpened: function() {
316 if (this.backdrop && getBackdrops().length < 2) {
317 this.scrim.classList.toggle('core-opened', this.opened);
321 completeBackdrop: function() {
322 if (this.backdrop) {
323 trackBackdrop(this);
324 if (getBackdrops().length === 0) {
325 this.scrim.parentNode.removeChild(this.scrim);
330 prepareMeasure: function(target) {
331 target.style.transition = target.style.webkitTransition = 'none';
332 target.style.transform = target.style.webkitTransform = 'none';
333 target.style.display = '';
336 finishMeasure: function(target) {
337 target.style.display = 'none';
338 target.style.transform = target.style.webkitTransform = '';
339 target.style.transition = target.style.webkitTransition = '';
342 getTransition: function() {
343 return this.meta.byId(this.transition);
346 getFocusNode: function() {
347 return this.target.querySelector('[autofocus]') || this.target;
350 applyFocus: function() {
351 var focusNode = this.getFocusNode();
352 if (this.opened) {
353 focusNode.focus();
354 } else {
355 focusNode.blur();
356 if (currentOverlay() == this) {
357 console.warn('Current core-overlay is attempting to focus itself as next! (bug)');
358 } else {
359 focusOverlay();
364 updateTargetDimensions: function() {
365 this.positionTarget();
366 this.sizeTarget();
368 if (this.layered) {
369 var rect = this.target.getBoundingClientRect();
370 this.target.style.top = rect.top + 'px';
371 this.target.style.left = rect.left + 'px';
372 this.target.style.right = this.target.style.bottom = 'auto';
376 sizeTarget: function() {
377 var sizer = this.sizingTarget || this.target;
378 var rect = sizer.getBoundingClientRect();
379 var mt = rect.top === this.margin ? this.margin : this.margin * 2;
380 var ml = rect.left === this.margin ? this.margin : this.margin * 2;
381 var h = window.innerHeight - rect.top - mt;
382 var w = window.innerWidth - rect.left - ml;
383 sizer.style.maxHeight = h + 'px';
384 sizer.style.maxWidth = w + 'px';
385 sizer.style.boxSizing = 'border-box';
388 positionTarget: function() {
389 // vertically and horizontally center if not positioned
390 if (this._shouldPosition.top) {
391 var t = Math.max((window.innerHeight -
392 this.target.offsetHeight - this.margin*2) / 2, this.margin);
393 this.target.style.top = t + 'px';
395 if (this._shouldPosition.left) {
396 var l = Math.max((window.innerWidth -
397 this.target.offsetWidth - this.margin*2) / 2, this.margin);
398 this.target.style.left = l + 'px';
402 resetTargetDimensions: function() {
403 this.target.style.top = this.target.style.left = '';
404 this.target.style.right = this.target.style.bottom = '';
405 this.target.style.width = this.target.style.height = '';
406 this._shouldPosition = null;
409 tapHandler: function(e) {
410 // closeSelector takes precedence since closeAttribute has a default non-null value.
411 if (e.target &&
412 (this.closeSelector && e.target.matches(this.closeSelector)) ||
413 (this.closeAttribute && e.target.hasAttribute(this.closeAttribute))) {
414 this.toggle();
415 } else {
416 if (this.autoCloseJob) {
417 this.autoCloseJob.stop();
418 this.autoCloseJob = null;
423 // We use the traditional approach of capturing events on document
424 // to to determine if the overlay needs to close. However, due to
425 // ShadowDOM event retargeting, the event target is not useful. Instead
426 // of using it, we attempt to close asynchronously and prevent the close
427 // if a tap event is immediately heard on the target.
428 // TODO(sorvell): This approach will not work with modal. For
429 // this we need a scrim.
430 captureHandler: function(e) {
431 if (!this.autoCloseDisabled && (currentOverlay() == this)) {
432 this.autoCloseJob = this.job(this.autoCloseJob, function() {
433 this.close();
438 keydownHandler: function(e) {
439 if (!this.autoCloseDisabled && (e.keyCode == this.keyHelper.ESCAPE_KEY)) {
440 this.close();
441 e.stopPropagation();
446 * Extensions of core-overlay should implement the `resizeHandler`
447 * method to adjust the size and position of the overlay when the
448 * browser window resizes.
449 * @method resizeHandler
451 resizeHandler: function() {
452 this.updateTargetDimensions();
455 // TODO(sorvell): these utility methods should not be here.
456 addElementListenerList: function(node, events) {
457 for (var i in events) {
458 this.addElementListener(node, i, events[i]);
462 removeElementListenerList: function(node, events) {
463 for (var i in events) {
464 this.removeElementListener(node, i, events[i]);
468 enableElementListener: function(enable, node, event, methodName, capture) {
469 if (enable) {
470 this.addElementListener(node, event, methodName, capture);
471 } else {
472 this.removeElementListener(node, event, methodName, capture);
476 addElementListener: function(node, event, methodName, capture) {
477 var fn = this._makeBoundListener(methodName);
478 if (node && fn) {
479 Polymer.addEventListener(node, event, fn, capture);
483 removeElementListener: function(node, event, methodName, capture) {
484 var fn = this._makeBoundListener(methodName);
485 if (node && fn) {
486 Polymer.removeEventListener(node, event, fn, capture);
490 _makeBoundListener: function(methodName) {
491 var self = this, method = this[methodName];
492 if (!method) {
493 return;
495 var bound = '_bound' + methodName;
496 if (!this[bound]) {
497 this[bound] = function(e) {
498 method.call(self, e);
501 return this[bound];
505 function forcePolyfillRender(target) {
506 if (window.ShadowDOMPolyfill) {
507 target.offsetHeight;
511 // TODO(sorvell): This should be an element with private state so it can
512 // be independent of overlay.
513 // track overlays for z-index and focus managemant
514 var overlays = [];
515 function addOverlay(overlay) {
516 var z0 = currentOverlayZ();
517 overlays.push(overlay);
518 var z1 = currentOverlayZ();
519 if (z1 <= z0) {
520 applyOverlayZ(overlay, z0);
524 function removeOverlay(overlay) {
525 var i = overlays.indexOf(overlay);
526 if (i >= 0) {
527 overlays.splice(i, 1);
528 setZ(overlay, '');
532 function applyOverlayZ(overlay, aboveZ) {
533 setZ(overlay.target, aboveZ + 2);
536 function setZ(element, z) {
537 element.style.zIndex = z;
540 function currentOverlay() {
541 return overlays[overlays.length-1];
544 var DEFAULT_Z = 10;
546 function currentOverlayZ() {
547 var z;
548 var current = currentOverlay();
549 if (current) {
550 var z1 = window.getComputedStyle(current.target).zIndex;
551 if (!isNaN(z1)) {
552 z = Number(z1);
555 return z || DEFAULT_Z;
558 function focusOverlay() {
559 var current = currentOverlay();
560 // We have to be careful to focus the next overlay _after_ any current
561 // transitions are complete (due to the state being toggled prior to the
562 // transition). Otherwise, we risk infinite recursion when a transitioning
563 // (closed) overlay becomes the current overlay.
565 // NOTE: We make the assumption that any overlay that completes a transition
566 // will call into focusOverlay to kick the process back off. Currently:
567 // transitionend -> applyFocus -> focusOverlay.
568 if (current && !current.transitioning) {
569 current.applyFocus();
573 var backdrops = [];
574 function trackBackdrop(element) {
575 if (element.opened) {
576 backdrops.push(element);
577 } else {
578 var i = backdrops.indexOf(element);
579 if (i >= 0) {
580 backdrops.splice(i, 1);
585 function getBackdrops() {
586 return backdrops;
588 })();