Why is querySelector returning null?

Hi,

I have the following code (more or less) in a Firefox extension:

var styles = `
        .NEAR {
            display:block;
        }
`
    window.styleSheet = document.createElement("style")
    styleSheet.textContent = styles
    document.head.appendChild(styleSheet)

    const element = document.head.querySelector('.NEAR');

But element is always null

I’ve also tried adding the style section to document.body and directly to the html file, with the same effect.

The quirk is that I’m reading a Firefox bookmark html export file (<!DOCTYPE NETSCAPE-Bookmark-file-1>), so it’s processed in quirk mode.

I haven’t explored this behaviour with a well formed HTML file yet.

The value of the document.head.style attribute in the DOM is CSS2Properties(0) and isn’t expandable, so presumably the list is empty, as per the source file. However, the dynamically added styles are working when the document is rendered, so they must be somewhere.

I want to modify the style for the class at runtime, but I’ve resorted to manipulating the classList of elements directly, which is a royal pain. Another option (which I haven’t tried yet) is to remove the whole style element and recreate it with changes, but it would be much simpler to just modify the element.style attribute.

Any explanation would be appreciated, as would a link to a tutorial that explains how these structures (sources, elements, nodes, DOM, etc) are related and how they interact.

Cheers,
bitrat

You can’t use querySelector to select a CSS style, it can select only HTML elements:

When you use:

const styleSheet = document.createElement("style");

to create a new CSSStyleSheet, you can call methods on it, like insertRule or deleteRule.
See the docs:

For example (as inspiration, not a solution!), here is my helper class for creating and removing styles dynamically:

import {DOMContentLoadedPromise} from '../html/dom_ready';
import {lazyValFunction} from '../executors/lazy_val';

export class DynamicStyle {
  private readonly _style = document.createElement('style');
  // instead of injecting script to the body right away, I will simply wait for the first usage
  private readonly injectToBody = lazyValFunction(async () => {
    await DOMContentLoadedPromise;
    // document.head can be "null" in some XML
    document.head?.appendChild(this._style);
  });

  constructor(initialRule?: string) {
    if (initialRule) this.loadRule(initialRule);
  }

  async loadRule(...styles: string[]) {
    await this.injectToBody();
    const _styleSheet = this._style.sheet;
    // in some XML the "sheet" is "null"
    if (_styleSheet) {
      for (const style of styles) {
        _styleSheet.insertRule(style);
      }
    }
  }

  async unloadAll() {
    await this.injectToBody();
    const _styleSheet = this._style.sheet;
    if (_styleSheet) {
      while (_styleSheet.cssRules.length > 0)
        _styleSheet.deleteRule(0);
    }
  }
}

You can also consult with ChatGPT, it’s great for solving this kind of “easy” requests :slight_smile:.

1 Like

Hey @bitrat! Unfortunately I don’t know of a good resource that specifically covers how HTML and CSS interact with JavaScript, or more specifically an overview of styling on the web using JavaScript. MDN has a lot of good content to help you learn about the platform, but I’ll admit that’s kind of like handing you a textbook and saying “this is a good overview of world history.” Still, here are a couple of entry points that might help you along the learning journey.

I’ll try to share a couple of pointers based on your question

For example, let’s take statically setting styles on a web page. We have two basic approaches to add a CSS stylesheet to a page using a <link rel="stylesheet"> element or using a <style> element. The basic difference between these is that <link> elements reference other resources (web pages, files, etc.) while <style> elements contain the stylesheet.

In addition to those static methods of interacting with a page’s styles, there are also a few different ways you can modify a page or element’s style using JavaScript. I can’t be exhaustive here, but some of the major approaches include:

1. Using JavaScript to add new <style> tag to the page. For example:

const stylesheet = document.createElement("style");
stylesheet.textContent = `body { background: red }`;
document.head.appendChild(stylesheet);

2. Directly modifying an element’s style (see HTMLElement.style). For example:

document.body.style.background = "red";

3. Attaching a constructed stylesheet to the page (Constructable Stylesheets). For example:

const stylesheet = new CSSStyleSheet();
stylesheet.replace("body { background: red; }");
document.adoptedStyleSheets.push(stylesheet);

Adding a style to an existing stylesheet. For example:

const stylesheet = document.querySelector('link[rel="stylesheet"]').sheet;
stylesheet.insertRule("body { background: red }", stylesheet.rules.length - 1);

Generally speaking, I’d strongly recommend against modifying existing stylesheets if possible.

Hope that helps get you pointed in the right direction :slight_smile:

1 Like

Thanks! Yes, you’re right! I’m a bit of a Luddite with these new AI tools, not that I don’t understand them, I’m just a bit nervous about their social impact… And probably want to avoid another rabbit hole… :smiley:

I once asked Bing to explain how to work with Lie algebras and it did a great job (as far as I could tell…) but I’ve not used it since. What (free) chat would you suggest for this kind of query? I’ve heard the meta one is good for math and coding…

This extension is for my own use, much as I’d use bash or python, so quick and dirty is the order of the day. I haven’t got time to grok in depth, although I’d like to…

Thank you for the helpful pointers too!

See below for my solution! :slight_smile:

Thanks again,
bitrat

Hi, yes very helpful summary and links, thank you.

I didn’t have a lot of luck with CSSStyleSheet… since the line document.adoptedStyleSheets.push(stylesheet); didn’t seem to translate to the available interfaces.

However, as I suggested above, I have found a quick and dirty solution (see below)!

Thanks again,
bitrat

So… I’ve solved my issue this way:

function doHide( hide ){

    const visibleStyles = `
            .NEAR { 
                display:block;
            }
    `
    const hiddenStyles = `
            .NEAR { 
                display:none;
            }

   if( typeof doHide.styleSheet == 'undefined' ){
      doHide.styleSheet = document.createElement("style");
      doHide.styleSheet.textContent = visibleStyles;
      document.head.appendChild(doHide.styleSheet);
   }
                        
   if( hide ){
      doHide.styleSheet.textContent = hiddenStyles;
   }else{
      doHide.styleSheet.textContent = visibleStyles;
   }
}

There may be caveats, and I’d be pleased to hear them, but this works fwiw, and is simple and clear.

As for querySelector returning null, I can see why that is now. I’m not even sure why I thought it would work, lol. I have written a bit of XSLT, but not for DOM. I’d be interested in a good reference…

One thing I am curious about is the priority in which conflicting rules are resolved…

I need to get a better understanding of the data structures involved, but that’s for another day. :slight_smile:

bitrat

1 Like

Assuming you’re referring to CSS priority calculation, check out these MDN and CSS Tricks articles on “specificity”. Once you feel like you’ve wrapped your head around the basics, I’d also suggest playing with a selector specificity calculator to help verify your understanding.

1 Like