diff --git a/src/actors/ClickHandlerChild.ts b/src/actors/ClickHandlerChild.ts
new file mode 100644
index 0000000..aa368fd
--- /dev/null
+++ b/src/actors/ClickHandlerChild.ts
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+///
+
+export type ClickHandlerMessage = {
+ name: 'openlink'
+ data: { href: string }
+} & { target: JSWindowActorParent }
+
+export class ClickHandlerChild extends JSWindowActorChild {
+ getHrefIfExists(target: Node): string | undefined {
+ if ((target as HTMLAnchorElement).href) {
+ return (target as HTMLAnchorElement).href
+ }
+
+ if (target.parentElement) {
+ return this.getHrefIfExists(target.parentElement)
+ }
+ }
+
+ handleEvent(event: MouseEvent) {
+ if (event.defaultPrevented || !event.target) return
+
+ const href = this.getHrefIfExists(event.target as Node)
+
+ const ctrlClick = event.button === 0 && event.ctrlKey
+ const middleClick = event.button === 1
+
+ const shouldOpenNewTab = href && (ctrlClick || middleClick)
+
+ if (!shouldOpenNewTab) return
+
+ this.sendAsyncMessage('openlink', { href })
+ }
+}
diff --git a/src/actors/ClickHandlerParent.ts b/src/actors/ClickHandlerParent.ts
new file mode 100644
index 0000000..fbc003c
--- /dev/null
+++ b/src/actors/ClickHandlerParent.ts
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+///
+import { ClickHandlerMessage } from './ClickHandlerChild'
+
+export class ClickHandlerParent extends JSWindowActorParent {
+ receiveMessage(event: ClickHandlerMessage) {
+ if (event.name == 'openlink') {
+ const win = event.target.browsingContext.embedderElement.ownerGlobal
+ const uri = Services.io.newURI(event.data.href)
+ win.windowApi.tabs.openTab(uri)
+ }
+ }
+}
diff --git a/src/actors/link.json b/src/actors/link.json
index 758dc8a..3435b4d 100644
--- a/src/actors/link.json
+++ b/src/actors/link.json
@@ -1,4 +1,6 @@
[
+ "ClickHandlerChild",
+ "ClickHandlerParent",
"ContextMenuChild",
"ContextMenuParent",
"LinkHandlerChild",
diff --git a/src/modules/BrowserGlue.ts b/src/modules/BrowserGlue.ts
index e2b3acf..de848ac 100644
--- a/src/modules/BrowserGlue.ts
+++ b/src/modules/BrowserGlue.ts
@@ -10,6 +10,18 @@ const lazy = lazyESModuleGetters({
const JS_PROCESS_ACTORS = {}
const JS_WINDOW_ACTORS = {
+ ClickHandler: {
+ parent: { esModuleURI: 'resource://app/actors/ClickHandlerParent.sys.mjs' },
+ child: {
+ esModuleURI: 'resource://app/actors/ClickHandlerChild.sys.mjs',
+ events: {
+ chromelinkclick: { capture: true, mozSystemGroup: true },
+ },
+ },
+
+ allFrames: true,
+ },
+
ContextMenu: {
parent: {
esModuleURI: 'resource://app/actors/ContextMenuParent.sys.mjs',