1const breakpoints = [{ 2 value: 'ui:rad:phone', 3 minWidth: 0, 4 maxWidth: 767 5}, { 6 value: 'ui:rad:tablet', 7 minWidth: 768, 8 maxWidth: 991 9}, { 10 value: 'ui:rad:desktop', 11 minWidth: 992, 12 maxWidth: 1000000 // Number.MAX_SAFE_INTEGER is not working on FF30 thus we are using a big number here 13}]; 14 15fordAnalytics = window.fordAnalytics || {}; 16 17for (let i = 0; i < breakpoints.length; i++) { 18 if (window.innerWidth >= breakpoints[i].minWidth 19 && window.innerWidth <= breakpoints[i].maxWidth) { 20 fordAnalytics['page.radUIVersion'] = breakpoints[i].value; 21 break; 22 } 23} 24 25// TODO: Review this: 26// These variables even though bellow they are being set on the window context these are being read in other places in the window context and in the global context 27var digitaldata = {}; 28var digitalDataUtils = {}; 29 30digitalDataUtils.satelliteMock = { 31 track: function() { 32 }, 33 getVar: function() { 34 } 35}; 36 37if (fordAnalytics) { 38 Object.keys(fordAnalytics).forEach(function(key) { 39 const keyPath = key.split('.'); 40 41 assign(digitaldata, keyPath, fordAnalytics[key]); 42 }); 43} 44 45function assign(obj, keyPath, value) { 46 const lastKeyIndex = keyPath.length - 1; 47 48 for (let i = 0; i < lastKeyIndex; ++i) { 49 const key = keyPath[i]; 50 51 if (!(key in obj)) { 52 obj[key] = {}; 53 } 54 55 obj = obj[key]; 56 } 57 58 obj[keyPath[lastKeyIndex]] = value; 59} 60 61window.digitaldata = digitaldata; 62window.digitalDataUtils = digitalDataUtils; 63 64/*! 65 * modernizr v3.0.0-alpha.3 66 * Build http://v3.modernizr.com/download/#-applicationcache-audio-backgroundsize-borderimage-borderradius-boxshadow-canvas-canvastext-cssanimations-csscolumns-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-draganddrop-flexbox-flexboxlegacy-fontface-generatedcontent-geolocation-hashchange-history-hsla-indexeddb-inlinesvg-localstorage-multiplebgs-opacity-picture-postmessage-rgba-sessionstorage-smil-svg-svgclippaths-textshadow-video-webgl-websockets-websqldatabase-webworkers-dontmin 67 * 68 * Copyright (c) 69 * Faruk Ates 70 * Paul Irish 71 * Alex Sexton 72 * Ryan Seddon 73 * Alexander Farkas 74 * Patrick Kettner 75 * Stu Cox 76 * Richard Herrera 77 78 * MIT License 79 */ 80 81/* 82 * Modernizr tests which native CSS3 and HTML5 features are available in the 83 * current UA and makes the results available to you in two ways: as properties on 84 * a global `Modernizr` object, and as classes on the `<html>` element. This 85 * information allows you to progressively enhance your pages with a granular level 86 * of control over the experience. 87 */ 88 89(function(window, document, undefined) { 90 const classes = []; 91 92 const tests = []; 93 94 const ModernizrProto = { 95 // The current version, dummy 96 _version: '3.0.0-alpha.3', 97 98 // Any settings that don't work as separate modules 99 // can go in here as configuration. 100 _config: { 101 classPrefix: '', 102 enableClasses: true, 103 enableJSClass: true, 104 usePrefixes: true 105 }, 106 107 // Queue of tests 108 _q: [], 109 110 // Stub these for people who are listening 111 on: function(test, cb) { 112 // I don't really think people should do this, but we can 113 // safe guard it a bit. 114 // -- NOTE:: this gets WAY overridden in src/addTest for 115 // actual async tests. This is in case people listen to 116 // synchronous tests. I would leave it out, but the code 117 // to *disallow* sync tests in the real version of this 118 // function is actually larger than this. 119 const self = this; 120 121 setTimeout(function() { 122 cb(self[test]); 123 }, 0); 124 }, 125 126 addTest: function(name, fn, options) { 127 tests.push({ name: name, fn: fn, options: options }); 128 }, 129 130 addAsyncTest: function(fn) { 131 tests.push({ name: null, fn: fn }); 132 } 133 }; 134 135 // Fake some of Object.create 136 // so we can force non test results 137 // to be non "own" properties. 138 let Modernizr = function() {}; 139 140 Modernizr.prototype = ModernizrProto; 141 142 // Leak modernizr globally when you `require` it 143 // rather than force it here. 144 // Overwrite name so constructor name is nicer :D 145 Modernizr = new Modernizr(); 146 147 /*! 148 { 149 "name": "Application Cache", 150 "property": "applicationcache", 151 "caniuse": "offline-apps", 152 "tags": ["storage", "offline"],
153 "notes": [{ 154 "name": "MDN documentation", 155 "href": "https://developer.mozilla.org/en/docs/HTML/Using_the_application_cache" 156 }], 157 "polyfills": ["html5gears"] 158 } 159 ! */ 160 /* DOC 161 Detects support for the Application Cache, for storing data to enable web-based applications run offline. 162 163 The API has been [heavily criticized](http://alistapart.com/article/application-cache-is-a-douchebag) and discussions are underway to address this. 164 */ 165 166 Modernizr.addTest('applicationcache', 'applicationCache' in window); 167 168 /*! 169 { 170 "name": "Geolocation API", 171 "property": "geolocation", 172 "caniuse": "geolocation", 173 "tags": ["media"], 174 "notes": [{ 175 "name": "MDN documentation", 176 "href": "https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation" 177 }], 178 "polyfills": [ 179 "joshuabell-polyfill", 180 "webshims", 181 "geo-location-javascript", 182 "geolocation-api-polyfill" 183 ] 184 } 185 ! */ 186 /* DOC 187 Detects support for the Geolocation API for users to provide their location to web applications. 188 */ 189 190 // geolocation is often considered a trivial feature detect... 191 // Turns out, it's quite tricky to get right: 192 // 193 // Using !!navigator.geolocation does two things we don't want. It: 194 // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513 195 // 2. Disables page caching in WebKit: webk.it/43956 196 // 197 // Meanwhile, in Firefox < 8, an about:config setting could expose 198 // a false positive that would throw an exception: bugzil.la/688158 199 200 Modernizr.addTest('geolocation', 'geolocation' in navigator); 201 202 /*! 203 { 204 "name": "History API", 205 "property": "history", 206 "caniuse": "history", 207 "tags": ["history"], 208 "authors": ["Hay Kranen", "Alexander Farkas"], 209 "notes": [{ 210 "name": "W3C Spec", 211 "href": "http://www.w3.org/TR/html51/browsers.html#the-history-interface" 212 }, { 213 "name": "MDN documentation", 214 "href": "https://developer.mozilla.org/en-US/docs/Web/API/window.history" 215 }], 216 "polyfills": ["historyjs", "html5historyapi"] 217 } 218 ! */ 219 /* DOC 220 Detects support for the History API for manipulating the browser session history. 221 */ 222 223 Modernizr.addTest('history', function() { 224 // Issue #733 225 // The stock browser on Android 2.2 & 2.3, and 4.0.x returns positive on history support 226 // Unfortunately support is really buggy and there is no clean way to detect 227 // these bugs, so we fall back to a user agent sniff :( 228 const ua = navigator.userAgent; 229 230 // We only want Android 2 and 4.0, stock browser, and not Chrome which identifies 231 // itself as 'Mobile Safari' as well, nor Windows Phone (issue #1471). 232 if ((ua.indexOf('Android 2.') !== -1 233 || (ua.indexOf('Android 4.0') !== -1)) 234 && ua.indexOf('Mobile Safari') !== -1 235 && ua.indexOf('Chrome') === -1 236 && ua.indexOf('Windows Phone') === -1) { 237 return false; 238 } 239 240 // Return the regular check 241 return (window.history && 'pushState' in window.history); 242 }); 243 244 /*! 245 { 246 "name": "Local Storage", 247 "property": "localstorage", 248 "caniuse": "namevalue-storage", 249 "tags": ["storage"], 250 "knownBugs": [], 251 "notes": [], 252 "warnings": [], 253 "polyfills": [ 254 "joshuabell-polyfill", 255 "cupcake", 256 "storagepolyfill", 257 "amplifyjs", 258 "yui-cacheoffline" 259 ] 260 } 261 ! */ 262 263 // In FF4, if disabled, window.localStorage should === null. 264 265 // Normally, we could not test that directly and need to do a 266 // `('localStorage' in window) && ` test first because otherwise Firefox will 267 // throw bugzil.la/365772 if cookies are disabled 268 269 // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem 270 // will throw the exception: 271 // QUOTA_EXCEEDED_ERRROR DOM Exception 22. 272 // Peculiarly, getItem and removeItem calls do not throw. 273 274 // Because we are forced to try/catch this, we'll go aggressive. 275 276 // Just FWIW: IE8 Compat mode supports these features completely: 277 // www.quirksmode.org/dom/html5.html 278 // But IE8 doesn't support either with local files 279 280 Modernizr.addTest('localstorage', function() { 281 const mod = 'modernizr'; 282 283 try { 284 localStorage.setItem(mod, mod); 285 localStorage.removeItem(mod); 286 287 return true; 288 } catch (e) { 289 return false;
290 } 291 }); 292 293 /*! 294 { 295 "name": "postMessage", 296 "property": "postmessage", 297 "caniuse": "x-doc-messaging", 298 "notes": [{ 299 "name": "W3C Spec", 300 "href": "http://www.w3.org/TR/html5/comms.html#posting-messages" 301 }], 302 "polyfills": ["easyxdm", "postmessage-jquery"] 303 } 304 ! */ 305 /* DOC 306 Detects support for the `window.postMessage` protocol for cross-document messaging. 307 */ 308 309 Modernizr.addTest('postmessage', 'postMessage' in window); 310 311 /*! 312 { 313 "name": "Session Storage", 314 "property": "sessionstorage", 315 "tags": ["storage"], 316 "polyfills": ["joshuabell-polyfill", "cupcake", "sessionstorage"] 317 } 318 ! */ 319 320 // Because we are forced to try/catch this, we'll go aggressive. 321 322 // Just FWIW: IE8 Compat mode supports these features completely: 323 // www.quirksmode.org/dom/html5.html 324 // But IE8 doesn't support either with local files 325 Modernizr.addTest('sessionstorage', function() { 326 const mod = 'modernizr'; 327 328 try { 329 sessionStorage.setItem(mod, mod); 330 sessionStorage.removeItem(mod); 331 332 return true; 333 } catch (e) { 334 return false; 335 } 336 }); 337 338 /*! 339 { 340 "name": "SVG", 341 "property": "svg", 342 "caniuse": "svg", 343 "tags": ["svg"], 344 "authors": ["Erik Dahlstrom"], 345 "polyfills": [ 346 "svgweb", 347 "raphael", 348 "amplesdk", 349 "canvg", 350 "svg-boilerplate", 351 "sie", 352 "dojogfx", 353 "fabricjs" 354 ] 355 } 356 ! */ 357 /* DOC 358 Detects support for SVG in `<embed>` or `<object>` elements. 359 */ 360 361 Modernizr.addTest('svg', !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect); 362 363 /*! 364 { 365 "name": "Web SQL Database", 366 "property": "websqldatabase", 367 "caniuse": "sql-storage", 368 "tags": ["storage"] 369 } 370 ! */ 371 372 // Chrome incognito mode used to throw an exception when using openDatabase 373 // It doesn't anymore. 374 Modernizr.addTest('websqldatabase', 'openDatabase' in window); 375 376 /*! 377 { 378 "name": "Web Workers", 379 "property": "webworkers", 380 "caniuse" : "webworkers", 381 "tags": ["performance", "workers"], 382 "notes": [{ 383 "name": "W3C Reference", 384 "href": "http://www.w3.org/TR/workers/" 385 }, { 386 "name": "HTML5 Rocks article", 387 "href": "http://www.html5rocks.com/en/tutorials/workers/basics/" 388 }, { 389 "name": "MDN documentation", 390 "href": "https://developer.mozilla.org/en-US/docs/Web/Guide/Performance/Using_web_workers" 391 }], 392 "polyfills": ["fakeworker", "html5shims"] 393 } 394 ! */ 395 /* DOC 396 Detects support for the basic `Worker` API from the Web Workers spec. Web Workers provide a simple means for web content to run scripts in background threads. 397 */ 398 399 Modernizr.addTest('webworkers', 'Worker' in window); 400 401 /*! 402 { 403 "name": "WebSockets Support", 404 "property": "websockets", 405 "authors": ["Phread [fearphage]", "Mike Sherov [mikesherov]", "Burak Yigit Kaya [BYK]"], 406 "caniuse": "websockets", 407 "tags": ["html5"], 408 "warnings": [ 409 "This test will reject any old version of WebSockets even if it is not prefixed such as in Safari 5.1" 410 ], 411 "notes": [{ 412 "name": "CLOSING State and Spec", 413 "href": "http://www.w3.org/TR/websockets/#the-websocket-interface" 414 }], 415 "polyfills": [ 416 "sockjs", 417 "socketio", 418 "kaazing-websocket-gateway", 419 "websocketjs", 420 "atmosphere", 421 "graceful-websocket", 422 "portal", 423 "datachannel" 424 ] 425 } 426 ! */ 427 428 Modernizr.addTest('websockets', 'WebSocket' in window && window.WebSocket.CLOSING === 2); 429 430 /** 431 * is returns a boolean for if typeof obj is exactly type. 432 */ 433 function is(obj, type) { 434 return typeof obj === type; 435 } 436 437 // Run through all tests and detect their support in the current UA. 438 function testRunner() { 439 let featureNames; 440 let feature; 441 let aliasIdx; 442 let result; 443 let nameIdx; 444 let featureName; 445 let featureNameSplit; 446 447 for (const featureIdx in tests) { 448 featureNames = []; 449 feature = tests[featureIdx]; 450 451 // run the test, throw the return value into the Modernizr, 452 // then based on that boolean, define an appropriate className 453 // and push it into an array of classes we'll join later. 454 // 455 // If there is no name, it's an 'async' test that is run, 456 // but not directly added to the object. That should 457 // be done with a post-run addTest call. 458 if (feature.name) { 459 featureNames.push(feature.name.toLowerCase()); 460 461 if (feature.options && feature.options.aliases && feature.options.aliases.length) { 462 // Add all the aliases into the names list 463 for (aliasIdx = 0; aliasIdx < feature.options.aliases.length; aliasIdx++) { 464 featureNames.push(feature.options.aliases[aliasIdx].toLowerCase()); 465 } 466 } 467 } 468 469 // Run the test, or use the raw value if it's not a function 470 result = is(feature.fn, 'function') ? feature.fn() : feature.fn; 471 472 // Set each of the names on the Modernizr object 473 for (nameIdx = 0; nameIdx < featureNames.length; nameIdx++) { 474 featureName = featureNames[nameIdx];
475 // Support dot properties as sub tests. We don't do checking to make sure 476 // that the implied parent tests have been added. You must call them in 477 // order (either in the test, or make the parent test a dependency). 478 // 479 // Cap it to TWO to make the logic simple and because who needs that kind of subtesting 480 // hashtag famous last words 481 featureNameSplit = featureName.split('.'); 482 483 if (featureNameSplit.length === 1) { 484 Modernizr[featureNameSplit[0]] = result; 485 } else { 486 // cast to a Boolean, if not one already 487 /* jshint -W053 */ 488 if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) { 489 Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]); 490 } 491 492 Modernizr[featureNameSplit[0]][featureNameSplit[1]] = result; 493 } 494 495 classes.push((result ? '' : 'no-') + featureNameSplit.join('-')); 496 } 497 } 498 } 499 500 const docElement = document.documentElement; 501 502 // Pass in an and array of class names, e.g.:
503 // ['no-webp', 'borderradius', ...] 504 function setClasses(classes) { 505 let className = docElement.className; 506 const classPrefix = Modernizr._config.classPrefix || ''; 507 508 // Change `no-js` to `js` (we do this independently of the `enableClasses` 509 // option) 510 // Handle classPrefix on this too 511 if (Modernizr._config.enableJSClass) { 512 const reJS = new RegExp(`(^|\\s)${classPrefix}no-js(\\s|$)`); 513 514 className = className.replace(reJS, `$1${classPrefix}js$2`); 515 } 516 517 if (Modernizr._config.enableClasses) { 518 // Add the new classes 519 className += ` ${classPrefix}${classes.join(` ${classPrefix}`)}`; 520 docElement.className = className; 521 } 522 } 523 524 const createElement = function() { 525 if (typeof document.createElement !== 'function') { 526 // This is the case in IE7, where the type of createElement is "object". 527 // For this reason, we cannot call apply() as Object is not a Function. 528 return document.createElement(arguments[0]); 529 } else { 530 return document.createElement.apply(document, arguments); 531 } 532 }; 533 534 /*! 535 { 536 "name": "Canvas", 537 "property": "canvas", 538 "caniuse": "canvas", 539 "tags": ["canvas", "graphics"], 540 "polyfills": ["flashcanvas", "excanvas", "slcanvas", "fxcanvas"] 541 } 542 ! */ 543 /* DOC 544 Detects support for the `<canvas>` element for 2D drawing. 545 */ 546 547 // On the S60 and BB Storm, getContext exists, but always returns undefined 548 // so we actually have to call getContext() to verify 549 // github.com/Modernizr/Modernizr/issues/issue/97/ 550 Modernizr.addTest('canvas', function() { 551 const elem = createElement('canvas'); 552 553 return !!(elem.getContext && elem.getContext('2d')); 554 }); 555 556 /*! 557 { 558 "name": "Canvas text", 559 "property": "canvastext", 560 "caniuse": "canvas-text", 561 "tags": ["canvas", "graphics"], 562 "polyfills": ["canvastext"] 563 } 564 ! */ 565 /* DOC 566 Detects support for the text APIs for `<canvas>` elements. 567 */ 568 569 Modernizr.addTest('canvastext', function() { 570 if (Modernizr.canvas === false) return false; 571 572 return typeof createElement('canvas').getContext('2d').fillText === 'function'; 573 }); 574 575 /*! 576 {
577 "name": "CSS Multiple Backgrounds", 578 "caniuse": "multibackgrounds", 579 "property": "multiplebgs", 580 "tags": ["css"] 581 } 582 ! */ 583 584 // Setting multiple images AND a color on the background shorthand property 585 // and then querying the style.background property value for the number of 586 // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! 587 588 Modernizr.addTest('multiplebgs', function() { 589 const elem = createElement('div'); 590 const style = elem.style; 591 592 style.cssText = 'background:url(https://),url(https://),red url(https://)'; 593 594 // If the UA supports multiple backgrounds, there should be three occurrences 595 // of the string "url(" in the return value for elemStyle.background 596 return (/(url\s*\(.*?){3}/).test(style.background); 597 }); 598 599 /*! 600 { 601 "name": "CSS rgba", 602 "caniuse": "css3-colors", 603 "property": "rgba", 604 "tags": ["css"], 605 "notes": [{ 606 "name": "CSSTricks Tutorial", 607 "href": "http://css-tricks.com/rgba-browser-support/" 608 }] 609 } 610 ! */ 611 612 Modernizr.addTest('rgba', function() { 613 const elem = createElement('div'); 614 const style = elem.style; 615 616 style.cssText = 'background-color:rgba(150,255,150,.5)'; 617 618 return (`${style.backgroundColor}`).indexOf('rgba') > -1; 619 }); 620 621 /*! 622 { 623 "name": "Drag & Drop", 624 "property": "draganddrop", 625 "caniuse": "dragndrop", 626 "knownBugs": ["Mobile browsers like Android, iOS < 6, and Firefox OS technically support the APIs, but don't expose it to the end user, resulting in a false positive."], 627 "notes": [{ 628 "name": "W3C spec", 629 "href": "http://www.w3.org/TR/2010/WD-html5-20101019/dnd.html" 630 }], 631 "polyfills": ["dropfile", "moxie", "fileapi"] 632 } 633 ! */ 634 /* DOC 635 Detects support for native drag & drop of elements. 636 */ 637 638 Modernizr.addTest('draganddrop', function() { 639 const div = createElement('div'); 640 641 return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); 642 }); 643 644 /*! 645 { 646 "name" : "HTML5 Audio Element", 647 "property": "audio", 648 "tags" : ["html5", "audio", "media"] 649 } 650 ! */ 651 /* DOC 652 Detects the audio element 653 */ 654 655 // This tests evaluates support of the audio element, as well as 656 // testing what types of content it supports. 657 // 658 // We're using the Boolean constructor here, so that we can extend the value 659 // e.g. Modernizr.audio // true 660 // Modernizr.audio.ogg // 'probably' 661 // 662 // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 663 // thx to NielsLeenheer and zcorpan 664 665 // Note: in some older browsers, "no" was a return value instead of empty string. 666 // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2 667 // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5 668 Modernizr.addTest('audio', function() { 669 /* jshint -W053 */ 670 const elem = createElement('audio'); 671 let bool = false;
672 673 try { 674 if (bool = !!elem.canPlayType) { 675 bool = new Boolean(bool); 676 bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ''); 677 bool.mp3 = elem.canPlayType('audio/mpeg;').replace(/^no$/, ''); 678 bool.opus = elem.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ''); 679 680 // Mimetypes accepted: 681 // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements 682 // bit.ly/iphoneoscodecs 683 bool.wav = elem.canPlayType('audio/wav; codecs="1"').replace(/^no$/, ''); 684 bool.m4a = (elem.canPlayType('audio/x-m4a;') 685 || elem.canPlayType('audio/aac;')).replace(/^no$/, ''); 686 } 687 } catch (e) { } 688 689 return bool; 690 }); 691 692 /*! 693 { 694 "name": "HTML5 Video", 695 "property": "video", 696 "caniuse": "video", 697 "tags": ["html5"], 698 "knownBugs": [ 699 "Without QuickTime, `Modernizr.video.h264` will be `undefined`; http://github.com/Modernizr/Modernizr/issues/546" 700 ], 701 "polyfills": [ 702 "html5media", 703 "mediaelementjs", 704 "sublimevideo", 705 "videojs", 706 "leanbackplayer", 707 "videoforeverybody" 708 ] 709 } 710 ! */ 711 /* DOC 712 Detects support for the video element, as well as testing what types of content it supports. 713 714 Subproperties are provided to describe support for `ogg`, `h264` and `webm` formats, e.g.: 715 716 ```javascript 717 Modernizr.video // true 718 Modernizr.video.ogg // 'probably' 719 ``` 720 */ 721 722 // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 723 // thx to NielsLeenheer and zcorpan 724 725 // Note: in some older browsers, "no" was a return value instead of empty string. 726 // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2 727 // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5 728 729 Modernizr.addTest('video', function() { 730 /* jshint -W053 */ 731 const elem = createElement('video'); 732 let bool = false;
733 734 // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 735 try { 736 if (bool = !!elem.canPlayType) { 737 bool = new Boolean(bool); 738 bool.ogg = elem.canPlayType('video/ogg; codecs="theora"').replace(/^no$/, ''); 739 740 // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546 741 bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/, ''); 742 743 bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/, ''); 744 745 bool.vp9 = elem.canPlayType('video/webm; codecs="vp9"').replace(/^no$/, ''); 746 747 bool.hls = elem.canPlayType('application/x-mpegURL; codecs="avc1.42E01E"').replace(/^no$/, ''); 748 } 749 } catch (e) {} 750 751 return bool; 752 }); 753 754 /*! 755 { 756 "name": "Inline SVG", 757 "property": "inlinesvg", 758 "caniuse": "svg-html5", 759 "tags": ["svg"], 760 "notes": [{ 761 "name": "Test page", 762 "href": "http://paulirish.com/demo/inline-svg" 763 }], 764 "polyfills": ["inline-svg-polyfill"] 765 } 766 ! */ 767 /* DOC 768 Detects support for inline SVG in HTML (not within XHTML). 769 */ 770 771 Modernizr.addTest('inlinesvg', function() { 772 const div = createElement('div'); 773 774 div.innerHTML = '<svg/>'; 775 776 return (div.firstChild && div.firstChild.namespaceURI) == 'http://www.w3.org/2000/svg'; 777 }); 778 779 /*! 780 { 781 "name": "WebGL", 782 "property": "webgl", 783 "caniuse": "webgl", 784 "tags": ["webgl", "graphics"], 785 "polyfills": ["jebgl", "cwebgl", "iewebgl"] 786 } 787 ! */ 788 789 Modernizr.addTest('webgl', function() { 790 const canvas = createElement('canvas'); 791 const supports = 'probablySupportsContext' in canvas ? 'probablySupportsContext' : 'supportsContext'; 792 793 if (supports in canvas) { 794 return canvas[supports]('webgl') || canvas[supports]('experimental-webgl'); 795 } 796 797 return 'WebGLRenderingContext' in window; 798 }); 799 800 // List of property values to set for css tests. See ticket #21 801 const prefixes = (ModernizrProto._config.usePrefixes ? ' -webkit- -moz- -o- -ms- '.split(' ') : []); 802 803 // expose these for the plugin API. Look in the source for how to join() them against your input 804 ModernizrProto._prefixes = prefixes; 805 806 /*! 807 { 808 "name": "CSS Gradients", 809 "caniuse": "css-gradients", 810 "property": "cssgradients", 811 "tags": ["css"], 812 "knownBugs": ["False-positives on webOS (https://github.com/Modernizr/Modernizr/issues/202)"], 813 "notes": [{ 814 "name": "Webkit Gradient Syntax", 815 "href": "http://webkit.org/blog/175/introducing-css-gradients/" 816 },{ 817 "name": "Mozilla Linear Gradient Syntax", 818 "href": "http://developer.mozilla.org/en/CSS/-moz-linear-gradient" 819 },{ 820 "name": "Mozilla Radial Gradient Syntax", 821 "href": "http://developer.mozilla.org/en/CSS/-moz-radial-gradient" 822 },{ 823 "name": "W3C Gradient Spec", 824 "href": "dev.w3.org/csswg/css3-images/#gradients-" 825 }] 826 } 827 ! */ 828 829 Modernizr.addTest('cssgradients', function() { 830 const str1 = 'background-image:'; 831 const str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));'; 832 const str3 = 'linear-gradient(left top,#9f9, white);'; 833 834 // standard syntax // trailing 'background-image:' 835 let css = str1 + prefixes.join(str3 + str1).slice(0, -str1.length); 836 837 if (Modernizr._config.usePrefixes) { 838 // legacy webkit syntax (FIXME: remove when syntax not in use anymore) 839 css += `${str1}-webkit-${str2}`; 840 } 841 842 const elem = createElement('div'); 843 const style = elem.style; 844 845 style.cssText = css; 846 847 // IE6 returns undefined so cast to string 848 return (`${style.backgroundImage}`).indexOf('gradient') > -1; 849 }); 850 851 /*! 852 { 853 "name": "CSS Opacity", 854 "caniuse": "css-opacity", 855 "property": "opacity", 856 "tags": ["css"] 857 } 858 ! */ 859 860 // Browsers that actually have CSS Opacity implemented have done so 861 // according to spec, which means their return values are within the 862 // range of [0.0,1.0] - including the leading zero. 863 864 Modernizr.addTest('opacity', function() { 865 const elem = createElement('div'); 866 const style = elem.style; 867 868 style.cssText = prefixes.join('opacity:.55;'); 869 870 // The non-literal . in this regex is intentional: 871 // German Chrome returns this value as 0,55 872 // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 873 return (/^0.55$/).test(style.opacity); 874 }); 875 876 /** 877 * contains returns a boolean for if substr is found within str. 878 */ 879 function contains(str, substr) {
880 return !!~(`${str}`).indexOf(substr); 881 } 882 883 /*! 884 { 885 "name": "CSS HSLA Colors", 886 "caniuse": "css3-colors", 887 "property": "hsla", 888 "tags": ["css"] 889 } 890 ! */ 891 892 Modernizr.addTest('hsla', function() { 893 const elem = createElement('div'); 894 const style = elem.style; 895 896 style.cssText = 'background-color:hsla(120,40%,100%,.5)'; 897 898 return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla'); 899 }); 900 901 /*! 902 { 903 "name": "CSS Supports", 904 "property": "supports", 905 "caniuse": "css-featurequeries", 906 "tags": ["css"], 907 "builderAliases": ["css_supports"], 908 "notes": [{ 909 "name": "W3 Spec", 910 "href": "http://dev.w3.org/csswg/css3-conditional/#at-supports" 911 },{ 912 "name": "Related Github Issue", 913 "href": "github.com/Modernizr/Modernizr/issues/648" 914 },{ 915 "name": "W3 Info", 916 "href": "http://dev.w3.org/csswg/css3-conditional/#the-csssupportsrule-interface" 917 }] 918 } 919 ! */ 920 921 const newSyntax = 'CSS' in window && 'supports' in window.CSS; 922 const oldSyntax = 'supportsCSS' in window; 923 924 Modernizr.addTest('supports', newSyntax || oldSyntax); 925 926 const toStringFn = ({}).toString; 927 928 /*! 929 { 930 "name": "SVG clip paths", 931 "property": "svgclippaths", 932 "tags": ["svg"], 933 "notes": [{ 934 "name": "Demo", 935 "href": "http://srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg" 936 }] 937 } 938 ! */ 939 /* DOC 940 Detects support for clip paths in SVG (only, not on HTML content). 941 942 See [this discussion](http://github.com/Modernizr/Modernizr/issues/213) regarding applying SVG clip paths to HTML content. 943 */ 944 945 Modernizr.addTest('svgclippaths', function() { 946 return !!document.createElementNS 947 && /SVGClipPath/.test(toStringFn.call(document.createElementNS('http://www.w3.org/2000/svg', 'clipPath'))); 948 }); 949 950 /*! 951 { 952 "name": "SVG SMIL animation", 953 "property": "smil", 954 "caniuse": "svg-smil", 955 "tags": ["svg"], 956 "notes": [{ 957 "name": "W3C Synchronised Multimedia spec", 958 "href": "http://www.w3.org/AudioVideo/" 959 }] 960 } 961 ! */ 962 963 // SVG SMIL animation 964 Modernizr.addTest('smil', function() { 965 return !!document.createElementNS 966 && /SVGAnimate/.test(toStringFn.call(document.createElementNS('http://www.w3.org/2000/svg', 'animate'))); 967 }); 968 969 // isEventSupported determines if the given element supports the given event 970 // kangax.github.com/iseventsupported/ 971 // github.com/Modernizr/Modernizr/pull/636 972 // 973 // Known incorrects: 974 // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative 975 // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333 976 const isEventSupported = (function(undefined) { 977 // Detect whether event support can be detected via `in`. Test on a DOM element 978 // using the "blur" event b/c it should always exist. bit.ly/event-detection 979 const needsFallback = !('onblur' in document.documentElement); 980 981 /** 982 * @param {string|*} eventName is the name of an event to test for (e.g. "resize") 983 * @param {(Object|string|*)=} element is the element|document|window|tagName to test on 984 * @return {boolean} 985 */ 986 function isEventSupportedInner(eventName, element) { 987 let isSupported; 988 989 if (!eventName) { return false; } 990 991 if (!element || typeof element === 'string') { 992 element = createElement(element || 'div'); 993 } 994 995 // Testing via the `in` operator is sufficient for modern browsers and IE. 996 // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and 997 // "resize", whereas `in` "catches" those. 998 eventName = `on${eventName}`; 999 isSupported = eventName in element; 1000 1001 // Fallback technique for old Firefox - bit.ly/event-detection 1002 if (!isSupported && needsFallback) { 1003 if (!element.setAttribute) { 1004 // Switch to generic element if it lacks `setAttribute`. 1005 // It could be the `document`, `window`, or something else. 1006 element = createElement('div'); 1007 } 1008 1009 element.setAttribute(eventName, ''); 1010 isSupported = typeof element[eventName] === 'function'; 1011 1012 if (element[eventName] !== undefined) { 1013 // If property was created, "remove it" by setting value to `undefined`. 1014 element[eventName] = undefined; 1015 } 1016 1017 element.removeAttribute(eventName); 1018 } 1019 1020 return isSupported; 1021 } 1022 1023 return isEventSupportedInner; 1024 }()); 1025 1026 // Modernizr.hasEvent() detects support for a given event, with an optional element to test on 1027 // Modernizr.hasEvent('gesturestart', elem) 1028 const hasEvent = ModernizrProto.hasEvent = isEventSupported; 1029 1030 /*! 1031 { 1032 "name": "Hashchange event",
1033 "property": "hashchange", 1034 "caniuse": "hashchange", 1035 "tags": ["history"], 1036 "notes": [{ 1037 "name": "MDN documentation", 1038 "href": "https://developer.mozilla.org/en-US/docs/Web/API/window.onhashchange" 1039 }], 1040 "polyfills": [ 1041 "jquery-hashchange", 1042 "moo-historymanager", 1043 "jquery-ajaxy", 1044 "hasher", 1045 "shistory" 1046 ] 1047 } 1048 ! */ 1049 /* DOC 1050 Detects support for the `hashchange` event, fired when the current location fragment changes. 1051 */ 1052 1053 Modernizr.addTest('hashchange', function() { 1054 if (hasEvent('hashchange', window) === false) { 1055 return false; 1056 } 1057 1058 // documentMode logic from YUI to filter out IE8 Compat Mode 1059 // which false positives. 1060 return (document.documentMode === undefined || document.documentMode > 7); 1061 }); 1062 1063 // Helper function for converting kebab-case to camelCase, 1064 // e.g. box-sizing -> boxSizing 1065 function cssToDOM(name) { 1066 return name.replace(/([a-z])-([a-z])/g, function(str, m1, m2) { 1067 return m1 + m2.toUpperCase(); 1068 }).replace(/^-/, ''); 1069 } 1070 1071 function getBody() { 1072 // After page load injecting a fake body doesn't work so check if body exists 1073 let body = document.body; 1074 1075 if (!body) { 1076 // Can't use the real body create a fake one. 1077 body = createElement('body'); 1078 body.fake = true; 1079 } 1080 1081 return body; 1082 } 1083 1084 // Inject element with style element and some CSS rules 1085 function injectElementWithStyles(rule, callback, nodes, testnames) { 1086 const mod = 'modernizr'; 1087 let style; 1088 let ret; 1089 let node; 1090 let docOverflow; 1091 const div = createElement('div'); 1092 const body = getBody(); 1093 1094 if (parseInt(nodes, 10)) { 1095 // In order not to give false positives we create a node for each test 1096 // This also allows the method to scale for unspecified uses 1097 while (nodes--) { 1098 node = createElement('div'); 1099 node.id = testnames ? testnames[nodes] : mod + (nodes + 1); 1100 div.appendChild(node); 1101 } 1102 } 1103 1104 // <style> elements in IE6-9 are considered 'NoScope' elements and therefore will be removed 1105 // when injected with innerHTML. To get around this you need to prepend the 'NoScope' element 1106 // with a 'scoped' element, in our case the soft-hyphen entity as it won't mess with our measurements. 1107 // msdn.microsoft.com/en-us/library/ms533897%28VS.85%29.aspx 1108 // Documents served as xml will throw if using ­ so use xml friendly encoded version. See issue #277 1109 style = ['­', '<style id="s', mod, '">', rule, '</style>'].join(''); 1110 div.id = mod; 1111 // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody. 1112 // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270 1113 (!body.fake ? div : body).innerHTML += style; 1114 body.appendChild(div); 1115 1116 if (body.fake) { 1117 // avoid crashing IE8, if background image is used 1118 body.style.background = ''; 1119 // Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible 1120 body.style.overflow = 'hidden'; 1121 docOverflow = docElement.style.overflow; 1122 docElement.style.overflow = 'hidden'; 1123 docElement.appendChild(body); 1124 } 1125 1126 ret = callback(div, rule); 1127 1128 // If this is done after page load we don't want to remove the body so check if body exists 1129 if (body.fake) { 1130 body.parentNode.removeChild(body); 1131 docElement.style.overflow = docOverflow; 1132 // Trigger layout so kinetic scrolling isn't disabled in iOS6+ 1133 docElement.offsetHeight; 1134 } else { 1135 div.parentNode.removeChild(div); 1136 } 1137 1138 return !!ret; 1139 } 1140 1141 const testStyles = ModernizrProto.testStyles = injectElementWithStyles; 1142 1143 /*! 1144 { 1145 "name": "@font-face", 1146 "property": "fontface", 1147 "authors": ["Diego Perini", "Mat Marquis"], 1148 "tags": ["css"], 1149 "knownBugs": [ 1150 "False Positive: WebOS http://github.com/Modernizr/Modernizr/issues/342", 1151 "False Postive: WP7 http://github.com/Modernizr/Modernizr/issues/538" 1152 ],
1153 "notes": [{ 1154 "name": "@font-face detection routine by Diego Perini", 1155 "href": "http://javascript.nwbox.com/CSSSupport/" 1156 },{ 1157 "name": "Filament Group @font-face compatibility research", 1158 "href": "https://docs.google.com/presentation/d/1n4NyG4uPRjAA8zn_pSQ_Ket0RhcWC6QlZ6LMjKeECo0/edit#slide=id.p" 1159 },{ 1160 "name": "Filament Grunticon/@font-face device testing results", 1161 "href": "https://docs.google.com/spreadsheet/ccc?key=0Ag5_yGvxpINRdHFYeUJPNnZMWUZKR2ItMEpRTXZPdUE#gid=0" 1162 },{ 1163 "name": "CSS fonts on Android", 1164 "href": "http://stackoverflow.com/questions/3200069/css-fonts-on-android" 1165 },{ 1166 "name": "@font-face and Android", 1167 "href": "http://archivist.incutio.com/viewlist/css-discuss/115960" 1168 }] 1169 } 1170 ! */ 1171 1172 const blacklist = (function() { 1173 const ua = navigator.userAgent; 1174 const wkvers = ua.match(/applewebkit\/([0-9]+)/gi) && parseFloat(RegExp.$1); 1175 const webos = ua.match(/w(eb)?osbrowser/gi); 1176 const wppre8 = ua.match(/windows phone/gi) && ua.match(/iemobile\/([0-9])+/gi) && parseFloat(RegExp.$1) >= 9; 1177 const oldandroid = wkvers < 533 && ua.match(/android/gi); 1178 1179 return webos || oldandroid || wppre8; 1180 }()); 1181 1182 if (blacklist) { 1183 Modernizr.addTest('fontface', false); 1184 } else { 1185 testStyles('@font-face {font-family:"font";src:url("https://")}', function(node, rule) { 1186 const style = document.getElementById('smodernizr'); 1187 const sheet = style.sheet || style.styleSheet; 1188 const cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; 1189 const bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; 1190 1191 Modernizr.addTest('fontface', bool); 1192 }); 1193 } 1194 1195 /*! 1196 { 1197 "name": "CSS Generated Content", 1198 "property": "generatedcontent", 1199 "tags": ["css"], 1200 "warnings": ["Android won't return correct height for anything below 7px #738"], 1201 "notes": [{ 1202 "name": "W3C CSS Selectors Level 3 spec", 1203 "href": "http://www.w3.org/TR/css3-selectors/#gen-content" 1204 },{ 1205 "name": "MDN article on :before", 1206 "href": "https://developer.mozilla.org/en-US/docs/Web/CSS/::before" 1207 },{ 1208 "name": "MDN article on :after", 1209 "href": "https://developer.mozilla.org/en-US/docs/Web/CSS/::before" 1210 }] 1211 } 1212 ! */ 1213 1214 testStyles('#modernizr{font:0/0 a}#modernizr:after{content:":)";visibility:hidden;font:7px/1 a}', function(node) { 1215 Modernizr.addTest('generatedcontent', node.offsetHeight >= 7); 1216 }); 1217 1218 // Following spec is to expose vendor-specific style properties as: 1219 // elem.style.WebkitBorderRadius 1220 // and the following would be incorrect: 1221 // elem.style.webkitBorderRadius 1222 1223 // Webkit ghosts their properties in lowercase but Opera & Moz do not. 1224 // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+ 1225 // erik.eae.net/archives/2008/03/10/21.48.10/ 1226 1227 // More here: github.com/Modernizr/Modernizr/issues/issue/21 1228 const omPrefixes = 'Moz O ms Webkit'; 1229 1230 const cssomPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.split(' ') : []); 1231 1232 ModernizrProto._cssomPrefixes = cssomPrefixes; 1233 1234 /** 1235 * atRule returns a given CSS property at-rule (eg @keyframes), possibly in 1236 * some prefixed form, or false, in the case of an unsupported rule 1237 * 1238 * @param prop - String naming the property to test 1239 */ 1240 1241 const atRule = function(prop) { 1242 const length = prefixes.length; 1243 const cssrule = window.CSSRule; 1244 let rule; 1245 1246 if (typeof cssrule === 'undefined') { 1247 return undefined; 1248 } 1249 1250 if (!prop) { 1251 return false; 1252 } 1253 1254 // remove literal @ from beginning of provided property 1255 prop = prop.replace(/^@/, ''); 1256 1257 // CSSRules use underscores instead of dashes 1258 rule = `${prop.replace(/-/g, '_').toUpperCase()}_RULE`; 1259 1260 if (rule in cssrule) { 1261 return `@${prop}`; 1262 } 1263 1264 for (let i = 0; i < length; i++) { 1265 // prefixes gives us something like -o-, and we want O_ 1266 const prefix = prefixes[i]; 1267 const thisRule = `${prefix.toUpperCase()}_${rule}`; 1268 1269 if (thisRule in cssrule) { 1270 return `@-${prefix.toLowerCase()}-${prop}`; 1271 } 1272 } 1273 1274 return false;
1275 }; 1276 1277 const domPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.toLowerCase().split(' ') : []); 1278 1279 ModernizrProto._domPrefixes = domPrefixes; 1280 1281 // Change the function's scope. 1282 function fnBind(fn, that) { 1283 return function() { 1284 return fn.apply(that, arguments); 1285 }; 1286 } 1287 1288 /** 1289 * testDOMProps is a generic DOM property test; if a browser supports 1290 * a certain property, it won't return undefined for it. 1291 */ 1292 function testDOMProps(props, obj, elem) { 1293 let item; 1294 1295 for (const i in props) { 1296 if (props[i] in obj) { 1297 // return the property name as a string 1298 if (elem === false) return props[i]; 1299 1300 item = obj[props[i]]; 1301 1302 // let's bind a function 1303 if (is(item, 'function')) { 1304 // bind to obj unless overriden 1305 return fnBind(item, elem || obj); 1306 } 1307 1308 // return the unbound function or obj or value 1309 return item; 1310 } 1311 } 1312 1313 return false; 1314 } 1315 1316 /** 1317 * Create our "modernizr" element that we do most feature tests on. 1318 */ 1319 const modElem = { 1320 elem: createElement('modernizr') 1321 }; 1322 1323 // Clean up this element 1324 Modernizr._q.push(function() { 1325 delete modElem.elem; 1326 }); 1327 1328 const mStyle = { 1329 style: modElem.elem.style 1330 }; 1331 1332 // kill ref for gc, must happen before 1333 // mod.elem is removed, so we unshift on to 1334 // the front of the queue. 1335 Modernizr._q.unshift(function() { 1336 delete mStyle.style; 1337 }); 1338 1339 // Helper function for converting camelCase to kebab-case, 1340 // e.g. boxSizing -> box-sizing 1341 function domToCSS(name) { 1342 return name.replace(/([A-Z])/g, function(str, m1) { 1343 return `-${m1.toLowerCase()}`; 1344 }).replace(/^ms-/, '-ms-'); 1345 } 1346 1347 // Function to allow us to use native feature detection functionality if available. 1348 // Accepts a list of property names and a single value 1349 // Returns `undefined` if native detection not available 1350 function nativeTestProps(props, value) { 1351 let i = props.length; 1352 1353 // Start with the JS API: http://www.w3.org/TR/css3-conditional/#the-css-interface 1354 if ('CSS' in window && 'supports' in window.CSS) { 1355 // Try every prefixed variant of the property 1356 while (i--) { 1357 if (window.CSS.supports(domToCSS(props[i]), value)) { 1358 return true; 1359 } 1360 } 1361 1362 return false; 1363 } 1364 // Otherwise fall back to at-rule (for Opera 12.x) 1365 else if ('CSSSupportsRule' in window) { 1366 // Build a condition string for every prefixed variant 1367 let conditionText = []; 1368 1369 while (i--) { 1370 conditionText.push(`(${domToCSS(props[i])}:${value})`); 1371 } 1372 1373 conditionText = conditionText.join(' or '); 1374 1375 return injectElementWithStyles(`@supports (${conditionText}) { #modernizr { position: absolute; } }`, function(node) { 1376 return getComputedStyle(node, null).position == 'absolute'; 1377 }); 1378 } 1379 1380 return undefined; 1381 } 1382 1383 // testProps is a generic CSS / DOM property test. 1384 1385 // In testing support for a given CSS property, it's legit to test: 1386 // `elem.style[styleName] !== undefined` 1387 // If the property is supported it will return an empty string, 1388 // if unsupported it will return undefined. 1389 1390 // We'll take advantage of this quick test and skip setting a style 1391 // on our modernizr element, but instead just testing undefined vs 1392 // empty string. 1393 1394 // Property names can be provided in either camelCase or kebab-case. 1395 1396 function testProps(props, prefixed, value, skipValueTest) { 1397 skipValueTest = is(skipValueTest, 'undefined') ? false : skipValueTest; 1398 1399 // Try native detect first 1400 if (!is(value, 'undefined')) { 1401 const result = nativeTestProps(props, value); 1402 1403 if (!is(result, 'undefined')) { 1404 return result; 1405 } 1406 } 1407 1408 // Otherwise do it properly 1409 let afterInit; let i; let propsLength; let prop; let 1410 before; 1411 1412 // If we don't have a style element, that means 1413 // we're running async or after the core tests, 1414 // so we'll need to create our own elements to use 1415 if (!mStyle.style) { 1416 afterInit = true; 1417 mStyle.modElem = createElement('modernizr'); 1418 mStyle.style = mStyle.modElem.style; 1419 } 1420 1421 // Delete the objects if we 1422 // we created them. 1423 function cleanElems() { 1424 if (afterInit) { 1425 delete mStyle.style; 1426 delete mStyle.modElem; 1427 } 1428 } 1429 1430 propsLength = props.length; 1431 1432 for (i = 0; i < propsLength; i++) { 1433 prop = props[i]; 1434 before = mStyle.style[prop]; 1435 1436 if (contains(prop, '-')) { 1437 prop = cssToDOM(prop); 1438 } 1439 1440 if (mStyle.style[prop] !== undefined) { 1441 // If value to test has been passed in, do a set-and-check test. 1442 // 0 (integer) is a valid property value, so check that `value` isn't 1443 // undefined, rather than just checking it's truthy. 1444 if (!skipValueTest && !is(value, 'undefined')) { 1445 // Needs a try catch block because of old IE. This is slow, but will 1446 // be avoided in most cases because `skipValueTest` will be used. 1447 try { 1448 mStyle.style[prop] = value; 1449 } catch (e) {} 1450 1451 // If the property value has changed, we assume the value used is 1452 // supported. If `value` is empty string, it'll fail here (because 1453 // it hasn't changed), which matches how browsers have implemented 1454 // CSS.supports() 1455 if (mStyle.style[prop] != before) { 1456 cleanElems(); 1457 1458 return prefixed == 'pfx' ? prop : true; 1459 } 1460 } 1461 // Otherwise just return true, or the property name if this is a 1462 // `prefixed()` call 1463 else { 1464 cleanElems(); 1465 1466 return prefixed == 'pfx' ? prop : true; 1467 } 1468 } 1469 } 1470 1471 cleanElems(); 1472 1473 return false;
1474 } 1475 1476 // Modernizr.testProp() investigates whether a given style property is recognized 1477 // Property names can be provided in either camelCase or kebab-case. 1478 // Modernizr.testProp('pointerEvents') 1479 // Also accepts optional 2nd arg, of a value to use for native feature detection, e.g.: 1480 // Modernizr.testProp('pointerEvents', 'none') 1481 const testProp = ModernizrProto.testProp = function(prop, value, useValue) { 1482 return testProps([prop], undefined, value, useValue); 1483 }; 1484 1485 /*! 1486 { 1487 "name": "CSS textshadow", 1488 "property": "textshadow", 1489 "caniuse": "css-textshadow", 1490 "tags": ["css"], 1491 "knownBugs": ["FF3.0 will false positive on this test"] 1492 } 1493 ! */ 1494 1495 Modernizr.addTest('textshadow', testProp('textShadow', '1px 1px')); 1496 1497 /** 1498 * testPropsAll tests a list of DOM properties we want to check against. 1499 * We specify literally ALL possible (known and/or likely) properties on 1500 * the element including the non-vendor prefixed one, for forward- 1501 * compatibility. 1502 */ 1503 function testPropsAll(prop, prefixed, elem, value, skipValueTest) { 1504 const ucProp = prop.charAt(0).toUpperCase() + prop.slice(1); 1505 let props = (`${prop} ${cssomPrefixes.join(`${ucProp} `)}${ucProp}`).split(' '); 1506 1507 // did they call .prefixed('boxSizing') or are we just testing a prop? 1508 if (is(prefixed, 'string') || is(prefixed, 'undefined')) { 1509 return testProps(props, prefixed, value, skipValueTest); 1510 1511 // otherwise, they called .prefixed('requestAnimationFrame', window[, elem]) 1512 } else { 1513 props = (`${prop} ${(domPrefixes).join(`${ucProp} `)}${ucProp}`).split(' '); 1514 1515 return testDOMProps(props, prefixed, elem); 1516 } 1517 } 1518 1519 // Modernizr.testAllProps() investigates whether a given style property, 1520 // or any of its vendor-prefixed variants, is recognized 1521 // Note that the property names must be provided in the camelCase variant. 1522 // Modernizr.testAllProps('boxSizing') 1523 ModernizrProto.testAllProps = testPropsAll; 1524 1525 /** 1526 * testAllProps determines whether a given CSS property, in some prefixed 1527 * form, is supported by the browser. It can optionally be given a value; in 1528 * which case testAllProps will only return true if the browser supports that 1529 * value for the named property; this latter case will use native detection 1530 * (via window.CSS.supports) if available. A boolean can be passed as a 3rd 1531 * parameter to skip the value check when native detection isn't available, 1532 * to improve performance when simply testing for support of a property. 1533 * 1534 * @param prop - String naming the property to test (either camelCase or 1535 * kebab-case) 1536 * @param value - [optional] String of the value to test 1537 * @param skipValueTest - [optional] Whether to skip testing that the value 1538 * is supported when using non-native detection 1539 * (default: false) 1540 */ 1541 function testAllProps(prop, value, skipValueTest) { 1542 return testPropsAll(prop, undefined, undefined, value, skipValueTest); 1543 } 1544 1545 ModernizrProto.testAllProps = testAllProps; 1546 1547 /*! 1548 { 1549 "name": "Background Size", 1550 "property": "backgroundsize", 1551 "tags": ["css"], 1552 "knownBugs": ["This will false positive in Opera Mini - http://github.com/Modernizr/Modernizr/issues/396"], 1553 "notes": [{ 1554 "name": "Related Issue", 1555 "href": "http://github.com/Modernizr/Modernizr/issues/396" 1556 }] 1557 } 1558 ! */ 1559 1560 Modernizr.addTest('backgroundsize', testAllProps('backgroundSize', '100%', true)); 1561 1562 /*! 1563 { 1564 "name": "Border Image", 1565 "property": "borderimage", 1566 "caniuse": "border-image", 1567 "polyfills": ["css3pie"], 1568 "knownBugs": ["Android < 2.0 is true, but has a broken implementation"], 1569 "tags": ["css"] 1570 } 1571 ! */ 1572 1573 Modernizr.addTest('borderimage', testAllProps('borderImage', 'url() 1', true)); 1574 1575 /*! 1576 { 1577 "name": "Border Radius",
1578 "property": "borderradius", 1579 "caniuse": "border-radius", 1580 "polyfills": ["css3pie"], 1581 "tags": ["css"], 1582 "notes": [{ 1583 "name": "Comprehensive Compat Chart", 1584 "href": "http://muddledramblings.com/table-of-css3-border-radius-compliance" 1585 }] 1586 } 1587 ! */ 1588 1589 Modernizr.addTest('borderradius', testAllProps('borderRadius', '0px', true)); 1590 1591 /*! 1592 { 1593 "name": "Box Shadow", 1594 "property": "boxshadow", 1595 "caniuse": "css-boxshadow", 1596 "tags": ["css"], 1597 "knownBugs": [ 1598 "WebOS false positives on this test.", 1599 "The Kindle Silk browser false positives" 1600 ] 1601 } 1602 ! */ 1603 1604 Modernizr.addTest('boxshadow', testAllProps('boxShadow', '1px 1px', true)); 1605 1606 /*! 1607 { 1608 "name": "CSS Animations", 1609 "property": "cssanimations", 1610 "caniuse": "css-animation", 1611 "polyfills": ["transformie", "csssandpaper"], 1612 "tags": ["css"], 1613 "warnings": ["Android < 4 will pass this test, but can only animate a single property at a time"], 1614 "notes": [{ 1615 "name" : "Article: 'Dispelling the Android CSS animation myths'", 1616 "href": "http://goo.gl/OGw5Gm" 1617 }] 1618 } 1619 ! */ 1620 /* DOC 1621 Detects whether or not elements can be animated using CSS 1622 */ 1623 1624 Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', true)); 1625 1626 /*! 1627 { 1628 "name": "CSS Columns", 1629 "property": "csscolumns", 1630 "caniuse": "multicolumn", 1631 "polyfills": ["css3multicolumnjs"], 1632 "tags": ["css"] 1633 } 1634 ! */ 1635 1636 (function() { 1637 /* jshint -W053 */ 1638 Modernizr.addTest('csscolumns', function() { 1639 let bool = false;
1640 const test = testAllProps('columnCount'); 1641 1642 try { 1643 if (bool = !!test) { 1644 bool = new Boolean(bool); 1645 } 1646 } catch (e) {} 1647 1648 return bool; 1649 }); 1650 1651 const props = ['Width', 'Span', 'Fill', 'Gap', 'Rule', 'RuleColor', 'RuleStyle', 'RuleWidth', 'BreakBefore', 'BreakAfter', 'BreakInside']; 1652 let name; let 1653 test; 1654 1655 for (let i = 0; i < props.length; i++) { 1656 name = props[i].toLowerCase(); 1657 test = testAllProps(`column${props[i]}`); 1658 1659 // break-before, break-after & break-inside are not "column"-prefixed in spec 1660 if (name === 'breakbefore' || name === 'breakafter' || name == 'breakinside') { 1661 test = test || testAllProps(props[i]); 1662 } 1663 1664 Modernizr.addTest(`csscolumns.${name}`, test); 1665 } 1666 }()); 1667 1668 /*! 1669 { 1670 "name": "CSS Reflections", 1671 "caniuse": "css-reflections", 1672 "property": "cssreflections", 1673 "tags": ["css"] 1674 } 1675 ! */ 1676 1677 Modernizr.addTest('cssreflections', testAllProps('boxReflect', 'above', true)); 1678 1679 /*! 1680 { 1681 "name": "CSS Transforms", 1682 "property": "csstransforms", 1683 "caniuse": "transforms2d", 1684 "tags": ["css"] 1685 } 1686 ! */ 1687 1688 Modernizr.addTest('csstransforms', function() { 1689 // Android < 3.0 is buggy, so we sniff and blacklist 1690 // http://git.io/hHzL7w 1691 return navigator.userAgent.indexOf('Android 2.') === -1 1692 && testAllProps('transform', 'scale(1)', true); 1693 }); 1694 1695 /*! 1696 { 1697 "name": "CSS Transforms 3D", 1698 "property": "csstransforms3d", 1699 "caniuse": "transforms3d", 1700 "tags": ["css"], 1701 "warnings": [ 1702 "Chrome may occassionally fail this test on some systems; more info: https://code.google.com/p/chromium/issues/detail?id=129004" 1703 ] 1704 } 1705 ! */ 1706 1707 Modernizr.addTest('csstransforms3d', function() { 1708 let ret = !!testAllProps('perspective', '1px', true); 1709 const usePrefix = Modernizr._config.usePrefixes; 1710 1711 // Webkit's 3D transforms are passed off to the browser's own graphics renderer. 1712 // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in 1713 // some conditions. As a result, Webkit typically recognizes the syntax but 1714 // will sometimes throw a false positive, thus we must do a more thorough check: 1715 if (ret && (!usePrefix || 'webkitPerspective' in docElement.style)) { 1716 let mq; 1717 1718 // Use CSS Conditional Rules if available 1719 if (Modernizr.supports) { 1720 mq = '@supports (perspective: 1px)'; 1721 } else { 1722 // Otherwise, Webkit allows this media query to succeed only if the feature is enabled. 1723 // `@media (transform-3d),(-webkit-transform-3d){ ... }` 1724 mq = '@media (transform-3d)'; 1725 if (usePrefix) mq += ',(-webkit-transform-3d)'; 1726 } 1727 1728 // If loaded inside the body tag and the test element inherits any padding, margin or borders it will fail #740 1729 mq += '{#modernizr{left:9px;position:absolute;height:5px;margin:0;padding:0;border:0}}'; 1730 1731 testStyles(mq, function(elem) { 1732 ret = elem.offsetLeft === 9 && elem.offsetHeight === 5; 1733 }); 1734 } 1735 1736 return ret; 1737 }); 1738 1739 /*! 1740 { 1741 "name": "CSS Transitions", 1742 "property": "csstransitions", 1743 "caniuse": "css-transitions", 1744 "tags": ["css"] 1745 } 1746 ! */ 1747 1748 Modernizr.addTest('csstransitions', testAllProps('transition', 'all', true)); 1749 1750 /*! 1751 { 1752 "name": "Flexbox", 1753 "property": "flexbox", 1754 "caniuse": "flexbox", 1755 "tags": ["css"], 1756 "notes": [{ 1757 "name": "The _new_ flexbox", 1758 "href": "http://dev.w3.org/csswg/css3-flexbox" 1759 }], 1760 "warnings": [ 1761 "A `true` result for this detect does not imply that the `flex-wrap` property is supported; see the `flexwrap` detect." 1762 ] 1763 } 1764 ! */ 1765 /* DOC 1766 Detects support for the Flexible Box Layout model, a.k.a. Flexbox, which allows easy manipulation of layout order and sizing within a container. 1767 */ 1768 1769 Modernizr.addTest('flexbox', testAllProps('flexBasis', '1px', true)); 1770 1771 Modernizr.addTest('overflowscrolling', function() { 1772 return Modernizr.testAllProps('overflowScrolling'); 1773 }); 1774 1775 Modernizr.addTest('touchevents', function() { 1776 let bool; 1777 1778 if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { 1779 bool = true; 1780 } else { 1781 // include the 'heartz' as a way to have a non matching MQ to help terminate the join 1782 // https://git.io/vznFH 1783 const query = ['@media (', prefixes.join('touch-enabled),('), 'heartz', ')', '{#modernizr{top:9px;position:absolute}}'].join(''); 1784 1785 testStyles(query, function(node) { 1786 bool = node.offsetTop === 9; 1787 }); 1788 } 1789 1790 return bool; 1791 }); 1792 1793 /*! 1794 { 1795 "name": "Flexbox (legacy)", 1796 "property": "flexboxlegacy", 1797 "tags": ["css"], 1798 "polyfills": ["flexie"],
1799 "notes": [{ 1800 "name": "The _old_ flexbox", 1801 "href": "http://www.w3.org/TR/2009/WD-css3-flexbox-20090723/" 1802 }] 1803 } 1804 ! */ 1805 1806 Modernizr.addTest('flexboxlegacy', testAllProps('boxDirection', 'reverse', true)); 1807 1808 // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input 1809 // Modernizr.prefixed('boxSizing') // 'MozBoxSizing' 1810 1811 // Properties can be passed as DOM-style camelCase or CSS-style kebab-case. 1812 // Return values will always be in camelCase; if you want kebab-case, use Modernizr.prefixedCSS(). 1813 1814 // If you're trying to ascertain which transition end event to bind to, you might do something like... 1815 // 1816 // var transEndEventNames = { 1817 // 'WebkitTransition' : 'webkitTransitionEnd',// Saf 6, Android Browser 1818 // 'MozTransition' : 'transitionend', // only for FF < 15 1819 // 'transition' : 'transitionend' // IE10, Opera, Chrome, FF 15+, Saf 7+ 1820 // }, 1821 // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; 1822 1823 const prefixed = ModernizrProto.prefixed = function(prop, obj, elem) { 1824 if (prop.indexOf('@') === 0) { 1825 return atRule(prop); 1826 } 1827 1828 if (prop.indexOf('-') != -1) { 1829 // Convert kebab-case to camelCase 1830 prop = cssToDOM(prop); 1831 } 1832 1833 if (!obj) { 1834 return testPropsAll(prop, 'pfx'); 1835 } else { 1836 // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame' 1837 return testPropsAll(prop, obj, elem); 1838 } 1839 }; 1840 1841 /*! 1842 { 1843 "name": "IndexedDB", 1844 "property": "indexeddb", 1845 "caniuse": "indexeddb", 1846 "tags": ["storage"], 1847 "polyfills": ["indexeddb"] 1848 } 1849 ! */ 1850 /* DOC 1851 Detects support for the IndexedDB client-side storage API (final spec). 1852 */ 1853 1854 // Vendors had inconsistent prefixing with the experimental Indexed DB: 1855 // - Webkit's implementation is accessible through webkitIndexedDB 1856 // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB 1857 // For speed, we don't test the legacy (and beta-only) indexedDB 1858 1859 const indexeddb = prefixed('indexedDB', window); 1860 1861 Modernizr.addTest('indexeddb', !!indexeddb); 1862 1863 if (indexeddb) { 1864 Modernizr.addTest('indexeddb.deletedatabase', 'deleteDatabase' in indexeddb); 1865 } 1866 1867 /*! 1868 { 1869 "name": "picture Element", 1870 "property": "picture", 1871 "tags": ["elem"], 1872 "authors": ["Scott Jehl", "Mat Marquis"], 1873 "notes": [{ 1874 "name": "Specification", 1875 "href": "http://picture.responsiveimages.org" 1876 },{ 1877 "name": "Relevant spec issue", 1878 "href": "https://github.com/ResponsiveImagesCG/picture-element/issues/87" 1879 }] 1880 } 1881 ! */ 1882 1883 Modernizr.addTest('picture', 'HTMLPictureElement' in window); 1884 1885 // Run each test 1886 testRunner(); 1887 1888 // Remove the "no-js" class if it exists 1889 setClasses(classes); 1890 1891 delete ModernizrProto.addTest; 1892 delete ModernizrProto.addAsyncTest; 1893 1894 // Run the things that are supposed to run after the tests 1895 for (let i = 0; i < Modernizr._q.length; i++) { 1896 Modernizr._q[i](); 1897 } 1898 1899 // Leak Modernizr namespace 1900 window.Modernizr = Modernizr; 1901}(window, document)); 1902 1903var cookieServiceAnalytics; 1904 1905function isCookieCategorySelected(prevCategoryPreference) { 1906 return prevCategoryPreference ? 'yes' : 'no'; 1907} 1908 1909function getOptInStatus(cookieCategories, prevCategoryPreferenceCollection) { 1910 var i, 1911 optInStatusText = ''; 1912 for (i = 0; i < cookieCategories.length; i++) { 1913 optInStatusText += (i !== 0 ? '|' : '') + cookieCategories[i].id + ':' + isCookieCategorySelected(prevCategoryPreferenceCollection[cookieCategories[i].id]); 1914 } 1915 return optInStatusText; 1916} 1917 1918cookieServiceAnalytics = { 1919 getOptInStatus: getOptInStatus 1920}; 1921 1922var cookieServiceModule, 1923 namespace = 'cookie-configuration', 1924 NAMES = { 1925 cookieDisclaimer: 'guxfoe-cookiedisclaimer', 1926 cookieSlideshow: 'slideshowImagToOpen', 1927 cookieComparePv: 'compare-pv', 1928 cookieCompareCv: 'compare-cv' 1929 }, 1930 isAuthor = !!(typeof CQ !== 'undefined' && CQ?.WCM?.isEditMode?.(true)), 1931 isConfigurationAvailable = (window.cookieConfiguration?.cookieCategoriesConfiguration), 1932 configuration = isConfigurationAvailable ? window.cookieConfiguration : null,
1933 dayInMiliseconds = 86400000, 1934 defaultExpirationDate = dayInMiliseconds * 360, 1935 expireIn = configuration ? dayInMiliseconds * configuration.cookieCategoriesConfiguration.expirationDate : defaultExpirationDate, 1936 defaultCategoryPreferenceCollection, 1937 prevCategoryPreferenceCollection, 1938 cookiesByCategory, 1939 cookieGroupCategoryNames, 1940 defaultPreferenceCollection, 1941 preferenceMessageCollection = { 1942 cookiesAgree: 'cookieDisclaimer:agree', 1943 cookiesDisagree: 'cookieDisclaimer:disagree', 1944 cookiesManage: 'cookieDisclaimer:manage', 1945 cookiesDefault: 'cookiesDisclaimer:default' 1946 }; 1947 1948if (isConfigurationAvailable) { 1949 digitaldata.user = digitaldata.user || {}; 1950 defaultCategoryPreferenceCollection = getDefaultCategoryPreferences(configuration.cookieCategoriesConfiguration.categories); 1951 prevCategoryPreferenceCollection = getConfiguration() || defaultCategoryPreferenceCollection; 1952 1953 cookiesByCategory = getCookiesByCategory(configuration.cookies, configuration.cookieCategoriesConfiguration.categories); 1954 cookieGroupCategoryNames = getCookieGroupCategoryNames(configuration.cookieCategoriesConfiguration.categories); 1955 defaultPreferenceCollection = { 1956 cookiesAgree: getAgreeDefaultPreferences(configuration.cookieCategoriesConfiguration.categories), 1957 cookiesDisagree: getDisagreeDefaultPreferences(configuration.cookieCategoriesConfiguration.categories), 1958 cookiesManage: getManageDefaultPreferences(configuration.cookieCategoriesConfiguration.categories), 1959 cookiesDefault: getDefaultCategoryPreferences(configuration.cookieCategoriesConfiguration.categories) 1960 }; 1961 // we need to populate this global analytics value here
1962 digitaldata.user.optInStatus = cookieServiceAnalytics.getOptInStatus(cookieGroupCategoryNames, prevCategoryPreferenceCollection); 1963 1964 setUrlCookieCombinedParameters(); 1965} 1966 1967function getParameterByName(name, url = window.location.href) { 1968 name = name.replace(/[[\]]/g, '\\$&'); 1969 var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); 1970 var results = regex.exec(url); 1971 1972 if (!results) { 1973 return null; 1974 } 1975 1976 if (!results[2]) { 1977 return ''; 1978 } 1979 1980 return decodeURIComponent(results[2].replace(/\+/g, ' ')); 1981} 1982 1983function getCookieValue(urlParamNames) { 1984 var values = []; 1985 1986 urlParamNames.forEach(function (paramName) { 1987 var paramValue = getParameterByName(paramName); 1988 1989 if (paramValue) { 1990 values.push(`${paramName}=${paramValue}`); 1991 } 1992 }); 1993 1994 return values.join('&'); 1995} 1996 1997function setCookiesFromUrl(cookieConfig) { 1998 var cookieName = cookieConfig.cookieName; 1999 var category = cookieConfig.category; 2000 var urlParamNames = cookieConfig.urlParamNames; 2001 var expirationDays = cookieConfig.expirationDays; 2002 var cookieCategoryConfiguration = getConfiguration(); 2003 var isValidConfig = !!(cookieName || (urlParamNames?.length) || category || expirationDays); 2004 var isCookieCategoryAcceptedByClient = cookieCategoryConfiguration?.[category]; 2005 var cookieValue; 2006 2007 if (!isCookieCategoryAcceptedByClient || !isValidConfig) { 2008 return; 2009 } 2010 2011 cookieValue = getCookieValue(urlParamNames); 2012 2013 if (cookieValue) { 2014 cookieSet({ 2015 name: cookieName, 2016 value: cookieValue, 2017 domain: getCurrentDomain(), 2018 expirationDays, 2019 path: '/' 2020 }, false); 2021 } 2022} 2023 2024function setUrlCookieCombinedParameters() { 2025 if (configuration?.urlCookieCombinedParameters) { 2026 configuration.urlCookieCombinedParameters.forEach(setCookiesFromUrl); 2027 } 2028} 2029 2030function getCookieGroupCategoryNames(defaultCategoryCookieConfig) { 2031 var i, 2032 cookieCategoryName = []; 2033 for (i = 0; i < defaultCategoryCookieConfig.length; i++) { 2034 cookieCategoryName.push({ 2035 id: defaultCategoryCookieConfig[i].id, 2036 analyticsName: defaultCategoryCookieConfig[i].analyticsName 2037 }); 2038 } 2039 return cookieCategoryName; 2040} 2041 2042function getCookiesByCategoryTemplate(categoryPreferenceList) { 2043 return categoryPreferenceList.reduce(function (prefCollection, item) { 2044 prefCollection[item.id] = []; 2045 return prefCollection; 2046 }, {}); 2047} 2048 2049function getManageDefaultPreferences(categoryPreferenceList) { 2050 return categoryPreferenceList.reduce(function (prefCollection, item) { 2051 var agreeToCategory = item.defaultState !== 'MANDATORY_SELECTABLE_OFF' && item.defaultState !== 'SELECTABLE_OFF'; 2052 prefCollection[item.id] = agreeToCategory; 2053 return prefCollection; 2054 }, {}); 2055} 2056 2057function getDisagreeDefaultPreferences(categoryPreferenceList) { 2058 return categoryPreferenceList.reduce(function (prefCollection, item) { 2059 prefCollection[item.id] = item.alwaysOn; 2060 return prefCollection; 2061 }, {}); 2062} 2063 2064function getAgreeDefaultPreferences(categoryPreferenceList) { 2065 return categoryPreferenceList.reduce(function (prefCollection, item) { 2066 var agreeToCategory = item.defaultState !== 'MANDATORY_SELECTABLE_OFF'; 2067 prefCollection[item.id] = agreeToCategory; 2068 return prefCollection; 2069 }, {}); 2070} 2071 2072function getDefaultCategoryPreferences(categoryPrefList) { 2073 return categoryPrefList.reduce(function (prefCollection, item) { 2074 prefCollection[item.id] = item.enabledByDefault; 2075 return prefCollection; 2076 }, {}); 2077} 2078 2079function getCookieCategory(name, cookieCategoryList) { 2080 return cookieCategoryList.filter(function (item) { 2081 return item.name === name; 2082 })[0]; 2083} 2084 2085function getCategoryPrefDiff(newCollection, prevCollection) { 2086 return Object.keys(newCollection).filter(function (key) { 2087 return newCollection[key] !== prevCollection[key]; 2088 }); 2089} 2090 2091function getCookiesByCategory(cookieCollection, categoryPreferenceList) { 2092 var cookiesByCategoryCollection = getCookiesByCategoryTemplate(categoryPreferenceList); 2093 2094 return cookieCollection.reduce(function (cookiesByCategoryCollection, item) { 2095 cookiesByCategoryCollection[item.category].push(item.name); 2096 return cookiesByCategoryCollection; 2097 }, cookiesByCategoryCollection); 2098} 2099 2100function deepCloneCollection(collection) { 2101 return JSON.parse(JSON.stringify(collection)); 2102} 2103 2104function onPreferenceChange(newCategoryPreferenceCollection) { 2105 var updatedCategoryList = getCategoryPrefDiff(newCategoryPreferenceCollection, prevCategoryPreferenceCollection); 2106 2107 updatedCategoryList.forEach(function (category) { 2108 var isUpdatedCategoryOn = newCategoryPreferenceCollection[category]; 2109 2110 if (!isUpdatedCategoryOn) {
2111 cookiesByCategory[category].forEach(function (cookieName) { 2112 buildCookiePathsThenDelete(cookieName); 2113 }); 2114 } 2115 }); 2116 2117 prevCategoryPreferenceCollection = deepCloneCollection(newCategoryPreferenceCollection); 2118} 2119 2120function storeGet(key) { 2121 const entry = JSON.parse(localStorage.getItem(key) || '0'); 2122 2123 if (!entry) { 2124 return null; 2125 } 2126 2127 return entry.value; 2128} 2129 2130function setLocalStorageConfig(value) { 2131 localStorage.setItem(namespace, JSON.stringify({ 2132 value: value 2133 })); 2134} 2135 2136function storeSet(value) { 2137 onPreferenceChange(value); 2138 2139 cookieSet({ 2140 path: '/', 2141 expirationDays: configuration.cookieCategoriesConfiguration.expirationDate || defaultExpirationDate, 2142 name: namespace, 2143 value: JSON.stringify({ 2144 timestamp: Date.now(), 2145 value 2146 }) 2147 }); 2148 2149 setLocalStorageConfig(value); 2150} 2151 2152function setCookieValue(contextType, callback) { 2153 if (isConfigurationAvailable) { 2154 callback(preferenceMessageCollection[contextType], defaultPreferenceCollection[contextType]); 2155 storeSet(defaultPreferenceCollection[contextType]); 2156 } 2157} 2158 2159function getConfiguration() { 2160 if (!isAuthor) { 2161 const cookieConfig = cookieGet(namespace); 2162 const cookieConfigTimestamp = cookieConfig ? JSON.parse(cookieConfig).timestamp : null; 2163 const aemConfigTimestamp = configuration ? configuration.timestamp : 0 2164 const isConfigCurrent = cookieConfigTimestamp >= aemConfigTimestamp; 2165 const isConfigExistAndIsValid = !!cookieConfig && isConfigCurrent; 2166 const cookieDisclaimerTimestamp = cookieGet(NAMES.cookieDisclaimer); 2167 const isCookieDisclaimerFresh = cookieDisclaimerTimestamp ? parseInt(cookieDisclaimerTimestamp, 10) >= aemConfigTimestamp : true; 2168 2169 if (!isCookieDisclaimerFresh) { 2170 buildCookiePathsThenDelete(NAMES.cookieDisclaimer); 2171 } 2172 2173 if (!isConfigExistAndIsValid) { 2174 buildCookiePathsThenDelete(namespace); 2175 window.localStorage.removeItem(namespace); 2176 2177 return {...defaultCategoryPreferenceCollection}; 2178 } else { 2179 setLocalStorageConfig(JSON.parse(cookieConfig).value); 2180 2181 return JSON.parse(cookieConfig).value; 2182 } 2183 } 2184} 2185 2186/** 2187 * @method cookieSet - Sets a single cookie. 2188 * @param {object} - {config} - Information about the cookie you wish to set. 2189 * @param {string} - {config.name} - The name of the cookie you wish to set. 2190 * @param {string} - {config.value} - The value you wish to cookie to have. 2191 * @param {number} - {config.expirationDays} - The date you want the cookie to expire upon. 2192 * @param {string} - {config.path} - A path to a location on your site. 2193 */ 2194function cookieSet(config, encode = true) { 2195 var expirationDate = config.expirationDays ? 'expires=' + new Date(Date.now() + (dayInMiliseconds * config.expirationDays)) : '', 2196 cookiePath = config.path ? ';path=' + config.path : '', 2197 cookieDomain = config.domain ? ';domain=' + config.domain : `;domain=${getCurrentDomain()}`, 2198 cookie = config.name + '=' + config.value + ';' + expirationDate + cookieDomain + cookiePath, 2199 encodedCookie = config.name + '=' + ((encode) ? encodeURIComponent(config.value) : config.value) + ';' + expirationDate + cookieDomain + cookiePath; 2200 2201 document.cookie = encodedCookie; 2202 return cookie; 2203} 2204 2205function cookieGet(name) { 2206 var cookieName = name + '=', 2207 cookieAttributes = document.cookie.split(';'), 2208 attribute, 2209 i; 2210 2211 for (i = 0; i < cookieAttributes.length; i++) { 2212 attribute = cookieAttributes[i].trim(); 2213 2214 while (attribute.startsWith(' ')) { 2215 attribute = attribute.substring(1); 2216 } 2217 if (attribute.startsWith(cookieName)) { 2218 return decodeURIComponent(attribute.substring(cookieName.length, attribute.length)); 2219 } 2220 } 2221 return ''; 2222} 2223 2224function buildCookiePathsThenDelete(name) { 2225 var pathname = location.pathname.replace(/\/$/, ''), 2226 segments = pathname.split('/'), 2227 pathList = [], 2228 i, 2229 l = segments.length; 2230 2231 for (i = 0; i < l; i++) { 2232 const path = segments.slice(0, i + 1).join('/'); 2233 2234 pathList.push(path); // as file 2235 pathList.push(path + '/'); // as directory 2236 } 2237 2238 cookieDelete({ 2239 name: name, 2240 pathList: pathList 2241 }); 2242} 2243 2244/** 2245 * @method cookieDelete - Removes each cookie of the name passed in under the list of paths passed in. 2246 * @param {object} - {config} - Information about the cookie you wish to remove. 2247 * @param {string} - {config.name} - The name of the cookie you wish to remove. 2248 * @param {array} - {config.pathList} - An array of 1 or more paths for the cookie you wish to remove. 2249 */ 2250function cookieDelete(config) { 2251 var expires = new Date(0).toUTCString(), 2252 pathList = config.pathList ? config.pathList : ['/'], 2253 l = pathList.length, 2254 i, 2255 hostname = window.location.hostname, 2256 indexOfHostname = hostname.indexOf('.ford'), 2257 additionalDomain = hostname.substr(indexOfHostname); 2258 2259 // expire null-path cookies as well 2260 document.cookie = config.name + '=; expires=' + expires; 2261 2262 for (i = 0; i < l; i++) { 2263 document.cookie = config.name + '=; path=' + pathList[i] + '; expires=' + expires; 2264 document.cookie = config.name + '=; path=' + pathList[i] + '; expires=' + expires + '; domain=' + additionalDomain + ''; 2265 } 2266} 2267 2268/** 2269 * @method validateCookieCategory - A gate to check whether a cookie is allowed to be set or not based on the users settings. 2270 * @param {object} - {config} - Information about the cookie. 2271 * @param {string} - {config.name} - The name of the cookie you wish to check. 2272 * EVERYTHING BEYOND HERE IS PASSED TO THE cookieSet() 2273 * @param {string} - {config.value} - The value you wish to cookie to have. 2274 * @param {number} - {config.expirationDays} - The date you want the cookie to expire upon. 2275 * @param {string} - {config.path} - A path to a location on your site. 2276 */ 2277function validateCookieCategory(config) { 2278 var isCategoryPermitted = getConfiguration() || defaultCategoryPreferenceCollection, 2279 cookieCategory = getCookieCategory(config.name, configuration.cookies), 2280 cookieCategoryName = cookieCategory ? cookieCategory.category : null; 2281 2282 if (isCategoryPermitted[cookieCategoryName] || cookieCategoryName === null) { 2283 return cookieSet(config); 2284 } 2285} 2286 2287function getCurrentDomain() { 2288 const domain = 'ford.'; 2289 const url = window.location.hostname.replace(/(https?:\/\/)?(www.)?/i, ''); 2290 const prefix = url => (url.indexOf('.') > -1 ? '.' : ''); 2291 const result = (url?.indexOf(domain)) > -1 ? url.slice(url.indexOf(domain)) : url; 2292 2293 return `${prefix(result)}${result}`; 2294} 2295 2296cookieServiceModule = { 2297 namespace, 2298 NAMES, 2299 configuredExpirationDate: isConfigurationAvailable ? configuration.cookieCategoriesConfiguration.expirationDate : 360, 2300 store: { 2301 get: storeGet, 2302 set: storeSet, 2303 config: getConfiguration(), 2304 getConfig: getConfiguration, 2305 getGroupCategoryNames: cookieGroupCategoryNames, 2306 defaultCategoryPreferenceCollection,
2307 setCookieValue 2308 }, 2309 cookie: { 2310 get: cookieGet, 2311 set: isConfigurationAvailable ? validateCookieCategory : cookieSet, 2312 remove: cookieDelete 2313 }, 2314 getCookieCategoryByCookieName(name) { 2315 if (cookiesByCategory) { 2316 return Object.keys(cookiesByCategory).find(category => 2317 cookiesByCategory[category].find(items => items.includes(name))); 2318 } 2319 }, 2320 setUrlCookieCombinedParameters 2321}; 2322
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.