Last active
May 16, 2018 13:05
-
-
Save cmcaine/b8cce27553ac7d0bf54dc77604a6a4d4 to your computer and use it in GitHub Desktop.
Is this as little cruft as possible?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Class C is loaded in two contexts: content and background. The decorators | |
* @content and @background replace the decorated function with a call to a | |
* messaging library if the current context is not the desired context for the | |
* function. | |
* | |
* The intention is that a developer can write code that is split between the | |
* content and background without having to think too much about where it is: | |
* calls that pass through messaging just look like regular async function | |
* calls. | |
* | |
* There is only ever one background script, but there are many content scripts. | |
* | |
* This all works fine, except I don't want to explicitly specify which content | |
* script to message when a background function calls a content function (it | |
* should be the content script that called this background func, or the func | |
* that called this one, etc). | |
* | |
* The underlying messaging API is: tabs.sendMessage(tabId, message) and I can | |
* pass the tabId to the decorator. **But can I avoid passing the tabId as an | |
* argument to the decorated function?** | |
* | |
* Best I've got so far is that I make a copy or proxy `this` and put the tabId | |
* on that. But this doesn't solve background -> content calls that cross class | |
* boundaries. | |
*/ | |
class C { | |
@content | |
foo(x, tabId) { | |
return this.baz(x + 1, tabId) | |
} | |
@content | |
bar(x, tabId) { | |
return x * 100 | |
} | |
@background | |
baz(x, tabId) { | |
return this.bar(x + 2, tabId) | |
} | |
} | |
C.foo(1) === 400 | |
// runtime-dispatch.ts | |
// I renamed cn and bg to content and background for the above. I've also not included | |
// all the messaging stuff, but all you need to know is that it lets you call functions | |
// and get their return values in an async way. | |
import { getContext } from "./webext" | |
import { | |
addListener, | |
attributeCaller, | |
messageBackground, | |
messageActiveTab, | |
} from "../messaging" | |
function funcDec(func) { | |
return (proto, name, propDesc) => (propDesc.value = func(propDesc.value)) | |
} | |
/** Runtime function dispatcher. | |
Give any function that you want to be available in both foreground and | |
background to one of background, content, or dispatch. Wrapped functions | |
must have a name, though this could be changed. | |
Import the same source file into content and background. | |
At execution time, each decorated function will be returned as-is or | |
wrapped with a messaging call to the background/content (on activeTab) | |
version of the same script. | |
Limitations: | |
- Helper functions that should be available on only one side can't be | |
expressed. | |
- It's a bit clumsy to write: | |
`export const foo = d.background(function foo(bar) {` | |
Advantages: | |
- Modules that involve a lot of communication between bg and content | |
will be simpler to read and write. | |
@param modName: the name of the module we're dispatching for. | |
*/ | |
export default class Dispatcher { | |
private context | |
private ourFuncs = Object.create(null) | |
constructor(private modName) { | |
this.context = getContext() | |
addListener(modName, attributeCaller(this.ourFuncs)) | |
} | |
background = <T extends Function>(func: T): T => { | |
return this.dispatch(undefined, func) | |
} | |
content = <T extends Function>(func: T): T => { | |
return this.dispatch(func, undefined) | |
} | |
// Decorator versions of the above | |
bg = funcDec(this.background) | |
cn = funcDec(this.content) | |
dispatch<T extends Function>(contentFun: T, backgroundFun: T): T { | |
if (this.context == "background") { | |
if (backgroundFun) { | |
this.ourFuncs[backgroundFun.name] = backgroundFun | |
return backgroundFun | |
} else { | |
return this.wrapContent(contentFun) | |
} | |
} else { | |
if (contentFun) { | |
this.ourFuncs[contentFun.name] = contentFun | |
return contentFun | |
} else { | |
return this.wrapBackground(backgroundFun) | |
} | |
} | |
} | |
/* private wrap(func) { */ | |
/* let fn */ | |
/* if (this.context == 'background') { */ | |
/* fn = (...args) => message(this.type, func.name, args) */ | |
/* } else { */ | |
/* fn = (...args) => messageActiveTab(this.type, func.name, args) */ | |
/* } */ | |
/* Object.defineProperty(fn, 'name', {value: func.name}) */ | |
/* return fn */ | |
/* } */ | |
private wrapBackground(func): any { | |
return (...args) => messageBackground(this.modName, func.name, args) | |
} | |
private wrapContent(func): any { | |
return (...args) => messageActiveTab(this.modName, func.name, args) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment