diff --git a/README.md b/README.md index e0b8c63..401e80a 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Currently, ChromeXt supports almost all [Tampermonkey APIs](https://www.tampermo 5. @grant: GM_addStyle, GM_addElement, GM_xmlhttpRequest, GM_openInTab, GM_registerMenuCommand (shown in the `Resources` panel of eruda), GM_unregisterMenuCommand, GM_download, unsafeWindow (= window) 6. @grant: GM_setValue, GM_getValue (less powerful than GM.getValue), GM_listValues, GM_addValueChangeListener, GM_removeValueChangeListener, GM_setClipboard, GM_cookie, GM_notification 7. @require, @resource (without [Subresource Integrity](https://www.tampermonkey.net/documentation.php#api:Subresource_Integrity)) +8. window.close (implemented for most Chromium based browsers) These APIs are implemented differently from the official ones, please refer to the source files [Local.kt](app/src/main/java/org/matrix/chromext/script/Local.kt) and diff --git a/app/src/main/assets/GM.js b/app/src/main/assets/GM.js index 2801af0..b0af3cb 100644 --- a/app/src/main/assets/GM.js +++ b/app/src/main/assets/GM.js @@ -1078,12 +1078,12 @@ GM.bootstrap = () => { const grants = meta.grants; - if ( - meta["inject-into"] == "page" || - grants.includes("none") || - grants.includes("unsafeWindow") - ) { + if (meta["inject-into"] == "page" || grants.includes("none")) { GM.globalThis = window; + if (grants.includes("window.close")) { + // The page may abuse window.close + window.close = () => ChromeXt.dispatch("close"); + } } else { const handler = { // A handler to block access to globalThis @@ -1108,7 +1108,9 @@ GM.bootstrap = () => { get(target, prop, receiver) { if (target[prop] == target) return receiver; // Block possible jail break - if (this.keys.includes(prop)) { + if (prop == "close" && grants.includes("window.close")) { + return () => ChromeXt.dispatch("close"); + } else if (this.keys.includes(prop)) { const val = target[prop]; return typeof val == "function" ? val.bind(target) : val; } else if ( diff --git a/app/src/main/java/org/matrix/chromext/Listener.kt b/app/src/main/java/org/matrix/chromext/Listener.kt index 5790adc..d57ccc7 100644 --- a/app/src/main/java/org/matrix/chromext/Listener.kt +++ b/app/src/main/java/org/matrix/chromext/Listener.kt @@ -36,7 +36,9 @@ import org.matrix.chromext.script.parseScript import org.matrix.chromext.utils.ERUD_URL import org.matrix.chromext.utils.Log import org.matrix.chromext.utils.XMLHttpRequest +import org.matrix.chromext.utils.findMethod import org.matrix.chromext.utils.invalidUserScriptUrls +import org.matrix.chromext.utils.invokeMethod import org.matrix.chromext.utils.isChromeXtFrontEnd import org.matrix.chromext.utils.isDevToolsFrontEnd import org.matrix.chromext.utils.isUserScript @@ -143,6 +145,45 @@ object Listener { if (isUserScript(url)) invalidUserScriptUrls.add(url!!) callback = "if (Symbol.ChromeXt) Symbol.ChromeXt.lock(${Local.key},'${Local.name}');" } + "close" -> { + val activity = Chrome.getContext() + if (Chrome.isSamsung && + currentTab != null && + activity::class.java == + Chrome.load("com.sec.android.app.sbrowser.SBrowserMainActivity")) { + val manager = activity.invokeMethod { name == "getTabManager" }!! + @Suppress("UNCHECKED_CAST") + val tabList = manager.invokeMethod { name == "getAllTabList" } as List + tabList + .find { it.invokeMethod { name == "getTab" } == currentTab } + ?.also { manager.invokeMethod(it) { name == "closeTab" } } + } else if (currentTab != null && + activity::class.java == UserScriptProxy.chromeTabbedActivity) { + val tab = Chrome.load("org.chromium.chrome.browser.tab.Tab") + val tabModel = Chrome.load("org.chromium.chrome.browser.tabmodel.TabModel") + val getCurrentTabModel = + findMethod(activity::class.java, true) { + parameterTypes.size == 0 && returnType == tabModel + } + val model = getCurrentTabModel.invoke(activity)!! + val closeTab = + findMethod(model::class.java) { + returnType == Boolean::class.java && + parameterTypes contentDeepEquals + arrayOf( + tab, + tab, + Boolean::class.java, + Boolean::class.java, + Boolean::class.java, + Int::class.java) + } + closeTab.invoke(model, currentTab, null, false, false, false, 0) + } else { + val msg = "Closing tab ${currentTab} with context ${activity}" + callback = "console.error(new TypeError('ChromeXt Action failure', {cause: '${msg}'}));" + } + } "focus" -> { Chrome.updateTab(currentTab) } diff --git a/app/src/main/java/org/matrix/chromext/MainHook.kt b/app/src/main/java/org/matrix/chromext/MainHook.kt index 9fad9bd..09c25ae 100644 --- a/app/src/main/java/org/matrix/chromext/MainHook.kt +++ b/app/src/main/java/org/matrix/chromext/MainHook.kt @@ -65,7 +65,7 @@ class MainHook : IXposedHookLoadPackage, IXposedHookZygoteInit { } .onFailure { initHooks(ContextMenuHook) - if (BuildConfig.DEBUG) Log.ex(it) + if (BuildConfig.DEBUG && !Chrome.isSamsung) Log.ex(it) } } } else { diff --git a/app/src/main/java/org/matrix/chromext/script/Local.kt b/app/src/main/java/org/matrix/chromext/script/Local.kt index 3cb678d..2b4b76d 100644 --- a/app/src/main/java/org/matrix/chromext/script/Local.kt +++ b/app/src/main/java/org/matrix/chromext/script/Local.kt @@ -49,6 +49,7 @@ object GM { "none" -> return@forEach "GM_info" -> return@forEach "GM.ChromeXt" -> return@forEach + "window.close" -> return@forEach else -> if (localScript.containsKey(it)) { grants += localScript.get(it)