summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/context-menu/context.js
blob: fc5aea50025698c1b1d7e03d1d21032e97840411 (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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { Class } = require("../core/heritage");
const { extend } = require("../util/object");
const { MatchPattern } = require("../util/match-pattern");
const readers = require("./readers");

// Context class is required to implement a single `isCurrent(target)` method
// that must return boolean value indicating weather given target matches a
// context or not. Most context implementations below will have an associated
// reader that way context implementation can setup a reader to extract necessary
// information to make decision if target is matching a context.
const Context = Class({
  isRequired: false,
  isCurrent(target) {
    throw Error("Context class must implement isCurrent(target) method");
  },
  get required() {
    Object.defineProperty(this, "required", {
      value: Object.assign(Object.create(Object.getPrototypeOf(this)),
                           this,
                           {isRequired: true})
    });
    return this.required;
  }
});
Context.required = function(...params) {
  return Object.assign(new this(...params), {isRequired: true});
};
exports.Context = Context;


// Next few context implementations use an associated reader to extract info
// from the context target and story it to a private symbol associtaed with
// a context implementation. That way name collisions are avoided while required
// information is still carried along.
const isPage = Symbol("context/page?")
const PageContext = Class({
  extends: Context,
  read: {[isPage]: new readers.isPage()},
  isCurrent: target => target[isPage]
});
exports.Page = PageContext;

const isFrame = Symbol("context/frame?");
const FrameContext = Class({
  extends: Context,
  read: {[isFrame]: new readers.isFrame()},
  isCurrent: target => target[isFrame]
});
exports.Frame = FrameContext;

const selection = Symbol("context/selection")
const SelectionContext = Class({
  read: {[selection]: new readers.Selection()},
  isCurrent: target => !!target[selection]
});
exports.Selection = SelectionContext;

const link = Symbol("context/link");
const LinkContext = Class({
  extends: Context,
  read: {[link]: new readers.LinkURL()},
  isCurrent: target => !!target[link]
});
exports.Link = LinkContext;

const isEditable = Symbol("context/editable?")
const EditableContext = Class({
  extends: Context,
  read: {[isEditable]: new readers.isEditable()},
  isCurrent: target => target[isEditable]
});
exports.Editable = EditableContext;


const mediaType = Symbol("context/mediaType")

const ImageContext = Class({
  extends: Context,
  read: {[mediaType]: new readers.MediaType()},
  isCurrent: target => target[mediaType] === "image"
});
exports.Image = ImageContext;


const VideoContext = Class({
  extends: Context,
  read: {[mediaType]: new readers.MediaType()},
  isCurrent: target => target[mediaType] === "video"
});
exports.Video = VideoContext;


const AudioContext = Class({
  extends: Context,
  read: {[mediaType]: new readers.MediaType()},
  isCurrent: target => target[mediaType] === "audio"
});
exports.Audio = AudioContext;

const isSelectorMatch = Symbol("context/selector/mathches?")
const SelectorContext = Class({
  extends: Context,
  initialize(selector) {
    this.selector = selector;
    // Each instance of selector context will need to store read
    // data into different field, so that case with multilpe selector
    // contexts won't cause a conflicts.
    this[isSelectorMatch] = Symbol(selector);
    this.read = {[this[isSelectorMatch]]: new readers.SelectorMatch(selector)};
  },
  isCurrent(target) {
    return target[this[isSelectorMatch]];
  }
});
exports.Selector = SelectorContext;

const url = Symbol("context/url");
const URLContext = Class({
  extends: Context,
  initialize(pattern) {
    this.pattern = new MatchPattern(pattern);
  },
  read: {[url]: new readers.PageURL()},
  isCurrent(target) {
    return this.pattern.test(target[url]);
  }
});
exports.URL = URLContext;

var PredicateContext = Class({
  extends: Context,
  initialize(isMatch) {
    if (typeof(isMatch) !== "function") {
      throw TypeError("Predicate context mus be passed a function");
    }

    this.isMatch = isMatch
  },
  isCurrent(target) {
    return this.isMatch(target);
  }
});
exports.Predicate = PredicateContext;