summaryrefslogtreecommitdiff
path: root/toolkit/profile
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commitad18d877ddd2a44d98fa12ccd3dbbcf4d0ac4299 (patch)
tree10027f336435511475e392454359edea8e25895d /toolkit/profile
parent15477ed9af4859dacb069040b5d4de600803d3bc (diff)
downloadaura-central-ad18d877ddd2a44d98fa12ccd3dbbcf4d0ac4299.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/profile')
-rw-r--r--toolkit/profile/ProfileUnlockerWin.cpp278
-rw-r--r--toolkit/profile/ProfileUnlockerWin.h60
-rw-r--r--toolkit/profile/content/createProfileWizard.js225
-rw-r--r--toolkit/profile/content/createProfileWizard.xul74
-rw-r--r--toolkit/profile/content/profileSelection.js269
-rw-r--r--toolkit/profile/content/profileSelection.xul70
-rw-r--r--toolkit/profile/gtest/TestProfileLock.cpp116
-rw-r--r--toolkit/profile/gtest/moz.build16
-rw-r--r--toolkit/profile/jar.mn9
-rw-r--r--toolkit/profile/moz.build43
-rw-r--r--toolkit/profile/notifications.txt61
-rw-r--r--toolkit/profile/nsIProfileMigrator.idl69
-rw-r--r--toolkit/profile/nsIProfileUnlocker.idl21
-rw-r--r--toolkit/profile/nsIToolkitProfile.idl89
-rw-r--r--toolkit/profile/nsIToolkitProfileService.idl108
-rw-r--r--toolkit/profile/nsProfileLock.cpp661
-rw-r--r--toolkit/profile/nsProfileLock.h95
-rw-r--r--toolkit/profile/nsProfileStringTypes.h32
-rw-r--r--toolkit/profile/nsToolkitProfileService.cpp1117
-rw-r--r--toolkit/profile/test/.eslintrc.js7
-rw-r--r--toolkit/profile/test/chrome.ini3
-rw-r--r--toolkit/profile/test/test_create_profile.xul134
22 files changed, 3557 insertions, 0 deletions
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<RMSTARTSESSION>(::GetProcAddress(module, "RmStartSession"));
+ if (!mRmStartSession) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRmRegisterResources =
+ reinterpret_cast<RMREGISTERRESOURCES>(::GetProcAddress(module,
+ "RmRegisterResources"));
+ if (!mRmRegisterResources) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRmGetList = reinterpret_cast<RMGETLIST>(::GetProcAddress(module,
+ "RmGetList"));
+ if (!mRmGetList) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRmEndSession = reinterpret_cast<RMENDSESSION>(::GetProcAddress(module,
+ "RmEndSession"));
+ if (!mRmEndSession) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mQueryFullProcessImageName =
+ reinterpret_cast<QUERYFULLPROCESSIMAGENAME>(::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<RM_PROCESS_INFO, 1> 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<nsIFile> 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<nsIFile> 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 <windows.h>
+#include <restartmanager.h>
+
+#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 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE wizard [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % profileDTD SYSTEM "chrome://mozapps/locale/profile/createProfileWizard.dtd">
+%profileDTD;
+]>
+
+<wizard id="createProfileWizard"
+ title="&newprofile.title;"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onwizardfinish="return onFinish();"
+ onload="initWizard();"
+ style="&window.size;">
+
+ <stringbundle id="bundle_profileManager"
+ src="chrome://mozapps/locale/profile/profileSelection.properties"/>
+
+ <script type="application/javascript" src="chrome://mozapps/content/profile/createProfileWizard.js"/>
+
+ <wizardpage id="explanation" onpageshow="enableNextButton();">
+ <description>&profileCreationExplanation_1.text;</description>
+ <description>&profileCreationExplanation_2.text;</description>
+ <description>&profileCreationExplanation_3.text;</description>
+ <spacer flex="1"/>
+#ifdef XP_MACOSX
+ <description>&profileCreationExplanation_4Mac.text;</description>
+#else
+#ifdef XP_UNIX
+ <description>&profileCreationExplanation_4Gnome.text;</description>
+#else
+ <description>&profileCreationExplanation_4.text;</description>
+#endif
+#endif
+ </wizardpage>
+
+ <wizardpage id="createProfile" onpageshow="initSecondWizardPage();">
+ <description>&profileCreationIntro.text;</description>
+
+ <label accesskey="&profilePrompt.accesskey;" control="ProfileName">&profilePrompt.label;</label>
+ <textbox id="profileName" value="&profileDefaultName;"
+ oninput="updateProfileName(this.value);"/>
+
+ <separator/>
+
+ <description>&profileDirectoryExplanation.text;</description>
+
+ <vbox class="indent" flex="1" style="overflow: auto;">
+ <description id="profileDisplay">*</description>
+ </vbox>
+
+ <hbox>
+ <button label="&button.choosefolder.label;" oncommand="chooseProfileFolder();"
+ accesskey="&button.choosefolder.accesskey;"/>
+
+ <button id="useDefault" label="&button.usedefault.label;"
+ oncommand="setDisplayToDefaultFolder(); updateProfileDisplay();"
+ accesskey="&button.usedefault.accesskey;" disabled="true"/>
+ </hbox>
+
+ <separator/>
+
+ <description id="finishText">*</description>
+ </wizardpage>
+
+</wizard>
diff --git a/toolkit/profile/content/profileSelection.js b/toolkit/profile/content/profileSelection.js
new file mode 100644
index 000000000..02b9d6873
--- /dev/null
+++ b/toolkit/profile/content/profileSelection.js
@@ -0,0 +1,269 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const C = Components.classes;
+const I = Components.interfaces;
+
+const ToolkitProfileService = "@mozilla.org/toolkit/profile-service;1";
+
+var gDialogParams;
+var gProfileManagerBundle;
+var gBrandBundle;
+var gProfileService;
+
+function startup()
+{
+ try {
+ gDialogParams = window.arguments[0].
+ QueryInterface(I.nsIDialogParamBlock);
+
+ gProfileService = C[ToolkitProfileService].getService(I.nsIToolkitProfileService);
+
+ gProfileManagerBundle = document.getElementById("bundle_profileManager");
+ gBrandBundle = document.getElementById("bundle_brand");
+
+ document.documentElement.centerWindowOnScreen();
+
+ var profilesElement = document.getElementById("profiles");
+
+ var profileList = gProfileService.profiles;
+ while (profileList.hasMoreElements()) {
+ var profile = profileList.getNext().QueryInterface(I.nsIToolkitProfile);
+
+ var listitem = profilesElement.appendItem(profile.name, "");
+
+ var tooltiptext =
+ gProfileManagerBundle.getFormattedString("profileTooltip", [profile.name, profile.rootDir.path]);
+ listitem.setAttribute("tooltiptext", tooltiptext);
+ listitem.setAttribute("class", "listitem-iconic");
+ listitem.profile = profile;
+ try {
+ if (profile === gProfileService.selectedProfile) {
+ setTimeout(function(a) {
+ profilesElement.ensureElementIsVisible(a);
+ profilesElement.selectItem(a);
+ }, 0, listitem);
+ }
+ }
+ catch (e) { }
+ }
+
+ var autoSelectLastProfile = document.getElementById("autoSelectLastProfile");
+ autoSelectLastProfile.checked = gProfileService.startWithLastProfile;
+ profilesElement.focus();
+ }
+ catch (e) {
+ window.close();
+ throw (e);
+ }
+}
+
+function acceptDialog()
+{
+ var appName = gBrandBundle.getString("brandShortName");
+
+ var profilesElement = document.getElementById("profiles");
+ var selectedProfile = profilesElement.selectedItem;
+ if (!selectedProfile) {
+ var pleaseSelectTitle = gProfileManagerBundle.getString("pleaseSelectTitle");
+ var pleaseSelect =
+ gProfileManagerBundle.getFormattedString("pleaseSelect", [appName]);
+ Services.prompt.alert(window, pleaseSelectTitle, pleaseSelect);
+
+ return false;
+ }
+
+ var profileLock;
+
+ try {
+ profileLock = selectedProfile.profile.lock({ value: null });
+ }
+ catch (e) {
+ if (!selectedProfile.profile.rootDir.exists()) {
+ var missingTitle = gProfileManagerBundle.getString("profileMissingTitle");
+ var missing =
+ gProfileManagerBundle.getFormattedString("profileMissing", [appName]);
+ Services.prompt.alert(window, missingTitle, missing);
+ return false;
+ }
+
+ var lockedTitle = gProfileManagerBundle.getString("profileLockedTitle");
+ var locked =
+ gProfileManagerBundle.getFormattedString("profileLocked2", [appName, selectedProfile.profile.name, appName]);
+ Services.prompt.alert(window, lockedTitle, locked);
+
+ return false;
+ }
+ gDialogParams.objects.insertElementAt(profileLock.nsIProfileLock, 0, false);
+
+ gProfileService.selectedProfile = selectedProfile.profile;
+ gProfileService.defaultProfile = selectedProfile.profile;
+ updateStartupPrefs();
+
+ gDialogParams.SetInt(0, 1);
+
+ gDialogParams.SetString(0, selectedProfile.profile.name);
+
+ return true;
+}
+
+function exitDialog()
+{
+ updateStartupPrefs();
+
+ return true;
+}
+
+function updateStartupPrefs()
+{
+ var autoSelectLastProfile = document.getElementById("autoSelectLastProfile");
+ gProfileService.startWithLastProfile = autoSelectLastProfile.checked;
+
+ /* Bug 257777 */
+ gProfileService.startOffline = document.getElementById("offlineState").checked;
+}
+
+// handle key event on listboxes
+function onProfilesKey(aEvent)
+{
+ switch ( aEvent.keyCode )
+ {
+ case KeyEvent.DOM_VK_BACK_SPACE:
+ if (AppConstants.platform != "macosx")
+ break;
+ case KeyEvent.DOM_VK_DELETE:
+ ConfirmDelete();
+ break;
+ case KeyEvent.DOM_VK_F2:
+ RenameProfile();
+ break;
+ }
+}
+
+function onProfilesDblClick(aEvent)
+{
+ if (aEvent.target.localName == "listitem")
+ document.documentElement.acceptDialog();
+}
+
+// invoke the createProfile Wizard
+function CreateProfileWizard()
+{
+ window.openDialog('chrome://mozapps/content/profile/createProfileWizard.xul',
+ '', 'centerscreen,chrome,modal,titlebar', gProfileService);
+}
+
+/**
+ * Called from createProfileWizard to update the display.
+ */
+function CreateProfile(aProfile)
+{
+ var profilesElement = document.getElementById("profiles");
+
+ var listitem = profilesElement.appendItem(aProfile.name, "");
+
+ var tooltiptext =
+ gProfileManagerBundle.getFormattedString("profileTooltip", [aProfile.name, aProfile.rootDir.path]);
+ listitem.setAttribute("tooltiptext", tooltiptext);
+ listitem.setAttribute("class", "listitem-iconic");
+ listitem.profile = aProfile;
+
+ profilesElement.ensureElementIsVisible(listitem);
+ profilesElement.selectItem(listitem);
+}
+
+// rename the selected profile
+function RenameProfile()
+{
+ var profilesElement = document.getElementById("profiles");
+ var selectedItem = profilesElement.selectedItem;
+ if (!selectedItem) {
+ return false;
+ }
+
+ var selectedProfile = selectedItem.profile;
+
+ var oldName = selectedProfile.name;
+ var newName = {value: oldName};
+
+ var dialogTitle = gProfileManagerBundle.getString("renameProfileTitle");
+ var msg =
+ gProfileManagerBundle.getFormattedString("renameProfilePrompt", [oldName]);
+
+ if (Services.prompt.prompt(window, dialogTitle, msg, newName, null, {value:0})) {
+ newName = newName.value;
+
+ // User hasn't changed the profile name. Treat as if cancel was pressed.
+ if (newName == oldName)
+ return false;
+
+ try {
+ selectedProfile.name = newName;
+ }
+ catch (e) {
+ var alTitle = gProfileManagerBundle.getString("profileNameInvalidTitle");
+ var alMsg = gProfileManagerBundle.getFormattedString("profileNameInvalid", [newName]);
+ Services.prompt.alert(window, alTitle, alMsg);
+ return false;
+ }
+
+ selectedItem.label = newName;
+ var tiptext = gProfileManagerBundle.
+ getFormattedString("profileTooltip",
+ [newName, selectedProfile.rootDir.path]);
+ selectedItem.setAttribute("tooltiptext", tiptext);
+
+ return true;
+ }
+
+ return false;
+}
+
+function ConfirmDelete()
+{
+ var deleteButton = document.getElementById("delbutton");
+ var profileList = document.getElementById( "profiles" );
+
+ var selectedItem = profileList.selectedItem;
+ if (!selectedItem) {
+ return false;
+ }
+
+ var selectedProfile = selectedItem.profile;
+ var deleteFiles = false;
+
+ if (selectedProfile.rootDir.exists()) {
+ var dialogTitle = gProfileManagerBundle.getString("deleteTitle");
+ var dialogText =
+ gProfileManagerBundle.getFormattedString("deleteProfileConfirm",
+ [selectedProfile.rootDir.path]);
+
+ var buttonPressed = Services.prompt.confirmEx(window, dialogTitle, dialogText,
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1) +
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2),
+ gProfileManagerBundle.getString("dontDeleteFiles"),
+ null,
+ gProfileManagerBundle.getString("deleteFiles"),
+ null, {value:0});
+ if (buttonPressed == 1)
+ return false;
+
+ if (buttonPressed == 2)
+ deleteFiles = true;
+ }
+
+ selectedProfile.remove(deleteFiles);
+ profileList.removeChild(selectedItem);
+ if (profileList.firstChild != undefined) {
+ profileList.selectItem(profileList.firstChild);
+ }
+
+ return true;
+}
diff --git a/toolkit/profile/content/profileSelection.xul b/toolkit/profile/content/profileSelection.xul
new file mode 100644
index 000000000..e5dfabb42
--- /dev/null
+++ b/toolkit/profile/content/profileSelection.xul
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
+<!--
+
+ 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/. -->
+
+<?xml-stylesheet href="chrome://mozapps/skin/profile/profileSelection.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % profileDTD SYSTEM "chrome://mozapps/locale/profile/profileSelection.dtd">
+%profileDTD;
+]>
+
+<dialog
+ id="profileWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="non-resizable"
+ title="&windowtitle.label;"
+ orient="vertical"
+ buttons="accept,cancel"
+ style="width: 30em;"
+ onload="startup();"
+ ondialogaccept="return acceptDialog()"
+ ondialogcancel="return exitDialog()"
+ buttonlabelaccept="&start.label;"
+ buttonlabelcancel="&exit.label;">
+
+ <stringbundle id="bundle_profileManager"
+ src="chrome://mozapps/locale/profile/profileSelection.properties"/>
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+
+ <script type="application/javascript" src="chrome://mozapps/content/profile/profileSelection.js"/>
+
+ <description class="label">&pmDescription.label;</description>
+
+ <separator class="thin"/>
+
+ <hbox class="profile-box indent" flex="1">
+
+ <vbox id="managebuttons">
+ <button id="newbutton" label="&newButton.label;"
+ accesskey="&newButton.accesskey;" oncommand="CreateProfileWizard();"/>
+ <button id="renbutton" label="&renameButton.label;"
+ accesskey="&renameButton.accesskey;" oncommand="RenameProfile();"/>
+ <button id="delbutton" label="&deleteButton.label;"
+ accesskey="&deleteButton.accesskey;" oncommand="ConfirmDelete();"/>
+ </vbox>
+
+ <separator flex="1"/>
+
+ <vbox flex="1">
+ <listbox id="profiles" rows="5" seltype="single"
+ ondblclick="onProfilesDblClick(event)"
+ onkeypress="onProfilesKey(event);">
+ </listbox>
+
+ <!-- Bug 257777 -->
+ <checkbox id="offlineState" label="&offlineState.label;" accesskey="&offlineState.accesskey;"/>
+
+ <checkbox id="autoSelectLastProfile" label="&useSelected.label;"
+ accesskey="&useSelected.accesskey;"/>
+ </vbox>
+
+ </hbox>
+</dialog>
diff --git a/toolkit/profile/gtest/TestProfileLock.cpp b/toolkit/profile/gtest/TestProfileLock.cpp
new file mode 100644
index 000000000..ac5117d74
--- /dev/null
+++ b/toolkit/profile/gtest/TestProfileLock.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; 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 "gtest/gtest.h"
+
+#include <sys/eventfd.h>
+#include <sched.h>
+
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsProfileLock.h"
+#include "nsString.h"
+
+static void CleanParentLock(const char *tmpdir)
+{
+ // nsProfileLock doesn't clean up all its files
+ char permanent_lockfile[] = "/.parentlock";
+
+ char * parentlock_name;
+ size_t parentlock_name_size = strlen(tmpdir) + strlen(permanent_lockfile) + 1;
+ parentlock_name = (char*)malloc(parentlock_name_size);
+
+ strcpy(parentlock_name, tmpdir);
+ strcat(parentlock_name, permanent_lockfile);
+
+ EXPECT_EQ(0, unlink(parentlock_name));
+ EXPECT_EQ(0, rmdir(tmpdir));
+ free(parentlock_name);
+}
+
+TEST(ProfileLock, BasicLock)
+{
+ char templ[] = "/tmp/profilelocktest.XXXXXX";
+ char * tmpdir = mkdtemp(templ);
+ ASSERT_NE(tmpdir, nullptr);
+
+ // This scope exits so the nsProfileLock destructor
+ // can clean up the files it leaves in tmpdir.
+ {
+ nsProfileLock myLock;
+ nsresult rv;
+ nsCOMPtr<nsIFile> dir(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ ASSERT_EQ(NS_SUCCEEDED(rv), true);
+
+ rv = dir->InitWithNativePath(nsCString(tmpdir));
+ ASSERT_EQ(NS_SUCCEEDED(rv), true);
+
+ rv = myLock.Lock(dir, nullptr);
+ EXPECT_EQ(NS_SUCCEEDED(rv), true);
+ }
+
+ CleanParentLock(tmpdir);
+}
+
+TEST(ProfileLock, RetryLock)
+{
+ char templ[] = "/tmp/profilelocktest.XXXXXX";
+ char * tmpdir = mkdtemp(templ);
+ ASSERT_NE(tmpdir, nullptr);
+
+ {
+ nsProfileLock myLock;
+ nsProfileLock myLock2;
+ nsresult rv;
+ nsCOMPtr<nsIFile> dir(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ ASSERT_EQ(NS_SUCCEEDED(rv), true);
+
+ rv = dir->InitWithNativePath(nsCString(tmpdir));
+ ASSERT_EQ(NS_SUCCEEDED(rv), true);
+
+ int eventfd_fd = eventfd(0, 0);
+ ASSERT_GE(eventfd_fd, 0);
+
+ // fcntl advisory locks are per process, so it's hard
+ // to avoid using fork here.
+ pid_t childpid = fork();
+
+ if (childpid) {
+ // parent
+
+ // blocking read causing us to lose the race
+ eventfd_t value;
+ EXPECT_EQ(0, eventfd_read(eventfd_fd, &value));
+
+ rv = myLock2.Lock(dir, nullptr);
+ EXPECT_EQ(NS_ERROR_FILE_ACCESS_DENIED, rv);
+
+ // kill the child
+ EXPECT_EQ(0, kill(childpid, SIGTERM));
+
+ // reap zombie (required to collect the lock)
+ int status;
+ EXPECT_EQ(childpid, waitpid(childpid, &status, 0));
+
+ rv = myLock2.Lock(dir, nullptr);
+ EXPECT_EQ(NS_SUCCEEDED(rv), true);
+ } else {
+ // child
+ rv = myLock.Lock(dir, nullptr);
+ ASSERT_EQ(NS_SUCCEEDED(rv), true);
+
+ // unblock parent
+ EXPECT_EQ(0, eventfd_write(eventfd_fd, 1));
+
+ // parent will kill us
+ for (;;)
+ sleep(1);
+ }
+
+ close(eventfd_fd);
+ }
+
+ CleanParentLock(tmpdir);
+}
diff --git a/toolkit/profile/gtest/moz.build b/toolkit/profile/gtest/moz.build
new file mode 100644
index 000000000..51b420e19
--- /dev/null
+++ b/toolkit/profile/gtest/moz.build
@@ -0,0 +1,16 @@
+# -*- 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/.
+
+LOCAL_INCLUDES += [
+ '..',
+]
+
+if CONFIG['OS_ARCH'] == 'Linux':
+ UNIFIED_SOURCES += [
+ 'TestProfileLock.cpp',
+ ]
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/toolkit/profile/jar.mn b/toolkit/profile/jar.mn
new file mode 100644
index 000000000..9b7c22266
--- /dev/null
+++ b/toolkit/profile/jar.mn
@@ -0,0 +1,9 @@
+# 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/.
+
+toolkit.jar:
+ content/mozapps/profile/createProfileWizard.js (content/createProfileWizard.js)
+* content/mozapps/profile/createProfileWizard.xul (content/createProfileWizard.xul)
+ content/mozapps/profile/profileSelection.js (content/profileSelection.js)
+ content/mozapps/profile/profileSelection.xul (content/profileSelection.xul)
diff --git a/toolkit/profile/moz.build b/toolkit/profile/moz.build
new file mode 100644
index 000000000..b2383a871
--- /dev/null
+++ b/toolkit/profile/moz.build
@@ -0,0 +1,43 @@
+# -*- 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/.
+
+MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
+
+if CONFIG['ENABLE_TESTS']:
+ DIRS += ['gtest']
+
+XPIDL_SOURCES += [
+ 'nsIProfileMigrator.idl',
+ 'nsIProfileUnlocker.idl',
+ 'nsIToolkitProfile.idl',
+ 'nsIToolkitProfileService.idl',
+]
+
+XPIDL_MODULE = 'toolkitprofile'
+
+UNIFIED_SOURCES += [
+ 'nsProfileLock.cpp'
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ UNIFIED_SOURCES += [
+ 'ProfileUnlockerWin.cpp'
+ ]
+
+UNIFIED_SOURCES += [
+ 'nsToolkitProfileService.cpp'
+]
+
+LOCAL_INCLUDES += [
+ '../xre',
+]
+
+FINAL_LIBRARY = 'xul'
+
+JAR_MANIFESTS += ['jar.mn']
+
+with Files('**'):
+ BUG_COMPONENT = ('Toolkit', 'Startup and Profile System')
diff --git a/toolkit/profile/notifications.txt b/toolkit/profile/notifications.txt
new file mode 100644
index 000000000..eab1e2c34
--- /dev/null
+++ b/toolkit/profile/notifications.txt
@@ -0,0 +1,61 @@
+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/.
+
+nsIObserver topics for profile changing. Profile changing happens in phases
+in the order given below. An observer may register separately for each phase
+of the process depending on its needs.
+
+"profile-change-teardown"
+ All async activity must be stopped in this phase. Typically,
+ the application level observer will close all open windows.
+ This is the last phase in which the subject's vetoChange()
+ method may still be called.
+ The next notification will be either
+ profile-change-teardown-veto or profile-before-change.
+
+"profile-before-change"
+ Called before the profile has changed. Use this notification
+ to prepare for the profile going away. If a component is
+ holding any state which needs to be flushed to a profile-relative
+ location, it should be done here.
+
+"profile-do-change"
+ Called after the profile has changed. Do the work to
+ respond to having a new profile. Any change which
+ affects others must be done in this phase.
+
+"profile-after-change"
+ Called after the profile has changed. Use this notification
+ to make changes that are dependent on what some other listener
+ did during its profile-do-change. For example, to respond to
+ new preferences.
+
+"profile-initial-state"
+ Called after all phases of a change have completed. Typically
+ in this phase, an application level observer will open a new window.
+
+Contexts for profile changes. These are passed as the someData param to the
+observer's Observe() method.
+
+"startup"
+ Going from no profile to a profile.
+ The following topics happen in this context:
+ profile-do-change
+ profile-after-change
+
+"shutdown-persist"
+ The user is logging out and whatever data the observer stores
+ for the current profile should be released from memory and
+ saved to disk.
+ The following topics happen in this context:
+ profile-change-net-teardown
+ profile-change-teardown
+ profile-before-change
+
+See https://wiki.mozilla.org/XPCOM_Shutdown for more details about the shutdown
+process.
+
+NOTE: Long ago there was be a "shutdown-cleanse" version of shutdown which was
+intended to clear profile data. This is no longer sent and observer code should
+remove support for it.
diff --git a/toolkit/profile/nsIProfileMigrator.idl b/toolkit/profile/nsIProfileMigrator.idl
new file mode 100644
index 000000000..e2351ca9b
--- /dev/null
+++ b/toolkit/profile/nsIProfileMigrator.idl
@@ -0,0 +1,69 @@
+/* -*- 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"
+interface nsIFile;
+
+/**
+ * Helper interface for nsIProfileMigrator.
+ *
+ * @provider Toolkit (Startup code)
+ * @client Application (Profile-migration code)
+ * @obtainable nsIProfileMigrator.migrate
+ */
+[scriptable, uuid(048e5ca1-0eb7-4bb1-a9a2-a36f7d4e0e3c)]
+interface nsIProfileStartup : nsISupports
+{
+ /**
+ * The root directory of the semi-current profile, during profile migration.
+ * After nsIProfileMigrator.migrate has returned, this object will not be
+ * useful.
+ */
+ readonly attribute nsIFile directory;
+
+ /**
+ * Do profile-startup by setting NS_APP_USER_PROFILE_50_DIR in the directory
+ * service and notifying the profile-startup observer topics.
+ */
+ void doStartup();
+};
+
+/**
+ * Migrate application settings from an outside source.
+ *
+ * @provider Application (Profile-migration code)
+ * @client Toolkit (Startup code)
+ * @obtainable service, contractid("@mozilla.org/toolkit/profile-migrator;1")
+ */
+[scriptable, uuid(3df284a5-2258-4d46-a664-761ecdc04c22)]
+interface nsIProfileMigrator : nsISupports
+{
+ /**
+ * Migrate data from an outside source, if possible. Does nothing
+ * otherwise.
+ *
+ * When this method is called, a default profile has been created;
+ * XPCOM has been initialized such that compreg.dat is in the
+ * profile; the directory service does *not* return a key for
+ * NS_APP_USER_PROFILE_50_DIR or any of the keys depending on an active
+ * profile. To figure out the directory of the "current" profile, use
+ * aStartup.directory.
+ *
+ * If your migrator needs to access services that use the profile (to
+ * set profile prefs or bookmarks, for example), use aStartup.doStartup.
+ *
+ * @param aStartup nsIProfileStartup object to use during migration.
+ * @param aKey optional key of a migrator to use to skip source selection.
+ * @param aProfileName optional name of the profile to use for migration.
+ *
+ * @note The startup code ignores COM exceptions thrown from this method.
+ */
+ void migrate(in nsIProfileStartup aStartup, in ACString aKey,
+ [optional] in ACString aProfileName);
+};
+
+%{C++
+#define NS_PROFILEMIGRATOR_CONTRACTID "@mozilla.org/toolkit/profile-migrator;1"
+%}
diff --git a/toolkit/profile/nsIProfileUnlocker.idl b/toolkit/profile/nsIProfileUnlocker.idl
new file mode 100644
index 000000000..cd1a71051
--- /dev/null
+++ b/toolkit/profile/nsIProfileUnlocker.idl
@@ -0,0 +1,21 @@
+/* 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"
+
+[scriptable, uuid(08923af1-e7a3-4fae-ba02-128502193994)]
+interface nsIProfileUnlocker : nsISupports
+{
+ const unsigned long ATTEMPT_QUIT = 0;
+ const unsigned long FORCE_QUIT = 1;
+
+ /**
+ * Try to unlock the specified profile by attempting or forcing the
+ * process that currently holds the lock to quit.
+ *
+ * @param aSeverity either ATTEMPT_QUIT or FORCE_QUIT
+ * @throws NS_ERROR_FAILURE if unlocking failed.
+ */
+ void unlock(in unsigned long aSeverity);
+};
diff --git a/toolkit/profile/nsIToolkitProfile.idl b/toolkit/profile/nsIToolkitProfile.idl
new file mode 100644
index 000000000..8d0c07c51
--- /dev/null
+++ b/toolkit/profile/nsIToolkitProfile.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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"
+
+interface nsIFile;
+interface nsIProfileUnlocker;
+
+/**
+ * Hold on to a profile lock. Once you release the last reference to this
+ * interface, the profile lock is released.
+ */
+[scriptable, uuid(7c58c703-d245-4864-8d75-9648ca4a6139)]
+interface nsIProfileLock : nsISupports
+{
+ /**
+ * The main profile directory.
+ */
+ readonly attribute nsIFile directory;
+
+ /**
+ * A directory corresponding to the main profile directory that exists for
+ * the purpose of storing data on the local filesystem, including cache
+ * files or other data files that may not represent critical user data.
+ * (e.g., this directory may not be included as part of a backup scheme.)
+ *
+ * In some cases, this directory may just be the main profile directory.
+ */
+ readonly attribute nsIFile localDirectory;
+
+ /**
+ * The timestamp of an existing profile lock at lock time.
+ */
+ readonly attribute PRTime replacedLockTime;
+
+ /**
+ * Unlock the profile.
+ */
+ void unlock();
+};
+
+/**
+ * A interface representing a profile.
+ * @note THIS INTERFACE SHOULD BE IMPLEMENTED BY THE TOOLKIT CODE ONLY! DON'T
+ * EVEN THINK ABOUT IMPLEMENTING THIS IN JAVASCRIPT!
+ */
+[scriptable, uuid(7422b090-4a86-4407-972e-75468a625388)]
+interface nsIToolkitProfile : nsISupports
+{
+ /**
+ * The location of the profile directory.
+ */
+ readonly attribute nsIFile rootDir;
+
+ /**
+ * The location of the profile local directory, which may be the same as
+ * the root directory. See nsIProfileLock::localDirectory.
+ */
+ readonly attribute nsIFile localDir;
+
+ /**
+ * The name of the profile.
+ */
+ attribute AUTF8String name;
+
+ /**
+ * Removes the profile from the registry of profiles.
+ *
+ * @param removeFiles
+ * Indicates whether or not the profile directory should be
+ * removed in addition.
+ */
+ void remove(in boolean removeFiles);
+
+ /**
+ * Lock this profile using platform-specific locking methods.
+ *
+ * @param lockFile If locking fails, this may return a lockFile object
+ * which can be used in platform-specific ways to
+ * determine which process has the file locked. Null
+ * may be passed.
+ * @return An interface which holds a profile lock as long as you reference
+ * it.
+ * @throws NS_ERROR_FILE_ACCESS_DENIED if the profile was already locked.
+ */
+ nsIProfileLock lock(out nsIProfileUnlocker aUnlocker);
+};
diff --git a/toolkit/profile/nsIToolkitProfileService.idl b/toolkit/profile/nsIToolkitProfileService.idl
new file mode 100644
index 000000000..46a5b3cbc
--- /dev/null
+++ b/toolkit/profile/nsIToolkitProfileService.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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"
+
+interface nsISimpleEnumerator;
+interface nsIFile;
+interface nsIToolkitProfile;
+interface nsIProfileLock;
+
+[scriptable, uuid(1947899b-f369-48fa-89da-f7c37bb1e6bc)]
+interface nsIToolkitProfileService : nsISupports
+{
+ attribute boolean startWithLastProfile;
+ attribute boolean startOffline;
+
+ readonly attribute nsISimpleEnumerator /*nsIToolkitProfile*/ profiles;
+
+ /**
+ * The currently selected profile (the one used or about to be used by the
+ * browser).
+ */
+ attribute nsIToolkitProfile selectedProfile;
+
+ /**
+ * The default profile (the one used or about to be used by the
+ * browser if no other profile is specified at runtime). This is the profile
+ * marked with Default=1 in profiles.ini and is usually the same as
+ * selectedProfile, except on Developer Edition.
+ *
+ * Developer Edition uses a profile named "dev-edition-default" as the
+ * default profile (which it creates if it doesn't exist), unless a special
+ * empty file named "ignore-dev-edition-profile" is present next to
+ * profiles.ini. In that case Developer Edition behaves the same as any
+ * other build of Firefox.
+ */
+ attribute nsIToolkitProfile defaultProfile;
+
+ /**
+ * Get a profile by name. This is mainly for use by the -P
+ * commandline flag.
+ *
+ * @param aName The profile name to find.
+ */
+ nsIToolkitProfile getProfileByName(in AUTF8String aName);
+
+ /**
+ * Lock an arbitrary path as a profile. If the path does not exist, it
+ * will be created and the defaults copied from the application directory.
+ */
+ nsIProfileLock lockProfilePath(in nsIFile aDirectory,
+ in nsIFile aTempDirectory);
+
+ /**
+ * Create a new profile.
+ *
+ * The profile temporary directory will be chosen based on where the
+ * profile directory is located.
+ *
+ * @param aRootDir
+ * The profile directory. May be null, in which case a suitable
+ * default will be chosen based on the profile name.
+ * @param aName
+ * The profile name.
+ */
+ nsIToolkitProfile createProfile(in nsIFile aRootDir,
+ in AUTF8String aName);
+
+ /**
+ * Create the default profile for an application.
+ *
+ * The profile will be typically in
+ * {Application Data}/.profilename/{salt}.default or
+ * {Application Data}/.appname/{salt}.default
+ * or if aVendorName is provided
+ * {Application Data}/.vendor/appname/{salt}.default
+ *
+ * @note Either aProfileName or aAppName must be non-empty
+ *
+ * @param aProfileName
+ * The name of the profile
+ * @param aAppName
+ * The name of the application
+ * @param aVendorName
+ * The name of the vendor
+ * @return The created profile.
+ */
+ nsIToolkitProfile createDefaultProfileForApp(in AUTF8String aProfileName,
+ in AUTF8String aAppName,
+ in AUTF8String aVendorName);
+
+ /**
+ * Returns the number of profiles.
+ * @return 0, 1, or 2. More than 2 profiles will always return 2.
+ */
+ readonly attribute unsigned long profileCount;
+
+ /**
+ * Flush the profiles list file.
+ */
+ void flush();
+};
+
+%{C++
+#define NS_PROFILESERVICE_CONTRACTID "@mozilla.org/toolkit/profile-service;1"
+%}
diff --git a/toolkit/profile/nsProfileLock.cpp b/toolkit/profile/nsProfileLock.cpp
new file mode 100644
index 000000000..08d109224
--- /dev/null
+++ b/toolkit/profile/nsProfileLock.cpp
@@ -0,0 +1,661 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsProfileStringTypes.h"
+#include "nsProfileLock.h"
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+
+#if defined(XP_WIN)
+#include "ProfileUnlockerWin.h"
+#include "nsAutoPtr.h"
+#endif
+
+#if defined(XP_MACOSX)
+#include <Carbon/Carbon.h>
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#ifdef XP_UNIX
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include "prnetdb.h"
+#include "prsystem.h"
+#include "prprf.h"
+#include "prenv.h"
+#endif
+
+#if defined(MOZ_WIDGET_GONK) && !defined(MOZ_CRASHREPORTER)
+#include <sys/syscall.h>
+#endif
+
+// **********************************************************************
+// class nsProfileLock
+//
+// This code was moved from profile/src/nsProfileAccess.
+// **********************************************************************
+
+#if defined (XP_UNIX)
+static bool sDisableSignalHandling = false;
+#endif
+
+nsProfileLock::nsProfileLock() :
+ mHaveLock(false),
+ mReplacedLockTime(0)
+#if defined (XP_WIN)
+ ,mLockFileHandle(INVALID_HANDLE_VALUE)
+#elif defined (XP_UNIX)
+ ,mPidLockFileName(nullptr)
+ ,mLockFileDesc(-1)
+#endif
+{
+#if defined (XP_UNIX)
+ next = prev = this;
+ sDisableSignalHandling = PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ? true : false;
+#endif
+}
+
+
+nsProfileLock::nsProfileLock(nsProfileLock& src)
+{
+ *this = src;
+}
+
+
+nsProfileLock& nsProfileLock::operator=(nsProfileLock& rhs)
+{
+ Unlock();
+
+ mHaveLock = rhs.mHaveLock;
+ rhs.mHaveLock = false;
+
+#if defined (XP_WIN)
+ mLockFileHandle = rhs.mLockFileHandle;
+ rhs.mLockFileHandle = INVALID_HANDLE_VALUE;
+#elif defined (XP_UNIX)
+ mLockFileDesc = rhs.mLockFileDesc;
+ rhs.mLockFileDesc = -1;
+ mPidLockFileName = rhs.mPidLockFileName;
+ rhs.mPidLockFileName = nullptr;
+ if (mPidLockFileName)
+ {
+ // rhs had a symlink lock, therefore it was on the list.
+ PR_REMOVE_LINK(&rhs);
+ PR_APPEND_LINK(this, &mPidLockList);
+ }
+#endif
+
+ return *this;
+}
+
+
+nsProfileLock::~nsProfileLock()
+{
+ Unlock();
+}
+
+
+#if defined (XP_UNIX)
+
+static int setupPidLockCleanup;
+
+PRCList nsProfileLock::mPidLockList =
+ PR_INIT_STATIC_CLIST(&nsProfileLock::mPidLockList);
+
+void nsProfileLock::RemovePidLockFiles(bool aFatalSignal)
+{
+ while (!PR_CLIST_IS_EMPTY(&mPidLockList))
+ {
+ nsProfileLock *lock = static_cast<nsProfileLock*>(mPidLockList.next);
+ lock->Unlock(aFatalSignal);
+ }
+}
+
+static struct sigaction SIGHUP_oldact;
+static struct sigaction SIGINT_oldact;
+static struct sigaction SIGQUIT_oldact;
+static struct sigaction SIGILL_oldact;
+static struct sigaction SIGABRT_oldact;
+static struct sigaction SIGSEGV_oldact;
+static struct sigaction SIGTERM_oldact;
+
+void nsProfileLock::FatalSignalHandler(int signo
+#ifdef SA_SIGINFO
+ , siginfo_t *info, void *context
+#endif
+ )
+{
+ // Remove any locks still held.
+ RemovePidLockFiles(true);
+
+ // Chain to the old handler, which may exit.
+ struct sigaction *oldact = nullptr;
+
+ switch (signo) {
+ case SIGHUP:
+ oldact = &SIGHUP_oldact;
+ break;
+ case SIGINT:
+ oldact = &SIGINT_oldact;
+ break;
+ case SIGQUIT:
+ oldact = &SIGQUIT_oldact;
+ break;
+ case SIGILL:
+ oldact = &SIGILL_oldact;
+ break;
+ case SIGABRT:
+ oldact = &SIGABRT_oldact;
+ break;
+ case SIGSEGV:
+ oldact = &SIGSEGV_oldact;
+ break;
+ case SIGTERM:
+ oldact = &SIGTERM_oldact;
+ break;
+ default:
+ NS_NOTREACHED("bad signo");
+ break;
+ }
+
+ if (oldact) {
+ if (oldact->sa_handler == SIG_DFL) {
+ // Make sure the default sig handler is executed
+ // We need it to get Mozilla to dump core.
+ sigaction(signo,oldact, nullptr);
+
+ // Now that we've restored the default handler, unmask the
+ // signal and invoke it.
+
+ sigset_t unblock_sigs;
+ sigemptyset(&unblock_sigs);
+ sigaddset(&unblock_sigs, signo);
+
+ sigprocmask(SIG_UNBLOCK, &unblock_sigs, nullptr);
+
+ raise(signo);
+ }
+#ifdef SA_SIGINFO
+ else if (oldact->sa_sigaction &&
+ (oldact->sa_flags & SA_SIGINFO) == SA_SIGINFO) {
+ oldact->sa_sigaction(signo, info, context);
+ }
+#endif
+ else if (oldact->sa_handler && oldact->sa_handler != SIG_IGN)
+ {
+ oldact->sa_handler(signo);
+ }
+ }
+
+#ifdef MOZ_WIDGET_GONK
+ switch (signo) {
+ case SIGQUIT:
+ case SIGILL:
+ case SIGABRT:
+ case SIGSEGV:
+#ifndef MOZ_CRASHREPORTER
+ // Retrigger the signal for those that can generate a core dump
+ signal(signo, SIG_DFL);
+ if (info->si_code <= 0) {
+ if (syscall(__NR_tgkill, getpid(), syscall(__NR_gettid), signo) < 0) {
+ break;
+ }
+ }
+#endif
+ return;
+ default:
+ break;
+ }
+#endif
+
+ // Backstop exit call, just in case.
+ _exit(signo);
+}
+
+nsresult nsProfileLock::LockWithFcntl(nsIFile *aLockFile)
+{
+ nsresult rv = NS_OK;
+
+ nsAutoCString lockFilePath;
+ rv = aLockFile->GetNativePath(lockFilePath);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Could not get native path");
+ return rv;
+ }
+
+ aLockFile->GetLastModifiedTime(&mReplacedLockTime);
+
+ mLockFileDesc = open(lockFilePath.get(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (mLockFileDesc != -1)
+ {
+ struct flock lock;
+ lock.l_start = 0;
+ lock.l_len = 0; // len = 0 means entire file
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+
+ // If fcntl(F_GETLK) fails then the server does not support/allow fcntl(),
+ // return failure rather than access denied in this case so we fallback
+ // to using a symlink lock, bug 303633.
+ struct flock testlock = lock;
+ if (fcntl(mLockFileDesc, F_GETLK, &testlock) == -1)
+ {
+ close(mLockFileDesc);
+ mLockFileDesc = -1;
+ rv = NS_ERROR_FAILURE;
+ }
+ else if (fcntl(mLockFileDesc, F_SETLK, &lock) == -1)
+ {
+ close(mLockFileDesc);
+ mLockFileDesc = -1;
+
+ // With OS X, on NFS, errno == ENOTSUP
+ // XXX Check for that and return specific rv for it?
+#ifdef DEBUG
+ printf("fcntl(F_SETLK) failed. errno = %d\n", errno);
+#endif
+ if (errno == EAGAIN || errno == EACCES)
+ rv = NS_ERROR_FILE_ACCESS_DENIED;
+ else
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ else
+ {
+ NS_ERROR("Failed to open lock file.");
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+static bool IsSymlinkStaleLock(struct in_addr* aAddr, const char* aFileName,
+ bool aHaveFcntlLock)
+{
+ // the link exists; see if it's from this machine, and if
+ // so if the process is still active
+ char buf[1024];
+ int len = readlink(aFileName, buf, sizeof buf - 1);
+ if (len > 0)
+ {
+ buf[len] = '\0';
+ char *colon = strchr(buf, ':');
+ if (colon)
+ {
+ *colon++ = '\0';
+ unsigned long addr = inet_addr(buf);
+ if (addr != (unsigned long) -1)
+ {
+ if (colon[0] == '+' && aHaveFcntlLock) {
+ // This lock was placed by a Firefox build which would have
+ // taken the fnctl lock, and we've already taken the fcntl lock,
+ // so the process that created this obsolete lock must be gone
+ return true;
+ }
+
+ char *after = nullptr;
+ pid_t pid = strtol(colon, &after, 0);
+ if (pid != 0 && *after == '\0')
+ {
+ if (addr != aAddr->s_addr)
+ {
+ // Remote lock: give up even if stuck.
+ return false;
+ }
+
+ // kill(pid,0) is a neat trick to check if a
+ // process exists
+ if (kill(pid, 0) == 0 || errno != ESRCH)
+ {
+ // Local process appears to be alive, ass-u-me it
+ // is another Mozilla instance, or a compatible
+ // derivative, that's currently using the profile.
+ // XXX need an "are you Mozilla?" protocol
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+nsresult nsProfileLock::LockWithSymlink(nsIFile *aLockFile, bool aHaveFcntlLock)
+{
+ nsresult rv;
+ nsAutoCString lockFilePath;
+ rv = aLockFile->GetNativePath(lockFilePath);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Could not get native path");
+ return rv;
+ }
+
+ // don't replace an existing lock time if fcntl already got one
+ if (!mReplacedLockTime)
+ aLockFile->GetLastModifiedTimeOfLink(&mReplacedLockTime);
+
+ struct in_addr inaddr;
+ inaddr.s_addr = htonl(INADDR_LOOPBACK);
+
+ char hostname[256];
+ PRStatus status = PR_GetSystemInfo(PR_SI_HOSTNAME, hostname, sizeof hostname);
+ if (status == PR_SUCCESS)
+ {
+ char netdbbuf[PR_NETDB_BUF_SIZE];
+ PRHostEnt hostent;
+ status = PR_GetHostByName(hostname, netdbbuf, sizeof netdbbuf, &hostent);
+ if (status == PR_SUCCESS)
+ memcpy(&inaddr, hostent.h_addr, sizeof inaddr);
+ }
+
+ char *signature =
+ PR_smprintf("%s:%s%lu", inet_ntoa(inaddr), aHaveFcntlLock ? "+" : "",
+ (unsigned long)getpid());
+ const char *fileName = lockFilePath.get();
+ int symlink_rv, symlink_errno = 0, tries = 0;
+
+ // use ns4.x-compatible symlinks if the FS supports them
+ while ((symlink_rv = symlink(signature, fileName)) < 0)
+ {
+ symlink_errno = errno;
+ if (symlink_errno != EEXIST)
+ break;
+
+ if (!IsSymlinkStaleLock(&inaddr, fileName, aHaveFcntlLock))
+ break;
+
+ // Lock seems to be bogus: try to claim it. Give up after a large
+ // number of attempts (100 comes from the 4.x codebase).
+ (void) unlink(fileName);
+ if (++tries > 100)
+ break;
+ }
+
+ PR_smprintf_free(signature);
+ signature = nullptr;
+
+ if (symlink_rv == 0)
+ {
+ // We exclusively created the symlink: record its name for eventual
+ // unlock-via-unlink.
+ rv = NS_OK;
+ mPidLockFileName = strdup(fileName);
+ if (mPidLockFileName)
+ {
+ PR_APPEND_LINK(this, &mPidLockList);
+ if (!setupPidLockCleanup++)
+ {
+ // Clean up on normal termination.
+ // This instanciates a dummy class, and will trigger the class
+ // destructor when libxul is unloaded. This is equivalent to atexit(),
+ // but gracefully handles dlclose().
+ static RemovePidLockFilesExiting r;
+
+ // Clean up on abnormal termination, using POSIX sigaction.
+ // Don't arm a handler if the signal is being ignored, e.g.,
+ // because mozilla is run via nohup.
+ if (!sDisableSignalHandling) {
+ struct sigaction act, oldact;
+#ifdef SA_SIGINFO
+ act.sa_sigaction = FatalSignalHandler;
+ act.sa_flags = SA_SIGINFO;
+#else
+ act.sa_handler = FatalSignalHandler;
+#endif
+ sigfillset(&act.sa_mask);
+
+#define CATCH_SIGNAL(signame) \
+PR_BEGIN_MACRO \
+ if (sigaction(signame, nullptr, &oldact) == 0 && \
+ oldact.sa_handler != SIG_IGN) \
+ { \
+ sigaction(signame, &act, &signame##_oldact); \
+ } \
+ PR_END_MACRO
+
+ CATCH_SIGNAL(SIGHUP);
+ CATCH_SIGNAL(SIGINT);
+ CATCH_SIGNAL(SIGQUIT);
+ CATCH_SIGNAL(SIGILL);
+ CATCH_SIGNAL(SIGABRT);
+ CATCH_SIGNAL(SIGSEGV);
+ CATCH_SIGNAL(SIGTERM);
+
+#undef CATCH_SIGNAL
+ }
+ }
+ }
+ }
+ else if (symlink_errno == EEXIST)
+ rv = NS_ERROR_FILE_ACCESS_DENIED;
+ else
+ {
+#ifdef DEBUG
+ printf("symlink() failed. errno = %d\n", errno);
+#endif
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+#endif /* XP_UNIX */
+
+nsresult nsProfileLock::GetReplacedLockTime(PRTime *aResult) {
+ *aResult = mReplacedLockTime;
+ return NS_OK;
+}
+
+nsresult nsProfileLock::Lock(nsIFile* aProfileDir,
+ nsIProfileUnlocker* *aUnlocker)
+{
+#if defined (XP_MACOSX)
+ NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock");
+ NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "parent.lock");
+#elif defined (XP_UNIX)
+ NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "lock");
+ NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock");
+#else
+ NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, "parent.lock");
+#endif
+
+ nsresult rv;
+ if (aUnlocker)
+ *aUnlocker = nullptr;
+
+ NS_ENSURE_STATE(!mHaveLock);
+
+ bool isDir;
+ rv = aProfileDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv))
+ return rv;
+ if (!isDir)
+ return NS_ERROR_FILE_NOT_DIRECTORY;
+
+ nsCOMPtr<nsIFile> lockFile;
+ rv = aProfileDir->Clone(getter_AddRefs(lockFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = lockFile->Append(LOCKFILE_NAME);
+ if (NS_FAILED(rv))
+ return rv;
+
+#if defined(XP_MACOSX)
+ // First, try locking using fcntl. It is more reliable on
+ // a local machine, but may not be supported by an NFS server.
+
+ rv = LockWithFcntl(lockFile);
+ if (NS_FAILED(rv) && (rv != NS_ERROR_FILE_ACCESS_DENIED))
+ {
+ // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
+ // assume we tried an NFS that does not support it. Now, try with symlink.
+ rv = LockWithSymlink(lockFile, false);
+ }
+
+ if (NS_SUCCEEDED(rv))
+ {
+ // Check for the old-style lock used by pre-mozilla 1.3 builds.
+ // Those builds used an earlier check to prevent the application
+ // from launching if another instance was already running. Because
+ // of that, we don't need to create an old-style lock as well.
+ struct LockProcessInfo
+ {
+ ProcessSerialNumber psn;
+ unsigned long launchDate;
+ };
+
+ PRFileDesc *fd = nullptr;
+ int32_t ioBytes;
+ ProcessInfoRec processInfo;
+ LockProcessInfo lockProcessInfo;
+
+ rv = lockFile->SetLeafName(OLD_LOCKFILE_NAME);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = lockFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
+ if (NS_SUCCEEDED(rv))
+ {
+ ioBytes = PR_Read(fd, &lockProcessInfo, sizeof(LockProcessInfo));
+ PR_Close(fd);
+
+ if (ioBytes == sizeof(LockProcessInfo))
+ {
+#ifdef __LP64__
+ processInfo.processAppRef = nullptr;
+#else
+ processInfo.processAppSpec = nullptr;
+#endif
+ processInfo.processName = nullptr;
+ processInfo.processInfoLength = sizeof(ProcessInfoRec);
+ if (::GetProcessInformation(&lockProcessInfo.psn, &processInfo) == noErr &&
+ processInfo.processLaunchDate == lockProcessInfo.launchDate)
+ {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ }
+ else
+ {
+ NS_WARNING("Could not read lock file - ignoring lock");
+ }
+ }
+ rv = NS_OK; // Don't propagate error from OpenNSPRFileDesc.
+ }
+#elif defined(XP_UNIX)
+ // Get the old lockfile name
+ nsCOMPtr<nsIFile> oldLockFile;
+ rv = aProfileDir->Clone(getter_AddRefs(oldLockFile));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = oldLockFile->Append(OLD_LOCKFILE_NAME);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // First, try locking using fcntl. It is more reliable on
+ // a local machine, but may not be supported by an NFS server.
+ rv = LockWithFcntl(lockFile);
+ if (NS_SUCCEEDED(rv)) {
+ // Check to see whether there is a symlink lock held by an older
+ // Firefox build, and also place our own symlink lock --- but
+ // mark it "obsolete" so that other newer builds can break the lock
+ // if they obtain the fcntl lock
+ rv = LockWithSymlink(oldLockFile, true);
+
+ // If the symlink failed for some reason other than it already
+ // exists, then something went wrong e.g. the file system
+ // doesn't support symlinks, or we don't have permission to
+ // create a symlink there. In such cases we should just
+ // continue because it's unlikely there is an old build
+ // running with a symlink there and we've already successfully
+ // placed a fcntl lock.
+ if (rv != NS_ERROR_FILE_ACCESS_DENIED)
+ rv = NS_OK;
+ }
+ else if (rv != NS_ERROR_FILE_ACCESS_DENIED)
+ {
+ // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
+ // assume we tried an NFS that does not support it. Now, try with symlink
+ // using the old symlink path
+ rv = LockWithSymlink(oldLockFile, false);
+ }
+
+#elif defined(XP_WIN)
+ nsAutoString filePath;
+ rv = lockFile->GetPath(filePath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ lockFile->GetLastModifiedTime(&mReplacedLockTime);
+
+ // always create the profile lock and never delete it so we can use its
+ // modification timestamp to detect startup crashes
+ mLockFileHandle = CreateFileW(filePath.get(),
+ GENERIC_READ | GENERIC_WRITE,
+ 0, // no sharing - of course
+ nullptr,
+ CREATE_ALWAYS,
+ 0,
+ nullptr);
+ if (mLockFileHandle == INVALID_HANDLE_VALUE) {
+ if (aUnlocker) {
+ RefPtr<mozilla::ProfileUnlockerWin> unlocker(
+ new mozilla::ProfileUnlockerWin(filePath));
+ if (NS_SUCCEEDED(unlocker->Init())) {
+ nsCOMPtr<nsIProfileUnlocker> unlockerInterface(
+ do_QueryObject(unlocker));
+ unlockerInterface.forget(aUnlocker);
+ }
+ }
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+#endif
+
+ if (NS_SUCCEEDED(rv))
+ mHaveLock = true;
+
+ return rv;
+}
+
+
+nsresult nsProfileLock::Unlock(bool aFatalSignal)
+{
+ nsresult rv = NS_OK;
+
+ if (mHaveLock)
+ {
+#if defined (XP_WIN)
+ if (mLockFileHandle != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(mLockFileHandle);
+ mLockFileHandle = INVALID_HANDLE_VALUE;
+ }
+#elif defined (XP_UNIX)
+ if (mPidLockFileName)
+ {
+ PR_REMOVE_LINK(this);
+ (void) unlink(mPidLockFileName);
+
+ // Only free mPidLockFileName if we're not in the fatal signal
+ // handler. The problem is that a call to free() might be the
+ // cause of this fatal signal. If so, calling free() might cause
+ // us to wait on the malloc implementation's lock. We're already
+ // holding this lock, so we'll deadlock. See bug 522332.
+ if (!aFatalSignal)
+ free(mPidLockFileName);
+ mPidLockFileName = nullptr;
+ }
+ if (mLockFileDesc != -1)
+ {
+ close(mLockFileDesc);
+ mLockFileDesc = -1;
+ // Don't remove it
+ }
+#endif
+
+ mHaveLock = false;
+ }
+
+ return rv;
+}
diff --git a/toolkit/profile/nsProfileLock.h b/toolkit/profile/nsProfileLock.h
new file mode 100644
index 000000000..e78a3577e
--- /dev/null
+++ b/toolkit/profile/nsProfileLock.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 __nsProfileLock_h___
+#define __nsProfileLock_h___
+
+#include "nsIFile.h"
+
+class nsIProfileUnlocker;
+
+#if defined (XP_WIN)
+#include <windows.h>
+#endif
+
+#if defined (XP_UNIX)
+#include <signal.h>
+#include "prclist.h"
+#endif
+
+class nsProfileLock
+#if defined (XP_UNIX)
+ : public PRCList
+#endif
+{
+public:
+ nsProfileLock();
+ nsProfileLock(nsProfileLock& src);
+
+ ~nsProfileLock();
+
+ nsProfileLock& operator=(nsProfileLock& rhs);
+
+ /**
+ * Attempt to lock a profile directory.
+ *
+ * @param aProfileDir [in] The profile directory to lock.
+ * @param aUnlocker [out] Optional. This is only returned when locking
+ * fails with NS_ERROR_FILE_ACCESS_DENIED, and may not
+ * be returned at all.
+ * @throws NS_ERROR_FILE_ACCESS_DENIED if the profile is locked.
+ */
+ nsresult Lock(nsIFile* aProfileDir, nsIProfileUnlocker* *aUnlocker);
+
+ /**
+ * Unlock a profile directory. If you're unlocking the directory because
+ * the application is in the process of shutting down because of a fatal
+ * signal, set aFatalSignal to true.
+ */
+ nsresult Unlock(bool aFatalSignal = false);
+
+ /**
+ * Get the modification time of a replaced profile lock, otherwise 0.
+ */
+ nsresult GetReplacedLockTime(PRTime* aResult);
+
+private:
+ bool mHaveLock;
+ PRTime mReplacedLockTime;
+
+#if defined (XP_WIN)
+ HANDLE mLockFileHandle;
+#elif defined (XP_UNIX)
+
+ struct RemovePidLockFilesExiting {
+ RemovePidLockFilesExiting() {}
+ ~RemovePidLockFilesExiting() {
+ RemovePidLockFiles(false);
+ }
+ };
+
+ static void RemovePidLockFiles(bool aFatalSignal);
+ static void FatalSignalHandler(int signo
+#ifdef SA_SIGINFO
+ , siginfo_t *info, void *context
+#endif
+ );
+ static PRCList mPidLockList;
+
+ nsresult LockWithFcntl(nsIFile *aLockFile);
+
+ /**
+ * @param aHaveFcntlLock if true, we've already acquired an fcntl lock so this
+ * lock is merely an "obsolete" lock to keep out old Firefoxes
+ */
+ nsresult LockWithSymlink(nsIFile *aLockFile, bool aHaveFcntlLock);
+
+ char* mPidLockFileName;
+ int mLockFileDesc;
+#endif
+
+};
+
+#endif /* __nsProfileLock_h___ */
diff --git a/toolkit/profile/nsProfileStringTypes.h b/toolkit/profile/nsProfileStringTypes.h
new file mode 100644
index 000000000..fddea519b
--- /dev/null
+++ b/toolkit/profile/nsProfileStringTypes.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/**
+ * We support two builds of the directory service provider.
+ * One, linked into the profile component, uses the internal
+ * string API. The other can be used by standalone embedding
+ * clients, and uses embed strings.
+ * To keep the code clean, we are using typedefs to equate
+ * embed/internal string types. We are also defining some
+ * internal macros in terms of the embedding strings API.
+ *
+ * When modifying the profile directory service provider, be
+ * sure to use methods supported by both the internal and
+ * embed strings APIs.
+ */
+
+#ifndef MOZILLA_INTERNAL_API
+
+#include "nsEmbedString.h"
+
+typedef nsCString nsPromiseFlatCString;
+typedef nsCString nsAutoCString;
+
+#define PromiseFlatCString nsCString
+
+#else
+#include "nsString.h"
+#include "nsPromiseFlatString.h"
+#endif
diff --git a/toolkit/profile/nsToolkitProfileService.cpp b/toolkit/profile/nsToolkitProfileService.cpp
new file mode 100644
index 000000000..38b3a37f1
--- /dev/null
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -0,0 +1,1117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "mozilla/ArrayUtils.h"
+#include "mozilla/UniquePtr.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <prprf.h>
+#include <prtime.h>
+#include "nsProfileLock.h"
+
+#ifdef XP_WIN
+#include <windows.h>
+#include <shlobj.h>
+#endif
+#ifdef XP_UNIX
+#include <unistd.h>
+#endif
+
+#include "nsIToolkitProfileService.h"
+#include "nsIToolkitProfile.h"
+#include "nsIFactory.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+
+#ifdef XP_MACOSX
+#include <CoreFoundation/CoreFoundation.h>
+#include "nsILocalFileMac.h"
+#endif
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsXULAppAPI.h"
+
+#include "nsINIParser.h"
+#include "nsXREDirProvider.h"
+#include "nsAppRunner.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsNativeCharsetUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+
+using namespace mozilla;
+
+class nsToolkitProfile final : public nsIToolkitProfile
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITOOLKITPROFILE
+
+ friend class nsToolkitProfileService;
+ RefPtr<nsToolkitProfile> mNext;
+ nsToolkitProfile *mPrev;
+
+private:
+ ~nsToolkitProfile() { }
+
+ nsToolkitProfile(const nsACString& aName,
+ nsIFile* aRootDir,
+ nsIFile* aLocalDir,
+ nsToolkitProfile* aPrev,
+ bool aForExternalApp);
+
+ friend class nsToolkitProfileLock;
+
+ nsCString mName;
+ nsCOMPtr<nsIFile> mRootDir;
+ nsCOMPtr<nsIFile> mLocalDir;
+ nsIProfileLock* mLock;
+ bool mForExternalApp;
+};
+
+class nsToolkitProfileLock final : public nsIProfileLock
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROFILELOCK
+
+ nsresult Init(nsToolkitProfile* aProfile, nsIProfileUnlocker* *aUnlocker);
+ nsresult Init(nsIFile* aDirectory, nsIFile* aLocalDirectory,
+ nsIProfileUnlocker* *aUnlocker);
+
+ nsToolkitProfileLock() { }
+
+private:
+ ~nsToolkitProfileLock();
+
+ RefPtr<nsToolkitProfile> mProfile;
+ nsCOMPtr<nsIFile> mDirectory;
+ nsCOMPtr<nsIFile> mLocalDirectory;
+
+ nsProfileLock mLock;
+};
+
+class nsToolkitProfileFactory final : public nsIFactory
+{
+ ~nsToolkitProfileFactory() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFACTORY
+};
+
+class nsToolkitProfileService final : public nsIToolkitProfileService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITOOLKITPROFILESERVICE
+
+private:
+ friend class nsToolkitProfile;
+ friend class nsToolkitProfileFactory;
+ friend nsresult NS_NewToolkitProfileService(nsIToolkitProfileService**);
+
+ nsToolkitProfileService() :
+ mDirty(false),
+ mStartWithLast(true),
+ mStartOffline(false)
+ {
+ gService = this;
+ }
+ ~nsToolkitProfileService()
+ {
+ gService = nullptr;
+ }
+
+ nsresult Init();
+
+ nsresult CreateTimesInternal(nsIFile *profileDir);
+
+ nsresult CreateProfileInternal(nsIFile* aRootDir,
+ const nsACString& aName,
+ const nsACString* aProfileName,
+ const nsACString* aAppName,
+ const nsACString* aVendorName,
+ bool aForExternalApp,
+ nsIToolkitProfile** aResult);
+
+ RefPtr<nsToolkitProfile> mFirst;
+ nsCOMPtr<nsIToolkitProfile> mChosen;
+ nsCOMPtr<nsIToolkitProfile> mDefault;
+ nsCOMPtr<nsIFile> mAppData;
+ nsCOMPtr<nsIFile> mTempData;
+ nsCOMPtr<nsIFile> mListFile;
+ bool mDirty;
+ bool mStartWithLast;
+ bool mStartOffline;
+
+ static nsToolkitProfileService *gService;
+
+ class ProfileEnumerator final : public nsISimpleEnumerator
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ explicit ProfileEnumerator(nsToolkitProfile *first)
+ { mCurrent = first; }
+ private:
+ ~ProfileEnumerator() { }
+ RefPtr<nsToolkitProfile> mCurrent;
+ };
+};
+
+nsToolkitProfile::nsToolkitProfile(const nsACString& aName,
+ nsIFile* aRootDir,
+ nsIFile* aLocalDir,
+ nsToolkitProfile* aPrev,
+ bool aForExternalApp) :
+ mPrev(aPrev),
+ mName(aName),
+ mRootDir(aRootDir),
+ mLocalDir(aLocalDir),
+ mLock(nullptr),
+ mForExternalApp(aForExternalApp)
+{
+ NS_ASSERTION(aRootDir, "No file!");
+
+ if (!aForExternalApp) {
+ if (aPrev) {
+ aPrev->mNext = this;
+ } else {
+ nsToolkitProfileService::gService->mFirst = this;
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfile, nsIToolkitProfile)
+
+NS_IMETHODIMP
+nsToolkitProfile::GetRootDir(nsIFile* *aResult)
+{
+ NS_ADDREF(*aResult = mRootDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::GetLocalDir(nsIFile* *aResult)
+{
+ NS_ADDREF(*aResult = mLocalDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::GetName(nsACString& aResult)
+{
+ aResult = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::SetName(const nsACString& aName)
+{
+ NS_ASSERTION(nsToolkitProfileService::gService,
+ "Where did my service go?");
+ NS_ENSURE_TRUE(!mForExternalApp, NS_ERROR_NOT_IMPLEMENTED);
+
+ mName = aName;
+ nsToolkitProfileService::gService->mDirty = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::Remove(bool removeFiles)
+{
+ NS_ASSERTION(nsToolkitProfileService::gService,
+ "Whoa, my service is gone.");
+
+ NS_ENSURE_TRUE(!mForExternalApp, NS_ERROR_NOT_IMPLEMENTED);
+
+ if (mLock)
+ return NS_ERROR_FILE_IS_LOCKED;
+
+ if (!mPrev && !mNext && nsToolkitProfileService::gService->mFirst != this)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (removeFiles) {
+ bool equals;
+ nsresult rv = mRootDir->Equals(mLocalDir, &equals);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // The root dir might contain the temp dir, so remove
+ // the temp dir first.
+ if (!equals)
+ mLocalDir->Remove(true);
+
+ mRootDir->Remove(true);
+ }
+
+ if (mPrev)
+ mPrev->mNext = mNext;
+ else
+ nsToolkitProfileService::gService->mFirst = mNext;
+
+ if (mNext)
+ mNext->mPrev = mPrev;
+
+ mPrev = nullptr;
+ mNext = nullptr;
+
+ if (nsToolkitProfileService::gService->mChosen == this)
+ nsToolkitProfileService::gService->mChosen = nullptr;
+
+ nsToolkitProfileService::gService->mDirty = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::Lock(nsIProfileUnlocker* *aUnlocker, nsIProfileLock* *aResult)
+{
+ if (mLock) {
+ NS_ADDREF(*aResult = mLock);
+ return NS_OK;
+ }
+
+ RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
+ if (!lock) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = lock->Init(this, aUnlocker);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*aResult = lock);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileLock, nsIProfileLock)
+
+nsresult
+nsToolkitProfileLock::Init(nsToolkitProfile* aProfile, nsIProfileUnlocker* *aUnlocker)
+{
+ nsresult rv;
+ rv = Init(aProfile->mRootDir, aProfile->mLocalDir, aUnlocker);
+ if (NS_SUCCEEDED(rv))
+ mProfile = aProfile;
+
+ return rv;
+}
+
+nsresult
+nsToolkitProfileLock::Init(nsIFile* aDirectory, nsIFile* aLocalDirectory,
+ nsIProfileUnlocker* *aUnlocker)
+{
+ nsresult rv;
+
+ rv = mLock.Lock(aDirectory, aUnlocker);
+
+ if (NS_SUCCEEDED(rv)) {
+ mDirectory = aDirectory;
+ mLocalDirectory = aLocalDirectory;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::GetDirectory(nsIFile* *aResult)
+{
+ if (!mDirectory) {
+ NS_ERROR("Not initialized, or unlocked!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ NS_ADDREF(*aResult = mDirectory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::GetLocalDirectory(nsIFile* *aResult)
+{
+ if (!mLocalDirectory) {
+ NS_ERROR("Not initialized, or unlocked!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ NS_ADDREF(*aResult = mLocalDirectory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::Unlock()
+{
+ if (!mDirectory) {
+ NS_ERROR("Unlocking a never-locked nsToolkitProfileLock!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mLock.Unlock();
+
+ if (mProfile) {
+ mProfile->mLock = nullptr;
+ mProfile = nullptr;
+ }
+ mDirectory = nullptr;
+ mLocalDirectory = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::GetReplacedLockTime(PRTime *aResult)
+{
+ mLock.GetReplacedLockTime(aResult);
+ return NS_OK;
+}
+
+nsToolkitProfileLock::~nsToolkitProfileLock()
+{
+ if (mDirectory) {
+ Unlock();
+ }
+}
+
+nsToolkitProfileService*
+nsToolkitProfileService::gService = nullptr;
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileService,
+ nsIToolkitProfileService)
+
+nsresult
+nsToolkitProfileService::Init()
+{
+ NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
+ nsresult rv;
+
+ rv = gDirServiceProvider->GetUserAppDataDirectory(getter_AddRefs(mAppData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = gDirServiceProvider->GetUserLocalDataDirectory(getter_AddRefs(mTempData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mAppData->Clone(getter_AddRefs(mListFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mListFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = mListFile->IsFile(&exists);
+ if (NS_FAILED(rv) || !exists) {
+ return NS_OK;
+ }
+
+ int64_t size;
+ rv = mListFile->GetFileSize(&size);
+ if (NS_FAILED(rv) || !size) {
+ return NS_OK;
+ }
+
+ nsINIParser parser;
+ rv = parser.Init(mListFile);
+ // Init does not fail on parsing errors, only on OOM/really unexpected
+ // conditions.
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString buffer;
+ rv = parser.GetString("General", "StartWithLastProfile", buffer);
+ if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("0"))
+ mStartWithLast = false;
+
+ nsToolkitProfile* currentProfile = nullptr;
+
+#ifdef MOZ_DEV_EDITION
+ nsCOMPtr<nsIFile> ignoreSeparateProfile;
+ rv = mAppData->Clone(getter_AddRefs(ignoreSeparateProfile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = ignoreSeparateProfile->AppendNative(NS_LITERAL_CSTRING("ignore-dev-edition-profile"));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool shouldIgnoreSeparateProfile;
+ rv = ignoreSeparateProfile->Exists(&shouldIgnoreSeparateProfile);
+ if (NS_FAILED(rv))
+ return rv;
+#endif
+
+ unsigned int c = 0;
+ bool foundAuroraDefault = false;
+ for (c = 0; true; ++c) {
+ nsAutoCString profileID("Profile");
+ profileID.AppendInt(c);
+
+ rv = parser.GetString(profileID.get(), "IsRelative", buffer);
+ if (NS_FAILED(rv)) break;
+
+ bool isRelative = buffer.EqualsLiteral("1");
+
+ nsAutoCString filePath;
+
+ rv = parser.GetString(profileID.get(), "Path", filePath);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Malformed profiles.ini: Path= not found");
+ continue;
+ }
+
+ nsAutoCString name;
+
+ rv = parser.GetString(profileID.get(), "Name", name);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Malformed profiles.ini: Name= not found");
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> rootDir;
+ rv = NS_NewNativeLocalFile(EmptyCString(), true,
+ getter_AddRefs(rootDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isRelative) {
+ rv = rootDir->SetRelativeDescriptor(mAppData, filePath);
+ } else {
+ rv = rootDir->SetPersistentDescriptor(filePath);
+ }
+ if (NS_FAILED(rv)) continue;
+
+ nsCOMPtr<nsIFile> localDir;
+ if (isRelative) {
+ rv = NS_NewNativeLocalFile(EmptyCString(), true,
+ getter_AddRefs(localDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localDir->SetRelativeDescriptor(mTempData, filePath);
+ } else {
+ localDir = rootDir;
+ }
+
+ currentProfile = new nsToolkitProfile(name,
+ rootDir, localDir,
+ currentProfile, false);
+ NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = parser.GetString(profileID.get(), "Default", buffer);
+ if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1") && !foundAuroraDefault) {
+ mChosen = currentProfile;
+ this->SetDefaultProfile(currentProfile);
+ }
+#ifdef MOZ_DEV_EDITION
+ // Use the dev-edition-default profile if this is an Aurora build and
+ // ignore-dev-edition-profile is not present.
+ if (name.EqualsLiteral("dev-edition-default") && !shouldIgnoreSeparateProfile) {
+ mChosen = currentProfile;
+ foundAuroraDefault = true;
+ }
+#endif
+ }
+
+#ifdef MOZ_DEV_EDITION
+ if (!foundAuroraDefault && !shouldIgnoreSeparateProfile) {
+ // If a single profile exists, it may not be already marked as default.
+ // Do it now to avoid problems when we create the dev-edition-default profile.
+ if (!mChosen && mFirst && !mFirst->mNext)
+ this->SetDefaultProfile(mFirst);
+
+ // Create a default profile for aurora, if none was found.
+ nsCOMPtr<nsIToolkitProfile> profile;
+ rv = CreateProfile(nullptr,
+ NS_LITERAL_CSTRING("dev-edition-default"),
+ getter_AddRefs(profile));
+ if (NS_FAILED(rv)) return rv;
+ mChosen = profile;
+ rv = Flush();
+ if (NS_FAILED(rv)) return rv;
+ }
+#endif
+
+ if (!mChosen && mFirst && !mFirst->mNext) // only one profile
+ mChosen = mFirst;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetStartWithLastProfile(bool aValue)
+{
+ if (mStartWithLast != aValue) {
+ mStartWithLast = aValue;
+ mDirty = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetStartWithLastProfile(bool *aResult)
+{
+ *aResult = mStartWithLast;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetStartOffline(bool *aResult)
+{
+ *aResult = mStartOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetStartOffline(bool aValue)
+{
+ mStartOffline = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfiles(nsISimpleEnumerator* *aResult)
+{
+ *aResult = new ProfileEnumerator(this->mFirst);
+ if (!*aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileService::ProfileEnumerator,
+ nsISimpleEnumerator)
+
+NS_IMETHODIMP
+nsToolkitProfileService::ProfileEnumerator::HasMoreElements(bool* aResult)
+{
+ *aResult = mCurrent ? true : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports* *aResult)
+{
+ if (!mCurrent) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aResult = mCurrent);
+
+ mCurrent = mCurrent->mNext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetSelectedProfile(nsIToolkitProfile* *aResult)
+{
+ if (!mChosen && mFirst && !mFirst->mNext) // only one profile
+ mChosen = mFirst;
+
+ if (!mChosen) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aResult = mChosen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetSelectedProfile(nsIToolkitProfile* aProfile)
+{
+ if (mChosen != aProfile) {
+ mChosen = aProfile;
+ mDirty = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile* *aResult)
+{
+ if (!mDefault) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aResult = mDefault);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile)
+{
+ if (mDefault != aProfile) {
+ mDefault = aProfile;
+ mDirty = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfileByName(const nsACString& aName,
+ nsIToolkitProfile* *aResult)
+{
+ nsToolkitProfile* curP = mFirst;
+ while (curP) {
+ if (curP->mName.Equals(aName)) {
+ NS_ADDREF(*aResult = curP);
+ return NS_OK;
+ }
+ curP = curP->mNext;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::LockProfilePath(nsIFile* aDirectory,
+ nsIFile* aLocalDirectory,
+ nsIProfileLock* *aResult)
+{
+ return NS_LockProfilePath(aDirectory, aLocalDirectory, nullptr, aResult);
+}
+
+nsresult
+NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
+ nsIProfileUnlocker* *aUnlocker, nsIProfileLock* *aResult)
+{
+ RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
+ if (!lock) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = lock->Init(aPath, aTempPath, aUnlocker);
+ if (NS_FAILED(rv)) return rv;
+
+ lock.forget(aResult);
+ return NS_OK;
+}
+
+static const char kTable[] =
+ { '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' };
+
+static void SaltProfileName(nsACString& aName)
+{
+ double fpTime = double(PR_Now());
+
+ // use 1e-6, granularity of PR_Now() on the mac is seconds
+ srand((unsigned int)(fpTime * 1e-6 + 0.5));
+
+ char salt[9];
+
+ int i;
+ for (i = 0; i < 8; ++i)
+ salt[i] = kTable[rand() % ArrayLength(kTable)];
+
+ salt[8] = '.';
+
+ aName.Insert(salt, 0, 9);
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::CreateDefaultProfileForApp(const nsACString& aProfileName,
+ const nsACString& aAppName,
+ const nsACString& aVendorName,
+ nsIToolkitProfile** aResult)
+{
+ NS_ENSURE_STATE(!aProfileName.IsEmpty() || !aAppName.IsEmpty());
+ nsCOMPtr<nsIFile> appData;
+ nsresult rv =
+ gDirServiceProvider->GetUserDataDirectory(getter_AddRefs(appData),
+ false,
+ &aProfileName,
+ &aAppName,
+ &aVendorName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> profilesini;
+ appData->Clone(getter_AddRefs(profilesini));
+ rv = profilesini->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ profilesini->Exists(&exists);
+ NS_ENSURE_FALSE(exists, NS_ERROR_ALREADY_INITIALIZED);
+
+ rv = CreateProfileInternal(nullptr,
+ NS_LITERAL_CSTRING("default"),
+ &aProfileName, &aAppName, &aVendorName,
+ true, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(*aResult);
+
+ nsCOMPtr<nsIFile> rootDir;
+ (*aResult)->GetRootDir(getter_AddRefs(rootDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString profileDir;
+ rv = rootDir->GetRelativeDescriptor(appData, profileDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString ini;
+ ini.SetCapacity(512);
+ ini.AppendLiteral("[General]\n");
+ ini.AppendLiteral("StartWithLastProfile=1\n\n");
+
+ ini.AppendLiteral("[Profile0]\n");
+ ini.AppendLiteral("Name=default\n");
+ ini.AppendLiteral("IsRelative=1\n");
+ ini.AppendLiteral("Path=");
+ ini.Append(profileDir);
+ ini.Append('\n');
+ ini.AppendLiteral("Default=1\n\n");
+
+ FILE* writeFile;
+ rv = profilesini->OpenANSIFileDesc("w", &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fwrite(ini.get(), sizeof(char), ini.Length(), writeFile) !=
+ ini.Length()) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ fclose(writeFile);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
+ const nsACString& aName,
+ nsIToolkitProfile** aResult)
+{
+ return CreateProfileInternal(aRootDir, aName,
+ nullptr, nullptr, nullptr, false, aResult);
+}
+
+nsresult
+nsToolkitProfileService::CreateProfileInternal(nsIFile* aRootDir,
+ const nsACString& aName,
+ const nsACString* aProfileName,
+ const nsACString* aAppName,
+ const nsACString* aVendorName,
+ bool aForExternalApp,
+ nsIToolkitProfile** aResult)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (!aForExternalApp) {
+ rv = GetProfileByName(aName, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIFile> rootDir (aRootDir);
+
+ nsAutoCString dirName;
+ if (!rootDir) {
+ rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir),
+ aProfileName, aAppName,
+ aVendorName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dirName = aName;
+ SaltProfileName(dirName);
+
+ if (NS_IsNativeUTF8()) {
+ rootDir->AppendNative(dirName);
+ } else {
+ rootDir->Append(NS_ConvertUTF8toUTF16(dirName));
+ }
+ }
+
+ nsCOMPtr<nsIFile> localDir;
+
+ bool isRelative;
+ rv = mAppData->Contains(rootDir, &isRelative);
+ if (NS_SUCCEEDED(rv) && isRelative) {
+ nsAutoCString path;
+ rv = rootDir->GetRelativeDescriptor(mAppData, path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewNativeLocalFile(EmptyCString(), true,
+ getter_AddRefs(localDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localDir->SetRelativeDescriptor(mTempData, path);
+ } else {
+ localDir = rootDir;
+ }
+
+ bool exists;
+ rv = rootDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ rv = rootDir->IsDirectory(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists)
+ return NS_ERROR_FILE_NOT_DIRECTORY;
+ }
+ else {
+ nsCOMPtr<nsIFile> profileDirParent;
+ nsAutoString profileDirName;
+
+ rv = rootDir->GetParent(getter_AddRefs(profileDirParent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = rootDir->GetLeafName(profileDirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // let's ensure that the profile directory exists.
+ rv = rootDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = rootDir->SetPermissions(0700);
+#ifndef ANDROID
+ // If the profile is on the sdcard, this will fail but its non-fatal
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ }
+
+ rv = localDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) {
+ rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We created a new profile dir. Let's store a creation timestamp.
+ // Note that this code path does not apply if the profile dir was
+ // created prior to launching.
+ rv = CreateTimesInternal(rootDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsToolkitProfile* last = aForExternalApp ? nullptr : mFirst.get();
+ if (last) {
+ while (last->mNext)
+ last = last->mNext;
+ }
+
+ nsCOMPtr<nsIToolkitProfile> profile =
+ new nsToolkitProfile(aName, rootDir, localDir, last, aForExternalApp);
+ if (!profile) return NS_ERROR_OUT_OF_MEMORY;
+
+ profile.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIFile> creationLog;
+ rv = aProfileDir->Clone(getter_AddRefs(creationLog));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = creationLog->AppendNative(NS_LITERAL_CSTRING("times.json"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ creationLog->Exists(&exists);
+ if (exists) {
+ return NS_OK;
+ }
+
+ rv = creationLog->Create(nsIFile::NORMAL_FILE_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We don't care about microsecond resolution.
+ int64_t msec = PR_Now() / PR_USEC_PER_MSEC;
+
+ // Write it out.
+ PRFileDesc *writeFile;
+ rv = creationLog->OpenNSPRFileDesc(PR_WRONLY, 0700, &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PR_fprintf(writeFile, "{\n\"created\": %lld\n}\n", msec);
+ PR_Close(writeFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfileCount(uint32_t *aResult)
+{
+ if (!mFirst)
+ *aResult = 0;
+ else if (! mFirst->mNext)
+ *aResult = 1;
+ else
+ *aResult = 2;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::Flush()
+{
+ // Errors during writing might cause unhappy semi-written files.
+ // To avoid this, write the entire thing to a buffer, then write
+ // that buffer to disk.
+
+ nsresult rv;
+ uint32_t pCount = 0;
+ nsToolkitProfile *cur;
+
+ for (cur = mFirst; cur != nullptr; cur = cur->mNext)
+ ++pCount;
+
+ uint32_t length;
+ const int bufsize = 100+MAXPATHLEN*pCount;
+ auto buffer = MakeUnique<char[]>(bufsize);
+
+ char *pos = buffer.get();
+ char *end = pos + bufsize;
+
+ pos += snprintf(pos, end - pos,
+ "[General]\n"
+ "StartWithLastProfile=%s\n\n",
+ mStartWithLast ? "1" : "0");
+
+ nsAutoCString path;
+ cur = mFirst;
+ pCount = 0;
+
+ while (cur) {
+ // if the profile dir is relative to appdir...
+ bool isRelative;
+ rv = mAppData->Contains(cur->mRootDir, &isRelative);
+ if (NS_SUCCEEDED(rv) && isRelative) {
+ // we use a relative descriptor
+ rv = cur->mRootDir->GetRelativeDescriptor(mAppData, path);
+ } else {
+ // otherwise, a persistent descriptor
+ rv = cur->mRootDir->GetPersistentDescriptor(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ pos += snprintf(pos, end - pos,
+ "[Profile%u]\n"
+ "Name=%s\n"
+ "IsRelative=%s\n"
+ "Path=%s\n",
+ pCount, cur->mName.get(),
+ isRelative ? "1" : "0", path.get());
+
+ nsCOMPtr<nsIToolkitProfile> profile;
+ rv = this->GetDefaultProfile(getter_AddRefs(profile));
+ if (NS_SUCCEEDED(rv) && profile == cur) {
+ pos += snprintf(pos, end - pos, "Default=1\n");
+ }
+
+ pos += snprintf(pos, end - pos, "\n");
+
+ cur = cur->mNext;
+ ++pCount;
+ }
+
+ FILE* writeFile;
+ rv = mListFile->OpenANSIFileDesc("w", &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ length = pos - buffer.get();
+
+ if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) {
+ fclose(writeFile);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ fclose(writeFile);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileFactory, nsIFactory)
+
+NS_IMETHODIMP
+nsToolkitProfileFactory::CreateInstance(nsISupports* aOuter, const nsID& aIID,
+ void** aResult)
+{
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsCOMPtr<nsIToolkitProfileService> profileService =
+ nsToolkitProfileService::gService;
+ if (!profileService) {
+ nsresult rv = NS_NewToolkitProfileService(getter_AddRefs(profileService));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return profileService->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsToolkitProfileFactory::LockFactory(bool aVal)
+{
+ return NS_OK;
+}
+
+nsresult
+NS_NewToolkitProfileFactory(nsIFactory* *aResult)
+{
+ *aResult = new nsToolkitProfileFactory();
+ if (!*aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+nsresult
+NS_NewToolkitProfileService(nsIToolkitProfileService* *aResult)
+{
+ nsToolkitProfileService* profileService = new nsToolkitProfileService();
+ if (!profileService)
+ return NS_ERROR_OUT_OF_MEMORY;
+ nsresult rv = profileService->Init();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsToolkitProfileService::Init failed!");
+ delete profileService;
+ return rv;
+ }
+
+ NS_ADDREF(*aResult = profileService);
+ return NS_OK;
+}
+
+nsresult
+XRE_GetFileFromPath(const char *aPath, nsIFile* *aResult)
+{
+#if defined(XP_MACOSX)
+ int32_t pathLen = strlen(aPath);
+ if (pathLen > MAXPATHLEN)
+ return NS_ERROR_INVALID_ARG;
+
+ CFURLRef fullPath =
+ CFURLCreateFromFileSystemRepresentation(nullptr, (const UInt8 *) aPath,
+ pathLen, true);
+ if (!fullPath)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> lf;
+ nsresult rv = NS_NewNativeLocalFile(EmptyCString(), true,
+ getter_AddRefs(lf));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsILocalFileMac> lfMac = do_QueryInterface(lf, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = lfMac->InitWithCFURL(fullPath);
+ if (NS_SUCCEEDED(rv)) {
+ lf.forget(aResult);
+ }
+ }
+ }
+ CFRelease(fullPath);
+ return rv;
+
+#elif defined(XP_UNIX)
+ char fullPath[MAXPATHLEN];
+
+ if (!realpath(aPath, fullPath))
+ return NS_ERROR_FAILURE;
+
+ return NS_NewNativeLocalFile(nsDependentCString(fullPath), true,
+ aResult);
+#elif defined(XP_WIN)
+ WCHAR fullPath[MAXPATHLEN];
+
+ if (!_wfullpath(fullPath, NS_ConvertUTF8toUTF16(aPath).get(), MAXPATHLEN))
+ return NS_ERROR_FAILURE;
+
+ return NS_NewLocalFile(nsDependentString(fullPath), true,
+ aResult);
+
+#else
+#error Platform-specific logic needed here.
+#endif
+}
diff --git a/toolkit/profile/test/.eslintrc.js b/toolkit/profile/test/.eslintrc.js
new file mode 100644
index 000000000..4e6d4bcf0
--- /dev/null
+++ b/toolkit/profile/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../testing/mochitest/chrome.eslintrc.js"
+ ]
+};
diff --git a/toolkit/profile/test/chrome.ini b/toolkit/profile/test/chrome.ini
new file mode 100644
index 000000000..e21c1022e
--- /dev/null
+++ b/toolkit/profile/test/chrome.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_create_profile.xul]
diff --git a/toolkit/profile/test/test_create_profile.xul b/toolkit/profile/test/test_create_profile.xul
new file mode 100644
index 000000000..040b1256b
--- /dev/null
+++ b/toolkit/profile/test/test_create_profile.xul
@@ -0,0 +1,134 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=543854
+-->
+<window title="Mozilla Bug 543854"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=543854"
+ target="_blank">Mozilla Bug 543854</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 543854 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ const ASCIIName = "myprofile";
+ const UnicodeName = "\u09A0\u09BE\u0995\u09C1\u09B0"; // A Bengali name
+
+ var gDirService;
+ var gIOService;
+ var gProfileService;
+
+ var gDefaultLocalProfileParent;
+
+ gDirService = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+
+ gIOService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ gProfileService = Cc["@mozilla.org/toolkit/profile-service;1"].
+ getService(Ci.nsIToolkitProfileService);
+
+ gDefaultLocalProfileParent = gDirService.get("DefProfLRt", Ci.nsIFile);
+
+ createProfile(ASCIIName);
+ createProfile(UnicodeName);
+ SimpleTest.finish();
+
+/**
+ * Read the contents of an nsIFile. Throws on error.
+
+ * @param file an nsIFile instance.
+ * @return string contents.
+ */
+function readFile(file) {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ let sstream = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Components.interfaces.nsIScriptableInputStream);
+
+ const RO = 0x01;
+ const READ_OTHERS = 4;
+
+ fstream.init(file, RO, READ_OTHERS, 0);
+ sstream.init(fstream);
+ let out = sstream.read(sstream.available());
+ sstream.close();
+ fstream.close();
+ return out;
+}
+
+function checkBounds(lowerBound, value, upperBound) {
+ ok(lowerBound <= value, "value " + value +
+ " is above lower bound " + lowerBound);
+ ok(upperBound >= value, "value " + value +
+ " is within upper bound " + upperBound);
+}
+
+function createProfile(profileName) {
+ // Filesystem precision is lower than Date precision.
+ let lowerBound = Date.now() - 1000;
+
+ let profile = gProfileService.createProfile(null, profileName);
+
+ // check that the directory was created
+ isnot(profile, null, "Profile " + profileName + " created");
+
+ let profileDir = profile.rootDir;
+
+ ok(profileDir.exists(), "Profile dir created");
+ ok(profileDir.isDirectory(), "Profile dir is a directory");
+
+ let profileDirPath = profileDir.path;
+
+ is(profileDirPath.substr(profileDirPath.length - profileName.length),
+ profileName, "Profile dir has expected name");
+
+ // Ensure that our timestamp file was created.
+ let jsonFile = profileDir.clone();
+ jsonFile.append("times.json");
+ ok(jsonFile.path, "Path is " + jsonFile.path);
+ ok(jsonFile.exists(), "Times file was created");
+ ok(jsonFile.isFile(), "Times file is a file");
+ let json = JSON.parse(readFile(jsonFile));
+
+ let upperBound = Date.now() + 1000;
+
+ let created = json.created;
+ ok(created, "created is set");
+
+ // Check against real clock time.
+ checkBounds(lowerBound, created, upperBound);
+
+ // Clean up the profile before local profile test.
+ profile.remove(true);
+
+ // Create with non-null aRootDir
+ profile = gProfileService.createProfile(profileDir, profileName);
+
+ let localProfileDir = profile.localDir;
+ ok(gDefaultLocalProfileParent.contains(localProfileDir, false),
+ "Local profile dir created in DefProfLRt");
+
+ // Clean up the profile.
+ profile.remove(true);
+}
+
+ ]]>
+ </script>
+</window>