Chrome Extension RPC Router

When developing chrome extension, communication between background script and content scripts is very typical use case. Chrome provides messaging APIs to achieve this goal. But this API has limitation that every message goes to the same listener.

Background script in Chrome extension usually works as a function hub for the whole extension, so background scripts usually required to process different types of messages. Then the limit of Chrome messaging API become an issue we need to face.

There are several approaches to resolve the limitation. Since API allow multiple listener, a simple and cheap solution is Responsibility Chain; adding listener for each message type, and checking message type at beginning of the listener.

Responsbility Chain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
if(message.type != 'reloadData')
return;
// reload data logic here
});
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
if(message.type != 'updateBrowerAction')
return;
// update browser action logic here
});

This approach works, but not that graceful. And there is potential performance issue when message types increases.

So I come up a new more graceful solution: RPC Message Router

RCPRouter
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
extractResponseHandler = (args) ->
return undefined if args.length == 0
last = args.pop()
if typeof last == 'function'
return last
else
args.push last
return undefined
class @RPCRouter
constructor: ->
chrome.runtime.onMessage.addListener @handleMassage
handleMassage: (message, sender, sendResponse) =>
{method: methodName, args} = message
method = this[methodName]
unless method?
console.error "Unknown RPC method: %s", methodName
return
args.push sendResponse
console.log "RPC Call: %s, args: %o", methodName, args
method.apply(this, args)
callBackground: (method, args...) ->
responseHandler = extractResponseHandler(args)
console.log "RPC Call to Background: %s, args: %o", method, args
chrome.runtime.sendMessage {method, args}, responseHandler
callTab: (tabId, method, args...) ->
responseHandler = extractResponseHandler(args)
console.log "RPC Call to Tab \"%s\": %s, args: %o", tabId, method, args
chrome.tabs.sendMessage tabId, {method, args}, responseHandler

So this new RPCRouter wraps the chrome original messaging API and provides a more convenient way to invoke a remote method. To create a specific RPC Router for background page or content page is quite easy.

Responsbility Chain
1
2
3
4
5
6
7
8
9
10
11
12
class BackgroundRPCRouter extends RPCRouter
refreshData: (isForceUpdate, dataLoadedCallback) ->
# reload data logic here
dataLoadedCallback(data)
updateBrowerAction: (icon, hintText) ->
# update browser action logic here
return false # protection: avoid channel leak

HINT:
Using messaging API in chrome here should be careful. Coffee script will return the last executed statement result as function result, which could be potentially truthy, such as non-zero number, object. The truthy return value will make the channel become a async channel, which won’t be closed until the sendResponse callback is invoked. But just as the handler updateBrowserAction, the handler doesn’t need a sendResponse callback, the issue will keep the channel alive forever. So do add false or return false at the end of the method unless you can ensure the function will never yield truthy value in last statement.

JSONView Chrome Extension Dark Theme

JSONView is a very popular JSON formatter for Chrome, which automatically prettifies the JSON content.

JSONView provide a very sweety feature that allow user to customize the css used to format the JSON. And I love dark theme and the Consolas font so much, so I customized my dark own dark theme for JSONView.

Here is my theme css, and you can copy it to your JSONView theme editor to apply.
Also you can find code on gist: https://gist.github.com/timnew/5167241

Theme Preview
Dark Theme for JSONView
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
body {
white-space: pre;
font-family: consolas;
color: white;
background: black;
}
.property {
color: orange;
font-weight: bold;
}
.type-null {
color: gray;
}
.type-boolean {
color: orangered;
}
.type-number {
color: lightblue;
}
.type-string {
color: lightgreen;
}
a {
color: dodgerBlue;
}
.callback-function {
color: gray;
}
.collapser:after {
content: "-";
}
.collapsed > .collapser:after {
content: "+";
}
.ellipsis:after {
content: "...";
}
.collapsible {
margin-left: 2em;
}
.hoverable {
padding: 1px 2px 1px 2px;
border-radius: 3px;
}
.hovered {
background-color: rgba(255, 255, 255, .3);
}
.collapser {
padding-right: 6px;
padding-left: 6px;
}

FluentAssertion is not compatible with xUnit.Extensions

I met a weird problem that I found the Resharper Test Runner hangs when I introduced theory test case in my unit test.
After some spikes, I found the problem seems caused by the incompatibility between Fluent Assertion and xUnit.Extension.
It is wired, and there seems to be no quick fix.
So I replace the Fluent Assertion with Should and Should.Fluent, which is a port of ShouldIt.
After that, everything goes well except the syntax between Fluent Assertion and Should Fluent are not compatible with each other, although they’re really similar.
But Should.Fluent doesn’t support something.Should.Be(), it requires something.Should.Be.Equals(), which is really annoying to me.

According to the Fluent’s introduction, Fluent is a direct fork of xUnit. And I’m not sure what’s the impact caused by this.