• Resolved Marcus

    (@power2009)


    Bug: dropdown.js — load_tlib() throws “callback is not a function” on repeat focus/hover

    Affected asset: https://cdn.gtranslate.net/widgets/latest/dropdown.js (the “dropdown” widget variant; other variants may share the same helper — worth checking float.js / dwf.js / globe.js too).

    Root cause

    load_tlib(callback) is defined as a general-purpose helper that lazy-injects lib.min.js and then runs a callback once it’s loaded:

    function load_tlib(callback){
    if(!window.gt_translate_script){
    window.gt_translate_script = document.createElement('script');
    gt_translate_script.src = 'https://cdn.gtranslate.net/widgets/latest/lib.min.js';
    document.body.appendChild(gt_translate_script);
    if(callback) gt_translate_script.addEventListener('load', callback, {once:true});
    } else {
    callback && callback();
    }
    }

    But it is also registered directly as a native DOM event listener:

    e.addEventListener('pointerenter', load_tlib);
    e.addEventListener('focusin', load_tlib);

    When the browser invokes an event listener, it always passes the triggering Event object as the argument. So on every focusin/pointerenter fired this way, callback inside load_tlib is actually a native Event, not a function.

    • First call (window.gt_translate_script not yet set): goes into the if branch. gt_translate_script.addEventListener('load', callback, {once:true}) is called with callback = an Event object. Per the addEventListener spec this is silently ignored (no crash, but also no real “on load” callback ever gets registered).
    • Any subsequent call on the same wrapper (user re-focuses or re-hovers the widget after the first interaction): window.gt_translate_script now exists, so execution hits the else branch: callback && callback();. callback is again a truthy Event object, so this attempts to invoke the Event instance as a function — which throws:
    Uncaught TypeError: callback is not a function

    Steps to reproduce

    1. Load a page with the dropdown widget using default settings (url_structure: 'none', so the interaction-based lazy-load path runs).
    2. Move keyboard focus into the widget’s wrapper element once, or hover it with a pointer once — no error; window.gt_translate_script gets set on this first pass.
    3. Move focus/hover away and then back onto the wrapper a second time (or trigger focusin/pointerenter on it again in any way) — Uncaught TypeError: callback is not a function is thrown from the callback && callback(); line inside load_tlib.
    4. This repeats on every subsequent focus/hover cycle on the same page load.

    Suggested fix

    load_tlib is used both as a plain callback-style helper (e.g. called manually inside doGTranslate with a real function) and directly as a native event listener — these two calling conventions are incompatible without normalizing the argument. Either:

    • (a) Wrap it before attaching as a listener, so it’s never invoked with the native Event as its argument: e.addEventListener('focusin', () => load_tlib());e.addEventListener('pointerenter', () => load_tlib());
    • (b) Or guard the type inside load_tlib itself, so any non-function argument is ignored instead of crashing: if (typeof callback === 'function') callback(); (and the same guard before the addEventListener('load', callback, ...) call).

    Either change is small, localized to load_tlib, and doesn’t affect the widget’s public config/settings.

Viewing 2 replies - 1 through 2 (of 2 total)
Viewing 2 replies - 1 through 2 (of 2 total)

You must be logged in to reply to this topic.