zotero/app/scripts/fetch_xulrunner
Dan Stillman d701c43862 fx153: Restore autocomplete popup sizing from the anchor element
Firefox now sizes autocomplete popups to their content (bug 2045828), so
add the --panel-width calculation back instead of adjusting it.
2026-08-03 11:48:39 -04:00

806 lines
37 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
set -euo pipefail
# Copyright (c) 2011 Zotero
# Center for History and New Media
# George Mason University, Fairfax, Virginia, USA
# http://zotero.org
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")"
. "$APP_ROOT_DIR/config.sh"
. "$SCRIPT_DIR/utils.sh"
cd "$APP_ROOT_DIR"
usage() {
cat >&2 <<'DONE'
Usage: fetch_xulrunner.sh -p <m|w|l> [-a ARCH]
Required
-p PLATFORM m (Mac), w (Windows), l (Linux)
Optional / Conditional
-a ARCH - Mac: disallowed (builds are universal)
- Windows: x64 | arm64 | win32
- Linux: x64 (or x86_64) | arm64 | i686
If omitted on Windows/Linux, all standard archs are fetched.
DONE
exit 1
}
BUILD_MAC=0
BUILD_WIN=0
BUILD_LINUX=0
arch=""
while getopts 'p:a:' opt; do
case $opt in
p)
for (( i=0; i<${#OPTARG}; i++ )); do
case ${OPTARG:i:1} in
m) BUILD_MAC=1;;
w) BUILD_WIN=1;;
l) BUILD_LINUX=1;;
*) echo "Invalid platform: ${OPTARG:i:1}"; echo; usage;;
esac
done;;
a) arch="$OPTARG";;
*) usage;;
esac
shift $((OPTIND-1)); OPTIND=1
done
# Require at least one platform
if [[ $BUILD_MAC == 0 ]] && [[ $BUILD_WIN == 0 ]] && [[ $BUILD_LINUX == 0 ]]; then
usage
fi
# Sanitycheck arch flag vs platform selections
if [[ $BUILD_MAC == 1 ]]; then
[[ -n $arch ]] && { echo "-a is not allowed for Mac" >&2; echo; usage; }
fi
if [[ $BUILD_WIN == 1 || $BUILD_LINUX == 1 ]]; then
# arch optional; if provided validate & map aliases now
if [[ -n $arch ]]; then
if [[ $BUILD_WIN == 1 && $BUILD_LINUX == 1 ]]; then
echo "-a cannot be used when building both Windows and Linux simultaneously" >&2; echo; usage
fi
if [[ $BUILD_WIN == 1 ]]; then
case $arch in
x64) arch="win-x64" ;;
arm64) arch="win-arm64" ;;
win32|win-x64|win-arm64) ;;
*) echo "Invalid Windows arch: $arch" >&2; echo; usage;;
esac
fi
if [[ $BUILD_LINUX == 1 ]]; then
[[ $arch == x64 ]] && arch="x86_64"
case $arch in x86_64|i686|arm64) ;; *) echo "Invalid Linux arch: $arch" >&2; echo; usage;; esac
fi
fi
fi
function get_utf16_chars {
str=$(echo -n "$1" | xxd -p | fold -w 2 | sed -r 's/(.+)/\\\\x{\1}\\\\x{00}/')
# Add NUL padding
if [ -n "${2:-}" ]; then
# Multiply characters x 2 for UTF-16
for i in `seq 1 $(($2 * 2))`; do
str+=$(echo '\\x{00}')
done
fi
echo $str | xargs | sed 's/ //g'
}
#
# Make various modifications to the stock Firefox app
#
function modify_omni {
platform=$1
mkdir omni
mv omni.ja omni
cd omni
# omni.ja is an "optimized" ZIP file, so use a script from Mozilla to avoid a warning from unzip
# here and to make it work after rezipping below
python3 "$APP_ROOT_DIR/scripts/optimizejars.py" --deoptimize ./ ./ ./
rm -f omni.ja.log
unzip omni.ja
rm omni.ja
rm -r chrome/pdfjs
rm -r modules/narrate
#rm -r modules/translation
rm actors/AboutTranslations{Parent,Child}.sys.mjs
rm actors/CookieBanner{Parent,Child}.sys.mjs
rm actors/PictureInPictureChild.sys.mjs
rm actors/ThumbnailsChild.sys.mjs
rm actors/Translations{Engine,}{Parent,Child}.sys.mjs
rm actors/AudioPlayback{Parent,Child}.sys.mjs
replace_line 'BROWSER_CHROME_URL:.+' 'BROWSER_CHROME_URL: "chrome:\/\/zotero\/content\/zoteroPane.xhtml",' modules/AppConstants.sys.mjs
# Used by OSKeyStore as the master-key label, visible in macOS Keychain Access.
# Verify that OSKeyStore still derives the label from MOZ_APP_BASENAME, so a
# future Mozilla change to a hardcoded string doesn't silently rebrand the
# keychain entry back to "Firefox Encrypted Storage".
replace_line 'MOZ_APP_BASENAME: "Firefox"' 'MOZ_APP_BASENAME: "Zotero"' modules/AppConstants.sys.mjs
check_line 'STORE_LABEL: AppConstants\.MOZ_APP_BASENAME \+ " Encrypted Storage"' modules/OSKeyStore.sys.mjs
# https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/internals/preferences.html
#
# It's not clear that most of these do anything anymore when not compiled in, but just in case
replace_line 'MOZ_REQUIRE_SIGNING:' 'MOZ_REQUIRE_SIGNING: false \&\&' modules/AppConstants.sys.mjs
replace_line 'MOZ_DATA_REPORTING:' 'MOZ_DATA_REPORTING: false \&\&' modules/AppConstants.sys.mjs
replace_line 'MOZ_TELEMETRY_REPORTING:' 'MOZ_TELEMETRY_REPORTING: false \&\&' modules/AppConstants.sys.mjs
replace_line 'MOZ_CRASHREPORTER:' 'MOZ_CRASHREPORTER: false \&\&' modules/AppConstants.sys.mjs
replace_line 'MOZ_UPDATE_CHANNEL:.+' 'MOZ_UPDATE_CHANNEL: "none",' modules/AppConstants.sys.mjs
replace_line '"https:\/\/[^\/]+mozilla.com.+"' '""' modules/AppConstants.sys.mjs
# Don't use Mozilla Maintenance Service on Windows
replace_line 'MOZ_MAINTENANCE_SERVICE:' 'MOZ_MAINTENANCE_SERVICE: false \&\&' modules/AppConstants.sys.mjs
# Continue using app.update.auto in prefs.js on Windows
replace_line 'PER_INSTALLATION_PREFS_PLATFORMS = \["win"\]' 'PER_INSTALLATION_PREFS_PLATFORMS = []' modules/UpdateUtils.sys.mjs
# Prompt if major update is available instead of installing automatically on restart
replace_line 'if \(!updateAuto\) \{' 'if (update.type == "major") {
LOG("UpdateService:_selectAndInstallUpdate - prompting because it is a major update");
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_SHOWPROMPT_PREF);
Services.obs.notifyObservers(update, "update-available", "show-prompt");
return;
}
if (!updateAuto) {' modules/UpdateService.sys.mjs
# Avoid console warning about resource://gre/modules/FxAccountsCommon.js
replace_line 'const logins = this._data.logins;' 'const logins = this._data.logins; if (this._data.logins.length != -1) return;' modules/LoginStore.sys.mjs
# Prevent error during network requests
replace_line 'async lazyInit\(\) \{' 'async lazyInit() { if (this.features) return false;' modules/UrlClassifierExceptionListService.sys.mjs
# Silence CrashManager "Could not get children of [...]/Crash Reports/events" spam
replace_line '\/\/ Missing events folders are allowed' '\/\/ Missing events folders are allowed (silence warning)
if (ex) return entries;' modules/CrashManager.sys.mjs
# Several services pull in services-settings (which we strip) and throw
# at profile-after-change. Remove their #init() methods to prevent errors.
replace_line 'async #init\(\) \{' 'async #init() {} async _unused_init() {' modules/EssentialDomainsRemoteSettings.sys.mjs
replace_line 'async #init\(\) \{' 'async #init() {} async _unused_init() {' modules/SimpleURIUnknownSchemesRemoteObserver.sys.mjs
# RemotePermissionService uses a top-level import, so even loading the
# module throws
replace_line 'import \{ RemoteSettings \} from "resource:\/\/services-settings\/remote-settings.sys.mjs";' \
'const RemoteSettings = () => ({ on() {}, get: async () => [] });' \
modules/RemotePermissionService.sys.mjs
replace_line 'pref\("network.captive-portal-service.enabled".+' 'pref("network.captive-portal-service.enabled", false);' greprefs.js
replace_line 'pref\("network.connectivity-service.enabled".+' 'pref("network.connectivity-service.enabled", false);' greprefs.js
replace_line 'pref\("toolkit.telemetry.server".+' 'pref("toolkit.telemetry.server", "");' greprefs.js
replace_line 'pref\("toolkit.telemetry.unified".+' 'pref("toolkit.telemetry.unified", false);' greprefs.js
replace_line 'pref\("media.gmp-manager.url".+' 'pref("media.gmp-manager.url", "");' greprefs.js
#
# # Disable transaction timeout
# perl -pi -e 's/let timeoutPromise/\/*let timeoutPromise/' modules/Sqlite.jsm
# perl -pi -e 's/return Promise.race\(\[transactionPromise, timeoutPromise\]\);/*\/return transactionPromise;/' modules/Sqlite.jsm
# rm -f jsloader/resource/gre/modules/Sqlite.jsm
#
# Disable unwanted components
remove_line '(RemoteSettings|services-|telemetry|Telemetry|URLDecorationAnnotationsService)' components/components.manifest
file="modules/ActorManagerParent.sys.mjs"
# Remove deleted actors
# Entries in the JSWINDOWACTORS/JSPROCESSACTORS literals:
remove_between '^ AudioPlayback: \{' '^ },' $file
remove_between '^ CookieBanner: \{' '^ },' $file
remove_between '^ Thumbnails: \{' '^ },' $file
remove_between '^ Translations: \{' '^ },' $file
remove_between '^ TranslationsEngine: \{' '^ },' $file
# Entries added conditionally after the literals:
remove_between '^ JSWINDOWACTORS\.AboutTranslations = \{' '^ \};' $file
remove_between '^ JSWINDOWACTORS\.PictureInPictureLauncher = \{' '^ \};' $file
remove_between '^ JSWINDOWACTORS\.PictureInPictureToggle = \{' '^ \};' $file
remove_between '^ JSWINDOWACTORS\.PictureInPicture = \{' '^ \};' $file
# Do not trigger LoginManager event that logs an error on autocomplete submission
remove_line 'DOMInputPasswordAdded: \{\},' $file
# On Mac/Linux, ignore relative paths in PATH in Subprocess.pathSearch(), used when a bare command is passed to Utilities.Internal.subprocess()
if [[ $platform != win* ]]; then
replace_line 'let path = PathUtils.join\(dir, bin\);' 'if (!dir.startsWith("\/")) continue; let path = PathUtils.join(dir, bin);' modules/subprocess/subprocess_unix.sys.mjs
fi
# Remove unwanted files
pushd modules
rm Bookmarks*
rm FxAccounts*
echo '' > FingerprintingWebCompatService.sys.mjs
echo '' > PartitioningExceptionListService.sys.mjs
rm Places*
echo '' > PlacesExpiration.sys.mjs
echo '' > PlacesFrecencyRecalculator.sys.mjs
rm -rf services-settings/*
touch services-settings/remote-settings.sys.mjs
# Causes a startup error -- try an empty file or a shim instead?
#rm Telemetry*
rm URLDecorationAnnotationsService.sys.mjs
popd
pushd chrome/toolkit/content/mozapps/extensions
rm -rf default-theme
popd
# This file loads remote-settings.sys.mjs, but if it's empty Zotero crashes
replace_line 'async #init\(\) \{' 'async #init() { if (true) { return; }' modules/URLQueryStrippingListService.sys.mjs
# Clear most WebExtension manifest properties
replace_line 'manifest = normalized.value;' 'manifest = normalized.value;
if (this.type == "extension") {
if (!manifest.applications?.zotero?.id) {
this.manifestError("applications.zotero.id not provided");
}
if (!manifest.applications?.zotero?.update_url) {
this.manifestError("applications.zotero.update_url not provided");
}
if (!manifest.applications?.zotero?.strict_max_version) {
this.manifestError("applications.zotero.strict_max_version not provided");
}
manifest.browser_specific_settings = undefined;
manifest.content_scripts = [];
manifest.permissions = [];
manifest.host_permissions = [];
manifest.web_accessible_resources = undefined;
manifest.experiment_apis = {};
}' modules/Extension.sys.mjs
# Use applications.zotero instead of applications.gecko
replace_line 'let bss = manifest.applications\?.gecko' 'let bss = manifest.applications?.zotero' modules/addons/XPIInstall.sys.mjs
replace_line 'manifest.applications\?.gecko' 'manifest.applications?.zotero' modules/Extension.sys.mjs
# When installing addon, use app version instead of toolkit version for targetApplication
replace_line "id: TOOLKIT_ID," "id: '$APP_ID'," modules/addons/XPIInstall.sys.mjs
# Set strictCompatibility to false in beta/dev/source builds to skip strict_max_version check
replace_line 'addon.strictCompatibility = true;' \
'let version = Services.appinfo.version;
addon.strictCompatibility = !version.includes("-beta") && !version.includes("-dev") && !version.includes("SOURCE");' modules/addons/XPIInstall.sys.mjs
# In beta/dev/source builds, ignore the persisted strictCompatibility flag for already-installed
# plugins, so plugins installed on a stable build (where the flag was set to true) aren't marked
# incompatible after upgrading. The manifest isn't re-parsed on app upgrade, so the flag set at
# install time sticks around.
replace_line '!this\.addon\.strictCompatibility' \
'(!this.addon.strictCompatibility || Services.appinfo.version.includes("-beta") || Services.appinfo.version.includes("-dev") || Services.appinfo.version.includes("SOURCE"))' modules/addons/XPIInstall.sys.mjs
replace_line '!this\.strictCompatibility &&' \
'(!this.strictCompatibility || Services.appinfo.version.includes("-beta") || Services.appinfo.version.includes("-dev") || Services.appinfo.version.includes("SOURCE")) &&' modules/addons/XPIDatabase.sys.mjs
# Accept zotero@chnm.gmu.edu for target application to allow Zotero 6 plugins to remain
# installed in Zotero 7
replace_line "if \(targetApp.id == Services.appinfo.ID\) \{" "if (targetApp.id == 'zotero\@chnm.gmu.edu') targetApp.id = '$APP_ID'; if (targetApp.id == Services.appinfo.ID) {" modules/addons/XPIDatabase.sys.mjs
# TEMP: Skip addons without ids until we figure out what's going on with the default theme.
# This fixes plugin updating and other things.
replace_line 'return addons;' 'return addons.filter(addon => addon.id);' modules/addons/XPIDatabase.sys.mjs
# For updates, look for applications.zotero instead of applications.gecko in manifest.json and
# use the app id and version for strict_min_version/strict_max_version comparisons
replace_line 'gecko: \{\},' 'zotero: {},' modules/addons/AddonUpdateChecker.sys.mjs
replace_line 'if \(!\("gecko" in applications\)\) \{' 'if (!("zotero" in applications)) {' modules/addons/AddonUpdateChecker.sys.mjs
replace_line '"gecko not in application entry' '"zotero not in application entry' modules/addons/AddonUpdateChecker.sys.mjs
replace_line 'let app = getProperty\(applications, "gecko", "object"\);' 'let app = getProperty(applications, "zotero", "object");' modules/addons/AddonUpdateChecker.sys.mjs
replace_line "id: TOOLKIT_ID," "id: '$APP_ID'," modules/addons/AddonUpdateChecker.sys.mjs
replace_line 'lazy.AddonManagerPrivate.webExtensionsMinPlatformVersion' '"7.0"' modules/addons/AddonUpdateChecker.sys.mjs
replace_line 'result.targetApplications.push' 'false && result.targetApplications.push' modules/addons/AddonUpdateChecker.sys.mjs
# In beta/dev/source builds, don't set strictCompatibility on parsed update entries so that
# AddonUpdateChecker doesn't reject an available update whose maxVersion is lower than the
# current (non-stable) app version.
replace_line 'result\.strictCompatibility = appEntry\.maxVersion != "\*";' \
'let appVer = Services.appinfo.version; result.strictCompatibility = appEntry.maxVersion != "*" && !appVer.includes("-beta") && !appVer.includes("-dev") && !appVer.includes("SOURCE");' modules/addons/AddonUpdateChecker.sys.mjs
# Set uninstall flag to true if the update config has it set
replace_line 'targetApplications: \[appEntry\],' \
'targetApplications: \[appEntry\],
uninstall: getProperty(update, "uninstall", "boolean"),' modules/addons/AddonUpdateChecker.sys.mjs
# Prevent automatic installation of default theme
# (chrome/toolkit/content/mozapps/extensions/default-theme, removed above)
replace_line 'this.maybeInstallBuiltinAddon\(' 'if (false) this.maybeInstallBuiltinAddon(' modules/addons/XPIProvider.sys.mjs
# Don't log a warning when checking 'distribution/extensions' for addons
replace_line 'logger.warn\(`Checking' 'logger.debug(`Checking' modules/addons/XPIProvider.sys.mjs
# Allow addon installation by bypassing confirmation dialogs. If we want a confirmation dialog,
# we need to either add gXPInstallObserver from browser-addons.js [1][2] or provide our own with
# Ci.amIWebInstallPrompt [3].
#
# [1] https://searchfox.org/mozilla-esr102/rev/5a6d529652045050c5cdedc0558238949b113741/browser/base/content/browser.js#1902-1923
# [2] https://searchfox.org/mozilla-esr102/rev/5a6d529652045050c5cdedc0558238949b113741/browser/base/content/browser-addons.js#201
# [3] https://searchfox.org/mozilla-esr102/rev/5a6d529652045050c5cdedc0558238949b113741/toolkit/mozapps/extensions/AddonManager.sys.mjs#3114-3124
replace_line 'if \(info.addon.installPermissions\) \{' 'if (false) {' modules/AddonManager.sys.mjs
replace_line '\} else if \(info.addon.sitePermissions\) \{' '} else if (false) {' modules/AddonManager.sys.mjs
replace_line '\} else if \(requireConfirm\) \{' '} else if (false) {' modules/AddonManager.sys.mjs
# Make addon listener methods wait for promises, to allow calling asynchronous plugin `shutdown`
# and `uninstall` methods in our `onInstalling` handler
replace_line 'callAddonListeners\(aMethod' 'async callAddonListeners(aMethod' modules/AddonManager.sys.mjs
# Don't need this one to be async, but we can't easily avoid modifying its `listener[aMethod].apply()` call
replace_line 'callManagerListeners\(aMethod' 'async callManagerListeners(aMethod' modules/AddonManager.sys.mjs
replace_line 'AddonManagerInternal.callAddonListeners.apply\(AddonManagerInternal, aArgs\);' \
'return AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs);' modules/AddonManager.sys.mjs
replace_line 'listener\[aMethod\].apply\(listener, aArgs\);' \
'let maybePromise = listener[aMethod].apply(listener, aArgs);
if (maybePromise && maybePromise.then) await maybePromise;' modules/AddonManager.sys.mjs
replace_line 'AddonManagerPrivate.callAddonListeners' 'await AddonManagerPrivate.callAddonListeners' modules/addons/XPIInstall.sys.mjs
replace_line 'let uninstall = \(\) => \{' 'let uninstall = async () => {' modules/addons/XPIInstall.sys.mjs
replace_line 'cancelUninstallAddon\(aAddon\)' 'async cancelUninstallAddon(aAddon)' modules/addons/XPIInstall.sys.mjs
# Check for `uninstall` flag in update config and uninstall the addon if set
replace_line 'if \(update && !this.addon.location.locked\) \{' \
'if (update && !this.addon.location.locked) {
if (update.uninstall) {
let aId = this.addon.id;
logger.debug(`AddonUpdateChecker: uninstall addon \${aId} with uninstall flag`);
let addon = await AddonManager.getAddonByID(aId);
if (addon && (addon.permissions & AddonManager.PERM_CAN_UNINSTALL)) {
try {
addon.uninstall();
} catch (err) {
logger.error(`AddonUpdateChecker: error uninstalling addon \${aId} with uninstall flag`, err);
}
sendUpdateAvailableMessages(this, null);
return;
}
else {
logger.error(`AddonUpdateChecker: addon \${aId} with uninstall flag not found or cannot be uninstalled`);
sendUpdateAvailableMessages(this, null);
return;
}
}
' modules/addons/XPIInstall.sys.mjs
# Prevent login manager from trying to load FxAccountsCommon.sys.mjs
replace_line 'return login.hostname == lazy.FXA_PWDMGR_HOST;' 'return false;' modules/storage-json.sys.mjs
# No idea why this is necessary, but without it initialization fails with "TypeError: "constructor" is read-only"
replace_line 'LoginStore.prototype.constructor = LoginStore;' '\/\/LoginStore.prototype.constructor = LoginStore;' modules/LoginStore.sys.mjs
#
# # Allow proxy password saving
# perl -pi -e 's/get _inPrivateBrowsing\(\) \{/get _inPrivateBrowsing() {if (true) { return false; }/' components/nsLoginManagerPrompter.js
#
# # Change text in update dialog
# perl -pi -e 's/A security and stability update for/A new version of/' chrome/en-US/locale/en-US/mozapps/update/updates.properties
# perl -pi -e 's/updateType_major=New Version/updateType_major=New Major Version/' chrome/en-US/locale/en-US/mozapps/update/updates.properties
# perl -pi -e 's/updateType_minor=Security Update/updateType_minor=New Version/' chrome/en-US/locale/en-US/mozapps/update/updates.properties
# perl -pi -e 's/update for &brandShortName; as soon as possible/update as soon as possible/' chrome/en-US/locale/en-US/mozapps/update/updates.dtd
#
# Set available locales
cp "$APP_ROOT_DIR/assets/multilocale.txt" res/multilocale.txt
# Use Zotero URL opening in Mozilla dialogs (e.g., app update dialog)
replace_line 'function openURL\(aURL\) \{' 'function openURL(aURL) {let {Zotero} = ChromeUtils.importESModule("chrome:\/\/zotero\/content\/zotero.mjs"); Zotero.launchURL(aURL); if (true) { return; }' chrome/toolkit/content/global/contentAreaUtils.js
#
# Modify Add-ons window
#
# Prevent display: block from overriding display: none on <hr>s
replace_line 'display: block !important;' 'display: block;' chrome/toolkit/content/global/elements/panel-list.css
file="chrome/toolkit/content/mozapps/extensions/aboutaddons.css"
echo >> $file
# Hide search bar, Themes and Plugins tabs, and sidebar footer
echo '.main-search, button[name="theme"], button[name="plugin"], sidebar-footer { display: none; }' >> $file
echo '.main-heading { margin-top: 2em; }' >> $file
# Hide Details/Permissions tabs in addon details so we only show details
echo 'addon-details > button-group { display: none !important; }' >> $file
# Hide "Run on sites with restrictions" row and helper message
echo '.addon-detail-row-quarantined-domains { display: none; }' >> $file
echo '.addon-detail-help-row:has([data-l10n-id="addon-detail-quarantined-domains-help"]) { display: none; }' >> $file
# Hide "Report" button from plugin menu
echo 'addon-card addon-options hr:first-of-type { display: none }' >> $file
echo '[data-l10n-id="report-addon-button"] { display: none }' >> $file
# Hide "Debug Addons" and "Manage Extension Shortcuts"
echo 'panel-item[action="debug-addons"], panel-item[action="reset-update-states"] + hr, panel-item[action="manage-shortcuts"] { display: none }' >> $file
# Show cursor feedback on plugin homepage links
echo '.addon-detail-row-homepage .text-link { cursor: pointer; color: LinkText; }' >> $file
echo '.addon-detail-row-homepage .text-link:hover { text-decoration: underline; }' >> $file
file="chrome/toolkit/content/mozapps/extensions/aboutaddons-utils.mjs"
# Hide unsigned-addon warning
replace_line 'export function isUnsignedWarningMessageDisabled\(\) \{' \
'export function isUnsignedWarningMessageDisabled() {if (true) return true;' $file
# Use our own localized string for blocked plugin notification
replace_line 'details-notification-hard-blocked-\$\{typeSuffix\}' 'plugins-blocked-plugin' $file
# Hide Recommendations tab in sidebar and recommendations in main pane
replace_line 'function isDiscoverEnabled\(\) \{' 'function isDiscoverEnabled() {return false;' $file
file="chrome/toolkit/content/mozapps/extensions/components/addon-details.mjs"
# Hide Private Browsing setting in addon details
replace_line 'pbRow\.' '\/\/pbRow.' $file
replace_line 'let isAllowed = await isAllowedInPrivateBrowsing' '\/\/let isAllowed = await isAllowedInPrivateBrowsing' $file
# Open plugin links in external browser
replace_line 'let homepageURL = homepageRow.querySelector\(\"a\"\);' 'let homepageURL = homepageRow.querySelector(\"\.text-link\");' $file
replace_line 'homepageURL.href = addon.homepageURL;' 'homepageURL.setAttribute("href", addon.homepageURL);' $file
replace_line '<a target="_blank" dir="ltr"><\/a>' \
'<label target="_blank" class="text-link" dir="ltr"><\/label>' $file
file="chrome/toolkit/content/mozapps/extensions/components/addon-card.mjs"
# Use our own strings for the removal prompt
replace_line 'let \{ BrowserAddonUI \} = windowRoot.window;' '' $file
replace_line 'await BrowserAddonUI.promptRemoveExtension' 'promptRemoveExtension' $file
# Customize empty-list message
replace_line 'createEmptyListMessage\(\) {' 'createEmptyListMessage() {
var p = document.createElement("p");
p.id = "empty-list-message";
return p;' chrome/toolkit/content/mozapps/extensions/components/addon-list.mjs
# Add zotero.ftl to the addons page for our own localized strings
replace_line 'href="toolkit\/about\/aboutAddons.ftl"' 'href="toolkit\/about\/aboutAddons.ftl" \/>\n <link rel="localization" href="zotero.ftl"' \
chrome/toolkit/content/mozapps/extensions/aboutaddons.html
# TODO: Use our own strings, but for now fix the bundled English ones
replace_line 'Manage Your Extensions' 'Manage Your Plugins' localization/en-US/toolkit/about/aboutAddons.ftl
replace_line '= Extensions' '= Plugins' localization/en-US/toolkit/about/aboutAddons.ftl
replace_line 'Add-on' 'Plugin' localization/en-US/toolkit/about/aboutAddons.ftl
replace_line 'add-ons' 'plugins' localization/en-US/toolkit/about/aboutAddons.ftl
replace_line 'Select add-on to install' 'Select plugin to install' localization/en-US/toolkit/about/aboutAddons.ftl
replace_line 'pref\("extensions.htmlaboutaddons.recommendations.enabled".+' 'pref("extensions.htmlaboutaddons.recommendations.enabled", false);' greprefs.js
# Hide Report option
replace_line 'pref\("extensions.abuseReport.enabled".+' 'pref("extensions.abuseReport.enabled", false);' greprefs.js
# The first displayed Services.prompt dialog's size jumps around because sizeToContent() is called twice
# Fix by preventing the first sizeToContent() call if the icon hasn't been loaded yet
replace_line 'window.sizeToContent\(\);' 'if (ui.infoIcon.complete) window.sizeToContent();' chrome/toolkit/content/global/commonDialog.js
replace_line 'ui.infoIcon.addEventListener' 'if (!ui.infoIcon.complete) ui.infoIcon.addEventListener' chrome/toolkit/content/global/commonDialog.js
# Import style into built-in dialogs
replace_line 'id=\"commonDialogWindow\"' \
'id=\"commonDialogWindow\" class=\"zotero-dialog\" drawintitlebar-platforms=\"mac\"' \
chrome/toolkit/content/global/commonDialog.xhtml
# commonDialog.css link is split across multiple lines, so we have to do a weird substitution,
# so check the one-line global.css to make sure the format hasn't changed
check_line '<html:link rel="stylesheet" href="chrome:\/\/global\/skin\/global.css" \/>' chrome/toolkit/content/global/commonDialog.xhtml
replace_line 'chrome:\/\/global\/skin\/commonDialog.css"' \
'chrome:\/\/global\/skin\/commonDialog.css"\/>
<html:link rel="stylesheet" href="chrome:\/\/zotero-platform\/content\/zotero.css"' \
chrome/toolkit/content/global/commonDialog.xhtml
# commonDialog.xhtml can be opened via Services.prompt in the Profile Manager, so check for a profile first
replace_line 'function commonDialogOnLoad\(\) \{' \
'function commonDialogOnLoad() {
try {
Services.dirsvc.get(\"ProfD\", Ci.nsIFile);
Services.scriptloader.loadSubScript(\"chrome:\/\/zotero\/content\/include.js\", this);
Services.scriptloader.loadSubScript(\"chrome:\/\/zotero\/content\/titlebar.js\", this);
Services.scriptloader.loadSubScript(\"chrome:\/\/zotero\/content\/customElements.js\", this);
} catch (e) {}' \
chrome/toolkit/content/global/commonDialog.js
# Use native checkbox instead of Firefox-themed version in prompt dialogs
replace_line '<xul:checkbox' '<xul:checkbox native=\"true\"' chrome/toolkit/content/global/commonDialog.xhtml
# The <browser> CE appends its autoscroll <popup> to document.documentElement, which doesn't work in zoteroPane.xhtml
# (in Firefox browser.xhtml, document.documentElement is <html>, which can contain <popup>s; in zoteroPane.xhtml, it's
# <window>, which can't)
# Fix by appending it to a <popupset> instead
replace_line 'document.documentElement.appendChild\(this._autoScrollPopup\);' 'let popupset = document.querySelector("popupset");
if (!popupset) {
popupset = document.createXULElement("popupset");
document.documentElement.appendChild(popupset);
}
popupset.appendChild(this._autoScrollPopup);' chrome/toolkit/content/global/elements/browser-custom-element.mjs
# Remove aria-autocomplete unnecessarily added to search-textbox to not confuse screen readers
remove_line 'this.inputField.setAttribute\("aria-autocomplete' chrome/toolkit/content/global/elements/search-textbox.js
# Remove non-native text input styles
remove_between 'html\|input\:where\(' '^}' chrome/toolkit/skin/classic/global/global-shared.css
# Prevent window dragging on scrollbars
echo 'scrollbar { -moz-window-dragging: no-drag; }' >> chrome/toolkit/res/scrollbars.css
# Provide a mechanism for non-UA stylesheets to override the overflow property
# of a text control's anonymous text node child
echo '::placeholder, ::-moz-text-control-editing-root, ::-moz-text-control-preview {
overflow: var(--moz-text-control-overflow);
}' >> chrome/toolkit/res/forms.css
# Size an autocomplete popup to the input that opened it, or to an ancestor that designates
# itself as the width container. Firefox sizes the popup to its content. (For creator inputs.)
replace_line '\/\/ invalidate\(\) depends on the width attribute' \
'let widthElement = aElement.closest(".autocomplete-popup-width-container") || aElement;
this.style.setProperty("--panel-width", Math.max(widthElement.getBoundingClientRect().width, 100) + "px");
\/\/ invalidate() depends on the width attribute' \
chrome/toolkit/content/global/elements/autocomplete-popup.js
zip -qr9XD omni.ja *
mv omni.ja ..
cd ..
python3 "$APP_ROOT_DIR/scripts/optimizejars.py" --optimize ./ ./ ./
rm -rf omni
# Unzip browser/omni.ja and leave unzipped
cd browser
mkdir omni
mv omni.ja omni
cd omni
ls -la
set +e
unzip omni.ja
set -e
rm omni.ja
# Remove Firefox update URLs
remove_line 'pref\("app.update.url.(manual|details)' defaults/preferences/firefox-branding.js
# Remove Firefox overrides (e.g., to use Firefox-specific strings for connection errors)
remove_line '(override)' chrome/chrome.manifest
# Remove WebExtension APIs
remove_line ext-browser.json components/components.manifest
# Don't open a second window if app is already open when launching .exe (Windows) or running via
# command line (macOS)
replace_line 'function dch_handle\(cmdLine\) \{' 'function dch_handle(cmdLine) {
if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) { return; }
' modules/BrowserContentHandler.sys.mjs
}
mkdir -p xulrunner
cd xulrunner
if [ $BUILD_MAC == 1 ]; then
GECKO_VERSION="$GECKO_VERSION_MAC"
DOWNLOAD_URL="https://ftp.mozilla.org/pub/firefox/releases/$GECKO_VERSION"
rm -rf Firefox.app
if [ -e "Firefox $GECKO_VERSION.app.zip" ]; then
echo "Using Firefox $GECKO_VERSION.app.zip"
unzip "Firefox $GECKO_VERSION.app.zip"
else
curl -o Firefox.dmg "$DOWNLOAD_URL/mac/en-US/Firefox%20$GECKO_VERSION.dmg"
set +e
hdiutil detach -quiet /Volumes/Firefox 2>/dev/null
set -e
hdiutil attach -quiet Firefox.dmg
cp -a /Volumes/Firefox/Firefox.app .
# Store local copy of unmodified app
zip -r "Firefox $GECKO_VERSION.app.zip" Firefox.app
hdiutil detach -quiet /Volumes/Firefox
fi
# Download custom components (e.g., XUL), as an xz-compressed tarball of the MacOS folder with
# custom files in it
echo
rm -rf MacOS
if [ -n "$custom_components_hash_mac" ]; then
if [ -e "Firefox $GECKO_VERSION MacOS.zip" ]; then
echo "Using Firefox $GECKO_VERSION MacOS.zip"
unzip "Firefox $GECKO_VERSION MacOS.zip"
else
echo "Downloading custom Firefox components"
echo
curl -o MacOS.tar.xz "${custom_components_url}mac/$custom_components_hash_mac.tar.xz"
# Verify hash
if [[ "`uname`" = "Darwin" ]]; then
shasum="shasum -a 256"
else
shasum=sha256sum
fi
echo
echo "$custom_components_hash_mac MacOS.tar.xz" | $shasum -c -
echo
tar xvf MacOS.tar.xz
fi
echo
fi
pushd Firefox.app/Contents/Resources
modify_omni mac
popd
# Replace "FirefoxCP" with "ZoteroCP" for subprocesses ("Isolated Web Content", "Socket Process", "Web Content", etc.)
info_plist=Firefox.app/Contents/MacOS/plugin-container.app/Contents/Resources/English.lproj/InfoPlist.strings
from=$(get_utf16_chars "FirefoxCP")
to=$(get_utf16_chars "ZoteroCP")
perl -pi -e "s/$from/$to/" $info_plist
# Check for UTF-16 "ZoteroCP"
if ! grep -a -q "Z.o.t.e.r.o.C.P." $info_plist; then
echo '"ZoteroCP" not found in InfoPlist.strings after replacement'
exit 1
fi
rm -f Firefox.dmg
echo $("$SCRIPT_DIR/xulrunner_hash" -p m) > hash-mac
fi
if [ $BUILD_WIN == 1 ]; then
GECKO_VERSION="$GECKO_VERSION_WIN"
DOWNLOAD_URL="https://ftp.mozilla.org/pub/firefox/releases/$GECKO_VERSION"
if [[ -n $arch ]]; then
arch_list=("$(get_canonical_arch w $arch)")
else
arch_list=(win-x64 win-arm64 win32)
fi
for arch in "${arch_list[@]}"; do
xdir=firefox-$arch
rm -rf $xdir
mkdir $xdir
archived_file="Firefox Setup $GECKO_VERSION-$arch.exe"
if [ -e "$archived_file" ]; then
echo "Using $archived_file"
cp "$archived_file" "Firefox%20Setup%20$GECKO_VERSION.exe"
else
if [ $arch = "win-x64" ]; then
moz_arch=win64
elif [ $arch = "win-arm64" ]; then
moz_arch=win64-aarch64
else
moz_arch=$arch
fi
curl -O "$DOWNLOAD_URL/$moz_arch/en-US/Firefox%20Setup%20$GECKO_VERSION.exe"
# Store local copy of unmodified app
cp "Firefox%20Setup%20$GECKO_VERSION.exe" "$archived_file"
fi
7z x Firefox%20Setup%20$GECKO_VERSION.exe -o$xdir 'core/*'
mv $xdir/core $xdir-core
rm -rf $xdir
mv $xdir-core $xdir
# Download custom components, as an architecture-specific ZIP containing xul.dll, etc.
echo
hash_arch_name="${arch/-/_}"
arch_var_name="custom_components_hash_${hash_arch_name}"
hash=${!arch_var_name:-}
if [ -n "$hash" ]; then
zipfile="Firefox $GECKO_VERSION-$arch Components.zip"
# ZIP filename doesn't include "esr" or "win-"
url="${custom_components_url}win/${GECKO_VERSION//esr}-${arch#win-}-$hash.zip"
if [[ "$(uname)" = "Darwin" ]]; then
shasum="shasum -a 256"
else
shasum=sha256sum
fi
download_zip() {
echo "Downloading custom Firefox components"
echo
echo $url
curl -fo "$zipfile" "$url"
}
verify_hash() {
echo "$hash $zipfile" | $shasum -c -
}
# Download if file doesn't exist
if [ ! -e "$zipfile" ]; then
download_zip
fi
echo
if ! verify_hash; then
echo "SHA hash mismatch. Redownloading ZIP…"
rm -f "$zipfile"
download_zip
echo
if ! verify_hash; then
echo "Hash still doesn't match. Aborting."
exit 1
fi
fi
echo
unzip -o "$zipfile" -d "$xdir"
echo
fi
pushd $xdir
#
# NOTE: Below is disabled, since the full UTF-16 string no longer appears directly in the DLL
# -- it's now truncated after "e594e8d1" for x64/win32 and doesn't appear at all for ARM64
#
# Replace "Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38" with "Zotero" for C:\ProgramData directory
#
# Mozilla uses a UUID in the path because they previously used just "Mozilla" and needed to
# recreate the folder with correct permissions for security reasons, but we never had a folder in
# ProgramData, so we can just create it as "Zotero". Instead of using a custom xul.dll, just
# replace the hard-coded string with "Zotero" and add a bunch of NULs.
#
#from=$(get_utf16_chars "Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38")
#to=$(get_utf16_chars "Zotero" 38)
#perl -pe "s/$from/$to/" < xul.dll > xul.dll.new
#mv xul.dll.new xul.dll
# Check for UTF-16 "Zotero" in DLL
#
# (The macOS strings command doesn't have an encoding option, so skip on macOS. You need to
# build on Windows to make an installer, so a Windows build from macOS likely isn't being
# deployed.)
#
#if [[ "`uname`" != "Darwin" ]] && [[ -z "$(strings -e l xul.dll | grep -m 1 Zotero)" ]]; then
# echo '"Zotero" not found in xul.dll after replacement'
# exit 1
#fi
# We re-sign DLL in build.sh, but presumably this file, which is for the Widevine CDM,
# isn't vaid anymore
rm xul.dll.sig
modify_omni $arch
# Disable skeleton UI window
# https://forums.zotero.org/discussion/comment/437636/#Comment_437636
replace_line 'pref\("browser\.startup\.preXulSkeletonUI", true\);' 'pref("browser.startup.preXulSkeletonUI", false);' defaults/preferences/firefox.js
popd
# Uncomment to create local copies for reuse
#cp "Firefox%20Setup%20$GECKO_VERSION.exe" "Firefox Setup $GECKO_VERSION-$arch.exe"
rm "Firefox%20Setup%20$GECKO_VERSION.exe"
echo
echo
echo "$( "$SCRIPT_DIR/xulrunner_hash" -p w -a "$arch" )" > "hash-$arch"
done
fi
if [ $BUILD_LINUX == 1 ]; then
GECKO_VERSION="$GECKO_VERSION_LINUX"
DOWNLOAD_URL="https://ftp.mozilla.org/pub/firefox/releases/$GECKO_VERSION"
if [[ -n $arch ]]; then
arch_list=("$arch")
else
arch_list=(x86_64 arm64 i686)
fi
for arch in "${arch_list[@]}"; do
xdir="firefox-linux-$arch"
rm -rf $xdir
archived_file="firefox-$GECKO_VERSION-$arch.tar.xz"
if [ -e "$archived_file" ]; then
echo "Using $archived_file"
cp "$archived_file" "firefox-$GECKO_VERSION.tar.xz"
else
if [ $arch = "arm64" ]; then
moz_arch=aarch64
else
moz_arch=$arch
fi
curl -O "$DOWNLOAD_URL/linux-$moz_arch/en-US/firefox-$GECKO_VERSION.tar.xz"
# Store local copy of unmodified app
cp "firefox-$GECKO_VERSION.tar.xz" "$archived_file"
fi
tar xvf firefox-$GECKO_VERSION.tar.xz
mv firefox $xdir
pushd $xdir
modify_omni $arch
popd
rm "firefox-$GECKO_VERSION.tar.xz"
echo "$( $SCRIPT_DIR/xulrunner_hash -p l -a "$arch" )" > "hash-linux-${arch}"
done
fi
echo Done