0

I am trying to develop a chrome extension that will be run on youtube.com and as such i need access to the iframe player api. I thought it was a cross origin problem at first but the origin is youtube.com so not sure that is the issue.

the annoying thing is that another extension called TubeBuddy seems to do exactly what I want so I know it's possible!

TubeBuddy Iframe API

Attempt 1 I have tried to implement youtube iframe api as found in the docs here: https://developers.google.com/youtube/player_parameters

In my manifest I have the following:

{
  "short_name": "Test app",
  "name": "TestApp",
  "manifest_version": 2,
  "description": "description",
  "background":
  {
    "page": "./background_scripts/background.html",
    "persistent": true
  },
  "chrome_url_overrides":
  {
    "newtab": "./new_tab/newtab.html"
  },
  "content_scripts": [
  {
    "matches": [
      "http://*/*",
      "https://*/*",
      "file:///*"
    ],
    "all_frames": true,
    "js": [
      "./inject_scripts/react_inject.js",
      "./inject_scripts/bundle.js",
      "./libraries/jquery.min.js",
      "./libraries/jquery-ui.min.js",
      "./inject_scripts/inject.js"
    ],
    "css": [
      "./inject_scripts/inject.css",
      "./libraries/jquery-ui.min.css"
    ]
  }],
  "permissions": [
    "https://*.youtube.com/*",
    "chrome://favicon/",
    "clipboardRead",
    "clipboardWrite",
    "cookies",
    "idle",
    "notifications",
    "tabs",
    "topSites",
    "bookmarks",
    "identity",
    "identity.email",
    "storage",
    "unlimitedStorage",
    "management",
    "tts",
    "tabs",
    "power",
    "activeTab"
  ],
  "externally_connectable":
  {
    "matches": ["*://*.youtube.com/*"]
  },
  "content_security_policy": "script-src 'self' https://www.youtube.com/ https://s.ytimg.com; object-src 'self'; child-src https://www.youtube.com/ https://s.ytimg.com",
  "options_ui":
  {
    "page": "./options/options.html",
    "open_in_tab": false
  },
  "web_accessible_resources": [
    "assets/img/*.svg",
    "assets/img/*.png",
    "https://www.youtube.com/"
  ],
  "version": "2.2.3"
}

where bundle.js contains the code to create a script tag which is supposed to call the iframe_api

   const tag = document.createElement('script');
      tag.src = 'https://www.youtube.com/iframe_api';
      const firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
      window.onYouTubeIframeAPIReady = function onYouTubeIframeAPIReady() {
        player = new YT.Player('wb-player', {
          height: '390',
          width: '640',
          videoId: 'M7lc1UVf-VE',
          events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
          }
        });
      };

I can observe that the script tag gets created in the DOM on the page but the window.onYoutubeIframeAPIReady is never called, which i assume is because the chrome extension doesn't have access to the window object.

I have tried to follow these answers to no avail:

Youtube player API in Chrome extension

youtube video in chrome extension content script

Detect youtube video events with Chrome extension content script

Youtube iframe API: OnReady not firing for Chrome extension

Attempt 2 I have tried using the sendMessage API to inject the YT object into the content_scripts but this doesn't work either as the sendResponse callback strips methods and functions due to JSON serialization.

background.js:

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if (request.message === 'youtube_api') {
    const tag = document.createElement('script');
    tag.src = 'https://www.youtube.com/iframe_api';
    const firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
    // sendResponse('ready');
    window.onYouTubeIframeAPIReady = function onYouTubeIframeAPIReady() {
      setTimeout(() => {
        sendResponse(YT); // strips methods and functions as sendResponse gets JSON serialized
      }, 0)
    }
  }
})

Attempt 3

I have also attempted to inject a script into the dom as suggested by this answer Chrome extension - retrieving global variable from webpage

In inject.js I have the following:

var s = document.createElement('script');
s.src = chrome.extension.getURL('./inject_scripts/youtube_api.js');
(document.head || document.documentElement).appendChild(s);
s.onload = function() {
  console.log(window.YT);
  s.remove();
};

and in youtube_api.js:

(() => {
  const tag = document.createElement('script');
  tag.src = 'https://www.youtube.com/iframe_api';
  const firstScriptTag = document.getElementsByTagName('script')[0];
  firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  // sendResponse('ready');
  window.onYouTubeIframeAPIReady = function onYouTubeIframeAPIReady() {
    console.log('player ready'); // called as it should be
    console.log(YT); // defined as it should be

    });

    setTimeout(() => {
      const event = new CustomEvent('RW759_connectExtension', {
        detail: YT, // null
      });
      window.dispatchEvent(event);
    }, 1000);
  }
})();

and in my bundle.js:

 window.addEventListener('RW759_connectExtension', function(e) {
        console.log('e.youtubeLoaded', e); // null
        console.log('window.YT', window.YT); // null
      });

I expected the event listener to return the YT object but it is returned as null.

If anyone has any suggestions or questions please ask :) Would really appreciate some help with this

James
  • 157
  • 13
  • Attempt 3 is almost correct but you need to dispatchEvent inside onYouTubeIframeAPIReady callback. Another problem is that YT object may have nonserializable stuff inside like DOM nodes. To strip it, simply transfer JSON.stringify(YT). Then your listener should read e.detail like JSON.parse(e.detail) – wOxxOm Mar 27 '19 at 04:30
  • @wOxxOm thanks for your reply. I have tried it inside the onYoutubeIframeAPIReady function and JSON.stringify however, the JSON string seems to be removing the object methods that i need to initialise the player :( ```"{"loading":1,"loaded":1,"PlayerState":{"UNSTARTED":-1,"ENDED":0,"PLAYING":1,"PAUSED":2,"BUFFERING":3,"CUED":5}}"``` – James Mar 27 '19 at 09:27
  • You can't transfer functions across the isolated world boundary. You'll have to call the API inside your page script, and use CustomEvent messaging to control the process. – wOxxOm Mar 27 '19 at 09:40
  • @wOxxOm Calling the API inside the page doesn't work. The iframe_api gets loaded but the window.onYoutubeIframeAPIReady function does not get called – James Mar 27 '19 at 10:55
  • That's a different issue that doesn't make my point any less valid. I haven't used the API myself so I can only suggest inspecting that other extension that works. – wOxxOm Mar 27 '19 at 10:59
  • Actually your code says it works "console.log('player ready'); // called as it should be" – wOxxOm Mar 27 '19 at 11:01

0 Answers0