Skip to content

Cross-browser library providing reliable `window` access for userscripting

License

Notifications You must be signed in to change notification settings

summivox/kisume

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kisume

Kisume

Kisume (pronounced: kee-ss-may) is a library written in coffee-script for cross-browser userscripting that works around the limitation of sandboxes using only standard DOM manipulation, while featuring a clean, node.js-inspired interface.

The name (and mascot) comes from the Touhou Project.

Some examples below are written in iced-coffee-script, a dialect of coffee-script with convenient async handling.

CAUTION: Kisume library should not be passed through any kind of manglers (e.g. uglifyjs -m), either directly or indirectly (after concatenation). dist/kisume.min.js should be used instead. Even if you really have to mangle, you should blacklist all coffee-script reserved words (__slice and friends).

Build

Prerequisite: node.js, npm

npm install --global grunt-cli
npm install
grunt

Then include dist/kisume.js or dist/kisume.min.js into your userscript.

Extra flags that you may pass to grunt:

--iced=true  :   Also include iced-coffee-script runtime.
--trace=true :   Enable postMessage tracing.

TL;DR

If this script was executed in target page:

/*...*/
window.a = 20;
/*...*/

And you put this in your userscript source (run after page script execution):

window.kisume = Kisume window, 'kisume_test', {coffee: true}, (err) ->
  if err
    console.error err
    return
  @run ((b) -> window.a + b), 22, (err, ret) ->
    if err then console.error err
    else console.log ret

42 should be printed.

Notice the first argument of @run has access to page's window, as well as simple arguments from the sandbox.

Background

Being able to generate both a Chrome plugin and GreaseMonkey-compatible userscript from the same set of sources is an attractive idea, especially for large userscripts offering rich functionality, where porting would be virtually impossible should platform-specific features be heavily relied upon.

However there are two often-needed features that happen to be in conflict:

  • XHR(GM_xmlhttpRequest), available only in userscript's window (sandbox)
  • Access to javascript environment of the page's "real" window

Either the script runs in the sandbox, which is an isolated namespace from the target window; or it could be injected as a <script> tag into the page, gaining access to the target window while losing access to cross-site XHR.

Prior to Chrome 27, the solution is to use unsafeWindow available in GM-like environments. However this is mostly broken now, and the future status of its support is unknown at the moment (TamperMonkey partially works around this issue, as a beta feature, though).

There is an alternative approach: injecting <script> tag into target with IIFEs so that nothing inadvertently leaks into target window, then bridge the gap using window.postMessage. Both are standard DOM manipulation. The main drawback of this approach was overwhelming amount of boilerplate code to make it work reliably and efficiently.

Kisume takes this approach, but instead takes care of this tedious process for you -- all you need is to tell it what to run in the target window, right from the sandbox, and it should Just Work.

Features

  • True zero runtime dependency (only DOM manipulation needed)
  • Trivially easy return value / error handling
  • Minimal pollution of global window object:
    • one kisume instance
    • coffee-script runtime library (__slice and friends) on your request
    • Does not add anything else to window unless your script does so
  • Cross-browser compatibility, with the following environments tested:
    • Chrome Stable + Chrome plugin content script
    • Chrome Stable + TamperMonkey Stable
    • Firefox + GreaseMonkey

Concept

Kisume provides a bridge between the sandbox's and the page's global scope. When Kisume is initialized, two objects are created: a sandbox-side instance, and a page-side instance. By using methods provided by the sandbox-side instance, the userscript may transfer code to the page-side instance and subsequently use it, thus gaining access to page-side resources indirectly.

Usage

All public methods of Kisume are asynchronous by nature, taking a callback (err, ...) -> ... as the final argument, following node.js convention. This allows easy integration with async libraries, as well as compile-to-javascript languages.

Initialization

Include kisume.js into your userscript (preferrably using some build system). Class Kisume is defined and exported into the sandbox as window.Kisume.

Kisume(W, instanceName, options, cb)

Creates a Kisume instance in W, where W may be window or some iframeElem.contentWindow. The sandbox-side instance is returned, while the page-side instance is registered in the page's global scope. cb(err) is invoked on initialization complete / error.

  • instanceName: string

    Name of the page-side instance. Name should be a valid javascript identifier and match /\w+/ (alpha-numeric + underscore).

  • options: dictionary

    • coffee: When set to true, coffee-script runtime is exported to target window. This is often necessary if you're using coffee-script.

