Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/chocolateboy/gm-compat
Portable monkey-patching for userscripts
https://github.com/chocolateboy/gm-compat
clone-into cloneinto export-function exportfunction greasemonkey shim tampermonkey unsafe-window unsafewindow userscript userscripts violentmonkey zero-dependency
Last synced: 3 months ago
JSON representation
Portable monkey-patching for userscripts
- Host: GitHub
- URL: https://github.com/chocolateboy/gm-compat
- Owner: chocolateboy
- License: artistic-2.0
- Created: 2020-09-05T17:36:55.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2021-03-01T23:00:40.000Z (almost 4 years ago)
- Last Synced: 2024-10-31T08:41:00.701Z (3 months ago)
- Topics: clone-into, cloneinto, export-function, exportfunction, greasemonkey, shim, tampermonkey, unsafe-window, unsafewindow, userscript, userscripts, violentmonkey, zero-dependency
- Language: JavaScript
- Homepage:
- Size: 142 KB
- Stars: 13
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
Awesome Lists containing this project
README
# gm-compat
[![NPM Version](https://img.shields.io/npm/v/gm-compat.svg)](https://www.npmjs.org/package/gm-compat)
- [NAME](#name)
- [FEATURES](#features)
- [INSTALLATION](#installation)
- [USAGE](#usage)
- [DESCRIPTION](#description)
- [Why?](#why)
- [Why not?](#why-not)
- [TYPES](#types)
- [API](#api)
- [apply](#apply)
- [call](#call)
- [cloneInto](#cloneinto)
- [export](#export)
- [exportFunction](#exportfunction)
- [unsafeWindow](#unsafewindow)
- [unwrap](#unwrap)
- [COMPATIBILITY](#compatibility)
- [DEVELOPMENT](#development)
- [SEE ALSO](#see-also)
- [VERSION](#version)
- [AUTHOR](#author)
- [COPYRIGHT AND LICENSE](#copyright-and-license)# NAME
gm-compat - portable monkey-patching for userscripts
# FEATURES
- Portable access to:
- [`unsafeWindow`][unsafeWindow]
- [`cloneInto`][cloneInto]
- [`exportFunction`][exportFunction]
- ~ 750 B minified
- CDN builds ([unpkg][], [jsDelivr][])# INSTALLATION
```
$ npm install gm-compat
```# USAGE
```javascript
// ==UserScript==
// @name My Userscript
// @description A userscript which hooks XMLHttpRequest#open
// @include https://www.example.com/*
// @require https://unpkg.com/[email protected]
// ==/UserScript==const xhrProto = GMCompat.unsafeWindow.XMLHttpRequest.prototype
const oldOpen = xhrProto.openfunction open (method, url) {
if (url === TARGET) {
this.addEventListener('load', () => {
process(this.responseText)
})
}GMCompat.apply(this, oldOpen, arguments)
}xhrProto.open = GMCompat.export(open)
```# DESCRIPTION
gm-compat is a tiny compatibility shim for userscripts which provides uniform,
cross-engine access to [`unsafeWindow`][unsafeWindow], [`cloneInto`][cloneInto]
and [`exportFunction`][exportFunction]. These can be used to portably modify
page properties, e.g. to hook [`XMLHttpRequest#open`][xhr#open] to intercept
HTTP requests.Modifications to a page's window (`unsafeWindow`) need to use the `cloneInto`
and `exportFunction` functions on Firefox. However, these functions are not
available by default in Chrome. In addition, `unsafeWindow` is not the actual
`unsafeWindow` object in all engines.Since the functions are needed on Firefox, the options for writing portable
scripts which modify page properties are either to implement
browser/engine-specific code in each script or to use a shim which exposes an
API that works with all engines. gm-compat provides an implementation of the
latter.When the functions are not needed (i.e. in Chrome), they're implemented as
pass-through (identity) functions, so they work consistently in all cases.Note that `GMCompat` is a local variable (declared with `const`), not a
property of the window object. This ensures that each script running on a page
can have its own isolated version of gm-compat without conflicts.## Why?
Because writing portable code which modifies page properties is error-prone and
long-winded. This shim aims to abstract away the inconsistencies and
incompatibilities so that scripts don't need to reinvent it.## Why not?
There may be no need to use this if scripts don't require Greasemonkey APIs and
are able to operate directly in the page context (i.e. `@grant none`), although
note that `@grant none` is [not supported][grant-none] by all engines, and
`@grant none` (and `unsafeWindow`) doesn't portably work on [all sites][csp].# TYPES
The following types are referenced in the descriptions below.
```typescript
type CloneIntoOptions = {
cloneFunctions?: boolean;
target?: object;
wrapReflectors?: boolean;
};type ExportOptions = {
target?: object;
};type ExportFunctionOptions = {
allowCrossOriginArguments?: boolean;
defineAs?: string;
target?: object;
};
```# API
## apply
**Type**: `($this: any, fn: ((...args: A) => R), args: A) => R`
Safely call a page function with an `arguments` object or array of arguments
from the script context. This is needed to avoid security errors when passing
arguments from the script to the page, e.g.:This doesn't work:
```javascript
function open (method, url) {
if (url === TARGET) // ...// XXX Error: Permission denied to access property "length"
oldOpen.apply(this, arguments)
}
```This works:
```javascript
function open (method, url) {
// ...
GMCompat.apply(this, oldOpen, arguments) // OK
}
```## call
**Type**: `($this: any, fn: ((...args: A) => R), ...A) => R`
Safely call a page function with arguments from the script context. This is
needed to avoid security errors when passing arguments from the script to the
page, e.g.:This doesn't work:
```javascript
// XXX Error: Permission denied to access property "value"
GMCompat.unsafeWindow.notify('loaded', { value: 42 })
```This works:
```javascript
GMCompat.call(undefined, GMCompat.unsafeWindow.notify, 'loaded', { value: 42 }) // OK
```## cloneInto
**Type**: `(object: T, options?: CloneIntoOptions) => T`
Portable access to Firefox's [`cloneInto`][cloneInto] function, which returns a
version of the supplied object that can be accessed in the provided context.```javascript
const dummyPerformance = {
now () { ... }
}GMCompat.unsafeWindow.performance = GMCompat.cloneInto(dummyPerformance)
```If no options are supplied, the default values are:
```javascript
{ cloneFunctions: true, target: GMCompat.unsafeWindow, wrapReflectors: true }
```If supplied, they are merged into/override the defaults.
## export
**Type**:
- `(value: T, options?: ExportOptions) => T`
- `(value: T, options?: ExportOptions) => T`A wrapper function which delegates to [`cloneInto`](#cloneinto) or
[`exportFunction`](#exportFunction), depending on the type of its argument,
i.e.:```javascript
const fn = () => { ... }
GMCompat.export(fn)
```is equivalent to:
```javascript
GMCompat.exportFunction(fn)
```and:
```javascript
const obj = { ... }
GMCompat.export(obj)
```is equivalent to:
```javascript
GMCompat.cloneInto(obj)
```An optional options object can be supplied to override the default target
([`unsafeWindow`](#unsafeWindow)). This is passed as the options parameter to
[`cloneInto`](#cloneInto) or [`exportFunction`](#exportFunction) and merged
into their default options.```javascript
GMCompat.export(obj, { target: iframe })
```## exportFunction
**Type**: `(fn: T, options?: ExportFunctionOptions) => T`
Portable access to Firefox's [`exportFunction`][exportFunction] function, which
returns a version of the supplied function that can be executed in the provided
context.```javascript
function log () { }GMCompat.unsafeWindow.log = GMCompat.exportFunction(log)
```If no options are supplied, the default values are:
```javascript
{ allowCrossOriginArguments: false, target: GMCompat.unsafeWindow }
```If supplied, they are merged into/override the defaults.
## unsafeWindow
**Type**: `Window`
Portable access to the page's window. This is distinct from the `window` object
in userscripts, which is an isolated wrapper of the original window (i.e. the
page's `window` can't see properties of the userscript's `window`).```javascript
GMCompat.unsafeWindow.log = GMCompat.unsafeWindow.noop
```Note that accessing `unsafeWindow` directly, AKA `window.unsafeWindow`, does
*not* work in all engines, so this should be used instead if scripts are to be
run portably.## unwrap
**Type**: `(object: T) => T`
Takes a wrapped object and returns its wrapped value. This is sometimes needed
when working with values transferred from the page to the userscript.```javascript
let resultfunction callback (value) {
result = value // may be wrapped (page -> userscript)
}const $callback = GMCompat.export(callback)
GMCompat.unsafeWindow.onResult($callback)
result = GMCompat.unwrap(result)
```# COMPATIBILITY
gm-compat has been tested on the following engines:
- Greasemonkey (v4.9)
- Tampermonkey for Firefox (v4.11.6117)
- Violentmonkey for Firefox (v2.12.7)
- Violentmonkey for Chrome (v2.12.7)# DEVELOPMENT
## NPM Scripts
The following NPM scripts are available:
- build - compile the library and save to the target directory
- build:doc - generate the README's TOC (table of contents)
- build:release - compile the library for release and save to the target directory
- clean - remove the target directory and its contents
- rebuild - clean the target directory and recompile the library# SEE ALSO
## Libraries
- [gm4-polyfill][] - a polyfill for the GM4 API for GM3-compatible userscript engines
- [gm-storage][] - an ES6 Map wrapper for the synchronous userscript storage API
- [UnCommonJs][] - a minimum viable shim for `module.exports`## Articles
- [MDN - Sharing objects with page scripts][mdn-sharing]
# VERSION
1.1.0
# AUTHOR
[chocolateboy](mailto:[email protected])
# COPYRIGHT AND LICENSE
Copyright © 2020 by chocolateboy.
This is free software; you can redistribute it and/or modify it under the
terms of the [Artistic License 2.0](https://www.opensource.org/licenses/artistic-license-2.0.php).[cloneInto]: https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.cloneInto
[csp]: https://github.com/violentmonkey/violentmonkey/issues/1001
[exportFunction]: https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
[gm4-polyfill]: https://github.com/greasemonkey/gm4-polyfill
[gm-storage]: https://www.npmjs.com/package/gm-storage
[grant-none]: https://github.com/greasemonkey/greasemonkey/issues/3015#issuecomment-436645719
[jsDelivr]: https://cdn.jsdelivr.net/npm/[email protected]/dist/index.iife.min.js
[mdn-sharing]: https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts
[UnCommonJS]: https://www.npmjs.com/package/@chocolateboy/uncommonjs
[unpkg]: https://unpkg.com/[email protected]/dist/index.iife.min.js
[unsafeWindow]: https://sourceforge.net/p/greasemonkey/wiki/unsafeWindow/
[xhr#open]: https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/open