summaryrefslogtreecommitdiff
path: root/src/content/staticNS.js
blob: 817351a003a260333768f313c771d84207704d4f (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
'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);
    },
    
    setup(DEFAULT, MARKER) {
      this.config.DEFAULT = DEFAULT;
      if(!this.config.CURRENT) this.config.CURRENT = DEFAULT;

      // ugly hack: since now we use registerContentScript instead of the
      // filterRequest dynamic script injection hack, we use top.name
      // to store per-tab information. We don't want web content to
      // mess with it, though, so we wrap it around auto-hiding accessors
      this.config.MARKER = MARKER;
      let eraseTabInfoRx = new RegExp(`[^]*${MARKER},?`);
      if (eraseTabInfoRx.test(top.name)) {
        let _name = top.name;
        let tabInfoRx = new RegExp(`^${MARKER}\\[([^]*?)\\]${MARKER},`);
        if (top === window) { // wrap to hide
          Reflect.defineProperty(top.wrappedJSObject, "name", {
            get: exportFunction(() => top.name.replace(eraseTabInfoRx, ""), top.wrappedJSObject),
            set: exportFunction(value => {
              let preamble = top.name.match(tabInfoRx);
              top.name = `${preamble && preamble[0] || ""}${value}`;
              return value;
            }, top.wrappedJSObject)
          });
        }
        let tabInfoMatch = _name.match(tabInfoRx);
        if (tabInfoMatch) try {
          this.config.tabInfo = JSON.parse(tabInfoMatch[1]);
        } catch (e) {
          error(e);
        }
      }
      
      if (!this.config.DEFAULT || this.config.tabInfo.unrestricted) {
        this.allows = () => true;
        this.capabilities =  Object.assign(
          new Set(["script"]), { has() { return true; } });
      } else {
        let perms = this.config.CURRENT;
        this.capabilities = new Set(perms.capabilities);
        new DocumentCSP(document).apply(this.capabilities, this.embeddingDocument);
      }
      
      this.canScript = this.allows("script");
      this.fire("capabilities");
    },
    config: { DEFAULT: null, CURRENT: null, tabInfo: {}, MARKER: "" },
        
    allows(cap) {
      return this.capabilities && this.capabilities.has(cap);
    },
    
    getWindowName() {
      let marker = this.config.MARKER;
      return (top === window && marker) ? 
          window.name.split(`${marker},`).pop()
          : window.name;
    }
  };
  
  if (this.ns) {
    this.ns.merge(ns);
  } else {
    this.ns = ns;
  }
}