NOTE:

  • If you're using another compile-to-javascript language, it's likely that you'll need to inject its runtime library into the target window as well. You can use .inject(script) for quick script tag injection.
  • Regarding Kisume(window, ...): Although window now refers to the sandbox, Kisume will correctly initialize itself in the page-side scope.

Keep It Simple, Stupid

After initialization, kisume.run can be used to execute a function in the page-side scope (in an IIFE fashion), as demonstrated in above example.

Getting Organized

In addition to running IIFEs, Kisume allows you to pass simple objects and functions down to the page-side instance and store them in namespaces.

A namespace is a normal javascript object managed by a page-side instance. Functions stored within a namespace are treated as methods of the namespace object by default, so when called from the sandbox, this in function body refers to its namespace object.

Additionally, you may easily refer to objects and functions stored in other namespaces by declaring "included namespaces" or by using the special ENV function to lookup any namespace by name.

Here is a concrete example:

console.log '=== begin kisume test ==='
await kisume = Kisume window, 'kisume_test', defer()

await kisume.set 'namespace1', [], {
  var1: {x: 1, y: 2}
}, defer(err)
console.assert !err?

await kisume.set 'namespace2', ['namespace1'], {
  var1: {x: 11, y: 22}
  func1: (a, b) -> {x: a.x + b.x, y: a.y + b.y}
  func2: (o) -> window.o = @func1(namespace1.var1, o)
  func3: (o, cb) -> setTimeout (=> cb null, o, ENV('namespace2').func2(o)), 1000
}, defer(err)
console.assert !err?

await kisume.run 'namespace2', 'func1', {x: 100, y: 200}, {x: 300, y: -400}, defer(err, ret)
console.assert !err?
console.assert ret.x == 400 && ret.y == -200

await kisume.runAsync 'namespace2', 'func3', {x: 100, y: 100}, defer(err, ret1, ret2)
console.assert !err?
console.assert ret1.x == 100 && ret1.y == 100
console.assert ret2.x == 101 && ret2.y == 102

await kisume.run (-> @namespace2.var1.x = -100), defer(err)
console.assert !err?

await kisume.run (-> window.o), defer(err, ret)
console.assert !err?
console.assert ret.x == 101 && ret.y == 102

await kisume.get 'namespace2', ['var1'], defer(err, {var1})
console.assert !err?
console.assert var1.x == -100 && var1.y == 22

console.log '=== end kisume test ==='

.set('ns', ['ns1', 'ns2'...], {name: func/obj}, cb)

Attempts to dump functions and objects within o into namespace ns, with the listed namespaces ns1, ns2... visible to said functions ("imported" into scope)

NOTE:

  • Name of a namespace must be a valid javascript identifier
  • Objects and functions within the same namespace can be accessed using this (e.g. @func1 as in func2)
  • Included namespaces can be used directly in the function as if they're global objects (e.g. namespace1.var1 as in func2)
  • ENV('namespace2') provides a way to lookup other namespaces even if they're not explicitly included (e.g. func3), similar to require() in node.js
  • To dump a whole simple object with methods: .set('obj', [], obj, cb)

.{run | runAsync}({func | 'ns', 'name'}, args..., cb)

Call a function in target window. Comes in 4 overloaded flavors:

  • A/synchronous:
    • run:
      • Equivalent to ret = f(args...)
      • cb(undefined, ret) on function return
    • runAsync:
      • Equivalent to f(args..., (err, rets) -> ...)
      • cb(err, rets...) on async complete
  • Function being called:
    • func: IIFE. The function will be transferred to page-side instance, then called as a method of the parent object of all namespaces (e.g. first IIFE in above example).
    • ns, name: calls function stored in a namespace as its method.

NOTE:

  • Error in argument passing / function throw will immediately invoke cb(err)
  • ENV('namespace') is always available to the function
  • runAsync: function should callback at most once

.get(ns, [name1, name2, ...], cb)

Attempts to read objects stored within the namespace.

  • cb(err) on error
  • cb(undefined, {name1: obj1, name2: obj2, ...}) invoked on success

Access ENV('namespace') from target window

ENV is an alias for pageSideInstance.ENV visible to functions transferred to page-side. All namespaces can be accessed by code in page-side through this interface.

Revision History

  • 2014-01-29 v1.0.0:

    • Now supports multiple Kisume instances on the same window object. This means multiple userscripts that all employ Kisume can now co-exist in the same page.

    • Compability break: Name of the page-side instance must be provided as the 2nd argument to the Kisume constructor.

      Migration should be easy (just change the constructor call).

License

See LICENSE.

About

Cross-browser library providing reliable `window` access for userscripting

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages