YouTube ads in Safari: you see them now, will you see them in the future?
Maybe slightly tangential since this is about Safari - but have 𝗬𝗼𝘂𝗧𝘂𝗯𝗲 𝗮𝗱𝘀 𝗿𝗲𝗰𝗲𝗻𝘁𝗹𝘆 𝗿𝗲𝗮𝗽𝗽𝗲𝗮𝗿𝗲𝗱 𝗶𝗻 𝗖𝗵𝗿𝗼𝗺𝗲?<p>The declarativeWebRequest YouTube adblocker I threw together in half an hour a few months ago recently stopped working on my end too.<p>Opting out of targeted ads serves you the bottom of the barrel, which was enough motivation to waste a day of JS spelunking (YouTube&#x27;s changes &quot;conveniently&quot; make declarative blocking nonviable).<p>Here&#x27;s what works for me... and here&#x27;s hoping it still works tomorrow :)<p><pre><code>  ▶ manifest.json:
  {
    &quot;name&quot;: &quot;ytadblock&quot;,
    &quot;description&quot;: &quot;(license: CC0)&quot;,
    &quot;version&quot;: &quot;2.0&quot;,
    &quot;manifest_version&quot;: 2,
    &quot;declarative_net_request&quot;: {
      &quot;rule_resources&quot;: [{
        &quot;id&quot;: &quot;1&quot;, &quot;enabled&quot;: true, &quot;path&quot;: &quot;ytadblock.json&quot;
      }]
    },
    &quot;permissions&quot;: [
      &quot;declarativeNetRequest&quot;,
      &quot;*:&#x2F;&#x2F;youtube.com&#x2F;*&quot;,
      &quot;*:&#x2F;&#x2F;www.youtube.com&#x2F;*&quot;
    ],
    &quot;content_scripts&quot;: [
      {
        &quot;matches&quot;: [&quot;https:&#x2F;&#x2F;*.youtube.com&#x2F;*&quot;],
        &quot;js&quot;: [&quot;contentscript.js&quot;],
        &quot;run_at&quot;: &quot;document_start&quot;
      }
    ],
    &quot;web_accessible_resources&quot;: [&quot;inject.js&quot;]
  }

  ▶ ytadblock.json:
  [{
    &quot;id&quot;: 1,
    &quot;priority&quot;: 1,
    &quot;action&quot;: { &quot;type&quot;: &quot;block&quot; },
    &quot;condition&quot;: {
      &quot;regexFilter&quot;: &quot;.*\\.googlevideo\\.com&#x2F;.*ctier=&quot;,
      &quot;resourceTypes&quot;: [
        &quot;main_frame&quot;, &quot;sub_frame&quot;, &quot;script&quot;, &quot;image&quot;, &quot;xmlhttprequest&quot;, &quot;media&quot;, &quot;other&quot;
      ]
    }
  }]

  ▶ contentscript.js:
  console.log(&#x27;hi from contentscript&#x27;);
  var s = document.createElement(&#x27;script&#x27;);
  s.src = chrome.runtime.getURL(&#x27;inject.js&#x27;);
  s.onload = function() { this.remove(); }
  document.documentElement.prepend(s);

  ▶ inject.js:
  &#x27;use strict&#x27;;
  (function() {
    var parse = JSON.parse;
    JSON.parse = function(input) {
      var data = parse(input);
      if (typeof data[&#x27;playerResponse&#x27;] !== &#x27;undefined&#x27;) {
        data.playerResponse.adPlacements = [];
        data.playerResponse.playerAds = [];
      }
      return data;
    }
    var fetch_ = fetch;
    fetch = async function(...args) {
      let response = await fetch_(...args);
      let text = await response.text();
      if (response.url.match(&#x2F;\&#x2F;player\?&#x2F;)) {
        try {
          var json = JSON.parse(text);
          if (typeof json[&#x27;playerAds&#x27;] !== &#x27;undefined&#x27;) {
            json.adPlacements = [];
            json.playerAds = [];
          }
          text = JSON.stringify(json);
        } catch (e) {}
      }
      return new Response(text, {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers
      });
    };
    var trap = {
      get(target, key) {
        if (key == &#x27;create&#x27; || key == &#x27;createAlternate&#x27;) {
          return function(...args) {
            args[1].args.raw_player_response.adPlacements = [];
            args[1].args.raw_player_response.playerAds = [];
            return target[key](...args);
          }
        } else if (typeof target[key] === &#x27;object&#x27; &amp;&amp; (key == &#x27;player&#x27; || key == &#x27;Application&#x27;)) {
          return new Proxy(target[key], trap);
        } else {
          return target[key];
        }
      }
    };
    var yt = new Proxy({}, trap);
  })();
</code></pre>
Notes:<p>- Ad video requests can actually be distinguished by the &quot;ctier&quot; parameter in the xxx.googlevideo.com URL. (I presume &quot;c&quot; is related to the &quot;CSI&quot; that seems to be everywhere on Google properties.) The sole declarative block is intended as a fallback in case the JavaScript hooks fail; you&#x27;ll know if you hit these, because ad videos will spin for 5 seconds before they fail - and there might be MoRe ThAn OnE - and this may drive you very, very crazy. :)<p>- Initial page loads now contain a JavaScript blob with content and ad video info. After a lot of stumbling around I got the idea to turn the `yt` object into a Proxy and intercept the yt.player.Application.createAlternate() function, which said blob is passed into as an argument.<p>- YouTube seems to be using a mixture of requests to &quot;...&#x2F;player?&quot; via Fetch and XHR POSTed requests to &quot;...&#x2F;watch?.*pbj=1&quot; to fetch next-video-info when you click video links. Hooking XHR proved... depressingly tricky (...wow...), so I got the idea from <a href="https:&#x2F;&#x2F;greasyfork.org&#x2F;en&#x2F;scripts&#x2F;32626-disable-youtube-video-ads&#x2F;code" rel="nofollow">https:&#x2F;&#x2F;greasyfork.org&#x2F;en&#x2F;scripts&#x2F;32626-disable-youtube-vide...</a> to hook JSON.parse instead (koooool). Hooking Fetch, or at least YouTube&#x27;s use of Fetch, was thankfully not that hard.<p>The above is quite brittle, depends on how YouTube works <i>now</i>, and may get caught in the crossfire of unrelated site changes. ¯\_(ツ)_&#x2F;¯*