0

I am wondering how to best handle the issue of not having enough permission inside of a Google Chrome Extension. I am interacting with the YouTube API, but I am not using swfobject.js, just using am embdeeded div. I do not believe this is introducing my security issue, but perhaps it is.

In development, I had to navigate to Adobe's flash player security page and designate my development folder as a 'safe' location. In deployment, I do not have the ability to do this. I do not want my users to have to click 'Allow All' on flash player security, but I do not see another way to achieve my results.

Does anyone have any experience dealing with this? What are my options?

Seems to be a duplicate of SWFobject in a Chrome Extension - API Unavaiable but remains unanswered.

Source: https://github.com/MeoMix/YouPod

To run: Pull from the repo, load up Chrome, click the wrench, go to extension, check 'Developer Tools' -> Load Unpacked Extension and browse to the folder.

In action: http://www.meomixes.com/Chrome%20Extension.crx

Community
  • 1
  • 1
Sean Anderson
  • 27,963
  • 30
  • 126
  • 237
  • I'm not a flash guru. Can you share more information about the script? If the code runs in the context of the webpage, will the permission problem be solved? – Rob W Feb 20 '12 at 23:23
  • Let me add a bit more to the post. Give me 15 or so. :) – Sean Anderson Feb 21 '12 at 00:20
  • Okay. I have added the source and a packaged version. To answer your question: Yes, I believe that would solve the problem -- I have never encountered Flash issues browsing YouTube, etc. – Sean Anderson Feb 21 '12 at 01:26
  • 1
    I created a fix based on your `.crx` file, but when I attempted to fork the file on Github, I saw that the source codes are not equal. Which one is newer? – Rob W Feb 21 '12 at 11:06
  • Oh man. You have no idea how many times you've saved my ass in this thread. I was just yelling "NOOO" at my PC for about 10 minutes before re-reading this thread. I had lost my latest source, but now I haven't! Hooray! I will update with all your changes and more work later tonight. Thank you so much for taking the time to help me on this -- it has completely reinvigorated my desire to work on the project. – Sean Anderson Feb 22 '12 at 02:30

2 Answers2

4

Because of the origin restrictions, you cannot use an <object> element. Instead, embed an <iframe> and use the YouTube player API to communicate with the frame.

Replace your function onYouTubePlayerReady and function Initialize(playlist) with the following (in background.js):

function Initialize(playlist) {
    port = chrome.extension.connect({ name: "statusPoller" });
    if (!player) {
        YT_ready(function() {
            var frameID = getFrameID("MusicHolder");
            if (frameID) {
                player = new YT.Player(frameID, {
                    events: {
                        "onReady": function() {
                            player.cueVideoById(playlist[0].ID, 0);
                        },
                        "onStateChange": onPlayerStateChange
                    }
                });
            }
        });
    } else {
        // Only reload if the player is not playing. Otherwise, the music
        // stops when re-opening the popup.
        if (player.getPlayerState && player.getPlayerState() != PLAYING) {
            player.cueVideoById(playlist[0].ID, 0);
        }
    }
}

To get the previous code to work, you have to load another script in background.htm. The contents of youtube-player-api-helper.js are based on my previous answer to Listening for Youtube Event in JavaScript or jQuery:

// @description Easier way to implement the YouTube JavaScript API
// @author      Rob W
// @global      getFrameID(id) Quick way to find the iframe object which corresponds to the given ID.
// @global      YT_ready(Function:function [, Boolean:qeue_at_start])
// @global      onYouTubePlayerAPIReady()  - Used to trigger the qeued functions
// @website     https://stackoverflow.com/a/7988536/938089?listening-for-youtube-event-in-javascript-or-jquery

function getFrameID(id) {
    var elem = document.getElementById(id);
    if (elem) {
        if(/^iframe$/i.test(elem.tagName)) return id; //Frame, OK
        // else: Look for frame
        var elems = elem.getElementsByTagName("iframe");
        if (!elems.length) return null; //No iframe found, FAILURE
        for (var i=0; i<elems.length; i++) {
           if (/^https?:\/\/(?:www\.)?youtube(?:-nocookie)?\.com(\/|$)/i.test(elems[i].src)) break;
        }
        elem = elems[i]; //The only, or the best iFrame
        if (elem.id) return elem.id; //Existing ID, return it
        // else: Create a new ID
        do { //Keep postfixing `-frame` until the ID is unique
            id += "-frame";
        } while (document.getElementById(id));
        elem.id = id;
        return id;
    }
    // If no element, return null.
    return null;
}

