librelist archives

« back to archive

buffer_dom_content_loaded_hook run many times per page load

buffer_dom_content_loaded_hook run many times per page load

From:
Sean McAfee
Date:
2014-06-05 @ 16:43
Recently I discovered that buffer_dom_content_loaded_hook can be run
multiple times while rendering a single web page.  I had expected it to run
only once; I was starting an interval timer in the hook that ran every two
seconds, and while one would not have been noticeable, the hook ran thirty
times for one particular page, and the overlapping timers noticeably
degraded performance.

Here's the bit from buffer.js that runs the hook:

    this.browser.addEventListener("DOMContentLoaded", function (event) {
            buffer_dom_content_loaded_hook.run(buffer);
        }, true /* capture */);

I surmised that perhaps embedded iframe elements were also triggering the
event, so I prepended a line to the callback body here:

    dumpln(event.target.documentURI);

Sure enough, URLs were printed that corresponded to iframe elements on the
page.  However, the main page's URL was printed thirteen of the thirty
times, and even this pointless-looking Javascript snippet occurred multiple
times:

javascript:"<html><body%20style='background:transparent'></body></html>"

I tried writing a simple wrapper around my callbacks that assumed that only
the first call to buffer_dom_content_loaded_hook corresponded to the main
page, but this turned out to be untrue; iframe elements can trigger the
event first, perhaps by virtue of being cached.  Since Conkeror's hook
ignores the details of the DOMContentLoaded event, there's no way for a
user hook to know that a page's DOM has actually finished loading.  (Unless
there's a method for it on buffer or window objects that I don't know
about.)

My interim solution is to add the event object to the arguments passed to
the callback:

    this.browser.addEventListener("DOMContentLoaded", function (event) {
            buffer_dom_content_loaded_hook.run(buffer, event);
        }, true /* capture */);

Then I wrap my hook callbacks thusly:

    const buffer_loaded_flag = "__conkeror_loaded_buffer";

    function add_dom_content_loaded_hook(callback) {
        add_hook("buffer_dom_content_loaded_hook", function (buffer, event)
{
            if (!(buffer_loaded_flag in buffer.top_frame)) {
                if (event.target.documentURI == buffer.top_frame.location) {
                    buffer.top_frame[buffer_loaded_flag] = true;
                    callback(buffer);
                }
            }
        });
    }

The flag is to ensure that the event happens only once per page.  At least,
I sure hope that the first event I let through corresponds to a real
page-loaded state, and not one of the later ones.

Obviously a cleaner solution would be welcome, but it's hard to see how it
could be possible without changing buffer.js, either by adding an argument
to the callback as I did above, or maybe by adding a new hook.
buffer_top_level_dom_content_loaded_hook, maybe?

Incidentally, does anyone know of a way to associate data with a loaded
page other than by stashing it in buffer.top_frame, as above?  It works,
but I'm worried that a sufficiently entrprising web page could detect it
(not that big a deal) or change it (more of a deal), however unlikely it
seems.