npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

gm-compat

v1.1.0

Published

Portable monkey-patching for userscripts

Downloads

318

Readme

gm-compat

NAME

gm-compat - portable monkey-patching for userscripts

FEATURES

INSTALLATION

$ npm install gm-compat

USAGE

// ==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.open

function 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, cloneInto and exportFunction. These can be used to portably modify page properties, e.g. to hook XMLHttpRequest#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 by all engines, and @grant none (and unsafeWindow) doesn't portably work on all sites.

TYPES

The following types are referenced in the descriptions below.

type CloneIntoOptions = {
    cloneFunctions?: boolean;
    target?: object;
    wrapReflectors?: boolean;
};

type ExportOptions = {
    target?: object;
};

type ExportFunctionOptions = {
    allowCrossOriginArguments?: boolean;
    defineAs?: string;
    target?: object;
};

API

apply

Type: <A extends any[], R>($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:

function open (method, url) {
    if (url === TARGET) // ...

    // XXX Error: Permission denied to access property "length"
    oldOpen.apply(this, arguments)
}

This works:

function open (method, url) {
    // ...
    GMCompat.apply(this, oldOpen, arguments) // OK
}

call

Type: <A extends any[], R>($this: any, fn: ((...args: ...A) => R), ...args: ...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:

// XXX Error: Permission denied to access property "value"
GMCompat.unsafeWindow.notify('loaded', { value: 42 })

This works:

GMCompat.call(undefined, GMCompat.unsafeWindow.notify, 'loaded', { value: 42 }) // OK

cloneInto

Type: <T extends object>(object: T, options?: CloneIntoOptions) => T

Portable access to Firefox's cloneInto function, which returns a version of the supplied object that can be accessed in the provided context.

const dummyPerformance = {
    now () { ... }
}

GMCompat.unsafeWindow.performance = GMCompat.cloneInto(dummyPerformance)

If no options are supplied, the default values are:

{ cloneFunctions: true, target: GMCompat.unsafeWindow, wrapReflectors: true }

If supplied, they are merged into/override the defaults.

export

Type:

  • <T extends Function>(value: T, options?: ExportOptions) => T
  • <T extends object>(value: T, options?: ExportOptions) => T

A wrapper function which delegates to cloneInto or exportFunction, depending on the type of its argument, i.e.:

const fn = () => { ... }
GMCompat.export(fn)

is equivalent to:

GMCompat.exportFunction(fn)

and:

const obj = { ... }
GMCompat.export(obj)

is equivalent to:

GMCompat.cloneInto(obj)

An optional options object can be supplied to override the default target (unsafeWindow). This is passed as the options parameter to cloneInto or exportFunction and merged into their default options.

GMCompat.export(obj, { target: iframe })

exportFunction

Type: <T extends Function>(fn: T, options?: ExportFunctionOptions) => T

Portable access to Firefox's exportFunction function, which returns a version of the supplied function that can be executed in the provided context.

function log () { }

GMCompat.unsafeWindow.log = GMCompat.exportFunction(log)

If no options are supplied, the default values are:

{ 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).

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: <T extends object>(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.

let result

function 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

VERSION

1.1.0

AUTHOR

chocolateboy

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.