Search code examples
javascriptgreasemonkeytampermonkeyscriptish

Why does waitForKeyElements need another timeout for Google search results?


The following script does what I want it to do but not as fast as I'd like it to do it. :-)

The goal is always show the search tools that become visible when the Search Tools button is clicked on Google Search results.

I can't get waitForKeyElements to work without applying a timer.
WaitForKeyElements was made for this purpose so I feel like I'm missing something.

This script works, sort of, but takes too long and seems brittle:

// ==UserScript==
// @name        GollyJer's Expand Google Search Tools
// @namespace   gollyjer.com
// @version     1.0
// @include      /^https?\:\/\/(www|news|maps|docs|cse|encrypted)\.google\./
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
// @require     https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant       GM_addStyle
// ==/UserScript==

function expandSearchTools () {
    // Fires too soon?
    // var searchToolsButton = document.getElementById("hdtb-tls");   
    // searchToolsButton.click();

    // Working but distracting.
    setTimeout(
        function(){
          var searchToolsButton = document.getElementById("hdtb-tls");   
          searchToolsButton.click();

    }, 1000);
}

waitForKeyElements ("#ires", expandSearchTools);

Solution

  • Sometimes it's not enough to wait for a particular element, to click it. Sometimes you must also allow the javascript associated with that element to initialize.

    This can be difficult on pages like Google's, where the javascript is very involved and not (easily) human readable.

    A general way around this, when clicking nodes is to check for the expected effect of the click, and repeat the click until that happens.

    In this case, the click is supposed to open the Search tools bar. So we can check to see if that happens and keep clicking until the page's click event handler is ready and responds.

    This also adds a timer, but it is faster and more flexible than the one-shot delay in the question.
    It also has the advantage that, since we use the page's expected inputs to change things, the page's javascript won't get out of sync with the page or enter an unexpected state (crash, in the worst case).

    Checking for the expected effect works in the general case, when a simple click is not working:

    // ==UserScript==
    // @name        GollyJer's Expand Google Search Tools
    // @include      /^https?\:\/\/(www|news|maps|docs|cse|encrypted)\.google\./
    // @require     http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
    // @require     https://gist.github.com/raw/2625891/waitForKeyElements.js
    // @grant       GM_addStyle
    // ==/UserScript==
    
    waitForKeyElements ("#hdtb-tls", clickNodeTilItSticks);
    
    function clickNodeTilItSticks (jNode) {
        var srchToolBar = $("#hdtbMenus")[0];
        var sanityCount = 1;
        var menusVisiblePoller = setInterval ( function () {
                if (sanityCount < 20  &&  srchToolBar.offsetWidth === 0  &&  srchToolBar.offsetHeight === 0) {
                    var clickEvent  = document.createEvent ('MouseEvents');
                    clickEvent.initEvent ('click', true, true);
                    jNode[0].dispatchEvent (clickEvent);
                }
                else {
                    clearInterval (menusVisiblePoller);
                }
                sanityCount++;
            },
            88
        );
    }