/ 3 min read / chromelearningbuildingextensions

Week in Review - CSS selectors, MutationObserver, opening GitHub Issues via link and more

I finished writing an extension for Chrome to hide comments. I wrote about my reasons for writing it before, but even as I was writing about it I had more features in mind and a few things I wanted to change. After a few weeks of fiddling around, I think I'm pretty much done with it.

What changed?

  • Added a full popup page, to makes it easier to add the current URL to the whitelist or request it be added to the blacklist. Can also toggle comments on the current page and open options.
  • Replaced the "tabs" permission with "activeTab" (less invasive, no scary permission request).
  • I was using individual content scripts to hide comments on specific sites, but that's not scalable at all. Replaced them with one script that uses an external file instead.
  • Created a "dev" version of the external file, which I can use for testing before making a change that would affect users.

What did I learn this week?

Can't do something like this without learning something new! Here's some of the more interesting things I learned...

Regular Expressions

I generally hate regex. It's tough to understand and maintain, even for seasoned programmers, and people that do learn it sometimes treat it like a golden hammer. Yes, sometimes it's necessary - but sometimes there's a more pragmatic solution.

I needed it for this though, and the regular expressions 101 site helped me keep my sanity while I was experimenting with different patterns. It not only shows you which part of your sample input matched your pattern, but it explains exactly why step-by-step.

Debugging a background page

When you're writing an extension, the background page has no interface, so it's a little difficult to debug issues. As it turns out, Chrome has you covered. When you load an extension in developer mode to test it, there's a link opens a separate floating console window just for background page output.

The following image, complete with hand-drawn circles, was borrowed shamelessly from Rob W's answer here.

CSS Selectors

My original idea was to specify arrays of classes, tags and ids for elements I wanted to hide, and then scan the DOM multiple times looking for each one. Hopefully you can see how that's pretty ineffecient.

{
    "sites":
    [
        {
            "pattern": "https?://(.*[.])?ijr[.]com",
            "classes": ["opt360-discuss-cta-top-comments"]
        },
        {
            "pattern": "https?://.*",
            "classes": ["fb-comments", "o2-display-comments-toggle", "o2-post-comments", "o2-post-comment-controls", "commentlist"],
            "tags": ["fb:comments-count"],
            "ids": ["disqus_thread", "comments", "respond"],
            "delayselectors": [
                                {"selector": "#disqus_thread > iframe", "id_element": "disqus_thread"}
                              ]
        }
    ]
}
classNames.forEach(function(className) {
    toggleElements(document.getElementsByClassName(className), isHidden);
});

tagNames.forEach(function(tagName) {
    toggleElements(document.getElementsByTagName(tagName), isHidden);
});

ids.forEach(function(id) {
    toggleElement(document.getElementById(id), isHidden);
});

Then I found out about CSS selectors and the document.querySelectorAll() function that makes searching much easier. I was able to specify, in a single string, all the elements I wanted to look for.. and then just search for them all at one time.

{
    "pattern": "https?://(.*[.])?dev[.]to",
    "immediate": ".comments-container-container, .comments-count"
},
{
    "pattern": "https?://(.*[.])?youtube[.]com",
    "immediate": "ytd-comments",
    "delay": "ytd-comments"
},
{
    "pattern": "https?://(.*[.])?reddit[.]com",
    "immediate": ".commentarea"
},
function handleSelectors(selector, isHidden) {
    toggleElements(document.querySelectorAll(selector), isHidden);
}

function handleDelaySelectors(selector, isHidden) {
    document.arrive(selector, {onceOnly: true}, function() {
        toggleElements(document.querySelectorAll(selector), isHidden);
    });
}

Here's an example you can check out that shows the complexity and versatility of selectors. It selects only certain child elements within an element.

Handling delayed elements in the DOM

Did you notice the function above called "handleDelaySelectors"?

One of issues I didn't plan for right away was delayed elements. Take Disqus for example. It's possible that it isn't loaded until the user scrolls down to where the comments are located, long after the page loads. I was initially only hiding elements on page load.

Luckily I found a nice library called arrive.js, which uses DOM mutation observers to watch for elements that change on the page after it's already loaded. The following function just waits until the expected element appears (assuming it every does appear), and then stops watching once it does (that's the {onceOnly: true} part).

function handleDelaySelectors(selector, isHidden) {
    document.arrive(selector, {onceOnly: true}, function() {
        toggleElements(document.querySelectorAll(selector), isHidden);
    });
}

One of the last things I wanted to add was a button to make it easier for users to request that a site be blocked. As it turns out, GitHub allows you to open a new issue with a link by passing the title and body as query parameters.

var title = "Here's a new site I'd like you to consider blocking";
var body = encodeURIComponent(title + ":\n\n" + urlToInclude);
var url = `https://github.com/grantwinney/hide-comments-in-chrome-sites/issues/new?title=${title}&body=${body}`;
window.open(url, '_blank')

Try it out!

If you'd like, get it from the Chrome store and let me know what you think of it!


Grant Winney

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.

Read More