• 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 4 replies - 1 through 4 (of 4 total)
  • Plugin Author edo888

    (@edo888)

    Hi,

    Thanks for the issue report. It is fixed now: https://plugins.trac.wordpress.org/changeset/3693190/google-language-translator

    It will be released with the next version.

    Thanks! 🙂

    Thread Starter Marcus

    (@power2009)

    Thank u for feedback)

    Thread Starter Marcus

    (@power2009)

    Hi @edo888,

    Thanks for r3693190 — the plugin-side part is verified working (the
    if(!window.gt_translate_script) guard in ToolBar.show() stops the repeated
    synthetic focusin crash on toolbar open).

    Unfortunately the original console error still reproduces, because it does
    not come from the plugin’s scripts.js — it comes from the widget file the
    plugin enqueues from your CDN:

    wp_enqueue_script(‘glt_widget_script’, ‘https://cdn.gtranslate.net/widgets/latest/dropdown.js’, array(), GLT_VERSION, true);

    Current state of that CDN file (checked today, Oct 2): Last-Modified
    Sep 6, 2026 — i.e. before the changeset — and the bug is still there:

    • line 91: function load_tlib(callback){ … else{callback&&callback();} }
    • line 97: e.addEventListener(‘pointerenter’,load_tlib);
      e.addEventListener(‘focusin’,load_tlib);

    Mechanics: load_tlib is registered directly as an event listener, so on
    pointerenter/focusin the browser calls it with the Event object as
    “callback”. The first call injects lib.min.js (silently); any following
    call takes the else branch and executes callback() on the Event:
    “Uncaught TypeError: callback is not a function @ dropdown.js:91”.
    A single real click on the language select produces pointerenter +
    focusin within ~10-50ms, so a fresh visitor gets the crash on the very
    FIRST click (lib.min.js doesn’t win the race). After a language is chosen
    the listeners are gone — which is why it’s hard to reproduce once you’ve
    switched languages.

    The same unguarded pattern is present in all widget variants on the CDN:
    dropdown.js:91, float.js:159, dwf.js, globe.js.

    The fix is a one-liner and needs no PHP — just guard the callback:

    function load_tlib(callback){
    if(typeof callback !== ‘function’) callback = null; // DOM events are not callbacks
    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();
    }
    }

    (or wrap the listeners: e.addEventListener(‘pointerenter’, function(){ load_tlib(); });)

    We verified this locally: serving a patched copy of dropdown.js with
    exactly this guard (via the script_loader_src filter, plugin files
    untouched) gives 0 console errors in all scenarios — including blocked
    lib.min.js and repeated hovers — and translation keeps working fully.

    One more thing: the CDN serves these files with
    cache-control: max-age=31536000 and Cloudflare caches them, and the ?ver=
    in the enqueue URL is the plugin version — so after fixing the CDN files
    a CF cache purge (or a versioned URL) will be needed for the fix to
    actually reach visitors.

    Could you update the CDN copies (dropdown.js / float.js / dwf.js /
    globe.js)? Happy to re-test and drop our local shim once “typeof callback”
    shows up in the CDN file.

    Plugin Author edo888

    (@edo888)

    Hi,

    Right, it will be fixed in the next release. Currently it is not affecting the functionality.

    Thanks! 🙂

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

You must be logged in to reply to this topic.