// Define YT_ready function.
var YT_ready = (function() {
    var onReady_funcs = [], api_isReady = false;
    /* @param func function     Function to execute on ready
     * @param func Boolean      If true, all qeued functions are executed
     * @param b_before Boolean  If true, the func will added to the first
                                 position in the queue*/
    return function(func, b_before) {
        if (func === true) {
            api_isReady = true;
            for (var i=0; i<onReady_funcs.length; i++){
                // Removes the first func from the array, and execute func
                onReady_funcs.shift()();
            }
        }
        else if(typeof func == "function") {
            if (api_isReady) func();
            else onReady_funcs[b_before?"unshift":"push"](func); 
        }
    }
})();
// This function will be called when the API is fully loaded
function onYouTubePlayerAPIReady() {YT_ready(true);}

// Load YouTube Frame API
(function() { //Closure, to not leak to the scope
  var s = document.createElement("script");
  s.src = "http://www.youtube.com/player_api"; /* Load Player API*/
  var before = document.getElementsByTagName("script")[0];
  before.parentNode.insertBefore(s, before);
})();

Explanation of the additional changes (bonus):

  • background.htm: <!DOCTYPE html /> is invalid. It should be: <!DOCTYPE html>.
  • All .htm files: The type attribute is optional on the <script> tag. Even if you want to specify one, use application/javascript instead of text/javascript. Both will work in a Chrome extension, but the first one is more correct.
  • popup.js: Changed detection of ctrl+c. Instead of detecting and remembering whether Ctrl was pressed, use the e.ctrlKey property.
  • And some more. Have a look at popup.js, and search for RobW: to find my annotations.

Modfied files

Summary of updated files (based on your Github repo):

Community
  • 1
  • 1
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • I.... I love you. Let me take the day to read through your post while I am at work so that I can respond in a more proper fashion. – Sean Anderson Feb 21 '12 at 15:18
  • 1
    @SeanAnderson I've also attempted to fix you lay-out, but failed. jqGrid keeps making XHR requests on click (to `popup.htm`, how useful..). If you're using jqGrid solely for the lay-out, I recommend to drop it, and remove jQuery from your extension altogether. Your extension is developed for Chrome, so it's not hard to create a fully working code without jQuery. That does also reduces the size of your extension (significantly). – Rob W Feb 21 '12 at 15:24
  • I was planning on adding more functionality in the future, but nothing overly complex. I was planning on having sorting, click events, etc. Do you have any suggestions on how to get started without using a plugin grid? I only have experience in using jqGrid or tables. :) – Sean Anderson Feb 22 '12 at 04:48
  • @SeanAnderson A tabular structure is easy to build. To sort tables, have a look at [this answer](http://stackoverflow.com/a/7558600/938089?sort-a-table-fast-by-its-first-column-with-javascript), for example. If you want to debug `popup.htm`, visit `chrome://extensions`, show more details about your extension, click on the background page (opens a console), paste `'location.protocol+'//'+location.host+'/options.htm'` in the console. Then click on the linkified output, and `options.htm` will show in a separate chrome tab, completely *with* developer tools etc. – Rob W Feb 22 '12 at 09:14
  • Hey Rob. I just encountered another snag regarding my YouTube player. If you're still active and around would you mind snooping my latest post? http://stackoverflow.com/questions/10873063/playing-a-monetized-youtube-song-inside-of-a-google-chrome-extension-do-i-have – Sean Anderson Jun 03 '12 at 19:20
0

Just as a heads up, I use this now (jQuery and requireJS involved, but more shorter)

//Provides an interface to the YouTube iFrame.
//Starts up Player/SongValidator object after receiving a ready response from the YouTube API.
define(['onYouTubePlayerAPIReady'],function(){
    'use strict';
    var events = {
        onApiReady: 'onApiReady'
    };

    //This code will trigger onYouTubePlayerAPIReady
    $(window).load(function(){
        $('script:first').before($('<script/>', {
            src: 'https://www.youtube.com/iframe_api'
        }));
    });

    return {
        ready: function(){
            $(this).trigger(events.onApiReady);
        },
        onApiReady: function(event){
            $(this).on(events.onApiReady, event);
        }
    };
});

//This function will be called when the API is fully loaded. Needs to be exposed globally so YouTube can call it.
var onYouTubePlayerAPIReady = function () {
    'use strict';
    require(['youtube-player-api-helper'], function(ytPlayerApiHelper){
        ytPlayerApiHelper.ready();
    });
}

Subscribe to the event using youtubePlayerApi.onApiReady.

Sean Anderson
  • 27,963
  • 30
  • 126
  • 237