• Stars
    star
    411
  • Rank 105,247 (Top 3 %)
  • Language
    Clojure
  • License
    Other
  • Created almost 9 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Write Chrome Extensions in ClojureScript

chromex GitHub license Clojars Project Travis Example Projects

Deprecated

Chromex only supports Chrome Extensions v2 which are now end of life. For basic v3 support, please see this example.

This library is auto-generated. Current version was generated on 2020-10-13 from Chromium @ 9269f9eb1d98.

Looking for a nightly version? Check out nightly branch which gets updated if there are any new API changes.

Chromex provides idiomatic ClojureScript interface

For Chrome Extensions and also for Chrome Apps:

API family namespaces properties functions events
Public Chrome Extension APIs 81 61 397 180
Public Chrome App APIs 68 32 450 153
Private Chrome Extension APIs 40 0 499 84
Private Chrome App APIs 37 0 370 79

Note: Chromex generator uses the same data source as developer.chrome.com/extensions/api_index and developer.chrome.com/apps/api_index docs.

Following documentation is mostly speaking about Chrome Extension development but the same patterns generally apply to Chrome App development as well.

This library is data-driven. Given an API namespace, all API methods, properties and events are described in a Clojure map along with their parameters, callbacks, versions and additional metadata (a simple example - look for api-table here). Chromex then provides a set of macros which consume this table and generate actual ClojureScript code wrapping native APIs.

These macros can be further parametrized which allows for greater flexibility. Sane defaults are provided with following goals:

  • API version checking and deprecation warnings at compile-time
  • flexible marshalling of Javascript values to ClojureScript and back
  • callbacks are converted to core.async channels
  • events are emitted into core.async channels

API versions and deprecation warnings

Chrome Extension API is evolving. You might want to target multiple Chrome versions with slightly different APIs. Good news is that our API data map contains full versioning and deprecation information.

By default you target the latest APIs. But you can target older API version instead and we will warn you during compilation in case you were accessing any API not yet available in that particular version.

Additionally we are able to detect calls to deprecated APIs and warn you during compilation.

Flexible marshalling

Generated API data map contains information about all parameters and their types. Chromex provides a pluggable system to specify how particular types are marshalled when crossing API boundary.

By default we marshall only a few types where it makes good sense. We don't want to blindly run all parameters through clj->js and js->clj conversions. That could have unexpected consequences and maybe performance implications. Instead we keep marshalling lean and give you an easy way how to provide your own macro which can optionally generate required marshalling code (during compilation).

There is also a practical reason. This library is auto-generated and quite large - it would be too laborious to maintain hairy marshalling conventions up-to-date with evolving Chrome API index. If you want to provide richer set of marshalling for particular APIs you care about, you can do that consistently.

Callbacks as core.async channels

Many Chrome API calls are async in nature and require you to specify a callback (for receiving an answer later). You might want to watch this video explaining API conventions in Chrome.

We automatically turn all API functions with a callback parameter to a ClojureScript function without that callback parameter but returning a new core.async channel instead (promise-chan). The channel eventually receives a vector of parameters passed into the callback. When an error occurs, the channel closes without receiving any result (you receive nil). In that case you can immediately call chromex.error/get-last-error to obtain relevant error object (which is what was found in chrome.runtime.lastError during the callback).

This mechanism is pluggable, so you can optionally implement your own mechanism of consuming callback calls.

Events are emitted into core.async channels

Chrome API namespaces usually provide multiple event objects which you can subscribe with .addListener. You provide a callback function which will get called with future events as they occur. Later you can call .removeListener to unsubscribe from the event stream.

We think consuming events via core.async channels is more natural for ClojureScript developers. In Chromex, you can request Chrome events to be emitted into a core.async channel provided by you. And then implement a single loop to sequentially process events as they appear on the channel.

Again this mechanism is pluggable, so you can optionally implement a different mechanism for consuming event streams.

Usage examples

We provide an example skeleton Chrome extensions chromex/examples. These projects acts as a code examples but also as a skeleton with project configuration. We recommended to use it as starting point when starting development of your own extension.

Please refer to readme in chromex/examples for further explanation and code examples.

Advanced mode compilation

Chromex does not rely on externs file. Instead it is rigorously using string names to access Javascript properties. I would recommend you to do the same in your own extension code. It is not that hard after all. You can use oget, ocall and oapply macros from the cljs-oops library, which is designed to work with string names.

Note: There is a chrome_extensions.js externs file available, but that's been updated ad-hoc by the community. It is definitely incomplete and may be incorrect. But of course you are free to include the externs file into your own project and rely on it if it works for your code. It depends on how recent/popular APIs are you going to use.

Tapping events

Let's say for example you want to subscribe to tab creation events and web navigation's "committed" events.

