Many web pages force the link to open in a new window (or a new tab, collectively referred to as a new window below). Some people find this approach convenient, as it preserves the original page. But I don’t like this approach. Because I use Firefox, if I open a web page in a new window or tab, I can’t use the touchpad to swipe back to the previous page. Today, I’ll share with you the method to force the link to open in the current window or web page.

Before we share the method, let’s see what are the ways to make the browser open tabs in a new window.

The easiest way is to add the target="_blank" attribute to all hyperlinks <a>. This is also the most common way.

Adding target to every <a> tag is a bit wasteful, inconvenient to modify, and increases the size of the HTML file. One optimization is to add a target attribute to the <base> tag. Modifying the <base> tag will affect all relative links. This means that this method only works for internal links.

The third option is to use JavaScript to dynamically modify the behavior of hyperlinks.

The easiest thing is to assign onclick callback function to each <a> tag and call window.open(url, '_blank') in the function to open the new page.

A slightly more optimized solution would be to register a click' event handler for document, extract the href attribute of the <a> tag based on the target of the event, and then call the open() function to open the new page. This solution requires the click event to bubble to the document node to work.

All of the above is done by changing the behavior of the <a> tag to open a new page, and the href attribute of the <a> tag is the target link. However, some content platforms do not write the target URL to the href attribute, but write the URL as a link shaped like https://example.com?target=real-url. When users click on it, they will jump to an intermediate page first and then click twice to jump to the target page, which is very inconvenient.

I wanted a unified way to deal with several of these types of issues. The first thing I did was to use a Chrome plugin called Death To _blank. This plugin does a very simple job of removing the target attribute from the a tag. But it didn’t cover the remaining cases. Then I migrated to Firefox and couldn’t find a similar extension. I ended up using Tampermonkey script to achieve the full functionality.

The script framework is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ==UserScript==
// @name     Remove target="_blank"
// @version  1
// @grant    none
// @match    *://*/*
// @run-at   document-end
// ==/UserScript==
(function (window) {
  "use strict";
  // todo...
})(window);

@run-at document-end means that the script is run after the page is loaded, and @match *://*/* means that the script is executed for all pages.

Then comes the real processing logic. The first step is to clear the target attribute of the <base> tag.

1
2
let base = document.querySelector('base');
if (base) removeAttribute('target');

Not all pages will have <base> tags, so you need to determine that.

The next step is to clean up the target attribute on each <a> tag. But there may be a lot of hyperlinks on the page, and only a few of them will actually be clicked, or not opened at all. It would be a waste to iterate through all the links and clean up the target attribute right away. For this reason, I use a delayed processing approach. Simply put, we listen to the mousedown event of the document and determine if the current node is an <a> tag when the event is triggered. Some web pages embed images, in-line code and other tags in the <a> tag, so the target of the mousedown event is not necessarily the <a> tag. For this reason, we have to iterate through the parent nodes of the current node to see if there is an <a> tag.

The complete check logic is as follows.

1
2
3
4
5
6
7
8
9
document.addEventListener('mousedown', function (event) {
  var a = event.target, depth = 3;
  
  // The current html tag may not be <a> and needs to be recursively queried for its parent tag.
  while (a && a.tagName != 'A' && depth-- > 0) { a = a.parentNode; }
  if (a && a.tagName == 'A') {
    // todo...
  }
}, true);

If only the target property is set, we can just clear it.

1
a.removeAttribute('target');

If the onclick callback is set or if the document listens for all <a> clicks, we can clear the callback and disable the click bubble.

1
a.onclick = (e) => e.stopImmediatePropagation();

Be careful though, some pages may use <a> as a button, usually its href will be #, so you need to skip this. There are also a few pages that do intercept <a> jumps via JavaScript, there is no way to identify this and you can only exclude specific domains via the plugin’s blacklist.

For those links with intermediate page jumps, we need to extract the target link and update the href attribute.

1
2
3
4
var u = new URL(a.href);
var p = u.searchParams
var t = p.get("target") || p.get("to");
if (t) { a.href = t; }

Combine all functions to get the complete code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ==UserScript==
// @name     Remove target="_blank"
// @version  1
// @grant    none
// @match    *://*/*
// @run-at   document-end
// ==/UserScript==
(function (window) {
  "use strict";
  let base = document.querySelector('base');
  if (base) removeAttribute('target');

  document.addEventListener('mousedown', function (event) {
    var a = event.target, depth = 3;

    while (a && a.tagName != 'A' && depth-- > 0) { a = a.parentNode; }
    
    if (a && a.tagName == 'A') {
      a.removeAttribute('target');
      b. onclick = (e) => e.stopImmediatePropagation();
      var u = new URL(a.href);
      var p = u.searchParams
      var t = p.get("target")||p.get("to");
      if (t) { a.href = t; }
    }
  }, true);
})(window);