summaryrefslogtreecommitdiff
path: root/src/content/staticNS.js
blob: 447d61916831bb9c67e103ac235b3994d8199636 (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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
{
  'use strict';
  let listenersMap = new Map();
  let backlog = new Set();

  let ns = {
    debug: true, // DEV_ONLY
    get embeddingDocument() {
      delete this.embeddingDocument;
      return this.embeddingDocument = CSP.isEmbedType(document.contentType);
    },
    on(eventName, listener) {
      let listeners = listenersMap.get(eventName);
      if (!listeners) listenersMap.set(eventName, listeners = new Set());
      listeners.add(listener);
      if (backlog.has(eventName)) this.fire(eventName, listener);
    },
    detach(eventName, listener) {
      let listeners = listenersMap.get(eventName);
      if (listeners) listeners.delete(listener);
    },
    fire(eventName, listener = null) {
      if (listener) {
        listener({type:eventName, source: this});
        return;
      }
      let listeners = listenersMap.get(eventName);
      if (listeners) {
        for (let l of listeners) {
          this.fire(eventName, l);
        }
      }
      backlog.add(eventName);
    },

    fetchPolicy() {
      let url = document.URL;
      if (!UA.isMozilla && url.startsWith("http")) {
        (async () => {
          this.setup(await Messages.send("fetchPolicy", {url, contextUrl: url}));
        })();
        return;
      }
      debug(`Fetching policy from document %s, readyState %s, content %s`,
        url, document.readyState, document.documentElement.outerHTML);
      let originalState = document.readyState;
      let blockedScripts = [];

      addEventListener("beforescriptexecute", e => {
        // safety net for syncrhonous load on Firefox
        if (!this.canScript) {
          e.preventDefault();
          let script = e.target;
          blockedScripts.push(script)
          log("Some script managed to be inserted in the DOM while fetching policy, blocking it.\n", script);
        }
      }, true);

      let policy = null;

      let setup = policy => {
        debug("Fetched %o, readyState %s", policy, document.readyState); // DEV_ONLY
        this.setup(policy);
        if (this.canScript && blockedScripts.length && originalState === "loading") {
          log("Blocked some scripts on %s even though they are actually permitted by policy.", url)
          // something went wrong, e.g. with session restore.
          for (let s of blockedScripts) {
            // reinsert the script
            s.replace(s.cloneNode(true));
          }
        }
      }

      for (;;) {
        try {
          policy = browser.runtime.sendSyncMessage(
            {id: "fetchPolicy", url, contextUrl: url}, setup);
          break;
        } catch (e) {
          if (!Messages.isMissingEndpoint(e)) {
            error(e);
            break;
          }
          error("Background page not ready yet, retrying to fetch policy...")
        }
      }

    },

    setup(policy) {
      debug("%s, %s, %o", document.URL, document.readyState, policy);
      if (!policy) {
        policy = {permissions: {capabilities: []}, localFallback: true};
      }
      this.policy = policy;

      if (!policy.permissions || policy.unrestricted) {
        this.allows = () => true;
        this.capabilities =  Object.assign(
          new Set(["script"]), { has() { return true; } });
      } else {
        let perms = policy.permissions;
        this.capabilities = new Set(perms.capabilities);
        new DocumentCSP(document).apply(this.capabilities, this.embeddingDocument);
      }

      this.canScript = this.allows("script");
      this.fire("capabilities");
    },

    policy: null,

    allows(cap) {
      return this.capabilities && this.capabilities.has(cap);
    },

    getWindowName() {
      return window.name;
    }
  };

  if (this.ns) {
    this.ns.merge(ns);
  } else {
    this.ns = ns;
  }
}