(ns your.project
  (:require [cljs.core.async :refer [chan close! go-loop]]
            [chromex.ext.tabs :as tabs]
            [chromex.ext.web-navigation :as web-navigation]
            [chromex.chrome-event-channel :refer [make-chrome-event-channel]]))

(let [chrome-event-channel (make-chrome-event-channel (chan))]
  (tabs/tap-on-created-events chrome-event-channel)
  (web-navigation/tap-on-committed-events chrome-event-channel (clj->js {"url" [{"hostSuffix" "google.com"}]}))

  ; do something with the channel...
  (go-loop []
    (when-some [[event-id event-params] (<! chrome-event-channel)]
      (process-chrome-event event-id event-params)
      (recur))
    (println "leaving main event loop"))

  ; alternatively
  (close! chrome-event-channel)) ; this will unregister all chrome event listeners on the channel

As we wrote in previous sections, by default you consume Chrome events via core.async channels:

  1. first, you have to create/provide a channel of your liking
  2. then optionally wrap it in make-chrome-event-channel call
  3. then call one or more tap-some-events calls
  4. then you can process events as they appear on the channel

If you don't want to use the channel anymore, you should close! it.

Events coming from the channel are pairs [event-id params], where params is a vector of parameters passed into event's callback function. See chromex/examples for example usage. Refer to Chrome's API docs for specific event objects.

Note: instead of calling tap-some-events you could call tap-all-events. This is a convenience function which will tap events on all valid non-deprecated event objects in given namespace. For example tabs/tap-all-events will subscribe to all existing tabs events in the latest API.

make-chrome-event-channel is a convenience wrapper for raw core.async channel. It is aware of event listeners and is able to unsubscribe them when channel gets closed. But you are free to remove listeners manually as well, tap calls return ChromeEventSubscription which gives you an interface to unsubscribe! given tap. This way you can dynamically add/remove subscriptions on the channel.

Tap calls accept not only channel but also more optional arguments. These arguments will be passed into .addListener call when registering Chrome event listener. This is needed for scenarios when event objects accept filters or other additional parameters. web-navigation/tap-on-committed-events is an example of such situation. Even more complex scenario is registering listeners on some webRequest API events (see 'Registering event listeners' section).

Synchronous event listeners

In some rare cases Chrome event listener has to be synchronous. For example webRequest's onBeforeRequest event accepts "blocking" flag which instructs Chrome to wait for listener's answer in a blocking call.

Here is an example how you would do this in chromex:

(ns your.project
  (:require ...
            [chromex.config :refer-macros [with-custom-event-listener-factory]]
            [chromex.chrome-event-channel :refer [make-chrome-event-channel]]
            [chromex.ext.web-request :as web-request]))

