Week in Review - Disabling a popup at runtime, Learning the importance of DOMContentLoaded, and Callback fun

Full article

I continued working on "Hide Comments Everywhere" this weekend. At this point, it's just been fun to learn new things and see how I can evolve it. According to the stats page there's about 20 users too, so hopefully someone besides me is finding it useful! Sick of seeing trollish comments? Try it out and let me know what you think.

Here are a few things I learned this weekend.

When chrome.runtime.openOptionsPage doesn't run the callback

Many of the functions in the set of Chrome JavaScript APIs are executed asynchronously. This poses a problem if you have a piece of code that needs to wait until the API call has finished. To solve that, there's usually an optional "callback" parameter, where you can specify some code that should only run after the previous function has completed. Until now, it's always worked fine for me.

I setup the "options" page so that it appears the content is in 3 tabs, but it's all one page and I just show or hide the correct <div> element. Then I setup the "popup" page with three buttons (one for each tab), which would open the "options" page and then show the correct tab.

<input id="options" type="submit" value="Options">
<input id="filters" type="submit" value="Filters">
<input id="support" type="submit" value="Support">

I ran into a weird issue where, even though I was specifying a callback to execute (in this, sending a message) after the "options" page was open, the callback wouldn't execute. Unless the "options" page was already open. No "options" page open? It would open up but not navigate to the correct tab. The "options" page is already open? It would focus on it and then would navigate to the correct tab.

document.getElementById('options').addEventListener('click', function() {
    chrome.runtime.openOptionsPage(function() {
        chrome.tabs.sendMessage(tabId, { event: 'options_request', pane_to_show: 'options' });
document.getElementById('filters').addEventListener('click', function() {
    chrome.runtime.openOptionsPage(function() {
        chrome.tabs.sendMessage(tabId, { event: 'options_request', pane_to_show: 'filters' });
document.getElementById('support').addEventListener('click', function() {
    chrome.runtime.openOptionsPage(function() {
        chrome.tabs.sendMessage(tabId, { event: 'options_request', pane_to_show: 'support' });

I have a couple theories but am not completely sure what's going on.

  • The act of opening the "options" page causes the "popup" page to close and halts execution of the callback.
  • The "options" page isn't completely loaded yet, so the message is not received nor acted upon.

I think the callback works just fine, as it does for all the APIs, but there's some interaction I'm not quite understanding. In the end I just gave up (for now?) and simply added a single button that opens the "options" page.

Toggle between browser action click and popup

I wanted to be able to disable the popup at runtime, without reloading the extension or the browser or anything disruptive. As it turns out, it was much easier than expected.

When a "popup" page is set, then chrome.browserAction.onClicked is not fired. If the "popup" page is not set, it will. So I added a setting where you can select whether or not you'd like the full popup when clicking the extension icon, or just toggle comments on the current tab. Then I added the following code to the background.js file, which checks that setting and either sets the popup to a legitimate file... or a blank string, which disables it!

chrome.storage.local.get('one_click_option', function(result) {
    var oneClickEnabled = (result != undefined && result.one_click_option == true);
    chrome.browserAction.setPopup({popup: oneClickEnabled ? "" : "../popup.html"});

Use 'DOMContentLoaded' instead of the 'load' event

Glad I stumbled across this one.

I do some extra work when the "popup" or "options" pages are loaded, and I was relying on the "load" event to let me know when I could kick off that work. But all I really needed was for the DOM to be loaded, so I was waiting longer than I had to. And suprisingly, even though I'm not pulling any resources from outside my extension, there was still a noticeable difference in loading/rendering.

From the MDN Web docs:

The DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading. A very different event load should be used only to detect a fully-loaded page. It is an incredibly popular mistake to use load where DOMContentLoaded would be much more appropriate, so be cautious.

Check out this thread on Stack Overflow, in particular the archived demo here. It's significant!


Grant Winney

I write when I've got something to share - a personal project, a solution to a difficult problem, or just an idea. We learn by doing and sharing. We've all got something to contribute.

Comments / Reactions