├── .eslintrc ├── .gitignore ├── .vscode └── settings.json ├── Gruntfile.js ├── README.md ├── build ├── codeview.html ├── css │ ├── application.css │ ├── normalize.css │ ├── panel.css │ ├── popup.css │ └── rules.css ├── default-rules │ ├── MIT-LICENSE.md │ ├── TODO.md │ ├── a-promotion-for-reviews.js │ ├── check-for-just-history-state-update.js │ ├── debug-stringify-page-object.js │ ├── dom-detect-client-side-rendering.js │ ├── dom-static-idle-parameterized-href.js │ ├── google-amp-cache-url.js │ ├── google-is-connected.js │ ├── google-search-console-is-indexed.js │ ├── google-search-console-webproperty-available.js │ ├── gsc-page-directory-worldwide-search-analytics.js │ ├── gsc-page-worldwide-search-analytics.js │ ├── gsc-top-queries-of-page.js │ ├── http-check-for-unavailable-after.js │ ├── http-detect-classic-deliver-from-cache.js │ ├── http-dom-check-common-mobile-setup.js │ ├── http-dom-detect-redirect-canonical-chains.js │ ├── http-gzip.js │ ├── http-has-http-header.js │ ├── http-has-link-header.js │ ├── http-hsts.js │ ├── http-http2-detection.js │ ├── http-sofft-404-check.js │ ├── http-status-code.js │ ├── http-vary-user-agent.js │ ├── http-x-cache-hit-miss.js │ ├── http-x-robots.js │ ├── idel-body-unsecure-input.js │ ├── idel-dom-node-count.js │ ├── idel-dom-node-depth.js │ ├── idle-body-linked-image-without-alt-tag-no-textnode.js │ ├── idle-body-nofollow.js │ ├── idle-dom-ldjson.js │ ├── idle-dom-top-words.js │ ├── idle-head-meta-keywords.js │ ├── idle-head-meta-viewport.js │ ├── idle-head-rel-alternate-media.js │ ├── idle-static-body-parameterized-links.js │ ├── mobile-friendly-test-async.js │ ├── robotstxt-exists-complexity-check.js │ ├── robotstxt-googlebot-url-check-v2.js │ ├── robotstxt-sitemap-reference.js │ ├── speed-dom-static-blocking-scripts.js │ ├── speed-first-paint.js │ ├── speed-page-speed-insights-v5-desktop-async.js │ ├── speed-page-speed-insights-v5-fcp-fid-mobile-async.js │ ├── speed-page-speed-insights-v5-mobile-async.js │ ├── speed-static-head-link-rel-preload.js │ ├── static-body-h1.js │ ├── static-head-amp.js │ ├── static-head-brand-in-title.js │ ├── static-head-canonical.js │ ├── static-head-link-shortlink.js │ ├── static-head-meta-description.js │ ├── static-head-meta-googlebot.js │ ├── static-head-meta-robots.js │ ├── static-head-open-graph-description.js │ ├── static-head-open-graph-title.js │ ├── static-head-open-graph-url.js │ ├── static-head-rel-alternate-hreflang-multipage-check.js │ ├── static-head-rel-alternate-hreflang.js │ ├── static-head-title-length.js │ ├── static-head-title.js │ ├── static-idle-dom-unavailable-after.js │ ├── static-idle-robotstxt-blocked-ressources.js │ ├── static-internal-links-check.js │ └── url-with-without-trailing-slash.js ├── images │ ├── _icon-inactive.png │ ├── _icon.png │ ├── icon-inactive.png │ └── icon.png ├── js │ ├── background.js │ ├── codeview.js │ ├── content_script.js │ ├── document_end.js │ ├── document_idle.js │ ├── document_start.js │ ├── panel.js │ ├── popup.js │ ├── rules.js │ ├── sandbox.js │ └── zepto.min.js ├── manifest.json ├── popup.html ├── rules.html └── sandbox.html ├── dist.zip ├── dist ├── codeview.html ├── css │ ├── application.css │ ├── normalize.css │ ├── panel.css │ ├── popup.css │ └── rules.css ├── default-rules │ ├── MIT-LICENSE.md │ ├── TODO.md │ ├── a-promotion-for-reviews.js │ ├── check-for-just-history-state-update.js │ ├── debug-stringify-page-object.js │ ├── dom-detect-client-side-rendering.js │ ├── dom-static-idle-parameterized-href.js │ ├── google-amp-cache-url.js │ ├── google-is-connected.js │ ├── google-search-console-is-indexed.js │ ├── google-search-console-webproperty-available.js │ ├── gsc-page-directory-worldwide-search-analytics.js │ ├── gsc-page-worldwide-search-analytics.js │ ├── gsc-top-queries-of-page.js │ ├── http-check-for-unavailable-after.js │ ├── http-detect-classic-deliver-from-cache.js │ ├── http-dom-check-common-mobile-setup.js │ ├── http-dom-detect-redirect-canonical-chains.js │ ├── http-gzip.js │ ├── http-has-http-header.js │ ├── http-has-link-header.js │ ├── http-hsts.js │ ├── http-http2-detection.js │ ├── http-sofft-404-check.js │ ├── http-status-code.js │ ├── http-vary-user-agent.js │ ├── http-x-cache-hit-miss.js │ ├── http-x-robots.js │ ├── idel-body-unsecure-input.js │ ├── idel-dom-node-count.js │ ├── idel-dom-node-depth.js │ ├── idle-body-linked-image-without-alt-tag-no-textnode.js │ ├── idle-body-nofollow.js │ ├── idle-dom-ldjson.js │ ├── idle-dom-top-words.js │ ├── idle-head-meta-keywords.js │ ├── idle-head-meta-viewport.js │ ├── idle-head-rel-alternate-media.js │ ├── idle-static-body-parameterized-links.js │ ├── mobile-friendly-test-async.js │ ├── robotstxt-exists-complexity-check.js │ ├── robotstxt-googlebot-url-check-v2.js │ ├── robotstxt-sitemap-reference.js │ ├── speed-dom-static-blocking-scripts.js │ ├── speed-first-paint.js │ ├── speed-page-speed-insights-v5-desktop-async.js │ ├── speed-page-speed-insights-v5-fcp-fid-mobile-async.js │ ├── speed-page-speed-insights-v5-mobile-async.js │ ├── speed-static-head-link-rel-preload.js │ ├── static-body-h1.js │ ├── static-head-amp.js │ ├── static-head-brand-in-title.js │ ├── static-head-canonical.js │ ├── static-head-link-shortlink.js │ ├── static-head-meta-description.js │ ├── static-head-meta-googlebot.js │ ├── static-head-meta-robots.js │ ├── static-head-open-graph-description.js │ ├── static-head-open-graph-title.js │ ├── static-head-open-graph-url.js │ ├── static-head-rel-alternate-hreflang-multipage-check.js │ ├── static-head-rel-alternate-hreflang.js │ ├── static-head-title-length.js │ ├── static-head-title.js │ ├── static-idle-dom-unavailable-after.js │ ├── static-idle-robotstxt-blocked-ressources.js │ ├── static-internal-links-check.js │ └── url-with-without-trailing-slash.js ├── export.zip ├── images │ ├── _icon-inactive.png │ ├── _icon.png │ ├── icon-inactive.png │ └── icon.png ├── js │ ├── background.js │ ├── codeview.js │ ├── content_script.js │ ├── document_end.js │ ├── document_idle.js │ ├── document_start.js │ ├── panel.js │ ├── popup.js │ ├── rules.js │ ├── sandbox.js │ └── zepto.min.js ├── manifest.json ├── popup.html ├── rules.html └── sandbox.html ├── package-lock.json ├── package.json ├── privacy.md ├── promotion ├── 1280-800-screenshot-f19n-lt-software-testing.png ├── 1280-800-screenshot-f19n-lt-tripadvisor-settings.png ├── 1280-800-screenshot-f19n-lt-tripadvisor.png ├── _old-Product-hunt.png ├── _old-olt-icon-128px.png ├── _old-olt-large-tile-960px.png ├── _old-olt-logo-144px.svg ├── _old-olt-marquee-tile-1400px.png ├── _old-olt-small-tile-440px.png ├── _old-results.png ├── _old-sample-output-2.png ├── _old-sample-output.png ├── _old-user-doku.png ├── f19n-lt-icon-128.png ├── f19n-lt-large-tile-960px-01.png ├── f19n-lt-logo-144px.svg ├── f19n-lt-marquee-tile-1400px-01.png ├── f19n-lt-small-tile-440px-01.png ├── screenshot-f19n-lt-manage-rules-user-doku.png ├── screenshot-f19n-lt-panel-github.png ├── screenshot-f19n-lt-settings.png ├── screenshot-f19n-lt-software-testing.png ├── screenshot-f19n-lt-tripadvisor-settings.png ├── screenshot-f19n-lt-tripadvisor.png ├── screenshot-f19n-lt-user-doku.png ├── screenshot-f19n-lt-wikipedia-user-doku.png └── src │ ├── _old-olt-large-tile-960px.ai │ ├── _old-olt-logo-144px.ai │ ├── _old-olt-marquee-tile-1400px.ai │ ├── _old-olt-small-tile-440px.ai │ ├── f19n-lt-large-tile-960px.ai │ ├── f19n-lt-logo-144px.ai │ ├── f19n-lt-marquee-tile-1400px.ai │ └── f19n-lt-small-tile-440px.ai ├── sample-rules ├── MIT-LICENSE.md ├── README.md ├── a-promotion-for-my-awesome-ebook-Q4-2018.js ├── async-detect-language-api-google.js ├── debug-hello-world-with-comments.js ├── debug-hello-world.js ├── debug-stringify-page-object.js ├── deprecated │ ├── asyncRuleTest.js │ ├── asyncRuleTest2.js │ ├── body-nofollow-links.js │ ├── brokenRule.js │ ├── checkForPreconnect.js │ ├── coffee-rules │ │ ├── amp.coffee │ │ └── amp.js │ ├── configurableRuleTest.js │ ├── contentEncodingNotGzip.js │ ├── deprecated_hasCanonicalTags.js │ ├── documentSize.js │ ├── http-has-unavailable-after-header.js │ ├── robotstxt-googlebot-url-check.js │ ├── site-blocked-by-robotstxt-googlebot.js │ ├── site-http-soft-404-check.js │ ├── site-robotstxt-check.js │ ├── speed-page-speed-insights-desktop-async.js │ ├── speed-page-speed-insights-mobile-async.js │ ├── static-head-meta-description-length.js │ ├── static-head-meta-robots-deprecated.js │ ├── static-head-title-length.js │ ├── static-html-schemaorg-existence.js │ ├── static-idle-body-text-compare.js │ ├── static-idle-head-title-compare.js │ ├── statusCode.js │ ├── statusCodeNot200.js │ ├── titletag.js │ └── xRobotsTags.js ├── fb-share-count.js ├── google-api-token.js ├── not-working │ └── idle-body-font-size.js ├── old-new-v5-psi.js ├── sample-page-object-25112017.json └── speed-page-speed-insights-v5-fcp-fid-desktop-async.js ├── src ├── javascripts │ ├── background.js │ ├── codeview.js │ ├── components │ │ ├── Panel │ │ │ ├── Footer.jsx │ │ │ ├── Panel.jsx │ │ │ ├── ResultItem.jsx │ │ │ └── ResultList.jsx │ │ ├── Popup │ │ │ └── Popup.jsx │ │ └── Rules │ │ │ ├── AddRule.js │ │ │ ├── EnabledSites.js │ │ │ ├── GlobalRuleVariables.js │ │ │ ├── GoogleApiOauth.js │ │ │ ├── RuleListItem.js │ │ │ ├── Rules.js │ │ │ ├── RulesList.js │ │ │ └── ViewRule.js │ ├── config.js │ ├── content_script.js │ ├── document_end.js │ ├── document_idle.js │ ├── document_start.js │ ├── panel.js │ ├── popup.js │ ├── rules.js │ ├── sandbox.js │ ├── store │ │ └── rules.js │ └── utils │ │ ├── EventCollection.js │ │ ├── EventCollector.js │ │ ├── RuleContext.js │ │ ├── Sandbox.js │ │ ├── activeForTab.js │ │ ├── configurableRules.js │ │ ├── extensionLiveReload.js │ │ ├── resultStoreKey.js │ │ └── syncDefaultRules.js ├── public │ ├── codeview.html │ ├── css │ │ └── normalize.css │ ├── default-rules │ │ ├── MIT-LICENSE.md │ │ ├── TODO.md │ │ ├── a-promotion-for-reviews.js │ │ ├── check-for-just-history-state-update.js │ │ ├── debug-stringify-page-object.js │ │ ├── dom-detect-client-side-rendering.js │ │ ├── dom-static-idle-parameterized-href.js │ │ ├── google-amp-cache-url.js │ │ ├── google-is-connected.js │ │ ├── google-search-console-is-indexed.js │ │ ├── google-search-console-webproperty-available.js │ │ ├── gsc-page-directory-worldwide-search-analytics.js │ │ ├── gsc-page-worldwide-search-analytics.js │ │ ├── gsc-top-queries-of-page.js │ │ ├── http-check-for-unavailable-after.js │ │ ├── http-detect-classic-deliver-from-cache.js │ │ ├── http-dom-check-common-mobile-setup.js │ │ ├── http-dom-detect-redirect-canonical-chains.js │ │ ├── http-gzip.js │ │ ├── http-has-http-header.js │ │ ├── http-has-link-header.js │ │ ├── http-hsts.js │ │ ├── http-http2-detection.js │ │ ├── http-sofft-404-check.js │ │ ├── http-status-code.js │ │ ├── http-vary-user-agent.js │ │ ├── http-x-cache-hit-miss.js │ │ ├── http-x-robots.js │ │ ├── idel-body-unsecure-input.js │ │ ├── idel-dom-node-count.js │ │ ├── idel-dom-node-depth.js │ │ ├── idle-body-linked-image-without-alt-tag-no-textnode.js │ │ ├── idle-body-nofollow.js │ │ ├── idle-dom-ldjson.js │ │ ├── idle-dom-top-words.js │ │ ├── idle-head-meta-keywords.js │ │ ├── idle-head-meta-viewport.js │ │ ├── idle-head-rel-alternate-media.js │ │ ├── idle-static-body-parameterized-links.js │ │ ├── mobile-friendly-test-async.js │ │ ├── robotstxt-exists-complexity-check.js │ │ ├── robotstxt-googlebot-url-check-v2.js │ │ ├── robotstxt-sitemap-reference.js │ │ ├── speed-dom-static-blocking-scripts.js │ │ ├── speed-first-paint.js │ │ ├── speed-page-speed-insights-v5-desktop-async.js │ │ ├── speed-page-speed-insights-v5-fcp-fid-mobile-async.js │ │ ├── speed-page-speed-insights-v5-mobile-async.js │ │ ├── speed-static-head-link-rel-preload.js │ │ ├── static-body-h1.js │ │ ├── static-head-amp.js │ │ ├── static-head-brand-in-title.js │ │ ├── static-head-canonical.js │ │ ├── static-head-link-shortlink.js │ │ ├── static-head-meta-description.js │ │ ├── static-head-meta-googlebot.js │ │ ├── static-head-meta-robots.js │ │ ├── static-head-open-graph-description.js │ │ ├── static-head-open-graph-title.js │ │ ├── static-head-open-graph-url.js │ │ ├── static-head-rel-alternate-hreflang-multipage-check.js │ │ ├── static-head-rel-alternate-hreflang.js │ │ ├── static-head-title-length.js │ │ ├── static-head-title.js │ │ ├── static-idle-dom-unavailable-after.js │ │ ├── static-idle-robotstxt-blocked-ressources.js │ │ ├── static-internal-links-check.js │ │ └── url-with-without-trailing-slash.js │ ├── images │ │ ├── _icon-inactive.png │ │ ├── _icon.png │ │ ├── icon-inactive.png │ │ └── icon.png │ ├── js │ │ └── zepto.min.js │ ├── manifest.json │ ├── popup.html │ ├── rules.html │ └── sandbox.html └── stylesheets │ ├── _base.scss │ ├── _button.scss │ ├── _definitions.scss │ ├── _highlight-javascript.scss │ ├── application.sass │ ├── panel.sass │ ├── popup.sass │ └── rules.sass ├── temp.js ├── test └── stuff.html └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "globals": { 5 | "chrome": true, 6 | }, 7 | "rules": { 8 | "no-console": 0, 9 | "no-empty-label": 0, 10 | "no-labels": 0, 11 | "react/prop-types": 0, 12 | "no-constant-condition": 0, 13 | "max-len": 0, 14 | "arrow-body-style": 0, 15 | "new-cap": 0, 16 | "no-eval": 0, 17 | "no-alert": 0, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/*.log 16 | /tmp 17 | .env 18 | 19 | /.ruby-version 20 | node_modules 21 | /.tmp 22 | npm-debug.log 23 | /.idea 24 | 25 | .DS_Store 26 | 27 | src/.DS_Store 28 | 29 | src/.DS_Store 30 | 31 | 32 | package-lock.json 33 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/node_modules": true, 4 | "**/bower_components": true, 5 | "**/build": true 6 | }, 7 | "editor.formatOnSave": false 8 | } 9 | -------------------------------------------------------------------------------- /build/codeview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | f19n codeview 4 | 5 | 6 | 7 |
 
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /build/css/application.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; } 3 | 4 | *, *::after, *::before { 5 | box-sizing: inherit; } 6 | -------------------------------------------------------------------------------- /build/default-rules/MIT-LICENSE.md: -------------------------------------------------------------------------------- 1 | This License applies to all files and all code within this source directory. 2 | 3 | The MIT License (MIT) 4 | ===================== 5 | 6 | Copyright © `2016` `Franz Enzenhofer` 7 | 8 | Permission is hereby granted, free of charge, to any person 9 | obtaining a copy of this software and associated documentation 10 | files (the “Software”), to deal in the Software without 11 | restriction, including without limitation the rights to use, 12 | copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following 15 | conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /build/default-rules/TODO.md: -------------------------------------------------------------------------------- 1 | finish idle-body-linked-image-without-alt-tag-no-textnode 2 | 3 | schema.org json ld detect 4 | strucured data summary 5 | -------------------------------------------------------------------------------- /build/default-rules/a-promotion-for-reviews.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | let lable = "PLZ"; 5 | let msg = 'Wohoo, more than 1000 daily active users! So, so cool! But only 20 reviews 😢. Please review this app in the Google Chrome Store! 👍 Thx!
You can disable this message in the Settings.'; 6 | let type = "promotion"; 7 | let what = ""; 8 | 9 | done(that.createResult(lable, msg, type, what));return; 10 | 11 | 12 | } -------------------------------------------------------------------------------- /build/default-rules/check-for-just-history-state-update.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | const all_header_received_events = page.eventsOfType('onHeadersReceived'); 6 | const all_on_completed = page.eventsOfType('onCompleted'); 7 | const all_onHistoryStateUpdated_events = page.eventsOfType('onHistoryStateUpdated'); 8 | 9 | let lable = "HTTP"; 10 | let msg = "Page delivered an webapp internal JS logic (URL change via JS History (URL) State update)! Most rules will fail. Try ⇧+↻ for hard refresh. This is not an error with this page, but with the test execution of this app."; 11 | let type = "error"; //we set error as we need this response at the top 12 | let what = ""; 13 | 14 | 15 | 16 | if(all_header_received_events.length>0||all_on_completed.length>0) 17 | { 18 | done();return; 19 | } 20 | else if(all_onHistoryStateUpdated_events.length>0) 21 | { 22 | //we go with the default values 23 | } 24 | else 25 | { 26 | msg = "Unkown error! Could not collect the lifecycle of the page. Collected data:"+this.stringifyLink(page); 27 | type = "error"; 28 | } 29 | done(that.createResult(lable, msg, type, what, 1000));return; 30 | } 31 | -------------------------------------------------------------------------------- /build/default-rules/debug-stringify-page-object.js: -------------------------------------------------------------------------------- 1 | function(page,done){ 2 | var debug = '%DEBUG%'; //must be set to true 3 | if(debug==='true') 4 | { 5 | done(this.createResult("DEBUG", "Complete Page Object "+this.stringifyLink(page), "info", null, 9000)); 6 | } 7 | done(); 8 | return null; 9 | } -------------------------------------------------------------------------------- /build/default-rules/dom-detect-client-side-rendering.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | //var static_dom = page.getFetchedStaticDom(); 3 | var idle_dom = page.getIdleDom(); 4 | var url = page.getURL("last"); 5 | 6 | this.fetch(url, { responseFormat: 'text' }, (response) => { 7 | var parser = new DOMParser(); 8 | var static_dom = parser.parseFromString(response.body, "text/html"); 9 | var status = 'info'; 10 | var diff_size = (idle_dom.head.innerHTML.length+idle_dom.head.innerHTML.length)/(static_dom.head.innerHTML.length+static_dom.head.innerHTML.length) 11 | var msg = ''; 12 | if(diff_size > 1) 13 | { 14 | var diff_percentage = Math.round((diff_size -1) * 100); 15 | msg = "Idle-DOM is "+diff_percentage+"% bigger then the Static-DOM."; 16 | } 17 | else if (diff_size>1) 18 | { 19 | var diff_percentage = Math.round((1 - diff_size) * 100); 20 | msg = "Idle-DOM removed about "+diff_percentage+"% from Static-DOM." 21 | } 22 | else { 23 | msg = "Idle-DOM equals Static-DOM. Either static page or server side rendering cache." 24 | } 25 | 26 | if (diff_percentage >= 20) 27 | { 28 | msg = "Client Side Rendering! "+msg; 29 | status = "warning"; 30 | } 31 | 32 | done(this.createResult('DOM', msg, status, null, 800)); 33 | }); 34 | //if(!(static_dom && idle_dom)){ return null; } 35 | //wait forever 36 | //done(); 37 | } 38 | -------------------------------------------------------------------------------- /build/default-rules/google-amp-cache-url.js: -------------------------------------------------------------------------------- 1 | (page, done) => 2 | { 3 | 4 | var that = this; 5 | 6 | let lable = "Amp"; 7 | let msg = ""; 8 | let type = "info"; 9 | let what = ""; 10 | 11 | let url = page.getURL('first'); 12 | 13 | const globals = that.getGlobals(); 14 | //if no google api key we fail silently 15 | if(!globals.variables.google_page_speed_insights_key) 16 | { 17 | done(); 18 | return null; 19 | } 20 | let api = "https://acceleratedmobilepageurl.googleapis.com/v1/ampUrls:batchGet?key="; 21 | api = api + globals.variables.google_page_speed_insights_key; 22 | 23 | fetch(api, 24 | { 25 | method: "POST", 26 | mode: 'cors', 27 | headers: new Headers( 28 | { 29 | 'Content-Type': 'application/json', 30 | 'Accept': 'application/json' 31 | }), 32 | body: JSON.stringify({ 33 | "lookupStrategy": "IN_INDEX_DOC", 34 | "urls": [ 35 | url 36 | ] 37 | }) 38 | }).then(function(response) 39 | { 40 | 41 | 42 | if(response.status == 200) 43 | { 44 | response.json().then(function(data) 45 | { 46 | 47 | 48 | }); 49 | } 50 | }).catch(function(err){ 51 | 52 | 53 | }); 54 | 55 | done(); 56 | return null; 57 | } -------------------------------------------------------------------------------- /build/default-rules/google-is-connected.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | 4 | var that = this; 5 | let lable = "GSC"; 6 | let msg = ""; 7 | let type = "warning"; 8 | let what = null; 9 | let prio = null; 10 | 11 | //GSC API 12 | const { googleApiAccessToken } = this.getGlobals(); 13 | if(!googleApiAccessToken) 14 | { 15 | msg = "To get the most out of this app, connect it with Google Search Console and Google Analytics. "+'Settings.'; 16 | done(that.createResult(lable, msg, type, what, prio)); 17 | return null; 18 | } 19 | done(); 20 | return null; 21 | } 22 | -------------------------------------------------------------------------------- /build/default-rules/http-check-for-unavailable-after.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | //var hh = page.getHttpHeaders("last"); 5 | var hr = page.getRawHttpHeaders("last"); 6 | var hr_stringy = JSON.stringify(hr, null, 2); 7 | let lable = "HTTP"; 8 | let msg = "Unavailable after HTTP header found."; 9 | let type = "warning"; 10 | 11 | if (hr_stringy.search(/unavailable_after/ig)===-1) 12 | { 13 | done();return; 14 | } 15 | msg = msg + that.partialCodeLink(hr); 16 | 17 | 18 | done(that.createResult(lable, msg, type));return; 19 | } 20 | -------------------------------------------------------------------------------- /build/default-rules/http-detect-classic-deliver-from-cache.js: -------------------------------------------------------------------------------- 1 | function(page,done) 2 | { 3 | const on_completed = page.firstEventOfType('onCompleted'); 4 | if(on_completed && on_completed.fromCache && on_completed.fromCache===true) 5 | { 6 | done(this.createResult('HTTP', 'Page delivered via browser-cache! Some rules might not report or might fail. Try ⇧+↻ for hard refresh.', 'warning', null, 1000)); 7 | } 8 | done();return; 9 | } -------------------------------------------------------------------------------- /build/default-rules/http-dom-check-common-mobile-setup.js: -------------------------------------------------------------------------------- 1 | function(page, done){ 2 | var hh = page.getHttpHeaders("last"); 3 | 4 | if(hh) 5 | { 6 | var vary = hh['vary'] || ""; 7 | var isvary = function(){if(vary.toLowerCase().indexOf("agent")===-1){ return false} return true}(); 8 | } 9 | else 10 | { 11 | var vary = ""; 12 | var isvary = false; 13 | } 14 | var u = page.getURL("last"); 15 | var idle_dom = page.getIdleDom(); 16 | var medias = idle_dom.querySelectorAll("link[rel=alternate][media][href]"); 17 | var meta_viewports = idle_dom.querySelectorAll('meta[name=viewport]'); 18 | 19 | if(!(isvary || (medias && medias.length>0) || (meta_viewports && meta_viewports.length > 0))) 20 | { 21 | done(this.createResult('HTTP DOM', "No common mobile setup (responsive, dynamic serving, different URL) discovered! Mobile Friendly Test", "error")); 22 | return null; 23 | } 24 | done(); 25 | } 26 | -------------------------------------------------------------------------------- /build/default-rules/http-gzip.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var u = page.getURL("last"); 5 | 6 | var gzip = true; 7 | var brotli = true; 8 | 9 | if(!hh){done();return;} //wenn keine header, keine aussage 10 | 11 | var encoding = hh['content-encoding'] || ""; 12 | if (encoding.indexOf('gzip')===-1) 13 | { 14 | gzip = false; 15 | } 16 | 17 | if (encoding.indexOf('br')===-1) 18 | { 19 | brotli = false; 20 | } 21 | 22 | if(brotli == false && gzip == false) 23 | { 24 | done(this.createResult('HTTP', u+" no GZIP/Brotli compression! - Test"+ this.partialCodeLink(hr), 'error')); 25 | } 26 | 27 | if(brotli) 28 | { 29 | done(this.createResult('HTTP', " Brotli compression detected! - Test"+ this.partialCodeLink(hr), 'info')); 30 | } 31 | 32 | if(gzip) 33 | { 34 | done(this.createResult('HTTP', " GZIP compression detected! - Test"+ this.partialCodeLink(hr), 'info')); 35 | } 36 | 37 | done(); 38 | } 39 | -------------------------------------------------------------------------------- /build/default-rules/http-has-http-header.js: -------------------------------------------------------------------------------- 1 | (page, done) => { 2 | var hh = page.getHttpHeaders("last"); 3 | //var staticdom = page.getDom(); 4 | if(!hh){ 5 | done(this.createResult('HTTP', "No HTTP-header found, most likely due to caching! HTTP-header depending tests might fail or not get reported!", 'warning')); 6 | } 7 | done(); 8 | } 9 | -------------------------------------------------------------------------------- /build/default-rules/http-has-link-header.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var l = hh['link'] || false; 5 | let msg = ""; 6 | let type = "info"; 7 | let msgA = []; 8 | var temp_msg_partial = ''; 9 | if (l) 10 | { 11 | msg = '"Link:" HTTP header detected' 12 | if(l.toLowerCase().includes('shortlink')) 13 | { 14 | type="warning"; 15 | msgA.push("Shortlink"); 16 | } 17 | if(l.toLowerCase().includes('canonical')) 18 | { 19 | type="warning"; 20 | msgA.push("Canonical"); 21 | } 22 | if(l.toLowerCase().includes('alternate')) 23 | { 24 | msgA.push("Alternate"); 25 | } 26 | if(msgA.length>0) 27 | { 28 | temp_msg_partial = " ("+msgA.join(',')+")"; 29 | } 30 | msg=msg+temp_msg_partial+"."+this.partialCodeLink(hr); 31 | } 32 | done(this.createResult("HTTP", msg, type));return; 33 | } 34 | -------------------------------------------------------------------------------- /build/default-rules/http-hsts.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var u = page.getURL("last"); 5 | 6 | var hsts_set = true; 7 | var hsts_duration = ""; 8 | var color = 'lightgreen'; 9 | var type = 'info'; 10 | 11 | if(!hh){done();return;} //wenn keine header, keine aussage 12 | 13 | var hsts = hh['strict-transport-security'] || ""; 14 | 15 | if (hsts.indexOf('max-age')===-1) 16 | { 17 | hsts_set = false; 18 | } else { 19 | var max_age = hsts.match(/max-age=(\d+)/); 20 | 21 | if (max_age) { 22 | hsts_duration = max_age[0]; 23 | if (max_age[1] < 31536000) { 24 | color = 'orange'; 25 | type = 'warning'; 26 | } 27 | } 28 | } 29 | 30 | 31 | if(hsts_set == false) 32 | { 33 | done(this.createResult('HTTP', "No HSTS header set! - Test"+ this.partialCodeLink(hr), 'warning')); 34 | } 35 | 36 | if(hsts_set == true) 37 | { 38 | done(this.createResult('HTTP', "HSTS header is set. - with a duration of: " + hsts_duration + "" + this.partialCodeLink(hr), type)); 39 | } 40 | 41 | done(); 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /build/default-rules/http-http2-detection.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var t = page.getWindowPerformanceNavigation(); 3 | var p = page.getLocation().protocol; 4 | 5 | var is_https = false; 6 | if(p == "https:"){ is_https = true; } 7 | 8 | if (t) { 9 | var prot = t.nextHopProtocol 10 | var is_h2 = false; 11 | if(['h2', 'hq'].includes(prot)) { is_h2 = true; } 12 | if(is_https == true && is_h2 == false) 13 | { 14 | return done(this.createResult('HTTP', "Network protocol: "+prot+" (but https protocol)", 'warning')); 15 | } 16 | 17 | return done(this.createResult('HTTP', "Network protocol: "+prot, 'info', null, 400)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /build/default-rules/http-status-code.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var sc = page.getStatusCode(); 3 | var rawHttpHeader = page.getRawHttpHeaders(); 4 | var url = page.getURL(); 5 | var text = ""; 6 | if (sc) { 7 | var type = 'info'; 8 | var anchor = ''; 9 | if (sc < 200) { type = 'warning'; anchor='#1xx_Informational_response';} 10 | if (sc <= 200) { anchor='#2xx_Success';} 11 | if (sc >= 300) { type = 'warning'; anchor='#3xx_Redirection'; } 12 | if (sc === 304) { 13 | type = 'info'; 14 | text = `${url} → HTTP ${sc}`; 15 | } 16 | if (sc >= 400) { type = 'error'; anchor="#4xx_Client_errors";} 17 | if (sc >= 500) { type ='error'; anchor="#5xx_Server_errors" } 18 | if(text==='') 19 | { 20 | text = `${url} → HTTP ${sc}`; 21 | } 22 | text = text + this.partialCodeLink(rawHttpHeader); 23 | done(this.createResult('HTTP', text, type, null, 700)); return; 24 | } 25 | done(); 26 | } 27 | -------------------------------------------------------------------------------- /build/default-rules/http-vary-user-agent.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var u = page.getURL("last"); 5 | var v = hh['vary'] || false; 6 | if (v!==false) 7 | { 8 | if(v.toLowerCase().indexOf('agent')!==-1) 9 | { 10 | done(this.createResult('HTTP', " Vary: "+v+" - Dynamic serving detected." +this.partialCodeLink(hr), 'warning')); 11 | return null; 12 | } 13 | } 14 | done(); 15 | } 16 | -------------------------------------------------------------------------------- /build/default-rules/http-x-cache-hit-miss.js: -------------------------------------------------------------------------------- 1 | function(page,done) 2 | { 3 | let that = this; 4 | let h = page.getHttpHeaders('last'); 5 | let hh = page.getRawHttpHeaders('last'); 6 | let msgA = []; 7 | if(h['x-cache']) 8 | { 9 | if(h['x-cache'].toLowerCase().includes('hit')) 10 | { 11 | msgA.push('Delivered via cache. X-Cache: '+h['x-cache']); 12 | } 13 | else if(h['x-cache'].toLowerCase().includes('miss')) 14 | { 15 | msgA.push('Cache missed. X-Cache: '+h['x-cache']); 16 | } 17 | } 18 | if(h['x-cache-lookup']) 19 | { 20 | if(h['x-cache-lookup'].toLowerCase().includes('hit')) 21 | { 22 | msgA.push('X-Cache-Lookup: '+h['x-cache-lookup']); 23 | } 24 | else if(h['x-cache-lookup'].toLowerCase().includes('miss')) 25 | { 26 | msgA.push('Cache Lookupm missed. X-Cache-Lookup: '+h['x-cache-lookup']); 27 | } 28 | } 29 | if(msgA.length>0) 30 | { 31 | //msgA[msgA.length-1]+that.partialCodeLink(hh); 32 | msgA.push(that.partialCodeLink(hh)); 33 | done(that.createResult('HTTP', msgA.join(' ')));return; 34 | } 35 | done();return; 36 | } -------------------------------------------------------------------------------- /build/default-rules/http-x-robots.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var u = page.getURL("last"); 5 | var xr = hh['x-robots-tag'] || false; 6 | if (xr!==false) 7 | { 8 | done(this.createResult('HTTP', "X-Robots-Tag HTTP Header: "+xr+ this.partialCodeLink(hr), 'warning')); 9 | } 10 | done(); 11 | } 12 | -------------------------------------------------------------------------------- /build/default-rules/idel-body-unsecure-input.js: -------------------------------------------------------------------------------- 1 | function(page,done) 2 | { 3 | let that=this; 4 | let p = page.getLocation('idle').protocol; 5 | 6 | if(p!=='http:'){ done();return; } 7 | let dom = page.getIdleDom(); 8 | let selector = 'input,textarea'; 9 | let inputs = dom.querySelectorAll(selector); 10 | if(inputs.length>0) 11 | { 12 | let text = "Unsecure http:// connection (not https://) with user data elements."+that.partialCodeLink(inputs)+that.highlightLink(selector); 13 | let type = 'warning'; 14 | let what = 'idle'; 15 | done(that.createResult('BODY', text, type, what)); return; 16 | } 17 | done();return; 18 | } 19 | -------------------------------------------------------------------------------- /build/default-rules/idel-dom-node-count.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var idle_dom = page.getIdleDom(); 3 | var nr_dom_nodes = idle_dom.getElementsByTagName("*").length; 4 | var type = "info"; 5 | var text = "Total DOM Nodes ~"+nr_dom_nodes+"."; 6 | var comment = ""; 7 | if(nr_dom_nodes > 1500) 8 | { 9 | comment = "Pretty big DOM!"; 10 | type="warning"; 11 | } 12 | 13 | if(nr_dom_nodes > 3000) 14 | { 15 | comment = "Excessive DOM!"; 16 | type="error"; 17 | } 18 | done(this.createResult("DOM", text+" "+comment, type, "Idle")); 19 | } 20 | 21 | 22 | /* 23 | [].forEach.call($$('*'), 24 | function (a) { 25 | a.style.outline = '1px solid #' + (~~(Math.random() * (1 << 24))).toString(16); 26 | }); 27 | */ -------------------------------------------------------------------------------- /build/default-rules/idel-dom-node-depth.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | 3 | function countDomDepth(main) { 4 | var depth = 0; 5 | var loop = function(main, d) { 6 | 7 | var temp = d; 8 | do { 9 | 10 | if(main.nodeType == 1) 11 | { 12 | d = d + 1; 13 | 14 | } 15 | if(main.hasChildNodes()){ 16 | loop(main.firstChild,d); 17 | } 18 | 19 | if(d>depth){ depth = d; } 20 | d = temp; 21 | } 22 | while (main = main.nextSibling); 23 | } 24 | loop(main, 0); 25 | return depth-1; 26 | } 27 | 28 | var idle_dom = page.getIdleDom(); 29 | var depth = countDomDepth(idle_dom.querySelector(':root')) 30 | var type = "info"; 31 | var text = "DOM Depth count: "+depth+""; 32 | var comment = "(Good value ~10!)"; 33 | if(depth > 20) 34 | { 35 | comment = "Deeply nested DOM! (Good value ~10! OK until ~20!)"; 36 | type="warning"; 37 | } 38 | 39 | if(depth > 32) 40 | { 41 | comment = "Excessivly nested DOM! (Good value ~10, OK until ~20! >32 harmful!)"; 42 | type="error"; 43 | } 44 | done(this.createResult("DOM", text+" "+comment, type, "Idle")); 45 | } 46 | -------------------------------------------------------------------------------- /build/default-rules/idle-body-linked-image-without-alt-tag-no-textnode.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var idom = page.getIdleDom(); 3 | let selector = "a>img[alt='']"; 4 | var elems = idom.querySelectorAll(selector); 5 | if (elems && elems.length>1) 6 | { 7 | var image_s = "images"; 8 | if (elems.length===1) { image_s = "image"; } 9 | var msg = elems.length+" linked "+image_s+" without alt-text or other linked text found."+this.partialCodeLink(elems)+this.highlightLink(selector); 10 | done(this.createResult('BODY',msg,'warning','idle')); 11 | } 12 | done(); 13 | } 14 | -------------------------------------------------------------------------------- /build/default-rules/idle-body-nofollow.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "BODY"; 6 | let msg = ""; 7 | let type = "warning"; 8 | let what = "idle"; 9 | 10 | let selector = "*[rel*=nofollow]"; 11 | 12 | let dom = page.getIdleDom(); 13 | let n = dom.querySelectorAll(selector); 14 | if(n.length>0) 15 | { 16 | msg = msg + n.length+" elements with rel=nofollow found."+that.partialCodeLink(n)+that.highlightLink(selector, "Highlight Nofollow"); 17 | //msg = msg + " Highlight Nofollow"; 18 | done(that.createResult(lable, msg, type, what));return; 19 | } 20 | done();return; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /build/default-rules/idle-dom-ldjson.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "DOM"; 6 | let msg = ""; 7 | let type = "info"; 8 | let what = "Idle"; 9 | 10 | let selector = "script[type*=json]"; 11 | 12 | let dom = page.getIdleDom(); 13 | let n = dom.querySelectorAll(selector); 14 | if(n.length>0) 15 | { 16 | let elements = "elements"; 17 | if(n.length===1){elements = "element";}; 18 | msg = msg + n.length+" Ld-Json Script "+elements+" found."+that.partialCodeLink(n); 19 | done(that.createResult(lable, msg, type, what, 600));return; 20 | } 21 | 22 | done();return; 23 | 24 | } -------------------------------------------------------------------------------- /build/default-rules/idle-dom-top-words.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | function getFrequency2(string, cutOff, minlength) { 3 | if (!minlength) { 4 | minlength = 0 5 | } 6 | var cleanString = string.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`~()<>"']/g, " "), 7 | words = cleanString.split(' '), 8 | frequencies = {}, 9 | word, frequency, i; 10 | for (i = 0; i < words.length; i++) { 11 | word = words[i].trim(); 12 | if (word.length >= minlength) { 13 | frequencies[word] = frequencies[word] || 0; 14 | frequencies[word]++; 15 | } 16 | } 17 | words = Object.keys(frequencies); 18 | return words.sort(function(a, b) { 19 | return frequencies[b] - frequencies[a]; 20 | }).slice(0, cutOff); 21 | } 22 | var dom = page.getIdleDom(); 23 | if (dom) { 24 | var parser = new DOMParser(); 25 | var doc = parser.parseFromString(dom.body.innerText, "text/html"); 26 | var wA = getFrequency2(doc.body.innerText, 1000, 5); 27 | var top10 = wA.slice(0, 10).join(', '); 28 | done(this.createResult('DOM', 'Top words (5 or more chars): ' + top10 + ' - ' + this.partialTextLink('more', wA), 'info', 'idle')); 29 | } 30 | } -------------------------------------------------------------------------------- /build/default-rules/idle-head-meta-keywords.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getIdleDom(); 3 | const what = 'idle'; 4 | var lable = 'HEAD'; 5 | //if (some requirement) 6 | var meta_ks = dom.querySelectorAll('meta[name="keywords"]'); 7 | if (meta_ks.length > 0) { 8 | 9 | if(meta_ks > 1) 10 | { 11 | done(this.createResult(lable, 'Multiple unnecessary meta keywords tags found.'+this.partialCodeLink(meta_ks), 'warning', what)); 12 | } 13 | 14 | //some category of stuff your are testing i.e.: 'DOM', 'HEAD', 'BODY', 'HTTP', 'SPEED', ... 15 | 16 | var msg = 'Unnecessary meta keywords tag: '+meta_ks[0]['content']; 17 | //you can create a link showing only the partial code of a nodeList 18 | //msg = msg+' '+this.partialCodeLink(dom); 19 | msg = msg+this.partialCodeLink(meta_ks); 20 | 21 | var type = 'warning'; //should be 'info', 'warning', 'error' 22 | 23 | done(this.createResult(lable, msg, type, what)); 24 | } 25 | done(); 26 | } 27 | -------------------------------------------------------------------------------- /build/default-rules/idle-head-meta-viewport.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var idle_dom = page.getIdleDom(); 3 | var meta_viewports = idle_dom.querySelectorAll('meta[name=viewport]'); 4 | if(meta_viewports && meta_viewports.length > 0) 5 | { 6 | done(this.createResult("HEAD","Responsive design meta viewport tag ("+meta_viewports.length+") discovered."+this.partialCodeLink(meta_viewports), "info", "Idle")); 7 | return; 8 | } 9 | done();return; 10 | } 11 | -------------------------------------------------------------------------------- /build/default-rules/idle-head-rel-alternate-media.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var idle_dom = page.getIdleDom(); 3 | var medias = idle_dom.querySelectorAll("link[rel=alternate][media][href]"); 4 | if(medias && medias.length > 0) 5 | { 6 | var murl = ""; 7 | var more = "("+medias.length+")"; 8 | if (medias.length===1) 9 | { 10 | murl = medias[0].href.trim(); 11 | more = ""+murl+"" 12 | } 13 | done(this.createResult("HEAD","Seperate mobile URL "+more+" discovered."+this.partialCodeLink(medias), "warning", "Idle")); 14 | } 15 | done(); 16 | } 17 | -------------------------------------------------------------------------------- /build/default-rules/idle-static-body-parameterized-links.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "BODY"; 6 | let msg = ""; 7 | let type = "info"; 8 | let what = "idle, static"; 9 | 10 | let selector = "a[href*='?']"; 11 | 12 | let dom = page.getIdleDom(); 13 | let sdom = page.getStaticDom(); 14 | let n = dom.querySelectorAll(selector); 15 | let n_static = sdom.querySelectorAll(selector); 16 | 17 | let n_array = [] 18 | n.forEach(function (elem, index) { 19 | n_array.push(elem.getAttribute('href')); 20 | }); 21 | let static_extra_elems = []; 22 | n_static.forEach(function (elem, index) { 23 | 24 | if(!n_array.includes(elem.getAttribute('href'))) 25 | { 26 | static_extra_elems.push(elem); 27 | } 28 | }); 29 | 30 | if(n.length>0) 31 | { 32 | msg = msg + n.length+" links with parameters found."+that.partialCodeLink(n)+that.highlightLink(selector, "Highlight paramterized links"); 33 | //msg = msg + " Highlight Nofollow"; 34 | if(static_extra_elems.length>0) 35 | { 36 | msg = msg + " Additonal "+static_extra_elems.length+" parametrized links found only in the static HTML."+that.partialCodeLink(static_extra_elems); 37 | } 38 | if(n.length+static_extra_elems.length>10) 39 | { 40 | type = "warning"; 41 | } 42 | done(that.createResult(lable, msg, type, what, 500));return; 43 | } 44 | done();return; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /build/default-rules/robotstxt-sitemap-reference.js: -------------------------------------------------------------------------------- 1 | function (page, done) { 2 | 3 | let url = page.getURL("last") 4 | if(!url){ done(); return; } 5 | let u = new URL(url); 6 | let r = u.origin+'/robots.txt' 7 | let rl = ""+u.origin+'/robots.txt'+""; 8 | let msg = ""; 9 | let type = "info"; 10 | //TODO: set the robots.txt body of that domain in a global map so to prevent unnecessary fetched 11 | this.fetch(r, { responseFormat: 'text' }, (response) => { 12 | //if(response.redirected == false ) 13 | //even a redirected sitemap can be valid 14 | if(true) 15 | { 16 | if(response.status===200) 17 | { 18 | if(!((response.body.includes('sitemap:')) || (response.body.includes('Sitemap:')) || (response.body.includes('SITEMAP:')))) 19 | { 20 | type="warning" 21 | msg= rl+' includes no "Sitemap:"" reference.'; 22 | } 23 | else 24 | { 25 | let count = (response.body.match(/sitemap\:/ig) || []).length; 26 | type="info" 27 | msg= rl+' includes '+count+' "Sitemap:" references.'; 28 | } 29 | } 30 | } 31 | if(msg!="") 32 | { 33 | msg = msg + " GSC" 34 | done(this.createResult('SITE', msg, type));return; 35 | } 36 | done(); 37 | }); 38 | } -------------------------------------------------------------------------------- /build/default-rules/speed-dom-static-blocking-scripts.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "SPEED"; 6 | let msg = " blocking scripts"; 7 | let type = "info"; 8 | let what = "static"; 9 | 10 | let dom = page.getStaticDom(); 11 | let selector = 'script[src]'; 12 | let scripts = dom.querySelectorAll(selector); 13 | let blocking = []; 14 | let async = []; 15 | let defer = []; 16 | for(let s of scripts){ 17 | let s_str = s.outerHTML; 18 | if((s_str.search(/defer/i)===-1)&&(s_str.search(/async/i)===-1)) 19 | { 20 | blocking.push(s); 21 | } 22 | else 23 | { 24 | if(s_str.search(/defer/i)!==-1) 25 | { 26 | defer.push(s); 27 | } 28 | if(s_str.search(/async/i)!==-1) 29 | { 30 | async.push(s); 31 | } 32 | } 33 | } 34 | 35 | if(blocking.length<1) 36 | { 37 | done();return; 38 | } 39 | 40 | if(blocking.length>1){type="warning";} 41 | if (blocking.length>9){type="error";} //just decided that if you have 10 or more blocking scripts then your site is broken 42 | 43 | 44 | 45 | msg = blocking.length + msg + that.partialCodeLink(blocking); 46 | 47 | if(async.length>0) 48 | { 49 | msg = msg + " " + async.length+" async scripts."+that.partialCodeLink(async); 50 | } 51 | 52 | if(defer.length>0) 53 | { 54 | msg = msg + " " + async.length+" defer scripts."+that.partialCodeLink(defer); 55 | } 56 | 57 | if(blocking.length!==scripts.length) 58 | { 59 | msg = msg + " All "+scripts.length+" scripts:"+that.partialCodeLink(scripts); 60 | } 61 | 62 | done(that.createResult(lable, msg, type, what, 550));return; 63 | } 64 | -------------------------------------------------------------------------------- /build/default-rules/speed-first-paint.js: -------------------------------------------------------------------------------- 1 | /* 2 | firstPaint = window.chrome.loadTimes().firstPaintTime * 1000; 3 | api.firstPaintTime = firstPaint - window.performance.timing.navigationStart; 4 | 5 | */ 6 | 7 | //based on this spec https://developers.google.com/web/updates/2017/12/chrome-loadtimes-deprecated 8 | function(page, done){ 9 | var paint = page.getWindowPerformancePaint(); 10 | var first_paint_object = paint[0]; 11 | if(first_paint_object.name==="first-paint") 12 | { 13 | var first_paint_time = Math.round(first_paint_object.startTime); 14 | } 15 | else 16 | { 17 | done(); 18 | return; 19 | } 20 | 21 | var text = "Time to first paint: "+first_paint_time+"ms." 22 | 23 | var type = "info" 24 | 25 | if(first_paint_time > 700) {type = "warning";} 26 | if(first_paint_time > 1400) {type = "error";} 27 | if(first_paint_time <= 0) { 28 | type = "unfinished" 29 | text = "Time to first paint: Could not calculate!" 30 | } 31 | 32 | done(this.createResult('SPEED', text, type)); 33 | } 34 | -------------------------------------------------------------------------------- /build/default-rules/speed-static-head-link-rel-preload.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "SPEED"; 6 | let msg = ""; 7 | let type = "info"; 8 | let what = "static"; 9 | let prio = 500; 10 | 11 | let dom = page.getStaticDom(); 12 | let selector = "link[rel*=preload]"; 13 | let n = dom.querySelectorAll(selector); 14 | 15 | if(n.length>0) 16 | { 17 | let elements = "elements"; 18 | if(n.length===1){elements = "element";} 19 | msg = msg + n.length + " link[rel=preload] "+ elements +" found."+that.partialCodeLink(n); 20 | done(that.createResult(lable, msg, type, what, prio)); 21 | return null; 22 | } 23 | 24 | done(that.createResult(lable, "No link[rel=preload] elements found.", 'warning', what, prio)); 25 | return null; 26 | } -------------------------------------------------------------------------------- /build/default-rules/static-body-h1.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | 3 | var dom = page.getStaticDom(); 4 | let selector = 'h1'; 5 | var h1 = dom.querySelectorAll(selector); 6 | if (h1.length>0) { 7 | if(h1.length === 1) { 8 | if(h1[0].innerText.trim()!='') { 9 | done(this.createResult('BODY', 'H1: '+h1[0].innerText+this.partialCodeLink(h1)+this.highlightLink(selector, "Highlight H1", '5px solid green'), 'info', 'static', 990)); 10 | return null; 11 | } 12 | else { 13 | done(this.createResult('BODY', '<H1> is empty!'+this.partialCodeLink(h1), 'error', 'static')); 14 | return null; 15 | } 16 | } 17 | done(this.createResult('BODY', 'Multiple <H1> found!'+this.partialCodeLink(h1)+this.highlightLink(selector, "Highlight H1"), 'warning', 'static')); 18 | return null; 19 | } 20 | done(this.createResult('BODY', 'No <H1> found!', 'error', 'static')); 21 | return null; 22 | } 23 | -------------------------------------------------------------------------------- /build/default-rules/static-head-amp.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | var amp = dom.querySelector('head > link[rel=amphtml]'); 4 | 5 | if (amp) { 6 | var href = amp.getAttribute('href'); 7 | var text = `Link Rel Amphtml URL: ${href} ${this.partialCodeLink(amp)} Validate`; 8 | done(this.createResult('HEAD', text, 'info', 'static', 500)); 9 | } 10 | done(); 11 | } 12 | -------------------------------------------------------------------------------- /build/default-rules/static-head-brand-in-title.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let lable = "HEAD"; 4 | let msg = ""; 5 | let type = "warning"; 6 | let what = "static"; 7 | let dom = page.getStaticDom(); 8 | let url = page.getURL('last'); 9 | let u = new URL(url); 10 | let host = u.host; 11 | let extractBrand = (host) => 12 | { 13 | let parts = host.split('.'); 14 | let longest = parts.reduce(function (a, b) { return a.length > b.length ? a : b; }); 15 | return longest; 16 | } 17 | let brand = extractBrand(host); 18 | if(!dom){done();} 19 | let titletags = dom.querySelectorAll('head > title'); 20 | let title = titletags[0].innerText.trim(); 21 | 22 | if(title.toLowerCase().includes(brand.toLowerCase())) {done();} 23 | msg = "Meta-Title does not include the brand \""+brand+"\"." 24 | done(this.createResult(lable, msg + this.partialCodeLink(titletags[0]), type, what)); 25 | } -------------------------------------------------------------------------------- /build/default-rules/static-head-link-shortlink.js: -------------------------------------------------------------------------------- 1 | function(page,done) 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let url = page.getURL('last'); 6 | let shortlink_elem = dom.querySelector('link[rel="shortlink"]'); 7 | if(!shortlink_elem){done();return;} 8 | let shortlink = shortlink_elem.href; 9 | let type = "info"; 10 | if(shortlink_elem && shortlink) 11 | { 12 | if(shortlink!=url) 13 | { 14 | type = "warning"; 15 | } 16 | done(that.createResult('HEAD', 'Shortlink detected: '+shortlink+''+that.partialCodeLink(shortlink_elem), type));return; 17 | } 18 | done(); 19 | } -------------------------------------------------------------------------------- /build/default-rules/static-head-meta-description.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | const what = 'static'; 4 | //if (some requirement) 5 | var meta_ds = dom.querySelectorAll('meta[name="description"]'); 6 | if (meta_ds.length > 0) { 7 | if (meta_ds > 1) { 8 | return this.createResult('HEAD', 'Multiple meta description found.' + this.partialCodeLink(meta_ds), 'error', what); 9 | } 10 | //some category of stuff your are testing i.e.: 'DOM', 'HEAD', 'BODY', 'HTTP', 'SPEED', ... 11 | var lable = 'HEAD'; 12 | var msg = 'Meta description: ' + meta_ds[0]['content']; 13 | msg = msg + this.partialCodeLink(meta_ds); 14 | //you can create a link showing only the partial code of a nodeList 15 | //msg = msg+' '+this.partialCodeLink(dom); 16 | var type = 'info'; //should be 'info', 'warning', 'error' 17 | done(this.createResult(lable, msg, type, what, 760)); 18 | } 19 | done(this.createResult('BODY', 'No meta description found.', 'error', what)); 20 | } -------------------------------------------------------------------------------- /build/default-rules/static-head-meta-googlebot.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | var elements = dom.querySelectorAll('head>meta[name=googlebot]'); 4 | var type = 'info'; 5 | var msg =''; 6 | const what = 'static'; 7 | 8 | if (elements.length===1) { 9 | var content = elements[0].getAttribute('content'); 10 | var instructions = content.split(','); 11 | instructions.forEach(function(v,i,robot_instructions){ 12 | robot_instructions[i]=v.trim().toLowerCase(); 13 | }); 14 | if (instructions.indexOf('noindex')!=-1){type = 'warning';} 15 | //if (instructions.indexOf('index')!=-1){} 16 | if (instructions.indexOf('nofollow')!=-1){type = 'warning';} 17 | //if (instructions.indexOf('follow')!=-1){msg = msg + ' follow';} 18 | 19 | msg = 'Meta Googlebot: '+content+''+this.partialCodeLink(elements); 20 | 21 | done(this.createResult('HEAD', msg, 'info', what, 600)); 22 | return null; 23 | } 24 | 25 | if (elements.length > 1) { 26 | done(this.createResult('HEAD', "Multiple googlebot meta tags."+this.partialCodeLink(elements), 'warning', what)); 27 | return null; 28 | } 29 | done(); 30 | } 31 | -------------------------------------------------------------------------------- /build/default-rules/static-head-meta-robots.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | const what = 'static'; 4 | var elements = dom.querySelectorAll('head>meta[name=robots]'); 5 | var type = 'info'; 6 | var msg =''; 7 | 8 | if (elements.length===1) { 9 | var content = elements[0].getAttribute('content'); 10 | var instructions = content.split(','); 11 | instructions.forEach(function(v,i,robot_instructions){ 12 | robot_instructions[i]=v.trim().toLowerCase(); 13 | }); 14 | if (instructions.indexOf('noindex')!=-1){type = 'warning';} 15 | //if (instructions.indexOf('index')!=-1){} 16 | if (instructions.indexOf('nofollow')!=-1){type = 'warning';} 17 | //if (instructions.indexOf('follow')!=-1){msg = msg + ' follow';} 18 | 19 | msg = 'Meta Robots: '+content+''+this.partialCodeLink(elements); 20 | 21 | done(this.createResult('HEAD', msg, type, what, 610)); 22 | } 23 | 24 | if (elements.length > 1) { 25 | done(this.createResult('HEAD', "Multiple robots meta tags."+this.partialCodeLink(elements), 'warning', what)); 26 | } 27 | 28 | done(this.createResult('HEAD', "No robots meta tag.", 'info', what)); 29 | } 30 | -------------------------------------------------------------------------------- /build/default-rules/static-head-open-graph-description.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let og_description = dom.querySelector('meta[property="og:description"]'); 6 | if(og_description && og_description.content) 7 | { 8 | done(that.createResult('HEAD', 'Open Graph (Facebook) description: "'+og_description.content+'"'+that.partialCodeLink(og_description), 'info'));return; 9 | } 10 | done(); 11 | } -------------------------------------------------------------------------------- /build/default-rules/static-head-open-graph-title.js: -------------------------------------------------------------------------------- 1 | function(page,done) 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let og_title = dom.querySelector('meta[property="og:title"]'); 6 | if(og_title && og_title.content) 7 | { 8 | done(that.createResult('HEAD', 'Open Graph (Facebook) title: "'+og_title.content+'"'+that.partialCodeLink(og_title), 'info'));return; 9 | } 10 | done(); 11 | } -------------------------------------------------------------------------------- /build/default-rules/static-head-open-graph-url.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let og_url = dom.querySelector('meta[property="og:url"]'); 6 | let c = (dom.querySelector('link[rel=canonical]')&&dom.querySelector('link[rel=canonical]').href); 7 | let l = page.getLocation().href; 8 | if(og_url && og_url.content) 9 | { 10 | if(c && og_url.content && c.trim() !== og_url.content.trim()) 11 | { 12 | done(that.createResult('HEAD', 'Open Graph (Facebook) URL: '+og_url.content+' does not equal canonical('+c+'")'+that.partialCodeLink(og_url), 'warning'));return; 13 | } else if(l && og_url.content && l.trim() !== og_url.content.trim()) 14 | { 15 | done(that.createResult('HEAD', 'Open Graph (Facebook) URL: '+og_url.content+' does not equal document location ('+l+')'+that.partialCodeLink(og_url), 'warning'));return; 16 | } 17 | done(that.createResult('HEAD', 'Open Graph (Facebook) URL: '+og_url.content+'" (OK)'+that.partialCodeLink(og_url)+' Fb Debugger', 'info'));return; 18 | } 19 | done(); 20 | } -------------------------------------------------------------------------------- /build/default-rules/static-head-title-length.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let lable = "HEAD"; 4 | let msg = "Meta-Title length: "; 5 | let type = "info"; 6 | let what = "static"; 7 | let dom = page.getStaticDom(); 8 | if(!dom){done();} 9 | let titletags = dom.querySelectorAll('head > title'); 10 | let title = titletags[0].innerText.trim(); 11 | msg = msg + title.length 12 | 13 | if(title.length < 40) 14 | { 15 | msg = msg + " (short title)"; 16 | type = "warning"; 17 | } 18 | if(title.length == 0) 19 | { 20 | msg = msg + " (blank title)"; 21 | type = "error"; 22 | } 23 | if(title.length > 120) 24 | { 25 | msg = msg + " (long title)"; 26 | type = "warning"; 27 | } 28 | if(title.length > 240) 29 | { 30 | msg = msg + " (very long title)"; 31 | type = "warning"; 32 | } 33 | done(this.createResult(lable, msg + this.partialCodeLink(titletags[0]), type, what)); 34 | } -------------------------------------------------------------------------------- /build/default-rules/static-head-title.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | const what = 'static'; 4 | var location = page.getLocation('static'); 5 | //check if we got some data to work with 6 | if (!dom) { 7 | return null; 8 | } 9 | var titletags = dom.querySelectorAll('head > title'); 10 | var lable = 'HEAD'; 11 | if (titletags.length > 0) { 12 | if (titletags.length === 1) { 13 | done(this.createResult(lable, 'SEO-<title>: ' + titletags[0].innerText + this.partialCodeLink(titletags), 'info', what, 1000)); 14 | //check size //throw to short, throw to long 15 | //check brand 16 | //TODO check for common non descriptive titles 17 | } else { 18 | done(this.createResult(lable, 'Multiple title-tags found in head. There should be only one.' + this.partialCodeLink(titletags), 'error', what)); 19 | } 20 | } else { 21 | done(this.createResult(lable, 'No title-tag found in head.', 'error', what)); 22 | } 23 | } -------------------------------------------------------------------------------- /build/default-rules/static-idle-dom-unavailable-after.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let lable = "DOM"; 6 | let msg = " unavailable after meta information found:"; 7 | let type = "warning"; 8 | let what = "static"; 9 | let selector ="meta[content^=unavailable_after]"; 10 | let m = dom.querySelectorAll(selector); 11 | //via https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-core/audits/seo/is-crawlable.js 12 | function isUnavailable(directive) { 13 | const parts = directive.split(':'); 14 | 15 | if (parts.length <= 1 || parts[0] !== 'unavailable_after') { 16 | return false; 17 | } 18 | 19 | const date = Date.parse(parts.slice(1).join(':')); 20 | 21 | return !isNaN(date) && date < Date.now(); 22 | } 23 | if (!m || (m && m.length < 1)) 24 | { 25 | done();return; 26 | } 27 | 28 | 29 | msg = m.length + msg +" "+ m[0].content +that.partialCodeLink(m); 30 | 31 | if(isUnavailable(m[0].content)) 32 | { 33 | type = "error"; 34 | msg = msg + "Date already in the past!"; 35 | } 36 | 37 | done(that.createResult(lable, msg, type, what));return; 38 | } 39 | -------------------------------------------------------------------------------- /build/images/_icon-inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/build/images/_icon-inactive.png -------------------------------------------------------------------------------- /build/images/_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/build/images/_icon.png -------------------------------------------------------------------------------- /build/images/icon-inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/build/images/icon-inactive.png -------------------------------------------------------------------------------- /build/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/build/images/icon.png -------------------------------------------------------------------------------- /build/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | f19n - Popup 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /build/rules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Settings - Franz Enzenhofer SEO 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /build/sandbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nothing todo here. Its just a container to hold and run the sandbox.js script. 6 | 7 | -------------------------------------------------------------------------------- /dist.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/dist.zip -------------------------------------------------------------------------------- /dist/codeview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | f19n codeview 4 | 5 | 6 | 7 |
 
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /dist/css/application.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; } 3 | 4 | *, *::after, *::before { 5 | box-sizing: inherit; } 6 | -------------------------------------------------------------------------------- /dist/default-rules/MIT-LICENSE.md: -------------------------------------------------------------------------------- 1 | This License applies to all files and all code within this source directory. 2 | 3 | The MIT License (MIT) 4 | ===================== 5 | 6 | Copyright © `2016` `Franz Enzenhofer` 7 | 8 | Permission is hereby granted, free of charge, to any person 9 | obtaining a copy of this software and associated documentation 10 | files (the “Software”), to deal in the Software without 11 | restriction, including without limitation the rights to use, 12 | copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following 15 | conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /dist/default-rules/TODO.md: -------------------------------------------------------------------------------- 1 | finish idle-body-linked-image-without-alt-tag-no-textnode 2 | 3 | schema.org json ld detect 4 | strucured data summary 5 | -------------------------------------------------------------------------------- /dist/default-rules/a-promotion-for-reviews.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | let lable = "PLZ"; 5 | let msg = 'Wohoo, more than 1000 daily active users! So, so cool! But only 20 reviews 😢. Please review this app in the Google Chrome Store! 👍 Thx!
You can disable this message in the Settings.'; 6 | let type = "promotion"; 7 | let what = ""; 8 | 9 | done(that.createResult(lable, msg, type, what));return; 10 | 11 | 12 | } -------------------------------------------------------------------------------- /dist/default-rules/check-for-just-history-state-update.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | const all_header_received_events = page.eventsOfType('onHeadersReceived'); 6 | const all_on_completed = page.eventsOfType('onCompleted'); 7 | const all_onHistoryStateUpdated_events = page.eventsOfType('onHistoryStateUpdated'); 8 | 9 | let lable = "HTTP"; 10 | let msg = "Page delivered an webapp internal JS logic (URL change via JS History (URL) State update)! Most rules will fail. Try ⇧+↻ for hard refresh. This is not an error with this page, but with the test execution of this app."; 11 | let type = "error"; //we set error as we need this response at the top 12 | let what = ""; 13 | 14 | 15 | 16 | if(all_header_received_events.length>0||all_on_completed.length>0) 17 | { 18 | done();return; 19 | } 20 | else if(all_onHistoryStateUpdated_events.length>0) 21 | { 22 | //we go with the default values 23 | } 24 | else 25 | { 26 | msg = "Unkown error! Could not collect the lifecycle of the page. Collected data:"+this.stringifyLink(page); 27 | type = "error"; 28 | } 29 | done(that.createResult(lable, msg, type, what, 1000));return; 30 | } 31 | -------------------------------------------------------------------------------- /dist/default-rules/debug-stringify-page-object.js: -------------------------------------------------------------------------------- 1 | function(page,done){ 2 | var debug = '%DEBUG%'; //must be set to true 3 | if(debug==='true') 4 | { 5 | done(this.createResult("DEBUG", "Complete Page Object "+this.stringifyLink(page), "info", null, 9000)); 6 | } 7 | done(); 8 | return null; 9 | } -------------------------------------------------------------------------------- /dist/default-rules/dom-detect-client-side-rendering.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | //var static_dom = page.getFetchedStaticDom(); 3 | var idle_dom = page.getIdleDom(); 4 | var url = page.getURL("last"); 5 | 6 | this.fetch(url, { responseFormat: 'text' }, (response) => { 7 | var parser = new DOMParser(); 8 | var static_dom = parser.parseFromString(response.body, "text/html"); 9 | var status = 'info'; 10 | var diff_size = (idle_dom.head.innerHTML.length+idle_dom.head.innerHTML.length)/(static_dom.head.innerHTML.length+static_dom.head.innerHTML.length) 11 | var msg = ''; 12 | if(diff_size > 1) 13 | { 14 | var diff_percentage = Math.round((diff_size -1) * 100); 15 | msg = "Idle-DOM is "+diff_percentage+"% bigger then the Static-DOM."; 16 | } 17 | else if (diff_size>1) 18 | { 19 | var diff_percentage = Math.round((1 - diff_size) * 100); 20 | msg = "Idle-DOM removed about "+diff_percentage+"% from Static-DOM." 21 | } 22 | else { 23 | msg = "Idle-DOM equals Static-DOM. Either static page or server side rendering cache." 24 | } 25 | 26 | if (diff_percentage >= 20) 27 | { 28 | msg = "Client Side Rendering! "+msg; 29 | status = "warning"; 30 | } 31 | 32 | done(this.createResult('DOM', msg, status, null, 800)); 33 | }); 34 | //if(!(static_dom && idle_dom)){ return null; } 35 | //wait forever 36 | //done(); 37 | } 38 | -------------------------------------------------------------------------------- /dist/default-rules/google-amp-cache-url.js: -------------------------------------------------------------------------------- 1 | (page, done) => 2 | { 3 | 4 | var that = this; 5 | 6 | let lable = "Amp"; 7 | let msg = ""; 8 | let type = "info"; 9 | let what = ""; 10 | 11 | let url = page.getURL('first'); 12 | 13 | const globals = that.getGlobals(); 14 | //if no google api key we fail silently 15 | if(!globals.variables.google_page_speed_insights_key) 16 | { 17 | done(); 18 | return null; 19 | } 20 | let api = "https://acceleratedmobilepageurl.googleapis.com/v1/ampUrls:batchGet?key="; 21 | api = api + globals.variables.google_page_speed_insights_key; 22 | 23 | fetch(api, 24 | { 25 | method: "POST", 26 | mode: 'cors', 27 | headers: new Headers( 28 | { 29 | 'Content-Type': 'application/json', 30 | 'Accept': 'application/json' 31 | }), 32 | body: JSON.stringify({ 33 | "lookupStrategy": "IN_INDEX_DOC", 34 | "urls": [ 35 | url 36 | ] 37 | }) 38 | }).then(function(response) 39 | { 40 | 41 | 42 | if(response.status == 200) 43 | { 44 | response.json().then(function(data) 45 | { 46 | 47 | 48 | }); 49 | } 50 | }).catch(function(err){ 51 | 52 | 53 | }); 54 | 55 | done(); 56 | return null; 57 | } -------------------------------------------------------------------------------- /dist/default-rules/google-is-connected.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | 4 | var that = this; 5 | let lable = "GSC"; 6 | let msg = ""; 7 | let type = "warning"; 8 | let what = null; 9 | let prio = null; 10 | 11 | //GSC API 12 | const { googleApiAccessToken } = this.getGlobals(); 13 | if(!googleApiAccessToken) 14 | { 15 | msg = "To get the most out of this app, connect it with Google Search Console and Google Analytics. "+'Settings.'; 16 | done(that.createResult(lable, msg, type, what, prio)); 17 | return null; 18 | } 19 | done(); 20 | return null; 21 | } 22 | -------------------------------------------------------------------------------- /dist/default-rules/http-check-for-unavailable-after.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | //var hh = page.getHttpHeaders("last"); 5 | var hr = page.getRawHttpHeaders("last"); 6 | var hr_stringy = JSON.stringify(hr, null, 2); 7 | let lable = "HTTP"; 8 | let msg = "Unavailable after HTTP header found."; 9 | let type = "warning"; 10 | 11 | if (hr_stringy.search(/unavailable_after/ig)===-1) 12 | { 13 | done();return; 14 | } 15 | msg = msg + that.partialCodeLink(hr); 16 | 17 | 18 | done(that.createResult(lable, msg, type));return; 19 | } 20 | -------------------------------------------------------------------------------- /dist/default-rules/http-detect-classic-deliver-from-cache.js: -------------------------------------------------------------------------------- 1 | function(page,done) 2 | { 3 | const on_completed = page.firstEventOfType('onCompleted'); 4 | if(on_completed && on_completed.fromCache && on_completed.fromCache===true) 5 | { 6 | done(this.createResult('HTTP', 'Page delivered via browser-cache! Some rules might not report or might fail. Try ⇧+↻ for hard refresh.', 'warning', null, 1000)); 7 | } 8 | done();return; 9 | } -------------------------------------------------------------------------------- /dist/default-rules/http-dom-check-common-mobile-setup.js: -------------------------------------------------------------------------------- 1 | function(page, done){ 2 | var hh = page.getHttpHeaders("last"); 3 | 4 | if(hh) 5 | { 6 | var vary = hh['vary'] || ""; 7 | var isvary = function(){if(vary.toLowerCase().indexOf("agent")===-1){ return false} return true}(); 8 | } 9 | else 10 | { 11 | var vary = ""; 12 | var isvary = false; 13 | } 14 | var u = page.getURL("last"); 15 | var idle_dom = page.getIdleDom(); 16 | var medias = idle_dom.querySelectorAll("link[rel=alternate][media][href]"); 17 | var meta_viewports = idle_dom.querySelectorAll('meta[name=viewport]'); 18 | 19 | if(!(isvary || (medias && medias.length>0) || (meta_viewports && meta_viewports.length > 0))) 20 | { 21 | done(this.createResult('HTTP DOM', "No common mobile setup (responsive, dynamic serving, different URL) discovered! Mobile Friendly Test", "error")); 22 | return null; 23 | } 24 | done(); 25 | } 26 | -------------------------------------------------------------------------------- /dist/default-rules/http-gzip.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var u = page.getURL("last"); 5 | 6 | var gzip = true; 7 | var brotli = true; 8 | 9 | if(!hh){done();return;} //wenn keine header, keine aussage 10 | 11 | var encoding = hh['content-encoding'] || ""; 12 | if (encoding.indexOf('gzip')===-1) 13 | { 14 | gzip = false; 15 | } 16 | 17 | if (encoding.indexOf('br')===-1) 18 | { 19 | brotli = false; 20 | } 21 | 22 | if(brotli == false && gzip == false) 23 | { 24 | done(this.createResult('HTTP', u+" no GZIP/Brotli compression! - Test"+ this.partialCodeLink(hr), 'error')); 25 | } 26 | 27 | if(brotli) 28 | { 29 | done(this.createResult('HTTP', " Brotli compression detected! - Test"+ this.partialCodeLink(hr), 'info')); 30 | } 31 | 32 | if(gzip) 33 | { 34 | done(this.createResult('HTTP', " GZIP compression detected! - Test"+ this.partialCodeLink(hr), 'info')); 35 | } 36 | 37 | done(); 38 | } 39 | -------------------------------------------------------------------------------- /dist/default-rules/http-has-http-header.js: -------------------------------------------------------------------------------- 1 | (page, done) => { 2 | var hh = page.getHttpHeaders("last"); 3 | //var staticdom = page.getDom(); 4 | if(!hh){ 5 | done(this.createResult('HTTP', "No HTTP-header found, most likely due to caching! HTTP-header depending tests might fail or not get reported!", 'warning')); 6 | } 7 | done(); 8 | } 9 | -------------------------------------------------------------------------------- /dist/default-rules/http-has-link-header.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var l = hh['link'] || false; 5 | let msg = ""; 6 | let type = "info"; 7 | let msgA = []; 8 | var temp_msg_partial = ''; 9 | if (l) 10 | { 11 | msg = '"Link:" HTTP header detected' 12 | if(l.toLowerCase().includes('shortlink')) 13 | { 14 | type="warning"; 15 | msgA.push("Shortlink"); 16 | } 17 | if(l.toLowerCase().includes('canonical')) 18 | { 19 | type="warning"; 20 | msgA.push("Canonical"); 21 | } 22 | if(l.toLowerCase().includes('alternate')) 23 | { 24 | msgA.push("Alternate"); 25 | } 26 | if(msgA.length>0) 27 | { 28 | temp_msg_partial = " ("+msgA.join(',')+")"; 29 | } 30 | msg=msg+temp_msg_partial+"."+this.partialCodeLink(hr); 31 | } 32 | done(this.createResult("HTTP", msg, type));return; 33 | } 34 | -------------------------------------------------------------------------------- /dist/default-rules/http-hsts.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var u = page.getURL("last"); 5 | 6 | var hsts_set = true; 7 | var hsts_duration = ""; 8 | var color = 'lightgreen'; 9 | var type = 'info'; 10 | 11 | if(!hh){done();return;} //wenn keine header, keine aussage 12 | 13 | var hsts = hh['strict-transport-security'] || ""; 14 | 15 | if (hsts.indexOf('max-age')===-1) 16 | { 17 | hsts_set = false; 18 | } else { 19 | var max_age = hsts.match(/max-age=(\d+)/); 20 | 21 | if (max_age) { 22 | hsts_duration = max_age[0]; 23 | if (max_age[1] < 31536000) { 24 | color = 'orange'; 25 | type = 'warning'; 26 | } 27 | } 28 | } 29 | 30 | 31 | if(hsts_set == false) 32 | { 33 | done(this.createResult('HTTP', "No HSTS header set! - Test"+ this.partialCodeLink(hr), 'warning')); 34 | } 35 | 36 | if(hsts_set == true) 37 | { 38 | done(this.createResult('HTTP', "HSTS header is set. - with a duration of: " + hsts_duration + "" + this.partialCodeLink(hr), type)); 39 | } 40 | 41 | done(); 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /dist/default-rules/http-http2-detection.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var t = page.getWindowPerformanceNavigation(); 3 | var p = page.getLocation().protocol; 4 | 5 | var is_https = false; 6 | if(p == "https:"){ is_https = true; } 7 | 8 | if (t) { 9 | var prot = t.nextHopProtocol 10 | var is_h2 = false; 11 | if(['h2', 'hq'].includes(prot)) { is_h2 = true; } 12 | if(is_https == true && is_h2 == false) 13 | { 14 | return done(this.createResult('HTTP', "Network protocol: "+prot+" (but https protocol)", 'warning')); 15 | } 16 | 17 | return done(this.createResult('HTTP', "Network protocol: "+prot, 'info', null, 400)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dist/default-rules/http-status-code.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var sc = page.getStatusCode(); 3 | var rawHttpHeader = page.getRawHttpHeaders(); 4 | var url = page.getURL(); 5 | var text = ""; 6 | if (sc) { 7 | var type = 'info'; 8 | var anchor = ''; 9 | if (sc < 200) { type = 'warning'; anchor='#1xx_Informational_response';} 10 | if (sc <= 200) { anchor='#2xx_Success';} 11 | if (sc >= 300) { type = 'warning'; anchor='#3xx_Redirection'; } 12 | if (sc === 304) { 13 | type = 'info'; 14 | text = `${url} → HTTP ${sc}`; 15 | } 16 | if (sc >= 400) { type = 'error'; anchor="#4xx_Client_errors";} 17 | if (sc >= 500) { type ='error'; anchor="#5xx_Server_errors" } 18 | if(text==='') 19 | { 20 | text = `${url} → HTTP ${sc}`; 21 | } 22 | text = text + this.partialCodeLink(rawHttpHeader); 23 | done(this.createResult('HTTP', text, type, null, 700)); return; 24 | } 25 | done(); 26 | } 27 | -------------------------------------------------------------------------------- /dist/default-rules/http-vary-user-agent.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var u = page.getURL("last"); 5 | var v = hh['vary'] || false; 6 | if (v!==false) 7 | { 8 | if(v.toLowerCase().indexOf('agent')!==-1) 9 | { 10 | done(this.createResult('HTTP', " Vary: "+v+" - Dynamic serving detected." +this.partialCodeLink(hr), 'warning')); 11 | return null; 12 | } 13 | } 14 | done(); 15 | } 16 | -------------------------------------------------------------------------------- /dist/default-rules/http-x-cache-hit-miss.js: -------------------------------------------------------------------------------- 1 | function(page,done) 2 | { 3 | let that = this; 4 | let h = page.getHttpHeaders('last'); 5 | let hh = page.getRawHttpHeaders('last'); 6 | let msgA = []; 7 | if(h['x-cache']) 8 | { 9 | if(h['x-cache'].toLowerCase().includes('hit')) 10 | { 11 | msgA.push('Delivered via cache. X-Cache: '+h['x-cache']); 12 | } 13 | else if(h['x-cache'].toLowerCase().includes('miss')) 14 | { 15 | msgA.push('Cache missed. X-Cache: '+h['x-cache']); 16 | } 17 | } 18 | if(h['x-cache-lookup']) 19 | { 20 | if(h['x-cache-lookup'].toLowerCase().includes('hit')) 21 | { 22 | msgA.push('X-Cache-Lookup: '+h['x-cache-lookup']); 23 | } 24 | else if(h['x-cache-lookup'].toLowerCase().includes('miss')) 25 | { 26 | msgA.push('Cache Lookupm missed. X-Cache-Lookup: '+h['x-cache-lookup']); 27 | } 28 | } 29 | if(msgA.length>0) 30 | { 31 | //msgA[msgA.length-1]+that.partialCodeLink(hh); 32 | msgA.push(that.partialCodeLink(hh)); 33 | done(that.createResult('HTTP', msgA.join(' ')));return; 34 | } 35 | done();return; 36 | } -------------------------------------------------------------------------------- /dist/default-rules/http-x-robots.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var u = page.getURL("last"); 5 | var xr = hh['x-robots-tag'] || false; 6 | if (xr!==false) 7 | { 8 | done(this.createResult('HTTP', "X-Robots-Tag HTTP Header: "+xr+ this.partialCodeLink(hr), 'warning')); 9 | } 10 | done(); 11 | } 12 | -------------------------------------------------------------------------------- /dist/default-rules/idel-body-unsecure-input.js: -------------------------------------------------------------------------------- 1 | function(page,done) 2 | { 3 | let that=this; 4 | let p = page.getLocation('idle').protocol; 5 | 6 | if(p!=='http:'){ done();return; } 7 | let dom = page.getIdleDom(); 8 | let selector = 'input,textarea'; 9 | let inputs = dom.querySelectorAll(selector); 10 | if(inputs.length>0) 11 | { 12 | let text = "Unsecure http:// connection (not https://) with user data elements."+that.partialCodeLink(inputs)+that.highlightLink(selector); 13 | let type = 'warning'; 14 | let what = 'idle'; 15 | done(that.createResult('BODY', text, type, what)); return; 16 | } 17 | done();return; 18 | } 19 | -------------------------------------------------------------------------------- /dist/default-rules/idel-dom-node-count.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var idle_dom = page.getIdleDom(); 3 | var nr_dom_nodes = idle_dom.getElementsByTagName("*").length; 4 | var type = "info"; 5 | var text = "Total DOM Nodes ~"+nr_dom_nodes+"."; 6 | var comment = ""; 7 | if(nr_dom_nodes > 1500) 8 | { 9 | comment = "Pretty big DOM!"; 10 | type="warning"; 11 | } 12 | 13 | if(nr_dom_nodes > 3000) 14 | { 15 | comment = "Excessive DOM!"; 16 | type="error"; 17 | } 18 | done(this.createResult("DOM", text+" "+comment, type, "Idle")); 19 | } 20 | 21 | 22 | /* 23 | [].forEach.call($$('*'), 24 | function (a) { 25 | a.style.outline = '1px solid #' + (~~(Math.random() * (1 << 24))).toString(16); 26 | }); 27 | */ -------------------------------------------------------------------------------- /dist/default-rules/idel-dom-node-depth.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | 3 | function countDomDepth(main) { 4 | var depth = 0; 5 | var loop = function(main, d) { 6 | 7 | var temp = d; 8 | do { 9 | 10 | if(main.nodeType == 1) 11 | { 12 | d = d + 1; 13 | 14 | } 15 | if(main.hasChildNodes()){ 16 | loop(main.firstChild,d); 17 | } 18 | 19 | if(d>depth){ depth = d; } 20 | d = temp; 21 | } 22 | while (main = main.nextSibling); 23 | } 24 | loop(main, 0); 25 | return depth-1; 26 | } 27 | 28 | var idle_dom = page.getIdleDom(); 29 | var depth = countDomDepth(idle_dom.querySelector(':root')) 30 | var type = "info"; 31 | var text = "DOM Depth count: "+depth+""; 32 | var comment = "(Good value ~10!)"; 33 | if(depth > 20) 34 | { 35 | comment = "Deeply nested DOM! (Good value ~10! OK until ~20!)"; 36 | type="warning"; 37 | } 38 | 39 | if(depth > 32) 40 | { 41 | comment = "Excessivly nested DOM! (Good value ~10, OK until ~20! >32 harmful!)"; 42 | type="error"; 43 | } 44 | done(this.createResult("DOM", text+" "+comment, type, "Idle")); 45 | } 46 | -------------------------------------------------------------------------------- /dist/default-rules/idle-body-linked-image-without-alt-tag-no-textnode.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var idom = page.getIdleDom(); 3 | let selector = "a>img[alt='']"; 4 | var elems = idom.querySelectorAll(selector); 5 | if (elems && elems.length>1) 6 | { 7 | var image_s = "images"; 8 | if (elems.length===1) { image_s = "image"; } 9 | var msg = elems.length+" linked "+image_s+" without alt-text or other linked text found."+this.partialCodeLink(elems)+this.highlightLink(selector); 10 | done(this.createResult('BODY',msg,'warning','idle')); 11 | } 12 | done(); 13 | } 14 | -------------------------------------------------------------------------------- /dist/default-rules/idle-body-nofollow.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "BODY"; 6 | let msg = ""; 7 | let type = "warning"; 8 | let what = "idle"; 9 | 10 | let selector = "*[rel*=nofollow]"; 11 | 12 | let dom = page.getIdleDom(); 13 | let n = dom.querySelectorAll(selector); 14 | if(n.length>0) 15 | { 16 | msg = msg + n.length+" elements with rel=nofollow found."+that.partialCodeLink(n)+that.highlightLink(selector, "Highlight Nofollow"); 17 | //msg = msg + " Highlight Nofollow"; 18 | done(that.createResult(lable, msg, type, what));return; 19 | } 20 | done();return; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /dist/default-rules/idle-dom-ldjson.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "DOM"; 6 | let msg = ""; 7 | let type = "info"; 8 | let what = "Idle"; 9 | 10 | let selector = "script[type*=json]"; 11 | 12 | let dom = page.getIdleDom(); 13 | let n = dom.querySelectorAll(selector); 14 | if(n.length>0) 15 | { 16 | let elements = "elements"; 17 | if(n.length===1){elements = "element";}; 18 | msg = msg + n.length+" Ld-Json Script "+elements+" found."+that.partialCodeLink(n); 19 | done(that.createResult(lable, msg, type, what, 600));return; 20 | } 21 | 22 | done();return; 23 | 24 | } -------------------------------------------------------------------------------- /dist/default-rules/idle-dom-top-words.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | function getFrequency2(string, cutOff, minlength) { 3 | if (!minlength) { 4 | minlength = 0 5 | } 6 | var cleanString = string.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`~()<>"']/g, " "), 7 | words = cleanString.split(' '), 8 | frequencies = {}, 9 | word, frequency, i; 10 | for (i = 0; i < words.length; i++) { 11 | word = words[i].trim(); 12 | if (word.length >= minlength) { 13 | frequencies[word] = frequencies[word] || 0; 14 | frequencies[word]++; 15 | } 16 | } 17 | words = Object.keys(frequencies); 18 | return words.sort(function(a, b) { 19 | return frequencies[b] - frequencies[a]; 20 | }).slice(0, cutOff); 21 | } 22 | var dom = page.getIdleDom(); 23 | if (dom) { 24 | var parser = new DOMParser(); 25 | var doc = parser.parseFromString(dom.body.innerText, "text/html"); 26 | var wA = getFrequency2(doc.body.innerText, 1000, 5); 27 | var top10 = wA.slice(0, 10).join(', '); 28 | done(this.createResult('DOM', 'Top words (5 or more chars): ' + top10 + ' - ' + this.partialTextLink('more', wA), 'info', 'idle')); 29 | } 30 | } -------------------------------------------------------------------------------- /dist/default-rules/idle-head-meta-keywords.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getIdleDom(); 3 | const what = 'idle'; 4 | var lable = 'HEAD'; 5 | //if (some requirement) 6 | var meta_ks = dom.querySelectorAll('meta[name="keywords"]'); 7 | if (meta_ks.length > 0) { 8 | 9 | if(meta_ks > 1) 10 | { 11 | done(this.createResult(lable, 'Multiple unnecessary meta keywords tags found.'+this.partialCodeLink(meta_ks), 'warning', what)); 12 | } 13 | 14 | //some category of stuff your are testing i.e.: 'DOM', 'HEAD', 'BODY', 'HTTP', 'SPEED', ... 15 | 16 | var msg = 'Unnecessary meta keywords tag: '+meta_ks[0]['content']; 17 | //you can create a link showing only the partial code of a nodeList 18 | //msg = msg+' '+this.partialCodeLink(dom); 19 | msg = msg+this.partialCodeLink(meta_ks); 20 | 21 | var type = 'warning'; //should be 'info', 'warning', 'error' 22 | 23 | done(this.createResult(lable, msg, type, what)); 24 | } 25 | done(); 26 | } 27 | -------------------------------------------------------------------------------- /dist/default-rules/idle-head-meta-viewport.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var idle_dom = page.getIdleDom(); 3 | var meta_viewports = idle_dom.querySelectorAll('meta[name=viewport]'); 4 | if(meta_viewports && meta_viewports.length > 0) 5 | { 6 | done(this.createResult("HEAD","Responsive design meta viewport tag ("+meta_viewports.length+") discovered."+this.partialCodeLink(meta_viewports), "info", "Idle")); 7 | return; 8 | } 9 | done();return; 10 | } 11 | -------------------------------------------------------------------------------- /dist/default-rules/idle-head-rel-alternate-media.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var idle_dom = page.getIdleDom(); 3 | var medias = idle_dom.querySelectorAll("link[rel=alternate][media][href]"); 4 | if(medias && medias.length > 0) 5 | { 6 | var murl = ""; 7 | var more = "("+medias.length+")"; 8 | if (medias.length===1) 9 | { 10 | murl = medias[0].href.trim(); 11 | more = ""+murl+"" 12 | } 13 | done(this.createResult("HEAD","Seperate mobile URL "+more+" discovered."+this.partialCodeLink(medias), "warning", "Idle")); 14 | } 15 | done(); 16 | } 17 | -------------------------------------------------------------------------------- /dist/default-rules/idle-static-body-parameterized-links.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "BODY"; 6 | let msg = ""; 7 | let type = "info"; 8 | let what = "idle, static"; 9 | 10 | let selector = "a[href*='?']"; 11 | 12 | let dom = page.getIdleDom(); 13 | let sdom = page.getStaticDom(); 14 | let n = dom.querySelectorAll(selector); 15 | let n_static = sdom.querySelectorAll(selector); 16 | 17 | let n_array = [] 18 | n.forEach(function (elem, index) { 19 | n_array.push(elem.getAttribute('href')); 20 | }); 21 | let static_extra_elems = []; 22 | n_static.forEach(function (elem, index) { 23 | 24 | if(!n_array.includes(elem.getAttribute('href'))) 25 | { 26 | static_extra_elems.push(elem); 27 | } 28 | }); 29 | 30 | if(n.length>0) 31 | { 32 | msg = msg + n.length+" links with parameters found."+that.partialCodeLink(n)+that.highlightLink(selector, "Highlight paramterized links"); 33 | //msg = msg + " Highlight Nofollow"; 34 | if(static_extra_elems.length>0) 35 | { 36 | msg = msg + " Additonal "+static_extra_elems.length+" parametrized links found only in the static HTML."+that.partialCodeLink(static_extra_elems); 37 | } 38 | if(n.length+static_extra_elems.length>10) 39 | { 40 | type = "warning"; 41 | } 42 | done(that.createResult(lable, msg, type, what, 500));return; 43 | } 44 | done();return; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /dist/default-rules/robotstxt-sitemap-reference.js: -------------------------------------------------------------------------------- 1 | function (page, done) { 2 | 3 | let url = page.getURL("last") 4 | if(!url){ done(); return; } 5 | let u = new URL(url); 6 | let r = u.origin+'/robots.txt' 7 | let rl = ""+u.origin+'/robots.txt'+""; 8 | let msg = ""; 9 | let type = "info"; 10 | //TODO: set the robots.txt body of that domain in a global map so to prevent unnecessary fetched 11 | this.fetch(r, { responseFormat: 'text' }, (response) => { 12 | //if(response.redirected == false ) 13 | //even a redirected sitemap can be valid 14 | if(true) 15 | { 16 | if(response.status===200) 17 | { 18 | if(!((response.body.includes('sitemap:')) || (response.body.includes('Sitemap:')) || (response.body.includes('SITEMAP:')))) 19 | { 20 | type="warning" 21 | msg= rl+' includes no "Sitemap:"" reference.'; 22 | } 23 | else 24 | { 25 | let count = (response.body.match(/sitemap\:/ig) || []).length; 26 | type="info" 27 | msg= rl+' includes '+count+' "Sitemap:" references.'; 28 | } 29 | } 30 | } 31 | if(msg!="") 32 | { 33 | msg = msg + " GSC" 34 | done(this.createResult('SITE', msg, type));return; 35 | } 36 | done(); 37 | }); 38 | } -------------------------------------------------------------------------------- /dist/default-rules/speed-dom-static-blocking-scripts.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "SPEED"; 6 | let msg = " blocking scripts"; 7 | let type = "info"; 8 | let what = "static"; 9 | 10 | let dom = page.getStaticDom(); 11 | let selector = 'script[src]'; 12 | let scripts = dom.querySelectorAll(selector); 13 | let blocking = []; 14 | let async = []; 15 | let defer = []; 16 | for(let s of scripts){ 17 | let s_str = s.outerHTML; 18 | if((s_str.search(/defer/i)===-1)&&(s_str.search(/async/i)===-1)) 19 | { 20 | blocking.push(s); 21 | } 22 | else 23 | { 24 | if(s_str.search(/defer/i)!==-1) 25 | { 26 | defer.push(s); 27 | } 28 | if(s_str.search(/async/i)!==-1) 29 | { 30 | async.push(s); 31 | } 32 | } 33 | } 34 | 35 | if(blocking.length<1) 36 | { 37 | done();return; 38 | } 39 | 40 | if(blocking.length>1){type="warning";} 41 | if (blocking.length>9){type="error";} //just decided that if you have 10 or more blocking scripts then your site is broken 42 | 43 | 44 | 45 | msg = blocking.length + msg + that.partialCodeLink(blocking); 46 | 47 | if(async.length>0) 48 | { 49 | msg = msg + " " + async.length+" async scripts."+that.partialCodeLink(async); 50 | } 51 | 52 | if(defer.length>0) 53 | { 54 | msg = msg + " " + async.length+" defer scripts."+that.partialCodeLink(defer); 55 | } 56 | 57 | if(blocking.length!==scripts.length) 58 | { 59 | msg = msg + " All "+scripts.length+" scripts:"+that.partialCodeLink(scripts); 60 | } 61 | 62 | done(that.createResult(lable, msg, type, what, 550));return; 63 | } 64 | -------------------------------------------------------------------------------- /dist/default-rules/speed-first-paint.js: -------------------------------------------------------------------------------- 1 | /* 2 | firstPaint = window.chrome.loadTimes().firstPaintTime * 1000; 3 | api.firstPaintTime = firstPaint - window.performance.timing.navigationStart; 4 | 5 | */ 6 | 7 | //based on this spec https://developers.google.com/web/updates/2017/12/chrome-loadtimes-deprecated 8 | function(page, done){ 9 | var paint = page.getWindowPerformancePaint(); 10 | var first_paint_object = paint[0]; 11 | if(first_paint_object.name==="first-paint") 12 | { 13 | var first_paint_time = Math.round(first_paint_object.startTime); 14 | } 15 | else 16 | { 17 | done(); 18 | return; 19 | } 20 | 21 | var text = "Time to first paint: "+first_paint_time+"ms." 22 | 23 | var type = "info" 24 | 25 | if(first_paint_time > 700) {type = "warning";} 26 | if(first_paint_time > 1400) {type = "error";} 27 | if(first_paint_time <= 0) { 28 | type = "unfinished" 29 | text = "Time to first paint: Could not calculate!" 30 | } 31 | 32 | done(this.createResult('SPEED', text, type)); 33 | } 34 | -------------------------------------------------------------------------------- /dist/default-rules/speed-static-head-link-rel-preload.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "SPEED"; 6 | let msg = ""; 7 | let type = "info"; 8 | let what = "static"; 9 | let prio = 500; 10 | 11 | let dom = page.getStaticDom(); 12 | let selector = "link[rel*=preload]"; 13 | let n = dom.querySelectorAll(selector); 14 | 15 | if(n.length>0) 16 | { 17 | let elements = "elements"; 18 | if(n.length===1){elements = "element";} 19 | msg = msg + n.length + " link[rel=preload] "+ elements +" found."+that.partialCodeLink(n); 20 | done(that.createResult(lable, msg, type, what, prio)); 21 | return null; 22 | } 23 | 24 | done(that.createResult(lable, "No link[rel=preload] elements found.", 'warning', what, prio)); 25 | return null; 26 | } -------------------------------------------------------------------------------- /dist/default-rules/static-body-h1.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | 3 | var dom = page.getStaticDom(); 4 | let selector = 'h1'; 5 | var h1 = dom.querySelectorAll(selector); 6 | if (h1.length>0) { 7 | if(h1.length === 1) { 8 | if(h1[0].innerText.trim()!='') { 9 | done(this.createResult('BODY', 'H1: '+h1[0].innerText+this.partialCodeLink(h1)+this.highlightLink(selector, "Highlight H1", '5px solid green'), 'info', 'static', 990)); 10 | return null; 11 | } 12 | else { 13 | done(this.createResult('BODY', '<H1> is empty!'+this.partialCodeLink(h1), 'error', 'static')); 14 | return null; 15 | } 16 | } 17 | done(this.createResult('BODY', 'Multiple <H1> found!'+this.partialCodeLink(h1)+this.highlightLink(selector, "Highlight H1"), 'warning', 'static')); 18 | return null; 19 | } 20 | done(this.createResult('BODY', 'No <H1> found!', 'error', 'static')); 21 | return null; 22 | } 23 | -------------------------------------------------------------------------------- /dist/default-rules/static-head-amp.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | var amp = dom.querySelector('head > link[rel=amphtml]'); 4 | 5 | if (amp) { 6 | var href = amp.getAttribute('href'); 7 | var text = `Link Rel Amphtml URL: ${href} ${this.partialCodeLink(amp)} Validate`; 8 | done(this.createResult('HEAD', text, 'info', 'static', 500)); 9 | } 10 | done(); 11 | } 12 | -------------------------------------------------------------------------------- /dist/default-rules/static-head-brand-in-title.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let lable = "HEAD"; 4 | let msg = ""; 5 | let type = "warning"; 6 | let what = "static"; 7 | let dom = page.getStaticDom(); 8 | let url = page.getURL('last'); 9 | let u = new URL(url); 10 | let host = u.host; 11 | let extractBrand = (host) => 12 | { 13 | let parts = host.split('.'); 14 | let longest = parts.reduce(function (a, b) { return a.length > b.length ? a : b; }); 15 | return longest; 16 | } 17 | let brand = extractBrand(host); 18 | if(!dom){done();} 19 | let titletags = dom.querySelectorAll('head > title'); 20 | let title = titletags[0].innerText.trim(); 21 | 22 | if(title.toLowerCase().includes(brand.toLowerCase())) {done();} 23 | msg = "Meta-Title does not include the brand \""+brand+"\"." 24 | done(this.createResult(lable, msg + this.partialCodeLink(titletags[0]), type, what)); 25 | } -------------------------------------------------------------------------------- /dist/default-rules/static-head-link-shortlink.js: -------------------------------------------------------------------------------- 1 | function(page,done) 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let url = page.getURL('last'); 6 | let shortlink_elem = dom.querySelector('link[rel="shortlink"]'); 7 | if(!shortlink_elem){done();return;} 8 | let shortlink = shortlink_elem.href; 9 | let type = "info"; 10 | if(shortlink_elem && shortlink) 11 | { 12 | if(shortlink!=url) 13 | { 14 | type = "warning"; 15 | } 16 | done(that.createResult('HEAD', 'Shortlink detected: '+shortlink+''+that.partialCodeLink(shortlink_elem), type));return; 17 | } 18 | done(); 19 | } -------------------------------------------------------------------------------- /dist/default-rules/static-head-meta-description.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | const what = 'static'; 4 | //if (some requirement) 5 | var meta_ds = dom.querySelectorAll('meta[name="description"]'); 6 | if (meta_ds.length > 0) { 7 | if (meta_ds > 1) { 8 | return this.createResult('HEAD', 'Multiple meta description found.' + this.partialCodeLink(meta_ds), 'error', what); 9 | } 10 | //some category of stuff your are testing i.e.: 'DOM', 'HEAD', 'BODY', 'HTTP', 'SPEED', ... 11 | var lable = 'HEAD'; 12 | var msg = 'Meta description: ' + meta_ds[0]['content']; 13 | msg = msg + this.partialCodeLink(meta_ds); 14 | //you can create a link showing only the partial code of a nodeList 15 | //msg = msg+' '+this.partialCodeLink(dom); 16 | var type = 'info'; //should be 'info', 'warning', 'error' 17 | done(this.createResult(lable, msg, type, what, 760)); 18 | } 19 | done(this.createResult('BODY', 'No meta description found.', 'error', what)); 20 | } -------------------------------------------------------------------------------- /dist/default-rules/static-head-meta-googlebot.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | var elements = dom.querySelectorAll('head>meta[name=googlebot]'); 4 | var type = 'info'; 5 | var msg =''; 6 | const what = 'static'; 7 | 8 | if (elements.length===1) { 9 | var content = elements[0].getAttribute('content'); 10 | var instructions = content.split(','); 11 | instructions.forEach(function(v,i,robot_instructions){ 12 | robot_instructions[i]=v.trim().toLowerCase(); 13 | }); 14 | if (instructions.indexOf('noindex')!=-1){type = 'warning';} 15 | //if (instructions.indexOf('index')!=-1){} 16 | if (instructions.indexOf('nofollow')!=-1){type = 'warning';} 17 | //if (instructions.indexOf('follow')!=-1){msg = msg + ' follow';} 18 | 19 | msg = 'Meta Googlebot: '+content+''+this.partialCodeLink(elements); 20 | 21 | done(this.createResult('HEAD', msg, 'info', what, 600)); 22 | return null; 23 | } 24 | 25 | if (elements.length > 1) { 26 | done(this.createResult('HEAD', "Multiple googlebot meta tags."+this.partialCodeLink(elements), 'warning', what)); 27 | return null; 28 | } 29 | done(); 30 | } 31 | -------------------------------------------------------------------------------- /dist/default-rules/static-head-meta-robots.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | const what = 'static'; 4 | var elements = dom.querySelectorAll('head>meta[name=robots]'); 5 | var type = 'info'; 6 | var msg =''; 7 | 8 | if (elements.length===1) { 9 | var content = elements[0].getAttribute('content'); 10 | var instructions = content.split(','); 11 | instructions.forEach(function(v,i,robot_instructions){ 12 | robot_instructions[i]=v.trim().toLowerCase(); 13 | }); 14 | if (instructions.indexOf('noindex')!=-1){type = 'warning';} 15 | //if (instructions.indexOf('index')!=-1){} 16 | if (instructions.indexOf('nofollow')!=-1){type = 'warning';} 17 | //if (instructions.indexOf('follow')!=-1){msg = msg + ' follow';} 18 | 19 | msg = 'Meta Robots: '+content+''+this.partialCodeLink(elements); 20 | 21 | done(this.createResult('HEAD', msg, type, what, 610)); 22 | } 23 | 24 | if (elements.length > 1) { 25 | done(this.createResult('HEAD', "Multiple robots meta tags."+this.partialCodeLink(elements), 'warning', what)); 26 | } 27 | 28 | done(this.createResult('HEAD', "No robots meta tag.", 'info', what)); 29 | } 30 | -------------------------------------------------------------------------------- /dist/default-rules/static-head-open-graph-description.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let og_description = dom.querySelector('meta[property="og:description"]'); 6 | if(og_description && og_description.content) 7 | { 8 | done(that.createResult('HEAD', 'Open Graph (Facebook) description: "'+og_description.content+'"'+that.partialCodeLink(og_description), 'info'));return; 9 | } 10 | done(); 11 | } -------------------------------------------------------------------------------- /dist/default-rules/static-head-open-graph-title.js: -------------------------------------------------------------------------------- 1 | function(page,done) 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let og_title = dom.querySelector('meta[property="og:title"]'); 6 | if(og_title && og_title.content) 7 | { 8 | done(that.createResult('HEAD', 'Open Graph (Facebook) title: "'+og_title.content+'"'+that.partialCodeLink(og_title), 'info'));return; 9 | } 10 | done(); 11 | } -------------------------------------------------------------------------------- /dist/default-rules/static-head-open-graph-url.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let og_url = dom.querySelector('meta[property="og:url"]'); 6 | let c = (dom.querySelector('link[rel=canonical]')&&dom.querySelector('link[rel=canonical]').href); 7 | let l = page.getLocation().href; 8 | if(og_url && og_url.content) 9 | { 10 | if(c && og_url.content && c.trim() !== og_url.content.trim()) 11 | { 12 | done(that.createResult('HEAD', 'Open Graph (Facebook) URL: '+og_url.content+' does not equal canonical('+c+'")'+that.partialCodeLink(og_url), 'warning'));return; 13 | } else if(l && og_url.content && l.trim() !== og_url.content.trim()) 14 | { 15 | done(that.createResult('HEAD', 'Open Graph (Facebook) URL: '+og_url.content+' does not equal document location ('+l+')'+that.partialCodeLink(og_url), 'warning'));return; 16 | } 17 | done(that.createResult('HEAD', 'Open Graph (Facebook) URL: '+og_url.content+'" (OK)'+that.partialCodeLink(og_url)+' Fb Debugger', 'info'));return; 18 | } 19 | done(); 20 | } -------------------------------------------------------------------------------- /dist/default-rules/static-head-title-length.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let lable = "HEAD"; 4 | let msg = "Meta-Title length: "; 5 | let type = "info"; 6 | let what = "static"; 7 | let dom = page.getStaticDom(); 8 | if(!dom){done();} 9 | let titletags = dom.querySelectorAll('head > title'); 10 | let title = titletags[0].innerText.trim(); 11 | msg = msg + title.length 12 | 13 | if(title.length < 40) 14 | { 15 | msg = msg + " (short title)"; 16 | type = "warning"; 17 | } 18 | if(title.length == 0) 19 | { 20 | msg = msg + " (blank title)"; 21 | type = "error"; 22 | } 23 | if(title.length > 120) 24 | { 25 | msg = msg + " (long title)"; 26 | type = "warning"; 27 | } 28 | if(title.length > 240) 29 | { 30 | msg = msg + " (very long title)"; 31 | type = "warning"; 32 | } 33 | done(this.createResult(lable, msg + this.partialCodeLink(titletags[0]), type, what)); 34 | } -------------------------------------------------------------------------------- /dist/default-rules/static-head-title.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | const what = 'static'; 4 | var location = page.getLocation('static'); 5 | //check if we got some data to work with 6 | if (!dom) { 7 | return null; 8 | } 9 | var titletags = dom.querySelectorAll('head > title'); 10 | var lable = 'HEAD'; 11 | if (titletags.length > 0) { 12 | if (titletags.length === 1) { 13 | done(this.createResult(lable, 'SEO-<title>: ' + titletags[0].innerText + this.partialCodeLink(titletags), 'info', what, 1000)); 14 | //check size //throw to short, throw to long 15 | //check brand 16 | //TODO check for common non descriptive titles 17 | } else { 18 | done(this.createResult(lable, 'Multiple title-tags found in head. There should be only one.' + this.partialCodeLink(titletags), 'error', what)); 19 | } 20 | } else { 21 | done(this.createResult(lable, 'No title-tag found in head.', 'error', what)); 22 | } 23 | } -------------------------------------------------------------------------------- /dist/default-rules/static-idle-dom-unavailable-after.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let lable = "DOM"; 6 | let msg = " unavailable after meta information found:"; 7 | let type = "warning"; 8 | let what = "static"; 9 | let selector ="meta[content^=unavailable_after]"; 10 | let m = dom.querySelectorAll(selector); 11 | //via https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-core/audits/seo/is-crawlable.js 12 | function isUnavailable(directive) { 13 | const parts = directive.split(':'); 14 | 15 | if (parts.length <= 1 || parts[0] !== 'unavailable_after') { 16 | return false; 17 | } 18 | 19 | const date = Date.parse(parts.slice(1).join(':')); 20 | 21 | return !isNaN(date) && date < Date.now(); 22 | } 23 | if (!m || (m && m.length < 1)) 24 | { 25 | done();return; 26 | } 27 | 28 | 29 | msg = m.length + msg +" "+ m[0].content +that.partialCodeLink(m); 30 | 31 | if(isUnavailable(m[0].content)) 32 | { 33 | type = "error"; 34 | msg = msg + "Date already in the past!"; 35 | } 36 | 37 | done(that.createResult(lable, msg, type, what));return; 38 | } 39 | -------------------------------------------------------------------------------- /dist/export.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/dist/export.zip -------------------------------------------------------------------------------- /dist/images/_icon-inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/dist/images/_icon-inactive.png -------------------------------------------------------------------------------- /dist/images/_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/dist/images/_icon.png -------------------------------------------------------------------------------- /dist/images/icon-inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/dist/images/icon-inactive.png -------------------------------------------------------------------------------- /dist/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/dist/images/icon.png -------------------------------------------------------------------------------- /dist/js/codeview.js: -------------------------------------------------------------------------------- 1 | !function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=251)}({251:function(e,n,t){"use strict";var r=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:window.location.search,n={};return e.replace(new RegExp("([^?=&]+)(=([^&]*))?","g"),(function(e,t,r,o){n[t]=o})),n}();window.document.querySelector("body > pre").innerText=decodeURIComponent(r.show)}}); -------------------------------------------------------------------------------- /dist/js/document_end.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=257)}({257:function(e,t,n){"use strict";chrome.runtime.sendMessage({event:"document_end",data:{html:document.querySelector("html").innerHTML,location:document.location}})}}); -------------------------------------------------------------------------------- /dist/js/document_idle.js: -------------------------------------------------------------------------------- 1 | !function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=258)}({258:function(e,n,t){"use strict";var r=window.performance;chrome.runtime.sendMessage({event:"window_performance",data:{snapshot:{performance:r,navigation:performance.getEntriesByType("navigation"),paint:performance.getEntriesByType("paint")},location:document.location}}),chrome.runtime.sendMessage({event:"document_idle",data:{html:document.querySelector("html").innerHTML,location:document.location}})}}); -------------------------------------------------------------------------------- /dist/js/document_start.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=259)}({259:function(e,t,n){"use strict";document.addEventListener("DOMContentLoaded",(function e(t){chrome.runtime.sendMessage({event:"DOMContentLoaded",data:{html:document.querySelector("html").innerHTML,location:document.location}}),document.removeEventListener("DOMContentLoaded",e)}),{once:!0});document.addEventListener("load",(function e(t){chrome.runtime.sendMessage({event:"load",data:{html:document.querySelector("html").innerHTML,location:document.location}}),document.removeEventListener("load",e)}),{once:!0})}}); -------------------------------------------------------------------------------- /dist/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | f19n - Popup 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /dist/rules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Settings - Franz Enzenhofer SEO 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /dist/sandbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nothing todo here. Its just a container to hold and run the sandbox.js script. 6 | 7 | -------------------------------------------------------------------------------- /privacy.md: -------------------------------------------------------------------------------- 1 | Fullstackoptimization Live Test 2 | 3 | does currently not track anything 4 | 5 | it does not set any cookies 6 | 7 | does not send any data somewhere 8 | 9 | does not have any analytics within the app 10 | 11 | on the chrome app store install page google analytics is enabled 12 | -------------------------------------------------------------------------------- /promotion/1280-800-screenshot-f19n-lt-software-testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/1280-800-screenshot-f19n-lt-software-testing.png -------------------------------------------------------------------------------- /promotion/1280-800-screenshot-f19n-lt-tripadvisor-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/1280-800-screenshot-f19n-lt-tripadvisor-settings.png -------------------------------------------------------------------------------- /promotion/1280-800-screenshot-f19n-lt-tripadvisor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/1280-800-screenshot-f19n-lt-tripadvisor.png -------------------------------------------------------------------------------- /promotion/_old-Product-hunt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/_old-Product-hunt.png -------------------------------------------------------------------------------- /promotion/_old-olt-icon-128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/_old-olt-icon-128px.png -------------------------------------------------------------------------------- /promotion/_old-olt-large-tile-960px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/_old-olt-large-tile-960px.png -------------------------------------------------------------------------------- /promotion/_old-olt-logo-144px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /promotion/_old-olt-marquee-tile-1400px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/_old-olt-marquee-tile-1400px.png -------------------------------------------------------------------------------- /promotion/_old-olt-small-tile-440px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/_old-olt-small-tile-440px.png -------------------------------------------------------------------------------- /promotion/_old-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/_old-results.png -------------------------------------------------------------------------------- /promotion/_old-sample-output-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/_old-sample-output-2.png -------------------------------------------------------------------------------- /promotion/_old-sample-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/_old-sample-output.png -------------------------------------------------------------------------------- /promotion/_old-user-doku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/_old-user-doku.png -------------------------------------------------------------------------------- /promotion/f19n-lt-icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/f19n-lt-icon-128.png -------------------------------------------------------------------------------- /promotion/f19n-lt-large-tile-960px-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/f19n-lt-large-tile-960px-01.png -------------------------------------------------------------------------------- /promotion/f19n-lt-marquee-tile-1400px-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/f19n-lt-marquee-tile-1400px-01.png -------------------------------------------------------------------------------- /promotion/f19n-lt-small-tile-440px-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/f19n-lt-small-tile-440px-01.png -------------------------------------------------------------------------------- /promotion/screenshot-f19n-lt-manage-rules-user-doku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/screenshot-f19n-lt-manage-rules-user-doku.png -------------------------------------------------------------------------------- /promotion/screenshot-f19n-lt-panel-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/screenshot-f19n-lt-panel-github.png -------------------------------------------------------------------------------- /promotion/screenshot-f19n-lt-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/screenshot-f19n-lt-settings.png -------------------------------------------------------------------------------- /promotion/screenshot-f19n-lt-software-testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/screenshot-f19n-lt-software-testing.png -------------------------------------------------------------------------------- /promotion/screenshot-f19n-lt-tripadvisor-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/screenshot-f19n-lt-tripadvisor-settings.png -------------------------------------------------------------------------------- /promotion/screenshot-f19n-lt-tripadvisor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/screenshot-f19n-lt-tripadvisor.png -------------------------------------------------------------------------------- /promotion/screenshot-f19n-lt-user-doku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/screenshot-f19n-lt-user-doku.png -------------------------------------------------------------------------------- /promotion/screenshot-f19n-lt-wikipedia-user-doku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/screenshot-f19n-lt-wikipedia-user-doku.png -------------------------------------------------------------------------------- /promotion/src/_old-olt-large-tile-960px.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/src/_old-olt-large-tile-960px.ai -------------------------------------------------------------------------------- /promotion/src/_old-olt-logo-144px.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/src/_old-olt-logo-144px.ai -------------------------------------------------------------------------------- /promotion/src/_old-olt-marquee-tile-1400px.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/src/_old-olt-marquee-tile-1400px.ai -------------------------------------------------------------------------------- /promotion/src/_old-olt-small-tile-440px.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/src/_old-olt-small-tile-440px.ai -------------------------------------------------------------------------------- /promotion/src/f19n-lt-large-tile-960px.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/src/f19n-lt-large-tile-960px.ai -------------------------------------------------------------------------------- /promotion/src/f19n-lt-logo-144px.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/src/f19n-lt-logo-144px.ai -------------------------------------------------------------------------------- /promotion/src/f19n-lt-marquee-tile-1400px.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/src/f19n-lt-marquee-tile-1400px.ai -------------------------------------------------------------------------------- /promotion/src/f19n-lt-small-tile-440px.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franzenzenhofer/f19n-obtrusive-livetest/b493f446f05d40ac849c9590f139a6a503fe6a73/promotion/src/f19n-lt-small-tile-440px.ai -------------------------------------------------------------------------------- /sample-rules/MIT-LICENSE.md: -------------------------------------------------------------------------------- 1 | This License applies to all files and all code within this source directory. 2 | 3 | The MIT License (MIT) 4 | ===================== 5 | 6 | Copyright © `2016` `Franz Enzenhofer` 7 | 8 | Permission is hereby granted, free of charge, to any person 9 | obtaining a copy of this software and associated documentation 10 | files (the “Software”), to deal in the Software without 11 | restriction, including without limitation the rights to use, 12 | copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following 15 | conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /sample-rules/a-promotion-for-my-awesome-ebook-Q4-2018.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | let lable = "BOOK"; 5 | let msg = 'Please support this app! Buy the awesome book
"Understanding SEO" by the creator of this software. - New: DRM-Free Ebook.
You can disable this message in the Settings.'; 6 | let type = "promotion"; 7 | let what = ""; 8 | 9 | done(that.createResult(lable, msg, type, what));return; 10 | 11 | 12 | } -------------------------------------------------------------------------------- /sample-rules/async-detect-language-api-google.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "DEBUG"; 6 | let msg = "Hello World"; 7 | let type = "info"; 8 | let what = "nothing"; 9 | let key = '%GOOGLE_API_KEY%'; if(key==='%'+'GOOGLE_API_KEY%'){ done();return;} 10 | let dom = page.getIdleDom(); 11 | 12 | let u = 'https://translation.googleapis.com/language/translate/v2/detect?key='+key; 13 | 14 | let stringistring = dom.body.innerText.replace(/)<[^<]*)*<\/script>/gi, ' '); 15 | 16 | fetch(u, 17 | { 18 | method: "POST", 19 | mode: 'cors', 20 | headers: new Headers({ 21 | 'Content-Type': 'application/json' 22 | }), 23 | body: JSON.stringify({ 24 | "q": stringistring 25 | }) 26 | }).then(function(response){ 27 | 28 | if(response.status == 200) 29 | { 30 | response.json().then(function(data){ 31 | 32 | if(data.mobileFriendliness=="MOBILE_FRIENDLY") 33 | { 34 | //done(that.createResult('MOBILE', "Page is Mobile Friendly.", "info")); 35 | } 36 | else { 37 | //done(that.createResult('MOBILE', "Page is "+data.mobileFriendliness+".", "error")); 38 | } 39 | }); 40 | } 41 | }); 42 | 43 | //done(that.createResult(lable, msg, type, what));return; 44 | } 45 | -------------------------------------------------------------------------------- /sample-rules/debug-hello-world.js: -------------------------------------------------------------------------------- 1 | (page,done) => 2 | { 3 | let that = this; 4 | 5 | let lable = "DEBUG"; 6 | let msg = "Hello World"; 7 | let type = "info"; 8 | let what = "nothing"; 9 | 10 | done(that.createResult(lable, msg, type, what));return; 11 | } 12 | -------------------------------------------------------------------------------- /sample-rules/debug-stringify-page-object.js: -------------------------------------------------------------------------------- 1 | function(page,done){ 2 | var debug = '%DEBUG%'; //must be set to true 3 | if(debug==='true') 4 | { 5 | done(this.createResult("DEBUG", "Complete Page Object "+this.stringifyLink(page), "info", null, 9000)); 6 | } 7 | done(); 8 | return null; 9 | } -------------------------------------------------------------------------------- /sample-rules/deprecated/asyncRuleTest.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | this.fetch('https://orf.at', { responseFormat: 'text' }, (response) => { 3 | done(this.createResult('FETCH', `response from orf.at: ${response.length}`, 'info')); 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /sample-rules/deprecated/asyncRuleTest2.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | const url = page.getURL('first'); 3 | 4 | setTimeout(() => { 5 | this.fetch('https://jsonplaceholder.typicode.com/posts/1', { responseFormat: 'json' }, (response) => { 6 | done(this.createResult('FETCH', `response from heute.at (${url}): ${JSON.stringify(response)}`, 'info')); 7 | }); 8 | }, 5000); 9 | } 10 | -------------------------------------------------------------------------------- /sample-rules/deprecated/body-nofollow-links.js: -------------------------------------------------------------------------------- 1 | function(eventCollection) { 2 | // Get the last documentIdleEvent from the event collection 3 | var documentIdleEvent = eventCollection.documentIdleEvent(); 4 | const what = "idle"; 5 | 6 | // Return if no documentIdleEvent (and so location & document) found 7 | if (!documentIdleEvent) { return null; } 8 | 9 | var { location, document } = documentIdleEvent; 10 | 11 | // Helper method to normalize url and extract domain 12 | function domain(url) { 13 | return url.replace('http://', '').replace('https://', '').split('/')[0]; 14 | }; 15 | 16 | // Find all -tags with rel=nofollow pointing to another domain 17 | var outgoingLinks = Array.prototype.filter.call(document.querySelectorAll('a[href^=http][rel=nofollow]'), function(link) { 18 | var href = link.getAttribute('href'); 19 | return domain(href) != domain(location.href); 20 | }); 21 | 22 | return outgoingLinks.length > 0 ? this.createResult('LINKS', `${outgoingLinks.length} outgoing links with rel nofollow`, 'info', what) : null; 23 | } 24 | -------------------------------------------------------------------------------- /sample-rules/deprecated/brokenRule.js: -------------------------------------------------------------------------------- 1 | // Used to test upload for broken rules 2 | 3 | function(page) { 4 | syntax error 5 | } 6 | -------------------------------------------------------------------------------- /sample-rules/deprecated/checkForPreconnect.js: -------------------------------------------------------------------------------- 1 | function(page) { 2 | var dom = page.getStaticDom(); 3 | const what = 'static'; 4 | if (!dom) { return null; } 5 | var preconnect = dom.querySelectorAll('link[rel=preconnect]'); 6 | 7 | if (preconnect.length > 0 ) { 8 | var hint_s = 'hints'; 9 | if (preconnect.length === 1) { hint_s = 'hint';} 10 | return this.createResult('SPEED', ''+preconnect.length+' preconnect '+hint_s+' found.', 'info', what); 11 | } 12 | 13 | return this.createResult('SPEED', 'No preconnect hints found.', 'warning', what); 14 | } 15 | -------------------------------------------------------------------------------- /sample-rules/deprecated/coffee-rules/amp.coffee: -------------------------------------------------------------------------------- 1 | (eventCollection) -> 2 | documentIdleEvent = eventCollection.documentIdleEvent() 3 | 4 | until documentIdleEvent then return null 5 | { location, document } = documentIdleEvent 6 | 7 | amp = document.querySelector('link[rel=amp]') 8 | 9 | if (amp) 10 | href = amp.getAttribute('href'); 11 | text = "amp: ${href}" 12 | return @createResult('HEAD', text, 'info') 13 | #return text ? this.createResult('HEAD', text, type) : null; 14 | -------------------------------------------------------------------------------- /sample-rules/deprecated/coffee-rules/amp.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | function(eventCollection) { 3 | var amp, document, documentIdleEvent, href, location, text; 4 | documentIdleEvent = eventCollection.documentIdleEvent(); 5 | while (!documentIdleEvent) { 6 | return null; 7 | } 8 | location = documentIdleEvent.location, document = documentIdleEvent.document; 9 | amp = document.querySelector('link[rel=amphtml]'); 10 | if (amp) { 11 | href = amp.getAttribute('href'); 12 | text = "amp: ${href}"; 13 | } 14 | return this.createResult('HEAD', text, 'info'); 15 | } 16 | -------------------------------------------------------------------------------- /sample-rules/deprecated/configurableRuleTest.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | return done(this.createResult('TEST', '%API_KEY%', 'info')); 3 | } 4 | -------------------------------------------------------------------------------- /sample-rules/deprecated/contentEncodingNotGzip.js: -------------------------------------------------------------------------------- 1 | function(eventCollection) { 2 | var onHeadersReceivedEvent = eventCollection.firstEventOfType('onHeadersReceived'); 3 | var { responseHeaders } = onHeadersReceivedEvent; 4 | var encoding = responseHeaders['content-encoding']; 5 | return !encoding || encoding !== 'gzip' ? this.createResult(5, 'HTTP', `Content-Encoding: ${encoding}`, 'warning') : null; 6 | } 7 | -------------------------------------------------------------------------------- /sample-rules/deprecated/deprecated_hasCanonicalTags.js: -------------------------------------------------------------------------------- 1 | function(eventCollection) { 2 | var documentIdleEvent = eventCollection.documentIdleEvent(); 3 | 4 | // Return if no documentIdleEvent (and so location & document) found 5 | if (!documentIdleEvent) { return null; } 6 | 7 | var { location, document } = documentIdleEvent; 8 | var text = null; 9 | var type = 'info'; 10 | 11 | var canonical = document.querySelector('link[rel=canonical]'); 12 | 13 | // If we found some link with rel=canonical check the href attribute 14 | if (canonical) { 15 | var href = canonical.getAttribute('href'); 16 | text = `canonical: ${href}`; 17 | 18 | // Change type to warning if href does not match with location.href 19 | if (href != location.href) { 20 | text = `canonical: ${href} != ${location.href}`; 21 | type = 'warning'; 22 | } 23 | } 24 | 25 | return text ? this.createResult('HEAD', text, type) : null; 26 | } 27 | -------------------------------------------------------------------------------- /sample-rules/deprecated/documentSize.js: -------------------------------------------------------------------------------- 1 | function(eventCollection) { 2 | var onHeadersReceivedEvent = eventCollection.firstEventOfType('onHeadersReceived'); 3 | var { responseHeaders } = onHeadersReceivedEvent; 4 | var contentLength = responseHeaders['content-length']; 5 | var encoding = responseHeaders['content-encoding']; 6 | var contentSizeInKb = contentLength / 1024; 7 | return encoding === 'gzip' && contentSizeInKb > 14.6 ? this.createResult(2, 'SPEED', `HTML size gzip: ${contentSizeInKb}`, 'warning') : null; 8 | } 9 | -------------------------------------------------------------------------------- /sample-rules/deprecated/http-has-unavailable-after-header.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var hh = page.getHttpHeaders("last"); 3 | var hr = page.getRawHttpHeaders("last"); 4 | var l = hh['unavailable_after'] || false; 5 | let msg = ""; 6 | let type = "info"; 7 | 8 | if(l) 9 | { 10 | msg = '"Unavailable after HTTP header detected:'; 11 | const date = Date.parse(l.join(':')); 12 | if(!isNaN(date)) 13 | { 14 | msg = msg + " "+l+" not a valid date."; 15 | type="error"; 16 | } 17 | else if(date < Date.now()) 18 | { 19 | msg = msg + " "+l+" already in the past!" 20 | if(type!=='error'){ type="warning";} 21 | } 22 | else 23 | { 24 | msg = msg + " "+l; 25 | } 26 | msg=msg+"."+this.partialCodeLink(hr); 27 | } 28 | done(this.createResult("HTTP", msg, type));return; 29 | } 30 | -------------------------------------------------------------------------------- /sample-rules/deprecated/site-blocked-by-robotstxt-googlebot.js: -------------------------------------------------------------------------------- 1 | function(page, done){ 2 | var robotstxt = page.getRobotsTxtStatus(); 3 | var robots = this.robotsParser(robotstxt.location.href, robotstxt.txt); 4 | var url = page.getURL("last"); 5 | //var ua = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"; 6 | var ua = "Googlebot"; 7 | if(robots.isAllowed(url, ua)) 8 | { 9 | //return this.createResult('SITE', url+' not blocked by the robots.txt', 'info'); 10 | //return null; 11 | } 12 | done(this.createResult('SITE', ''+url+' blocked by '+robotstxt.location.href+'', 'error')); 13 | } 14 | -------------------------------------------------------------------------------- /sample-rules/deprecated/site-http-soft-404-check.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var soft404 = page.getSoft404Status(); 3 | if (soft404.ok === false) 4 | { 5 | var msg = "Soft 404 Error: "+soft404.location.href+" → HTTP "+soft404.status 6 | done(this.createResult('HTTP', msg, 'error')); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample-rules/deprecated/site-robotstxt-check.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var robotstxt = page.getRobotsTxtStatus(); 3 | if (robotstxt.ok === false) 4 | { 5 | if (robotstxt.status === 404) { 6 | var msg = "No robots.txt found. "+robotstxt.location.href+" → HTTP "+robotstxt.status+" "+robotstxt.statusText; 7 | done(this.createResult('SITE', msg,'info')); 8 | } 9 | var msg = "robots.txt error! "+robotstxt.location.href+" → HTTP "+robotstxt.status+" "+robotstxt.statusText+" ("+robotstxt.contentType+")"; 10 | done(this.createResult('SITE', msg,'error')); 11 | } 12 | done(this.createResult('SITE', "robots.txt found.", 'info')); 13 | } 14 | -------------------------------------------------------------------------------- /sample-rules/deprecated/static-head-meta-description-length.js: -------------------------------------------------------------------------------- 1 | function(page) { 2 | var dom = page.getStaticDom(); 3 | const what = 'static'; 4 | var md = dom.querySelector('meta[name="description"]'); 5 | 6 | if (md) { 7 | return this.createResult('HEAD', 'Meta description length: '+md.content.trim().length+this.partialCodeLink(md), 'info', what); 8 | } 9 | return null; 10 | } 11 | -------------------------------------------------------------------------------- /sample-rules/deprecated/static-head-title-length.js: -------------------------------------------------------------------------------- 1 | function(page) { 2 | var dom = page.getStaticDom(); 3 | const what = 'static'; 4 | var title = dom.querySelector('head>title'); 5 | 6 | if (title) { 7 | return this.createResult('HEAD', 'Title-tag length: '+title.innerText.trim().length+this.partialCodeLink(title), 'info', what); 8 | } 9 | return null; 10 | } 11 | -------------------------------------------------------------------------------- /sample-rules/deprecated/static-html-schemaorg-existence.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | const what = 'static'; 4 | var any = dom.querySelectorAll('*[itemtype]'); 5 | var url = page.getURL(); 6 | 7 | if (any && any.length > 1) { 8 | var a=[]; 9 | any.forEach(function(e){ 10 | a.push(e.getAttribute('itemtype').trim().replace('http.*org/','')); 11 | }); 12 | done(this.createResult('HTML', 'Schema.org: '+a.join(', ')+ '
Structured Data Testing Tool', 'info', what)); 13 | } 14 | else 15 | { 16 | done(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample-rules/deprecated/static-idle-head-title-compare.js: -------------------------------------------------------------------------------- 1 | function(page, done) { 2 | var dom = page.getStaticDom(); 3 | var idom = page.getIdleDom(); 4 | var st = dom.querySelector('title'); 5 | var it = idom.querySelector('title'); 6 | 7 | 8 | if (st && it && st.innerText && it.innerText) { 9 | if (st.innerText.trim() !== it.innerText.trim()) 10 | { 11 | done(this.createResult('HEAD', "Static and Idle Titles to not match! "+this.partialCodeLink('Static DOM title:',st,'Idle DOM title:',it), "error")); 12 | } 13 | else 14 | { 15 | done(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample-rules/deprecated/statusCode.js: -------------------------------------------------------------------------------- 1 | function(page) { 2 | var sc = page.getStatusCode(); 3 | var url = page.getURL(); 4 | if (sc) { 5 | var type = 'info'; 6 | var anchor = ''; 7 | if (sc < 200) { type = 'warning'; anchor='#1xx_Informational';} 8 | if (sc <= 200) { anchor='#2xx_Success';} 9 | if (sc >= 300) { type = 'warning'; anchor='#3xx_Redirection'; } 10 | if (sc >= 400) { type = 'error'; anchor="#4xx_Client_Error";} 11 | if (sc >= 500) { type ='error'; anchor="#5xx_Server_Error" } 12 | var text = `${url} → HTTP ${sc}`; 13 | return this.createResult('HTTP', text, type); 14 | } 15 | return null; 16 | } 17 | -------------------------------------------------------------------------------- /sample-rules/deprecated/statusCodeNot200.js: -------------------------------------------------------------------------------- 1 | function(eventCollection) { 2 | var onHeadersReceivedEvent = eventCollection.firstEventOfType('onHeadersReceived'); 3 | if (!onHeadersReceivedEvent) { return null; } 4 | var { statusCode, responseHeaders: { location } } = onHeadersReceivedEvent; 5 | return Number(statusCode) !== 200 ? this.createResult(5, 'HTTP', `HTTP ${statusCode} -> ${location}`, 'warning') : null; 6 | } 7 | -------------------------------------------------------------------------------- /sample-rules/deprecated/titletag.js: -------------------------------------------------------------------------------- 1 | function(page) { 2 | var dom = page.getStaticDom(); 3 | var location = page.getLocation('static'); 4 | 5 | //check if we got some data to work with 6 | if (!dom) { return null; } 7 | var titletags = dom.getElementsByTagName('title'); 8 | var lable = 'HEAD'; 9 | 10 | if (titletags.length > 0) { 11 | if(titletags.length === 1) 12 | { 13 | return this.createResult(lable, 'Title: '+titletags[0].innerText, 'info'); 14 | //check size //throw to short, throw to long 15 | //check brand 16 | //TODO check for common non descriptive titles 17 | } 18 | else 19 | { 20 | return this.createResult(lable, 'Multiple title-tags found. There should be only one.', 'error'); 21 | } 22 | } 23 | else 24 | { 25 | return this.createResult(lable, 'No title-tag found.', 'error'); 26 | } 27 | return null; 28 | } 29 | -------------------------------------------------------------------------------- /sample-rules/deprecated/xRobotsTags.js: -------------------------------------------------------------------------------- 1 | function(eventCollection) { 2 | var onHeadersReceivedEvent = eventCollection.firstEventOfType('onHeadersReceived'); 3 | var { responseHeaders } = onHeadersReceivedEvent; 4 | return responseHeaders && responseHeaders['x-robots-tag'] ? this.createResult('HTTP', `X-Robots-Tag: ${responseHeaders['x-robots-tag']}`, 'warning') : null; 5 | } 6 | -------------------------------------------------------------------------------- /sample-rules/fb-share-count.js: -------------------------------------------------------------------------------- 1 | (page,done)=> 2 | { 3 | let that = this; 4 | let dom = page.getStaticDom(); 5 | let og_url = (dom.querySelector('meta[property="og:url"]')&&dom.querySelector('meta[property="og:url"]').content); 6 | 7 | let c = (dom.querySelector('link[rel=canonical]')&&dom.querySelector('link[rel=canonical]').href); 8 | let l = page.getLocation().href; 9 | let scu = "https://graph.facebook.com/?id="; 10 | 11 | let url = (og_url || c || l); 12 | if(url) 13 | { 14 | fetch(scu+url).then( 15 | (response) => { 16 | if (response.status !== 200) { done(that.createResult("fb", "Could not fetch Facebook share count."+' Fb Debugger', "unfinished"));return; } 17 | response.json().then((data) => { 18 | 19 | let msg = "Facebook share count: "+data.share.share_count+" shares, "+data.share.comment_count+" comment. "+that.stringifyLink(data)+'
Facebook Debugger'; 20 | done(that.createResult('Fb', msg, 'info', null, 800)); 21 | }); 22 | // done(that.createResult('SPEED', 'No Page Speed Insights mobile data. (Response Status '+response.status+' '+response.text+')', "warning")); 23 | return; 24 | } 25 | ) 26 | 27 | } 28 | else 29 | { 30 | done();return; 31 | } 32 | } -------------------------------------------------------------------------------- /sample-rules/google-api-token.js: -------------------------------------------------------------------------------- 1 | (_, done) => { 2 | const { googleApiAccessToken } = this.getGlobals(); 3 | done({ message: `Google API Token is: ${googleApiAccessToken}`, type: 'info', label: 'TEST', priority: 100000 }); 4 | } 5 | -------------------------------------------------------------------------------- /sample-rules/not-working/idle-body-font-size.js: -------------------------------------------------------------------------------- 1 | /* 2 | var all = document.getElementsByTagName("body"); 3 | 4 | for (var i=0, max=all.length; i < max; i++) { 5 | let f = window.getComputedStyle(all[i]).fontSize; 6 | } 7 | */ 8 | 9 | function(page, done) { 10 | var idom = page.getIdleDom(); 11 | let selector = "body > *"; 12 | var elems = idom.querySelectorAll(selector); 13 | let msg = ""; 14 | if (elems && elems.length>1) 15 | { 16 | for (let i=0, max=elems.length; i < max; i++) { 17 | let f = window.getComputedStyle(elems[i]).fontSize; 18 | 19 | 20 | 21 | msg = msg+" "+f 22 | } 23 | done(this.createResult('BODY',msg,'info','idle')); 24 | } 25 | done(); 26 | } -------------------------------------------------------------------------------- /src/javascripts/codeview.js: -------------------------------------------------------------------------------- 1 | var parseQueryString = function(str=window.location.search) { 2 | var objURL = {}; 3 | 4 | str.replace( 5 | new RegExp( "([^?=&]+)(=([^&]*))?", "g" ), 6 | function( $0, $1, $2, $3 ){ 7 | objURL[ $1 ] = $3; 8 | } 9 | ); 10 | return objURL; 11 | }; 12 | 13 | var params = parseQueryString(); 14 | 15 | window.document.querySelector('body > pre').innerText=decodeURIComponent(params['show']); 16 | -------------------------------------------------------------------------------- /src/javascripts/components/Panel/Panel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ResultList from './ResultList'; 3 | import Footer from './Footer'; 4 | 5 | export default function Panel(props) { 6 | const { onClosePanelRequest, storeKey, results, url } = props; 7 | return ( 8 |
9 | 10 |
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/javascripts/components/Panel/ResultItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | export default function ResultItem(props) { 5 | var { label, message, type, what} = props; 6 | label = label+""; 7 | message = message+""; 8 | var whatstring = '' 9 | if(what) 10 | { 11 | whatstring = (({what}) ); 12 | } 13 | 14 | return ( 15 | 16 | {type} 17 | 18 | {label} 19 | 20 | {whatstring} 21 | 22 | ); 23 | } 24 | 25 | // {type} 26 | -------------------------------------------------------------------------------- /src/javascripts/components/Rules/AddRule.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class AddRule extends Component { 4 | constructor(props) { 5 | super(props); 6 | this.handleAddRule = this.handleAddRule.bind(this); 7 | } 8 | 9 | handleAddRule() { 10 | const files = this.refs.file.files; 11 | 12 | const readFile = (files, i, onfile, eof) => { 13 | const file = files[i]; 14 | const reader = new FileReader(); 15 | reader.onload = (data) => { 16 | onfile(data, file.name); 17 | 18 | if (files[i + 1]) { 19 | readFile(files, i + 1, onfile, eof); 20 | } else { 21 | eof(); 22 | } 23 | }; 24 | reader.readAsText(file); 25 | }; 26 | 27 | const callback = (upload, name) => { 28 | const body = upload.target.result; 29 | this.props.onAddRule({ name, body }); 30 | }; 31 | 32 | readFile(files, 0, callback, () => { 33 | this.refs.file.value = ''; 34 | }); 35 | } 36 | 37 | render() { 38 | return ( 39 | 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/javascripts/components/Rules/EnabledSites.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import Config from './../../config'; 4 | 5 | export default class EnabledSites extends Component { 6 | render() { 7 | const { onChange, onModeChange, sites, mode } = this.props; 8 | return ( 9 |
10 |
11 |

Enabled sites - Whitelist / Blacklist

12 | 13 |
14 | 18 | 22 |
23 | 24 |
Blacklisted URL-schemas start with !
25 | 26 |