summaryrefslogtreecommitdiff
path: root/src/content/media.js
blob: 5c258f20dd201e22b9c053821a3b4ad98cca1d47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
if ("MediaSource" in window) {
  let notify = allowed => {
    let request = {
      id: "noscript-media",
      type: "media",
      url: document.URL,
      documentUrl: document.URL,
      embeddingDocument: true,
    };
    seen.record({policyType: "media", request, allowed});
    debug("MSE notification", document.URL); // DEV_ONLY
    notifyPage();
    return request;
  };
  let createPlaceholder = (mediaElement, request) => {
    try {
      let ph = PlaceHolder.create("media", request);
      ph.replace(mediaElement);
      PlaceHolder.listen();
      debug("MSE placeholder for %o", mediaElement); // DEV_ONLY
    } catch (e) {
      error(e);
    }
  };

  if (typeof exportFunction === "function") {
    // Mozilla
    let mediablocker = true;
    ns.on("capabilities", e => {
      mediaBlocker = !ns.allows("media");
    });

    let unpatched = new Map();
    function patch(obj, methodName, replacement) {
       let methods = unpatched.get(obj) || {};
       methods[methodName] = obj[methodName];
       exportFunction(replacement, obj, {defineAs: methodName});
       unpatched.set(obj, methods);
    }
    let urlMap = new WeakMap();
    patch(window.URL, "createObjectURL",  function(o, ...args) {
      let url = unpatched.get(window.URL).createObjectURL.call(this, o, ...args);
      if (o instanceof MediaSource) {
        let urls = urlMap.get(o);
        if (!urls) urlMap.set(o, urls = new Set());
        urls.add(url);
      }
      return url;
    });

    patch(window.MediaSource.prototype, "addSourceBuffer", function(mime, ...args) {
      let ms = this;
      let urls = urlMap.get(ms);
      let request = notify(!mediaBlocker);
      if (mediaBlocker) {
        let exposedMime = `${mime} (MSE)`;
        setTimeout(() => {
          let me = Array.from(document.querySelectorAll("video,audio"))
            .find(e => e.srcObject === ms || urls && urls.has(e.src));
          if (me) createPlaceholder(me, request);
        }, 0);
        throw new Error(`${exposedMime} blocked by NoScript`);
      }

      return unpatched.get(window.MediaSource.prototype).addSourceBuffer.call(ms, mime, ...args);
    });

  } else if ("SecurityPolicyViolationEvent" in window) {
    // Chromium
    let createPlaceholders = () => {
      let request = notify(false);
      for (let me of document.querySelectorAll("video,audio")) {
        if (!(me.src || me.currentSrc) || me.src.startsWith("blob")) {
          createPlaceholder(me, request);
        }
      }
    }
    let processedURIs = new Set();
    let whenReady = false;
    addEventListener("securitypolicyviolation", e => {
      if (!e.isTrusted || ns.allows("media")) return;
      let {blockedURI, violatedDirective} = e;
      if (blockedURI.startsWith("blob") &&
          violatedDirective.startsWith("media-src") &&
          !processedURIs.has(blockedURI)) {
        processedURIs.add(blockedURI);
        setTimeout(createPlaceholders, 0);
      }
    }, true);
  }
}