diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryClientID_reset.js b/toolkit/components/telemetry/tests/unit/test_TelemetryClientID_reset.js index 8263f59f8c37..fd105eeda78d 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryClientID_reset.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryClientID_reset.js @@ -15,10 +15,10 @@ const PING_FORMAT_VERSION = 4; const OPTOUT_PING_TYPE = "optout"; const TEST_PING_TYPE = "test-ping-type"; -function sendPing() { +function sendPing(addEnvironment = false) { let options = { addClientId: true, - addEnvironment: false, + addEnvironment, }; return TelemetryController.submitExternalPing(TEST_PING_TYPE, {}, options); } @@ -211,6 +211,7 @@ add_task(async function test_clientid_canary_nonunified_no_pref_trigger() { // Restart the instance await TelemetryController.testShutdown(); + await TelemetryStorage.testClearPendingPings(); await TelemetryController.testReset(); let newClientId = await ClientID.getClientID(); @@ -221,6 +222,7 @@ add_task(async function test_clientid_canary_nonunified_no_pref_trigger() { // Restart the instance await TelemetryController.testShutdown(); + await TelemetryStorage.testClearPendingPings(); await TelemetryController.testReset(); newClientId = await ClientID.getClientID(); diff --git a/toolkit/modules/ClientID.jsm b/toolkit/modules/ClientID.jsm index e81abb581f52..18eb0a3b221e 100644 --- a/toolkit/modules/ClientID.jsm +++ b/toolkit/modules/ClientID.jsm @@ -9,9 +9,12 @@ var EXPORTED_SYMBOLS = ["ClientID"]; ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.import("resource://gre/modules/Log.jsm"); +ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); const LOGGER_NAME = "Toolkit.Telemetry"; const LOGGER_PREFIX = "ClientID::"; +// Must match ID in TelemetryUtils +const CANARY_CLIENT_ID = "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0"; ChromeUtils.defineModuleGetter(this, "CommonUtils", "resource://services-common/utils.js"); @@ -56,6 +59,18 @@ var ClientID = Object.freeze({ return ClientIDImpl.getClientID(); }, + /** + * This returns true if the client ID prior to the last client ID reset was a canary client ID. + * Android only. Always returns null on Desktop. + */ + wasCanaryClientID() { + if (AppConstants.platform == "android") { + return ClientIDImpl.wasCanaryClientID(); + } + + return null; + }, + /** * Get the client id synchronously without hitting the disk. * This returns: @@ -107,6 +122,7 @@ var ClientIDImpl = { _saveClientIdTask: null, _removeClientIdTask: null, _logger: null, + _wasCanary: null, _loadClientID() { if (this._loadClientIdTask) { @@ -130,6 +146,9 @@ var ClientIDImpl = { // Try to load the client id from the DRS state file. try { let state = await CommonUtils.readJSON(gStateFilePath); + if (AppConstants.platform == "android" && state && "wasCanary" in state) { + this._wasCanary = state.wasCanary; + } if (state && this.updateClientID(state.clientID)) { return this._clientID; } @@ -157,6 +176,10 @@ var ClientIDImpl = { */ async _saveClientID() { let obj = { clientID: this._clientID }; + // We detected a canary client ID when resetting, storing this as a flag + if (AppConstants.platform == "android" && this._wasCanary) { + obj.wasCanary = true; + } await OS.File.makeDir(gDatareportingPath); await CommonUtils.writeJSON(obj, gStateFilePath); this._saveClientIdTask = null; @@ -176,6 +199,14 @@ var ClientIDImpl = { return Promise.resolve(this._clientID); }, + /** + * This returns true if the client ID prior to the last client ID reset was a canary client ID. + * Android only. Always returns null on Desktop. + */ + wasCanaryClientID() { + return this._wasCanary; + }, + /** * Get the client id synchronously without hitting the disk. * This returns: @@ -243,6 +274,8 @@ var ClientIDImpl = { }, async resetClientID() { + let oldClientId = this._clientID; + // Wait for the removal. // Asynchronous calls to getClientID will also be blocked on this. this._removeClientIdTask = this._doRemoveClientID(); @@ -251,6 +284,11 @@ var ClientIDImpl = { await this._removeClientIdTask; + // On Android we detect resets after a canary client ID. + if (AppConstants.platform == "android" ) { + this._wasCanary = oldClientId == CANARY_CLIENT_ID; + } + // Generate a new id. return this.getClientID(); }, diff --git a/toolkit/modules/tests/xpcshell/test_client_id.js b/toolkit/modules/tests/xpcshell/test_client_id.js index af3fbc7a3e98..f7de7a0e8666 100644 --- a/toolkit/modules/tests/xpcshell/test_client_id.js +++ b/toolkit/modules/tests/xpcshell/test_client_id.js @@ -7,6 +7,7 @@ ChromeUtils.import("resource://gre/modules/ClientID.jsm"); ChromeUtils.import("resource://services-common/utils.js"); ChromeUtils.import("resource://gre/modules/osfile.jsm"); ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); +ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); const PREF_CACHED_CLIENTID = "toolkit.telemetry.cachedClientID"; @@ -130,3 +131,27 @@ add_task(async function test_resetParallelGet() { Assert.notEqual(firstClientID, newClientID, "After reset client ID should be different."); Assert.equal(newClientID, otherClientID, "Getting the client ID in parallel to a reset should give the same id."); }); + +add_task({ + skip_if: () => AppConstants.platform != "android", +}, async function test_FennecCanaryDetect() { + const KNOWN_UUID = "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0"; + + // We should get a valid UUID after reset + let firstClientID = await ClientID.resetClientID(); + Assert.notEqual(KNOWN_UUID, firstClientID, "Client ID should be random."); + + // Set the canary client ID. + await ClientID.setClientID(KNOWN_UUID); + Assert.equal(KNOWN_UUID, await ClientID.getClientID(), "Client ID should be known canary."); + + let newClientID = await ClientID.resetClientID(); + Assert.notEqual(KNOWN_UUID, newClientID, "After reset Client ID should be random."); + Assert.notEqual(firstClientID, newClientID, "After reset Client ID should be new."); + Assert.ok(ClientID.wasCanaryClientID(), "After reset we should have detected a canary client ID"); + + let clientID = await ClientID.resetClientID(); + Assert.notEqual(KNOWN_UUID, clientID, "After reset Client ID should be random."); + Assert.notEqual(newClientID, clientID, "After reset Client ID should be new."); + Assert.ok(!ClientID.wasCanaryClientID(), "After reset we should not have detected a canary client ID"); +}); diff --git a/toolkit/modules/tests/xpcshell/xpcshell.ini b/toolkit/modules/tests/xpcshell/xpcshell.ini index 3741384d9028..d03667d9f102 100644 --- a/toolkit/modules/tests/xpcshell/xpcshell.ini +++ b/toolkit/modules/tests/xpcshell/xpcshell.ini @@ -12,7 +12,6 @@ support-files = skip-if = toolkit == 'android' [test_CanonicalJSON.js] [test_client_id.js] -skip-if = toolkit == 'android' [test_Color.js] [test_CreditCard.js] [test_DeferredTask.js]