diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 299e65e9ac..10a367cffe 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -308,6 +308,7 @@ @BINPATH@/components/toolkit_finalizationwitness.xpt @BINPATH@/components/toolkit_formautofill.xpt @BINPATH@/components/toolkit_osfile.xpt +@BINPATH@/components/toolkit_perfmonitoring.xpt @BINPATH@/components/toolkit_xulstore.xpt @BINPATH@/components/toolkitprofile.xpt #ifdef MOZ_ENABLE_XREMOTE diff --git a/browser/app/nsBrowserApp.cpp b/browser/app/nsBrowserApp.cpp index 317f57db77..d5c71583dc 100644 --- a/browser/app/nsBrowserApp.cpp +++ b/browser/app/nsBrowserApp.cpp @@ -35,10 +35,6 @@ #include "nsIFile.h" #include "nsStringGlue.h" -// Easy access to a five second startup delay used to get -// a debugger attached in the metro environment. -// #define DEBUG_delay_start_metro - #ifdef XP_WIN // we want a wmain entry point #include "nsWindowsWMain.cpp" @@ -56,12 +52,6 @@ using namespace mozilla; #define kOSXResourcesFolder "Resources" #endif #define kDesktopFolder "browser" -#define kMetroFolder "metro" -#define kMetroAppIniFilename "metroapp.ini" -#ifdef XP_WIN -#define kMetroTestFile "tests.ini" -const char* kMetroConsoleIdParam = "testconsoleid="; -#endif static void Output(const char *fmt, ... ) { @@ -114,40 +104,6 @@ static bool IsArg(const char* arg, const char* s) return false; } -#ifdef XP_WIN -/* - * AttachToTestHarness - Windows helper for when we are running - * in the immersive environment. Firefox is launched by Windows in - * response to a request by metrotestharness, which is launched by - * runtests.py. As such stdout in fx doesn't point to the right - * stream. This helper touches up stdout such that test output gets - * routed to a named pipe metrotestharness creates and dumps to its - * stdout. - */ -static void AttachToTestHarness() -{ - // attach to the metrotestharness named logging pipe - HANDLE winOut = CreateFileA("\\\\.\\pipe\\metrotestharness", - GENERIC_WRITE, - FILE_SHARE_WRITE, 0, - OPEN_EXISTING, 0, 0); - - if (winOut == INVALID_HANDLE_VALUE) { - OutputDebugStringW(L"Could not create named logging pipe.\n"); - return; - } - - // Set the c runtime handle - int stdOut = _open_osfhandle((intptr_t)winOut, _O_APPEND); - if (stdOut == -1) { - OutputDebugStringW(L"Could not open c-runtime handle.\n"); - return; - } - FILE *fp = _fdopen(stdOut, "a"); - *stdout = *fp; -} -#endif - XRE_GetFileFromPathType XRE_GetFileFromPath; XRE_CreateAppDataType XRE_CreateAppData; XRE_FreeAppDataType XRE_FreeAppData; @@ -426,6 +382,7 @@ InitXPCOMGlue(const char *argv0, nsIFile **xreDirectory) return rv; } + // This will set this thread as the main thread. NS_LogInit(); // chop XPCOM_DLL off exePath @@ -447,9 +404,6 @@ InitXPCOMGlue(const char *argv0, nsIFile **xreDirectory) int main(int argc, char* argv[]) { -#ifdef DEBUG_delay_start_metro - Sleep(5000); -#endif uint64_t start = TimeStamp_Now(); #ifdef XP_MACOSX diff --git a/browser/base/content/abouthealthreport/abouthealth.js b/browser/base/content/abouthealthreport/abouthealth.js new file mode 100644 index 0000000000..7abb0b467a --- /dev/null +++ b/browser/base/content/abouthealthreport/abouthealth.js @@ -0,0 +1,127 @@ +/* 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/Preferences.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +const prefs = new Preferences("datareporting.healthreport."); + +let healthReportWrapper = { + init: function () { + let iframe = document.getElementById("remote-report"); + iframe.addEventListener("load", healthReportWrapper.initRemotePage, false); + iframe.src = this._getReportURI().spec; + iframe.onload = () => { + MozSelfSupport.getHealthReportPayload().then(this.updatePayload, + this.handleInitFailure); + }; + prefs.observe("uploadEnabled", this.updatePrefState, healthReportWrapper); + }, + + uninit: function () { + prefs.ignore("uploadEnabled", this.updatePrefState, healthReportWrapper); + }, + + _getReportURI: function () { + let url = Services.urlFormatter.formatURLPref("datareporting.healthreport.about.reportUrl"); + return Services.io.newURI(url, null, null); + }, + + setDataSubmission: function (enabled) { + MozSelfSupport.healthReportDataSubmissionEnabled = enabled; + this.updatePrefState(); + }, + + updatePrefState: function () { + try { + let prefs = { + enabled: MozSelfSupport.healthReportDataSubmissionEnabled, + }; + healthReportWrapper.injectData("prefs", prefs); + } + catch (ex) { + healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PREFS_FAILED); + } + }, + + refreshPayload: function () { + MozSelfSupport.getHealthReportPayload().then(this.updatePayload, + this.handlePayloadFailure); + }, + + updatePayload: function (payload) { + healthReportWrapper.injectData("payload", JSON.stringify(payload)); + }, + + injectData: function (type, content) { + let report = this._getReportURI(); + + // file URIs can't be used for targetOrigin, so we use "*" for this special case + // in all other cases, pass in the URL to the report so we properly restrict the message dispatch + let reportUrl = report.scheme == "file" ? "*" : report.spec; + + let data = { + type: type, + content: content + } + + let iframe = document.getElementById("remote-report"); + iframe.contentWindow.postMessage(data, reportUrl); + }, + + handleRemoteCommand: function (evt) { + switch (evt.detail.command) { + case "DisableDataSubmission": + this.setDataSubmission(false); + break; + case "EnableDataSubmission": + this.setDataSubmission(true); + break; + case "RequestCurrentPrefs": + this.updatePrefState(); + break; + case "RequestCurrentPayload": + this.refreshPayload(); + break; + default: + Cu.reportError("Unexpected remote command received: " + evt.detail.command + ". Ignoring command."); + break; + } + }, + + initRemotePage: function () { + let iframe = document.getElementById("remote-report").contentDocument; + iframe.addEventListener("RemoteHealthReportCommand", + function onCommand(e) {healthReportWrapper.handleRemoteCommand(e);}, + false); + healthReportWrapper.updatePrefState(); + }, + + // error handling + ERROR_INIT_FAILED: 1, + ERROR_PAYLOAD_FAILED: 2, + ERROR_PREFS_FAILED: 3, + + reportFailure: function (error) { + let details = { + errorType: error, + } + healthReportWrapper.injectData("error", details); + }, + + handleInitFailure: function () { + healthReportWrapper.reportFailure(healthReportWrapper.ERROR_INIT_FAILED); + }, + + handlePayloadFailure: function () { + healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PAYLOAD_FAILED); + }, +} + +window.addEventListener("load", function () { healthReportWrapper.init(); }); +window.addEventListener("unload", function () { healthReportWrapper.uninit(); }); diff --git a/browser/branding/official/VisualElements_150.png b/browser/branding/official/VisualElements_150.png deleted file mode 100644 index aa784449d1..0000000000 Binary files a/browser/branding/official/VisualElements_150.png and /dev/null differ diff --git a/browser/branding/official/VisualElements_70.png b/browser/branding/official/VisualElements_70.png deleted file mode 100644 index e785bfbc1b..0000000000 Binary files a/browser/branding/official/VisualElements_70.png and /dev/null differ diff --git a/browser/branding/official/palemoon.VisualElementsManifest.xml b/browser/branding/official/palemoon.VisualElementsManifest.xml deleted file mode 100644 index e9e1f136aa..0000000000 --- a/browser/branding/official/palemoon.VisualElementsManifest.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/browser/branding/shared/branding.mozbuild b/browser/branding/shared/branding.mozbuild index 0636be64a3..b7e23f0dc9 100644 --- a/browser/branding/shared/branding.mozbuild +++ b/browser/branding/shared/branding.mozbuild @@ -14,13 +14,6 @@ JS_PREFERENCE_FILES += [ ] if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': - FINAL_TARGET_FILES['..'] += [ - 'palemoon.VisualElementsManifest.xml', - ] - FINAL_TARGET_FILES.VisualElements += [ - 'VisualElements_150.png', - 'VisualElements_70.png', - ] BRANDING_FILES += [ '../shared/newtab.ico', '../shared/newwindow.ico', @@ -52,4 +45,4 @@ elif CONFIG['MOZ_WIDGET_GTK']: DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION'] DEFINES['MOZ_BRANDING_DIRECTORY'] = CONFIG['MOZ_BRANDING_DIRECTORY'] DEFINES['MOZILLA_UAVERSION_U'] = CONFIG['MOZILLA_UAVERSION_U'] -DEFINES['MOZILLA_COMPATVERSION_U'] = CONFIG['MOZILLA_COMPATVERSION_U'] \ No newline at end of file +DEFINES['MOZILLA_COMPATVERSION_U'] = CONFIG['MOZILLA_COMPATVERSION_U'] diff --git a/browser/branding/unofficial/VisualElements_150.png b/browser/branding/unofficial/VisualElements_150.png deleted file mode 100644 index 3fc7deb5b0..0000000000 Binary files a/browser/branding/unofficial/VisualElements_150.png and /dev/null differ diff --git a/browser/branding/unofficial/VisualElements_70.png b/browser/branding/unofficial/VisualElements_70.png deleted file mode 100644 index be9f6670a8..0000000000 Binary files a/browser/branding/unofficial/VisualElements_70.png and /dev/null differ diff --git a/browser/branding/unofficial/palemoon.VisualElementsManifest.xml b/browser/branding/unofficial/palemoon.VisualElementsManifest.xml deleted file mode 100644 index 070bfc99af..0000000000 --- a/browser/branding/unofficial/palemoon.VisualElementsManifest.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/browser/branding/unstable/VisualElements_150.png b/browser/branding/unstable/VisualElements_150.png deleted file mode 100644 index 320623dc10..0000000000 Binary files a/browser/branding/unstable/VisualElements_150.png and /dev/null differ diff --git a/browser/branding/unstable/VisualElements_70.png b/browser/branding/unstable/VisualElements_70.png deleted file mode 100644 index cb4c868459..0000000000 Binary files a/browser/branding/unstable/VisualElements_70.png and /dev/null differ diff --git a/browser/branding/unstable/palemoon.VisualElementsManifest.xml b/browser/branding/unstable/palemoon.VisualElementsManifest.xml deleted file mode 100644 index 3bdebe2b76..0000000000 --- a/browser/branding/unstable/palemoon.VisualElementsManifest.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/browser/build.mk b/browser/build.mk index fc692eda5f..0117564409 100644 --- a/browser/build.mk +++ b/browser/build.mk @@ -49,9 +49,4 @@ mochitest:: mochitest-browser-chrome .PHONY: mochitest-browser-chrome -mochitest-metro-chrome: - $(RUN_MOCHITEST) --metro-immersive --browser-chrome - $(CHECK_TEST_ERROR) - - endif diff --git a/browser/components/moz.build b/browser/components/moz.build index 1c1098f0a2..c87b6330af 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -17,6 +17,7 @@ DIRS += [ 'search', 'sessionstore', 'shell', + 'selfsupport', 'migration', ] @@ -41,4 +42,4 @@ EXTRA_PP_COMPONENTS += [ EXTRA_JS_MODULES += [ 'distribution.js', -] \ No newline at end of file +] diff --git a/browser/components/selfsupport/SelfSupportService.js b/browser/components/selfsupport/SelfSupportService.js new file mode 100644 index 0000000000..9a44377d4f --- /dev/null +++ b/browser/components/selfsupport/SelfSupportService.js @@ -0,0 +1,78 @@ +/* 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +const policy = Cc["@mozilla.org/datareporting/service;1"] + .getService(Ci.nsISupports) + .wrappedJSObject + .policy; + +XPCOMUtils.defineLazyGetter(this, "reporter", () => { + return Cc["@mozilla.org/datareporting/service;1"] + .getService(Ci.nsISupports) + .wrappedJSObject + .healthReporter; +}); + +function MozSelfSupportInterface() { +} + +MozSelfSupportInterface.prototype = { + classDescription: "MozSelfSupport", + classID: Components.ID("{d30aae8b-f352-4de3-b936-bb9d875df0bb}"), + contractID: "@mozilla.org/mozselfsupport;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer]), + + _window: null, + + init: function (window) { + this._window = window; + }, + + get healthReportDataSubmissionEnabled() { + return policy.healthReportUploadEnabled; + }, + + set healthReportDataSubmissionEnabled(enabled) { + let reason = "Self-support interface sent " + + (enabled ? "opt-in" : "opt-out") + + " command."; + policy.recordHealthReportUploadEnabled(enabled, reason); + }, + + getHealthReportPayload: function () { + return new this._window.Promise(function (aResolve, aReject) { + if (reporter) { + let resolvePayload = function () { + reporter.collectAndObtainJSONPayload(true).then(aResolve, aReject); + }; + + if (reporter.initialized) { + resolvePayload(); + } else { + reporter.onInit().then(resolvePayload, aReject); + } + } else { + aReject(new Error("No reporter")); + } + }.bind(this)); + }, + + resetPref: function(name) { + Services.prefs.clearUserPref(name); + }, + + resetSearchEngines: function() { + Services.search.restoreDefaultEngines(); + Services.search.resetToOriginalDefaultEngine(); + }, +} + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozSelfSupportInterface]); diff --git a/browser/components/selfsupport/SelfSupportService.manifest b/browser/components/selfsupport/SelfSupportService.manifest new file mode 100644 index 0000000000..0e87857e73 --- /dev/null +++ b/browser/components/selfsupport/SelfSupportService.manifest @@ -0,0 +1,2 @@ +component {d30aae8b-f352-4de3-b936-bb9d875df0bb} SelfSupportService.js +contract @mozilla.org/mozselfsupport;1 {d30aae8b-f352-4de3-b936-bb9d875df0bb} diff --git a/browser/components/selfsupport/moz.build b/browser/components/selfsupport/moz.build new file mode 100644 index 0000000000..af311e75a2 --- /dev/null +++ b/browser/components/selfsupport/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; c-basic-offset: 4; 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/. + +EXTRA_COMPONENTS += [ + 'SelfSupportService.js', + 'SelfSupportService.manifest', +] + +BROWSER_CHROME_MANIFESTS += [ + 'test/browser.ini', +] diff --git a/browser/components/selfsupport/test/browser.ini b/browser/components/selfsupport/test/browser.ini new file mode 100644 index 0000000000..ba56857b3a --- /dev/null +++ b/browser/components/selfsupport/test/browser.ini @@ -0,0 +1,3 @@ +[DEFAULT] + +[browser_selfsupportAPI.js] diff --git a/browser/components/selfsupport/test/browser_selfsupportAPI.js b/browser/components/selfsupport/test/browser_selfsupportAPI.js new file mode 100644 index 0000000000..2a54d4ae65 --- /dev/null +++ b/browser/components/selfsupport/test/browser_selfsupportAPI.js @@ -0,0 +1,88 @@ +Cu.import("resource://gre/modules/Preferences.jsm"); + +function test_resetPref() { + const prefNewName = "browser.newpref.fake"; + Assert.ok(!Preferences.has(prefNewName), "pref should not exist"); + + const prefExistingName = "extensions.hotfix.id"; + Assert.ok(Preferences.has(prefExistingName), "pref should exist"); + Assert.ok(!Preferences.isSet(prefExistingName), "pref should not be user-set"); + let prefExistingOriginalValue = Preferences.get(prefExistingName); + + registerCleanupFunction(function() { + Preferences.set(prefExistingName, prefExistingOriginalValue); + Services.prefs.deleteBranch(prefNewName); + }); + + // 1. do nothing on an inexistent pref + MozSelfSupport.resetPref(prefNewName); + Assert.ok(!Preferences.has(prefNewName), "pref should still not exist"); + + // 2. creation of a new pref + Preferences.set(prefNewName, 10); + Assert.ok(Preferences.has(prefNewName), "pref should exist"); + Assert.equal(Preferences.get(prefNewName), 10, "pref value should be 10"); + + MozSelfSupport.resetPref(prefNewName); + Assert.ok(!Preferences.has(prefNewName), "pref should not exist any more"); + + // 3. do nothing on an unchanged existing pref + MozSelfSupport.resetPref(prefExistingName); + Assert.ok(Preferences.has(prefExistingName), "pref should still exist"); + Assert.equal(Preferences.get(prefExistingName), prefExistingOriginalValue, "pref value should be the same as original"); + + // 4. change the value of an existing pref + Preferences.set(prefExistingName, "anyone@mozilla.org"); + Assert.ok(Preferences.has(prefExistingName), "pref should exist"); + Assert.equal(Preferences.get(prefExistingName), "anyone@mozilla.org", "pref value should have changed"); + + MozSelfSupport.resetPref(prefExistingName); + Assert.ok(Preferences.has(prefExistingName), "pref should still exist"); + Assert.equal(Preferences.get(prefExistingName), prefExistingOriginalValue, "pref value should be the same as original"); + + // 5. delete an existing pref + // deleteBranch is implemented in such a way that + // clearUserPref can't undo its action + // see discussion in bug 1075160 +} + +function test_resetSearchEngines() +{ + const defaultEngineOriginal = Services.search.defaultEngine; + const visibleEnginesOriginal = Services.search.getVisibleEngines(); + + // 1. do nothing on unchanged search configuration + MozSelfSupport.resetSearchEngines(); + Assert.equal(Services.search.defaultEngine, defaultEngineOriginal, "default engine should be reset"); + Assert.deepEqual(Services.search.getVisibleEngines(), visibleEnginesOriginal, + "default visible engines set should be reset"); + + // 2. change the default search engine + const defaultEngineNew = visibleEnginesOriginal[3]; + Assert.notEqual(defaultEngineOriginal, defaultEngineNew, "new default engine should be different from original"); + Services.search.defaultEngine = defaultEngineNew; + Assert.equal(Services.search.defaultEngine, defaultEngineNew, "default engine should be set to new"); + MozSelfSupport.resetSearchEngines(); + Assert.equal(Services.search.defaultEngine, defaultEngineOriginal, "default engine should be reset"); + Assert.deepEqual(Services.search.getVisibleEngines(), visibleEnginesOriginal, + "default visible engines set should be reset"); + + // 3. remove an engine + const engineRemoved = visibleEnginesOriginal[2]; + Services.search.removeEngine(engineRemoved); + Assert.ok(Services.search.getVisibleEngines().indexOf(engineRemoved) == -1, + "removed engine should not be visible any more"); + MozSelfSupport.resetSearchEngines(); + Assert.equal(Services.search.defaultEngine, defaultEngineOriginal, "default engine should be reset"); + Assert.deepEqual(Services.search.getVisibleEngines(), visibleEnginesOriginal, + "default visible engines set should be reset"); + + // 4. add an angine + // we don't remove user-added engines as they are only used if selected +} + +function test() +{ + test_resetPref(); + test_resetSearchEngines(); +} diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 9120455e0b..e1d506fb3b 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -149,9 +149,6 @@ ; [Base Browser Files] #ifndef XP_UNIX @BINPATH@/@MOZ_APP_NAME@.exe -@BINPATH@/palemoon.VisualElementsManifest.xml -@BINPATH@/browser/VisualElements/VisualElements_150.png -@BINPATH@/browser/VisualElements/VisualElements_70.png #else @RESPATH@/@MOZ_APP_NAME@-bin @BINPATH@/@MOZ_APP_NAME@ @@ -193,7 +190,6 @@ @RESPATH@/components/chrome.xpt @RESPATH@/components/commandhandler.xpt @RESPATH@/components/commandlines.xpt -@RESPATH@/components/compartments.xpt @RESPATH@/components/composer.xpt @RESPATH@/components/content_events.xpt @RESPATH@/components/content_html.xpt @@ -343,6 +339,7 @@ @RESPATH@/components/toolkit_finalizationwitness.xpt @RESPATH@/components/toolkit_formautofill.xpt @RESPATH@/components/toolkit_osfile.xpt +@RESPATH@/components/toolkit_perfmonitoring.xpt @RESPATH@/components/toolkit_xulstore.xpt @RESPATH@/components/toolkitprofile.xpt #ifdef MOZ_ENABLE_XREMOTE diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in index f00d7a0401..fe4a572ecd 100644 --- a/browser/locales/Makefile.in +++ b/browser/locales/Makefile.in @@ -118,10 +118,6 @@ install:: $(addprefix generic/profile/,$(PROFILE_FILES)) install:: $(call MERGE_FILES,$(addprefix profile/chrome/,$(PROFILE_CHROME))) $(SYSINSTALL) $(IFLAGS1) $^ $(DESTDIR)$(mozappdir)/defaults/profile/chrome -# metro build calls back here for search engine plugins -searchplugins: $(addprefix $(FINAL_TARGET)/searchplugins/,$(SEARCHPLUGINS)) -.PHONY: searchplugins - libs-%: $(NSINSTALL) -D $(DIST)/install @$(MAKE) -C ../../toolkit/locales libs-$* diff --git a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd index 6ef29c6802..6f52fa1088 100644 --- a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd +++ b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd @@ -72,13 +72,8 @@ - - - @@ -87,8 +82,6 @@ - - diff --git a/browser/locales/en-US/chrome/browser/preferences/preferences.properties b/browser/locales/en-US/chrome/browser/preferences/preferences.properties index 826f1463dc..3b9f8ef93d 100644 --- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties +++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties @@ -122,11 +122,6 @@ actualDiskCacheSize=Your web content cache is currently using %1$S %2$S of disk # %2$S = unit (MB, KB, etc.) actualAppCacheSize=Your application cache is currently using %1$S %2$S of disk space -###Preferences::Advanced::Update -#LOCALIZATION NOTE: The next string is for updating in Windows 8 only instead of updateAuto1.label. %S = brandShortName -updateAutoDesktop.label=Automatically install updates from desktop %S -updateAutoDesktop.accessKey=A - syncUnlink.title=Do you want to unlink your device? syncUnlink.label=This device will no longer be associated with your Sync account. All of your personal data, both on this device and in your Sync account, will remain intact. syncUnlinkConfirm.label=Unlink diff --git a/browser/locales/en-US/chrome/browser/syncSetup.dtd b/browser/locales/en-US/chrome/browser/syncSetup.dtd index 7ee938e5d6..f993f7ac9d 100644 --- a/browser/locales/en-US/chrome/browser/syncSetup.dtd +++ b/browser/locales/en-US/chrome/browser/syncSetup.dtd @@ -33,8 +33,6 @@ - - diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp index 11ca33adb3..1aad9b56f8 100644 --- a/docshell/base/nsAboutRedirector.cpp +++ b/docshell/base/nsAboutRedirector.cpp @@ -71,7 +71,7 @@ static RedirEntry kRedirMap[] = { nsIAboutModule::ALLOW_SCRIPT }, { - "compartments", "chrome://global/content/aboutCompartments.xhtml", + "performance", "chrome://global/content/aboutPerformance.xhtml", nsIAboutModule::ALLOW_SCRIPT }, { diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp index ef46d3f489..8e4dcc9488 100644 --- a/docshell/build/nsDocShellModule.cpp +++ b/docshell/build/nsDocShellModule.cpp @@ -163,9 +163,8 @@ const mozilla::Module::ContractIDEntry kDocShellContracts[] = { { NS_ABOUT_MODULE_CONTRACTID_PREFIX "buildconfig", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "license", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, - { NS_ABOUT_MODULE_CONTRACTID_PREFIX "compartments", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "memory", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, - { NS_ABOUT_MODULE_CONTRACTID_PREFIX "compartments", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "performance", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "addons", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newaddon", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, { NS_ABOUT_MODULE_CONTRACTID_PREFIX "support", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, diff --git a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp index 132affe79a..d6daf660f0 100644 --- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp +++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp @@ -165,7 +165,7 @@ WMFVideoMFTManager::InitializeDXVA() } if (gfxWindowsPlatform::GetPlatform()->IsWARP() || - !gfxPlatform::CanUseDXVA()) { + !gfxPlatform::CanUseHardwareVideoDecoding()) { return false; } diff --git a/dom/media/wmf/WMFReader.cpp b/dom/media/wmf/WMFReader.cpp index c032039c92..65f063d2da 100644 --- a/dom/media/wmf/WMFReader.cpp +++ b/dom/media/wmf/WMFReader.cpp @@ -84,9 +84,11 @@ WMFReader::~WMFReader() bool WMFReader::InitializeDXVA() { - if (!Preferences::GetBool("media.windows-media-foundation.use-dxva", false)) { + if (gfxWindowsPlatform::GetPlatform()->IsWARP() || + !gfxPlatform::CanUseHardwareVideoDecoding()) { return false; } + MOZ_ASSERT(mDecoder->GetImageContainer()); // Extract the layer manager backend type so that we can determine @@ -111,11 +113,6 @@ WMFReader::InitializeDXVA() return false; } - if (gfxWindowsPlatform::GetPlatform()->IsWARP() || - !gfxPlatform::CanUseDXVA()) { - return false; - } - mDXVA2Manager = DXVA2Manager::CreateD3D9DXVA(); return mDXVA2Manager != nullptr; diff --git a/dom/webidl/MozSelfSupport.webidl b/dom/webidl/MozSelfSupport.webidl index 160141d76b..c4345bcc08 100644 --- a/dom/webidl/MozSelfSupport.webidl +++ b/dom/webidl/MozSelfSupport.webidl @@ -28,7 +28,7 @@ interface MozSelfSupport * clientID: String, * clientIDVersion: Number, * thisPingDate: String, - * goannaAppInfo: Object, + * geckoAppInfo: Object, * data: Object * } * @@ -39,4 +39,20 @@ interface MozSelfSupport * Resolved when the FHR payload data has been collected. */ Promise getHealthReportPayload(); + + /** + * Resets a named pref: + * - if there is a default value, then change the value back to default, + * - if there's no default value, then delete the pref, + * - no-op otherwise. + * + * @param DOMString + * The name of the pref to reset. + */ + void resetPref(DOMString name); + + /** + * Resets original search engines, and resets the default one. + */ + void resetSearchEngines(); }; diff --git a/gfx/2d/Logging.h b/gfx/2d/Logging.h index 6ac02622d6..c4fbb20a5a 100644 --- a/gfx/2d/Logging.h +++ b/gfx/2d/Logging.h @@ -220,6 +220,9 @@ public: NoLog() {} ~NoLog() {} + // No-op + MOZ_IMPLICIT NoLog(const NoLog&) {} + template NoLog &operator <<(const T &aLogText) { return *this; } }; @@ -253,18 +256,10 @@ public: // Logger::ShouldOutputMessage. Since we currently don't have a different // version of that method for different loggers, this is OK. Once we do, // change BasicLogger::ShouldOutputMessage to Logger::ShouldOutputMessage. - explicit Log(int aOptions = Log::DefaultOptions(L == LOG_CRITICAL)) - : mOptions(aOptions) - , mLogIt(BasicLogger::ShouldOutputMessage(L)) - { - if (mLogIt && AutoPrefix()) { - if (mOptions & int(LogOptions::AssertOnCall)) { - mMessage << "[GFX" << L << "]: "; - } else { - mMessage << "[GFX" << L << "-]: "; - } - } + explicit Log(int aOptions = Log::DefaultOptions(L == LOG_CRITICAL)) { + Init(aOptions, BasicLogger::ShouldOutputMessage(L)); } + ~Log() { Flush(); } @@ -276,12 +271,6 @@ public: if (!str.empty()) { WriteLog(str); } - if (AutoPrefix()) { - mMessage.str("[GFX"); - mMessage << L << "]: "; - } else { - mMessage.str(""); - } mMessage.clear(); } @@ -478,8 +467,24 @@ public: inline bool NoNewline() const { return mOptions & int(LogOptions::NoNewline); } inline bool AutoPrefix() const { return mOptions & int(LogOptions::AutoPrefix); } + // We do not want this version to do any work, and stringstream can't be + // copied anyway. It does come in handy for the "Once" macro defined below. + MOZ_IMPLICIT Log(const Log& log) { Init(log.mOptions, false); } private: + // Initialization common to two constructors + void Init(int aOptions, bool aLogIt) { + mOptions = aOptions; + mLogIt = aLogIt; + if (mLogIt && AutoPrefix()) { + if (mOptions & int(LogOptions::AssertOnCall)) { + mMessage << "[GFX" << L << "]: "; + } else { + mMessage << "[GFX" << L << "-]: "; + } + } + } + void WriteLog(const std::string &aString) { if (MOZ_UNLIKELY(LogIt())) { Logger::OutputMessage(aString, L, NoNewline()); @@ -498,19 +503,39 @@ typedef Log DebugLog; typedef Log WarningLog; typedef Log CriticalLog; -#ifdef GFX_LOG_DEBUG -#define gfxDebug mozilla::gfx::DebugLog -#else -#define gfxDebug if (1) ; else mozilla::gfx::NoLog -#endif -#ifdef GFX_LOG_WARNING -#define gfxWarning mozilla::gfx::WarningLog -#else -#define gfxWarning if (1) ; else mozilla::gfx::NoLog +// Macro to glue names to get us less chance of name clashing. +#if defined GFX_LOGGING_GLUE1 || defined GFX_LOGGING_GLUE +#error "Clash of the macro GFX_LOGGING_GLUE1 or GFX_LOGGING_GLUE" #endif +#define GFX_LOGGING_GLUE1(x, y) x##y +#define GFX_LOGGING_GLUE(x, y) GFX_LOGGING_GLUE1(x, y) // This log goes into crash reports, use with care. #define gfxCriticalError mozilla::gfx::CriticalLog +#define gfxCriticalErrorOnce static gfxCriticalError GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxCriticalError + +// The "once" versions will only trigger the first time through. You can do this: +// gfxCriticalErrorOnce() << "This message only shows up once; +// instead of the usual: +// static bool firstTime = true; +// if (firstTime) { +// firstTime = false; +// gfxCriticalError() << "This message only shows up once; +// } +#ifdef GFX_LOG_DEBUG +#define gfxDebug mozilla::gfx::DebugLog +#define gfxDebugOnce static gfxDebug GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxDebug +#else +#define gfxDebug if (1) ; else mozilla::gfx::NoLog +#define gfxDebugOnce if (1) ; else mozilla::gfx::NoLog +#endif +#ifdef GFX_LOG_WARNING +#define gfxWarning mozilla::gfx::WarningLog +#define gfxWarningOnce static gfxWarning GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxWarning +#else +#define gfxWarning if (1) ; else mozilla::gfx::NoLog +#define gfxWarningOnce if (1) ; else mozilla::gfx::NoLog +#endif // See nsDebug.h and the NS_WARN_IF macro diff --git a/gfx/layers/client/ClientPaintedLayer.cpp b/gfx/layers/client/ClientPaintedLayer.cpp index 7235dbceaf..d86c79dc1f 100644 --- a/gfx/layers/client/ClientPaintedLayer.cpp +++ b/gfx/layers/client/ClientPaintedLayer.cpp @@ -25,6 +25,10 @@ #include "gfx2DGlue.h" #include "ReadbackProcessor.h" +#ifdef XP_WIN +#include "gfxWindowsPlatform.h" +#endif + namespace mozilla { namespace layers { @@ -33,6 +37,13 @@ using namespace mozilla::gfx; void ClientPaintedLayer::PaintThebes() { +#ifdef XP_WIN + if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset()) { + // If our rendering device has reset simply avoid rendering completely. + return; + } +#endif + PROFILER_LABEL("ClientPaintedLayer", "PaintThebes", js::ProfileEntry::Category::GRAPHICS); diff --git a/gfx/layers/client/TextureClient.cpp b/gfx/layers/client/TextureClient.cpp index b2dbb08724..783d667e30 100644 --- a/gfx/layers/client/TextureClient.cpp +++ b/gfx/layers/client/TextureClient.cpp @@ -66,6 +66,17 @@ using namespace mozilla::ipc; using namespace mozilla::gl; using namespace mozilla::gfx; +struct ReleaseKeepAlive : public nsRunnable +{ + NS_IMETHOD Run() + { + mKeep = nullptr; + return NS_OK; + } + + UniquePtr mKeep; +}; + /** * TextureChild is the content-side incarnation of the PTexture IPDL actor. * @@ -79,13 +90,21 @@ using namespace mozilla::gfx; */ class TextureChild final : public PTextureChild { - ~TextureChild() {} + ~TextureChild() + { + if (mKeep && mMainThreadOnly && !NS_IsMainThread()) { + nsRefPtr release = new ReleaseKeepAlive(); + release->mKeep = Move(mKeep); + NS_DispatchToMainThread(release); + } + } public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureChild) TextureChild() : mForwarder(nullptr) , mTextureClient(nullptr) + , mMainThreadOnly(false) , mIPCOpen(false) { } @@ -135,6 +154,7 @@ private: RefPtr mWaitForRecycle; TextureClient* mTextureClient; UniquePtr mKeep; + bool mMainThreadOnly; bool mIPCOpen; friend class TextureClient; @@ -507,11 +527,12 @@ TextureClient::~TextureClient() } void -TextureClient::KeepUntilFullDeallocation(UniquePtr aKeep) +TextureClient::KeepUntilFullDeallocation(UniquePtr aKeep, bool aMainThreadOnly) { MOZ_ASSERT(mActor); MOZ_ASSERT(!mActor->mKeep); mActor->mKeep = Move(aKeep); + mActor->mMainThreadOnly = aMainThreadOnly; } void TextureClient::ForceRemove(bool sync) diff --git a/gfx/layers/client/TextureClient.h b/gfx/layers/client/TextureClient.h index 920372906d..546868d14f 100644 --- a/gfx/layers/client/TextureClient.h +++ b/gfx/layers/client/TextureClient.h @@ -403,7 +403,7 @@ public: * It's a temporary hack to ensure that DXGI textures don't get destroyed * between serialization and deserialization. */ - void KeepUntilFullDeallocation(UniquePtr aKeep); + void KeepUntilFullDeallocation(UniquePtr aKeep, bool aMainThreadOnly = false); /** * Create and init the TextureChild/Parent IPDL actor pair. diff --git a/gfx/layers/d3d11/TextureD3D11.cpp b/gfx/layers/d3d11/TextureD3D11.cpp index a9aaad566a..56d783ec87 100644 --- a/gfx/layers/d3d11/TextureD3D11.cpp +++ b/gfx/layers/d3d11/TextureD3D11.cpp @@ -545,7 +545,7 @@ protected: DXGIYCbCrTextureClient::~DXGIYCbCrTextureClient() { if (mHoldRefs[0] && mActor) { - KeepUntilFullDeallocation(MakeUnique(mHoldRefs)); + KeepUntilFullDeallocation(MakeUnique(mHoldRefs), true); } MOZ_COUNT_DTOR(DXGIYCbCrTextureClient); } diff --git a/gfx/tests/gtest/TestGfxWidgets.cpp b/gfx/tests/gtest/TestGfxWidgets.cpp new file mode 100644 index 0000000000..cc946d8ab0 --- /dev/null +++ b/gfx/tests/gtest/TestGfxWidgets.cpp @@ -0,0 +1,57 @@ +/* -*- 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 "gtest/gtest.h" +#include "GfxDriverInfo.h" +#include "nsVersionComparator.h" + +using namespace mozilla::widget; + +TEST(GfxWidgets, Split) { + char aStr[8], bStr[8], cStr[8], dStr[8]; + + ASSERT_TRUE(SplitDriverVersion("33.4.3.22", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 33 && atoi(bStr) == 4 && atoi(cStr) == 3 && atoi(dStr) == 22); + + ASSERT_TRUE(SplitDriverVersion("28.74.0.0", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 28 && atoi(bStr) == 74 && atoi(cStr) == 0 && atoi(dStr) == 0); + + ASSERT_TRUE(SplitDriverVersion("132.0.0.0", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 132 && atoi(bStr) == 0 && atoi(cStr) == 0 && atoi(dStr) == 0); + + ASSERT_TRUE(SplitDriverVersion("2.3.0.0", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 2 && atoi(bStr) == 3 && atoi(cStr) == 0 && atoi(dStr) == 0); + + ASSERT_TRUE(SplitDriverVersion("25.4.0.8", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 25 && atoi(bStr) == 4 && atoi(cStr) == 0 && atoi(dStr) == 8); + +} + +TEST(GfxWidgets, Versioning) { + ASSERT_TRUE(mozilla::Version("0") < mozilla::Version("41.0a1")); + ASSERT_TRUE(mozilla::Version("39.0.5b7") < mozilla::Version("41.0a1")); + ASSERT_TRUE(mozilla::Version("18.0.5b7") < mozilla::Version("18.2")); + ASSERT_TRUE(mozilla::Version("30.0.5b7") < mozilla::Version("41.0b9")); + ASSERT_TRUE(mozilla::Version("100") > mozilla::Version("43.0a1")); + ASSERT_FALSE(mozilla::Version("42.0") < mozilla::Version("42.0")); + ASSERT_TRUE(mozilla::Version("42.0b2") < mozilla::Version("42.0")); + ASSERT_TRUE(mozilla::Version("42.0b2") < mozilla::Version("42")); + ASSERT_TRUE(mozilla::Version("42.0b2") < mozilla::Version("43.0a1")); + ASSERT_TRUE(mozilla::Version("42") < mozilla::Version("43.0a1")); + ASSERT_TRUE(mozilla::Version("42.0") < mozilla::Version("43.0a1")); + ASSERT_TRUE(mozilla::Version("42.0.5") < mozilla::Version("43.0a1")); + ASSERT_TRUE(mozilla::Version("42.1") < mozilla::Version("43.0a1")); + ASSERT_TRUE(mozilla::Version("42.0a1") < mozilla::Version("42")); + ASSERT_TRUE(mozilla::Version("42.0a1") < mozilla::Version("42.0.5")); + ASSERT_TRUE(mozilla::Version("42.0b7") < mozilla::Version("42.0.5")); + ASSERT_TRUE(mozilla::Version("") == mozilla::Version("0")); + + // Note these two; one would expect for 42.0b1 and 42b1 to compare the + // same, but they do not. If this ever changes, we want to know, so + // leave the test here to fail. + ASSERT_TRUE(mozilla::Version("42.0a1") < mozilla::Version("42.0b2")); + ASSERT_FALSE(mozilla::Version("42.0a1") < mozilla::Version("42b2")); +} + diff --git a/gfx/tests/gtest/moz.build b/gfx/tests/gtest/moz.build index 5cacdd3539..803299cac7 100644 --- a/gfx/tests/gtest/moz.build +++ b/gfx/tests/gtest/moz.build @@ -13,6 +13,7 @@ UNIFIED_SOURCES += [ 'TestColorNames.cpp', 'TestCompositor.cpp', 'TestGfxPrefs.cpp', + 'TestGfxWidgets.cpp', 'TestLayers.cpp', 'TestRegion.cpp', 'TestSkipChars.cpp', diff --git a/gfx/thebes/gfxAndroidPlatform.h b/gfx/thebes/gfxAndroidPlatform.h index f7c145c28b..d6772102be 100644 --- a/gfx/thebes/gfxAndroidPlatform.h +++ b/gfx/thebes/gfxAndroidPlatform.h @@ -96,6 +96,10 @@ public: virtual bool IsInGonkEmulator() const { return mIsInGonkEmulator; } #endif + virtual bool SupportsApzTouchInput() override { + return true; + } + private: int mScreenDepth; gfxImageFormat mOffscreenFormat; diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index c849c14718..6aaa243587 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -379,6 +379,7 @@ gfxPlatform::gfxPlatform() : mTileWidth(-1) , mTileHeight(-1) , mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo) + , mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo) { mAllowDownloadableFonts = UNINITIALIZED_VALUE; mFallbackUsesCmaps = UNINITIALIZED_VALUE; @@ -2173,7 +2174,7 @@ gfxPlatform::OptimalFormatForContent(gfxContentType aContent) */ static bool sLayersSupportsD3D9 = false; static bool sLayersSupportsD3D11 = false; -static bool sLayersSupportsDXVA = false; +static bool sLayersSupportsHardwareVideoDecoding = false; static bool sANGLESupportsD3D11 = false; static bool sBufferRotationCheckPref = true; static bool sPrefBrowserTabsRemoteAutostart = false; @@ -2194,42 +2195,46 @@ InitLayersAccelerationPrefs() gfxPrefs::GetSingleton(); sPrefBrowserTabsRemoteAutostart = BrowserTabsRemoteAutostart(); + nsCOMPtr gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); + int32_t status; #ifdef XP_WIN if (gfxPrefs::LayersAccelerationForceEnabled()) { sLayersSupportsD3D9 = true; sLayersSupportsD3D11 = true; - } else { - nsCOMPtr gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); - if (gfxInfo) { - int32_t status; - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, &status))) { - if (status == nsIGfxInfo::FEATURE_STATUS_OK) { - sLayersSupportsD3D9 = true; - } + } else if (gfxInfo) { + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, &status))) { + if (status == nsIGfxInfo::FEATURE_STATUS_OK) { + sLayersSupportsD3D9 = true; } - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status))) { - if (status == nsIGfxInfo::FEATURE_STATUS_OK) { - sLayersSupportsD3D11 = true; - } - } - if (!gfxPrefs::LayersD3D11DisableWARP()) { - // Always support D3D11 when WARP is allowed. + } + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status))) { + if (status == nsIGfxInfo::FEATURE_STATUS_OK) { sLayersSupportsD3D11 = true; } - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DXVA, &status))) { - if (status == nsIGfxInfo::FEATURE_STATUS_OK) { - sLayersSupportsDXVA = true; - } - } - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, &status))) { - if (status == nsIGfxInfo::FEATURE_STATUS_OK) { + } + if (!gfxPrefs::LayersD3D11DisableWARP()) { + // Always support D3D11 when WARP is allowed. + sLayersSupportsD3D11 = true; + } + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, &status))) { + if (status == nsIGfxInfo::FEATURE_STATUS_OK) { sANGLESupportsD3D11 = true; - } } } } #endif + if (Preferences::GetBool("media.hardware-video-decoding.enabled", false) && +#ifdef XP_WIN + Preferences::GetBool("media.windows-media-foundation.use-dxva", true) && +#endif + NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, + &status))) { + if (status == nsIGfxInfo::FEATURE_STATUS_OK) { + sLayersSupportsHardwareVideoDecoding = true; + } + } + sLayersAccelerationPrefsInitialized = true; } } @@ -2253,12 +2258,12 @@ gfxPlatform::CanUseDirect3D11() } bool -gfxPlatform::CanUseDXVA() +gfxPlatform::CanUseHardwareVideoDecoding() { // this function is called from the compositor thread, so it is not // safe to init the prefs etc. from here. MOZ_ASSERT(sLayersAccelerationPrefsInitialized); - return sLayersSupportsDXVA; + return sLayersSupportsHardwareVideoDecoding; } bool @@ -2328,6 +2333,45 @@ gfxPlatform::UsesOffMainThreadCompositing() return result; } +void +gfxPlatform::GetApzSupportInfo(mozilla::widget::InfoObject& aObj) +{ + if (SupportsApzWheelInput()) { + static const char *sBadPrefs[] = { + "mousewheel.system_scroll_override_on_root_content.enabled", + "mousewheel.default.delta_multiplier_x", + "mousewheel.with_alt.delta_multiplier_x", + "mousewheel.with_alt.delta_multiplier_x", + "mousewheel.with_control.delta_multiplier_x", + "mousewheel.with_meta.delta_multiplier_x", + "mousewheel.with_shift.delta_multiplier_x", + "mousewheel.with_win.delta_multiplier_x", + "mousewheel.with_alt.delta_multiplier_y", + "mousewheel.with_control.delta_multiplier_y", + "mousewheel.with_meta.delta_multiplier_y", + "mousewheel.with_shift.delta_multiplier_y", + "mousewheel.with_win.delta_multiplier_y", + }; + + nsString badPref; + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sBadPrefs); i++) { + if (Preferences::HasUserValue(sBadPrefs[i])) { + badPref.AssignASCII(sBadPrefs[i]); + break; + } + } + + aObj.DefineProperty("ApzWheelInput", 1); + if (badPref.Length()) { + aObj.DefineProperty("ApzWheelInputWarning", badPref); + } + } + + if (SupportsApzTouchInput()) { + aObj.DefineProperty("ApzTouchInput", 1); + } +} + already_AddRefed gfxPlatform::CreateHardwareVsyncSource() { diff --git a/gfx/thebes/gfxPlatform.h b/gfx/thebes/gfxPlatform.h index 421aec3589..4486627354 100644 --- a/gfx/thebes/gfxPlatform.h +++ b/gfx/thebes/gfxPlatform.h @@ -167,7 +167,9 @@ enum class DeviceResetReason REMOVED, RESET, DRIVER_ERROR, - INVALID_CALL + INVALID_CALL, + OUT_OF_MEMORY, + UNKNOWN }; class gfxPlatform { @@ -296,6 +298,7 @@ public: aObj.DefineProperty("AzureFallbackCanvasBackend", GetBackendName(mFallbackCanvasBackend)); aObj.DefineProperty("AzureContentBackend", GetBackendName(mContentBackend)); } + void GetApzSupportInfo(mozilla::widget::InfoObject& aObj); mozilla::gfx::BackendType GetContentBackend() { return mContentBackend; @@ -495,7 +498,7 @@ public: static bool CanUseDirect3D9(); static bool CanUseDirect3D11(); - static bool CanUseDXVA(); + static bool CanUseHardwareVideoDecoding(); static bool CanUseDirect3D11ANGLE(); /** @@ -610,6 +613,16 @@ public: return mVsyncSource; } + /** + * Used to test which input types are handled via APZ. + */ + virtual bool SupportsApzWheelInput() { + return false; + } + virtual bool SupportsApzTouchInput() { + return false; + } + protected: gfxPlatform(); virtual ~gfxPlatform(); @@ -724,6 +737,7 @@ private: int mTileHeight; mozilla::widget::GfxInfoCollector mAzureCanvasBackendCollector; + mozilla::widget::GfxInfoCollector mApzSupportCollector; mozilla::RefPtr mRecorder; mozilla::RefPtr mSkiaGlue; diff --git a/gfx/thebes/gfxPlatformGtk.h b/gfx/thebes/gfxPlatformGtk.h index 14df8a40d0..43be537baf 100644 --- a/gfx/thebes/gfxPlatformGtk.h +++ b/gfx/thebes/gfxPlatformGtk.h @@ -110,6 +110,10 @@ public: virtual int GetScreenDepth() const override; + bool SupportsApzWheelInput() override { + return true; + } + protected: static gfxFontconfigUtils *sFontconfigUtils; diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp index 0e25b138e1..0629d85e04 100644 --- a/gfx/thebes/gfxWindowsPlatform.cpp +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -405,6 +405,8 @@ NS_IMPL_ISUPPORTS(D3D9SharedTextureReporter, nsIMemoryReporter) gfxWindowsPlatform::gfxWindowsPlatform() : mD3D11DeviceInitialized(false) , mIsWARP(false) + , mCanInitMediaDevice(false) + , mHasDeviceReset(false) { mUseClearTypeForDownloadableFonts = UNINITIALIZED_VALUE; mUseClearTypeAlways = UNINITIALIZED_VALUE; @@ -475,6 +477,8 @@ gfxWindowsPlatform::UpdateRenderMode() mD3D11Device = nullptr; mD3D11ContentDevice = nullptr; mAdapter = nullptr; + mDeviceResetReason = DeviceResetReason::OK; + mHasDeviceReset = false; imgLoader::Singleton()->ClearCache(true); imgLoader::Singleton()->ClearCache(false); @@ -1132,46 +1136,69 @@ gfxWindowsPlatform::IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlag return true; } +static DeviceResetReason HResultToResetReason(HRESULT hr) +{ + switch (hr) { + case DXGI_ERROR_DEVICE_HUNG: + return DeviceResetReason::HUNG; + case DXGI_ERROR_DEVICE_REMOVED: + return DeviceResetReason::REMOVED; + case DXGI_ERROR_DEVICE_RESET: + return DeviceResetReason::RESET; + case DXGI_ERROR_DRIVER_INTERNAL_ERROR: + return DeviceResetReason::DRIVER_ERROR; + case DXGI_ERROR_INVALID_CALL: + return DeviceResetReason::INVALID_CALL; + case E_OUTOFMEMORY: + return DeviceResetReason::OUT_OF_MEMORY; + default: + MOZ_ASSERT(false); + } + return DeviceResetReason::UNKNOWN; +} + +bool +gfxWindowsPlatform::IsDeviceReset(HRESULT hr, DeviceResetReason* aResetReason) +{ + if (hr != S_OK) { + mDeviceResetReason = HResultToResetReason(hr); + mHasDeviceReset = true; + if (aResetReason) { + *aResetReason = mDeviceResetReason; + } + return true; + } + return false; +} + bool gfxWindowsPlatform::DidRenderingDeviceReset(DeviceResetReason* aResetReason) { + if (mHasDeviceReset) { + if (aResetReason) { + *aResetReason = mDeviceResetReason; + } + return true; + } if (aResetReason) { *aResetReason = DeviceResetReason::OK; } if (mD3D11Device) { HRESULT hr = mD3D11Device->GetDeviceRemovedReason(); - if (hr != S_OK) { - if (aResetReason) { - switch (hr) { - case DXGI_ERROR_DEVICE_HUNG: - *aResetReason = DeviceResetReason::HUNG; - break; - case DXGI_ERROR_DEVICE_REMOVED: - *aResetReason = DeviceResetReason::REMOVED; - break; - case DXGI_ERROR_DEVICE_RESET: - *aResetReason = DeviceResetReason::RESET; - break; - case DXGI_ERROR_DRIVER_INTERNAL_ERROR: - *aResetReason = DeviceResetReason::DRIVER_ERROR; - break; - case DXGI_ERROR_INVALID_CALL: - *aResetReason = DeviceResetReason::INVALID_CALL; - default: - MOZ_ASSERT(false); - } - } + if (IsDeviceReset(hr, aResetReason)) { return true; } } if (mD3D11ContentDevice) { - if (mD3D11ContentDevice->GetDeviceRemovedReason() != S_OK) { + HRESULT hr = mD3D11ContentDevice->GetDeviceRemovedReason(); + if (IsDeviceReset(hr, aResetReason)) { return true; } } if (GetD3D10Device()) { - if (GetD3D10Device()->GetDeviceRemovedReason() != S_OK) { + HRESULT hr = GetD3D10Device()->GetDeviceRemovedReason(); + if (IsDeviceReset(hr, aResetReason)) { return true; } } @@ -1575,11 +1602,51 @@ gfxWindowsPlatform::GetD3D11ContentDevice() ID3D11Device* gfxWindowsPlatform::GetD3D11MediaDevice() { - if (mD3D11DeviceInitialized) { + if (mD3D11MediaDevice) { return mD3D11MediaDevice; } - InitD3D11Devices(); + if (!mCanInitMediaDevice) { + return nullptr; + } + + mCanInitMediaDevice = false; + + nsModuleHandle d3d11Module(LoadLibrarySystem32(L"d3d11.dll")); + decltype(D3D11CreateDevice)* d3d11CreateDevice = (decltype(D3D11CreateDevice)*) + GetProcAddress(d3d11Module, "D3D11CreateDevice"); + MOZ_ASSERT(d3d11CreateDevice); + + nsTArray featureLevels; + if (IsWin8OrLater()) { + featureLevels.AppendElement(D3D_FEATURE_LEVEL_11_1); + } + featureLevels.AppendElement(D3D_FEATURE_LEVEL_11_0); + featureLevels.AppendElement(D3D_FEATURE_LEVEL_10_1); + featureLevels.AppendElement(D3D_FEATURE_LEVEL_10_0); + featureLevels.AppendElement(D3D_FEATURE_LEVEL_9_3); + + RefPtr adapter = GetDXGIAdapter(); + MOZ_ASSERT(adapter); + + HRESULT hr = E_INVALIDARG; + + MOZ_SEH_TRY{ + hr = d3d11CreateDevice(adapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, + featureLevels.Elements(), featureLevels.Length(), + D3D11_SDK_VERSION, byRef(mD3D11MediaDevice), nullptr, nullptr); + } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { + mD3D11MediaDevice = nullptr; + } + + d3d11Module.disown(); + + if (FAILED(hr)) { + return nullptr; + } + + mD3D11MediaDevice->SetExceptionMode(0); return mD3D11MediaDevice; } @@ -1958,23 +2025,8 @@ gfxWindowsPlatform::InitD3D11Devices() Factory::SetDirect3D11Device(mD3D11ContentDevice); } - if (!useWARP || gfxPrefs::LayersD3D11ForceWARP()) { - hr = E_INVALIDARG; - MOZ_SEH_TRY{ - hr = d3d11CreateDevice(adapter, useWARP ? D3D_DRIVER_TYPE_WARP : D3D_DRIVER_TYPE_UNKNOWN, nullptr, - D3D11_CREATE_DEVICE_BGRA_SUPPORT, - featureLevels.Elements(), featureLevels.Length(), - D3D11_SDK_VERSION, byRef(mD3D11MediaDevice), nullptr, nullptr); - } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { - mD3D11MediaDevice = nullptr; - } - - if (FAILED(hr)) { - d3d11Module.disown(); - return; - } - - mD3D11MediaDevice->SetExceptionMode(0); + if (!useWARP) { + mCanInitMediaDevice = true; } // We leak these everywhere and we need them our entire runtime anyway, let's @@ -2215,3 +2267,10 @@ gfxWindowsPlatform::CreateHardwareVsyncSource() nsRefPtr d3dVsyncSource = new D3DVsyncSource(); return d3dVsyncSource.forget(); } + +bool +gfxWindowsPlatform::SupportsApzTouchInput() +{ + int value = Preferences::GetInt("dom.w3c_touch_events.enabled", 0); + return value == 1 || value == 2; +} diff --git a/gfx/thebes/gfxWindowsPlatform.h b/gfx/thebes/gfxWindowsPlatform.h index 74874758de..e8dbbd72ff 100644 --- a/gfx/thebes/gfxWindowsPlatform.h +++ b/gfx/thebes/gfxWindowsPlatform.h @@ -257,6 +257,11 @@ public: bool IsWARP() { return mIsWARP; } + bool SupportsApzWheelInput() override { + return true; + } + bool SupportsApzTouchInput() override; + virtual already_AddRefed CreateHardwareVsyncSource() override; static mozilla::Atomic sD3D11MemoryUsed; static mozilla::Atomic sD3D9MemoryUsed; @@ -273,6 +278,7 @@ private: void Init(); void InitD3D11Devices(); IDXGIAdapter1 *GetDXGIAdapter(); + bool IsDeviceReset(HRESULT hr, DeviceResetReason* aReason); bool mUseDirectWrite; bool mUsingGDIFonts; @@ -294,6 +300,9 @@ private: bool mD3D11DeviceInitialized; mozilla::RefPtr mD3D11ReadbackManager; bool mIsWARP; + bool mCanInitMediaDevice; + bool mHasDeviceReset; + DeviceResetReason mDeviceResetReason; virtual void GetPlatformCMSOutputProfile(void* &mem, size_t &size); }; diff --git a/js/ipc/CPOWTimer.cpp b/js/ipc/CPOWTimer.cpp index c1935999f2..2dc25806d0 100644 --- a/js/ipc/CPOWTimer.cpp +++ b/js/ipc/CPOWTimer.cpp @@ -6,22 +6,20 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "jsfriendapi.h" -#include "xpcprivate.h" +#include "nsContentUtils.h" #include "CPOWTimer.h" CPOWTimer::~CPOWTimer() { - /* This is a best effort to find the compartment responsible for this CPOW call */ - nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal(); - if (!global) + JSContext* cx = nsContentUtils::GetCurrentJSContextForThread(); + if (!cx) return; - JSObject* obj = global->GetGlobalJSObject(); - if (!obj) + + JSRuntime* runtime = JS_GetRuntime(cx); + if (!js::IsStopwatchActive(runtime)) return; - JSCompartment* compartment = js::GetObjectCompartment(obj); - xpc::CompartmentPrivate* compartmentPrivate = xpc::CompartmentPrivate::Get(compartment); - if (!compartmentPrivate) - return; - PRIntervalTime time = PR_IntervalNow() - startInterval; - compartmentPrivate->CPOWTime += time; + + js::PerformanceData *performance = js::GetPerformanceData(runtime); + uint64_t duration = PR_IntervalToMicroseconds(PR_IntervalNow() - startInterval); + performance->totalCPOWTime += duration; } diff --git a/js/ipc/CPOWTimer.h b/js/ipc/CPOWTimer.h index 5a568c1800..158ea9e981 100644 --- a/js/ipc/CPOWTimer.h +++ b/js/ipc/CPOWTimer.h @@ -12,12 +12,30 @@ class JSObject; -class MOZ_STACK_CLASS CPOWTimer { +/** + * A stopwatch measuring the duration of a CPOW call. + * + * As the process is consuming neither user time nor system time + * during a CPOW call, we measure such durations using wallclock time. + * + * This stopwatch is active iff JSRuntime::stopwatch.isActive is set. + * Upon destruction, update JSRuntime::stopwatch.data.totalCPOWTime. + */ +class MOZ_STACK_CLASS CPOWTimer final { public: - CPOWTimer(): startInterval(PR_IntervalNow()) {} + explicit inline CPOWTimer(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) + : startInterval(PR_IntervalNow()) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } ~CPOWTimer(); private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + + /** + * The instant at which the stopwatch was started. + */ PRIntervalTime startInterval; }; diff --git a/js/ipc/moz.build b/js/ipc/moz.build index 7a8a223191..8be6f22e77 100644 --- a/js/ipc/moz.build +++ b/js/ipc/moz.build @@ -35,6 +35,7 @@ LOCAL_INCLUDES += [ '/dom/base', '/js/ipc', '/js/public', + '/js/src', '/js/xpconnect/src', '/js/xpconnect/wrappers', ] diff --git a/js/public/Principals.h b/js/public/Principals.h index 3e648e5a3e..33e5cfa2af 100644 --- a/js/public/Principals.h +++ b/js/public/Principals.h @@ -15,6 +15,10 @@ #include "jspubtd.h" +namespace js { + struct PerformanceGroup; +} + struct JSPrincipals { /* Don't call "destroy"; use reference counting macros below. */ mozilla::Atomic refcount; diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index c77a6e9dfa..9b4c7e3b00 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -665,7 +665,7 @@ TokenStream::reportCompileErrorNumberVA(uint32_t offset, unsigned flags, unsigne NonBuiltinFrameIter iter(cx->asJSContext(), FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED, FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK, - cx->compartment()->principals); + cx->compartment()->principals()); if (!iter.done() && iter.scriptFilename()) { callerFilename = true; err.report.filename = iter.scriptFilename(); diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index cb79acb239..0df52f3d35 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -8,6 +8,7 @@ #include "mozilla/DebugOnly.h" +#include "jsgc.h" #include "jsprf.h" #include "gc/GCInternals.h" @@ -425,25 +426,29 @@ namespace js { namespace gc { template -static bool -IsMarked(T** thingp) +static inline void +CheckIsMarkedThing(T** thingp) { +#ifdef DEBUG + MOZ_ASSERT(thingp); + MOZ_ASSERT(*thingp); + JSRuntime* rt = (*thingp)->runtimeFromAnyThread(); MOZ_ASSERT_IF(!ThingIsPermanentAtom(*thingp), - CurrentThreadCanAccessRuntime((*thingp)->runtimeFromMainThread())); - return IsMarkedFromAnyThread(thingp); + CurrentThreadCanAccessRuntime(rt) || + (rt->isHeapCollecting() && rt->gc.state() == SWEEP)); +#endif } template static bool -IsMarkedFromAnyThread(T** thingp) +IsMarked(T** thingp) { - MOZ_ASSERT(thingp); - MOZ_ASSERT(*thingp); + CheckIsMarkedThing(thingp); JSRuntime* rt = (*thingp)->runtimeFromAnyThread(); if (IsInsideNursery(*thingp)) { - Nursery& nursery = rt->gc.nursery; - return nursery.getForwardedPointer(thingp); + MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); + return rt->gc.nursery.getForwardedPointer(thingp); } Zone* zone = (*thingp)->asTenured().zoneFromAnyThread(); @@ -458,18 +463,7 @@ template static bool IsAboutToBeFinalized(T** thingp) { - MOZ_ASSERT_IF(!ThingIsPermanentAtom(*thingp), - CurrentThreadCanAccessRuntime((*thingp)->runtimeFromMainThread())); - return IsAboutToBeFinalizedFromAnyThread(thingp); -} - -template -static bool -IsAboutToBeFinalizedFromAnyThread(T** thingp) -{ - MOZ_ASSERT(thingp); - MOZ_ASSERT(*thingp); - + CheckIsMarkedThing(thingp); T* thing = *thingp; JSRuntime* rt = thing->runtimeFromAnyThread(); @@ -554,24 +548,12 @@ Mark##base##RootRange(JSTracer* trc, size_t len, type** vec, const char* name) } \ \ bool \ -Is##base##MarkedFromAnyThread(type **thingp) \ -{ \ - return IsMarkedFromAnyThread(thingp); \ -} \ - \ -bool \ Is##base##Marked(type **thingp) \ { \ return IsMarked(thingp); \ } \ \ bool \ -Is##base##MarkedFromAnyThread(BarrieredBase* thingp) \ -{ \ - return IsMarkedFromAnyThread(thingp->unsafeGet()); \ -} \ - \ -bool \ Is##base##Marked(BarrieredBase* thingp) \ { \ return IsMarked(thingp->unsafeGet()); \ @@ -584,12 +566,6 @@ Is##base##AboutToBeFinalized(type** thingp) } \ \ bool \ -Is##base##AboutToBeFinalizedFromAnyThread(type** thingp) \ -{ \ - return IsAboutToBeFinalizedFromAnyThread(thingp); \ -} \ - \ -bool \ Is##base##AboutToBeFinalized(BarrieredBase* thingp) \ { \ return IsAboutToBeFinalized(thingp->unsafeGet()); \ @@ -883,28 +859,6 @@ gc::IsValueAboutToBeFinalized(Value* v) return rv; } -bool -gc::IsValueAboutToBeFinalizedFromAnyThread(Value* v) -{ - MOZ_ASSERT(v->isMarkable()); - bool rv; - if (v->isString()) { - JSString* str = (JSString*)v->toGCThing(); - rv = IsAboutToBeFinalizedFromAnyThread(&str); - v->setString(str); - } else if (v->isObject()) { - JSObject* obj = (JSObject*)v->toGCThing(); - rv = IsAboutToBeFinalizedFromAnyThread(&obj); - v->setObject(*obj); - } else { - MOZ_ASSERT(v->isSymbol()); - JS::Symbol* sym = v->toSymbol(); - rv = IsAboutToBeFinalizedFromAnyThread(&sym); - v->setSymbol(sym); - } - return rv; -} - /*** Type Marking ***/ void diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index 2072b81e81..ccd94fe151 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -93,10 +93,7 @@ void Mark##base##Range(JSTracer *trc, size_t len, HeapPtr *thing, const c void Mark##base##RootRange(JSTracer *trc, size_t len, type **thing, const char *name); \ bool Is##base##Marked(type **thingp); \ bool Is##base##Marked(BarrieredBase *thingp); \ -bool Is##base##MarkedFromAnyThread(type **thingp); \ -bool Is##base##MarkedFromAnyThread(BarrieredBase *thingp); \ bool Is##base##AboutToBeFinalized(type **thingp); \ -bool Is##base##AboutToBeFinalizedFromAnyThread(type **thingp); \ bool Is##base##AboutToBeFinalized(BarrieredBase *thingp); \ type* Update##base##IfRelocated(JSRuntime *rt, BarrieredBase *thingp); \ type* Update##base##IfRelocated(JSRuntime *rt, type **thingp); @@ -216,9 +213,6 @@ IsValueMarked(Value* v); bool IsValueAboutToBeFinalized(Value* v); -bool -IsValueAboutToBeFinalizedFromAnyThread(Value* v); - /*** Slot Marking ***/ bool diff --git a/js/src/jit/JitcodeMap.cpp b/js/src/jit/JitcodeMap.cpp index d7a7cd364a..3aff298a4a 100644 --- a/js/src/jit/JitcodeMap.cpp +++ b/js/src/jit/JitcodeMap.cpp @@ -453,12 +453,8 @@ JitcodeGlobalTable::lookupForSampler(void *ptr, JitcodeGlobalEntry *result, JSRu // the sweep phase of the GC must be on stack, and on-stack frames must // already be marked at the beginning of the sweep phase. This assumption // is verified below. - if (rt->isHeapBusy() && - rt->gc.stats.currentPhase() >= gcstats::PHASE_SWEEP && - rt->gc.stats.currentPhase() <= gcstats::PHASE_GC_END) - { + if (rt->isHeapBusy() && rt->gc.state() == gc::SWEEP) MOZ_ASSERT(entry->isMarkedFromAnyThread(rt)); - } #endif *result = *entry; @@ -799,7 +795,7 @@ JitcodeGlobalTable::sweep(JSRuntime *rt) bool JitcodeGlobalEntry::BaseEntry::markJitcodeIfUnmarked(JSTracer *trc) { - if (!IsJitCodeMarkedFromAnyThread(&jitcode_)) { + if (!IsJitCodeMarked(&jitcode_)) { MarkJitCodeUnbarriered(trc, &jitcode_, "jitcodglobaltable-baseentry-jitcode"); return true; } @@ -809,7 +805,7 @@ JitcodeGlobalEntry::BaseEntry::markJitcodeIfUnmarked(JSTracer *trc) bool JitcodeGlobalEntry::BaseEntry::isJitcodeMarkedFromAnyThread() { - return IsJitCodeMarkedFromAnyThread(&jitcode_) || + return IsJitCodeMarked(&jitcode_) || jitcode_->arenaHeader()->allocatedDuringIncremental; } @@ -822,7 +818,7 @@ JitcodeGlobalEntry::BaseEntry::isJitcodeAboutToBeFinalized() bool JitcodeGlobalEntry::BaselineEntry::markIfUnmarked(JSTracer *trc) { - if (!IsScriptMarkedFromAnyThread(&script_)) { + if (!IsScriptMarked(&script_)) { MarkScriptUnbarriered(trc, &script_, "jitcodeglobaltable-baselineentry-script"); return true; } @@ -838,7 +834,7 @@ JitcodeGlobalEntry::BaselineEntry::sweep() bool JitcodeGlobalEntry::BaselineEntry::isMarkedFromAnyThread() { - return IsScriptMarkedFromAnyThread(&script_) || + return IsScriptMarked(&script_) || script_->arenaHeader()->allocatedDuringIncremental; } @@ -848,7 +844,7 @@ JitcodeGlobalEntry::IonEntry::markIfUnmarked(JSTracer *trc) bool markedAny = false; for (unsigned i = 0; i < numScripts(); i++) { - if (!IsScriptMarkedFromAnyThread(&sizedScriptList()->pairs[i].script)) { + if (!IsScriptMarked(&sizedScriptList()->pairs[i].script)) { MarkScriptUnbarriered(trc, &sizedScriptList()->pairs[i].script, "jitcodeglobaltable-ionentry-script"); markedAny = true; @@ -861,15 +857,15 @@ JitcodeGlobalEntry::IonEntry::markIfUnmarked(JSTracer *trc) for (IonTrackedTypeWithAddendum *iter = optsAllTypes_->begin(); iter != optsAllTypes_->end(); iter++) { - if (!TypeSet::IsTypeMarkedFromAnyThread(&iter->type)) { + if (!TypeSet::IsTypeMarked(&iter->type)) { TypeSet::MarkTypeUnbarriered(trc, &iter->type, "jitcodeglobaltable-ionentry-type"); markedAny = true; } - if (iter->hasAllocationSite() && !IsScriptMarkedFromAnyThread(&iter->script)) { + if (iter->hasAllocationSite() && !IsScriptMarked(&iter->script)) { MarkScriptUnbarriered(trc, &iter->script, "jitcodeglobaltable-ionentry-type-addendum-script"); markedAny = true; - } else if (iter->hasConstructor() && !IsObjectMarkedFromAnyThread(&iter->constructor)) { + } else if (iter->hasConstructor() && !IsObjectMarked(&iter->constructor)) { MarkObjectUnbarriered(trc, &iter->constructor, "jitcodeglobaltable-ionentry-type-addendum-constructor"); markedAny = true; @@ -905,7 +901,7 @@ bool JitcodeGlobalEntry::IonEntry::isMarkedFromAnyThread() { for (unsigned i = 0; i < numScripts(); i++) { - if (!IsScriptMarkedFromAnyThread(&sizedScriptList()->pairs[i].script) && + if (!IsScriptMarked(&sizedScriptList()->pairs[i].script) && !sizedScriptList()->pairs[i].script->arenaHeader()->allocatedDuringIncremental) { return false; @@ -918,7 +914,7 @@ JitcodeGlobalEntry::IonEntry::isMarkedFromAnyThread() for (IonTrackedTypeWithAddendum *iter = optsAllTypes_->begin(); iter != optsAllTypes_->end(); iter++) { - if (!TypeSet::IsTypeMarkedFromAnyThread(&iter->type) && + if (!TypeSet::IsTypeMarked(&iter->type) && !TypeSet::IsTypeAllocatedDuringIncremental(iter->type)) { return false; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 5369ca300c..b33612d383 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -271,30 +271,78 @@ JS_GetEmptyString(JSRuntime* rt) return rt->emptyString; } -JS_PUBLIC_API(bool) -JS_GetCompartmentStats(JSRuntime* rt, CompartmentStatsVector& stats) -{ - for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) { - if (!stats.growBy(1)) - return false; +namespace js { - CompartmentTimeStats* stat = &stats.back(); - stat->time = c.get()->totalTime; - stat->compartment = c.get(); - stat->addonId = c.get()->addonId; - if (rt->compartmentNameCallback) { - (*rt->compartmentNameCallback)(rt, stat->compartment, - stat->compartmentName, - MOZ_ARRAY_LENGTH(stat->compartmentName)); +JS_PUBLIC_API(bool) +GetPerformanceStats(JSRuntime* rt, + PerformanceStatsVector& stats, + PerformanceStats& processStats) +{ + // As a PerformanceGroup is typically associated to several + // compartments, use a HashSet to make sure that we only report + // each PerformanceGroup once. + typedef HashSet, + js::SystemAllocPolicy> Set; + Set set; + if (!set.init(100)) { + return false; + } + + for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) { + JSCompartment* compartment = c.get(); + if (!compartment->performanceMonitoring.isLinked()) { + // Don't report compartments that do not even have a PerformanceGroup. + continue; + } + PerformanceGroup* group = compartment->performanceMonitoring.getGroup(); + + if (group->data.ticks == 0) { + // Don't report compartments that have never been used. + continue; + } + + Set::AddPtr ptr = set.lookupForAdd(group); + if (ptr) { + // Don't report the same group twice. + continue; + } + + if (!stats.growBy(1)) { + // Memory issue + return false; + } + PerformanceStats* stat = &stats.back(); + stat->isSystem = compartment->isSystem(); + if (compartment->addonId) + stat->addonId = compartment->addonId; + + if (compartment->addonId || !compartment->isSystem()) { + if (rt->compartmentNameCallback) { + (*rt->compartmentNameCallback)(rt, compartment, + stat->name, + mozilla::ArrayLength(stat->name)); + } else { + strcpy(stat->name, ""); + } } else { - strcpy(stat->compartmentName, ""); + strcpy(stat->name, ""); + } + stat->performance = group->data; + if (!set.add(ptr, group)) { + // Memory issue + return false; } } + + strcpy(processStats.name, ""); + processStats.addonId = nullptr; + processStats.isSystem = true; + processStats.performance = rt->stopwatch.performance; + return true; } -namespace js { - void AssertHeapIsIdle(JSRuntime* rt) { diff --git a/js/src/jsapi.h b/js/src/jsapi.h index d449552d14..b0554868a2 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -964,19 +964,6 @@ JS_GetEmptyStringValue(JSContext* cx); extern JS_PUBLIC_API(JSString*) JS_GetEmptyString(JSRuntime* rt); -struct CompartmentTimeStats { - char compartmentName[1024]; - JSAddonId* addonId; - JSCompartment* compartment; - uint64_t time; // microseconds - uint64_t cpowTime; // microseconds -}; - -typedef js::Vector CompartmentStatsVector; - -extern JS_PUBLIC_API(bool) -JS_GetCompartmentStats(JSRuntime* rt, CompartmentStatsVector& stats); - extern JS_PUBLIC_API(bool) JS_ValueToObject(JSContext* cx, JS::HandleValue v, JS::MutableHandleObject objp); @@ -990,7 +977,7 @@ extern JS_PUBLIC_API(JSString*) JS_ValueToSource(JSContext* cx, JS::Handle v); extern JS_PUBLIC_API(bool) -JS_DoubleIsInt32(double d, int32_t* ip); +JS_DoubleIsInt32(double d, int32_t *ip); extern JS_PUBLIC_API(JSType) JS_TypeOfValue(JSContext* cx, JS::Handle v); @@ -5271,4 +5258,258 @@ StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString } /* namespace JS */ + +/* Stopwatch-based CPU monitoring. */ + +namespace js { + +struct AutoStopwatch; + +// Container for performance data +// All values are monotonic. +struct PerformanceData { + // Number of times we have spent at least 2^n consecutive + // milliseconds executing code in this group. + // durations[0] is increased whenever we spend at least 1 ms + // executing code in this group + // durations[1] whenever we spend 2ms+ + // + // durations[i] whenever we spend 2^ims+ + uint64_t durations[10]; + + // Total amount of time spent executing code in this group, in + // microseconds. + uint64_t totalUserTime; + uint64_t totalSystemTime; + uint64_t totalCPOWTime; + + // Total number of times code execution entered this group, + // since process launch. This may be greater than the number + // of times we have entered the event loop. + uint64_t ticks; + + PerformanceData() + : totalUserTime(0) + , totalSystemTime(0) + , totalCPOWTime(0) + , ticks(0) + { + mozilla::PodArrayZero(durations); + } + PerformanceData(const PerformanceData& from) + : totalUserTime(from.totalUserTime) + , totalSystemTime(from.totalSystemTime) + , totalCPOWTime(from.totalCPOWTime) + , ticks(from.ticks) + { + mozilla::PodArrayCopy(durations, from.durations); + } + PerformanceData& operator=(const PerformanceData& from) + { + mozilla::PodArrayCopy(durations, from.durations); + totalUserTime = from.totalUserTime; + totalSystemTime = from.totalSystemTime; + totalCPOWTime = from.totalCPOWTime; + ticks = from.ticks; + return *this; + } +}; + +// A group of compartments forming a single unit in terms of +// performance monitoring. +// +// Two compartments belong to the same group if either: +// - they are part of the same add-on; +// - they are part of the same webpage; +// - they are both system built-ins. +// +// This class is refcounted by instances of `JSCompartment`. +// Do not attempt to hold to a pointer to a `PerformanceGroup`. +struct PerformanceGroup { + + // Performance data for this group. + PerformanceData data; + + // `true` if an instance of `AutoStopwatch` is already monitoring + // the performance of this performance group for this iteration + // of the event loop, `false` otherwise. + bool hasStopwatch(uint64_t iteration) const { + return stopwatch_ != nullptr && iteration_ == iteration; + } + + // Mark that an instance of `AutoStopwatch` is monitoring + // the performance of this group for a given iteration. + void acquireStopwatch(uint64_t iteration, const AutoStopwatch *stopwatch) { + iteration_ = iteration; + stopwatch_ = stopwatch; + } + + // Mark that no `AutoStopwatch` is monitoring the + // performance of this group for the iteration. + void releaseStopwatch(uint64_t iteration, const AutoStopwatch *stopwatch) { + if (iteration_ != iteration) + return; + + MOZ_ASSERT(stopwatch == stopwatch_ || stopwatch_ == nullptr); + stopwatch_ = nullptr; + } + + PerformanceGroup() + : stopwatch_(nullptr) + , iteration_(0) + , refCount_(0) + { } + ~PerformanceGroup() + { + MOZ_ASSERT(refCount_ == 0); + } + private: + PerformanceGroup& operator=(const PerformanceGroup&) = delete; + PerformanceGroup(const PerformanceGroup&) = delete; + + // The stopwatch currently monitoring the group, + // or `nullptr` if none. Used ony for comparison. + const AutoStopwatch *stopwatch_; + + // The current iteration of the event loop. If necessary, + // may safely overflow. + uint64_t iteration_; + + // Increment/decrement the refcounter, return the updated value. + uint64_t incRefCount() { + MOZ_ASSERT(refCount_ + 1 > 0); + return ++refCount_; + } + uint64_t decRefCount() { + MOZ_ASSERT(refCount_ > 0); + return --refCount_; + } + friend struct PerformanceGroupHolder; + + private: + // A reference counter. Maintained by PerformanceGroupHolder. + uint64_t refCount_; +}; + +// +// Indirection towards a PerformanceGroup. +// This structure handles reference counting for instances of PerformanceGroup. +// +struct PerformanceGroupHolder { + // Get the group. + // On first call, this causes a single Hashtable lookup. + // Successive calls do not require further lookups. + js::PerformanceGroup *getGroup(); + + // `true` if the this holder is currently associated to a + // PerformanceGroup, `false` otherwise. Use this method to avoid + // instantiating a PerformanceGroup if you only need to get + // available performance data. + inline bool isLinked() const { + return group_ != nullptr; + } + + // Remove the link to the PerformanceGroup. This method is designed + // as an invalidation mechanism if the JSCompartment changes nature + // (new values of `isSystem()`, `principals()` or `addonId`). + void unlink(); + + PerformanceGroupHolder(JSRuntime *runtime, JSCompartment *compartment) + : runtime_(runtime) + , compartment_(compartment) + , group_(nullptr) + { } + ~PerformanceGroupHolder(); +private: + // Return the key representing this PerformanceGroup in + // Runtime::Stopwatch. + // Do not deallocate the key. + void* getHashKey(); + + JSRuntime *runtime_; + JSCompartment *compartment_; + + // The PerformanceGroup held by this object. + // Initially set to `nullptr` until the first cal to `getGroup`. + // May be reset to `nullptr` by a call to `unlink`. + js::PerformanceGroup *group_; +}; + +/** + * Reset any stopwatch currently measuring. + * + * This function is designed to be called when we process a new event. + */ +extern JS_PUBLIC_API(void) +ResetStopwatches(JSRuntime*); + +/** + * Turn on/off stopwatch-based CPU monitoring. + * + * `SetStopwatchActive` may return `false` if monitoring could not be + * activated, which may happen if we are out of memory. + */ +extern JS_PUBLIC_API(bool) +SetStopwatchActive(JSRuntime*, bool); +extern JS_PUBLIC_API(bool) +IsStopwatchActive(JSRuntime*); + +/** + * Access the performance information stored in a compartment. + */ +extern JS_PUBLIC_API(PerformanceData*) +GetPerformanceData(JSRuntime*); + +/** + * Performance statistics for a performance group (a process, an + * add-on, a webpage, the built-ins or a special compartment). + */ +struct PerformanceStats { + /** + * If this group represents an add-on, the ID of the addon, + * otherwise `nullptr`. + */ + JSAddonId *addonId; + + /** + * If this group represents a webpage, the process itself or a special + * compartment, a human-readable name. Unspecified for add-ons. + */ + char name[1024]; + + /** + * `true` if the group represents in system compartments, `false` + * otherwise. A group may never contain both system and non-system + * compartments. + */ + bool isSystem; + + /** + * Performance information. + */ + js::PerformanceData performance; + + PerformanceStats() + : addonId(nullptr) + , isSystem(false) + { + name[0] = '\0'; + } +}; + +typedef js::Vector PerformanceStatsVector; + + /** + * Extract the performance statistics. + * + * After a successful call, `stats` holds the `PerformanceStats` for + * all performance groups, and `global` holds a `PerformanceStats` + * representing the entire process. + */ +extern JS_PUBLIC_API(bool) +GetPerformanceStats(JSRuntime *rt, js::PerformanceStatsVector &stats, js::PerformanceStats &global); + +} /* namespace js */ + + #endif /* jsapi_h */ diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index d70ac712c5..1c3cdb9eb5 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -255,7 +255,7 @@ JSRuntime::sweepAtoms() for (AtomSet::Enum e(*atoms_); !e.empty(); e.popFront()) { AtomStateEntry entry = e.front(); JSAtom* atom = entry.asPtr(); - bool isDying = IsStringAboutToBeFinalizedFromAnyThread(&atom); + bool isDying = IsStringAboutToBeFinalized(&atom); /* Pinned or interned key cannot be finalized. */ MOZ_ASSERT_IF(hasContexts() && entry.isTagged(), !isDying); diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 0f4ded9ede..b46d88690b 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -258,7 +258,7 @@ PopulateReportBlame(JSContext* cx, JSErrorReport* report) * Walk stack until we find a frame that is associated with a non-builtin * rather than a builtin frame and which we're allowed to know about. */ - NonBuiltinFrameIter iter(cx, cx->compartment()->principals); + NonBuiltinFrameIter iter(cx, cx->compartment()->principals()); if (iter.done()) return; diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h index dd431319a6..b33969c42a 100644 --- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -375,7 +375,7 @@ JSContext::setPendingException(js::Value v) inline bool JSContext::runningWithTrustedPrincipals() const { - return !compartment() || compartment()->principals == runtime()->trustedPrincipals(); + return !compartment() || compartment()->principals() == runtime()->trustedPrincipals(); } inline void diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 6f7712e93e..5b4247bce6 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -41,8 +41,8 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = : options_(options), zone_(zone), runtime_(zone->runtimeFromMainThread()), - principals(nullptr), - isSystem(false), + principals_(nullptr), + isSystem_(false), isSelfHosting(false), marked(true), warnedAboutNoSuchMethod(false), @@ -53,7 +53,7 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = #endif global_(nullptr), enterCompartmentDepth(0), - totalTime(0), + performanceMonitoring(runtime_, this), data(nullptr), objectMetadataCallback(nullptr), lastAnimationTime(0), @@ -522,7 +522,7 @@ JSCompartment::sweepSavedStacks() void JSCompartment::sweepGlobalObject(FreeOp* fop) { - if (global_.unbarrieredGet() && IsObjectAboutToBeFinalizedFromAnyThread(global_.unsafeGet())) { + if (global_.unbarrieredGet() && IsObjectAboutToBeFinalized(global_.unsafeGet())) { if (isDebuggee()) Debugger::detachAllDebuggersFromGlobal(fop, global_); global_.set(nullptr); @@ -533,7 +533,7 @@ void JSCompartment::sweepSelfHostingScriptSource() { if (selfHostingScriptSource.unbarrieredGet() && - IsObjectAboutToBeFinalizedFromAnyThread((JSObject**) selfHostingScriptSource.unsafeGet())) + IsObjectAboutToBeFinalized((JSObject**) selfHostingScriptSource.unsafeGet())) { selfHostingScriptSource.set(nullptr); } @@ -580,7 +580,7 @@ JSCompartment::sweepNativeIterators() while (ni != enumerators) { JSObject* iterObj = ni->iterObj(); NativeIterator* next = ni->next(); - if (gc::IsObjectAboutToBeFinalizedFromAnyThread(&iterObj)) + if (gc::IsObjectAboutToBeFinalized(&iterObj)) ni->unlink(); ni = next; } @@ -605,24 +605,24 @@ JSCompartment::sweepCrossCompartmentWrappers() case CrossCompartmentKey::DebuggerSource: MOZ_ASSERT(IsInsideNursery(key.wrapped) || key.wrapped->asTenured().getTraceKind() == JSTRACE_OBJECT); - keyDying = IsObjectAboutToBeFinalizedFromAnyThread( + keyDying = IsObjectAboutToBeFinalized( reinterpret_cast(&key.wrapped)); break; case CrossCompartmentKey::StringWrapper: MOZ_ASSERT(key.wrapped->asTenured().getTraceKind() == JSTRACE_STRING); - keyDying = IsStringAboutToBeFinalizedFromAnyThread( + keyDying = IsStringAboutToBeFinalized( reinterpret_cast(&key.wrapped)); break; case CrossCompartmentKey::DebuggerScript: MOZ_ASSERT(key.wrapped->asTenured().getTraceKind() == JSTRACE_SCRIPT); - keyDying = IsScriptAboutToBeFinalizedFromAnyThread( + keyDying = IsScriptAboutToBeFinalized( reinterpret_cast(&key.wrapped)); break; default: MOZ_CRASH("Unknown key kind"); } - bool valDying = IsValueAboutToBeFinalizedFromAnyThread(e.front().value().unsafeGet()); - bool dbgDying = key.debugger && IsObjectAboutToBeFinalizedFromAnyThread(&key.debugger); + bool valDying = IsValueAboutToBeFinalized(e.front().value().unsafeGet()); + bool dbgDying = key.debugger && IsObjectAboutToBeFinalized(&key.debugger); if (keyDying || valDying || dbgDying) { MOZ_ASSERT(key.kind != CrossCompartmentKey::StringWrapper); e.removeFront(); diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index c17f41adae..cc479473c8 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -144,8 +144,50 @@ struct JSCompartment JSRuntime* runtime_; public: - JSPrincipals* principals; - bool isSystem; + /* + * The principals associated with this compartment. Note that the + * same several compartments may share the same principals and + * that a compartment may change principals during its lifetime + * (e.g. in case of lazy parsing). + */ + inline JSPrincipals* principals() { + return principals_; + } + inline void setPrincipals(JSPrincipals* principals) { + if (principals_ == principals) + return; + + // If we change principals, we need to unlink immediately this + // compartment from its PerformanceGroup. For one thing, the + // performance data we collect should not be improperly associated + // with a group to which we do not belong anymore. For another thing, + // we use `principals()` as part of the key to map compartments + // to a `PerformanceGroup`, so if we do not unlink now, this will + // be too late once we have updated `principals_`. + performanceMonitoring.unlink(); + principals_ = principals; + } + inline bool isSystem() const { + return isSystem_; + } + inline void setIsSystem(bool isSystem) { + if (isSystem_ == isSystem) + return; + + // If we change `isSystem*(`, we need to unlink immediately this + // compartment from its PerformanceGroup. For one thing, the + // performance data we collect should not be improperly associated + // to a group to which we do not belong anymore. For another thing, + // we use `isSystem()` as part of the key to map compartments + // to a `PerformanceGroup`, so if we do not unlink now, this will + // be too late once we have updated `isSystem_`. + performanceMonitoring.unlink(); + isSystem_ = isSystem; + } + private: + JSPrincipals* principals_; + bool isSystem_; + public: bool isSelfHosting; bool marked; bool warnedAboutNoSuchMethod; @@ -153,7 +195,7 @@ struct JSCompartment // A null add-on ID means that the compartment is not associated with an // add-on. - JSAddonId* addonId; + JSAddonId* const addonId; #ifdef DEBUG bool firedOnNewGlobalObject; @@ -171,18 +213,13 @@ struct JSCompartment int64_t startInterval; public: - int64_t totalTime; + js::PerformanceGroupHolder performanceMonitoring; + void enter() { - if (addonId && !enterCompartmentDepth) { - startInterval = PRMJ_Now(); - } enterCompartmentDepth++; } void leave() { enterCompartmentDepth--; - if (addonId && !enterCompartmentDepth) { - totalTime += (PRMJ_Now() - startInterval); - } } bool hasBeenEntered() { return !!enterCompartmentDepth; } @@ -366,7 +403,7 @@ struct JSCompartment bool putWrapper(JSContext* cx, const js::CrossCompartmentKey& wrapped, const js::Value& wrapper); - js::WrapperMap::Ptr lookupWrapper(const js::Value& wrapped) { + js::WrapperMap::Ptr lookupWrapper(const js::Value& wrapped) const { return crossCompartmentWrappers.lookup(js::CrossCompartmentKey(wrapped)); } diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index 49b5bcb930..07cf16e5f2 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -274,7 +274,7 @@ js::ComputeStackString(JSContext* cx) SuppressErrorsGuard seg(cx); for (NonBuiltinFrameIter i(cx, FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED, FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK, - cx->compartment()->principals); + cx->compartment()->principals()); !i.done(); ++i) { @@ -367,7 +367,7 @@ Error(JSContext* cx, unsigned argc, Value* vp) } /* Find the scripted caller, but only ones we're allowed to know about. */ - NonBuiltinFrameIter iter(cx, cx->compartment()->principals); + NonBuiltinFrameIter iter(cx, cx->compartment()->principals()); /* Set the 'fileName' property. */ RootedString fileName(cx); @@ -887,7 +887,7 @@ ErrorReport::populateUncaughtExceptionReportVA(JSContext* cx, va_list ap) // XXXbz this assumes the stack we have right now is still // related to our exception object. It would be better if we // could accept a passed-in stack of some sort instead. - NonBuiltinFrameIter iter(cx, cx->compartment()->principals); + NonBuiltinFrameIter iter(cx, cx->compartment()->principals()); if (!iter.done()) { ownedReport.filename = iter.scriptFilename(); ownedReport.lineno = iter.computeLine(&ownedReport.column); diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 9bd41b6c65..04f51377c2 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -156,14 +156,14 @@ JS_NewObjectWithoutMetadata(JSContext *cx, const JSClass *clasp, JS::Handleprincipals; + return compartment->principals(); } JS_FRIEND_API(void) JS_SetCompartmentPrincipals(JSCompartment* compartment, JSPrincipals* principals) { // Short circuit if there's no change. - if (principals == compartment->principals) + if (principals == compartment->principals()) return; // Any compartment with the trusted principals -- and there can be @@ -172,24 +172,24 @@ JS_SetCompartmentPrincipals(JSCompartment* compartment, JSPrincipals* principals bool isSystem = principals && principals == trusted; // Clear out the old principals, if any. - if (compartment->principals) { - JS_DropPrincipals(compartment->runtimeFromMainThread(), compartment->principals); - compartment->principals = nullptr; + if (compartment->principals()) { + JS_DropPrincipals(compartment->runtimeFromMainThread(), compartment->principals()); + compartment->setPrincipals(nullptr); // We'd like to assert that our new principals is always same-origin // with the old one, but JSPrincipals doesn't give us a way to do that. // But we can at least assert that we're not switching between system // and non-system. - MOZ_ASSERT(compartment->isSystem == isSystem); + MOZ_ASSERT(compartment->isSystem() == isSystem); } // Set up the new principals. if (principals) { JS_HoldPrincipals(principals); - compartment->principals = principals; + compartment->setPrincipals(principals); } // Update the system flag. - compartment->isSystem = isSystem; + compartment->setIsSystem(isSystem); } JS_FRIEND_API(JSPrincipals*) @@ -280,7 +280,7 @@ js::GetCompartmentZone(JSCompartment* comp) JS_FRIEND_API(bool) js::IsSystemCompartment(JSCompartment* comp) { - return comp->isSystem; + return comp->isSystem(); } JS_FRIEND_API(bool) diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index f661836443..c56fb788be 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3530,8 +3530,8 @@ Zone::sweepCompartments(FreeOp *fop, bool keepAtleastOne, bool destroyingRuntime if ((!comp->marked && !dontDelete) || destroyingRuntime) { if (callback) callback(fop, comp); - if (comp->principals) - JS_DropPrincipals(rt, comp->principals); + if (comp->principals()) + JS_DropPrincipals(rt, comp->principals()); js_delete(comp); } else { *write++ = comp; diff --git a/js/src/jsscriptinlines.h b/js/src/jsscriptinlines.h index 5a802a6ce5..492023cdb0 100644 --- a/js/src/jsscriptinlines.h +++ b/js/src/jsscriptinlines.h @@ -142,7 +142,7 @@ JSScript::global() const inline JSPrincipals* JSScript::principals() { - return compartment()->principals; + return compartment()->principals(); } inline void diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 9c69ad4db5..eb15d76bfc 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -1093,12 +1093,12 @@ InnerViewTable::removeViews(ArrayBufferObject* obj) bool InnerViewTable::sweepEntry(JSObject** pkey, ViewVector& views) { - if (IsObjectAboutToBeFinalizedFromAnyThread(pkey)) + if (IsObjectAboutToBeFinalized(pkey)) return true; MOZ_ASSERT(!views.empty()); for (size_t i = 0; i < views.length(); i++) { - if (IsObjectAboutToBeFinalizedFromAnyThread(&views[i])) { + if (IsObjectAboutToBeFinalized(&views[i])) { views[i--] = views.back(); views.popBack(); } diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 34e7ba85ec..92b62d1e62 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -372,7 +372,7 @@ js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& optio if (!global) return false; - JS_SetCompartmentPrincipals(global->compartment(), cx->compartment()->principals); + JS_SetCompartmentPrincipals(global->compartment(), cx->compartment()->principals()); // Initialize all classes required for parsing while still on the main // thread, for both the target and the new global so that prototype diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 76cfd42762..a0ac0df995 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -10,6 +10,7 @@ #include "vm/Interpreter-inl.h" +#include "mozilla/ArrayUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/FloatingPoint.h" #include "mozilla/PodOperations.h" @@ -52,6 +53,13 @@ #include "vm/ScopeObject-inl.h" #include "vm/Stack-inl.h" +#if defined(XP_UNIX) +#include +#elif defined(XP_WIN) +#include +#include +#endif // defined(XP_UNIX) || defined(XP_WIN) + using namespace js; using namespace js::gc; @@ -410,12 +418,234 @@ ExecuteState::pushInterpreterFrame(JSContext* cx) return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, thisv_, scopeChain_, type_, evalInFrame_); } +namespace js { + +// Implementation of per-performance group performance measurement. +// +// +// All mutable state is stored in `Runtime::stopwatch` (per-process +// performance stats and logistics) and in `PerformanceGroup` (per +// group performance stats). +struct AutoStopwatch final +{ + // If the stopwatch is active, constructing an instance of + // AutoStopwatch causes it to become the current owner of the + // stopwatch. + // + // Previous owner is restored upon destruction. + explicit inline AutoStopwatch(JSContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : compartment_(nullptr) + , runtime_(nullptr) + , iteration_(0) + , isActive_(false) + , isTop_(false) + , userTimeStart_(0) + , systemTimeStart_(0) + , CPOWTimeStart_(0) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + runtime_ = cx->runtime(); + if (!runtime_->stopwatch.isActive()) + return; + compartment_ = cx->compartment(); + MOZ_ASSERT(compartment_); + if (compartment_->scheduledForDestruction) + return; + iteration_ = runtime_->stopwatch.iteration; + + PerformanceGroup *group = compartment_->performanceMonitoring.getGroup(); + MOZ_ASSERT(group); + + if (group->hasStopwatch(iteration_)) { + // Someone is already monitoring this group during this + // tick, no need for further monitoring. + return; + } + + // Start the stopwatch. + if (!this->getTimes(&userTimeStart_, &systemTimeStart_)) + return; + isActive_ = true; + CPOWTimeStart_ = runtime_->stopwatch.performance.totalCPOWTime; + + // We are now in charge of monitoring this group for the tick, + // until destruction of `this` or until we enter a nested event + // loop and `iteration_` is incremented. + group->acquireStopwatch(iteration_, this); + + if (runtime_->stopwatch.isEmpty) { + // This is the topmost stopwatch on the stack. + // It will be in charge of updating the per-process + // performance data. + runtime_->stopwatch.isEmpty = false; + runtime_->stopwatch.performance.ticks++; + isTop_ = true; + } + } + inline ~AutoStopwatch() { + if (!isActive_) { + // We are not in charge of monitoring anything. + return; + } + + MOZ_ASSERT(!compartment_->scheduledForDestruction); + + if (!runtime_->stopwatch.isActive()) { + // Monitoring has been stopped while we were + // executing the code. Drop everything. + return; + } + + if (iteration_ != runtime_->stopwatch.iteration) { + // We have entered a nested event loop at some point. + // Any information we may have is obsolete. + return; + } + + PerformanceGroup *group = compartment_->performanceMonitoring.getGroup(); + MOZ_ASSERT(group); + + // Compute time spent. + group->releaseStopwatch(iteration_, this); + uint64_t userTimeEnd, systemTimeEnd; + if (!this->getTimes(&userTimeEnd, &systemTimeEnd)) + return; + + uint64_t userTimeDelta = userTimeEnd - userTimeStart_; + uint64_t systemTimeDelta = systemTimeEnd - systemTimeStart_; + uint64_t CPOWTimeDelta = runtime_->stopwatch.performance.totalCPOWTime - CPOWTimeStart_; + group->data.totalUserTime += userTimeDelta; + group->data.totalSystemTime += systemTimeDelta; + group->data.totalCPOWTime += CPOWTimeDelta; + + uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta; + updateDurations(totalTimeDelta, group->data.durations); + group->data.ticks++; + + if (isTop_) { + // This is the topmost stopwatch on the stack. + // Record the timing information. + runtime_->stopwatch.performance.totalUserTime = userTimeEnd; + runtime_->stopwatch.performance.totalSystemTime = systemTimeEnd; + updateDurations(totalTimeDelta, runtime_->stopwatch.performance.durations); + runtime_->stopwatch.isEmpty = true; + } + } + + private: + + // Update an array containing the number of times we have missed + // at least 2^0 successive ms, 2^1 successive ms, ... + // 2^i successive ms. + template + void updateDurations(uint64_t totalTimeDelta, uint64_t (&array)[N]) const { + // Duration of one frame, i.e. 16ms in museconds + size_t i = 0; + uint64_t duration = 1000; + for (i = 0, duration = 1000; + i < N && duration < totalTimeDelta; + ++i, duration *= 2) { + array[i]++; + } + } + + // Get the OS-reported time spent in userland/systemland, + // in microseconds. + bool getTimes(uint64_t *userTime, uint64_t *systemTime) const { + MOZ_ASSERT(userTime); + MOZ_ASSERT(systemTime); + +#if defined(XP_UNIX) + + struct rusage rusage; +#if defined(RUSAGE_THREAD) + // Under Linux, we can obtain per-thread statistics + int err = getrusage(RUSAGE_THREAD, &rusage); +#else + // Under other Unices, including MacOS X, we need to + // do with more noisy per-process statistics. + int err = getrusage(RUSAGE_SELF, &rusage); +#endif // defined(RUSAGE_THREAD) + MOZ_ASSERT(!err); + if (err) + return false; + + *userTime = rusage.ru_utime.tv_usec + + rusage.ru_utime.tv_sec * 1000000; + *systemTime = rusage.ru_stime.tv_usec + + rusage.ru_stime.tv_sec * 1000000; + +#elif defined(XP_WIN) + // Under Windows, we can obtain per-thread statistics, + // although experience seems to suggest that they are + // not very good under Windows XP. + FILETIME creationFileTime; // Ignored + FILETIME exitFileTime; // Ignored + FILETIME kernelFileTime; + FILETIME userFileTime; + BOOL success = GetThreadTimes(GetCurrentThread(), + &creationFileTime, &exitFileTime, + &kernelFileTime, &userFileTime); + MOZ_ASSERT(success); + if (!success) + return false; + + ULARGE_INTEGER kernelTimeInt; + ULARGE_INTEGER userTimeInt; + kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime; + kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime; + *systemTime = kernelTimeInt.QuadPart / 10; // 100 ns to 1 us + + userTimeInt.LowPart = userFileTime.dwLowDateTime; + userTimeInt.HighPart = userFileTime.dwHighDateTime; + *userTime = userTimeInt.QuadPart / 10; // 100 ns to 1 us +#endif // defined(XP_UNIX) || defined(XP_WIN) + + return true; + } + + private: + // The compartment with which this object was initialized. + // Non-null. + JSCompartment *compartment_; + + // The runtime with which this object was initialized. + // Non-null. + JSRuntime *runtime_; + + // An indication of the number of times we have entered the event + // loop. Used only for comparison. + uint64_t iteration_; + + // `true` if this object is currently used to monitor performance, + // `false` otherwise, i.e. if the stopwatch mechanism is off or if + // another stopwatch is already in charge of monitoring for the + // same PerformanceGroup. + bool isActive_; + + // `true` if this stopwatch is the topmost stopwatch on the stack + // for this event, `false` otherwise. + bool isTop_; + + // Timestamps captured while starting the stopwatch. + uint64_t userTimeStart_; + uint64_t systemTimeStart_; + uint64_t CPOWTimeStart_; + + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +} bool js::RunScript(JSContext* cx, RunState& state) { JS_CHECK_RECURSION(cx, return false); +#if defined(NIGHTLY_BUILD) + js::AutoStopwatch stopwatch(cx); +#endif // defined(NIGHTLY_BUILD) + SPSEntryMarker marker(cx->runtime(), state.script()); state.script()->ensureNonLazyCanonicalFunction(cx); diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index 5397987f91..fb891aac4c 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -795,7 +795,7 @@ JS::SystemCompartmentCount(JSRuntime* rt) { size_t n = 0; for (CompartmentsIter comp(rt, WithAtoms); !comp.done(); comp.next()) { - if (comp->isSystem) + if (comp->isSystem()) ++n; } return n; @@ -806,7 +806,7 @@ JS::UserCompartmentCount(JSRuntime* rt) { size_t n = 0; for (CompartmentsIter comp(rt, WithAtoms); !comp.done(); comp.next()) { - if (!comp->isSystem) + if (!comp->isSystem()) ++n; } return n; diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index 862c6b2c89..5e5db3be10 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -1392,17 +1392,17 @@ ObjectGroupCompartment::sweep(FreeOp* fop) bool remove = false; if (!key.type.isUnknown() && key.type.isGroup()) { ObjectGroup* group = key.type.groupNoBarrier(); - if (IsObjectGroupAboutToBeFinalizedFromAnyThread(&group)) + if (IsObjectGroupAboutToBeFinalized(&group)) remove = true; else key.type = TypeSet::ObjectType(group); } if (key.proto && key.proto != TaggedProto::LazyProto && - IsObjectAboutToBeFinalizedFromAnyThread(&key.proto)) + IsObjectAboutToBeFinalized(&key.proto)) { remove = true; } - if (IsObjectGroupAboutToBeFinalizedFromAnyThread(e.front().value().unsafeGet())) + if (IsObjectGroupAboutToBeFinalized(e.front().value().unsafeGet())) remove = true; if (remove) @@ -1418,19 +1418,19 @@ ObjectGroupCompartment::sweep(FreeOp* fop) PlainObjectEntry& entry = e.front().value(); bool remove = false; - if (IsObjectGroupAboutToBeFinalizedFromAnyThread(entry.group.unsafeGet())) + if (IsObjectGroupAboutToBeFinalized(entry.group.unsafeGet())) remove = true; - if (IsShapeAboutToBeFinalizedFromAnyThread(entry.shape.unsafeGet())) + if (IsShapeAboutToBeFinalized(entry.shape.unsafeGet())) remove = true; for (unsigned i = 0; !remove && i < key.nproperties; i++) { if (JSID_IS_STRING(key.properties[i])) { JSString* str = JSID_TO_STRING(key.properties[i]); - if (IsStringAboutToBeFinalizedFromAnyThread(&str)) + if (IsStringAboutToBeFinalized(&str)) remove = true; MOZ_ASSERT(AtomToId((JSAtom*)str) == key.properties[i]); } else if (JSID_IS_SYMBOL(key.properties[i])) { JS::Symbol* sym = JSID_TO_SYMBOL(key.properties[i]); - if (IsSymbolAboutToBeFinalizedFromAnyThread(&sym)) + if (IsSymbolAboutToBeFinalized(&sym)) remove = true; } @@ -1438,7 +1438,7 @@ ObjectGroupCompartment::sweep(FreeOp* fop) ObjectGroup* group = nullptr; if (entry.types[i].isGroup()) { group = entry.types[i].groupNoBarrier(); - if (IsObjectGroupAboutToBeFinalizedFromAnyThread(&group)) + if (IsObjectGroupAboutToBeFinalized(&group)) remove = true; else if (group != entry.types[i].groupNoBarrier()) entry.types[i] = TypeSet::ObjectType(group); @@ -1456,8 +1456,8 @@ ObjectGroupCompartment::sweep(FreeOp* fop) if (allocationSiteTable) { for (AllocationSiteTable::Enum e(*allocationSiteTable); !e.empty(); e.popFront()) { AllocationSiteKey key = e.front().key(); - bool keyDying = IsScriptAboutToBeFinalizedFromAnyThread(&key.script); - bool valDying = IsObjectGroupAboutToBeFinalizedFromAnyThread(e.front().value().unsafeGet()); + bool keyDying = IsScriptAboutToBeFinalized(&key.script); + bool valDying = IsObjectGroupAboutToBeFinalized(e.front().value().unsafeGet()); if (keyDying || valDying) e.removeFront(); else if (key.script != e.front().key().script) @@ -1475,8 +1475,8 @@ ObjectGroupCompartment::sweepNewTable(NewTable* table) if (table && table->initialized()) { for (NewTable::Enum e(*table); !e.empty(); e.popFront()) { NewEntry entry = e.front(); - if (IsObjectGroupAboutToBeFinalizedFromAnyThread(entry.group.unsafeGet()) || - (entry.associated && IsObjectAboutToBeFinalizedFromAnyThread(&entry.associated))) + if (IsObjectGroupAboutToBeFinalized(entry.group.unsafeGet()) || + (entry.associated && IsObjectAboutToBeFinalized(&entry.associated))) { e.removeFront(); } else { diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 47d466f4c8..b87ab69399 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -920,11 +920,11 @@ RegExpCompartment::sweep(JSRuntime* rt) // the RegExpShared if it was accidentally marked earlier but wasn't // marked by the current trace. bool keep = shared->marked() && - IsStringMarkedFromAnyThread(&shared->source); + IsStringMarked(&shared->source); for (size_t i = 0; i < ArrayLength(shared->compilationArray); i++) { RegExpShared::RegExpCompilation& compilation = shared->compilationArray[i]; if (compilation.jitCode && - IsJitCodeAboutToBeFinalizedFromAnyThread(compilation.jitCode.unsafeGet())) + IsJitCodeAboutToBeFinalized(compilation.jitCode.unsafeGet())) { keep = false; } @@ -938,7 +938,7 @@ RegExpCompartment::sweep(JSRuntime* rt) } if (matchResultTemplateObject_ && - IsObjectAboutToBeFinalizedFromAnyThread(matchResultTemplateObject_.unsafeGet())) + IsObjectAboutToBeFinalized(matchResultTemplateObject_.unsafeGet())) { matchResultTemplateObject_.set(nullptr); } diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 516c4bc416..4c571d8698 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -292,7 +292,7 @@ JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes) if (!atomsZone->compartments.append(atomsCompartment.get())) return false; - atomsCompartment->isSystem = true; + atomsCompartment->setIsSystem(true); atomsZone.forget(); this->atomsCompartment_ = atomsCompartment.forget(); @@ -848,3 +848,93 @@ JS::IsProfilingEnabledForRuntime(JSRuntime *runtime) MOZ_ASSERT(runtime); return runtime->spsProfiler.enabled(); } + +void +js::ResetStopwatches(JSRuntime *rt) +{ + MOZ_ASSERT(rt); + rt->stopwatch.reset(); +} + +bool +js::SetStopwatchActive(JSRuntime *rt, bool isActive) +{ + MOZ_ASSERT(rt); + return rt->stopwatch.setIsActive(isActive); +} + +bool +js::IsStopwatchActive(JSRuntime *rt) +{ + MOZ_ASSERT(rt); + return rt->stopwatch.isActive(); +} + +js::PerformanceGroupHolder::~PerformanceGroupHolder() +{ + unlink(); +} + +void* +js::PerformanceGroupHolder::getHashKey() +{ + return compartment_->isSystem() ? + (void*)compartment_->addonId : + (void*)JS_GetCompartmentPrincipals(compartment_); + // This key may be `nullptr` if we have `isSystem() == true` + // and `compartment_->addonId`. This is absolutely correct, + // and this represents the `PerformanceGroup` used to track + // the performance of the the platform compartments. +} + +void +js::PerformanceGroupHolder::unlink() +{ + if (!group_) { + // The group has never been instantiated. + return; + } + + js::PerformanceGroup* group = group_; + group_ = nullptr; + + if (group->decRefCount() > 0) { + // The group has at least another owner. + return; + } + + + JSRuntime::Stopwatch::Groups::Ptr ptr = + runtime_->stopwatch.groups_.lookup(getHashKey()); + MOZ_ASSERT(ptr); + runtime_->stopwatch.groups_.remove(ptr); + js_delete(group); +} + +PerformanceGroup * +js::PerformanceGroupHolder::getGroup() +{ + if (group_) + return group_; + + void* key = getHashKey(); + JSRuntime::Stopwatch::Groups::AddPtr ptr = + runtime_->stopwatch.groups_.lookupForAdd(key); + if (ptr) { + group_ = ptr->value(); + MOZ_ASSERT(group_); + } else { + group_ = runtime_->new_(); + runtime_->stopwatch.groups_.add(ptr, key, group_); + } + + group_->incRefCount(); + + return group_; +} + +PerformanceData* +js::GetPerformanceData(JSRuntime *rt) +{ + return &rt->stopwatch.performance; +} diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index b192b3ce13..e647c6eebf 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -576,6 +576,7 @@ class PerThreadData : public PerThreadDataFriendFields class AutoLockForExclusiveAccess; +struct AutoStopwatch; } // namespace js struct JSRuntime : public JS::shadow::Runtime, @@ -1440,6 +1441,113 @@ struct JSRuntime : public JS::shadow::Runtime, /* Last time at which an animation was played for this runtime. */ int64_t lastAnimationTime; + + public: + + /* ------------------------------------------ + Performance measurements + ------------------------------------------ */ + struct Stopwatch { + /** + * The number of times we have entered the event loop. + * Used to reset counters whenever we enter the loop, + * which may be caused either by having completed the + * previous run of the event loop, or by entering a + * nested loop. + * + * Always incremented by 1, may safely overflow. + */ + uint64_t iteration; + + /** + * `true` if no stopwatch has been registered for the + * current run of the event loop, `false` until then. + */ + bool isEmpty; + + /** + * Performance data on the entire runtime. + */ + js::PerformanceData performance; + + Stopwatch() + : iteration(0) + , isEmpty(true) + , isActive_(false) + { } + + /** + * Reset the stopwatch. + * + * This method is meant to be called whenever we start processing + * an event, to ensure that stop any ongoing measurement that would + * otherwise provide irrelevant results. + */ + void reset() { + ++iteration; + isEmpty = true; + } + + /** + * Activate/deactivate stopwatch measurement. + * + * Noop if `value` is `true` and the stopwatch is already active, + * or if `value` is `false` and the stopwatch is already inactive. + * + * Otherwise, any pending measurements are dropped, but previous + * measurements remain stored. + * + * May return `false` if the underlying hashtable cannot be allocated. + */ + bool setIsActive(bool value) { + if (isActive_ != value) + reset(); + + if (value && !groups_.initialized()) { + if (!groups_.init(128)) + return false; + } + + isActive_ = value; + return true; + } + + /** + * `true` if the stopwatch is currently monitoring, `false` otherwise. + */ + bool isActive() const { + return isActive_; + } + + private: + /** + * A map used to collapse compartments belonging to the same + * add-on (respectively to the same webpage, to the platform) + * into a single group. + * + * Keys: for system compartments, a `JSAddonId*` (which may be + * `nullptr`), and for webpages, a `JSPrincipals*` (which may + * not). Note that compartments may start as non-system + * compartments and become compartments later during their + * lifetime, which requires an invalidation. + * + * This map is meant to be accessed only by instances of + * PerformanceGroupHolder, which handle both reference-counting + * of the values and invalidation of the key/value pairs. + */ + typedef js::HashMap, + js::SystemAllocPolicy> Groups; + + Groups groups_; + friend struct js::PerformanceGroupHolder; + + /** + * `true` if stopwatch monitoring is active, `false` otherwise. + */ + bool isActive_; + }; + Stopwatch stopwatch; }; namespace js { diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index cbc91d1ccf..d2f1dc1a09 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -368,7 +368,7 @@ GetFirstSubsumedFrame(JSContext *cx, HandleSavedFrame frame, bool &skippedAsync) if (!subsumes) return frame; - JSPrincipals* principals = cx->compartment()->principals; + JSPrincipals* principals = cx->compartment()->principals(); RootedSavedFrame rootedFrame(cx, frame); while (rootedFrame && !subsumes(principals, rootedFrame->getPrincipals())) { @@ -473,8 +473,8 @@ public: if (obj && cx->compartment() != obj->compartment()) { JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; - if (subsumes && subsumes(cx->compartment()->principals, - obj->compartment()->principals)) + if (subsumes && subsumes(cx->compartment()->principals(), + obj->compartment()->principals())) { ac_.emplace(cx, obj); } @@ -631,48 +631,56 @@ GetSavedFrameParent(JSContext *cx, HandleObject savedFrame, MutableHandleObject JS_PUBLIC_API(bool) StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString stringp) { - AutoMaybeEnterFrameCompartment ac(cx, stack); - bool skippedAsync; - js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack, skippedAsync)); - if (!frame) { - stringp.set(cx->runtime()->emptyString); - return true; - } - js::StringBuffer sb(cx); - DebugOnly subsumes = cx->runtime()->securityCallbacks->subsumes; - DebugOnly principals = cx->compartment()->principals; - js::RootedSavedFrame parent(cx); - do { - MOZ_ASSERT_IF(subsumes, (*subsumes)(principals, frame->getPrincipals())); - if (!frame->isSelfHosted()) { - RootedString asyncCause(cx, frame->getAsyncCause()); - if (!asyncCause && skippedAsync) { - asyncCause.set(cx->names().Async); - } - js::RootedAtom name(cx, frame->getFunctionDisplayName()); - if ((asyncCause && (!sb.append(asyncCause) || !sb.append('*'))) - || (name && !sb.append(name)) - || !sb.append('@') - || !sb.append(frame->getSource()) - || !sb.append(':') - || !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb) - || !sb.append(':') - || !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb) - || !sb.append('\n')) - { - return false; - } + // Enter a new block to constrain the scope of possibly entering the stack's + // compartment. This ensures that when we finish the StringBuffer, we are + // back in the cx's original compartment, and fulfill our contract with + // callers to place the output string in the cx's current compartment. + { + AutoMaybeEnterFrameCompartment ac(cx, stack); + bool skippedAsync; + js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack, skippedAsync)); + if (!frame) { + stringp.set(cx->runtime()->emptyString); + return true; } + DebugOnly subsumes = cx->runtime()->securityCallbacks->subsumes; + DebugOnly principals = cx->compartment()->principals(); - parent = frame->getParent(); - frame = js::GetFirstSubsumedFrame(cx, parent, skippedAsync); - } while (frame); + js::RootedSavedFrame parent(cx); + do { + MOZ_ASSERT_IF(subsumes, (*subsumes)(principals, frame->getPrincipals())); + + if (!frame->isSelfHosted()) { + RootedString asyncCause(cx, frame->getAsyncCause()); + if (!asyncCause && skippedAsync) + asyncCause.set(cx->names().Async); + + js::RootedAtom name(cx, frame->getFunctionDisplayName()); + if ((asyncCause && (!sb.append(asyncCause) || !sb.append('*'))) + || (name && !sb.append(name)) + || !sb.append('@') + || !sb.append(frame->getSource()) + || !sb.append(':') + || !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb) + || !sb.append(':') + || !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb) + || !sb.append('\n')) + { + return false; + } + } + + parent = frame->getParent(); + frame = js::GetFirstSubsumedFrame(cx, parent, skippedAsync); + } while (frame); + } JSString* str = sb.finishString(); if (!str) return false; + assertSameCompartment(cx, str); stringp.set(str); return true; } @@ -801,7 +809,7 @@ SavedStacks::sweep(JSRuntime* rt) JSObject* obj = e.front().unbarrieredGet(); JSObject* temp = obj; - if (IsObjectAboutToBeFinalizedFromAnyThread(&obj)) { + if (IsObjectAboutToBeFinalized(&obj)) { e.removeFront(); } else { SavedFrame* frame = &obj->as(); @@ -918,7 +926,7 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram iter.isNonEvalFunctionFrame() ? iter.functionDisplayAtom() : nullptr, nullptr, nullptr, - iter.compartment()->principals + iter.compartment()->principals() ); ++iter; @@ -1058,7 +1066,7 @@ SavedStacks::sweepPCLocationMap() for (PCLocationMap::Enum e(pcLocationMap); !e.empty(); e.popFront()) { PCKey key = e.front().key(); JSScript* script = key.script.get(); - if (IsScriptAboutToBeFinalizedFromAnyThread(&script)) { + if (IsScriptAboutToBeFinalized(&script)) { e.removeFront(); } else if (script != key.script.get()) { key.script = script; diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index f2213c1f47..f5ec736e7f 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -1226,7 +1226,7 @@ void LiveScopeVal::sweep() { if (staticScope_) - MOZ_ALWAYS_FALSE(IsObjectAboutToBeFinalizedFromAnyThread(staticScope_.unsafeGet())); + MOZ_ALWAYS_FALSE(IsObjectAboutToBeFinalized(staticScope_.unsafeGet())); } // Live ScopeIter values may be added to DebugScopes::liveScopes, as @@ -1933,7 +1933,7 @@ DebugScopes::sweep(JSRuntime* rt) */ for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) { DebugScopeObject** debugScope = e.front().value().unsafeGet(); - if (IsObjectAboutToBeFinalizedFromAnyThread(debugScope)) { + if (IsObjectAboutToBeFinalized(debugScope)) { /* * Note that onPopCall and onPopBlock rely on missingScopes to find * scope objects that we synthesized for the debugger's sake, and @@ -1971,7 +1971,7 @@ DebugScopes::sweep(JSRuntime* rt) * Scopes can be finalized when a debugger-synthesized ScopeObject is * no longer reachable via its DebugScopeObject. */ - if (IsObjectAboutToBeFinalizedFromAnyThread(&scope)) + if (IsObjectAboutToBeFinalized(&scope)) e.removeFront(); else if (scope != e.front().key()) e.rekeyFront(scope); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 99bf84c96c..919fa4638d 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1095,7 +1095,7 @@ JSRuntime::createSelfHostingGlobal(JSContext* cx) cx->runtime()->selfHostingGlobal_ = shg; compartment->isSelfHosting = true; - compartment->isSystem = true; + compartment->setIsSystem(true); if (!GlobalObject::initSelfHostingBuiltins(cx, shg, intrinsic_functions)) return nullptr; diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp index 6ec35e4f32..8547c840cf 100644 --- a/js/src/vm/Shape.cpp +++ b/js/src/vm/Shape.cpp @@ -1253,7 +1253,7 @@ JSCompartment::sweepBaseShapeTable() for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) { UnownedBaseShape *base = e.front().unbarrieredGet(); - if (IsBaseShapeAboutToBeFinalizedFromAnyThread(&base)) { + if (IsBaseShapeAboutToBeFinalized(&base)) { e.removeFront(); } else if (base != e.front().unbarrieredGet()) { ReadBarriered b(base); @@ -1536,8 +1536,8 @@ JSCompartment::sweepInitialShapeTable() const InitialShapeEntry& entry = e.front(); Shape* shape = entry.shape.unbarrieredGet(); JSObject* proto = entry.proto.raw(); - if (IsShapeAboutToBeFinalizedFromAnyThread(&shape) || - (entry.proto.isObject() && IsObjectAboutToBeFinalizedFromAnyThread(&proto))) + if (IsShapeAboutToBeFinalized(&shape) || + (entry.proto.isObject() && IsObjectAboutToBeFinalized(&proto))) { e.removeFront(); } else { diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index 6126b90a66..f63201da03 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -252,7 +252,7 @@ uint8_t* InterpreterStack::allocateFrame(JSContext* cx, size_t size) { size_t maxFrames; - if (cx->compartment()->principals == cx->runtime()->trustedPrincipals()) + if (cx->compartment()->principals() == cx->runtime()->trustedPrincipals()) maxFrames = MAX_FRAMES_TRUSTED; else maxFrames = MAX_FRAMES; diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 9971fdd9bb..a1c8a6ab68 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -530,7 +530,7 @@ FrameIter::settleOnActivation() if (data_.principals_) { JSContext* cx = data_.cx_->asJSContext(); if (JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes) { - if (!subsumes(data_.principals_, activation->compartment()->principals)) { + if (!subsumes(data_.principals_, activation->compartment()->principals())) { ++data_.activations_; continue; } diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index dae25a8700..9cbc5b3001 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -704,16 +704,16 @@ TypeSet::readBarrier(const TypeSet* types) } /* static */ bool -TypeSet::IsTypeMarkedFromAnyThread(TypeSet::Type *v) +TypeSet::IsTypeMarked(TypeSet::Type* v) { bool rv; if (v->isSingletonUnchecked()) { JSObject *obj = v->singleton(); - rv = IsObjectMarkedFromAnyThread(&obj); + rv = IsObjectMarked(&obj); *v = TypeSet::ObjectType(obj); } else if (v->isGroupUnchecked()) { ObjectGroup *group = v->group(); - rv = IsObjectGroupMarkedFromAnyThread(&group); + rv = IsObjectGroupMarked(&group); *v = TypeSet::ObjectType(group); } else { rv = true; diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index 01a684f034..7c3a148176 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -530,7 +530,7 @@ class TypeSet static void MarkTypeRoot(JSTracer *trc, Type *v, const char *name); static void MarkTypeUnbarriered(JSTracer *trc, Type *v, const char *name); - static bool IsTypeMarkedFromAnyThread(Type *v); + static bool IsTypeMarked(Type *v); static bool IsTypeAllocatedDuringIncremental(Type v); static bool IsTypeAboutToBeFinalized(Type *v); }; diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp index 37d77abbe4..45f6ec0207 100644 --- a/js/xpconnect/src/nsXPConnect.cpp +++ b/js/xpconnect/src/nsXPConnect.cpp @@ -348,13 +348,6 @@ xpc::TraceXPCGlobal(JSTracer* trc, JSObject* obj) namespace xpc { -uint64_t -GetCompartmentCPOWMicroseconds(JSCompartment* compartment) -{ - xpc::CompartmentPrivate* compartmentPrivate = xpc::CompartmentPrivate::Get(compartment); - return compartmentPrivate ? PR_IntervalToMicroseconds(compartmentPrivate->CPOWTime) : 0; -} - JSObject* CreateGlobalObject(JSContext* cx, const JSClass* clasp, nsIPrincipal* principal, JS::CompartmentOptions& aOptions) diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h index ea1d10f98d..b57a309e97 100644 --- a/js/xpconnect/src/xpcprivate.h +++ b/js/xpconnect/src/xpcprivate.h @@ -633,6 +633,7 @@ public: void OnProcessNextEvent() { mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes(); mSlowScriptSecondHalf = false; + js::ResetStopwatches(Get()->Runtime()); } void OnAfterProcessNextEvent() { mSlowScriptCheckpoint = mozilla::TimeStamp(); @@ -3653,7 +3654,6 @@ public: , skipWriteToGlobalPrototype(false) , universalXPConnectEnabled(false) , forcePermissiveCOWs(false) - , CPOWTime(0) , skipCOWCallableChecks(false) , scriptability(c) , scope(nullptr) @@ -3707,9 +3707,6 @@ public: // Using it in production is inherently unsafe. bool forcePermissiveCOWs; - // A running count of how much time we've spent processing CPOWs. - PRIntervalTime CPOWTime; - // Disables the XPConnect security checks that deny access to callables and // accessor descriptors on COWs. Do not use this unless you are bholley. bool skipCOWCallableChecks; diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 848312a7e6..07edf2cf57 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -409,6 +409,10 @@ AddAnimationsForProperty(nsIFrame* aFrame, nsCSSProperty aProperty, Layer* aLayer, AnimationData& aData, bool aPending) { + MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty, + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR), + "inconsistent property flags"); + for (size_t playerIdx = 0; playerIdx < aPlayers.Length(); playerIdx++) { AnimationPlayer* player = aPlayers[playerIdx]; if (!player->IsPlaying()) { @@ -462,6 +466,10 @@ nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(Layer* aLayer, nsIFrame* aFrame, nsCSSProperty aProperty) { + MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty, + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR), + "inconsistent property flags"); + // This function can be called in two ways: from // nsDisplay*::BuildLayer while constructing a layer (with all // pointers non-null), or from RestyleManager's handling of diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index c00eb1c504..5cfd95f651 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -66,6 +66,7 @@ #include "CounterStyleManager.h" #include "FrameLayerBuilder.h" #include "mozilla/dom/RequestSyncWifiService.h" +#include "AnimationCommon.h" #include "AudioChannelService.h" #include "mozilla/dom/DataStoreService.h" diff --git a/layout/style/AnimationCommon.cpp b/layout/style/AnimationCommon.cpp index 8d1c049801..70e5705dc4 100644 --- a/layout/style/AnimationCommon.cpp +++ b/layout/style/AnimationCommon.cpp @@ -416,6 +416,42 @@ CommonAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement, nsDisplayItem::TYPE_OPACITY, nsChangeHint_UpdateOpacityLayer } }; +#ifdef DEBUG +/* static */ void +CommonAnimationManager::Initialize() +{ + const auto& info = css::CommonAnimationManager::sLayerAnimationInfo; + for (size_t i = 0; i < ArrayLength(info); i++) { + auto record = info[i]; + MOZ_ASSERT(nsCSSProps::PropHasFlags(record.mProperty, + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR), + "CSS property with entry in sLayerAnimationInfo does not " + "have the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR flag"); + } + + // Check that every property with the flag for animating on the + // compositor has an entry in sLayerAnimationInfo. + for (nsCSSProperty prop = nsCSSProperty(0); + prop < eCSSProperty_COUNT; + prop = nsCSSProperty(prop + 1)) { + if (nsCSSProps::PropHasFlags(prop, + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR)) { + bool found = false; + for (size_t i = 0; i < ArrayLength(info); i++) { + auto record = info[i]; + if (record.mProperty == prop) { + found = true; + break; + } + } + MOZ_ASSERT(found, + "CSS property with the CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR " + "flag does not have an entry in sLayerAnimationInfo"); + } + } +} +#endif + NS_IMPL_ISUPPORTS(AnimValuesStyleRule, nsIStyleRule) /* virtual */ void diff --git a/layout/style/AnimationCommon.h b/layout/style/AnimationCommon.h index af8f6b3b0d..7afc1ef6c8 100644 --- a/layout/style/AnimationCommon.h +++ b/layout/style/AnimationCommon.h @@ -73,6 +73,10 @@ public: virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const MOZ_MUST_OVERRIDE override; +#ifdef DEBUG + static void Initialize(); +#endif + /** * Notify the manager that the pres context is going away. */ diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index aacddf16a2..98a2761bd6 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -2660,7 +2660,8 @@ CSS_PROP_DISPLAY( opacity, Opacity, CSS_PROPERTY_PARSE_VALUE | - CSS_PROPERTY_APPLIES_TO_PLACEHOLDER, + CSS_PROPERTY_APPLIES_TO_PLACEHOLDER | + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR, "", VARIANT_HN, nullptr, @@ -3320,7 +3321,8 @@ CSS_PROP_POSITION( Transform, CSS_PROPERTY_PARSE_FUNCTION | CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH | - CSS_PROPERTY_CREATES_STACKING_CONTEXT, + CSS_PROPERTY_CREATES_STACKING_CONTEXT | + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR, "", 0, nullptr, diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h index bb15112db8..ad174a8f21 100644 --- a/layout/style/nsCSSProps.h +++ b/layout/style/nsCSSProps.h @@ -232,6 +232,9 @@ static_assert((CSS_PROPERTY_PARSE_PROPERTY_MASK & // margin-block-start or margin-inline-start). #define CSS_PROPERTY_LOGICAL_END_EDGE (1<<26) +// This property can be animated on the compositor. +#define CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR (1<<27) + /** * Types of animatable values. */ diff --git a/layout/style/test/test_animations_omta.html b/layout/style/test/test_animations_omta.html index f8b8342d56..fe11d601e3 100644 --- a/layout/style/test/test_animations_omta.html +++ b/layout/style/test/test_animations_omta.html @@ -2101,5 +2101,110 @@ addAsyncAnimTest(function *() { done_div(); }); +// Bug 847287 - Test that changes of when an animation is dynamically +// overridden work correctly. +addAsyncAnimTest(function *() { + // anim2 and anim3 are both animations from opacity 0 to 1 + + new_div("animation: anim2 1s linear forwards; opacity: 0.5 ! important"); + yield waitForPaintsFlushed(); + omta_todo_is("opacity", 0.5, RunningOn.TodoMainThread, + "opacity overriding animation at start (0s)"); + advance_clock(750); + omta_todo_is("opacity", 0.5, RunningOn.TodoMainThread, + "opacity overriding animation while running (750ms)"); + advance_clock(1000); + omta_todo_is("opacity", 0.5, RunningOn.TodoMainThread, + "opacity overriding animation while filling (1750ms)"); + done_div(); + + new_div("animation: anim2 1s linear; opacity: 0.5 ! important"); + yield waitForPaintsFlushed(); + omta_todo_is("opacity", 0.5, RunningOn.TodoMainThread, + "opacity overriding animation at start (0s)"); + advance_clock(750); + omta_todo_is("opacity", 0.5, RunningOn.TodoMainThread, + "opacity overriding animation while running (750ms)"); + advance_clock(1000); + omta_todo_is("opacity", 0.5, RunningOn.TodoMainThread, + "opacity overriding animation after complete (1750ms)"); + done_div(); + + // One animation overriding another, and then not. + new_div("animation: anim2 1s linear, anim3 500ms linear reverse"); + yield waitForPaintsFlushed(); + omta_todo_is("opacity", 1, RunningOn.Compositor, + "anim3 overriding anim2 at start (0s)"); + advance_clock(400); + omta_todo_is("opacity", 0.2, RunningOn.Compositor, + "anim3 overriding anim2 at 400ms"); + advance_clock(200); + // Wait for paints because we're resending animations to the + // compositor via an UpdateOpacityLayer hint, which does the resending + // via painting. + yield waitForPaints(); + omta_is("opacity", 0.6, RunningOn.Compositor, + "anim2 at 600ms"); + done_div(); + + // One animation overriding another, and then not, but without a + // restyle when the overriding one ends. + new_div("animation: anim2 1s steps(8, end)"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "anim2 at start (0s)"); + advance_clock(300); + omta_is("opacity", 0.25, RunningOn.Compositor, + "anim2 at 300ms"); + gDiv.style.animation = "anim2 1s steps(8, end), anim3 500ms steps(4, end)"; + yield waitForPaintsFlushed(); + omta_todo_is("opacity", 0, RunningOn.Compositor, + "anim3 overriding anim2 at 300ms"); + advance_clock(475); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim3 the same as anim2 at 775ms"); + advance_clock(50); + // Wait for paints because we're resending animations to the + // compositor via an UpdateOpacityLayer hint, which does the resending + // via painting. + yield waitForPaints(); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim2 at 825ms"); + advance_clock(75); + omta_is("opacity", 0.875, RunningOn.Compositor, + "anim2 at 900ms"); + done_div(); + + // Exactly the same as the previous test, except with an extra + // waitForPaintsFlushed(), since that extra one exposes other bugs. + new_div("animation: anim2 1s steps(8, end)"); + yield waitForPaintsFlushed(); + omta_is("opacity", 0, RunningOn.Compositor, + "anim2 at start (0s)"); + advance_clock(300); + omta_is("opacity", 0.25, RunningOn.Compositor, + "anim2 at 300ms"); + gDiv.style.animation = "anim2 1s steps(8, end), anim3 500ms steps(4, end)"; + yield waitForPaintsFlushed(); + omta_todo_is("opacity", 0, RunningOn.Compositor, + "anim3 overriding anim2 at 300ms"); + advance_clock(475); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim3 the same as anim2 at 775ms"); + // Extra waitForPaintsFlushed to expose bugs. + yield waitForPaintsFlushed(); + advance_clock(50); + // Wait for paints because we're resending animations to the + // compositor via an UpdateOpacityLayer hint, which does the resending + // via painting. + yield waitForPaints(); + omta_is("opacity", 0.75, RunningOn.Compositor, + "anim2 at 825ms"); + advance_clock(75); + omta_is("opacity", 0.875, RunningOn.Compositor, + "anim2 at 900ms"); + done_div(); +}); + diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index f5558e7a10..526d67e3e8 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -292,6 +292,8 @@ pref("media.wakelock_timeout", 2000); // opened as top-level documents, as opposed to inside a media element. pref("media.play-stand-alone", true); +pref("media.hardware-video-decoding.enabled", true); + // Whether we should delay actioning a "play()" JS function call and autoplay // attribute until the media element's owner document is visible. pref("media.block-play-until-visible", false); @@ -331,7 +333,6 @@ pref("media.fragmented-mp4.enabled", true); pref("media.use-blank-decoder", false); #ifdef MOZ_WMF pref("media.windows-media-foundation.enabled", true); -pref("media.windows-media-foundation.use-dxva", true); #endif #if defined(MOZ_FFMPEG) pref("media.ffmpeg.enabled", true); diff --git a/netwerk/base/nsIBrowserSearchService.idl b/netwerk/base/nsIBrowserSearchService.idl index 201999662b..d13f870d1b 100644 --- a/netwerk/base/nsIBrowserSearchService.idl +++ b/netwerk/base/nsIBrowserSearchService.idl @@ -253,7 +253,7 @@ interface nsIBrowserSearchInitObserver : nsISupports void onInitComplete(in nsresult aStatus); }; -[scriptable, uuid(4a4ce87d-7cb9-4975-a267-345f6a49bb8f)] +[scriptable, uuid(75731859-c7b1-4edf-8d1c-3d4d79a55d1d)] interface nsIBrowserSearchService : nsISupports { /** @@ -281,6 +281,11 @@ interface nsIBrowserSearchService : nsISupports */ readonly attribute bool isInitialized; + /** + * Resets the default engine to its original value. + */ + void resetToOriginalDefaultEngine(); + /** * Adds a new search engine from the file at the supplied URI, optionally * asking the user for confirmation first. If a confirmation dialog is diff --git a/services/healthreport/docs/dataformat.rst b/services/healthreport/docs/dataformat.rst index 9cadf0c9a8..730cdbf3a9 100644 --- a/services/healthreport/docs/dataformat.rst +++ b/services/healthreport/docs/dataformat.rst @@ -128,7 +128,7 @@ Leading by example:: "isTelemetryEnabled": 1, "isBlocklistEnabled": 1 }, - "goannaAppInfo": { + "geckoAppInfo": { "updateChannel": "nightly", "id": "{aa3c5121-dab2-40e2-81ca-7ea25febc110}", "os": "Android", @@ -150,7 +150,7 @@ Leading by example:: } }, "k2O3hlreMeS7L1qtxeMsYWxgWWQ=": { - "goannaAppInfo": { + "geckoAppInfo": { "platformBuildID": "20130630031138", "appBuildID": "20130630031138", "_v": 1 @@ -160,7 +160,7 @@ Leading by example:: } }, "1+KN9TutMpzdl4TJEl+aCxK+xcw=": { - "goannaAppInfo": { + "geckoAppInfo": { "platformBuildID": "20130626031100", "appBuildID": "20130626031100", "_v": 1 @@ -353,9 +353,9 @@ Version 2 ========= Version 2 is the same as version 1 with the exception that it has an additional -top-level field, *goannaAppInfo*, which contains basic application info. +top-level field, *geckoAppInfo*, which contains basic application info. -goannaAppInfo +geckoAppInfo ------------ This field is an object that is a simple map of string keys and values diff --git a/services/healthreport/healthreporter.jsm b/services/healthreport/healthreporter.jsm index fa55dd24c5..286e3494ca 100644 --- a/services/healthreport/healthreporter.jsm +++ b/services/healthreport/healthreporter.jsm @@ -943,7 +943,7 @@ AbstractHealthReporter.prototype = Object.freeze({ clientID: this._state.clientID, clientIDVersion: this._state.clientIDVersion, thisPingDate: pingDateString, - goannaAppInfo: this.obtainAppInfo(this._log), + geckoAppInfo: this.obtainAppInfo(this._log), data: {last: {}, days: {}}, }; diff --git a/services/healthreport/tests/xpcshell/test_healthreporter.js b/services/healthreport/tests/xpcshell/test_healthreporter.js index f18563e48a..835730a4c8 100644 --- a/services/healthreport/tests/xpcshell/test_healthreporter.js +++ b/services/healthreport/tests/xpcshell/test_healthreporter.js @@ -881,7 +881,7 @@ add_task(function test_basic_appinfo() { verify(reporter.obtainAppInfo()); let payload = yield reporter.collectAndObtainJSONPayload(true); do_check_eq(payload["version"], 2); - verify(payload["goannaAppInfo"]); + verify(payload["geckoAppInfo"]); } finally { yield reporter._shutdown(); } diff --git a/toolkit/components/aboutcompartments/content/aboutCompartments.js b/toolkit/components/aboutcompartments/content/aboutCompartments.js index fbcac91bc9..e69de29bb2 100644 --- a/toolkit/components/aboutcompartments/content/aboutCompartments.js +++ b/toolkit/components/aboutcompartments/content/aboutCompartments.js @@ -1,75 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const { classes: Cc, interfaces: Ci, utils: Cu } = Components; - -const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); - -function go() { - let compartmentInfo = Cc["@mozilla.org/compartment-info;1"] - .getService(Ci.nsICompartmentInfo); - let compartments = compartmentInfo.getCompartments(); - let count = compartments.length; - let addons = {}; - for (let i = 0; i < count; i++) { - let compartment = compartments.queryElementAt(i, Ci.nsICompartment); - if (addons[compartment.addonId]) { - addons[compartment.addonId].time += compartment.time; - addons[compartment.addonId].CPOWTime += compartment.CPOWTime; - addons[compartment.addonId].compartments.push(compartment); - } else { - addons[compartment.addonId] = { - time: compartment.time, - CPOWTime: compartment.CPOWTime, - compartments: [compartment] - }; - } - } - let dataDiv = document.getElementById("data"); - for (let addon in addons) { - let el = document.createElement("tr"); - let name = document.createElement("td"); - let time = document.createElement("td"); - let cpow = document.createElement("td"); - name.className = "addon"; - time.className = "time"; - cpow.className = "cpow"; - name.textContent = addon; - AddonManager.getAddonByID(addon, function(a) { - if (a) { - name.textContent = a.name; - } - }); - time.textContent = addons[addon].time +"μs"; - cpow.textContent = addons[addon].CPOWTime +"μs"; - el.appendChild(time); - el.appendChild(cpow); - el.appendChild(name); - let div = document.createElement("tr"); - for (let comp of addons[addon].compartments) { - let c = document.createElement("tr"); - let name = document.createElement("td"); - let time = document.createElement("td"); - let cpow = document.createElement("td"); - name.className = "addon"; - time.className = "time"; - cpow.className = "cpow"; - name.textContent = comp.compartmentName; - time.textContent = comp.time +"μs"; - cpow.textContent = comp.CPOWTime +"μs"; - c.appendChild(time); - c.appendChild(cpow); - c.appendChild(name); - div.appendChild(c); - div.className = "details"; - } - el.addEventListener("click", function() { div.style.display = (div.style.display != "block" ? "block" : "none"); }); - el.appendChild(div); - dataDiv.appendChild(el); - } -} diff --git a/toolkit/components/aboutcompartments/content/aboutCompartments.xhtml b/toolkit/components/aboutcompartments/content/aboutCompartments.xhtml index d3de58a178..e69de29bb2 100644 --- a/toolkit/components/aboutcompartments/content/aboutCompartments.xhtml +++ b/toolkit/components/aboutcompartments/content/aboutCompartments.xhtml @@ -1,43 +0,0 @@ - - - - - - - about:compartments - - - - - - - - - - -
timetime in CPOWsname
- - diff --git a/toolkit/components/aboutcompartments/nsCompartmentInfo.cpp b/toolkit/components/aboutcompartments/nsCompartmentInfo.cpp index 845fab8bf7..e69de29bb2 100644 --- a/toolkit/components/aboutcompartments/nsCompartmentInfo.cpp +++ b/toolkit/components/aboutcompartments/nsCompartmentInfo.cpp @@ -1,94 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsCompartmentInfo.h" -#include "nsMemory.h" -#include "nsLiteralString.h" -#include "nsCRTGlue.h" -#include "nsIJSRuntimeService.h" -#include "nsServiceManagerUtils.h" -#include "nsIMutableArray.h" -#include "nsJSUtils.h" -#include "xpcpublic.h" - -class nsCompartment : public nsICompartment { -public: - nsCompartment(nsAString& aCompartmentName, nsAString& aAddonId, - uint64_t aTime, uint64_t aCPOWTime) - : mCompartmentName(aCompartmentName), mAddonId(aAddonId), mTime(aTime), mCPOWTime(aCPOWTime) {} - - NS_DECL_ISUPPORTS - - /* readonly attribute wstring compartmentName; */ - NS_IMETHOD GetCompartmentName(nsAString& aCompartmentName) override { - aCompartmentName.Assign(mCompartmentName); - return NS_OK; - }; - - /* readonly attribute unsigned long time; */ - NS_IMETHOD GetTime(uint64_t* aTime) override { - *aTime = mTime; - return NS_OK; - } - /* readonly attribute wstring addon id; */ - NS_IMETHOD GetAddonId(nsAString& aAddonId) override { - aAddonId.Assign(mAddonId); - return NS_OK; - }; - - /* readonly attribute unsigned long CPOW time; */ - NS_IMETHOD GetCPOWTime(uint64_t* aCPOWTime) override { - *aCPOWTime = mCPOWTime; - return NS_OK; - } - -private: - nsString mCompartmentName; - nsString mAddonId; - uint64_t mTime; - uint64_t mCPOWTime; - virtual ~nsCompartment() {} -}; - -NS_IMPL_ISUPPORTS(nsCompartment, nsICompartment) -NS_IMPL_ISUPPORTS(nsCompartmentInfo, nsICompartmentInfo) - -nsCompartmentInfo::nsCompartmentInfo() -{ -} - -nsCompartmentInfo::~nsCompartmentInfo() -{ -} - -NS_IMETHODIMP -nsCompartmentInfo::GetCompartments(nsIArray** aCompartments) -{ - JSRuntime* rt; - nsCOMPtr svc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1")); - NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE); - svc->GetRuntime(&rt); - nsCOMPtr compartments = do_CreateInstance(NS_ARRAY_CONTRACTID); - CompartmentStatsVector stats; - if (!JS_GetCompartmentStats(rt, stats)) - return NS_ERROR_OUT_OF_MEMORY; - - size_t num = stats.length(); - for (size_t pos = 0; pos < num; pos++) { - nsString addonId; - if (stats[pos].addonId) { - AssignJSFlatString(addonId, (JSFlatString*)stats[pos].addonId); - } else { - addonId.AssignLiteral(""); - } - - uint32_t cpowTime = xpc::GetCompartmentCPOWMicroseconds(stats[pos].compartment); - nsCString compartmentName(stats[pos].compartmentName); - NS_ConvertUTF8toUTF16 name(compartmentName); - compartments->AppendElement(new nsCompartment(name, addonId, stats[pos].time, cpowTime), false); - } - compartments.forget(aCompartments); - return NS_OK; -} diff --git a/toolkit/components/aboutcompartments/nsICompartmentInfo.idl b/toolkit/components/aboutcompartments/nsICompartmentInfo.idl index 1e07c8ad29..e69de29bb2 100644 --- a/toolkit/components/aboutcompartments/nsICompartmentInfo.idl +++ b/toolkit/components/aboutcompartments/nsICompartmentInfo.idl @@ -1,31 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" -#include "nsIArray.idl" - -[scriptable, uuid(13dd4c09-ff11-4943-8dc2-d96eb69c963b)] -interface nsICompartment : nsISupports { - /* name of compartment */ - readonly attribute AString compartmentName; - /* time spent executing code in this compartment in microseconds */ - readonly attribute unsigned long long time; - /* the id of the addon associated with this compartment, or null */ - readonly attribute AString addonId; - /* time spent processing CPOWs in microseconds */ - readonly attribute unsigned long long CPOWTime; -}; - -[scriptable, builtinclass, uuid(5795113a-39a1-4087-ba09-98b7d07d025a)] -interface nsICompartmentInfo : nsISupports { - nsIArray getCompartments(); -}; - -%{C++ -#define NS_COMPARTMENT_INFO_CID \ -{ 0x2d3c2f2d, 0x698d, 0x471d, \ -{ 0xba, 0x3e, 0x14, 0x44, 0xdd, 0x52, 0x1e, 0x29 } } -%} diff --git a/toolkit/components/aboutperformance/content/aboutPerformance.js b/toolkit/components/aboutperformance/content/aboutPerformance.js new file mode 100644 index 0000000000..3dfebff76e --- /dev/null +++ b/toolkit/components/aboutperformance/content/aboutPerformance.js @@ -0,0 +1,157 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); +const { PerformanceStats } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {}); + +/** + * The various measures we display. + */ +const MEASURES = [ + {key: "longestDuration", percentOfDeltaT: false, label: "Jank level"}, + {key: "totalUserTime", percentOfDeltaT: true, label: "User (%)"}, + {key: "totalSystemTime", percentOfDeltaT: true, label: "System (%)"}, + {key: "totalCPOWTime", percentOfDeltaT: true, label: "Cross-Process (%)"}, + {key: "ticks", percentOfDeltaT: false, label: "Activations"}, +]; + +let State = { + /** + * @type{PerformanceData} + */ + _processData: null, + /** + * A mapping from name to PerformanceData + * + * @type{Map} + */ + _componentsData: new Map(), + + /** + * A number of milliseconds since the high-performance epoch. + */ + _date: window.performance.now(), + + /** + * Fetch the latest information, compute diffs. + * + * @return {object} An object with the following fields: + * - `components`: an array of `PerformanceDiff` representing + * the components, sorted by `longestDuration`, then by `totalUserTime` + * - `process`: a `PerformanceDiff` representing the entire process; + * - `deltaT`: the number of milliseconds elapsed since the data + * was last displayed. + */ + update: function() { + let snapshot = PerformanceStats.getSnapshot(); + let newData = new Map(); + let deltas = []; + for (let componentNew of snapshot.componentsData) { + let componentOld = State._componentsData.get(componentNew.name); + deltas.push(componentNew.substract(componentOld)); + newData.set(componentNew.name, componentNew); + } + State._componentsData = newData; + let now = window.performance.now(); + let result = { + components: deltas.filter(x => x.ticks > 0), + process: snapshot.processData.substract(State._processData), + deltaT: now - State._date + }; + result.components.sort((a, b) => { + if (a.longestDuration < b.longestDuration) { + return true; + } + if (a.longestDuration == b.longestDuration) { + return a.totalUserTime <= b.totalUserTime + } + return false; + }); + State._processData = snapshot.processData; + State._date = now; + return result; + } +}; + + +function update() { + try { + let dataElt = document.getElementById("data"); + dataElt.innerHTML = ""; + + // Generate table headers + let headerElt = document.createElement("tr"); + dataElt.appendChild(headerElt); + headerElt.classList.add("header"); + for (let column of [...MEASURES, {key: "name", name: ""}]) { + let el = document.createElement("td"); + el.classList.add(column.key); + el.textContent = column.label; + headerElt.appendChild(el); + } + + let deltas = State.update(); + + for (let item of deltas.components) { + let row = document.createElement("tr"); + if (item.addonId) { + row.classList.add("addon"); + } else if (item.isSystem) { + row.classList.add("platform"); + } else { + row.classList.add("content"); + } + dataElt.appendChild(row); + + // Measures + for (let {key, percentOfDeltaT} of MEASURES) { + let el = document.createElement("td"); + el.classList.add(key); + el.classList.add("contents"); + row.appendChild(el); + + let value = percentOfDeltaT ? Math.round(item[key] / deltas.deltaT) : item[key]; + if (key == "longestDuration") { + value += 1; + el.classList.add("jank" + value); + } + el.textContent = value; + } + + // Name + let el = document.createElement("td"); + let id = item.id; + el.classList.add("contents"); + el.classList.add("name"); + row.appendChild(el); + if (item.addonId) { + let _el = el; + let _item = item; + AddonManager.getAddonByID(item.addonId, a => { + _el.textContent = a?a.name:_item.name + }); + } else { + el.textContent = item.name; + } + } + } catch (ex) { + console.error(ex); + } +} + +function go() { + // Compute initial state immediately, then wait a little + // before we start computing diffs and refreshing. + State.update(); + + window.setTimeout(() => { + window.setInterval(update, 2000); + }, 1000); +} diff --git a/toolkit/components/aboutperformance/content/aboutPerformance.xhtml b/toolkit/components/aboutperformance/content/aboutPerformance.xhtml new file mode 100644 index 0000000000..943a2fcfae --- /dev/null +++ b/toolkit/components/aboutperformance/content/aboutPerformance.xhtml @@ -0,0 +1,80 @@ + + + + + + + about:performance + + + + + +
+ + diff --git a/toolkit/components/aboutcompartments/jar.mn b/toolkit/components/aboutperformance/jar.mn similarity index 54% rename from toolkit/components/aboutcompartments/jar.mn rename to toolkit/components/aboutperformance/jar.mn index ca1a3352c5..26d594b047 100644 --- a/toolkit/components/aboutcompartments/jar.mn +++ b/toolkit/components/aboutperformance/jar.mn @@ -3,5 +3,5 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. toolkit.jar: -+ content/global/aboutCompartments.xhtml (content/aboutCompartments.xhtml) -+ content/global/aboutCompartments.js (content/aboutCompartments.js) ++ content/global/aboutPerformance.xhtml (content/aboutPerformance.xhtml) ++ content/global/aboutPerformance.js (content/aboutPerformance.js) diff --git a/toolkit/components/aboutcompartments/moz.build b/toolkit/components/aboutperformance/moz.build similarity index 60% rename from toolkit/components/aboutcompartments/moz.build rename to toolkit/components/aboutperformance/moz.build index 9872f4f72d..fb0cac3b50 100644 --- a/toolkit/components/aboutcompartments/moz.build +++ b/toolkit/components/aboutperformance/moz.build @@ -4,20 +4,25 @@ # 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/. +FAIL_ON_WARNINGS = True + JAR_MANIFESTS += ['jar.mn'] -XPIDL_MODULE = 'compartments' +XPIDL_MODULE = 'toolkit_perfmonitoring' XPIDL_SOURCES += [ - 'nsICompartmentInfo.idl', + 'nsIPerformanceStats.idl', ] UNIFIED_SOURCES += [ - 'nsCompartmentInfo.cpp' + 'nsPerformanceStats.cpp' ] EXPORTS += [ - 'nsCompartmentInfo.h' + 'nsPerformanceStats.h' ] +BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini'] +XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini'] + FINAL_LIBRARY = 'xul' diff --git a/toolkit/components/aboutperformance/nsIPerformanceStats.idl b/toolkit/components/aboutperformance/nsIPerformanceStats.idl new file mode 100644 index 0000000000..72f105ea63 --- /dev/null +++ b/toolkit/components/aboutperformance/nsIPerformanceStats.idl @@ -0,0 +1,111 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIArray.idl" + +/** + * Mechanisms for querying the current process about performance + * information. + * + * JavaScript clients should rather use PerformanceStats.jsm. + */ + +/** + * Snapshot of the performance of a component, e.g. an add-on, a web + * page, system built-ins, or the entire process itself. + * + * All values are monotonic and are updated only when + * `nsIPerformanceStatsService.isStopwatchActive` is `true`. + */ +[scriptable, uuid(f015cbad-e16f-4982-a080-67e4e69a5b2e)] +interface nsIPerformanceStats: nsISupports { + /** + * The name of the component: + * - for the process itself, ""; + * - for platform code, ""; + * - for an add-on, the identifier of the addon (e.g. "myaddon@foo.bar"); + * - for a webpage, the url of the page. + */ + readonly attribute AString name; + + /** + * If the component is an add-on, the ID of the addon, + * otherwise an empty string. + */ + readonly attribute AString addonId; + + /** + * Total amount of time spent executing code in this group, in + * microseconds. + */ + readonly attribute unsigned long long totalUserTime; + readonly attribute unsigned long long totalSystemTime; + readonly attribute unsigned long long totalCPOWTime; + + /** + * Total number of times code execution entered this group, + * since process launch. This may be greater than the number + * of times we have entered the event loop. + */ + readonly attribute unsigned long long ticks; + + /** + * `true` if this component is executed with system privileges + * (e.g. the platform itself or an add-on), `false` otherwise + * (e.g. webpages). + */ + readonly attribute bool isSystem; + + /** + * Jank indicator. + * + * durations[i] == number of times execution of this group + * lasted at lest 2^i ms. + */ + void getDurations([optional] out unsigned long aCount, + [retval, array, size_is(aCount)]out unsigned long long aNumberOfOccurrences); +}; + +/** + * A snapshot of the performance data of the process. + */ +[scriptable, uuid(29ecebd0-908a-4b34-8f62-a6015dea1141)] +interface nsIPerformanceSnapshot: nsISupports { + /** + * Data on all individual components. + */ + nsIArray getComponentsData(); + + /** + * Information on the process itself. + * + * This contains the total amount of time spent executing JS code, + * the total amount of time spent waiting for system calls while + * executing JS code, the total amount of time performing blocking + * inter-process calls, etc. + */ + nsIPerformanceStats getProcessData(); +}; + +[scriptable, builtinclass, uuid(5795113a-39a1-4087-ba09-98b7d07d025a)] +interface nsIPerformanceStatsService : nsISupports { + /** + * `true` if we should monitor performance, `false` otherwise. + */ + [implicit_jscontext] attribute bool isStopwatchActive; + + /** + * Capture a snapshot of the performance data. + */ + nsIPerformanceSnapshot getSnapshot(); +}; + +%{C++ +#define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID {0xfd7435d4, 0x9ec4, 0x4699, \ + {0xad, 0xd4, 0x1b, 0xe8, 0x3d, 0xd6, 0x8e, 0xf3} } +#define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID "@mozilla.org/toolkit/performance-stats-service;1" +%} diff --git a/toolkit/components/aboutperformance/nsPerformanceStats.cpp b/toolkit/components/aboutperformance/nsPerformanceStats.cpp new file mode 100644 index 0000000000..544fe01210 --- /dev/null +++ b/toolkit/components/aboutperformance/nsPerformanceStats.cpp @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jsapi.h" +#include "nsPerformanceStats.h" +#include "nsMemory.h" +#include "nsLiteralString.h" +#include "nsCRTGlue.h" +#include "nsIJSRuntimeService.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMArray.h" +#include "nsIMutableArray.h" +#include "nsJSUtils.h" +#include "xpcpublic.h" +#include "jspubtd.h" + +class nsPerformanceStats: public nsIPerformanceStats { +public: + nsPerformanceStats(nsAString& aName, nsAString& aAddonId, bool aIsSystem, js::PerformanceData& aPerformanceData) + : mName(aName) + , mAddonId(aAddonId) + , mIsSystem(aIsSystem) + , mPerformanceData(aPerformanceData) + { + } + explicit nsPerformanceStats() {} + + NS_DECL_ISUPPORTS + + /* readonly attribute AString name; */ + NS_IMETHOD GetName(nsAString& aName) override { + aName.Assign(mName); + return NS_OK; + }; + + /* readonly attribute AString addon id; */ + NS_IMETHOD GetAddonId(nsAString& aAddonId) override { + aAddonId.Assign(mAddonId); + return NS_OK; + }; + + /* readonly attribute unsigned long long totalUserTime; */ + NS_IMETHOD GetTotalUserTime(uint64_t *aTotalUserTime) override { + *aTotalUserTime = mPerformanceData.totalUserTime; + return NS_OK; + }; + + /* readonly attribute unsigned long long totalSystemTime; */ + NS_IMETHOD GetTotalSystemTime(uint64_t *aTotalSystemTime) override { + *aTotalSystemTime = mPerformanceData.totalSystemTime; + return NS_OK; + }; + + /* readonly attribute unsigned long long totalCPOWTime; */ + NS_IMETHOD GetTotalCPOWTime(uint64_t *aCpowTime) override { + *aCpowTime = mPerformanceData.totalCPOWTime; + return NS_OK; + }; + + /* readonly attribute unsigned long long ticks; */ + NS_IMETHOD GetTicks(uint64_t *aTicks) override { + *aTicks = mPerformanceData.ticks; + return NS_OK; + }; + + /* void getDurations (out unsigned long aCount, [array, size_is (aCount), retval] out unsigned long long aNumberOfOccurrences); */ + NS_IMETHODIMP GetDurations(uint32_t *aCount, uint64_t **aNumberOfOccurrences) override { + const size_t length = mozilla::ArrayLength(mPerformanceData.durations); + if (aCount) { + *aCount = length; + } + *aNumberOfOccurrences = new uint64_t[length]; + for (size_t i = 0; i < length; ++i) { + (*aNumberOfOccurrences)[i] = mPerformanceData.durations[i]; + } + return NS_OK; + }; + + /* readonly attribute bool isSystem; */ + NS_IMETHOD GetIsSystem(bool *_retval) override { + *_retval = mIsSystem; + return NS_OK; + } + +private: + nsString mName; + nsString mAddonId; + bool mIsSystem; + js::PerformanceData mPerformanceData; + + virtual ~nsPerformanceStats() {} +}; + +NS_IMPL_ISUPPORTS(nsPerformanceStats, nsIPerformanceStats) + + +class nsPerformanceSnapshot : public nsIPerformanceSnapshot +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPERFORMANCESNAPSHOT + + nsPerformanceSnapshot(); + nsresult Init(); +private: + virtual ~nsPerformanceSnapshot(); + + /** + * Import a `PerformanceStats` as a `nsIPerformanceStats`. + */ + already_AddRefed ImportStats(js::PerformanceStats* c); + + nsCOMArray mComponentsData; + nsCOMPtr mProcessData; +}; + +NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot) + +nsPerformanceSnapshot::nsPerformanceSnapshot() +{ +} + +nsPerformanceSnapshot::~nsPerformanceSnapshot() +{ +} + +already_AddRefed +nsPerformanceSnapshot::ImportStats(js::PerformanceStats* c) { + nsString addonId; + if (c->addonId) { + AssignJSFlatString(addonId, (JSFlatString*)c->addonId); + } + nsCString cname(c->name); + NS_ConvertUTF8toUTF16 name(cname); + nsCOMPtr result = new nsPerformanceStats(name, addonId, c->isSystem, c->performance); + return result.forget(); +} + +nsresult +nsPerformanceSnapshot::Init() { + JSRuntime* rt; + nsCOMPtr svc(do_GetService("@mozilla.org/js/xpc/RuntimeService;1")); + NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE); + svc->GetRuntime(&rt); + js::PerformanceStats processStats; + js::PerformanceStatsVector componentsStats; + if (!js::GetPerformanceStats(rt, componentsStats, processStats)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + size_t num = componentsStats.length(); + for (size_t pos = 0; pos < num; pos++) { + nsCOMPtr stats = ImportStats(&componentsStats[pos]); + mComponentsData.AppendObject(stats); + } + mProcessData = ImportStats(&processStats); + return NS_OK; +} + + +/* void getComponentsData (out nsIArray aComponents); */ +NS_IMETHODIMP nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents) +{ + const size_t length = mComponentsData.Length(); + nsCOMPtr components = do_CreateInstance(NS_ARRAY_CONTRACTID); + for (size_t i = 0; i < length; ++i) { + nsCOMPtr stats = mComponentsData[i]; + mozilla::DebugOnly rv = components->AppendElement(stats, false); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + components.forget(aComponents); + return NS_OK; +} + +/* readonly attribute nsIPerformanceStats process; */ +NS_IMETHODIMP nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess) +{ + NS_IF_ADDREF(*aProcess = mProcessData); + return NS_OK; +} + + +NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService) + +nsPerformanceStatsService::nsPerformanceStatsService() +{ +} + +nsPerformanceStatsService::~nsPerformanceStatsService() +{ +} + +/* [implicit_jscontext] attribute bool isStopwatchActive; */ +NS_IMETHODIMP nsPerformanceStatsService::GetIsStopwatchActive(JSContext* cx, bool *aIsStopwatchActive) +{ + JSRuntime *runtime = JS_GetRuntime(cx); + *aIsStopwatchActive = js::IsStopwatchActive(runtime); + return NS_OK; +} +NS_IMETHODIMP nsPerformanceStatsService::SetIsStopwatchActive(JSContext* cx, bool aIsStopwatchActive) +{ + JSRuntime *runtime = JS_GetRuntime(cx); + if (!js::SetStopwatchActive(runtime, aIsStopwatchActive)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +/* readonly attribute nsIPerformanceSnapshot snapshot; */ +NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(nsIPerformanceSnapshot * *aSnapshot) +{ + nsRefPtr snapshot = new nsPerformanceSnapshot(); + nsresult rv = snapshot->Init(); + if (NS_FAILED(rv)) { + return rv; + } + + snapshot.forget(aSnapshot); + return NS_OK; +} + + diff --git a/toolkit/components/aboutcompartments/nsCompartmentInfo.h b/toolkit/components/aboutperformance/nsPerformanceStats.h similarity index 56% rename from toolkit/components/aboutcompartments/nsCompartmentInfo.h rename to toolkit/components/aboutperformance/nsPerformanceStats.h index 4efba6d92f..54e1a89bbb 100644 --- a/toolkit/components/aboutcompartments/nsCompartmentInfo.h +++ b/toolkit/components/aboutperformance/nsPerformanceStats.h @@ -3,21 +3,21 @@ * 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 nsCompartmentInfo_h -#define nsCompartmentInfo_h +#ifndef nsPerformanceStats_h +#define nsPerformanceStats_h -#include "nsICompartmentInfo.h" +#include "nsIPerformanceStats.h" -class nsCompartmentInfo : public nsICompartmentInfo +class nsPerformanceStatsService : public nsIPerformanceStatsService { public: NS_DECL_ISUPPORTS - NS_DECL_NSICOMPARTMENTINFO + NS_DECL_NSIPERFORMANCESTATSSERVICE - nsCompartmentInfo(); + nsPerformanceStatsService(); private: - virtual ~nsCompartmentInfo(); + virtual ~nsPerformanceStatsService(); protected: }; diff --git a/toolkit/components/aboutperformance/tests/browser/browser.ini b/toolkit/components/aboutperformance/tests/browser/browser.ini new file mode 100644 index 0000000000..b1a8805178 --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/browser.ini @@ -0,0 +1,12 @@ +# 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/. + +[DEFAULT] +head = head.js +support-files = + browser_compartments.html + +[browser_aboutperformance.js] +skip-if = e10s # Feature not implemented yet – bug 1140310 +[browser_compartments.js] diff --git a/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js b/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js new file mode 100644 index 0000000000..c4e57740fd --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource://testing-common/ContentTask.jsm", this); + +const URL = "http://example.com/browser/toolkit/components/aboutperformance/tests/browser/browser_compartments.html?test=" + Math.random(); + +// This function is injected as source as a frameScript +function frameScript() { + "use strict"; + + addMessageListener("aboutperformance-test:hasItems", ({data: url}) => { + let hasPlatform = false; + let hasURL = false; + + try { + let eltData = content.document.getElementById("data"); + if (!eltData) { + return; + } + + // Find if we have a row for "platform" + hasPlatform = eltData.querySelector("tr.platform") != null; + + // Find if we have a row for our URL + hasURL = false; + for (let eltContent of eltData.querySelectorAll("tr.content td.name")) { + if (eltContent.textContent == url) { + hasURL = true; + break; + } + } + + } catch (ex) { + Cu.reportError("Error in content: " + ex); + Cu.reportError(ex.stack); + } finally { + sendAsyncMessage("aboutperformance-test:hasItems", {hasPlatform, hasURL}); + } + }); +} + + +add_task(function* test() { + let tabAboutPerformance = gBrowser.addTab("about:performance"); + let tabContent = gBrowser.addTab(URL); + + yield ContentTask.spawn(tabAboutPerformance.linkedBrowser, null, frameScript); + + while (true) { + yield new Promise(resolve => setTimeout(resolve, 100)); + let {hasPlatform, hasURL} = (yield promiseContentResponse(tabAboutPerformance.linkedBrowser, "aboutperformance-test:hasItems", URL)); + info(`Platform: ${hasPlatform}, url: ${hasURL}`); + if (hasPlatform && hasURL) { + Assert.ok(true, "Found a row for and a row for our URL"); + break; + } + } + + // Cleanup + gBrowser.removeTab(tabContent); + gBrowser.removeTab(tabAboutPerformance); +}); diff --git a/toolkit/components/aboutperformance/tests/browser/browser_compartments.html b/toolkit/components/aboutperformance/tests/browser/browser_compartments.html new file mode 100644 index 0000000000..120aeff789 --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/browser_compartments.html @@ -0,0 +1,25 @@ + + + + + browser_compartments.html + + + + +browser_compartments.html + + diff --git a/toolkit/components/aboutperformance/tests/browser/browser_compartments.js b/toolkit/components/aboutperformance/tests/browser/browser_compartments.js new file mode 100644 index 0000000000..5c7d598f19 --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/browser_compartments.js @@ -0,0 +1,156 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource://gre/modules/PerformanceStats.jsm", this); +Cu.import("resource://testing-common/ContentTask.jsm", this); + +const URL = "http://example.com/browser/toolkit/components/aboutperformance/tests/browser/browser_compartments.html?test=" + Math.random(); + +// This function is injected as source as a frameScript +function frameScript() { + "use strict"; + + const { utils: Cu, classes: Cc, interfaces: Ci } = Components; + Cu.import("resource://gre/modules/PerformanceStats.jsm"); + + let performanceStatsService = + Cc["@mozilla.org/toolkit/performance-stats-service;1"]. + getService(Ci.nsIPerformanceStatsService); + + // Make sure that the stopwatch is now active. + performanceStatsService.isStopwatchActive = true; + + addMessageListener("compartments-test:getStatistics", () => { + try { + sendAsyncMessage("compartments-test:getStatistics", PerformanceStats.getSnapshot()); + } catch (ex) { + Cu.reportError("Error in content: " + ex); + Cu.reportError(ex.stack); + } + }); +} + +function Assert_leq(a, b, msg) { + Assert.ok(a <= b, `${msg}: ${a} <= ${b}`); +} + +function monotinicity_tester(source, testName) { + // In the background, check invariants: + // - numeric data can only ever increase; + // - the name, addonId, isSystem of a component never changes; + // - the name, addonId, isSystem of the process data; + // - there is at most one component with a combination of `name` and `addonId`; + // - types, etc. + let previous = { + processData: null, + componentsMap: new Map(), + }; + + let sanityCheck = function(prev, next) { + dump(`Sanity check: ${JSON.stringify(next, null, "\t")}\n`); + if (prev == null) { + return; + } + for (let k of ["name", "addonId", "isSystem"]) { + Assert.equal(prev[k], next[k], `Sanity check (${name}): ${k} hasn't changed.`); + } + for (let k of ["totalUserTime", "totalSystemTime", "totalCPOWTime", "ticks"]) { + Assert.equal(typeof next[k], "number", `Sanity check (${name}): ${k} is a number.`); + Assert_leq(prev[k], next[k], `Sanity check (${name}): ${k} is monotonic.`); + Assert_leq(0, next[k], `Sanity check (${name}): ${k} is >= 0.`) + } + Assert.equal(prev.durations.length, next.durations.length); + for (let i = 0; i < next.durations.length; ++i) { + Assert.ok(typeof next.durations[i] == "number" && next.durations[i] >= 0, + `Sanity check (${name}): durations[${i}] is a non-negative number.`); + Assert_leq(prev.durations[i], next.durations[i], + `Sanity check (${name}): durations[${i}] is monotonic.`) + } + for (let i = 0; i < next.durations.length - 1; ++i) { + Assert_leq(next.durations[i + 1], next.durations[i], + `Sanity check (${name}): durations[${i}] >= durations[${i + 1}].`) + } + }; + let iteration = 0; + let frameCheck = Task.async(function*() { + let name = `${testName}: ${iteration++}`; + let snapshot = yield source(); + if (!snapshot) { + // This can happen at the end of the test when we attempt + // to communicate too late with the content process. + window.clearInterval(interval); + return; + } + + // Sanity check on the process data. + sanityCheck(previous.processData, snapshot.processData); + Assert.equal(snapshot.processData.isSystem, true); + Assert.equal(snapshot.processData.name, ""); + Assert.equal(snapshot.processData.addonId, ""); + previous.procesData = snapshot.processData; + + // Sanity check on components data. + let set = new Set(); + for (let item of snapshot.componentsData) { + let key = `{name: ${item.name}, addonId: ${item.addonId}}`; + set.add(key); + sanityCheck(previous.componentsMap.get(key), item); + previous.componentsMap.set(key, item); + + for (let k of ["totalUserTime", "totalSystemTime", "totalCPOWTime"]) { + Assert_leq(item[k], snapshot.processData[k], + `Sanity check (${name}): component has a lower ${k} than process`); + } + for (let i = 0; i < item.durations.length; ++i) { + Assert_leq(item.durations[i], snapshot.processData.durations[i], + `Sanity check (${name}): component has a lower durations[${i}] than process.`); + } + } + // Check that we do not have duplicate components. + Assert.equal(set.size, snapshot.componentsData.length); + }); + let interval = window.setInterval(frameCheck, 300); + registerCleanupFunction(() => { + window.clearInterval(interval); + }); +} + +add_task(function* test() { + info("Extracting initial state"); + let stats0 = PerformanceStats.getSnapshot(); + Assert.notEqual(stats0.componentsData.length, 0, "There is more than one component"); + Assert.ok(!stats0.componentsData.find(stat => stat.name.indexOf(URL) != -1), + "The url doesn't appear yet"); + + let newTab = gBrowser.addTab(); + let browser = newTab.linkedBrowser; + // Setup monitoring in the tab + info("Setting up monitoring in the tab"); + yield ContentTask.spawn(newTab.linkedBrowser, null, frameScript); + + info("Opening URL"); + newTab.linkedBrowser.loadURI(URL); + + info("Skipping monotonicity testing (1149897)"); + if (false) { + monotinicity_tester(() => PerformanceStats.getSnapshot(), "parent process"); + monotinicity_tester(() => promiseContentResponseOrNull(browser, "compartments-test:getStatistics", null), "content process" ); + } + + while (true) { + let stats = (yield promiseContentResponse(browser, "compartments-test:getStatistics", null)); + let found = stats.componentsData.find(stat => { + return (stat.name.indexOf(URL) != -1) + && (stat.totalUserTime > 1000) + }); + if (found) { + break; + } + yield new Promise(resolve => setTimeout(resolve, 100)); + } + + // Cleanup + gBrowser.removeTab(newTab); +}); diff --git a/toolkit/components/aboutperformance/tests/browser/head.js b/toolkit/components/aboutperformance/tests/browser/head.js new file mode 100644 index 0000000000..e2a3c4b9a2 --- /dev/null +++ b/toolkit/components/aboutperformance/tests/browser/head.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { utils: Cu, interfaces: Ci, classes: Cc } = Components; + +function promiseContentResponse(browser, name, message) { + let mm = browser.messageManager; + let promise = new Promise(resolve => { + function removeListener() { + mm.removeMessageListener(name, listener); + } + + function listener(msg) { + removeListener(); + resolve(msg.data); + } + + mm.addMessageListener(name, listener); + registerCleanupFunction(removeListener); + }); + mm.sendAsyncMessage(name, message); + return promise; +} +function promiseContentResponseOrNull(browser, name, message) { + if (!browser.messageManager) { + return null; + } + return promiseContentResponse(browser, name, message); +} diff --git a/toolkit/components/aboutperformance/tests/xpcshell/test_compartments.js b/toolkit/components/aboutperformance/tests/xpcshell/test_compartments.js new file mode 100644 index 0000000000..88c0712ee4 --- /dev/null +++ b/toolkit/components/aboutperformance/tests/xpcshell/test_compartments.js @@ -0,0 +1,131 @@ +"use strict"; + +const {utils: Cu, interfaces: Ci, classes: Cc} = Components; + +Cu.import("resource://gre/modules/Task.jsm", this); +Cu.import("resource://gre/modules/Services.jsm", this); +Cu.import("resource://gre/modules/PerformanceStats.jsm", this); + +function run_test() { + run_next_test(); +} + +let promiseStatistics = Task.async(function*(name) { + yield Promise.resolve(); // Make sure that we wait until + // statistics have been updated. + let snapshot = PerformanceStats.getSnapshot(); + do_print("Statistics: " + name); + do_print(JSON.stringify(snapshot.processData, null, "\t")); + do_print(JSON.stringify(snapshot.componentsData, null, "\t")); + return snapshot; +}); + +let promiseSetMonitoring = Task.async(function*(to) { + Cc["@mozilla.org/toolkit/performance-stats-service;1"]. + getService(Ci.nsIPerformanceStatsService). + isStopwatchActive = to; + yield Promise.resolve(); +}); + +function getBuiltinStatistics(snapshot) { + let stats = snapshot.componentsData.find(stats => + stats.isSystem && !stats.addonId + ); + return stats; +} + +function burnCPU(ms) { + do_print("Burning CPU"); + let counter = 0; + let ignored = []; + let start = Date.now(); + while (Date.now() - start < ms) { + ignored.push(0); + ignored.shift(); + ++counter; + } + do_print("Burning CPU over, after " + counter + " iterations"); +} + +function ensureEquals(snap1, snap2, name) { + Assert.equal( + JSON.stringify(snap1.processData), + JSON.stringify(snap2.processData), + "Same process data: " + name); + let stats1 = snap1.componentsData.sort((a, b) => a.name <= b.name); + let stats2 = snap2.componentsData.sort((a, b) => a.name <= b.name); + Assert.equal( + JSON.stringify(stats1), + JSON.stringify(stats2), + "Same components data: " + name + ); +} + +function hasLowPrecision() { + let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")]; + do_print(`Running ${sysName} version ${sysVersion}`); + + if (sysName != "Windows_NT") { + do_print("Not running Windows, precision should be good."); + return false; + } + if (sysVersion >= 6) { + do_print("Running a recent version of Windows, precision should be good."); + return false; + } + do_print("Running old Windows, need to deactivate tests due to bad precision."); + return true; +} + +add_task(function* test_measure() { + let skipPrecisionTests = hasLowPrecision(); + + do_print("Burn CPU without the stopwatch"); + yield promiseSetMonitoring(false); + let stats0 = yield promiseStatistics("Initial state"); + burnCPU(300); + let stats1 = yield promiseStatistics("Initial state + burn, without stopwatch"); + + do_print("Burn CPU with the stopwatch"); + yield promiseSetMonitoring(true); + burnCPU(300); + let stats2 = yield promiseStatistics("Second burn, with stopwatch"); + + do_print("Burn CPU without the stopwatch again") + yield promiseSetMonitoring(false); + let stats3 = yield promiseStatistics("Before third burn, without stopwatch"); + burnCPU(300); + let stats4 = yield promiseStatistics("After third burn, without stopwatch"); + + ensureEquals(stats0, stats1, "Initial state vs. Initial state + burn, without stopwatch"); + let process1 = stats1.processData; + let process2 = stats2.processData; + let process3 = stats3.processData; + let process4 = stats4.processData; + if (skipPrecisionTests) { + do_print("Skipping totalUserTime check under Windows XP, as timer is not always updated by the OS.") + } else { + Assert.ok(process2.totalUserTime - process1.totalUserTime >= 10000, `At least 10ms counted for process time (${process2.totalUserTime - process1.totalUserTime})`); + } + Assert.equal(process2.totalCPOWTime, process1.totalCPOWTime, "We haven't used any CPOW time during the first burn"); + Assert.equal(process4.totalUserTime, process3.totalUserTime, "After deactivating the stopwatch, we didn't count any time"); + Assert.equal(process4.totalCPOWTime, process3.totalCPOWTime, "After deactivating the stopwatch, we didn't count any CPOW time"); + + let builtin1 = getBuiltinStatistics(stats1) || { totalUserTime: 0, totalCPOWTime: 0 }; + let builtin2 = getBuiltinStatistics(stats2); + let builtin3 = getBuiltinStatistics(stats3); + let builtin4 = getBuiltinStatistics(stats4); + Assert.notEqual(builtin2, null, "Found the statistics for built-ins 2"); + Assert.notEqual(builtin3, null, "Found the statistics for built-ins 3"); + Assert.notEqual(builtin4, null, "Found the statistics for built-ins 4"); + + if (skipPrecisionTests) { + do_print("Skipping totalUserTime check under Windows XP, as timer is not always updated by the OS.") + } else { + Assert.ok(builtin2.totalUserTime - builtin1.totalUserTime >= 10000, `At least 10ms counted for built-in statistics (${builtin2.totalUserTime - builtin1.totalUserTime})`); + } + Assert.equal(builtin2.totalCPOWTime, builtin1.totalCPOWTime, "We haven't used any CPOW time during the first burn for the built-in"); + Assert.equal(builtin2.totalCPOWTime, builtin1.totalCPOWTime, "No CPOW for built-in statistics"); + Assert.equal(builtin4.totalUserTime, builtin3.totalUserTime, "After deactivating the stopwatch, we didn't count any time for the built-in"); + Assert.equal(builtin4.totalCPOWTime, builtin3.totalCPOWTime, "After deactivating the stopwatch, we didn't count any CPOW time for the built-in"); +}); diff --git a/toolkit/components/aboutperformance/tests/xpcshell/xpcshell.ini b/toolkit/components/aboutperformance/tests/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..c4a1aa8ce9 --- /dev/null +++ b/toolkit/components/aboutperformance/tests/xpcshell/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head= +tail= + +[test_compartments.js] diff --git a/toolkit/components/build/moz.build b/toolkit/components/build/moz.build index 57f7272e7d..3fcd62632d 100644 --- a/toolkit/components/build/moz.build +++ b/toolkit/components/build/moz.build @@ -16,6 +16,7 @@ FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ '../../xre', + '../aboutperformance', '../alerts', '../downloads', '../feeds', diff --git a/toolkit/components/build/nsToolkitCompsModule.cpp b/toolkit/components/build/nsToolkitCompsModule.cpp index 9988bbcda1..7182718dd7 100644 --- a/toolkit/components/build/nsToolkitCompsModule.cpp +++ b/toolkit/components/build/nsToolkitCompsModule.cpp @@ -43,12 +43,16 @@ #include "nsTerminator.h" #endif +#include "nsPerformanceStats.h" + using namespace mozilla; ///////////////////////////////////////////////////////////////////////////// NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppStartup, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsPerformanceStatsService) + #if defined(MOZ_HAS_TERMINATOR) NS_GENERIC_FACTORY_CONSTRUCTOR(nsTerminator) #endif @@ -80,6 +84,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NativeFileWatcherService, Init) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonPathService, AddonPathService::GetInstance) NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID); +NS_DEFINE_NAMED_CID(NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID); #if defined(MOZ_HAS_TERMINATOR) NS_DEFINE_NAMED_CID(NS_TOOLKIT_TERMINATOR_CID); #endif @@ -107,6 +112,7 @@ static const Module::CIDEntry kToolkitCIDs[] = { #if defined(MOZ_HAS_TERMINATOR) { &kNS_TOOLKIT_TERMINATOR_CID, false, nullptr, nsTerminatorConstructor }, #endif + { &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID, false, nullptr, nsPerformanceStatsServiceConstructor }, { &kNS_USERINFO_CID, false, nullptr, nsUserInfoConstructor }, { &kNS_ALERTSSERVICE_CID, false, nullptr, nsAlertsServiceConstructor }, #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS) @@ -133,6 +139,7 @@ static const Module::ContractIDEntry kToolkitContracts[] = { #if defined(MOZ_HAS_TERMINATOR) { NS_TOOLKIT_TERMINATOR_CONTRACTID, &kNS_TOOLKIT_TERMINATOR_CID }, #endif + { NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID, &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID }, { NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID }, { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID }, #if !defined(MOZ_DISABLE_PARENTAL_CONTROLS) diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build index 4263bb6e10..e25c9c62f1 100644 --- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -10,8 +10,8 @@ if CONFIG['MOZ_ENABLE_XREMOTE']: DIRS += [ 'aboutcache', - 'aboutcompartments', 'aboutmemory', + 'aboutperformance', 'addoncompat', 'alerts', 'apppicker', diff --git a/toolkit/components/search/nsSearchService.js b/toolkit/components/search/nsSearchService.js index 201d19eea7..65ecb30f24 100644 --- a/toolkit/components/search/nsSearchService.js +++ b/toolkit/components/search/nsSearchService.js @@ -3304,6 +3304,10 @@ SearchService.prototype = { return this.getEngineByName(defaultEngine); }, + resetToOriginalDefaultEngine: function SRCH_SVC__resetToOriginalDefaultEngine() { + this.defaultEngine = this._originalDefaultEngine; + }, + _buildCache: function SRCH_SVC__buildCache() { if (!getBoolPref(BROWSER_SEARCH_PREF + "cache.enabled", true)) return; diff --git a/toolkit/components/startup/public/nsIAppStartup.idl b/toolkit/components/startup/public/nsIAppStartup.idl index b6c8997983..ff78c83716 100644 --- a/toolkit/components/startup/public/nsIAppStartup.idl +++ b/toolkit/components/startup/public/nsIAppStartup.idl @@ -7,7 +7,7 @@ interface nsICmdLineService; -[scriptable, uuid(bc0cb41f-4924-4c69-a65b-e35225a8650f)] +[scriptable, uuid(6621f6d5-6c04-4a0e-9e74-447db221484e)] interface nsIAppStartup : nsISupports { diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index c1ccab1064..69b86dfcf8 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -214,7 +214,7 @@ "expires_in_version": "never", "kind": "enumerated", "n_values": 10, - "description": "GPU Device Reset Reason (ok, hung, removed, reset, internal error, invalid call)" + "description": "GPU Device Reset Reason (ok, hung, removed, reset, internal error, invalid call, out of memory)" }, "FORGET_SKIPPABLE_MAX": { "expires_in_version": "never", @@ -4578,6 +4578,22 @@ "keyed" : true, "description" : "Exceptions thrown by add-ons" }, + "MISBEHAVING_ADDONS_CPOW_TIME_MS": { + "expires_in_version": "never", + "kind": "exponential", + "low": 1, + "high": 10000, + "n_buckets": 20, + "keyed": true, + "description": "Time spent by an add-on performing blocking cross-process communications (ms, keyed by add-on ID, updated every 15s by default)" + }, + "MISBEHAVING_ADDONS_JANK_LEVEL": { + "expires_in_version": "never", + "kind": "enumerated", + "n_values": 10, + "keyed": true, + "description": "Longest blocking operation performed by the add-on (log2(duration in ms), keyed by add-on, updated every 15s by default)" + }, "SEARCH_COUNTS": { "expires_in_version": "never", "kind": "count", diff --git a/toolkit/content/aboutSupport.js b/toolkit/content/aboutSupport.js index a13e375f45..665eb498ce 100644 --- a/toolkit/content/aboutSupport.js +++ b/toolkit/content/aboutSupport.js @@ -106,8 +106,65 @@ let snapshotFormatters = { }, graphics: function graphics(data) { + let strings = stringBundle(); + + function localizedMsg(msgArray) { + let nameOrMsg = msgArray.shift(); + if (msgArray.length) { + // formatStringFromName logs an NS_ASSERTION failure otherwise that says + // "use GetStringFromName". Lame. + try { + return strings.formatStringFromName(nameOrMsg, msgArray, + msgArray.length); + } + catch (err) { + // Throws if nameOrMsg is not a name in the bundle. This shouldn't + // actually happen though, since msgArray.length > 1 => nameOrMsg is a + // name in the bundle, not a message, and the remaining msgArray + // elements are parameters. + return nameOrMsg; + } + } + try { + return strings.GetStringFromName(nameOrMsg); + } + catch (err) { + // Throws if nameOrMsg is not a name in the bundle. + } + return nameOrMsg; + } + + // Read APZ info out of data.info, stripping it out in the process. + let apzInfo = []; + let formatApzInfo = function (info) { + let out = []; + for (let type of ['Wheel', 'Touch']) { + let key = 'Apz' + type + 'Input'; + let warningKey = key + 'Warning'; + + if (!(key in info)) + continue; + + let badPref = info[warningKey]; + delete info[key]; + delete info[warningKey]; + + let message; + if (badPref) + message = localizedMsg([type.toLowerCase() + 'Warning', badPref]); + else + message = localizedMsg([type.toLowerCase() + 'Enabled']); + dump(message + ', ' + (type.toLowerCase() + 'Warning') + ', ' + badPref + '\n'); + out.push(message); + } + + return out; + }; + // graphics-info-properties tbody if ("info" in data) { + apzInfo = formatApzInfo(data.info); + let trs = sortedArrayFromObject(data.info).map(function ([prop, val]) { return $.new("tr", [ $.new("th", prop, "column"), @@ -151,34 +208,12 @@ let snapshotFormatters = { // graphics-tbody tbody - function localizedMsg(msgArray) { - let nameOrMsg = msgArray.shift(); - if (msgArray.length) { - // formatStringFromName logs an NS_ASSERTION failure otherwise that says - // "use GetStringFromName". Lame. - try { - return strings.formatStringFromName(nameOrMsg, msgArray, - msgArray.length); - } - catch (err) { - // Throws if nameOrMsg is not a name in the bundle. This shouldn't - // actually happen though, since msgArray.length > 1 => nameOrMsg is a - // name in the bundle, not a message, and the remaining msgArray - // elements are parameters. - return nameOrMsg; - } - } - try { - return strings.GetStringFromName(nameOrMsg); - } - catch (err) { - // Throws if nameOrMsg is not a name in the bundle. - } - return nameOrMsg; - } - let out = Object.create(data); - let strings = stringBundle(); + + if (apzInfo.length == 0) + out.asyncPanZoom = "none"; + else + out.asyncPanZoom = apzInfo.join("; "); out.acceleratedWindows = data.numAcceleratedWindows + "/" + data.numTotalWindows; diff --git a/toolkit/locales/en-US/chrome/global/aboutSupport.properties b/toolkit/locales/en-US/chrome/global/aboutSupport.properties index d7fa4f3ffd..6a2af5bbe3 100644 --- a/toolkit/locales/en-US/chrome/global/aboutSupport.properties +++ b/toolkit/locales/en-US/chrome/global/aboutSupport.properties @@ -94,3 +94,11 @@ canSandboxMedia = Media Plugin Sandboxing # of windows, respectively, while %3$S will indicate whether windows are remote by default ('true' # or 'false') multiProcessStatus = %1$S/%2$S (default: %3$S) + +asyncPanZoom = Asynchronous Pan/Zoom +wheelEnabled = wheel input enabled +touchEnabled = touch input enabled + +# LOCALIZATION NOTE %1 will be replaced with the key of a preference. +wheelWarning = async wheel input disabled due to unsupported pref: %S +touchWarning = async touch input disabled due to unsupported pref: %S diff --git a/toolkit/modules/AddonWatcher.jsm b/toolkit/modules/AddonWatcher.jsm index d9f0d437e0..490e3eb7ab 100644 --- a/toolkit/modules/AddonWatcher.jsm +++ b/toolkit/modules/AddonWatcher.jsm @@ -13,24 +13,56 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Preferences", "resource://gre/modules/Preferences.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "console", + "resource://gre/modules/devtools/Console.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PerformanceStats", + "resource://gre/modules/PerformanceStats.jsm"); +XPCOMUtils.defineLazyServiceGetter(this, "Telemetry", + "@mozilla.org/base/telemetry;1", + Ci.nsITelemetry); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); let AddonWatcher = { - _lastAddonTime: {}, + _previousPerformanceIndicators: {}, _timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), _callback: null, - _interval: 1500, + /** + * The interval at which we poll the available performance information + * to find out about possibly slow add-ons, in milliseconds. + */ + _interval: 15000, _ignoreList: null, + /** + * Initialize and launch the AddonWatcher. + * + * @param {function} callback A callback, called whenever we determine + * that an add-on is causing performance issues. It takes as argument + * {string} addonId The identifier of the add-on known to cause issues. + * {string} reason The reason for which the add-on has been flagged, + * as one of "totalCPOWTime" (the add-on has caused blocking process + * communications, which freeze the UX) + * Use preference "browser.addon-watch.limits.totalCPOWTime" to control + * the maximal amount of CPOW time per watch interval. + * + * or "longestDuration" (the add-on has caused user-visible missed frames). + * Use preference "browser.addon-watch.limits.longestDuration" to control + * the longest uninterrupted execution of code of an add-on during a watch + * interval. + */ init: function(callback) { if (!callback) { return; } if (this._callback) { + // Already initialized return; } this._interval = Preferences.get("browser.addon-watch.interval", 15000); if (this._interval == -1) { + // Deactivated by preferences return; } @@ -41,38 +73,117 @@ let AddonWatcher = { // probably some malformed JSON, ignore and carry on this._ignoreList = new Set(); } - this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK); + + // Start monitoring + this.paused = false; + + Services.obs.addObserver(() => { + this.uninit(); + }, "profile-before-change", false); }, uninit: function() { - if (this._timer) { - this._timer.cancel(); - this._timer = null; - } + this.paused = true; + this._callback = null; }, - _checkAddons: function() { - let compartmentInfo = Cc["@mozilla.org/compartment-info;1"] - .getService(Ci.nsICompartmentInfo); - let compartments = compartmentInfo.getCompartments(); - let count = compartments.length; - let addons = {}; - for (let i = 0; i < count; i++) { - let compartment = compartments.queryElementAt(i, Ci.nsICompartment); - if (compartment.addonId) { - if (addons[compartment.addonId]) { - addons[compartment.addonId] += compartment.time; - } else { - addons[compartment.addonId] = compartment.time; - } - } + + /** + * Interrupt temporarily add-on watching. + */ + set paused(isPaused) { + if (!this._callback || this._interval == -1) { + return; } - let limit = this._interval * Preferences.get("browser.addon-watch.percentage-limit", 75) * 10; - for (let addonId in addons) { - if (!this._ignoreList.has(addonId)) { - if (this._lastAddonTime[addonId] && ((addons[addonId] - this._lastAddonTime[addonId]) > limit)) { - this._callback(addonId); + if (isPaused) { + this._timer.cancel(); + } else { + PerformanceStats.init(); + this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK); + } + this._isPaused = isPaused; + }, + get paused() { + return this._isPaused; + }, + _isPaused: true, + + /** + * Check the performance of add-ons during the latest slice of time. + * + * We consider that an add-on is causing slowdown if it has executed + * without interruption for at least 64ms (4 frames) at least once + * during the latest slice, or if it has used any CPOW during the latest + * slice. + */ + _checkAddons: function() { + try { + let snapshot = PerformanceStats.getSnapshot(); + + let limits = { + // By default, warn if we have a total time of 1s of CPOW per 15 seconds + totalCPOWTime: Math.round(Preferences.get("browser.addon-watch.limits.totalCPOWTime", 1000) * this._interval / 15000), + // By default, warn if we have skipped 4 consecutive frames + // at least once during the latest slice. + longestDuration: Math.round(Math.log2(Preferences.get("browser.addon-watch.limits.longestDuration", 7))), + }; + + for (let item of snapshot.componentsData) { + let addonId = item.addonId; + if (!item.isSystem || !addonId) { + // We are only interested in add-ons. + continue; + } + if (this._ignoreList.has(addonId)) { + // This add-on has been explicitly put in the ignore list + // by the user. Don't waste time with it. + continue; + } + let previous = this._previousPerformanceIndicators[addonId]; + this._previousPerformanceIndicators[addonId] = item; + + if (!previous) { + // This is the first time we see the addon, so we are probably + // executed right during/after startup. Performance is always + // weird during startup, with the JIT warming up, competition + // in disk access, etc. so we do not take this as a reason to + // display the slow addon warning. + continue; + } + + // Report misbehaviors to Telemetry + + let diff = item.substract(previous); + if (diff.longestDuration > 5) { + Telemetry.getKeyedHistogramById("MISBEHAVING_ADDONS_JANK_LEVEL"). + add(addonId, diff.longestDuration); + } + if (diff.totalCPOWTime > 0) { + Telemetry.getKeyedHistogramById("MISBEHAVING_ADDONS_CPOW_TIME_MS"). + add(addonId, diff.totalCPOWTime); + } + + // Report mibehaviors to the user. + let reason = null; + + for (let k of ["longestDuration", "totalCPOWTime"]) { + if (limits[k] > 0 && diff[k] > limits[k]) { + reason = k; + } + } + + if (!reason) { + continue; + } + + try { + this._callback(addonId, reason); + } catch (ex) { + Cu.reportError("Error in AddonWatcher._checkAddons callback " + ex); + Cu.reportError(ex.stack); } - this._lastAddonTime[addonId] = addons[addonId]; } + } catch (ex) { + Cu.reportError("Error in AddonWatcher._checkAddons " + ex); + Cu.reportError(ex.stack); } }, ignoreAddonForSession: function(addonid) { diff --git a/toolkit/modules/PerformanceStats.jsm b/toolkit/modules/PerformanceStats.jsm new file mode 100644 index 0000000000..47bbb4c061 --- /dev/null +++ b/toolkit/modules/PerformanceStats.jsm @@ -0,0 +1,205 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["PerformanceStats"]; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +/** + * API for querying and examining performance data. + * + * The data exposed by this API is computed internally by the JavaScript VM. + * See `PerformanceData` for the detail of the information provided by this + * API. + */ + +Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); + +let performanceStatsService = + Cc["@mozilla.org/toolkit/performance-stats-service;1"]. + getService(Ci.nsIPerformanceStatsService); + + +const PROPERTIES_NUMBERED = ["totalUserTime", "totalSystemTime", "totalCPOWTime", "ticks"]; +const PROPERTIES_META = ["name", "addonId", "isSystem"]; +const PROPERTIES_FLAT = [...PROPERTIES_NUMBERED, ...PROPERTIES_META]; + +/** + * Information on a single component. + * + * This offers the following fields: + * + * @field {string} name The name of the component: + * - for the process itself, ""; + * - for platform code, ""; + * - for an add-on, the identifier of the addon (e.g. "myaddon@foo.bar"); + * - for a webpage, the url of the page. + * + * @field {string} addonId The identifier of the addon (e.g. "myaddon@foo.bar"). + * + * @field {boolean} isSystem `true` if the component is a system component (i.e. + * an add-on or platform-code), `false` otherwise (i.e. a webpage). + * + * @field {number} totalUserTime The total amount of time spent executing code. + * + * @field {number} totalSystemTime The total amount of time spent executing + * system calls. + * + * @field {number} totalCPOWTime The total amount of time spent waiting for + * blocking cross-process communications + * + * @field {number} ticks The number of times the JavaScript VM entered the code + * of this component to execute it. + * + * @field {Array} durations An array containing at each position `i` + * the number of times execution of this component has lasted at least `2^i` + * milliseconds. + * + * All numeric values are non-negative and can only increase. + */ +function PerformanceData(xpcom) { + for (let k of PROPERTIES_FLAT) { + this[k] = xpcom[k]; + } + this.durations = xpcom.getDurations(); +} +PerformanceData.prototype = { + /** + * Compare two instances of `PerformanceData` + * + * @return `true` if `this` and `to` have equal values in all fields. + */ + equals: function(to) { + if (!(to instanceof PerformanceData)) { + throw new TypeError(); + } + for (let k of PROPERTIES_FLAT) { + if (this[k] != to[k]) { + return false; + } + } + for (let i = 0; i < this.durations.length; ++i) { + if (to.durations[i] != this.durations[i]) { + return false; + } + } + return true; + }, + + /** + * Compute the delta between two instances of `PerformanceData`. + * + * @param {PerformanceData|null} to. If `null`, assumed an instance of + * `PerformanceData` in which all numeric values are 0. + * + * @return {PerformanceDiff} The performance usage between `to` and `this`. + */ + substract: function(to = null) { + return new PerformanceDiff(this, to); + } +}; + +/** + * The delta between two instances of `PerformanceData`. + * + * Used to monitor resource usage between two timestamps. + * + * @field {number} longestDuration An indication of the longest + * execution duration between two timestamps: + * - -1 == less than 1ms + * - 0 == [1, 2[ ms + * - 1 == [2, 4[ ms + * - 3 == [4, 8[ ms + * - 4 == [8, 16[ ms + * - ... + * - 7 == [128, ...] ms + */ +function PerformanceDiff(current, old = null) { + for (let k of PROPERTIES_META) { + this[k] = current[k]; + } + + if (old) { + if (!(old instanceof PerformanceData)) { + throw new TypeError(); + } + if (current.durations.length != old.durations.length) { + throw new TypeError("Internal error: mismatched length for `durations`."); + } + + this.durations = []; + + this.longestDuration = -1; + + for (let i = 0; i < current.durations.length; ++i) { + let delta = current.durations[i] - old.durations[i]; + this.durations[i] = delta; + if (delta > 0) { + this.longestDuration = i; + } + } + for (let k of PROPERTIES_NUMBERED) { + this[k] = current[k] - old[k]; + } + } else { + this.durations = current.durations.slice(0); + + for (let k of PROPERTIES_NUMBERED) { + this[k] = current[k]; + } + + this.longestDuration = -1; + for (let i = this.durations.length - 1; i >= 0; --i) { + if (this.durations[i] > 0) { + this.longestDuration = i; + break; + } + } + } +} + +/** + * A snapshot of the performance usage of the process. + */ +function Snapshot(xpcom) { + this.componentsData = []; + let enumeration = xpcom.getComponentsData().enumerate(); + while (enumeration.hasMoreElements()) { + let stat = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats); + this.componentsData.push(new PerformanceData(stat)); + } + this.processData = new PerformanceData(xpcom.getProcessData()); +} + + +this.PerformanceStats = { + /** + * Activate monitoring. + */ + init() { + // + // The implementation actually does nothing, as monitoring is + // initiated when loading the module. + // + // This function is actually provided as a gentle way to ensure + // that client code that imports `PerformanceStats` lazily + // does not forget to force the import, hence triggering + // actual load of the module. + // + }, + + /** + * Get a snapshot of the performance usage of the current process. + * + * @type {Snapshot} + */ + getSnapshot() { + return new Snapshot(performanceStatsService.getSnapshot()); + }, +}; + +performanceStatsService.isStopwatchActive = true; diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build index d81b8631e7..c2ea65e2fe 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -33,6 +33,7 @@ EXTRA_JS_MODULES += [ 'NewTabUtils.jsm', 'PageMenu.jsm', 'PageMetadata.jsm', + 'PerformanceStats.jsm', 'PermissionsUtils.jsm', 'PopupNotifications.jsm', 'Preferences.jsm', diff --git a/toolkit/modules/tests/browser/browser_AddonWatcher.js b/toolkit/modules/tests/browser/browser_AddonWatcher.js new file mode 100644 index 0000000000..cc7a005a21 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_AddonWatcher.js @@ -0,0 +1,126 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests for AddonWatcher.jsm + +"use strict"; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/Promise.jsm", this); +Cu.import("resource://gre/modules/AddonManager.jsm", this); +Cu.import("resource://gre/modules/AddonWatcher.jsm", this); +Cu.import("resource://gre/modules/Services.jsm", this); + +const ADDON_URL = "http://example.com/browser/toolkit/modules/tests/browser/browser_Addons_sample.xpi"; +const ADDON_ID = "addonwatcher-test@mozilla.com"; + +add_task(function* init() { + AddonWatcher.uninit(); + + info("Installing test add-on"); + let installer = yield new Promise(resolve => AddonManager.getInstallForURL(ADDON_URL, resolve, "application/x-xpinstall")); + if (installer.error) { + throw installer.error; + } + let ready = new Promise((resolve, reject) => installer.addListener({ + onInstallEnded: (_, addon) => resolve(addon), + onInstallFailed: reject, + onDownloadFailed: reject + })); + installer.install(); + + info("Waiting for installation to terminate"); + let addon = yield ready; + + registerCleanupFunction(() => { + info("Uninstalling test add-on"); + addon.uninstall() + }); + + Services.prefs.setIntPref("browser.addon-watch.interval", 1000); + registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.addon-watch.interval"); + }); + + let oldCanRecord = Services.telemetry.canRecord; + Services.telemetry.canRecordExtended = true; + registerCleanupFunction(function () { + Services.telemetry.canRecordExtended = oldCanRecord; + }); +}); + +// Utility function to burn some resource, trigger a reaction of the add-on watcher +// and check both its notification and telemetry. +let burn_rubber = Task.async(function*({histogramName, topic, expectedReason, prefs, expectedMinSum}) { + try { + for (let key of Object.keys(prefs)) { + Services.prefs.setIntPref(key, prefs[key]); + } + info("Preparing add-on watcher"); + let wait = new Promise(resolve => AddonWatcher.init((id, reason) => { + Assert.equal(id, ADDON_ID, "The add-on watcher has detected the misbehaving addon"); + resolve(reason); + })); + let done = false; + wait = wait.then(result => { + done = true; + return result; + }); + + let histogram = Services.telemetry.getKeyedHistogramById(histogramName); + histogram.clear(); + let snap1 = histogram.snapshot(ADDON_ID); + Assert.equal(snap1.sum, 0, `Histogram ${histogramName} is initially empty for the add-on`); + while (!done) { + yield new Promise(resolve => setTimeout(resolve, 100)); + info("Burning some CPU. This should cause an add-on watcher notification"); + Services.obs.notifyObservers(null, topic, ""); + } + let reason = yield wait; + + Assert.equal(reason, expectedReason, "Reason is valid"); + let snap2 = histogram.snapshot(ADDON_ID); + + Assert.ok(snap2.sum > expectedMinSum, `Histogram ${histogramName} recorded a gravity of ${snap2.sum}, expecting at least ${expectedMinSum}.`); + } finally { + AddonWatcher.uninit(); + for (let key of Object.keys(prefs)) { + Services.prefs.clearUserPref(key); + } + } +}); + +// Test that burning CPU will cause the add-on watcher to notice that +// the add-on is misbehaving. +add_task(function* test_burn_CPU() { + yield burn_rubber({ + prefs: { + "browser.addon-watch.limits.longestDuration": 2, + "browser.addon-watch.limits.totalCPOWTime": -1, + }, + histogramName: "MISBEHAVING_ADDONS_JANK_LEVEL", + topic: "test-addonwatcher-burn-some-cpu", + expectedReason: "longestDuration", + expectedMinSum: 7, + }); +}); + +// Test that burning CPOW will cause the add-on watcher to notice that +// the add-on is misbehaving. +add_task(function* test_burn_CPOW() { + if (!gMultiProcessBrowser) { + info("This is a single-process Firefox, we can't test for CPOW"); + return; + } + yield burn_rubber({ + prefs: { + "browser.addon-watch.limits.longestDuration": -1, + "browser.addon-watch.limits.totalCPOWTime": 100, + }, + histogramName: "MISBEHAVING_ADDONS_CPOW_TIME_MS", + topic: "test-addonwatcher-burn-some-cpow", + expectedReason: "totalCPOWTime", + expectedMinSum: 1000, + }); +}); diff --git a/widget/GfxInfoBase.cpp b/widget/GfxInfoBase.cpp index e732fbe5b3..886eb4dba4 100644 --- a/widget/GfxInfoBase.cpp +++ b/widget/GfxInfoBase.cpp @@ -16,6 +16,7 @@ #include "nsAutoPtr.h" #include "nsString.h" #include "nsUnicharUtils.h" +#include "nsVersionComparator.h" #include "mozilla/Services.h" #include "mozilla/Observer.h" #include "nsIObserver.h" @@ -26,6 +27,7 @@ #include "nsIDOMNodeList.h" #include "nsTArray.h" #include "nsXULAppAPI.h" +#include "nsIXULAppInfo.h" #include "mozilla/Preferences.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/gfx/2D.h" @@ -119,8 +121,8 @@ GetPrefNameForFeature(int32_t aFeature) case nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS: name = BLACKLIST_PREF_BRANCH "layers.direct3d11"; break; - case nsIGfxInfo::FEATURE_DXVA: - name = BLACKLIST_PREF_BRANCH "dxva"; + case nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING: + name = BLACKLIST_PREF_BRANCH "hardwarevideodecoding"; break; case nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE: name = BLACKLIST_PREF_BRANCH "direct3d11angle"; @@ -140,6 +142,9 @@ GetPrefNameForFeature(int32_t aFeature) case nsIGfxInfo::FEATURE_STAGEFRIGHT: name = BLACKLIST_PREF_BRANCH "stagefright"; break; + case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION: + name = BLACKLIST_PREF_BRANCH "webrtc.hw.acceleration"; + break; default: break; }; @@ -213,6 +218,29 @@ BlacklistNodeToTextValue(nsIDOMNode *aBlacklistNode, nsAString& aValue) return true; } +// finds "Hello" if the aAttrName is "attr". +static bool +BlacklistAttrToTextValue(nsIDOMNode *aBlacklistNode, + const nsAString& aAttrName, + nsAString& aValue) +{ + nsCOMPtr element = do_QueryInterface(aBlacklistNode); + if (!element) { + return false; + } + + nsAutoString value; + if (NS_FAILED(element->GetAttribute(aAttrName, value))) { + return false; + } + + value.Trim(" \t\r\n"); + aValue = value; + + return true; +} + + static OperatingSystem BlacklistOSToOperatingSystem(const nsAString& os) { @@ -291,8 +319,8 @@ BlacklistFeatureToGfxFeature(const nsAString& aFeature) return nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS; else if (aFeature.EqualsLiteral("DIRECT3D_11_ANGLE")) return nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE; - else if (aFeature.EqualsLiteral("DXVA")) - return nsIGfxInfo::FEATURE_DXVA; + else if (aFeature.EqualsLiteral("HARDWARE_VIDEO_DECODING")) + return nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING; else if (aFeature.EqualsLiteral("OPENGL_LAYERS")) return nsIGfxInfo::FEATURE_OPENGL_LAYERS; else if (aFeature.EqualsLiteral("WEBGL_OPENGL")) @@ -303,6 +331,8 @@ BlacklistFeatureToGfxFeature(const nsAString& aFeature) return nsIGfxInfo::FEATURE_WEBGL_MSAA; else if (aFeature.EqualsLiteral("STAGEFRIGHT")) return nsIGfxInfo::FEATURE_STAGEFRIGHT; + else if (aFeature.EqualsLiteral("WEBRTC_HW_ACCELERATION")) + return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION; return 0; } @@ -404,6 +434,42 @@ BlacklistEntryToDriverInfo(nsIDOMNode* aBlacklistEntry, nsCOMPtr dataNode; nsAutoString dataValue; + // If we get an application version to be zero, something is not working + // and we are not going to bother checking the blocklist versions. + // See TestGfxWidgets.cpp for how version comparison works. + // + static mozilla::Version zeroV("0"); + static mozilla::Version appV(GfxInfoBase::GetApplicationVersion().get()); + if (appV <= zeroV) { + gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false)) << "Invalid application version " << GfxInfoBase::GetApplicationVersion().get(); + } else if (BlacklistNodeGetChildByName(element, + NS_LITERAL_STRING("versionRange"), + getter_AddRefs(dataNode))) { + if (BlacklistAttrToTextValue(dataNode, + NS_LITERAL_STRING("minVersion"), + dataValue)) { + mozilla::Version minV(NS_ConvertUTF16toUTF8(dataValue).get()); + if (minV > zeroV && appV < minV) { + // The version of the application is less than the minimal version + // this blocklist entry applies to, so we can just ignore it by + // returning false and letting the caller deal with it. + return false; + } + } + + if (BlacklistAttrToTextValue(dataNode, + NS_LITERAL_STRING("maxVersion"), + dataValue)) { + mozilla::Version maxV(NS_ConvertUTF16toUTF8(dataValue).get()); + if (maxV > zeroV && appV > maxV) { + // The version of the application is more than the maximal version + // this blocklist entry applies to, so we can just ignore it by + // returning false and letting the caller deal with it. + return false; + } + } + } + // WINNT 6.0 if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("os"), getter_AddRefs(dataNode))) { @@ -528,9 +594,9 @@ BlacklistEntriesToDriverInfo(nsIDOMHTMLCollection* aBlacklistEntries, GfxDriverInfo di; if (BlacklistEntryToDriverInfo(blacklistEntry, di)) { aDriverInfo[i] = di; + // Prevent di falling out of scope from destroying the devices. + di.mDeleteDevices = false; } - // Prevent di falling out of scope from destroying the devices. - di.mDeleteDevices = false; } } } @@ -609,6 +675,21 @@ GfxInfoBase::FindBlocklistedDeviceInList(const nsTArray& info, uint32_t i = 0; for (; i < info.Length(); i++) { + // Do the operating system check first, no point in getting the driver + // info if we won't need to use it. Note we also catch and skips the + // application version mismatches that would leave operating system + // set to unknown. + if (info[i].mOperatingSystem == DRIVER_OS_UNKNOWN || + (info[i].mOperatingSystem != DRIVER_OS_ALL && + info[i].mOperatingSystem != os)) + { + continue; + } + + if (info[i].mOperatingSystemVersion && info[i].mOperatingSystemVersion != OperatingSystemVersion()) { + continue; + } + // XXX: it would be better not to do this everytime round the loop nsAutoString adapterVendorID; nsAutoString adapterDeviceID; @@ -634,17 +715,6 @@ GfxInfoBase::FindBlocklistedDeviceInList(const nsTArray& info, ParseDriverVersion(adapterDriverVersionString, &driverVersion); #endif - - if (info[i].mOperatingSystem != DRIVER_OS_ALL && - info[i].mOperatingSystem != os) - { - continue; - } - - if (info[i].mOperatingSystemVersion && info[i].mOperatingSystemVersion != OperatingSystemVersion()) { - continue; - } - if (!info[i].mAdapterVendor.Equals(GfxDriverInfo::GetDeviceVendor(VendorAll), nsCaseInsensitiveStringComparator()) && !info[i].mAdapterVendor.Equals(adapterVendorID, nsCaseInsensitiveStringComparator())) { continue; @@ -860,12 +930,13 @@ GfxInfoBase::EvaluateDownloadedBlacklist(nsTArray& aDriverInfo) nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS, nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, - nsIGfxInfo::FEATURE_DXVA, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_WEBGL_ANGLE, nsIGfxInfo::FEATURE_WEBGL_MSAA, nsIGfxInfo::FEATURE_STAGEFRIGHT, + nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION, 0 }; @@ -923,8 +994,8 @@ GfxInfoBase::LogFailure(const nsACString &failure) /* void getFailures (out unsigned long failureCount, [optional, array, size_is (failureCount)] out long indices, [array, size_is (failureCount), retval] out string failures); */ /* XPConnect method of returning arrays is very ugly. Would not recommend. */ NS_IMETHODIMP GfxInfoBase::GetFailures(uint32_t* failureCount, - int32_t** indices, - char ***failures) + int32_t** indices, + char ***failures) { MutexAutoLock lock(mMutex); @@ -975,7 +1046,7 @@ NS_IMETHODIMP GfxInfoBase::GetFailures(uint32_t* failureCount, if (!(*failures)[i]) { /* I'm too afraid to use an inline function... */ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, (*failures)); - *failureCount = i; + *failureCount = i; return NS_ERROR_OUT_OF_MEMORY; } } @@ -1015,6 +1086,24 @@ nsresult GfxInfoBase::GetInfo(JSContext* aCx, JS::MutableHandle aResu return NS_OK; } +const nsCString& +GfxInfoBase::GetApplicationVersion() +{ + static nsAutoCString version; + static bool versionInitialized = false; + if (!versionInitialized) { + // If we fail to get the version, we will not try again. + versionInitialized = true; + + // Get the version from xpcom/system/nsIXULAppInfo.idl + nsCOMPtr app = do_GetService("@mozilla.org/xre/app-info;1"); + if (app) { + app->GetVersion(version); + } + } + return version; +} + void GfxInfoBase::AddCollector(GfxInfoCollectorBase* collector) { @@ -1038,6 +1127,20 @@ GfxInfoBase::RemoveCollector(GfxInfoCollectorBase* collector) } } +NS_IMETHODIMP +GfxInfoBase::GetMonitors(JSContext* aCx, JS::MutableHandleValue aResult) +{ + JS::Rooted array(aCx, JS_NewArrayObject(aCx, 0)); + + nsresult rv = FindMonitors(aCx, array); + if (NS_FAILED(rv)) { + return rv; + } + + aResult.setObject(*array); + return NS_OK; +} + GfxInfoCollectorBase::GfxInfoCollectorBase() { GfxInfoBase::AddCollector(this); diff --git a/widget/GfxInfoBase.h b/widget/GfxInfoBase.h index 29cb480ebf..0d170b0f15 100644 --- a/widget/GfxInfoBase.h +++ b/widget/GfxInfoBase.h @@ -54,7 +54,8 @@ public: NS_IMETHOD GetFeatureSuggestedDriverVersion(int32_t aFeature, nsAString & _retval) override; NS_IMETHOD GetWebGLParameter(const nsAString & aParam, nsAString & _retval) override; - NS_IMETHOD GetFailures(uint32_t *failureCount, int32_t** indices, char ***failures) override; + NS_IMETHOD GetMonitors(JSContext* cx, JS::MutableHandleValue _retval); + NS_IMETHOD GetFailures(uint32_t *failureCount, int32_t** indices, char ***failures) override; NS_IMETHOD_(void) LogFailure(const nsACString &failure) override; NS_IMETHOD GetInfo(JSContext*, JS::MutableHandle) override; @@ -82,6 +83,13 @@ public: virtual nsString Manufacturer() { return EmptyString(); } virtual uint32_t OperatingSystemVersion() { return 0; } + // Convenience to get the application version + static const nsCString& GetApplicationVersion(); + + virtual nsresult FindMonitors(JSContext* cx, JS::HandleObject array) { + return NS_ERROR_NOT_IMPLEMENTED; + } + protected: virtual ~GfxInfoBase(); diff --git a/widget/GfxInfoX11.cpp b/widget/GfxInfoX11.cpp index 23dce8645e..74ec36621e 100644 --- a/widget/GfxInfoX11.cpp +++ b/widget/GfxInfoX11.cpp @@ -14,6 +14,7 @@ #include "prenv.h" #include "GfxInfoX11.h" +#include "mozilla/X11Util.h" namespace mozilla { namespace widget { @@ -523,6 +524,34 @@ GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) return NS_ERROR_FAILURE; } +nsresult +GfxInfo::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray) +{ +#if defined(MOZ_WIDGET_GTK) + // No display in xpcshell mode. + if (!gdk_display_get_default()) { + return NS_OK; + } +#endif + + // Note: this doesn't support Xinerama. Two physical displays will be + // reported as one monitor covering the entire virtual screen. + Display* display = DefaultXDisplay(); + Screen* screen = DefaultScreenOfDisplay(display); + + JS::Rooted obj(aCx, JS_NewPlainObject(aCx)); + + JS::Rooted screenWidth(aCx, JS::Int32Value(WidthOfScreen(screen))); + JS_SetProperty(aCx, obj, "screenWidth", screenWidth); + + JS::Rooted screenHeight(aCx, JS::Int32Value(HeightOfScreen(screen))); + JS_SetProperty(aCx, obj, "screenHeight", screenHeight); + + JS::Rooted element(aCx, JS::ObjectValue(*obj)); + JS_SetElement(aCx, aOutArray, 0, element); + return NS_OK; +} + #ifdef DEBUG // Implement nsIGfxInfoDebug diff --git a/widget/GfxInfoX11.h b/widget/GfxInfoX11.h index 04e8e2e24f..d54c16235d 100644 --- a/widget/GfxInfoX11.h +++ b/widget/GfxInfoX11.h @@ -48,6 +48,8 @@ public: NS_IMETHOD_(void) GetData() override; + nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override; + #ifdef DEBUG NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIGFXINFODEBUG diff --git a/widget/cocoa/GfxInfo.h b/widget/cocoa/GfxInfo.h index 38ac43c849..1673710583 100644 --- a/widget/cocoa/GfxInfo.h +++ b/widget/cocoa/GfxInfo.h @@ -59,6 +59,8 @@ public: virtual uint32_t OperatingSystemVersion() override { return mOSXVersion; } + nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override; + protected: virtual ~GfxInfo() {} diff --git a/widget/cocoa/GfxInfo.mm b/widget/cocoa/GfxInfo.mm index 7d0503e99f..e6e202e182 100644 --- a/widget/cocoa/GfxInfo.mm +++ b/widget/cocoa/GfxInfo.mm @@ -339,6 +339,33 @@ GfxInfo::GetFeatureStatusImpl(int32_t aFeature, return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, &os); } +nsresult +GfxInfo::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray) +{ + // Getting the refresh rate is a little hard on OS X. We could use + // CVDisplayLinkGetNominalOutputVideoRefreshPeriod, but that's a little + // involved. Ideally we could query it from vsync. For now, we leave it out. + int32_t deviceCount = 0; + for (NSScreen* screen in [NSScreen screens]) { + NSRect rect = [screen frame]; + + JS::Rooted obj(aCx, JS_NewPlainObject(aCx)); + + JS::Rooted screenWidth(aCx, JS::Int32Value((int)rect.size.width)); + JS_SetProperty(aCx, obj, "screenWidth", screenWidth); + + JS::Rooted screenHeight(aCx, JS::Int32Value((int)rect.size.height)); + JS_SetProperty(aCx, obj, "screenHeight", screenHeight); + + JS::Rooted scale(aCx, JS::NumberValue(nsCocoaUtils::GetBackingScaleFactor(screen))); + JS_SetProperty(aCx, obj, "scale", scale); + + JS::Rooted element(aCx, JS::ObjectValue(*obj)); + JS_SetElement(aCx, aOutArray, deviceCount++, element); + } + return NS_OK; +} + #ifdef DEBUG // Implement nsIGfxInfoDebug diff --git a/widget/nsIGfxInfo.idl b/widget/nsIGfxInfo.idl index 005a150e80..119ad4f31d 100644 --- a/widget/nsIGfxInfo.idl +++ b/widget/nsIGfxInfo.idl @@ -54,7 +54,19 @@ interface nsIGfxInfo : nsISupports readonly attribute boolean isGPU2Active; + /** + * Returns an array of objects describing each monitor. Guaranteed properties + * are "screenWidth" and "screenHeight". This is only implemented on Desktop. + * + * Windows additionally supplies "refreshRate" and "pseudoDisplay". + * + * OS X additionally supplies "scale". + */ + [implicit_jscontext] + jsval getMonitors(); + void getFailures( + out unsigned long failureCount, [optional, array, size_is(failureCount)] out long indices, [retval, array, size_is(failureCount)] out string failures); @@ -87,8 +99,8 @@ interface nsIGfxInfo : nsISupports const long FEATURE_WEBRTC_HW_ACCELERATION = 10; /* Whether Direct3D 11 is supported for layers. */ const long FEATURE_DIRECT3D_11_LAYERS = 11; - /* Whether DXVA is supported for video decoding. */ - const long FEATURE_DXVA = 12; + /* Whether hardware accelerated video decoding is supported. */ + const long FEATURE_HARDWARE_VIDEO_DECODING = 12; /* Whether Direct3D 11 is supported for ANGLE. */ const long FEATURE_DIRECT3D_11_ANGLE = 13; diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index 8f6ea3b77c..d5cdcde74a 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -16,6 +16,7 @@ #include "GfxDriverInfo.h" #include "mozilla/Preferences.h" #include "nsPrintfCString.h" +#include "jsapi.h" using namespace mozilla; using namespace mozilla::widget; @@ -987,28 +988,28 @@ GfxInfo::GetGfxDriverInfo() /* Bug 1151721: Black video on youtube, block DXVA for all older intel cards. */ APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(AMDRadeonHD5800), - nsIGfxInfo::FEATURE_DXVA, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions); /* Bug 1139503: DXVA crashes with ATI cards on windows 10. */ APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_WINDOWS_10, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices, - nsIGfxInfo::FEATURE_DXVA, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL, V(15,200,1006,0)); APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), GfxDriverInfo::allDevices, - nsIGfxInfo::FEATURE_DXVA, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN, V(8,15,10,2622)); APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_WINDOWS_7, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Bug1155608), - nsIGfxInfo::FEATURE_DXVA, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN_OR_EQUAL, V(8,15,10,2869)); APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, (nsAString&)GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*)GfxDriverInfo::GetDeviceFamily(Nvidia8800GTS), - nsIGfxInfo::FEATURE_DXVA, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, + nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_EQUAL, V(9,18,13,4052)); /* Bug 1137716: XXX this should really check for the matching Intel piece as well. @@ -1102,6 +1103,49 @@ GfxInfo::GetFeatureStatusImpl(int32_t aFeature, return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, &os); } +nsresult +GfxInfo::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray) +{ + int deviceCount = 0; + for (int deviceIndex = 0;; deviceIndex++) { + DISPLAY_DEVICEA device; + device.cb = sizeof(device); + if (!::EnumDisplayDevicesA(nullptr, deviceIndex, &device, 0)) { + break; + } + + if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) { + continue; + } + + DEVMODEA mode; + mode.dmSize = sizeof(mode); + mode.dmDriverExtra = 0; + if (!::EnumDisplaySettingsA(device.DeviceName, ENUM_CURRENT_SETTINGS, &mode)) { + continue; + } + + JS::Rooted obj(aCx, JS_NewPlainObject(aCx)); + + JS::Rooted screenWidth(aCx, JS::Int32Value(mode.dmPelsWidth)); + JS_SetProperty(aCx, obj, "screenWidth", screenWidth); + + JS::Rooted screenHeight(aCx, JS::Int32Value(mode.dmPelsHeight)); + JS_SetProperty(aCx, obj, "screenHeight", screenHeight); + + JS::Rooted refreshRate(aCx, JS::Int32Value(mode.dmDisplayFrequency)); + JS_SetProperty(aCx, obj, "refreshRate", refreshRate); + + JS::Rooted pseudoDisplay(aCx, + JS::BooleanValue(!!(device.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))); + JS_SetProperty(aCx, obj, "pseudoDisplay", pseudoDisplay); + + JS::Rooted element(aCx, JS::ObjectValue(*obj)); + JS_SetElement(aCx, aOutArray, deviceCount++, element); + } + return NS_OK; +} + #ifdef DEBUG // Implement nsIGfxInfoDebug diff --git a/widget/windows/GfxInfo.h b/widget/windows/GfxInfo.h index b60d61ae56..2967fec413 100644 --- a/widget/windows/GfxInfo.h +++ b/widget/windows/GfxInfo.h @@ -51,6 +51,8 @@ public: virtual uint32_t OperatingSystemVersion() override { return mWindowsVersion; } + nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override; + NS_DECL_ISUPPORTS_INHERITED #ifdef DEBUG NS_DECL_NSIGFXINFODEBUG diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp index 8547d9e55f..d8dbcd93d0 100644 --- a/xpcom/build/XPCOMInit.cpp +++ b/xpcom/build/XPCOMInit.cpp @@ -50,8 +50,6 @@ #include "nsThreadManager.h" #include "nsThreadPool.h" -#include "nsCompartmentInfo.h" - #include "xptinfo.h" #include "nsIInterfaceInfoManager.h" #include "xptiprivate.h" @@ -229,8 +227,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsMemoryInfoDumper) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsStatusReporterManager, Init) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsCompartmentInfo) - NS_GENERIC_FACTORY_CONSTRUCTOR(nsIOUtil) NS_GENERIC_FACTORY_CONSTRUCTOR(nsSecurityConsoleMessage) diff --git a/xpcom/build/XPCOMModule.inc b/xpcom/build/XPCOMModule.inc index 3b330462fe..6386bafc59 100644 --- a/xpcom/build/XPCOMModule.inc +++ b/xpcom/build/XPCOMModule.inc @@ -82,4 +82,3 @@ COMPONENT(CYCLE_COLLECTOR_LOGGER, nsCycleCollectorLoggerConstructor) COMPONENT(MESSAGE_LOOP, nsMessageLoopConstructor) COMPONENT(STATUS_REPORTER_MANAGER, nsStatusReporterManagerConstructor) - COMPONENT(COMPARTMENT_INFO, nsCompartmentInfoConstructor) \ No newline at end of file