mirror of
https://github.com/vale981/tridactyl
synced 2025-03-09 20:26:40 -04:00

This fixes https://github.com/cmcaine/tridactyl/issues/613. This was a really fun bug. What happened was this: - First, the content script set state.mode to "hint", which was then synchronized - The event listener in the background script noticed the update and set state.mode to "hint" in the background script - Then, the content script translated the hint selection into an excmd that needed to be executed in the background script and sent said command to the controller, which lives in the background script - The content script then set state.mode to "normal" - The background script executed the command and saved it in state.last_ex_str, which was then synchronized - The event listener in the content script noticed the update and set last_ex_str to the last executed str and "state.mode" to "hint" So basically, the problem was that the background script didn't notice the state.mode update right after it happened. I fixed this by moving last_ex_str out of "state" since it doesn't need to be synchronized with the content script. This means that the same kind of race condition can still happen. I'm not sure how to fix this. We could just kill state completely and instead have state be updated through message passing, but this probably wouldn't be very ergonomic. Another solution, the one envisioned for Tridactylv2, is to move to the content script entirely. This is probably the best option.
79 lines
2.2 KiB
TypeScript
79 lines
2.2 KiB
TypeScript
/** Tridactyl shared state
|
|
|
|
Any context with access to browser.storage can safely import this file and
|
|
get a self-updating consistent copy of the shared program state.
|
|
|
|
Any context may modify their copy of the state and that modification will
|
|
be propagated to the rest of the program.
|
|
|
|
This works by proxying the state object such that setting any property
|
|
causes the entire state to be saved to storage and adding a listener that
|
|
listens for storage events and updates the proxied object on each storage
|
|
event.
|
|
|
|
If this turns out to be expensive there are improvements available.
|
|
*/
|
|
|
|
import Logger from "./logging"
|
|
const logger = new Logger("state")
|
|
|
|
export type ModeName =
|
|
| "normal"
|
|
| "insert"
|
|
| "hint"
|
|
| "ignore"
|
|
| "gobble"
|
|
| "input"
|
|
| "find"
|
|
class State {
|
|
mode: ModeName = "normal"
|
|
cmdHistory: string[] = []
|
|
prevInputs: { inputId: string; tab: number; jumppos?: number }[] = [
|
|
{
|
|
inputId: undefined,
|
|
tab: undefined,
|
|
jumppos: undefined,
|
|
},
|
|
]
|
|
}
|
|
|
|
// Don't change these from const or you risk breaking the Proxy below.
|
|
const defaults = Object.freeze(new State())
|
|
|
|
const overlay = {} as any
|
|
browser.storage.local
|
|
.get("state")
|
|
.then(res => {
|
|
if ("state" in res) {
|
|
logger.debug("Loaded initial state:", res.state)
|
|
Object.assign(overlay, res.state)
|
|
}
|
|
})
|
|
.catch((...args) => logger.error(...args))
|
|
|
|
const state = (new Proxy(overlay, {
|
|
/** Give defaults if overlay doesn't have the key */
|
|
get: function(target, property) {
|
|
if (property in target) {
|
|
return target[property]
|
|
} else {
|
|
return defaults[property]
|
|
}
|
|
},
|
|
|
|
/** Persist sets to storage immediately */
|
|
set: function(target, property, value) {
|
|
logger.debug("State changed!", property, value)
|
|
target[property] = value
|
|
browser.storage.local.set({ state: target })
|
|
return true
|
|
},
|
|
}) as any) as State
|
|
|
|
browser.storage.onChanged.addListener((changes, areaname) => {
|
|
if (areaname === "local" && "state" in changes) {
|
|
Object.assign(overlay, changes.state.newValue)
|
|
}
|
|
})
|
|
|
|
export { state as default }
|