Bug 1336208 - Part 5: Implementing the font downloading for fingerprinting resistance. r?jfkthame,arthuredelstein,gijs
This patch enables Firefox to download fonts according to the fonts list, which is
downloaded through Kinto Server, when fingerprinting resistance is enabled. This
patch adds FontsDownloader.js. which is responsible for downloading fonts from the
server. The nsRFPService will issue FontsDownloader to try to download fonts when
'privacy.resistFingerprinting' is flipped to true. The FontsDownloader will check
whether local fonts are up-to-date and download fonts if they are outdated. The
downloaded fonts will be first put in a staging directory and be moved to the
actual font directory at the next start-up of Firefox. The downloaded fonts will
be moved into the GRE directory if it is writable, otherwise, they will be moved
to the font directory in the profile directory. From then on, we will maintain
fonts on that.
In addition, this adds a new IPC method in PContent for allowing content process
to issue parent process to download fonts. This is necessary since the sync of
Kinto data could happen out of parent process.
MozReview-Commit-ID: KrMGdiq8UdF
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -64,16 +64,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
ShellService: "resource:///modules/ShellService.jsm",
SimpleServiceDiscovery: "resource://gre/modules/SimpleServiceDiscovery.jsm",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
UIState: "resource://services-sync/UIState.jsm",
UITour: "resource:///modules/UITour.jsm",
WebChannel: "resource://gre/modules/WebChannel.jsm",
WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm",
+ FontsDownloader: "resource:///modules/FontsDownloader.jsm",
});
/* global AboutHome:false, ContentPrefServiceParent:false, ContentSearch:false,
UpdateListener:false, webrtcUI:false */
/**
* IF YOU ADD OR REMOVE FROM THIS LIST, PLEASE UPDATE THE LIST ABOVE AS WELL.
* XXX Bug 1325373 is for making eslint detect these automatically.
@@ -1009,16 +1010,17 @@ BrowserGlue.prototype = {
if (Object.prototype.hasOwnProperty.call(this, "pingCentre")) {
this.pingCentre.uninit();
}
PageThumbs.uninit();
NewTabUtils.uninit();
AutoCompletePopup.uninit();
DateTimePickerHelper.uninit();
+ FontsDownloader.uninit();
},
// All initial windows have opened.
_onWindowsRestored: function BG__onWindowsRestored() {
if (this._windowsWereRestored) {
return;
}
this._windowsWereRestored = true;
@@ -1161,16 +1163,20 @@ BrowserGlue.prototype = {
handlerService.asyncInit();
});
if (AppConstants.platform == "win") {
Services.tm.idleDispatchToMainThread(() => {
JawsScreenReaderVersionCheck.onWindowsRestored();
});
}
+
+ Services.tm.idleDispatchToMainThread(() => {
+ FontsDownloader.init();
+ });
},
/**
* Use this function as an entry point to schedule tasks that need
* to run once per session, at any arbitrary point in time.
* This function will be called from an idle observer. Check the value of
* LATE_TASKS_IDLE_TIME_SEC to see the current value for this idle
* observer.
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5432,16 +5432,23 @@ mozilla::ipc::IPCResult
ContentParent::RecvMaybeReloadPlugins()
{
RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
pluginHost->ReloadPlugins();
return IPC_OK();
}
mozilla::ipc::IPCResult
+ContentParent::RecvRFPDownloadFonts()
+{
+ nsRFPService::GetOrCreate()->MaybeDownloadFonts();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
ContentParent::RecvDeviceReset()
{
GPUProcessManager* pm = GPUProcessManager::Get();
if (pm) {
pm->SimulateDeviceReset();
}
return IPC_OK();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -298,16 +298,18 @@ public:
virtual mozilla::ipc::IPCResult RecvCreateGMPService() override;
virtual mozilla::ipc::IPCResult RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv,
uint32_t* aRunID,
Endpoint<PPluginModuleParent>* aEndpoint) override;
virtual mozilla::ipc::IPCResult RecvMaybeReloadPlugins() override;
+ virtual mozilla::ipc::IPCResult RecvRFPDownloadFonts() override;
+
virtual mozilla::ipc::IPCResult RecvConnectPluginBridge(const uint32_t& aPluginId,
nsresult* aRv,
Endpoint<PPluginModuleParent>* aEndpoint) override;
virtual mozilla::ipc::IPCResult RecvUngrabPointer(const uint32_t& aTime) override;
virtual mozilla::ipc::IPCResult RecvRemovePermission(const IPC::Principal& aPrincipal,
const nsCString& aPermissionType,
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1105,16 +1105,18 @@ parent:
IHandlerControlHolder aHandlerControl);
async AddMemoryReport(MemoryReport aReport);
async FinishMemoryReport(uint32_t aGeneration);
async MaybeReloadPlugins();
async BHRThreadHang(HangDetails aHangDetails);
+
+ async RFPDownloadFonts();
both:
async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
Principal aPrincipal, ClonedMessageData aData);
/**
* Notify `push-subscription-modified` observers in the parent and child.
*/
async NotifyPushSubscriptionModifiedObservers(nsCString scope,
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2620,16 +2620,17 @@ pref("services.blocklist.pinning.collect
pref("services.blocklist.pinning.checked", 0);
pref("services.blocklist.gfx.collection", "gfx");
pref("services.blocklist.gfx.checked", 0);
// Fonts downloading for fingerprinting resistance via settings server (Kinto)
pref("privacy.resistFingerprinting.fonts.bucket", "fingerprinting-defenses");
pref("privacy.resistFingerprinting.fonts.collection", "fonts");
pref("privacy.resistFingerprinting.fonts.checked", 0);
+pref("privacy.resistFingerprinting.fonts.server", "https://firefox-settings-attachments.cdn.mozilla.net/");
// Controls whether signing should be enforced on signature-capable blocklist
// collections.
pref("services.blocklist.signing.enforced", true);
// Enable blocklists via the services settings mechanism
pref("services.blocklist.update_enabled", true);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/FontsDownloader.jsm
@@ -0,0 +1,236 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["FontsDownloader"];
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+Cu.importGlobalProperties(["crypto"]);
+
+const RFP_FONTS_LIST_DIR = "fingerprinting-defenses";
+const RFP_FONTS_DIR = "rfp-fonts";
+const RFP_FONTS_STAGING_DIR = "rfp-fonts-staging";
+const RFP_FONTS_LIST_FILE = "fonts.json";
+const RFP_PREF = "privacy.resistFingerprinting";
+const RFP_FONTS_DOWNLOAD_SERVER_PREF = "privacy.resistFingerprinting.fonts.server";
+const RFP_FONTS_NEED_MOVE_PREF = "privacy.resistFingerprinting.fonts.need_move";
+const RFP_FONTS_LAST_UPDATE_TIME_PREF = "privacy.resistFingerprinting.fonts.last_update_time";
+
+/**
+ * A helper function for verifying data with the given hash.
+ *
+ * @param {Uint8Array} aData The data to be verified.
+ * @param {String} aHash The hash value
+ * @param {String} aAlgorithm The hash algorithm
+ * @return {boolean} A boolean indicates whether hash matches.
+ */
+async function verifyHash(aData, aHash, aAlgorithm) {
+ const hash = await crypto.subtle.digest(aAlgorithm, aData);
+ const hashArray = Array.from(new Uint8Array(hash));
+ const hashHex = hashArray.map(
+ b => ("00" + b.toString(16)).slice(-2)).join(""); // convert bytes to hex string
+
+ return hashHex === aHash;
+}
+
+let FontsDownloader = {
+ init() {
+ this._shutdown = false;
+ this._initialized = true;
+
+ // We will try to download fonts if fingerprinting resistance is enabled during
+ // initialization.
+ if (Services.prefs.getBoolPref(RFP_PREF)) {
+ this.maybeDownloadFonts();
+ }
+
+ Services.obs.addObserver(this, "resist-fingerprinting:start-download-fonts");
+ },
+
+ uninit() {
+ if (!this._initialized) {
+ return;
+ }
+
+ this._shutdown = true;
+ Services.obs.removeObserver(this, "resist-fingerprinting:start-download-fonts");
+ },
+
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === "resist-fingerprinting:start-download-fonts") {
+ this.maybeDownloadFonts();
+ }
+ },
+
+ async maybeDownloadFonts() {
+ if (this.downloading) {
+ return;
+ }
+
+ this.downloading = true;
+
+ try {
+ let fontsList = await this.loadFontsList();
+
+ await this.downloadFonts(fontsList);
+
+ // We set the pref here for telling nsRFPService to move fonts from staging
+ // directory to the font directory.
+ Services.prefs.setBoolPref(RFP_FONTS_NEED_MOVE_PREF, true);
+
+ // Record the last time of the successful downloading.
+ Services.prefs.setIntPref(RFP_FONTS_LAST_UPDATE_TIME_PREF, (Date.now() / 1000));
+ } catch (aError) {
+ Cu.reportError(aError);
+ }
+
+ this.downloading = false;
+ },
+
+ async loadFontsList() {
+ // We first read fonts list file in profile directory. This file is the
+ // up-to-date list which is updated by the blocklist client.
+ let fontsListPath = OS.Path.join(OS.Constants.Path.profileDir,
+ RFP_FONTS_LIST_DIR,
+ RFP_FONTS_LIST_FILE);
+
+ try {
+ let data = await OS.File.read(fontsListPath, {encoding: "utf-8"});
+ return JSON.parse(data);
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+ throw ex;
+ }
+ }
+
+ // If the fonts list file in the profile folder doesn't exist, this means
+ // the first update has not been triggered by Firefox. So we will use the
+ // default fonts list instead which is shipped with Firefox.
+ let defaultFile = Services.dirsvc.get("DefRt",
+ Ci.nsIFile);
+ fontsListPath = OS.Path.join(defaultFile.path,
+ RFP_FONTS_LIST_DIR,
+ RFP_FONTS_LIST_FILE);
+
+ let data = await OS.File.read(fontsListPath, {encoding: "utf-8"});
+ return JSON.parse(data);
+ },
+
+ async downloadFonts(aFontList) {
+ let fonts = aFontList.data;
+ let lastFontsUpdateTime = Services.prefs.getIntPref(RFP_FONTS_LAST_UPDATE_TIME_PREF, 0) * 1000;
+
+ // If the server url is empty, we will bail out here and throw nothing. So,
+ // we can disable the whole download by clearing this server pref.
+ let serverURL = Services.prefs.getCharPref(RFP_FONTS_DOWNLOAD_SERVER_PREF);
+
+ if (!serverURL) {
+ return;
+ }
+
+ // We only download the font that matches to this platform and skip fonts
+ // that are still up-to-date.
+ let expectedDownloadFonts = fonts.filter(f => f.platforms.includes(AppConstants.platform))
+ .filter(f => f.last_modified >= lastFontsUpdateTime);
+
+ for (let font of expectedDownloadFonts) {
+ if (this._shutdown) {
+ return;
+ }
+
+ let fontFilename = font.attachment.filename;
+ let localFontPath = await this._getLocalFontPath(fontFilename);
+ let localFontData;
+
+ // Check the local font file and verify whether it is up-to-date.
+ try {
+ let localFontStat = await OS.File.stat(localFontPath);
+
+ if (localFontStat.size === font.attachment.size) {
+ // If the size matches, we need to further check the hash to know
+ // whether it is up-to-date.
+ localFontData = await OS.File.read(localFontPath);
+ }
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+ throw ex;
+ }
+ }
+
+ if (localFontData &&
+ await verifyHash(localFontData, font.attachment.hash, "SHA-256")) {
+ // The local font file is up-to-date, we skip to the next download.
+ continue;
+ }
+
+ // Download the font and decompress it.
+ let fontURL = serverURL + font.attachment.location;
+ let response = await fetch(fontURL);
+ let fontData = new Uint8Array(await response.arrayBuffer());
+
+ // If the download size is wrong, we skip to the next one.
+ if (fontData.byteLength !== font.attachment.size) {
+ continue;
+ }
+
+ let stagingFontPath = OS.Path.join(OS.Constants.Path.profileDir,
+ RFP_FONTS_STAGING_DIR,
+ fontFilename);
+
+ await OS.File.makeDir(OS.Path.dirname(stagingFontPath),
+ {from: OS.Constants.Path.profileDir});
+
+ // Write the downloaded font into the staging font directory. The font file will
+ // be moved into the font directory at the next start-up of Firefox.
+ await OS.File.writeAtomic(stagingFontPath, fontData,
+ {tmpPath: stagingFontPath + ".tmp"});
+
+ }
+ },
+
+ /**
+ * Find the path to use for rfp fonts. Registering fonts from 2 directories
+ * is likely to cause issues. While we prefer the GreD directory in the application,
+ * the user may not have write permissions for it. If this is the case, fonts will
+ * be downloaded and/or copied into the profile directory. Thus, when looking for
+ * fonts, we check the profile directory first, because if there are fonts there
+ * they are guaranteed to be at least as new as what is in GreD.
+ *
+ * @param {String} aFontFileName The file name of the font.
+ * @return {String} The path of the local font.
+ */
+ async _getLocalFontPath(aFontFileName) {
+ // First, we try to get the path of font directory.
+ if (!this._getLocalFontDirPath) {
+ // We use the font file in profile directory if it exists. Otherwise, we use
+ // the one in GRE directory.
+ let localFontPath = OS.Path.join(OS.Constants.Path.localProfileDir,
+ RFP_FONTS_DIR);
+ let isExist = await OS.File.exists(localFontPath);
+
+ if (!isExist) {
+ let greDir = Services.dirsvc.get("GreD",
+ Ci.nsIFile);
+ localFontPath = OS.Path.join(greDir.path,
+ RFP_FONTS_DIR);
+ }
+
+ this._getLocalFontDirPath = localFontPath;
+ }
+
+ return OS.Path.join(this._getLocalFontDirPath, aFontFileName);
+ }
+};
--- a/toolkit/components/resistfingerprinting/moz.build
+++ b/toolkit/components/resistfingerprinting/moz.build
@@ -3,16 +3,22 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
UNIFIED_SOURCES += [
'nsRFPService.cpp',
]
+EXTRA_JS_MODULES += [
+ 'FontsDownloader.jsm',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
FINAL_LIBRARY = 'xul'
FINAL_TARGET_FILES.defaults['fingerprinting-defenses'] += ['fonts.json']
if CONFIG['MOZ_BUILD_APP'] == 'browser':
DIST_SUBDIR = 'browser'
EXPORTS += [
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -8,45 +8,65 @@
#include <algorithm>
#include <time.h>
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+
+#include "gfxPlatform.h"
+
+#include "nsAppDirectoryServiceDefs.h"
#include "nsCOMPtr.h"
#include "nsCoord.h"
+#include "nsDirectoryServiceDefs.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsXULAppAPI.h"
#include "nsPrintfCString.h"
+#include "nsIFontEnumerator.h"
#include "nsIObserverService.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
+#include "nsISimpleEnumerator.h"
#include "nsIXULAppInfo.h"
#include "nsIXULRuntime.h"
#include "nsJSUtils.h"
#include "prenv.h"
#include "js/Date.h"
using namespace mozilla;
using namespace std;
#define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting"
#define RFP_SPOOFED_FRAMES_PER_SEC_PREF "privacy.resistFingerprinting.video_frames_per_sec"
#define RFP_SPOOFED_DROPPED_RATIO_PREF "privacy.resistFingerprinting.video_dropped_ratio"
#define RFP_TARGET_VIDEO_RES_PREF "privacy.resistFingerprinting.target_video_res"
+#define RFP_FONTS_DIR "rfp-fonts"
+#define RFP_FONTS_STAGING_DIR "rfp-fonts-staging"
+#define RFP_FONTS_NEED_MOVE_PREF "privacy.resistFingerprinting.fonts.need_move"
#define RFP_SPOOFED_FRAMES_PER_SEC_DEFAULT 30
#define RFP_SPOOFED_DROPPED_RATIO_DEFAULT 5
#define RFP_TARGET_VIDEO_RES_DEFAULT 480
#define PROFILE_INITIALIZED_TOPIC "profile-initial-state"
+#define PROFILE_DO_CHANGE_TOPIC "profile-do-change"
+
+#define RFP_DOWNLOAD_FONTS_MSG "resist-fingerprinting:download-fonts"
+#define RFP_DOWNLOAD_FONTS_START_MSG "resist-fingerprinting:start-download-fonts"
+// A message for telling nsRFPService to move fonts, this is for testing purpose.
+#define RFP_MOVE_FONTS_MSG "resist-fingerprinting:move-fonts"
+// A message for notifying the moving of fonts is finished, this is for testing purpose.
+#define RFP_MOVE_FONTS_FINISH "resist-fingerprinting:move-fonts-finish"
NS_IMPL_ISUPPORTS(nsRFPService, nsIObserver)
static StaticRefPtr<nsRFPService> sRFPService;
static bool sInitialized = false;
Atomic<bool, ReleaseAcquire> nsRFPService::sPrivacyResistFingerprinting;
static uint32_t kResolutionUSec = 100000;
static uint32_t sVideoFramesPerSec;
@@ -231,16 +251,25 @@ nsRFPService::Init()
rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
NS_ENSURE_SUCCESS(rv, rv);
#if defined(XP_WIN)
rv = obs->AddObserver(this, PROFILE_INITIALIZED_TOPIC, false);
NS_ENSURE_SUCCESS(rv, rv);
#endif
+ rv = obs->AddObserver(this, PROFILE_DO_CHANGE_TOPIC, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = obs->AddObserver(this, RFP_DOWNLOAD_FONTS_MSG, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = obs->AddObserver(this, RFP_MOVE_FONTS_MSG, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
NS_ENSURE_TRUE(prefs, NS_ERROR_NOT_AVAILABLE);
rv = prefs->AddObserver(RESIST_FINGERPRINTING_PREF, this, false);
NS_ENSURE_SUCCESS(rv, rv);
Preferences::AddUintVarCache(&sVideoFramesPerSec,
RFP_SPOOFED_FRAMES_PER_SEC_PREF,
@@ -269,18 +298,20 @@ void
nsRFPService::UpdatePref()
{
MOZ_ASSERT(NS_IsMainThread());
sPrivacyResistFingerprinting = Preferences::GetBool(RESIST_FINGERPRINTING_PREF);
if (sPrivacyResistFingerprinting) {
PR_SetEnv("TZ=UTC");
JS::SetTimeResolutionUsec(kResolutionUSec);
+ MaybeDownloadFonts();
} else if (sInitialized) {
JS::SetTimeResolutionUsec(0);
+
// We will not touch the TZ value if 'privacy.resistFingerprinting' is false during
// the time of initialization.
if (!mInitialTZValue.IsEmpty()) {
nsAutoCString tzValue = NS_LITERAL_CSTRING("TZ=") + mInitialTZValue;
static char* tz = nullptr;
// If the tz has been set before, we free it first since it will be allocated
// a new value later.
@@ -313,25 +344,143 @@ void
nsRFPService::StartShutdown()
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ obs->RemoveObserver(this, PROFILE_DO_CHANGE_TOPIC);
+ obs->RemoveObserver(this, RFP_DOWNLOAD_FONTS_MSG);
+ obs->RemoveObserver(this, RFP_MOVE_FONTS_MSG);
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
prefs->RemoveObserver(RESIST_FINGERPRINTING_PREF, this);
}
}
}
+void
+nsRFPService::MaybeDownloadFonts()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+
+ // Notify the observer for telling FontsDownloader to download fonts.
+ if (obs) {
+ obs->NotifyObservers(nullptr, RFP_DOWNLOAD_FONTS_START_MSG, nullptr);
+ }
+}
+
+void
+nsRFPService::MaybeMoveFontFiles()
+{
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ // First, we check the pref for whether we need to move staging fonts.
+ if (!Preferences::GetBool(RFP_FONTS_NEED_MOVE_PREF)) {
+ return;
+ }
+
+ nsCOMPtr<nsIFile> stagingDir;
+ nsresult rv = NS_GetSpecialDirectory(
+ NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(stagingDir));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = stagingDir->Append(NS_LITERAL_STRING(RFP_FONTS_STAGING_DIR));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // We will try to use GRE directory first. If we cannot write into this directory
+ // we will fallback to use profile directory instead.
+ nsCOMPtr<nsIFile> destFontDir;
+ bool usingProfileDir = false;
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(destFontDir));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = destFontDir->Append(NS_LITERAL_STRING(RFP_FONTS_DIR));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ rv = stagingDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ bool hasMore;
+
+ while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> next;
+ rv = entries->GetNext(getter_AddRefs(next));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ nsCOMPtr<nsIFile> fontFile = do_QueryInterface(next);
+ NS_ENSURE_TRUE_VOID(fontFile);
+
+ rv = fontFile->MoveToNative(destFontDir, EmptyCString());
+ if (!usingProfileDir &&
+ (rv == NS_ERROR_FILE_ACCESS_DENIED || rv == NS_ERROR_FILE_READ_ONLY)) {
+ // We move to use profile directory if we cannot write into GRE directory.
+ rv = NS_GetSpecialDirectory(
+ NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(destFontDir));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = destFontDir->Append(NS_LITERAL_STRING(RFP_FONTS_DIR));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ usingProfileDir = true;
+ bool isExists = false;
+
+ rv = destFontDir->Exists(&isExists);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // If the font directory doesn't exist in the profile directory and this
+ // profile has no right to access GreD, this means it's the first time
+ // this profile tries to move fonts from staging direcotry. In this case,
+ // we need to first copy all fonts into the profile directory from the
+ // GreD directory.
+ if (!isExists) {
+ nsCOMPtr<nsIFile> greDFontDir;
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(destFontDir));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = greDFontDir->Append(NS_LITERAL_STRING(RFP_FONTS_DIR));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIFile> profDir;
+ rv = NS_GetSpecialDirectory(
+ NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profDir));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = greDFontDir->CopyToNative(profDir, EmptyCString());
+ if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+
+ // And move again.
+ rv = fontFile->MoveToNative(destFontDir, EmptyCString());
+ NS_ENSURE_SUCCESS_VOID(rv);
+ } else {
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+
+ Preferences::ClearUser(RFP_FONTS_NEED_MOVE_PREF);
+
+ // After moving all fonts, we remove the staging directory.
+ rv = stagingDir->Remove(true);
+ NS_ENSURE_SUCCESS_VOID(rv);
+}
+
NS_IMETHODIMP
nsRFPService::Observe(nsISupports* aObject, const char* aTopic,
const char16_t* aMessage)
{
if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) {
NS_ConvertUTF16toUTF8 pref(aMessage);
if (pref.EqualsLiteral(RESIST_FINGERPRINTING_PREF)) {
@@ -343,16 +492,50 @@ nsRFPService::Observe(nsISupports* aObje
// are not reflected immediately on that platform as they are on UNIX
// systems without this call.
_tzset();
}
#endif
}
}
+ // We will catch this when the fonts list get updated. We only download fonts
+ // when fingerprinting resistance is enabled.
+ if (!strcmp(RFP_DOWNLOAD_FONTS_MSG, aTopic)) {
+ if (sPrivacyResistFingerprinting) {
+ if (XRE_IsParentProcess()) {
+ MaybeDownloadFonts();
+ } else {
+ dom::ContentChild* cc = dom::ContentChild::GetSingleton();
+
+ if (cc) {
+ Unused << cc->SendRFPDownloadFonts();
+ }
+ }
+ }
+ }
+
+ // We will start to move fonts if we receive this. This is for testing purpose.
+ if (!strcmp(RFP_MOVE_FONTS_MSG, aTopic)) {
+ MaybeMoveFontFiles();
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+
+ // Notify the observer for telling moving fonts finishes.
+ if (obs) {
+ obs->NotifyObservers(nullptr, RFP_MOVE_FONTS_FINISH, nullptr);
+ }
+ }
+
+ if (!strcmp(PROFILE_DO_CHANGE_TOPIC, aTopic)) {
+ // We move staging font files into the font directory as soon as the profile
+ // is available. This stage is before the initialization of gfxPlatfromFontList.
+ MaybeMoveFontFiles();
+ }
+
if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
StartShutdown();
}
#if defined(XP_WIN)
else if (!strcmp(PROFILE_INITIALIZED_TOPIC, aTopic)) {
// If we're e10s, then we don't need to run this, since the child process will
// simply inherit the environment variable from the parent process, in which
// case it's unnecessary to call _tzset().
--- a/toolkit/components/resistfingerprinting/nsRFPService.h
+++ b/toolkit/components/resistfingerprinting/nsRFPService.h
@@ -47,25 +47,29 @@ public:
// depend on the video resolution.
static uint32_t GetSpoofedTotalFrames(double aTime);
static uint32_t GetSpoofedDroppedFrames(double aTime, uint32_t aWidth, uint32_t aHeight);
static uint32_t GetSpoofedPresentedFrames(double aTime, uint32_t aWidth, uint32_t aHeight);
// This method generates the spoofed value of User Agent.
static nsresult GetSpoofedUserAgent(nsACString &userAgent);
+ // This method triggers the font downloading process.
+ void MaybeDownloadFonts();
+
private:
nsresult Init();
nsRFPService() {}
~nsRFPService() {}
void UpdatePref();
void StartShutdown();
+ void MaybeMoveFontFiles();
static Atomic<bool, ReleaseAcquire> sPrivacyResistFingerprinting;
nsCString mInitialTZValue;
};
} // mozilla namespace