How to call this YouTube function from Greasemonke

2019-05-30 05:37发布

问题:

I'm trying to display an alert with the time watched of the video with Greasemonkey.

My script:

// ==UserScript==
// @name        Time YouTube
// @description Does not work ! Help !
// @namespace   time_youtube
// @include     *youtube.com/watch*
// @grant       GM_setValue
// @icon        http://aux3.iconpedia.net/uploads/520882026785186105.png
// @version     1.3
// ==/UserScript==

ytplayer        = document.getElementById ("movie_player");
var time        = document.createElement ('a');
time.innerHTML  = '<a style="position: fixed; top: 200px; left: 3px; color:black;">Time</a>';

//time.addEventListener ('click', function(){GM_setValue('url_time',(document.location.href) + '&t=' + (ytplayer.getCurrentTime()));}, false);
time.addEventListener ('click',
    function () {
        alert (ytplayer.getCurrentTime() );
    },
    false
);

document.body.appendChild (time);


But this does not work.

Can you help me? Thank you!

回答1:

Due to various security and scoping issues, you cannot run ytplayer.getCurrentTime() from the Greasemonkey script scope.

Likewise, the click event handler must run in the target page's scope to access that function. Attempts to do otherwise will yield combinations of undefined and Error: Bad NPObject as private data!.

This means that you must "Inject" the click handler for your time button.
But first, here's a few more issues to consider:

  1. Youtube, and most Google sites, make extensive use of <iframe>s. To avoid problems with multiple executions of your script, use checks like window.top === window.self to ensure that the script only runs in the frame(s) or containing page that you are targeting.
  2. Avoid using innerHTML and inline styles as much as possible. This will make the code easier to maintain, faster in many instances, and less likely to trigger unexpected side effects.

Putting that all together, here is your complete script refactored:

// ==UserScript==
// @name        Time YouTube
// @description Does not work ! Help !
// @namespace   time_youtube
// @include     *youtube.com/watch*
// @icon        http://aux3.iconpedia.net/uploads/520882026785186105.png
// @grant       GM_setValue
// @grant       GM_addStyle
// @version     1.3
// ==/UserScript==

//-- Only run in the top page, not the various iframes.
if (window.top === window.self) {
    var timeBtn         = document.createElement ('a');
    timeBtn.id          = "gmTimeBtn";
    timeBtn.textContent = "Time";
    //-- Button is styled using CSS, in GM_addStyle, below.

    document.body.appendChild (timeBtn);

    addJS_Node (null, null, activateTimeButton);
}

function activateTimeButton () {
    var timeBtn = document.getElementById ("gmTimeBtn");
    if (timeBtn) {
        timeBtn.addEventListener ('click',
            function () {
                var ytplayer = document.getElementById ("movie_player");
                //-- IMPORTANT:  GM_functions will not work here.

                console.log ("getCurrentTime(): ", ytplayer.getCurrentTime() );
                //alert (ytplayer.getCurrentTime() );
            },
            false
        );
    }
    else {
        alert ("Time button not found!");
    }
}

//-- Style and position our button the CSS way.
GM_addStyle ( "                 \
    #gmTimeBtn {                \
        position:   fixed;      \
        top:        200px;      \
        left:       3px;        \
        color:      black;      \
        margin:     0;          \
        padding:    0;          \
    }                           \
" );

//-- This is a standard-ish utility function...
function addJS_Node (text, s_URL, funcToRun, runOnLoad) {
    var D                                   = document;
    var scriptNode                          = D.createElement ('script');
    if (runOnLoad) {
        scriptNode.addEventListener ("load", runOnLoad, false);
    }
    scriptNode.type                         = "text/javascript";
    if (text)       scriptNode.textContent  = text;
    if (s_URL)      scriptNode.src          = s_URL;
    if (funcToRun)  scriptNode.textContent  = '(' + funcToRun.toString() + ')()';

    var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
    targ.appendChild (scriptNode);
}

Finally, from your script, it looks like you hope to eventually use GM_setValue(), etc., when clicking that button. That requires messaging across scopes. See this answer or open a new question when you get to that part.