Bug 1933479 - Add tab close button on hover to vertical tabs when sidebar is collapse...
[gecko.git] / toolkit / components / backgroundtasks / BackgroundTasks.cpp
blob41c492a9ab7d1bd94cb2c52379ef391a101816e0
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "mozilla/BackgroundTasks.h"
7 #include "nsIBackgroundTasksManager.h"
8 #include "nsICommandLine.h"
9 #include "nsIFile.h"
10 #include "nsImportModule.h"
11 #include "nsPrintfCString.h"
12 #include "nsProfileLock.h"
13 #include "nsTSubstring.h"
14 #include "nsThreadUtils.h"
15 #include "nsXULAppAPI.h"
16 #include "prenv.h"
17 #include "prtime.h"
19 #include "mozilla/CmdLineAndEnvUtils.h"
20 #include "mozilla/LateWriteChecks.h"
21 #include "mozilla/UniquePtr.h"
22 #include "mozilla/Unused.h"
24 namespace mozilla {
26 NS_IMPL_ISUPPORTS(BackgroundTasks, nsIBackgroundTasks);
28 BackgroundTasks::BackgroundTasks(Maybe<nsCString> aBackgroundTask)
29 : mBackgroundTask(std::move(aBackgroundTask)), mIsEphemeralProfile(false) {
30 // Log when a background task is created.
31 if (mBackgroundTask.isSome()) {
32 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Info,
33 ("Created background task: %s", mBackgroundTask->get()));
37 void BackgroundTasks::Init(Maybe<nsCString> aBackgroundTask) {
38 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
40 MOZ_RELEASE_ASSERT(!sSingleton,
41 "BackgroundTasks singleton already initialized");
42 // The singleton will be cleaned up by `Shutdown()`.
43 sSingleton = new BackgroundTasks(std::move(aBackgroundTask));
46 void BackgroundTasks::Shutdown() {
47 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
49 MOZ_LOG(sBackgroundTasksLog, LogLevel::Info, ("Shutdown"));
51 if (!sSingleton) {
52 return;
55 if (sSingleton->mProfD &&
56 !EnvHasValue("MOZ_BACKGROUNDTASKS_NO_REMOVE_PROFILE")) {
57 AutoSuspendLateWriteChecks suspend;
59 if (sSingleton->mIsEphemeralProfile) {
60 // Log that the ephemeral profile is being removed.
61 if (MOZ_LOG_TEST(sBackgroundTasksLog, mozilla::LogLevel::Info)) {
62 nsAutoString path;
63 if (NS_SUCCEEDED(sSingleton->mProfD->GetPath(path))) {
64 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Info,
65 ("Removing profile: %s",
66 NS_LossyConvertUTF16toASCII(path).get()));
70 Unused << sSingleton->mProfD->Remove(/* aRecursive */ true);
71 } else {
72 // Log that the non-ephemeral profile is not being removed.
73 if (MOZ_LOG_TEST(sBackgroundTasksLog, mozilla::LogLevel::Debug)) {
74 nsAutoString path;
75 if (NS_SUCCEEDED(sSingleton->mProfD->GetPath(path))) {
76 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Debug,
77 ("Not removing non-ephemeral profile: %s",
78 NS_LossyConvertUTF16toASCII(path).get()));
84 sSingleton = nullptr;
87 BackgroundTasks* BackgroundTasks::GetSingleton() {
88 if (!sSingleton) {
89 // xpcshell doesn't set up background tasks: default to no background
90 // task.
91 Init(Nothing());
94 MOZ_RELEASE_ASSERT(sSingleton,
95 "BackgroundTasks singleton should have been initialized");
97 return sSingleton.get();
100 already_AddRefed<BackgroundTasks> BackgroundTasks::GetSingletonAddRefed() {
101 return RefPtr<BackgroundTasks>(GetSingleton()).forget();
104 Maybe<nsCString> BackgroundTasks::GetBackgroundTasks() {
105 if (!XRE_IsParentProcess()) {
106 return Nothing();
109 return GetSingleton()->mBackgroundTask;
112 bool BackgroundTasks::IsBackgroundTaskMode() {
113 if (!XRE_IsParentProcess()) {
114 return false;
117 return GetBackgroundTasks().isSome();
120 nsresult BackgroundTasks::CreateEphemeralProfileDirectory(
121 nsIFile* aRootDir, const nsCString& aProfilePrefix, nsIFile** aFile) {
122 if (!XRE_IsParentProcess()) {
123 return NS_ERROR_NOT_AVAILABLE;
126 Maybe<nsCString> task = GetBackgroundTasks();
127 sSingleton->mIsEphemeralProfile =
128 task.isSome() && IsEphemeralProfileTaskName(task.ref());
130 MOZ_RELEASE_ASSERT(sSingleton->mIsEphemeralProfile);
132 nsresult rv = sSingleton->CreateEphemeralProfileDirectoryImpl(
133 aRootDir, aProfilePrefix, aFile);
135 // Log whether the ephemeral profile was created.
136 if (NS_WARN_IF(NS_FAILED(rv))) {
137 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Warning,
138 ("Failed to create ephemeral profile directory!"));
139 } else {
140 if (MOZ_LOG_TEST(sBackgroundTasksLog, mozilla::LogLevel::Info)) {
141 nsAutoString path;
142 if (aFile && *aFile && NS_SUCCEEDED((*aFile)->GetPath(path))) {
143 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Info,
144 ("Created ephemeral profile directory: %s",
145 NS_LossyConvertUTF16toASCII(path).get()));
150 return rv;
153 nsresult BackgroundTasks::CreateNonEphemeralProfileDirectory(
154 nsIFile* aRootDir, const nsCString& aProfilePrefix, nsIFile** aFile) {
155 if (!XRE_IsParentProcess()) {
156 return NS_ERROR_NOT_AVAILABLE;
159 Maybe<nsCString> task = GetBackgroundTasks();
160 sSingleton->mIsEphemeralProfile =
161 task.isSome() && IsEphemeralProfileTaskName(task.ref());
163 MOZ_RELEASE_ASSERT(!sSingleton->mIsEphemeralProfile);
165 nsresult rv = sSingleton->CreateNonEphemeralProfileDirectoryImpl(
166 aRootDir, aProfilePrefix, aFile);
168 // Log whether the non-ephemeral profile was created.
169 if (NS_WARN_IF(NS_FAILED(rv))) {
170 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Warning,
171 ("Failed to create non-ephemeral profile directory!"));
172 } else {
173 if (MOZ_LOG_TEST(sBackgroundTasksLog, mozilla::LogLevel::Info)) {
174 nsAutoString path;
175 if (aFile && *aFile && NS_SUCCEEDED((*aFile)->GetPath(path))) {
176 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Info,
177 ("Non-ephemeral profile directory existed or was created: %s",
178 NS_LossyConvertUTF16toASCII(path).get()));
183 return rv;
186 bool BackgroundTasks::IsEphemeralProfile() {
187 return sSingleton && sSingleton->mIsEphemeralProfile && sSingleton->mProfD;
190 class BackgroundTaskLaunchRunnable : public Runnable {
191 public:
192 explicit BackgroundTaskLaunchRunnable(nsIBackgroundTasksManager* aManager,
193 const char* aTaskName,
194 nsICommandLine* aCmdLine)
195 : Runnable("BackgroundTaskLaunchRunnable"),
196 mManager(aManager),
197 mTaskName(aTaskName),
198 mCmdLine(aCmdLine) {}
200 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
201 // bug 1535398.
202 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
203 return mManager->RunBackgroundTaskNamed(mTaskName, mCmdLine);
206 private:
207 nsCOMPtr<nsIBackgroundTasksManager> mManager;
208 NS_ConvertASCIItoUTF16 mTaskName;
209 nsCOMPtr<nsICommandLine> mCmdLine;
212 nsresult BackgroundTasks::RunBackgroundTask(nsICommandLine* aCmdLine) {
213 Maybe<nsCString> task = GetBackgroundTasks();
214 if (task.isNothing()) {
215 return NS_ERROR_NOT_AVAILABLE;
218 nsCOMPtr<nsIBackgroundTasksManager> manager =
219 do_GetService("@mozilla.org/backgroundtasksmanager;1");
221 MOZ_RELEASE_ASSERT(manager, "Could not get background tasks manager service");
223 // Give the initial storm of startup runnables a chance to run before our
224 // payload is going to potentially block the main thread for a while.
225 auto r = MakeRefPtr<BackgroundTaskLaunchRunnable>(manager, task.ref().get(),
226 aCmdLine);
227 return GetCurrentSerialEventTarget()->DelayedDispatch(r.forget(), 100);
230 bool BackgroundTasks::IsUpdatingTaskName(const nsCString& aName) {
231 return aName.EqualsLiteral("backgroundupdate") ||
232 aName.EqualsLiteral("shouldprocessupdates");
235 bool BackgroundTasks::IsEphemeralProfileTaskName(const nsCString& aName) {
236 return !(aName.EqualsLiteral("backgroundupdate") ||
237 aName.EqualsLiteral("defaultagent") ||
238 aName.EqualsLiteral("message") || // Just for development.
239 aName.EqualsLiteral("not_ephemeral_profile")); // Just for testing.
242 bool BackgroundTasks::IsNoOutputTaskName(const nsCString& aName) {
243 return aName.EqualsLiteral("pingsender") ||
244 aName.EqualsLiteral("removeDirectory") ||
245 aName.EqualsLiteral("removeProfileFiles") ||
246 aName.EqualsLiteral("no_output"); // Just for testing.
249 nsCString BackgroundTasks::GetProfilePrefix(const nsCString& aInstallHash) {
250 return nsPrintfCString("%sBackgroundTask-%s-%s", MOZ_APP_VENDOR,
251 aInstallHash.get(), GetBackgroundTasks().ref().get());
254 nsresult BackgroundTasks::CreateNonEphemeralProfileDirectoryImpl(
255 nsIFile* aRootDir, const nsCString& aProfilePrefix, nsIFile** aFile) {
256 if (mBackgroundTask.isNothing()) {
257 return NS_ERROR_NOT_AVAILABLE;
260 nsresult rv;
262 nsCOMPtr<nsIFile> file;
263 if (mProfD) {
264 rv = mProfD->Clone(getter_AddRefs(file));
265 NS_ENSURE_SUCCESS(rv, rv);
266 } else {
267 file = aRootDir;
269 // The base path is
270 // /{UAppData}/Background Tasks
271 // Profiles/[salt].[vendor]BackgroundTask-[pathHash]-[taskName].
272 rv = file->AppendNative(aProfilePrefix);
273 NS_ENSURE_SUCCESS(rv, rv);
275 // Create the persistent profile directory if it does not exist.
276 bool exists;
277 rv = file->Exists(&exists);
278 NS_ENSURE_SUCCESS(rv, rv);
280 if (!exists) {
281 rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
282 NS_ENSURE_SUCCESS(rv, rv);
285 rv = file->Clone(getter_AddRefs(mProfD));
286 NS_ENSURE_SUCCESS(rv, rv);
289 file.forget(aFile);
290 return NS_OK;
293 nsresult BackgroundTasks::CreateEphemeralProfileDirectoryImpl(
294 nsIFile* aRootDir, const nsCString& aProfilePrefix, nsIFile** aFile) {
295 if (mBackgroundTask.isNothing()) {
296 return NS_ERROR_NOT_AVAILABLE;
299 nsresult rv;
301 nsCOMPtr<nsIFile> file;
302 if (mProfD) {
303 rv = mProfD->Clone(getter_AddRefs(file));
304 NS_ENSURE_SUCCESS(rv, rv);
305 } else {
306 file = aRootDir;
308 // Windows file cleanup is unreliable, so let's take a moment to clean up
309 // any prior background task profiles. We can continue if there was an error
310 // as creating a new ephemeral profile does not require cleaning up the old.
311 rv = RemoveStaleEphemeralProfileDirectories(file, aProfilePrefix);
312 if (NS_WARN_IF(NS_FAILED(rv))) {
313 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Warning,
314 ("Error cleaning up stale ephemeral profile directories."));
317 // The base path is /tmp/[vendor]BackgroundTask-[pathHash]-[taskName].
318 rv = file->AppendNative(aProfilePrefix);
319 NS_ENSURE_SUCCESS(rv, rv);
321 // Create a unique profile directory. This can fail if there are too many
322 // (thousands) of existing directories, which is unlikely to happen.
323 rv = file->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700);
324 NS_ENSURE_SUCCESS(rv, rv);
326 rv = file->Clone(getter_AddRefs(mProfD));
327 NS_ENSURE_SUCCESS(rv, rv);
330 file.forget(aFile);
331 return NS_OK;
334 nsresult BackgroundTasks::RemoveStaleEphemeralProfileDirectories(
335 nsIFile* const aRoot, const nsCString& aPrefix) {
336 nsresult rv;
338 if (MOZ_LOG_TEST(sBackgroundTasksLog, LogLevel::Info)) {
339 nsAutoString path;
340 if (NS_SUCCEEDED(aRoot->GetPath(path))) {
341 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Info,
342 ("Checking \"%s\" for stale profiles matching \"%s\".",
343 NS_LossyConvertUTF16toASCII(path.get()).get(), aPrefix.get()));
347 // Check how old a background task should be before being deleted.
348 PRTime timeoutMillis = 60 * 1000; // Default to 1 minute.
349 bool deleteAll = false;
350 // MOZ_BACKGROUNDTASKS_PURGE_STALE_PROFILES = ["0"+ in ms, "always", "never"]
351 nsAutoCString envTimeoutStr(
352 PR_GetEnv("MOZ_BACKGROUNDTASKS_PURGE_STALE_PROFILES"));
354 if (!envTimeoutStr.IsEmpty()) {
355 int64_t envTimeoutMillis = envTimeoutStr.ToInteger64(&rv);
356 if (NS_SUCCEEDED(rv)) {
357 if (envTimeoutMillis >= 0) {
358 timeoutMillis = envTimeoutMillis;
359 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Info,
360 ("Setting stale profile age to %sms", envTimeoutStr.get()));
361 } else {
362 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Warning,
363 ("MOZ_BACKGROUNDTASKS_PURGE_STALE_PROFILES is set less than 0, "
364 "using default timeout instead."));
366 } else {
367 if (envTimeoutStr.Equals("always")) {
368 deleteAll = true;
369 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Info,
370 ("Deleting profiles regardless of age."));
371 } else if (envTimeoutStr.Equals("never")) {
372 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Info,
373 ("Skipping cleanup of stale background task profiles."));
374 return NS_OK;
375 } else {
376 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Warning,
377 ("MOZ_BACKGROUNDTASKS_PURGE_STALE_PROFILES is set to invalid "
378 "value, using default timeout instead."));
383 nsCOMPtr<nsIDirectoryEnumerator> entries;
384 rv = aRoot->GetDirectoryEntries(getter_AddRefs(entries));
385 NS_ENSURE_SUCCESS(rv, rv);
387 nsCOMPtr<nsIFile> entry;
388 int removedProfiles = 0;
389 // Limit the number of stale ephemeral profiles we clean up so that we don't
390 // timeout the background task.
391 const int kMaxRemovedProfiles = 5;
393 // Loop over the ephemeral directory entries, deleting folders matching our
394 // profile prefix. Continue if there is an error interacting with the entry to
395 // more reliably make progress on cleaning up stale ephemeral profiles.
396 while (removedProfiles < kMaxRemovedProfiles &&
397 NS_SUCCEEDED(rv = entries->GetNextFile(getter_AddRefs(entry))) &&
398 entry) {
399 nsCString entryName;
400 rv = entry->GetNativeLeafName(entryName);
401 if (NS_FAILED(rv)) {
402 continue;
405 // Find profile folders matching our prefix.
406 if (!StringBeginsWith(entryName, aPrefix)) {
407 continue;
410 if (!deleteAll) {
411 // Skip profiles that were recently created to prevent deleting a profile
412 // after creating the directory but before creating the lockfile.
413 PRTime profileModifyTime;
414 if (NS_FAILED(entry->GetLastModifiedTime(&profileModifyTime))) {
415 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Warning,
416 ("Skipping deletion of %s, unable to retrieve when profile was "
417 "last modified.",
418 entryName.get()));
419 continue;
421 PRTime now = PR_Now() / PR_USEC_PER_MSEC;
422 // Timeout only needs to be large enough to prevent deleting a ephemeral
423 // profile between it being created and locked.
424 if (now - profileModifyTime < timeoutMillis) {
425 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Debug,
426 ("Skipping deletion of %s, profile is not yet stale.",
427 entryName.get()));
428 continue;
432 // Check if the profile is locked. If successful drop the lock so we can
433 // delete the folder. Background tasks' ephemeral profiles are not reused or
434 // remembered once released, so we don't need to hold this lock while
435 // deleting it.
436 nsProfileLock lock;
437 if (NS_FAILED(lock.Lock(entry, nullptr)) || NS_FAILED(lock.Unlock())) {
438 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Warning,
439 ("Skipping deletion of %s, unable to lock/unlock profile.",
440 entryName.get()));
441 continue;
444 rv = entry->Remove(true);
445 if (NS_FAILED(rv)) {
446 if (MOZ_LOG_TEST(sBackgroundTasksLog, mozilla::LogLevel::Warning)) {
447 nsAutoString path;
448 if (NS_SUCCEEDED(entry->GetPath(path))) {
449 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Warning,
450 ("Error removing stale ephemeral profile directory: %s",
451 NS_LossyConvertUTF16toASCII(path).get()));
455 continue;
458 if (MOZ_LOG_TEST(sBackgroundTasksLog, mozilla::LogLevel::Info)) {
459 nsAutoString path;
460 if (NS_SUCCEEDED(entry->GetPath(path))) {
461 MOZ_LOG(sBackgroundTasksLog, mozilla::LogLevel::Info,
462 ("Removed stale ephemeral profile directory: %s",
463 NS_LossyConvertUTF16toASCII(path).get()));
467 removedProfiles++;
470 return NS_OK;
473 nsresult BackgroundTasks::GetIsBackgroundTaskMode(bool* result) {
474 *result = mBackgroundTask.isSome();
475 return NS_OK;
478 nsresult BackgroundTasks::BackgroundTaskName(nsAString& name) {
479 name.SetIsVoid(true);
480 if (mBackgroundTask.isSome()) {
481 name.AssignASCII(mBackgroundTask.ref());
483 return NS_OK;
486 nsresult BackgroundTasks::OverrideBackgroundTaskNameForTesting(
487 const nsAString& name) {
488 if (name.IsVoid()) {
489 mBackgroundTask = Nothing();
490 } else {
491 mBackgroundTask = Some(NS_LossyConvertUTF16toASCII(name));
493 return NS_OK;
496 StaticRefPtr<BackgroundTasks> BackgroundTasks::sSingleton;
498 LazyLogModule BackgroundTasks::sBackgroundTasksLog("BackgroundTasks");
500 } // namespace mozilla