1;( function ( $ ) { 2 "use strict"; 3 4 // IIFE generating the functions $.documentWidth, $.documentHeight, $.windowWidth, $.windowHeight, and 5 // $.scrollbarWidth. 6 // 7 // These functions need to run feature detections which requires insertion of an iframe ($.documentWidth/Height) and 8 // a div ($.scrollbarWidth). The body element in the main document must be available when that happens (ie, the 9 // opening body tag must have been parsed). 10 // 11 // For that reason, the detection does not run instantly - after all, the code might be loaded and run while parsing 12 // the head. Instead, detection happens on DOM-ready, or when any of the functions is invoked for the first time. 13 // Given the purpose of the functions, they won't be called until after the opening body tag has been parsed. 14 15 var _scrollbarWidth, 16 _supportsWindowInnerWidth, 17 _supportsSubpixelAccuracy, 18 elementNameForDocSizeQuery, 19 ieVersion, 20 useGetComputedStyle = !! window.getComputedStyle; 21 22 /** 23 * @param {Document} [_document=document] 24 * @returns {number} 25 */ 26 $.documentWidth = function ( _document ) { 27 var width; 28 29 _document || ( _document = document ); 30 31 try { 32 33 if ( elementNameForDocSizeQuery === undefined ) testDocumentScroll(); 34 width = _document[elementNameForDocSizeQuery].scrollWidth; 35 36 } catch ( e ) { 37 38 // Fallback for unsupported, broken browsers which can't run the behaviour test successfully 39 width = guessDocumentSize( "Width", _document ); 40 41 } 42 43 return width; 44 }; 45 46 /** 47 * @param {Document} [_document=document] 48 * @returns {number} 49 */ 50 $.documentHeight = function ( _document ) { 51 var height; 52 53 _document || ( _document = document ); 54 55 try { 56 57 if ( elementNameForDocSizeQuery === undefined ) testDocumentScroll(); 58 height = _document[elementNameForDocSizeQuery].scrollHeight; 59 60 } catch ( e ) { 61 62 // Fallback for unsupported, broken browsers which can't run the behaviour test successfully 63 height = guessDocumentSize( "Height", _document ); 64 65 } 66 67 return height; 68 }; 69 70 /** 71 * @param {string|Object|Window} [options="visual"] (a) viewport option: strings "visual", "layout" (case- 72 * insensitive), or a hash with a `viewport` property 73 * (b) the window argument 74 * @param {string|Object|Window} [_window=window] same as first argument; positions for window and viewport 75 * argument are interchangeable 76 * @returns {number} 77 */ 78 $.windowWidth = function ( options, _window ) { 79 var config = getWindowQueryConfig( arguments ); 80 return getWindowSize( "Width", config ); 81 }; 82 83 /** 84 * @param {string|Object|Window} [options="visual"] (a) viewport option: strings "visual", "layout" (case- 85 * insensitive), or a hash with a `viewport` property 86 * (b) the window argument 87 * @param {string|Object|Window} [_window=window] same as first argument; positions for window and viewport 88 * argument are interchangeable 89 * @returns {number} 90 */ 91 $.windowHeight = function ( options, _window ) { 92 var config = getWindowQueryConfig( arguments ); 93 return getWindowSize( "Height", config ); 94 }; 95 96 /** 97 * @param {Window} [_window=window] 98 * @returns {number} 99 */ 100 $.pinchZoomFactor = function ( _window ) { 101 102 // Calculate the zoom factor based on the width, not the height. getPinchZoomFactor() does just that. 103 // 104 // It would be more accurate to use the longest side for the calculation, keeping the effect of rounding errors 105 // low (unless the browser supports sub-pixel accuracy anyway). 106 // 107 // Unfortunately, iOS does not allow that approach. Switching from normal to minimal UI is not reflected in the 108 // clientHeight, so the zoom factor would seem to change when the UI disappears (even though in reality, it 109 // doesn't). We have to use the width, irrespective of orientation. 110 111 return getPinchZoomFactor( _window ); 112 }; 113 114 /** 115 * @returns {number} 116 */ 117 $.scrollbarWidth = browserScrollbarWidth; 118 119 120 /** 121 * Returns the window width or height. Does the actual work of $.windowWidth() and $.windowHeight(). 122 * 123 * Handles minimal UI in iOS properly. Returns the size of either the visual viewport (default) or the layout 124 * viewport. 125 * 126 * Precision: 127 * 128 * - The maximum rounding error for the visual viewport is +/- 1px. 129 * - Layout viewport width is precise. 130 * - Layout viewport height would potentially be prone to larger rounding errors (though in practice they rarely 131 * seem to exceed +/- 2px). Additional logic is employed to detect and correct these errors. As a result, the 132 * layout viewport height is precise as well. 133 * 134 * @param {string} dimension must be "Width" or "Height" (upper case!) 135 * @param {WindowQueryConfig} config 136 * @returns {number} 137 */ 138 function getWindowSize ( dimension, config ) { 139 var ddeClientHeight, visualSize, zoomFactor, snapToKnownHeight, lBound, uBound, 140 _window = config.window, 141 142 scrollbarsConsumeSpace = browserScrollbarWidth() !== 0, 143 getLayoutViewportWidth = config.useLayoutViewport && dimension === "Width", 144 145 useClientSize = scrollbarsConsumeSpace || !supportsWindowInnerWidth() || getLayoutViewportWidth, 146 size = useClientSize ? _window.document.documentElement[ "client" + dimension ] : getWindowInnerSize( dimension, _window ); 147 148 if ( config.useLayoutViewport && ! useClientSize ) { 149 150 // Looking for the layout viewport height on mobile. Calculate it from window.innerHeight and the zoom 151 // factor, so as to capture the real height when the browser is in minimal UI on iOS. 152 // 153 // NB The layout viewport has a size in full CSS pixels (unaffected by zooming, hence no sub pixels). 154 155 visualSize = size; 156 zoomFactor = getPinchZoomFactor( _window, { asRange: true } ); 157 158 size = Math.round( visualSize * zoomFactor.calculated ); 159 160 // Fix rounding errors, caused by the visual height ignoring sub-pixel fractions. If we know
160we are near a 161 // known layout viewport height, use that. 162 if ( !supportsSubpixelAccuracy() ) { 163 164 ddeClientHeight = _window.document.documentElement.clientHeight; 165 166 // In Android, the height we get from ddE.clientHeight and the one calculated from window.innerHeight 167 // should be the same, except for rounding errors in the calculation. So basically, we could just set 168 // any calculated value to the clientHeight, no matter how large the difference is. (That is, if we even 169 // end up here - in some versions of Chrome on Android, for instance, we have sub-pixel accuracy.) 170 // 171 // Not so in iOS. In minimal UI, the real layout viewport may be larger than the one reported by 172 // clientHeight, by about 60px. So we need to impose reasonable limits on what is considered to be near 173 // the clientHeight. 174 // 175 // - If the calculated value is within 3px of the clientHeight, we consider it to be a clear case of a 176 // rounding error. 177 // - Alternatively, the clientHeight must be between the theoretical maximum and minimum values of the 178 // computation, assuming maximum rounding errors at every stage. 179 // - If we use that range, its upper bound is capped at 30px above the clientHeight - keeping a safe 180 // distance from current minimal UI heights, or possible future ones. 181 182 lBound = ( visualSize - 1 ) * zoomFactor.min; 183 uBound = ( visualSize + 1 ) * zoomFactor.max; 184 185 snapToKnownHeight = ( size <= ddeClientHeight + 3 && size >= ddeClientHeight - 3 ) || 186 ( lBound <= ddeClientHeight && uBound >= ddeClientHeight && uBound < ddeClientHeight + 30 ); 187 188 if ( snapToKnownHeight ) size = ddeClientHeight; 189 190 } 191 } 192 193 return size; 194 } 195 196 /** 197 * Calculates the zoom factor for pinch zooming on mobile. A factor > 1 means that the page is zoomed in (content 198 * enlarged). 199 * 200 * The zoom factor returned here measures the size of the visual viewport with respect to the size of the layout 201 * viewport. 202 * 203 * Ignores page zoom on the desktop (returning a zoom factor of 1). For the distinction between pinch and page zoom, 204 * again see Chapter 3 in PPK's book. 205 * 206 * NB visualViewport.scale, part of the new visualViewport API, is not used here even if available. Browsers usually 207 * calculate their zoom level with respect to the ideal layout viewport, not the layout viewport actually in use 208 * (see Peter-Paul Koch, The Mobile Web Handbook, Chapter 3: Viewports, Section "Minimum and Maximum Zoom"). So, in 209 * Chrome for Android, visualViewport.scale returns that ratio, rather than the isolated effect of zooming into a 210 * part of the page. 211 * 212 * The distinction doesn't matter if the meta viewport tag makes the browser use its ideal viewport anyway 213 * (width=device-width, initial-scale=1.0). But it leads to confusing results if, for example, initial-scale is set 214 * to values < 1, or if the meta viewport tag is left out altogether. Then, the page shrinks visually _by default_, 215 * without the user zooming. The scale reported by the visualViewport API captures the effect and returns a value 216 * < 1. However, the pinch zoom factor should remain at 1 because the visual viewport is still identical to the 217 * layout viewport - never mind the diminutive size of its content. 218 * 219 * @param {Window} [_window=window] 220 * @param {Object} [options] 221 * @param {boolean} [options.asRange=false] 222 * @returns {number|ZoomAccuracyRange} 223 */ 224 function getPinchZoomFactor ( _window, options ) { 225 var ddeClientWidth, windowInnerWidth, 226 asRange = options && options.asRange, 227 factors = { 228 calculated: 1, 229 min: 1, 230 max: 1 231 }, 232 skip = browserScrollbarWidth() !== 0 || !supportsWindowInnerWidth(); 233 234 if ( !skip ) { 235 236 _window || ( _window = window ); 237 ddeClientWidth = _window.document.documentElement.clientWidth; 238 windowInnerWidth = getWindowInnerWidth( _window ); 239 240 // Calculate the zoom factor, assuming window.innerWidth is precise (no rounding errors). 241 factors.calculated = ddeClientWidth / windowInnerWidth; 242 243 // If requested, determine the minimum and maximum value of the zoom factor in the presence of rounding errors. 244 if ( asRange ) { 245 if ( supportsSubpixelAccuracy() ) { 246 // No need to take rounding errors into account 247 factors.min = factors.max = factors.calculated; 248 } else { 249 factors.min = ddeClientWidth / ( windowInnerWidth + 1 ); 250 factors.max = ddeClientWidth / ( windowInnerWidth - 1 ); 251 } 252 } 253 254 } 255 256 return asRange ? factors : factors.calculated; 257 } 258 259 /** 260 * Handles the argument juggling for $.windowWidth() and $.windowHeight(). Extracts the window and viewport settings 261 * from the arguments, applying the defaults (use global window, use visual viewport) where necessary. 262 * 263 * Examines the first two arguments. The window and the viewport setting can appear in either position, 264 * interchangeably. The viewport setting can be passed in as a string, or as part of an options hash, 265 * `{ viewport: ... }`. 266 * 267 * Recognized viewport names are "visual" and "layout" (case-insensitive). 268 * 269 * @param {Array|Arguments} args 270 * @returns {WindowQueryConfig} 271 */ 272 function getWindowQueryConfig( args ) { 273 var isWindowArg0, isOptionArg0, isOptionArg1, 274 275 // Defaults 276 _window = window, 277 _useVisualViewport = true; 278 279 if ( args && args.length ) { 280 281 // Coerce to a real array 282 args = Array.prototype.slice.call( args ); 283 284 // Examine the first argument. Cast strings to an options hash with a `viewport` property. 285 isWindowArg0 = isWindow( args[0] ); 286 if( !isWindowArg0 ) args[0] = castStringToViewportOption( args[0] ); 287 isOptionArg0 = !isWindowArg0 && args[0]; 288 289 // Examine the second argument. Again, cast strings to an options hash with a `viewport` property. 290 if ( !isOptionArg0 ) args[1] = castStringToViewportOption( args[1] ); 291 isOptionArg1 = !isOptionArg0 && args[1]; 292 293 // Extract window and viewport option, if available. 294 if ( isWindowArg0 ) { 295 296 _window = args[0]; 297 if( isOptionArg1 && args[1].viewport ) _useVisualViewport = isVisualViewport( args[1].viewport ); 298 299 } else if ( isOptionArg0 ) { 300 301 if( args[0].viewport ) _useVisualViewport = isVisualViewport( args[0].viewport ); 302 if ( isWindow( args[1] ) ) _window = args[1]; 303 304 } else if ( !args[0] && args[1] ) { 305 306 // First argument was falsy, e.g. undefined, null. Ignore it. But process the second, non-falsy argument. 307 if( isOptionArg1 && args[1].viewport ) { 308 _useVisualViewport = isVisualViewport( args[1].viewport ); 309 } else if ( isWindow( args[1] ) ) { 310 _window = args[1]; 311 } 312 313 } 314 } 315 316 return { 317 window: _window, 318 useVisualViewport: _useVisualViewport, 319 useLayoutViewport: !_useVisualViewport 320 }; 321 } 322 323 /** 324 * Checks if the argument is the name of the visual viewport. The check is case-insensitive. 325 * 326 * Expects a string. Tolerates falsy values, returning false then (argument is not naming the visual viewport). 327 * Throws an error for everything else. Also throws an error if the viewport name is a string but not recognized 328 * (typo alert). 329 * 330 * Helper for getWindowQueryConfig(). 331 * 332 * @param {string} [name] strings "visual", "layout" (case-insensitive) 333 * @returns {boolean} 334 */ 335 function isVisualViewport ( name ) { 336 var viewport = isString( name ) && name.toLowerCase(); 337
338 if ( name && !viewport ) throw new Error( "Invalid viewport option: " + name ); 339 if ( viewport && viewport !== "visual" && viewport !== "layout" ) throw new Error( "Invalid viewport name: " + name ); 340 341 return viewport === "visual"; 342 } 343 344 /** 345 * Examines a value and casts a string to a hash with a `viewport` property (the string being its value). If the 346 * value is not a string, or if the string is empty, it is returned as-is. Helper for getWindowQueryConfig(). 347 * 348 * @param {*} arg 349 * @returns {*} 350 */ 351 function castStringToViewportOption ( arg ) { 352 return ( isString( arg ) && arg !== "" ) ? { viewport: arg } : arg; 353 } 354 355 /** 356 * Checks if the browser supports window.visualViewport.width and window.visualViewport.height, or window.innerWidth 357 * and window.innerHeight otherwise. 358 * 359 * See getWindowInnerSize() for the distinction between the visualViewport API and window.innerWidth/Height. 360 * 361 * The check is run on demand, rather than up front while loading the component, because the window properties can 362 * behave strangely in the early stages of opening a window. The component might be loaded in the document head, 363 * which could potentially be early enough to run into these oddities. So we wait until the first call. 364 * 365 * IE 8 does not support window.innerWidth and .innerHeight. FF has a bug which as only been fixed in FF25 (released 366 * 29 Oct 2013). It manifests itself in a fictional return value of 10 for these properties. See 367 * 368 * - https://developer.mozilla.org/en-US/docs/Web/API/Window.innerWidth 369 * - https://developer.mozilla.org/en-US/docs/Web/API/Window.innerHeight 370 * - https://bugzilla.mozilla.org/show_bug.cgi?id=641188 371 * 372 * @returns {boolean} 373 */ 374 function supportsWindowInnerWidth () { 375 if ( _supportsWindowInnerWidth === undefined ) _supportsWindowInnerWidth = getWindowInnerWidth() > 10; 376 return _supportsWindowInnerWidth; 377 } 378 379 /** 380 * Does the actual work of $.scrollbarWidth. Protected from external modification. See $.scrollbarWidth for details. 381 * 382 * Adapted from Ben Alman's scrollbarWidth plugin. See 383 * - http://benalman.com/projects/jquery-misc-plugins/#scrollbarwidth 384 * - http://jsbin.com/zeliy/1 385 * 386 * @returns {number} 387 */ 388 function browserScrollbarWidth () { 389 var testEl; 390 391 if ( _scrollbarWidth === undefined ) { 392 393 testEl = document.createElement( "div" ); 394 testEl.style.cssText = "width: 100px; height: 100px; overflow: scroll; position: absolute; top: -500px; left: -500px; margin: 0px; padding: 0px; border: none;"; 395 396 document.body.appendChild( testEl ); 397 _scrollbarWidth = testEl.offsetWidth - testEl.clientWidth; 398 document.body.removeChild( testEl ); 399 400 } 401 402 return _scrollbarWidth; 403 } 404 405 /** 406 * Detects which element to use for a document size query (body or documentElement). 407 * 408 * Sandbox 409 * ------- 410 * 411 * The detection is sandboxed in an iframe element created for the purpose. If the iframe window can't be 412 * accessed because of some obscure policy restriction or browser bug, the main window and document is used 413 * as a fallback. 414 * 415 * The test is designed to minimize the visual and rendering impact in the test window, in case the fallback 416 * should ever be used. 417 * 418 * Test method 419 * ----------- 420 * 421 * We can't test directly which call to use (at least not with an even worse amount of intervention than is 422 * already the case, which matters if the iframe is not accessible). But we can work by exclusion. 423 * 424 * In Chrome (desktop and mobile), Safari (also iOS), and Opera, body.scrollWidth returns the true document 425 * width. In Firefox and IE, body.scrollWidth responds to the body content size instead. In those browsers, 426 * true document width is returned by document.documentElement.scrollWidth. 427 * 428 * So we test the behaviour of body.scrollWidth by manipulating the body size, while keeping the document size 429 * constant. 430 * 431 * - We prepare for the test by making sure the body does not display its overflow. 432 * - Then we inject a small test element into the body and give it a relative position far outside the viewport. 433 * 434 * The body size is expanded, but the document size remains unaffected because the body hides the overflowing 435 * test element (either outright, or by putting it in a hidden part of the scroll pane). Then we check if 436 * body.scrollWidth has responded to the change. From that, we infer the right element to use for a document 437 * width query. 438 * 439 * The function does not return anything. It sets the elementNameForDocSizeQuery in the closure instead. 440 */ 441 function testDocumentScroll () { 442 443 var initialDocumentState, _testEl, initialScrollWidth, responds, 444 445 iframe = createTestIframe(), 446 _document = iframe && iframe.contentDocument || document, 447 _body = _document.body, 448 inIframe = _document !== document; 449 450 // Create a test element which will be used to to expand the body content way to the right. 451 _testEl = _document.createElement( "div" ); 452 _testEl.style.cssText = "width: 1px; height: 1px; position: relative; top: 0px; left: 32000px;"; 453 454 // Make sure that the body (but not the window) hides its overflow. Only applies if the iframe is not 455 // accessible. The iframe document already contains the required styles. 456 if ( ! inIframe ) initialDocumentState = prepareGlobalDocument(); 457 458 // Inject the test element, then test if the body.scrollWidth property responds 459 initialScrollWidth = _body.scrollWidth; 460 _body.appendChild( _testEl ); 461 responds = initialScrollWidth !== _body.scrollWidth; 462 _body.removeChild( _testEl ); 463 464 // Restore the overflow settings for window and body 465 if ( ! inIframe ) restoreGlobalDocument( initialDocumentState ); 466 467 // If body.scrollWidth responded, it reacts to body content size, not document size. Default to 468 // ddE.scrollWidth. If it did not react, however, it is linked to the (unchanged) document size. 469 elementNameForDocSizeQuery = responds ? "documentElement" : "body"; 470 471 if ( iframe ) document.body.removeChild( iframe ); 472 473 } 474 475 /** 476 * Creates an iframe document with an HTML5 doctype and UTF-8 encoding and positions it off screen. Window size 477 * is 500px x 500px. Body and window (document element) are set to overflow: hidden. 478 * 479 * In case the content document of the iframe can't be accessed for some reason, the function returns undefined. 480 * This is unlikely to ever happen, though. 481 * 482 * @returns {HTMLIFrameElement|undefined} 483 */ 484 function createTestIframe () { 485 var iframe = document.createElement( "iframe" ), 486 body = document.body; 487 488 iframe.style.cssText = "position: absolute; top: -600px; left: -600px; width: 500px; height: 500px; margin: 0px; padding: 0px; border: none; display: block;"; 489 iframe.frameborder = "0"; 490 491 body.appendChild( iframe ); 492 iframe.src = 'about:blank'; 493 494 if ( !iframe.contentDocument ) return; 495 496 iframe.contentDocument.write( '<!DOCTYPE html><html><head><meta charset="UTF-8"><title></title><style type="text/css">html, body { overflow: hidden; }</style></head><body></body></html>' ); 497 498 return iframe; 499 } 500 501 /** 502 * Makes sure the body (but not the window) hides its overflow. Works with the global document, returns the initial 503 * state before manipulation (including properties indicating what has been modified). 504 * 505 * Used only if iframe creation or access has failed for some reason. 506 */ 507 function prepareGlobalDocument () { 508 var ddEStyle, bodyStyle, 509 510 ddE = document.documentElement, 511 body = document.body, 512 513 ddEComputedStyles = useGetComputedStyle ? window.getComputedStyle( ddE, null ) : ddE.currentStyle, 514 bodyComputedStyles = useGetComputedStyle ? window.getComputedStyle( body, null ) : body.currentStyle, 515 516 ddEOverflowX = ( ddEComputedStyles.overflowX || ddEComputedStyles.overflow || "visible" ).toLowerCase(), 517 bodyOverflowX = ( bodyComputedStyles.overflowX || bodyComputedStyles.overflow || "visible" ).toLowerCase(), 518 519 modifyBody = bodyOverflowX !== "hidden", 520 modifyDocumentElement = ddEOverflowX === "visible", 521 522 initialState = { 523 documentElement: { 524 modified: modifyDocumentElement 525 }, 526 body: { 527 modified: modifyBody 528 } 529 }; 530 531 if ( modifyDocumentElement ) { 532 ddEStyle = ddE.style; 533 initialState.documentElement.styleOverflowX = ddEStyle.overflowX; 534 ddEStyle.overflowX = "auto"; 535 } 536 537 if ( modifyBody ) { 538 bodyStyle = body.style; 539 initialState.body.styleOverflowX = bodyStyle.overflowX; 540 bodyStyle.overflowX = "hidden"; 541 } 542 543 return initialState; 544 545 } 546 547 /** 548 * Restores the body and documentElement styles to their initial state, which is passed in as an argument. Works 549 * with the global document. 550 * 551 * Used only if iframe creation or access has failed for some reason. 552 * 553 * @param {Object} previousState the initial state, as returned by prepareGlobalDocument() 554 */ 555 function restoreGlobalDocument ( previousState ) { 556 557 if ( previousState.documentElement.modified ) document.documentElement.style.overflowX = previousState.documentElement.styleOverflowX; 558 if ( previousState.body.modified ) document.body.style.overflowX = previousState.body.styleOverflowX; 559 560 } 561 562 /** 563 * Returns a best guess for the window width or height. Used as a fallback for unsupported browsers which are too 564 * broken to even run the feature test. 565 * 566 * The conventional jQuery method of guessing the document size is used here: every conceivable value is queried and 567 * the largest one is picked. 568 * 569 * @param {string} dimension accepted values are "Width" or "Height" (capitalized first letter!) 570 * @param {Document} [_document] 571 */ 572 function guessDocumentSize( dimension, _document ) { 573 var ddE = _document.documentElement; 574 575 return Math.max( 576 ddE.body[ "scroll" + dimension ], _document[ "scroll" + dimension ], 577 ddE.body[ "offset" + dimension ], _document[ "offset" + dimension ], 578 _document[ "client" + dimension ] 579 ); 580 } 581 582 /**
583 * Returns window.visualViewport.width if available, or window.innerWidth otherwise. 584 * 585 * Along the way, the return value is examined to see if the browser supports sub-pixel accuracy (floating-point 586 * values). 587 * 588 * @param {Window} [_window=window] 589 * @returns {number} 590 */ 591 function getWindowInnerWidth ( _window ) { 592 return getWindowInnerSize( "Width", _window ); 593 } 594 595 /** 596 * Returns window.visualViewport.height if available, or window.innerHeight otherwise. 597 * 598 * Along the way, the return value is examined to see if the browser supports sub-pixel accuracy (floating-point 599 * values). 600 * 601 * @param {Window} [_window=window] 602 * @returns {number} 603 */ 604 function getWindowInnerHeight ( _window ) { 605 return getWindowInnerSize( "Height", _window ); 606 } 607 608 /** 609 * Returns window.visualViewport.width if available, or window.innerWidth otherwise, for width. Likewise, it 610 * returns window.visualViewport.height or window.innerHeight for height. The dimension argument determines whether 611 * width or height is returned. 612 * 613 * Along the way, the return value is examined to see if the browser supports sub-pixel accuracy (floating-point 614 * values). 615 * 616 * If the visualViewport API is available, it is preferred over window.innerWidth/Height. That's because Chrome, 617 * from version 62, has changed the behaviour of window.innerWidth/Height, which used to return the size of the 618 * visual viewport. In Chrome, it now returns the size of the layout viewport, breaking compatibility with all other 619 * browsers and its own past behaviour. Other browsers may follow suit, though. See 620 * 621 * - https://www.quirksmode.org/blog/archives/2017/09/chrome_breaks_v.html 622 * - https://developers.google.com/web/updates/2017/09/visual-viewport-api 623 * - https://bugs.chromium.org/p/chromium/issues/detail?id=767388#c8 624 * 625 * @param {string} dimension must be "Width" or "Height" (upper case!) 626 * @param {Window} [_window=window] 627 * @returns {number} 628 */ 629 function getWindowInnerSize ( dimension, _window ) { 630 var size = ( _window || window ).visualViewport ? 631 ( _window || window ).visualViewport[ dimension.toLowerCase() ] : 632 ( _window || window )[ "inner" + dimension]; 633 634 // Check for fractions. Exclude undefined return values in browsers which don't support window.innerWidth/Height. 635 if ( size ) checkForFractions( size ); 636 return size; 637 } 638 639 /** 640 * Accepts an observed value of window.innerWidth or window.innerHeight and examines it for fractional values. 641 * Caches the finding if there is one. Helper for getWindowInnerWidth() and friends. 642 * 643 * See supportsSubpixelAccuracy() for more. 644 * 645 * @param {number} sizeMeasurement 646 */ 647 function checkForFractions ( sizeMeasurement ) { 648 649 // When checking the cached value, we can't accept false as a definitive answer. We have to continue checking 650 // unless the cached value is `true`. 651 // 652 // See supportsSubpixelAccuracy(). 653 if ( !_supportsSubpixelAccuracy && isFloat( sizeMeasurement ) ) _supportsSubpixelAccuracy = true; 654 655 } 656 657 /** 658 * Returns whether or not the browser supports sub-pixel accuracy (floating-point values) for window.innerWidth and 659 * window.innerHeight - as far as we know yet. 660 * 661 * The browser behaviour can't be tested directly, and must be figured out by continuous observation. We have to 662 * monitor the return values of window.innerWidth/Height for fractions. That is done with getWindowInnerWidth etc. 663 * As soon as we find a fraction, we know that the browser supports floating-point accuracy. 664 * 665 * However, we can never be sure of the opposite. If we haven't encountered a fraction yet, it could simply mean 666 * that we are at 100% zoom, and integers have indeed been the correct return value so far. 667 * 668 * So when the method here answers with true, it is definitive. When it returns false, that just means "maybe not, 669 * but we don't know". And the the return value might flip to true in a future query. 670 * 671 * @returns {boolean} 672 */ 673 function supportsSubpixelAccuracy () {
674 return !!_supportsSubpixelAccuracy; 675 } 676 677 function isWindow ( value ) { 678 // Identical to jQuery.isWindow() 679 return value != null && value.window == value; // jshint ignore:line 680 } 681 682 function isString ( value ) { 683 // Done as in the Lodash compatibility build 684 return typeof value === 'string' || value && typeof value === 'object' && Object.prototype.toString.call(value) === '[object String]' || false; 685 } 686 687 /** 688 * Returns whether or not a value is of type number. Also rejects NaN as a number. 689 * 690 * NB This is not the same as $.isNumeric because $.isNumeric( "3" ) is true while isNumber( "3" ) is false. 691 * 692 * @param {*} value 693 * @returns {boolean} 694 */ 695 function isNumber ( value ) { 696 // Done as in the Lodash compatibility build, but rejecting NaN as a number. 697 var isNumeric = typeof value === 'number' || value && typeof value === 'object' && Object.prototype.toString.call( value ) === '[object Number]' || false; 698 699 // Reject NaN before returning 700 return isNumeric && value === +value; 701 } 702 703 /** 704 * Returns whether or not a number is a float, ie has decimals. 705 * 706 * Requires the argument to be a number. If unsure, guard against false detections with 707 * `isNumber( value ) && isFloat ( value )`. 708 * 709 * For the technique, see http://stackoverflow.com/a/3885844/508355 710 * 711 * @param {number} num 712 * @returns {boolean} 713 */ 714 function isFloat ( num ) { 715 return num === +num && num !== ( num | 0 ); // jshint ignore:line 716 } 717 718 /** 719 * Returns the IE version, or false if the browser is not IE. 720 * 721 * The result is determined by browser sniffing, rather than a test tailored to the use case. The function must only 722 * be called as a last resort, for scenarios where there is no alternative to browser sniffing. 723 * 724 * These scenarios include: 725 * 726 * - Preventing IE6 and IE7 from crashing 727 * - Preventing IE9 from blocking or delaying the load event 728 * 729 * The test follows the MSDN recommendation at https://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx#parsingua 730 * The result is cached. 731 * 732 * @returns {number|boolean} 733 */ 734 function getIEVersion () { 735 var userAgent, userAgentTestRx; 736 737 if ( ieVersion === undefined ) { 738 739 ieVersion = false; 740 userAgent = navigator && navigator.userAgent; 741 742 if ( navigator && navigator.appName === "Microsoft Internet Explorer" && userAgent ) { 743 userAgentTestRx = new RegExp( "MSIE ([0-9]{1,}[\.0-9]{0,})" ); // jshint ignore:line 744 if ( userAgentTestRx.exec( userAgent ) != null ) ieVersion = parseFloat( RegExp.$1 ); 745 } 746 747 } 748 749 return ieVersion; 750 } 751 752 /** 753 * Checks if we are dealing with a truly ancient version of IE (< IE8). 754 * 755 * This is done by browser sniffing, rather than a test tailored to the use case. Use it only if there is no 756 * alternative. 757 * 758 * @returns {boolean} 759 */
760 function isAncientIE () { 761 var ieVersion = getIEVersion(); 762 return ieVersion && ieVersion < 8; 763 } 764 765 /** 766 * Checks if the browser is IE9. 767 * 768 * This is done by browser sniffing, rather than a test tailored to the use case. Use it only if there is no 769 * alternative. 770 * 771 * @returns {boolean} 772 */ 773 function isIE9 () { 774 return getIEVersion() === 9; 775 } 776 777 778 // Let's prime $.documentWidth(), $.documentHeight() and $.scrollbarWidth() immediately after the DOM is ready. It 779 // is best to do it up front because the test touches the DOM, so let's get it over with before people set up 780 // handlers for mutation events and such. 781 // 782 // This step has to be skipped for the following browsers: 783 // 784 // - ancient versions of IE (IE6, IE7). 785 // IE6 and IE7 can't handle the feature tests on DOM ready - they crash right away. Later on, the tests are ok. 786 // 787 // - IE9. 788 // If the feature tests were run on DOM ready, the window load event would become unreliable. The event might not 789 // fire until the user moves the mouse over the document. This bug is rare and not triggered by jQuery.documentSize 790 // alone; third-party code likely plays a role. The exact circumstances are not clear - see issue #3. 791 // 792 // For these browsers, we don't run the feature tests preemptively. Instead, we do it on demand, when the first 793 // document size query is made. 794 if ( typeof $ === "function" && !isAncientIE() && !isIE9() ) { 795 796 // Try-catch acts as a safety net for unsupported, broken browsers 797 try { 798 799 $( function () { 800 if ( elementNameForDocSizeQuery === undefined ) testDocumentScroll(); 801 browserScrollbarWidth(); 802 } ); 803 804 } catch ( e ) {} 805 806 } 807 808 809 /** 810 * Custom types. 811 * 812 * For easier documentation and type inference. 813 */ 814 815 /** 816 * @name ZoomAccuracyRange 817 * @type {Object} 818 * 819 * @property {number} calculated 820 * @property {number} min 821 * @property {number} max 822 */ 823 824 /** 825 * @name WindowQueryConfig 826 * @type {Object} 827 * 828 * @property {Window} window 829 * @property {boolean} useVisualViewport 830 * @property {boolean} useLayoutViewport 831 */ 832 833}( 834 typeof jQuery !== "undefined" ? jQuery : 835 typeof Zepto !== "undefined" ? Zepto : 836 $ 837));
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.