Why can't my user script close a window it opens?

I have this userscript:

var spans = document.getElementsByTagName('span');
var promoted, parent, link, tab, button;

function findParentDiv(element) {
  if (!element) {
    return null;
  }

  parent = element.parentElement;
  while (parent) {
    if (parent.tagName.toLowerCase() === 'div' && parent.querySelector('a')) {
      return parent; // Found the parent div with an anchor tag
    }
    parent = parent.parentElement; // Move to the next parent element
  }

  return null; // Parent div not found
}


function findDownvoteButton(element) {
  if (!element) {
    return null;
  }

  parent = element.parentElement;
  while (parent) {
    if (parent.tagName.toLowerCase() === 'div') {
      button = parent.querySelector('button')
      if (button)
        if (button.aria_label == "through")
          return button;
    }
    parent = parent.parentElement; // Move to the next parent element
  }

  return null; // Parent div not found
}


for (var i = 0; i < spans.length; i++) {
  if (spans[i].textContent == 'promoted') {
    promoted = spans[i];
    if (findParentDiv(promoted)){
      link = parent.querySelector('a');
      tab = window.open(link.href);
      setTimeout(function(){tab.close();}, 1000);
      if (button){
        button.click();
        button = null;
      }
    }

  }
}

My issue is that tab.close() in the for loop causes this exception: VM1958:56 Uncaught TypeError: Cannot read properties of null (reading 'close') although the window does in fact open and link.href holds the correct value.

I added the setTimeout thinking maybe there was a race condition. It didn’t help, even when allowing enough time for the window to completely load.

Strangely, running this in the console after the script produces no error and has the desired effect:

  tab = window.open(link.href);
  tab.close();

That works when ran manually after running the script but it will not close the window when the script runs. Any ideas why this is happening?

>Solution :

Use closure to handle this issue.
More here in details with explanation:
The issue you’re experiencing is related to the asynchronous nature of opening and closing windows using window.open and window.close within a loop. The window.open function returns a reference to the opened window, which is stored in the tab variable. However, the window opening process takes some time, and the loop continues executing immediately without waiting for the window to fully load.

By the time the loop reaches the setTimeout function, the value of tab might still be null because the window hasn’t finished opening yet. Therefore, when setTimeout tries to call tab.close(), it throws the "Uncaught TypeError: Cannot read properties of null" error.

To address this issue, you can modify your code to utilize a closure or a separate function to maintain the context of each iteration of the loop. Here’s an updated version of your code that should work correctly:

var spans = document.getElementsByTagName('span');
var promoted, parent, link, tab, button;

function findParentDiv(element) {
  if (!element) {
    return null;
  }

  parent = element.parentElement;
  while (parent) {
    if (parent.tagName.toLowerCase() === 'div' && parent.querySelector('a')) {
      return parent; // Found the parent div with an anchor tag
    }
    parent = parent.parentElement; // Move to the next parent element
  }

  return null; // Parent div not found
}

function findDownvoteButton(element) {
  if (!element) {
    return null;
  }

  parent = element.parentElement;
  while (parent) {
    if (parent.tagName.toLowerCase() === 'div') {
      button = parent.querySelector('button');
      if (button && button.aria_label === "through") {
        return button;
      }
    }
    parent = parent.parentElement; // Move to the next parent element
  }

  return null; // Parent div not found
}

function openAndCloseWindow(url) {
  var newTab = window.open(url);
  setTimeout(function() {
    newTab.close();
  }, 1000);
}

for (var i = 0; i < spans.length; i++) {
  if (spans[i].textContent == 'promoted') {
    promoted = spans[i];
    if (findParentDiv(promoted)) {
      link = parent.querySelector('a');
      openAndCloseWindow(link.href);
      if (button) {
        button.click();
        button = null;
      }
    }
  }
}

In this updated code, the openAndCloseWindow function is introduced. It receives the URL as a parameter, opens the window using window.open, and then sets a timeout to close the window after a delay of 1000 milliseconds (1 second).

By using a separate function and passing the URL as a parameter, each iteration of the loop will have its own closure, ensuring that the correct tab reference is used when calling close().

With this modification, the windows should open and close correctly without throwing any errors.

Leave a Reply