This commit is contained in:
Ryan VanderMeulen 2012-05-09 18:42:00 -04:00
Родитель 37845d28b0 add06019f5
Коммит e6a700a873
19 изменённых файлов: 608 добавлений и 110 удалений

Просмотреть файл

@ -1012,7 +1012,7 @@ nsDOMWindowUtils::GarbageCollect(nsICycleCollectorListener *aListener,
}
#endif
nsJSContext::GarbageCollectNow(js::gcreason::DOM_UTILS);
nsJSContext::GarbageCollectNow(js::gcreason::DOM_UTILS, nsGCNormal, true);
nsJSContext::CycleCollectNow(aListener, aExtraForgetSkippableCalls);
return NS_OK;

Просмотреть файл

@ -134,6 +134,10 @@ static PRLogModuleInfo* gJSDiagnostics;
// doing the first GC.
#define NS_FIRST_GC_DELAY 10000 // ms
#define NS_FULL_GC_DELAY 60000 // ms
#define NS_MAX_COMPARTMENT_GC_COUNT 20
// Maximum amount of time that should elapse between incremental GC slices
#define NS_INTERSLICE_GC_DELAY 100 // ms
@ -159,6 +163,7 @@ static PRLogModuleInfo* gJSDiagnostics;
static nsITimer *sGCTimer;
static nsITimer *sShrinkGCBuffersTimer;
static nsITimer *sCCTimer;
static nsITimer *sFullGCTimer;
static PRTime sLastCCEndTime;
@ -178,6 +183,7 @@ static bool sLoadingInProgress;
static PRUint32 sCCollectedWaitingForGC;
static bool sPostGCEventsToConsole;
static bool sDisableExplicitCompartmentGC;
static PRUint32 sCCTimerFireCount = 0;
static PRUint32 sMinForgetSkippableTime = PR_UINT32_MAX;
static PRUint32 sMaxForgetSkippableTime = 0;
@ -185,9 +191,10 @@ static PRUint32 sTotalForgetSkippableTime = 0;
static PRUint32 sRemovedPurples = 0;
static PRUint32 sForgetSkippableBeforeCC = 0;
static PRUint32 sPreviousSuspectedCount = 0;
static PRUint32 sCompartmentGCCount = NS_MAX_COMPARTMENT_GC_COUNT;
static PRUint32 sCleanupsSinceLastGC = PR_UINT32_MAX;
static bool sNeedsFullCC = false;
static nsJSContext *sContextList = nsnull;
nsScriptNameSpaceManager *gNameSpaceManager;
@ -229,7 +236,8 @@ nsMemoryPressureObserver::Observe(nsISupports* aSubject, const char* aTopic,
const PRUnichar* aData)
{
if (sGCOnMemoryPressure) {
nsJSContext::GarbageCollectNow(js::gcreason::MEM_PRESSURE, nsGCShrinking);
nsJSContext::GarbageCollectNow(js::gcreason::MEM_PRESSURE, nsGCShrinking,
true);
nsJSContext::CycleCollectNow();
}
return NS_OK;
@ -929,6 +937,8 @@ static const char js_pccounts_content_str[] = JS_OPTIONS_DOT_STR "pccounts.con
static const char js_pccounts_chrome_str[] = JS_OPTIONS_DOT_STR "pccounts.chrome";
static const char js_jit_hardening_str[] = JS_OPTIONS_DOT_STR "jit_hardening";
static const char js_memlog_option_str[] = JS_OPTIONS_DOT_STR "mem.log";
static const char js_disable_explicit_compartment_gc[] =
JS_OPTIONS_DOT_STR "disable_explicit_compartment_gc";
int
nsJSContext::JSOptionChangedCallback(const char *pref, void *data)
@ -938,6 +948,8 @@ nsJSContext::JSOptionChangedCallback(const char *pref, void *data)
PRUint32 newDefaultJSOptions = oldDefaultJSOptions;
sPostGCEventsToConsole = Preferences::GetBool(js_memlog_option_str);
sDisableExplicitCompartmentGC =
Preferences::GetBool(js_disable_explicit_compartment_gc);
bool strict = Preferences::GetBool(js_strict_option_str);
if (strict)
@ -1038,9 +1050,16 @@ nsJSContext::JSOptionChangedCallback(const char *pref, void *data)
}
nsJSContext::nsJSContext(JSRuntime *aRuntime)
: mGCOnDestruction(true),
: mActive(false),
mGCOnDestruction(true),
mExecuteDepth(0)
{
mNext = sContextList;
mPrev = &sContextList;
if (sContextList) {
sContextList->mPrev = &mNext;
}
sContextList = this;
++sContextCount;
@ -1079,6 +1098,11 @@ nsJSContext::~nsJSContext()
nsCycleCollector_DEBUG_wasFreed(static_cast<nsIScriptContext*>(this));
#endif
*mPrev = mNext;
if (mNext) {
mNext->mPrev = mPrev;
}
// We may still have pending termination functions if the context is destroyed
// before they could be executed. In this case, free the references to their
// parameters, but don't execute the functions (see bug 622326).
@ -2849,6 +2873,7 @@ nsJSContext::ScriptEvaluated(bool aTerminated)
if (aTerminated) {
mOperationCallbackTime = 0;
mModalStateTime = 0;
mActive = true;
}
}
@ -2916,9 +2941,20 @@ nsJSContext::ScriptExecuted()
return NS_OK;
}
void
FullGCTimerFired(nsITimer* aTimer, void* aClosure)
{
NS_RELEASE(sFullGCTimer);
uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
nsJSContext::GarbageCollectNow(static_cast<js::gcreason::Reason>(reason),
nsGCNormal, true);
}
//static
void
nsJSContext::GarbageCollectNow(js::gcreason::Reason reason, PRUint32 gckind)
nsJSContext::GarbageCollectNow(js::gcreason::Reason aReason, PRUint32 aGckind,
bool aGlobal)
{
NS_TIME_FUNCTION_MIN(1.0);
SAMPLE_LABEL("GC", "GarbageCollectNow");
@ -2935,9 +2971,35 @@ nsJSContext::GarbageCollectNow(js::gcreason::Reason reason, PRUint32 gckind)
sPendingLoadCount = 0;
sLoadingInProgress = false;
if (nsContentUtils::XPConnect()) {
nsContentUtils::XPConnect()->GarbageCollect(reason, gckind);
if (!nsContentUtils::XPConnect()) {
return;
}
// Use compartment GC when we're not asked to do a shrinking GC nor
// global GC and compartment GC has been called less than
// NS_MAX_COMPARTMENT_GC_COUNT times after the previous global GC.
if (!sDisableExplicitCompartmentGC &&
aGckind != nsGCShrinking && !aGlobal &&
sCompartmentGCCount < NS_MAX_COMPARTMENT_GC_COUNT) {
js::PrepareForFullGC(nsJSRuntime::sRuntime);
for (nsJSContext* cx = sContextList; cx; cx = cx->mNext) {
if (!cx->mActive && cx->mContext) {
if (JSObject* global = cx->GetNativeGlobal()) {
js::SkipCompartmentForGC(js::GetObjectCompartment(global));
}
}
cx->mActive = false;
}
if (js::IsGCScheduled(nsJSRuntime::sRuntime)) {
js::IncrementalGC(nsJSRuntime::sRuntime, aReason);
}
return;
}
for (nsJSContext* cx = sContextList; cx; cx = cx->mNext) {
cx->mActive = false;
}
nsContentUtils::XPConnect()->GarbageCollect(aReason, aGckind);
}
//static
@ -2963,7 +3025,7 @@ nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener,
if (sCCLockedOut) {
// We're in the middle of an incremental GC; finish it first
nsJSContext::GarbageCollectNow(js::gcreason::CC_FORCED, nsGCNormal);
nsJSContext::GarbageCollectNow(js::gcreason::CC_FORCED, nsGCNormal, true);
}
SAMPLE_LABEL("GC", "CycleCollectNow");
@ -3100,7 +3162,8 @@ GCTimerFired(nsITimer *aTimer, void *aClosure)
NS_RELEASE(sGCTimer);
uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
nsJSContext::GarbageCollectNow(static_cast<js::gcreason::Reason>(reason), nsGCIncremental);
nsJSContext::GarbageCollectNow(static_cast<js::gcreason::Reason>(reason),
nsGCNormal, false);
}
void
@ -3156,7 +3219,7 @@ CCTimerFired(nsITimer *aTimer, void *aClosure)
}
// Finish the current incremental GC
nsJSContext::GarbageCollectNow(js::gcreason::CC_FORCED, nsGCNormal);
nsJSContext::GarbageCollectNow(js::gcreason::CC_FORCED, nsGCNormal, true);
}
++sCCTimerFireCount;
@ -3309,6 +3372,15 @@ nsJSContext::KillGCTimer()
}
}
void
nsJSContext::KillFullGCTimer()
{
if (sFullGCTimer) {
sFullGCTimer->Cancel();
NS_RELEASE(sFullGCTimer);
}
}
//static
void
nsJSContext::KillShrinkGCBuffersTimer()
@ -3336,6 +3408,7 @@ nsJSContext::KillCCTimer()
void
nsJSContext::GC(js::gcreason::Reason aReason)
{
mActive = true;
PokeGC(aReason);
}
@ -3417,20 +3490,23 @@ DOMGCSliceCallback(JSRuntime *aRt, js::GCProgress aProgress, const js::GCDescrip
sCCollectedWaitingForGC = 0;
sCleanupsSinceLastGC = 0;
if (aDesc.isCompartment) {
// If this is a compartment GC, restart it. We still want
// a full GC to happen. Compartment GCs usually happen as a
// result of last-ditch or MaybeGC. In both cases it is
// probably a time of heavy activity and we want to delay
// the full GC, but we do want it to happen eventually.
nsJSContext::PokeGC(js::gcreason::POST_COMPARTMENT);
}
sNeedsFullCC = true;
nsJSContext::MaybePokeCC();
if (!aDesc.isCompartment) {
if (aDesc.isCompartment) {
++sCompartmentGCCount;
if (!sFullGCTimer) {
CallCreateInstance("@mozilla.org/timer;1", &sFullGCTimer);
js::gcreason::Reason reason = js::gcreason::FULL_GC_TIMER;
sFullGCTimer->InitWithFuncCallback(FullGCTimerFired,
reinterpret_cast<void *>(reason),
NS_FULL_GC_DELAY,
nsITimer::TYPE_ONE_SHOT);
}
} else {
sCompartmentGCCount = 0;
nsJSContext::KillFullGCTimer();
// Avoid shrinking during heavy activity, which is suggested by
// compartment GC.
nsJSContext::PokeShrinkGCBuffers();
@ -3530,7 +3606,7 @@ void
nsJSRuntime::Startup()
{
// initialize all our statics, so that we can restart XPCOM
sGCTimer = sCCTimer = nsnull;
sGCTimer = sFullGCTimer = sCCTimer = nsnull;
sCCLockedOut = false;
sCCLockedOutTime = 0;
sLastCCEndTime = 0;
@ -3538,6 +3614,7 @@ nsJSRuntime::Startup()
sLoadingInProgress = false;
sCCollectedWaitingForGC = 0;
sPostGCEventsToConsole = false;
sDisableExplicitCompartmentGC = false;
sNeedsFullCC = false;
gNameSpaceManager = nsnull;
sRuntimeService = nsnull;
@ -3829,6 +3906,7 @@ nsJSRuntime::Shutdown()
nsJSContext::KillGCTimer();
nsJSContext::KillShrinkGCBuffersTimer();
nsJSContext::KillCCTimer();
nsJSContext::KillFullGCTimer();
NS_IF_RELEASE(gNameSpaceManager);

Просмотреть файл

@ -184,7 +184,9 @@ public:
static void LoadStart();
static void LoadEnd();
static void GarbageCollectNow(js::gcreason::Reason reason, PRUint32 gckind = nsGCNormal);
static void GarbageCollectNow(js::gcreason::Reason reason,
PRUint32 aGckind,
bool aGlobal);
static void ShrinkGCBuffersNow();
// If aExtraForgetSkippableCalls is -1, forgetSkippable won't be
// called even if the previous collection was GC.
@ -199,6 +201,7 @@ public:
static void MaybePokeCC();
static void KillCCTimer();
static void KillFullGCTimer();
virtual void GC(js::gcreason::Reason aReason);
@ -238,7 +241,7 @@ private:
nsrefcnt GetCCRefcnt();
JSContext *mContext;
PRUint32 mNumEvaluations;
bool mActive;
protected:
struct TerminationFuncHolder;
@ -307,6 +310,9 @@ private:
PRTime mModalStateTime;
PRUint32 mModalStateDepth;
nsJSContext *mNext;
nsJSContext **mPrev;
// mGlobalObjectRef ensures that the outer window stays alive as long as the
// context does. It is eventually collected by the cycle collector.
nsCOMPtr<nsIScriptGlobalObject> mGlobalObjectRef;

Просмотреть файл

@ -2810,7 +2810,8 @@ class Parser(Tokenizer):
def p_ExtendedAttributeIdent(self, p):
"""
ExtendedAttributeIdent : IDENTIFIER EQUALS IDENTIFIER
ExtendedAttributeIdent : IDENTIFIER EQUALS STRING
| IDENTIFIER EQUALS IDENTIFIER
"""
p[0] = (p[1], p[3])

Просмотреть файл

@ -9,3 +9,13 @@ def WebIDLTest(parser, harness):
""")
results = parser.finish()
parser = parser.reset()
parser.parse("""
[Flippety="foo.bar",Floppety=flop]
interface TestExtendedAttr {
[Foopy="foo.bar"] attribute byte b;
};
""")
results = parser.finish()

Просмотреть файл

@ -791,14 +791,14 @@ ContentChild::GetIndexedDBPath()
bool
ContentChild::RecvGarbageCollect()
{
nsJSContext::GarbageCollectNow(js::gcreason::DOM_IPC);
nsJSContext::GarbageCollectNow(js::gcreason::DOM_IPC, nsGCNormal, true);
return true;
}
bool
ContentChild::RecvCycleCollect()
{
nsJSContext::GarbageCollectNow(js::gcreason::DOM_IPC);
nsJSContext::GarbageCollectNow(js::gcreason::DOM_IPC, nsGCNormal, true);
nsJSContext::CycleCollectNow();
return true;
}

Просмотреть файл

@ -81,6 +81,7 @@ waitFor(function() {
# Verify that the callstate changes to connected on the caller as well.
self.assertTrue(receiver.execute_async_script("""
waitFor(function() {
window.wrappedJSObject.incoming.hangUp();
marionetteScriptFinished(true);
}, function() {
return window.wrappedJSObject.callstate == "connected";

Просмотреть файл

@ -52,4 +52,6 @@ window.navigator.mozTelephony.dial("%s");
# Verify the phone number of the incoming call.
self.assertEqual(received, fromPhoneNumber)
sender.execute_script("window.navigator.mozTelephony.calls[0].hangUp();")
receiver.execute_script("window.navigator.mozTelephony.calls[0].hangUp();")

Просмотреть файл

@ -181,6 +181,12 @@ struct JSCompartment
gcState = GCScheduled;
}
void unscheduleGC() {
JS_ASSERT(!rt->gcRunning);
JS_ASSERT(gcState != GCRunning);
gcState = NoGCScheduled;
}
bool isGCScheduled() const {
return gcState == GCScheduled;
}

Просмотреть файл

@ -155,6 +155,12 @@ js::IsGCScheduled(JSRuntime *rt)
return false;
}
JS_FRIEND_API(void)
js::SkipCompartmentForGC(JSCompartment *comp)
{
comp->unscheduleGC();
}
JS_FRIEND_API(void)
js::GCForReason(JSRuntime *rt, gcreason::Reason reason)
{

Просмотреть файл

@ -622,7 +622,8 @@ SizeOfJSContext();
D(DOM_IPC) \
D(DOM_WORKER) \
D(INTER_SLICE_GC) \
D(REFRESH_FRAME)
D(REFRESH_FRAME) \
D(FULL_GC_TIMER)
namespace gcreason {
@ -646,6 +647,9 @@ PrepareForFullGC(JSRuntime *rt);
extern JS_FRIEND_API(bool)
IsGCScheduled(JSRuntime *rt);
extern JS_FRIEND_API(void)
SkipCompartmentForGC(JSCompartment *comp);
/*
* When triggering a GC using one of the functions below, it is first necessary
* to select the compartments to be collected. To do this, you can call

Просмотреть файл

@ -111,6 +111,7 @@ class Marionette(object):
self.session = None
self.window = None
self.emulator = None
self.extra_emulators = []
self.homedir = homedir
self.baseurl = baseurl
self.noWindow = noWindow
@ -141,6 +142,8 @@ class Marionette(object):
self.emulator.close()
if self.b2gbin:
self.b2ginstance.close()
for qemu in self.extra_emulators:
qemu.emulator.close()
def _send_message(self, command, response_key, **kwargs):
if not self.session and command not in ('newSession', 'getStatus'):
@ -313,7 +316,7 @@ class Marionette(object):
return unwrapped
def execute_js_script(self, script, script_args=None, timeout=True):
def execute_js_script(self, script, script_args=None, timeout=True, new_sandbox=True):
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
@ -321,21 +324,30 @@ class Marionette(object):
'value',
value=script,
args=args,
timeout=timeout)
timeout=timeout,
newSandbox=new_sandbox)
return self.unwrapValue(response)
def execute_script(self, script, script_args=None):
def execute_script(self, script, script_args=None, new_sandbox=True):
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
response = self._send_message('executeScript', 'value', value=script, args=args)
response = self._send_message('executeScript',
'value',
value=script,
args=args,
newSandbox=new_sandbox)
return self.unwrapValue(response)
def execute_async_script(self, script, script_args=None):
def execute_async_script(self, script, script_args=None, new_sandbox=True):
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
response = self._send_message('executeAsyncScript', 'value', value=script, args=args)
response = self._send_message('executeAsyncScript',
'value',
value=script,
args=args,
newSandbox=new_sandbox)
return self.unwrapValue(response)
def find_element(self, method, target, id=None):

Просмотреть файл

@ -18,7 +18,6 @@ def skip_if_b2g(target):
class CommonTestCase(unittest.TestCase):
def __init__(self, methodName):
self._qemu = []
unittest.TestCase.__init__(self, methodName)
def kill_gaia_app(self, url):
@ -56,26 +55,27 @@ window.addEventListener('message', function frameload(e) {
def tearDown(self):
if self.marionette.session is not None:
self.marionette.delete_session()
for _qemu in self._qemu:
_qemu.emulator.close()
_qemu = None
self._qemu = []
class MarionetteTestCase(CommonTestCase):
def __init__(self, marionette, methodName='runTest', **kwargs):
self.marionette = marionette
self.extra_emulator_index = -1
CommonTestCase.__init__(self, methodName, **kwargs)
def get_new_emulator(self):
_qemu = Marionette(emulator=True,
homedir=self.marionette.homedir,
baseurl=self.marionette.baseurl,
noWindow=self.marionette.noWindow)
_qemu.start_session()
self._qemu.append(_qemu)
return _qemu
self.extra_emulator_index += 1
if len(self.marionette.extra_emulators) == self.extra_emulator_index:
qemu = Marionette(emulator=True,
homedir=self.marionette.homedir,
baseurl=self.marionette.baseurl,
noWindow=self.marionette.noWindow)
qemu.start_session()
self.marionette.extra_emulators.append(qemu)
else:
qemu = self.marionette.extra_emulators[self.extra_emulator_index]
return qemu
class MarionetteJSTestCase(CommonTestCase):

Просмотреть файл

@ -71,11 +71,11 @@ class TestExecuteAsyncContent(MarionetteTestCase):
def test_same_context(self):
var1 = 'testing'
self.assertEqual(self.marionette.execute_script("""
window.wrappedJSObject._testvar = '%s';
return window.wrappedJSObject._testvar;
this.testvar = '%s';
return this.testvar;
""" % var1), var1)
self.assertEqual(self.marionette.execute_async_script(
"marionetteScriptFinished(window.wrappedJSObject._testvar);"), var1)
"marionetteScriptFinished(this.testvar);", new_sandbox=False), var1)
def test_execute_no_return(self):
self.assertEqual(self.marionette.execute_async_script("marionetteScriptFinished()"), None)
@ -103,6 +103,19 @@ let prefs = Components.classes["@mozilla.org/preferences-service;1"]
marionetteScriptFinished(1);
""")
def test_sandbox_reuse(self):
# Sandboxes between `execute_script()` invocations are shared.
self.marionette.execute_async_script("this.foobar = [23, 42];"
"marionetteScriptFinished();")
self.assertEqual(self.marionette.execute_async_script(
"marionetteScriptFinished(this.foobar);", new_sandbox=False), [23, 42])
self.marionette.execute_async_script("global.barfoo = [42, 23];"
"marionetteScriptFinished();")
self.assertEqual(self.marionette.execute_async_script(
"marionetteScriptFinished(global.barfoo);", new_sandbox=False), [42, 23])
class TestExecuteAsyncChrome(TestExecuteAsyncContent):
def setUp(self):
super(TestExecuteAsyncChrome, self).setUp()
@ -120,3 +133,6 @@ var c = Components.classes;
marionetteScriptFinished(1);
"""))
def test_sandbox_reuse(self):
pass

Просмотреть файл

@ -67,6 +67,13 @@ let prefs = Components.classes["@mozilla.org/preferences-service;1"]
self.assertEqual(self.marionette.execute_script("return {'foo': [1, 'a', 2]};"),
{'foo': [1, 'a', 2]})
def test_sandbox_reuse(self):
# Sandboxes between `execute_script()` invocations are shared.
self.marionette.execute_script("this.foobar = [23, 42];")
self.assertEqual(self.marionette.execute_script("return this.foobar;", new_sandbox=False), [23, 42])
self.marionette.execute_script("global.barfoo = [42, 23];")
self.assertEqual(self.marionette.execute_script("return global.barfoo;", new_sandbox=False), [42, 23])
class TestExecuteChrome(TestExecuteContent):
def setUp(self):
@ -76,3 +83,5 @@ class TestExecuteChrome(TestExecuteContent):
def test_execute_permission(self):
self.assertEqual(1, self.marionette.execute_script("var c = Components.classes;return 1;"))
def test_sandbox_reuse(self):
pass

Просмотреть файл

@ -472,8 +472,16 @@ MarionetteDriverActor.prototype = {
* function body
*/
execute: function MDA_execute(aRequest, directInject) {
logger.info("newSandbox: " + aRequest.newSandbox);
if (aRequest.newSandbox == undefined) {
//if client does not send a value in newSandbox,
//then they expect the same behaviour as webdriver
aRequest.newSandbox = true;
}
if (this.context == "content") {
this.sendAsync("executeScript", {value: aRequest.value, args: aRequest.args});
this.sendAsync("executeScript", {value: aRequest.value,
args: aRequest.args,
newSandbox:aRequest.newSandbox});
return;
}
@ -533,6 +541,11 @@ MarionetteDriverActor.prototype = {
*/
executeJSScript: function MDA_executeJSScript(aRequest) {
//all pure JS scripts will need to call Marionette.finish() to complete the test.
if (aRequest.newSandbox == undefined) {
//if client does not send a value in newSandbox,
//then they expect the same behaviour as webdriver
aRequest.newSandbox = true;
}
if (this.context == "chrome") {
if (aRequest.timeout) {
this.executeWithCallback(aRequest, aRequest.timeout);
@ -562,12 +575,18 @@ MarionetteDriverActor.prototype = {
* function body
*/
executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) {
if (aRequest.newSandbox == undefined) {
//if client does not send a value in newSandbox,
//then they expect the same behaviour as webdriver
aRequest.newSandbox = true;
}
this.command_id = this.uuidGen.generateUUID().toString();
if (this.context == "content") {
this.sendAsync("executeAsyncScript", {value: aRequest.value,
args: aRequest.args,
id: this.command_id});
id: this.command_id,
newSandbox: aRequest.newSandbox});
return;
}

Просмотреть файл

@ -29,6 +29,15 @@ let activeFrame = null;
let curWindow = content;
let elementManager = new ElementManager([]);
// The sandbox we execute test scripts in. Gets lazily created in
// createExecuteContentSandbox().
let sandbox;
// Flag to indicate whether an async script is currently running or not.
let asyncTestRunning = false;
let asyncTestCommandId;
let asyncTestTimeoutId;
/**
* Called when listener is first started up.
* The listener sends its unique window ID and its current URI to the actor.
@ -178,6 +187,7 @@ function sendError(message, status, trace, command_id) {
* Clear test values after completion of test
*/
function resetValues() {
sandbox = null;
marionetteTimeout = null;
curWin = content;
}
@ -197,28 +207,54 @@ function errUnload() {
/**
* Returns a content sandbox that can be used by the execute_foo functions.
*/
function createExecuteContentSandbox(aWindow, marionette, args) {
try {
args = elementManager.convertWrappedArguments(args, aWindow);
}
catch(e) {
sendError(e.message, e.num, e.stack);
return;
}
function createExecuteContentSandbox(aWindow) {
let sandbox = new Cu.Sandbox(aWindow);
sandbox.global = sandbox;
sandbox.window = aWindow;
sandbox.document = sandbox.window.document;
sandbox.navigator = sandbox.window.navigator;
sandbox.__namedArgs = elementManager.applyNamedArgs(args);
sandbox.__marionetteParams = args;
sandbox.__proto__ = sandbox.window;
sandbox.testUtils = utils;
let marionette = new Marionette(false, aWindow, "content", marionetteLogObj);
sandbox.marionette = marionette;
marionette.exports.forEach(function(fn) {
sandbox[fn] = marionette[fn].bind(marionette);
});
sandbox.asyncComplete = function sandbox_asyncComplete(value, status) {
curWindow.removeEventListener("unload", errUnload, false);
/* clear all timeouts potentially generated by the script*/
for (let i = 0; i <= asyncTestTimeoutId; i++) {
curWindow.clearTimeout(i);
}
sendSyncMessage("Marionette:testLog",
{value: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
if (status == 0){
sendResponse({value: elementManager.wrapValue(value), status: status}, asyncTestCommandId);
}
else {
sendError(value, status, null, asyncTestCommandId);
}
asyncTestRunning = false;
asyncTestTimeoutId = undefined;
asyncTestCommandId = undefined;
};
sandbox.finish = function sandbox_finish() {
if (asyncTestRunning) {
sandbox.asyncComplete(marionette.generate_results(), 0);
} else {
return marionette.generate_results();
}
};
sandbox.marionetteScriptFinished = function sandbox_marionetteScriptFinished(value) {
return sandbox.asyncComplete(value, 0);
};
return sandbox;
}
@ -228,15 +264,14 @@ function createExecuteContentSandbox(aWindow, marionette, args) {
*/
function executeScript(msg, directInject) {
let script = msg.json.value;
let marionette = new Marionette(false, curWindow, "content", marionetteLogObj);
let sandbox = createExecuteContentSandbox(curWindow, marionette, msg.json.args);
if (!sandbox)
return;
sandbox.finish = function sandbox_finish() {
return marionette.generate_results();
};
if (msg.json.newSandbox || !sandbox) {
sandbox = createExecuteContentSandbox(curWindow);
if (!sandbox) {
sendError("Could not create sandbox!");
return;
}
}
try {
if (directInject) {
@ -251,6 +286,15 @@ function executeScript(msg, directInject) {
}
}
else {
try {
sandbox.__marionetteParams = elementManager.convertWrappedArguments(
msg.json.args, curWindow);
}
catch(e) {
sendError(e.message, e.num, e.stack);
return;
}
let scriptSrc = "let __marionetteFunc = function(){" + script + "};" +
"__marionetteFunc.apply(null, __marionetteParams);";
let res = Cu.evalInSandbox(scriptSrc, sandbox, "1.8");
@ -302,39 +346,29 @@ function executeJSScript(msg) {
function executeWithCallback(msg, timeout) {
curWindow.addEventListener("unload", errUnload, false);
let script = msg.json.value;
let command_id = msg.json.id;
asyncTestCommandId = msg.json.id;
// Error code 28 is scriptTimeout, but spec says execute_async should return 21 (Timeout),
// see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/execute_async.
// However Selenium code returns 28, see
// http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js.
// We'll stay compatible with the Selenium code.
let timeoutId = curWindow.setTimeout(function() {
contentAsyncReturnFunc('timed out', 28);
asyncTestTimeoutId = curWindow.setTimeout(function() {
sandbox.asyncComplete('timed out', 28);
}, marionetteTimeout);
curWindow.addEventListener('error', function win__onerror(evt) {
curWindow.removeEventListener('error', win__onerror, true);
contentAsyncReturnFunc(evt, 17);
sandbox.asyncComplete(evt, 17);
return true;
}, true);
function contentAsyncReturnFunc(value, status) {
curWindow.removeEventListener("unload", errUnload, false);
/* clear all timeouts potentially generated by the script*/
for(let i=0; i<=timeoutId; i++) {
curWindow.clearTimeout(i);
if (msg.json.newSandbox || !sandbox) {
sandbox = createExecuteContentSandbox(curWindow);
if (!sandbox) {
sendError("Could not create sandbox!");
return;
}
sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
if (status == 0){
sendResponse({value: elementManager.wrapValue(value), status: status}, command_id);
}
else {
sendError(value, status, null, command_id);
}
};
}
let scriptSrc;
if (timeout) {
@ -344,25 +378,23 @@ function executeWithCallback(msg, timeout) {
scriptSrc = script;
}
else {
scriptSrc = "let marionetteScriptFinished = function(value) { return asyncComplete(value,0);};" +
"__marionetteParams.push(marionetteScriptFinished);" +
try {
sandbox.__marionetteParams = elementManager.convertWrappedArguments(
msg.json.args, curWindow);
}
catch(e) {
sendError(e.message, e.num, e.stack);
return;
}
scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" +
"let __marionetteFunc = function() { " + script + "};" +
"__marionetteFunc.apply(null, __marionetteParams); ";
}
let marionette = new Marionette(true, curWindow, "content", marionetteLogObj);
let sandbox = createExecuteContentSandbox(curWindow, marionette, msg.json.args);
if (!sandbox)
return;
sandbox.asyncComplete = contentAsyncReturnFunc;
sandbox.finish = function sandbox_finish() {
contentAsyncReturnFunc(marionette.generate_results(), 0);
};
try {
Cu.evalInSandbox(scriptSrc, sandbox, "1.8");
asyncTestRunning = true;
Cu.evalInSandbox(scriptSrc, sandbox, "1.8");
} catch (e) {
// 17 = JavascriptException
sendError(e.name + ': ' + e.message, 17, e.stack);
@ -621,13 +653,15 @@ function switchToFrame(msg) {
}
break;
}
if (foundFrame != null) {
curWindow = curWindow.frames[foundFrame];
curWindow.focus();
sendOk();
} else {
if (foundFrame == null) {
sendError("Unable to locate frame: " + msg.json.value, 8, null);
return;
}
curWindow = curWindow.frames[foundFrame];
curWindow.focus();
sendOk();
sandbox = null;
}
//call register self when we get loaded

Просмотреть файл

@ -11,10 +11,11 @@ function Marionette(is_async, window, context, logObj) {
this.tests = [];
this.logObj = logObj;
this.context = context;
this.exports = ['ok', 'is', 'isnot', 'log', 'getLogs', 'generate_results', 'waitFor'];
}
Marionette.prototype = {
exports: ['ok', 'is', 'isnot', 'log', 'getLogs', 'generate_results', 'waitFor'],
ok: function Marionette__ok(condition, name, diag) {
let test = {'result': !!condition, 'name': name, 'diag': diag};
this.logResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL");
@ -62,6 +63,8 @@ Marionette.prototype = {
'diag': this.tests[i].diag});
}
}
// Reset state in case this object is reused for more tests.
this.tests = [];
return {"passed": passed, "failed": failed, "failures": failures};
},

Просмотреть файл

@ -0,0 +1,291 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is an NSIS installer for the maintenance service
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2011
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Brian R. Bondy <netzen@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
!verbose 3
; 7-Zip provides better compression than the lzma from NSIS so we add the files
; uncompressed and use 7-Zip to create a SFX archive of it
SetDatablockOptimize on
SetCompress off
CRCCheck on
RequestExecutionLevel admin
!addplugindir ./
; Variables
Var TempMaintServiceName
Var BrandFullNameDA
Var BrandFullName
; Other included files may depend upon these includes!
; The following includes are provided by NSIS.
!include FileFunc.nsh
!include LogicLib.nsh
!include MUI.nsh
!include WinMessages.nsh
!include WinVer.nsh
!include WordFunc.nsh
!insertmacro GetOptions
!insertmacro GetParameters
!insertmacro GetSize
; The test slaves use this fallback key to run tests.
; And anyone that wants to run tests themselves should already have
; this installed.
!define FallbackKey \
"SOFTWARE\Mozilla\MaintenanceService\3932ecacee736d366d6436db0f55bce4"
!define CompanyName "Mozilla Corporation"
!define BrandFullNameInternal ""
; The following includes are custom.
!include defines.nsi
; We keep defines.nsi defined so that we get other things like
; the version number, but we redefine BrandFullName
!define MaintFullName "Mozilla Maintenance Service"
!undef BrandFullName
!define BrandFullName "${MaintFullName}"
!include common.nsh
!include locales.nsi
VIAddVersionKey "FileDescription" "${MaintFullName} Installer"
VIAddVersionKey "OriginalFilename" "maintenanceservice_installer.exe"
Name "${MaintFullName}"
OutFile "maintenanceservice_installer.exe"
; Get installation folder from registry if available
InstallDirRegKey HKLM "Software\Mozilla\MaintenanceService" ""
SetOverwrite on
!define MaintUninstallKey \
"Software\Microsoft\Windows\CurrentVersion\Uninstall\MozillaMaintenanceService"
; The HAVE_64BIT_OS define also means that we have an x64 build,
; not just an x64 OS.
!ifdef HAVE_64BIT_OS
; See below, we actually abort the install for x64 builds currently.
InstallDir "$PROGRAMFILES64\${MaintFullName}\"
!else
InstallDir "$PROGRAMFILES32\${MaintFullName}\"
!endif
ShowUnInstDetails nevershow
################################################################################
# Modern User Interface - MUI
!define MUI_ICON setup.ico
!define MUI_UNICON setup.ico
!define MUI_WELCOMEPAGE_TITLE_3LINES
!define MUI_UNWELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
;Interface Settings
!define MUI_ABORTWARNING
; Uninstaller Pages
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
################################################################################
# Language
!insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
!verbose push
!verbose 3
!include "overrideLocale.nsh"
!include "customLocale.nsh"
!verbose pop
; Set this after the locale files to override it if it is in the locale
; using " " for BrandingText will hide the "Nullsoft Install System..." branding
BrandingText " "
Function .onInit
SetSilent silent
!ifdef HAVE_64BIT_OS
; We plan to eventually enable 64bit native builds to use the maintenance
; service, but for the initial release, to reduce testing and development,
; 64-bit builds will not install the maintenanceservice.
Abort
!endif
; On Windows 2000 we do not install the maintenance service.
; We won't run this installer from the parent installer, but just in case
; someone tries to execute it on Windows 2000...
${Unless} ${AtLeastWinXP}
Abort
${EndUnless}
FunctionEnd
Function un.onInit
StrCpy $BrandFullNameDA "${MaintFullName}"
StrCpy $BrandFullName "${MaintFullName}"
FunctionEnd
Section "MaintenanceService"
AllowSkipFiles off
CreateDirectory $INSTDIR
SetOutPath $INSTDIR
; If the service already exists, then it will be stopped when upgrading it
; via the maintenanceservice_tmp.exe command executed below.
; The maintenanceservice_tmp.exe command will rename the file to
; maintenanceservice.exe if maintenanceservice_tmp.exe is newer.
; If the service does not exist yet, we install it and drop the file on
; disk as maintenanceservice.exe directly.
StrCpy $TempMaintServiceName "maintenanceservice.exe"
IfFileExists "$INSTDIR\maintenanceservice.exe" 0 skipAlreadyExists
StrCpy $TempMaintServiceName "maintenanceservice_tmp.exe"
skipAlreadyExists:
; We always write out a copy and then decide whether to install it or
; not via calling its 'install' cmdline which works by version comparison.
CopyFiles "$EXEDIR\maintenanceservice.exe" "$INSTDIR\$TempMaintServiceName"
; The updater.ini file is only used when performing an install or upgrade,
; and only if that install or upgrade is successful. If an old updater.ini
; happened to be copied into the maintenance service installation directory
; but the service was not newer, the updater.ini file would be unused.
; It is used to fill the description of the service on success.
CopyFiles "$EXEDIR\updater.ini" "$INSTDIR\updater.ini"
; Install the application maintenance service.
; If a service already exists, the command line parameter will stop the
; service and only install itself if it is newer than the already installed
; service. If successful it will remove the old maintenanceservice.exe
; and replace it with maintenanceservice_tmp.exe.
ClearErrors
;${GetParameters} $0
;${GetOptions} "$0" "/Upgrade" $0
;${If} ${Errors}
nsExec::Exec '"$INSTDIR\$TempMaintServiceName" forceinstall'
;${Else}
; The upgrade cmdline is the same as install except
; It will fail if the service isn't already installed.
; nsExec::Exec '"$INSTDIR\$TempMaintServiceName" upgrade'
;${EndIf}
WriteUninstaller "$INSTDIR\Uninstall.exe"
WriteRegStr HKLM "${MaintUninstallKey}" "DisplayName" "${MaintFullName}"
WriteRegStr HKLM "${MaintUninstallKey}" "UninstallString" \
'"$INSTDIR\uninstall.exe"'
WriteRegStr HKLM "${MaintUninstallKey}" "DisplayIcon" \
"$INSTDIR\Uninstall.exe,0"
WriteRegStr HKLM "${MaintUninstallKey}" "DisplayVersion" "${AppVersion}"
WriteRegStr HKLM "${MaintUninstallKey}" "Publisher" "Mozilla"
WriteRegStr HKLM "${MaintUninstallKey}" "Comments" \
"${BrandFullName} ${AppVersion} (${ARCH} ${AB_CD})"
WriteRegDWORD HKLM "${MaintUninstallKey}" "NoModify" 1
${GetSize} "$INSTDIR" "/S=0K" $R2 $R3 $R4
WriteRegDWORD HKLM "${MaintUninstallKey}" "EstimatedSize" $R2
; Write out that a maintenance service was attempted.
; We do this because on upgrades we will check this value and we only
; want to install once on the first upgrade to maintenance service.
; Also write out that we are currently installed, preferences will check
; this value to determine if we should show the service update pref.
; Since the Maintenance service can be installed either x86 or x64,
; always use the 64-bit registry for checking if an attempt was made.
${If} ${RunningX64}
SetRegView 64
${EndIf}
WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Attempted" 1
WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Installed" 1
; Included here for debug purposes only.
; These keys are used to bypass the installation dir is a valid installation
; check from the service so that tests can be run.
WriteRegStr HKLM "${FallbackKey}\0" "name" "Mozilla Corporation"
WriteRegStr HKLM "${FallbackKey}\0" "issuer" "Thawte Code Signing CA - G2"
WriteRegStr HKLM "${FallbackKey}\1" "name" "Mozilla Fake SPC"
WriteRegStr HKLM "${FallbackKey}\1" "issuer" "Mozilla Fake CA"
${If} ${RunningX64}
SetRegView lastused
${EndIf}
SectionEnd
; By renaming before deleting we improve things slightly in case
; there is a file in use error. In this case a new install can happen.
Function un.RenameDelete
Pop $9
; If the .moz-delete file already exists previously, delete it
; If it doesn't exist, the call is ignored.
; We don't need to pass /REBOOTOK here since it was already marked that way
; if it exists.
Delete "$9.moz-delete"
Rename "$9" "$9.moz-delete"
${If} ${Errors}
Delete /REBOOTOK "$9"
${Else}
Delete /REBOOTOK "$9.moz-delete"
${EndIf}
ClearErrors
FunctionEnd
Section "Uninstall"
; Delete the service so that no updates will be attempted
nsExec::Exec '"$INSTDIR\maintenanceservice.exe" uninstall'
Push "$INSTDIR\updater.ini"
Call un.RenameDelete
Push "$INSTDIR\maintenanceservice.exe"
Call un.RenameDelete
Push "$INSTDIR\maintenanceservice_tmp.exe"
Call un.RenameDelete
Push "$INSTDIR\maintenanceservice.old"
Call un.RenameDelete
Push "$INSTDIR\Uninstall.exe"
Call un.RenameDelete
RMDir /REBOOTOK "$INSTDIR"
DeleteRegKey HKLM "${MaintUninstallKey}"
${If} ${RunningX64}
SetRegView 64
${EndIf}
DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "Installed"
DeleteRegKey HKLM "${FallbackKey}\"
${If} ${RunningX64}
SetRegView lastused
${EndIf}
SectionEnd