Skip to content

Commit

Permalink
Implement window.close for Chromium browsers
Browse files Browse the repository at this point in the history
Close #180 as finished.

`window.close` API should not be exposed to the main webpage.
Hence, we must make a stricter rule for the usage of unsafeWindow:
currently, one must use unsafeWindow to access relevant properties when
it is granted, no implicit equivalence between window and unsafeWindow
will be implied.

Especially, unsafeWindow.close != window.close when window.close is
granted.
  • Loading branch information
JingMatrix committed Jul 27, 2024
1 parent f412167 commit 12a9773
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 8 additions & 6 deletions app/src/main/assets/GM.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 (
Expand Down
41 changes: 41 additions & 0 deletions app/src/main/java/org/matrix/chromext/Listener.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Any>
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)
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/org/matrix/chromext/MainHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/org/matrix/chromext/script/Local.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 12a9773

Please sign in to comment.