mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-21 06:37:30 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
36
toolkit/components/startup/StartupTimeline.cpp
Normal file
36
toolkit/components/startup/StartupTimeline.cpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* 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 "StartupTimeline.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
TimeStamp StartupTimeline::sStartupTimeline[StartupTimeline::MAX_EVENT_ID];
|
||||
const char *StartupTimeline::sStartupTimelineDesc[StartupTimeline::MAX_EVENT_ID] = {
|
||||
#define mozilla_StartupTimeline_Event(ev, desc) desc,
|
||||
#include "StartupTimeline.h"
|
||||
#undef mozilla_StartupTimeline_Event
|
||||
};
|
||||
|
||||
} /* namespace mozilla */
|
||||
|
||||
using mozilla::StartupTimeline;
|
||||
using mozilla::TimeStamp;
|
||||
|
||||
/**
|
||||
* The XRE_StartupTimeline_Record function is to be used by embedding
|
||||
* applications that can't use mozilla::StartupTimeline::Record() directly.
|
||||
*
|
||||
* @param aEvent The event to be recorded, must correspond to an element of the
|
||||
* mozilla::StartupTimeline::Event enumartion
|
||||
* @param aWhen The time at which the event happened
|
||||
*/
|
||||
void
|
||||
XRE_StartupTimelineRecord(int aEvent, TimeStamp aWhen)
|
||||
{
|
||||
StartupTimeline::Record((StartupTimeline::Event)aEvent, aWhen);
|
||||
}
|
||||
100
toolkit/components/startup/StartupTimeline.h
Normal file
100
toolkit/components/startup/StartupTimeline.h
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/* 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/. */
|
||||
|
||||
#ifdef mozilla_StartupTimeline_Event
|
||||
mozilla_StartupTimeline_Event(PROCESS_CREATION, "process")
|
||||
mozilla_StartupTimeline_Event(START, "start")
|
||||
mozilla_StartupTimeline_Event(MAIN, "main")
|
||||
mozilla_StartupTimeline_Event(SELECT_PROFILE, "selectProfile")
|
||||
mozilla_StartupTimeline_Event(AFTER_PROFILE_LOCKED, "afterProfileLocked")
|
||||
// Record the beginning and end of startup crash detection to compare with crash stats to know whether
|
||||
// detection should be improved to start or end sooner.
|
||||
mozilla_StartupTimeline_Event(STARTUP_CRASH_DETECTION_BEGIN, "startupCrashDetectionBegin")
|
||||
mozilla_StartupTimeline_Event(STARTUP_CRASH_DETECTION_END, "startupCrashDetectionEnd")
|
||||
mozilla_StartupTimeline_Event(FIRST_PAINT, "firstPaint")
|
||||
mozilla_StartupTimeline_Event(SESSION_RESTORE_INIT, "sessionRestoreInit")
|
||||
mozilla_StartupTimeline_Event(SESSION_RESTORED, "sessionRestored")
|
||||
mozilla_StartupTimeline_Event(CREATE_TOP_LEVEL_WINDOW, "createTopLevelWindow")
|
||||
mozilla_StartupTimeline_Event(LINKER_INITIALIZED, "linkerInitialized")
|
||||
mozilla_StartupTimeline_Event(LIBRARIES_LOADED, "librariesLoaded")
|
||||
mozilla_StartupTimeline_Event(FIRST_LOAD_URI, "firstLoadURI")
|
||||
|
||||
// The following are actually shutdown events, used to monitor the duration of shutdown
|
||||
mozilla_StartupTimeline_Event(QUIT_APPLICATION, "quitApplication")
|
||||
mozilla_StartupTimeline_Event(PROFILE_BEFORE_CHANGE, "profileBeforeChange")
|
||||
#else
|
||||
|
||||
#ifndef mozilla_StartupTimeline
|
||||
#define mozilla_StartupTimeline
|
||||
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nscore.h"
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
#ifdef MOZ_LINKER
|
||||
extern "C" {
|
||||
/* This symbol is resolved by the custom linker. The function it resolves
|
||||
* to dumps some statistics about the linker at the key events recorded
|
||||
* by the startup timeline. */
|
||||
extern void __moz_linker_stats(const char *str)
|
||||
NS_VISIBILITY_DEFAULT __attribute__((weak));
|
||||
} /* extern "C" */
|
||||
#else
|
||||
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void RecordShutdownEndTimeStamp();
|
||||
void RecordShutdownStartTimeStamp();
|
||||
|
||||
class StartupTimeline {
|
||||
public:
|
||||
enum Event {
|
||||
#define mozilla_StartupTimeline_Event(ev, z) ev,
|
||||
#include "StartupTimeline.h"
|
||||
#undef mozilla_StartupTimeline_Event
|
||||
MAX_EVENT_ID
|
||||
};
|
||||
|
||||
static TimeStamp Get(Event ev) {
|
||||
return sStartupTimeline[ev];
|
||||
}
|
||||
|
||||
static const char *Describe(Event ev) {
|
||||
return sStartupTimelineDesc[ev];
|
||||
}
|
||||
|
||||
static void Record(Event ev) {
|
||||
PROFILER_MARKER(Describe(ev));
|
||||
Record(ev, TimeStamp::Now());
|
||||
}
|
||||
|
||||
static void Record(Event ev, TimeStamp when) {
|
||||
sStartupTimeline[ev] = when;
|
||||
#ifdef MOZ_LINKER
|
||||
if (__moz_linker_stats)
|
||||
__moz_linker_stats(Describe(ev));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void RecordOnce(Event ev) {
|
||||
if (!HasRecord(ev))
|
||||
Record(ev);
|
||||
}
|
||||
|
||||
static bool HasRecord(Event ev) {
|
||||
return !sStartupTimeline[ev].IsNull();
|
||||
}
|
||||
|
||||
private:
|
||||
static NS_EXTERNAL_VIS_(TimeStamp) sStartupTimeline[MAX_EVENT_ID];
|
||||
static NS_EXTERNAL_VIS_(const char *) sStartupTimelineDesc[MAX_EVENT_ID];
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* mozilla_StartupTimeline */
|
||||
|
||||
#endif /* mozilla_StartupTimeline_Event */
|
||||
38
toolkit/components/startup/moz.build
Normal file
38
toolkit/components/startup/moz.build
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# -*- 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/.
|
||||
|
||||
DIRS += ['public']
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'StartupTimeline.h',
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'nsAppStartup.cpp',
|
||||
'StartupTimeline.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
# This file cannot be built in unified mode because of name clashes with Windows headers.
|
||||
SOURCES += [
|
||||
'nsUserInfoWin.cpp',
|
||||
]
|
||||
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
|
||||
UNIFIED_SOURCES += [
|
||||
'nsUserInfoMac.mm',
|
||||
]
|
||||
else:
|
||||
UNIFIED_SOURCES += [
|
||||
'nsUserInfoUnix.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Toolkit', 'Startup and Profile System')
|
||||
29
toolkit/components/startup/mozprofilerprobe.mof
Normal file
29
toolkit/components/startup/mozprofilerprobe.mof
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma namespace("\\\\.\\root\\wmi")
|
||||
#pragma autorecover
|
||||
|
||||
[dynamic: ToInstance, Description("Mozilla Generic Provider"),
|
||||
Guid("{509962E0-406B-46F4-99BA-5A009F8D2225}")]
|
||||
class MozillaProvider : EventTrace
|
||||
{
|
||||
};
|
||||
|
||||
[dynamic: ToInstance, Description("Mozilla Event: Places Init is complete."): Amended,
|
||||
Guid("{A3DA04E0-57D7-482A-A1C1-61DA5F95BACB}"),
|
||||
EventType(1)]
|
||||
class MozillaEventPlacesInit : MozillaProvider
|
||||
{
|
||||
};
|
||||
|
||||
[dynamic: ToInstance, Description("Mozilla Event: Session Store Window Restored."): Amended,
|
||||
Guid("{917B96B1-ECAD-4DAB-A760-8D49027748AE}"),
|
||||
EventType(1)]
|
||||
class MozillaEventSessionStoreWindowRestored : MozillaProvider
|
||||
{
|
||||
};
|
||||
|
||||
[dynamic: ToInstance, Description("Mozilla Event: XPCOM Shutdown."): Amended,
|
||||
Guid("{26D1E091-0AE7-4F49-A554-4214445C505C}"),
|
||||
EventType(1)]
|
||||
class MozillaEventXPCOMShutdown : MozillaProvider
|
||||
{
|
||||
};
|
||||
1030
toolkit/components/startup/nsAppStartup.cpp
Normal file
1030
toolkit/components/startup/nsAppStartup.cpp
Normal file
File diff suppressed because it is too large
Load diff
76
toolkit/components/startup/nsAppStartup.h
Normal file
76
toolkit/components/startup/nsAppStartup.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef nsAppStartup_h__
|
||||
#define nsAppStartup_h__
|
||||
|
||||
#include "nsIAppStartup.h"
|
||||
#include "nsIWindowCreator2.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsWeakReference.h"
|
||||
|
||||
#include "nsINativeAppSupport.h"
|
||||
#include "nsIAppShell.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#if defined(XP_WIN)
|
||||
//XPerf-backed probes
|
||||
#include "mozilla/perfprobe.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#endif //defined(XP_WIN)
|
||||
|
||||
|
||||
// {7DD4D320-C84B-4624-8D45-7BB9B2356977}
|
||||
#define NS_TOOLKIT_APPSTARTUP_CID \
|
||||
{ 0x7dd4d320, 0xc84b, 0x4624, { 0x8d, 0x45, 0x7b, 0xb9, 0xb2, 0x35, 0x69, 0x77 } }
|
||||
|
||||
|
||||
class nsAppStartup final : public nsIAppStartup,
|
||||
public nsIWindowCreator2,
|
||||
public nsIObserver,
|
||||
public nsSupportsWeakReference
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIAPPSTARTUP
|
||||
NS_DECL_NSIWINDOWCREATOR
|
||||
NS_DECL_NSIWINDOWCREATOR2
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
nsAppStartup();
|
||||
nsresult Init();
|
||||
|
||||
private:
|
||||
~nsAppStartup() { }
|
||||
|
||||
void CloseAllWindows();
|
||||
|
||||
friend class nsAppExitEvent;
|
||||
|
||||
nsCOMPtr<nsIAppShell> mAppShell;
|
||||
|
||||
int32_t mConsiderQuitStopper; // if > 0, Quit(eConsiderQuit) fails
|
||||
bool mRunning; // Have we started the main event loop?
|
||||
bool mShuttingDown; // Quit method reentrancy check
|
||||
bool mStartingUp; // Have we passed final-ui-startup?
|
||||
bool mAttemptingQuit; // Quit(eAttemptQuit) still trying
|
||||
bool mRestart; // Quit(eRestart)
|
||||
bool mInterrupted; // Was startup interrupted by an interactive prompt?
|
||||
bool mIsSafeModeNecessary; // Whether safe mode is necessary
|
||||
bool mStartupCrashTrackingEnded; // Whether startup crash tracking has already ended
|
||||
bool mRestartNotSameProfile; // Quit(eRestartNotSameProfile)
|
||||
|
||||
#if defined(XP_WIN)
|
||||
//Interaction with OS-provided profiling probes
|
||||
typedef mozilla::probes::ProbeManager ProbeManager;
|
||||
typedef mozilla::probes::Probe Probe;
|
||||
RefPtr<ProbeManager> mProbesManager;
|
||||
RefPtr<Probe> mPlacesInitCompleteProbe;
|
||||
RefPtr<Probe> mSessionWindowRestoredProbe;
|
||||
RefPtr<Probe> mXPCOMShutdownProbe;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // nsAppStartup_h__
|
||||
23
toolkit/components/startup/nsUserInfo.h
Normal file
23
toolkit/components/startup/nsUserInfo.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef __nsUserInfo_h
|
||||
#define __nsUserInfo_h
|
||||
|
||||
#include "nsIUserInfo.h"
|
||||
|
||||
class nsUserInfo: public nsIUserInfo
|
||||
|
||||
{
|
||||
public:
|
||||
nsUserInfo(void);
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIUSERINFO
|
||||
|
||||
protected:
|
||||
virtual ~nsUserInfo();
|
||||
};
|
||||
|
||||
#endif /* __nsUserInfo_h */
|
||||
25
toolkit/components/startup/nsUserInfoMac.h
Normal file
25
toolkit/components/startup/nsUserInfoMac.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef __nsUserInfoMac_h
|
||||
#define __nsUserInfoMac_h
|
||||
|
||||
#include "nsIUserInfo.h"
|
||||
#include "nsReadableUtils.h"
|
||||
|
||||
class nsUserInfo: public nsIUserInfo
|
||||
{
|
||||
public:
|
||||
nsUserInfo();
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIUSERINFO
|
||||
|
||||
nsresult GetPrimaryEmailAddress(nsCString &aEmailAddress);
|
||||
|
||||
protected:
|
||||
virtual ~nsUserInfo() {}
|
||||
};
|
||||
|
||||
#endif /* __nsUserInfo_h */
|
||||
84
toolkit/components/startup/nsUserInfoMac.mm
Normal file
84
toolkit/components/startup/nsUserInfoMac.mm
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/* -*- Mode: Objective-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 "nsUserInfoMac.h"
|
||||
#include "nsObjCExceptions.h"
|
||||
#include "nsString.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <AddressBook/AddressBook.h>
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsUserInfo, nsIUserInfo)
|
||||
|
||||
nsUserInfo::nsUserInfo() {}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetFullname(char16_t **aFullname)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
|
||||
|
||||
NS_ConvertUTF8toUTF16 fullName([NSFullUserName() UTF8String]);
|
||||
*aFullname = ToNewUnicode(fullName);
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetUsername(char **aUsername)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
|
||||
|
||||
nsAutoCString username([NSUserName() UTF8String]);
|
||||
*aUsername = ToNewCString(username);
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsUserInfo::GetPrimaryEmailAddress(nsCString &aEmailAddress)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
|
||||
|
||||
// Try to get this user's primary email from the system addressbook's "me card"
|
||||
// (if they've filled it)
|
||||
ABPerson *me = [[ABAddressBook sharedAddressBook] me];
|
||||
ABMultiValue *emailAddresses = [me valueForProperty:kABEmailProperty];
|
||||
if ([emailAddresses count] > 0) {
|
||||
// get the index of the primary email, in case there are more than one
|
||||
int primaryEmailIndex = [emailAddresses indexForIdentifier:[emailAddresses primaryIdentifier]];
|
||||
aEmailAddress.Assign([[emailAddresses valueAtIndex:primaryEmailIndex] UTF8String]);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetEmailAddress(char **aEmailAddress)
|
||||
{
|
||||
nsAutoCString email;
|
||||
if (NS_SUCCEEDED(GetPrimaryEmailAddress(email)))
|
||||
*aEmailAddress = ToNewCString(email);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetDomain(char **aDomain)
|
||||
{
|
||||
nsAutoCString email;
|
||||
if (NS_SUCCEEDED(GetPrimaryEmailAddress(email))) {
|
||||
int32_t index = email.FindChar('@');
|
||||
if (index != -1) {
|
||||
// chop off everything before, and including the '@'
|
||||
*aDomain = ToNewCString(Substring(email, index + 1));
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
167
toolkit/components/startup/nsUserInfoUnix.cpp
Normal file
167
toolkit/components/startup/nsUserInfoUnix.cpp
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
/* -*- 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 "nsUserInfo.h"
|
||||
#include "nsCRT.h"
|
||||
|
||||
#include <pwd.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include "nsString.h"
|
||||
#include "nsXPIDLString.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsNativeCharsetUtils.h"
|
||||
|
||||
/* Some UNIXy platforms don't have pw_gecos. In this case we use pw_name */
|
||||
#if defined(NO_PW_GECOS)
|
||||
#define PW_GECOS pw_name
|
||||
#else
|
||||
#define PW_GECOS pw_gecos
|
||||
#endif
|
||||
|
||||
nsUserInfo::nsUserInfo()
|
||||
{
|
||||
}
|
||||
|
||||
nsUserInfo::~nsUserInfo()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsUserInfo,nsIUserInfo)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetFullname(char16_t **aFullname)
|
||||
{
|
||||
struct passwd *pw = nullptr;
|
||||
|
||||
pw = getpwuid (geteuid());
|
||||
|
||||
if (!pw || !pw->PW_GECOS) return NS_ERROR_FAILURE;
|
||||
|
||||
#ifdef DEBUG_sspitzer
|
||||
printf("fullname = %s\n", pw->PW_GECOS);
|
||||
#endif
|
||||
|
||||
nsAutoCString fullname(pw->PW_GECOS);
|
||||
|
||||
// now try to parse the GECOS information, which will be in the form
|
||||
// Full Name, <other stuff> - eliminate the ", <other stuff>
|
||||
// also, sometimes GECOS uses "&" to mean "the user name" so do
|
||||
// the appropriate substitution
|
||||
|
||||
// truncate at first comma (field delimiter)
|
||||
int32_t index;
|
||||
if ((index = fullname.Find(",")) != kNotFound)
|
||||
fullname.Truncate(index);
|
||||
|
||||
// replace ampersand with username
|
||||
if (pw->pw_name) {
|
||||
nsAutoCString username(pw->pw_name);
|
||||
if (!username.IsEmpty() && nsCRT::IsLower(username.CharAt(0)))
|
||||
username.SetCharAt(nsCRT::ToUpper(username.CharAt(0)), 0);
|
||||
|
||||
fullname.ReplaceSubstring("&", username.get());
|
||||
}
|
||||
|
||||
nsAutoString unicodeFullname;
|
||||
NS_CopyNativeToUnicode(fullname, unicodeFullname);
|
||||
|
||||
*aFullname = ToNewUnicode(unicodeFullname);
|
||||
|
||||
if (*aFullname)
|
||||
return NS_OK;
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetUsername(char * *aUsername)
|
||||
{
|
||||
struct passwd *pw = nullptr;
|
||||
|
||||
// is this portable? those are POSIX compliant calls, but I need to check
|
||||
pw = getpwuid(geteuid());
|
||||
|
||||
if (!pw || !pw->pw_name) return NS_ERROR_FAILURE;
|
||||
|
||||
#ifdef DEBUG_sspitzer
|
||||
printf("username = %s\n", pw->pw_name);
|
||||
#endif
|
||||
|
||||
*aUsername = strdup(pw->pw_name);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetDomain(char * *aDomain)
|
||||
{
|
||||
nsresult rv = NS_ERROR_FAILURE;
|
||||
|
||||
struct utsname buf;
|
||||
char *domainname = nullptr;
|
||||
|
||||
if (uname(&buf) < 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
domainname = buf.domainname;
|
||||
#endif
|
||||
|
||||
if (domainname && domainname[0]) {
|
||||
*aDomain = strdup(domainname);
|
||||
rv = NS_OK;
|
||||
}
|
||||
else {
|
||||
// try to get the hostname from the nodename
|
||||
// on machines that use DHCP, domainname may not be set
|
||||
// but the nodename might.
|
||||
if (buf.nodename[0]) {
|
||||
// if the nodename is foo.bar.org, use bar.org as the domain
|
||||
char *pos = strchr(buf.nodename,'.');
|
||||
if (pos) {
|
||||
*aDomain = strdup(pos+1);
|
||||
rv = NS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetEmailAddress(char * *aEmailAddress)
|
||||
{
|
||||
// use username + "@" + domain for the email address
|
||||
|
||||
nsresult rv;
|
||||
|
||||
nsAutoCString emailAddress;
|
||||
nsXPIDLCString username;
|
||||
nsXPIDLCString domain;
|
||||
|
||||
rv = GetUsername(getter_Copies(username));
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = GetDomain(getter_Copies(domain));
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
if (!username.IsEmpty() && !domain.IsEmpty()) {
|
||||
emailAddress = (const char *)username;
|
||||
emailAddress += "@";
|
||||
emailAddress += (const char *)domain;
|
||||
}
|
||||
else {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
*aEmailAddress = ToNewCString(emailAddress);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
133
toolkit/components/startup/nsUserInfoWin.cpp
Normal file
133
toolkit/components/startup/nsUserInfoWin.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/* -*- 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 "nsUserInfo.h"
|
||||
|
||||
#include "mozilla/ArrayUtils.h" // ArrayLength
|
||||
#include "nsString.h"
|
||||
#include "windows.h"
|
||||
#include "nsCRT.h"
|
||||
#include "nsXPIDLString.h"
|
||||
|
||||
#define SECURITY_WIN32
|
||||
#include "lm.h"
|
||||
#include "security.h"
|
||||
|
||||
nsUserInfo::nsUserInfo()
|
||||
{
|
||||
}
|
||||
|
||||
nsUserInfo::~nsUserInfo()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsUserInfo, nsIUserInfo)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetUsername(char **aUsername)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aUsername);
|
||||
*aUsername = nullptr;
|
||||
|
||||
// ULEN is the max username length as defined in lmcons.h
|
||||
wchar_t username[UNLEN +1];
|
||||
DWORD size = mozilla::ArrayLength(username);
|
||||
if (!GetUserNameW(username, &size))
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
*aUsername = ToNewUTF8String(nsDependentString(username));
|
||||
return (*aUsername) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetFullname(char16_t **aFullname)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aFullname);
|
||||
*aFullname = nullptr;
|
||||
|
||||
wchar_t fullName[512];
|
||||
DWORD size = mozilla::ArrayLength(fullName);
|
||||
|
||||
if (GetUserNameExW(NameDisplay, fullName, &size)) {
|
||||
*aFullname = ToNewUnicode(nsDependentString(fullName));
|
||||
} else {
|
||||
DWORD getUsernameError = GetLastError();
|
||||
|
||||
// Try to use the net APIs regardless of the error because it may be
|
||||
// able to obtain the information.
|
||||
wchar_t username[UNLEN + 1];
|
||||
size = mozilla::ArrayLength(username);
|
||||
if (!GetUserNameW(username, &size)) {
|
||||
// ERROR_NONE_MAPPED means the user info is not filled out on this computer
|
||||
return getUsernameError == ERROR_NONE_MAPPED ?
|
||||
NS_ERROR_NOT_AVAILABLE : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
const DWORD level = 2;
|
||||
LPBYTE info;
|
||||
// If the NetUserGetInfo function has no full name info it will return
|
||||
// success with an empty string.
|
||||
NET_API_STATUS status = NetUserGetInfo(nullptr, username, level, &info);
|
||||
if (status != NERR_Success) {
|
||||
// We have an error with NetUserGetInfo but we know the info is not
|
||||
// filled in because GetUserNameExW returned ERROR_NONE_MAPPED.
|
||||
return getUsernameError == ERROR_NONE_MAPPED ?
|
||||
NS_ERROR_NOT_AVAILABLE : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsDependentString fullName =
|
||||
nsDependentString(reinterpret_cast<USER_INFO_2 *>(info)->usri2_full_name);
|
||||
|
||||
// NetUserGetInfo returns an empty string if the full name is not filled out
|
||||
if (fullName.Length() == 0) {
|
||||
NetApiBufferFree(info);
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
*aFullname = ToNewUnicode(fullName);
|
||||
NetApiBufferFree(info);
|
||||
}
|
||||
|
||||
return (*aFullname) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetDomain(char **aDomain)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aDomain);
|
||||
*aDomain = nullptr;
|
||||
|
||||
const DWORD level = 100;
|
||||
LPBYTE info;
|
||||
NET_API_STATUS status = NetWkstaGetInfo(nullptr, level, &info);
|
||||
if (status == NERR_Success) {
|
||||
*aDomain =
|
||||
ToNewUTF8String(nsDependentString(reinterpret_cast<WKSTA_INFO_100 *>(info)->
|
||||
wki100_langroup));
|
||||
NetApiBufferFree(info);
|
||||
}
|
||||
|
||||
return (*aDomain) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsUserInfo::GetEmailAddress(char **aEmailAddress)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aEmailAddress);
|
||||
*aEmailAddress = nullptr;
|
||||
|
||||
// RFC3696 says max length of an email address is 254
|
||||
wchar_t emailAddress[255];
|
||||
DWORD size = mozilla::ArrayLength(emailAddress);
|
||||
|
||||
if (!GetUserNameExW(NameUserPrincipal, emailAddress, &size)) {
|
||||
DWORD getUsernameError = GetLastError();
|
||||
return getUsernameError == ERROR_NONE_MAPPED ?
|
||||
NS_ERROR_NOT_AVAILABLE : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
*aEmailAddress = ToNewUTF8String(nsDependentString(emailAddress));
|
||||
return (*aEmailAddress) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
13
toolkit/components/startup/public/moz.build
Normal file
13
toolkit/components/startup/public/moz.build
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# -*- 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/.
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIAppStartup.idl',
|
||||
'nsIUserInfo.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'appstartup'
|
||||
|
||||
195
toolkit/components/startup/public/nsIAppStartup.idl
Normal file
195
toolkit/components/startup/public/nsIAppStartup.idl
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/* -*- 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 nsICmdLineService;
|
||||
interface nsIToolkitProfile;
|
||||
|
||||
[scriptable, uuid(6621f6d5-6c04-4a0e-9e74-447db221484e)]
|
||||
|
||||
interface nsIAppStartup : nsISupports
|
||||
{
|
||||
/**
|
||||
* Create the hidden window.
|
||||
*/
|
||||
void createHiddenWindow();
|
||||
|
||||
/**
|
||||
* Destroys the hidden window. This will have no effect if the hidden window
|
||||
* has not yet been created.
|
||||
*/
|
||||
void destroyHiddenWindow();
|
||||
|
||||
/**
|
||||
* Runs an application event loop: normally the main event pump which
|
||||
* defines the lifetime of the application. If there are no windows open
|
||||
* and no outstanding calls to enterLastWindowClosingSurvivalArea this
|
||||
* method will exit immediately.
|
||||
*
|
||||
* @returnCode NS_SUCCESS_RESTART_APP
|
||||
* This return code indicates that the application should be
|
||||
* restarted because quit was called with the eRestart flag.
|
||||
|
||||
* @returnCode NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE
|
||||
* This return code indicates that the application should be
|
||||
* restarted without necessarily using the same profile because
|
||||
* quit was called with the eRestartNotSameProfile flag.
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* There are situations where all application windows will be
|
||||
* closed but we don't want to take this as a signal to quit the
|
||||
* app. Bracket the code where the last window could close with
|
||||
* these.
|
||||
*/
|
||||
void enterLastWindowClosingSurvivalArea();
|
||||
void exitLastWindowClosingSurvivalArea();
|
||||
|
||||
/**
|
||||
* Startup Crash Detection
|
||||
*
|
||||
* Keeps track of application startup begining and success using flags to
|
||||
* determine whether the application is crashing on startup.
|
||||
* When the number of crashes crosses the acceptable threshold, safe mode
|
||||
* or other repair procedures are performed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Whether automatic safe mode is necessary at this time. This gets set
|
||||
* in trackStartupCrashBegin.
|
||||
*
|
||||
* @see trackStartupCrashBegin
|
||||
*/
|
||||
readonly attribute boolean automaticSafeModeNecessary;
|
||||
|
||||
/**
|
||||
* Restart the application in safe mode
|
||||
* @param aQuitMode
|
||||
* This parameter modifies how the app is shutdown.
|
||||
* @see nsIAppStartup::quit
|
||||
*/
|
||||
void restartInSafeMode(in uint32_t aQuitMode);
|
||||
|
||||
/**
|
||||
* Run a new instance of this app with a specified profile
|
||||
* @param aProfile
|
||||
* The profile we want to use.
|
||||
* @see nsIAppStartup::quit
|
||||
*/
|
||||
void createInstanceWithProfile(in nsIToolkitProfile aProfile);
|
||||
|
||||
/**
|
||||
* If the last startup crashed then increment a counter.
|
||||
* Set a flag so on next startup we can detect whether TrackStartupCrashEnd
|
||||
* was called (and therefore the application crashed).
|
||||
* @return whether safe mode is necessary
|
||||
*/
|
||||
bool trackStartupCrashBegin();
|
||||
|
||||
/**
|
||||
* We have succesfully started without crashing. Clear flags that were
|
||||
* tracking past crashes.
|
||||
*/
|
||||
void trackStartupCrashEnd();
|
||||
|
||||
/**
|
||||
* The following flags may be passed as the aMode parameter to the quit
|
||||
* method. One and only one of the "Quit" flags must be specified. The
|
||||
* eRestart flag may be bit-wise combined with one of the "Quit" flags to
|
||||
* cause the application to restart after it quits.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Attempt to quit if all windows are closed.
|
||||
*/
|
||||
const uint32_t eConsiderQuit = 0x01;
|
||||
|
||||
/**
|
||||
* Try to close all windows, then quit if successful.
|
||||
*/
|
||||
const uint32_t eAttemptQuit = 0x02;
|
||||
|
||||
/**
|
||||
* Quit, damnit!
|
||||
*/
|
||||
const uint32_t eForceQuit = 0x03;
|
||||
|
||||
/**
|
||||
* Restart the application after quitting. The application will be
|
||||
* restarted with the same profile and an empty command line.
|
||||
*/
|
||||
const uint32_t eRestart = 0x10;
|
||||
|
||||
/**
|
||||
* When restarting attempt to start in the i386 architecture. Only supported
|
||||
* on OSX.
|
||||
*/
|
||||
const uint32_t eRestarti386 = 0x20;
|
||||
|
||||
/**
|
||||
* When restarting attempt to start in the x86_64 architecture. Only
|
||||
* supported on OSX.
|
||||
*/
|
||||
const uint32_t eRestartx86_64 = 0x40;
|
||||
|
||||
/**
|
||||
* Restart the application after quitting. The application will be
|
||||
* restarted with an empty command line and the normal profile selection
|
||||
* process will take place on startup.
|
||||
*/
|
||||
const uint32_t eRestartNotSameProfile = 0x100;
|
||||
|
||||
/**
|
||||
* Exit the event loop, and shut down the app.
|
||||
*
|
||||
* @param aMode
|
||||
* This parameter modifies how the app is shutdown, and it is
|
||||
* constructed from the constants defined above.
|
||||
*/
|
||||
void quit(in uint32_t aMode);
|
||||
|
||||
/**
|
||||
* True if the application is in the process of shutting down.
|
||||
*/
|
||||
readonly attribute boolean shuttingDown;
|
||||
|
||||
/**
|
||||
* True if the application is in the process of starting up.
|
||||
*
|
||||
* Startup is complete once all observers of final-ui-startup have returned.
|
||||
*/
|
||||
readonly attribute boolean startingUp;
|
||||
|
||||
/**
|
||||
* Mark the startup as completed.
|
||||
*
|
||||
* Called at the end of startup by nsAppRunner.
|
||||
*/
|
||||
[noscript] void doneStartingUp();
|
||||
|
||||
/**
|
||||
* True if the application is being restarted
|
||||
*/
|
||||
readonly attribute boolean restarting;
|
||||
|
||||
/**
|
||||
* True if this is the startup following restart, i.e. if the application
|
||||
* was restarted using quit(eRestart*).
|
||||
*/
|
||||
readonly attribute boolean wasRestarted;
|
||||
|
||||
/**
|
||||
* Returns an object with main, process, firstPaint, sessionRestored properties.
|
||||
* Properties may not be available depending on platform or application
|
||||
*/
|
||||
[implicit_jscontext] jsval getStartupInfo();
|
||||
|
||||
/**
|
||||
* True if startup was interrupted by an interactive prompt.
|
||||
*/
|
||||
attribute boolean interrupted;
|
||||
};
|
||||
32
toolkit/components/startup/public/nsIUserInfo.idl
Normal file
32
toolkit/components/startup/public/nsIUserInfo.idl
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* -*- 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"
|
||||
|
||||
[scriptable, uuid(6c1034f0-1dd2-11b2-aa14-e6657ed7bb0b)]
|
||||
interface nsIUserInfo : nsISupports
|
||||
{
|
||||
/* these are things the system may know about the current user */
|
||||
|
||||
readonly attribute wstring fullname;
|
||||
|
||||
readonly attribute string emailAddress;
|
||||
|
||||
/* should this be a wstring? */
|
||||
readonly attribute string username;
|
||||
|
||||
readonly attribute string domain;
|
||||
};
|
||||
|
||||
%{C++
|
||||
|
||||
// 14c13684-1dd2-11b2-9463-bb10ba742554
|
||||
#define NS_USERINFO_CID \
|
||||
{ 0x14c13684, 0x1dd2, 0x11b2, \
|
||||
{0x94, 0x63, 0xbb, 0x10, 0xba, 0x74, 0x25, 0x54}}
|
||||
|
||||
#define NS_USERINFO_CONTRACTID "@mozilla.org/userinfo;1"
|
||||
|
||||
%}
|
||||
7
toolkit/components/startup/tests/browser/.eslintrc.js
Normal file
7
toolkit/components/startup/tests/browser/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/mochitest/browser.eslintrc.js"
|
||||
]
|
||||
};
|
||||
10
toolkit/components/startup/tests/browser/beforeunload.html
Normal file
10
toolkit/components/startup/tests/browser/beforeunload.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<script>
|
||||
window.onbeforeunload = function(event) {
|
||||
event.returnValue = 'Test beforeunload handler';
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
Test page
|
||||
</body>
|
||||
</html>
|
||||
8
toolkit/components/startup/tests/browser/browser.ini
Normal file
8
toolkit/components/startup/tests/browser/browser.ini
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
beforeunload.html
|
||||
|
||||
[browser_bug511456.js]
|
||||
[browser_bug537449.js]
|
||||
[browser_crash_detection.js]
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URL = "http://example.com/browser/toolkit/components/startup/tests/browser/beforeunload.html";
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
ignoreAllUncaughtExceptions();
|
||||
|
||||
let win2 = window.openDialog(location, "", "chrome,all,dialog=no", "about:blank");
|
||||
win2.addEventListener("load", function onLoad() {
|
||||
win2.removeEventListener("load", onLoad);
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
|
||||
whenBrowserLoaded(browser, function () {
|
||||
let seenDialog = false;
|
||||
|
||||
// Cancel the prompt the first time.
|
||||
waitForOnBeforeUnloadDialog(browser, (btnLeave, btnStay) => {
|
||||
seenDialog = true;
|
||||
btnStay.click();
|
||||
});
|
||||
|
||||
let appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
|
||||
getService(Ci.nsIAppStartup);
|
||||
appStartup.quit(Ci.nsIAppStartup.eAttemptQuit);
|
||||
ok(seenDialog, "Should have seen a prompt dialog");
|
||||
ok(!win2.closed, "Shouldn't have closed the additional window");
|
||||
win2.close();
|
||||
|
||||
// Leave the page the second time.
|
||||
waitForOnBeforeUnloadDialog(browser, (btnLeave, btnStay) => {
|
||||
btnLeave.click();
|
||||
});
|
||||
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
executeSoon(finish);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: this.docShell is null");
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
|
||||
|
||||
const TEST_URL = "http://example.com/browser/toolkit/components/startup/tests/browser/beforeunload.html";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
|
||||
whenBrowserLoaded(browser, function () {
|
||||
let seenDialog = false;
|
||||
|
||||
// Cancel the prompt the first time.
|
||||
waitForOnBeforeUnloadDialog(browser, (btnLeave, btnStay) => {
|
||||
seenDialog = true;
|
||||
btnStay.click();
|
||||
});
|
||||
|
||||
let appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
|
||||
getService(Ci.nsIAppStartup);
|
||||
appStartup.quit(Ci.nsIAppStartup.eAttemptQuit);
|
||||
ok(seenDialog, "Should have seen a prompt dialog");
|
||||
ok(!window.closed, "Shouldn't have closed the window");
|
||||
|
||||
let win2 = window.openDialog(location, "", "chrome,all,dialog=no", "about:blank");
|
||||
ok(win2 != null, "Should have been able to open a new window");
|
||||
win2.addEventListener("load", function onLoad() {
|
||||
win2.removeEventListener("load", onLoad);
|
||||
win2.close();
|
||||
|
||||
// Leave the page the second time.
|
||||
waitForOnBeforeUnloadDialog(browser, (btnLeave, btnStay) => {
|
||||
btnLeave.click();
|
||||
});
|
||||
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
executeSoon(finish);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
function test() {
|
||||
function checkLastSuccess() {
|
||||
let lastSuccess = Services.prefs.getIntPref("toolkit.startup.last_success");
|
||||
let si = Services.startup.getStartupInfo();
|
||||
is(lastSuccess, parseInt(si["main"].getTime() / 1000, 10),
|
||||
"Startup tracking pref should be set after a delay at the end of startup");
|
||||
finish();
|
||||
}
|
||||
|
||||
if (Services.prefs.getPrefType("toolkit.startup.max_resumed_crashes") == Services.prefs.PREF_INVALID) {
|
||||
info("Skipping this test since startup crash detection is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
const startupCrashEndDelay = 35 * 1000;
|
||||
waitForExplicitFinish();
|
||||
requestLongerTimeout(2);
|
||||
setTimeout(checkLastSuccess, startupCrashEndDelay);
|
||||
}
|
||||
29
toolkit/components/startup/tests/browser/head.js
Normal file
29
toolkit/components/startup/tests/browser/head.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
function whenBrowserLoaded(browser, callback) {
|
||||
return BrowserTestUtils.browserLoaded(browser).then(callback);
|
||||
}
|
||||
|
||||
function waitForOnBeforeUnloadDialog(browser, callback) {
|
||||
browser.addEventListener("DOMWillOpenModalDialog", function onModalDialog(event) {
|
||||
if (Cu.isCrossProcessWrapper(event.target)) {
|
||||
// This event fires in both the content and chrome processes. We
|
||||
// want to ignore the one in the content process.
|
||||
return;
|
||||
}
|
||||
|
||||
browser.removeEventListener("DOMWillOpenModalDialog", onModalDialog, true);
|
||||
|
||||
executeSoon(() => {
|
||||
let stack = browser.parentNode;
|
||||
let dialogs = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
|
||||
let {button0, button1} = dialogs[0].ui;
|
||||
callback(button0, button1);
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
7
toolkit/components/startup/tests/unit/.eslintrc.js
Normal file
7
toolkit/components/startup/tests/unit/.eslintrc.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"../../../../../testing/xpcshell/xpcshell.eslintrc.js"
|
||||
]
|
||||
};
|
||||
30
toolkit/components/startup/tests/unit/head_startup.js
Normal file
30
toolkit/components/startup/tests/unit/head_startup.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const XULRUNTIME_CONTRACTID = "@mozilla.org/xre/runtime;1";
|
||||
const XULRUNTIME_CID = Components.ID("7685dac8-3637-4660-a544-928c5ec0e714}");
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
var gAppInfo = null;
|
||||
|
||||
function createAppInfo(ID, name, version, platformVersion="1.0") {
|
||||
let tmp = {};
|
||||
Components.utils.import("resource://testing-common/AppInfo.jsm", tmp);
|
||||
gAppInfo = tmp.newAppInfo({
|
||||
ID, name, version, platformVersion,
|
||||
crashReporter: true,
|
||||
replacedLockTime: 0,
|
||||
});
|
||||
|
||||
let XULAppInfoFactory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer != null)
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
return gAppInfo.QueryInterface(iid);
|
||||
}
|
||||
};
|
||||
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
registrar.registerFactory(XULRUNTIME_CID, "XULRuntime",
|
||||
XULRUNTIME_CONTRACTID, XULAppInfoFactory);
|
||||
}
|
||||
300
toolkit/components/startup/tests/unit/test_startup_crash.js
Normal file
300
toolkit/components/startup/tests/unit/test_startup_crash.js
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "10.0");
|
||||
|
||||
var prefService = Services.prefs;
|
||||
var appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
|
||||
getService(Ci.nsIAppStartup);
|
||||
|
||||
const pref_last_success = "toolkit.startup.last_success";
|
||||
const pref_recent_crashes = "toolkit.startup.recent_crashes";
|
||||
const pref_max_resumed_crashes = "toolkit.startup.max_resumed_crashes";
|
||||
const pref_always_use_safe_mode = "toolkit.startup.always_use_safe_mode";
|
||||
|
||||
function run_test() {
|
||||
prefService.setBoolPref(pref_always_use_safe_mode, true);
|
||||
|
||||
resetTestEnv(0);
|
||||
|
||||
test_trackStartupCrashBegin();
|
||||
test_trackStartupCrashEnd();
|
||||
test_trackStartupCrashBegin_safeMode();
|
||||
test_trackStartupCrashEnd_safeMode();
|
||||
test_maxResumed();
|
||||
resetTestEnv(0);
|
||||
|
||||
prefService.clearUserPref(pref_always_use_safe_mode);
|
||||
}
|
||||
|
||||
// reset prefs to default
|
||||
function resetTestEnv(replacedLockTime) {
|
||||
try {
|
||||
// call begin to reset mStartupCrashTrackingEnded
|
||||
appStartup.trackStartupCrashBegin();
|
||||
} catch (x) { }
|
||||
prefService.setIntPref(pref_max_resumed_crashes, 2);
|
||||
prefService.clearUserPref(pref_recent_crashes);
|
||||
gAppInfo.replacedLockTime = replacedLockTime;
|
||||
prefService.clearUserPref(pref_last_success);
|
||||
}
|
||||
|
||||
function now_seconds() {
|
||||
return ms_to_s(Date.now());
|
||||
}
|
||||
|
||||
function ms_to_s(ms) {
|
||||
return Math.floor(ms / 1000);
|
||||
}
|
||||
|
||||
function test_trackStartupCrashBegin() {
|
||||
let max_resumed = prefService.getIntPref(pref_max_resumed_crashes);
|
||||
do_check_false(gAppInfo.inSafeMode);
|
||||
|
||||
// first run with startup crash tracking - existing profile lock
|
||||
let replacedLockTime = Date.now();
|
||||
resetTestEnv(replacedLockTime);
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_eq(replacedLockTime, gAppInfo.replacedLockTime);
|
||||
try {
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_throw("Should have thrown since last_success is not set");
|
||||
} catch (x) { }
|
||||
|
||||
do_check_false(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// first run with startup crash tracking - no existing profile lock
|
||||
replacedLockTime = 0;
|
||||
resetTestEnv(replacedLockTime);
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_eq(replacedLockTime, gAppInfo.replacedLockTime);
|
||||
try {
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_throw("Should have thrown since last_success is not set");
|
||||
} catch (x) { }
|
||||
|
||||
do_check_false(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup - last startup was success
|
||||
replacedLockTime = Date.now();
|
||||
resetTestEnv(replacedLockTime);
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
do_check_eq(ms_to_s(replacedLockTime), prefService.getIntPref(pref_last_success));
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_check_eq(ms_to_s(replacedLockTime), prefService.getIntPref(pref_last_success));
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup with 1 recent crash
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_recent_crashes, 1);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_check_eq(ms_to_s(replacedLockTime), prefService.getIntPref(pref_last_success));
|
||||
do_check_eq(1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup with max_resumed_crashes crash
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_check_eq(ms_to_s(replacedLockTime), prefService.getIntPref(pref_last_success));
|
||||
do_check_eq(max_resumed, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup with too many recent crashes
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
do_check_true(appStartup.trackStartupCrashBegin());
|
||||
// should remain the same since the last startup was not a crash
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_true(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup with too many recent crashes and startup crash tracking disabled
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_max_resumed_crashes, -1);
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
// should remain the same since the last startup was not a crash
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
// returns false when disabled
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
do_check_eq(-1, prefService.getIntPref(pref_max_resumed_crashes));
|
||||
|
||||
// normal startup after 1 non-recent crash (1 year ago), no other recent
|
||||
replacedLockTime = Date.now() - 365 * 24 * 60 * 60 * 1000;
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 365 * 24 * 60 * 60);
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
// recent crash count pref should be unset since the last crash was not recent
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup after 1 crash (1 minute ago), no other recent
|
||||
replacedLockTime = Date.now() - 60 * 1000;
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 60 * 60); // last success - 1 hour ago
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
// recent crash count pref should be created with value 1
|
||||
do_check_eq(1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup after another crash (1 minute ago), 1 already
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 60 * 60); // last success - 1 hour ago
|
||||
replacedLockTime = Date.now() - 60 * 1000;
|
||||
gAppInfo.replacedLockTime = replacedLockTime;
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
// recent crash count pref should be incremented by 1
|
||||
do_check_eq(2, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup after another crash (1 minute ago), 2 already
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 60 * 60); // last success - 1 hour ago
|
||||
do_check_true(appStartup.trackStartupCrashBegin());
|
||||
// recent crash count pref should be incremented by 1
|
||||
do_check_eq(3, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_true(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// normal startup after 1 non-recent crash (1 year ago), 3 crashes already
|
||||
replacedLockTime = Date.now() - 365 * 24 * 60 * 60 * 1000;
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 60 * 60); // last success - 1 hour ago
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
// recent crash count should be unset since the last crash was not recent
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
}
|
||||
|
||||
function test_trackStartupCrashEnd() {
|
||||
// successful startup with no last_success (upgrade test)
|
||||
let replacedLockTime = Date.now() - 10 * 1000; // 10s ago
|
||||
resetTestEnv(replacedLockTime);
|
||||
try {
|
||||
appStartup.trackStartupCrashBegin(); // required to be called before end
|
||||
do_throw("Should have thrown since last_success is not set");
|
||||
} catch (x) { }
|
||||
appStartup.trackStartupCrashEnd();
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_false(prefService.prefHasUserValue(pref_last_success));
|
||||
|
||||
// successful startup - should set last_success
|
||||
replacedLockTime = Date.now() - 10 * 1000; // 10s ago
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
appStartup.trackStartupCrashBegin(); // required to be called before end
|
||||
appStartup.trackStartupCrashEnd();
|
||||
// ensure last_success was set since we have declared a succesful startup
|
||||
// main timestamp doesn't get set in XPCShell so approximate with now
|
||||
do_check_true(prefService.getIntPref(pref_last_success) <= now_seconds());
|
||||
do_check_true(prefService.getIntPref(pref_last_success) >= now_seconds() - 4 * 60 * 60);
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
|
||||
// successful startup with 1 recent crash
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
prefService.setIntPref(pref_recent_crashes, 1);
|
||||
appStartup.trackStartupCrashBegin(); // required to be called before end
|
||||
appStartup.trackStartupCrashEnd();
|
||||
// ensure recent_crashes was cleared since we have declared a succesful startup
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
}
|
||||
|
||||
function test_trackStartupCrashBegin_safeMode() {
|
||||
gAppInfo.inSafeMode = true;
|
||||
resetTestEnv(0);
|
||||
let max_resumed = prefService.getIntPref(pref_max_resumed_crashes);
|
||||
|
||||
// check manual safe mode doesn't change prefs without crash
|
||||
let replacedLockTime = Date.now() - 10 * 1000; // 10s ago
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_true(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
do_check_false(appStartup.trackStartupCrashBegin());
|
||||
do_check_false(prefService.prefHasUserValue(pref_recent_crashes));
|
||||
do_check_true(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// check forced safe mode doesn't change prefs without crash
|
||||
replacedLockTime = Date.now() - 10 * 1000; // 10s ago
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime));
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_true(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
do_check_true(appStartup.trackStartupCrashBegin());
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_true(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_true(appStartup.automaticSafeModeNecessary);
|
||||
|
||||
// check forced safe mode after old crash
|
||||
replacedLockTime = Date.now() - 365 * 24 * 60 * 60 * 1000;
|
||||
resetTestEnv(replacedLockTime);
|
||||
// one year ago
|
||||
let last_success = ms_to_s(replacedLockTime) - 365 * 24 * 60 * 60;
|
||||
prefService.setIntPref(pref_last_success, last_success);
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_true(prefService.prefHasUserValue(pref_last_success));
|
||||
do_check_true(appStartup.automaticSafeModeNecessary);
|
||||
do_check_true(appStartup.trackStartupCrashBegin());
|
||||
do_check_eq(max_resumed + 1, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_eq(last_success, prefService.getIntPref(pref_last_success));
|
||||
do_check_true(appStartup.automaticSafeModeNecessary);
|
||||
}
|
||||
|
||||
function test_trackStartupCrashEnd_safeMode() {
|
||||
gAppInfo.inSafeMode = true;
|
||||
let replacedLockTime = Date.now();
|
||||
resetTestEnv(replacedLockTime);
|
||||
let max_resumed = prefService.getIntPref(pref_max_resumed_crashes);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 24 * 60 * 60);
|
||||
|
||||
// ensure recent_crashes were not cleared in manual safe mode
|
||||
prefService.setIntPref(pref_recent_crashes, 1);
|
||||
appStartup.trackStartupCrashBegin(); // required to be called before end
|
||||
appStartup.trackStartupCrashEnd();
|
||||
do_check_eq(1, prefService.getIntPref(pref_recent_crashes));
|
||||
|
||||
// recent_crashes should be set to max_resumed in forced safe mode to allow the user
|
||||
// to try and start in regular mode after making changes.
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
appStartup.trackStartupCrashBegin(); // required to be called before end
|
||||
appStartup.trackStartupCrashEnd();
|
||||
do_check_eq(max_resumed, prefService.getIntPref(pref_recent_crashes));
|
||||
}
|
||||
|
||||
function test_maxResumed() {
|
||||
resetTestEnv(0);
|
||||
gAppInfo.inSafeMode = false;
|
||||
let max_resumed = prefService.getIntPref(pref_max_resumed_crashes);
|
||||
let replacedLockTime = Date.now();
|
||||
resetTestEnv(replacedLockTime);
|
||||
prefService.setIntPref(pref_max_resumed_crashes, -1);
|
||||
|
||||
prefService.setIntPref(pref_recent_crashes, max_resumed + 1);
|
||||
prefService.setIntPref(pref_last_success, ms_to_s(replacedLockTime) - 24 * 60 * 60);
|
||||
appStartup.trackStartupCrashBegin();
|
||||
// should remain the same since the last startup was not a crash
|
||||
do_check_eq(max_resumed + 2, prefService.getIntPref(pref_recent_crashes));
|
||||
do_check_false(appStartup.automaticSafeModeNecessary);
|
||||
}
|
||||
6
toolkit/components/startup/tests/unit/xpcshell.ini
Normal file
6
toolkit/components/startup/tests/unit/xpcshell.ini
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[DEFAULT]
|
||||
head = head_startup.js
|
||||
tail =
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_startup_crash.js]
|
||||
Loading…
Add table
Add a link
Reference in a new issue