oxedyne/daimond/ext/announce.js
2.4 KiB, 1 run
created by r2519314175:867, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // Runs only on the Daimond origins. It does two small things. |
| 2 | // |
| 3 | // One: it lets the page discover the extension without hard-coding an id, by |
| 4 | // stamping the id on <html>. The page then talks to the broker with |
| 5 | // chrome.runtime.sendMessage(id, msg). |
| 6 | // |
| 7 | // Two: it carries the language the user chose in the app across to the |
| 8 | // extension, which has no other way to learn it. Chrome would dress the grant |
| 9 | // window in the BROWSER's UI language -- and a person reading a Japanese app is |
| 10 | // not necessarily running a Japanese Chrome. The one place the extension asks |
| 11 | // the user to trust it is the worst place in the product to change language. |
| 12 | // |
| 13 | // It reads one key of the app's own storage. It sends nothing anywhere else. |
| 14 | |
| 15 | 'use strict'; |
| 16 | |
| 17 | (() => { |
| 18 | |
| 19 | const id = chrome.runtime.id; |
| 20 | const root = document.documentElement; |
| 21 | |
| 22 | // Stamp for a page that is already parsed, and for one that is not. |
| 23 | root.dataset.daimondHands = id; |
| 24 | root.dataset.daimondHandsVer = chrome.runtime.getManifest().version; |
| 25 | |
| 26 | // The page may have been listening before we ran. |
| 27 | window.dispatchEvent(new CustomEvent('daimond-hands', { |
| 28 | detail: { id, version: chrome.runtime.getManifest().version }, |
| 29 | })); |
| 30 | |
| 31 | // ── The language the app is speaking ──────────────────────────────── |
| 32 | |
| 33 | /// Where the app keeps the chosen language. Empty means the user has not |
| 34 | /// chosen one, and the browser's own language is the honest answer. |
| 35 | const LS = 'daimond-locale'; |
| 36 | let last = null; |
| 37 | |
| 38 | function mirror() { |
| 39 | let code = ''; |
| 40 | try { |
| 41 | code = localStorage.getItem(LS) || ''; |
| 42 | } catch (e) { |
| 43 | return; // Private mode. Whatever was mirrored before stands. |
| 44 | } |
| 45 | if (code === last) return; |
| 46 | last = code; |
| 47 | try { chrome.storage.local.set({ locale: code }); } catch (e) { /* worker asleep */ } |
| 48 | } |
| 49 | |
| 50 | mirror(); |
| 51 | |
| 52 | // The app writes the language to storage and then stamps it on <html>, so |
| 53 | // the attribute is the signal that a choice has just been made -- and it |
| 54 | // arrives whether the change came from the picker, a sync, or a reload. |
| 55 | try { |
| 56 | new MutationObserver(mirror).observe(root, { attributes: true, attributeFilter: ['lang'] }); |
| 57 | } catch (e) { /* no observer, no live updates; the load-time read stands */ } |
| 58 | |
| 59 | // A change made in another tab reaches this one as a storage event. |
| 60 | window.addEventListener('storage', (ev) => { if (!ev.key || ev.key === LS) mirror(); }); |
| 61 | |
| 62 | })(); |