// Copyright 2023 Arnaud Ferraris, Oliver Smith // SPDX-License-Identifier: MPL-2.0 // This is a Firefox autoconfig file: // https://support.mozilla.org/en-US/kb/customizing-firefox-using-autoconfig // Import custom userChrome.css on startup or new profile creation // Log file: $(find ~/.mozilla -name mobile-config-firefox.log) const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); var updated = false; var fragments_cache = {}; // cache for css_file_get_fragments() // Create /chrome/ directory if not already present var chromeDir = Services.dirsvc.get("ProfD", Ci.nsIFile); chromeDir.append("chrome"); if (!chromeDir.exists()) { chromeDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); } var logFile = chromeDir.clone(); logFile.append("mobile-config-firefox.log"); var mode = FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_APPEND; var logFileStream = FileUtils.openFileOutputStream(logFile, mode); function log(line) { var date = new Date().toISOString().replace("T", " ").slice(0, 19); line = "[" + date + "] " + line + "\n"; logFileStream.write(line, line.length); } // Debug function for logging object attributes function log_obj(obj) { var prop; var value; for (var prop in obj) { try { value = obj[prop]; } catch(e) { value = e; } log(" - " + prop + ": " + value); } } function get_firefox_version() { try { return Services.appinfo.lastAppVersion; } catch(e) { log("Failed to get FF version: " + e); return 0; } } function trigger_firefox_restart() { log("Triggering Firefox restart"); var appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup); appStartup.quit(Ci.nsIAppStartup.eForceQuit | Ci.nsIAppStartup.eRestart); } function set_default_prefs() { log("Setting default preferences"); // Select a mobile user agent for firefox (same as tor browser on android) defaultPref('general.useragent.override', 'Mozilla/5.0 (Android 10; Mobile; rv:110.0) Gecko/110.0 Firefox/110.0'); // Do not suggest facebook, ebay, reddit etc. in the urlbar. Same as // Settings -> Privacy & Security -> Address Bar -> Shortcuts. As // side-effect, the urlbar results are not immediatelly opened once // clicking the urlbar. defaultPref('browser.urlbar.suggest.topsites', false); // Do not suggest search engines. Even though amazon is removed via // policies.json, it gets installed shortly after the browser is opened. // With this option, at least there is no big "Search with Amazon" message // in the urlbar results as soon as typing the letter "a". defaultPref('browser.urlbar.suggest.engines', false); // Show about:home in new tabs, so it's not just a weird looking completely // empty page. defaultPref('browser.newtabpage.enabled', true); } // Get an array of paths to the fragments for one CSS file // name: either "userChrome" or "userContent" function css_file_get_fragments(name) { if (name in fragments_cache) return fragments_cache[name]; var ret = []; var path = "/etc/mobile-config-firefox/" + name + ".files"; log("Reading fragments from file: " + path); var file = new FileUtils.File(path); var istream = Cc["@mozilla.org/network/file-input-stream;1"]. createInstance(Components.interfaces.nsIFileInputStream); istream.init(file, 0x01, 0444, 0); istream.QueryInterface(Components.interfaces.nsILineInputStream); var has_more; do { var line = {}; has_more = istream.readLine(line); ret.push("/etc/mobile-config-firefox/" + line.value); } while (has_more); istream.close(); fragments_cache[name] = ret; return ret; } // Create a nsIFile object with one of the CSS files in the user's profile as // path. The file doesn't need to exist at this point. // name: either "userChrome" or "userContent" function css_file_get(name) { var ret = chromeDir.clone(); ret.append(name + ".css"); return ret; } // Delete either userChrome.css or userContent.css inside the user's profile if // they have an older timestamp than the CSS fragments (or list of CSS // fragments) installed system-wide. // name: either "userChrome" or "userContent" // file: return of css_file_get() function css_file_delete_outdated(name, file) { var depends = css_file_get_fragments(name).slice(); /* copy the array */ depends.push("/etc/mobile-config-firefox/" + name + ".files"); for (var i in depends) { var depend = depends[i]; var file_depend = new FileUtils.File(depend); if (file.lastModifiedTime < file_depend.lastModifiedTime) { log("Removing outdated file: " + file.path + " (newer: " + depend + ")"); file.remove(false); return; } } log("File is up-to-date: " + file.path); return; } // Create userChrome.css / userContent.css in the user's profile, based on the // CSS fragments stored in /etc/mobile-config-firefox. // name: either "userChrome" or "userContent" // file: return of css_file_get() function css_file_merge(name, file) { log("Creating CSS file from fragments: " + file.path); var ostream = Cc["@mozilla.org/network/file-output-stream;1"]. createInstance(Components.interfaces.nsIFileOutputStream); ostream.init(file, 0x02 | 0x08 | 0x20, 0644, 0); var fragments = css_file_get_fragments(name); for (var i in fragments) { var line; var fragment = fragments[i]; log("- " + fragment); line = "/* === " + fragment + " === */\n" ostream.write(line, line.length); var file_fragment = new FileUtils.File(fragment); var istream = Cc["@mozilla.org/network/file-input-stream;1"]. createInstance(Components.interfaces.nsIFileInputStream); istream.init(file_fragment, 0x01, 0444, 0); istream.QueryInterface(Components.interfaces.nsILineInputStream); var has_more; do { var line = {}; has_more = istream.readLine(line); line = line.value + "\n"; ostream.write(line, line.length); } while (has_more); istream.close(); } ostream.close(); updated = true; } function css_files_update() { var ff_version = get_firefox_version(); log("Firefox version: " + ff_version); var names = ["userChrome", "userContent"]; for (var i in names) { var name = names[i]; var file = css_file_get(name); if (file.exists()) { css_file_delete_outdated(name, file); } if (!file.exists()) { css_file_merge(name, file); } } } log("Running mobile-config-autoconfig.js"); css_files_update(); // Restart Firefox immediately if one of the files got updated if (updated == true) trigger_firefox_restart(); else set_default_prefs(); log("Done"); logFileStream.close();