diff --git a/src/popup/App.svelte b/src/popup/App.svelte
index 085394cd..9a9d57ec 100644
--- a/src/popup/App.svelte
+++ b/src/popup/App.svelte
@@ -31,9 +31,12 @@
let header;
let warn;
let err;
+ let scriptChecking;
+ let scriptInstalled;
let showInstallPrompt;
let showInstall;
- let installViewUserscript;
+ let installUserscript; // url, content
+ let installViewUserscript; // metadata
let installViewUserscriptError;
let showAll;
let allItems = [];
@@ -139,6 +142,9 @@
function refreshView() {
errorNotification = undefined;
+ scriptChecking = undefined;
+ scriptInstalled = undefined;
+ showInstallPrompt = undefined;
loading = true;
disabled = true;
items = [];
@@ -333,26 +339,12 @@
// check if current page url is a userscript
if (strippedUrl.endsWith(".user.js")) {
- // if it does, send message to content script
- // context script will check the document contentType
- // if it's not an applicable type, it'll return {invalid: true} response and no install prompt shown
- // if the contentType is applicable, what is mentioned below happens
- // content script will get dom content, and send it to the bg page
- // the bg page will send the content to the swift side for parsing
- // when swift side parses and returns, the bg page will send a response to the content script
- // 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(currentTab.id, {name: "USERSCRIPT_INSTALL_00"});
- if (response.error) {
- console.error(`Error checking .user.js url: ${response.error}`);
- errorNotification = response.error;
- } else if (!response.invalid) {
- // the response will contain the string to display
- // ex: {success: "Click to install"}
- const prompt = response.success;
- showInstallPrompt = prompt;
- }
+ // set checking state
+ scriptChecking = true;
+ // show checking banner
+ showInstallPrompt = "checking...";
+ // start async check
+ installCheck(currentTab);
}
loading = false;
@@ -398,43 +390,69 @@
}, 25);
}
- async function showInstallView() {
- // disable all buttons
- disabled = true;
- // show the install view
- showInstall = true;
- // get the active tab
- const currentTab = await browser.tabs.getCurrent();
- // send content script a message on the active tab
- 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
- // swift side will parse then send a response to the bg page
- // the bg page will then send the response to the content script
- // the content script will then send a response here
-
- // if the response includes an error, display it in the view
+ async function installCheck(currentTab) {
+ // refetch script from URL to avoid tampered DOM content
+ const res = await fetch(currentTab.url);
+ if (!res.ok) {
+ console.error(`Error fetching .user.js url: httpcode-${res.status}`);
+ errorNotification = `Fetching failed, refresh to retry. (${res.status})`;
+ showInstallPrompt = undefined;
+ return;
+ }
+ const content = await res.text();
+ // caching script data
+ installUserscript = {url: currentTab.url, content};
+ // send native swift a message, parse metadata and check if installed
+ const response = await browser.runtime.sendNativeMessage({name: "POPUP_INSTALL_CHECK", content});
+ console.info("POPUP_INSTALL_CHECK:", response);
if (response.error) {
- console.error(`Can not install userscript: ${response.error}`);
+ console.error(`Error checking .user.js url: ${response.error}`);
+ // errorNotification = response.error;
installViewUserscriptError = response.error;
} else {
- installViewUserscript = response;
+ scriptInstalled = response.installed;
+ // caching script metadata
+ installViewUserscript = response.metadata;
+ // the response will contain the string to display
+ // ex: {success: "Click to install"}
+ showInstallPrompt = response.success;
}
- disabled = false;
+ scriptChecking = false;
+ }
+
+ async function showInstallView() {
+ // show the install view
+ showInstall = true;
}
async function installConfirm() {
+ // clear all banner during installation
+ errorNotification = undefined;
+ showInstallPrompt = undefined;
// disabled all buttons
disabled = true;
// show loading element
loading = true;
// go back to main view
showInstall = false;
- // get the active tab
+ // double check before send install message
+ if (!installUserscript || !installUserscript.content) {
+ errorNotification = "install failed: userscript missing";
+ }
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(currentTab.id, {name: "USERSCRIPT_INSTALL_02"});
+ if (currentTab.url !== installUserscript.url) {
+ errorNotification = "install failed: tab changed unexpectedly";
+ }
+ if (errorNotification) {
+ disabled = false;
+ loading = false;
+ return;
+ }
+ // send native swift a message, which will start the install process
+ const response = await browser.runtime.sendNativeMessage({
+ name: "POPUP_INSTALL_SCRIPT",
+ content: installUserscript.content
+ });
if (response.error) {
errorNotification = response.error;
disabled = false;
@@ -494,8 +512,14 @@
{/if}
{#if showInstallPrompt}
-
- Userscript Detected:
{showInstallPrompt}
+
+ Userscript
+ {#if scriptChecking}
+ {showInstallPrompt}
+ {:else}
+ {scriptInstalled ? "Installed" : "Detected"}:
+ {showInstallPrompt}
+ {/if}
{/if}
{#if errorNotification}
@@ -650,6 +674,10 @@
background-color: var(--color-yellow);
}
+ .warn.done {
+ background-color: var(--color-green);
+ }
+
.warn span {
border-bottom: 1px dotted var(--color-bg-secondary);
}
diff --git a/src/shared/dev.js b/src/shared/dev.js
index 5a2c0eae..b376101d 100644
--- a/src/shared/dev.js
+++ b/src/shared/dev.js
@@ -392,19 +392,19 @@ const _browser = {
},
sendMessage(tabId, message, responseCallback) {
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") {
+ if (message.name === "POPUP_INSTALL_CHECK") {
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"
+ success: "Click to install (test)",
+ metadata: {
+ 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"};
+ // response.error = "something went wrong (dev)";
}
if (!responseCallback) {
return new Promise(resolve => {
diff --git a/xcode/Safari-Extension/Functions.swift b/xcode/Safari-Extension/Functions.swift
index f7b20659..1e06aaef 100644
--- a/xcode/Safari-Extension/Functions.swift
+++ b/xcode/Safari-Extension/Functions.swift
@@ -1966,12 +1966,12 @@ func popupInit() -> [String: String]? {
}
// userscript install
-func installCheck(_ content: String) -> [String: String]? {
+func installCheck(_ content: String) -> [String: Any] {
// this func checks a userscript's metadata to determine if it's already installed
guard let files = getAllFiles() else {
err("installCheck failed at (1)")
- return nil
+ return ["error": "installCheck failed at (1)"]
}
guard
@@ -2002,30 +2002,28 @@ func installCheck(_ content: String) -> [String: String]? {
#endif
if names.contains(newName) {
- return ["success": "\(directive) to re-install"]
+ return [
+ "success": "\(directive) to re-install",
+ "metadata": metadata,
+ "installed": true
+ ]
}
- return ["success": "\(directive) to install"];
-}
-
-func installParse(_ content: String) -> [String: Any]? {
- guard
- let parsed = parse(content),
- let metadata = parsed["metadata"] as? [String: [String]]
- else {
- return ["error": "userscript metadata is invalid"]
- }
- return metadata
+ return [
+ "success": "\(directive) to install",
+ "metadata": metadata,
+ "installed": false
+ ];
}
-func installUserscript(_ content: String) -> [String: Any]? {
+func installUserscript(_ content: String) -> [String: Any] {
guard
let parsed = parse(content),
let metadata = parsed["metadata"] as? [String: [String]],
let n = metadata["name"]?[0]
else {
err("installUserscript failed at (1)")
- return nil
+ return ["error": "installUserscript failed at (1)"]
}
let name = sanitize(n)
let filename = "\(name).user.js"
diff --git a/xcode/Safari-Extension/Resources/background.js b/xcode/Safari-Extension/Resources/background.js
index 7f7726d0..3b7d243f 100644
--- a/xcode/Safari-Extension/Resources/background.js
+++ b/xcode/Safari-Extension/Resources/background.js
@@ -436,15 +436,6 @@ function handleMessage(request, sender, sendResponse) {
}
break;
}
- case "USERSCRIPT_INSTALL_00":
- case "USERSCRIPT_INSTALL_01":
- case "USERSCRIPT_INSTALL_02": {
- const message = {name: request.name, content: request.content};
- browser.runtime.sendNativeMessage(message, response => {
- sendResponse(response);
- });
- return true;
- }
case "REFRESH_SESSION_RULES": {
setSessionRules();
break;
diff --git a/xcode/Safari-Extension/Resources/content.js b/xcode/Safari-Extension/Resources/content.js
index f1dcdf41..85c62f9a 100644
--- a/xcode/Safari-Extension/Resources/content.js
+++ b/xcode/Safari-Extension/Resources/content.js
@@ -429,38 +429,7 @@ function listeners() {
// listens for messages from background, popup, etc...
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
const name = request.name;
- if (
- name === "USERSCRIPT_INSTALL_00"
- || name === "USERSCRIPT_INSTALL_01"
- || name === "USERSCRIPT_INSTALL_02"
- ) {
- // only respond to top frame messages
- if (window !== window.top) return;
- const types = [
- "text/plain",
- "application/ecmascript",
- "application/javascript",
- "text/ecmascript",
- "text/javascript"
- ];
- if (
- !document.contentType
- || types.indexOf(document.contentType) === -1
- || !document.querySelector("pre")
- ) {
- sendResponse({invalid: true});
- } else {
- const message = {
- name,
- content:
- document.querySelector("pre").innerText
- };
- browser.runtime.sendMessage(message, response => {
- sendResponse(response);
- });
- return true;
- }
- } else if (name === "CONTEXT_RUN") {
+ 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;
diff --git a/xcode/Safari-Extension/SafariWebExtensionHandler.swift b/xcode/Safari-Extension/SafariWebExtensionHandler.swift
index 94db00f4..4805adf0 100644
--- a/xcode/Safari-Extension/SafariWebExtensionHandler.swift
+++ b/xcode/Safari-Extension/SafariWebExtensionHandler.swift
@@ -179,28 +179,18 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
}
#endif
}
- else if name == "USERSCRIPT_INSTALL_00" {
- if
- let content = message?["content"] as? String,
- let reply = installCheck(content)
- {
- response.userInfo = [SFExtensionMessageKey: reply]
- }
- }
- else if name == "USERSCRIPT_INSTALL_01" {
- if
- let content = message?["content"] as? String,
- let reply = installParse(content)
- {
- response.userInfo = [SFExtensionMessageKey: reply]
+ else if name == "POPUP_INSTALL_CHECK" {
+ if let content = message?["content"] as? String {
+ response.userInfo = [SFExtensionMessageKey: installCheck(content)]
+ } else {
+ response.userInfo = [SFExtensionMessageKey: ["error": "failed to get script content"]]
}
}
- else if name == "USERSCRIPT_INSTALL_02" {
- if
- let content = message?["content"] as? String,
- let reply = installUserscript(content)
- {
- response.userInfo = [SFExtensionMessageKey: reply]
+ else if name == "POPUP_INSTALL_SCRIPT" {
+ if let content = message?["content"] as? String {
+ response.userInfo = [SFExtensionMessageKey: installUserscript(content)]
+ } else {
+ response.userInfo = [SFExtensionMessageKey: ["error": "failed to get script content (2)"]]
}
}
else if name == "PAGE_INIT_DATA" {