Frostdev
All guides

Glaze Field guide

Development

How Glaze is put together, and how to build, test, package and publish it.

5 min read4 chaptersBy FrostdevUpdated
GlazeA practical path
  1. 01Enable on your site
  2. 02Choose your palette
  3. 03Make it comfortable

How Glaze is put together, and how to build, test, package and publish it. See also Contributing, Releasing and Firefox for Android testing.

How it works

The content script adds three stylesheets it owns (theme.css, motion.css, extras.css) at page start, so the popup can switch the theme on and off without a reload. theme.css remaps Blackboard’s own --palette-* custom properties onto Glaze’s --gz-* tokens and restyles Ultra’s components. Every setting becomes either a --gz-* variable or a data-gz-* attribute on <html>, which the content script writes from your saved settings, so the CSS never needs JavaScript to decide anything. A small DOM sweep tags the long tail — hardcoded light backgrounds, dark text, viewport-fixed bars — with data-gz-* attributes the stylesheets then restyle.

Each LMS loads only what it needs at first paint: the shared content.js (with the small libraries it uses) plus that site’s own part, content-ultra.js on Blackboard Ultra and content-canvas.js on Canvas. Moodle has no part; it is themed by content.js and moodle.css. Everything else, from the command palette to the grade calculators, loads on demand from features/ the first time a page needs it.

Building

./package.sh

Writes dist/glaze-v<version>.zip, ready for the Chrome Web Store. Requires node and zip. sh scripts/package-firefox.sh writes it too, plus dist/glaze-v<version>-firefox.zip for Firefox Add-ons. For a release, sh scripts/release.sh builds everything into release/<version>/; see RELEASING.md.

node --test

Runs the tests for the colour, settings and page-text logic. No dependencies to install.

Publishing to the Chrome Web Store

sh scripts/publish-chrome.sh

Packages the extension, uploads it with the Chrome Web Store API, and submits it for review. Pass a zip path to upload a specific build; set CWS_NO_PUBLISH=1 to upload without submitting. It signs in as the cws-publisher service account through gcloud impersonation, so no key file is kept: your Google account needs the Service Account Token Creator role on it, and the service account must be added under Account in the Developer Dashboard. The listing text, screenshots and privacy answers aren’t in the API; edit those in the dashboard. The publisher and item IDs default to Glaze’s; override them with CWS_PUBLISHER_ID and CWS_EXTENSION_ID (the publisher ID is in the dashboard’s address, after /devconsole/).

Safari

Safari loads Glaze from this Mac app. The Xcode project is in safari/; to build the app yourself you need Xcode and the public key from Mac update release setup:

sh scripts/build-safari.sh

That packages the extension, copies it into a temporary copy of the project, and builds a universal app in release/<version>/build/Glaze.app, signed with the configured Developer ID. Override SAFARI_SIGN_IDENTITY and SAFARI_TEAM_ID to use a different identity. Safari only installs apps from outside the App Store when they are notarized, so pass a saved notarytool Keychain profile:

NOTARY_PROFILE=your-profile sh scripts/build-safari.sh

or set ASC_KEY_PATH, ASC_KEY_ID and ASC_ISSUER_ID for an App Store Connect API key. The script then notarizes the app, staples the ticket, checks Gatekeeper, and writes release/<version>/github/glaze-<version>-safari-macos.zip and a notarized .dmg, each with its SHA-256 checksum. Without credentials the zip is marked unnotarized and is for development only.

For the Mac App Store, scripts/appstore-safari.sh archives the same project with App Store signing and uploads the build to App Store Connect. It needs an App Store Connect API key with the Admin role in ASC_KEY_PATH, ASC_KEY_ID and ASC_ISSUER_ID; Xcode creates the distribution certificate and profiles on first use. APPSTORE_EXPORT_ONLY=1 builds the signed package without uploading. Listing text, screenshots and review submission happen in App Store Connect.

The app bundles only the files the Xcode project lists for its two extension targets (Mac and iOS); a folder reference brings everything under it. Every build script copies the package into the project’s Resources folder and then runs node scripts/safari-resources.js on it, which stops the build if the package has a file the project doesn’t list. Add a new top-level file or folder to both extension targets in Xcode; test/safari-resources.test.js checks the project against everything the package ships.

scripts/package-safari.sh regenerates the Xcode project from scratch and replaces its native edits. Use it only when you mean to regenerate the project. It doesn’t know about the iPhone and iPad targets below.

Mac update release setup

The Developer ID build stages Sparkle 2.10.0 in a temporary Mac project through scripts/stage-safari-dmg.py; the checked-in App Store project stays free of Sparkle. Glaze’s signing key was made once with the Sparkle distribution’s key generator (bin/generate_keys), which keeps the private key in the login Keychain (account ed25519) of the Mac that made it. Back it up with generate_keys -x <file> and keep that file somewhere safe: without it, no update can be signed for apps already installed. scripts/build-safari.sh passes the public key (0lbeC9MO1QDsjSGjs6iyxSBwXKMpEiKVoB+5wZwQEqY=, or SAFARI_UPDATE_PUBLIC_KEY if set) into SUPublicEDKey in the staged Mac app’s Glaze/Info.plist. Staging on its own without a key leaves updates disabled; a release build rejects the REPLACE_WITH_SPARKLE_EDDSA_PUBLIC_KEY placeholder.

After building and notarizing a release, generate its signed update entry locally (replace <version> with the manifest version):

sh scripts/safari-appcast.sh "release/<version>/github/glaze-<version>-safari-macos.dmg" /path/to/Sparkle/bin

The helper verifies the notarized DMG and app, checks that the Keychain signing key matches the app’s public key, and writes release/appcast.xml, preserving older entries. It does not publish anything. Upload the DMG to its GitHub release before publishing that feed at https://frostdev.io/projects/glaze/appcast.xml (the frostdev.io path is /projects/glaze/appcast.xml). Use the same Sparkle Keychain account for key generation and appcast signing; if you choose a custom account, set SAFARI_UPDATE_KEY_ACCOUNT to it when running the helper.

iPhone and iPad

The iOS app is two more targets in the same Xcode project (Glaze iOS and Glaze iOS Extension), sharing the extension’s resources and native handler with the Mac targets. scripts/appstore-ios.sh archives it with App Store signing into release/<version>/build/Glaze-iOS.xcarchive and release/<version>/app-store/glaze-<version>-ios.ipa. It uses the App Store profiles already on the Mac; set ASC_KEY_PATH, ASC_KEY_ID and ASC_ISSUER_ID to let Xcode create them. It never uploads unless APPSTORE_UPLOAD=1 is set. To try it in the Simulator, run the Glaze iOS scheme in Xcode.

Bundled libraries

Glaze is MIT licensed (LICENSE). Bundled with it, each under its own license beside it: three.js (MIT, lib/three.LICENSE), lottie-web’s light canvas player (MIT, lib/lottie/LICENSE), pdf.js and its JBIG2 and JPEG 2000 decoders (Apache-2.0 and BSD, lib/pdfjs/), and the Tesseract OCR engine and its tessdata_fast models (Apache-2.0, lib/tesseract/), each rebuilt by its scripts/vendor-*.sh.

Also on Glaze's GitHub wiki.