Search code examples
javascripthtmlaccessibilitymutation-observers

MutationObserver not firing on elements with role="grid"


I'm attempting to carry out an accessibility fix on a table that has been poorly formatted using incorrect ARIA techniques.

To do this, I've written a MutationObserver to watch the page for instances of the table with role="grid" and dynamically change this to role="table", which addresses an accessibility issue when navigating the table with a screen reader.

I've been able to use the MutationObserver on other elements for this app (e.g., to fix incorrect use of aria-labelledby and role="group") and in those cases, I use the same logic and the MutationObserver executes the callback function and successfully changes the ARIA attributes. However, I can't understand why the MutationObserver is not identifying any role="grid" elements here when they appear in the DOM.

Here is my MutationObserver code:



const callback = (mutationList, observer) => {
    try {
        for (const mutation of mutationList) {
            if (mutation.type === "attributes") {
                if (mutation.attributeName === 'role') {
                    if (mutation.target.getAttribute('role') == 'grid') {
                        /* Replace ARIA role='grid' with role='table' */
                        mutation.target.setAttribute('role', 'table');
                        console.log(`The ${mutation.attributeName} attribute was changed`
                            + ` from ${mutation.target} to`
                            + ` ${mutation.target.getAttribute(mutation.attributeName)}`);
                    }
                }
            } else if (mutation.type === 'childList') {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        if (node.hasAttribute('role')) {
                            if (node.getAttribute('role') == 'grid') {
                                /* Replace ARIA role='grid' with role='table' */
                                node.setAttribute('role', 'table');
                                console.log(`The ${mutation.attributeName} attribute was changed`
                                    + ` from ${mutation.target} to`
                                    + ` ${mutation.target.getAttribute(mutation.attributeName)}`);
                            }
                        }
                    }
                }
            }
        }
    } catch(e) {
        console.error(e.message);
    }
};



// Select the node that will be observed for mutations
const targetNode = document.body;

// Options for the observer (which mutations to observe)
const config = {
    attributes: true,
    attributesOldValue: true,
    childList: true,
    subtree: true};

// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config);


I can't directly copy the HTML source from the app because it's a proprietary app, but the structure of the role="grid" object looks like this:

<div
  class="_xzy"
  role="grid"
  aria-rowcount="1"
  aria-colcount="11"
  id="HospitalityGrid"
  aria-multiselectable="true"
  aria-label="Hospitality"
  tabindex="0"
  style="height:
  250px; width: 720px;">
...
</div>

Just to anticipate any questions as to why I'm writing a MutationObserver to fix accessibility rather than requesting that the correct ARIA be implemented in the app itself, I don't have access to the source code or developers and so I'm writing this fix as an extension/add-in to make it more accessible to screen reader users.

To summarise, I tried writing a MutationObserver to automatically catch and fix elements with incorrect ARIA attribute role="grid". Currently nothing is happening as the role="grid" elements are not being identified by the MutationObserver callback function.


Solution

  • I figured out what was wrong. While my MutationObserver was checking the added nodes, I wasn't checking the parent node (i.e., mutation.target). When I added an if statement to check the mutation.target node as well, my MutationObserver successfully registered the offending div element with role='grid' and changed it to role='table'.

      // Callback function to execute when mutations are observed
      const callback = (mutationList, observer) => {
        try {
          for (const mutation of mutationList) {
            if (mutation.type === 'childList') {
              /* Examine parent node first */
              if (mutation.target.hasAttribute('role')) {
                if (mutation.target.getAttribute('role') === 'grid') {
                    mutation.target.setAttribute('role', 'table');
                    console.log(`The role attribute was changed from `
                      + `role='grid' to role='table'.`);
                }
              }
              /* Examine child/descendant nodes */
              for (const node of mutation.addedNodes) {
                if (node.nodeType === Node.ELEMENT_NODE) {
                  if (node.hasAttribute('role')) {
                    if (node.getAttribute('role') === 'grid') {
                      node.setAttribute('role', 'table');
                      console.log(`The role attribute was changed from `
                        + `role='grid' to role='table'.`);
                    }
                  }
                }
              }
            }
          }
        } catch(e) {
         console.error(e.message);
        }
      };
    
      // Select the node that will be observed for mutations
      const targetNode = document.getElementById('mainContainer');
    
      // Options for the observer (which mutations to observe)
      const config = {
        attributes: true,
        attributesOldValue: true,
        childList: true,
        subtree: true
      };
    
      // Create an observer instance linked to the callback function
      const observer = new MutationObserver(callback);
      
      // Start observing the target node for configured mutations
      observer.observe(targetNode, config);