From d1db1bbf6a4dcd57c6d60d80d4d5fd7c5e9cf8dc Mon Sep 17 00:00:00 2001 From: "Brian R. Bondy" Date: Wed, 9 May 2012 13:37:11 -0400 Subject: [PATCH 1/5] Bug 749956 - Start tracking this releng only talos installer file in the tree. r=ehsan. DONTBUILD --- .../maintenanceservice_installer.nsi | 291 ++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 toolkit/components/maintenanceservice/bootstrapinstaller/maintenanceservice_installer.nsi diff --git a/toolkit/components/maintenanceservice/bootstrapinstaller/maintenanceservice_installer.nsi b/toolkit/components/maintenanceservice/bootstrapinstaller/maintenanceservice_installer.nsi new file mode 100644 index 000000000000..d6d71455c3a8 --- /dev/null +++ b/toolkit/components/maintenanceservice/bootstrapinstaller/maintenanceservice_installer.nsi @@ -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 +# +# 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 + From 2034ae6a403a4aa10ea367f8d7f6ab6ea580cbc0 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Wed, 9 May 2012 11:49:56 -0700 Subject: [PATCH 2/5] Update the WebIDL Parser to pick up Bug 753459. r=bz --- dom/bindings/parser/WebIDL.py | 3 ++- dom/bindings/parser/tests/test_extended_attributes.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index a0e4e85afadd..320acc438406 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -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]) diff --git a/dom/bindings/parser/tests/test_extended_attributes.py b/dom/bindings/parser/tests/test_extended_attributes.py index fc1cdd9cc784..042cfc49966f 100644 --- a/dom/bindings/parser/tests/test_extended_attributes.py +++ b/dom/bindings/parser/tests/test_extended_attributes.py @@ -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() From 8fa8928a940f1f93634e3ddc82538cd02b92683e Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Wed, 9 May 2012 12:05:39 -0700 Subject: [PATCH 3/5] Bug 751783 - Allow sandbox reuse between execute_script calls, r=jgriffin, r=mdas, DONTBUILD because NPOTB, --- .../client/marionette/marionette.py | 21 ++- .../tests/unit/test_execute_async_script.py | 22 ++- .../tests/unit/test_execute_script.py | 9 ++ testing/marionette/marionette-actors.js | 23 ++- testing/marionette/marionette-listener.js | 150 +++++++++++------- testing/marionette/marionette-simpletest.js | 5 +- 6 files changed, 160 insertions(+), 70 deletions(-) diff --git a/testing/marionette/client/marionette/marionette.py b/testing/marionette/client/marionette/marionette.py index da37302b4cf9..9756e74e7cc8 100644 --- a/testing/marionette/client/marionette/marionette.py +++ b/testing/marionette/client/marionette/marionette.py @@ -313,7 +313,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 +321,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): diff --git a/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py b/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py index 0bf2c9260da3..5d8aa974ab50 100644 --- a/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py +++ b/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py @@ -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) @@ -102,6 +102,19 @@ var c = Components.classes; 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() @@ -119,3 +132,6 @@ var c = Components.classes; marionetteScriptFinished(1); """)) + def test_sandbox_reuse(self): + pass + diff --git a/testing/marionette/client/marionette/tests/unit/test_execute_script.py b/testing/marionette/client/marionette/tests/unit/test_execute_script.py index a1a6d2abd28a..1f23733f2288 100644 --- a/testing/marionette/client/marionette/tests/unit/test_execute_script.py +++ b/testing/marionette/client/marionette/tests/unit/test_execute_script.py @@ -64,6 +64,13 @@ class TestExecuteContent(MarionetteTestCase): 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): @@ -73,3 +80,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 diff --git a/testing/marionette/marionette-actors.js b/testing/marionette/marionette-actors.js index e23143da4357..e229b2c8ffc7 100644 --- a/testing/marionette/marionette-actors.js +++ b/testing/marionette/marionette-actors.js @@ -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; } diff --git a/testing/marionette/marionette-listener.js b/testing/marionette/marionette-listener.js index 9828e20aa47e..e0c7c900bdc8 100644 --- a/testing/marionette/marionette-listener.js +++ b/testing/marionette/marionette-listener.js @@ -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 diff --git a/testing/marionette/marionette-simpletest.js b/testing/marionette/marionette-simpletest.js index 8e69add72ea9..b20c063f4438 100644 --- a/testing/marionette/marionette-simpletest.js +++ b/testing/marionette/marionette-simpletest.js @@ -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}; }, From 6bd9a2374a315148f6a1a8866ff191d21eae7178 Mon Sep 17 00:00:00 2001 From: Jonathan Griffin Date: Wed, 9 May 2012 13:39:19 -0700 Subject: [PATCH 4/5] Bug 753013 reuse secondary emulators, r=mdas, DONTBUILD because NPOTB --- .../test/marionette/test_dial_answer.py | 1 + .../marionette/test_dial_between_emulators.py | 2 ++ .../client/marionette/marionette.py | 3 +++ .../client/marionette/marionette_test.py | 24 +++++++++---------- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/dom/telephony/test/marionette/test_dial_answer.py b/dom/telephony/test/marionette/test_dial_answer.py index 29c84f5f6570..ceae6c7c8483 100644 --- a/dom/telephony/test/marionette/test_dial_answer.py +++ b/dom/telephony/test/marionette/test_dial_answer.py @@ -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"; diff --git a/dom/telephony/test/marionette/test_dial_between_emulators.py b/dom/telephony/test/marionette/test_dial_between_emulators.py index 4bd01f8f0d66..f1c58eaf0022 100644 --- a/dom/telephony/test/marionette/test_dial_between_emulators.py +++ b/dom/telephony/test/marionette/test_dial_between_emulators.py @@ -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();") diff --git a/testing/marionette/client/marionette/marionette.py b/testing/marionette/client/marionette/marionette.py index 9756e74e7cc8..5908929a12e6 100644 --- a/testing/marionette/client/marionette/marionette.py +++ b/testing/marionette/client/marionette/marionette.py @@ -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'): diff --git a/testing/marionette/client/marionette/marionette_test.py b/testing/marionette/client/marionette/marionette_test.py index fed2293494ec..3086ac3419a0 100644 --- a/testing/marionette/client/marionette/marionette_test.py +++ b/testing/marionette/client/marionette/marionette_test.py @@ -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): From add06019f5fc01c253955a89b52d5ad75d95daf0 Mon Sep 17 00:00:00 2001 From: Olli Pettay Date: Wed, 9 May 2012 21:53:23 +0300 Subject: [PATCH 5/5] Bug 716014 Investigate if we could use CompartmentGC more often, r=billm+terrence --HG-- extra : rebase_source : f0cfb9cdd2e2823898f4c18402df53e7b6041bac --- dom/base/nsDOMWindowUtils.cpp | 2 +- dom/base/nsJSEnvironment.cpp | 120 ++++++++++++++++++++++++++++------ dom/base/nsJSEnvironment.h | 10 ++- dom/ipc/ContentChild.cpp | 4 +- js/src/jscompartment.h | 6 ++ js/src/jsfriendapi.cpp | 6 ++ js/src/jsfriendapi.h | 6 +- 7 files changed, 127 insertions(+), 27 deletions(-) diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 9389dba056c9..2f8e10df1eb5 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -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; diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index c8dd5e76b5ee..d48cd657f34b 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -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(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(aClosure); + nsJSContext::GarbageCollectNow(static_cast(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(aClosure); - nsJSContext::GarbageCollectNow(static_cast(reason), nsGCIncremental); + nsJSContext::GarbageCollectNow(static_cast(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(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); diff --git a/dom/base/nsJSEnvironment.h b/dom/base/nsJSEnvironment.h index bf03a9a3215c..141dbb99c610 100644 --- a/dom/base/nsJSEnvironment.h +++ b/dom/base/nsJSEnvironment.h @@ -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 mGlobalObjectRef; diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index ae4d8fcf733a..dd0f815fda53 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -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; } diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 460ae8c751ef..399b120f5257 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -172,6 +172,12 @@ struct JSCompartment gcState = GCScheduled; } + void unscheduleGC() { + JS_ASSERT(!rt->gcRunning); + JS_ASSERT(gcState != GCRunning); + gcState = NoGCScheduled; + } + bool isGCScheduled() const { return gcState == GCScheduled; } diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 9382bd4dbb0e..cfaa5b8d3eea 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -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) { diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 33edbd070e70..059cd2391283 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -619,7 +619,8 @@ SizeOfJSContext(); D(DOM_IPC) \ D(DOM_WORKER) \ D(INTER_SLICE_GC) \ - D(REFRESH_FRAME) + D(REFRESH_FRAME) \ + D(FULL_GC_TIMER) namespace gcreason { @@ -643,6 +644,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