diff --git a/browser/base/content/test/performance/PerfTestHelpers.jsm b/browser/base/content/test/performance/PerfTestHelpers.jsm new file mode 100644 index 000000000000..bf5a1543f7ab --- /dev/null +++ b/browser/base/content/test/performance/PerfTestHelpers.jsm @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var EXPORTED_SYMBOLS = ["PerfTestHelpers"]; + +const lazy = {}; + +ChromeUtils.defineModuleGetter( + lazy, + "NetUtil", + "resource://gre/modules/NetUtil.jsm" +); + +var PerfTestHelpers = { + /** + * Maps the entries in the given iterable to the given + * promise-returning task function, and waits for all returned + * promises to have resolved. At most `limit` promises may remain + * unresolved at a time. When the limit is reached, the function will + * wait for some to resolve before spawning more tasks. + */ + async throttledMapPromises(iterable, task, limit = 64) { + let pending = new Set(); + let promises = []; + for (let data of iterable) { + while (pending.size >= limit) { + await Promise.race(pending); + } + + let promise = task(data); + promises.push(promise); + if (promise) { + promise.finally(() => pending.delete(promise)); + pending.add(promise); + } + } + + return Promise.all(promises); + }, + + /** + * Returns a promise which resolves to true if the resource at the + * given URI exists, false if it doesn't. This should only be used + * with local resources, such as from resource:/chrome:/jar:/file: + * URIs. + */ + checkURIExists(uri) { + return new Promise(resolve => { + try { + let channel = lazy.NetUtil.newChannel({ + uri, + loadUsingSystemPrincipal: true, + }); + + channel.asyncOpen({ + onStartRequest(request) { + resolve(Components.isSuccessCode(request.status)); + request.cancel(Cr.NS_BINDING_ABORTED); + }, + + onStopRequest(request, status) { + // We should have already resolved from `onStartRequest`, but + // we resolve again here just as a failsafe. + resolve(Components.isSuccessCode(status)); + }, + }); + } catch (e) { + if ( + e.result != Cr.NS_ERROR_FILE_NOT_FOUND && + e.result != Cr.NS_ERROR_NOT_AVAILABLE + ) { + throw e; + } + resolve(false); + } + }); + }, +}; diff --git a/browser/base/content/test/performance/browser_startup.js b/browser/base/content/test/performance/browser_startup.js index c7bf95851d69..dddd5fe61c75 100644 --- a/browser/base/content/test/performance/browser_startup.js +++ b/browser/base/content/test/performance/browser_startup.js @@ -57,8 +57,6 @@ const startupPhases = { "before first paint": { denylist: { modules: new Set([ - "chrome://webcompat/content/data/ua_overrides.jsm", - "chrome://webcompat/content/lib/ua_overrider.jsm", "resource:///modules/AboutNewTab.jsm", "resource:///modules/BrowserUsageTelemetry.jsm", "resource:///modules/ContentCrashHandlers.jsm", @@ -86,7 +84,6 @@ const startupPhases = { "resource://gre/modules/BookmarkHTMLUtils.jsm", "resource://gre/modules/Bookmarks.jsm", "resource://gre/modules/ContextualIdentityService.jsm", - "resource://gre/modules/CrashSubmit.jsm", "resource://gre/modules/FxAccounts.jsm", "resource://gre/modules/FxAccountsStorage.jsm", "resource://gre/modules/PlacesBackups.jsm", @@ -94,10 +91,7 @@ const startupPhases = { "resource://gre/modules/PlacesSyncUtils.jsm", "resource://gre/modules/PushComponents.jsm", ]), - services: new Set([ - "@mozilla.org/browser/annotation-service;1", - "@mozilla.org/browser/nav-bookmarks-service;1", - ]), + services: new Set(["@mozilla.org/browser/nav-bookmarks-service;1"]), }, }, @@ -128,6 +122,12 @@ if ( ); } +if (AppConstants.MOZ_CRASHREPORTER) { + startupPhases["before handling user events"].denylist.modules.add( + "resource://gre/modules/CrashSubmit.jsm" + ); +} + add_task(async function() { if ( !AppConstants.NIGHTLY_BUILD && @@ -219,6 +219,29 @@ add_task(async function() { } } } + + if (denylist.modules) { + let results = await PerfTestHelpers.throttledMapPromises( + denylist.modules, + async uri => ({ + uri, + exists: await PerfTestHelpers.checkURIExists(uri), + }) + ); + + for (let { uri, exists } of results) { + ok(exists, `denylist entry ${uri} for phase "${phase}" must exist`); + } + } + + if (denylist.services) { + for (let contract of denylist.services) { + ok( + contract in Cc, + `denylist entry ${contract} for phase "${phase}" must exist` + ); + } + } } } }); diff --git a/browser/base/content/test/performance/browser_startup_content.js b/browser/base/content/test/performance/browser_startup_content.js index 539061157a0a..ec63d9bf009a 100644 --- a/browser/base/content/test/performance/browser_startup_content.js +++ b/browser/base/content/test/performance/browser_startup_content.js @@ -170,7 +170,7 @@ add_task(async function() { loadedInfo.processScripts[uri] = ""; } - checkLoadedScripts({ + await checkLoadedScripts({ loadedInfo, known: known_scripts, intermittent: intermittently_loaded_scripts, diff --git a/browser/base/content/test/performance/browser_startup_content_subframe.js b/browser/base/content/test/performance/browser_startup_content_subframe.js index 9b7ae5d43323..cb3c40feb329 100644 --- a/browser/base/content/test/performance/browser_startup_content_subframe.js +++ b/browser/base/content/test/performance/browser_startup_content_subframe.js @@ -141,7 +141,7 @@ add_task(async function() { loadedInfo.processScripts[uri] = ""; } - checkLoadedScripts({ + await checkLoadedScripts({ loadedInfo, known: known_scripts, intermittent: intermittently_loaded_scripts, diff --git a/browser/base/content/test/performance/browser_startup_images.js b/browser/base/content/test/performance/browser_startup_images.js index 0d713394f3eb..600b336dbbaa 100644 --- a/browser/base/content/test/performance/browser_startup_images.js +++ b/browser/base/content/test/performance/browser_startup_images.js @@ -82,6 +82,19 @@ add_task(async function() { return el.platforms.includes(AppConstants.platform); }); + { + let results = await PerfTestHelpers.throttledMapPromises( + knownImagesForPlatform, + async image => ({ + uri: image.file, + exists: await PerfTestHelpers.checkURIExists(image.file), + }) + ); + for (let { uri, exists } of results) { + ok(exists, `Unshown image entry ${uri} must exist`); + } + } + let loadedImages = data["image-loading"]; let shownImages = data["image-drawing"]; diff --git a/browser/base/content/test/performance/head.js b/browser/base/content/test/performance/head.js index 53d489d6059e..43fb0582d1bc 100644 --- a/browser/base/content/test/performance/head.js +++ b/browser/base/content/test/performance/head.js @@ -2,6 +2,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { AboutNewTab: "resource:///modules/AboutNewTab.jsm", + PerfTestHelpers: "resource://testing-common/PerfTestHelpers.jsm", PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm", PlacesUtils: "resource://gre/modules/PlacesUtils.jsm", UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.jsm", @@ -843,7 +844,7 @@ async function runUrlbarTest( * If true, dump the stacks for all loaded modules. Makes the output * noisy. */ -function checkLoadedScripts({ +async function checkLoadedScripts({ loadedInfo, known, intermittent, @@ -852,6 +853,32 @@ function checkLoadedScripts({ }) { let loadedList = {}; + async function checkAllExist(scriptType, list, listType) { + if (scriptType == "services") { + for (let contract of list) { + ok( + contract in Cc, + `${listType} entry ${contract} for content process startup must exist` + ); + } + } else { + let results = await PerfTestHelpers.throttledMapPromises( + list, + async uri => ({ + uri, + exists: await PerfTestHelpers.checkURIExists(uri), + }) + ); + + for (let { uri, exists } of results) { + ok( + exists, + `${listType} entry ${uri} for content process startup must exist` + ); + } + } + } + for (let scriptType in known) { loadedList[scriptType] = Object.keys(loadedInfo[scriptType]).filter(c => { if (!known[scriptType].has(c)) { @@ -880,6 +907,8 @@ function checkLoadedScripts({ ); } + await checkAllExist(scriptType, intermittent[scriptType], "intermittent"); + is( known[scriptType].size, 0, @@ -919,5 +948,7 @@ function checkLoadedScripts({ ); } } + + await checkAllExist(scriptType, forbidden[scriptType], "forbidden"); } } diff --git a/browser/base/content/test/performance/moz.build b/browser/base/content/test/performance/moz.build new file mode 100644 index 000000000000..5fc779f7c750 --- /dev/null +++ b/browser/base/content/test/performance/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +BROWSER_CHROME_MANIFESTS += [ + "browser.ini", + "hidpi/browser.ini", + "io/browser.ini", + "lowdpi/browser.ini", +] + +TESTING_JS_MODULES += [ + "PerfTestHelpers.jsm", +] diff --git a/browser/base/content/test/static/browser_all_files_referenced.js b/browser/base/content/test/static/browser_all_files_referenced.js index 04fb6dbe2dd6..111da88f801d 100644 --- a/browser/base/content/test/static/browser_all_files_referenced.js +++ b/browser/base/content/test/static/browser_all_files_referenced.js @@ -744,30 +744,13 @@ function convertToCodeURI(fileUri) { } } -function chromeFileExists(aURI) { - let available = 0; +async function chromeFileExists(aURI) { try { - let channel = NetUtil.newChannel({ - uri: aURI, - loadUsingSystemPrincipal: true, - contentPolicyType: Ci.nsIContentPolicy.TYPE_FETCH, - }); - let stream = channel.open(); - let sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( - Ci.nsIScriptableInputStream - ); - sstream.init(stream); - available = sstream.available(); - sstream.close(); + return await PerfTestHelpers.checkURIExists(aURI); } catch (e) { - if ( - e.result != Cr.NS_ERROR_FILE_NOT_FOUND && - e.result != Cr.NS_ERROR_NOT_AVAILABLE - ) { - todo(false, "Failed to check if " + aURI + "exists: " + e); - } + todo(false, `Failed to check if ${aURI} exists: ${e}`); + return false; } - return available > 0; } function findChromeUrlsFromArray(array, prefix) { @@ -875,7 +858,7 @@ add_task(async function checkAllTheFiles() { }); // Wait for all manifest to be parsed - await throttledMapPromises(manifestURIs, parseManifest); + await PerfTestHelpers.throttledMapPromises(manifestURIs, parseManifest); for (let jsm of Components.manager.getComponentJSMs()) { gReferencesFromCode.set(jsm, null); @@ -908,7 +891,9 @@ add_task(async function checkAllTheFiles() { } // Wait for all the files to have actually loaded: - await throttledMapPromises(allPromises, ([task, uri]) => task(uri)); + await PerfTestHelpers.throttledMapPromises(allPromises, ([task, uri]) => + task(uri) + ); // Keep only chrome:// files, and filter out either the devtools paths or // the non-devtools paths: @@ -1069,7 +1054,7 @@ add_task(async function checkAllTheFiles() { if ( (file.startsWith("chrome://") || file.startsWith("resource://")) && - !chromeFileExists(file) + !(await chromeFileExists(file)) ) { // Ignore chrome prefixes that have been automatically expanded. let pathParts = diff --git a/browser/base/content/test/static/browser_parsable_css.js b/browser/base/content/test/static/browser_parsable_css.js index dde51220c601..d15a61c3f426 100644 --- a/browser/base/content/test/static/browser_parsable_css.js +++ b/browser/base/content/test/static/browser_parsable_css.js @@ -437,7 +437,7 @@ add_task(async function checkAllTheCSS() { return true; }); // Wait for all manifest to be parsed - await throttledMapPromises(manifestURIs, parseManifest); + await PerfTestHelpers.throttledMapPromises(manifestURIs, parseManifest); // filter out either the devtools paths or the non-devtools paths: let isDevtools = SimpleTest.harnessParameters.subsuite == "devtools"; @@ -488,7 +488,7 @@ add_task(async function checkAllTheCSS() { } // Wait for all the files to have actually loaded: - await throttledMapPromises(allPromises, loadCSS); + await PerfTestHelpers.throttledMapPromises(allPromises, loadCSS); // Check if all the files referenced from CSS actually exist. // Files in browser/ should never be referenced outside browser/. diff --git a/browser/base/content/test/static/browser_parsable_script.js b/browser/base/content/test/static/browser_parsable_script.js index 23bbaafd03e4..fb7ac946353d 100644 --- a/browser/base/content/test/static/browser_parsable_script.js +++ b/browser/base/content/test/static/browser_parsable_script.js @@ -153,7 +153,7 @@ add_task(async function checkAllTheJS() { // We create an array of promises so we can parallelize all our parsing // and file loading activity: - await throttledMapPromises(uris, uri => { + await PerfTestHelpers.throttledMapPromises(uris, uri => { if (uriIsWhiteListed(uri)) { info("Not checking whitelisted " + uri.spec); return undefined; diff --git a/browser/base/content/test/static/head.js b/browser/base/content/test/static/head.js index 5be974349d7e..52828401249a 100644 --- a/browser/base/content/test/static/head.js +++ b/browser/base/content/test/static/head.js @@ -17,6 +17,9 @@ const ZipReader = new Components.Constructor( const IS_ALPHA = /^[a-z]+$/i; +var { PerfTestHelpers } = ChromeUtils.import( + "resource://testing-common/PerfTestHelpers.jsm" +); var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); var { OS, require } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); @@ -157,23 +160,6 @@ function fetchFile(uri) { }); } -async function throttledMapPromises(iterable, task, limit = 64) { - let promises = new Set(); - for (let data of iterable) { - while (promises.size >= limit) { - await Promise.race(promises); - } - - let promise = task(data); - if (promise) { - promise.finally(() => promises.delete(promise)); - promises.add(promise); - } - } - - await Promise.all(promises); -} - /** * Returns whether or not a word (presumably in en-US) is capitalized per * expectations. diff --git a/browser/base/moz.build b/browser/base/moz.build index ef7f61710464..ce1780aa0240 100644 --- a/browser/base/moz.build +++ b/browser/base/moz.build @@ -39,10 +39,6 @@ BROWSER_CHROME_MANIFESTS += [ "content/test/pageActions/browser.ini", "content/test/pageinfo/browser.ini", "content/test/pageStyle/browser.ini", - "content/test/performance/browser.ini", - "content/test/performance/hidpi/browser.ini", - "content/test/performance/io/browser.ini", - "content/test/performance/lowdpi/browser.ini", "content/test/permissions/browser.ini", "content/test/plugins/browser.ini", "content/test/popupNotifications/browser.ini", @@ -71,6 +67,10 @@ BROWSER_CHROME_MANIFESTS += [ "content/test/zoom/browser.ini", ] +DIRS += [ + "content/test/performance/", +] + PERFTESTS_MANIFESTS += ["content/test/perftest.ini"] DEFINES["MOZ_APP_VERSION"] = CONFIG["MOZ_APP_VERSION"] diff --git a/browser/components/urlbar/UrlbarProviderAutofill.jsm b/browser/components/urlbar/UrlbarProviderAutofill.jsm index 715536553722..8a8891fef292 100644 --- a/browser/components/urlbar/UrlbarProviderAutofill.jsm +++ b/browser/components/urlbar/UrlbarProviderAutofill.jsm @@ -778,16 +778,20 @@ class ProviderAutofill extends UrlbarProvider { queryType != QUERYTYPE.AUTOFILL_ORIGIN && queryContext.searchString.length == autofilledValue.length ) { + // Use `new URL().href` to lowercase the domain in the final completed + // URL. This isn't necessary since domains are case insensitive, but it + // looks nicer because it means the domain will remain lowercased in the + // input, and it also reflects the fact that Firefox will visit the + // lowercased name. const originalCompleteValue = new URL(finalCompleteValue).href; - // Make sure the domain is lowercased in the final URL. This isn't - // necessary since domains are case insensitive, but it looks nicer - // because it means the domain will remain lowercased in the input, and it - // also reflects the fact that Firefox will visit the lowercased name. + let strippedAutofilledValue = autofilledValue.substring( + this._strippedPrefix.length + ); finalCompleteValue = new URL( finalCompleteValue.substring( 0, - finalCompleteValue.length - autofilledValue.length - ) + autofilledValue + finalCompleteValue.length - strippedAutofilledValue.length + ) + strippedAutofilledValue ).href; // If the character case of except origin part of the original diff --git a/browser/components/urlbar/tests/unit/test_autofill_urls.js b/browser/components/urlbar/tests/unit/test_autofill_urls.js index b4eebb998ea0..dca2a2bc6dcb 100644 --- a/browser/components/urlbar/tests/unit/test_autofill_urls.js +++ b/browser/components/urlbar/tests/unit/test_autofill_urls.js @@ -814,3 +814,95 @@ add_task(async function aboutAsPrefix() { await cleanupPlaces(); }); + +// Checks a URL that has www name in history. +add_task(async function wwwHistory() { + const testData = [ + { + input: "example.com/", + visitHistory: [{ uri: "http://www.example.com/", title: "Example" }], + expected: { + autofilled: "example.com/", + completed: "http://www.example.com/", + hasAutofillTitle: true, + results: [ + context => + makeVisitResult(context, { + uri: "http://www.example.com/", + title: "Example", + heuristic: true, + }), + ], + }, + }, + { + input: "https://example.com/", + visitHistory: [{ uri: "https://www.example.com/", title: "Example" }], + expected: { + autofilled: "https://example.com/", + completed: "https://www.example.com/", + hasAutofillTitle: true, + results: [ + context => + makeVisitResult(context, { + uri: "https://www.example.com/", + title: "Example", + heuristic: true, + }), + ], + }, + }, + { + input: "https://example.com/abc", + visitHistory: [{ uri: "https://www.example.com/abc", title: "Example" }], + expected: { + autofilled: "https://example.com/abc", + completed: "https://www.example.com/abc", + hasAutofillTitle: true, + results: [ + context => + makeVisitResult(context, { + uri: "https://www.example.com/abc", + title: "Example", + heuristic: true, + }), + ], + }, + }, + { + input: "https://example.com/ABC", + visitHistory: [{ uri: "https://www.example.com/abc", title: "Example" }], + expected: { + autofilled: "https://example.com/ABC", + completed: "https://www.example.com/ABC", + hasAutofillTitle: false, + results: [ + context => + makeVisitResult(context, { + uri: "https://www.example.com/ABC", + title: "https://www.example.com/ABC", + heuristic: true, + }), + context => + makeVisitResult(context, { + uri: "https://www.example.com/abc", + title: "Example", + }), + ], + }, + }, + ]; + + for (const { input, visitHistory, expected } of testData) { + await PlacesTestUtils.addVisits(visitHistory); + const context = createContext(input, { isPrivate: false }); + await check_results({ + context, + completed: expected.completed, + autofilled: expected.autofilled, + hasAutofillTitle: expected.hasAutofillTitle, + matches: expected.results.map(f => f(context)), + }); + await cleanupPlaces(); + } +}); diff --git a/build/clang-plugin/ThreadAllows.txt b/build/clang-plugin/ThreadAllows.txt index 9e15451ea396..0ef3031d51a9 100644 --- a/build/clang-plugin/ThreadAllows.txt +++ b/build/clang-plugin/ThreadAllows.txt @@ -12,6 +12,7 @@ COM Intcpt Log COM MTA Cache I/O Cameras IPC +CanvasRenderer ChainedPipePump ChainedPipeRecv Checker Test diff --git a/gfx/config/gfxVars.h b/gfx/config/gfxVars.h index e6e3ee337483..57d5645855eb 100644 --- a/gfx/config/gfxVars.h +++ b/gfx/config/gfxVars.h @@ -92,7 +92,8 @@ class gfxVarReceiver; _(UseVP9HwDecode, bool, false) \ _(HwDecodedVideoZeroCopy, bool, false) \ _(UseDMABufSurfaceExport, bool, true) \ - _(ReuseDecoderDevice, bool, false) + _(ReuseDecoderDevice, bool, false) \ + _(UseCanvasRenderThread, bool, false) /* Add new entries above this line. */ diff --git a/gfx/ipc/CanvasManagerParent.cpp b/gfx/ipc/CanvasManagerParent.cpp index 076b2ce4fc01..694c0d605e56 100644 --- a/gfx/ipc/CanvasManagerParent.cpp +++ b/gfx/ipc/CanvasManagerParent.cpp @@ -6,6 +6,7 @@ #include "CanvasManagerParent.h" #include "mozilla/dom/WebGLParent.h" +#include "mozilla/gfx/CanvasRenderThread.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/ipc/Endpoint.h" #include "mozilla/layers/CompositorThread.h" @@ -23,9 +24,7 @@ CanvasManagerParent::ManagerSet CanvasManagerParent::sManagers; auto manager = MakeRefPtr(); - if (gfxVars::SupportsThreadsafeGL()) { - manager->Bind(std::move(aEndpoint)); - } else { + if (!gfxVars::SupportsThreadsafeGL()) { nsCOMPtr owningThread; owningThread = wr::RenderThread::GetRenderThread(); MOZ_ASSERT(owningThread); @@ -33,6 +32,16 @@ CanvasManagerParent::ManagerSet CanvasManagerParent::sManagers; owningThread->Dispatch(NewRunnableMethod&&>( "CanvasManagerParent::Bind", manager, &CanvasManagerParent::Bind, std::move(aEndpoint))); + } else if (gfxVars::UseCanvasRenderThread()) { + nsCOMPtr owningThread; + owningThread = gfx::CanvasRenderThread::GetCanvasRenderThread(); + MOZ_ASSERT(owningThread); + + owningThread->Dispatch(NewRunnableMethod&&>( + "CanvasManagerParent::Bind", manager, &CanvasManagerParent::Bind, + std::move(aEndpoint))); + } else { + manager->Bind(std::move(aEndpoint)); } } @@ -40,10 +49,12 @@ CanvasManagerParent::ManagerSet CanvasManagerParent::sManagers; MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr owningThread; - if (gfxVars::SupportsThreadsafeGL()) { - owningThread = layers::CompositorThread(); - } else { + if (!gfxVars::SupportsThreadsafeGL()) { owningThread = wr::RenderThread::GetRenderThread(); + } else if (gfxVars::UseCanvasRenderThread()) { + owningThread = gfx::CanvasRenderThread::GetCanvasRenderThread(); + } else { + owningThread = layers::CompositorThread(); } if (!owningThread) { return; diff --git a/gfx/ipc/CanvasRenderThread.cpp b/gfx/ipc/CanvasRenderThread.cpp new file mode 100644 index 000000000000..513bf0e63b92 --- /dev/null +++ b/gfx/ipc/CanvasRenderThread.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasRenderThread.h" + +#include "mozilla/BackgroundHangMonitor.h" +#include "transport/runnable_utils.h" + +namespace mozilla::gfx { + +static StaticRefPtr sCanvasRenderThread; +static mozilla::BackgroundHangMonitor* sBackgroundHangMonitor; + +CanvasRenderThread::CanvasRenderThread(RefPtr aThread) + : mThread(std::move(aThread)) {} + +CanvasRenderThread::~CanvasRenderThread() {} + +// static +CanvasRenderThread* CanvasRenderThread::Get() { return sCanvasRenderThread; } + +// static +void CanvasRenderThread::Start() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sCanvasRenderThread); + + // This is 512K, which is higher than the default 256K. + // Increased to accommodate Mesa in bug 1753340. + // + // Previously increased to 320K to avoid a stack overflow in the + // Intel Vulkan driver initialization in bug 1716120. + // + // Note: we only override it if it's limited already. + const uint32_t stackSize = + nsIThreadManager::DEFAULT_STACK_SIZE ? 512 << 10 : 0; + + RefPtr thread; + nsresult rv = NS_NewNamedThread( + "CanvasRenderer", getter_AddRefs(thread), + NS_NewRunnableFunction( + "CanvasRender::BackgroundHanSetup", + []() { + sBackgroundHangMonitor = new mozilla::BackgroundHangMonitor( + "CanvasRenderer", + /* Timeout values are powers-of-two to enable us get better + data. 128ms is chosen for transient hangs because 8Hz should + be the minimally acceptable goal for Compositor + responsiveness (normal goal is 60Hz). */ + 128, + /* 2048ms is chosen for permanent hangs because it's longer than + * most Compositor hangs seen in the wild, but is short enough + * to not miss getting native hang stacks. */ + 2048); + nsCOMPtr thread = NS_GetCurrentThread(); + nsThread* nsthread = static_cast(thread.get()); + nsthread->SetUseHangMonitor(true); + nsthread->SetPriority(nsISupportsPriority::PRIORITY_HIGH); + }), + stackSize); + + if (NS_FAILED(rv)) { + return; + } + + sCanvasRenderThread = new CanvasRenderThread(thread); +} + +// static +void CanvasRenderThread::ShutDown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(sCanvasRenderThread); + + layers::SynchronousTask task("CanvasRenderThread"); + RefPtr runnable = + WrapRunnable(RefPtr(sCanvasRenderThread.get()), + &CanvasRenderThread::ShutDownTask, &task); + sCanvasRenderThread->PostRunnable(runnable.forget()); + task.Wait(); + + sCanvasRenderThread = nullptr; +} + +void CanvasRenderThread::ShutDownTask(layers::SynchronousTask* aTask) { + layers::AutoCompleteTask complete(aTask); + MOZ_ASSERT(IsInCanvasRenderThread()); +} + +// static +bool CanvasRenderThread::IsInCanvasRenderThread() { + return sCanvasRenderThread && + sCanvasRenderThread->mThread == NS_GetCurrentThread(); +} + +// static +already_AddRefed CanvasRenderThread::GetCanvasRenderThread() { + nsCOMPtr thread; + if (sCanvasRenderThread) { + thread = sCanvasRenderThread->mThread; + } + return thread.forget(); +} + +void CanvasRenderThread::PostRunnable(already_AddRefed aRunnable) { + nsCOMPtr runnable = aRunnable; + mThread->Dispatch(runnable.forget()); +} + +} // namespace mozilla::gfx diff --git a/gfx/ipc/CanvasRenderThread.h b/gfx/ipc/CanvasRenderThread.h new file mode 100644 index 000000000000..ba06931de4b4 --- /dev/null +++ b/gfx/ipc/CanvasRenderThread.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _include_gfx_ipc_CanvasRenderThread_h__ +#define _include_gfx_ipc_CanvasRenderThread_h__ + +#include "mozilla/DataMutex.h" +#include "mozilla/layers/SynchronousTask.h" +#include "mozilla/StaticPtr.h" +#include "nsISupportsImpl.h" +#include "nsThread.h" + +namespace mozilla::gfx { + +class CanvasRenderThread final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( + CanvasRenderThread) + + public: + /// Can be called from any thread. + static CanvasRenderThread* Get(); + + /// Can only be called from the main thread. + static void Start(); + + /// Can only be called from the main thread. + static void ShutDown(); + + /// Can be called from any thread. + static bool IsInCanvasRenderThread(); + + /// Can be called from any thread. + static already_AddRefed GetCanvasRenderThread(); + + private: + explicit CanvasRenderThread(RefPtr aThread); + ~CanvasRenderThread(); + + void ShutDownTask(layers::SynchronousTask* aTask); + void PostRunnable(already_AddRefed aRunnable); + + RefPtr const mThread; +}; + +} // namespace mozilla::gfx + +#endif // _include_gfx_ipc_CanvasRenderThread_h__ diff --git a/gfx/ipc/GPUParent.cpp b/gfx/ipc/GPUParent.cpp index a285aaaa4956..62e1a2e08689 100644 --- a/gfx/ipc/GPUParent.cpp +++ b/gfx/ipc/GPUParent.cpp @@ -38,6 +38,7 @@ #include "mozilla/TimeStamp.h" #include "mozilla/dom/MemoryReportRequest.h" #include "mozilla/gfx/2D.h" +#include "mozilla/gfx/CanvasRenderThread.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/glean/GleanMetrics.h" #include "mozilla/image/ImageMemoryReporter.h" @@ -360,6 +361,9 @@ mozilla::ipc::IPCResult GPUParent::RecvInit( // Make sure to do this *after* we update gfxVars above. if (gfxVars::UseWebRender()) { + if (gfxVars::UseCanvasRenderThread()) { + gfx::CanvasRenderThread::Start(); + } wr::RenderThread::Start(aWrNamespace); image::ImageMemoryReporter::InitForWebRender(); } @@ -695,6 +699,9 @@ void GPUParent::ActorDestroy(ActorDestroyReason aWhy) { if (wr::RenderThread::Get()) { wr::RenderThread::ShutDown(); } + if (gfx::CanvasRenderThread::Get()) { + gfx::CanvasRenderThread::ShutDown(); + } #ifdef XP_WIN if (widget::WinCompositorWindowThread::Get()) { widget::WinCompositorWindowThread::ShutDown(); diff --git a/gfx/ipc/moz.build b/gfx/ipc/moz.build index fc8da9800c12..ab8fb34fcd1e 100644 --- a/gfx/ipc/moz.build +++ b/gfx/ipc/moz.build @@ -12,6 +12,7 @@ EXPORTS.mozilla += ["D3DMessageUtils.h", "GfxMessageUtils.h"] EXPORTS.mozilla.gfx += [ "CanvasManagerChild.h", "CanvasManagerParent.h", + "CanvasRenderThread.h", "CrossProcessPaint.h", "GPUChild.h", "GPUParent.h", @@ -39,6 +40,7 @@ EXPORTS.mozilla.widget += [ UNIFIED_SOURCES += [ "CanvasManagerChild.cpp", "CanvasManagerParent.cpp", + "CanvasRenderThread.cpp", "CompositorSession.cpp", "CompositorWidgetVsyncObserver.cpp", "CrossProcessPaint.cpp", diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index da1a831c0603..2cef526b43e5 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -22,6 +22,7 @@ #include "mozilla/gfx/GraphicsMessages.h" #include "mozilla/gfx/CanvasManagerChild.h" #include "mozilla/gfx/CanvasManagerParent.h" +#include "mozilla/gfx/CanvasRenderThread.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPrefs_accessibility.h" #include "mozilla/StaticPrefs_apz.h" @@ -1291,6 +1292,9 @@ void gfxPlatform::InitLayersIPC() { #endif if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS) && UseWebRender()) { RemoteTextureMap::Init(); + if (gfxVars::UseCanvasRenderThread()) { + gfx::CanvasRenderThread::Start(); + } wr::RenderThread::Start(GPUProcessManager::Get()->AllocateNamespace()); image::ImageMemoryReporter::InitForWebRender(); } @@ -1340,6 +1344,9 @@ void gfxPlatform::ShutdownLayersIPC() { nsDependentCString( StaticPrefs::GetPrefName_gfx_webrender_blob_tile_size())); } + if (gfx::CanvasRenderThread::Get()) { + gfx::CanvasRenderThread::ShutDown(); + } #if defined(XP_WIN) widget::WinWindowOcclusionTracker::ShutDown(); #endif @@ -2854,6 +2861,10 @@ void gfxPlatform::InitWebGLConfig() { threadsafeGL &= !StaticPrefs::webgl_threadsafe_gl_force_disabled_AtStartup(); gfxVars::SetSupportsThreadsafeGL(threadsafeGL); + bool useCanvasRenderThread = + threadsafeGL && StaticPrefs::webgl_use_canvas_render_thread_AtStartup(); + gfxVars::SetUseCanvasRenderThread(useCanvasRenderThread); + if (kIsAndroid) { // Don't enable robust buffer access on Adreno 630 devices. // It causes the linking of some shaders to fail. See bug 1485441. @@ -3520,6 +3531,9 @@ void gfxPlatform::DisableGPUProcess() { RemoteTextureMap::Init(); if (gfxVars::UseWebRender()) { + if (gfxVars::UseCanvasRenderThread()) { + gfx::CanvasRenderThread::Start(); + } // We need to initialize the parent process to prepare for WebRender if we // did not end up disabling it, despite losing the GPU process. wr::RenderThread::Start(GPUProcessManager::Get()->AllocateNamespace()); diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 8c6c62facc14..b5c0a23e17b1 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -13480,6 +13480,11 @@ value: false mirror: once +- name: webgl.use-canvas-render-thread + type: bool + value: @IS_NOT_ANDROID@ + mirror: once + - name: webgl.override-unmasked-renderer type: String value: ""