зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1647489 - Ensure WDBA notification data is not lost r=mhowell,agashlin,nalexander
Use caching to ensure that data collected when a ping is not sent eventually gets sent in a later ping. This is necessary only when a notification has actually been shown. Differential Revision: https://phabricator.services.mozilla.com/D80711
This commit is contained in:
Родитель
38c6d7dc8c
Коммит
a9a1270406
|
@ -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<mozilla::Ok> 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<std::string> maybeType = typeResult.unwrap();
|
||||
mozilla::Maybe<std::string> maybeShown = shownResult.unwrap();
|
||||
mozilla::Maybe<std::string> 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<std::string> 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<std::string> 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<std::string> 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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче