Discussions » Development

Access Iframe Content

§
Posted: 2019-04-06

Access Iframe Content

I'm trying to access a video inside an iframe, but I am getting the error "Permission denied to access property x on cross-origin object" for the innerDoc video. The iframe is youtube, so I have this as my userscript property:

// @include https://original-website.com/*
// @match *.youtube.com/*

And here is my javascript:

    var video, mode, iframe, innerDoc;

    $('.speed-control button').on('click', (evt) => {

        mode = $(event.target).attr('class').match(/speed-control-(.*)/)[1];        
        iframe = document.getElementById('videoPlayer_Youtube_api');
        innerDoc = (iframe.contentWindow || iframe.contentDocument);
        video = innerDoc.querySelectorAll('video');

        switch(mode){
            case 'slow':
                video.playbackRate  = 0.5;
                break;
            case 'normal':
                video.playbackRate  = 1;
                break;
            case 'fast':
                video.playbackRate  = 2;
                break;
            default:
        }

    });
wOxxOmMod
§
Posted: 2019-04-06

Modern browsers can't access a cross-domain iframe contents directly. Since your userscript runs both on the main page and inside the iframe you can use cross-window messaging:

const MSG_PREFIX = GM_info.script.name + '\n';

if (window === top) {

  $('.speed-control button').on('click', function () {
    const payload = JSON.stringify({
      action: 'speedControl',
      data: this.className.match(/speed-control-(.*)/)[1],
    });
    const iframe = document.getElementById('videoPlayer_Youtube_api');
    iframe.contentWindow.postMessage(MSG_PREFIX + payload, '*');
  });

} else if (location.hostname.endsWith('youtube.com')) {

  const ACTIONS = {
    speedControl(mode) {
      switch (mode) {
        case 'slow':
          video.playbackRate = 0.5;
          break;
        case 'normal':
          video.playbackRate = 1;
          break;
        case 'fast':
          video.playbackRate = 2;
          break;
        default:
      }
    },
  };

  window.addEventListener('message', e => {
    if (typeof e.data === 'string' && e.data.startsWith(MSG_PREFIX)) {
      try {
        const payload = e.data.slice(MSG_PREFIX.length);
        const {action, data} = JSON.parse(payload);
        if (ACTIONS.hasOwnProperty(action)) {
          ACTIONS[action](data);
        }
      }
      catch (ex) {}
    }
  });

}
§
Posted: 2019-04-06

Thank you so much! I just had to define the video variable but other than that, your code works really well. Gonna look into postMessage more to understand it better.

Post reply

Sign in to post a reply.