From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- toolkit/profile/ProfileUnlockerWin.cpp | 278 ++++++ toolkit/profile/ProfileUnlockerWin.h | 60 ++ toolkit/profile/content/createProfileWizard.js | 225 +++++ toolkit/profile/content/createProfileWizard.xul | 74 ++ toolkit/profile/content/profileSelection.js | 269 ++++++ toolkit/profile/content/profileSelection.xul | 70 ++ toolkit/profile/gtest/TestProfileLock.cpp | 116 +++ toolkit/profile/gtest/moz.build | 16 + toolkit/profile/jar.mn | 9 + toolkit/profile/moz.build | 43 + toolkit/profile/notifications.txt | 61 ++ toolkit/profile/nsIProfileMigrator.idl | 69 ++ toolkit/profile/nsIProfileUnlocker.idl | 21 + toolkit/profile/nsIToolkitProfile.idl | 89 ++ toolkit/profile/nsIToolkitProfileService.idl | 108 +++ toolkit/profile/nsProfileLock.cpp | 661 ++++++++++++++ toolkit/profile/nsProfileLock.h | 95 ++ toolkit/profile/nsProfileStringTypes.h | 32 + toolkit/profile/nsToolkitProfileService.cpp | 1117 +++++++++++++++++++++++ toolkit/profile/test/.eslintrc.js | 7 + toolkit/profile/test/chrome.ini | 3 + toolkit/profile/test/test_create_profile.xul | 134 +++ 22 files changed, 3557 insertions(+) create mode 100644 toolkit/profile/ProfileUnlockerWin.cpp create mode 100644 toolkit/profile/ProfileUnlockerWin.h create mode 100644 toolkit/profile/content/createProfileWizard.js create mode 100644 toolkit/profile/content/createProfileWizard.xul create mode 100644 toolkit/profile/content/profileSelection.js create mode 100644 toolkit/profile/content/profileSelection.xul create mode 100644 toolkit/profile/gtest/TestProfileLock.cpp create mode 100644 toolkit/profile/gtest/moz.build create mode 100644 toolkit/profile/jar.mn create mode 100644 toolkit/profile/moz.build create mode 100644 toolkit/profile/notifications.txt create mode 100644 toolkit/profile/nsIProfileMigrator.idl create mode 100644 toolkit/profile/nsIProfileUnlocker.idl create mode 100644 toolkit/profile/nsIToolkitProfile.idl create mode 100644 toolkit/profile/nsIToolkitProfileService.idl create mode 100644 toolkit/profile/nsProfileLock.cpp create mode 100644 toolkit/profile/nsProfileLock.h create mode 100644 toolkit/profile/nsProfileStringTypes.h create mode 100644 toolkit/profile/nsToolkitProfileService.cpp create mode 100644 toolkit/profile/test/.eslintrc.js create mode 100644 toolkit/profile/test/chrome.ini create mode 100644 toolkit/profile/test/test_create_profile.xul (limited to 'toolkit/profile') diff --git a/toolkit/profile/ProfileUnlockerWin.cpp b/toolkit/profile/ProfileUnlockerWin.cpp new file mode 100644 index 000000000..0e0139188 --- /dev/null +++ b/toolkit/profile/ProfileUnlockerWin.cpp @@ -0,0 +1,278 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "ProfileUnlockerWin.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsTArray.h" +#include "nsXPCOM.h" + +namespace mozilla { + +/** + * RAII class to obtain and manage a handle to a Restart Manager session. + * It opens a new handle upon construction and releases it upon destruction. + */ +class MOZ_STACK_CLASS ScopedRestartManagerSession +{ +public: + explicit ScopedRestartManagerSession(ProfileUnlockerWin& aUnlocker) + : mError(ERROR_INVALID_HANDLE) + , mHandle((DWORD)-1) // 0 is a valid restart manager handle + , mUnlocker(aUnlocker) + { + mError = mUnlocker.StartSession(mHandle); + } + + ~ScopedRestartManagerSession() + { + if (mError == ERROR_SUCCESS) { + mUnlocker.EndSession(mHandle); + } + } + + /** + * @return true if the handle is a valid Restart Ranager handle. + */ + inline bool + ok() + { + return mError == ERROR_SUCCESS; + } + + /** + * @return the Restart Manager handle to pass to other Restart Manager APIs. + */ + inline DWORD + handle() + { + return mHandle; + } + +private: + DWORD mError; + DWORD mHandle; + ProfileUnlockerWin& mUnlocker; +}; + +ProfileUnlockerWin::ProfileUnlockerWin(const nsAString& aFileName) + : mRmStartSession(nullptr) + , mRmRegisterResources(nullptr) + , mRmGetList(nullptr) + , mRmEndSession(nullptr) + , mQueryFullProcessImageName(nullptr) + , mFileName(aFileName) +{ +} + +ProfileUnlockerWin::~ProfileUnlockerWin() +{ +} + +NS_IMPL_ISUPPORTS(ProfileUnlockerWin, nsIProfileUnlocker) + +nsresult +ProfileUnlockerWin::Init() +{ + MOZ_ASSERT(!mRestartMgrModule); + if (mFileName.IsEmpty()) { + return NS_ERROR_ILLEGAL_VALUE; + } + + nsModuleHandle module(::LoadLibraryW(L"Rstrtmgr.dll")); + if (!module) { + return NS_ERROR_NOT_AVAILABLE; + } + mRmStartSession = + reinterpret_cast(::GetProcAddress(module, "RmStartSession")); + if (!mRmStartSession) { + return NS_ERROR_UNEXPECTED; + } + mRmRegisterResources = + reinterpret_cast(::GetProcAddress(module, + "RmRegisterResources")); + if (!mRmRegisterResources) { + return NS_ERROR_UNEXPECTED; + } + mRmGetList = reinterpret_cast(::GetProcAddress(module, + "RmGetList")); + if (!mRmGetList) { + return NS_ERROR_UNEXPECTED; + } + mRmEndSession = reinterpret_cast(::GetProcAddress(module, + "RmEndSession")); + if (!mRmEndSession) { + return NS_ERROR_UNEXPECTED; + } + + mQueryFullProcessImageName = + reinterpret_cast(::GetProcAddress( + ::GetModuleHandleW(L"kernel32.dll"), + "QueryFullProcessImageNameW")); + if (!mQueryFullProcessImageName) { + return NS_ERROR_NOT_AVAILABLE; + } + + mRestartMgrModule.steal(module); + return NS_OK; +} + +DWORD +ProfileUnlockerWin::StartSession(DWORD& aHandle) +{ + WCHAR sessionKey[CCH_RM_SESSION_KEY + 1] = {0}; + return mRmStartSession(&aHandle, 0, sessionKey); +} + +void +ProfileUnlockerWin::EndSession(DWORD aHandle) +{ + mRmEndSession(aHandle); +} + +NS_IMETHODIMP +ProfileUnlockerWin::Unlock(uint32_t aSeverity) +{ + if (!mRestartMgrModule) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (aSeverity != FORCE_QUIT) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + ScopedRestartManagerSession session(*this); + if (!session.ok()) { + return NS_ERROR_FAILURE; + } + + LPCWSTR resources[] = { mFileName.get() }; + DWORD error = mRmRegisterResources(session.handle(), 1, resources, 0, nullptr, + 0, nullptr); + if (error != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + // Using a AutoTArray here because we expect the required size to be 1. + AutoTArray info; + UINT numEntries; + UINT numEntriesNeeded = 1; + error = ERROR_MORE_DATA; + DWORD reason = RmRebootReasonNone; + while (error == ERROR_MORE_DATA) { + info.SetLength(numEntriesNeeded); + numEntries = numEntriesNeeded; + error = mRmGetList(session.handle(), &numEntriesNeeded, &numEntries, + &info[0], &reason); + } + if (error != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + if (numEntries == 0) { + // Nobody else is locking the file; the other process must have terminated + return NS_OK; + } + + nsresult rv = NS_ERROR_FAILURE; + for (UINT i = 0; i < numEntries; ++i) { + rv = TryToTerminate(info[i].Process); + if (NS_SUCCEEDED(rv)) { + return NS_OK; + } + } + + // If nothing could be unlocked then we return the error code of the last + // failure that was returned. + return rv; +} + +nsresult +ProfileUnlockerWin::TryToTerminate(RM_UNIQUE_PROCESS& aProcess) +{ + // Subtle: If the target process terminated before this call to OpenProcess, + // this call will still succeed. This is because the restart manager session + // internally retains a handle to the target process. The rules for Windows + // PIDs state that the PID of a terminated process remains valid as long as + // at least one handle to that process remains open, so when we reach this + // point the PID is still valid and the process will open successfully. + DWORD accessRights = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE; + nsAutoHandle otherProcess(::OpenProcess(accessRights, FALSE, + aProcess.dwProcessId)); + if (!otherProcess) { + return NS_ERROR_FAILURE; + } + + FILETIME creationTime, exitTime, kernelTime, userTime; + if (!::GetProcessTimes(otherProcess, &creationTime, &exitTime, &kernelTime, + &userTime)) { + return NS_ERROR_FAILURE; + } + if (::CompareFileTime(&aProcess.ProcessStartTime, &creationTime)) { + return NS_ERROR_NOT_AVAILABLE; + } + + WCHAR imageName[MAX_PATH]; + DWORD imageNameLen = MAX_PATH; + if (!mQueryFullProcessImageName(otherProcess, 0, imageName, &imageNameLen)) { + // The error codes for this function are not very descriptive. There are + // actually two failure cases here: Either the call failed because the + // process is no longer running, or it failed for some other reason. We + // need to know which case that is. + DWORD otherProcessExitCode; + if (!::GetExitCodeProcess(otherProcess, &otherProcessExitCode) || + otherProcessExitCode == STILL_ACTIVE) { + // The other process is still running. + return NS_ERROR_FAILURE; + } + // The other process must have terminated. We should return NS_OK so that + // this process may proceed with startup. + return NS_OK; + } + nsCOMPtr otherProcessImageName; + if (NS_FAILED(NS_NewLocalFile(nsDependentString(imageName, imageNameLen), + false, getter_AddRefs(otherProcessImageName)))) { + return NS_ERROR_FAILURE; + } + nsAutoString otherProcessLeafName; + if (NS_FAILED(otherProcessImageName->GetLeafName(otherProcessLeafName))) { + return NS_ERROR_FAILURE; + } + + imageNameLen = MAX_PATH; + if (!mQueryFullProcessImageName(::GetCurrentProcess(), 0, imageName, + &imageNameLen)) { + return NS_ERROR_FAILURE; + } + nsCOMPtr thisProcessImageName; + if (NS_FAILED(NS_NewLocalFile(nsDependentString(imageName, imageNameLen), + false, getter_AddRefs(thisProcessImageName)))) { + return NS_ERROR_FAILURE; + } + nsAutoString thisProcessLeafName; + if (NS_FAILED(thisProcessImageName->GetLeafName(thisProcessLeafName))) { + return NS_ERROR_FAILURE; + } + + // Make sure the image leaf names match + if (_wcsicmp(otherProcessLeafName.get(), thisProcessLeafName.get())) { + return NS_ERROR_NOT_AVAILABLE; + } + + // We know that another process holds the lock and that it shares the same + // image name as our process. Let's kill it. + // Subtle: TerminateProcess returning ERROR_ACCESS_DENIED is actually an + // indicator that the target process managed to shut down on its own. In that + // case we should return NS_OK since we may proceed with startup. + if (!::TerminateProcess(otherProcess, 1) && + ::GetLastError() != ERROR_ACCESS_DENIED) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +} // namespace mozilla + diff --git a/toolkit/profile/ProfileUnlockerWin.h b/toolkit/profile/ProfileUnlockerWin.h new file mode 100644 index 000000000..47c91c913 --- /dev/null +++ b/toolkit/profile/ProfileUnlockerWin.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 ProfileUnlockerWin_h +#define ProfileUnlockerWin_h + +#include +#include + +#include "nsIProfileUnlocker.h" +#include "nsProfileStringTypes.h" +#include "nsWindowsHelpers.h" + +namespace mozilla { + +class ProfileUnlockerWin final : public nsIProfileUnlocker +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROFILEUNLOCKER + + explicit ProfileUnlockerWin(const nsAString& aFileName); + + nsresult Init(); + + DWORD StartSession(DWORD& aHandle); + void EndSession(DWORD aHandle); + +private: + ~ProfileUnlockerWin(); + nsresult TryToTerminate(RM_UNIQUE_PROCESS& aProcess); + +private: + typedef DWORD (WINAPI *RMSTARTSESSION)(DWORD*, DWORD, WCHAR[]); + typedef DWORD (WINAPI *RMREGISTERRESOURCES)(DWORD, UINT, LPCWSTR[], UINT, + RM_UNIQUE_PROCESS[], UINT, + LPCWSTR[]); + typedef DWORD (WINAPI *RMGETLIST)(DWORD, UINT*, UINT*, RM_PROCESS_INFO[], + LPDWORD); + typedef DWORD (WINAPI *RMENDSESSION)(DWORD); + typedef BOOL (WINAPI *QUERYFULLPROCESSIMAGENAME)(HANDLE, DWORD, LPWSTR, PDWORD); + +private: + nsModuleHandle mRestartMgrModule; + RMSTARTSESSION mRmStartSession; + RMREGISTERRESOURCES mRmRegisterResources; + RMGETLIST mRmGetList; + RMENDSESSION mRmEndSession; + QUERYFULLPROCESSIMAGENAME mQueryFullProcessImageName; + + nsString mFileName; +}; + +} // namespace mozilla + +#endif // ProfileUnlockerWin_h + diff --git a/toolkit/profile/content/createProfileWizard.js b/toolkit/profile/content/createProfileWizard.js new file mode 100644 index 000000000..1963f66bc --- /dev/null +++ b/toolkit/profile/content/createProfileWizard.js @@ -0,0 +1,225 @@ +/* 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/. */ + +const C = Components.classes; +const I = Components.interfaces; + +Components.utils.import("resource://gre/modules/AppConstants.jsm"); + +const ToolkitProfileService = "@mozilla.org/toolkit/profile-service;1"; + +var gProfileService; +var gProfileManagerBundle; + +var gDefaultProfileParent; + +// The directory where the profile will be created. +var gProfileRoot; + +// Text node to display the location and name of the profile to create. +var gProfileDisplay; + +// Called once when the wizard is opened. +function initWizard() +{ + try { + gProfileService = C[ToolkitProfileService].getService(I.nsIToolkitProfileService); + gProfileManagerBundle = document.getElementById("bundle_profileManager"); + + var dirService = C["@mozilla.org/file/directory_service;1"].getService(I.nsIProperties); + gDefaultProfileParent = dirService.get("DefProfRt", I.nsIFile); + + // Initialize the profile location display. + gProfileDisplay = document.getElementById("profileDisplay").firstChild; + setDisplayToDefaultFolder(); + } + catch (e) { + window.close(); + throw (e); + } +} + +// Called every time the second wizard page is displayed. +function initSecondWizardPage() +{ + var profileName = document.getElementById("profileName"); + profileName.select(); + profileName.focus(); + + // Initialize profile name validation. + checkCurrentInput(profileName.value); +} + +const kSaltTable = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' ]; + +var kSaltString = ""; +for (var i = 0; i < 8; ++i) { + kSaltString += kSaltTable[Math.floor(Math.random() * kSaltTable.length)]; +} + + +function saltName(aName) +{ + return kSaltString + "." + aName; +} + +function setDisplayToDefaultFolder() +{ + var defaultProfileDir = gDefaultProfileParent.clone(); + defaultProfileDir.append(saltName(document.getElementById("profileName").value)); + gProfileRoot = defaultProfileDir; + document.getElementById("useDefault").disabled = true; +} + +function updateProfileDisplay() +{ + gProfileDisplay.data = gProfileRoot.path; +} + +// Invoke a folder selection dialog for choosing the directory of profile storage. +function chooseProfileFolder() +{ + var newProfileRoot; + + var dirChooser = C["@mozilla.org/filepicker;1"].createInstance(I.nsIFilePicker); + dirChooser.init(window, gProfileManagerBundle.getString("chooseFolder"), + I.nsIFilePicker.modeGetFolder); + dirChooser.appendFilters(I.nsIFilePicker.filterAll); + + // default to the Profiles folder + dirChooser.displayDirectory = gDefaultProfileParent; + + dirChooser.show(); + newProfileRoot = dirChooser.file; + + // Disable the "Default Folder..." button when the default profile folder + // was selected manually in the File Picker. + document.getElementById("useDefault").disabled = + (newProfileRoot.parent.equals(gDefaultProfileParent)); + + gProfileRoot = newProfileRoot; + updateProfileDisplay(); +} + +// Checks the current user input for validity and triggers an error message accordingly. +function checkCurrentInput(currentInput) +{ + var finishButton = document.documentElement.getButton("finish"); + var finishText = document.getElementById("finishText"); + var canAdvance; + + var errorMessage = checkProfileName(currentInput); + + if (!errorMessage) { + finishText.className = ""; + if (AppConstants.platform == "macosx") { + finishText.firstChild.data = gProfileManagerBundle.getString("profileFinishTextMac"); + } + else { + finishText.firstChild.data = gProfileManagerBundle.getString("profileFinishText"); + } + canAdvance = true; + } + else { + finishText.className = "error"; + finishText.firstChild.data = errorMessage; + canAdvance = false; + } + + document.documentElement.canAdvance = canAdvance; + finishButton.disabled = !canAdvance; + + updateProfileDisplay(); + + return canAdvance; +} + +function updateProfileName(aNewName) +{ + if (checkCurrentInput(aNewName)) { + gProfileRoot.leafName = saltName(aNewName); + updateProfileDisplay(); + } +} + +// Checks whether the given string is a valid profile name. +// Returns an error message describing the error in the name or "" when it's valid. +function checkProfileName(profileNameToCheck) +{ + // Check for emtpy profile name. + if (!/\S/.test(profileNameToCheck)) + return gProfileManagerBundle.getString("profileNameEmpty"); + + // Check whether all characters in the profile name are allowed. + if (/([\\*:?<>|\/\"])/.test(profileNameToCheck)) + return gProfileManagerBundle.getFormattedString("invalidChar", [RegExp.$1]); + + // Check whether a profile with the same name already exists. + if (profileExists(profileNameToCheck)) + return gProfileManagerBundle.getString("profileExists"); + + // profileNameToCheck is valid. + return ""; +} + +function profileExists(aName) +{ + var profiles = gProfileService.profiles; + while (profiles.hasMoreElements()) { + var profile = profiles.getNext().QueryInterface(I.nsIToolkitProfile); + if (profile.name.toLowerCase() == aName.toLowerCase()) + return true; + } + + return false; +} + +// Called when the first wizard page is shown. +function enableNextButton() +{ + document.documentElement.canAdvance = true; +} + +function onFinish() +{ + var profileName = document.getElementById("profileName").value; + var profile; + + // Create profile named profileName in profileRoot. + try { + profile = gProfileService.createProfile(gProfileRoot, profileName); + } + catch (e) { + var profileCreationFailed = + gProfileManagerBundle.getString("profileCreationFailed"); + var profileCreationFailedTitle = + gProfileManagerBundle.getString("profileCreationFailedTitle"); + var promptService = C["@mozilla.org/embedcomp/prompt-service;1"]. + getService(I.nsIPromptService); + promptService.alert(window, profileCreationFailedTitle, + profileCreationFailed + "\n" + e); + + return false; + } + + // window.opener is false if the Create Profile Wizard was opened from the + // command line. + if (window.opener) { + // Add new profile to the list in the Profile Manager. + window.opener.CreateProfile(profile); + } + else { + // Use the newly created Profile. + var profileLock = profile.lock(null); + + var dialogParams = window.arguments[0].QueryInterface(I.nsIDialogParamBlock); + dialogParams.objects.insertElementAt(profileLock, 0, false); + } + + // Exit the wizard. + return true; +} diff --git a/toolkit/profile/content/createProfileWizard.xul b/toolkit/profile/content/createProfileWizard.xul new file mode 100644 index 000000000..eab1a9341 --- /dev/null +++ b/toolkit/profile/content/createProfileWizard.xul @@ -0,0 +1,74 @@ + + + + + + +%brandDTD; + +%profileDTD; +]> + + + + + + + + + + Mozilla Bug 543854 + + + + + -- cgit v1.2.3