Search code examples
javascriptdomgoogle-chrome-extensiongetelementsbytagname

List unattached document elements


I'm trying to create an extension for Chrome to control playback using global hotkeys. I've faced such problem: somewhere the <audio/> elements are not attached to DOM. These elements are audible, but I can't get them with document.querySelector('audio') (because it iterates over attached DOM elements only).

Using DevTools I can see these elements:

> queryObjects(HTMLAudioElement)
< undefined
  (3) [audio, audio, audio]

But it's impossible to use this function outside DevTools (it doesn't even return result - this func just prints result to the console).

I'm looking a way to get unattached audio elements from the content_script (or background) of my extension.

I've tried:

  1. document.getElementsByTagName('audio')
  2. document.querySelectorAll('audio')
  3. document.evaluate('//audio', ...
  4. document.createNodeIterator(document.body, NodeFilter.SHOW_ALL, ...
  5. redefine constructor of HTMLAudioElement and document.createElement (failed: extension has its own window and linked document)
  6. listen 'play' and 'pause' events - they don't bubble

Have any other ideas? Maybe there are some extension-specific capabilities?


Solution

  • Thanks to @wOxxOm

    I've followed my idea #5: redefine constructor of HTMLAudioElement Audio and document.createElement

    inject.js:

    var instances = [];
    
    document.createElement = (function () {
        var _createElement = document.createElement;
        return function createElement() {
            var result = _createElement.apply(document, Array.prototype.slice.call(arguments, 0));
            if (result.tagName === 'AUDIO') {
                instances.push(result);
            }
            return result;
        };
    })();
    
    window.Audio = (function () {
        var _Audio = window.Audio;
        function Audio() {
            var result = new _Audio();
            instances.push(result);
            return result;
        };
        Audio.prototype = Object.create(_Audio);
        Audio.prototype.constructor = Audio;
        return Audio;
    })();
    
    // further code (dom-event-based communication, misc functions etc)...
    

    content.js:

    var injected = document.createElement('script');
    injected.src = chrome.extension.getURL('inject.js');
    (document.head || document.documentElement).appendChild(injected);
    

    manifest.json:

    {
          "manifest_version": 2
        , "content_scripts": [{
              "matches": ["<all_urls>"]
            , "js": ["content.js"]
            , "run_at": "document_start" // <-- important
            , "all_frames": true
        }]
        , "web_accessible_resources": [
            "inject.js"
        ]
    
        // ...
    }
    

    It's harmful to store all instances in an array, but this is the only way that I see: WeakSet is non-iterable.