├── .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(/
9 |