From 6443deaaeeba3191575566286473302426cddc6a Mon Sep 17 00:00:00 2001 From: Adam Gashlin Date: Fri, 11 Dec 2020 23:17:53 +0000 Subject: [PATCH] Bug 1676296 Part 1: XPCOM component for Windows Task Scheduler. r=mhowell Differential Revision: https://phabricator.services.mozilla.com/D97841 --- toolkit/components/moz.build | 3 + .../components/taskscheduler/components.conf | 18 + toolkit/components/taskscheduler/moz.build | 23 ++ .../nsIWinTaskSchedulerService.idl | 104 ++++++ .../taskscheduler/nsWinTaskScheduler.cpp | 308 ++++++++++++++++++ .../taskscheduler/nsWinTaskScheduler.h | 22 ++ .../xpcshell/test_WinTaskSchedulerService.js | 191 +++++++++++ .../taskscheduler/tests/xpcshell/xpcshell.ini | 2 + 8 files changed, 671 insertions(+) create mode 100644 toolkit/components/taskscheduler/components.conf create mode 100644 toolkit/components/taskscheduler/moz.build create mode 100644 toolkit/components/taskscheduler/nsIWinTaskSchedulerService.idl create mode 100644 toolkit/components/taskscheduler/nsWinTaskScheduler.cpp create mode 100644 toolkit/components/taskscheduler/nsWinTaskScheduler.h create mode 100644 toolkit/components/taskscheduler/tests/xpcshell/test_WinTaskSchedulerService.js create mode 100644 toolkit/components/taskscheduler/tests/xpcshell/xpcshell.ini diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build index 2b5f72a51636..8fb75fad7042 100644 --- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -110,6 +110,9 @@ DIRS += ["captivedetect"] if CONFIG["OS_TARGET"] != "Android": DIRS += ["terminator"] +if CONFIG["MOZ_UPDATE_AGENT"]: + DIRS += ["taskscheduler"] + DIRS += ["build"] if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": diff --git a/toolkit/components/taskscheduler/components.conf b/toolkit/components/taskscheduler/components.conf new file mode 100644 index 000000000000..c6e96da1b40d --- /dev/null +++ b/toolkit/components/taskscheduler/components.conf @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [] + +if buildconfig.substs['OS_TARGET'] == 'WINNT': + Classes += [ + { + 'cid': '{e2113dfc-8efe-43a1-8a20-ad720dd771d6}', + 'contract_ids': ['@mozilla.org/win-task-scheduler-service;1'], + 'type': 'nsWinTaskSchedulerService', + 'headers': ['nsWinTaskScheduler.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, + ] diff --git a/toolkit/components/taskscheduler/moz.build b/toolkit/components/taskscheduler/moz.build new file mode 100644 index 000000000000..5e7b1c1def22 --- /dev/null +++ b/toolkit/components/taskscheduler/moz.build @@ -0,0 +1,23 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Application Update") + +XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.ini"] + +XPIDL_MODULE = "taskscheduler" +XPCOM_MANIFESTS += ["components.conf"] + +# This whole component is currently Windows-only, but a Mac implementation is planned. +# Only the Windows C++ interface is an XPCOM component. +if CONFIG["OS_TARGET"] == "WINNT": + XPIDL_SOURCES += ["nsIWinTaskSchedulerService.idl"] + EXPORTS += ["nsWinTaskScheduler.h"] + SOURCES += ["nsWinTaskScheduler.cpp"] + OS_LIBS += ["taskschd"] + +FINAL_LIBRARY = "xul" diff --git a/toolkit/components/taskscheduler/nsIWinTaskSchedulerService.idl b/toolkit/components/taskscheduler/nsIWinTaskSchedulerService.idl new file mode 100644 index 000000000000..7a7c5747c0e8 --- /dev/null +++ b/toolkit/components/taskscheduler/nsIWinTaskSchedulerService.idl @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "nsISupports.idl" + +/** + * An interface for Windows Task Scheduler 2.0. + * Documentation for the underlying APIs can be found at + * https://docs.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page + */ +[scriptable, main_process_scriptable_only, uuid(a8d36901-0b6a-46c3-a214-a9e1d5d6047a)] +interface nsIWinTaskSchedulerService : nsISupports +{ + /** + * Register (create) a task from an XML definition. + * The task will be created so that it only runs as the current user + * (TASK_LOGON_INTERACTIVE_TOKEN). + * + * @throws NS_ERROR_FILE_NOT_FOUND if the folder does not exist. + * @throws NS_ERROR_FILE_ALREADY_EXISTS if the task already existed and aUpdateExisting is false. + * + * @param aFolderName Full name of the folder in which to create the task, starting with "\". + * + * @param aTaskName Name of the task. + * + * @param aDefinitionXML XML definition of the task. This is passed directly to Task Scheduler, + * see the schema at + * https://docs.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-schema + * + * @param aUpdateExisting Whether to update an existing task with the same name, default false. + */ + void registerTask(in wstring aFolderName, + in wstring aTaskName, + in wstring aDefinitionXML, + [optional] in boolean aUpdateExisting); + + /** + * Validate the XML task definition with Task Scheduler without creating a task, for testing. + * Doesn't throw if only the final ITaskFolder::RegisterTask() fails. + * + * @param aDefinitionXML Definition to validate. + * @return HRESULT from ITaskFolder::RegisterTask() + * Success should be S_OK (0). XML validation failure could be one of + * SCHED_E_UNEXPECTED_NODE, SCHED_E_NAMESPACE, SCHED_E_INVALIDVALUE, + * SCHED_E_MISSINGNODE, SCHED_E_MALFORMEDXML, but there may be others. + */ + long validateTaskDefinition(in wstring aDefinitionXML); + + /** + * Get the registration information for a task. + * + * @throws NS_ERROR_FILE_NOT_FOUND if the folder or task do not exist. + * + * @param aFolderName Full name of the folder containing the task, starting with "\". + * @param aTaskName Name of the task to read. + * @return Registration information for the task, as XML text. + */ + AString getTaskXML(in wstring aFolderName, in wstring aTaskName); + + /** + * Delete a task. + * + * @throws NS_ERROR_FILE_NOT_FOUND if the folder or task do not exist. + * + * @param aFolderName Full name of the folder containing the task, starting with "\". + * @param aTaskName Name of the task to delete. + */ + void deleteTask(in wstring aFolderName, in wstring aTaskName); + + /** + * List the names of all tasks in a task folder. + * + * @throws NS_ERROR_FILE_NOT_FOUND if the folder doesn't exist. + * + * @param aFolderName The full name of the task folder to enumerate, starting with "\". + * + * @return An array with the names of the tasks found. + */ + Array getFolderTasks(in wstring aFolderName); + + /** + * Create a new task subfolder under a given parent folder. + * + * @throws NS_ERROR_FILE_NOT_FOUND if the parent folder does not exist. + * @throws NS_ERROR_FILE_ALREADY_EXISTS if the subfolder already exists. + * + * @param aParentFolderName Immediate parent for the new folder, starting with "\". + * @param aSubFolderName Name of the new folder to create. + */ + void createFolder(in wstring aParentFolderName, in wstring aSubFolderName); + + /** + * Delete a folder. + * + * @throws NS_ERROR_FILE_NOT_FOUND if the parent folder does not exist. + * @throws NS_ERROR_FILE_DIR_NOT_EMPTY if the folder was not empty. + * + * @param aParentFolderName Immediate parent of the folder to delete, starting with "\". + * @param aSubFolderName Name of the folder to delete. + */ + void deleteFolder(in wstring aParentFolderName, in wstring aSubFolderName); +}; diff --git a/toolkit/components/taskscheduler/nsWinTaskScheduler.cpp b/toolkit/components/taskscheduler/nsWinTaskScheduler.cpp new file mode 100644 index 000000000000..21b2909805af --- /dev/null +++ b/toolkit/components/taskscheduler/nsWinTaskScheduler.cpp @@ -0,0 +1,308 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 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/. */ + +#include "nsWinTaskScheduler.h" + +#include +#include +#include + +#include "nsString.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/ResultVariant.h" + +using namespace mozilla; + +struct SysFreeStringDeleter { + void operator()(BSTR aPtr) { ::SysFreeString(aPtr); } +}; +using BStrPtr = mozilla::UniquePtr; + +static nsresult ToNotFoundOrFailure(HRESULT hr) { + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { + return NS_ERROR_FILE_NOT_FOUND; + } else { + return NS_ERROR_FAILURE; + } +} + +static nsresult ToAlreadyExistsOrFailure(HRESULT hr) { + if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) { + return NS_ERROR_FILE_ALREADY_EXISTS; + } else { + return NS_ERROR_FAILURE; + } +} + +[[nodiscard]] static Result, HRESULT> GetTaskFolder( + const char16_t* aFolderName) { + HRESULT hr; + RefPtr scheduler = nullptr; + + hr = CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER, + IID_ITaskService, getter_AddRefs(scheduler)); + if (FAILED(hr)) { + return Err(hr); + } + + // Connect to the local Task Scheduler. + hr = scheduler->Connect(VARIANT{}, VARIANT{}, VARIANT{}, VARIANT{}); + if (FAILED(hr)) { + return Err(hr); + } + + BStrPtr bstrFolderName = + BStrPtr(::SysAllocString(reinterpret_cast(aFolderName))); + + RefPtr folder = nullptr; + hr = scheduler->GetFolder(bstrFolderName.get(), getter_AddRefs(folder)); + if (FAILED(hr)) { + return Err(hr); + } + + return folder; +} + +[[nodiscard]] static Result, HRESULT> GetRegisteredTask( + const char16_t* aFolderName, const char16_t* aTaskName) { + auto folder = GetTaskFolder(aFolderName); + if (!folder.isOk()) { + return Err(folder.unwrapErr()); + } + + BStrPtr bstrTaskName = + BStrPtr(::SysAllocString(reinterpret_cast(aTaskName))); + + RefPtr task = nullptr; + HRESULT hr = + folder.unwrap()->GetTask(bstrTaskName.get(), getter_AddRefs(task)); + if (FAILED(hr)) { + return Err(hr); + } + + return task; +} + +NS_IMPL_ISUPPORTS(nsWinTaskSchedulerService, nsIWinTaskSchedulerService) + +NS_IMETHODIMP +nsWinTaskSchedulerService::GetTaskXML(const char16_t* aFolderName, + const char16_t* aTaskName, + nsAString& aResult) { + if (!aFolderName || !aTaskName) { + return NS_ERROR_NULL_POINTER; + } + + auto task = GetRegisteredTask(aFolderName, aTaskName); + if (!task.isOk()) { + return ToNotFoundOrFailure(task.unwrapErr()); + } + + { + BSTR bstrXml = nullptr; + if (FAILED(task.unwrap()->get_Xml(&bstrXml))) { + return NS_ERROR_FAILURE; + } + + aResult.Assign(bstrXml, ::SysStringLen(bstrXml)); + ::SysFreeString(bstrXml); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWinTaskSchedulerService::RegisterTask(const char16_t* aFolderName, + const char16_t* aTaskName, + const char16_t* aDefinitionXML, + bool aUpdateExisting) { + if (!aFolderName || !aTaskName || !aDefinitionXML) { + return NS_ERROR_NULL_POINTER; + } + + auto folder = GetTaskFolder(aFolderName); + if (!folder.isOk()) { + return ToNotFoundOrFailure(folder.unwrapErr()); + } + + BStrPtr bstrTaskName = + BStrPtr(::SysAllocString(reinterpret_cast(aTaskName))); + BStrPtr bstrXml = BStrPtr( + ::SysAllocString(reinterpret_cast(aDefinitionXML))); + LONG flags = aUpdateExisting ? TASK_CREATE_OR_UPDATE : TASK_CREATE; + TASK_LOGON_TYPE logonType = TASK_LOGON_INTERACTIVE_TOKEN; + + // The outparam is not needed, but not documented as optional. + RefPtr unusedTaskOutput = nullptr; + HRESULT hr = folder.unwrap()->RegisterTask( + bstrTaskName.get(), bstrXml.get(), flags, VARIANT{} /* userId */, + VARIANT{} /* password */, logonType, VARIANT{} /* sddl */, + getter_AddRefs(unusedTaskOutput)); + + if (FAILED(hr)) { + return ToAlreadyExistsOrFailure(hr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWinTaskSchedulerService::ValidateTaskDefinition( + const char16_t* aDefinitionXML, int32_t* aResult) { + if (!aDefinitionXML) { + return NS_ERROR_NULL_POINTER; + } + + auto folder = GetTaskFolder(reinterpret_cast(L"\\")); + if (!folder.isOk()) { + return NS_ERROR_FAILURE; + } + + BStrPtr bstrXml = BStrPtr( + ::SysAllocString(reinterpret_cast(aDefinitionXML))); + LONG flags = TASK_VALIDATE_ONLY; + TASK_LOGON_TYPE logonType = TASK_LOGON_INTERACTIVE_TOKEN; + + // The outparam is not needed, but not documented as optional. + RefPtr unusedTaskOutput = nullptr; + HRESULT hr = folder.unwrap()->RegisterTask( + nullptr /* path */, bstrXml.get(), flags, VARIANT{} /* userId */, + VARIANT{} /* password */, logonType, VARIANT{} /* sddl */, + getter_AddRefs(unusedTaskOutput)); + + if (aResult) { + *aResult = hr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWinTaskSchedulerService::DeleteTask(const char16_t* aFolderName, + const char16_t* aTaskName) { + if (!aFolderName || !aTaskName) { + return NS_ERROR_NULL_POINTER; + } + + auto folder = GetTaskFolder(aFolderName); + if (!folder.isOk()) { + return ToNotFoundOrFailure(folder.unwrapErr()); + } + + BStrPtr bstrTaskName = + BStrPtr(::SysAllocString(reinterpret_cast(aTaskName))); + + HRESULT hr = folder.unwrap()->DeleteTask(bstrTaskName.get(), 0 /* flags */); + if (FAILED(hr)) { + return ToNotFoundOrFailure(hr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWinTaskSchedulerService::GetFolderTasks(const char16_t* aFolderName, + nsTArray& aResult) { + if (!aFolderName) { + return NS_ERROR_NULL_POINTER; + } + + auto folder = GetTaskFolder(aFolderName); + if (!folder.isOk()) { + return ToNotFoundOrFailure(folder.unwrapErr()); + } + + RefPtr taskCollection = nullptr; + if (FAILED(folder.unwrap()->GetTasks(TASK_ENUM_HIDDEN, + getter_AddRefs(taskCollection)))) { + return NS_ERROR_FAILURE; + } + + LONG taskCount = 0; + if (FAILED(taskCollection->get_Count(&taskCount))) { + return NS_ERROR_FAILURE; + } + + aResult.Clear(); + + for (LONG i = 0; i < taskCount; ++i) { + RefPtr task = nullptr; + + // nb: Collections are indexed from 1. + if (FAILED(taskCollection->get_Item(_variant_t(i + 1), + getter_AddRefs(task)))) { + return NS_ERROR_FAILURE; + } + + BStrPtr bstrTaskName; + { + BSTR tempTaskName = nullptr; + if (FAILED(task->get_Name(&tempTaskName))) { + return NS_ERROR_FAILURE; + } + bstrTaskName = BStrPtr(tempTaskName); + } + + aResult.AppendElement(nsString(bstrTaskName.get())); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWinTaskSchedulerService::CreateFolder(const char16_t* aParentFolderName, + const char16_t* aSubFolderName) { + if (!aParentFolderName || !aSubFolderName) { + return NS_ERROR_NULL_POINTER; + } + + auto parentFolder = GetTaskFolder(aParentFolderName); + if (!parentFolder.isOk()) { + return ToNotFoundOrFailure(parentFolder.unwrapErr()); + } + + BStrPtr bstrSubFolderName = BStrPtr( + ::SysAllocString(reinterpret_cast(aSubFolderName))); + + HRESULT hr = parentFolder.unwrap()->CreateFolder(bstrSubFolderName.get(), + VARIANT{}, // sddl + nullptr); // ppFolder + + if (FAILED(hr)) { + return ToAlreadyExistsOrFailure(hr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWinTaskSchedulerService::DeleteFolder(const char16_t* aParentFolderName, + const char16_t* aSubFolderName) { + if (!aParentFolderName || !aSubFolderName) { + return NS_ERROR_NULL_POINTER; + } + + auto parentFolder = GetTaskFolder(aParentFolderName); + if (!parentFolder.isOk()) { + return ToNotFoundOrFailure(parentFolder.unwrapErr()); + } + + BStrPtr bstrSubFolderName = BStrPtr( + ::SysAllocString(reinterpret_cast(aSubFolderName))); + + HRESULT hr = parentFolder.unwrap()->DeleteFolder(bstrSubFolderName.get(), + 0 /* flags */); + + if (FAILED(hr)) { + if (hr == HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY)) { + return NS_ERROR_FILE_DIR_NOT_EMPTY; + } else { + return ToNotFoundOrFailure(hr); + } + } + + return NS_OK; +} diff --git a/toolkit/components/taskscheduler/nsWinTaskScheduler.h b/toolkit/components/taskscheduler/nsWinTaskScheduler.h new file mode 100644 index 000000000000..e89c1b9b05ec --- /dev/null +++ b/toolkit/components/taskscheduler/nsWinTaskScheduler.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef WINTASKSCHEDULER_H_ +#define WINTASKSCHEDULER_H_ + +#include "nsIWinTaskSchedulerService.h" + +class nsWinTaskSchedulerService : public nsIWinTaskSchedulerService { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWINTASKSCHEDULERSERVICE + + nsWinTaskSchedulerService() = default; + + protected: + virtual ~nsWinTaskSchedulerService() = default; +}; + +#endif /* WINTASKSCHEDULER_H_ */ diff --git a/toolkit/components/taskscheduler/tests/xpcshell/test_WinTaskSchedulerService.js b/toolkit/components/taskscheduler/tests/xpcshell/test_WinTaskSchedulerService.js new file mode 100644 index 000000000000..5ffe018573e1 --- /dev/null +++ b/toolkit/components/taskscheduler/tests/xpcshell/test_WinTaskSchedulerService.js @@ -0,0 +1,191 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +// Unit tests for access to the Windows Task Scheduler via nsIWinTaskSchedulerService. + +const svc = Cc["@mozilla.org/win-task-scheduler-service;1"].getService( + Ci.nsIWinTaskSchedulerService +); + +const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService( + Ci.nsIUUIDGenerator +); + +function randomName() { + return ( + "moz-taskschd-test-" + + uuidGenerator + .generateUUID() + .toString() + .slice(1, -1) + ); +} + +const gParentFolderName = randomName(); +const gParentFolderPath = `\\${gParentFolderName}`; +const gSubFolderName = randomName(); +const gSubFolderPath = `\\${gParentFolderName}\\${gSubFolderName}`; +// This folder will not be created +const gMissingFolderName = randomName(); +const gMissingFolderPath = `\\${gParentFolderName}\\${gMissingFolderName}`; + +const gValidTaskXML = ` + + + false + + + + xyz123.exe + + +`; + +// Missing actions +const gInvalidTaskXML = ` + + + false + +`; + +function cleanup() { + let tasksToDelete = svc.getFolderTasks(gSubFolderPath); + + for (const task of tasksToDelete) { + svc.deleteTask(gSubFolderPath, task); + } + + svc.deleteFolder(gParentFolderPath, gSubFolderName); + + svc.deleteFolder("\\", gParentFolderPath); +} + +registerCleanupFunction(() => { + try { + cleanup(); + } catch (_ex) { + // Folders may not exist + } +}); + +add_task(async function test_svc() { + /***** FOLDERS *****/ + + // Try creating subfolder before parent folder exists + Assert.throws( + () => svc.createFolder(gParentFolderPath, gSubFolderName), + /NS_ERROR_FILE_NOT_FOUND/ + ); + + // Create parent folder + svc.createFolder("\\", gParentFolderName); + + // Create subfolder + svc.createFolder(gParentFolderPath, gSubFolderName); + + // Try creating existing folder + Assert.throws( + () => svc.createFolder(gParentFolderPath, gSubFolderName), + /NS_ERROR_FILE_ALREADY_EXISTS/ + ); + + // Try deleting nonexistent subfolder + Assert.throws( + () => svc.deleteFolder(gParentFolderPath, gMissingFolderName), + /NS_ERROR_FILE_NOT_FOUND/ + ); + + /***** TASKS *****/ + const taskNames = [randomName(), randomName(), randomName()]; + + // Try enumerating nonexistent subfolder + Assert.throws( + () => svc.getFolderTasks(gMissingFolderPath), + /NS_ERROR_FILE_NOT_FOUND/ + ); + + // List empty subfolder + Assert.deepEqual(svc.getFolderTasks(gSubFolderPath), []); + + // Try to create task in nonexistent subfolder + Assert.throws( + () => svc.registerTask(gMissingFolderPath, taskNames[0], gValidTaskXML), + /NS_ERROR_FILE_NOT_FOUND/ + ); + + // Create task 0 + + svc.registerTask(gSubFolderPath, taskNames[0], gValidTaskXML); + + // Try to recreate task 0 + Assert.throws( + () => svc.registerTask(gSubFolderPath, taskNames[0], gValidTaskXML), + /NS_ERROR_FILE_ALREADY_EXISTS/ + ); + + // Update task 0 + svc.registerTask( + gSubFolderPath, + taskNames[0], + gValidTaskXML, + true /* aUpdateExisting */ + ); + + // Read back XML + Assert.ok(svc.getTaskXML(gSubFolderPath, taskNames[0])); + + // Create remaining tasks + for (const task of taskNames.slice(1)) { + svc.registerTask(gSubFolderPath, task, gValidTaskXML); + } + + // Try to create with invalid XML + Assert.throws( + () => svc.registerTask(gSubFolderPath, randomName(), gInvalidTaskXML), + /NS_ERROR_FAILURE/ + ); + + // Validate XML + Assert.equal(svc.validateTaskDefinition(gValidTaskXML), 0 /* S_OK */); + + // Try to validate invalid XML + Assert.notEqual(svc.validateTaskDefinition(gInvalidTaskXML), 0 /* S_OK */); + + // Test enumeration + { + let foundTasks = svc.getFolderTasks(gSubFolderPath); + foundTasks.sort(); + + let allTasks = taskNames.slice(); + allTasks.sort(); + + Assert.deepEqual(foundTasks, allTasks); + } + + // Try deleting non-empty folder + Assert.throws( + () => svc.deleteFolder(gParentFolderPath, gSubFolderName), + /NS_ERROR_FILE_DIR_NOT_EMPTY/ + ); + + const missingTaskName = randomName(); + + // Try deleting non-existent task + Assert.throws( + () => svc.deleteTask(gSubFolderName, missingTaskName), + /NS_ERROR_FILE_NOT_FOUND/ + ); + + // Try reading non-existent task + Assert.throws( + () => svc.getTaskXML(gSubFolderPath, missingTaskName), + /NS_ERROR_FILE_NOT_FOUND/ + ); + + /***** Cleanup *****/ + // Explicitly call cleanup() to test that it removes the folder without error. + cleanup(); +}); diff --git a/toolkit/components/taskscheduler/tests/xpcshell/xpcshell.ini b/toolkit/components/taskscheduler/tests/xpcshell/xpcshell.ini new file mode 100644 index 000000000000..c2cd2f9ade37 --- /dev/null +++ b/toolkit/components/taskscheduler/tests/xpcshell/xpcshell.ini @@ -0,0 +1,2 @@ +[test_WinTaskSchedulerService.js] +run-if = os == "win" # Test of Windows only service