import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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>

View file

@ -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;
}

View file

@ -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>

View file

@ -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);
}

View file

@ -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'

9
toolkit/profile/jar.mn Normal file
View file

@ -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)

43
toolkit/profile/moz.build Normal file
View file

@ -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')

View file

@ -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.

View file

@ -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"
%}

View file

@ -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);
};

View file

@ -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);
};

View file

@ -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"
%}

View file

@ -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;
}

View file

@ -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___ */

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"../../../testing/mochitest/chrome.eslintrc.js"
]
};

View file

@ -0,0 +1,3 @@
[DEFAULT]
[test_create_profile.xul]

View file

@ -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>