From ac8bc316e8fd70a1af1648f573446beb02c616e7 Mon Sep 17 00:00:00 2001 From: quoid Date: Wed, 2 Mar 2022 22:28:22 -0500 Subject: [PATCH 01/47] bump version --- extension/Userscripts.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/extension/Userscripts.xcodeproj/project.pbxproj b/extension/Userscripts.xcodeproj/project.pbxproj index 426a0d70..6a3a4cee 100644 --- a/extension/Userscripts.xcodeproj/project.pbxproj +++ b/extension/Userscripts.xcodeproj/project.pbxproj @@ -679,7 +679,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; OTHER_LDFLAGS = ( "-framework", SafariServices, @@ -720,7 +720,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; OTHER_LDFLAGS = ( "-framework", SafariServices, @@ -755,7 +755,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; OTHER_LDFLAGS = ( "-framework", SafariServices, @@ -788,7 +788,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.1; + MARKETING_VERSION = 1.2; OTHER_LDFLAGS = ( "-framework", SafariServices, @@ -939,7 +939,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 4.1.0; + MARKETING_VERSION = 4.2.0; PRODUCT_BUNDLE_IDENTIFIER = "com.userscripts.macos.Userscripts-Extension"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -963,7 +963,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 4.1.0; + MARKETING_VERSION = 4.2.0; PRODUCT_BUNDLE_IDENTIFIER = "com.userscripts.macos.Userscripts-Extension"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -989,7 +989,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 4.1.0; + MARKETING_VERSION = 4.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.userscripts.macos; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -1014,7 +1014,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 4.1.0; + MARKETING_VERSION = 4.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.userscripts.macos; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; From 768ed2ac4bf0b0ebc9d702656fe64bb996056bf8 Mon Sep 17 00:00:00 2001 From: quoid Date: Wed, 2 Mar 2022 22:31:09 -0500 Subject: [PATCH 02/47] use shared style vars and reset --- public/page/index.html | 4 +- public/popup/css/reset.css | 53 ----------------------- public/popup/css/variables.css | 51 ---------------------- public/popup/index.html | 4 +- public/{page/css => shared}/reset.css | 0 public/{page/css => shared}/variables.css | 0 rollup.config.js | 22 +++++++++- 7 files changed, 25 insertions(+), 109 deletions(-) delete mode 100644 public/popup/css/reset.css delete mode 100644 public/popup/css/variables.css rename public/{page/css => shared}/reset.css (100%) rename public/{page/css => shared}/variables.css (100%) diff --git a/public/page/index.html b/public/page/index.html index 1afc287c..702ec971 100644 --- a/public/page/index.html +++ b/public/page/index.html @@ -6,8 +6,8 @@ Userscripts Safari - - + + diff --git a/public/popup/css/reset.css b/public/popup/css/reset.css deleted file mode 100644 index 9ea2b45a..00000000 --- a/public/popup/css/reset.css +++ /dev/null @@ -1,53 +0,0 @@ -*, -*::before, -*::after { - box-sizing: border-box; -} - -body, -p { - margin: 0; -} - -ul { - list-style: none; - margin: 0; - padding: 0; -} - -button, -input, -select, -textarea { - font-family: inherit; - font-size: 100%; - line-height: inherit; - margin: 0; -} - -button, -input[type="button"], -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; -} - -input[type="search"] { - -webkit-appearance: textfield; -} - -::-webkit-input-placeholder { - color: inherit; - opacity: 0.54; -} - -::-webkit-search-decoration, -::-webkit-search-cancel-button, -::-webkit-search-results-button, -::-webkit-search-results-decoration { - -webkit-appearance: none; -} - -img { - border-style: none; -} diff --git a/public/popup/css/variables.css b/public/popup/css/variables.css deleted file mode 100644 index b6f01c06..00000000 --- a/public/popup/css/variables.css +++ /dev/null @@ -1,51 +0,0 @@ -:root { - --border-radius: 0.188rem; - --box-shadow: 0 0.5rem 1rem 0 rgba(0, 0, 0, 0.25); - --color-bg-primary: #323639; - --color-bg-secondary: #2f3337; - --color-script-highlighted: rgba(116, 178, 235, 0.1); - --color-black: #1d2023; - --color-blue: #74b1eb; - --color-green: #60f36c; - --color-grey: rgba(255, 255, 255, 0.15); - --color-red: #ff453a; - --color-yellow: #e4f360; - --letter-spacing-large: -0.031rem; - --letter-spacing-default: -0.029rem; - --letter-spacing-medium: -0.018rem; - --letter-spacing-small: -0.008rem; - --opacity-disabled: 0.3; - --text-color-primary: rgb(255, 255, 255); - --text-color-secondary: rgba(255, 255, 255, 0.65); - --text-color-disabled: rgba(255, 255, 255, 0.4); - --font-family: system-ui, -apple-system, "Helvetica Neue", "Helvetica", sans-serif; - --text-default: 1rem/1.5rem var(--font-family); - --text-large: 1.25rem/1.5rem var(--font-family); - --text-medium: 0.875rem/1.313rem var(--font-family); - --text-small: 0.719rem/1rem var(--font-family); - - /* editor variables */ - --editor-font: monaco, monospace; - --editor-font-size: 14px; - --editor-line-height: 24px; - --editor-invisible: rgba(255, 255, 255, 0.15); - --editor-active-line: var(--color-bg-secondary); - --editor-selected-bg: rgba(116, 178, 235, 0.35); - --editor-matched-highlight: rgba(116, 178, 235, 0.2); - --editor-search-highlight: rgba(255, 166, 0, 0.3); - --editor-number: #77e26a; - --editor-comment: rgba(255, 255, 255, 0.35); - --editor-def: #efc371; - --editor-default: #cdcfd1; - --editor-keyword: #96c3ed; - --editor-atom: #59ebf5; - --editor-operator: #8c99a7; - --editor-property: #e86c8a; - --editor-string: #f5eea2; - --editor-string-2: #cdabff; - --editor-error: var(--color-red); - --editor-cursor: #e3e7eb; - --editor-matching-bracket-color: #fff; - --editor-matching-bracket-border: var(--editor-number); - --editor-non-matching-bracket: var(--editor-error); -} diff --git a/public/popup/index.html b/public/popup/index.html index 779c5a41..0e790a80 100644 --- a/public/popup/index.html +++ b/public/popup/index.html @@ -5,8 +5,8 @@ Userscripts Safari - - + + diff --git a/public/page/css/reset.css b/public/shared/reset.css similarity index 100% rename from public/page/css/reset.css rename to public/shared/reset.css diff --git a/public/page/css/variables.css b/public/shared/variables.css similarity index 100% rename from public/page/css/variables.css rename to public/shared/variables.css diff --git a/rollup.config.js b/rollup.config.js index 01bcaae2..e8a64207 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,3 +1,4 @@ +import fs from "fs"; import svelte from "rollup-plugin-svelte"; import resolve from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; @@ -50,6 +51,8 @@ export default { css({ output: "public/" + directory + "/build/lib.css" }), + copyAndWatch("./public/shared/variables.css", "variables.css"), + copyAndWatch("./public/shared/reset.css", "reset.css"), inlineSvg({}), svelte({ // enable run-time checks when not in production @@ -76,10 +79,27 @@ export default { // Watch the `public` directory and refresh the // browser on changes when not in production !production && livereload({ - watch: ["public/" + directory, "public/shared"] + watch: ["public/" + directory] }) ], watch: { clearScreen: false } }; + +// https://dev.to/lukap/creating-a-rollup-plugin-to-copy-and-watch-a-file-3hi2 +function copyAndWatch(fileIn, fileOut) { + return { + name: "copy-and-watch", + async buildStart() { + this.addWatchFile(fileIn); + }, + async generateBundle() { + this.emitFile({ + type: "asset", + fileName: fileOut, + source: fs.readFileSync(fileIn) + }); + } + }; +} From ffc81754168b4de16242fe74a8a42b4ba9938803 Mon Sep 17 00:00:00 2001 From: quoid Date: Wed, 2 Mar 2022 22:43:38 -0500 Subject: [PATCH 03/47] update eslint config and rules --- .eslintignore | 3 -- .eslintrc.json | 108 ++++++++++++++++--------------------------------- 2 files changed, 34 insertions(+), 77 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 4e68479e..00000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -/node_modules/ -/public/build/ -codemirror/ diff --git a/.eslintrc.json b/.eslintrc.json index b33c1c1e..cc384952 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,92 +22,52 @@ "_browser": "writable", "browser": "readonly" }, + "ignorePatterns": ["/node_modules/", "/public/*/build/", "codemirror/"], "rules": { - "array-bracket-spacing": [ - "error", - "never" - ], - "arrow-parens": [ - "error", - "as-needed" - ], - "comma-spacing": [ - "error", - { - "before": false, - "after": true - } - ], + "array-bracket-spacing": ["error", "never"], + "arrow-parens": ["error", "as-needed"], + "brace-style": ["error", "1tbs", {"allowSingleLine": false}], + "comma-spacing": ["error", { + "before": false, + "after": true + }], + "curly": "error", "dot-notation": "error", "eqeqeq": ["error", "smart"], - "indent": [ - "error", - 4, - { - "SwitchCase": 1 - } - ], - "linebreak-style": [ - "error", - "unix" - ], - "keyword-spacing": [ - "error", - { - "after": true, - "before": true - } - ], + "indent": ["error", 4, {"SwitchCase": 1}], + "linebreak-style": ["error","unix"], + "keyword-spacing": ["error", { + "after": true, + "before": true + }], "no-bitwise": "error", "no-mixed-operators": "error", - "no-unused-vars": [ - "error", - { - "args": "none" - } - ], - "no-use-before-define": [ - "error", - { - "classes": true, - "functions": false, - "variables": true - } - ], + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-multiple-empty-lines": ["error", {"max": 1}], + "no-unused-vars": ["error", {"args": "none"}], + "no-useless-concat": "error", + "no-use-before-define": ["error", { + "classes": true, + "functions": false, + "variables": true + }], "no-var": "error", - "object-curly-spacing": [ - "error", - "never" - ], - "operator-assignment": [ - "error", - "always" - ], - "operator-linebreak": [ - "error", - "before" - ], + "nonblock-statement-body-position": ["error", "below"], + "object-curly-spacing": ["error", "never"], + "operator-assignment": ["error", "always"], + "operator-linebreak": ["error", "before"], "prefer-const": "error", - "quotes": [ - "error", - "double" - ], - "semi": [ - "error", - "always", - { - "omitLastInOneLineBlock": true - } - ], + "prefer-template": "error", + "quotes": ["error", "double"], + "semi": ["error", "always", {"omitLastInOneLineBlock": true}], "space-before-function-paren": ["error", { "anonymous": "never", "named": "never", "asyncArrow": "always" }], - "space-in-parens": [ - "error", - "never" - ], + "space-in-parens": ["error", "never"], + "template-curly-spacing": ["error", "never"], "jsx-a11y/a11y-missing-attribute": "off" } } From 6d6ca54fd0e2387f97942d3ebd2bc01acabae5bf Mon Sep 17 00:00:00 2001 From: quoid Date: Wed, 2 Mar 2022 22:43:47 -0500 Subject: [PATCH 04/47] update stylelint --- .stylelintrc.json | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/.stylelintrc.json b/.stylelintrc.json index 30769c11..479de6c8 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -3,21 +3,18 @@ "stylelint-config-standard", "stylelint-config-html/svelte" ], + "ignoreFiles": ["**/codemirror/*"], "rules": { - "comment-empty-line-before": [ - "always", - { - "ignore": ["after-comment", "stylelint-commands"] - } - ], + "alpha-value-notation": "percentage", + "comment-empty-line-before": ["always",{ + "ignore": ["after-comment", "stylelint-commands"] + }], "indentation": 4, + "max-empty-lines": 1, "no-descending-specificity": null, "selector-class-pattern": null, - "selector-pseudo-class-no-unknown": [ - true, - { - "ignorePseudoClasses": ["global"] - } - ] + "selector-pseudo-class-no-unknown": [true, { + "ignorePseudoClasses": ["global"] + }] } } From 9784cae4144a3a031c12dd5a15abdeeddd6b5d95 Mon Sep 17 00:00:00 2001 From: quoid Date: Thu, 3 Mar 2022 22:52:24 -0500 Subject: [PATCH 05/47] updated config --- .stylelintrc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stylelintrc.json b/.stylelintrc.json index 479de6c8..9915e994 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -3,9 +3,9 @@ "stylelint-config-standard", "stylelint-config-html/svelte" ], - "ignoreFiles": ["**/codemirror/*"], + "ignoreFiles": ["**/codemirror/*", "**/reset.css"], "rules": { - "alpha-value-notation": "percentage", + "alpha-value-notation": "number", "comment-empty-line-before": ["always",{ "ignore": ["after-comment", "stylelint-commands"] }], From 45f8663cff0ae85e2433e2591ee0f13007844adb Mon Sep 17 00:00:00 2001 From: quoid Date: Fri, 4 Mar 2022 21:34:21 -0500 Subject: [PATCH 06/47] code style updates --- gulpfile.js | 13 ++++++------- public/shared/variables.css | 20 ++++++++++---------- rollup.config.js | 25 +++++++++++++++---------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 6ad2f12b..3b6ec62b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,21 +6,20 @@ const autoprefixer = require("autoprefixer"); const htmlmin = require("gulp-html-minifier-terser"); const dom = require("gulp-dom"); const rename = require("gulp-rename"); - -const directory = process.env.NODE_ENV === "popup" ? "popup" : "page"; +const directory = process.env.NODE_ENV === "popup" ? "popup" : "page"; const copyLocation = "./temp"; const destLocation = "./extension/Userscripts Extension/Resources"; // clone public directory to avoid prefixing development assets function copy() { - return src("./public/" + directory + "/**/*") + return src(`./public/${directory}/**/*`) .pipe(dest(copyLocation)); } // autoprefix select stylesheets and overwrite in place function autoprefix() { - return src([copyLocation + "/build/bundle.css", copyLocation + "/css/global.css"]) + return src([`${copyLocation}/build/bundle.css`, `${copyLocation}/css/global.css`]) .pipe(postcss([ autoprefixer({overrideBrowserslist: ["safari >= 13"]}) ])) @@ -55,7 +54,7 @@ function bundleJS() { }); return result; }, false)) - .pipe(rename(directory + ".js")) + .pipe(rename(`${directory}.js`)) .pipe(dest(destLocation)); } @@ -69,11 +68,11 @@ function removeTags() { parent.removeChild(script); }); const f = this.createElement("script"); - f.setAttribute("src", directory + ".js"); + f.setAttribute("src", `${directory}.js`); this.body.appendChild(f); return this; }, false)) - .pipe(rename(directory + ".html")) + .pipe(rename(`${directory}.html`)) .pipe(dest(destLocation)); } diff --git a/public/shared/variables.css b/public/shared/variables.css index c894af7e..acdc671d 100644 --- a/public/shared/variables.css +++ b/public/shared/variables.css @@ -1,13 +1,13 @@ :root { --border-radius: 0.188rem; - --box-shadow: 0 0.5rem 1rem 0 rgba(0, 0, 0, 0.25); + --box-shadow: 0 0.5rem 1rem 0 rgb(0 0 0 / 0.25); --color-bg-primary: #323639; --color-bg-secondary: #2f3337; --color-script-highlighted: #364049; /* rgba(116, 178, 235, 0.1); */ --color-black: #1d2023; --color-blue: #74b1eb; --color-green: #60f36c; - --color-grey: rgba(255, 255, 255, 0.15); + --color-grey: rgb(255 255 255 / 0.15); --color-red: #ff453a; --color-yellow: #e4f360; --letter-spacing-large: -0.031rem; @@ -15,9 +15,9 @@ --letter-spacing-medium: -0.018rem; --letter-spacing-small: -0.008rem; --opacity-disabled: 0.3; - --text-color-primary: rgb(255, 255, 255); - --text-color-secondary: rgba(255, 255, 255, 0.65); - --text-color-disabled: rgba(255, 255, 255, 0.4); + --text-color-primary: rgb(255 255 255); + --text-color-secondary: rgb(255 255 255 / 0.65); + --text-color-disabled: rgb(255 255 255 / 0.4); --font-family: system-ui, -apple-system, "Helvetica Neue", "Helvetica", sans-serif; --text-default: 1rem/1.5rem var(--font-family); --text-large: 1.25rem/1.5rem var(--font-family); @@ -28,13 +28,13 @@ --editor-font: monaco, monospace; --editor-font-size: 14px; --editor-line-height: 24px; - --editor-invisible: rgba(255, 255, 255, 0.15); + --editor-invisible: rgb(255 255 255 / 0.15); --editor-active-line: var(--color-bg-secondary); - --editor-selected-bg: rgba(116, 178, 235, 0.35); - --editor-matched-highlight: rgba(116, 178, 235, 0.2); - --editor-search-highlight: rgba(255, 166, 0, 0.3); + --editor-selected-bg: rgb(116 178 235 / 0.35); + --editor-matched-highlight: rgb(116 178 235 / 0.2); + --editor-search-highlight: rgb(255 166 0 / 0.3); --editor-number: #77e26a; - --editor-comment: rgba(255, 255, 255, 0.35); + --editor-comment: rgb(255 255 255 / 0.35); --editor-def: #efc371; --editor-default: #cdcfd1; --editor-keyword: #96c3ed; diff --git a/rollup.config.js b/rollup.config.js index e8a64207..6209eb82 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -7,25 +7,30 @@ import css from "rollup-plugin-css-only"; import inlineSvg from "rollup-plugin-inline-svg"; import multi from "@rollup/plugin-multi-entry"; - const production = !process.env.ROLLUP_WATCH; -const directory = process.env.NODE_ENV === "popup" ? "popup" : "page"; -let input = ["src/" + directory + "/dev.js", "src/" + directory + "/main.js"]; -if (production) input = "src/" + directory + "/main.js"; +const directory = process.env.NODE_ENV === "popup" ? "popup" : "page"; +let input = [`src/${directory}/dev.js`, `src/${directory}/main.js`]; +if (production) { + input = `src/${directory}/main.js`; +} function serve() { let server; function toExit() { - if (server) server.kill(0); + if (server) { + server.kill(0); + } } return { writeBundle() { - if (server) return; + if (server) { + return; + } server = require("child_process").spawn( "npm", - ["run", "start:" + directory, "--", "--dev"], + ["run", `start:${directory}`, "--", "--dev"], { stdio: ["ignore", "inherit", "inherit"], shell: true @@ -43,13 +48,13 @@ export default { sourcemap: !production, format: "iife", name: "app", - file: "public/" + directory + "/build/bundle.js" + file: `public/${directory}/build/bundle.js` }, inlineDynamicImports: true, plugins: [ multi(), css({ - output: "public/" + directory + "/build/lib.css" + output: `public/${directory}/build/lib.css` }), copyAndWatch("./public/shared/variables.css", "variables.css"), copyAndWatch("./public/shared/reset.css", "reset.css"), @@ -79,7 +84,7 @@ export default { // Watch the `public` directory and refresh the // browser on changes when not in production !production && livereload({ - watch: ["public/" + directory] + watch: [`public/${directory}`] }) ], watch: { From a94216a97843076d80e0603cd365bd5590d076ed Mon Sep 17 00:00:00 2001 From: quoid Date: Fri, 4 Mar 2022 23:03:19 -0500 Subject: [PATCH 07/47] update eslint --- .eslintrc.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index cc384952..21d49aac 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -31,7 +31,7 @@ "before": false, "after": true }], - "curly": "error", + "curly": ["error", "multi-line"], "dot-notation": "error", "eqeqeq": ["error", "smart"], "indent": ["error", 4, {"SwitchCase": 1}], @@ -44,7 +44,7 @@ "no-mixed-operators": "error", "no-multi-spaces": "error", "no-multi-str": "error", - "no-multiple-empty-lines": ["error", {"max": 1}], + "no-multiple-empty-lines": ["error", {"max": 1, "maxBOF": 2}], // https://github.com/sveltejs/eslint-plugin-svelte3/issues/41 "no-unused-vars": ["error", {"args": "none"}], "no-useless-concat": "error", "no-use-before-define": ["error", { @@ -53,7 +53,9 @@ "variables": true }], "no-var": "error", - "nonblock-statement-body-position": ["error", "below"], + "nonblock-statement-body-position": ["error", "below", { + "overrides": {"if": "any"} + }], "object-curly-spacing": ["error", "never"], "operator-assignment": ["error", "always"], "operator-linebreak": ["error", "before"], @@ -61,6 +63,7 @@ "prefer-template": "error", "quotes": ["error", "double"], "semi": ["error", "always", {"omitLastInOneLineBlock": true}], + "space-before-blocks": "error", "space-before-function-paren": ["error", { "anonymous": "never", "named": "never", From b0a326e238231c4c94694f617b41f6d81c859245 Mon Sep 17 00:00:00 2001 From: quoid Date: Sat, 5 Mar 2022 12:27:32 -0500 Subject: [PATCH 08/47] comments and formatting --- .eslintrc.json | 4 +--- gulpfile.js | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 21d49aac..544058f5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,9 +9,7 @@ "ecmaVersion": 12, "sourceType": "module" }, - "plugins": [ - "svelte3" - ], + "plugins": ["svelte3"], "overrides": [ { "files": ["*.svelte"], diff --git a/gulpfile.js b/gulpfile.js index 3b6ec62b..8051388b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -17,7 +17,7 @@ function copy() { .pipe(dest(copyLocation)); } -// autoprefix select stylesheets and overwrite in place +// autoprefix select stylesheets and overwrite in place, at copy location function autoprefix() { return src([`${copyLocation}/build/bundle.css`, `${copyLocation}/css/global.css`]) .pipe(postcss([ @@ -26,7 +26,7 @@ function autoprefix() { .pipe(dest(file => file.base)); } -// inline assets +// inline assets, minify css and remove comment in monolithic html file function inlineAssets() { return src(`${copyLocation}/index.html`) .pipe(inline({ From f4967ccb10fe10656b4a4a47d625a28f3ae6fb47 Mon Sep 17 00:00:00 2001 From: quoid Date: Sat, 12 Mar 2022 16:14:54 -0500 Subject: [PATCH 09/47] upgrade eslint --- package-lock.json | 928 ++++++++++++++++++++-------------------------- package.json | 2 +- 2 files changed, 398 insertions(+), 532 deletions(-) diff --git a/package-lock.json b/package-lock.json index 60f86b4e..de4e9936 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "cm-show-invisibles": "^3.1.0", "codemirror": "^5.58.2", "del": "^6.0.0", - "eslint": "^7.11.0", + "eslint": "^8.10.0", "eslint-plugin-svelte3": "^2.7.3", "gulp": "^4.0.2", "gulp-dom": "^1.0.0", @@ -85,26 +85,31 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", - "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.0.tgz", + "integrity": "sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w==", "dev": true, "dependencies": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", + "debug": "^4.3.2", + "espree": "^9.3.1", + "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "js-yaml": "^4.1.0", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/@eslint/eslintrc/node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -114,6 +119,38 @@ "node": ">= 4" } }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -280,9 +317,9 @@ "dev": true }, "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -314,10 +351,13 @@ } }, "node_modules/acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } }, "node_modules/acorn-walk": { "version": "6.2.0", @@ -353,15 +393,6 @@ "uri-js": "^4.2.2" } }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-gray": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", @@ -769,15 +800,6 @@ "node": ">=0.10.0" } }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/async-done": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", @@ -1229,6 +1251,9 @@ }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/chalk/node_modules/color-convert": { @@ -1952,9 +1977,9 @@ } }, "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2281,12 +2306,6 @@ "integrity": "sha512-x5uZWXcVNYkTh4JubD7KSC1VMKz0vZwJUqVwY3ihsW0bst1BXDe494Uqbg3Y0fDGVjJqA8vEeGuvO5foyH2+qw==", "dev": true }, - "node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -2296,18 +2315,6 @@ "once": "^1.4.0" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", @@ -2506,46 +2513,44 @@ } }, "node_modules/eslint": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz", - "integrity": "sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.10.0.tgz", + "integrity": "sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.1.3", + "@eslint/eslintrc": "^1.2.0", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.0", - "esquery": "^1.2.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", + "glob-parent": "^6.0.1", + "globals": "^13.6.0", + "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^5.2.3", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -2553,7 +2558,10 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-plugin-svelte3": { @@ -2563,78 +2571,126 @@ "dev": true }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" } }, "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "engines": { - "node": ">=6" + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" } }, "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, "engines": { - "node": ">=4" + "node": ">=10" } }, "node_modules/eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true, "engines": { "node": ">= 4" } }, - "node_modules/espree": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", - "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.3.0" + "argparse": "^2.0.1" }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/espree": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", + "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", "dev": true, + "dependencies": { + "acorn": "^8.7.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^3.3.0" + }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/esprima": { @@ -2651,9 +2707,9 @@ } }, "node_modules/esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -2663,9 +2719,9 @@ } }, "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -2684,9 +2740,9 @@ } }, "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { "node": ">=4.0" @@ -3004,15 +3060,15 @@ } }, "node_modules/file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { - "flat-cache": "^2.0.1" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=4" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/file-uri-to-path": { @@ -3209,35 +3265,22 @@ } }, "node_modules/flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "dependencies": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" + "flatted": "^3.1.0", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=4" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, "node_modules/flush-write-stream": { @@ -3553,15 +3596,18 @@ } }, "node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", + "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", "dev": true, "dependencies": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globby": { @@ -4362,19 +4408,10 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { "is-extglob": "^2.1.1" @@ -4983,6 +5020,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -6619,15 +6662,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -6922,12 +6956,15 @@ } }, "node_modules/regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, "node_modules/relateurl": { @@ -7490,20 +7527,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -7875,41 +7898,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/string.prototype.trimend": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", @@ -8017,6 +8005,9 @@ "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/style-search": { @@ -8201,37 +8192,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/stylelint/node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/stylelint/node_modules/flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true - }, "node_modules/stylelint/node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -8545,21 +8505,6 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "node_modules/table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "dependencies": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/terser": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", @@ -8793,12 +8738,15 @@ } }, "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/typedarray": { @@ -9305,18 +9253,6 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "node_modules/write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", @@ -9500,31 +9436,62 @@ } }, "@eslint/eslintrc": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", - "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.0.tgz", + "integrity": "sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w==", "dev": true, "requires": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", + "debug": "^4.3.2", + "espree": "^9.3.1", + "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "js-yaml": "^4.1.0", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } } } }, + "@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -9672,9 +9639,9 @@ "dev": true }, "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "dev": true }, "acorn-globals": { @@ -9696,10 +9663,11 @@ } }, "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} }, "acorn-walk": { "version": "6.2.0", @@ -9729,12 +9697,6 @@ "uri-js": "^4.2.2" } }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, "ansi-gray": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", @@ -10060,12 +10022,6 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, "async-done": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", @@ -11027,9 +10983,9 @@ } }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" @@ -11302,12 +11258,6 @@ "integrity": "sha512-x5uZWXcVNYkTh4JubD7KSC1VMKz0vZwJUqVwY3ihsW0bst1BXDe494Uqbg3Y0fDGVjJqA8vEeGuvO5foyH2+qw==", "dev": true }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -11317,15 +11267,6 @@ "once": "^1.4.0" } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", @@ -11490,55 +11431,83 @@ } }, "eslint": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.11.0.tgz", - "integrity": "sha512-G9+qtYVCHaDi1ZuWzBsOWo2wSwd70TXnU6UHA3cTYHp7gCTXZcpggWFoUVAMRarg68qtPoNfFbzPh+VdOgmwmw==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.10.0.tgz", + "integrity": "sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.1.3", + "@eslint/eslintrc": "^1.2.0", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.0", - "esquery": "^1.2.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", + "glob-parent": "^6.0.1", + "globals": "^13.6.0", + "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^5.2.3", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } } } }, @@ -11549,55 +11518,55 @@ "dev": true }, "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "requires": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } } }, "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.1.0" + "eslint-visitor-keys": "^2.0.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true } } }, "eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, "espree": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", - "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", + "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", "dev": true, "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "acorn": "^8.7.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^3.3.0" } }, "esprima": { @@ -11607,18 +11576,18 @@ "dev": true }, "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -11633,9 +11602,9 @@ }, "dependencies": { "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true } } @@ -11906,12 +11875,12 @@ } }, "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "flat-cache": "^3.0.4" } }, "file-uri-to-path": { @@ -12077,31 +12046,19 @@ "dev": true }, "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "dependencies": { - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "flatted": "^3.1.0", + "rimraf": "^3.0.2" } }, "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, "flush-write-stream": { @@ -12357,12 +12314,12 @@ } }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", + "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" } }, "globby": { @@ -12997,16 +12954,10 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -13493,6 +13444,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -14779,12 +14736,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -15031,9 +14982,9 @@ } }, "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "relateurl": { @@ -15475,17 +15426,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -15799,34 +15739,6 @@ "safe-buffer": "~5.1.0" } }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, "string.prototype.trimend": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", @@ -16041,31 +15953,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", - "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", - "dev": true - }, "global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -16329,18 +16216,6 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - } - }, "terser": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", @@ -16530,9 +16405,9 @@ } }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, "typedarray": { @@ -16965,15 +16840,6 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, "write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", diff --git a/package.json b/package.json index 8cb81f9b..8e6c2a9d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "cm-show-invisibles": "^3.1.0", "codemirror": "^5.58.2", "del": "^6.0.0", - "eslint": "^7.11.0", + "eslint": "^8.10.0", "eslint-plugin-svelte3": "^2.7.3", "gulp": "^4.0.2", "gulp-dom": "^1.0.0", From cc7dc7d73aa545f2ff7a5c1c8a22074eaf795254 Mon Sep 17 00:00:00 2001 From: quoid Date: Sat, 12 Mar 2022 16:15:11 -0500 Subject: [PATCH 10/47] update lint configs --- .eslintrc.json | 1 + .stylelintrc.json | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 544058f5..13aa5583 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -43,6 +43,7 @@ "no-multi-spaces": "error", "no-multi-str": "error", "no-multiple-empty-lines": ["error", {"max": 1, "maxBOF": 2}], // https://github.com/sveltejs/eslint-plugin-svelte3/issues/41 + "no-tabs": "error", "no-unused-vars": ["error", {"args": "none"}], "no-useless-concat": "error", "no-use-before-define": ["error", { diff --git a/.stylelintrc.json b/.stylelintrc.json index 9915e994..4f559d3a 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -3,15 +3,17 @@ "stylelint-config-standard", "stylelint-config-html/svelte" ], - "ignoreFiles": ["**/codemirror/*", "**/reset.css"], + "ignoreFiles": ["**/codemirror/*", "**/reset.css", "**/*.js"], "rules": { "alpha-value-notation": "number", "comment-empty-line-before": ["always",{ "ignore": ["after-comment", "stylelint-commands"] }], + "custom-property-empty-line-before": null, "indentation": 4, "max-empty-lines": 1, "no-descending-specificity": null, + "property-no-vendor-prefix": null, "selector-class-pattern": null, "selector-pseudo-class-no-unknown": [true, { "ignorePseudoClasses": ["global"] From d78f67c233e132419ff1b85bf41fa5e6cad27ee5 Mon Sep 17 00:00:00 2001 From: quoid Date: Sat, 12 Mar 2022 16:19:41 -0500 Subject: [PATCH 11/47] use single dev file --- src/page/dev.js | 308 --------------------------------- src/popup/dev.js | 233 ------------------------- src/shared/dev.js | 428 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 428 insertions(+), 541 deletions(-) delete mode 100644 src/page/dev.js delete mode 100644 src/popup/dev.js create mode 100644 src/shared/dev.js diff --git a/src/page/dev.js b/src/page/dev.js deleted file mode 100644 index af2d27ce..00000000 --- a/src/page/dev.js +++ /dev/null @@ -1,308 +0,0 @@ -import {parse} from "./utils"; - - -async function getRemoteFileContents(url) { - let r = {}; - await fetch(url).then(response => { - if (!response.ok) throw Error(response.statusText); - return response.text(); - }).then(text => { - r.contents = text; - }).catch(error => { - console.log(error); - r.error = "Remote url bad response!"; - }); - return r; -} - -// example file contents -const exampleCSSContent = `/* ==UserStyle== -@name Example CSS UserStyle -@description This is an example of a UserStyle. It applies css to webpages rather than js. -@match https://userstyles.org/* -==/UserStyle== */ -#id, -.class, -.pseudo-element::after { - background-color: tomato; -} -`; - -const exampleJSContent = `// ==UserScript== -// @name Example JS Userscript -// @description This a standard userscript with javascript code -// @match https://github.com/quoid/userscripts -// @exclude-match *://*.* -// @version 1.0 -// @updateURL https://www.k21p.com/example.user.js -// @noframes -// ==/UserScript== -console.log("I am an example userscript"); -`; - -// dummy file directory -let files = [ - { - content: exampleCSSContent, - filename: "Example CSS UserStyle.css", - lastModified: 1606009623000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript2.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript3.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript4.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript5.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript6.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript7.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript8.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript9.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript10.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript11.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript12.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript13.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript14.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript15.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript16.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript17.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript18.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript19.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript20.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript21.js", - lastModified: 1605862023000 - }, - { - content: exampleJSContent, - filename: "Example JS Userscript22.js", - lastModified: 1605862023000 - }, -]; - -function saveFile(content, lastMod, newFilename, oldName) { - const ind = files.findIndex(f => f.filename === oldName); - const s = { - content: content, - filename: newFilename, - lastModified: lastMod - }; - if (ind != -1) { - // overwrite at index - files[ind] = s; - } else { - // add to beginning of array - files.unshift(s); - } -} - -const _browser = { - runtime: { - async sendNativeMessage(message, responseCallback) { - console.log(`Got message: ${message.name}`); - let response = {}; - const name = message.name; - if (name === "PAGE_INIT_DATA") { - response = { - active: "true", - autoCloseBrackets: "true", - autoHint: "true", - blacklist: [], - descriptions: "true", - languageCode: "en", - lint: "true", - log: "false", - saveLocation: "/Users/someone/Library/Directory", - showCount: "true", - showInvisibles: "true", - sortOrder: "lastModifiedDesc", - tabSize: "4", - version: "4.0.0" - }; - } else if (name === "PAGE_ALL_FILES") { - response = []; - files.forEach(file => { - const content = file.content; - const parsed = parse(content); - const metadata = parsed.metadata; - const canUpdate = (metadata.version && metadata.updateURL) ? true : false; - const scriptData = { - canUpdate: canUpdate, - content: content, - description: metadata.description[0], - disabled: false, - filename: file.filename, - lastModified: file.lastModified, - metadata: metadata, - name: metadata.name[0], - type: file.filename.substring(file.filename.lastIndexOf(".") + 1) - }; - response.push(scriptData); - }); - } else if (name === "TOGGLE_ITEM") { - response = {success: true}; - //response = {error: true}; - } else if (name === "PAGE_UPDATE_SETTINGS") { - response = {success: true}; - } else if (name === "PAGE_UPDATE_BLACKLIST") { - response = {success: true}; - } else if (name === "PAGE_NEW_REMOTE") { - let r = await getRemoteFileContents(message.url); - response = r; - } else if (name === "PAGE_SAVE") { - console.log(message); - const newContent = message.content; - const oldFilename = message.item.filename; - const parsed = parse(newContent); - const lastModified = Date.now(); - let canUpdate = false; - // script failed to parse - if (!parsed) { - return {error: "save failed, file has invalid metadata"}; - } - const name = parsed.metadata.name[0]; - const newFilename = `${name}.${message.item.type}`; - // filename length too long - if (newFilename.length > 255) { - return {error: "save failed, filename too long!"}; - } - - // check if file can be remotely updated - if (parsed.metadata.version && parsed.metadata.updateURL) { - canUpdate = true; - } - - let success = { - canUpdate: canUpdate, - content: newContent, - filename: newFilename, - lastModified: lastModified, - name: name, - }; - - // add description if in file metadata - if (parsed.metadata.description) { - success.description = parsed.metadata.description[0]; - } - - // overwriting - if (newFilename.toLowerCase() === oldFilename.toLowerCase()) { - saveFile(newContent, lastModified, newFilename, oldFilename); - return success; - } - - // not overwriting, check if filename for that type is taken - if (files.find(a => a.filename.toLowerCase() === newFilename.toLowerCase())) { - return {error: "save failed, name already taken!"}; - } - - // not overwriting but all validation passed - saveFile(newContent, lastModified, newFilename, oldFilename); - return success; - } else if (name === "PAGE_UPDATE") { - response.content = message.content + "\n//I was updated"; - } - if (!responseCallback) { - return new Promise(resolve => setTimeout(() => resolve(response), 500)); - } - setTimeout(() => { - responseCallback(response); - }, 500); - } - }, - tabs: { - query(message, responseCallback) { - const response = [ - {id: 1, url: "https://www.filmgarb.com/"}, - {id: 2, url: "https://www.k21p.com/"} - ]; - if (!responseCallback) { - return new Promise(resolve => setTimeout(() => resolve(response), 500)); - } - setTimeout(() => { - responseCallback(response); - }, 500); - } - } -}; - -window.browser = _browser; diff --git a/src/popup/dev.js b/src/popup/dev.js deleted file mode 100644 index c3aec39a..00000000 --- a/src/popup/dev.js +++ /dev/null @@ -1,233 +0,0 @@ -const _browser = { - delay: 200, - platform: "ios", - runtime: { - getURL() { - return "https://www.example.com/"; - }, - sendNativeMessage(message, responseCallback) { - console.log(`Got message: ${message.name}`); - let response = {}; - if (message.name === "POPUP_TOGGLE_EXTENSION") { - //response = {error: "Failed toggle extension"}; - response = {success: true}; - } else if (message.name === "POPUP_UPDATE_ALL" || message.name === "POPUP_UPDATE_SINGLE") { - response = {error: "Failed refresh scripts"}; - response = { - items: [ - { - name: "Google Images Restored", - filename: "Google Images Restored.js", - disabled: false, - type: "js" - }, - { - name: "Wikipedia Mobile Redirect", - filename: "Wikipedia Mobile Redirect.js", - disabled: true, - type: "js" - }, - { - name: "A Special Userscript", - filename: "A Special Userscript.js", - disabled: false, - type: "js" - }, - { - name: "CSS Adblock", - filename: "CSS Adblock.css", - disabled: false, - type: "css" - }, - { - name: "New Userscript With a Really Really Long Name", - filename: "New Userscript With a Really Really Long Name.css", - disabled: true, - type: "css" - }, - { - name: "Subframe Script Managerial Staffing Company", - filename: "Subframe Script.js", - disabled: false, - subframe: true, - type: "css" - } - ], - updates: [] - }; - } else if (message.name === "POPUP_CHECK_UPDATES") { - response = { - updates: [ - { - filename: "Google Images Restored.js", - name: "Google Images Restored", - url: "https://www.k21p.com" - }, - { - filename: "New Userscript With a Really Really Long Name.js", - name: "New Userscript With a Really Really Long Name", - url: "https://www.filmgarb.com" - } - ] - }; - } else if (message.name === "POPUP_INIT") { - response.initData = { - active: "true", - items: [ - { - name: "Google Images Restored", - filename: "Google Images Restored.js", - disabled: false, - type: "js" - }, - { - name: "Subframe Script Managerial Staffing Company", - filename: "Subframe Script.js", - disabled: false, - subframe: true, - type: "css" - } - ], - updates: [ - { - filename: "Google Images Restored.js", - name: "Google Images Restored", - url: "https://www.k21p.com" - }, - { - filename: "New Userscript With a Really Really Long Name.js", - name: "New Userscript With a Really Really Long Name", - url: "https://www.filmgarb.com" - } - ] - }; - } else if (message.name === "POPUP_MATCHES") { - response = { - matches: [ - { - name: "Google Images Restored", - filename: "Google Images Restored.js", - disabled: false, - type: "js" - }, - { - name: "Subframe Script Managerial Staffing Company", - filename: "Subframe Script.js", - disabled: false, - subframe: true, - type: "css" - } - ] - }; - } else if (message.name === "POPUP_UPDATES") { - response = { - updates: [ - { - filename: "Google Images Restored.js", - name: "Google Images Restored", - url: "https://www.k21p.com" - }, - { - filename: "New Userscript With a Really Really Long Name.js", - name: "New Userscript With a Really Really Long Name", - url: "https://www.filmgarb.com" - } - ] - }; - } else if (message.name === "REQ_PLATFORM") { - response = {platform: _browser.platform}; - } else if (message.name === "TOGGLE_ITEM") { - //response = {error: "Failed to toggle item"}; - response = {success: true}; - } else if (message.name === "POPUP_OPEN_EXTENSION_PAGE") { - response = {error: "Failed to get page url"}; - window.open("https://github.com/quoid/userscripts"); - } else if (message.name === "OPEN_SAVE_LOCATION") { - if (_browser.platform === "macos") { - response = {success: true}; - } else { - response = { - items: [ - { - name: "Google Images Restored", - filename: "Google Images Restored.js", - disabled: false, - type: "js", - metadata: [] - }, - { - name: "Subframe Script Managerial Staffing Company", - filename: "Subframe Script.js", - disabled: false, - type: "css", - metadata: [] - }, - { - name: "Another Script from Managerial Staffing Company", - filename: "Cool Script.js", - disabled: false, - type: "js", - metadata: [] - } - ] - }; - } - } - if (!responseCallback) { - return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); - } - setTimeout(() => { - responseCallback(response); - }, _browser.delay); - } - }, - tabs: { - query(message, responseCallback) { - const response = [{url: "https://www.filmgarb.com/foo.user.js", id: 101}]; - if (!responseCallback) { - return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); - } - setTimeout(() => { - responseCallback(response); - }, _browser.delay); - }, - sendMessage(tabId, message, responseCallback) { - console.log(`Tab ${tabId} got message: ${message.name}`); - let response = {}; - if (message.name === "USERSCRIPT_INSTALL_00") { - response = {success: "Click to install"}; - //response.error = "something went wrong"; - } else if (message.name === "USERSCRIPT_INSTALL_01") { - //response = {error: "a userscript with this @name value already exists, @name needs to be unique"}; - response = { - description: "This userscript re-implements the \"View Image\" and \"Search by image\" buttons into google images.", - grant: ["GM.getValue", "GM.setValue", "GM.xmlHttpRequest"], - match: ["https://www.example.com/*", "https://www.example.com/somethingReallylong/goesRightHere"], - name: "Test Install Userscript", - require: ["https://code.jquery.com/jquery-3.5.1.min.js", "https://code.jquery.com/jquery-1.7.1.min.js"], - source: "https://greasyforx.org/scripts/00000-something-something-long-name/code/Something%20something%20long20name.user.js" - }; - } - if (!responseCallback) { - return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); - } - setTimeout(() => { - responseCallback(response); - }, _browser.delay); - } - }, - webNavigation: { - getAllFrames(message, responseCallback) { - const response = []; - if (!responseCallback) { - return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); - } - setTimeout(() => { - responseCallback(response); - }, _browser.delay); - } - } -}; - -window.browser = _browser; -document.documentElement.style.backgroundColor = "grey"; diff --git a/src/shared/dev.js b/src/shared/dev.js new file mode 100644 index 00000000..3d0b7655 --- /dev/null +++ b/src/shared/dev.js @@ -0,0 +1,428 @@ +import {parse, uniqueId, wait} from "../page/utils"; + +/** + * + * @param {("css"|"js")} type + * @param {boolean?} updates + * @param {boolean?} longName + * @returns + */ +function generateFile(type, updates = false, longName = false) { + const uid = uniqueId(); + let name = `${uid}-example-${type}`; + if (longName) name = `${uid}${uid}${uid}-example-${type}`; + const randomDate = +(new Date() - Math.floor(Math.random()*10000000000)); + let content = "// ==UserScript==" + +`\n// @name ${name}` + +`\n// @description Custom description for userscript with name "${name}"` + +"\n// @match *://*.*" + +"\n// @exclude-match https://github.com/quoid/userscripts" + +"\n// @version 1.0" + +"\n// @noframes" + +"\n// ==/UserScript==" + +`\n\nconsole.log("I am ${name}");`; + if (type === "css") { + content = content.replace("// ==UserScript==", "/* ==UserStyle=="); + content = content.replace("// ==/UserScript==", "==/UserStyle== */"); + content = content.replaceAll("// @", "@"); + content = content.replace( + `console.log("I am ${name}");`, + "#id,\n.class\n.pseudo-element::after {\n color: red\n}" + ); + updates = false; + } + if (updates) { + content = content.replace( + "1.0", + "1.0\n// @updateURL https://www.k21p.com/example.user.js" + ); + content = content.replace( + `console.log("I am ${name}");`, + `console.log("I am ${name} and you can update me!");` + ); + } + return { + content: content, + filename: `${name}.${type}`, + lastModified: randomDate + }; +} + +const files = [ + generateFile("js", true, true), + generateFile("css"), + ...Array.from({length: 5}, (_, i) => generateFile("js")) +]; + +const _browser = { + delay: 200, + platform: "ios", + runtime: { + getURL() { + return "https://www.example.com/"; + }, + async sendNativeMessage(message, responseCallback) { + const name = message.name; + console.info(`Got message: ${name}`); + let response = {}; + if (name === "PAGE_INIT_DATA") { + response = { + active: "true", + autoCloseBrackets: "true", + autoHint: "true", + blacklist: [], + descriptions: "true", + languageCode: "en", + lint: "true", + log: "false", + saveLocation: "/Users/someone/Library/Directory", + showCount: "true", + showInvisibles: "true", + sortOrder: "lastModifiedDesc", + tabSize: "4", + version: "4.0.0", + build: "44" + }; + } else if (name === "PAGE_ALL_FILES") { + response = []; + files.forEach(file => { + const content = file.content; + const parsed = parse(content); + const metadata = parsed.metadata; + const canUpdate = (metadata.version && metadata.updateURL) ? true : false; + const scriptData = { + canUpdate: canUpdate, + content: content, + description: metadata.description ? metadata.description[0] : "", + disabled: false, + filename: file.filename, + lastModified: file.lastModified, + metadata: metadata, + name: metadata.name[0], + type: file.filename.substring(file.filename.lastIndexOf(".") + 1) + }; + response.push(scriptData); + }); + } else if (name === "TOGGLE_ITEM") { + response = {success: true}; + //response = {error: true}; + } else if (name === "PAGE_UPDATE_SETTINGS") { + response = {success: true}; + } else if (name === "PAGE_UPDATE_BLACKLIST") { + response = {success: true}; + } else if (name === "PAGE_NEW_REMOTE") { + const result = await getRemoteFileContents(message.url); + response = result; + } else if (name === "PAGE_SAVE") { + console.log(message); + const newContent = message.content; + const oldFilename = message.item.filename; + const parsed = parse(newContent); + const lastModified = Date.now(); + let canUpdate = false; + // script failed to parse + if (!parsed) { + return {error: "save failed, file has invalid metadata"}; + } + const name = parsed.metadata.name[0]; + const newFilename = `${name}.${message.item.type}`; + // filename length too long + if (newFilename.length > 255) { + return {error: "save failed, filename too long!"}; + } + + // check if file can be remotely updated + if (parsed.metadata.version && parsed.metadata.updateURL) { + canUpdate = true; + } + + const success = { + canUpdate: canUpdate, + content: newContent, + filename: newFilename, + lastModified: lastModified, + name: name, + }; + + // add description if in file metadata + if (parsed.metadata.description) { + success.description = parsed.metadata.description[0]; + } + + // overwriting + if (newFilename.toLowerCase() === oldFilename.toLowerCase()) { + saveFile(newContent, lastModified, newFilename, oldFilename); + return success; + } + + // not overwriting, check if filename for that type is taken + if (files.find(a => a.filename.toLowerCase() === newFilename.toLowerCase())) { + return {error: "save failed, name already taken!"}; + } + + // not overwriting but all validation passed + saveFile(newContent, lastModified, newFilename, oldFilename); + return success; + } else if (name === "PAGE_UPDATE") { + const url = parse(message.content).metadata.updateURL; + await wait(500); + const result = await getRemoteFileContents(url); + response = result; + // response.error = "Something went wrong!"; + // response.info = "No updates found"; + } else if (name === "POPUP_TOGGLE_EXTENSION") { + //response = {error: "Failed toggle extension"}; + response = {success: true}; + } else if (name === "POPUP_UPDATE_ALL" || name === "POPUP_UPDATE_SINGLE") { + // response = {error: "Failed refresh scripts"}; + response = { + items: [ + { + name: "Google Images Restored", + filename: "Google Images Restored.js", + disabled: false, + type: "js" + }, + { + name: "Wikipedia Mobile Redirect", + filename: "Wikipedia Mobile Redirect.js", + disabled: true, + type: "js" + }, + { + name: "A Special Userscript", + filename: "A Special Userscript.js", + disabled: false, + type: "js" + }, + { + name: "CSS Adblock", + filename: "CSS Adblock.css", + disabled: false, + type: "css" + }, + { + name: "New Userscript With a Really Really Long Name", + filename: "New Userscript With a Really Really Long Name.css", + disabled: true, + type: "css" + }, + { + name: "Subframe Script Managerial Staffing Company", + filename: "Subframe Script.js", + disabled: false, + subframe: true, + type: "css" + } + ], + updates: [] + }; + } else if (name === "POPUP_CHECK_UPDATES") { + response = { + updates: [ + { + filename: "Google Images Restored.js", + name: "Google Images Restored", + url: "https://www.k21p.com" + }, + { + filename: "New Userscript With a Really Really Long Name.js", + name: "New Userscript With a Really Really Long Name", + url: "https://www.filmgarb.com" + } + ] + }; + } else if (name === "POPUP_INIT") { + response.initData = { + active: "true", + items: [ + { + name: "Google Images Restored", + filename: "Google Images Restored.js", + disabled: false, + type: "js" + }, + { + name: "Subframe Script Managerial Staffing Company", + filename: "Subframe Script.js", + disabled: false, + subframe: true, + type: "css" + } + ], + updates: [ + { + filename: "Google Images Restored.js", + name: "Google Images Restored", + url: "https://www.k21p.com" + }, + { + filename: "New Userscript With a Really Really Long Name.js", + name: "New Userscript With a Really Really Long Name", + url: "https://www.filmgarb.com" + } + ] + }; + } else if (name === "POPUP_MATCHES") { + response = { + matches: [ + { + name: "Google Images Restored", + filename: "Google Images Restored.js", + disabled: false, + type: "js" + }, + { + name: "Subframe Script Managerial Staffing Company", + filename: "Subframe Script.js", + disabled: false, + subframe: true, + type: "css" + } + ] + }; + } else if (name === "POPUP_UPDATES") { + response = { + updates: [ + { + filename: "Google Images Restored.js", + name: "Google Images Restored", + url: "https://www.k21p.com" + }, + { + filename: "New Userscript With a Really Really Long Name.js", + name: "New Userscript With a Really Really Long Name", + url: "https://www.filmgarb.com" + } + ] + }; + response.updates = []; + } else if (name === "REQ_PLATFORM") { + response = {platform: _browser.platform}; + } else if (name === "POPUP_OPEN_EXTENSION_PAGE") { + response = {error: "Failed to get page url"}; + window.open("https://github.com/quoid/userscripts"); + } else if (name === "OPEN_SAVE_LOCATION") { + if (_browser.platform === "macos") { + response = {success: true}; + } else { + response = { + items: [ + { + name: "Google Images Restored", + filename: "Google Images Restored.js", + disabled: false, + type: "js", + metadata: [] + }, + { + name: "Subframe Script Managerial Staffing Company", + filename: "Subframe Script.js", + disabled: false, + type: "css", + metadata: [] + }, + { + name: "Another Script from Managerial Staffing Company", + filename: "Cool Script.js", + disabled: false, + type: "js", + metadata: [] + } + ] + }; + } + } + if (!responseCallback) { + return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); + } + setTimeout(() => responseCallback(response), _browser.delay); + } + }, + tabs: { + query(message, responseCallback) { + const response = [{url: "https://www.filmgarb.com/foo.user.js", id: 101}]; + if (!responseCallback) { + return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); + } + setTimeout(() => responseCallback(response), _browser.delay); + }, + sendMessage(tabId, message, responseCallback) { + console.log(`Tab ${tabId} got message: ${message.name}`); + let response = {}; + if (message.name === "USERSCRIPT_INSTALL_00") { + response = {success: "Click to install"}; + //response.error = "something went wrong"; + } else if (message.name === "USERSCRIPT_INSTALL_01") { + response = { + description: "This userscript re-implements the \"View Image\" and \"Search by image\" buttons into google images.", + grant: ["GM.getValue", "GM.setValue", "GM.xmlHttpRequest"], + match: ["https://www.example.com/*", "https://www.example.com/somethingReallylong/goesRightHere"], + name: "Test Install Userscript", + require: ["https://code.jquery.com/jquery-3.5.1.min.js", "https://code.jquery.com/jquery-1.7.1.min.js"], + source: "https://greasyforx.org/scripts/00000-something-something-long-name/code/Something%20something%20long20name.user.js" + }; + response = {error: "a userscript with this @name value already exists, @name needs to be unique"}; + } + if (!responseCallback) { + return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); + } + setTimeout(() => responseCallback(response), _browser.delay); + } + }, + webNavigation: { + getAllFrames(message, responseCallback) { + const response = []; + if (!responseCallback) { + return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); + } + setTimeout(() => responseCallback(response), _browser.delay); + } + }, + storage: { + local: { + get(items, responseCallback) { + const response = {}; + if (!responseCallback) { + return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); + } + setTimeout(() => responseCallback(response), _browser.delay); + }, + set() { + return new Promise(resolve => setTimeout(() => resolve(), _browser.delay)); + } + } + } +}; + +async function getRemoteFileContents(url) { + const result = {}; + await fetch(url).then(response => { + if (!response.ok) throw Error(response.statusText); + return response.text(); + }).then(text => { + result.content = text; + }).catch(error => { + console.log(error); + result.error = "Remote url bad response!"; + }); + return result; +} + +function saveFile(content, lastMod, newFilename, oldName) { + const ind = files.findIndex(f => f.filename === oldName); + const s = { + content: content, + filename: newFilename, + lastModified: lastMod + }; + if (ind !== -1) { + // overwrite at index + files[ind] = s; + } else { + // add to beginning of array + files.unshift(s); + } +} + +window.browser = _browser; From b5ea8455a63be8fc3a9380b6734ad9f47d7577e2 Mon Sep 17 00:00:00 2001 From: quoid Date: Sat, 12 Mar 2022 17:50:25 -0500 Subject: [PATCH 12/47] code styling --- .../Resources/background.js | 12 +- .../Resources/content.js | 8 +- rollup.config.js | 32 +-- src/page/App.svelte | 62 ++--- src/page/Components/Editor/CodeMirror.svelte | 14 +- src/page/Components/Editor/Editor.svelte | 141 +++++------ .../Components/Editor/EditorSearch.svelte | 38 +-- src/page/Components/Notification.svelte | 25 +- src/page/Components/Settings.svelte | 237 +++++++++--------- src/page/Components/Sidebar/Sidebar.svelte | 81 +++--- .../Components/Sidebar/SidebarFilter.svelte | 87 +++---- .../Components/Sidebar/SidebarItem.svelte | 47 ++-- src/page/store.js | 26 +- src/page/utils.js | 38 ++- src/popup/App.svelte | 46 ++-- src/popup/Components/PopupItem.svelte | 18 +- src/popup/Components/View.svelte | 2 + .../Components/Views/AllItemsView.svelte | 15 ++ src/popup/Components/Views/InstallView.svelte | 14 +- src/popup/Components/Views/UpdateView.svelte | 19 +- src/shared/Components/Dropdown.svelte | 20 +- src/shared/Components/IconButton.svelte | 2 + src/shared/Components/Loader.svelte | 18 +- src/shared/Components/Tag.svelte | 5 +- src/shared/Components/Toggle.svelte | 2 + 25 files changed, 552 insertions(+), 457 deletions(-) diff --git a/extension/Userscripts Extension/Resources/background.js b/extension/Userscripts Extension/Resources/background.js index fd1fb056..a5234424 100644 --- a/extension/Userscripts Extension/Resources/background.js +++ b/extension/Userscripts Extension/Resources/background.js @@ -83,13 +83,13 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => { return true; } else if (name === "API_SET_VALUE") { const item = {}; - item[request.filename + "---" + request.key] = request.value; + item[`${request.filename}---${request.key}`] = request.value; browser.storage.local.set(item, () => { sendResponse({success: true}); }); return true; } else if (name === "API_GET_VALUE") { - const key = request.filename + "---" + request.key; + const key = `${request.filename}---${request.key}`; browser.storage.local.get(key, item => { if (Object.keys(item).length === 0) { if (request.defaultValue) { @@ -103,13 +103,13 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => { }); return true; } else if (name === "API_DELETE_VALUE") { - const key = request.filename + "---" + request.key; + const key = `${request.filename}---${request.key}`; browser.storage.local.remove(key, response => { sendResponse({success: true}); }); return true; } else if (name === "API_LIST_VALUES") { - const prefix = request.filename + "---"; + const prefix = `${request.filename}---`; const keys = []; browser.storage.local.get().then(items => { for (const key in items) { @@ -272,7 +272,9 @@ function purgeContextMenus() { // loop through all tabs and remove context menu items targeting tab urls that don't exist browser.tabs.query({}, tabs => { const tabUrls = []; - tabs.forEach(tab => {if (tab.url) tabUrls.push(tab.url);}); + tabs.forEach(tab => { + if (tab.url) tabUrls.push(tab.url); + }); const contextMenuItemsUrls = []; // parse urls from context menu item ids contextMenuItems.forEach(item => contextMenuItemsUrls.push(item.split("&$&")[0])); diff --git a/extension/Userscripts Extension/Resources/content.js b/extension/Userscripts Extension/Resources/content.js index 70298821..0e479a94 100644 --- a/extension/Userscripts Extension/Resources/content.js +++ b/extension/Userscripts Extension/Resources/content.js @@ -262,9 +262,9 @@ function addContextMenuItem(filename, name) { // https://stackoverflow.com/q/68431201 let pathname = window.location.pathname; if (pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1); - const url = window.location.protocol + "//" + window.location.hostname + pathname; + const url = `${window.location.protocol}//${window.location.hostname}${pathname}`; - const menuItemId = url + "&$&" + filename; + const menuItemId = `${url}&$&${filename}`; const message = {name: "CONTEXT_CREATE", menuItemId: menuItemId, title: name, url: url}; browser.runtime.sendMessage(message, response => { // avoid adding unnecessary event listeners @@ -515,7 +515,7 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => { // arraybuffer responses had their data converted, convert it back to arraybuffer if (request.response.responseType === "arraybuffer" && resp.response) { try { - const r = new Uint8Array(resp.response).buffer; + const r = new Uint8Array(resp.response).buffer; resp.response = r; } catch (error) { console.error("error parsing xhr arraybuffer response", error); @@ -585,7 +585,7 @@ window.addEventListener("message", e => { } else if (name === "API_GET_VALUE") { message = {name: "API_GET_VALUE", filename: e.data.filename, pid: pid, key: e.data.key, defaultValue: e.data.defaultValue}; browser.runtime.sendMessage(message, response => { - const resp = response === `undefined--${pid}` ? undefined : response; + const resp = response === `undefined--${pid}` ? undefined : response; window.postMessage({id: id, pid: pid, name: "RESP_GET_VALUE", filename: e.data.filename, response: resp}); }); } else if (name === "API_DELETE_VALUE") { diff --git a/rollup.config.js b/rollup.config.js index 6209eb82..fcd4eb41 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -9,25 +9,21 @@ import multi from "@rollup/plugin-multi-entry"; const production = !process.env.ROLLUP_WATCH; const directory = process.env.NODE_ENV === "popup" ? "popup" : "page"; -let input = [`src/${directory}/dev.js`, `src/${directory}/main.js`]; -if (production) { - input = `src/${directory}/main.js`; -} + +//let input = [`src/${directory}/dev.js`, `src/${directory}/main.js`]; +let input = ["src/shared/dev.js", `src/${directory}/main.js`]; +if (production) input = `src/${directory}/main.js`; function serve() { let server; function toExit() { - if (server) { - server.kill(0); - } + if (server) server.kill(0); } return { writeBundle() { - if (server) { - return; - } + if (server) return; server = require("child_process").spawn( "npm", ["run", `start:${directory}`, "--", "--dev"], @@ -92,18 +88,24 @@ export default { } }; -// https://dev.to/lukap/creating-a-rollup-plugin-to-copy-and-watch-a-file-3hi2 -function copyAndWatch(fileIn, fileOut) { +/** + * Simple plugin for watching and copying asset for use in app. + * @see {@link https://dev.to/lukap/creating-a-rollup-plugin-to-copy-and-watch-a-file-3hi2 Tutorial} + * @see {@link https://rollupjs.org/guide/en/#build-hooks Rollup Build Hooks} + * @param {string} inputFilePath + * @param {string} outputFilename + */ +function copyAndWatch(inputFilePath, outputFilename) { return { name: "copy-and-watch", async buildStart() { - this.addWatchFile(fileIn); + this.addWatchFile(inputFilePath); }, async generateBundle() { this.emitFile({ type: "asset", - fileName: fileOut, - source: fs.readFileSync(fileIn) + fileName: outputFilename, + source: fs.readFileSync(inputFilePath) }); } }; diff --git a/src/page/App.svelte b/src/page/App.svelte index 16e8f5ce..462b664d 100644 --- a/src/page/App.svelte +++ b/src/page/App.svelte @@ -8,7 +8,7 @@ import Notification from "./Components/Notification.svelte"; import logo from "../shared/img/logo.svg"; - let logger = []; + const logger = []; $: $log.some(item => { if (!logger.includes(item)) { @@ -32,13 +32,12 @@ } // currently inactive, but could be used to globally prevent auto text replacement in app - // eslint-disable-next-line no-unused-vars - function preventAutoTextReplacements(e) { - if (e.inputType === "insertReplacementText" && e.data === ". ") { - e.preventDefault(); - e.target.value += " "; - } - } + // function preventAutoTextReplacements(e) { + // if (e.inputType === "insertReplacementText" && e.data === ". ") { + // e.preventDefault(); + // e.target.value += " "; + // } + // } onMount(async () => { log.add("Requesting initialization data", "info", false); @@ -60,6 +59,30 @@ state.remove("items-loading"); }); + + + +{#if $state.includes("init")} +
+ {@html logo} + {#if $state.includes("init-error")} + Failed to initialize app, check the browser console + {:else} + Initializing app... + {/if} +
+{/if} +
+ + +
+
    + {#each $notifications as item (item.id)} + notifications.remove(item.id)} {item}/> + {/each} +
+{#if $state.includes("settings")}{/if} + - - - -{#if $state.includes("init")} -
- {@html logo} - {#if $state.includes("init-error")} - Failed to initialize app, check the console! - {:else} - Initializing page... - {/if} -
-{/if} -
- - -
-
    - {#each $notifications as item (item.id)} - notifications.remove(item.id)} {item}/> - {/each} -
-{#if $state.includes("settings")}{/if} diff --git a/src/page/Components/Editor/CodeMirror.svelte b/src/page/Components/Editor/CodeMirror.svelte index f8a0dc8d..4519efb1 100644 --- a/src/page/Components/Editor/CodeMirror.svelte +++ b/src/page/Components/Editor/CodeMirror.svelte @@ -124,10 +124,10 @@ "Cmd-/": "toggleComment", "Cmd-S": () => saveHandler(), "Cmd-F": () => activateSearch(), - "Esc": () => {searchActive = false}, + "Esc": () => searchActive = false, Tab: cm => { // convert tabs to spaces and add invisible elements - var s = Array(cm.getOption("indentUnit") + 1).join(" "); + const s = Array(cm.getOption("indentUnit") + 1).join(" "); cm.replaceSelection(s); } } @@ -162,7 +162,7 @@ function autoHint(cm) { // check for valid key combinations - const validKeys = keysPressed.every(v => { + const validKeys = keysPressed.every(v => { // if a single key press, only show hint if letter/number if (keysPressed.length === 1) { return v.startsWith("Digit") || v.startsWith("Key"); @@ -177,7 +177,7 @@ // ensure hinting not active already && !cm.state.completionActive // not first position on the line - && cm.getCursor().ch != 0 + && cm.getCursor().ch !== 0 // only hint when 1-2 key combos && keysPressed.length < 3 // valid keys combo @@ -206,8 +206,8 @@ if ( changeObj.origin === "+input" && changeObj.text.length === 1 - && changeObj.text[0] === ". ") - { + && changeObj.text[0] === ". " + ) { changeObj.update(changeObj.from, changeObj.to, [" "]); } } @@ -257,7 +257,7 @@ this={EditorSearch} active={searchActive} bind:this={search} - closeHandler={() => {searchActive = false}} + closeHandler={() => searchActive = false} instance={instance} /> {/if} diff --git a/src/page/Components/Editor/Editor.svelte b/src/page/Components/Editor/Editor.svelte index 00bea0c8..3f40c7cf 100644 --- a/src/page/Components/Editor/Editor.svelte +++ b/src/page/Components/Editor/Editor.svelte @@ -57,9 +57,9 @@ // overwrite item in items store items.update(i => { const index = i.findIndex(a => a.active); - const disabled = i[index].disabled != undefined ? i[index].disabled : false; + const disabled = i[index].disabled !== undefined ? i[index].disabled : false; const type = i[index].type; - const visible = i[index].visible != undefined ? i[index].visible : true; + const visible = i[index].visible !== undefined ? i[index].visible : true; i[index] = response; i[index].active = true; i[index].disabled = disabled; @@ -79,7 +79,7 @@ const link = document.createElement("a"); const content = codemirror.getValue(); const filename = activeItem.filename; - link.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(content)); + link.setAttribute("href", `data:text/plain;charset=utf-8,${encodeURIComponent(content)}`); link.setAttribute("download", filename); link.style.display = "none"; document.body.appendChild(link); @@ -125,7 +125,7 @@ log.add(response.error, "error", true); } else { items.update(i => i.filter(a => !a.active)); - log.add("Successfully trashed " + activeItem.filename, "info", false); + log.add(`Successfully trashed ${activeItem.filename}`, "info", false); } } state.remove("trashing"); @@ -146,6 +146,72 @@ } } + +
+ {#if $state.includes("editor-loading") || $state.includes("fetching")} + + {/if} + {#if !activeItem} +
No Item Selected
+ {/if} +
+
+
+ +
{name}
+
+
+
+ {#if $state.includes("saving")} + Saving... + {:else if $state.includes("trashing")} + (◞‸◟) + {:else if $state.includes("updating")} + Updating code, cancel request + {:else if remote} + Code was remotely fetched, check carefully before saving! + {:else if temp} + Ready for code! + {:else} + Last modified: {lastModified} + {/if} +
+
+
+
+ + + +
+
+
+ +
+ +
+ - -
- {#if $state.includes("editor-loading") || $state.includes("fetching")} - - {/if} - {#if !activeItem} -
No Item Selected
- {/if} -
-
-
- -
{name}
-
-
-
- {#if $state.includes("saving")} - Saving... - {:else if $state.includes("trashing")} - (◞‸◟) - {:else if $state.includes("updating")} - Updating code, cancel request - {:else if remote} - Code was remotely fetched, check carefully before saving! - {:else if temp} - Ready for code! - {:else} - Last modified: {lastModified} - {/if} -
-
-
-
- - - -
-
-
- -
- -
diff --git a/src/page/Components/Editor/EditorSearch.svelte b/src/page/Components/Editor/EditorSearch.svelte index 3b5d1a91..e55ae011 100644 --- a/src/page/Components/Editor/EditorSearch.svelte +++ b/src/page/Components/Editor/EditorSearch.svelte @@ -104,7 +104,7 @@ cm.scrollIntoView({from: ranges[i].anchor, to: ranges[i].head}, 20); // mark currently selected element marks.forEach(marker => marker.clear()); - const m = cm.markText(ranges[i].anchor, ranges[i].head, {className: "cm-search-mark"}); + const m = cm.markText(ranges[i].anchor, ranges[i].head, {className: "cm-search-mark"}); marks.push(m); // increment display index after getting data from array // display index is always +1 compared to index within array @@ -126,11 +126,29 @@ cm.scrollIntoView({from: ranges[i].anchor, to: ranges[i].head}, 20); // mark currently selected element marks.forEach(marker => marker.clear()); - const m = cm.markText(ranges[i].anchor, ranges[i].head, {className: "cm-search-mark"}); + const m = cm.markText(ranges[i].anchor, ranges[i].head, {className: "cm-search-mark"}); marks.push(m); } } + +{#if active} + +{/if} + -{#if active} - -{/if} diff --git a/src/page/Components/Notification.svelte b/src/page/Components/Notification.svelte index c0c8c8dd..23dffbc7 100644 --- a/src/page/Components/Notification.svelte +++ b/src/page/Components/Notification.svelte @@ -11,6 +11,19 @@ const icon = item.type === "error" ? iconError : item.type === "info" ? iconInfo : iconWarn; + +
  • +
    {@html icon}
    + {item.message} + +
  • + - -
  • -
    {@html icon}
    - {item.message} - -
  • diff --git a/src/page/Components/Settings.svelte b/src/page/Components/Settings.svelte index a1eb8b98..2bb1cc4e 100644 --- a/src/page/Components/Settings.svelte +++ b/src/page/Components/Settings.svelte @@ -20,7 +20,7 @@ const val = blacklist.value.split(",").map(item => item.trim()).filter(n => n); // compare blacklist input to saved blacklist - if ([...val].sort().toString() != [...$settings.blacklist].sort().toString()) { + if ([...val].sort().toString() !== [...$settings.blacklist].sort().toString()) { settings.updateSingleSetting("blacklist", val); // when blacklistSaving, visual indication of saving occurs on element // the visual save indication is mostly ux only indicates a setting save was attempted @@ -48,17 +48,134 @@ // close all open extension pages const url = browser.runtime.getURL("page.html"); const close = []; - const tabs = await browser.tabs.query({}); + const tabs = await browser.tabs.query({}); tabs.forEach(tab => tab.url === url && close.push(tab.id)); if (close.length > 0) browser.tabs.remove(close); } + +
    state.remove("settings")} + in:fade={{duration: 150}} + out:fade={{duration: 150, delay: 75}} +> + +
    + - -
    {state.remove("settings")}} - in:fade={{duration: 150}} - out:fade={{duration: 150, delay: 75}} -> - -
    diff --git a/src/page/Components/Sidebar/Sidebar.svelte b/src/page/Components/Sidebar/Sidebar.svelte index 2b0e9929..5a622c6b 100644 --- a/src/page/Components/Sidebar/Sidebar.svelte +++ b/src/page/Components/Sidebar/Sidebar.svelte @@ -20,7 +20,7 @@ // disable buttons accordingly $: disabled = !$state.includes("ready"); - $: list = sortBy($items, $settings.sortOrder).filter(a => a.visible != false); + $: list = sortBy($items, $settings.sortOrder).filter(a => a.visible !== false); // always scroll to an active item // when sorting is changed, a save occurs, etc... will scroll to active item @@ -43,7 +43,7 @@ const random = uniqueId(); const namePrefix = type === "js" ? "NewScript-" : "NewStyle-"; const name = namePrefix + random; - const filename = name + "." + type; + const filename = `${name}.${type}`; const description = "This is your new file, start writing code"; const item = { content: content || newScriptDefault(description, name, type), @@ -70,17 +70,17 @@ // check if there's a temp item and it's not the item to be activated // can occur when user clicks a non-temp item while a temp item exists const temp = $items.find(i => i.temp); - if ((temp && (temp != item) || cmChanged()) && !warn()) return; + if (((temp && (temp !== item)) || cmChanged()) && !warn()) return; // the editor has changed or the above scenario is true // the user has been warned and has accepted the warning // if another item already active, deactivate it const activeItem = $items.find(i => i.active); - if (activeItem) { + if (activeItem) { activeItem.active = false; $items = $items; } // remove the temp item if needed - if (temp && (temp != item)) items.update(i => i.filter(a => !a.temp)); + if (temp && (temp !== item)) items.update(i => i.filter(a => !a.temp)); // set the saved and session code variables properly const savedCode = item.temp ? null : item.content; @@ -166,6 +166,41 @@ sidebarTimeout = setTimeout(() => showCount = true, 750); } + + + - - diff --git a/src/page/Components/Sidebar/SidebarFilter.svelte b/src/page/Components/Sidebar/SidebarFilter.svelte index 3d3f8cf5..7624ab1f 100644 --- a/src/page/Components/Sidebar/SidebarFilter.svelte +++ b/src/page/Components/Sidebar/SidebarFilter.svelte @@ -16,7 +16,7 @@ function filter(query) { $items = $items.map(item => { const visible = item.filename.toLowerCase().includes(query.trim().toLowerCase()); - if (visible != item.visible) return {...item, visible}; + if (visible !== item.visible) return {...item, visible}; return item; }); } @@ -25,6 +25,49 @@ settings.updateSingleSetting("sortOrder", order); } + +
    + + +
  • updateSortOrder("lastModifiedAsc")} + > + Last Modified: Asc{sortOrder === "lastModifiedAsc" ? " *" : ""} +
  • +
  • updateSortOrder("lastModifiedDesc")} + > + Last Modified: Desc{sortOrder === "lastModifiedDesc" ? " *" : ""} +
  • +
  • updateSortOrder("nameAsc")} + > + Name: Asc{sortOrder === "nameAsc" ? " *" : ""} +
  • +
  • updateSortOrder("nameDesc")} + > + Name: Desc{sortOrder === "nameDesc" ? " *" : ""} +
  • +
    + {#if query} + query = ""} {disabled}/> + {/if} +
    + - -
    - - -
  • updateSortOrder("lastModifiedAsc")} - > - Last Modified: Asc{sortOrder === "lastModifiedAsc" ? " *" : ""} -
  • -
  • updateSortOrder("lastModifiedDesc")} - > - Last Modified: Desc{sortOrder === "lastModifiedDesc" ? " *" : ""} -
  • -
  • updateSortOrder("nameAsc")} - > - Name: Asc{sortOrder === "nameAsc" ? " *" : ""} -
  • -
  • updateSortOrder("nameDesc")} - > - Name: Desc{sortOrder === "nameDesc" ? " *" : ""} -
  • -
    - {#if query} - query = ""} {disabled}/> - {/if} -
    diff --git a/src/page/Components/Sidebar/SidebarItem.svelte b/src/page/Components/Sidebar/SidebarItem.svelte index 6b5b3066..8b7f836d 100644 --- a/src/page/Components/Sidebar/SidebarItem.svelte +++ b/src/page/Components/Sidebar/SidebarItem.svelte @@ -14,7 +14,7 @@ function formatDescription(str) { if (str && str.length > 120) { showTitle = true; - return str.substring(0, 120).trim() + "..."; + return `${str.substring(0, 120).trim()}...`; } else { showTitle = false; return str; @@ -23,6 +23,29 @@ $: description = formatDescription(data.description); + +
    +
    + +
    {data.name}
    + +
    + {#if description} +
    + {description} +
    + {/if} +
    + - -
    -
    - -
    {data.name}
    - -
    - {#if description} -
    - {description} -
    - {/if} -
    diff --git a/src/page/store.js b/src/page/store.js index 1d34972a..f366e2c2 100644 --- a/src/page/store.js +++ b/src/page/store.js @@ -1,6 +1,19 @@ import {writable} from "svelte/store"; import {uniqueId} from "./utils.js"; +function notificationStore() { + const {subscribe, update} = writable([]); + const add = item => { + update(a => { + a.push(item); + return a; + }); + }; + const remove = id => update(a => a.filter(b => b.id !== id)); + return {subscribe, add, remove}; +} +export const notifications = notificationStore(); + function logStore() { const {subscribe, set, update} = writable([]); const add = (message, type, notify) => { @@ -18,19 +31,6 @@ function logStore() { } export const log = logStore(); -function notificationStore() { - const {subscribe, update} = writable([]); - const add = item => { - update(a => { - a.push(item); - return a; - }); - }; - const remove = id => update(a => a.filter(b => b.id !== id)); - return {subscribe, add, remove}; -} -export const notifications = notificationStore(); - function stateStore() { const {subscribe, update} = writable(["init"]); // store oldState to see how state transitioned diff --git a/src/page/utils.js b/src/page/utils.js index e17998ff..278c16ac 100644 --- a/src/page/utils.js +++ b/src/page/utils.js @@ -1,3 +1,7 @@ +/** + * @param {number} ms millisecond timestamp + * @returns {string} + */ export function formatDate(ms) { const d = new Date(ms); const yr = new Intl.DateTimeFormat("en", {year: "numeric"}).format(d); @@ -9,9 +13,24 @@ export function formatDate(ms) { } export function uniqueId() { - return Math.random().toString(36).substr(2, 8); + return Math.random().toString(36).substring(2, 10); } +/** + * awaitable function for waiting an arbitrary amount of time + * @param {number} ms the amount of time to wait in milliseconds + * @returns {Promise.} + */ +export function wait(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// TODO: describe the items array that should get passed to this function +/** + * @param {array} array + * @param {("lastModifiedAsc"|"lastModifiedDesc"|"nameAsc"|"nameDesc")} order + * @returns + */ export function sortBy(array, order) { if (order === "nameAsc") { array.sort((a, b) => a.name.localeCompare(b.name)); @@ -27,7 +46,14 @@ export function sortBy(array, order) { return array; } -export function newScriptDefault(description, name, type) { +/** + * + * @param {string} description + * @param {string} name + * @param {("css"|"js")} type + * @returns {string} + */ +export function newScriptDefault(description, name, type) { if (type === "css") { return `/* ==UserStyle==\n@name ${name}\n@description ${description}\n@match \n==/UserStyle== */`; } else if (type === "js") { @@ -35,6 +61,10 @@ export function newScriptDefault(description, name, type) { } } +/** + * @param {string} str + * @returns {{code: string, content: str, metablock: string, metadata: {string: string[]}}} + */ export function parse(str) { if (typeof str != "string") return null; const blocksReg = /(?:(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)|(\/\* ==UserStyle==[ \t]*?\r?\n([\S\s]*?)\r?\n==\/UserStyle== \*\/)([\S\s]*))/; @@ -49,8 +79,8 @@ export function parse(str) { const metadata = {}; const metaArray = metas.split("\n"); metaArray.forEach(function(m) { - var parts = m.trim().match(/^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]+([^\s]+[^\r\n\t\v\f]*)/); - var parts2 = m.trim().match(/^(?:[ \t]*(?:\/\/)?[ \t]*@)(noframes)[ \t]*$/); + const parts = m.trim().match(/^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]+([^\s]+[^\r\n\t\v\f]*)/); + const parts2 = m.trim().match(/^(?:[ \t]*(?:\/\/)?[ \t]*@)(noframes)[ \t]*$/); if (parts) { metadata[parts[1]] = metadata[parts[1]] || []; metadata[parts[1]].push(parts[2]); diff --git a/src/popup/App.svelte b/src/popup/App.svelte index 73e4e492..08cf8ebf 100644 --- a/src/popup/App.svelte +++ b/src/popup/App.svelte @@ -147,7 +147,7 @@ async function openExtensionPage() { const url = browser.runtime.getURL("page.html"); - const tabs = await browser.tabs.query({}); + const tabs = await browser.tabs.query({}); for (let i = 0; i < tabs.length; i++) { if (tabs[i].url === url) { await browser.windows.update(tabs[i].windowId, {focused: true}); @@ -176,7 +176,7 @@ try { lastUpdateCheckObj = await browser.storage.local.get(["lastUpdateCheck"]); } catch (error) { - console.error("Error checking extension storage " + error); + console.error(`Error checking extension storage ${error}`); return false; } // if extension storage doesn't have key, run the check @@ -200,7 +200,7 @@ return false; } - console.log(((timestampMs - lastUpdateCheck) / (1000 * 60 * 60)) + " hours have passed"); + console.log(`${(timestampMs - lastUpdateCheck) / (1000 * 60 * 60)} hours have passed`); console.log("running update check"); // otherwise run the check return true; @@ -215,8 +215,9 @@ } else if (response.items) { showAll = true; allItems = response.items; + allItems = []; } else if (response.error) { - console.log("Error opening save location: " + response.error); + console.log(`Error opening save location: ${response.error}`); error = response.error; } disabled = false; @@ -229,7 +230,7 @@ try { pltfm = await browser.runtime.sendNativeMessage({name: "REQ_PLATFORM"}); } catch (error) { - console.log("Error for pltfm promise: " + error); + console.log(`Error for pltfm promise: ${error}`); initError = true; loading = false; return; @@ -249,7 +250,7 @@ try { init = await browser.runtime.sendNativeMessage({name: "POPUP_INIT"}); } catch (error) { - console.log("Error for init promise: " + error); + console.log(`Error for init promise: ${error}`); initError = true; loading = false; return; @@ -287,7 +288,7 @@ matches = await browser.runtime.sendNativeMessage(message); // response = await browser.runtime.sendMessage(message); } catch (error) { - console.log("Error for matches promise: " + error); + console.log(`Error for matches promise: ${error}`); initError = true; loading = false; return; @@ -312,7 +313,7 @@ abort = true; updatesResponse = await browser.runtime.sendNativeMessage({name: "POPUP_UPDATES"}); } catch (error) { - console.error("Error for updates promise: " + error); + console.error(`Error for updates promise: ${error}`); initError = true; loading = false; abort = false; @@ -346,7 +347,7 @@ // https://developer.apple.com/documentation/safariservices/safari_web_extensions/messaging_between_the_app_and_javascript_in_a_safari_web_extension const response = await browser.tabs.sendMessage(tabs[0].id, {name: "USERSCRIPT_INSTALL_00"}); if (response.error) { - console.log("Error checking .user.js url: " + response.error); + console.log(`Error checking .user.js url: ${response.error}`); error = response.error; } else if (!response.invalid) { // the response will contain the string to display @@ -394,10 +395,10 @@ let addHeight = 0; // if warn or error elements visible, also subtract that from applied height if (warn) addHeight += warn.offsetHeight; - if (err) addHeight += err.offsetHeight; + if (err) addHeight += err.offsetHeight; windowHeight = (window.outerHeight - (headerHeight + addHeight)); - main.style.height = windowHeight + "px"; - main.style.paddingBottom = (headerHeight + addHeight) + "px"; + main.style.height = `${windowHeight}px`; + main.style.paddingBottom = `${headerHeight + addHeight}px`; }, 25); } @@ -419,7 +420,7 @@ // if the response includes an error, display it in the view if (response.error) { - console.log("Can not install userscript: " + response.error); + console.log(`Can not install userscript: ${response.error}`); installViewUserscriptError = response.error; } else { installViewUserscript = response; @@ -456,6 +457,7 @@ resize(); }); +
    Popup inactive on extension page
    {:else if initError}
    - Something went wrong:  window.location.reload()}> click to retry + Something went wrong:  + window.location.reload()} + > + click to retry +
    {:else if items.length < 1}
    No matched userscripts
    @@ -531,7 +539,9 @@ {#if !inactive && platform === "macos"} {/if} {#if showUpdates} @@ -568,7 +578,10 @@ {showAll = false; refreshView()}} + closeClick={() => { + showAll = false; + refreshView(); + }} showLoaderOnDisabled={false} > {/if} + -
    - -
    {name}
    - {#if subframe}
    SUB
    {/if} - -
    diff --git a/src/popup/Components/View.svelte b/src/popup/Components/View.svelte index 58664c90..e67587e6 100644 --- a/src/popup/Components/View.svelte +++ b/src/popup/Components/View.svelte @@ -20,6 +20,7 @@ }; } +
    {headerTitle} @@ -37,6 +38,7 @@ {/if}
    + diff --git a/src/popup/Components/Views/InstallView.svelte b/src/popup/Components/Views/InstallView.svelte index df3e7fe5..c0d730fb 100644 --- a/src/popup/Components/Views/InstallView.svelte +++ b/src/popup/Components/Views/InstallView.svelte @@ -6,12 +6,13 @@ export let installCancelClick; export let installConfirmClick; +
    {#if installError}
    {@html iconError} -
    The usercript can not be installed.
    -

    Error: OK GO

    +
    Couldn't install userscript
    +

    {installError}

    {:else if userscript}
      @@ -57,11 +58,16 @@
      Be sure you trust the author before installing. Nefarious code can exploit your security and privacy.
    - - + +
    {/if} + - -
    - -
      - -
    • At least one slot is required...
    • -
      -
    -
    diff --git a/src/shared/Components/IconButton.svelte b/src/shared/Components/IconButton.svelte index c9bb5a00..e871ff1e 100644 --- a/src/shared/Components/IconButton.svelte +++ b/src/shared/Components/IconButton.svelte @@ -5,6 +5,7 @@ export let title = undefined; export let notification = false; + + -
    - {@html iconLoader} - {#if abort} -
    - Fetching resources, cancel request -
    - {/if} -
    diff --git a/src/shared/Components/Tag.svelte b/src/shared/Components/Tag.svelte index 89870cf4..98bd234b 100644 --- a/src/shared/Components/Tag.svelte +++ b/src/shared/Components/Tag.svelte @@ -1,7 +1,9 @@ + +
    + -
    diff --git a/src/shared/Components/Toggle.svelte b/src/shared/Components/Toggle.svelte index 53e70e71..37bd8c94 100644 --- a/src/shared/Components/Toggle.svelte +++ b/src/shared/Components/Toggle.svelte @@ -3,11 +3,13 @@ export let disabled = false; export let title = undefined; + +
    \ No newline at end of file +Userscripts Safari
    \ No newline at end of file diff --git a/extension/Userscripts Extension/Resources/page.js b/extension/Userscripts Extension/Resources/page.js index d872fa3e..8f8e7895 100644 --- a/extension/Userscripts Extension/Resources/page.js +++ b/extension/Userscripts Extension/Resources/page.js @@ -962,6 +962,10 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o return { set, update, subscribe }; } + /** + * @param {number} ms millisecond timestamp + * @returns {string} + */ function formatDate(ms) { const d = new Date(ms); const yr = new Intl.DateTimeFormat("en", {year: "numeric"}).format(d); @@ -973,9 +977,15 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o } function uniqueId() { - return Math.random().toString(36).substr(2, 8); + return Math.random().toString(36).substring(2, 10); } + // TODO: describe the items array that should get passed to this function + /** + * @param {Array} array + * @param {("lastModifiedAsc"|"lastModifiedDesc"|"nameAsc"|"nameDesc")} order + * @returns + */ function sortBy(array, order) { if (order === "nameAsc") { array.sort((a, b) => a.name.localeCompare(b.name)); @@ -991,7 +1001,14 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o return array; } - function newScriptDefault(description, name, type) { + /** + * + * @param {string} description + * @param {string} name + * @param {("css"|"js")} type + * @returns {string} + */ + function newScriptDefault(description, name, type) { if (type === "css") { return `/* ==UserStyle==\n@name ${name}\n@description ${description}\n@match \n==/UserStyle== */`; } else if (type === "js") { @@ -999,6 +1016,92 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o } } + /** + * @param {string} text editor code + * @returns {{match: boolean, meta: boolean} | {key: string, value: string, text: string}[]} + */ + function parseMetadata(text) { + const groupsRe = /(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)/; + const groups = text.match(groupsRe); + // userscript code doesn't match the regex expression + // could be missing opening/closing tags, malformed + // or missing metadata between opening/closing tags (group 2 in regex exp) + if (!groups) { + return {match: false}; + } + + // userscript code matches but content between opening/closing tag missing + // ex. opening/closing tags present, but newline characters between the tags + const metas = groups[2]; + if (!metas) return {match: true, meta: false}; + + const metadata = []; + const metaArray = metas.split("\n"); + + for (let i = 0; i < metaArray.length; i++) { + const metaRegex = /^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]*([^\s]+[^\r\n\t\v\f]*)?/; + const meta = metaArray[i]; + const parts = meta.match(metaRegex); + if (parts) metadata.push({key: parts[1], value: parts[2], text: parts[0]}); + } + + // if there is content between the opening/closing tags, match will be found + // this additionally checks that there's at least one properly formed key + // if not keys found, assume metadata is missing + // checking that required keys are present will happen elsewhere + if (!Object.keys(metadata).length) return {match: true, meta: false}; + + return metadata; + } + + const validGrants = new Set([ + "GM.addStyle", + "GM.deleteValue", + "GM.getValue", + "GM.info", + "GM.listValues", + "GM.openInTab", + "GM.setClipboard", + "GM.setValue", + "GM.xmlHttpRequest", + "GM_addStyle", + "GM_info", + "GM_setClipboard", + "GM_xmlhttpRequest" + ]); + + const validKeys = new Set([ + "author", + "description", + "downloadURL", + "exclude", + "exclude-match", + "grant", + "include", + "inject-into", + "match", + "name", + "noframes", + "require", + "run-at", + "updateURL", + "version", + "weight" + ]); + + function notificationStore() { + const {subscribe, update} = writable([]); + const add = item => { + update(a => { + a.push(item); + return a; + }); + }; + const remove = id => update(a => a.filter(b => b.id !== id)); + return {subscribe, add, remove}; + } + const notifications = notificationStore(); + function logStore() { const {subscribe, set, update} = writable([]); const add = (message, type, notify) => { @@ -1016,19 +1119,6 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o } const log = logStore(); - function notificationStore() { - const {subscribe, update} = writable([]); - const add = item => { - update(a => { - a.push(item); - return a; - }); - }; - const remove = id => update(a => a.filter(b => b.id !== id)); - return {subscribe, add, remove}; - } - const notifications = notificationStore(); - function stateStore() { const {subscribe, update} = writable(["init"]); // store oldState to see how state transitioned @@ -1318,7 +1408,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o // button click events bubble to document body // can use to determine if button triggered body event listener func // event.target = button, e.target = body event listener trigger el - if (e.target != event.target) { + if (e.target !== event.target) { // the body event listener not triggered by button // if omitted, dropdown would close immediately $$invalidate(3, active = false); @@ -1470,7 +1560,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (113:4) {#if query} + // (66:4) {#if query} function create_if_block(ctx) { let iconbutton; let current; @@ -1642,7 +1732,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o items, $items = $items.map(item => { const visible = item.filename.toLowerCase().includes(query.trim().toLowerCase()); - if (visible != item.visible) return { ...item, visible }; + if (visible !== item.visible) return { ...item, visible }; return item; }), $items @@ -1843,13 +1933,13 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o return { c() { div = element("div"); - attr(div, "class", div_class_value = "script__tag " + (/*type*/ ctx[0] ? "script__tag--" + /*type*/ ctx[0] : "") + " svelte-p9vdxd"); + attr(div, "class", div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-p9vdxd"); }, m(target, anchor) { insert(target, div, anchor); }, p(ctx, [dirty]) { - if (dirty & /*type*/ 1 && div_class_value !== (div_class_value = "script__tag " + (/*type*/ ctx[0] ? "script__tag--" + /*type*/ ctx[0] : "") + " svelte-p9vdxd")) { + if (dirty & /*type*/ 1 && div_class_value !== (div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-p9vdxd")) { attr(div, "class", div_class_value); } }, @@ -2171,7 +2261,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o function formatDescription(str) { if (str && str.length > 120) { $$invalidate(2, showTitle = true); - return str.substring(0, 120).trim() + "..."; + return `${str.substring(0, 120).trim()}...`; } else { $$invalidate(2, showTitle = false); return str; @@ -17351,6 +17441,47 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o instance$7.display.wrapper.removeAttribute("style"); } + /** + * @param {{set: Set, setName: string, message: string}} + * @returns {{from: {line: number, ch: number}, + * to: {line: number, ch: number}, message: string, severity: string}} + */ + function makeErrors({ set, setName, message }) { + const errors = []; + + //for (let i = 0; i < array.length; i++) { + set.forEach(el => { + //const el = array[i]; + let regex = new RegExp(`^${el}$`); + + if (setName === "invalidKeys") { + regex = new RegExp(`^// @${el}.*?$`); + } + + const cursor = instance$7.getSearchCursor(regex); + const ranges = []; + + while (cursor.findNext()) { + ranges.push({ anchor: cursor.from(), head: cursor.to() }); + } + + for (let j = 0; j < ranges.length; j++) { + const range = ranges[j]; + + const err = { + from: range?.anchor, + to: range?.head, + severity: "warning", + message + }; + + errors.push(err); + } + }); + + return errors; + } + function getValue() { return instance$7.getValue(); } @@ -17384,7 +17515,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o let searchActive = false; // linter options - https://jshint.com/docs/options/ - const lintOptions = { asi: true, esversion: 9 }; + const lintOptions = { async: true, getAnnotations: linter }; function init() { // do lint settings check @@ -17394,7 +17525,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o instance$7 = codemirror.fromTextArea(textarea, { mode: "javascript", autoCloseBrackets: $settings.autoCloseBrackets, - continueComments: false, + continueComments: true, foldGutter: true, lineNumbers: true, lineWrapping: true, @@ -17417,12 +17548,10 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o "Cmd-/": "toggleComment", "Cmd-S": () => saveHandler(), "Cmd-F": () => activateSearch(), - "Esc": () => { - $$invalidate(2, searchActive = false); - }, + "Esc": () => $$invalidate(2, searchActive = false), Tab: cm => { // convert tabs to spaces and add invisible elements - var s = Array(cm.getOption("indentUnit") + 1).join(" "); + const s = Array(cm.getOption("indentUnit") + 1).join(" "); cm.replaceSelection(s); } @@ -17471,7 +17600,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o if (// check if setting is enabled $settings.autoHint && // ensure hinting not active already !cm.state.completionActive && // not first position on the line - cm.getCursor().ch != 0 && // only hint when 1-2 key combos + cm.getCursor().ch !== 0 && // only hint when 1-2 key combos keysPressed.length < 3 && // valid keys combo validKeys) cm.showHint({ completeSingle: false }); } @@ -17501,6 +17630,118 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o instance$7.refresh(); } + function linter(text, updateLinting, options) { + + // only lint in javascript mode + if (instance$7?.options.mode !== "javascript") return; + + // normal javascript linting through CodeMirror addon and jshint + let errors = codemirror.lint.javascript(text, { asi: true, esversion: 9 }); + + // errors from custom checks & metadata parser will populate this array + let customErrors = []; + + { + // check if whitespace characters precede userscript tags + if ((/^\s/).test(text)) { + customErrors.push({ + from: { line: 0, ch: 0 }, + to: { line: 0, ch: 0 }, + severity: "warning", + message: "Userscript starts with whitespace characters." + }); + } + + // parse the code metadata + const parsedMetadata = parseMetadata(text); + + // check parser result for initial errors + if (parsedMetadata.match === false) { + customErrors.push({ + from: { line: 0, ch: 0 }, + to: { line: 0, ch: 0 }, + severity: "error", + message: "Userscript metadata missing or improperly formatted." + }); + } else if (parsedMetadata.meta === false) { + customErrors.push({ + from: { line: 0, ch: 0 }, + to: { line: 0, ch: 0 }, + severity: "error", + message: "Userscript metadata missing." + }); + } + + // run additional checks + const invalidKeys = new Set(); + + let invalidKeysErrors = []; + const invalidGrants = new Set(); + let invalidGrantsErrors = []; + const emptyKeyValues = new Set(); + let emptyKeyValuesErrors = []; + + // if metadata fails initial check, it won't be array + // don't run the additional checks if initial checks failed + if (Array.isArray(parsedMetadata)) { + // find invalid keys or empty key values + for (let i = 0; i < parsedMetadata.length; i++) { + const { key, value: val, text } = parsedMetadata[i]; + + if (!validKeys.has(key)) { + invalidKeys.add(key); + } + + if (key !== "noframes" && !invalidKeys.has(key) && !val) { + emptyKeyValues.add(text); + } + } + + // find unsupported @grant methods + const grants = parsedMetadata.filter(a => a.key === "grant"); + + for (let i = 0; i < grants.length; i++) { + const { value, text } = grants[i]; + + if (!validGrants.has(value)) { + invalidGrants.add(text); + } + } + } + + invalidKeysErrors = makeErrors({ + set: invalidKeys, + setName: "invalidKeys", + message: "Unsupported metadata key." + }); + + emptyKeyValuesErrors = makeErrors({ + set: emptyKeyValues, + setName: "emptyKeyValues", + message: "Key requires a value." + }); + + invalidGrantsErrors = makeErrors({ + set: invalidGrants, + setName: "invalidGrants", + message: "@grant method not supported." + }); + + // combine all custom error arrays + customErrors = [ + ...customErrors, + ...invalidKeysErrors, + ...emptyKeyValuesErrors, + ...invalidGrantsErrors + ]; + } + + // if the custom linter returned errors, add them to errors array + if (customErrors.length) errors = [...errors, ...customErrors]; + + updateLinting(errors); + } + function discardChanges() { if ($state.includes("ready")) { instance$7.setValue(savedCode); @@ -17517,9 +17758,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }); } - const func = () => { - $$invalidate(2, searchActive = false); - }; + const func = () => $$invalidate(2, searchActive = false); function switch_instance_binding($$value) { binding_callbacks[$$value ? "unshift" : "push"](() => { @@ -17641,7 +17880,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o return child_ctx; } - // (230:8) + // (180:8) function create_default_slot$1(ctx) { let li0; let t1; @@ -17692,7 +17931,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (237:8) {#if $state.includes("items-loading")} + // (187:8) {#if $state.includes("items-loading")} function create_if_block_1(ctx) { let loader; let current; @@ -17721,7 +17960,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (240:8) {#each list as item (item.filename)} + // (190:8) {#each list as item (item.filename)} function create_each_block(key_1, ctx) { let first; let switch_instance; @@ -17819,7 +18058,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (249:4) {#if showCount} + // (199:4) {#if showCount} function create_if_block$5(ctx) { let div; let t0_value = /*list*/ ctx[3].length + ""; @@ -17833,7 +18072,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o div = element("div"); t0 = text(t0_value); t1 = text(" Items"); - attr(div, "class", "sidebar__count svelte-11mx0re"); + attr(div, "class", "sidebar__count svelte-23v8pd"); }, m(target, anchor) { insert(target, div, anchor); @@ -17942,13 +18181,13 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o t4 = space(); if (if_block1) if_block1.c(); - attr(div0, "class", "sidebar__filter svelte-11mx0re"); - attr(div1, "class", "sidebar__header svelte-11mx0re"); - attr(div2, "class", "sidebar__body svelte-11mx0re"); + attr(div0, "class", "sidebar__filter svelte-23v8pd"); + attr(div1, "class", "sidebar__header svelte-23v8pd"); + attr(div2, "class", "sidebar__body svelte-23v8pd"); attr(div3, "class", div3_class_value = "sidebar " + (!/*$settings*/ ctx[4].descriptions ? "sidebar--compact" - : "") + " svelte-11mx0re"); + : "") + " svelte-23v8pd"); }, m(target, anchor) { insert(target, div3, anchor); @@ -18045,7 +18284,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o if (!current || dirty & /*$settings*/ 16 && div3_class_value !== (div3_class_value = "sidebar " + (!/*$settings*/ ctx[4].descriptions ? "sidebar--compact" - : "") + " svelte-11mx0re")) { + : "") + " svelte-23v8pd")) { attr(div3, "class", div3_class_value); } }, @@ -18129,7 +18368,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o const namePrefix = type === "js" ? "NewScript-" : "NewStyle-"; const name = namePrefix + random; - const filename = name + "." + type; + const filename = `${name}.${type}`; const description = "This is your new file, start writing code"; const item = { @@ -18160,7 +18399,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o // can occur when user clicks a non-temp item while a temp item exists const temp = $items.find(i => i.temp); - if ((temp && temp != item || cmChanged()) && !warn()) return; + if ((temp && temp !== item || cmChanged()) && !warn()) return; // the editor has changed or the above scenario is true // the user has been warned and has accepted the warning @@ -18173,7 +18412,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o } // remove the temp item if needed - if (temp && temp != item) items.update(i => i.filter(a => !a.temp)); + if (temp && temp !== item) items.update(i => i.filter(a => !a.temp)); // set the saved and session code variables properly const savedCode = item.temp ? null : item.content; @@ -18275,11 +18514,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o const click_handler = () => state.add("settings"); const click_handler_1 = () => newItem("css"); const click_handler_2 = () => newItem("js"); - - const func = (item, e) => { - toggleItem(e, item); - }; - + const func = (item, e) => toggleItem(e, item); const click_handler_3 = item => activate(item); let disabled; let list; @@ -18291,7 +18526,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o } if ($$self.$$.dirty & /*$items, $settings*/ 65552) { - $$invalidate(3, list = sortBy($items, $settings.sortOrder).filter(a => a.visible != false)); + $$invalidate(3, list = sortBy($items, $settings.sortOrder).filter(a => a.visible !== false)); } if ($$self.$$.dirty & /*list*/ 8) { @@ -18366,7 +18601,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (268:4) {#if !activeItem} + // (154:4) {#if !activeItem} function create_if_block_5(ctx) { let div; @@ -18374,7 +18609,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o c() { div = element("div"); div.textContent = "No Item Selected"; - attr(div, "class", "editor__empty svelte-cbo01e"); + attr(div, "class", "editor__empty svelte-5bq9yj"); }, m(target, anchor) { insert(target, div, anchor); @@ -18385,7 +18620,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (289:20) {:else} + // (175:20) {:else} function create_else_block(ctx) { let t0; let t1; @@ -18409,7 +18644,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (287:35) + // (173:35) function create_if_block_4(ctx) { let t; @@ -18427,7 +18662,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (285:37) + // (171:37) function create_if_block_3(ctx) { let t0; let span; @@ -18440,7 +18675,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o span = element("span"); t1 = text("remotely fetched"); t2 = text(", check carefully before saving!"); - attr(span, "class", "info svelte-cbo01e"); + attr(span, "class", "info svelte-5bq9yj"); attr(span, "title", /*remote*/ ctx[4]); }, m(target, anchor) { @@ -18462,7 +18697,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (283:58) + // (169:58) function create_if_block_2(ctx) { let t0; let span; @@ -18495,7 +18730,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (281:58) + // (167:58) function create_if_block_1$1(ctx) { let t; @@ -18513,7 +18748,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (279:20) {#if $state.includes("saving")} + // (165:20) {#if $state.includes("saving")} function create_if_block$6(ctx) { let t; @@ -18659,19 +18894,19 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o t11 = space(); button1 = element("button"); t12 = text("Save"); - attr(div0, "class", "editor__title truncate svelte-cbo01e"); - attr(div1, "class", "svelte-cbo01e"); - attr(div3, "class", "editor__status svelte-cbo01e"); - attr(div4, "class", "editor__header__content svelte-cbo01e"); - attr(div5, "class", "editor__header__buttons svelte-cbo01e"); - attr(div6, "class", "editor__header svelte-cbo01e"); - attr(div7, "class", "editor__code svelte-cbo01e"); + attr(div0, "class", "editor__title truncate svelte-5bq9yj"); + attr(div1, "class", "svelte-5bq9yj"); + attr(div3, "class", "editor__status svelte-5bq9yj"); + attr(div4, "class", "editor__header__content svelte-5bq9yj"); + attr(div5, "class", "editor__header__buttons svelte-5bq9yj"); + attr(div6, "class", "editor__header svelte-5bq9yj"); + attr(div7, "class", "editor__code svelte-5bq9yj"); button0.disabled = button0_disabled_value = /*disabled*/ ctx[9] || /*discardDisabled*/ ctx[7]; - attr(button0, "class", "svelte-cbo01e"); + attr(button0, "class", "svelte-5bq9yj"); button1.disabled = button1_disabled_value = /*disabled*/ ctx[9] || /*saveDisabled*/ ctx[8]; - attr(button1, "class", "svelte-cbo01e"); - attr(div8, "class", "editor__footer svelte-cbo01e"); - attr(div9, "class", "editor svelte-cbo01e"); + attr(button1, "class", "svelte-5bq9yj"); + attr(div8, "class", "editor__footer svelte-5bq9yj"); + attr(div9, "class", "editor svelte-5bq9yj"); }, m(target, anchor) { insert(target, div9, anchor); @@ -18864,12 +19099,12 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o items.update(i => { const index = i.findIndex(a => a.active); - const disabled = i[index].disabled != undefined + const disabled = i[index].disabled !== undefined ? i[index].disabled : false; const type = i[index].type; - const visible = i[index].visible != undefined ? i[index].visible : true; + const visible = i[index].visible !== undefined ? i[index].visible : true; i[index] = response; i[index].active = true; i[index].disabled = disabled; @@ -18891,7 +19126,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o const link = document.createElement("a"); const content = codemirror.getValue(); const filename = activeItem.filename; - link.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(content)); + link.setAttribute("href", `data:text/plain;charset=utf-8,${encodeURIComponent(content)}`); link.setAttribute("download", filename); link.style.display = "none"; document.body.appendChild(link); @@ -18946,7 +19181,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o log.add(response.error, "error", true); } else { items.update(i => i.filter(a => !a.active)); - log.add("Successfully trashed " + activeItem.filename, "info", false); + log.add(`Successfully trashed ${activeItem.filename}`, "info", false); } } @@ -19281,7 +19516,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o t22 = space(); div26 = element("div"); div16 = element("div"); - div16.innerHTML = `
    General Settings
    `; + div16.innerHTML = `
    General Settings
    `; t24 = space(); div18 = element("div"); div17 = element("div"); @@ -19337,51 +19572,51 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o a2 = element("a"); a2.textContent = "supporting the project"; t53 = text("."); - attr(div0, "class", "svelte-1ibrghz"); - attr(div1, "class", "modal__title svelte-1ibrghz"); - attr(div2, "class", "svelte-1ibrghz"); - attr(div3, "class", "modal__row svelte-1ibrghz"); - attr(div4, "class", "svelte-1ibrghz"); - attr(div5, "class", "modal__row svelte-1ibrghz"); - attr(div6, "class", "svelte-1ibrghz"); - attr(div7, "class", "modal__row svelte-1ibrghz"); - attr(div8, "class", "svelte-1ibrghz"); - attr(div9, "class", "modal__row svelte-1ibrghz"); - attr(div10, "class", "svelte-1ibrghz"); - attr(div11, "class", "modal__row svelte-1ibrghz"); - attr(div12, "class", "svelte-1ibrghz"); + attr(div0, "class", "svelte-9f6q4c"); + attr(div1, "class", "modal__title svelte-9f6q4c"); + attr(div2, "class", "svelte-9f6q4c"); + attr(div3, "class", "modal__row svelte-9f6q4c"); + attr(div4, "class", "svelte-9f6q4c"); + attr(div5, "class", "modal__row svelte-9f6q4c"); + attr(div6, "class", "svelte-9f6q4c"); + attr(div7, "class", "modal__row svelte-9f6q4c"); + attr(div8, "class", "svelte-9f6q4c"); + attr(div9, "class", "modal__row svelte-9f6q4c"); + attr(div10, "class", "svelte-9f6q4c"); + attr(div11, "class", "modal__row svelte-9f6q4c"); + attr(div12, "class", "svelte-9f6q4c"); option0.__value = "2"; option0.value = option0.__value; option1.__value = "4"; option1.value = option1.__value; if (/*$settings*/ ctx[3].tabSize === void 0) add_render_callback(() => /*select_change_handler*/ ctx[13].call(select)); - attr(div13, "class", "modal__row svelte-1ibrghz"); + attr(div13, "class", "modal__row svelte-9f6q4c"); attr(div14, "class", "modal__section"); - attr(div16, "class", "modal__title svelte-1ibrghz"); - attr(div17, "class", "svelte-1ibrghz"); + attr(div16, "class", "modal__title svelte-9f6q4c"); + attr(div17, "class", "svelte-9f6q4c"); toggle_class(div17, "red", !/*$settings*/ ctx[3].active); - attr(div18, "class", "modal__row svelte-1ibrghz"); - attr(div19, "class", "svelte-1ibrghz"); - attr(div20, "class", "modal__row svelte-1ibrghz"); - attr(div21, "class", "svelte-1ibrghz"); - attr(div22, "class", "truncate svelte-1ibrghz"); - attr(div23, "class", "modal__row saveLocation svelte-1ibrghz"); - attr(div24, "class", "blacklist svelte-1ibrghz"); + attr(div18, "class", "modal__row svelte-9f6q4c"); + attr(div19, "class", "svelte-9f6q4c"); + attr(div20, "class", "modal__row svelte-9f6q4c"); + attr(div21, "class", "svelte-9f6q4c"); + attr(div22, "class", "truncate svelte-9f6q4c"); + attr(div23, "class", "modal__row saveLocation svelte-9f6q4c"); + attr(div24, "class", "blacklist svelte-9f6q4c"); attr(textarea, "placeholder", "Comma separated domain patterns"); attr(textarea, "spellcheck", "false"); textarea.value = /*blacklisted*/ ctx[2]; textarea.disabled = textarea_disabled_value = /*$state*/ ctx[4].includes("blacklist-saving") || /*blacklistSaving*/ ctx[1]; - attr(textarea, "class", "svelte-1ibrghz"); - attr(div25, "class", "modal__row modal__row--wrap svelte-1ibrghz"); + attr(textarea, "class", "svelte-9f6q4c"); + attr(div25, "class", "modal__row modal__row--wrap svelte-9f6q4c"); attr(div26, "class", "modal__section"); - attr(div27, "class", "modal__title svelte-1ibrghz"); + attr(div27, "class", "modal__title svelte-9f6q4c"); attr(a0, "href", "https://github.com/quoid/userscripts"); attr(a1, "href", "https://apps.apple.com/us/app/userscripts/id1463298887"); attr(a2, "href", "https://github.com/quoid/userscripts#support"); - attr(p, "class", "svelte-1ibrghz"); + attr(p, "class", "svelte-9f6q4c"); attr(div28, "class", "modal__section"); - attr(div29, "class", "modal svelte-1ibrghz"); - attr(div30, "class", "settings svelte-1ibrghz"); + attr(div29, "class", "modal svelte-9f6q4c"); + attr(div30, "class", "settings svelte-9f6q4c"); }, m(target, anchor) { insert(target, div30, anchor); @@ -19643,7 +19878,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o const val = blacklist.value.split(",").map(item => item.trim()).filter(n => n); // compare blacklist input to saved blacklist - if ([...val].sort().toString() != [...$settings.blacklist].sort().toString()) { + if ([...val].sort().toString() !== [...$settings.blacklist].sort().toString()) { settings.updateSingleSetting("blacklist", val); // when blacklistSaving, visual indication of saving occurs on element @@ -19683,10 +19918,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }); } - const click_handler_8 = () => { - state.remove("settings"); - }; - + const click_handler_8 = () => state.remove("settings"); let blacklisted; $$self.$$.update = () => { @@ -19852,7 +20084,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o return child_ctx; } - // (107:0) {#if $state.includes("init")} + // (65:0) {#if $state.includes("init")} function create_if_block_1$2(ctx) { let div; let html_tag; @@ -19913,14 +20145,14 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (112:8) {:else} + // (70:8) {:else} function create_else_block$1(ctx) { let span; return { c() { span = element("span"); - span.textContent = "Initializing page..."; + span.textContent = "Initializing app..."; attr(span, "class", "svelte-17m9goi"); }, m(target, anchor) { @@ -19932,14 +20164,14 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (110:8) {#if $state.includes("init-error")} + // (68:8) {#if $state.includes("init-error")} function create_if_block_2$1(ctx) { let span; return { c() { span = element("span"); - span.textContent = "Failed to initialize app, check the console!"; + span.textContent = "Failed to initialize app, check the browser console"; attr(span, "class", "svelte-17m9goi"); }, m(target, anchor) { @@ -19951,7 +20183,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (122:4) {#each $notifications as item (item.id)} + // (80:4) {#each $notifications as item (item.id)} function create_each_block$1(key_1, ctx) { let first; let notification; @@ -19999,7 +20231,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (126:0) {#if $state.includes("settings")} + // (84:0) {#if $state.includes("settings")} function create_if_block$8(ctx) { let settings_1; let current; @@ -20224,7 +20456,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o component_subscribe($$self, log, $$value => $$invalidate(4, $log = $$value)); component_subscribe($$self, state, $$value => $$invalidate(0, $state = $$value)); component_subscribe($$self, notifications, $$value => $$invalidate(1, $notifications = $$value)); - let logger = []; + const logger = []; // app proportions can get messed up when opening/closing new tabs async function windowResize() { @@ -20236,6 +20468,13 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o document.documentElement.removeAttribute("style"); } + // currently inactive, but could be used to globally prevent auto text replacement in app + // function preventAutoTextReplacements(e) { + // if (e.inputType === "insertReplacementText" && e.data === ". ") { + // e.preventDefault(); + // e.target.value += " "; + // } + // } onMount(async () => { log.add("Requesting initialization data", "info", false); const initData = await browser.runtime.sendNativeMessage({ name: "PAGE_INIT_DATA" }); diff --git a/src/page/Components/Editor/CodeMirror.svelte b/src/page/Components/Editor/CodeMirror.svelte index ed3e39da..ea7c012e 100644 --- a/src/page/Components/Editor/CodeMirror.svelte +++ b/src/page/Components/Editor/CodeMirror.svelte @@ -264,7 +264,6 @@ errors.push(err); } }); - console.log(errors); return errors; } @@ -308,6 +307,7 @@ message: "Userscript metadata missing." }); } + // run additional checks const invalidKeys = new Set(); let invalidKeysErrors = []; @@ -316,26 +316,29 @@ const emptyKeyValues = new Set(); let emptyKeyValuesErrors = []; - // find invalid keys or empty key values - for (let i = 0; i < parsedMetadata.length; i++) { - const {key, value: val, text} = parsedMetadata[i]; - if (!validKeys.has(key)) { - invalidKeys.add(key); - } - if (key !== "noframes" && !invalidKeys.has(key) && !val) { - emptyKeyValues.add(text); + // if metadata fails initial check, it won't be array + // don't run the additional checks if initial checks failed + if (Array.isArray(parsedMetadata)) { + // find invalid keys or empty key values + for (let i = 0; i < parsedMetadata.length; i++) { + const {key, value: val, text} = parsedMetadata[i]; + if (!validKeys.has(key)) { + invalidKeys.add(key); + } + if (key !== "noframes" && !invalidKeys.has(key) && !val) { + emptyKeyValues.add(text); + } } - } - // find unsupported @grant methods - const grants = parsedMetadata.filter(a => a.key === "grant"); - for (let i = 0; i < grants.length; i++) { - const {value, text} = grants[i]; - if (!validGrants.has(value)) { - invalidGrants.add(text); + // find unsupported @grant methods + const grants = parsedMetadata.filter(a => a.key === "grant"); + for (let i = 0; i < grants.length; i++) { + const {value, text} = grants[i]; + if (!validGrants.has(value)) { + invalidGrants.add(text); + } } } - invalidKeysErrors = makeErrors({ set: invalidKeys, setName: "invalidKeys", From e52c48f6d3d768b0b0855c984773cf49d8ff70fd Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 27 Mar 2022 16:12:47 -0400 Subject: [PATCH 19/47] improve how frameUrls are sent on popup init --- .../Resources/popup.html | 2 +- .../Userscripts Extension/Resources/popup.js | 157 ++++++++++-------- src/popup/App.svelte | 22 ++- 3 files changed, 105 insertions(+), 76 deletions(-) diff --git a/extension/Userscripts Extension/Resources/popup.html b/extension/Userscripts Extension/Resources/popup.html index 27c86ca9..5ebce0d5 100644 --- a/extension/Userscripts Extension/Resources/popup.html +++ b/extension/Userscripts Extension/Resources/popup.html @@ -1 +1 @@ -Userscripts Safari
    \ No newline at end of file +Userscripts Safari
    \ No newline at end of file diff --git a/extension/Userscripts Extension/Resources/popup.js b/extension/Userscripts Extension/Resources/popup.js index 94409f66..41e0fb43 100644 --- a/extension/Userscripts Extension/Resources/popup.js +++ b/extension/Userscripts Extension/Resources/popup.js @@ -1115,13 +1115,13 @@ return { c() { div = element("div"); - attr(div, "class", div_class_value = "script__tag " + (/*type*/ ctx[0] ? "script__tag--" + /*type*/ ctx[0] : "") + " svelte-p9vdxd"); + attr(div, "class", div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-p9vdxd"); }, m(target, anchor) { insert(target, div, anchor); }, p(ctx, [dirty]) { - if (dirty & /*type*/ 1 && div_class_value !== (div_class_value = "script__tag " + (/*type*/ ctx[0] ? "script__tag--" + /*type*/ ctx[0] : "") + " svelte-p9vdxd")) { + if (dirty & /*type*/ 1 && div_class_value !== (div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-p9vdxd")) { attr(div, "class", div_class_value); } }, @@ -1159,7 +1159,7 @@ c() { div = element("div"); div.textContent = "SUB"; - attr(div, "class", "subframe svelte-1lgjchf"); + attr(div, "class", "subframe svelte-51jdr7"); }, m(target, anchor) { insert(target, div, anchor); @@ -1197,9 +1197,9 @@ if (if_block) if_block.c(); t3 = space(); create_component(tag.$$.fragment); - attr(span, "class", "svelte-1lgjchf"); - attr(div0, "class", "truncate svelte-1lgjchf"); - attr(div1, "class", div1_class_value = "item " + (/*enabled*/ ctx[0] ? "enabled" : "disabled") + " svelte-1lgjchf"); + attr(span, "class", "svelte-51jdr7"); + attr(div0, "class", "truncate svelte-51jdr7"); + attr(div1, "class", div1_class_value = "item " + (/*enabled*/ ctx[0] ? "enabled" : "disabled") + " svelte-51jdr7"); }, m(target, anchor) { insert(target, div1, anchor); @@ -1236,7 +1236,7 @@ if (dirty & /*type*/ 4) tag_changes.type = /*type*/ ctx[2]; tag.$set(tag_changes); - if (!current || dirty & /*enabled*/ 1 && div1_class_value !== (div1_class_value = "item " + (/*enabled*/ ctx[0] ? "enabled" : "disabled") + " svelte-1lgjchf")) { + if (!current || dirty & /*enabled*/ 1 && div1_class_value !== (div1_class_value = "item " + (/*enabled*/ ctx[0] ? "enabled" : "disabled") + " svelte-51jdr7")) { attr(div1, "class", div1_class_value); } }, @@ -1335,7 +1335,7 @@ }; } - // (33:8) {#if loading && showLoaderOnDisabled} + // (34:8) {#if loading && showLoaderOnDisabled} function create_if_block$2(ctx) { let loader; let current; @@ -1377,7 +1377,7 @@ }; } - // (36:18)
    + // (37:18)
    function fallback_block(ctx) { let div; @@ -1576,7 +1576,7 @@ return child_ctx; } - // (25:0) {:else} + // (27:0) {:else} function create_else_block$1(ctx) { let div2; let html_tag; @@ -1598,7 +1598,7 @@ br = element("br"); t2 = space(); div0 = element("div"); - div0.textContent = "Check Again"; + div0.textContent = "Check Updates"; html_tag = new HtmlTag(t0); attr(div0, "class", "link svelte-1v987ms"); attr(div1, "class", "svelte-1v987ms"); @@ -1633,7 +1633,7 @@ }; } - // (9:0) {#if updates.length} + // (10:0) {#if updates.length} function create_if_block$3(ctx) { let each_blocks = []; let each_1_lookup = new Map(); @@ -1708,7 +1708,7 @@ }; } - // (10:4) {#each updates as item (item.name)} + // (11:4) {#each updates as item (item.name)} function create_each_block(key_1, ctx) { let div1; let div0; @@ -1881,7 +1881,7 @@ return child_ctx; } - // (16:25) + // (17:25) function create_if_block_1(ctx) { let ul; let li; @@ -2072,7 +2072,7 @@ }; } - // (10:4) {#if installError} + // (11:4) {#if installError} function create_if_block$4(ctx) { let div1; let html_tag; @@ -2080,16 +2080,17 @@ let div0; let t2; let p; + let t3; return { c() { div1 = element("div"); t0 = space(); div0 = element("div"); - div0.textContent = "The usercript can not be installed."; + div0.textContent = "Couldn't install userscript"; t2 = space(); p = element("p"); - p.textContent = "Error: OK GO"; + t3 = text(/*installError*/ ctx[1]); html_tag = new HtmlTag(t0); attr(p, "class", "svelte-tdpz3j"); attr(div1, "class", "install__error svelte-tdpz3j"); @@ -2101,15 +2102,18 @@ append(div1, div0); append(div1, t2); append(div1, p); + append(p, t3); + }, + p(ctx, dirty) { + if (dirty & /*installError*/ 2) set_data(t3, /*installError*/ ctx[1]); }, - p: noop, d(detaching) { if (detaching) detach(div1); } }; } - // (19:12) {#if userscript.description} + // (20:12) {#if userscript.description} function create_if_block_6(ctx) { let li; let t_value = /*userscript*/ ctx[0].description + ""; @@ -2134,7 +2138,7 @@ }; } - // (22:12) {#if userscript.match} + // (23:12) {#if userscript.match} function create_if_block_5(ctx) { let li; let div; @@ -2200,7 +2204,7 @@ }; } - // (25:20) {#each userscript.match as match} + // (26:20) {#each userscript.match as match} function create_each_block_3(ctx) { let div; let t_value = /*match*/ ctx[13] + ""; @@ -2225,7 +2229,7 @@ }; } - // (30:12) {#if userscript.include} + // (31:12) {#if userscript.include} function create_if_block_4(ctx) { let li; let div; @@ -2291,7 +2295,7 @@ }; } - // (33:20) {#each userscript.include as include} + // (34:20) {#each userscript.include as include} function create_each_block_2(ctx) { let div; let t_value = /*include*/ ctx[10] + ""; @@ -2316,7 +2320,7 @@ }; } - // (38:12) {#if userscript.require} + // (39:12) {#if userscript.require} function create_if_block_3(ctx) { let li; let div; @@ -2382,7 +2386,7 @@ }; } - // (41:20) {#each userscript.require as require} + // (42:20) {#each userscript.require as require} function create_each_block_1(ctx) { let div; let t_value = /*require*/ ctx[7] + ""; @@ -2407,7 +2411,7 @@ }; } - // (46:12) {#if userscript.grant} + // (47:12) {#if userscript.grant} function create_if_block_2(ctx) { let li; let div; @@ -2473,7 +2477,7 @@ }; } - // (49:20) {#each userscript.grant as grant} + // (50:20) {#each userscript.grant as grant} function create_each_block$1(ctx) { let div; let t_value = /*grant*/ ctx[4] + ""; @@ -2581,7 +2585,7 @@ return child_ctx; } - // (31:0) {:else} + // (32:0) {:else} function create_else_block$2(ctx) { let div; @@ -2589,7 +2593,7 @@ c() { div = element("div"); div.textContent = "No valid files found in directory"; - attr(div, "class", "none"); + attr(div, "class", "none svelte-rd8r5o"); }, m(target, anchor) { insert(target, div, anchor); @@ -2603,7 +2607,7 @@ }; } - // (19:0) {#if allItems.length} + // (20:0) {#if allItems.length} function create_if_block$5(ctx) { let div; let each_blocks = []; @@ -2627,7 +2631,7 @@ each_blocks[i].c(); } - attr(div, "class", div_class_value = "items view--all " + (/*rowColorsAll*/ ctx[2] || "") + " svelte-d4295n"); + attr(div, "class", div_class_value = "items view--all " + (/*rowColorsAll*/ ctx[2] || "") + " svelte-rd8r5o"); toggle_class(div, "disabled", /*disabled*/ ctx[4]); }, m(target, anchor) { @@ -2647,7 +2651,7 @@ check_outros(); } - if (!current || dirty & /*rowColorsAll*/ 4 && div_class_value !== (div_class_value = "items view--all " + (/*rowColorsAll*/ ctx[2] || "") + " svelte-d4295n")) { + if (!current || dirty & /*rowColorsAll*/ 4 && div_class_value !== (div_class_value = "items view--all " + (/*rowColorsAll*/ ctx[2] || "") + " svelte-rd8r5o")) { attr(div, "class", div_class_value); } @@ -2681,7 +2685,7 @@ }; } - // (21:8) {#each list as item (item.filename)} + // (22:8) {#each list as item (item.filename)} function create_each_block$2(key_1, ctx) { let first; let popupitem; @@ -2863,12 +2867,12 @@ return child_ctx; } - // (487:0) {#if !active} + // (497:0) {#if !active} function create_if_block_10(ctx) { return { c: noop, m: noop, d: noop }; } - // (490:0) {#if showInstallPrompt} + // (500:0) {#if showInstallPrompt} function create_if_block_9(ctx) { let div; let t0; @@ -2910,7 +2914,7 @@ }; } - // (495:0) {#if error} + // (505:0) {#if error} function create_if_block_8(ctx) { let div; let t0; @@ -2960,7 +2964,7 @@ }; } - // (517:8) {:else} + // (533:8) {:else} function create_else_block$3(ctx) { let div; let each_blocks = []; @@ -3033,7 +3037,7 @@ }; } - // (515:35) + // (531:35) function create_if_block_7(ctx) { let div; @@ -3055,7 +3059,7 @@ }; } - // (511:28) + // (521:28) function create_if_block_6$1(ctx) { let div; let t0; @@ -3066,7 +3070,7 @@ return { c() { div = element("div"); - t0 = text("Something went wrong: "); + t0 = text("Something went wrong: \n "); span = element("span"); span.textContent = "click to retry"; attr(span, "class", "link svelte-1w80sz6"); @@ -3093,7 +3097,7 @@ }; } - // (509:8) {#if inactive} + // (519:8) {#if inactive} function create_if_block_5$1(ctx) { let div; @@ -3115,7 +3119,7 @@ }; } - // (506:4) {#if loading} + // (516:4) {#if loading} function create_if_block_4$1(ctx) { let loader; let current; @@ -3155,7 +3159,7 @@ }; } - // (519:16) {#each list as item (item.filename)} + // (535:16) {#each list as item (item.filename)} function create_each_block$3(key_1, ctx) { let first; let popupitem; @@ -3214,7 +3218,7 @@ }; } - // (532:0) {#if !inactive && platform === "macos"} + // (548:0) {#if !inactive && platform === "macos"} function create_if_block_3$1(ctx) { let div1; let div0; @@ -3247,7 +3251,7 @@ }; } - // (567:18) + // (585:18) function create_if_block_2$1(ctx) { let view; let current; @@ -3297,7 +3301,7 @@ }; } - // (553:22) + // (571:22) function create_if_block_1$1(ctx) { let view; let current; @@ -3347,7 +3351,7 @@ }; } - // (537:0) {#if showUpdates} + // (555:0) {#if showUpdates} function create_if_block$6(ctx) { let view; let current; @@ -3400,7 +3404,7 @@ }; } - // (568:4) {showAll = false; refreshView()}} showLoaderOnDisabled={false} > + // (586:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > function create_default_slot_2(ctx) { let allitemsview; let current; @@ -3440,7 +3444,7 @@ }; } - // (554:4) showInstall = false} showLoaderOnDisabled={true} > + // (572:4) showInstall = false} showLoaderOnDisabled={true} > function create_default_slot_1(ctx) { let installview; let current; @@ -3484,7 +3488,7 @@ }; } - // (538:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > + // (556:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > function create_default_slot(ctx) { let updateview; let current; @@ -3921,7 +3925,7 @@ try { lastUpdateCheckObj = await browser.storage.local.get(["lastUpdateCheck"]); } catch(error) { - console.error("Error checking extension storage " + error); + console.error(`Error checking extension storage ${error}`); return false; } @@ -3949,7 +3953,7 @@ return false; } - console.log((timestampMs - lastUpdateCheck) / (1000 * 60 * 60) + " hours have passed"); + console.log(`${(timestampMs - lastUpdateCheck) / (1000 * 60 * 60)} hours have passed`); console.log("running update check"); // otherwise run the check @@ -4110,8 +4114,9 @@ } else if (response.items) { $$invalidate(19, showAll = true); $$invalidate(20, allItems = response.items); + $$invalidate(20, allItems = []); } else if (response.error) { - console.log("Error opening save location: " + response.error); + console.log(`Error opening save location: ${response.error}`); $$invalidate(0, error = response.error); } @@ -4126,7 +4131,7 @@ try { pltfm = await browser.runtime.sendNativeMessage({ name: "REQ_PLATFORM" }); } catch(error) { - console.log("Error for pltfm promise: " + error); + console.log(`Error for pltfm promise: ${error}`); $$invalidate(11, initError = true); $$invalidate(2, loading = false); return; @@ -4148,7 +4153,7 @@ try { init = await browser.runtime.sendNativeMessage({ name: "POPUP_INIT" }); } catch(error) { - console.log("Error for init promise: " + error); + console.log(`Error for init promise: ${error}`); $$invalidate(11, initError = true); $$invalidate(2, loading = false); return; @@ -4169,9 +4174,14 @@ // get matches const extensionPageUrl = browser.runtime.getURL("page.html"); - const tabs = await browser.tabs.query({ currentWindow: true, active: true }); - const url = tabs[0].url; - const frameUrls = []; + const currentTab = await browser.tabs.getCurrent(); + const url = currentTab.url; + + if (!url) { + $$invalidate(11, initError = true); + $$invalidate(2, loading = false); + return console.error("Error getting current tab url"); + } if (url === extensionPageUrl) { // disable popup on extension page @@ -4181,18 +4191,29 @@ return; } - if (url) { - const frames = await browser.webNavigation.getAllFrames({ tabId: tabs[0].id }); - frames.forEach(frame => frameUrls.push(frame.url)); + const frameUrls = new Set(); + const frames = await browser.webNavigation.getAllFrames({ tabId: currentTab.id }); + + for (let i = 0; i < frames.length; i++) { + const frameUrl = frames[i].url; + + if (frameUrl !== url && frameUrl.startsWith("http")) { + frameUrls.add(frameUrl); + } } - const message = { name: "POPUP_MATCHES", url, frameUrls }; + const message = { + name: "POPUP_MATCHES", + url, + frameUrls: Array.from(frameUrls) + }; + let matches; try { matches = await browser.runtime.sendNativeMessage(message); } catch(error) { - console.log("Error for matches promise: " + error); // response = await browser.runtime.sendMessage(message); + console.log(`Error for matches promise: ${error}`); // response = await browser.runtime.sendMessage(message); $$invalidate(11, initError = true); $$invalidate(2, loading = false); return; @@ -4221,7 +4242,7 @@ $$invalidate(21, abort = true); updatesResponse = await browser.runtime.sendNativeMessage({ name: "POPUP_UPDATES" }); } catch(error) { - console.error("Error for updates promise: " + error); + console.error(`Error for updates promise: ${error}`); $$invalidate(11, initError = true); $$invalidate(2, loading = false); $$invalidate(21, abort = false); @@ -4259,7 +4280,7 @@ const response = await browser.tabs.sendMessage(tabs[0].id, { name: "USERSCRIPT_INSTALL_00" }); if (response.error) { - console.log("Error checking .user.js url: " + response.error); + console.log(`Error checking .user.js url: ${response.error}`); $$invalidate(0, error = response.error); } else if (!response.invalid) { // the response will contain the string to display @@ -4308,8 +4329,8 @@ if (err) addHeight += err.offsetHeight; windowHeight = window.outerHeight - (headerHeight + addHeight); - $$invalidate(7, main.style.height = windowHeight + "px", main); - $$invalidate(7, main.style.paddingBottom = headerHeight + addHeight + "px", main); + $$invalidate(7, main.style.height = `${windowHeight}px`, main); + $$invalidate(7, main.style.paddingBottom = `${headerHeight + addHeight}px`, main); }, 25 ); @@ -4336,7 +4357,7 @@ // the content script will then send a response here // if the response includes an error, display it in the view if (response.error) { - console.log("Can not install userscript: " + response.error); + console.log(`Can not install userscript: ${response.error}`); $$invalidate(18, installViewUserscriptError = response.error); } else { $$invalidate(17, installViewUserscript = response); diff --git a/src/popup/App.svelte b/src/popup/App.svelte index 08cf8ebf..e09eb469 100644 --- a/src/popup/App.svelte +++ b/src/popup/App.svelte @@ -269,20 +269,28 @@ // get matches const extensionPageUrl = browser.runtime.getURL("page.html"); - const tabs = await browser.tabs.query({currentWindow: true, active: true}); - const url = tabs[0].url; - const frameUrls = []; + const currentTab = await browser.tabs.getCurrent(); + const url = currentTab.url; + if (!url) { + initError = true; + loading = false; + return console.error("Error getting current tab url"); + } if (url === extensionPageUrl) { // disable popup on extension page inactive = true; loading = false; return; } - if (url) { - const frames = await browser.webNavigation.getAllFrames({tabId: tabs[0].id}); - frames.forEach(frame => frameUrls.push(frame.url)); + const frameUrls = new Set(); + const frames = await browser.webNavigation.getAllFrames({tabId: currentTab.id}); + for (let i = 0; i < frames.length; i++) { + const frameUrl = frames[i].url; + if (frameUrl !== url && frameUrl.startsWith("http")) { + frameUrls.add(frameUrl); + } } - const message = {name: "POPUP_MATCHES", url: url, frameUrls: frameUrls}; + const message = {name: "POPUP_MATCHES", url: url, frameUrls: Array.from(frameUrls)}; let matches; try { matches = await browser.runtime.sendNativeMessage(message); From e752ef0b0c7000708a423ff947c004e67278ac80 Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 27 Mar 2022 16:19:33 -0400 Subject: [PATCH 20/47] use browser.tabs.getCurrent --- .../Userscripts Extension/Resources/popup.js | 51 ++++++++++--------- src/popup/App.svelte | 19 +++---- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/extension/Userscripts Extension/Resources/popup.js b/extension/Userscripts Extension/Resources/popup.js index 41e0fb43..4310d0c8 100644 --- a/extension/Userscripts Extension/Resources/popup.js +++ b/extension/Userscripts Extension/Resources/popup.js @@ -2867,12 +2867,12 @@ return child_ctx; } - // (497:0) {#if !active} + // (498:0) {#if !active} function create_if_block_10(ctx) { return { c: noop, m: noop, d: noop }; } - // (500:0) {#if showInstallPrompt} + // (501:0) {#if showInstallPrompt} function create_if_block_9(ctx) { let div; let t0; @@ -2914,7 +2914,7 @@ }; } - // (505:0) {#if error} + // (506:0) {#if error} function create_if_block_8(ctx) { let div; let t0; @@ -2964,7 +2964,7 @@ }; } - // (533:8) {:else} + // (534:8) {:else} function create_else_block$3(ctx) { let div; let each_blocks = []; @@ -3037,7 +3037,7 @@ }; } - // (531:35) + // (532:35) function create_if_block_7(ctx) { let div; @@ -3059,7 +3059,7 @@ }; } - // (521:28) + // (522:28) function create_if_block_6$1(ctx) { let div; let t0; @@ -3097,7 +3097,7 @@ }; } - // (519:8) {#if inactive} + // (520:8) {#if inactive} function create_if_block_5$1(ctx) { let div; @@ -3119,7 +3119,7 @@ }; } - // (516:4) {#if loading} + // (517:4) {#if loading} function create_if_block_4$1(ctx) { let loader; let current; @@ -3159,7 +3159,7 @@ }; } - // (535:16) {#each list as item (item.filename)} + // (536:16) {#each list as item (item.filename)} function create_each_block$3(key_1, ctx) { let first; let popupitem; @@ -3218,7 +3218,7 @@ }; } - // (548:0) {#if !inactive && platform === "macos"} + // (549:0) {#if !inactive && platform === "macos"} function create_if_block_3$1(ctx) { let div1; let div0; @@ -3251,7 +3251,7 @@ }; } - // (585:18) + // (586:18) function create_if_block_2$1(ctx) { let view; let current; @@ -3301,7 +3301,7 @@ }; } - // (571:22) + // (572:22) function create_if_block_1$1(ctx) { let view; let current; @@ -3351,7 +3351,7 @@ }; } - // (555:0) {#if showUpdates} + // (556:0) {#if showUpdates} function create_if_block$6(ctx) { let view; let current; @@ -3404,7 +3404,7 @@ }; } - // (586:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > + // (587:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > function create_default_slot_2(ctx) { let allitemsview; let current; @@ -3444,7 +3444,7 @@ }; } - // (572:4) showInstall = false} showLoaderOnDisabled={true} > + // (573:4) showInstall = false} showLoaderOnDisabled={true} > function create_default_slot_1(ctx) { let installview; let current; @@ -3488,7 +3488,7 @@ }; } - // (556:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > + // (557:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > function create_default_slot(ctx) { let updateview; let current; @@ -4028,12 +4028,12 @@ async function updateItem(item) { $$invalidate(3, disabled = true); - const tabs = await browser.tabs.query({ currentWindow: true, active: true }); - const url = tabs[0].url; + const currentTab = await browser.tabs.getCurrent(); + const url = currentTab.url; const frameUrls = []; if (url) { - const frames = await browser.webNavigation.getAllFrames({ tabId: tabs[0].id }); + const frames = await browser.webNavigation.getAllFrames({ tabId: currentTab.id }); frames.forEach(frame => frameUrls.push(frame.url)); } @@ -4178,9 +4178,10 @@ const url = currentTab.url; if (!url) { + console.error("Error getting current tab url"); $$invalidate(11, initError = true); $$invalidate(2, loading = false); - return console.error("Error getting current tab url"); + return; } if (url === extensionPageUrl) { @@ -4277,7 +4278,7 @@ // then the content script will send response to the popup // Content scripts that are injected into web content cannot send messages to the native app // https://developer.apple.com/documentation/safariservices/safari_web_extensions/messaging_between_the_app_and_javascript_in_a_safari_web_extension - const response = await browser.tabs.sendMessage(tabs[0].id, { name: "USERSCRIPT_INSTALL_00" }); + const response = await browser.tabs.sendMessage(currentTab.id, { name: "USERSCRIPT_INSTALL_00" }); if (response.error) { console.log(`Error checking .user.js url: ${response.error}`); @@ -4344,10 +4345,10 @@ $$invalidate(16, showInstall = true); // get the active tab - const tabs = await browser.tabs.query({ currentWindow: true, active: true }); + const currentTab = await browser.tabs.getCurrent(); // send content script a message on the active tab - const response = await browser.tabs.sendMessage(tabs[0].id, { name: "USERSCRIPT_INSTALL_01" }); + const response = await browser.tabs.sendMessage(currentTab.id, { name: "USERSCRIPT_INSTALL_01" }); // when above message is sent, content script will get active tab's stringified dom content // and then send that content and a message to the bg page @@ -4377,10 +4378,10 @@ $$invalidate(16, showInstall = false); // get the active tab - const tabs = await browser.tabs.query({ currentWindow: true, active: true }); + const currentTab = await browser.tabs.getCurrent(); // send content script a message on the active tab, which will start the install process - const response = await browser.tabs.sendMessage(tabs[0].id, { name: "USERSCRIPT_INSTALL_02" }); + const response = await browser.tabs.sendMessage(currentTab.id, { name: "USERSCRIPT_INSTALL_02" }); if (response.error) { $$invalidate(0, error = response.error); diff --git a/src/popup/App.svelte b/src/popup/App.svelte index e09eb469..ed29cbeb 100644 --- a/src/popup/App.svelte +++ b/src/popup/App.svelte @@ -78,11 +78,11 @@ async function updateItem(item) { disabled = true; - const tabs = await browser.tabs.query({currentWindow: true, active: true}); - const url = tabs[0].url; + const currentTab = await browser.tabs.getCurrent(); + const url = currentTab.url; const frameUrls = []; if (url) { - const frames = await browser.webNavigation.getAllFrames({tabId: tabs[0].id}); + const frames = await browser.webNavigation.getAllFrames({tabId: currentTab.id}); frames.forEach(frame => frameUrls.push(frame.url)); } const message = { @@ -272,9 +272,10 @@ const currentTab = await browser.tabs.getCurrent(); const url = currentTab.url; if (!url) { + console.error("Error getting current tab url"); initError = true; loading = false; - return console.error("Error getting current tab url"); + return; } if (url === extensionPageUrl) { // disable popup on extension page @@ -353,7 +354,7 @@ // then the content script will send response to the popup // Content scripts that are injected into web content cannot send messages to the native app // https://developer.apple.com/documentation/safariservices/safari_web_extensions/messaging_between_the_app_and_javascript_in_a_safari_web_extension - const response = await browser.tabs.sendMessage(tabs[0].id, {name: "USERSCRIPT_INSTALL_00"}); + const response = await browser.tabs.sendMessage(currentTab.id, {name: "USERSCRIPT_INSTALL_00"}); if (response.error) { console.log(`Error checking .user.js url: ${response.error}`); error = response.error; @@ -416,9 +417,9 @@ // show the install view showInstall = true; // get the active tab - const tabs = await browser.tabs.query({currentWindow: true, active: true}); + const currentTab = await browser.tabs.getCurrent(); // send content script a message on the active tab - const response = await browser.tabs.sendMessage(tabs[0].id, {name: "USERSCRIPT_INSTALL_01"}); + const response = await browser.tabs.sendMessage(currentTab.id, {name: "USERSCRIPT_INSTALL_01"}); // when above message is sent, content script will get active tab's stringified dom content // and then send that content and a message to the bg page // the bg page will send a message and the content to the swift side for parsing @@ -444,9 +445,9 @@ // go back to main view showInstall = false; // get the active tab - const tabs = await browser.tabs.query({currentWindow: true, active: true}); + const currentTab = await browser.tabs.getCurrent(); // send content script a message on the active tab, which will start the install process - const response = await browser.tabs.sendMessage(tabs[0].id, {name: "USERSCRIPT_INSTALL_02"}); + const response = await browser.tabs.sendMessage(currentTab.id, {name: "USERSCRIPT_INSTALL_02"}); if (response.error) { error = response.error; disabled = false; From 329bbe50aeca56d3442e14d2c201612240c79046 Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 27 Mar 2022 16:20:16 -0400 Subject: [PATCH 21/47] update eslint rules --- .eslintrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.json b/.eslintrc.json index 13aa5583..9719c487 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -34,6 +34,7 @@ "eqeqeq": ["error", "smart"], "indent": ["error", 4, {"SwitchCase": 1}], "linebreak-style": ["error","unix"], + "key-spacing": ["error", {"afterColon": true}], "keyword-spacing": ["error", { "after": true, "before": true From 82da92fc6a9b97f4017c04fd3260682924d636ee Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 27 Mar 2022 16:20:24 -0400 Subject: [PATCH 22/47] code formatting --- src/page/Components/Editor/CodeMirror.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/page/Components/Editor/CodeMirror.svelte b/src/page/Components/Editor/CodeMirror.svelte index ea7c012e..501bb83e 100644 --- a/src/page/Components/Editor/CodeMirror.svelte +++ b/src/page/Components/Editor/CodeMirror.svelte @@ -105,7 +105,7 @@ // create codemirror instance instance = CodeMirror.fromTextArea(textarea, { - mode: "javascript", + mode: "javascript", autoCloseBrackets: $settings.autoCloseBrackets, continueComments: true, foldGutter: true, @@ -114,7 +114,7 @@ matchBrackets: true, smartIndent: true, styleActiveLine: true, - indentUnit: parseInt($settings.tabSize), + indentUnit: parseInt($settings.tabSize), showInvisibles: $settings.showInvisibles, tabSize: parseInt($settings.tabSize), highlightSelectionMatches: false, From f3f85d15e7597af280b8d851af17e6c58886be8e Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 27 Mar 2022 16:21:59 -0400 Subject: [PATCH 23/47] clarify error message --- extension/Userscripts Extension/SafariWebExtensionHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/Userscripts Extension/SafariWebExtensionHandler.swift b/extension/Userscripts Extension/SafariWebExtensionHandler.swift index 5b3ffc10..a0151fb5 100644 --- a/extension/Userscripts Extension/SafariWebExtensionHandler.swift +++ b/extension/Userscripts Extension/SafariWebExtensionHandler.swift @@ -28,7 +28,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { { response.userInfo = [SFExtensionMessageKey: ["code": code]] } else { - response.userInfo = [SFExtensionMessageKey: ["error": "couldn't get userscripts"]] + response.userInfo = [SFExtensionMessageKey: ["error": "REQ_USERSCRIPTS failed"]] } } else { inBoundError = true From 0735262973728886b525d65fd54ed5ec3048c95f Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 27 Mar 2022 16:23:20 -0400 Subject: [PATCH 24/47] fix bug where subframe script not showing in popup --- extension/Userscripts Extension/Functions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/Userscripts Extension/Functions.swift b/extension/Userscripts Extension/Functions.swift index 7ad2f9ed..747d749b 100644 --- a/extension/Userscripts Extension/Functions.swift +++ b/extension/Userscripts Extension/Functions.swift @@ -1386,7 +1386,7 @@ func getPopupMatches(_ url: String, _ subframeUrls: [String]) -> [[String: Any]] // can force unwrap filename b/c getAllFiles always returns it let frameMatchMetadata = files.filter{$0["filename"] as! String == frameMatch}.first // can force unwrap noframes b/c getAllFiles always returns it - let noFrames = frameMatchMetadata!["noframes"] != nil ? true : false + let noFrames = frameMatchMetadata?["noframes"] as? Bool ?? false if !matched.contains(frameMatch) && !noFrames { frameUrlsMatches.append(frameMatch) } From a786deefc225a5983e99c2c3fbd269cca32db6c5 Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 27 Mar 2022 16:29:41 -0400 Subject: [PATCH 25/47] remove old code, formatting --- .../Userscripts Extension/Functions.swift | 44 ------------------- .../SafariWebExtensionHandler.swift | 28 +----------- 2 files changed, 1 insertion(+), 71 deletions(-) diff --git a/extension/Userscripts Extension/Functions.swift b/extension/Userscripts Extension/Functions.swift index 747d749b..2560554d 100644 --- a/extension/Userscripts Extension/Functions.swift +++ b/extension/Userscripts Extension/Functions.swift @@ -1836,47 +1836,3 @@ func installUserscript(_ content: String) -> [String: Any]? { let saved = saveFile(["filename": filename, "type": "js"], content) return saved } -//func popupMatches(_ url: String, _ subframeUrls: [String]) -> [[String: Any]]? { -// var matches = [[String: Any]]() -// // if the url doesn't start with http/s return empty array -// if !url.starts(with: "http://") && !url.starts(with: "https://") { -// return matches -// } -// // get all the files saved to manifest that match the passed url -// let matched = getMatchedFiles(url) -// // get all the files at the save location -// guard let files = getAllFiles() else { -// err("popupMatches failed at (1)") -// return nil -// } -// // filter out the files that are present in both files and matched -// // force unwrap filename to string since getAllFiles always returns it -// matches = files.filter{matched.contains($0["filename"] as! String)} -// -// // get the subframe url matches -// var frameUrlsMatched = [[String: Any]]() -// var frameUrlsMatches = [String]() -// // filter out the top page url from the frame urls -// let frameUrls = subframeUrls.filter{$0 != url} -// // for each url just pushed to frameUrls, get all the files saved to manifest that match their url -// for frameUrl in frameUrls { -// let frameMatches = getMatchedFiles(frameUrl) -// for frameMatch in frameMatches { -// if !matched.contains(frameMatch) { -// frameUrlsMatches.append(frameMatch) -// } -// } -// } -// // filter out the files that are present in both files and frameUrlsMatches -// // force unwrap filename to string since getAllFiles always returns it -// frameUrlsMatched = files.filter{frameUrlsMatches.contains($0["filename"] as! String)} -// // loop through frameUrlsMatched and add subframe key/val -// for (index, var frameUrlsMatch) in frameUrlsMatched.enumerated() { -// frameUrlsMatch["subframe"] = true -// frameUrlsMatched[index] = frameUrlsMatch -// } -// // add frameUrlsMatched to matches array -// matches.append(contentsOf: frameUrlsMatched) -// -// return matches -//} diff --git a/extension/Userscripts Extension/SafariWebExtensionHandler.swift b/extension/Userscripts Extension/SafariWebExtensionHandler.swift index a0151fb5..56a7dd1c 100644 --- a/extension/Userscripts Extension/SafariWebExtensionHandler.swift +++ b/extension/Userscripts Extension/SafariWebExtensionHandler.swift @@ -15,7 +15,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { // send standard error when there's an issue parsing inbound message var inBoundError = false // these if/else if statement are formatted so that they can be neatly collapsed in Xcode - // typically the "else if" would be on the same line as the preceding statements close backet + // typically the "else if" would be on the same line as the preceding statements close bracket // ie. } else if { if name == "REQ_PLATFORM" { let platform = getPlatform() @@ -72,32 +72,6 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { response.userInfo = [SFExtensionMessageKey: ["error": "failed to get updates"]] } } - else if name == "POPUP_INITX" { - if let url = message?["url"] as? String, let frameUrls = message?["frameUrls"] as? [String] { - let platform = getPlatform() - let manifest = getManifest() - if - checkDefaultDirectories(), - checkSettings(), - let files = getAllFiles(), - updateManifestMatches(files), - updateManifestRequired(files), - purgeManifest(files), - let matches = getPopupMatches(url, frameUrls), - let active = manifest.settings["active"], - let updates = checkForRemoteUpdates() - { - let r = [ - "active": active, "items": matches, "platform": platform, "updates": updates - ] as [String : Any] - response.userInfo = [SFExtensionMessageKey: r] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to run init sequence"]] - } - } else { - inBoundError = true - } - } else if name == "POPUP_UPDATE_ALL" { if popupUpdateAll(), let updates = checkForRemoteUpdates() { response.userInfo = [SFExtensionMessageKey: ["updates": updates]] From a7cb56267ce9e52fb302cbd1913ae25fd225463d Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 27 Mar 2022 17:24:10 -0400 Subject: [PATCH 26/47] injection refactoring & optimization #184 --- .../Userscripts Extension/Functions.swift | 115 +-- .../Resources/background.js | 775 +++++++++++------ .../Resources/content.js | 788 +++++------------- .../SafariWebExtensionHandler.swift | 2 +- 4 files changed, 773 insertions(+), 907 deletions(-) diff --git a/extension/Userscripts Extension/Functions.swift b/extension/Userscripts Extension/Functions.swift index 2560554d..b7b612e1 100644 --- a/extension/Userscripts Extension/Functions.swift +++ b/extension/Userscripts Extension/Functions.swift @@ -1107,35 +1107,15 @@ func getMatchedFiles(_ url: String, _ optionalManifest: Manifest?, _ checkBlockl } // injection -// func getCode(_ filenames: [String], _ isTop: Bool)-> [String: [String: [String: Any]]]? { func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? { - //var allFiles = [String: [String: [String: Any]]]() - var allFiles = [String: Any]() - var cssFiles = [String:[String:String]]() - var jsFiles = [String: [String: [String: [String: Any]]]]() - jsFiles["auto"] = ["document-start": [:], "document-end": [:], "document-idle": [:]] - jsFiles["content"] = ["document-start": [:], "document-end": [:], "document-idle": [:]] - jsFiles["page"] = ["document-start": [:], "document-end": [:], "document-idle": [:]] - jsFiles["context-menu"] = ["auto": [:], "content": [:], "page": [:]] - var auto_docStart = [String: [String: Any]]() - var auto_docEnd = [String: [String: Any]]() - var auto_docIdle = [String: [String: Any]]() - var content_docStart = [String: [String: Any]]() - var content_docEnd = [String: [String: Any]]() - var content_docIdle = [String: [String: Any]]() - var page_docStart = [String: [String: Any]]() - var page_docEnd = [String: [String: Any]]() - var page_docIdle = [String: [String: Any]]() - - var auto_context_scripts = [String: [String: Any]]() - var content_context_scripts = [String: [String: Any]]() - var page_context_scripts = [String: [String: Any]]() - + var cssFiles = [Any]() + var jsFiles = [Any]() + guard let saveLocation = getSaveLocation() else { err("getCode failed at (1)") return nil } - + for filename in filenames { guard let contents = getFileContentsParsed(saveLocation.appendingPathComponent(filename)), @@ -1193,7 +1173,8 @@ func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? { "description": description, "excludes": excludes, "exclude-match": excludeMatches, - "grant": grants, + "filename": filename, + "grants": grants, "includes": includes, "inject-into": injectInto, "matches": matches, @@ -1244,73 +1225,29 @@ func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? { } if type == "css" { - cssFiles[filename] = ["code": code, "weight": weight, "name": name] + cssFiles.append([ + "code": code, + "filename": filename, + "name": name, + "type": "css", + "weight": weight + ]) } else if type == "js" { - let data = [ + jsFiles.append([ "code": code, - "weight": weight, "scriptMetaStr": scriptMetaStr, - "scriptObject": scriptObject - ] as [String : Any] - // add file data to appropriate dict - if injectInto == "auto" && runAt == "document-start" { - auto_docStart[filename] = data - } else if injectInto == "auto" && runAt == "document-end" { - auto_docEnd[filename] = data - } else if injectInto == "auto" && runAt == "document-idle" { - auto_docIdle[filename] = data - } else if injectInto == "content" && runAt == "document-start" { - content_docStart[filename] = data - } else if injectInto == "content" && runAt == "document-end" { - content_docEnd[filename] = data - } else if injectInto == "content" && runAt == "document-idle" { - content_docIdle[filename] = data - } else if injectInto == "page" && runAt == "document-start" { - page_docStart[filename] = data - } else if injectInto == "page" && runAt == "document-end" { - page_docEnd[filename] = data - } else if injectInto == "page" && runAt == "document-idle" { - page_docIdle[filename] = data - } - if runAt == "context-menu" && injectInto == "auto" { - auto_context_scripts[filename] = data - } - if runAt == "context-menu" && injectInto == "content" { - content_context_scripts[filename] = data - } - if runAt == "context-menu" && injectInto == "page" { - page_context_scripts[filename] = data - } - } - } - - // construct the js specific dictionaries - jsFiles["auto"]!["document-start"] = auto_docStart - jsFiles["auto"]!["document-end"] = auto_docEnd - jsFiles["auto"]!["document-idle"] = auto_docIdle - jsFiles["content"]!["document-start"] = content_docStart - jsFiles["content"]!["document-end"] = content_docEnd - jsFiles["content"]!["document-idle"] = content_docIdle - jsFiles["page"]!["document-start"] = page_docStart - jsFiles["page"]!["document-end"] = page_docEnd - jsFiles["page"]!["document-idle"] = page_docIdle - // the context-menu dictionaries are constructed differently - // they will need to be handled in a unique way on the JS side - jsFiles["context-menu"]!["auto"] = auto_context_scripts - jsFiles["context-menu"]!["content"] = content_context_scripts - jsFiles["context-menu"]!["page"] = page_context_scripts - - // construct the returned dictionary - allFiles["css"] = cssFiles - allFiles["js"] = jsFiles - - // add global values - let scriptHandler = "Userscripts" - let scriptHandlerVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??" - allFiles["scriptHandler"] = scriptHandler - allFiles["scriptHandlerVersion"] = scriptHandlerVersion - - return allFiles + "scriptObject": scriptObject, + "type": "js", + "weight": weight + ]) + } + } + let resp = [ + "files": ["css": cssFiles, "js": jsFiles], + "scriptHandler": "Userscripts", + "scriptHandlerVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??" + ] as [String : Any] + return resp } func getFileContentsParsed(_ url: URL) -> [String: Any]? { diff --git a/extension/Userscripts Extension/Resources/background.js b/extension/Userscripts Extension/Resources/background.js index a5234424..19d13c08 100644 --- a/extension/Userscripts Extension/Resources/background.js +++ b/extension/Userscripts Extension/Resources/background.js @@ -1,200 +1,270 @@ -// store all active context menu items here -// this var will be referenced to determine the removal of the context menu click event handler -// it'll also be used to know if a specific url already has active context menu items -let contextMenuItems = []; -let platformGlobal; +// globally scoped vars in a nonpersistent background page is not advised +// however references to active XMLHttpRequests needed to abort requests +// when an xhr is triggered, the bg page will load +// it's probable that while the xhr is active, the bg page will stay loaded +// xhrs are only kept in this array when active, otherwise array is empty +// that means it's ok that this var gets reset when the bg page unloads let xhrs = []; -browser.runtime.onMessage.addListener((request, sender, sendResponse) => { - // content script listening seems to be the most reliable way to trigger injection - const name = request.name; - if (name === "REQ_USERSCRIPTS") { - const url = sender.url; - // use frameId to determine if request came from top level window - const isTop = sender.frameId === 0 ? true : false; - // ask swift layer to provide code for current url(s) - const message = {name: name, url: url, isTop: isTop}; - browser.runtime.sendNativeMessage(message, response => { - // send code back to content script for parsing and injection - // could use tabs.executeScript(sender.tab.id) for content context injection - // but for now, will rely on eval() in content script - const code = response.code; - sendResponse({code: code}); - // update badge count on injection - // especially useful when injection is deferred (ie. subframes) - setBadgeCount(); - }); - // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage#sending_an_asynchronous_response_using_sendresponse - return true; - } else if (name === "CONTEXT_CREATE") { - const menuItemId = request.menuItemId; - const menuObj = { - contexts: ["all"], - documentUrlPatterns: [request.url], - id: menuItemId, - title: request.title, - }; - const onCreate = () => { - if (browser.runtime.lastError) { - console.error(browser.runtime.lastError); - } else { - sendResponse({menuItemId: menuItemId}); - contextMenuItems.push(menuItemId); - // only add listener if not already attached - if (!browser.menus.onClicked.hasListener(contextClick)) { - browser.menus.onClicked.addListener(contextClick); - } - } - }; - // first check if the context menu item is already present for a tab on the same url - // if so, remove the current context menu item & entry in context menu items array - // although already created context menu items automatically apply - // to subsequent tab urls that match the documentUrlPatterns supplied at creation - // the user could have edited the userscript since the first application - if (contextMenuItems.includes(menuItemId)) { - browser.contextMenus.remove(menuItemId, () => { - contextMenuItems = contextMenuItems.filter(a => a !== menuItemId); - browser.contextMenus.create(menuObj, onCreate); - }); - } else { - browser.contextMenus.create(menuObj, onCreate); - } - return true; - } else if (name === "CONTEXT_REMOVE") { - // tab closes events dispatch remove request with a menuItemId - // remove the context menu item associated with the menuItemId when that event comes in - // if tabs with the same url exist, the context menu item will be recreated on right click - const menuItemId = request.menuItemId; - browser.contextMenus.remove(menuItemId, () => { - contextMenuItems = contextMenuItems.filter(a => a !== menuItemId); - purgeContextMenus(); +/* global filename, uid */ +// filename and uid will be available to functions at runtime +const apis = { + US_openInTab(url, openInBackground) { + const pid = Math.random().toString(36).substring(1, 9); + return new Promise(resolve => { + const callback = e => { + if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_OPEN_TAB") return; + resolve(e.data.response); + window.removeEventListener("message", callback); + }; + window.addEventListener("message", callback); + const active = (openInBackground === true) ? false : true; + window.postMessage({id: uid, pid: pid, name: "API_OPEN_TAB", url: url, active: active}); }); - } else if (name === "API_OPEN_TAB") { - const active = (request.active === true) ? true : false; - browser.tabs.create({active: active, index: sender.tab.index + 1, url: request.url}, response => { - sendResponse(response); + }, + US_closeTab(tabId) { + const pid = Math.random().toString(36).substring(1, 9); + return new Promise(resolve => { + const callback = e => { + if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_CLOSE_TAB") return; + resolve(e.data.response); + window.removeEventListener("message", callback); + }; + window.addEventListener("message", callback); + window.postMessage({id: uid, pid: pid, name: "API_CLOSE_TAB", tabId: tabId}); }); - return true; - } else if (name === "API_CLOSE_TAB") { - const tabId = request.tabId !== undefined ? request.tabId : sender.tab.id; - browser.tabs.remove(tabId, () => { - sendResponse({success: true}); + }, + US_setValue(key, value) { + const pid = Math.random().toString(36).substring(1, 9); + return new Promise(resolve => { + const callback = e => { + if ( + e.data.pid !== pid + || e.data.id !== uid + || e.data.name !== "RESP_SET_VALUE" + || e.data.filename !== filename + ) return; + resolve(e.data.response); + window.removeEventListener("message", callback); + }; + window.addEventListener("message", callback); + window.postMessage({ + id: uid, + pid: pid, + name: "API_SET_VALUE", + filename: filename, + key: key, + value: value + }); }); - return true; - } else if (name === "API_SET_VALUE") { - const item = {}; - item[`${request.filename}---${request.key}`] = request.value; - browser.storage.local.set(item, () => { - sendResponse({success: true}); + }, + US_getValue(key, defaultValue) { + const pid = Math.random().toString(36).substring(1, 9); + return new Promise(resolve => { + const callback = e => { + if ( + e.data.pid !== pid + || e.data.id !== uid + || e.data.name !== "RESP_GET_VALUE" + || e.data.filename !== filename + ) return; + const response = e.data.response; + resolve(response); + window.removeEventListener("message", callback); + }; + window.addEventListener("message", callback); + window.postMessage({ + id: uid, pid: pid, + name: "API_GET_VALUE", + filename: filename, + key: key, + defaultValue: defaultValue + }); }); - return true; - } else if (name === "API_GET_VALUE") { - const key = `${request.filename}---${request.key}`; - browser.storage.local.get(key, item => { - if (Object.keys(item).length === 0) { - if (request.defaultValue) { - sendResponse(request.defaultValue); - } else { - sendResponse(`undefined--${request.pid}`); - } - } else { - sendResponse(Object.values(item)[0]); - } + }, + US_listValues() { + const pid = Math.random().toString(36).substring(1, 9); + return new Promise(resolve => { + const callback = e => { + if ( + e.data.pid !== pid + || e.data.id !== uid + || e.data.name !== "RESP_LIST_VALUES" + || e.data.filename !== filename + ) return; + resolve(e.data.response); + window.removeEventListener("message", callback); + }; + window.addEventListener("message", callback); + window.postMessage({id: uid, pid: pid, name: "API_LIST_VALUES", filename: filename}); }); - return true; - } else if (name === "API_DELETE_VALUE") { - const key = `${request.filename}---${request.key}`; - browser.storage.local.remove(key, response => { - sendResponse({success: true}); + }, + US_deleteValue(key) { + const pid = Math.random().toString(36).substring(1, 9); + return new Promise(resolve => { + const callback = e => { + if ( + e.data.pid !== pid + || e.data.id !== uid + || e.data.name !== "RESP_DELETE_VALUE" + || e.data.filename !== filename + ) return; + resolve(e.data.response); + window.removeEventListener("message", callback); + }; + window.addEventListener("message", callback); + window.postMessage({id: uid, pid: pid, name: "API_DELETE_VALUE", filename: filename, key: key}); }); - return true; - } else if (name === "API_LIST_VALUES") { - const prefix = `${request.filename}---`; - const keys = []; - browser.storage.local.get().then(items => { - for (const key in items) { - if (key.startsWith(prefix)) { - const k = key.replace(prefix, ""); - keys.push(k); - } - } - sendResponse(keys); + }, + US_addStyleSync(css) { + window.postMessage({id: uid, name: "API_ADD_STYLE_SYNC", css: css}); + return css; + }, + US_addStyle(css) { + const pid = Math.random().toString(36).substring(1, 9); + return new Promise(resolve => { + const callback = e => { + if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_ADD_STYLE") return; + resolve(e.data.response); + window.removeEventListener("message", callback); + }; + window.addEventListener("message", callback); + window.postMessage({id: uid, pid: pid, name: "API_ADD_STYLE", css: css}); }); - return true; - } else if (name === "API_ADD_STYLE" || name === "API_ADD_STYLE_SYNC") { - const tabId = sender.tab.id; - browser.tabs.insertCSS(tabId, {code: request.css}, () => { - if (name === "API_ADD_STYLE") sendResponse(request.css); + }, + US_setClipboard(data, type) { + const pid = Math.random().toString(36).substring(1, 9); + return new Promise(resolve => { + const callback = e => { + if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_SET_CLIPBOARD") return; + resolve(e.data.response); + if (!e.data.response) console.error("clipboard write failed"); + window.removeEventListener("message", callback); + }; + window.addEventListener("message", callback); + window.postMessage({id: uid, pid: pid, name: "API_SET_CLIPBOARD", data: data, type: type}); }); - return true; - } else if (name === "REQ_PLATFORM") { - (async () => { - try { - const p = await getPlatform(); - sendResponse({platform: p}); - } catch (error) { - console.error(error); - sendResponse({error: "Couldn't get platform"}); - } - })(); - return true; - } else if (name === "API_SET_CLIPBOARD") { - const result = setClipboard(request.data, request.type); - sendResponse(result); - } else if (name === "API_XHR_CS") { - // https://jsonplaceholder.typicode.com/posts - // get tab id and respond only to the content script that sent message - const tab = sender.tab.id; - const details = request.details; - const method = details.method ? details.method : "GET"; - const user = details.user || null; - const password = details.password || null; - let body = details.data || null; - if (body && details.binary) { - const len = body.length; - const arr = new Uint8Array(len); - for (let i = 0; i < len; i++) { - arr[i] = body.charCodeAt(i); - } - body = new Blob([arr], {type: "text/plain"}); - } - const xhr = new XMLHttpRequest(); - // push to global scoped array so it can be aborted - xhrs.push({xhr: xhr, xhrId: request.xhrId}); - xhr.withCredentials = (details.user && details.password); - xhr.timeout = details.timeout || 0; - if (details.overrideMimeType) xhr.overrideMimeType(details.overrideMimeType); - xhrAddListeners(xhr, tab, request.id, request.xhrId, details); - xhr.open(method, details.url, true, user, password); - xhr.responseType = details.responseType || ""; - if (details.headers) { - for (const key in details.headers) { - const val = details.headers[key]; - xhr.setRequestHeader(key, val); + }, + US_setClipboardSync(data, type) { + // there's actually no sync method since a promise needs to be sent to bg page + // however make a dummy sync method for compatibility + window.postMessage({id: uid, name: "API_SET_CLIPBOARD", data: data, type: type}); + return undefined; + }, + // when xhr is called it sends a message to the content script + // and adds it's own event listener to get responses from content script + // each xhr has a unique id so it won't respond to different xhr + // the content script sends the xhr details to the background script + // the background script sends messages back to the content script for all xhr events + // the content script relays these messages back to the context where xhr was called + // if xhr was called with event handler functions they will be executed when those relays come in + xhr(details) { + // if details didn't include url, do nothing + if (!details.url) return; + // create unique id for the xhr + const xhrId = Math.random().toString(36).substring(1, 9); + // strip out functions from details, kind of hacky + const detailsParsed = JSON.parse(JSON.stringify(details)); + // check which functions are included in the original details object + // add a bool to indicate if event listeners should be attached + if (details.onabort) detailsParsed.onabort = true; + if (details.onerror) detailsParsed.onerror = true; + if (details.onload) detailsParsed.onload = true; + if (details.onloadend) detailsParsed.onloadend = true; + if (details.onloadstart) detailsParsed.onloadstart = true; + if (details.onprogress) detailsParsed.onprogress = true; + if (details.onreadystatechange) detailsParsed.onreadystatechange = true; + if (details.ontimeout) detailsParsed.ontimeout = true; + // abort function gets returned when this function is called + const abort = () => { + window.postMessage({id: uid, name: "API_XHR_ABORT_INJ", xhrId: xhrId}); + }; + const callback = e => { + const name = e.data.name; + const response = e.data.response; + // ensure callback is responding to the proper message + if ( + e.data.id !== uid + || e.data.xhrId !== xhrId + || !name + || !name.startsWith("RESP_API_XHR_CS") + ) return; + if (name.includes("LOADEND") && details.onloadend) { + details.onloadend(response); + // remove event listener when xhr is complete + window.removeEventListener("message", callback); + } else if (name === "RESP_API_XHR_CS_LOAD" && details.onload) { + details.onload(response); + } else if (name.includes("PROGRESS") && details.onprogress) { + details.onprogress(response); + } else if (name.includes("READYSTATECHANGE") && details.onreadystatechange) { + details.onreadystatechange(response); + } else if (name.includes("LOADSTART") && details.onloadstart) { + details.onloadtstart(response); + } else if (name.includes("ABORT") && details.onabort) { + details.onabort(response); + } else if (name.includes("ERROR") && details.onerror) { + details.onerror(response); + } else if (name.includes("TIMEOUT") && details.ontimeout) { + details.ontimeout(response); + } else if (name === "RESP_API_XHR_CS") { + // ignore } + }; + window.addEventListener("message", callback); + window.postMessage({id: uid, name: "API_XHR_INJ", details: detailsParsed, xhrId: xhrId}); + return {abort: abort}; + } +}; + +function xhrMake(request, sender) { + // XMLHttpRequests are a bit complex and involve multiple messages + // when a userscript with proper @grant executes an XMLHttpRequest (xhr) + // from the context of the userscript (page, content script), the xhr function runs + // a custom message event listener is added to the userscript context and then... + // a message from the userscript is sent to the content script with the details of the xhr + // this is because userscript contexts can't directly message bg page + // further, can't run xhrs from content script due to CORS issues + // the bg page receives this message, initiates the xhr and attaches the needed event listeners to the xhr + // when those event listeners fire, messages are sent back to the content script + // these messages include the details of the xhr + // when the content script receives these messages, it passes them back to the userscript context + // all xhrs send a message onloadend which removes the custom event listener in the userscript context + + // get tab id and respond only to the content script that sent message + const tab = sender.tab.id; + const details = request.details; + const method = details.method ? details.method : "GET"; + const user = details.user || null; + const password = details.password || null; + let body = details.data || null; + if (body && details.binary) { + const len = body.length; + const arr = new Uint8Array(len); + for (let i = 0; i < len; i++) { + arr[i] = body.charCodeAt(i); } - xhr.send(body); - // remove xhr from global scope when completed - xhr.onloadend = progressEvent => xhrs = xhrs.filter(x => x.xhrId !== request.xhrId); - // sendResponse({details: details}); - } else if (name === "API_XHR_ABORT_CS") { - // get the xhrId from request - const xhrId = request.xhrId; - const match = xhrs.find(x => x.xhrId === xhrId); - if (match) { - match.xhr.abort(); - // sendResponse(match); - } else { - console.log(`abort message recieved for ${xhrId}, but it couldn't be found`); + body = new Blob([arr], {type: "text/plain"}); + } + const xhr = new XMLHttpRequest(); + // push to global scoped array so it can be aborted + xhrs.push({xhr: xhr, xhrId: request.xhrId}); + xhr.withCredentials = (details.user && details.password); + xhr.timeout = details.timeout || 0; + if (details.overrideMimeType) xhr.overrideMimeType(details.overrideMimeType); + xhrAddListeners(xhr, tab, request.id, request.xhrId, details); + xhr.open(method, details.url, true, user, password); + xhr.responseType = details.responseType || ""; + if (details.headers) { + for (const key in details.headers) { + const val = details.headers[key]; + xhr.setRequestHeader(key, val); } - } else if (["USERSCRIPT_INSTALL_00", "USERSCRIPT_INSTALL_01", "USERSCRIPT_INSTALL_02"].includes(name)) { - const message = {name: name, content: request.content}; - browser.runtime.sendNativeMessage(message, response => { - sendResponse(response); - }); - return true; } -}); + xhr.send(body); + // remove xhr from global scope when completed + xhr.onloadend = () => xhrs = xhrs.filter(x => x.xhrId !== request.xhrId); +} function xhrHandleEvent(e, xhr, tab, id, xhrId) { const name = `RESP_API_XHR_BG_${e.type.toUpperCase()}`; @@ -210,7 +280,9 @@ function xhrHandleEvent(e, xhr, tab, id, xhrId) { withCredentials: xhr.withCredentials }; // only include responseText when applicable - if (["", "text"].includes(xhr.responseType)) x.responseText = xhr.responseText; + if (xhr.responseType === "" || xhr.responseType === "text") { + x.responseText = xhr.responseText; + } // convert data if response is arraybuffer so sendMessage can pass it if (xhr.responseType === "arraybuffer") { const arr = Array.from(new Uint8Array(xhr.response)); @@ -262,81 +334,90 @@ function xhrAddListeners(xhr, tab, id, xhrId, details) { } } -function contextClick(info, tab) { - browser.tabs.query({currentWindow: true, active: true}, tabs => { - browser.tabs.sendMessage(tabs[0].id, {name: "CONTEXT_RUN", menuItemId: info.menuItemId}); - }); -} - -function purgeContextMenus() { - // loop through all tabs and remove context menu items targeting tab urls that don't exist - browser.tabs.query({}, tabs => { - const tabUrls = []; - tabs.forEach(tab => { - if (tab.url) tabUrls.push(tab.url); - }); - const contextMenuItemsUrls = []; - // parse urls from context menu item ids - contextMenuItems.forEach(item => contextMenuItemsUrls.push(item.split("&$&")[0])); - // get content menu items targeting urls not in tabUrls - const staleUrls = contextMenuItemsUrls.filter(a => !tabUrls.includes(a)); - // remove stale elements from context menu item array & remove menu item - staleUrls.forEach(staleUrl => { - contextMenuItems.forEach(contextMenuItem => { - if (contextMenuItem.includes(staleUrl)) { - contextMenuItems.splice(contextMenuItems.indexOf(contextMenuItem), 1); - browser.contextMenus.remove(contextMenuItem); - } - }); - }); - // if there are no more context menu items in array remove the event listener - browser.menus.onClicked.removeListener(contextClick); - // remove any lingering context menu items - browser.contextMenus.removeAll(); - }); -} - -async function setBadgeCount() { - // only set badge on macOS - const platform = await getPlatform(); - if (platform !== "macos") return; - - const tabs = await new Promise(resolve => { - browser.tabs.query({currentWindow: true, active: true}, tabs => { - resolve(tabs); - }); - }); - if (!tabs) return; - const url = tabs[0].url; - const message = {name: "POPUP_BADGE_COUNT", url: url, frameUrls: []}; - if (url) { - const frames = await new Promise(resolve => { - browser.webNavigation.getAllFrames({tabId: tabs[0].id}, frames => { - resolve(frames); - }); - }); - frames.forEach(frame => message.frameUrls.push(frame.url)); - } - browser.runtime.sendNativeMessage(message, response => { - if (response.error) return console.error(response.error); - const count = response.count; - if (count > 0) { - browser.browserAction.setBadgeText({text: count.toString()}); - } else { - browser.browserAction.setBadgeText({text: ""}); +function addApis({userscripts, uid, scriptHandler, scriptHandlerVersion}) { + for (let i = 0; i < userscripts.length; i++) { + const gmMethods = []; + const includedMethods = []; + const userscript = userscripts[i]; + const filename = userscript.scriptObject.filename; + const grants = userscript.scriptObject.grants; + // prepare the api string + let api = `const uid = "${uid}";\nconst filename = "${filename}";`; + // all scripts get access to US_info / GM./GM_info, prepare that object + const scriptData = { + "script": userscript.scriptObject, + "scriptHandler": scriptHandler, + "scriptHandlerVersion": scriptHandlerVersion, + "scriptMetaStr": userscript.scriptMetaStr + }; + api += `\nconst US_info = ${JSON.stringify(scriptData)}`; + api += "\nconst GM_info = US_info;"; + gmMethods.push("info: US_info"); + // loop through each @grant for the userscript, add methods as needed + for (let j = 0; j < grants.length; j++) { + const grant = grants[j]; + switch (grant) { + case "GM.openInTab": + api += `\n${apis.US_openInTab}`; + gmMethods.push("openInTab: US_openInTab"); + break; + case "US_closeTab": + api += `\n${apis.US_closeTab}`; + break; + case "GM.setValue": + api += `\n${apis.US_setValue}`; + gmMethods.push("setValue: US_setValue"); + break; + case "GM.getValue": + api += `\n${apis.US_getValue}`; + gmMethods.push("getValue: US_getValue"); + break; + case "GM.deleteValue": + api += `\n${apis.US_deleteValue}`; + gmMethods.push("deleteValue: US_deleteValue"); + break; + case "GM.listValues": + api += `\n${apis.US_listValues}`; + gmMethods.push("listValues: US_listValues"); + break; + case "GM_addStyle": + api += `\n${apis.US_addStyleSync}`; + api += "\nconst GM_addStyle = US_addStyleSync;"; + break; + case "GM.addStyle": + api += `\n${apis.US_addStyle}`; + gmMethods.push("addStyle: US_addStyle"); + break; + case "GM.setClipboard": + api += `\n${apis.US_setClipboard}`; + gmMethods.push("setClipboard: US_setClipboard"); + break; + case "GM_setClipboard": + api += `\n${apis.US_setClipboardSync}`; + api += "\nconst GM_setClipboard = US_setClipboardSync;"; + break; + case "GM_xmlhttpRequest": + case "GM.xmlHttpRequest": + if (!includedMethods.includes("xhr")) { + api += `\n${apis.xhr}`; + includedMethods.push("xhr"); + } + if (grant === "GM_xmlhttpRequest") { + api += "\nconst GM_xmlhttpRequest = xhr;\n"; + } else { + gmMethods.push("xmlHttpRequest: xhr"); + } + break; + } } - }); -} - -async function getPlatform() { - if (platformGlobal) return platformGlobal; - const response = await browser.runtime.sendNativeMessage({name: "REQ_PLATFORM"}); - if (!response.platform) { - console.error("Failed to get platform"); - return ""; + // make the GM api string + const GM = `const GM = {${gmMethods.join(",")}};`; + // update the final code string + userscript.code = `${api}\n${GM}\n${userscript.code}`; } - platformGlobal = response.platform; - return response.platform; + + // return the updated userscripts + return userscripts; } function setClipboard(data, type = "text/plain") { @@ -366,6 +447,186 @@ function setClipboard(data, type = "text/plain") { } } +async function getPlatform() { + let platform = localStorage.getItem("platform"); + if (!platform) { + const response = await browser.runtime.sendNativeMessage({name: "REQ_PLATFORM"}); + if (!response.platform) { + console.error("Failed to get platform"); + return ""; + } + platform = response.platform; + localStorage.setItem("platform", platform); + } + return platform; +} + +async function setBadgeCount() { + // only set badge on macOS + const platform = await getPlatform(); + if (platform !== "macos") return; + const currentTab = await browser.tabs.getCurrent(); + // no active tabs exist (user closed all windows) + if (!currentTab) return; + const url = currentTab.url; + // if url doesn't exist, stop + if (!url) return; + // only check for http/s pages + if (!url.startsWith("http://") && !url.startsWith("https://")) { + browser.browserAction.setBadgeText({text: ""}); + return; + } + const frameUrls = new Set(); + const frames = await browser.webNavigation.getAllFrames({tabId: currentTab.id}); + for (let i = 0; i < frames.length; i++) { + const frameUrl = frames[i].url; + if (frameUrl !== url && frameUrl.startsWith("http")) { + frameUrls.add(frameUrl); + } + } + const message = {name: "POPUP_BADGE_COUNT", url: url, frameUrls: Array.from(frameUrls)}; + browser.runtime.sendNativeMessage(message, response => { + if (response.error) return console.error(response.error); + const count = response.count; + if (count > 0) { + browser.browserAction.setBadgeText({text: count.toString()}); + } else { + browser.browserAction.setBadgeText({text: ""}); + } + }); +} + +browser.runtime.onMessage.addListener((request, sender, sendResponse) => { + const name = request.name; + switch (name) { + case "REQ_USERSCRIPTS": { + // get the page url from the content script that sent request + const url = sender.url; + // use frameId to determine if request came from top level window + const isTop = sender.frameId === 0 ? true : false; + // ask swift layer to provide code for current url(s) + const message = {name: name, url: url, isTop: isTop}; + browser.runtime.sendNativeMessage(message, response => { + // if request failed, send error to content script for logging + if (response.error) return sendResponse(response); + // add api methods to js code + const apiConfig = { + userscripts: response.files.js, + uid: request.uid, + scriptHandler: response.scriptHandler, + scriptHandlerVersion: response.scriptHandlerVersion + }; + response.files.js = addApis(apiConfig); + // sort files by weight + response.files.js.sort((a, b) => Number(a.weight) < Number(b.weight)); + response.files.css.sort((a, b) => Number(a.weight) < Number(b.weight)); + sendResponse(response); + // update badge count on injection + // especially useful when injection is deferred (ie. subframes) + setBadgeCount(); + }); + return true; + } + case "API_OPEN_TAB": { + const active = (request.active === true) ? true : false; + const props = {active: active, index: sender.tab.index + 1, url: request.url}; + browser.tabs.create(props, response => { + sendResponse(response); + }); + return true; + } + case "API_CLOSE_TAB": { + const tabId = request.tabId !== undefined ? request.tabId : sender.tab.id; + browser.tabs.remove(tabId, () => { + sendResponse({success: true}); + }); + return true; + } + case "API_SET_VALUE": { + const item = {}; + item[`${request.filename}---${request.key}`] = request.value; + browser.storage.local.set(item, () => { + sendResponse({success: true}); + }); + return true; + } + case "API_GET_VALUE": { + const key = `${request.filename}---${request.key}`; + browser.storage.local.get(key, item => { + if (Object.keys(item).length === 0) { + if (request.defaultValue) { + sendResponse(request.defaultValue); + } else { + sendResponse(`undefined--${request.pid}`); + } + } else { + sendResponse(Object.values(item)[0]); + } + }); + return true; + } + case "API_DELETE_VALUE": { + const key = `${request.filename}---${request.key}`; + browser.storage.local.remove(key, response => { + sendResponse({success: true}); + }); + return true; + } + case "API_LIST_VALUES": { + const prefix = `${request.filename}---`; + const keys = []; + browser.storage.local.get().then(items => { + for (const key in items) { + if (key.startsWith(prefix)) { + const k = key.replace(prefix, ""); + keys.push(k); + } + } + sendResponse(keys); + }); + return true; + } + case "API_ADD_STYLE": + case "API_ADD_STYLE_SYNC": { + const tabId = sender.tab.id; + browser.tabs.insertCSS(tabId, {code: request.css, cssOrigin: "user"}, () => { + if (name === "API_ADD_STYLE") sendResponse(request.css); + }); + return true; + } + case "API_SET_CLIPBOARD": { + const result = setClipboard(request.data, request.type); + sendResponse(result); + break; + } + case "API_XHR_CS": { + xhrMake(request, sender); + break; + } + case "API_XHR_ABORT_CS": { + // get the xhrId from request + const xhrId = request.xhrId; + const match = xhrs.find(x => x.xhrId === xhrId); + if (match) { + match.xhr.abort(); + // sendResponse(match); + } else { + console.error(`abort message received for ${xhrId}, but it couldn't be found`); + } + break; + } + case "USERSCRIPT_INSTALL_00": + case "USERSCRIPT_INSTALL_01": + case "USERSCRIPT_INSTALL_02": { + const message = {name: name, content: request.content}; + browser.runtime.sendNativeMessage(message, response => { + sendResponse(response); + }); + return true; + } + + } +}); browser.tabs.onActivated.addListener(setBadgeCount); browser.windows.onFocusChanged.addListener(setBadgeCount); browser.webNavigation.onCompleted.addListener(setBadgeCount); diff --git a/extension/Userscripts Extension/Resources/content.js b/extension/Userscripts Extension/Resources/content.js index 0e479a94..e013221c 100644 --- a/extension/Userscripts Extension/Resources/content.js +++ b/extension/Userscripts Extension/Resources/content.js @@ -1,113 +1,84 @@ // store code received let data; +// instance unique id +const uid = Math.random().toString(36).substring(2, 8); // determines whether strict csp injection has already run (JS only) -let cspFallbackAttempted = 0; -// track whether event listener added -let beforeunload = 0; -// unique id for api messaging -const uid = Math.random().toString(36).substr(2, 8); -// keep reference to platform -let platform; +let cspFallbackAttempted = false; -// request code immediately -browser.runtime.sendMessage({name: "REQ_USERSCRIPTS"}, response => { - // save code to data var so cspFallback can be attempted - data = response.code; - if (Object.keys(data).length !== 0) parseCode(data); +// request code as soon as possible +browser.runtime.sendMessage({name: "REQ_USERSCRIPTS", uid: uid}, response => { + if (!response) { + console.error("REQ_USERSCRIPTS returned undefined"); + return; + } + if (response.error) { + console.error(response.error); + return; + } + // save response locally in case CSP events occur + data = response; + for (let i = 0; i < data.files.js.length; i++) { + const userscript = data.files.js[i]; + processJS( + userscript.scriptObject.name, + userscript.scriptObject.filename, + userscript.code, + userscript.scriptObject["inject-into"], + userscript.scriptObject["run-at"], + false + ); + } + for (let i = 0; i < data.files.css.length; i++) { + const userstyle = data.files.css[i]; + injectCSS(userstyle.name, userstyle.code); + } }); -function sortByWeight(o) { - const sorted = {}; - Object.keys(o).sort((a, b) => o[b].weight - o[a].weight).forEach(key => sorted[key] = o[key]); - return sorted; -} - -function injectCSS(name, code) { - // there's no fallback if blocked by CSP - // future fix?: https://wicg.github.io/construct-stylesheets/ - console.info(`Injecting ${name} %c(css)`, "color: #60f36c"); - const tag = document.createElement("style"); - tag.textContent = code; - document.head.appendChild(tag); +function processJS(name, filename, code, scope, timing, fallback) { + switch (timing) { + case "document-start": + if (document.readyState === "loading") { + document.addEventListener("readystatechange", function() { + if (document.readyState === "interactive") { + injectJS(name, filename, code, scope, fallback); + } + }); + } else { + injectJS(name, filename, code, scope, fallback); + } + break; + case "document-end": + if (document.readyState !== "loading") { + injectJS(name, filename, code, scope, fallback); + } else { + document.addEventListener("DOMContentLoaded", function() { + injectJS(name, filename, code, scope, fallback); + }); + } + break; + case "document-idle": + if (document.readyState === "complete") { + injectJS(name, filename, code, scope, fallback); + } else { + document.addEventListener("readystatechange", e => { + if (document.readyState === "complete") { + injectJS(name, filename, code, scope, fallback); + } + }); + } + break; + } } -function injectJS(filename, code, scope, timing, grants, fallback) { - // include api methods - const gmVals = []; - const usVals = []; - const includedFunctions = []; - // when a csp violation occurs, the scope is set to "content", when previously it's "auto" - // if the scope isn't changed back to "auto" pre-injection, the scriptDataKey will be null - // and the fallback attempt will fail - // this will change back to "content" below - scope = fallback ? "auto" : scope; - let scriptDataKey; - if (timing === "context-menu") { - scriptDataKey = data.js["context-menu"][scope][filename]; +function injectJS(name, filename, code, scope, fallback) { + code = `(function() {${code}\n//# sourceURL=${filename.replace(/\s/g, "-")}\n})();`; + // if fallback or cspFallbackAttempted true and scope is auto, change scope to content + if (scope === "auto" && (fallback || cspFallbackAttempted)) { + scope = "content"; + console.warn(`Attempting fallback injection for ${name}`); } else { - scriptDataKey = data.js[scope][timing][filename]; + console.info(`Injecting ${name} %c(js)`, "color: #fff600"); } - console.info(`Injecting ${scriptDataKey.scriptObject.name} %c(js)`, "color: #e4f360"); - const scriptData = { - "script": scriptDataKey.scriptObject, - "scriptHandler": data.scriptHandler, - "scriptHandlerVersion": data.scriptHandlerVersion, - "scriptMetaStr": scriptDataKey.scriptMetaStr - }; - // change the scope back so it properly inject on fallback attempt - if (fallback) scope = "content"; - // TODO: use us_info instead of const variables - let api = `const uid = "${uid}";\nconst filename = "${filename}";`; - const us_info = {filename: filename, info: scriptData, uid: uid}; - api += `\nconst us_info = ${JSON.stringify(us_info)}`; - // all scripts get acces to GM.info and GM_info - api += "\nconst GM_info = us_info.info;\n"; - gmVals.push("info: us_info.info"); - grants.forEach(grant => { - if (grant === "GM.openInTab") { - api += `\n${us_openInTab}`; - gmVals.push("openInTab: us_openInTab"); - } else if (grant === "US.closeTab") { - api += `\n${us_closeTab}`; - usVals.push("closeTab: us_closeTab"); - } else if (grant === "GM.setValue") { - api += `\n${us_setValue}`; - gmVals.push("setValue: us_setValue"); - } else if (grant === "GM.getValue") { - api += `\n${us_getValue}`; - gmVals.push("getValue: us_getValue"); - } else if (grant === "GM.deleteValue") { - api += `\n${us_deleteValue}`; - gmVals.push("deleteValue: us_deleteValue"); - } else if (grant === "GM.listValues") { - api += `\n${us_listValues}`; - gmVals.push("listValues: us_listValues"); - } else if (grant === "GM_addStyle") { - api += `\n${us_addStyleSync}\nconst GM_addStyle = us_addStyleSync;`; - } else if (grant === "GM.addStyle") { - api += `\n${us_addStyle}\n`; - gmVals.push("addStyle: us_addStyle"); - } else if (grant === "GM.setClipboard") { - api += `\n${us_setClipboard}`; - gmVals.push("setClipboard: us_setClipboard"); - } else if (grant === "GM_setClipboard") { - api += `\n${us_setClipboardSync}\nconst GM_setClipboard = us_setClipboardSync;`; - } else if (grant === "GM_xmlhttpRequest" || grant === "GM.xmlHttpRequest") { - if (!includedFunctions.includes(xhr.name)) { - api += `\n${xhr}`; - includedFunctions.push(xhr.name); - } - if (grant === "GM_xmlhttpRequest") { - api += "\nconst GM_xmlhttpRequest = xhr;\n"; - } else if (grant === "GM.xmlHttpRequest") { - gmVals.push("xmlHttpRequest: xhr"); - } - } - }); - // create api aliases - const GM = `const GM = {${gmVals.join(",")}};`; - const US = `const US = {${usVals.join(",")}};`; - code = `(function() {\n${api}\n${GM}\n${US}\n${code}\n//# sourceURL=${filename.replace(/\s/g, "-")}\n})();`; if (scope !== "content") { const tag = document.createElement("script"); tag.textContent = code; @@ -117,90 +88,9 @@ function injectJS(filename, code, scope, timing, grants, fallback) { } } -function processJS(filename, code, scope, timing, grants, fallback) { - // this is about to get ugly - if (timing === "document-start") { - if (document.readyState === "loading") { - document.addEventListener("readystatechange", function() { - if (document.readyState === "interactive") { - injectJS(filename, code, scope, timing, grants, fallback); - } - }); - } else { - injectJS(filename, code, scope, timing, grants, fallback); - } - } else if (timing === "document-end") { - if (document.readyState !== "loading") { - injectJS(filename, code, scope, timing, grants, fallback); - } else { - document.addEventListener("DOMContentLoaded", function() { - injectJS(filename, code, scope, timing, grants, fallback); - }); - } - } else if (timing === "document-idle") { - if (document.readyState === "complete") { - injectJS(filename, code, scope, timing, grants, fallback); - } else { - document.addEventListener("readystatechange", function(e) { - if (document.readyState === "complete") { - injectJS(filename, code, scope, timing, grants, fallback); - } - }); - } - } -} - -function parseCode(data, fallback = false) { - // get css/js code separately - for (const type in data) { - // separate code type object (ie. {"css":{ ... }} {"js": { ... }}) - const codeTypeObject = data[type]; - // will be used for ordered code injection - let sorted = {}; - if (type === "css") { - sorted = sortByWeight(codeTypeObject); - for (const filename in sorted) { - const code = sorted[filename].code; - const name = sorted[filename].name; - // css is only injected into the page scope after DOMContentLoaded event - if (document.readyState !== "loading") { - injectCSS(name, code); - } else { - document.addEventListener("DOMContentLoaded", function() { - injectCSS(name, code); - }); - } - } - } else if (type === "js") { - // js code can be context scoped to the content script, page, or auto - // if auto is set, page scope is attempted, if fails content scope attempted - for (let scope in codeTypeObject) { - // context menu scripts will be handled in event listener below - if (scope === "context-menu") continue; - // get the nested scoped objects, separated by timing - const scopeObject = codeTypeObject[scope]; - // possible execution timings - const timings = ["document-start", "document-end", "document-idle"]; - timings.forEach(timing => { - // get the nested timing objects, separated by filename, skip if empty - const timingObject = scopeObject[timing]; - if (Object.keys(timingObject).length !== 0) { - sorted = sortByWeight(timingObject); - for (const filename in sorted) { - const code = sorted[filename].code; - const grants = sorted[filename].scriptObject.grant; - // when block by csp rules, auto scope script will auto retry injection - if (fallback) { - console.warn(`Attempting fallback injection for ${filename}`); - scope = "content"; - } - processJS(filename, code, scope, timing, grants, fallback); - } - } - }); - } - } - } +function injectCSS(name, code) { + console.info(`Injecting ${name} %c(css)`, "color: #60f36c"); + browser.runtime.sendMessage({name: "API_ADD_STYLE_SYNC", css: code}); } function cspFallback(e) { @@ -208,332 +98,183 @@ function cspFallback(e) { // it's fair to assume that there is a strict CSP for scripts // if there's a strict CSP for scripts, it's unlikely this extension uri is whitelisted // when any script-src violation is detected, re-attempt injection - // since it's fair to assume injection was blocked for extension's content script + // since it's fair to assume injection was blocked for all userscripts if (e.effectiveDirective === "script-src") { // get all "auto" code - // since other extensions can trigger a security policy violation event + // since other code can trigger a security policy violation event // make sure data var is not undefined before attempting fallback - if (data && Object.keys(data.js.auto).length !== 0 && cspFallbackAttempted < 1) { - const n = {"js": {"auto": {}}}; - n.js.auto = data.js.auto; - parseCode(n, true); + if (!data || cspFallbackAttempted) return; + // update global that tracks security policy violations + cspFallbackAttempted = true; + // loop through all js files if they are @inject-into: auto, attempt re-injection + for (let i = 0; i < data.files.js.length; i++) { + const userscript = data.files.js[i]; + if (userscript.scriptObject["inject-into"] !== "auto") continue; + processJS( + userscript.scriptObject.name, + userscript.scriptObject.filename, + userscript.code, + userscript.scriptObject["inject-into"], + userscript.scriptObject["run-at"], + true + ); } - cspFallbackAttempted = 1; } } -async function processJSContextMenuItems() { - // if not top window, stop execution - if (window !== window.top) return; - // context menu injection is macOS exclusive - // check if platform is stored - if (!platform) { - const response = await browser.runtime.sendMessage({name: "REQ_PLATFORM"}); - if (response.error) console.error(response.error); - if (response.platform) platform = response.platform; - } - // if not macOS, stop execution - if (platform !== "macos") return; - const contextMenuCodeObject = data.js["context-menu"]; - for (const scope in contextMenuCodeObject) { - const scopeObject = contextMenuCodeObject[scope]; - for (const filename in scopeObject) { - const name = scopeObject[filename].scriptObject.name; - if (document.readyState === "complete") { - addContextMenuItem(filename, name); - } else { - window.addEventListener("load", () => { - addContextMenuItem(filename, name); - }); - } - } - } -} - -function addContextMenuItem(filename, name) { - // when context menu item found, create a unique menuItemId and clean name - // the menuItemId will be passed back and forth between content and background - // for that reason use the current url + filename for the menuItemId - // when this file gets an run request, which file to run can be parsed from the menuItemId - // construct url from window.location since url params in href can break match pattern - // run on window load since urls can change during the load process - - // potential bug? https://developer.apple.com/forums/thread/685273 - // https://stackoverflow.com/q/68431201 - let pathname = window.location.pathname; - if (pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1); - const url = `${window.location.protocol}//${window.location.hostname}${pathname}`; - - const menuItemId = `${url}&$&${filename}`; - const message = {name: "CONTEXT_CREATE", menuItemId: menuItemId, title: name, url: url}; - browser.runtime.sendMessage(message, response => { - // avoid adding unnecessary event listeners - if (!beforeunload) { - window.addEventListener("beforeunload", () => { - // beforeunload doesn't always fire on page refresh? - // OK since we wouldn't want to remove the context menu items when that happens - // BAD for when user disabled a context-menu script then refreshes... - // b/c of this all context menu items for a url will be removed/remade on refresh - browser.runtime.sendMessage({name: "CONTEXT_REMOVE", menuItemId: menuItemId}); +function handleApiMessages(e) { + // only respond to messages with matching unique id and have a name value + if (e.data.id !== uid || !e.data.name) return; + const id = e.data.id; + const name = e.data.name; + const pid = e.data.pid; + let message; + const respMessage = {name: name.replace("API_", "RESP_"), id: id, pid: pid}; + switch (name) { + case "API_OPEN_TAB": + message = {name: name, url: e.data.url, active: e.data.active}; + browser.runtime.sendMessage(message, response => { + respMessage.response = response; + window.postMessage(respMessage); }); - beforeunload = 1; - } - }); -} - -// api - https://developer.chrome.com/docs/extensions/mv3/content_scripts/#host-page-communication -function us_openInTab(url, openInBackground) { - const pid = Math.random().toString(36).substring(1, 9); - return new Promise(resolve => { - const callback = e => { - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_OPEN_TAB") return; - resolve(e.data.response); - window.removeEventListener("message", callback); - }; - window.addEventListener("message", callback); - const active = (openInBackground === true) ? false : true; - window.postMessage({id: uid, pid: pid, name: "API_OPEN_TAB", url: url, active: active}); - }); -} - -function us_closeTab(tabId) { - const pid = Math.random().toString(36).substring(1, 9); - return new Promise(resolve => { - const callback = e => { - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_CLOSE_TAB") return; - resolve(e.data.response); - window.removeEventListener("message", callback); - }; - window.addEventListener("message", callback); - window.postMessage({id: uid, pid: pid, name: "API_CLOSE_TAB", tabId: tabId}); - }); -} - -function us_setValue(key, value) { - const pid = Math.random().toString(36).substring(1, 9); - return new Promise(resolve => { - const callback = e => { - // eslint-disable-next-line no-undef -- filename var accessible to the function at runtime - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_SET_VALUE" || e.data.filename !== filename) return; - resolve(e.data.response); - window.removeEventListener("message", callback); - }; - window.addEventListener("message", callback); - // eslint-disable-next-line no-undef -- filename var accessible to the function at runtime - window.postMessage({id: uid, pid: pid, name: "API_SET_VALUE", filename: filename, key: key, value: value}); - }); -} - -function us_getValue(key, defaultValue) { - const pid = Math.random().toString(36).substring(1, 9); - return new Promise(resolve => { - const callback = e => { - // eslint-disable-next-line no-undef -- filename var accessible to the function at runtime - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_GET_VALUE" || e.data.filename !== filename) return; - const response = e.data.response; - resolve(response); - window.removeEventListener("message", callback); - }; - window.addEventListener("message", callback); - // eslint-disable-next-line no-undef -- filename var accessible to the function at runtime - window.postMessage({id: uid, pid: pid, name: "API_GET_VALUE", filename: filename, key: key, defaultValue: defaultValue}); - }); -} - -function us_listValues() { - const pid = Math.random().toString(36).substring(1, 9); - return new Promise(resolve => { - const callback = e => { - // eslint-disable-next-line no-undef -- filename var accessible to the function at runtime - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_LIST_VALUES" || e.data.filename !== filename) return; - resolve(e.data.response); - window.removeEventListener("message", callback); - }; - window.addEventListener("message", callback); - // eslint-disable-next-line no-undef -- filename var accessible to the function at runtime - window.postMessage({id: uid, pid: pid, name: "API_LIST_VALUES", filename: filename}); - }); -} - -function us_deleteValue(key) { - const pid = Math.random().toString(36).substring(1, 9); - return new Promise(resolve => { - const callback = e => { - // eslint-disable-next-line no-undef -- filename var accessible to the function at runtime - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_DELETE_VALUE" || e.data.filename !== filename) return; - resolve(e.data.response); - window.removeEventListener("message", callback); - }; - window.addEventListener("message", callback); - // eslint-disable-next-line no-undef -- filename var accessible to the function at runtime - window.postMessage({id: uid, pid: pid, name: "API_DELETE_VALUE", filename: filename, key: key}); - }); -} - -function us_addStyleSync(css) { - window.postMessage({id: uid, name: "API_ADD_STYLE_SYNC", css: css}); - return css; -} - -function us_addStyle(css) { - const pid = Math.random().toString(36).substring(1, 9); - return new Promise(resolve => { - const callback = e => { - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_ADD_STYLE") return; - resolve(e.data.response); - window.removeEventListener("message", callback); - }; - window.addEventListener("message", callback); - window.postMessage({id: uid, pid: pid, name: "API_ADD_STYLE", css: css}); - }); -} - -function us_setClipboard(data, type) { - const pid = Math.random().toString(36).substring(1, 9); - return new Promise(resolve => { - const callback = e => { - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_SET_CLIPBOARD") return; - resolve(e.data.response); - if (!e.data.response) console.error("clipboard write failed"); - window.removeEventListener("message", callback); - }; - window.addEventListener("message", callback); - window.postMessage({id: uid, pid: pid, name: "API_SET_CLIPBOARD", data: data, type: type}); - }); -} - -function us_setClipboardSync(data, type) { - // there's actually no sync method since a promise needs to be sent to bg page - // however make a dummy sync method for compatibility - window.postMessage({id: uid, name: "API_SET_CLIPBOARD", data: data, type: type}); - return undefined; -} - -// when xhr is called it sends a message to the content script -// and adds it's own event listener to get responses from content script -// each xhr has a unique id so it won't respond to different xhr -// the content script sends the xhr details to the background script -// the background script sends messages back to the content script for all xhr events -// the content script relays these messages back to the context where xhr was called -// if xhr was called with event handler functions they will be executed when those relays come in -function xhr(details) { - // if details didn't include url, do nothing - if (!details.url) return; - // create unique id for the xhr - const xhrId = Math.random().toString(36).substring(1, 9); - // strip out functions from details, kind of hacky - const detailsParsed = JSON.parse(JSON.stringify(details)); - // check which functions are included in the original details object - // add a bool to indicate if event listeners should be attached - if (details.onabort) detailsParsed.onabort = true; - if (details.onerror) detailsParsed.onerror = true; - if (details.onload) detailsParsed.onload = true; - if (details.onloadend) detailsParsed.onloadend = true; - if (details.onloadstart) detailsParsed.onloadstart = true; - if (details.onprogress) detailsParsed.onprogress = true; - if (details.onreadystatechange) detailsParsed.onreadystatechange = true; - if (details.ontimeout) detailsParsed.ontimeout = true; - // abort function gets returned when this function is called - const abort = () => { - window.postMessage({id: uid, name: "API_XHR_ABORT_INJ", xhrId: xhrId}); - }; - const callback = e => { - const name = e.data.name; - const response = e.data.response; - // ensure callback is responding to the proper message - if ( - e.data.id !== uid - || e.data.xhrId !== xhrId - || !name - || !name.startsWith("RESP_API_XHR_CS") - ) return; - if (name === "RESP_API_XHR_CS") { - // ignore - } else if (name.includes("ABORT") && details.onabort) { - details.onabort(response); - } else if (name.includes("ERROR") && details.onerror) { - details.onerror(response); - } else if (name === "RESP_API_XHR_CS_LOAD" && details.onload) { - details.onload(response); - } else if (name.includes("LOADEND") && details.onloadend) { - details.onloadend(response); - // remove event listener when xhr is complete - window.removeEventListener("message", callback); - } else if (name.includes("LOADSTART") && details.onloadstart) { - details.onloadtstart(response); - } else if (name.includes("PROGRESS") && details.onprogress) { - details.onprogress(response); - } else if (name.includes("READYSTATECHANGE") && details.onreadystatechange) { - details.onreadystatechange(response); - } else if (name.includes("TIMEOUT") && details.ontimeout) { - details.ontimeout(response); - } - }; - window.addEventListener("message", callback); - window.postMessage({id: uid, name: "API_XHR_INJ", details: detailsParsed, xhrId: xhrId}); - return {abort: abort}; + break; + case "API_CLOSE_TAB": + message = {name: name, tabId: e.data.tabId}; + browser.runtime.sendMessage(message, response => { + respMessage.response = response; + window.postMessage(respMessage); + }); + break; + case "API_SET_CLIPBOARD": + message = {name: name, data: e.data.data, type: e.data.type}; + browser.runtime.sendMessage(message, response => { + respMessage.response = response; + window.postMessage(respMessage); + }); + break; + case "API_SET_VALUE": + message = { + name: name, + filename: e.data.filename, + key: e.data.key, + value: e.data.value + }; + browser.runtime.sendMessage(message, response => { + respMessage.response = response; + respMessage.filename = e.data.filename; + window.postMessage(respMessage); + }); + break; + case "API_GET_VALUE": + message = { + name: name, + filename: e.data.filename, + pid: pid, + key: e.data.key, + defaultValue: e.data.defaultValue + }; + browser.runtime.sendMessage(message, response => { + const undef = response === `undefined--${pid}`; + respMessage.response = undef ? undefined : response; + respMessage.filename = e.data.filename; + window.postMessage(respMessage); + }); + break; + case "API_DELETE_VALUE": + message = {name: name, filename: e.data.filename, key: e.data.key}; + browser.runtime.sendMessage(message, response => { + respMessage.response = response; + respMessage.filename = e.data.filename; + window.postMessage(respMessage); + }); + break; + case "API_LIST_VALUES": + message = {name: name, filename: e.data.filename}; + browser.runtime.sendMessage(message, response => { + respMessage.response = response; + respMessage.filename = e.data.filename; + window.postMessage(respMessage); + }); + break; + case "API_ADD_STYLE": + message = {name: name, css: e.data.css}; + browser.runtime.sendMessage(message, response => { + respMessage.response = response; + window.postMessage(respMessage); + }); + break; + case "API_ADD_STYLE_SYNC": + message = {name: name, css: e.data.css}; + browser.runtime.sendMessage(message); + break; + case "API_XHR_INJ": + message = { + name: "API_XHR_CS", + details: e.data.details, + id: id, + xhrId: e.data.xhrId + }; + browser.runtime.sendMessage(message, response => { + respMessage.response = response; + respMessage.xhrId = e.data.xhrId; + window.postMessage(respMessage); + }); + break; + case "API_XHR_ABORT_INJ": + message = {name: "API_XHR_ABORT_CS", xhrId: e.data.xhrId}; + browser.runtime.sendMessage(message); + break; + } } -// listen for messages from background, popup, etc... +// listens for messages from background, popup, etc... browser.runtime.onMessage.addListener((request, sender, sendResponse) => { const name = request.name; - if (name === "CONTEXT_RUN") { - // only run context menu script in top window - if (window !== window.top) return; - // get the filename from the menuItemId sent with the request - const filename = request.menuItemId.split("&$&")[1]; - // clone the context menu scripts object from the data var - const contextMenuCodeObject = data.js["context-menu"]; - let found = false; - // loop through all the context menu scripts to find match against filename - for (let scope in contextMenuCodeObject) { - for (const fn in contextMenuCodeObject[scope]) { - if (fn === filename) { - // get code from object and send for injection along with filename & scope - const code = contextMenuCodeObject[scope][filename].code; - const grants = contextMenuCodeObject[scope][filename].scriptObject.grant; - // if strict csp already detected change auto scoped scripts to content - let fallback = false; - if (cspFallbackAttempted && scope === "auto") { - console.warn(`Attempting fallback injection for ${filename}`); - scope = "content"; - fallback = true; - } - scope = cspFallbackAttempted && scope === "auto" ? "content" : scope; - injectJS(filename, code, scope, "context-menu", grants, fallback); - found = true; - break; - } - } - if (found) break; - } - } else if (name.startsWith("RESP_API_XHR_BG_")) { + if (name.startsWith("RESP_API_XHR_BG_")) { // only respond to messages on the correct content script if (request.id !== uid) return; - const resp = request.response; - const n = name.replace("_BG_", "_CS_"); + const xhrResponse = request.response; + const responseName = name.replace("_BG_", "_CS_"); // arraybuffer responses had their data converted, convert it back to arraybuffer - if (request.response.responseType === "arraybuffer" && resp.response) { + if (xhrResponse.responseType === "arraybuffer" && xhrResponse.response) { try { - const r = new Uint8Array(resp.response).buffer; - resp.response = r; + const buffer = new Uint8Array(xhrResponse.response).buffer; + xhrResponse.response = buffer; } catch (error) { console.error("error parsing xhr arraybuffer response", error); } // blob responses had their data converted, convert it back to blob - } else if (request.response.responseType === "blob" && resp.response && resp.response.data) { + } else if ( + request.response.responseType === "blob" + && xhrResponse.response + && xhrResponse.response.data + ) { fetch(request.response.response.data) .then(res => res.blob()) .then(b => { - resp.response = b; - window.postMessage({name: n, response: resp, id: request.id, xhrId: request.xhrId}); + xhrResponse.response = b; + window.postMessage({ + name: responseName, + response: xhrResponse, + id: request.id, + xhrId: request.xhrId + }); }); } // blob response will execute its own postMessage call if (request.response.responseType !== "blob") { - window.postMessage({name: n, response: resp, id: request.id, xhrId: request.xhrId}); + window.postMessage({ + name: responseName, + response: xhrResponse, + id: request.id, xhrId: + request.xhrId + }); } - } else if (["USERSCRIPT_INSTALL_00", "USERSCRIPT_INSTALL_01", "USERSCRIPT_INSTALL_02"].includes(name)) { + } else if ( + name === "USERSCRIPT_INSTALL_00" + || name === "USERSCRIPT_INSTALL_01" + || name === "USERSCRIPT_INSTALL_02" + ) { const types = [ "text/plain", "application/ecmascript", @@ -542,90 +283,17 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => { "text/javascript" ]; if (!document.contentType || types.indexOf(document.contentType) === -1) { - // only allow installation if contentType is in list above sendResponse({invalid: true}); } else { - const content = document.body.innerText; - browser.runtime.sendMessage({name: name, content: content}, response => { + const message = {name: name, content: document.body.innerText}; + browser.runtime.sendMessage(message, response => { sendResponse(response); }); return true; } } }); - -// listen for message from api -window.addEventListener("message", e => { - // only respond to messages that have matching unique id and have a name value - if (e.data.id !== uid || !e.data.name) return; - const id = e.data.id; - const name = e.data.name; - const pid = e.data.pid; - let message; - if (name === "API_OPEN_TAB") { - // ignore requests that don't supply a url - if (!e.data.url) return; - message = {name: "API_OPEN_TAB", url: e.data.url, active: e.data.active}; - browser.runtime.sendMessage(message, response => { - window.postMessage({id: id, pid: pid, name: "RESP_OPEN_TAB", response: response}); - }); - } else if (name === "API_CLOSE_TAB") { - browser.runtime.sendMessage({name: "API_CLOSE_TAB", tabId: e.data.tabId}, response => { - window.postMessage({id: id, pid: pid, name: "RESP_CLOSE_TAB", response: response}); - }); - } else if (name === "API_SET_CLIPBOARD") { - browser.runtime.sendMessage({name: "API_SET_CLIPBOARD", data: e.data.data, type: e.data.type}, response => { - window.postMessage({id: id, pid: pid, name: "RESP_SET_CLIPBOARD", response: response}); - }); - } else if (name === "API_SET_VALUE") { - message = {name: "API_SET_VALUE", filename: e.data.filename, key: e.data.key, value: e.data.value}; - browser.runtime.sendMessage(message, response => { - window.postMessage({id: id, pid: pid, name: "RESP_SET_VALUE", filename: e.data.filename, response: response}); - }); - } else if (name === "API_GET_VALUE") { - message = {name: "API_GET_VALUE", filename: e.data.filename, pid: pid, key: e.data.key, defaultValue: e.data.defaultValue}; - browser.runtime.sendMessage(message, response => { - const resp = response === `undefined--${pid}` ? undefined : response; - window.postMessage({id: id, pid: pid, name: "RESP_GET_VALUE", filename: e.data.filename, response: resp}); - }); - } else if (name === "API_DELETE_VALUE") { - message = {name: "API_DELETE_VALUE", filename: e.data.filename, key: e.data.key}; - browser.runtime.sendMessage(message, response => { - window.postMessage({id: id, pid: pid, name: "RESP_DELETE_VALUE", filename: e.data.filename, response: response}); - }); - } else if (name === "API_LIST_VALUES") { - message = {name: "API_LIST_VALUES", filename: e.data.filename}; - browser.runtime.sendMessage(message, response => { - window.postMessage({id: id, pid: pid, name: "RESP_LIST_VALUES", filename: e.data.filename, response: response}); - }); - } else if (name === "API_ADD_STYLE") { - try { - message = {name: "API_ADD_STYLE", css: e.data.css}; - browser.runtime.sendMessage(message, response => { - window.postMessage({id: id, pid: pid, name: "RESP_ADD_STYLE", response: response}); - }); - } catch (e) { - console.log(e); - } - } else if (name === "API_ADD_STYLE_SYNC") { - try { - message = {name: "API_ADD_STYLE_SYNC", css: e.data.css}; - browser.runtime.sendMessage(message); - } catch (e) { - console.log(e); - } - } else if (name === "API_XHR_INJ") { - message = {name: "API_XHR_CS", details: e.data.details, id: id, xhrId: e.data.xhrId}; - browser.runtime.sendMessage(message, response => { - window.postMessage({id: id, name: "RESP_API_XHR_CS", response: response, xhrId: e.data.xhrId}); - }); - } else if (name === "API_XHR_ABORT_INJ") { - message = {name: "API_XHR_ABORT_CS", xhrId: e.data.xhrId}; - browser.runtime.sendMessage(message); - } -}); - -// when userscript fails due to a CSP and has @inject-into value of auto +// listens for messages from api methods calls in content script or current page +window.addEventListener("message", handleApiMessages); +// when userscript fails due to a CSP and has @inject-into value of auto or page document.addEventListener("securitypolicyviolation", cspFallback); -// create context menu items as needed -document.addEventListener("contextmenu", processJSContextMenuItems); diff --git a/extension/Userscripts Extension/SafariWebExtensionHandler.swift b/extension/Userscripts Extension/SafariWebExtensionHandler.swift index 56a7dd1c..6c5a0737 100644 --- a/extension/Userscripts Extension/SafariWebExtensionHandler.swift +++ b/extension/Userscripts Extension/SafariWebExtensionHandler.swift @@ -26,7 +26,7 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { let matches = getInjectionFilenames(url), let code = getCode(matches, isTop) { - response.userInfo = [SFExtensionMessageKey: ["code": code]] + response.userInfo = [SFExtensionMessageKey: code] } else { response.userInfo = [SFExtensionMessageKey: ["error": "REQ_USERSCRIPTS failed"]] } From d985eddc4e2e2b33292d6a6ab3843868d789e06e Mon Sep 17 00:00:00 2001 From: quoid Date: Mon, 28 Mar 2022 22:18:20 -0400 Subject: [PATCH 27/47] update eslint rules --- .eslintrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.json b/.eslintrc.json index 9719c487..582aa512 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -25,6 +25,7 @@ "array-bracket-spacing": ["error", "never"], "arrow-parens": ["error", "as-needed"], "brace-style": ["error", "1tbs", {"allowSingleLine": false}], + "comma-dangle": ["error", "never"], "comma-spacing": ["error", { "before": false, "after": true From 5939c39d8e686c81571cfcbc8d74169f39a41745 Mon Sep 17 00:00:00 2001 From: quoid Date: Mon, 28 Mar 2022 22:28:46 -0400 Subject: [PATCH 28/47] show no matches when page url undefined --- .../Userscripts Extension/Resources/popup.js | 35 +++++++++---------- src/popup/App.svelte | 3 +- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/extension/Userscripts Extension/Resources/popup.js b/extension/Userscripts Extension/Resources/popup.js index 4310d0c8..9bbb846f 100644 --- a/extension/Userscripts Extension/Resources/popup.js +++ b/extension/Userscripts Extension/Resources/popup.js @@ -2867,12 +2867,12 @@ return child_ctx; } - // (498:0) {#if !active} + // (497:0) {#if !active} function create_if_block_10(ctx) { return { c: noop, m: noop, d: noop }; } - // (501:0) {#if showInstallPrompt} + // (500:0) {#if showInstallPrompt} function create_if_block_9(ctx) { let div; let t0; @@ -2914,7 +2914,7 @@ }; } - // (506:0) {#if error} + // (505:0) {#if error} function create_if_block_8(ctx) { let div; let t0; @@ -2964,7 +2964,7 @@ }; } - // (534:8) {:else} + // (533:8) {:else} function create_else_block$3(ctx) { let div; let each_blocks = []; @@ -3037,7 +3037,7 @@ }; } - // (532:35) + // (531:35) function create_if_block_7(ctx) { let div; @@ -3059,7 +3059,7 @@ }; } - // (522:28) + // (521:28) function create_if_block_6$1(ctx) { let div; let t0; @@ -3097,7 +3097,7 @@ }; } - // (520:8) {#if inactive} + // (519:8) {#if inactive} function create_if_block_5$1(ctx) { let div; @@ -3119,7 +3119,7 @@ }; } - // (517:4) {#if loading} + // (516:4) {#if loading} function create_if_block_4$1(ctx) { let loader; let current; @@ -3159,7 +3159,7 @@ }; } - // (536:16) {#each list as item (item.filename)} + // (535:16) {#each list as item (item.filename)} function create_each_block$3(key_1, ctx) { let first; let popupitem; @@ -3218,7 +3218,7 @@ }; } - // (549:0) {#if !inactive && platform === "macos"} + // (548:0) {#if !inactive && platform === "macos"} function create_if_block_3$1(ctx) { let div1; let div0; @@ -3251,7 +3251,7 @@ }; } - // (586:18) + // (585:18) function create_if_block_2$1(ctx) { let view; let current; @@ -3301,7 +3301,7 @@ }; } - // (572:22) + // (571:22) function create_if_block_1$1(ctx) { let view; let current; @@ -3351,7 +3351,7 @@ }; } - // (556:0) {#if showUpdates} + // (555:0) {#if showUpdates} function create_if_block$6(ctx) { let view; let current; @@ -3404,7 +3404,7 @@ }; } - // (587:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > + // (586:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > function create_default_slot_2(ctx) { let allitemsview; let current; @@ -3444,7 +3444,7 @@ }; } - // (573:4) showInstall = false} showLoaderOnDisabled={true} > + // (572:4) showInstall = false} showLoaderOnDisabled={true} > function create_default_slot_1(ctx) { let installview; let current; @@ -3488,7 +3488,7 @@ }; } - // (557:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > + // (556:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > function create_default_slot(ctx) { let updateview; let current; @@ -4178,9 +4178,8 @@ const url = currentTab.url; if (!url) { - console.error("Error getting current tab url"); - $$invalidate(11, initError = true); $$invalidate(2, loading = false); + $$invalidate(3, disabled = false); return; } diff --git a/src/popup/App.svelte b/src/popup/App.svelte index ed29cbeb..51b9981b 100644 --- a/src/popup/App.svelte +++ b/src/popup/App.svelte @@ -272,9 +272,8 @@ const currentTab = await browser.tabs.getCurrent(); const url = currentTab.url; if (!url) { - console.error("Error getting current tab url"); - initError = true; loading = false; + disabled = false; return; } if (url === extensionPageUrl) { From 84b58ecc982c0575862c06bc3d21a89f8bdd4ee1 Mon Sep 17 00:00:00 2001 From: quoid Date: Thu, 31 Mar 2022 21:57:50 -0400 Subject: [PATCH 29/47] change filename var to be more unique #237 --- .../Resources/background.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/extension/Userscripts Extension/Resources/background.js b/extension/Userscripts Extension/Resources/background.js index 19d13c08..a8e66be8 100644 --- a/extension/Userscripts Extension/Resources/background.js +++ b/extension/Userscripts Extension/Resources/background.js @@ -6,7 +6,7 @@ // that means it's ok that this var gets reset when the bg page unloads let xhrs = []; -/* global filename, uid */ +/* global US_filename, uid */ // filename and uid will be available to functions at runtime const apis = { US_openInTab(url, openInBackground) { @@ -42,7 +42,7 @@ const apis = { e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_SET_VALUE" - || e.data.filename !== filename + || e.data.filename !== US_filename ) return; resolve(e.data.response); window.removeEventListener("message", callback); @@ -52,7 +52,7 @@ const apis = { id: uid, pid: pid, name: "API_SET_VALUE", - filename: filename, + filename: US_filename, key: key, value: value }); @@ -66,7 +66,7 @@ const apis = { e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_GET_VALUE" - || e.data.filename !== filename + || e.data.filename !== US_filename ) return; const response = e.data.response; resolve(response); @@ -76,7 +76,7 @@ const apis = { window.postMessage({ id: uid, pid: pid, name: "API_GET_VALUE", - filename: filename, + filename: US_filename, key: key, defaultValue: defaultValue }); @@ -90,13 +90,13 @@ const apis = { e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_LIST_VALUES" - || e.data.filename !== filename + || e.data.filename !== US_filename ) return; resolve(e.data.response); window.removeEventListener("message", callback); }; window.addEventListener("message", callback); - window.postMessage({id: uid, pid: pid, name: "API_LIST_VALUES", filename: filename}); + window.postMessage({id: uid, pid: pid, name: "API_LIST_VALUES", filename: US_filename}); }); }, US_deleteValue(key) { @@ -107,13 +107,13 @@ const apis = { e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_DELETE_VALUE" - || e.data.filename !== filename + || e.data.filename !== US_filename ) return; resolve(e.data.response); window.removeEventListener("message", callback); }; window.addEventListener("message", callback); - window.postMessage({id: uid, pid: pid, name: "API_DELETE_VALUE", filename: filename, key: key}); + window.postMessage({id: uid, pid: pid, name: "API_DELETE_VALUE", filename: US_filename, key: key}); }); }, US_addStyleSync(css) { @@ -342,7 +342,7 @@ function addApis({userscripts, uid, scriptHandler, scriptHandlerVersion}) { const filename = userscript.scriptObject.filename; const grants = userscript.scriptObject.grants; // prepare the api string - let api = `const uid = "${uid}";\nconst filename = "${filename}";`; + let api = `const uid = "${uid}";\nconst US_filename = "${filename}";`; // all scripts get access to US_info / GM./GM_info, prepare that object const scriptData = { "script": userscript.scriptObject, From 84f987f93e63d25504f97857b8850043affbf1dc Mon Sep 17 00:00:00 2001 From: quoid Date: Thu, 31 Mar 2022 22:03:14 -0400 Subject: [PATCH 30/47] change uid var to be more unique #237 --- .../Resources/background.js | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/extension/Userscripts Extension/Resources/background.js b/extension/Userscripts Extension/Resources/background.js index a8e66be8..62407968 100644 --- a/extension/Userscripts Extension/Resources/background.js +++ b/extension/Userscripts Extension/Resources/background.js @@ -6,32 +6,32 @@ // that means it's ok that this var gets reset when the bg page unloads let xhrs = []; -/* global US_filename, uid */ +/* global US_filename, US_uid */ // filename and uid will be available to functions at runtime const apis = { US_openInTab(url, openInBackground) { const pid = Math.random().toString(36).substring(1, 9); return new Promise(resolve => { const callback = e => { - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_OPEN_TAB") return; + if (e.data.pid !== pid || e.data.id !== US_uid || e.data.name !== "RESP_OPEN_TAB") return; resolve(e.data.response); window.removeEventListener("message", callback); }; window.addEventListener("message", callback); const active = (openInBackground === true) ? false : true; - window.postMessage({id: uid, pid: pid, name: "API_OPEN_TAB", url: url, active: active}); + window.postMessage({id: US_uid, pid: pid, name: "API_OPEN_TAB", url: url, active: active}); }); }, US_closeTab(tabId) { const pid = Math.random().toString(36).substring(1, 9); return new Promise(resolve => { const callback = e => { - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_CLOSE_TAB") return; + if (e.data.pid !== pid || e.data.id !== US_uid || e.data.name !== "RESP_CLOSE_TAB") return; resolve(e.data.response); window.removeEventListener("message", callback); }; window.addEventListener("message", callback); - window.postMessage({id: uid, pid: pid, name: "API_CLOSE_TAB", tabId: tabId}); + window.postMessage({id: US_uid, pid: pid, name: "API_CLOSE_TAB", tabId: tabId}); }); }, US_setValue(key, value) { @@ -40,7 +40,7 @@ const apis = { const callback = e => { if ( e.data.pid !== pid - || e.data.id !== uid + || e.data.id !== US_uid || e.data.name !== "RESP_SET_VALUE" || e.data.filename !== US_filename ) return; @@ -49,7 +49,7 @@ const apis = { }; window.addEventListener("message", callback); window.postMessage({ - id: uid, + id: US_uid, pid: pid, name: "API_SET_VALUE", filename: US_filename, @@ -64,7 +64,7 @@ const apis = { const callback = e => { if ( e.data.pid !== pid - || e.data.id !== uid + || e.data.id !== US_uid || e.data.name !== "RESP_GET_VALUE" || e.data.filename !== US_filename ) return; @@ -74,7 +74,8 @@ const apis = { }; window.addEventListener("message", callback); window.postMessage({ - id: uid, pid: pid, + id: US_uid, + pid: pid, name: "API_GET_VALUE", filename: US_filename, key: key, @@ -88,7 +89,7 @@ const apis = { const callback = e => { if ( e.data.pid !== pid - || e.data.id !== uid + || e.data.id !== US_uid || e.data.name !== "RESP_LIST_VALUES" || e.data.filename !== US_filename ) return; @@ -96,7 +97,7 @@ const apis = { window.removeEventListener("message", callback); }; window.addEventListener("message", callback); - window.postMessage({id: uid, pid: pid, name: "API_LIST_VALUES", filename: US_filename}); + window.postMessage({id: US_uid, pid: pid, name: "API_LIST_VALUES", filename: US_filename}); }); }, US_deleteValue(key) { @@ -105,7 +106,7 @@ const apis = { const callback = e => { if ( e.data.pid !== pid - || e.data.id !== uid + || e.data.id !== US_uid || e.data.name !== "RESP_DELETE_VALUE" || e.data.filename !== US_filename ) return; @@ -113,42 +114,48 @@ const apis = { window.removeEventListener("message", callback); }; window.addEventListener("message", callback); - window.postMessage({id: uid, pid: pid, name: "API_DELETE_VALUE", filename: US_filename, key: key}); + window.postMessage({ + id: US_uid, + pid: pid, + name: "API_DELETE_VALUE", + filename: US_filename, + key: key + }); }); }, US_addStyleSync(css) { - window.postMessage({id: uid, name: "API_ADD_STYLE_SYNC", css: css}); + window.postMessage({id: US_uid, name: "API_ADD_STYLE_SYNC", css: css}); return css; }, US_addStyle(css) { const pid = Math.random().toString(36).substring(1, 9); return new Promise(resolve => { const callback = e => { - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_ADD_STYLE") return; + if (e.data.pid !== pid || e.data.id !== US_uid || e.data.name !== "RESP_ADD_STYLE") return; resolve(e.data.response); window.removeEventListener("message", callback); }; window.addEventListener("message", callback); - window.postMessage({id: uid, pid: pid, name: "API_ADD_STYLE", css: css}); + window.postMessage({id: US_uid, pid: pid, name: "API_ADD_STYLE", css: css}); }); }, US_setClipboard(data, type) { const pid = Math.random().toString(36).substring(1, 9); return new Promise(resolve => { const callback = e => { - if (e.data.pid !== pid || e.data.id !== uid || e.data.name !== "RESP_SET_CLIPBOARD") return; + if (e.data.pid !== pid || e.data.id !== US_uid || e.data.name !== "RESP_SET_CLIPBOARD") return; resolve(e.data.response); if (!e.data.response) console.error("clipboard write failed"); window.removeEventListener("message", callback); }; window.addEventListener("message", callback); - window.postMessage({id: uid, pid: pid, name: "API_SET_CLIPBOARD", data: data, type: type}); + window.postMessage({id: US_uid, pid: pid, name: "API_SET_CLIPBOARD", data: data, type: type}); }); }, US_setClipboardSync(data, type) { // there's actually no sync method since a promise needs to be sent to bg page // however make a dummy sync method for compatibility - window.postMessage({id: uid, name: "API_SET_CLIPBOARD", data: data, type: type}); + window.postMessage({id: US_uid, name: "API_SET_CLIPBOARD", data: data, type: type}); return undefined; }, // when xhr is called it sends a message to the content script @@ -177,14 +184,14 @@ const apis = { if (details.ontimeout) detailsParsed.ontimeout = true; // abort function gets returned when this function is called const abort = () => { - window.postMessage({id: uid, name: "API_XHR_ABORT_INJ", xhrId: xhrId}); + window.postMessage({id: US_uid, name: "API_XHR_ABORT_INJ", xhrId: xhrId}); }; const callback = e => { const name = e.data.name; const response = e.data.response; // ensure callback is responding to the proper message if ( - e.data.id !== uid + e.data.id !== US_uid || e.data.xhrId !== xhrId || !name || !name.startsWith("RESP_API_XHR_CS") @@ -212,7 +219,7 @@ const apis = { } }; window.addEventListener("message", callback); - window.postMessage({id: uid, name: "API_XHR_INJ", details: detailsParsed, xhrId: xhrId}); + window.postMessage({id: US_uid, name: "API_XHR_INJ", details: detailsParsed, xhrId: xhrId}); return {abort: abort}; } }; @@ -342,7 +349,7 @@ function addApis({userscripts, uid, scriptHandler, scriptHandlerVersion}) { const filename = userscript.scriptObject.filename; const grants = userscript.scriptObject.grants; // prepare the api string - let api = `const uid = "${uid}";\nconst US_filename = "${filename}";`; + let api = `const US_uid = "${uid}";\nconst US_filename = "${filename}";`; // all scripts get access to US_info / GM./GM_info, prepare that object const scriptData = { "script": userscript.scriptObject, From d95ab81ec452abf201f4a13d5ffe9fb7a7710749 Mon Sep 17 00:00:00 2001 From: quoid Date: Thu, 7 Apr 2022 20:32:50 -0400 Subject: [PATCH 31/47] update eslint rules --- .eslintrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 582aa512..17589967 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -46,8 +46,8 @@ "no-multi-str": "error", "no-multiple-empty-lines": ["error", {"max": 1, "maxBOF": 2}], // https://github.com/sveltejs/eslint-plugin-svelte3/issues/41 "no-tabs": "error", - "no-unused-vars": ["error", {"args": "none"}], "no-useless-concat": "error", + "no-unused-vars": ["error", {"args": "none"}], "no-use-before-define": ["error", { "classes": true, "functions": false, @@ -72,6 +72,7 @@ }], "space-in-parens": ["error", "never"], "template-curly-spacing": ["error", "never"], + "space-infix-ops": "error", "jsx-a11y/a11y-missing-attribute": "off" } } From e7b5fc72c44405034409efbe41ca4dd9d9e0b01e Mon Sep 17 00:00:00 2001 From: quoid Date: Thu, 7 Apr 2022 21:10:12 -0400 Subject: [PATCH 32/47] content blocking (declarativeNetRequest) script support ~ #184 --- .../Userscripts Extension/Functions.swift | 184 +++++++++++++++++- .../Resources/background.js | 55 +++++- .../Resources/manifest.json | 1 + .../Userscripts Extension/Resources/page.html | 2 +- .../Userscripts Extension/Resources/page.js | 32 ++- .../Resources/popup.html | 2 +- .../Userscripts Extension/Resources/popup.js | 68 ++++--- .../SafariWebExtensionHandler.swift | 7 + src/page/Components/Editor/Editor.svelte | 2 +- src/page/Components/Sidebar/Sidebar.svelte | 3 +- .../Components/Sidebar/SidebarItem.svelte | 2 +- src/page/utils.js | 2 +- src/popup/App.svelte | 5 +- src/popup/Components/PopupItem.svelte | 3 +- .../Components/Views/AllItemsView.svelte | 1 + src/shared/Components/Tag.svelte | 9 + src/shared/dev.js | 74 ++++--- 17 files changed, 375 insertions(+), 77 deletions(-) diff --git a/extension/Userscripts Extension/Functions.swift b/extension/Userscripts Extension/Functions.swift index b7b612e1..30a6a022 100644 --- a/extension/Userscripts Extension/Functions.swift +++ b/extension/Userscripts Extension/Functions.swift @@ -246,6 +246,7 @@ func parse(_ content: String) -> [String: Any]? { // manifest struct Manifest: Codable { var blacklist:[String] + var declarativeNetRequest: [String] var disabled:[String] var exclude: [String: [String]] var excludeMatch: [String: [String]] @@ -254,7 +255,7 @@ struct Manifest: Codable { var require: [String: [String]] var settings: [String: String] private enum CodingKeys : String, CodingKey { - case blacklist, disabled, exclude, excludeMatch = "exclude-match", include, match, require, settings + case blacklist, declarativeNetRequest, disabled, exclude, excludeMatch = "exclude-match", include, match, require, settings } } @@ -301,6 +302,7 @@ func getManifest() -> Manifest { // create new manifest with default key/vals let manifest = Manifest( blacklist: [], + declarativeNetRequest: [], disabled: [], exclude: [:], excludeMatch: [:], @@ -329,6 +331,11 @@ func updateManifestMatches(_ optionalFilesArray: [[String: Any]] = []) -> Bool { // can be force unwrapped because getAllFiles didn't return nil let metadata = file["metadata"] as! [String: [String]] let filename = file["filename"] as! String + // skip request type userscripts + let runAt = metadata["run-at"]?[0] ?? "document-end" + if runAt == "request" { + continue + } // populate excludes & matches var excludeMatched = [String]() var matched = [String]() @@ -346,6 +353,14 @@ func updateManifestMatches(_ optionalFilesArray: [[String: Any]] = []) -> Bool { if metadata["exclude"] != nil { excluded.append(contentsOf: metadata["exclude"]!) } + // if in declarativeNetRequest array, remove it + if manifest.declarativeNetRequest.contains(filename) { + if let index = manifest.declarativeNetRequest.firstIndex(of: filename) { + manifest.declarativeNetRequest.remove(at: index) + } else { + err("failed to remove \(filename) from declarativeNetRequest array") + } + } // update manifest values manifest.excludeMatch = updatePatternDict(filename, excludeMatched, manifest.excludeMatch) @@ -470,6 +485,85 @@ func updateManifestRequired(_ optionalFilesArray: [[String: Any]] = []) -> Bool return true } +func updateManifestDeclarativeNetRequests(_ optionalFilesArray: [[String: Any]] = []) -> Bool { + logText("updateManifestDeclarativeNetRequests started") + var files = [[String: Any]]() + if optionalFilesArray.isEmpty { + guard let getFiles = getAllFiles() else { + err("updateManifestDeclarativeNetRequests failed at (1)") + return false + } + files = getFiles + } else { + files = optionalFilesArray + } + var manifest = getManifest() + for file in files { + // can be force unwrapped because getAllFiles didn't return nil + // and getAllFiles always returns the following + let metadata = file["metadata"] as! [String: [String]] + let filename = file["filename"] as! String + let runAt = metadata["run-at"]?[0] ?? "document-end" + // if not a request type, ignore + if runAt != "request" { + continue + } + var update = false + // if filename already in manifest + if !manifest.declarativeNetRequest.contains(filename) { + manifest.declarativeNetRequest.append(filename) + update = true + } + // if filename in another array remove it + for (pattern, filenames) in manifest.match { + for fn in filenames { + if fn == filename, let index = manifest.match[pattern]?.firstIndex(of: filename) { + manifest.match[pattern]?.remove(at: index) + update = true + } else { + err("updateManifestDeclarativeNetRequests failed at (2), \(filename)") + } + } + } + for (pattern, filenames) in manifest.excludeMatch { + for fn in filenames { + if fn == filename, let index = manifest.excludeMatch[pattern]?.firstIndex(of: filename) { + manifest.excludeMatch[pattern]?.remove(at: index) + update = true + } else { + err("updateManifestDeclarativeNetRequests failed at (3), \(filename)") + } + } + } + for (pattern, filenames) in manifest.include { + for fn in filenames { + if fn == filename, let index = manifest.include[pattern]?.firstIndex(of: filename) { + manifest.include[pattern]?.remove(at: index) + update = true + } else { + err("updateManifestDeclarativeNetRequests failed at (4), \(filename)") + } + } + } + for (pattern, filenames) in manifest.exclude { + for fn in filenames { + if fn == filename, let index = manifest.exclude[pattern]?.firstIndex(of: filename) { + manifest.exclude[pattern]?.remove(at: index) + update = true + } else { + err("updateManifestDeclarativeNetRequests failed at (5), \(filename)") + } + } + } + if update, !updateManifest(with: manifest) { + err("updateManifestDeclarativeNetRequests failed at (6)") + return false + } + } + logText("updateManifestDeclarativeNetRequests complete") + return true +} + func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool { logText("purgeManifest started") // purge all manifest keys of any stale entries @@ -583,6 +677,16 @@ func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool { } } } + // loop through manifest declarativeNetRequest + for filename in manifest.declarativeNetRequest { + if !allSaveLocationFilenames.contains(filename) { + if let index = manifest.declarativeNetRequest.firstIndex(of: filename) { + manifest.declarativeNetRequest.remove(at: index) + update = true + logText("Could not find \(filename) in save location, removed from declarativeNetRequest") + } + } + } // remove obsolete settings for setting in manifest.settings { if !defaultSettings.keys.contains(setting.key) { @@ -630,7 +734,7 @@ func updateSettings(_ settings: [String: String]) -> Bool { } // files -func getAllFiles() -> [[String: Any]]? { +func getAllFiles(includeCode: Bool = false) -> [[String: Any]]? { // returns all files of proper type with filenames, metadata & more var files = [[String: Any]]() let fm = FileManager.default @@ -673,9 +777,14 @@ func getAllFiles() -> [[String: Any]]? { fileData["filename"] = filename fileData["lastModified"] = dateToMilliseconds(dateMod) fileData["metadata"] = metadata - // for unwrap name since parse ensure it exists + // force unwrap name since parser ensure it exists fileData["name"] = metadata["name"]![0] fileData["type"] = "\(type)" + // add extra data if a request userscript + let runAt = metadata["run-at"]?[0] ?? "document-end" + if runAt == "request" { + fileData["request"] = true + } if metadata["description"] != nil { fileData["description"] = metadata["description"]![0] } @@ -683,6 +792,11 @@ func getAllFiles() -> [[String: Any]]? { fileData["canUpdate"] = true } fileData["noframes"] = metadata["noframes"] != nil ? true : false + // if asked, also return the code string + if (includeCode) { + // can force unwrap because always returned from parser + fileData["code"] = parsed["code"] as! String + } files.append(fileData) } logText("getAllFiles completed") @@ -1135,14 +1249,19 @@ func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? { continue } + // get run-at values and set default if missing + // if type request, ignore + var runAt = metadata["run-at"]?[0] ?? "document-end" + if runAt == "request" { + continue + } + // normalize weight var weight = metadata["weight"]?[0] ?? "1" weight = normalizeWeight(weight) - // get inject-into and run-at values - // if either is missing, use default value + // get inject-into and set default if missing var injectInto = metadata["inject-into"]?[0] ?? "auto" - var runAt = metadata["run-at"]?[0] ?? "document-end" let injectVals: Set = ["auto", "content", "page"] let runAtVals: Set = ["context-menu", "document-start", "document-end", "document-idle"] // if either is invalid use default value @@ -1288,6 +1407,40 @@ func getInjectionFilenames(_ url: String) -> [String]? { return filenames } +func getRequestScripts() -> [[String: String]]? { + var requestScripts = [[String: String]]() + // check the manifest to see if injection is enabled + let manifest = getManifest() + guard let active = manifest.settings["active"] else { + err("getRequestScripts failed at (1)") + return nil + } + // if not enabled, do not apply any net requests, ie. return empty array + if active != "true" { + return requestScripts + } + guard let files = getAllFiles(includeCode: true) else { + err("getRequestScripts failed at (2)") + return nil + } + for file in files { + let isRequest = file["request"] as? Bool ?? false + // skip any non-request userscripts + if !isRequest { + continue + } + // can be force unwrapped because getAllFiles always returns these + let name = file["name"] as! String + let code = file["code"] as! String + let filename = file["filename"] as! String + + if !manifest.disabled.contains(filename) { + requestScripts.append(["name": name, "code": code]) + } + } + return requestScripts +} + // popup func getPopupMatches(_ url: String, _ subframeUrls: [String]) -> [[String: Any]]? { var matches = [[String: Any]]() @@ -1539,7 +1692,13 @@ func saveFile(_ item: [String: Any],_ content: String) -> [String: Any] { } // update manifest for new file and purge anything from old file - guard updateManifestMatches(), updateManifestRequired(), purgeManifest() else { + guard + let allFiles = getAllFiles(), + updateManifestMatches(allFiles), + updateManifestRequired(allFiles), + updateManifestDeclarativeNetRequests(allFiles), + purgeManifest(allFiles) + else { err("saveFile failed at (4)") return ["error": "file save but manifest couldn't be updated"] } @@ -1559,6 +1718,11 @@ func saveFile(_ item: [String: Any],_ content: String) -> [String: Any] { if metadata["version"] != nil && metadata["updateURL"] != nil { response["canUpdate"] = true } + // if a request "type" userscript add key/val + let runAt = metadata["run-at"]?[0] ?? "document-end" + if runAt == "request" { + response["request"] = true + } return response } @@ -1663,6 +1827,8 @@ func popupInit() -> [String: String]? { let updateManifestMatches = updateManifestMatches(allFiles) // update the required resources let updateManifestRequired = updateManifestRequired(allFiles) + // update declarativeNetRequest + let updateDeclarativeNetRequest = updateManifestDeclarativeNetRequests(allFiles) // verbose error checking if !checkDefaultDirectories { err("Failed to checkDefaultDirectories in popupInit") @@ -1684,6 +1850,10 @@ func popupInit() -> [String: String]? { err("Failed to updateManifestRequired in popupInit") return nil } + if !updateDeclarativeNetRequest { + err("Failed to updateDeclarativeNetRequest in popupInit") + return nil + } let manifest = getManifest() guard let active = manifest.settings["active"] else { err("Failed at getManifest active in popupInit") diff --git a/extension/Userscripts Extension/Resources/background.js b/extension/Userscripts Extension/Resources/background.js index 62407968..ac126e8c 100644 --- a/extension/Userscripts Extension/Resources/background.js +++ b/extension/Userscripts Extension/Resources/background.js @@ -477,7 +477,10 @@ async function setBadgeCount() { if (!currentTab) return; const url = currentTab.url; // if url doesn't exist, stop - if (!url) return; + if (!url) { + browser.browserAction.setBadgeText({text: ""}); + return; + } // only check for http/s pages if (!url.startsWith("http://") && !url.startsWith("https://")) { browser.browserAction.setBadgeText({text: ""}); @@ -631,8 +634,58 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => { }); return true; } + case "REFRESH_SESSION_RULES": { + setSessionRules(); + } + } +}); +async function setSessionRules() { + await clearAllSessionRules(); + const response = await browser.runtime.sendNativeMessage({name: "REQ_REQUESTS"}); + if (response.error) { + console.error(response.error); + return; + } + console.log(response); + // there are no rules to apply + if (!response.length) return; + // loop through response, parse the rules, push to array and log + const rules = []; + for (let i = 0; i < response.length; i++) { + const rule = response[i]; + rules.push(JSON.parse(rule.code)); + console.info(`Setting session rule: ${rule.name}`); + } + console.log(rules); + try { + await browser.declarativeNetRequest.updateSessionRules({ + addRules: rules + }); + } catch (error) { + console.error(`Error setting session rules: ${error}`); + return; } + console.info("done setting session rules"); +} + +async function clearAllSessionRules() { + const rules = await browser.declarativeNetRequest.getSessionRules(); + if (!rules.length) return; + console.log(`Clearing ${rules.length} session rules`); + const ruleIds = rules.map(a => a.id); + await browser.declarativeNetRequest.updateSessionRules({ + removeRuleIds: ruleIds + }); +} + +browser.runtime.onStartup.addListener(async () => { + // on startup get declarativeNetRequests + // and set the requests for the session + // should also check and refresh when: + // 1. popup opens (done) 2. the extension page is opened (TODO) + // 3. a new save event in the page occurs (TODO) 4. the refresh button is pushed in the popup (TODO) + await setSessionRules(); }); browser.tabs.onActivated.addListener(setBadgeCount); browser.windows.onFocusChanged.addListener(setBadgeCount); diff --git a/extension/Userscripts Extension/Resources/manifest.json b/extension/Userscripts Extension/Resources/manifest.json index 6071c77c..563d724c 100644 --- a/extension/Userscripts Extension/Resources/manifest.json +++ b/extension/Userscripts Extension/Resources/manifest.json @@ -35,6 +35,7 @@ "", "clipboardWrite", "contextMenus", + "declarativeNetRequest", "menus", "nativeMessaging", "storage", diff --git a/extension/Userscripts Extension/Resources/page.html b/extension/Userscripts Extension/Resources/page.html index 7e7973d6..911691ed 100644 --- a/extension/Userscripts Extension/Resources/page.html +++ b/extension/Userscripts Extension/Resources/page.html @@ -1 +1 @@ -Userscripts Safari
    \ No newline at end of file +Userscripts Safari
    \ No newline at end of file diff --git a/extension/Userscripts Extension/Resources/page.js b/extension/Userscripts Extension/Resources/page.js index 8f8e7895..7a384309 100644 --- a/extension/Userscripts Extension/Resources/page.js +++ b/extension/Userscripts Extension/Resources/page.js @@ -1933,13 +1933,13 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o return { c() { div = element("div"); - attr(div, "class", div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-p9vdxd"); + attr(div, "class", div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-1rzbr97"); }, m(target, anchor) { insert(target, div, anchor); }, p(ctx, [dirty]) { - if (dirty & /*type*/ 1 && div_class_value !== (div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-p9vdxd")) { + if (dirty & /*type*/ 1 && div_class_value !== (div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-1rzbr97")) { attr(div, "class", div_class_value); } }, @@ -2124,7 +2124,14 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o let current; let mounted; let dispose; - tag = new Tag({ props: { type: /*data*/ ctx[0].type } }); + + tag = new Tag({ + props: { + type: /*data*/ ctx[0].request + ? "request" + : /*data*/ ctx[0].type + } + }); toggle = new Toggle({ props: { checked: !/*data*/ ctx[0].disabled } @@ -2179,7 +2186,11 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o p(new_ctx, [dirty]) { ctx = new_ctx; const tag_changes = {}; - if (dirty & /*data*/ 1) tag_changes.type = /*data*/ ctx[0].type; + + if (dirty & /*data*/ 1) tag_changes.type = /*data*/ ctx[0].request + ? "request" + : /*data*/ ctx[0].type; + tag.$set(tag_changes); if ((!current || dirty & /*data*/ 1) && t1_value !== (t1_value = /*data*/ ctx[0].name + "")) set_data(t1, t1_value); const toggle_changes = {}; @@ -17880,7 +17891,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o return child_ctx; } - // (180:8) + // (181:8) function create_default_slot$1(ctx) { let li0; let t1; @@ -17931,7 +17942,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (187:8) {#if $state.includes("items-loading")} + // (188:8) {#if $state.includes("items-loading")} function create_if_block_1(ctx) { let loader; let current; @@ -17960,7 +17971,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (190:8) {#each list as item (item.filename)} + // (191:8) {#each list as item (item.filename)} function create_each_block(key_1, ctx) { let first; let switch_instance; @@ -18058,7 +18069,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (199:4) {#if showCount} + // (200:4) {#if showCount} function create_if_block$5(ctx) { let div; let t0_value = /*list*/ ctx[3].length + ""; @@ -18430,7 +18441,8 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o await tick(); const cm = cmGetInstance(); - const mode = item.type === "js" ? "javascript" : item.type; + let mode = "javascript"; + if (item.type === "css") mode = "css"; cm.setOption("mode", mode); cm.setValue(item.content); cm.clearHistory(); @@ -19233,7 +19245,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o $$invalidate(1, name = activeItem.name); $$invalidate(4, remote = activeItem.remote); $$invalidate(5, temp = activeItem.temp); - $$invalidate(2, type = activeItem.type); + $$invalidate(2, type = activeItem.request ? "request" : activeItem.type); $$invalidate(0, canUpdate = activeItem.canUpdate); // on load if temp item, disabled discard and enable save, if not disable both diff --git a/extension/Userscripts Extension/Resources/popup.html b/extension/Userscripts Extension/Resources/popup.html index 5ebce0d5..e915b271 100644 --- a/extension/Userscripts Extension/Resources/popup.html +++ b/extension/Userscripts Extension/Resources/popup.html @@ -1 +1 @@ -Userscripts Safari
    \ No newline at end of file +Userscripts Safari
    \ No newline at end of file diff --git a/extension/Userscripts Extension/Resources/popup.js b/extension/Userscripts Extension/Resources/popup.js index 9bbb846f..e7e97f30 100644 --- a/extension/Userscripts Extension/Resources/popup.js +++ b/extension/Userscripts Extension/Resources/popup.js @@ -1115,13 +1115,13 @@ return { c() { div = element("div"); - attr(div, "class", div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-p9vdxd"); + attr(div, "class", div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-1rzbr97"); }, m(target, anchor) { insert(target, div, anchor); }, p(ctx, [dirty]) { - if (dirty & /*type*/ 1 && div_class_value !== (div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-p9vdxd")) { + if (dirty & /*type*/ 1 && div_class_value !== (div_class_value = "script__tag " + `script__tag--${/*type*/ ctx[0]}` + " svelte-1rzbr97")) { attr(div, "class", div_class_value); } }, @@ -1184,7 +1184,12 @@ let mounted; let dispose; let if_block = /*subframe*/ ctx[3] && create_if_block$1(); - tag = new Tag({ props: { type: /*type*/ ctx[2] } }); + + tag = new Tag({ + props: { + type: /*request*/ ctx[4] ? "request" : /*type*/ ctx[2] + } + }); return { c() { @@ -1214,7 +1219,7 @@ current = true; if (!mounted) { - dispose = listen(div1, "click", /*click_handler*/ ctx[4]); + dispose = listen(div1, "click", /*click_handler*/ ctx[5]); mounted = true; } }, @@ -1233,7 +1238,7 @@ } const tag_changes = {}; - if (dirty & /*type*/ 4) tag_changes.type = /*type*/ ctx[2]; + if (dirty & /*request, type*/ 20) tag_changes.type = /*request*/ ctx[4] ? "request" : /*type*/ ctx[2]; tag.$set(tag_changes); if (!current || dirty & /*enabled*/ 1 && div1_class_value !== (div1_class_value = "item " + (/*enabled*/ ctx[0] ? "enabled" : "disabled") + " svelte-51jdr7")) { @@ -1264,6 +1269,7 @@ let { name } = $$props; let { type } = $$props; let { subframe } = $$props; + let { request = false } = $$props; function click_handler(event) { bubble($$self, event); @@ -1274,9 +1280,10 @@ if ("name" in $$props) $$invalidate(1, name = $$props.name); if ("type" in $$props) $$invalidate(2, type = $$props.type); if ("subframe" in $$props) $$invalidate(3, subframe = $$props.subframe); + if ("request" in $$props) $$invalidate(4, request = $$props.request); }; - return [enabled, name, type, subframe, click_handler]; + return [enabled, name, type, subframe, request, click_handler]; } class PopupItem extends SvelteComponent { @@ -1287,7 +1294,8 @@ enabled: 0, name: 1, type: 2, - subframe: 3 + subframe: 3, + request: 4 }); } } @@ -2585,7 +2593,7 @@ return child_ctx; } - // (32:0) {:else} + // (33:0) {:else} function create_else_block$2(ctx) { let div; @@ -2700,7 +2708,8 @@ enabled: !/*item*/ ctx[6].disabled, name: /*item*/ ctx[6].name, subframe: /*item*/ ctx[6].subframe, - type: /*item*/ ctx[6].type + type: /*item*/ ctx[6].type, + request: /*item*/ ctx[6].request ? true : false } }); @@ -2726,6 +2735,7 @@ if (dirty & /*list*/ 8) popupitem_changes.name = /*item*/ ctx[6].name; if (dirty & /*list*/ 8) popupitem_changes.subframe = /*item*/ ctx[6].subframe; if (dirty & /*list*/ 8) popupitem_changes.type = /*item*/ ctx[6].type; + if (dirty & /*list*/ 8) popupitem_changes.request = /*item*/ ctx[6].request ? true : false; popupitem.$set(popupitem_changes); }, i(local) { @@ -2867,12 +2877,12 @@ return child_ctx; } - // (497:0) {#if !active} + // (499:0) {#if !active} function create_if_block_10(ctx) { return { c: noop, m: noop, d: noop }; } - // (500:0) {#if showInstallPrompt} + // (502:0) {#if showInstallPrompt} function create_if_block_9(ctx) { let div; let t0; @@ -2914,7 +2924,7 @@ }; } - // (505:0) {#if error} + // (507:0) {#if error} function create_if_block_8(ctx) { let div; let t0; @@ -2964,7 +2974,7 @@ }; } - // (533:8) {:else} + // (535:8) {:else} function create_else_block$3(ctx) { let div; let each_blocks = []; @@ -3037,7 +3047,7 @@ }; } - // (531:35) + // (533:35) function create_if_block_7(ctx) { let div; @@ -3059,7 +3069,7 @@ }; } - // (521:28) + // (523:28) function create_if_block_6$1(ctx) { let div; let t0; @@ -3097,7 +3107,7 @@ }; } - // (519:8) {#if inactive} + // (521:8) {#if inactive} function create_if_block_5$1(ctx) { let div; @@ -3119,7 +3129,7 @@ }; } - // (516:4) {#if loading} + // (518:4) {#if loading} function create_if_block_4$1(ctx) { let loader; let current; @@ -3159,7 +3169,7 @@ }; } - // (535:16) {#each list as item (item.filename)} + // (537:16) {#each list as item (item.filename)} function create_each_block$3(key_1, ctx) { let first; let popupitem; @@ -3174,7 +3184,8 @@ enabled: !/*item*/ ctx[48].disabled, name: /*item*/ ctx[48].name, subframe: /*item*/ ctx[48].subframe, - type: /*item*/ ctx[48].type + type: /*item*/ ctx[48].type, + request: /*item*/ ctx[48].request ? true : false } }); @@ -3200,6 +3211,7 @@ if (dirty[0] & /*list*/ 4194304) popupitem_changes.name = /*item*/ ctx[48].name; if (dirty[0] & /*list*/ 4194304) popupitem_changes.subframe = /*item*/ ctx[48].subframe; if (dirty[0] & /*list*/ 4194304) popupitem_changes.type = /*item*/ ctx[48].type; + if (dirty[0] & /*list*/ 4194304) popupitem_changes.request = /*item*/ ctx[48].request ? true : false; popupitem.$set(popupitem_changes); }, i(local) { @@ -3218,7 +3230,7 @@ }; } - // (548:0) {#if !inactive && platform === "macos"} + // (551:0) {#if !inactive && platform === "macos"} function create_if_block_3$1(ctx) { let div1; let div0; @@ -3251,7 +3263,7 @@ }; } - // (585:18) + // (588:18) function create_if_block_2$1(ctx) { let view; let current; @@ -3301,7 +3313,7 @@ }; } - // (571:22) + // (574:22) function create_if_block_1$1(ctx) { let view; let current; @@ -3351,7 +3363,7 @@ }; } - // (555:0) {#if showUpdates} + // (558:0) {#if showUpdates} function create_if_block$6(ctx) { let view; let current; @@ -3404,7 +3416,7 @@ }; } - // (586:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > + // (589:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > function create_default_slot_2(ctx) { let allitemsview; let current; @@ -3444,7 +3456,7 @@ }; } - // (572:4) showInstall = false} showLoaderOnDisabled={true} > + // (575:4) showInstall = false} showLoaderOnDisabled={true} > function create_default_slot_1(ctx) { let installview; let current; @@ -3488,7 +3500,7 @@ }; } - // (556:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > + // (559:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > function create_default_slot(ctx) { let updateview; let current; @@ -4114,7 +4126,6 @@ } else if (response.items) { $$invalidate(19, showAll = true); $$invalidate(20, allItems = response.items); - $$invalidate(20, allItems = []); } else if (response.error) { console.log(`Error opening save location: ${response.error}`); $$invalidate(0, error = response.error); @@ -4168,6 +4179,9 @@ $$invalidate(1, active = init.initData.active === "true" ? true : false); } + // refresh session rules + browser.runtime.sendMessage({ name: "REFRESH_SESSION_RULES" }); + // set popup height resize(); diff --git a/extension/Userscripts Extension/SafariWebExtensionHandler.swift b/extension/Userscripts Extension/SafariWebExtensionHandler.swift index 6c5a0737..0f2117f7 100644 --- a/extension/Userscripts Extension/SafariWebExtensionHandler.swift +++ b/extension/Userscripts Extension/SafariWebExtensionHandler.swift @@ -34,6 +34,13 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { inBoundError = true } } + else if name == "REQ_REQUESTS" { + if let requestScripts = getRequestScripts() { + response.userInfo = [SFExtensionMessageKey: requestScripts] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get requestScripts"]] + } + } else if name == "POPUP_BADGE_COUNT" { #if os(macOS) if let url = message?["url"] as? String, let frameUrls = message?["frameUrls"] as? [String] { diff --git a/src/page/Components/Editor/Editor.svelte b/src/page/Components/Editor/Editor.svelte index 3f40c7cf..74302f5b 100644 --- a/src/page/Components/Editor/Editor.svelte +++ b/src/page/Components/Editor/Editor.svelte @@ -30,7 +30,7 @@ name = activeItem.name; remote = activeItem.remote; temp = activeItem.temp; - type = activeItem.type; + type = activeItem.request ? "request" : activeItem.type; canUpdate = activeItem.canUpdate; // on load if temp item, disabled discard and enable save, if not disable both if (temp) { diff --git a/src/page/Components/Sidebar/Sidebar.svelte b/src/page/Components/Sidebar/Sidebar.svelte index 5a622c6b..5591ba2b 100644 --- a/src/page/Components/Sidebar/Sidebar.svelte +++ b/src/page/Components/Sidebar/Sidebar.svelte @@ -95,7 +95,8 @@ // set up editor after activating file await tick(); const cm = cmGetInstance(); - const mode = item.type === "js" ? "javascript" : item.type; + let mode = "javascript"; + if (item.type === "css") mode = "css"; cm.setOption("mode", mode); cm.setValue(item.content); cm.clearHistory(); diff --git a/src/page/Components/Sidebar/SidebarItem.svelte b/src/page/Components/Sidebar/SidebarItem.svelte index 8b7f836d..06f24c78 100644 --- a/src/page/Components/Sidebar/SidebarItem.svelte +++ b/src/page/Components/Sidebar/SidebarItem.svelte @@ -35,7 +35,7 @@ on:click >
    - +
    {data.name}
    diff --git a/src/page/utils.js b/src/page/utils.js index 0fccebd6..c8ff9225 100644 --- a/src/page/utils.js +++ b/src/page/utils.js @@ -19,7 +19,7 @@ export function uniqueId() { /** * awaitable function for waiting an arbitrary amount of time * @param {number} ms the amount of time to wait in milliseconds - * @returns {Promise.} + * @returns {Promise} */ export function wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); diff --git a/src/popup/App.svelte b/src/popup/App.svelte index 51b9981b..f5d1e72f 100644 --- a/src/popup/App.svelte +++ b/src/popup/App.svelte @@ -215,7 +215,6 @@ } else if (response.items) { showAll = true; allItems = response.items; - allItems = []; } else if (response.error) { console.log(`Error opening save location: ${response.error}`); error = response.error; @@ -264,6 +263,9 @@ active = init.initData.active === "true" ? true : false; } + // refresh session rules + browser.runtime.sendMessage({name: "REFRESH_SESSION_RULES"}); + // set popup height resize(); @@ -538,6 +540,7 @@ name={item.name} subframe={item.subframe} type={item.type} + request={item.request ? true : false} on:click={() => toggleItem(item)} /> {/each} diff --git a/src/popup/Components/PopupItem.svelte b/src/popup/Components/PopupItem.svelte index 848849df..0cd46cd6 100644 --- a/src/popup/Components/PopupItem.svelte +++ b/src/popup/Components/PopupItem.svelte @@ -4,13 +4,14 @@ export let name; export let type; export let subframe; + export let request = false;
    {name}
    {#if subframe}
    SUB
    {/if} - +
    diff --git a/src/shared/dev.js b/src/shared/dev.js index 3d0b7655..19de88f4 100644 --- a/src/shared/dev.js +++ b/src/shared/dev.js @@ -7,20 +7,20 @@ import {parse, uniqueId, wait} from "../page/utils"; * @param {boolean?} longName * @returns */ -function generateFile(type, updates = false, longName = false) { +function generateFile(type, updates = false, longName = false, request = false) { const uid = uniqueId(); - let name = `${uid}-example-${type}`; + let name = request ? `${uid}-request-${type}` : `${uid}-example-${type}`; if (longName) name = `${uid}${uid}${uid}-example-${type}`; - const randomDate = +(new Date() - Math.floor(Math.random()*10000000000)); + const randomDate = +(new Date() - Math.floor(Math.random() * 10000000000)); let content = "// ==UserScript==" - +`\n// @name ${name}` - +`\n// @description Custom description for userscript with name "${name}"` - +"\n// @match *://*.*" - +"\n// @exclude-match https://github.com/quoid/userscripts" - +"\n// @version 1.0" - +"\n// @noframes" - +"\n// ==/UserScript==" - +`\n\nconsole.log("I am ${name}");`; + + `\n// @name ${name}` + + `\n// @description Custom description for userscript with name "${name}"` + + "\n// @match *://*.*" + + "\n// @exclude-match https://github.com/quoid/userscripts" + + "\n// @version 1.0" + + "\n// @noframes" + + "\n// ==/UserScript==" + + `\n\nconsole.log("I am ${name}");`; if (type === "css") { content = content.replace("// ==UserScript==", "/* ==UserStyle=="); content = content.replace("// ==/UserScript==", "==/UserStyle== */"); @@ -31,6 +31,9 @@ function generateFile(type, updates = false, longName = false) { ); updates = false; } + if (request) { + content = content.replace("// @noframes", "// @noframes\n// @run-at request"); + } if (updates) { content = content.replace( "1.0", @@ -44,14 +47,18 @@ function generateFile(type, updates = false, longName = false) { return { content: content, filename: `${name}.${type}`, - lastModified: randomDate + lastModified: randomDate, + name: name, + request: request, + type: type }; } const files = [ generateFile("js", true, true), generateFile("css"), - ...Array.from({length: 5}, (_, i) => generateFile("js")) + generateFile("js", false, false, true), + ...Array.from({length: 7}, (_, i) => generateFile("js")) ]; const _browser = { @@ -61,6 +68,18 @@ const _browser = { getURL() { return "https://www.example.com/"; }, + async sendMessage(message, responseCallback) { + const name = message.name; + console.info(`Got message: ${name}`); + let response = {}; + if (name === "REFRESH_SESSION_RULES") { + response = {success: true}; + } + if (!responseCallback) { + return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); + } + setTimeout(() => responseCallback(response), _browser.delay); + }, async sendNativeMessage(message, responseCallback) { const name = message.name; console.info(`Got message: ${name}`); @@ -99,7 +118,9 @@ const _browser = { lastModified: file.lastModified, metadata: metadata, name: metadata.name[0], - type: file.filename.substring(file.filename.lastIndexOf(".") + 1) + request: file.request, + // type: file.filename.substring(file.filename.lastIndexOf(".") + 1) + type: file.type }; response.push(scriptData); }); @@ -114,7 +135,6 @@ const _browser = { const result = await getRemoteFileContents(message.url); response = result; } else if (name === "PAGE_SAVE") { - console.log(message); const newContent = message.content; const oldFilename = message.item.filename; const parsed = parse(newContent); @@ -141,14 +161,20 @@ const _browser = { content: newContent, filename: newFilename, lastModified: lastModified, - name: name, + name: name }; // add description if in file metadata if (parsed.metadata.description) { success.description = parsed.metadata.description[0]; } - + // check if declarative network request + if ( + parsed.metadata["run-at"] + && parsed.metadata["run-at"][0] === "request" + ) { + success.request = true; + } // overwriting if (newFilename.toLowerCase() === oldFilename.toLowerCase()) { saveFile(newContent, lastModified, newFilename, oldFilename); @@ -264,20 +290,16 @@ const _browser = { ] }; } else if (name === "POPUP_MATCHES") { + console.log(files); response = { matches: [ - { - name: "Google Images Restored", - filename: "Google Images Restored.js", - disabled: false, - type: "js" - }, + ...files, { name: "Subframe Script Managerial Staffing Company", filename: "Subframe Script.js", disabled: false, subframe: true, - type: "css" + type: "js" } ] }; @@ -340,6 +362,10 @@ const _browser = { } }, tabs: { + getCurrent(responseCallback) { + const response = {url: "https://www.filmgarb.com/foo.user.js", id: 101}; + return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); + }, query(message, responseCallback) { const response = [{url: "https://www.filmgarb.com/foo.user.js", id: 101}]; if (!responseCallback) { From eba8062a380655816cb9775f2cf3d216d9bc7f0c Mon Sep 17 00:00:00 2001 From: quoid Date: Sat, 23 Apr 2022 22:40:38 -0400 Subject: [PATCH 33/47] allow array like request rule sets, make request rule ids unique --- .../Resources/background.js | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/extension/Userscripts Extension/Resources/background.js b/extension/Userscripts Extension/Resources/background.js index ac126e8c..2c52cc41 100644 --- a/extension/Userscripts Extension/Resources/background.js +++ b/extension/Userscripts Extension/Resources/background.js @@ -647,44 +647,61 @@ async function setSessionRules() { console.error(response.error); return; } - console.log(response); // there are no rules to apply if (!response.length) return; // loop through response, parse the rules, push to array and log const rules = []; for (let i = 0; i < response.length; i++) { const rule = response[i]; - rules.push(JSON.parse(rule.code)); - console.info(`Setting session rule: ${rule.name}`); + const code = JSON.parse(rule.code); + // check if an array or single rule + if (Array.isArray(code)) { + code.forEach(rule => rules.push(rule)); + console.info(`Setting session rule: ${rule.name} (${code.length})`); + } else { + rules.push(code); + console.info(`Setting session rule: ${rule.name}`); + } } - console.log(rules); + // generate unique ids for all rules to ensure no repeats + const ids = randomNumberSet(1000, rules.length); + rules.map((rule, index) => rule.id = ids[index]); try { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: rules - }); + await browser.declarativeNetRequest.updateSessionRules({addRules: rules}); } catch (error) { console.error(`Error setting session rules: ${error}`); return; } - console.info("done setting session rules"); + console.info(`Finished setting ${rules.length} session rules`); } async function clearAllSessionRules() { const rules = await browser.declarativeNetRequest.getSessionRules(); if (!rules.length) return; - console.log(`Clearing ${rules.length} session rules`); + console.info(`Clearing ${rules.length} session rules`); const ruleIds = rules.map(a => a.id); await browser.declarativeNetRequest.updateSessionRules({ removeRuleIds: ruleIds }); } +function randomNumberSet(max, count) { + // generates a set of random unique numbers + // returns an array + const numbers = new Set(); + while (numbers.size < count) { + numbers.add((Math.floor(Math.random() * (max - 1 + 1)) + 1)); + } + return [...numbers]; +} + browser.runtime.onStartup.addListener(async () => { // on startup get declarativeNetRequests // and set the requests for the session // should also check and refresh when: - // 1. popup opens (done) 2. the extension page is opened (TODO) - // 3. a new save event in the page occurs (TODO) 4. the refresh button is pushed in the popup (TODO) + // 1. popup opens (done) + // 2. a new save event in the page occurs + // 3. the refresh button is pushed in the popup await setSessionRules(); }); browser.tabs.onActivated.addListener(setBadgeCount); From 562566dbdb1f83ce0191438f91e54d0bcf36ac4f Mon Sep 17 00:00:00 2001 From: quoid Date: Sat, 23 Apr 2022 22:40:54 -0400 Subject: [PATCH 34/47] update session rules when request userscript saved --- .../Userscripts Extension/Resources/page.html | 2 +- .../Userscripts Extension/Resources/page.js | 19 ++++++++++++------- src/page/Components/Editor/Editor.svelte | 4 ++++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/extension/Userscripts Extension/Resources/page.html b/extension/Userscripts Extension/Resources/page.html index 911691ed..7761b95e 100644 --- a/extension/Userscripts Extension/Resources/page.html +++ b/extension/Userscripts Extension/Resources/page.html @@ -1 +1 @@ -Userscripts Safari
    \ No newline at end of file +Userscripts Safari
    \ No newline at end of file diff --git a/extension/Userscripts Extension/Resources/page.js b/extension/Userscripts Extension/Resources/page.js index 7a384309..8b736049 100644 --- a/extension/Userscripts Extension/Resources/page.js +++ b/extension/Userscripts Extension/Resources/page.js @@ -18613,7 +18613,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (154:4) {#if !activeItem} + // (158:4) {#if !activeItem} function create_if_block_5(ctx) { let div; @@ -18632,7 +18632,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (175:20) {:else} + // (179:20) {:else} function create_else_block(ctx) { let t0; let t1; @@ -18656,7 +18656,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (173:35) + // (177:35) function create_if_block_4(ctx) { let t; @@ -18674,7 +18674,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (171:37) + // (175:37) function create_if_block_3(ctx) { let t0; let span; @@ -18709,7 +18709,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (169:58) + // (173:58) function create_if_block_2(ctx) { let t0; let span; @@ -18742,7 +18742,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (167:58) + // (171:58) function create_if_block_1$1(ctx) { let t; @@ -18760,7 +18760,7 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o }; } - // (165:20) {#if $state.includes("saving")} + // (169:20) {#if $state.includes("saving")} function create_if_block$6(ctx) { let t; @@ -19127,6 +19127,11 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o // set the newly saved file contents in codemirror instance cmSetSavedCode(response.content); + + // update session declarativeNetRequest rules if needed + if (response.request) { + browser.runtime.sendMessage({ name: "REFRESH_SESSION_RULES" }); + } } state.remove("saving"); diff --git a/src/page/Components/Editor/Editor.svelte b/src/page/Components/Editor/Editor.svelte index 74302f5b..e37d22d8 100644 --- a/src/page/Components/Editor/Editor.svelte +++ b/src/page/Components/Editor/Editor.svelte @@ -69,6 +69,10 @@ }); // set the newly saved file contents in codemirror instance cmSetSavedCode(response.content); + // update session declarativeNetRequest rules if needed + if (response.request) { + browser.runtime.sendMessage({name: "REFRESH_SESSION_RULES"}); + } } state.remove("saving"); } From b73b3449981ef5901b7f5a16bd666c473a226d26 Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 24 Apr 2022 17:21:36 -0400 Subject: [PATCH 35/47] update build number --- extension/Userscripts.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/extension/Userscripts.xcodeproj/project.pbxproj b/extension/Userscripts.xcodeproj/project.pbxproj index 6a3a4cee..74d4813b 100644 --- a/extension/Userscripts.xcodeproj/project.pbxproj +++ b/extension/Userscripts.xcodeproj/project.pbxproj @@ -663,7 +663,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS/Userscripts-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS/Info.plist"; @@ -704,7 +704,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS/Userscripts-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS/Info.plist"; @@ -743,7 +743,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS Extension/Userscripts-iOS Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS Extension/Info.plist"; @@ -776,7 +776,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS Extension/Userscripts-iOS Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS Extension/Info.plist"; @@ -929,7 +929,7 @@ CODE_SIGN_ENTITLEMENTS = "Userscripts Extension/Userscripts Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Userscripts Extension/Info.plist"; @@ -953,7 +953,7 @@ CODE_SIGN_ENTITLEMENTS = "Userscripts Extension/Userscripts Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Userscripts Extension/Info.plist"; @@ -980,7 +980,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Userscripts/Info.plist; @@ -1005,7 +1005,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Userscripts/Info.plist; From 63e33fb037091fd90bd5db9dd86798cf17a5dd02 Mon Sep 17 00:00:00 2001 From: Eli Young Date: Wed, 27 Apr 2022 17:58:20 -0700 Subject: [PATCH 36/47] Support falsey default values in getValue() This allows scripts to use falsey default values (e.g. `false`, the empty string) in calls to getValue(). --- extension/Userscripts Extension/Resources/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/Userscripts Extension/Resources/background.js b/extension/Userscripts Extension/Resources/background.js index fd1fb056..fec89b01 100644 --- a/extension/Userscripts Extension/Resources/background.js +++ b/extension/Userscripts Extension/Resources/background.js @@ -92,7 +92,7 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => { const key = request.filename + "---" + request.key; browser.storage.local.get(key, item => { if (Object.keys(item).length === 0) { - if (request.defaultValue) { + if (request.defaultValue !== undefined) { sendResponse(request.defaultValue); } else { sendResponse(`undefined--${request.pid}`); From bdd23470d6cfb6fb148437d78c9d077c432f18af Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 1 May 2022 22:36:13 -0400 Subject: [PATCH 37/47] style updates for install view on iOS - #243 --- public/popup/css/global.css | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/public/popup/css/global.css b/public/popup/css/global.css index 342768c8..ca69fbc3 100644 --- a/public/popup/css/global.css +++ b/public/popup/css/global.css @@ -41,10 +41,25 @@ body.ipados { width: 18rem; } +@media screen and (max-width: 360px) { + body.ipados { + height: 100vh; + } + + body.ipados #app { + height: 100%; + } +} + body.ios { + height: 100vh; width: 100vw; } +body.ios #app { + height: 100%; +} + noscript { display: block; } From b99c842e4606b3d903077c9fc945935438c3931b Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 1 May 2022 22:36:28 -0400 Subject: [PATCH 38/47] formatting --- src/popup/App.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/popup/App.svelte b/src/popup/App.svelte index f5d1e72f..1b6ae08a 100644 --- a/src/popup/App.svelte +++ b/src/popup/App.svelte @@ -383,7 +383,6 @@ async function resize() { if (!platform || platform === "macos") return; - // special styling for ipados and split views clearTimeout(resizeTimer); resizeTimer = setTimeout(async () => { if (platform === "ipados") { From d044bf1fa57a898573ca7d5050e8f45c95fe9088 Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 1 May 2022 22:36:38 -0400 Subject: [PATCH 39/47] remove install view error in dev mode --- src/shared/dev.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/dev.js b/src/shared/dev.js index 19de88f4..1097378f 100644 --- a/src/shared/dev.js +++ b/src/shared/dev.js @@ -388,7 +388,7 @@ const _browser = { require: ["https://code.jquery.com/jquery-3.5.1.min.js", "https://code.jquery.com/jquery-1.7.1.min.js"], source: "https://greasyforx.org/scripts/00000-something-something-long-name/code/Something%20something%20long20name.user.js" }; - response = {error: "a userscript with this @name value already exists, @name needs to be unique"}; + //response = {error: "a userscript with this @name value already exists, @name needs to be unique"}; } if (!responseCallback) { return new Promise(resolve => setTimeout(() => resolve(response), _browser.delay)); From 5ad4e9a9d83f8029c56c3aa8548c232e8d7d5b9f Mon Sep 17 00:00:00 2001 From: quoid Date: Mon, 2 May 2022 22:43:31 -0400 Subject: [PATCH 40/47] update url component func - #249 --- extension/Userscripts Extension/Functions.swift | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/extension/Userscripts Extension/Functions.swift b/extension/Userscripts Extension/Functions.swift index 30a6a022..b41bb566 100644 --- a/extension/Userscripts Extension/Functions.swift +++ b/extension/Userscripts Extension/Functions.swift @@ -1047,22 +1047,15 @@ func checkDefaultDirectories() -> Bool { // matching func getUrlProps(_ url: String) -> [String: String]? { - let pattern = #"^(.*:)\/\/((?:\*\.)?(?:[a-z0-9-:]+\.?)+(?:[a-z0-9]+))(\/.*)?$"# - let regex = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive) guard - let result = regex.firstMatch(in: url, options: [], range: NSMakeRange(0, url.utf16.count)), - let ptclRange = Range(result.range(at: 1), in: url), - let hostRange = Range(result.range(at: 2), in: url) + let parts = URLComponents(string: url), + let ptcl = parts.scheme, + let host = parts.host else { + err("failed to parse url in getUrlProps") return nil } - let ptcl = String(url[ptclRange]) - let host = String(url[hostRange]) - var path = "/" - if let pathRange = Range(result.range(at: 3), in: url) { - path = String(url[pathRange]) - } - return ["protocol": ptcl, "host": host, "pathname": path, "href": url] + return ["protocol": "\(ptcl):", "host": host, "pathname": parts.path, "href": url] } func stringToRegex(_ stringPattern: String) -> NSRegularExpression? { From 207ffd973d64adc5b6eac5fa03823e429860e3d8 Mon Sep 17 00:00:00 2001 From: quoid Date: Mon, 2 May 2022 22:43:42 -0400 Subject: [PATCH 41/47] basic matching test - #249 --- .../UserscriptsTests/UserscriptsTests.swift | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/extension/UserscriptsTests/UserscriptsTests.swift b/extension/UserscriptsTests/UserscriptsTests.swift index 44690877..c1ab95dc 100644 --- a/extension/UserscriptsTests/UserscriptsTests.swift +++ b/extension/UserscriptsTests/UserscriptsTests.swift @@ -88,6 +88,30 @@ class UserscriptsTests: XCTestCase { } XCTAssert(result.count == urls.count) } + + func testMatching() throws { + let pattern = "*://www.google.com/*" + let urls = [ + "https://www.google.com/://aa", + "https://www.google.com/preferences?prev=https://www.google.com/", + "https://www.google.com/preferences?prev=", + "https://www.google.com/" + ] + var result = [String]() + for url in urls { + if + let parts = getUrlProps(url), + let ptcl = parts["protocol"], + let host = parts["host"], + let path = parts["pathname"] + { + if match(ptcl, host, path, pattern) { + result.append("1") + } + } + } + XCTAssert(result.count == urls.count) + } func testPerformanceExample() throws { // This is an example of a performance test case. From 726bb01875dcd494553042f305d6942a0088d538 Mon Sep 17 00:00:00 2001 From: quoid Date: Wed, 4 May 2022 22:14:07 -0400 Subject: [PATCH 42/47] popup rebuild for #243 --- .../Resources/popup.html | 2 +- .../Userscripts Extension/Resources/popup.js | 34 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/extension/Userscripts Extension/Resources/popup.html b/extension/Userscripts Extension/Resources/popup.html index e915b271..d7d8e0a0 100644 --- a/extension/Userscripts Extension/Resources/popup.html +++ b/extension/Userscripts Extension/Resources/popup.html @@ -1 +1 @@ -Userscripts Safari
    \ No newline at end of file +Userscripts Safari
    \ No newline at end of file diff --git a/extension/Userscripts Extension/Resources/popup.js b/extension/Userscripts Extension/Resources/popup.js index e7e97f30..9da92737 100644 --- a/extension/Userscripts Extension/Resources/popup.js +++ b/extension/Userscripts Extension/Resources/popup.js @@ -2877,12 +2877,12 @@ return child_ctx; } - // (499:0) {#if !active} + // (498:0) {#if !active} function create_if_block_10(ctx) { return { c: noop, m: noop, d: noop }; } - // (502:0) {#if showInstallPrompt} + // (501:0) {#if showInstallPrompt} function create_if_block_9(ctx) { let div; let t0; @@ -2924,7 +2924,7 @@ }; } - // (507:0) {#if error} + // (506:0) {#if error} function create_if_block_8(ctx) { let div; let t0; @@ -2974,7 +2974,7 @@ }; } - // (535:8) {:else} + // (534:8) {:else} function create_else_block$3(ctx) { let div; let each_blocks = []; @@ -3047,7 +3047,7 @@ }; } - // (533:35) + // (532:35) function create_if_block_7(ctx) { let div; @@ -3069,7 +3069,7 @@ }; } - // (523:28) + // (522:28) function create_if_block_6$1(ctx) { let div; let t0; @@ -3107,7 +3107,7 @@ }; } - // (521:8) {#if inactive} + // (520:8) {#if inactive} function create_if_block_5$1(ctx) { let div; @@ -3129,7 +3129,7 @@ }; } - // (518:4) {#if loading} + // (517:4) {#if loading} function create_if_block_4$1(ctx) { let loader; let current; @@ -3169,7 +3169,7 @@ }; } - // (537:16) {#each list as item (item.filename)} + // (536:16) {#each list as item (item.filename)} function create_each_block$3(key_1, ctx) { let first; let popupitem; @@ -3230,7 +3230,7 @@ }; } - // (551:0) {#if !inactive && platform === "macos"} + // (550:0) {#if !inactive && platform === "macos"} function create_if_block_3$1(ctx) { let div1; let div0; @@ -3263,7 +3263,7 @@ }; } - // (588:18) + // (587:18) function create_if_block_2$1(ctx) { let view; let current; @@ -3313,7 +3313,7 @@ }; } - // (574:22) + // (573:22) function create_if_block_1$1(ctx) { let view; let current; @@ -3363,7 +3363,7 @@ }; } - // (558:0) {#if showUpdates} + // (557:0) {#if showUpdates} function create_if_block$6(ctx) { let view; let current; @@ -3416,7 +3416,7 @@ }; } - // (589:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > + // (588:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > function create_default_slot_2(ctx) { let allitemsview; let current; @@ -3456,7 +3456,7 @@ }; } - // (575:4) showInstall = false} showLoaderOnDisabled={true} > + // (574:4) showInstall = false} showLoaderOnDisabled={true} > function create_default_slot_1(ctx) { let installview; let current; @@ -3500,7 +3500,7 @@ }; } - // (559:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > + // (558:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > function create_default_slot(ctx) { let updateview; let current; @@ -4311,8 +4311,6 @@ async function resize() { if (!platform || platform === "macos") return; - - // special styling for ipados and split views clearTimeout(resizeTimer); resizeTimer = setTimeout( From 057824bfa17dc4377413d12890639deb8c407aa2 Mon Sep 17 00:00:00 2001 From: quoid Date: Wed, 4 May 2022 22:15:34 -0400 Subject: [PATCH 43/47] bump build --- extension/Userscripts.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/extension/Userscripts.xcodeproj/project.pbxproj b/extension/Userscripts.xcodeproj/project.pbxproj index 74d4813b..adde3fdc 100644 --- a/extension/Userscripts.xcodeproj/project.pbxproj +++ b/extension/Userscripts.xcodeproj/project.pbxproj @@ -663,7 +663,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS/Userscripts-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 22; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS/Info.plist"; @@ -704,7 +704,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS/Userscripts-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 22; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS/Info.plist"; @@ -743,7 +743,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS Extension/Userscripts-iOS Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 22; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS Extension/Info.plist"; @@ -776,7 +776,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS Extension/Userscripts-iOS Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 22; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS Extension/Info.plist"; @@ -929,7 +929,7 @@ CODE_SIGN_ENTITLEMENTS = "Userscripts Extension/Userscripts Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Userscripts Extension/Info.plist"; @@ -953,7 +953,7 @@ CODE_SIGN_ENTITLEMENTS = "Userscripts Extension/Userscripts Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Userscripts Extension/Info.plist"; @@ -980,7 +980,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Userscripts/Info.plist; @@ -1005,7 +1005,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Userscripts/Info.plist; From 82e7e49cd04769f02393c2c76fd778805c5a2c2a Mon Sep 17 00:00:00 2001 From: quoid Date: Thu, 5 May 2022 21:05:02 -0400 Subject: [PATCH 44/47] style updates #243 --- .../Resources/popup.html | 2 +- .../Userscripts Extension/Resources/popup.js | 48 +++++++++---------- public/popup/css/global.css | 11 ++--- src/popup/Components/Views/InstallView.svelte | 5 ++ 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/extension/Userscripts Extension/Resources/popup.html b/extension/Userscripts Extension/Resources/popup.html index d7d8e0a0..8e3c7eae 100644 --- a/extension/Userscripts Extension/Resources/popup.html +++ b/extension/Userscripts Extension/Resources/popup.html @@ -1 +1 @@ -Userscripts Safari
    \ No newline at end of file +Userscripts Safari
    \ No newline at end of file diff --git a/extension/Userscripts Extension/Resources/popup.js b/extension/Userscripts Extension/Resources/popup.js index 9da92737..b990c510 100644 --- a/extension/Userscripts Extension/Resources/popup.js +++ b/extension/Userscripts Extension/Resources/popup.js @@ -1946,14 +1946,14 @@ t11 = space(); button1 = element("button"); button1.textContent = "Install"; - attr(li, "class", "userscript--name svelte-tdpz3j"); - attr(ul, "class", "svelte-tdpz3j"); - attr(div0, "class", "badge--icon svelte-tdpz3j"); - attr(div1, "class", "badge--text svelte-tdpz3j"); - attr(div2, "class", "badge svelte-tdpz3j"); - attr(button0, "class", "cancel svelte-tdpz3j"); - attr(button1, "class", "install svelte-tdpz3j"); - attr(div3, "class", "buttons svelte-tdpz3j"); + attr(li, "class", "userscript--name svelte-p5i392"); + attr(ul, "class", "svelte-p5i392"); + attr(div0, "class", "badge--icon svelte-p5i392"); + attr(div1, "class", "badge--text svelte-p5i392"); + attr(div2, "class", "badge svelte-p5i392"); + attr(button0, "class", "cancel svelte-p5i392"); + attr(button1, "class", "install svelte-p5i392"); + attr(div3, "class", "buttons svelte-p5i392"); }, m(target, anchor) { insert(target, ul, anchor); @@ -2100,8 +2100,8 @@ p = element("p"); t3 = text(/*installError*/ ctx[1]); html_tag = new HtmlTag(t0); - attr(p, "class", "svelte-tdpz3j"); - attr(div1, "class", "install__error svelte-tdpz3j"); + attr(p, "class", "svelte-p5i392"); + attr(div1, "class", "install__error svelte-p5i392"); }, m(target, anchor) { insert(target, div1, anchor); @@ -2131,7 +2131,7 @@ c() { li = element("li"); t = text(t_value); - attr(li, "class", "userscript--description svelte-tdpz3j"); + attr(li, "class", "userscript--description svelte-p5i392"); }, m(target, anchor) { insert(target, li, anchor); @@ -2169,8 +2169,8 @@ each_blocks[i].c(); } - attr(div, "class", "svelte-tdpz3j"); - attr(li, "class", "userscript--field svelte-tdpz3j"); + attr(div, "class", "svelte-p5i392"); + attr(li, "class", "userscript--field svelte-p5i392"); }, m(target, anchor) { insert(target, li, anchor); @@ -2222,7 +2222,7 @@ c() { div = element("div"); t = text(t_value); - attr(div, "class", "truncate svelte-tdpz3j"); + attr(div, "class", "truncate svelte-p5i392"); }, m(target, anchor) { insert(target, div, anchor); @@ -2260,8 +2260,8 @@ each_blocks[i].c(); } - attr(div, "class", "svelte-tdpz3j"); - attr(li, "class", "userscript--field svelte-tdpz3j"); + attr(div, "class", "svelte-p5i392"); + attr(li, "class", "userscript--field svelte-p5i392"); }, m(target, anchor) { insert(target, li, anchor); @@ -2313,7 +2313,7 @@ c() { div = element("div"); t = text(t_value); - attr(div, "class", "truncate svelte-tdpz3j"); + attr(div, "class", "truncate svelte-p5i392"); }, m(target, anchor) { insert(target, div, anchor); @@ -2351,8 +2351,8 @@ each_blocks[i].c(); } - attr(div, "class", "svelte-tdpz3j"); - attr(li, "class", "userscript--field svelte-tdpz3j"); + attr(div, "class", "svelte-p5i392"); + attr(li, "class", "userscript--field svelte-p5i392"); }, m(target, anchor) { insert(target, li, anchor); @@ -2404,7 +2404,7 @@ c() { div = element("div"); t = text(t_value); - attr(div, "class", "truncate svelte-tdpz3j"); + attr(div, "class", "truncate svelte-p5i392"); }, m(target, anchor) { insert(target, div, anchor); @@ -2442,8 +2442,8 @@ each_blocks[i].c(); } - attr(div, "class", "svelte-tdpz3j"); - attr(li, "class", "userscript--field svelte-tdpz3j"); + attr(div, "class", "svelte-p5i392"); + attr(li, "class", "userscript--field svelte-p5i392"); }, m(target, anchor) { insert(target, li, anchor); @@ -2495,7 +2495,7 @@ c() { div = element("div"); t = text(t_value); - attr(div, "class", "svelte-tdpz3j"); + attr(div, "class", "svelte-p5i392"); }, m(target, anchor) { insert(target, div, anchor); @@ -2525,7 +2525,7 @@ c() { div = element("div"); if (if_block) if_block.c(); - attr(div, "class", "view--install svelte-tdpz3j"); + attr(div, "class", "view--install svelte-p5i392"); }, m(target, anchor) { insert(target, div, anchor); diff --git a/public/popup/css/global.css b/public/popup/css/global.css index ca69fbc3..cce11fd5 100644 --- a/public/popup/css/global.css +++ b/public/popup/css/global.css @@ -39,16 +39,11 @@ body { body.ipados { width: 18rem; + height: 100vh; } -@media screen and (max-width: 360px) { - body.ipados { - height: 100vh; - } - - body.ipados #app { - height: 100%; - } +body.ipados #app { + height: 100%; } body.ios { diff --git a/src/popup/Components/Views/InstallView.svelte b/src/popup/Components/Views/InstallView.svelte index c0d730fb..a4212435 100644 --- a/src/popup/Components/Views/InstallView.svelte +++ b/src/popup/Components/Views/InstallView.svelte @@ -74,6 +74,11 @@ padding: 0 1rem; } + :global(body.ios) .view--install, + :global(body.ipados) .view--install { + padding-bottom: calc(39px + 1rem); + } + .install__error { color: var(--text-color-disabled); font-weight: 600; From 53b88766bc4bea851027509f3f49796017dc23ff Mon Sep 17 00:00:00 2001 From: quoid Date: Thu, 5 May 2022 22:17:18 -0400 Subject: [PATCH 45/47] bump build --- extension/Userscripts.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/extension/Userscripts.xcodeproj/project.pbxproj b/extension/Userscripts.xcodeproj/project.pbxproj index adde3fdc..67b24557 100644 --- a/extension/Userscripts.xcodeproj/project.pbxproj +++ b/extension/Userscripts.xcodeproj/project.pbxproj @@ -663,7 +663,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS/Userscripts-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS/Info.plist"; @@ -704,7 +704,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS/Userscripts-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS/Info.plist"; @@ -743,7 +743,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS Extension/Userscripts-iOS Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS Extension/Info.plist"; @@ -776,7 +776,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS Extension/Userscripts-iOS Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 22; + CURRENT_PROJECT_VERSION = 23; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS Extension/Info.plist"; @@ -929,7 +929,7 @@ CODE_SIGN_ENTITLEMENTS = "Userscripts Extension/Userscripts Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 50; + CURRENT_PROJECT_VERSION = 51; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Userscripts Extension/Info.plist"; @@ -953,7 +953,7 @@ CODE_SIGN_ENTITLEMENTS = "Userscripts Extension/Userscripts Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 50; + CURRENT_PROJECT_VERSION = 51; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Userscripts Extension/Info.plist"; @@ -980,7 +980,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 50; + CURRENT_PROJECT_VERSION = 51; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Userscripts/Info.plist; @@ -1005,7 +1005,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 50; + CURRENT_PROJECT_VERSION = 51; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Userscripts/Info.plist; From fa2427474f471eab37276b71886a3ae767c39ba4 Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 22 May 2022 14:56:28 -0400 Subject: [PATCH 46/47] Context Menu Injection Updates #159 --- .../Userscripts Extension/Functions.swift | 71 ++++++++++++-- .../Resources/background.js | 92 +++++++++++++++++++ .../Resources/content.js | 18 ++++ .../Userscripts Extension/Resources/page.html | 2 +- .../Userscripts Extension/Resources/page.js | 9 +- .../Userscripts Extension/Resources/popup.js | 35 +++---- .../SafariWebExtensionHandler.swift | 7 ++ src/page/Components/Editor/Editor.svelte | 8 +- src/popup/App.svelte | 2 + 9 files changed, 211 insertions(+), 33 deletions(-) diff --git a/extension/Userscripts Extension/Functions.swift b/extension/Userscripts Extension/Functions.swift index b41bb566..57534e8c 100644 --- a/extension/Userscripts Extension/Functions.swift +++ b/extension/Userscripts Extension/Functions.swift @@ -1217,6 +1217,7 @@ func getMatchedFiles(_ url: String, _ optionalManifest: Manifest?, _ checkBlockl func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? { var cssFiles = [Any]() var jsFiles = [Any]() + var menuFiles = [Any]() guard let saveLocation = getSaveLocation() else { err("getCode failed at (1)") @@ -1345,17 +1346,29 @@ func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? { "weight": weight ]) } else if type == "js" { - jsFiles.append([ - "code": code, - "scriptMetaStr": scriptMetaStr, - "scriptObject": scriptObject, - "type": "js", - "weight": weight - ]) + if runAt == "context-menu" { + #if os(macOS) + menuFiles.append([ + "code": code, + "scriptMetaStr": scriptMetaStr, + "scriptObject": scriptObject, + "type": "js", + "weight": weight + ]) + #endif + } else { + jsFiles.append([ + "code": code, + "scriptMetaStr": scriptMetaStr, + "scriptObject": scriptObject, + "type": "js", + "weight": weight + ]) + } } } let resp = [ - "files": ["css": cssFiles, "js": jsFiles], + "files": ["css": cssFiles, "js": jsFiles, "menu": menuFiles], "scriptHandler": "Userscripts", "scriptHandlerVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??" ] as [String : Any] @@ -1434,6 +1447,48 @@ func getRequestScripts() -> [[String: String]]? { return requestScripts } +func getContextMenuScripts() -> [String: Any]? { + var menuFilenames = [String]() + // check the manifest to see if injection is enabled + let manifest = getManifest() + guard let active = manifest.settings["active"] else { + err("getContextMenuScripts failed at (1)") + return nil + } + // if not enabled return empty array + if active != "true" { + return ["files": ["menu": []]] + } + // get all files at save location + guard let files = getAllFiles() else { + err("getContextMenuScripts failed at (2)") + return nil + } + // loop through files and find @run-at context-menu script filenames + for file in files { + if + let fileMetadata = file["metadata"] as? [String: [String]], + let filename = file["filename"] as? String + { + let runAt = fileMetadata["run-at"]?[0] ?? "document-end" + if runAt != "context-menu" { + continue + } + if !manifest.disabled.contains(filename) { + menuFilenames.append(filename) + } + } else { + err("getContextMenuScripts failed at (3), couldn't get metadata for \(file)") + } + } + // get and return script objects for all context-menu scripts + guard let scripts = getCode(menuFilenames, true) else { + err("getContextMenuScripts failed at (4)") + return nil + } + return scripts +} + // popup func getPopupMatches(_ url: String, _ subframeUrls: [String]) -> [[String: Any]]? { var matches = [[String: Any]]() diff --git a/extension/Userscripts Extension/Resources/background.js b/extension/Userscripts Extension/Resources/background.js index 941e8316..00c74025 100644 --- a/extension/Userscripts Extension/Resources/background.js +++ b/extension/Userscripts Extension/Resources/background.js @@ -506,6 +506,88 @@ async function setBadgeCount() { }); } +async function getContextMenuItems() { + // macos exclusive feature + const platform = await getPlatform(); + if (platform !== "macos") return; + // since it's not possible to get a list of currently active menu items + // on update, all context-menu items are clears, then re-added + // this is done to get fresh code changes to context-menu scripts on certain events + await browser.menus.removeAll(); + // get the context-menu scripts + const response = await browser.runtime.sendNativeMessage({name: "REQ_CONTEXT_MENU_SCRIPTS"}); + if (response.error) { + console.error(response.error); + return; + } + // add menus items + const items = response.files?.menu || []; + if (items.length) { + console.info(`Setting ${items.length} context-menu userscripts`); + } + for (let i = 0; i < items.length; i++) { + const item = items[i]; + // context-menu scripts require @match value + // @include values are ignored + if (!item.scriptObject.matches.length) continue; + addContextMenuItem(item); + } +} + +async function addContextMenuItem(userscript) { + // context-menu items persist for a session + // to avoid duplication, when created, save the filename to session storage + const savedItems = sessionStorage.getItem("menu"); + // if the session storage key doesn't exist use empty array + const activeItems = savedItems ? JSON.parse(savedItems) : []; + if (activeItems.indexOf(userscript.scriptObject.filename) !== -1) { + // if already saved, remove it, to get fresh code changes + await browser.menus.remove(userscript.scriptObject.filename); + } + // potential bug? https://developer.apple.com/forums/thread/685273 + // https://stackoverflow.com/q/68431201 + // parse through match values and change pathnames to deal with bug + const patterns = userscript.scriptObject.matches; + patterns.forEach((pattern, index) => { + try { + const url = new URL(pattern); + let pathname = url.pathname; + if (pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1); + patterns[index] = `${url.protocol}//${url.hostname}${pathname}`; + } catch (error) { + // prevent breaking when non-url pattern present + } + }); + + browser.menus.create({ + contexts: ["all"], + documentUrlPatterns: patterns, + id: userscript.scriptObject.filename, + title: userscript.scriptObject.name + }, () => { + // add event listener if needed + if (!browser.menus.onClicked.hasListener(contextClick)) { + browser.menus.onClicked.addListener(contextClick); + } + // save the context-menu item reference to sessionStorage + sessionStorage.setItem("menu", JSON.stringify([userscript.scriptObject.filename])); + }); +} + +function contextClick(info, tab) { + // when any created context-menu item is clicked, send message to tab + // the content script for that tag will have the context-menu code + // which will get send back in the response if/when found + const message = {name: "CONTEXT_RUN", menuItemId: info.menuItemId}; + browser.tabs.sendMessage(tab.id, message, response => { + // if code is returned, execute on that tab + if (!response.code) return; + browser.tabs.executeScript(tab.id, { + code: response.code + }); + }); +} + browser.runtime.onMessage.addListener((request, sender, sendResponse) => { const name = request.name; switch (name) { @@ -534,6 +616,10 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => { // update badge count on injection // especially useful when injection is deferred (ie. subframes) setBadgeCount(); + // refresh context menu items if needed + // if (response.files.menu.length) { + // getContextMenuItems(); + // } }); return true; } @@ -636,6 +722,11 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => { } case "REFRESH_SESSION_RULES": { setSessionRules(); + break; + } + case "REFRESH_CONTEXT_MENU_SCRIPTS": { + getContextMenuItems(); + break; } } }); @@ -703,6 +794,7 @@ browser.runtime.onStartup.addListener(async () => { // 2. a new save event in the page occurs // 3. the refresh button is pushed in the popup await setSessionRules(); + await getContextMenuItems(); }); browser.tabs.onActivated.addListener(setBadgeCount); browser.windows.onFocusChanged.addListener(setBadgeCount); diff --git a/extension/Userscripts Extension/Resources/content.js b/extension/Userscripts Extension/Resources/content.js index e013221c..631d0ac1 100644 --- a/extension/Userscripts Extension/Resources/content.js +++ b/extension/Userscripts Extension/Resources/content.js @@ -291,8 +291,26 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => { }); return true; } + } else if (name === "CONTEXT_RUN") { + // from bg script when context-menu item is clicked + // double check to ensure context-menu scripts only run in top windows + if (window !== window.top) return; + + // loop through context-menu scripts saved to data object and find match + // if no match found, nothing will execute and error will log + const filename = request.menuItemId; + for (let i = 0; i < data.files.menu.length; i++) { + const item = data.files.menu[i]; + if (item.scriptObject.filename === filename) { + console.info(`Injecting ${filename} %c(js)`, "color: #fff600"); + sendResponse({code: item.code}); + return; + } + } + console.error(`Couldn't find ${filename} code!`); } }); + // listens for messages from api methods calls in content script or current page window.addEventListener("message", handleApiMessages); // when userscript fails due to a CSP and has @inject-into value of auto or page diff --git a/extension/Userscripts Extension/Resources/page.html b/extension/Userscripts Extension/Resources/page.html index 7761b95e..d0a7c906 100644 --- a/extension/Userscripts Extension/Resources/page.html +++ b/extension/Userscripts Extension/Resources/page.html @@ -1 +1 @@ -Userscripts Safari
    \ No newline at end of file +Userscripts Safari
    \ No newline at end of file diff --git a/extension/Userscripts Extension/Resources/page.js b/extension/Userscripts Extension/Resources/page.js index 8b736049..ada155d3 100644 --- a/extension/Userscripts Extension/Resources/page.js +++ b/extension/Userscripts Extension/Resources/page.js @@ -19128,10 +19128,11 @@ var JSHINT;"undefined"==typeof window&&(window={}),function(){var f=function u(o // set the newly saved file contents in codemirror instance cmSetSavedCode(response.content); - // update session declarativeNetRequest rules if needed - if (response.request) { - browser.runtime.sendMessage({ name: "REFRESH_SESSION_RULES" }); - } + // refresh session rules + browser.runtime.sendMessage({ name: "REFRESH_SESSION_RULES" }); + + // refresh context-menu scripts + browser.runtime.sendMessage({ name: "REFRESH_CONTEXT_MENU_SCRIPTS" }); } state.remove("saving"); diff --git a/extension/Userscripts Extension/Resources/popup.js b/extension/Userscripts Extension/Resources/popup.js index b990c510..90817eae 100644 --- a/extension/Userscripts Extension/Resources/popup.js +++ b/extension/Userscripts Extension/Resources/popup.js @@ -2877,12 +2877,12 @@ return child_ctx; } - // (498:0) {#if !active} + // (500:0) {#if !active} function create_if_block_10(ctx) { return { c: noop, m: noop, d: noop }; } - // (501:0) {#if showInstallPrompt} + // (503:0) {#if showInstallPrompt} function create_if_block_9(ctx) { let div; let t0; @@ -2924,7 +2924,7 @@ }; } - // (506:0) {#if error} + // (508:0) {#if error} function create_if_block_8(ctx) { let div; let t0; @@ -2974,7 +2974,7 @@ }; } - // (534:8) {:else} + // (536:8) {:else} function create_else_block$3(ctx) { let div; let each_blocks = []; @@ -3047,7 +3047,7 @@ }; } - // (532:35) + // (534:35) function create_if_block_7(ctx) { let div; @@ -3069,7 +3069,7 @@ }; } - // (522:28) + // (524:28) function create_if_block_6$1(ctx) { let div; let t0; @@ -3107,7 +3107,7 @@ }; } - // (520:8) {#if inactive} + // (522:8) {#if inactive} function create_if_block_5$1(ctx) { let div; @@ -3129,7 +3129,7 @@ }; } - // (517:4) {#if loading} + // (519:4) {#if loading} function create_if_block_4$1(ctx) { let loader; let current; @@ -3169,7 +3169,7 @@ }; } - // (536:16) {#each list as item (item.filename)} + // (538:16) {#each list as item (item.filename)} function create_each_block$3(key_1, ctx) { let first; let popupitem; @@ -3230,7 +3230,7 @@ }; } - // (550:0) {#if !inactive && platform === "macos"} + // (552:0) {#if !inactive && platform === "macos"} function create_if_block_3$1(ctx) { let div1; let div0; @@ -3263,7 +3263,7 @@ }; } - // (587:18) + // (589:18) function create_if_block_2$1(ctx) { let view; let current; @@ -3313,7 +3313,7 @@ }; } - // (573:22) + // (575:22) function create_if_block_1$1(ctx) { let view; let current; @@ -3363,7 +3363,7 @@ }; } - // (557:0) {#if showUpdates} + // (559:0) {#if showUpdates} function create_if_block$6(ctx) { let view; let current; @@ -3416,7 +3416,7 @@ }; } - // (588:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > + // (590:4) { showAll = false; refreshView(); }} showLoaderOnDisabled={false} > function create_default_slot_2(ctx) { let allitemsview; let current; @@ -3456,7 +3456,7 @@ }; } - // (574:4) showInstall = false} showLoaderOnDisabled={true} > + // (576:4) showInstall = false} showLoaderOnDisabled={true} > function create_default_slot_1(ctx) { let installview; let current; @@ -3500,7 +3500,7 @@ }; } - // (558:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > + // (560:4) showUpdates = false} showLoaderOnDisabled={true} abortClick={abortUpdates} abort={showUpdates} > function create_default_slot(ctx) { let updateview; let current; @@ -4182,6 +4182,9 @@ // refresh session rules browser.runtime.sendMessage({ name: "REFRESH_SESSION_RULES" }); + // refresh context-menu scripts + browser.runtime.sendMessage({ name: "REFRESH_CONTEXT_MENU_SCRIPTS" }); + // set popup height resize(); diff --git a/extension/Userscripts Extension/SafariWebExtensionHandler.swift b/extension/Userscripts Extension/SafariWebExtensionHandler.swift index 0f2117f7..0c3bf641 100644 --- a/extension/Userscripts Extension/SafariWebExtensionHandler.swift +++ b/extension/Userscripts Extension/SafariWebExtensionHandler.swift @@ -41,6 +41,13 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { response.userInfo = [SFExtensionMessageKey: ["error": "failed to get requestScripts"]] } } + else if name == "REQ_CONTEXT_MENU_SCRIPTS" { + if let contextMenuScripts = getContextMenuScripts() { + response.userInfo = [SFExtensionMessageKey: contextMenuScripts] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get contextMenuScripts"]] + } + } else if name == "POPUP_BADGE_COUNT" { #if os(macOS) if let url = message?["url"] as? String, let frameUrls = message?["frameUrls"] as? [String] { diff --git a/src/page/Components/Editor/Editor.svelte b/src/page/Components/Editor/Editor.svelte index e37d22d8..3ec99568 100644 --- a/src/page/Components/Editor/Editor.svelte +++ b/src/page/Components/Editor/Editor.svelte @@ -69,10 +69,10 @@ }); // set the newly saved file contents in codemirror instance cmSetSavedCode(response.content); - // update session declarativeNetRequest rules if needed - if (response.request) { - browser.runtime.sendMessage({name: "REFRESH_SESSION_RULES"}); - } + // refresh session rules + browser.runtime.sendMessage({name: "REFRESH_SESSION_RULES"}); + // refresh context-menu scripts + browser.runtime.sendMessage({name: "REFRESH_CONTEXT_MENU_SCRIPTS"}); } state.remove("saving"); } diff --git a/src/popup/App.svelte b/src/popup/App.svelte index 1b6ae08a..ef8a72e2 100644 --- a/src/popup/App.svelte +++ b/src/popup/App.svelte @@ -265,6 +265,8 @@ // refresh session rules browser.runtime.sendMessage({name: "REFRESH_SESSION_RULES"}); + // refresh context-menu scripts + browser.runtime.sendMessage({name: "REFRESH_CONTEXT_MENU_SCRIPTS"}); // set popup height resize(); From 978facc6cd2b636d424e5a22f9f4212d5830cf22 Mon Sep 17 00:00:00 2001 From: quoid Date: Sun, 22 May 2022 14:59:32 -0400 Subject: [PATCH 47/47] bump build --- extension/Userscripts.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/extension/Userscripts.xcodeproj/project.pbxproj b/extension/Userscripts.xcodeproj/project.pbxproj index 67b24557..972f026d 100644 --- a/extension/Userscripts.xcodeproj/project.pbxproj +++ b/extension/Userscripts.xcodeproj/project.pbxproj @@ -663,7 +663,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS/Userscripts-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 23; + CURRENT_PROJECT_VERSION = 24; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS/Info.plist"; @@ -704,7 +704,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS/Userscripts-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 23; + CURRENT_PROJECT_VERSION = 24; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS/Info.plist"; @@ -743,7 +743,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS Extension/Userscripts-iOS Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 23; + CURRENT_PROJECT_VERSION = 24; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS Extension/Info.plist"; @@ -776,7 +776,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "Userscripts-iOS Extension/Userscripts-iOS Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 23; + CURRENT_PROJECT_VERSION = 24; DEVELOPMENT_TEAM = J74Q8V8V8N; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Userscripts-iOS Extension/Info.plist"; @@ -929,7 +929,7 @@ CODE_SIGN_ENTITLEMENTS = "Userscripts Extension/Userscripts Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Userscripts Extension/Info.plist"; @@ -953,7 +953,7 @@ CODE_SIGN_ENTITLEMENTS = "Userscripts Extension/Userscripts Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "Userscripts Extension/Info.plist"; @@ -980,7 +980,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Userscripts/Info.plist; @@ -1005,7 +1005,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 51; + CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_TEAM = J74Q8V8V8N; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Userscripts/Info.plist;