5

I asked a question and got an answer here: How to call this YouTube function from Greasemonkey?

That code works and adds a button to the page, which captures the video time.
But, the key part must run in the target-page scope -- where Greasemonkey's GM_ functions are not available.

I want to use GM_setValue() to record the video time. How do I call GM_setValue() from my button's click handler?

Here is the relevant part of the complete script (right-click to save):

... ...

//-- 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!");
    }
}

... ...


Thank you :-)

Community
  • 1
  • 1
Romaric
  • 65
  • 1
  • 5
  • I know I told you to ask this question, but Stack Overflow is not (normally) a code writing service. You need to phrase the question in a way that others might find useful for their problems. You should also show what you have tried, not just spit the last answer back at us and ask for additional features. ... This question is in danger of being closed as "too localized" (by others, not me). Please edit the question with that in mind. I'll answer tomorrow, if nobody beats me to it. – Brock Adams Jan 09 '13 at 06:43
  • 1
    I understand, but I know very little JavaScript. And being French and speaking very little English and bad, it is very difficult to express myself. – Romaric Jan 09 '13 at 07:07
  • Refactored the question for, hopefully, more general applicability. – Brock Adams Jan 10 '13 at 10:35
  • It's okay, it works! And thank you for refactoring, it is much better that way. :) – Romaric Jan 10 '13 at 19:14

1 Answers1

9

To use Greasemonkey's GM_ functions from code that must run in the page scope (Such as your timeBtn click handler), do the following:

  1. Have the page-scope code use postMessage to send the data in string format.
  2. Have the Greasemonkey script listen for the appropriate messages and call the desired GM_ function(s) with the message data.
  3. Use JSON to safely package data in strings.

Adding window.postMessage () and window.addEventListener ("message"... to your code, it becomes:

... ...

//-- 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);

    window.addEventListener ("message", receiveTimeMessage, false);
}

function activateTimeButton () {
    var timeBtn = document.getElementById ("gmTimeBtn");
    if (timeBtn) {
        timeBtn.addEventListener ('click',
            function () {
                var ytplayer = document.getElementById ("movie_player");
                /*-- GM_functions will not work here, so send the data
                    back to the GM script scope.
                */
                //-- Tag the message, we may not be the only ones sending.
                var messageTxt  = JSON.stringify (
                    {currentVidTime: ytplayer.getCurrentTime ()}
                );
                window.postMessage (messageTxt, "*");
            },
            false
        );
    }
    else {
        alert ("Time button not found!");
    }
}

function receiveTimeMessage (event) {
    var messageJSON;
    try {
        messageJSON     = JSON.parse (event.data);
    }
    catch (zError) {
        // Do nothing
    }

    if ( ! messageJSON  ||  ! messageJSON.currentVidTime)
        return; //-- Message is not for us.

    /*--- We have a time value, set it with GM_setValue ()
        But, WARNING: First make sure that the stored value is
        a safe string.  GM_setValue() crashes on just about anything else.
    */
    var safeValue       = JSON.stringify (messageJSON.currentVidTime);
    GM_setValue ("videoMarkedTime", safeValue);
    console.log ("Video time recorded with GM_setValue ().");
}

... ...


You can see the stored value by opening about:config and searching for videoMarkedTime.

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • Do you know where in Chrome we can see the `about:config`? I tried looking inside [`about:about`](http://superuser.com/a/239544/138315) but found nothing... Do we really need to stringify, parse and stringify again? I'm just sending `event.data` as object and stringifying to `GM_setValue`. . . . Great snippet, btw; GM values are awesome as they are cross-domain, thanks again! – brasofilo Oct 01 '14 at 10:34
  • `about:config` is a Firefox thing. See [Where does GM_setValue store data (for Tampermonkey)](http://stackoverflow.com/a/16829262/331508). And yes, you need the stringify, parse, and stringify in most cases of doing things like this. – Brock Adams Oct 01 '14 at 15:20