(defn my-event-listener-factory []
  (fn [& args]
    ; do something useful with args...
    #js ["return native answer"])) ; note: this value will be passed back to Chrome as-is, marshalling won't be applied here

...
(with-custom-event-listener-factory my-event-listener-factory
  (web-request/tap-on-before-request-events chan (clj->js {"urls" ["<all_urls>"]}) #js ["blocking"]))
...

What happened here? We have specified our own event listener factory which is responsible for creating a new event callback function whenever chromex asks for it. The default implementation is here. This function is part of our config object, so it can be redefined during runtime. with-custom-event-listener-factory is just a convenience macro to override this config setting temporarily.

This way we get all benefits of chromex (marshalling, logging, API deprecation/version checking, etc.) but still we have a flexibility to hook our own custom listener code if needed. Please note that event listener has to return a native value. We don't have type information here to do the marshalling automatically. Also note that incoming parameters into event listener get marshalled to ClojureScript (as expected). And obviously this event won't appear on the channel unless you put! it there in your custom listener code.

Also note how we passed extra arguments for .addListener call. This was discussed in the previous section.

Advanced tip: similarly you can replace some other configurable functions in the config object. For example you can change the way how callbacks are turned into core.async channels. Theoretically you could replace it with some other mechanism (e.g. with js promises).

Projects using Chromex

Similar libraries

More Repositories

1

cljs-devtools

A collection of Chrome DevTools enhancements for ClojureScript developers
Clojure
1,107
star
2

dirac

A Chrome DevTools fork for ClojureScript developers
Clojure
769
star
3

totalterminal

Terminal.app plugin for quick access to terminal window (Quake-style)
Objective-C
390
star
4

cljs-oops

ClojureScript macros for convenient native Javascript object access.
Clojure
351
star
5

asepsis

a solution for .DS_Store pollution
C
350
star
6

drydrop

Deploy static sites to App Engine by pushing to GitHub
Python
224
star
7

firelogger.py

Python library for FireLogger - a logger console integrated into Firebug
Python
219
star
8

totalfinder-i18n

Localization for TotalFinder
HTML
184
star
9

firequery

Firebug extension for jQuery development
JavaScript
170
star
10

cljs-devtools-sample

An example integration of cljs-devtools
Clojure
75
star
11

firelogger.php

PHP library for FireLogger - a logger console integrated into Firebug
PHP
68
star
12

xrefresh

Browser refresh automation for web developers
C#
64
star
13

leechgate

Google Analytics for your S3 bucket
PHP
64
star
14

firelogger

Firebug logging support for server side languages/frameworks (Python, PHP)
JavaScript
61
star
15

firerainbow

Javascript syntax highlighting for Firebug
JavaScript
58
star
16

totalfinder-osax

Scripting additions used by TotalFinder (SIMBL replacement)
Objective-C++
55
star
17

chromex-sample

An example Chrome extension using Chromex library (ClojureScript)
Clojure
53
star
18

cljs-zones

A magical binding macro which survives async calls
Clojure
43
star
19

totalfinder-kext

The kernel extension used by TotalFinder for .DS_Store redirection [obsolete]
C
40
star
20

totalspaces2-api

API bindings to enable you to get information from and to control TotalSpaces2
HTML
32
star
21

env-config

A Clojure(Script) library for config map overrides via environmental variables
Clojure
29
star
22

restatic

Google Docs content parser
CoffeeScript
24
star
23

terminal-profiles

Terminal.app profiles contributed by the community
21
star
24

crashwatcher

Agent process which monitors crash logs and offers sending a crash report
Objective-C
21
star
25

totalfinder-web

Web site for TotalFinder - the best Finder plugin around
HTML
18
star
26

dirac-sample

An example integration of Dirac DevTools
Clojure
17
star
27

cljs-react-three-fiber

ClojureScript port of react-three-fiber/examples.
JavaScript
17
star
28

totalterminal-osax

SIMBL replacement for TotalTerminal (Visor)
Objective-C++
16
star
29

totalspaces-api

API bindings to enable you to get information from and to control TotalSpaces
HTML
16
star
30

totalspaces-osax

Scripting additions used by TotalSpaces (SIMBL replacement)
Objective-C
12
star
31

fscript-osax

Simple way how to inject F-Script into any app from the command-line (replacement for F-Script Anywhere and F-Script SIMBL)
Objective-C
12
star
32

electrum-server-docker

Run your own electrum-server+bitcoind easily, isolated by docker
Shell
12
star
33

totalfinder-installer

A support project for TotalFinder - installer scripts
Shell
11
star
34

bitcoind-docker

A docker container for running bitcoind node
Shell
10
star
35

totalspaces2-display-manager

Small app to manage which display spaces are on
Objective-C
9
star
36

blog

Ideas from BinaryAge
HTML
7
star
37

uninstaller

Minimalist Cocoa app for wrapping TotalFinder uninstall script
Objective-C++
6
star
38

totalspaces2-i18n

TotalSpaces2 Internationalization
HTML
6
star
39

holycrash-osax

Crash any Mac application using an AppleScript event (for debugging purposes)
Objective-C
6
star
40

discourse-hipchat-plugin

A plugin for reporting discourse activity into a HipChat room.
Ruby
5
star
41

layouts

Jekyll layouts for main site
5
star
42

clearcut

Unified logging overlay on top of console.log and clojure.tools.logging (WIP)
Clojure
5
star
43

totalspaces-i18n

Localization for TotalSpaces
Ruby
4
star
44

totalfinder-tests

automated scripts to test TotalFinder (powered by Sikuli)
Python
4
star
45

totalterminal-i18n

TotalTerminal internationalization
Ruby
4
star
46

totalspaces-web

the web site for TotalSpaces - a set of handy tweaks for OS X Spaces
HTML
4
star
47

asepsis-web

the website for the Asepsis project
HTML
4
star
48

instaedit

JavaScript
4
star
49

totalterminal-installer

Installer-related files for TotalTerminal
Shell
4
star
50

php-ga

NO LONGER IN USE
PHP
4
star
51

site

An umbrella project for our web sites at binaryage.com
Ruby
4
star
52

totalterminal-web

Website for TotalTerminal
HTML
3
star
53

shared

Shared static content for binaryage sites
Stylus
3
star
54

hints

Your feed of Mac-related hints and tips from BinaryAge
HTML
3
star
55

www

BinaryAge home page
HTML
3
star
56

totalspaces2-desktopname

TotalSpaces2 plugin to display the name of the current space in the menu bar
Objective-C
3
star
57

restatic-web

Regenerator of static websites with fresh data from Google spreadsheets
CSS
3
star
58

cljs-oops-sample

An example integration of cljs-oops
Clojure
2
star
59

badev

A command-line tool to aid developers in BinaryAge
Ruby
1
star
60

support

BinaryAge support page
HTML
1
star
61

crash-prism

Symbolizing crash reports with ease (a custom tool for BinaryAge apps)
Ruby
1
star