diff --git a/toolkit/mozapps/defaultagent/Telemetry.cpp b/toolkit/mozapps/defaultagent/Telemetry.cpp index 9ee144e6eca6..087344f9cf0a 100644 --- a/toolkit/mozapps/defaultagent/Telemetry.cpp +++ b/toolkit/mozapps/defaultagent/Telemetry.cpp @@ -13,12 +13,14 @@ #include "common.h" #include "EventLog.h" +#include "Policy.h" #include "json/json.h" #include "mozilla/ArrayUtils.h" #include "mozilla/CmdLineAndEnvUtils.h" #include "mozilla/HelperMacros.h" #include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" #include "mozilla/WinHeaderOnlyUtils.h" #define TELEMETRY_BASE_URL "https://incoming.telemetry.mozilla.org/submit" @@ -36,6 +38,13 @@ // supposed to run every 24 hours. #define MINIMUM_PING_PERIOD_SEC ((23 * 60 * 60) + (45 * 60)) +// The cache only stores data when a notification is shown, so one cache slot +// per possible notification is the maximum we should ever need. +#define MAX_NOTIFICATION_DATA_CACHE_SIZE 2 +#define NOTIFICATION_TYPE_CACHE_PREFIX L"PingCacheNotificationType" +#define NOTIFICATION_SHOWN_CACHE_PREFIX L"PingCacheNotificationShown" +#define NOTIFICATION_ACTION_CACHE_PREFIX L"PingCacheNotificationAction" + #if !defined(RRF_SUBKEY_WOW6464KEY) # define RRF_SUBKEY_WOW6464KEY 0x00010000 #endif // !defined(RRF_SUBKEY_WOW6464KEY) @@ -345,6 +354,249 @@ static TelemetryFieldResult GetAndUpdatePreviousDefaultBrowser( return oldCurrentDefault; } +// If notifications actions occurred, we want to make sure a ping gets sent for +// them. If we aren't sending a ping right now, we want to cache the ping values +// for the next time the ping is sent. +// The values passed will only be cached if actions were actually taken +// (i.e. not when notificationShown == "not-shown") +HRESULT MaybeCache(const std::string& notificationType, + const std::string& notificationShown, + const std::string& notificationAction) { + std::string notShown = + GetStringForNotificationShown(NotificationShown::NotShown); + if (notificationShown == notShown) { + return S_OK; + } + + // Find the first free cache index so we can write to the end of the cache. + // (The cache is a FIFO queue) + unsigned int cacheIndex = 0; + while (true) { + std::wstring valueName = NOTIFICATION_TYPE_CACHE_PREFIX; + valueName += std::to_wstring(cacheIndex); + LSTATUS ls = + RegGetValueW(HKEY_CURRENT_USER, AGENT_REGKEY_NAME, valueName.c_str(), + RRF_RT_REG_SZ, nullptr, nullptr, nullptr); + if (ls == ERROR_FILE_NOT_FOUND) { + break; + } + if (ls != ERROR_SUCCESS) { + LOG_ERROR_MESSAGE(L"Error probing cache: %#X", ls); + return HRESULT_FROM_WIN32(ls); + } + + ++cacheIndex; + if (cacheIndex >= MAX_NOTIFICATION_DATA_CACHE_SIZE) { + LOG_ERROR_MESSAGE(L"Cache full. Cannot store another value."); + return E_FAIL; + } + } + + // Generate the value names for that index + std::wstring typeValueName = NOTIFICATION_TYPE_CACHE_PREFIX; + typeValueName += std::to_wstring(cacheIndex); + std::wstring shownValueName = NOTIFICATION_SHOWN_CACHE_PREFIX; + shownValueName += std::to_wstring(cacheIndex); + std::wstring actionValueName = NOTIFICATION_ACTION_CACHE_PREFIX; + actionValueName += std::to_wstring(cacheIndex); + + // Store the data at that index. + mozilla::WindowsErrorResult result = RegistrySetValueString( + IsPrefixed::Unprefixed, typeValueName.c_str(), notificationType.c_str()); + if (result.isErr()) { + HRESULT hr = result.unwrapErr().AsHResult(); + LOG_ERROR_MESSAGE(L"Failed to write to cache: %#X", hr); + return hr; + } + result = + RegistrySetValueString(IsPrefixed::Unprefixed, shownValueName.c_str(), + notificationShown.c_str()); + if (result.isErr()) { + HRESULT hr = result.unwrapErr().AsHResult(); + LOG_ERROR_MESSAGE(L"Failed to write to cache: %#X", hr); + return hr; + } + result = + RegistrySetValueString(IsPrefixed::Unprefixed, actionValueName.c_str(), + notificationAction.c_str()); + if (result.isErr()) { + HRESULT hr = result.unwrapErr().AsHResult(); + LOG_ERROR_MESSAGE(L"Failed to write to cache: %#X", hr); + return hr; + } + return S_OK; +} + +// Pops the first item off the cache and shifts the remaining ones down by one +// to fill the space left behind. +// If there is an error reading the cached values, they will be discarded and +// the next cache will be read. +// If there are no entries in the cache, the first outparam will be set to True +// to indicate that no values were read. +HRESULT PopCache(bool& cacheEmpty, std::string& notificationType, + std::string& notificationShown, + std::string& notificationAction) { + // This function body will be in a loop so that we read more than once on + // cache read problems. But we are putting a limit on the number of possible + // iterations to prevent us from ever getting stuck in this loop. Under + // expected operation, we should never finish this loop without returning. + for (unsigned int i = 0; i < MAX_NOTIFICATION_DATA_CACHE_SIZE; ++i) { + // Cache is a FIFO queue. We always read from the beginning + unsigned int readIndex = 0; + + // Generate the value names for that index + std::wstring typeValueName = NOTIFICATION_TYPE_CACHE_PREFIX; + typeValueName += std::to_wstring(readIndex); + std::wstring shownValueName = NOTIFICATION_SHOWN_CACHE_PREFIX; + shownValueName += std::to_wstring(readIndex); + std::wstring actionValueName = NOTIFICATION_ACTION_CACHE_PREFIX; + actionValueName += std::to_wstring(readIndex); + + // Read from the cache + MaybeStringResult typeResult = + RegistryGetValueString(IsPrefixed::Unprefixed, typeValueName.c_str()); + MaybeStringResult shownResult = + RegistryGetValueString(IsPrefixed::Unprefixed, shownValueName.c_str()); + MaybeStringResult actionResult = + RegistryGetValueString(IsPrefixed::Unprefixed, actionValueName.c_str()); + + bool cacheReadSuccess = false; + if (typeResult.isOk() && shownResult.isOk() && actionResult.isOk()) { + mozilla::Maybe maybeType = typeResult.unwrap(); + mozilla::Maybe maybeShown = shownResult.unwrap(); + mozilla::Maybe maybeAction = actionResult.unwrap(); + if (maybeType.isNothing() && maybeShown.isNothing() && + maybeAction.isNothing()) { + // This is the most common case - nothing is in the cache. Return early. + cacheEmpty = true; + return S_OK; + } + cacheReadSuccess = + maybeType.isSome() && maybeShown.isSome() && maybeAction.isSome(); + if (cacheReadSuccess) { + notificationType = maybeType.value(); + notificationShown = maybeShown.value(); + notificationAction = maybeAction.value(); + } else { + LOG_ERROR_MESSAGE( + L"Some notification data cache data is missing. " + L"Cache entry dropped"); + } + } else { + LOG_ERROR_MESSAGE( + L"Error reading cache data. Entry dropped: %#X, %#X, %#X", + typeResult.unwrapErr().AsHResult(), + shownResult.unwrapErr().AsHResult(), + actionResult.unwrapErr().AsHResult()); + } + + // Shift the cache entries + for (unsigned int shiftTo = 0; + shiftTo + 1 < MAX_NOTIFICATION_DATA_CACHE_SIZE; ++shiftTo) { + const unsigned int shiftFrom = shiftTo + 1; + + // Generate the value names for those indicies + std::wstring shiftToTypeName = NOTIFICATION_TYPE_CACHE_PREFIX; + shiftToTypeName += std::to_wstring(shiftTo); + std::wstring shiftToShownName = NOTIFICATION_SHOWN_CACHE_PREFIX; + shiftToShownName += std::to_wstring(shiftTo); + std::wstring shiftToActionName = NOTIFICATION_ACTION_CACHE_PREFIX; + shiftToActionName += std::to_wstring(shiftTo); + + std::wstring shiftFromTypeName = NOTIFICATION_TYPE_CACHE_PREFIX; + shiftFromTypeName += std::to_wstring(shiftFrom); + std::wstring shiftFromShownName = NOTIFICATION_SHOWN_CACHE_PREFIX; + shiftFromShownName += std::to_wstring(shiftFrom); + std::wstring shiftFromActionName = NOTIFICATION_ACTION_CACHE_PREFIX; + shiftFromActionName += std::to_wstring(shiftFrom); + + // Shift stored values down by an index. If there is nothing in the value + // we are shifting, delete the values we would have overwritten, since + // there is nothing to overwrite with. + MaybeStringResult result = RegistryGetValueString( + IsPrefixed::Unprefixed, shiftFromTypeName.c_str()); + if (result.isOk()) { + mozilla::Maybe maybeValue = result.unwrap(); + if (maybeValue.isSome()) { + std::string value = maybeValue.value(); + mozilla::Unused << RegistrySetValueString( + IsPrefixed::Unprefixed, shiftToTypeName.c_str(), value.c_str()); + } else { + mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed, + shiftToTypeName.c_str()); + } + } + result = RegistryGetValueString(IsPrefixed::Unprefixed, + shiftFromShownName.c_str()); + if (result.isOk()) { + mozilla::Maybe maybeValue = result.unwrap(); + if (maybeValue.isSome()) { + std::string value = maybeValue.value(); + mozilla::Unused << RegistrySetValueString( + IsPrefixed::Unprefixed, shiftToShownName.c_str(), value.c_str()); + } else { + mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed, + shiftToShownName.c_str()); + } + } + result = RegistryGetValueString(IsPrefixed::Unprefixed, + shiftFromActionName.c_str()); + if (result.isOk()) { + mozilla::Maybe maybeValue = result.unwrap(); + if (maybeValue.isSome()) { + std::string value = maybeValue.value(); + mozilla::Unused << RegistrySetValueString( + IsPrefixed::Unprefixed, shiftToActionName.c_str(), value.c_str()); + } else { + mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed, + shiftToActionName.c_str()); + } + } + + // Delete the values we just shifted. + mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed, + shiftFromTypeName.c_str()); + mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed, + shiftFromShownName.c_str()); + mozilla::Unused << RegistryDeleteValue(IsPrefixed::Unprefixed, + shiftFromActionName.c_str()); + } + + // If we got good data, return it. Otherwise, repeat to try to get the next + // cache entry. + if (cacheReadSuccess) { + cacheEmpty = false; + return S_OK; + } + } + return E_FAIL; +} + +// This function retrieves values cached by MaybeCache. If any values were +// loaded from the cache, the values passed in to this function are passed to +// MaybeCache so that they are not lost. If there are no values in the cache, +// the values passed will not be changed. +// Values retrieved from the cache will also be removed from it. +HRESULT MaybeSwapForCached(std::string& notificationType, + std::string& notificationShown, + std::string& notificationAction) { + bool cacheEmpty; + std::string cachedType, cachedShown, cachedAction; + HRESULT hr = PopCache(cacheEmpty, cachedType, cachedShown, cachedAction); + if (FAILED(hr)) { + LOG_ERROR_MESSAGE(L"Failed to read cache: %#X", hr); + return hr; + } + if (cacheEmpty) { + return S_OK; + } + MaybeCache(notificationType, notificationShown, notificationAction); + notificationType = cachedType; + notificationShown = cachedShown; + notificationAction = cachedAction; + return S_OK; +} + HRESULT SendDefaultBrowserPing( const DefaultBrowserInfo& browserInfo, const NotificationActivities& activitiesPerformed) { @@ -372,8 +624,8 @@ HRESULT SendDefaultBrowserPing( // Do not send the ping if we are not an official telemetry-enabled build; // don't even generate the ping in fact, because if we write the file out // then some other build might find it later and decide to submit it. - if (!IsOfficialTelemetry()) { - return S_OK; + if (!IsOfficialTelemetry() || IsTelemetryDisabled()) { + return MaybeCache(notificationType, notificationShown, notificationAction); } // Pings are limited to one per day (across all installations), so check if we @@ -392,7 +644,13 @@ HRESULT SendDefaultBrowserPing( } bool pingAlreadySent = pingAlreadySentResult.unwrap(); if (pingAlreadySent) { - return S_OK; + return MaybeCache(notificationType, notificationShown, notificationAction); + } + + HRESULT hr = MaybeSwapForCached(notificationType, notificationShown, + notificationAction); + if (FAILED(hr)) { + return hr; } // Don't update the registry's default browser data until we are sure we diff --git a/toolkit/mozapps/defaultagent/main.cpp b/toolkit/mozapps/defaultagent/main.cpp index 47d562fb53d0..a74ee06f4b16 100644 --- a/toolkit/mozapps/defaultagent/main.cpp +++ b/toolkit/mozapps/defaultagent/main.cpp @@ -17,7 +17,6 @@ #include "DefaultBrowser.h" #include "EventLog.h" #include "Notification.h" -#include "Policy.h" #include "Registry.h" #include "ScheduledTask.h" #include "Telemetry.h" @@ -334,10 +333,7 @@ int wmain(int argc, wchar_t** argv) { NotificationActivities activitiesPerformed = MaybeShowNotification(browserInfo, argv[2]); - if (!IsTelemetryDisabled()) { - return SendDefaultBrowserPing(browserInfo, activitiesPerformed); - } - return S_OK; + return SendDefaultBrowserPing(browserInfo, activitiesPerformed); } else { return E_INVALIDARG; }