To handle the difference in scope injection we expose multiple utilities which behave differently per browser in src/utils.js
and ContentFeature
base class. For Firefox the code exposed handles xrays correctly without needing the features to be authored differently.
https://github.com/duckduckgo/content-scope-scripts
https://github.com/orgs/duckduckgo/teams/core
group accessImportant: Before cloning the repo on Windows, to avoid major headaches, make sure you clone it with unix-style line endings:
git clone --config core.autocrlf=false https://github.com/duckduckgo/content-scope-scripts
The Content Scope Scripts repo includes the build artifacts, which need to be generated as part of your commit.
Inside the repo, run:
npm ci # Preferred over 'npm install' for accurate lockfile updates
Ensure you have a version of node that matches what's in the .nvmrc
file.
Now, to ensure everything's setup, try a full build:
npm run build
This will place built files into the top-level build
folder. If this command ran successfully, you can continue with development.
The tools should work on Windows, but if you have problems you may wish to try using WSL.
Optional:: Use Windows Subsystem for Linux - WSL Installation Guide
Once you have WSL running, make sure you have node and make installed:
sudo apt update
sudo apt install make
For Node.js installation: How to Install Node.js on Ubuntu/Debian
Once node is installed, navigate to the repo path (e.g., /mnt/c/dev/git/content-scope-scripts
) and:
npm install # Install packages
npm run build # Build JS artifacts
After this, you can commit the generated files from your Windows environment through the usual git tools.
If you don't want to install npm on your machine, you can use Docker instead:
docker run -it --rm -v "<repo root>/submodules/content-scope-scripts:/content-scope-scripts" node:latest /bin/bash
root@<id>:/# cd /content-scope-scripts
root@<id>:/content-scope-scripts# npm install
root@<id>:/content-scope-scripts# npm run build
ContentFeature.defineProperty(object, propertyName, descriptor)
Behaves the same as Object.defineProperty(object, propertyName, descriptor)
. The difference is for Firefox we export the relevant functions so it can go across the xray. Use this method if Object.getOwnPropertyDescriptors(object).propertyName
should exist in the supporting browser.
ContentFeature.wrapProperty(object, propertyName, descriptor)
A simple wrapper around defineProperty()
that ignores non-existing properties and retains unspecified descriptor keys.
Example usage:
this.wrapProperty('Navigator.prototype.userAgent', { get: () => 'fakeUA' });
ContentFeature.wrapMethod(object, propertyName, wrapperFn)
Overrides a native method. wrapperFn()
will be called in place of the original method. The original method will be passed as the first argument.
Example usage:
this.wrapMethod(Permissions.prototype, 'query', async function (originalFn, queryObject) {
if (queryObject.name === 'blocked-permission') {
return {
name: queryObject.name,
state: 'denied',
status: 'denied',
};
}
return await nativeImpl.call(this, queryObject);
});
ContentFeature.shimInterface(interfaceName, ImplClass, options)
API for shimming standard constructors. See the WebCompat feature and JSDoc for more details.
Example usage:
this.shimInterface('MediaSession', MyMediaSessionClass, {
disallowConstructor: true,
allowConstructorCall: false,
wrapToString: true,
});
ContentFeature.shimProperty(instanceHost, instanceProp, implInstance, readOnly = false)
API for shimming standard global objects. Usually you want to call shimInterface()
first, and pass an object instance as implInstance
. See the WebCompat feature and JSDoc for more details.
Example usage:
this.shimProperty(Navigator.prototype, 'mediaSession', myMediaSessionInstance, true);
DDGProxy
Behaves a lot like new window.Proxy
with a few differences:
overload
method to actually apply the function to the native property_native
such that it can be called elsewhere if needed without going through the proxyaddDebugFlag
if get/apply is calledshouldExemptMethod
value.toString()
to appear like it was defined nativelyExample usage:
const historyMethodProxy = new DDGProxy(this, History.prototype, 'pushState', {
apply(target, thisArg, args) {
applyRules(activeRules);
return DDGReflect.apply(target, thisArg, args);
},
});
historyMethodProxy.overload();
DDGReflect
Calls into wrappedJSObject.Reflect
for Firefox but otherwise exactly the same as window.Reflect