| ... |
... |
@@ -5,59 +5,89 @@ |
|
5
|
5
|
* Element to display a single bridge emoji, with a localized name.
|
|
6
|
6
|
*/
|
|
7
|
7
|
class BridgeEmoji extends HTMLElement {
|
|
|
8
|
+ /**
|
|
|
9
|
+ * The instances that are active (in the DOM).
|
|
|
10
|
+ *
|
|
|
11
|
+ * @type {Set<BridgeEmoji>}
|
|
|
12
|
+ */
|
|
8
|
13
|
static #activeInstances = new Set();
|
|
9
|
|
- static #observer(subject, topic) {
|
|
10
|
|
- if (topic === "intl:app-locales-changed") {
|
|
11
|
|
- BridgeEmoji.#updateEmojiLangCode();
|
|
12
|
|
- }
|
|
13
|
|
- }
|
|
14
|
14
|
|
|
15
|
|
- static #addActiveInstance(inst) {
|
|
16
|
|
- if (this.#activeInstances.size === 0) {
|
|
17
|
|
- Services.obs.addObserver(this.#observer, "intl:app-locales-changed");
|
|
18
|
|
- this.#updateEmojiLangCode();
|
|
19
|
|
- }
|
|
20
|
|
- this.#activeInstances.add(inst);
|
|
21
|
|
- }
|
|
22
|
|
-
|
|
23
|
|
- static #removeActiveInstance(inst) {
|
|
24
|
|
- this.#activeInstances.delete(inst);
|
|
25
|
|
- if (this.#activeInstances.size === 0) {
|
|
26
|
|
- Services.obs.removeObserver(this.#observer, "intl:app-locales-changed");
|
|
27
|
|
- }
|
|
28
|
|
- }
|
|
|
15
|
+ /**
|
|
|
16
|
+ * A promise that resolves with a list of emoji strings, in a specific
|
|
|
17
|
+ * order. Each BridgeEmoji instance has an `#index` in this list, which
|
|
|
18
|
+ * points to its corresponding emoji.
|
|
|
19
|
+ *
|
|
|
20
|
+ * @type {Promise<string[]>}
|
|
|
21
|
+ */
|
|
|
22
|
+ static #emojiListPromise = fetch(
|
|
|
23
|
+ "chrome://browser/content/torpreferences/bridgemoji/bridge-emojis.json"
|
|
|
24
|
+ ).then(response => response.json());
|
|
29
|
25
|
|
|
30
|
26
|
/**
|
|
31
|
|
- * The language code for emoji annotations.
|
|
|
27
|
+ * @typedef {{[key: string]: string}} EmojiAnnotations
|
|
32
|
28
|
*
|
|
33
|
|
- * null if unset.
|
|
|
29
|
+ * A map from an emoji's codepoint to a locale's annotation.
|
|
|
30
|
+ */
|
|
|
31
|
+ /**
|
|
|
32
|
+ * A promise that resolves with a map from locales to their annotations.
|
|
|
33
|
+ *
|
|
|
34
|
+ * @type {Promise<{[key: string]: EmojiAnnotattions}>}
|
|
|
35
|
+ */
|
|
|
36
|
+ static #annotationsPromise = fetch(
|
|
|
37
|
+ "chrome://browser/content/torpreferences/bridgemoji/annotations.json"
|
|
|
38
|
+ ).then(response => response.json());
|
|
|
39
|
+
|
|
|
40
|
+ /**
|
|
|
41
|
+ * @typedef {object} EmojiLocaleDetails
|
|
34
|
42
|
*
|
|
35
|
|
- * @type {string?}
|
|
|
43
|
+ * @property {string[]} [emojiList] - A list of emojis as strings, in a
|
|
|
44
|
+ * specific order.
|
|
|
45
|
+ * @property {EmojiAnnotations} [annotations] - The annotations for the
|
|
|
46
|
+ * locale.
|
|
|
47
|
+ * @property {string} [unknownString] - The string to use for emojis with
|
|
|
48
|
+ * undefined annotations.
|
|
36
|
49
|
*/
|
|
37
|
|
- static #emojiLangCode = null;
|
|
38
|
50
|
/**
|
|
39
|
|
- * A promise that resolves to two JSON structures for bridge-emojis.json and
|
|
40
|
|
- * annotations.json, respectively.
|
|
|
51
|
+ * The cached locale details.
|
|
41
|
52
|
*
|
|
42
|
|
- * @type {Promise}
|
|
|
53
|
+ * @type {EmojiLocaleDetails}
|
|
43
|
54
|
*/
|
|
44
|
|
- static #emojiPromise = Promise.all([
|
|
45
|
|
- fetch(
|
|
46
|
|
- "chrome://browser/content/torpreferences/bridgemoji/bridge-emojis.json"
|
|
47
|
|
- ).then(response => response.json()),
|
|
48
|
|
- fetch(
|
|
49
|
|
- "chrome://browser/content/torpreferences/bridgemoji/annotations.json"
|
|
50
|
|
- ).then(response => response.json()),
|
|
51
|
|
- ]);
|
|
|
55
|
+ static #emojiLocaleDetails = {};
|
|
52
|
56
|
|
|
53
|
|
- static #unknownStringPromise = null;
|
|
|
57
|
+ /**
|
|
|
58
|
+ * A promise that resolves when the previous call to appLocalesChanged
|
|
|
59
|
+ * completes.
|
|
|
60
|
+ *
|
|
|
61
|
+ * @type {Promise?}
|
|
|
62
|
+ */
|
|
|
63
|
+ static #prevAppLocalesChangedCall = null;
|
|
54
|
64
|
|
|
55
|
65
|
/**
|
|
56
|
|
- * Update #emojiLangCode.
|
|
|
66
|
+ * Update the locale used for bridge emoji widget.
|
|
57
|
67
|
*/
|
|
58
|
|
- static async #updateEmojiLangCode() {
|
|
|
68
|
+ static async appLocalesChanged() {
|
|
|
69
|
+ // Introduce a queue to ensure calls apply in the order they are invoked,
|
|
|
70
|
+ // so the last seen locale will eventually be the applied one.
|
|
|
71
|
+ const { promise, resolve } = Promise.withResolvers();
|
|
|
72
|
+ const prevAppLocalesChangedCall = this.#prevAppLocalesChangedCall;
|
|
|
73
|
+ this.#prevAppLocalesChangedCall = promise;
|
|
|
74
|
+ try {
|
|
|
75
|
+ await prevAppLocalesChangedCall;
|
|
|
76
|
+ await this.#appLocalesChangedInternal();
|
|
|
77
|
+ } finally {
|
|
|
78
|
+ resolve();
|
|
|
79
|
+ }
|
|
|
80
|
+ }
|
|
|
81
|
+
|
|
|
82
|
+ static async #appLocalesChangedInternal() {
|
|
|
83
|
+ let [emojiAnnotations, emojiList, unknownString] = await Promise.all([
|
|
|
84
|
+ this.#annotationsPromise,
|
|
|
85
|
+ this.#emojiListPromise,
|
|
|
86
|
+ // Grab the string for the new locale.
|
|
|
87
|
+ document.l10n.formatValue("tor-bridges-emoji-unknown"),
|
|
|
88
|
+ ]);
|
|
|
89
|
+
|
|
59
|
90
|
let langCode;
|
|
60
|
|
- const emojiAnnotations = (await BridgeEmoji.#emojiPromise)[1];
|
|
61
|
91
|
// Find the first desired locale we have annotations for.
|
|
62
|
92
|
// Add "en" as a fallback.
|
|
63
|
93
|
for (const bcp47 of [...Services.locale.appLocalesAsBCP47, "en"]) {
|
| ... |
... |
@@ -71,14 +101,14 @@ |
|
71
|
101
|
break;
|
|
72
|
102
|
}
|
|
73
|
103
|
}
|
|
74
|
|
- if (langCode !== this.#emojiLangCode) {
|
|
75
|
|
- this.#emojiLangCode = langCode;
|
|
76
|
|
- this.#unknownStringPromise = document.l10n.formatValue(
|
|
77
|
|
- "tor-bridges-emoji-unknown"
|
|
78
|
|
- );
|
|
79
|
|
- for (const inst of this.#activeInstances) {
|
|
80
|
|
- inst.update();
|
|
81
|
|
- }
|
|
|
104
|
+
|
|
|
105
|
+ this.#emojiLocaleDetails = {
|
|
|
106
|
+ emojiList,
|
|
|
107
|
+ annotations: emojiAnnotations[langCode],
|
|
|
108
|
+ unknownString,
|
|
|
109
|
+ };
|
|
|
110
|
+ for (const inst of this.#activeInstances) {
|
|
|
111
|
+ inst.update();
|
|
82
|
112
|
}
|
|
83
|
113
|
}
|
|
84
|
114
|
|
| ... |
... |
@@ -86,22 +116,15 @@ |
|
86
|
116
|
* Update the bridge emoji to show their corresponding emoji with an
|
|
87
|
117
|
* annotation that matches the current locale.
|
|
88
|
118
|
*/
|
|
89
|
|
- async update() {
|
|
90
|
|
- if (!this.#active) {
|
|
91
|
|
- return;
|
|
92
|
|
- }
|
|
|
119
|
+ update() {
|
|
|
120
|
+ const { emojiList, annotations, unknownString } =
|
|
|
121
|
+ BridgeEmoji.#emojiLocaleDetails;
|
|
93
|
122
|
|
|
94
|
|
- if (!BridgeEmoji.#emojiLangCode) {
|
|
95
|
|
- // No lang code yet, wait until it is updated.
|
|
|
123
|
+ if (!this.#active || !emojiList || !annotations || !unknownString) {
|
|
96
|
124
|
return;
|
|
97
|
125
|
}
|
|
98
|
126
|
|
|
99
|
127
|
const doc = this.ownerDocument;
|
|
100
|
|
- const [unknownString, [emojiList, emojiAnnotations]] = await Promise.all([
|
|
101
|
|
- BridgeEmoji.#unknownStringPromise,
|
|
102
|
|
- BridgeEmoji.#emojiPromise,
|
|
103
|
|
- ]);
|
|
104
|
|
-
|
|
105
|
128
|
const emoji = emojiList[this.#index];
|
|
106
|
129
|
let emojiName;
|
|
107
|
130
|
if (!emoji) {
|
| ... |
... |
@@ -113,7 +136,7 @@ |
|
113
|
136
|
"src",
|
|
114
|
137
|
`chrome://browser/content/torpreferences/bridgemoji/svgs/${cp}.svg`
|
|
115
|
138
|
);
|
|
116
|
|
- emojiName = emojiAnnotations[BridgeEmoji.#emojiLangCode][cp];
|
|
|
139
|
+ emojiName = annotations[cp];
|
|
117
|
140
|
}
|
|
118
|
141
|
if (!emojiName) {
|
|
119
|
142
|
doc.defaultView.console.error(`No emoji for index ${this.#index}`);
|
| ... |
... |
@@ -157,13 +180,13 @@ |
|
157
|
180
|
}
|
|
158
|
181
|
|
|
159
|
182
|
this.#active = true;
|
|
160
|
|
- BridgeEmoji.#addActiveInstance(this);
|
|
|
183
|
+ BridgeEmoji.#activeInstances.add(this);
|
|
161
|
184
|
this.update();
|
|
162
|
185
|
}
|
|
163
|
186
|
|
|
164
|
187
|
disconnectedCallback() {
|
|
165
|
188
|
this.#active = false;
|
|
166
|
|
- BridgeEmoji.#removeActiveInstance(this);
|
|
|
189
|
+ BridgeEmoji.#activeInstances.delete(this);
|
|
167
|
190
|
}
|
|
168
|
191
|
|
|
169
|
192
|
/**
|
| ... |
... |
@@ -196,4 +219,20 @@ |
|
196
|
219
|
}
|
|
197
|
220
|
|
|
198
|
221
|
customElements.define("tor-bridge-emoji", BridgeEmoji);
|
|
|
222
|
+
|
|
|
223
|
+ {
|
|
|
224
|
+ const appLocalesChanged = BridgeEmoji.appLocalesChanged.bind(BridgeEmoji);
|
|
|
225
|
+ Services.obs.addObserver(appLocalesChanged, "intl:app-locales-changed");
|
|
|
226
|
+ window.addEventListener(
|
|
|
227
|
+ "unload",
|
|
|
228
|
+ () => {
|
|
|
229
|
+ Services.obs.removeObserver(
|
|
|
230
|
+ appLocalesChanged,
|
|
|
231
|
+ "intl:app-locales-changed"
|
|
|
232
|
+ );
|
|
|
233
|
+ },
|
|
|
234
|
+ { once: true }
|
|
|
235
|
+ );
|
|
|
236
|
+ appLocalesChanged();
|
|
|
237
|
+ }
|
|
199
|
238
|
} |