Socket
Socket
Sign inDemoInstall

coach-core

Package Overview
Dependencies
Maintainers
1
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

coach-core - npm Package Compare versions

Comparing version 5.1.1 to 6.0.0-alpha.1

lib/dom/.DS_Store

25

CHANGELOG.md
# CHANGELOG - coach-core
## 6.0.0 - UNRELEASED
### Added
* Added Element Timings, Paint Timings and Largest Contentful Paint [#16](https://github.com/sitespeedio/coach-core/pull/16).
* Added CLS advice [#18](https://github.com/sitespeedio/coach-core/pull/18).
* Added Long Task advice [#17](https://github.com/sitespeedio/coach-core/pull/17).
* Added support for HTTP3 [#26](https://github.com/sitespeedio/coach-core/pull/26).
* New info section where we share info third party statistics from third party web [#29](https://github.com/sitespeedio/coach-core/pull/29).
* New technology section with where Wappalyzer is used to get info [#28](https://github.com/sitespeedio/coach-core/pull/28).
### Changed
* Remove RUM Speed Index [#12](https://github.com/sitespeedio/coach-core/pull/12).
* Remove First Paint and timings calculated from the Navigation Timing API [#15](https://github.com/sitespeedio/coach-core/pull/15).
* Removed the advice for PUSH [#26](https://github.com/sitespeedio/coach-core/pull/26).
* Removed the accesibilty advice [#32](https://github.com/sitespeedio/coach-core/pull/32). If you are a sitespeed.io use `--axe`. It's better to use AXE-core that gives better advice than the old coach advice.
### Tech
* Use const/let instead of var [#24](https://github.com/sitespeedio/coach-core/pull/24) and [#25](https://github.com/sitespeedio/coach-core/pull/25).
* Expose PageXray and third-party-web in the API [#23](https://github.com/sitespeedio/coach-core/pull/23).
* Updated dev dependencies.
### Fixed
* Testing for JQuery removed the $ reference on the page [#22](https://github.com/sitespeedio/coach-core/pull/22).
## 5.1.1 - 2020-08-18

@@ -4,0 +29,0 @@ ### Fixed

2

dist/coach.min.js

@@ -1,1 +0,1 @@

(function(){if("undefined"!=typeof window){"use strict";var a={getAbsoluteURL:function(e){var t=window.document.createElement("a");return t.href=e,t.href},getHostname:function(e){var t=window.document.createElement("a");return t.href=e,t.hostname},exists:function(t,e){return e.some(function(e){return e===t})},caseInsensitiveAttributeValueFilter:function(t,n){return function(e){if((e.getAttribute(t)||"").toLowerCase()===n.toLowerCase())return e}},isHTTP2:function(){var e=a.getConnectionType().toLowerCase();return"h2"===e||e.startsWith("spdy")},getConnectionType:function(){if(window.performance.getEntriesByType("navigation")&&window.performance.getEntriesByType("navigation")[0]&&window.performance.getEntriesByType("navigation")[0].nextHopProtocol)return window.performance.getEntriesByType("navigation")[0].nextHopProtocol;if(window.performance&&window.performance.getEntriesByType&&window.performance.getEntriesByType("resource")){var e=window.performance.getEntriesByType("resource");if(1<e.length&&e[0].nextHopProtocol)for(var t=document.domain,n=0,r=e.length;n<r;n++)if(t===a.getHostname(e[n].name))return e[n].nextHopProtocol}return"unknown"},getSynchJSFiles:function(e){return Array.prototype.slice.call(e.getElementsByTagName("script")).filter(function(e){return!e.async&&e.src&&!e.defer}).map(function(e){return a.getAbsoluteURL(e.src)})},getAsynchJSFiles:function(e){return Array.prototype.slice.call(e.getElementsByTagName("script")).filter(function(e){return e.async&&e.src}).map(function(e){return a.getAbsoluteURL(e.src)})},getResourceHintsHrefs:function(t){return Array.prototype.slice.call(window.document.head.getElementsByTagName("link")).filter(function(e){return e.rel===t}).map(function(e){return e.href})},getCSSFiles:function(e){return Array.prototype.slice.call(e.getElementsByTagName("link")).filter(function(e){return"stylesheet"===e.rel&&!e.href.startsWith("data:")}).map(function(e){return a.getAbsoluteURL(e.href)})},plural:function(e){return 1!==e?"s":""},getTransferSize:function(e){var t=window.performance.getEntriesByName(e,"resource");return 1===t.length&&"number"==typeof t[0].transferSize?t[0].transferSize:0}};return function(i){var e={},t={},n={},r={};try{n.altImages=function(e){"use strict";var t=Array.prototype.slice.call(window.document.getElementsByTagName("img")),n=0,r=[],a=0,i=0,o="",s={};return t.forEach(function(e){e.alt&&""!==e.alt||"presentation"===e.getAttribute("role")&&e.getAttribute("role")?e.alt&&125<e.alt.length&&(n+=5,r.push(e.src),i++):(n+=10,a++,e.src&&(r.push(e.src),s[e.src]=1))}),0<a&&(o="The page has "+a+" image"+e.plural(a)+" that lack alt attribute(s) and "+Object.keys(s).length+" of them are unique."),0<i&&(o+="The page has "+i+" image"+e.plural(i)+" where the alt text are too long (longer than 125 characters)."),{id:"altImages",title:"Always use an alt attribute on image tags",description:"All img tags require an alt attribute. This goes without exception. Everything else is an error. If you have an img tag in your HTML without an alt attribute, add it now. https://www.marcozehe.de/2015/12/14/the-web-accessibility-basics/",advice:o,score:Math.max(0,100-n),weight:5,offending:r,tags:["accessibility","images"]}}(i)}catch(e){r.altImages=e.message}try{n.headings=function(){"use strict";var e=["h6","h5","h4","h3","h2","h1"],n=0,t=0,r="";if(e.forEach(function(e){t+=Array.prototype.slice.call(window.document.getElementsByTagName(e)).length}),0===t)n=100,r="The page is missing headings. Use them to get a better structure of your content.";else{var a=!1,i=[];e.forEach(function(e){var t=Array.prototype.slice.call(window.document.getElementsByTagName(e));a&&0===t.length&&(n+=10,i.push("The page is missing a "+e+" and has heading(s) with lower priority.")),0<t.length&&(a=!0)}),r=i.join(" ")}return{id:"headings",title:"Use heading tags to structure your page",description:"Headings give your document a logical, easy to follow structure. Have you ever wondered how Wikipedia puts together its table of contents for each article? They use the logical heading structure for that, too! The H1 through H6 elements are unambiguous in telling screen readers, search engines and other technologies what the structure of your document is. https://www.marcozehe.de/2015/12/14/the-web-accessibility-basics/",advice:r,score:Math.max(0,100-n),weight:4,offending:[],tags:["accessibility","html"]}}()}catch(e){r.headings=e.message}try{n.labelOnInput=function(){"use strict";var t=Array.prototype.slice.call(window.document.getElementsByTagName("label")),n=0,r=[],e=Array.prototype.slice.call(window.document.querySelectorAll("input, textarea, select")),a=["button","hidden","image","reset","submit"];return e.forEach(function(e){(function(e,t){return t.includes(e.type)})(e,a)||function(e){return"LABEL"===e.parentElement.nodeName}(e)||function(e,t){return e.id&&0<function(t,e){return e.filter(function(e){return e.attributes.for&&e.attributes.for.value===t})}(e.id,t).length}(e,t)||(r.push(e.id||e.name||e.outerHTML),n+=10)}),{id:"labelOnInput",title:"Always set labels on inputs in forms",description:"Most input elements, as well as the select and textarea elements, need an associated label element that states their purpose. The only exception is those that produce a button, like the reset and submit buttons do. Others, be it text, checkbox, password, radio (button), search etc. require a label element to be present. https://www.marcozehe.de/2015/12/14/the-web-accessibility-basics/",advice:0<n?"There are "+n/10+" input(s) that are missing labels on a form.":"",score:Math.max(0,100-n),weight:3,offending:r,tags:["accessibility","form"]}}()}catch(e){r.labelOnInput=e.message}try{n.landmarks=function(){"use strict";var t=0;return["article","aside","footer","header","nav","main"].forEach(function(e){t+=Array.prototype.slice.call(window.document.getElementsByTagName(e)).length}),{id:"landmarks",title:"Structure your content by using landmarks",description:"Landmarks can be article, aside, footer, header, nav or main tag. Adding such landmarks appropriately can help further provide sense to your document and help users more easily navigate it. https://www.marcozehe.de/2015/12/14/the-web-accessibility-basics/",advice:0===t?"The page doesn't use any landmarks.":"",score:0<t?100:0,weight:5,offending:[],tags:["accessibility","html"]}}()}catch(e){r.landmarks=e.message}try{n.neverSuppressZoom=function(e){"use strict";var t=Array.prototype.slice.call(document.querySelectorAll("meta[name][content]"));t=t.filter(e.caseInsensitiveAttributeValueFilter("name","viewport"));var n=100,r=[];return t.forEach(function(e){(-1<e.content.indexOf("user-scalable=no")||-1<e.content.indexOf("initial-scale=1.0; maximum-scale=1.0"))&&(n=0,r.push(e.content))}),{id:"neverSuppressZoom",title:"Don't suppress pinch zoom",description:"A key feature of mobile browsing is being able to zoom in to read content and out to locate content within a page. http://www.iheni.com/mobile-accessibility-tip-dont-suppress-pinch-zoom/",advice:0===n?"What! The page suppresses zooming, you really shouldn't do that.":"",score:n,weight:8,offending:r,tags:["accessibility"]}}(i)}catch(e){r.neverSuppressZoom=e.message}try{n.sections=function(){"use strict";var e=["h6","h5","h4","h3","h2","h1"],r=0,t="",n=Array.prototype.slice.call(window.document.getElementsByTagName("section"));return 0===n.length?(t="The page doesn't use sections. You could use them to get a better structure of your content.",r=100):(n.forEach(function(t){var n=!1;e.forEach(function(e){0<Array.prototype.slice.call(t.getElementsByTagName(e)).length&&(n=!0)}),n||(r+=10)}),0<r&&(t="The page is missing heading(s) within a section tag on the page. It happens "+r/10+" times.")),{id:"sections",title:"Use headings tags within section tags to better structure your page",description:"Section tags should have at least one heading element as a direct descendant.",advice:t,score:Math.max(0,100-r),weight:0,offending:[],tags:["accessibility","html"]}}()}catch(e){r.sections=e.message}try{n.table=function(){"use strict";var e=Array.prototype.slice.call(window.document.getElementsByTagName("table")),n=0,r=[];return e.forEach(function(e){0===e.getElementsByTagName("caption").length&&(n+=5,e.id&&r.push(e.id));var t=e.getElementsByTagName("tr");t[0]&&0===t[0].getElementsByTagName("th").length&&(n+=5,e.id&&r.push(e.id))}),{id:"table",title:"Use caption and th in tables",description:"Add a caption element to give the table a proper heading or summary. Use th elements to denote column and row headings. Make use of their scope and other attributes to clearly associate what belongs to which. https://www.marcozehe.de/2015/12/14/the-web-accessibility-basics/",advice:0<n?"The page has tables that are missing caption, please use them to give them a proper heading or summary.":"",score:Math.max(0,100-n),weight:5,offending:r,tags:["accessibility","html"]}}()}catch(e){r.table=e.message}e.accessibility={adviceList:n},0<Object.keys(r).length&&(t.accessibility=r);var a={},o={};try{a.charset=function(){"use strict";var e=100,t="",n=document.characterSet;return null===n?(t="The page is missing a character set. If you use Chrome/Firefox we know you are missing it, if you use another browser, it could be an implementation problem.",e=0):"UTF-8"!==n&&(t="You are not using charset UTF-8?",e=50),{id:"charset",title:"Declare a charset in your document",description:"The Unicode Standard (UTF-8) covers (almost) all the characters, punctuations, and symbols in the world. Please use that.",advice:t,score:e,weight:2,offending:[],tags:["bestpractice"]}}()}catch(e){o.charset=e.message}try{a.doctype=function(){"use strict";var e=100,t="",n=document.doctype;return null===n?(t="The page is missing a doctype. Please use <!DOCTYPE html>.",e=0):("html"!==n.name.toLowerCase()||""!==n.systemId&&"about:legacy-compat"!==n.systemId.toLowerCase())&&(t="Just do yourself a favor and use the HTML5 doctype declaration: <!DOCTYPE html>",e=25),{id:"doctype",title:"Declare a doctype in your document",description:"The <!DOCTYPE> declaration is not an HTML tag; it is an instruction to the web browser about what version of HTML the page is written in.",advice:t,score:e,weight:2,offending:[],tags:["bestpractice"]}}()}catch(e){o.doctype=e.message}try{a.language=function(){"use strict";var e=document.getElementsByTagName("html"),t=100,n=e[0].getAttribute("lang"),r="";return 0<e.length?null===n&&(t=0,r='The page is missing a language definition in the HTML tag. Define it with <html lang="YOUR_LANGUAGE_CODE">'):(t=0,r="What! The page is missing the HTML tag!"),{id:"language",title:"Declare the language code for your document",description:"According to the W3C recommendation you should declare the primary language for each Web page with the lang attribute inside the <html> tag https://www.w3.org/International/questions/qa-html-language-declarations#basics.",advice:r,score:t,weight:3,offending:[],tags:["bestpractice"]}}()}catch(e){o.language=e.message}try{a.metaDescription=function(e){"use strict";var t=100,n="",r=Array.prototype.slice.call(document.querySelectorAll("meta[name][content]")),a=0<(r=r.filter(e.caseInsensitiveAttributeValueFilter("name","description"))).length?r[0].getAttribute("content"):"";return 0===a.length?(n="The page is missing a meta description.",t=0):155<a.length&&(n="The meta description is too long. It has "+a.length+" characters, the recommended max is 155",t=50),{id:"metaDescription",title:"Meta description",description:"Use a page description to make the page more relevant to search engines.",advice:n,score:t,weight:5,offending:[],tags:["bestpractice"]}}(i)}catch(e){o.metaDescription=e.message}try{a.optimizely=function(t){"use strict";var n=100,e=t.getSynchJSFiles(document.head),r="",a=[];return e.forEach(function(e){"cdn.optimizely.com"===t.getHostname(e)&&(a.push(e),n=0,r="The page is using Optimizely. Use it with care because it hurts your performance. Only turn it on (= load the JavaScript) when you run your A/B tests. Then when you are finished make sure to turn it off.")}),{id:"optimizely",title:"Only use Optimizely when you need it",description:"Use Optimizely with care because it hurts your performance since Javascript is loaded synchronously inside of the head tag, making the first paint happen later. Only turn on Optimzely (= load the javascript) when you run your A/B tests.",advice:r,score:n,weight:2,offending:a,tags:["bestpractice"]}}(i)}catch(e){o.optimizely=e.message}try{a.pageTitle=function(){"use strict";var e=100,t="",n=document.title;return 0===n.length?(t="The page is missing a title.",e=0):60<n.length&&(t="The title is too long by "+(n.length-60)+" characters. The recommended max is 60",e=50),{id:"pageTitle",title:"Page title",description:"Use a title to make the page more relevant to search engines.",advice:t,score:e,weight:5,offending:[],tags:["bestpractice"]}}()}catch(e){o.pageTitle=e.message}try{a.spdy=function(){"use strict";var e=100,t="";return-1!==i.getConnectionType().indexOf("spdy")&&(e=0,t="The page is using SPDY. Chrome dropped support for SPDY in Chrome 51. Change to HTTP/2 asap."),{id:"spdy",title:"EOL for SPDY in Chrome",description:"Chrome dropped supports for SPDY in Chrome 51, upgrade to HTTP/2 as soon as possible. The page has more users (browsers) supporting HTTP/2 than supports SPDY.",advice:t,score:e,weight:1,offending:[],tags:["bestpractice"]}}()}catch(e){o.spdy=e.message}try{a.url=function(){"use strict";var e=100,t="",n=document.URL;return-1<n.indexOf("?")&&n.indexOf("jsessionid")>n.indexOf("?")&&(e=0,t="The page has the session id for the user as a parameter, please change so the session handling is done only with cookies. "),1<(n.match(/&/g)||[]).length&&(e-=50,t+="The page is using more than two request parameters. You should really rethink and try to minimize the number of parameters. "),100<n.length&&(e-=10,t+="The URL is "+n.length+" characters long. Try to make it less than 100 characters. "),(-1<n.indexOf(" ")||-1<n.indexOf("%20"))&&(e-=10,t+="Could the developer or the CMS be on Windows? Avoid using spaces in the URLs, use hyphens or underscores. "),{id:"url",title:"Have a good URL format",description:"A clean URL is good for the user and for SEO. Make them human readable, avoid too long URLs, spaces in the URL, too many request parameters, and never ever have the session id in your URL.",advice:t,score:e<0?0:e,weight:2,offending:[],tags:["bestpractice"]}}()}catch(e){o.url=e.message}e.bestpractice={adviceList:a},0<Object.keys(o).length&&(t.bestpractice=o);var s={},c={};try{s.amp=function(){"use strict";var e=document.getElementsByTagName("html")[0];return!!(e&&e.getAttribute("amp-version")||window.AMP)&&(e.getAttribute("amp-version")||!0)}()}catch(e){c.amp=e.message}try{s.browser=function(){"use strict";var e="unknown",t=window.navigator.userAgent.match(/(Chrome|Firefox)\/(\S+)/);return e=t?t[1]+" "+t[2]:e}()}catch(e){c.browser=e.message}try{s.connectionType=function(){"use strict";return i.getConnectionType()}()}catch(e){c.connectionType=e.message}try{s.documentHeight=function(){"use strict";return Math.max(document.body.scrollHeight,document.body.offsetHeight,document.documentElement.clientHeight,document.documentElement.scrollHeight,document.documentElement.offsetHeight)}()}catch(e){c.documentHeight=e.message}try{s.documentTitle=function(){"use strict";return document.title}()}catch(e){c.documentTitle=e.message}try{s.documentWidth=function(){"use strict";return Math.max(document.body.scrollWidth,document.body.offsetWidth,document.documentElement.clientWidth,document.documentElement.scrollWidth,document.documentElement.offsetWidth)}()}catch(e){c.documentWidth=e.message}try{s.domDepth=function(){"use strict";function o(e){var t=0;if(e.parentNode)for(;e=e.parentNode;)t++;return t}var e=function(e){for(var t=e.getElementsByTagName("*"),n=t.length,r=0,a=0;n--;){var i=o(t[n]);a<i&&(a=i),r+=i}return{avg:r/t.length,max:a}}(document);return{avg:Math.round(e.avg),max:e.max}}()}catch(e){c.domDepth=e.message}try{s.domElements=function(){"use strict";return document.getElementsByTagName("*").length}()}catch(e){c.domElements=e.message}try{s.generator=function(){"use strict";var e=document.querySelector('meta[name="generator"]');if(e)return e.getAttribute("content")}()}catch(e){c.generator=e.message}try{s.head=function(e){"use strict";return{jssync:e.getSynchJSFiles(document.head),jsasync:e.getAsynchJSFiles(document.head),css:e.getCSSFiles(document.head)}}(i)}catch(e){c.head=e.message}try{s.iframes=function(){"use strict";return document.getElementsByTagName("iframe").length}()}catch(e){c.iframes=e.message}try{s.jsframework=function(){"use strict";return{angular:!!window.angular&&window.angular.version.full,backbone:!!window.Backbone&&window.Backbone.VERSION,preact:!!window.preact,vue:!!window.Vue}}()}catch(e){c.jsframework=e.message}try{s.localStorageSize=function(){"use strict";return function(e){if(e){for(var t=e.length||Object.keys(e).length,n=0,r=0;r<t;r++){var a=e.key(r),i=e.getItem(a);n+=a.length+i.length}return n}return 0}(window.localStorage)}()}catch(e){c.localStorageSize=e.message}try{s.metaDescription=function(){"use strict";var e=document.querySelector('meta[name="description"]'),t=document.querySelector('meta[property="og:description"]');return e?e.getAttribute("content"):t?t.getAttribute("content"):""}()}catch(e){c.metaDescription=e.message}try{s.networkConnectionType=function(){"use strict";return window.navigator.connection?window.navigator.connection.effectiveType:"unknown"}()}catch(e){c.networkConnectionType=e.message}try{s.resourceHints=function(e){"use strict";return{"dns-prefetch":e.getResourceHintsHrefs("dns-prefetch"),preconnect:e.getResourceHintsHrefs("preconnect"),prefetch:e.getResourceHintsHrefs("prefetch"),prerender:e.getResourceHintsHrefs("prerender")}}(i)}catch(e){c.resourceHints=e.message}try{s.responsive=function(){"use strict";var e=!0,t=document.body.scrollWidth,n=window.innerWidth,r=document.body.children;for(var a in n<t&&(e=!1),r)r[a].scrollWidth>n&&(e=!1);return e}()}catch(e){c.responsive=e.message}try{s.scripts=function(){"use strict";return document.getElementsByTagName("script").length}()}catch(e){c.scripts=e.message}try{s.serializedDomSize=function(){"use strict";return document.body.innerHTML.length}()}catch(e){c.serializedDomSize=e.message}try{s.serviceWorker=function(){"use strict";return"serviceWorker"in navigator&&(!!navigator.serviceWorker.controller&&("activated"===navigator.serviceWorker.controller.state&&navigator.serviceWorker.controller.scriptURL))}()}catch(e){c.serviceWorker=e.message}try{s.sessionStorageSize=function(){"use strict";return function(e){for(var t=e.length||Object.keys(e).length,n=0,r=0;r<t;r++){var a=e.key(r),i=e.getItem(a);n+=a.length+i.length}return n}(window.sessionStorage)}()}catch(e){c.sessionStorageSize=e.message}try{s.thirdparty=function(){"use strict";return{boomerang:!!window.BOOMR&&window.BOOMR.version,facebook:!!window.FB,gtm:!!window.google_tag_manager,ga:!!window.ga,jquery:!!window.jQuery&&window.jQuery.fn.jquery,newrelic:!!window.newrelic,matomo:!!window.Piwik||!!window.Matomo}}()}catch(e){c.thirdparty=e.message}try{s.userTiming=function(){"use strict";var e=0,t=0;return window.performance&&window.performance.getEntriesByType&&(t=window.performance.getEntriesByType("measure").length,e=window.performance.getEntriesByType("mark").length),{marks:e,measures:t}}()}catch(e){c.userTiming=e.message}try{s.windowSize=function(){"use strict";return(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)+"x"+(window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight)}()}catch(e){c.windowSize=e.message}e.info=s,0<Object.keys(c).length&&(t.info=c);var u={},h={};try{u.avoidScalingImages=function(e){"use strict";for(var t="",n=0,r=[],a=Array.prototype.slice.call(document.getElementsByTagName("img")),i=0,o=a.length;i<o;i++){var s=a[i];s.clientWidth+100<s.naturalWidth&&0<s.clientWidth&&(r.push(e.getAbsoluteURL(s.currentSrc)),n+=10)}return 0<n&&(t="The page has "+n/10+" image(s) that are scaled more than 100 pixels. It would be better if those images are sent so the browser don't need to scale them."),{id:"avoidScalingImages",title:"Don't scale images in the browser",description:"It's easy to scale images in the browser and make sure they look good in different devices, however that is bad for performance! Scaling images in the browser takes extra CPU time and will hurt performance on mobile. And the user will download extra kilobytes (sometimes megabytes) of data that could be avoided. Don't do that, make sure you create multiple version of the same image server-side and serve the appropriate one.",advice:t,score:Math.max(0,100-n),weight:5,offending:r,tags:["performance","image"]}}(i)}catch(e){h.avoidScalingImages=e.message}try{u.cssPrint=function(e){"use strict";for(var t=[],n=document.getElementsByTagName("link"),r=0,a=n.length;r<a;r++)"print"===n[r].media&&t.push(e.getAbsoluteURL(n[r].href));var i=10*t.length;return{id:"cssPrint",title:"Do not load specific print stylesheets.",description:"Loading a specific stylesheet for printing slows down the page, even though it is not used. You can include the print styles inside your other CSS file(s) just by using an @media query targeting type print.",advice:0<t.length?"The page has "+t.length+" print stylesheets. You should include that stylesheet using @media type print instead.":"",score:Math.max(0,100-i),weight:1,offending:t,tags:["performance","css"]}}(i)}catch(e){h.cssPrint=e.message}try{u.fastRender=function(n){"use strict";var t="",r=0,a=[],e=n.getCSSFiles(document.head),i=n.getSynchJSFiles(document.head),o=document.domain,s=[],c=0,u=0,h=n.getResourceHintsHrefs("preconnect").map(function(e){return n.getHostname(e)});function d(e){var t=n.getHostname(e);t!==o?(a.push(e),n.exists(t,s)||(r+=n.exists(t,h)?5:10,s.push(t))):a.push(e),r+=5}return n.isHTTP2()?(0<e.length&&(t="Maybe you could server push your CSS resources for faster rendering but that is actually really hard to get right :(. ",e.forEach(function(e){14500<n.getTransferSize(e)&&(a.push(e),r+=5,c++,t+="The style "+e+" is larger than the magic number TCP window size 14.5 kB. Make the file smaller and the page will render faster. ")})),0<i.length&&(r+=10*i.length,i.forEach(function(e){a.push(e),u++}),t+="Avoid loading synchronously JavaScript inside of head, you shouldn't need JavaScript to render your page! ")):(e.forEach(function(e){d(e)}),c=e.length,i.forEach(function(e){d(e)}),u=i.length),0<a.length&&(t+="The page has "+c+" render blocking CSS request(s) and "+u+" blocking JavaScript request(s) inside of head."),{id:"fastRender",title:"Avoid slowing down the critical rendering path",description:"The critical rendering path is what the browser needs to do to start rendering the page. Every file requested inside of the head element will postpone the rendering of the page, because the browser need to do the request. Avoid loading JavaScript synchronously inside of the head (you should not need JavaScript to render the page), request files from the same domain as the main document (to avoid DNS lookups) and inline CSS or use server push for really fast rendering and a short rendering path.",advice:t,score:Math.max(0,100-r),weight:10,offending:a,tags:["performance"]}}(i)}catch(e){h.fastRender=e.message}try{u.googletagmanager=function(){"use strict";var e=100;return window.google_tag_manager&&(e=0),{id:"googletagmanager",title:"Avoid using Google Tag Manager",description:"Google Tag Manager makes it possible for non tech users to add scripts to your page that will downgrade performance.",advice:0===e?"The page is using Google Tag Manager, this is a performance risk since non-tech users can add JavaScript to your page.":"",score:e,weight:5,offending:[],tags:["performance","js"]}}()}catch(e){h.googletagmanager=e.message}try{u.inlineCss=function(){"use strict";var e="",t=0,n=[],r=i.getCSSFiles(document.head),a=Array.prototype.slice.call(window.document.head.getElementsByTagName("style"));return i.isHTTP2()&&0<r.length&&0<a.length?(t+=5,e="The page has both inline CSS and CSS requests even though it uses a HTTP/2-ish connection. If you have many users on slow connections, it can be better to only inline the CSS. Run your own tests and check the waterfall graph to see what happens."):i.isHTTP2()&&0<a.length&&0===r.length?e+="The page has inline CSS and uses HTTP/2. Do you have a lot of users with slow connections on the site? It is good to inline CSS when using HTTP/2. If not, and your server supports server push, you can probably push the CSS files instead.":i.isHTTP2()&&0<r.length&&(e+="It is always faster for the user if you inline CSS instead of making a CSS request."),i.isHTTP2()||(0<r.length&&0===a.length&&(t+=10*r.length,e="The page loads "+r.length+" CSS request(s) inside of head, try to inline the CSS for the first render and lazy load the rest.",n.push.apply(n,r)),0<a.length&&0<r.length&&(t+=10,e+="The page has both inline styles as well as it is requesting "+r.length+" CSS file(s) inside of the head. Let's only inline CSS for really fast render.",n.push.apply(n,r))),{id:"inlineCss",title:"Inline CSS for faster first render",description:"In the early days of the Internet, inlining CSS was one of the ugliest things you can do. That has changed if you want your page to start rendering fast for your user. Always inline the critical CSS when you use HTTP/1 and HTTP/2 (avoid doing CSS requests that block rendering) and lazy load and cache the rest of the CSS. It is a little more complicated when using HTTP/2. Does your server support HTTP push? Then maybe that can help. Do you have a lot of users on a slow connection and are serving large chunks of HTML? Then it could be better to use the inline technique, becasue some servers always prioritize HTML content over CSS so the user needs to download the HTML first, before the CSS is downloaded.",advice:e,score:Math.max(0,100-t),weight:7,offending:n,tags:["performance","css"]}}()}catch(e){h.inlineCss=e.message}try{u.jquery=function(){"use strict";var e=[];if("function"==typeof window.jQuery){e.push(window.jQuery.fn.jquery);for(var t=window.jQuery;t.fn&&t.fn.jquery&&(t=window.jQuery.noConflict(!0),window.jQuery&&window.jQuery.fn)&&t.fn.jquery!==window.jQuery.fn.jquery;)e.push(window.jQuery.fn.jquery)}return{id:"jquery",title:"Avoid using more than one jQuery version per page",description:"There are sites out there that use multiple versions of jQuery on the same page. You shouldn't do that because the user will then unnecessarily download extra data. Cleanup the code and make sure you only use one version.",advice:1<e.length?"The page uses "+e.length+" versions of jQuery! You only need one version, please remove the unnecessary version(s).":"",score:1<e.length?0:100,weight:4,offending:e,tags:["jQuery","performance"]}}()}catch(e){h.jquery=e.message}try{u.spof=function(n){"use strict";var r=0,a=[],i=[],o=document.domain;return n.getCSSFiles(document.head).forEach(function(e){var t=n.getHostname(e);t!==o&&(a.push(e),-1===i.indexOf(t)&&(i.push(t),r+=10))}),n.getSynchJSFiles(document.head).forEach(function(e){var t=n.getHostname(e);t!==o&&(a.push(e),-1===i.indexOf(t)&&(i.push(t),r+=10))}),{id:"spof",title:"Avoid Frontend single point of failures",description:"A page can be stopped from loading in the browser if a single JavaScript, CSS, and in some cases a font, couldn't be fetched or is loading really slowly (the white screen of death). That is a scenario you really want to avoid. Never load 3rd-party components synchronously inside of the head tag.",advice:0<a.length?"The page has "+a.length+" requests inside of the head that can cause a SPOF (single point of failure). Load them asynchronously or move them outside of the document head.":"",score:Math.max(0,100-r),weight:7,offending:a,tags:["performance","css","js"]}}(i)}catch(e){h.spof=e.message}try{u.thirdPartyAsyncJs=function(a){"use strict";var i=["ajax.googleapis.com","apis.google.com",".google-analytics.com","connect.facebook.net","platform.twitter.com","code.jquery.com","platform.linkedin.com",".disqus.com","assets.pinterest.com","widgets.digg.com",".addthis.com","code.jquery.com","ad.doubleclick.net",".lognormal.com","embed.spotify.com"];function e(e){for(var t=a.getHostname(e),n=0,r=i.length;n<r;n++)if(new RegExp(i[n]).test(t))return!0;return!1}for(var t=0,n=[],r=a.getSynchJSFiles(document),o=0,s=r.length;o<s;o++)e(r[o])&&(n.push(r[o]),t+=10);return{id:"thirdPartyAsyncJs",title:"Always load third-party JavaScript asynchronously",description:"Use JavaScript snippets that load the JS files asynchronously in order to speed up the user experience and avoid blocking the initial load.",advice:0<n.length?"The page has "+n.length+" synchronous 3rd-party JavaScript request(s). Change it to be asynchronous instead.":"",score:Math.max(0,100-t),weight:5,offending:n,tags:["performance","js"]}}(i)}catch(e){h.thirdPartyAsyncJs=e.message}e.performance={adviceList:u},0<Object.keys(h).length&&(t.performance=h);var d={},g={};try{d.ampPrivacy=function(){"use strict";var e=100,t=document.getElementsByTagName("html")[0];return(t&&t.getAttribute("amp-version")||window.AMP)&&(e=0),{id:"ampPrivacy",title:"Avoid including AMP",description:"You share share private user information with Google that your user hasn't agreed on sharing.",advice:0===e?"The page is using AMP, that makes you share private user information with Google.":"",score:e,weight:8,offending:[],tags:["privacy"]}}()}catch(e){g.ampPrivacy=e.message}try{d.facebook=function(){"use strict";var e=100;return window.FB&&(e=0),{id:"facebook",title:"Avoid including Facebook",description:"You share share private user information with Facebook that your user hasn't agreed on sharing.",advice:0===e?"The page gets content from Facebook. That means you share your users private information with Facebook.":"",score:e,weight:8,offending:[],tags:["privacy"]}}()}catch(e){g.facebook=e.message}try{d.ga=function(){"use strict";var e=100;return window.ga&&window.ga.create&&(e=0),{id:"ga",title:"Avoid using Google Analytics",description:"Google Analytics share private user information with Google that your user hasn't agreed on sharing.",advice:0===e?"The page is using Google Analytics meaning you share your users private information with Google. You should use analytics that care about user privacy, something like https://matomo.org.":"",score:e,weight:8,offending:[],tags:["privacy"]}}()}catch(e){g.ga=e.message}try{d.https=function(){"use strict";var e=100,t="";return-1===document.URL.indexOf("https://")&&(e=0,t="What!! The page is not using HTTPS. Every unencrypted HTTP request reveals information about user’s behavior, read more about it at https://https.cio.gov/everything/. You can get a totally free SSL/TLS certificate from https://letsencrypt.org/."),{id:"https",title:"Serve your content securely",description:"A page should always use HTTPS (https://https.cio.gov/everything/). You also need that for HTTP/2. You can get your free SSL/TLC certificate from https://letsencrypt.org/.",advice:t,score:e,weight:10,offending:[],tags:["privacy"]}}()}catch(e){g.https=e.message}try{d.surveillance=function(){"use strict";for(var e=100,t=document.domain,n=[],r=[".google.","facebook.com","youtube.","yahoo.com"],a=0;a<r.length;a++)-1<t.indexOf(r[a])&&(e=0,n.push(t));return{id:"surveillance",title:"Avoid using surveillance web sites",description:"Do not use web sites that harvest private user information and sell it to other companies.",advice:0===e?t+" uses harvest user information and sell it to other companies without the users agreement. That is not OK.":"",score:e,weight:10,offending:n,tags:["privacy"]}}()}catch(e){g.surveillance=e.message}try{d.youtube=function(){"use strict";var e=100;return window.YT&&(e=0),{id:"youtube",title:"Avoid including Youtube videos",description:"If you include Youtube videos on your page, you are sharing private user information with Google.",advice:0===e?"The page is including code from Youtube. You share user private information with Google. Instead you can host a video screenshot and let the user choose to go to Youtube or not, by clicking on the screenshot. You can look at http://labnol.org/?p=27941 and make sure you host your screenshot yourself. Or choose another video service.":"",score:e,weight:6,offending:[],tags:["privacy"]}}()}catch(e){g.youtube=e.message}e.privacy={adviceList:d},0<Object.keys(g).length&&(t.privacy=g);var y,l,m,p,f={},v={};try{f.firstPaint=function(){"use strict";var e=window.performance,t=e.timing,n=e.getEntriesByType("paint");if(0<n.length){for(var r=0;r<n.length;r++)if("first-paint"===n[r].name)return Number(n[r].startTime.toFixed(0))}else{if(t.timeToNonBlankPaint)return Number((t.timeToNonBlankPaint-t.navigationStart).toFixed(0));if("number"==typeof t.msFirstPaint)return t.msFirstPaint-t.navigationStart}}()}catch(e){v.firstPaint=e.message}try{f.fullyLoaded=function(){"use strict";if(window.performance&&window.performance.getEntriesByType){for(var e=window.performance.getEntriesByType("resource"),t=0,n=1,r=e.length;n<r;n++)e[n].responseEnd>t&&(t=e[n].responseEnd);return t}return-1}()}catch(e){v.fullyLoaded=e.message}try{f.navigationTimings=function(){"use strict";var e=window.performance.timing,t={navigationStart:0,unloadEventStart:0<e.unloadEventStart?e.unloadEventStart-e.navigationStart:void 0,unloadEventEnd:0<e.unloadEventEnd?e.unloadEventEnd-e.navigationStart:void 0,redirectStart:0<e.redirectStart?e.redirectStart-e.navigationStart:void 0,redirectEnd:0<e.redirectEnd?e.redirectEnd-e.navigationStart:void 0,fetchStart:e.fetchStart-e.navigationStart,domainLookupStart:e.domainLookupStart-e.navigationStart,domainLookupEnd:e.domainLookupEnd-e.navigationStart,connectStart:e.connectStart-e.navigationStart,connectEnd:e.connectEnd-e.navigationStart,secureConnectionStart:e.secureConnectionStart?e.secureConnectionStart-e.navigationStart:void 0,requestStart:e.requestStart-e.navigationStart,responseStart:e.responseStart-e.navigationStart,responseEnd:e.responseEnd-e.navigationStart,domLoading:e.domLoading-e.navigationStart,domInteractive:e.domInteractive-e.navigationStart,domContentLoadedEventStart:e.domContentLoadedEventStart-e.navigationStart,domContentLoadedEventEnd:e.domContentLoadedEventEnd-e.navigationStart,domComplete:e.domComplete-e.navigationStart,loadEventStart:e.loadEventStart-e.navigationStart,loadEventEnd:e.loadEventEnd-e.navigationStart};return Object.keys(t).forEach(function(e){void 0===t[e]&&delete t[e]}),t}()}catch(e){v.navigationTimings=e.message}try{f.rumSpeedIndex=(y=function(u){function c(e){var t=!1;if(e.getBoundingClientRect){var n=e.getBoundingClientRect();(t={top:Math.max(n.top,0),left:Math.max(n.left,0),bottom:Math.min(n.bottom,u.innerHeight||g.documentElement.clientHeight),right:Math.min(n.right,u.innerWidth||g.documentElement.clientWidth)}).bottom<=t.top||t.right<=t.left?t=!1:t.area=(t.bottom-t.top)*(t.right-t.left)}return t}function h(e,t){if(t){var n=c(e);n&&l.push({url:t,area:n.area,rect:n})}}var d,a,g=(u=u||window).document,l=[],m=[],p=.1;try{var f=u.performance.timing.navigationStart;(function(){for(var e=g.getElementsByTagName("*"),t=/url\(.*(http.*)\)/gi,n=0;n<e.length;n++){var r=e[n],a=u.getComputedStyle(r);if("IMG"==r.tagName&&h(r,r.src),a["background-image"]){t.lastIndex=0;var i=t.exec(a["background-image"]);i&&1<i.length&&h(r,i[1].replace('"',""))}if("IFRAME"==r.tagName)try{var o=c(r);if(o){var s=y(r.contentWindow);s&&l.push({tm:s,area:o.area,rect:o})}}catch(e){}}})(),function(){for(var e={},t=u.performance.getEntriesByType("resource"),n=0;n<t.length;n++)e[t[n].name]=t[n].responseEnd;for(var r=0;r<l.length;r++)"tm"in l[r]||(l[r].tm=void 0!==e[l[r].url]?e[l[r].url]:0)}(),function(){try{for(var e=performance.getEntriesByType("paint"),t=0;t<e.length;t++)if("first-paint"==e[t].name){f=performance.getEntriesByType("navigation")[0].startTime,d=e[t].startTime-f;break}}catch(e){}if(void 0===d&&"msFirstPaint"in u.performance.timing&&(d=u.performance.timing.msFirstPaint-f),void 0===d||d<0||12e4<d){d=u.performance.timing.responseStart-f;var n={},r=g.getElementsByTagName("head")[0].children;for(t=0;t<r.length;t++){var a=r[t];"SCRIPT"==a.tagName&&a.src&&!a.async&&(n[a.src]=!0),"LINK"==a.tagName&&"stylesheet"==a.rel&&a.href&&(n[a.href]=!0)}for(var i=u.performance.getEntriesByType("resource"),o=!1,s=0;s<i.length;s++)if(o||!n[i[s].name]||"script"!=i[s].initiatorType&&"link"!=i[s].initiatorType)o=!0;else{var c=i[s].responseEnd;(void 0===d||d<c)&&(d=c)}}d=Math.max(d,0)}(),function(){for(var e={0:0},t=0,n=0;n<l.length;n++){var r=d;"tm"in l[n]&&l[n].tm>d&&(r=l[n].tm),void 0===e[r]&&(e[r]=0),e[r]+=l[n].area,t+=l[n].area}var a=Math.max(g.documentElement.clientWidth,u.innerWidth||0)*Math.max(g.documentElement.clientHeight,u.innerHeight||0);if(0<a&&(a=Math.max(a-t,0)*p,void 0===e[d]&&(e[d]=0),e[d]+=a,t+=a),t){for(var i in e)e.hasOwnProperty(i)&&m.push({tm:i,area:e[i]});m.sort(function(e,t){return e.tm-t.tm});for(var o=0,s=0;s<m.length;s++)o+=m[s].area,m[s].progress=o/t}}(),function(){if(m.length)for(var e=a=0,t=0,n=0;n<m.length;n++){var r=m[n].tm-e;0<r&&t<1&&(a+=(1-t)*r),e=m[n].tm,t=m[n].progress}else a=d}()}catch(e){}return Number(a.toFixed(0))})()||-1}catch(e){v.rumSpeedIndex=e.message}try{f.timings=function(){"use strict";var e=window.performance.timing;return{domainLookupTime:e.domainLookupEnd-e.domainLookupStart,redirectionTime:e.fetchStart-e.navigationStart,serverConnectionTime:e.connectEnd-e.connectStart,serverResponseTime:e.responseEnd-e.requestStart,pageDownloadTime:e.responseEnd-e.responseStart,domInteractiveTime:e.domInteractive-e.navigationStart,domContentLoadedTime:e.domContentLoadedEventStart-e.navigationStart,pageLoadTime:e.loadEventStart-e.navigationStart,frontEndTime:e.loadEventStart-e.responseEnd,backEndTime:e.responseStart-e.navigationStart}}()}catch(e){v.timings=e.message}try{f.userTimings=function(){"use strict";var t=[],n=[];window.performance&&window.performance.getEntriesByType&&(Array.prototype.slice.call(window.performance.getEntriesByType("mark")).forEach(function(e){n.push({name:e.name,startTime:e.startTime})}),Array.prototype.slice.call(window.performance.getEntriesByType("measure")).forEach(function(e){t.push({name:e.name,duration:e.duration,startTime:e.startTime})}));return{marks:n,measures:t}}()}catch(e){v.userTimings=e.message}return e.timings=f,0<Object.keys(v).length&&(t.timings=v),l=e,p=m=0,Object.keys(l).forEach(function(e){var n=0,r=0,a=l[e].adviceList;a&&Object.keys(a).forEach(function(e){var t=a[e];m+=t.score*t.weight,n+=t.score*t.weight,p+=t.weight,r+=t.weight}),0<r&&(l[e].score=Math.round(n/r))}),l.score=Math.round(m/p),{advice:e,errors:t,url:document.URL,version:"5.1.1"}}(a)}console.error("Missing window or window document")})();
(function(){if("undefined"!=typeof window){"use strict";const e={getAbsoluteURL:function(e){const t=window.document.createElement("a");return t.href=e,t.href},getHostname:function(e){const t=window.document.createElement("a");return t.href=e,t.hostname},exists:function(e,t){return t.some((function(t){return t===e}))},caseInsensitiveAttributeValueFilter:function(e,t){return function(n){if((n.getAttribute(e)||"").toLowerCase()===t.toLowerCase())return n}},isHTTP2:function(){const t=e.getConnectionType().toLowerCase();return"h2"===t||t.startsWith("spdy")},isHTTP3:function(){return e.getConnectionType().toLowerCase().startsWith("h3")},getConnectionType:function(){if(window.performance.getEntriesByType("navigation")&&window.performance.getEntriesByType("navigation")[0]&&window.performance.getEntriesByType("navigation")[0].nextHopProtocol)return window.performance.getEntriesByType("navigation")[0].nextHopProtocol;if(window.performance&&window.performance.getEntriesByType&&window.performance.getEntriesByType("resource")){const t=window.performance.getEntriesByType("resource");if(t.length>1&&t[0].nextHopProtocol){const n=document.domain;for(let o=0,r=t.length;o<r;o++)if(n===e.getHostname(t[o].name))return t[o].nextHopProtocol}}return"unknown"},getSynchJSFiles:function(t){return Array.prototype.slice.call(t.getElementsByTagName("script")).filter((function(e){return!e.async&&e.src&&!e.defer})).map((function(t){return e.getAbsoluteURL(t.src)}))},getAsynchJSFiles:function(t){return Array.prototype.slice.call(t.getElementsByTagName("script")).filter((function(e){return e.async&&e.src})).map((function(t){return e.getAbsoluteURL(t.src)}))},getResourceHintsHrefs:function(e){return Array.prototype.slice.call(window.document.head.getElementsByTagName("link")).filter((function(t){return t.rel===e})).map((function(e){return e.href}))},getCSSFiles:function(t){return Array.prototype.slice.call(t.getElementsByTagName("link")).filter((function(e){return"stylesheet"===e.rel&&!e.href.startsWith("data:")})).map((function(t){return e.getAbsoluteURL(t.href)}))},plural:function(e){return 1!==e?"s":""},getTransferSize:function(e){const t=window.performance.getEntriesByName(e,"resource");return 1===t.length&&"number"==typeof t[0].transferSize?t[0].transferSize:0}};return function(e){var t={},n={},o={},r={};try{o.charset=function(){"use strict";let e=100,t="";const n=document.characterSet;return null===n?(t="The page is missing a character set. If you use Chrome/Firefox we know you are missing it, if you use another browser, it could be an implementation problem.",e=0):"UTF-8"!==n&&(t="You are not using charset UTF-8?",e=50),{id:"charset",title:"Declare a charset in your document",description:"The Unicode Standard (UTF-8) covers (almost) all the characters, punctuations, and symbols in the world. Please use that.",advice:t,score:e,weight:2,offending:[],tags:["bestpractice"]}}()}catch(e){r.charset=e.message}try{o.doctype=function(){"use strict";let e=100,t="";const n=document.doctype;return null===n?(t="The page is missing a doctype. Please use <!DOCTYPE html>.",e=0):("html"!==n.name.toLowerCase()||""!==n.systemId&&"about:legacy-compat"!==n.systemId.toLowerCase())&&(t="Just do yourself a favor and use the HTML5 doctype declaration: <!DOCTYPE html>",e=25),{id:"doctype",title:"Declare a doctype in your document",description:"The <!DOCTYPE> declaration is not an HTML tag; it is an instruction to the web browser about what version of HTML the page is written in.",advice:t,score:e,weight:2,offending:[],tags:["bestpractice"]}}()}catch(e){r.doctype=e.message}try{o.language=function(){"use strict";const e=document.getElementsByTagName("html"),t=e[0].getAttribute("lang");let n=100,o="";return e.length>0?null===t&&(n=0,o='The page is missing a language definition in the HTML tag. Define it with <html lang="YOUR_LANGUAGE_CODE">'):(n=0,o="What! The page is missing the HTML tag!"),{id:"language",title:"Declare the language code for your document",description:"According to the W3C recommendation you should declare the primary language for each Web page with the lang attribute inside the <html> tag https://www.w3.org/International/questions/qa-html-language-declarations#basics.",advice:o,score:n,weight:3,offending:[],tags:["bestpractice"]}}()}catch(e){r.language=e.message}try{o.layoutShift=function(){"use strict";let e="",t=0;const n=PerformanceObserver.supportedEntryTypes;if(n&&-1!==n.indexOf("layout-shift")){const e=new PerformanceObserver((()=>{}));e.observe({type:"layout-shift",buffered:!0});for(let n of e.takeRecords())t+=n.value}else e="Layout Shift is not supported in this browser";return{id:"layoutShift",title:"Cumulative Layout Shift",description:"Cumulative Layout Shift measures the sum total of all individual layout shift scores for every unexpected layout shift that occurs during the entire lifespan of the page. A layout shift occurs any time a visible element changes its position from one rendered frame to the next. ",advice:e,score:Math.round(Math.max(0,100-100*t)),weight:8,offending:[],tags:["bestpractice"]}}()}catch(e){r.layoutShift=e.message}try{o.metaDescription=function(e){"use strict";let t=100,n="",o=Array.prototype.slice.call(document.querySelectorAll("meta[name][content]"));o=o.filter(e.caseInsensitiveAttributeValueFilter("name","description"));const r=o.length>0?o[0].getAttribute("content"):"";return 0===r.length?(n="The page is missing a meta description.",t=0):r.length>155&&(n="The meta description is too long. It has "+r.length+" characters, the recommended max is 155",t=50),{id:"metaDescription",title:"Meta description",description:"Use a page description to make the page more relevant to search engines.",advice:n,score:t,weight:5,offending:[],tags:["bestpractice"]}}(e)}catch(e){r.metaDescription=e.message}try{o.optimizely=function(e){"use strict";const t=e.getSynchJSFiles(document.head),n=[];let o=100,r="";return t.forEach((function(t){"cdn.optimizely.com"===e.getHostname(t)&&(n.push(t),o=0,r="The page is using Optimizely. Use it with care because it hurts your performance. Only turn it on (= load the JavaScript) when you run your A/B tests. Then when you are finished make sure to turn it off.")})),{id:"optimizely",title:"Only use Optimizely when you need it",description:"Use Optimizely with care because it hurts your performance since Javascript is loaded synchronously inside of the head tag, making the first paint happen later. Only turn on Optimzely (= load the javascript) when you run your A/B tests.",advice:r,score:o,weight:2,offending:n,tags:["bestpractice"]}}(e)}catch(e){r.optimizely=e.message}try{o.pageTitle=function(){"use strict";const e=document.title;let t=100,n="";return 0===e.length?(n="The page is missing a title.",t=0):e.length>60&&(n="The title is too long by "+(e.length-60)+" characters. The recommended max is 60",t=50),{id:"pageTitle",title:"Page title",description:"Use a title to make the page more relevant to search engines.",advice:n,score:t,weight:5,offending:[],tags:["bestpractice"]}}()}catch(e){r.pageTitle=e.message}try{o.spdy=function(){"use strict";let t=100,n="";return-1!==e.getConnectionType().indexOf("spdy")&&(t=0,n="The page is using SPDY. Chrome dropped support for SPDY in Chrome 51. Change to HTTP/2 asap."),{id:"spdy",title:"EOL for SPDY in Chrome",description:"Chrome dropped supports for SPDY in Chrome 51, upgrade to HTTP/2 as soon as possible. The page has more users (browsers) supporting HTTP/2 than supports SPDY.",advice:n,score:t,weight:1,offending:[],tags:["bestpractice"]}}()}catch(e){r.spdy=e.message}try{o.url=function(){"use strict";const e=document.URL;let t=100,n="";return e.indexOf("?")>-1&&e.indexOf("jsessionid")>e.indexOf("?")&&(t=0,n="The page has the session id for the user as a parameter, please change so the session handling is done only with cookies. "),(e.match(/&/g)||[]).length>1&&(t-=50,n+="The page is using more than two request parameters. You should really rethink and try to minimize the number of parameters. "),e.length>100&&(t-=10,n+="The URL is "+e.length+" characters long. Try to make it less than 100 characters. "),(e.indexOf(" ")>-1||e.indexOf("%20")>-1)&&(t-=10,n+="Could the developer or the CMS be on Windows? Avoid using spaces in the URLs, use hyphens or underscores. "),{id:"url",title:"Have a good URL format",description:"A clean URL is good for the user and for SEO. Make them human readable, avoid too long URLs, spaces in the URL, too many request parameters, and never ever have the session id in your URL.",advice:n,score:t<0?0:t,weight:2,offending:[],tags:["bestpractice"]}}()}catch(e){r.url=e.message}t.bestpractice={adviceList:o},Object.keys(r).length>0&&(n.bestpractice=r);var i={},s={};try{i.amp=function(){"use strict";const e=document.getElementsByTagName("html")[0];return!!(e&&e.getAttribute("amp-version")||window.AMP)&&(e.getAttribute("amp-version")||!0)}()}catch(e){s.amp=e.message}try{i.browser=function(){"use strict";const{userAgent:e}=navigator;return e.includes("Firefox/")?"Firefox "+e.split("Firefox/")[1]:e.includes("Edg/")?"Edge "+e.split("Edg/")[1]:e.includes("Chrome/")?"Chrome "+e.match(/(Chrome)\/(\S+)/)[2]:e.includes("Safari/")?"Safari "+e.match(/(Version)\/(\S+)/)[2]:"Unknown"}()}catch(e){s.browser=e.message}try{i.connectionType=function(e){"use strict";return e.getConnectionType()}(e)}catch(e){s.connectionType=e.message}try{i.documentHeight=function(){"use strict";return Math.max(document.body.scrollHeight,document.body.offsetHeight,document.documentElement.clientHeight,document.documentElement.scrollHeight,document.documentElement.offsetHeight)}()}catch(e){s.documentHeight=e.message}try{i.documentTitle=function(){"use strict";return document.title}()}catch(e){s.documentTitle=e.message}try{i.documentWidth=function(){"use strict";return Math.max(document.body.scrollWidth,document.body.offsetWidth,document.documentElement.clientWidth,document.documentElement.scrollWidth,document.documentElement.offsetWidth)}()}catch(e){s.documentWidth=e.message}try{i.domDepth=function(){"use strict";function e(e){let t=0;if(e.parentNode)for(;e=e.parentNode;)t++;return t}const t=function(t){const n=t.getElementsByTagName("*");let o=n.length,r=0,i=0;for(;o--;){let t=e(n[o]);t>i&&(i=t),r+=t}return{avg:r/n.length,max:i}}(document);return{avg:Math.round(t.avg),max:t.max}}()}catch(e){s.domDepth=e.message}try{i.domElements=function(){"use strict";return document.getElementsByTagName("*").length}()}catch(e){s.domElements=e.message}try{i.generator=function(){"use strict";const e=document.querySelector('meta[name="generator"]');if(e)return e.getAttribute("content")}()}catch(e){s.generator=e.message}try{i.head=function(e){"use strict";return{jssync:e.getSynchJSFiles(document.head),jsasync:e.getAsynchJSFiles(document.head),css:e.getCSSFiles(document.head)}}(e)}catch(e){s.head=e.message}try{i.iframes=function(){"use strict";return document.getElementsByTagName("iframe").length}()}catch(e){s.iframes=e.message}try{i.jsframework=function(){"use strict";return{angular:!!window.angular&&window.angular.version.full,backbone:!!window.Backbone&&window.Backbone.VERSION,preact:!!window.preact,vue:!!window.Vue}}()}catch(e){s.jsframework=e.message}try{i.localStorageSize=function(){"use strict";return function(e){if(e){const t=e.length||Object.keys(e).length;let n=0;for(let o=0;o<t;o++){const t=e.key(o),r=e.getItem(t);n+=t.length+r.length}return n}return 0}(window.localStorage)}()}catch(e){s.localStorageSize=e.message}try{i.metaDescription=function(){"use strict";const e=document.querySelector('meta[name="description"]'),t=document.querySelector('meta[property="og:description"]');return e?e.getAttribute("content"):t?t.getAttribute("content"):""}()}catch(e){s.metaDescription=e.message}try{i.networkConnectionType=function(){"use strict";return window.navigator.connection?window.navigator.connection.effectiveType:"unknown"}()}catch(e){s.networkConnectionType=e.message}try{i.resourceHints=function(e){"use strict";return{"dns-prefetch":e.getResourceHintsHrefs("dns-prefetch"),preconnect:e.getResourceHintsHrefs("preconnect"),prefetch:e.getResourceHintsHrefs("prefetch"),prerender:e.getResourceHintsHrefs("prerender")}}(e)}catch(e){s.resourceHints=e.message}try{i.responsive=function(){"use strict";let e=!0;const t=document.body.scrollWidth,n=window.innerWidth,o=document.body.children;for(var r in t>n&&(e=!1),o)o[r].scrollWidth>n&&(e=!1);return e}()}catch(e){s.responsive=e.message}try{i.scripts=function(){"use strict";return document.getElementsByTagName("script").length}()}catch(e){s.scripts=e.message}try{i.serializedDomSize=function(){"use strict";return document.body.innerHTML.length}()}catch(e){s.serializedDomSize=e.message}try{i.serviceWorker=function(){"use strict";return"serviceWorker"in navigator&&(!!navigator.serviceWorker.controller&&("activated"===navigator.serviceWorker.controller.state&&navigator.serviceWorker.controller.scriptURL))}()}catch(e){s.serviceWorker=e.message}try{i.sessionStorageSize=function(){"use strict";return function(e){const t=e.length||Object.keys(e).length;let n=0;for(let o=0;o<t;o++){const t=e.key(o),r=e.getItem(t);n+=t.length+r.length}return n}(window.sessionStorage)}()}catch(e){s.sessionStorageSize=e.message}try{i.thirdparty=function(){"use strict";return{boomerang:!!window.BOOMR&&window.BOOMR.version,facebook:!!window.FB,gtm:!!window.google_tag_manager,ga:!!window.ga,jquery:!!window.jQuery&&window.jQuery.fn.jquery,newrelic:!!window.newrelic,matomo:!!window.Piwik||!!window.Matomo}}()}catch(e){s.thirdparty=e.message}try{i.userTiming=function(){"use strict";let e=0,t=0;return window.performance&&window.performance.getEntriesByType&&(t=window.performance.getEntriesByType("measure").length,e=window.performance.getEntriesByType("mark").length),{marks:e,measures:t}}()}catch(e){s.userTiming=e.message}try{i.windowSize=function(){"use strict";return(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)+"x"+(window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight)}()}catch(e){s.windowSize=e.message}t.info=i,Object.keys(s).length>0&&(n.info=s);var a={},c={};try{a.avoidScalingImages=function(e){"use strict";const t=[],n=Array.prototype.slice.call(document.getElementsByTagName("img"));let o=0,r="";for(let r=0,i=n.length;r<i;r++){const i=n[r];i.clientWidth+100<i.naturalWidth&&i.clientWidth>0&&(t.push(e.getAbsoluteURL(i.currentSrc)),o+=10)}return o>0&&(r="The page has "+o/10+" image(s) that are scaled more than 100 pixels. It would be better if those images are sent so the browser don't need to scale them."),{id:"avoidScalingImages",title:"Don't scale images in the browser",description:"It's easy to scale images in the browser and make sure they look good in different devices, however that is bad for performance! Scaling images in the browser takes extra CPU time and will hurt performance on mobile. And the user will download extra kilobytes (sometimes megabytes) of data that could be avoided. Don't do that, make sure you create multiple version of the same image server-side and serve the appropriate one.",advice:r,score:Math.max(0,100-o),weight:5,offending:t,tags:["performance","image"]}}(e)}catch(e){c.avoidScalingImages=e.message}try{a.cssPrint=function(e){"use strict";const t=[],n=document.getElementsByTagName("link");for(let o=0,r=n.length;o<r;o++)"print"===n[o].media&&t.push(e.getAbsoluteURL(n[o].href));const o=10*t.length;return{id:"cssPrint",title:"Do not load specific print stylesheets.",description:"Loading a specific stylesheet for printing slows down the page, even though it is not used. You can include the print styles inside your other CSS file(s) just by using an @media query targeting type print.",advice:t.length>0?"The page has "+t.length+" print stylesheets. You should include that stylesheet using @media type print instead.":"",score:Math.max(0,100-o),weight:1,offending:t,tags:["performance","css"]}}(e)}catch(e){c.cssPrint=e.message}try{a.fastRender=function(e){"use strict";const t=[],n=e.getCSSFiles(document.head),o=e.getSynchJSFiles(document.head),r=document.domain,i=[],s=e.getResourceHintsHrefs("preconnect").map((function(t){return e.getHostname(t)}));let a=0,c=0,u="",d=0;function h(n){const o=e.getHostname(n);o!==r?(t.push(n),e.exists(o,i)||(d+=e.exists(o,s)?5:10,i.push(o)),d+=5):(t.push(n),d+=5)}return e.isHTTP2()?(n.length>0&&(u="Maybe you could server push your CSS resources for faster rendering but that is actually really hard to get right :(. ",n.forEach((function(n){e.getTransferSize(n)>14500&&(t.push(n),d+=5,a++,u+="The style "+n+" is larger than the magic number TCP window size 14.5 kB. Make the file smaller and the page will render faster. ")}))),o.length>0&&(d+=10*o.length,o.forEach((function(e){t.push(e),c++})),u+="Avoid loading synchronously JavaScript inside of head, you shouldn't need JavaScript to render your page! ")):(n.forEach((function(e){h(e)})),a=n.length,o.forEach((function(e){h(e)})),c=o.length),t.length>0&&(u+="The page has "+a+" render blocking CSS request(s) and "+c+" blocking JavaScript request(s) inside of head."),{id:"fastRender",title:"Avoid slowing down the critical rendering path",description:"The critical rendering path is what the browser needs to do to start rendering the page. Every file requested inside of the head element will postpone the rendering of the page, because the browser need to do the request. Avoid loading JavaScript synchronously inside of the head (you should not need JavaScript to render the page), request files from the same domain as the main document (to avoid DNS lookups) and inline CSS or use server push for really fast rendering and a short rendering path.",advice:u,score:Math.max(0,100-d),weight:10,offending:t,tags:["performance"]}}(e)}catch(e){c.fastRender=e.message}try{a.googletagmanager=function(){"use strict";var e=100;return window.google_tag_manager&&(e=0),{id:"googletagmanager",title:"Avoid using Google Tag Manager",description:"Google Tag Manager makes it possible for non tech users to add scripts to your page that will downgrade performance.",advice:0===e?"The page is using Google Tag Manager, this is a performance risk since non-tech users can add JavaScript to your page.":"",score:e,weight:5,offending:[],tags:["performance","js"]}}()}catch(e){c.googletagmanager=e.message}try{a.inlineCss=function(){"use strict";const t=[],n=e.getCSSFiles(document.head),o=Array.prototype.slice.call(window.document.head.getElementsByTagName("style"));let r="",i=0;return e.isHTTP2()&&n.length>0&&o.length>0?(i+=5,r="The page has both inline CSS and CSS requests even though it uses a HTTP/2-ish connection. If you have many users on slow connections, it can be better to only inline the CSS. Run your own tests and check the waterfall graph to see what happens."):e.isHTTP2()&&o.length>0&&0===n.length?r+="The page has inline CSS and uses HTTP/2. Do you have a lot of users with slow connections on the site? It is good to inline CSS when using HTTP/2.":e.isHTTP2()&&n.length>0&&(r+="It is always faster for the user if you inline CSS instead of making a CSS request."),e.isHTTP3()?r="The page uses HTTP3. HTTP3 is (super) new and it is hard to say if inlined is good or not. The coach will improve the advice when there is a new best practice.":e.isHTTP2()||(n.length>0&&0===o.length&&(i+=10*n.length,r="The page loads "+n.length+" CSS request(s) inside of head, try to inline the CSS for the first render and lazy load the rest.",t.push.apply(t,n)),o.length>0&&n.length>0&&(i+=10,r+="The page has both inline styles as well as it is requesting "+n.length+" CSS file(s) inside of the head. Let's only inline CSS for really fast render.",t.push.apply(t,n))),{id:"inlineCss",title:"Inline CSS for faster first render",description:"In the early days of the Internet, inlining CSS was one of the ugliest things you can do. That has changed if you want your page to start rendering fast for your user. Always inline the critical CSS when you use HTTP/1 and HTTP/2 (avoid doing CSS requests that block rendering) and lazy load and cache the rest of the CSS. It is a little more complicated when using HTTP/2. Does your server support HTTP push? Then maybe that can help. Do you have a lot of users on a slow connection and are serving large chunks of HTML? Then it could be better to use the inline technique, becasue some servers always prioritize HTML content over CSS so the user needs to download the HTML first, before the CSS is downloaded.",advice:r,score:Math.max(0,100-i),weight:7,offending:t,tags:["performance","css"]}}()}catch(e){c.inlineCss=e.message}try{a.jquery=function(){"use strict";const e=[];if("function"==typeof window.jQuery){let t=window.$;e.push(window.jQuery.fn.jquery);let n=window.jQuery;for(;n.fn&&n.fn.jquery&&(n=window.jQuery.noConflict(!0),window.jQuery&&window.jQuery.fn)&&n.fn.jquery!==window.jQuery.fn.jquery;)e.push(window.jQuery.fn.jquery);window.$=t}return{id:"jquery",title:"Avoid using more than one jQuery version per page",description:"There are sites out there that use multiple versions of jQuery on the same page. You shouldn't do that because the user will then unnecessarily download extra data. Cleanup the code and make sure you only use one version.",advice:e.length>1?"The page uses "+e.length+" versions of jQuery! You only need one version, please remove the unnecessary version(s).":"",score:e.length>1?0:100,weight:4,offending:e,tags:["jQuery","performance"]}}()}catch(e){c.jquery=e.message}try{a.longTasks=function(){"use strict";const e=[];let t=0,n=0,o="";const r=PerformanceObserver.supportedEntryTypes;if(r&&-1!==r.indexOf("longtask")){const o=new PerformanceObserver((()=>{}));o.observe({type:"longtask",buffered:!0});for(let r of o.takeRecords()){t+=20,n+=r.duration;const o={};o.duration=r.duration,o.name=r.name,o.startTime=r.startTime,o.attribution=[];for(let e of r.attribution){const t={};t.containerId=e.containerId,t.containerName=e.containerName,t.containerSrc=e.containerSrc,t.containerType=e.containerType,o.attribution.push(t)}e.push(o)}}else o="The Long Task API is not supported in this browser.";return{id:"longTasks",title:"Avoid CPU Long Tasks",description:"Long CPU tasks locks the thread. To the user this is commonly visible as a “locked up” page where the browser is unable to respond to user input; this is a major source of bad user experience on the web today.",advice:e.length>0?"The page has "+e.length+" CPU long tasks with the total of "+n+" ms":o,score:Math.max(0,100-t),weight:8,offending:e,tags:["performance","js"]}}()}catch(e){c.longTasks=e.message}try{a.spof=function(e){"use strict";const t=[],n=[],o=document.domain,r=e.getCSSFiles(document.head);let i=0;r.forEach((function(r){const s=e.getHostname(r);s!==o&&(t.push(r),-1===n.indexOf(s)&&(n.push(s),i+=10))}));return e.getSynchJSFiles(document.head).forEach((function(r){const s=e.getHostname(r);s!==o&&(t.push(r),-1===n.indexOf(s)&&(n.push(s),i+=10))})),{id:"spof",title:"Avoid Frontend single point of failures",description:"A page can be stopped from loading in the browser if a single JavaScript, CSS, and in some cases a font, couldn't be fetched or is loading really slowly (the white screen of death). That is a scenario you really want to avoid. Never load 3rd-party components synchronously inside of the head tag.",advice:t.length>0?"The page has "+t.length+" requests inside of the head that can cause a SPOF (single point of failure). Load them asynchronously or move them outside of the document head.":"",score:Math.max(0,100-i),weight:7,offending:t,tags:["performance","css","js"]}}(e)}catch(e){c.spof=e.message}try{a.thirdPartyAsyncJs=function(e){"use strict";const t=["ajax.googleapis.com","apis.google.com",".google-analytics.com","connect.facebook.net","platform.twitter.com","code.jquery.com","platform.linkedin.com",".disqus.com","assets.pinterest.com","widgets.digg.com",".addthis.com","code.jquery.com","ad.doubleclick.net",".lognormal.com","embed.spotify.com"];function n(n){const o=e.getHostname(n);let r;for(let e=0,n=t.length;e<n;e++)if(r=new RegExp(t[e]),r.test(o))return!0;return!1}let o=0;const r=[],i=e.getSynchJSFiles(document);for(let e=0,t=i.length;e<t;e++)n(i[e])&&(r.push(i[e]),o+=10);return{id:"thirdPartyAsyncJs",title:"Always load third-party JavaScript asynchronously",description:"Use JavaScript snippets that load the JS files asynchronously in order to speed up the user experience and avoid blocking the initial load.",advice:r.length>0?"The page has "+r.length+" synchronous 3rd-party JavaScript request(s). Change it to be asynchronous instead.":"",score:Math.max(0,100-o),weight:5,offending:r,tags:["performance","js"]}}(e)}catch(e){c.thirdPartyAsyncJs=e.message}t.performance={adviceList:a},Object.keys(c).length>0&&(n.performance=c);var u={},d={};try{u.ampPrivacy=function(){"use strict";const e=document.getElementsByTagName("html")[0];let t=100;return(e&&e.getAttribute("amp-version")||window.AMP)&&(t=0),{id:"ampPrivacy",title:"Avoid including AMP",description:"You share share private user information with Google that your user hasn't agreed on sharing.",advice:0===t?"The page is using AMP, that makes you share private user information with Google.":"",score:t,weight:8,offending:[],tags:["privacy"]}}()}catch(e){d.ampPrivacy=e.message}try{u.facebook=function(){"use strict";let e=100;return window.FB&&(e=0),{id:"facebook",title:"Avoid including Facebook",description:"You share share private user information with Facebook that your user hasn't agreed on sharing.",advice:0===e?"The page gets content from Facebook. That means you share your users private information with Facebook.":"",score:e,weight:8,offending:[],tags:["privacy"]}}()}catch(e){d.facebook=e.message}try{u.ga=function(){"use strict";let e=100;return window.ga&&window.ga.create&&(e=0),{id:"ga",title:"Avoid using Google Analytics",description:"Google Analytics share private user information with Google that your user hasn't agreed on sharing.",advice:0===e?"The page is using Google Analytics meaning you share your users private information with Google. You should use analytics that care about user privacy, something like https://matomo.org.":"",score:e,weight:8,offending:[],tags:["privacy"]}}()}catch(e){d.ga=e.message}try{u.https=function(){"use strict";let e=100,t="";return-1===document.URL.indexOf("https://")&&(e=0,t="What!! The page is not using HTTPS. Every unencrypted HTTP request reveals information about user’s behavior, read more about it at https://https.cio.gov/everything/. You can get a totally free SSL/TLS certificate from https://letsencrypt.org/."),{id:"https",title:"Serve your content securely",description:"A page should always use HTTPS (https://https.cio.gov/everything/). You also need that for HTTP/2. You can get your free SSL/TLC certificate from https://letsencrypt.org/.",advice:t,score:e,weight:10,offending:[],tags:["privacy"]}}()}catch(e){d.https=e.message}try{u.surveillance=function(){"use strict";const e=document.domain,t=[],n=[".google.","facebook.com","youtube.","yahoo.com"];let o=100;for(var r=0;r<n.length;r++)e.indexOf(n[r])>-1&&(o=0,t.push(e));return{id:"surveillance",title:"Avoid using surveillance web sites",description:"Do not use web sites that harvest private user information and sell it to other companies.",advice:0===o?e+" uses harvest user information and sell it to other companies without the users agreement. That is not OK.":"",score:o,weight:10,offending:t,tags:["privacy"]}}()}catch(e){d.surveillance=e.message}try{u.youtube=function(){"use strict";let e=100;return window.YT&&(e=0),{id:"youtube",title:"Avoid including Youtube videos",description:"If you include Youtube videos on your page, you are sharing private user information with Google.",advice:0===e?"The page is including code from Youtube. You share user private information with Google. Instead you can host a video screenshot and let the user choose to go to Youtube or not, by clicking on the screenshot. You can look at http://labnol.org/?p=27941 and make sure you host your screenshot yourself. Or choose another video service.":"",score:e,weight:6,offending:[],tags:["privacy"]}}()}catch(e){d.youtube=e.message}t.privacy={adviceList:u},Object.keys(d).length>0&&(n.privacy=d);var h={},l={};try{h.elementTimings=function(){"use strict";const e=PerformanceObserver.supportedEntryTypes;if(!e||-1===e.indexOf("element"))return;const t=new PerformanceObserver((()=>{}));t.observe({type:"element",buffered:!0});const n=t.takeRecords(),o={};for(let e of n)o[e.identifier]={duration:e.duration,url:e.url,loadTime:Number(e.loadTime.toFixed(0)),renderTime:Number(e.renderTime.toFixed(0)),startTime:Number(e.startTime.toFixed(0)),naturalHeight:e.naturalHeight,naturalWidth:e.naturalWidth,tagName:e.element?e.element.tagName:""};return o}()}catch(e){l.elementTimings=e.message}try{h.fullyLoaded=function(){"use strict";if(window.performance&&window.performance.getEntriesByType){const e=window.performance.getEntriesByType("resource");let t=0;for(let n=1,o=e.length;n<o;n++)e[n].responseEnd>t&&(t=e[n].responseEnd);return t}return-1}()}catch(e){l.fullyLoaded=e.message}try{h.largestContentfulPaint=function(){"use strict";const e=PerformanceObserver.supportedEntryTypes;if(!e||-1===e.indexOf("largest-contentful-paint"))return;const t=new PerformanceObserver((()=>{}));t.observe({type:"largest-contentful-paint",buffered:!0});const n=t.takeRecords();if(n.length>0){const e=n[n.length-1];return{duration:e.duration,id:e.id,url:e.url,loadTime:Number(e.loadTime.toFixed(0)),renderTime:Number(Math.max(e.renderTime,e.loadTime).toFixed(0)),size:e.size,startTime:Number(e.startTime.toFixed(0)),tagName:e.element?e.element.tagName:""}}}()}catch(e){l.largestContentfulPaint=e.message}try{h.navigationTimings=function(){"use strict";const e=window.performance.timing,t={navigationStart:0,unloadEventStart:e.unloadEventStart>0?e.unloadEventStart-e.navigationStart:void 0,unloadEventEnd:e.unloadEventEnd>0?e.unloadEventEnd-e.navigationStart:void 0,redirectStart:e.redirectStart>0?e.redirectStart-e.navigationStart:void 0,redirectEnd:e.redirectEnd>0?e.redirectEnd-e.navigationStart:void 0,fetchStart:e.fetchStart-e.navigationStart,domainLookupStart:e.domainLookupStart-e.navigationStart,domainLookupEnd:e.domainLookupEnd-e.navigationStart,connectStart:e.connectStart-e.navigationStart,connectEnd:e.connectEnd-e.navigationStart,secureConnectionStart:e.secureConnectionStart?e.secureConnectionStart-e.navigationStart:void 0,requestStart:e.requestStart-e.navigationStart,responseStart:e.responseStart-e.navigationStart,responseEnd:e.responseEnd-e.navigationStart,domLoading:e.domLoading-e.navigationStart,domInteractive:e.domInteractive-e.navigationStart,domContentLoadedEventStart:e.domContentLoadedEventStart-e.navigationStart,domContentLoadedEventEnd:e.domContentLoadedEventEnd-e.navigationStart,domComplete:e.domComplete-e.navigationStart,loadEventStart:e.loadEventStart-e.navigationStart,loadEventEnd:e.loadEventEnd-e.navigationStart};return Object.keys(t).forEach((function(e){void 0===t[e]&&delete t[e]})),t}()}catch(e){l.navigationTimings=e.message}try{h.paintTimings=function(){"use strict";const e=window.performance.getEntriesByType("paint"),t={};if(e.length>0){for(var n=0;n<e.length;n++)t[e[n].name]=Number(e[n].startTime.toFixed(0));return t}}()}catch(e){l.paintTimings=e.message}try{h.userTimings=function(){"use strict";const e=[],t=[];if(window.performance&&window.performance.getEntriesByType){Array.prototype.slice.call(window.performance.getEntriesByType("mark")).forEach((function(e){t.push({name:e.name,startTime:e.startTime})}));Array.prototype.slice.call(window.performance.getEntriesByType("measure")).forEach((function(t){e.push({name:t.name,duration:t.duration,startTime:t.startTime})}))}return{marks:t,measures:e}}()}catch(e){l.userTimings=e.message}return t.timings=h,Object.keys(l).length>0&&(n.timings=l),function(e){var t=0,n=0;Object.keys(e).forEach((function(o){var r=0,i=0,s=e[o].adviceList;s&&Object.keys(s).forEach((function(e){var o=s[e];t+=o.score*o.weight,r+=o.score*o.weight,n+=o.weight,i+=o.weight})),i>0&&(e[o].score=Math.round(r/i))})),e.score=Math.round(t/n)}(t),{advice:t,errors:n,url:document.URL,version:"6.0.0-alpha.1"}}(e)}console.error("Missing window or window document")})();

@@ -1,7 +0,7 @@

(function() {
(function () {
'use strict';
var score = 100;
var message = '';
var charSet = document.characterSet;
let score = 100;
let message = '';
const charSet = document.characterSet;

@@ -8,0 +8,0 @@ if (charSet === null) {

@@ -1,7 +0,7 @@

(function() {
(function () {
'use strict';
var score = 100;
var message = '';
var docType = document.doctype;
let score = 100;
let message = '';
const docType = document.doctype;

@@ -8,0 +8,0 @@ if (docType === null) {

@@ -1,8 +0,8 @@

(function() {
(function () {
'use strict';
var html = document.getElementsByTagName('html');
var score = 100;
var language = html[0].getAttribute('lang');
var message = '';
const html = document.getElementsByTagName('html');
const language = html[0].getAttribute('lang');
let score = 100;
let message = '';

@@ -9,0 +9,0 @@ if (html.length > 0) {

@@ -1,8 +0,8 @@

(function(util) {
(function (util) {
'use strict';
var maxLength = 155;
var score = 100;
var message = '';
var metas = Array.prototype.slice.call(
const maxLength = 155;
let score = 100;
let message = '';
let metas = Array.prototype.slice.call(
document.querySelectorAll('meta[name][content]')

@@ -13,3 +13,3 @@ );

);
var description = metas.length > 0 ? metas[0].getAttribute('content') : '';
const description = metas.length > 0 ? metas[0].getAttribute('content') : '';

@@ -16,0 +16,0 @@ if (description.length === 0) {

@@ -1,10 +0,10 @@

(function(util) {
(function (util) {
'use strict';
var score = 100;
var scripts = util.getSynchJSFiles(document.head);
var advice = '';
var offending = [];
const scripts = util.getSynchJSFiles(document.head);
const offending = [];
let score = 100;
let advice = '';
scripts.forEach(function(script) {
scripts.forEach(function (script) {
if (util.getHostname(script) === 'cdn.optimizely.com') {

@@ -11,0 +11,0 @@ offending.push(script);

@@ -1,8 +0,8 @@

(function() {
(function () {
'use strict';
var max = 60;
var score = 100;
var message = '';
var title = document.title;
const max = 60;
const title = document.title;
let score = 100;
let message = '';

@@ -9,0 +9,0 @@ if (title.length === 0) {

@@ -1,7 +0,7 @@

(function() {
(function () {
'use strict';
var connectionType = util.getConnectionType();
var score = 100;
var message = '';
const connectionType = util.getConnectionType();
let score = 100;
let message = '';

@@ -8,0 +8,0 @@ if (connectionType.indexOf('spdy') !== -1) {

@@ -1,7 +0,7 @@

(function() {
(function () {
'use strict';
var score = 100;
var message = '';
var url = document.URL;
const url = document.URL;
let score = 100;
let message = '';

@@ -8,0 +8,0 @@ // ok all Java lovers, please do not use the sessionid in your URLs

@@ -1,5 +0,5 @@

(function() {
(function () {
'use strict';
var html = document.getElementsByTagName('html')[0];
const html = document.getElementsByTagName('html')[0];

@@ -6,0 +6,0 @@ if ((html && html.getAttribute('amp-version')) || window.AMP) {

@@ -1,9 +0,13 @@

(function() {
(function () {
'use strict';
var browser = 'unknown';
var match = window.navigator.userAgent.match(/(Chrome|Firefox)\/(\S+)/);
browser = match ? match[1] + ' ' + match[2] : browser;
return browser;
const { userAgent } = navigator;
if (userAgent.includes('Firefox/')) {
return `Firefox ${userAgent.split('Firefox/')[1]}`;
} else if (userAgent.includes('Edg/')) {
return `Edge ${userAgent.split('Edg/')[1]}`;
} else if (userAgent.includes('Chrome/')) {
return `Chrome ${userAgent.match(/(Chrome)\/(\S+)/)[2]}`;
} else if (userAgent.includes('Safari/')) {
return `Safari ${userAgent.match(/(Version)\/(\S+)/)[2]}`;
} else return 'Unknown';
})();

@@ -1,2 +0,2 @@

(function(util) {
(function (util) {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,12 +0,12 @@

(function() {
(function () {
'use strict';
function domDepth(document) {
var allElems = document.getElementsByTagName('*');
var allElemsLen = allElems.length;
var totalParents = 0;
var maxParents = 0;
const allElems = document.getElementsByTagName('*');
let allElemsLen = allElems.length;
let totalParents = 0;
let maxParents = 0;
while (allElemsLen--) {
var parents = numParents(allElems[allElemsLen]);
let parents = numParents(allElems[allElemsLen]);
if (parents > maxParents) {

@@ -18,3 +18,3 @@ maxParents = parents;

var average = totalParents / allElems.length;
const average = totalParents / allElems.length;

@@ -28,3 +28,3 @@ return {

function numParents(elem) {
var n = 0;
let n = 0;

@@ -39,3 +39,3 @@ if (elem.parentNode) {

var depth = domDepth(document);
const depth = domDepth(document);

@@ -42,0 +42,0 @@ return {

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,4 +0,4 @@

(function() {
(function () {
'use strict';
var description = document.querySelector('meta[name="generator"]');
const description = document.querySelector('meta[name="generator"]');
if (description) {

@@ -5,0 +5,0 @@ return description.getAttribute('content');

@@ -1,2 +0,2 @@

(function(util) {
(function (util) {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@ return {

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -7,8 +7,8 @@

if (storage) {
var keys = storage.length || Object.keys(storage).length;
var bytes = 0;
const keys = storage.length || Object.keys(storage).length;
let bytes = 0;
for (var i = 0; i < keys; i++) {
var key = storage.key(i);
var val = storage.getItem(key);
for (let i = 0; i < keys; i++) {
const key = storage.key(i);
const val = storage.getItem(key);
bytes += key.length + val.length;

@@ -15,0 +15,0 @@ }

@@ -1,6 +0,6 @@

(function() {
(function () {
'use strict';
var description = document.querySelector('meta[name="description"]');
var og = document.querySelector('meta[property="og:description"]');
const description = document.querySelector('meta[name="description"]');
const og = document.querySelector('meta[property="og:description"]');
if (description) {

@@ -7,0 +7,0 @@ return description.getAttribute('content');

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

(function(util) {
(function (util) {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,9 +0,9 @@

(function() {
(function () {
'use strict';
// we now do the same check as WebPageTest
var isResponsive = true;
var bodyScrollWidth = document.body.scrollWidth;
var windowInnerWidth = window.innerWidth;
var nodes = document.body.children;
let isResponsive = true;
const bodyScrollWidth = document.body.scrollWidth;
const windowInnerWidth = window.innerWidth;
const nodes = document.body.children;

@@ -10,0 +10,0 @@ if (bodyScrollWidth > windowInnerWidth) {

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@

@@ -1,11 +0,11 @@

(function() {
(function () {
'use strict';
function storageSize(storage) {
var keys = storage.length || Object.keys(storage).length;
var bytes = 0;
const keys = storage.length || Object.keys(storage).length;
let bytes = 0;
for (var i = 0; i < keys; i++) {
var key = storage.key(i);
var val = storage.getItem(key);
for (let i = 0; i < keys; i++) {
const key = storage.key(i);
const val = storage.getItem(key);
bytes += key.length + val.length;

@@ -12,0 +12,0 @@ }

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@ return {

@@ -1,6 +0,6 @@

(function() {
(function () {
'use strict';
var mark = 0;
var measure = 0;
let mark = 0;
let measure = 0;
if (window.performance && window.performance.getEntriesByType) {

@@ -7,0 +7,0 @@ measure = window.performance.getEntriesByType('measure').length;

@@ -1,9 +0,9 @@

(function() {
(function () {
'use strict';
var width =
const width =
window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth;
var height =
const height =
window.innerHeight ||

@@ -10,0 +10,0 @@ document.documentElement.clientHeight ||

@@ -1,12 +0,14 @@

(function(util) {
(function (util) {
'use strict';
var message = '';
var minLimit = 100;
var score = 0;
var offending = [];
var images = Array.prototype.slice.call(document.getElementsByTagName('img'));
const minLimit = 100;
const offending = [];
const images = Array.prototype.slice.call(
document.getElementsByTagName('img')
);
let score = 0;
let message = '';
for (var i = 0, len = images.length; i < len; i++) {
var img = images[i];
for (let i = 0, len = images.length; i < len; i++) {
const img = images[i];
// skip images that are 0 (carousel etc)

@@ -13,0 +15,0 @@ if (img.clientWidth + minLimit < img.naturalWidth && img.clientWidth > 0) {

@@ -1,8 +0,8 @@

(function(util) {
(function (util) {
'use strict';
var offending = [];
var links = document.getElementsByTagName('link');
const offending = [];
const links = document.getElementsByTagName('link');
for (var i = 0, len = links.length; i < len; i++) {
for (let i = 0, len = links.length; i < len; i++) {
if (links[i].media === 'print') {

@@ -13,3 +13,3 @@ offending.push(util.getAbsoluteURL(links[i].href));

var score = offending.length * 10;
const score = offending.length * 10;

@@ -16,0 +16,0 @@ return {

@@ -1,21 +0,21 @@

(function(util) {
(function (util) {
'use strict';
var message = '';
var score = 0;
var offending = [];
var styles = util.getCSSFiles(document.head);
var scripts = util.getSynchJSFiles(document.head);
var docDomain = document.domain;
var domains = [];
var blockingCSS = 0;
var blockingJS = 0;
const offending = [];
const styles = util.getCSSFiles(document.head);
const scripts = util.getSynchJSFiles(document.head);
const docDomain = document.domain;
const domains = [];
// TODO does preconnect really matter when you are inside of head?
var preconnects = util.getResourceHintsHrefs('preconnect');
var preconnectDomains = preconnects.map(function(preconnect) {
const preconnects = util.getResourceHintsHrefs('preconnect');
const preconnectDomains = preconnects.map(function (preconnect) {
return util.getHostname(preconnect);
});
let blockingCSS = 0;
let blockingJS = 0;
let message = '';
let score = 0;
function testByType(assetUrl) {
var domain = util.getHostname(assetUrl);
const domain = util.getHostname(assetUrl);
// if it is from different domain or not

@@ -44,3 +44,3 @@ if (domain !== docDomain) {

// check the size
styles.forEach(function(url) {
styles.forEach(function (url) {
if (util.getTransferSize(url) > 14500) {

@@ -59,3 +59,3 @@ offending.push(url);

score += scripts.length * 10;
scripts.forEach(function(url) {
scripts.forEach(function (url) {
offending.push(url);

@@ -69,7 +69,7 @@ blockingJS++;

// we are using HTTP/1
styles.forEach(function(style) {
styles.forEach(function (style) {
testByType(style);
});
blockingCSS = styles.length;
scripts.forEach(function(script) {
scripts.forEach(function (script) {
testByType(script);

@@ -76,0 +76,0 @@ });

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -3,0 +3,0 @@ var score = 100;

@@ -1,12 +0,11 @@

(function() {
(function () {
'use strict';
var message = '';
var score = 0;
var offending = [];
var cssFilesInHead = util.getCSSFiles(document.head);
var styles = Array.prototype.slice.call(
const offending = [];
const cssFilesInHead = util.getCSSFiles(document.head);
const styles = Array.prototype.slice.call(
window.document.head.getElementsByTagName('style')
);
let message = '';
let score = 0;

@@ -25,3 +24,3 @@ // If we use HTTP/2, do CSS request in head and inline CSS

message +=
'The page has inline CSS and uses HTTP/2. Do you have a lot of users with slow connections on the site? It is good to inline CSS when using HTTP/2. If not, and your server supports server push, you can probably push the CSS files instead.';
'The page has inline CSS and uses HTTP/2. Do you have a lot of users with slow connections on the site? It is good to inline CSS when using HTTP/2.';
} else if (util.isHTTP2() && cssFilesInHead.length > 0) {

@@ -33,4 +32,8 @@ // we have HTTP/2 and do CSS requests in HEAD.

if (util.isHTTP3()) {
message =
'The page uses HTTP3. HTTP3 is (super) new and it is hard to say if inlined is good or not. The coach will improve the advice when there is a new best practice.';
}
// If we have HTTP/1
if (!util.isHTTP2()) {
else if (!util.isHTTP2()) {
// and files served inside of head, inline them instead

@@ -37,0 +40,0 @@ if (cssFilesInHead.length > 0 && styles.length === 0) {

@@ -1,9 +0,11 @@

(function() {
(function () {
'use strict';
var versions = [];
const versions = [];
// check that we got a jQuery
if (typeof window.jQuery === 'function') {
// https://github.com/sitespeedio/coach-core/issues/21#issuecomment-717041439
let keepRef = window.$;
versions.push(window.jQuery.fn.jquery);
var old = window.jQuery;
let old = window.jQuery;
while (old.fn && old.fn.jquery) {

@@ -19,2 +21,3 @@ old = window.jQuery.noConflict(true);

}
window.$ = keepRef;
}

@@ -21,0 +24,0 @@

@@ -1,16 +0,14 @@

(function(util) {
(function (util) {
'use strict';
var score = 0;
var offending = [];
var offendingDomains = [];
const offending = [];
const offendingDomains = [];
// simplify and only look for css & js spof
var docDomain = document.domain;
const docDomain = document.domain;
// do we have any CSS loaded inside of head from a different domain?
var styles = util.getCSSFiles(document.head);
const styles = util.getCSSFiles(document.head);
let score = 0;
styles.forEach(function(style) {
var styleDomain = util.getHostname(style);
styles.forEach(function (style) {
const styleDomain = util.getHostname(style);
if (styleDomain !== docDomain) {

@@ -26,6 +24,6 @@ offending.push(style);

// do we have any JS loaded inside of head from a different domain?
var scripts = util.getSynchJSFiles(document.head);
const scripts = util.getSynchJSFiles(document.head);
scripts.forEach(function(script) {
var scriptDomain = util.getHostname(script);
scripts.forEach(function (script) {
const scriptDomain = util.getHostname(script);
if (scriptDomain !== docDomain) {

@@ -32,0 +30,0 @@ offending.push(script);

@@ -1,5 +0,5 @@

(function(util) {
(function (util) {
'use strict';
var patterns = [
const patterns = [
'ajax.googleapis.com',

@@ -23,5 +23,5 @@ 'apis.google.com',

function is3rdParty(url) {
var hostname = util.getHostname(url);
var re;
for (var i = 0, len = patterns.length; i < len; i++) {
const hostname = util.getHostname(url);
let re;
for (let i = 0, len = patterns.length; i < len; i++) {
re = new RegExp(patterns[i]);

@@ -35,7 +35,7 @@ if (re.test(hostname)) {

var score = 0;
var offending = [];
var scripts = util.getSynchJSFiles(document);
let score = 0;
const offending = [];
const scripts = util.getSynchJSFiles(document);
for (var i = 0, len = scripts.length; i < len; i++) {
for (let i = 0, len = scripts.length; i < len; i++) {
if (is3rdParty(scripts[i])) {

@@ -42,0 +42,0 @@ offending.push(scripts[i]);

@@ -1,9 +0,8 @@

(function() {
(function () {
'use strict';
var score = 100;
var offending = [];
const offending = [];
const html = document.getElementsByTagName('html')[0];
let score = 100;
var html = document.getElementsByTagName('html')[0];
if ((html && html.getAttribute('amp-version')) || window.AMP) {

@@ -10,0 +9,0 @@ score = 0;

@@ -1,6 +0,6 @@

(function() {
(function () {
'use strict';
var score = 100;
var offending = [];
const offending = [];
let score = 100;

@@ -7,0 +7,0 @@ if (window.FB) {

@@ -1,6 +0,6 @@

(function() {
(function () {
'use strict';
var score = 100;
var offending = [];
const offending = [];
let score = 100;

@@ -7,0 +7,0 @@ if (window.ga && window.ga.create) {

@@ -1,7 +0,7 @@

(function() {
(function () {
'use strict';
var url = document.URL;
var score = 100;
var message = '';
const url = document.URL;
let score = 100;
let message = '';

@@ -8,0 +8,0 @@ if (url.indexOf('https://') === -1) {

@@ -1,8 +0,8 @@

(function() {
(function () {
'use strict';
var score = 100;
var docDomain = document.domain;
var offending = [];
var offenders = ['.google.', 'facebook.com', 'youtube.', 'yahoo.com'];
const docDomain = document.domain;
const offending = [];
const offenders = ['.google.', 'facebook.com', 'youtube.', 'yahoo.com'];
let score = 100;

@@ -9,0 +9,0 @@ for (var i = 0; i < offenders.length; i++) {

@@ -1,6 +0,6 @@

(function() {
(function () {
'use strict';
var score = 100;
var offending = [];
const offending = [];
let score = 100;

@@ -7,0 +7,0 @@ if (window.YT) {

@@ -1,2 +0,2 @@

(function() {
(function () {
'use strict';

@@ -7,6 +7,6 @@

if (window.performance && window.performance.getEntriesByType) {
var resources = window.performance.getEntriesByType('resource');
var max = 0;
const resources = window.performance.getEntriesByType('resource');
let max = 0;
for (var i = 1, len = resources.length; i < len; i++) {
for (let i = 1, len = resources.length; i < len; i++) {
if (resources[i].responseEnd > max) {

@@ -13,0 +13,0 @@ max = resources[i].responseEnd;

@@ -1,7 +0,7 @@

(function() {
(function () {
'use strict';
var t = window.performance.timing;
const t = window.performance.timing;
var metrics = {
const metrics = {
navigationStart: 0,

@@ -41,3 +41,3 @@ unloadEventStart:

// the undefined keys
Object.keys(metrics).forEach(function(key) {
Object.keys(metrics).forEach(function (key) {
if (metrics[key] === undefined) {

@@ -44,0 +44,0 @@ delete metrics[key];

@@ -1,13 +0,13 @@

(function() {
(function () {
'use strict';
var measures = [];
var marks = [];
const measures = [];
const marks = [];
if (window.performance && window.performance.getEntriesByType) {
var myMarks = Array.prototype.slice.call(
const myMarks = Array.prototype.slice.call(
window.performance.getEntriesByType('mark')
);
myMarks.forEach(function(mark) {
myMarks.forEach(function (mark) {
marks.push({

@@ -19,7 +19,7 @@ name: mark.name,

var myMeasures = Array.prototype.slice.call(
const myMeasures = Array.prototype.slice.call(
window.performance.getEntriesByType('measure')
);
myMeasures.forEach(function(measure) {
myMeasures.forEach(function (measure) {
measures.push({

@@ -26,0 +26,0 @@ name: measure.name,

'use strict';
var util = {
const util = {
/**

@@ -10,4 +10,4 @@ * Make your URL absolute.

*/
getAbsoluteURL: function(url) {
var a = window.document.createElement('a');
getAbsoluteURL: function (url) {
const a = window.document.createElement('a');
a.href = url;

@@ -23,4 +23,4 @@ return a.href;

*/
getHostname: function(url) {
var a = window.document.createElement('a');
getHostname: function (url) {
const a = window.document.createElement('a');
a.href = url;

@@ -38,4 +38,4 @@ return a.hostname;

*/
exists: function(element, array) {
return array.some(function(e) {
exists: function (element, array) {
return array.some(function (e) {
return e === element;

@@ -51,5 +51,8 @@ });

*/
caseInsensitiveAttributeValueFilter: function(attributeName, attributeValue) {
return function(item) {
var attribute = item.getAttribute(attributeName) || '';
caseInsensitiveAttributeValueFilter: function (
attributeName,
attributeValue
) {
return function (item) {
const attribute = item.getAttribute(attributeName) || '';
if (attribute.toLowerCase() === attributeValue.toLowerCase()) {

@@ -63,10 +66,10 @@ return item;

/**
* Is the connection used for the main document using HTTP/2? Only
* works in Chrome and Firefox Nightly + browsers that supports Resource Timing
* API v2.
* Is the connection used for the main document using HTTP/2?
* Works in Chrome, Firefox, Edge and other browsers that supports Resource Timing
* API v2 (not Safari yet).
* @memberof util
* @returns {Boolean} true if the connection is HTTP/2
*/
isHTTP2: function() {
var type = util.getConnectionType().toLowerCase();
isHTTP2: function () {
const type = util.getConnectionType().toLowerCase();
return type === 'h2' || type.startsWith('spdy');

@@ -76,4 +79,14 @@ },

/**
* Get the connection type used for the main document. Only works
* in Chrome and Firefox Nightly + browsers that support Resource Timing
* Is the connection used for the main document using HTTP/3?
* @memberof util
* @returns {Boolean} true if the connection is HTTP/3
*/
isHTTP3: function () {
const type = util.getConnectionType().toLowerCase();
return type.startsWith('h3');
},
/**
* Get the connection type used for the main document. Works in Chrome, Firefox,
* Edge and + browsers that support Resource Timing
* API v2.

@@ -83,3 +96,3 @@ * @memberof util

*/
getConnectionType: function() {
getConnectionType: function () {
// it's easy in Chrome

@@ -100,9 +113,9 @@ if (

// it's kind of easy too
var resources = window.performance.getEntriesByType('resource');
const resources = window.performance.getEntriesByType('resource');
// now we "only" need to know if it is v2
if (resources.length > 1 && resources[0].nextHopProtocol) {
// if it's the same domain, say it's OK
var host = document.domain;
const host = document.domain;
for (var i = 0, len = resources.length; i < len; i++) {
for (let i = 0, len = resources.length; i < len; i++) {
if (host === util.getHostname(resources[i].name)) {

@@ -123,4 +136,4 @@ return resources[i].nextHopProtocol;

*/
getSynchJSFiles: function(parentElement) {
var scripts = Array.prototype.slice.call(
getSynchJSFiles: function (parentElement) {
const scripts = Array.prototype.slice.call(
parentElement.getElementsByTagName('script')

@@ -130,6 +143,6 @@ );

return scripts
.filter(function(s) {
.filter(function (s) {
return !s.async && s.src && !s.defer;
})
.map(function(s) {
.map(function (s) {
return util.getAbsoluteURL(s.src);

@@ -145,4 +158,4 @@ });

*/
getAsynchJSFiles: function(parentElement) {
var scripts = Array.prototype.slice.call(
getAsynchJSFiles: function (parentElement) {
const scripts = Array.prototype.slice.call(
parentElement.getElementsByTagName('script')

@@ -152,6 +165,6 @@ );

return scripts
.filter(function(s) {
.filter(function (s) {
return s.async && s.src;
})
.map(function(s) {
.map(function (s) {
return util.getAbsoluteURL(s.src);

@@ -167,4 +180,4 @@ });

*/
getResourceHintsHrefs: function(type) {
var links = Array.prototype.slice.call(
getResourceHintsHrefs: function (type) {
const links = Array.prototype.slice.call(
window.document.head.getElementsByTagName('link')

@@ -174,6 +187,6 @@ );

return links
.filter(function(link) {
.filter(function (link) {
return link.rel === type;
})
.map(function(link) {
.map(function (link) {
return link.href;

@@ -189,4 +202,4 @@ });

*/
getCSSFiles: function(parentElement) {
var links = Array.prototype.slice.call(
getCSSFiles: function (parentElement) {
const links = Array.prototype.slice.call(
parentElement.getElementsByTagName('link')

@@ -196,7 +209,7 @@ );

return links
.filter(function(link) {
.filter(function (link) {
// make sure we skip data:
return link.rel === 'stylesheet' && !link.href.startsWith('data:');
})
.map(function(link) {
.map(function (link) {
return util.getAbsoluteURL(link.href);

@@ -206,3 +219,3 @@ });

plural: function(value) {
plural: function (value) {
return value !== 1 ? 's' : '';

@@ -215,4 +228,4 @@ },

**/
getTransferSize: function(url) {
var entries = window.performance.getEntriesByName(url, 'resource');
getTransferSize: function (url) {
const entries = window.performance.getEntriesByName(url, 'resource');
if (entries.length === 1 && typeof entries[0].transferSize === 'number') {

@@ -219,0 +232,0 @@ return entries[0].transferSize;

@@ -9,7 +9,7 @@ 'use strict';

tags: ['bestpractice', 'header'],
processPage: function(page) {
processPage: function (page) {
const offending = [];
let score = 100;
let advice = '';
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
const headerNames = Object.keys(asset.headers.response);

@@ -16,0 +16,0 @@ const maxHeaderLength = 600;

@@ -9,7 +9,7 @@ 'use strict';

tags: ['bestpractice', 'headers'],
processPage: function(page) {
processPage: function (page) {
const offending = [];
let score = 100;
let advice = '';
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
const headerNames = Object.keys(asset.headers.response);

@@ -16,0 +16,0 @@ const maxHeaders = 30;

@@ -11,3 +11,3 @@ 'use strict';

tags: ['bestpractice'],
processPage: function(page) {
processPage: function (page) {
let score = 100;

@@ -14,0 +14,0 @@ let advice = '';

@@ -10,3 +10,3 @@ 'use strict';

tags: ['bestpractice', 'header'],
processPage: function(page) {
processPage: function (page) {
const offending = [];

@@ -20,3 +20,3 @@ const p3pHeaders = [];

let advice = '';
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
if (asset.headers.response.p3p) {

@@ -23,0 +23,0 @@ offending.push(asset.url);

@@ -5,3 +5,3 @@ 'use strict';

module.exports.pickAPage = function(har, pageIndex) {
module.exports.pickAPage = function (har, pageIndex) {
if (typeof har.log.pages[pageIndex] !== 'undefined') {

@@ -8,0 +8,0 @@ const myHar = JSON.parse(JSON.stringify(har));

@@ -7,3 +7,3 @@ 'use strict';

module.exports.runAdvice = function(
module.exports.runAdvice = function (
pages,

@@ -20,18 +20,20 @@ harAdvicesByCategory,

const adviceList = harAdvicesByCategory[category];
const allAdvice = adviceList.filter(validateAdvice);
results[category] = {
adviceList: {}
};
for (let advice of allAdvice) {
const result = advice.processPage(page, domAdvice, options);
results[category].adviceList[advice.id] = {
id: advice.id,
title: advice.title,
description: advice.description,
advice: result.advice,
weight: advice.weight,
tags: advice.tags,
score: result.score,
offending: result.offending
if (category !== 'info') {
const allAdvice = adviceList.filter(validateAdvice);
results[category] = {
adviceList: {}
};
for (let advice of allAdvice) {
const result = advice.processPage(page, domAdvice, options);
results[category].adviceList[advice.id] = {
id: advice.id,
title: advice.title,
description: advice.description,
advice: result.advice,
weight: advice.weight,
tags: advice.tags,
score: result.score,
offending: result.offending
};
}
}

@@ -68,2 +70,12 @@ }

}
const infoFromHAR = harAdvicesByCategory['info'];
for (let info of infoFromHAR) {
withoutCategories.advice['info'][info.id] = info.processPage(
page,
domAdvice,
options
);
}
all.push(withoutCategories);

@@ -70,0 +82,0 @@ }

@@ -18,7 +18,7 @@ 'use strict';

tags: ['performance'],
processPage: function(page) {
processPage: function (page) {
let score = 100;
let offending = [];
let sameDomainAsDocument = 0;
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
// skip the main document if that is redirected

@@ -25,0 +25,0 @@ // it's caught the ine documentRedirect advice

@@ -19,7 +19,7 @@ 'use strict';

processPage: function(page) {
processPage: function (page) {
let score = 100;
let offending = [];
let saveSize = 0;
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
// Don't check the main page/document since it is common to not

@@ -26,0 +26,0 @@ // cache that

@@ -33,6 +33,6 @@ 'use strict';

processPage: function(page) {
processPage: function (page) {
let score = 100;
let offending = [];
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
// Don't check the main page/document since it is common to not

@@ -39,0 +39,0 @@ // cache that

@@ -28,6 +28,6 @@ 'use strict';

processPage: function(page) {
processPage: function (page) {
let score = 100;
let offending = [];
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
let myScore = processAsset(asset);

@@ -34,0 +34,0 @@ if (myScore > 0) {

@@ -12,3 +12,3 @@ 'use strict';

tags: ['performance', 'server'],
processPage: function(page) {
processPage: function (page) {
let score = 100;

@@ -19,3 +19,3 @@ let offending = [];

let closedPerDomain = {};
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
const connectionHeader = asset.headers.response.connection || '';

@@ -22,0 +22,0 @@ if (asset.type !== avoid && connectionHeader.indexOf('close') > -1) {

@@ -9,3 +9,3 @@ 'use strict';

tags: ['performance', 'CSS'],
processPage: function(page) {
processPage: function (page) {
if (page.cpu && page.cpu.categories) {

@@ -12,0 +12,0 @@ // 500 ms limit, it's high!

@@ -9,3 +9,3 @@ 'use strict';

tags: ['performance', 'JavaScript'],
processPage: function(page) {
processPage: function (page) {
if (page.cpu && page.cpu.categories) {

@@ -12,0 +12,0 @@ // 1000 ms limit, it's high!

@@ -13,4 +13,4 @@ 'use strict';

processPage: function(page, domAdvice, options) {
let cssAssets = page.assets.filter(asset => asset.type === 'css');
processPage: function (page, domAdvice, options) {
let cssAssets = page.assets.filter((asset) => asset.type === 'css');
let transferSize = 0;

@@ -23,3 +23,3 @@ let contentSize = 0;

cssAssets.forEach(function(asset) {
cssAssets.forEach(function (asset) {
transferSize += asset.transferSize;

@@ -26,0 +26,0 @@ contentSize += asset.contentSize;

@@ -10,3 +10,3 @@ 'use strict';

tags: ['performance'],
processPage: function(page) {
processPage: function (page) {
let score = page.documentRedirects > 0 ? 0 : 100;

@@ -13,0 +13,0 @@ let advice =

@@ -11,7 +11,7 @@ 'use strict';

processPage: function(page) {
processPage: function (page) {
let total = 0;
let offending = [];
let advice = '';
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
if (asset.type === 'favicon') {

@@ -18,0 +18,0 @@ if (asset.status >= 299) {

@@ -10,6 +10,6 @@ 'use strict';

tags: ['performance', 'font'],
processPage: function(page) {
processPage: function (page) {
const urls = page.assets
.filter(asset => asset.type === 'font')
.map(asset => asset.url);
.filter((asset) => asset.type === 'font')
.map((asset) => asset.url);

@@ -16,0 +16,0 @@ // only hurt if we got more than one font

@@ -12,3 +12,3 @@ 'use strict';

tags: ['performance', 'HTTP/1'],
processPage: function(page) {
processPage: function (page) {
let limit = 30;

@@ -22,2 +22,9 @@ if (util.isHTTP2(page)) {

};
} else if (util.isHTTP3(page)) {
return {
score: 100,
offending: [],
advice:
'HTTP/3 connections is new and there are no best practices at the moment. Please check your HAR file, does it look OK?'
};
}

@@ -47,3 +54,3 @@

Object.keys(domainAndRequests).forEach(function(domain) {
Object.keys(domainAndRequests).forEach(function (domain) {
advice += domain + ' got ' + domainAndRequests[domain] + ' requests. ';

@@ -50,0 +57,0 @@ });

@@ -12,3 +12,3 @@ 'use strict';

tags: ['performance', 'mobile'],
processPage: function(page) {
processPage: function (page) {
// H2 sends headers compressed and we don't get the right size now

@@ -23,2 +23,9 @@ // so it's better just to skip those

};
} else if (util.isHTTP3(page)) {
return {
score: 100,
offending: [],
advice:
"The page uses an HTTP/3 connection and the headers are sent compressed, that's good. The current coach version cannot say if the size is good or not."
};
} else {

@@ -28,3 +35,3 @@ let limit = 20000;

let offending = [];
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
if (asset.headerSize > limit) {

@@ -31,0 +38,0 @@ offending.push(asset.url);

@@ -13,5 +13,5 @@ 'use strict';

processPage: function(page) {
processPage: function (page) {
let images = page.assets.filter(
asset => asset.type === 'image' || asset.type === 'svg'
(asset) => asset.type === 'image' || asset.type === 'svg'
);

@@ -22,3 +22,3 @@ let contentSize = 0;

images.forEach(function(image) {
images.forEach(function (image) {
contentSize += image.contentSize;

@@ -25,0 +25,0 @@ });

@@ -13,4 +13,4 @@ 'use strict';

processPage: function(page) {
let cssAssets = page.assets.filter(asset => asset.type === 'javascript');
processPage: function (page) {
let cssAssets = page.assets.filter((asset) => asset.type === 'javascript');
let transferSize = 0;

@@ -22,3 +22,3 @@ let contentSize = 0;

cssAssets.forEach(function(asset) {
cssAssets.forEach(function (asset) {
transferSize += asset.transferSize;

@@ -25,0 +25,0 @@ contentSize += asset.contentSize;

@@ -9,7 +9,7 @@ 'use strict';

tags: ['performance', 'bestpractice'],
processPage: function(page) {
processPage: function (page) {
let score = 100;
let offending = [];
page.assets.forEach(function(asset) {
if (asset.type == 'other' && (asset.status > 199 && asset.status < 300)) {
page.assets.forEach(function (asset) {
if (asset.type == 'other' && asset.status > 199 && asset.status < 300) {
score--;

@@ -16,0 +16,0 @@ offending.push(asset.url);

@@ -13,4 +13,4 @@ 'use strict';

processPage: function(page) {
let cssAssets = page.assets.filter(asset => asset.type === 'css');
processPage: function (page) {
let cssAssets = page.assets.filter((asset) => asset.type === 'css');
let transferLimit = 14500;

@@ -21,3 +21,3 @@ let score = 100;

cssAssets.forEach(function(asset) {
cssAssets.forEach(function (asset) {
if (asset.transferSize > transferLimit) {

@@ -24,0 +24,0 @@ score -= 10;

@@ -12,3 +12,3 @@ 'use strict';

tags: ['performance', 'mobile'],
processPage: function(page, domAdvice, options) {
processPage: function (page, domAdvice, options) {
let sizeLimit = options.mobile ? 1000000 : 2000000;

@@ -15,0 +15,0 @@ if (page.transferSize > sizeLimit) {

@@ -22,7 +22,7 @@ 'use strict';

tags: ['performance', 'server'],
processPage: function(page) {
processPage: function (page) {
let advice = '';
let score = 100;
let offending = [];
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
let myScore = processAsset(asset);

@@ -29,0 +29,0 @@ if (myScore > 0 && page.url !== asset.url) {

@@ -11,3 +11,3 @@ 'use strict';

processPage: function(page) {
processPage: function (page) {
let score = 100;

@@ -17,3 +17,3 @@ let offending = [];

let advice = '';
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
if (asset.status >= 400) {

@@ -32,3 +32,3 @@ offending.push(asset.url);

advice = 'The page has ' + offending.length + ' error response(s). ';
Object.keys(offendingCodes).forEach(function(errorCode) {
Object.keys(offendingCodes).forEach(function (errorCode) {
advice +=

@@ -35,0 +35,0 @@ 'The page has ' +

@@ -11,3 +11,3 @@ 'use strict';

tags: ['privacy', 'bestpractice', 'headers'],
processPage: function(page) {
processPage: function (page) {
const offending = [];

@@ -17,3 +17,3 @@ let score = 0;

const finalUrl = page.finalUrl;
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
if (asset.url === finalUrl) {

@@ -20,0 +20,0 @@ if (asset.headers.response['content-security-policy']) {

@@ -10,3 +10,3 @@ 'use strict';

tags: ['privacy'],
processPage: function(page) {
processPage: function (page) {
const offending = [];

@@ -17,3 +17,3 @@ let score = 100;

if (finalUrl.indexOf('https://') > -1) {
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
if (asset.url.indexOf('http://') > -1) {

@@ -20,0 +20,0 @@ score = 0;

@@ -11,3 +11,3 @@ 'use strict';

tags: ['headers', 'privacy'],
processPage: function(page) {
processPage: function (page) {
const offending = [];

@@ -17,3 +17,3 @@ let score = 0;

const finalUrl = page.finalUrl;
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
if (asset.url === finalUrl) {

@@ -20,0 +20,0 @@ if (asset.headers.response['referrer-policy']) {

@@ -11,3 +11,3 @@ 'use strict';

tags: ['headers', 'privacy'],
processPage: function(page) {
processPage: function (page) {
const offending = [];

@@ -19,3 +19,3 @@ let score = 100;

score;
page.assets.forEach(function(asset) {
page.assets.forEach(function (asset) {
if (asset.url === finalUrl) {

@@ -22,0 +22,0 @@ if (asset.headers.response['strict-transport-security']) {

@@ -13,3 +13,3 @@ 'use strict';

tags: ['privacy'],
processPage: function(page) {
processPage: function (page) {
const thirdParties = thirdParty.getThirdParty(page);

@@ -16,0 +16,0 @@

@@ -5,3 +5,3 @@ 'use strict';

module.exports.getThirdParty = function(page) {
module.exports.getThirdParty = function (page) {
const toolsByCategory = {};

@@ -18,7 +18,12 @@ const offending = [];

for (let asset of page.assets) {
const entity =
(asset && asset.url.indexOf('localhost') > -1) ||
(asset && !asset.url.startsWith('http'))
? undefined
: getEntity(asset.url);
let entity;
try {
entity =
(asset && asset.url.indexOf('localhost') > -1) ||
(asset && !asset.url.startsWith('http'))
? undefined
: getEntity(asset.url);
} catch (e) {
continue;
}
if (entity !== undefined) {

@@ -25,0 +30,0 @@ if (company && company.name === entity.name) {

@@ -9,2 +9,5 @@ 'use strict';

},
isHTTP3(page) {
return page.httpType.startsWith('h3');
},
/**

@@ -42,3 +45,3 @@ * Get the hostname from a URL string.

*/
prettyPrintSeconds: function(seconds) {
prettyPrintSeconds: function (seconds) {
if (typeof seconds === 'undefined') {

@@ -106,3 +109,3 @@ return '';

let clean = {};
Object.keys(node).forEach(function(key) {
Object.keys(node).forEach(function (key) {
clean[key] = pretty(node[key]);

@@ -109,0 +112,0 @@ });

@@ -6,2 +6,3 @@ 'use strict';

const path = require('path');
const thirdPartyWeb = require('third-party-web');
const merger = require('./merge').merge;

@@ -85,3 +86,9 @@ const merge = require('lodash.merge');

return pickAPage(har, pageIndex);
},
getThirdPartyWeb() {
return thirdPartyWeb;
},
getPageXray() {
return pagexray;
}
};

@@ -9,3 +9,3 @@ 'use strict';

Object.keys(results.advice).forEach(result => {
Object.keys(results.advice).forEach((result) => {
const category = results.advice[result];

@@ -17,3 +17,3 @@ var categoryScore = 0,

if (adviceList) {
Object.keys(adviceList).forEach(adviceName => {
Object.keys(adviceList).forEach((adviceName) => {
var advice = adviceList[adviceName];

@@ -20,0 +20,0 @@

{
"name": "coach-core",
"version": "5.1.1",
"version": "6.0.0-alpha.1",
"description": "Core package for the Coach.",

@@ -17,2 +17,5 @@ "keywords": [

},
"publishConfig": {
"tag": "canary"
},
"files": [

@@ -35,3 +38,3 @@ "lib",

"test:merge": "mocha --recursive test/merge",
"pretest": "npm run combine && npm run uglify",
"pretest": "npm run combine && npm run terser",
"test": "mocha --recursive test/api test/dom test/har test/merge",

@@ -43,9 +46,9 @@ "jsdoc": "jsdoc -R README.md -r -d dist/doc lib",

"travis": "npm run eslint-check && npm run lint && npm run test",
"prepare": "npm run combine && npm run uglify && npm run jsdoc",
"gen-dist": "npm run combine && npm run uglify",
"uglify": "uglifyjs dist/coach.js --compress side_effects=false,negate_iife=false --mangle > dist/coach.min.js"
"prepare": "npm run combine && npm run terser && npm run jsdoc",
"gen-dist": "npm run combine && npm run terser",
"terser": "terser dist/coach.js --compress side_effects=false,negate_iife=false --mangle > dist/coach.min.js"
},
"devDependencies": {
"browsertime": "8.1.2",
"bluebird": "3.5.5",
"browsertime": "10.6.5",
"bluebird": "3.7.2",
"bookmarkletify": "^1.0.0",

@@ -55,12 +58,12 @@ "chai": "^4.2.0",

"connect": "^3.7.0",
"eslint": "6.2.2",
"eslint-config-prettier": "6.1.0",
"eslint-plugin-prettier": "3.1.0",
"eslint": "7.13.0",
"eslint-config-prettier": "6.15.0",
"eslint-plugin-prettier": "3.1.4",
"http2": "3.3.7",
"jsdoc": "3.6.3",
"jsdoc": "3.6.6",
"mkdirp": "0.5.1",
"mocha": "6.2.0",
"prettier": "1.18.2",
"prettier": "2.1.2",
"serve-static": "1.14.1",
"uglify-js": "3.6.0"
"terser": "5.3.8"
},

@@ -85,7 +88,8 @@ "homepage": "https://www.sitespeed.io/documentation/coach/",

"lodash.groupby": "4.6.0",
"lodash.merge": "^4.6.2",
"lodash.merge": "4.6.2",
"lodash.sortby": "4.7.0",
"pagexray": "2.5.10",
"third-party-web": "0.12.2"
"pagexray": "2.6.0",
"third-party-web": "0.12.2",
"wappalyzer-core": "6.4.17"
}
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc