diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
index 6c4707696eb5..e0a150b50b6e 100644
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -523,6 +523,10 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks {
pointer-events: none;
}
+#urlbar[pageproxystate="invalid"] > #identity-box > #notification-popup-box {
+ pointer-events: auto;
+}
+
#identity-icon-labels {
max-width: 18em;
}
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 83afc9b5d8d1..52ea15f5a528 100755
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2252,6 +2252,26 @@ function BrowserViewSourceOfDocument(aArgsOrDocument) {
let inTab = Services.prefs.getBoolPref("view_source.tab");
if (inTab) {
let tabBrowser = gBrowser;
+ let forceNotRemote = false;
+ if (!tabBrowser) {
+ if (!args.browser) {
+ throw new Error("BrowserViewSourceOfDocument should be passed the " +
+ "subject browser if called from a window without " +
+ "gBrowser defined.");
+ }
+ forceNotRemote = !args.browser.isRemoteBrowser;
+ } else {
+ // Some internal URLs (such as specific chrome: and about: URLs that are
+ // not yet remote ready) cannot be loaded in a remote browser. View
+ // source in tab expects the new view source browser's remoteness to match
+ // that of the original URL, so disable remoteness if necessary for this
+ // URL.
+ let contentProcess = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
+ forceNotRemote =
+ gMultiProcessBrowser &&
+ !E10SUtils.canLoadURIInProcess(args.URL, contentProcess)
+ }
+
// In the case of sidebars and chat windows, gBrowser is defined but null,
// because no #content element exists. For these cases, we need to find
// the most recent browser window.
@@ -2261,15 +2281,7 @@ function BrowserViewSourceOfDocument(aArgsOrDocument) {
let browserWindow = RecentWindow.getMostRecentBrowserWindow();
tabBrowser = browserWindow.gBrowser;
}
- // Some internal URLs (such as specific chrome: and about: URLs that are
- // not yet remote ready) cannot be loaded in a remote browser. View
- // source in tab expects the new view source browser's remoteness to match
- // that of the original URL, so disable remoteness if necessary for this
- // URL.
- let contentProcess = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
- let forceNotRemote =
- gMultiProcessBrowser &&
- !E10SUtils.canLoadURIInProcess(args.URL, contentProcess);
+
// `viewSourceInBrowser` will load the source content from the page
// descriptor for the tab (when possible) or fallback to the network if
// that fails. Either way, the view source module will manage the tab's
@@ -2322,7 +2334,8 @@ function BrowserViewSource(browser) {
// initialTab - name of the initial tab to display, or null for the first tab
// imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
// frameOuterWindowID - the id of the frame that the context menu opened in; can be null/omitted
-function BrowserPageInfo(documentURL, initialTab, imageElement, frameOuterWindowID) {
+// browser - the browser containing the document we're interested in inspecting; can be null/omitted
+function BrowserPageInfo(documentURL, initialTab, imageElement, frameOuterWindowID, browser) {
if (documentURL instanceof HTMLDocument) {
Deprecated.warning("Please pass the location URL instead of the document " +
"to BrowserPageInfo() as the first argument.",
@@ -2330,7 +2343,7 @@ function BrowserPageInfo(documentURL, initialTab, imageElement, frameOuterWindow
documentURL = documentURL.location;
}
- let args = { initialTab, imageElement, frameOuterWindowID };
+ let args = { initialTab, imageElement, frameOuterWindowID, browser };
var windows = Services.wm.getEnumerator("Browser:page-info");
documentURL = documentURL || window.gBrowser.selectedBrowser.currentURI.spec;
diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul
index de010d8151f2..701df31dfc57 100644
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -714,46 +714,6 @@
pageproxystate="invalid"
onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'"
onblur="setTimeout(() => { document.getElementById('identity-box').style.MozUserFocus = ''; }, 0);">
-
+ //
+ //
+ this.xulPanelWrapper = this._createXulPanelWrapper();
+ let inner = this.doc.createElementNS(XHTML_NS, "div");
+ inner.classList.add("tooltip-xul-wrapper-inner");
+
+ this.doc.documentElement.appendChild(this.xulPanelWrapper);
+ this.xulPanelWrapper.appendChild(inner);
+ inner.appendChild(this.container);
+ } else if (this._isXUL()) {
this.doc.documentElement.appendChild(this.container);
} else {
// In non-XUL context the container is ready to use as is.
@@ -224,6 +262,13 @@ HTMLTooltip.prototype = {
return this.container.querySelector(".tooltip-arrow");
},
+ /**
+ * Retrieve the displayed position used for the tooltip. Null if the tooltip is hidden.
+ */
+ get position() {
+ return this.isVisible() ? this._position : null;
+ },
+
/**
* Set the tooltip content element. The preferred width/height should also be
* specified here.
@@ -260,39 +305,53 @@ HTMLTooltip.prototype = {
* - {Number} x: optional, horizontal offset between the anchor and the tooltip
* - {Number} y: optional, vertical offset between the anchor and the tooltip
*/
- show: function (anchor, {position, x = 0, y = 0} = {}) {
+ show: Task.async(function* (anchor, {position, x = 0, y = 0} = {}) {
// Get anchor geometry
let anchorRect = getRelativeRect(anchor, this.doc);
- // Get document geometry
- let docRect = this.doc.documentElement.getBoundingClientRect();
+ if (this.useXulWrapper) {
+ anchorRect = this._convertToScreenRect(anchorRect);
+ }
+
+ // Get viewport size
+ let viewportRect = this._getViewportRect();
let themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type];
let preferredHeight = this.preferredHeight + themeHeight;
let {top, height, computedPosition} =
- calculateVerticalPosition(anchorRect, docRect, preferredHeight, position, y);
+ calculateVerticalPosition(anchorRect, viewportRect, preferredHeight, position, y);
- // Apply height and top information before measuring the content width (if "auto").
+ this._position = computedPosition;
+ // Apply height before measuring the content width (if width="auto").
let isTop = computedPosition === POSITION.TOP;
this.container.classList.toggle("tooltip-top", isTop);
this.container.classList.toggle("tooltip-bottom", !isTop);
this.container.style.height = height + "px";
- this.container.style.top = top + "px";
- let themeWidth = 2 * EXTRA_BORDER[this.type];
- let preferredWidth = this.preferredWidth === "auto" ?
- this._measureContainerWidth() : this.preferredWidth + themeWidth;
+ let preferredWidth;
+ if (this.preferredWidth === "auto") {
+ preferredWidth = this._measureContainerWidth();
+ } else {
+ let themeWidth = 2 * EXTRA_BORDER[this.type];
+ preferredWidth = this.preferredWidth + themeWidth;
+ }
let {left, width, arrowLeft} =
- calculateHorizontalPosition(anchorRect, docRect, preferredWidth, this.type, x);
+ calculateHorizontalPosition(anchorRect, viewportRect, preferredWidth, this.type, x);
this.container.style.width = width + "px";
- this.container.style.left = left + "px";
if (this.type === TYPE.ARROW) {
this.arrow.style.left = arrowLeft + "px";
}
+ if (this.useXulWrapper) {
+ this._showXulWrapperAt(left, top);
+ } else {
+ this.container.style.left = left + "px";
+ this.container.style.top = top + "px";
+ }
+
this.container.classList.add("tooltip-visible");
// Keep a pointer on the focused element to refocus it when hiding the tooltip.
@@ -304,14 +363,53 @@ HTMLTooltip.prototype = {
this.topWindow.addEventListener("click", this._onClick, true);
this.emit("shown");
}, 0);
+ }),
+
+ /**
+ * Calculate the rect of the viewport that limits the tooltip dimensions. When using a
+ * XUL panel wrapper, the viewport will be able to use the whole screen (excluding space
+ * reserved by the OS for toolbars etc.). Otherwise, the viewport is limited to the
+ * tooltip's document.
+ *
+ * @return {Object} DOMRect-like object with the Number properties: top, right, bottom,
+ * left, width, height
+ */
+ _getViewportRect: function () {
+ if (this.useXulWrapper) {
+ // availLeft/Top are the coordinates first pixel available on the screen for
+ // applications (excluding space dedicated for OS toolbars, menus etc...)
+ // availWidth/Height are the dimensions available to applications excluding all
+ // the OS reserved space
+ let {availLeft, availTop, availHeight, availWidth} = this.doc.defaultView.screen;
+ return {
+ top: availTop,
+ right: availLeft + availWidth,
+ bottom: availTop + availHeight,
+ left: availLeft,
+ width: availWidth,
+ height: availHeight,
+ };
+ }
+
+ return this.doc.documentElement.getBoundingClientRect();
},
_measureContainerWidth: function () {
+ let xulParent = this.container.parentNode;
+ if (this.useXulWrapper && !this.isVisible()) {
+ // Move the container out of the XUL Panel to measure it.
+ this.doc.documentElement.appendChild(this.container);
+ }
+
this.container.classList.add("tooltip-hidden");
- this.container.style.left = "0px";
this.container.style.width = "auto";
let width = this.container.getBoundingClientRect().width;
this.container.classList.remove("tooltip-hidden");
+
+ if (this.useXulWrapper && !this.isVisible()) {
+ xulParent.appendChild(this.container);
+ }
+
return width;
},
@@ -319,7 +417,7 @@ HTMLTooltip.prototype = {
* Hide the current tooltip. The event "hidden" will be fired when the tooltip
* is hidden.
*/
- hide: function () {
+ hide: Task.async(function* () {
this.doc.defaultView.clearTimeout(this.attachEventsTimer);
if (!this.isVisible()) {
return;
@@ -327,6 +425,10 @@ HTMLTooltip.prototype = {
this.topWindow.removeEventListener("click", this._onClick, true);
this.container.classList.remove("tooltip-visible");
+ if (this.useXulWrapper) {
+ yield this._hideXulWrapper();
+ }
+
this.emit("hidden");
let tooltipHasFocus = this.container.contains(this.doc.activeElement);
@@ -334,7 +436,7 @@ HTMLTooltip.prototype = {
this._focusedElement.focus();
this._focusedElement = null;
}
- },
+ }),
/**
* Check if the tooltip is currently displayed.
@@ -351,6 +453,9 @@ HTMLTooltip.prototype = {
destroy: function () {
this.hide();
this.container.remove();
+ if (this.xulPanelWrapper) {
+ this.xulPanelWrapper.remove();
+ }
},
_createContainer: function () {
@@ -407,13 +512,6 @@ HTMLTooltip.prototype = {
return false;
},
- /**
- * Check if the tooltip's owner document is a XUL document.
- */
- _isXUL: function () {
- return this.doc.documentElement.namespaceURI === XUL_NS;
- },
-
/**
* If the tootlip is configured to autofocus and a focusable element can be found,
* focus it.
@@ -427,4 +525,52 @@ HTMLTooltip.prototype = {
focusableElement.focus();
}
},
+
+ /**
+ * Check if the tooltip's owner document is a XUL document.
+ */
+ _isXUL: function () {
+ return this.doc.documentElement.namespaceURI === XUL_NS;
+ },
+
+ _createXulPanelWrapper: function () {
+ let panel = this.doc.createElementNS(XUL_NS, "panel");
+
+ // XUL panel is only a way to display DOM elements outside of the document viewport,
+ // so disable all features that impact the behavior.
+ panel.setAttribute("animate", false);
+ panel.setAttribute("consumeoutsideclicks", false);
+ panel.setAttribute("noautofocus", true);
+ panel.setAttribute("ignorekeys", true);
+
+ panel.setAttribute("level", "float");
+ panel.setAttribute("class", "tooltip-xul-wrapper");
+
+ return panel;
+ },
+
+ _showXulWrapperAt: function (left, top) {
+ let onPanelShown = listenOnce(this.xulPanelWrapper, "popupshown");
+ this.xulPanelWrapper.openPopupAtScreen(left, top, false);
+ return onPanelShown;
+ },
+
+ _hideXulWrapper: function () {
+ let onPanelHidden = listenOnce(this.xulPanelWrapper, "popuphidden");
+ this.xulPanelWrapper.hidePopup();
+ return onPanelHidden;
+ },
+
+ /**
+ * Convert from coordinates relative to the tooltip's document, to coordinates relative
+ * to the "available" screen. By "available" we mean the screen, excluding the OS bars
+ * display on screen edges.
+ */
+ _convertToScreenRect: function ({left, top, width, height}) {
+ // mozInnerScreenX/Y are the coordinates of the top left corner of the window's
+ // viewport, excluding chrome UI.
+ left += this.doc.defaultView.mozInnerScreenX;
+ top += this.doc.defaultView.mozInnerScreenY;
+ return {top, right: left + width, bottom: top + height, left, width, height};
+ },
};
diff --git a/devtools/client/storage/test/browser.ini b/devtools/client/storage/test/browser.ini
index 8b258a1da52a..6b3fa9675850 100644
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -36,6 +36,7 @@ support-files =
[browser_storage_localstorage_error.js]
[browser_storage_overflow.js]
[browser_storage_search.js]
+[browser_storage_search_keyboard_trap.js]
[browser_storage_sessionstorage_edit.js]
[browser_storage_sidebar.js]
[browser_storage_sidebar_update.js]
diff --git a/devtools/client/storage/test/browser_storage_search_keyboard_trap.js b/devtools/client/storage/test/browser_storage_search_keyboard_trap.js
new file mode 100644
index 000000000000..71dfd32c0030
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_search_keyboard_trap.js
@@ -0,0 +1,15 @@
+// Test ability to focus search field by using keyboard
+"use strict";
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-search.html");
+
+ gUI.tree.expandAll();
+ yield selectTreeItem(["localStorage", "http://test1.example.org"]);
+
+ yield focusSearchBoxUsingShortcut(gPanelWindow);
+ ok(containsFocus(gPanelWindow.document, gUI.searchBox),
+ "Focus is in a searchbox");
+
+ yield finishTests();
+});
diff --git a/devtools/client/storage/test/head.js b/devtools/client/storage/test/head.js
index 9a35d342c87e..50a076494f31 100644
--- a/devtools/client/storage/test/head.js
+++ b/devtools/client/storage/test/head.js
@@ -793,3 +793,37 @@ function* checkState(state) {
}
}
}
+
+/**
+ * Checks if document's active element is within the given element.
+ * @param {HTMLDocument} doc document with active element in question
+ * @param {DOMNode} container element tested on focus containment
+ * @return {Boolean}
+ */
+function containsFocus(doc, container) {
+ let elm = doc.activeElement;
+ while (elm) {
+ if (elm === container) {
+ return true;
+ }
+ elm = elm.parentNode;
+ }
+ return false;
+}
+
+var focusSearchBoxUsingShortcut = Task.async(function* (panelWin, callback) {
+ info("Focusing search box");
+ let searchBox = panelWin.document.getElementById("storage-searchbox");
+ let focused = once(searchBox, "focus");
+
+ panelWin.focus();
+ let strings = Services.strings.createBundle(
+ "chrome://devtools/locale/storage.properties");
+ synthesizeKeyShortcut(strings.GetStringFromName("storage.filter.key"));
+
+ yield focused;
+
+ if (callback) {
+ callback();
+ }
+});
diff --git a/devtools/client/storage/ui.js b/devtools/client/storage/ui.js
index 93a74d86d509..8b6fe58df4fe 100644
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -8,6 +8,7 @@
const {Task} = require("devtools/shared/task");
const EventEmitter = require("devtools/shared/event-emitter");
const {LocalizationHelper} = require("devtools/client/shared/l10n");
+const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
loader.lazyRequireGetter(this, "TreeWidget",
"devtools/client/shared/widgets/TreeWidget", true);
@@ -111,6 +112,15 @@ function StorageUI(front, target, panelWin, toolbox) {
this.filterItems = this.filterItems.bind(this);
this.searchBox.addEventListener("command", this.filterItems);
+ let shortcuts = new KeyShortcuts({
+ window: this._panelDoc.defaultView,
+ });
+ let key = L10N.getStr("storage.filter.key");
+ shortcuts.on(key, (name, event) => {
+ event.preventDefault();
+ this.searchBox.focus();
+ });
+
this.front.listStores().then(storageTypes => {
this.populateStorageTree(storageTypes);
}).then(null, console.error);
diff --git a/devtools/client/themes/tooltips.css b/devtools/client/themes/tooltips.css
index d90dc8e70c09..c493d91e5f5a 100644
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -109,6 +109,17 @@
overflow: hidden;
}
+.tooltip-xul-wrapper {
+ -moz-appearance: none;
+ background: transparent;
+ overflow: visible;
+ border-style: none;
+}
+
+.tooltip-xul-wrapper .tooltip-container {
+ position: absolute;
+}
+
.tooltip-top {
flex-direction: column;
}
@@ -137,6 +148,11 @@
filter: drop-shadow(0 3px 4px var(--theme-tooltip-shadow));
}
+.tooltip-xul-wrapper .tooltip-container[type="arrow"] {
+ /* When displayed in a XUL panel the drop shadow would be abruptly cut by the panel */
+ filter: none;
+}
+
.tooltip-container[type="arrow"] > .tooltip-panel {
position: relative;
flex-grow: 0;
@@ -265,7 +281,7 @@
.event-tooltip-content-box {
display: none;
- height: 54px;
+ height: 100px;
overflow: hidden;
margin-inline-end: 0;
border: 1px solid var(--theme-splitter-color);
diff --git a/devtools/server/actors/highlighters.css b/devtools/server/actors/highlighters.css
index a4bdffca63b0..34e3c337369b 100644
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -13,6 +13,17 @@
element.
*/
+:-moz-native-anonymous {
+ /*
+ Content CSS applying to the html element impact the highlighters.
+ To avoid that, possible cases have been set to initial.
+ */
+ text-transform: initial;
+ text-indent: initial;
+ letter-spacing: initial;
+ word-spacing: initial;
+}
+
:-moz-native-anonymous .highlighter-container {
--highlighter-guide-color: #08c;
--highlighter-content-color: #87ceeb;
diff --git a/devtools/server/actors/memory.js b/devtools/server/actors/memory.js
index e0636f759b22..5c41a7dc19e5 100644
--- a/devtools/server/actors/memory.js
+++ b/devtools/server/actors/memory.js
@@ -47,8 +47,8 @@ exports.MemoryActor = protocol.ActorClassWithSpec(memorySpec, {
getState: actorBridgeWithSpec("getState"),
- saveHeapSnapshot: function () {
- return this.bridge.saveHeapSnapshot();
+ saveHeapSnapshot: function (boundaries) {
+ return this.bridge.saveHeapSnapshot(boundaries);
},
takeCensus: actorBridgeWithSpec("takeCensus"),
diff --git a/devtools/server/actors/script.js b/devtools/server/actors/script.js
index ba7bd6d9795d..d5aa32479803 100644
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -620,7 +620,7 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
}
this._state = "attached";
- this._debuggerSourcesSeen = new Set();
+ this._debuggerSourcesSeen = new WeakSet();
Object.assign(this._options, aRequest.options || {});
this.sources.setOptions(this._options);
diff --git a/devtools/server/performance/memory.js b/devtools/server/performance/memory.js
index 35638b515239..77ce348cc190 100644
--- a/devtools/server/performance/memory.js
+++ b/devtools/server/performance/memory.js
@@ -120,8 +120,8 @@ var Memory = exports.Memory = Class({
*/
_onWindowReady: function ({ isTopLevel }) {
if (this.state == "attached") {
+ this._clearDebuggees();
if (isTopLevel && this.isRecordingAllocations()) {
- this._clearDebuggees();
this._frameCache.initFrames();
}
this.dbg.addDebuggees();
@@ -140,15 +140,19 @@ var Memory = exports.Memory = Class({
* Save a heap snapshot scoped to the current debuggees' portion of the heap
* graph.
*
+ * @param {Object|null} boundaries
+ *
* @returns {String} The snapshot id.
*/
- saveHeapSnapshot: expectState("attached", function () {
+ saveHeapSnapshot: expectState("attached", function (boundaries = null) {
// If we are observing the whole process, then scope the snapshot
// accordingly. Otherwise, use the debugger's debuggees.
- const opts = this.parent instanceof ChromeActor || this.parent instanceof ChildProcessActor
- ? { runtime: true }
- : { debugger: this.dbg };
- const path = ThreadSafeChromeUtils.saveHeapSnapshot(opts);
+ if (!boundaries) {
+ boundaries = this.parent instanceof ChromeActor || this.parent instanceof ChildProcessActor
+ ? { runtime: true }
+ : { debugger: this.dbg };
+ }
+ const path = ThreadSafeChromeUtils.saveHeapSnapshot(boundaries);
return HeapSnapshotFileUtils.getSnapshotIdFromPath(path);
}, "saveHeapSnapshot"),
diff --git a/devtools/shared/fronts/memory.js b/devtools/shared/fronts/memory.js
index ac162b16b478..d7a6a3108e2c 100644
--- a/devtools/shared/fronts/memory.js
+++ b/devtools/shared/fronts/memory.js
@@ -35,10 +35,14 @@ const MemoryFront = protocol.FrontClassWithSpec(memorySpec, {
* Always force a bulk data copy of the saved heap snapshot, even when
* the server and client share a file system.
*
+ * @params {Object|undefined} options.boundaries
+ * The boundaries for the heap snapshot. See
+ * ThreadSafeChromeUtils.webidl for more details.
+ *
* @returns Promise
*/
saveHeapSnapshot: protocol.custom(Task.async(function* (options = {}) {
- const snapshotId = yield this._saveHeapSnapshotImpl();
+ const snapshotId = yield this._saveHeapSnapshotImpl(options.boundaries);
if (!options.forceCopy &&
(yield HeapSnapshotFileUtils.haveHeapSnapshotTempFile(snapshotId))) {
diff --git a/devtools/shared/specs/memory.js b/devtools/shared/specs/memory.js
index b8dea3962c5e..3bff6dc380c9 100644
--- a/devtools/shared/specs/memory.js
+++ b/devtools/shared/specs/memory.js
@@ -111,6 +111,9 @@ const memorySpec = generateActorSpec({
response: { value: RetVal("number") }
},
saveHeapSnapshot: {
+ request: {
+ boundaries: Arg(0, "nullable:json")
+ },
response: {
snapshotId: RetVal("string")
}
diff --git a/dom/media/NextFrameSeekTask.cpp b/dom/media/NextFrameSeekTask.cpp
index e97acedc6c2d..a9e9fd5d5e42 100644
--- a/dom/media/NextFrameSeekTask.cpp
+++ b/dom/media/NextFrameSeekTask.cpp
@@ -50,12 +50,10 @@ NextFrameSeekTask::NextFrameSeekTask(const void* aDecoderID,
, mAudioQueue(aAudioQueue)
, mVideoQueue(aVideoQueue)
, mCurrentTimeBeforeSeek(aCurrentMediaTime)
- , mHasAudio(aInfo.HasAudio())
- , mHasVideo(aInfo.HasVideo())
, mDuration(aDuration)
{
AssertOwnerThread();
- MOZ_ASSERT(HasVideo());
+ MOZ_ASSERT(aInfo.HasVideo());
// Configure MediaDecoderReaderWrapper.
SetCallbacks();
@@ -67,20 +65,6 @@ NextFrameSeekTask::~NextFrameSeekTask()
MOZ_ASSERT(mIsDiscarded);
}
-bool
-NextFrameSeekTask::HasAudio() const
-{
- AssertOwnerThread();
- return mHasAudio;
-}
-
-bool
-NextFrameSeekTask::HasVideo() const
-{
- AssertOwnerThread();
- return mHasVideo;
-}
-
void
NextFrameSeekTask::Discard()
{
@@ -194,10 +178,10 @@ bool
NextFrameSeekTask::IsVideoDecoding() const
{
AssertOwnerThread();
- return HasVideo() && !mIsVideoQueueFinished;
+ return !mIsVideoQueueFinished;
}
-nsresult
+void
NextFrameSeekTask::EnsureVideoDecodeTaskQueued()
{
AssertOwnerThread();
@@ -207,11 +191,10 @@ NextFrameSeekTask::EnsureVideoDecodeTaskQueued()
if (!IsVideoDecoding() ||
mReader->IsRequestingVideoData() ||
mReader->IsWaitingVideoData()) {
- return NS_OK;
+ return;
}
RequestVideoData();
- return NS_OK;
}
const char*
@@ -248,9 +231,7 @@ NextFrameSeekTask::IsAudioSeekComplete()
// Just make sure that we are not requesting or waiting for audio data. We
// don't really need to get an decoded audio data or get EOS here.
- return
- !HasAudio() ||
- (Exists() && !mReader->IsRequestingAudioData() && !mReader->IsWaitingAudioData());
+ return !mReader->IsRequestingAudioData() && !mReader->IsWaitingAudioData();
}
bool
@@ -260,8 +241,7 @@ NextFrameSeekTask::IsVideoSeekComplete()
SAMPLE_LOG("IsVideoSeekComplete() curTarVal=%d vqFin=%d vqSz=%d",
mSeekJob.Exists(), mIsVideoQueueFinished, !!mSeekedVideoData);
- return
- !HasVideo() || (Exists() && (mIsVideoQueueFinished || mSeekedVideoData));
+ return mIsVideoQueueFinished || mSeekedVideoData;
}
void
@@ -272,12 +252,9 @@ NextFrameSeekTask::CheckIfSeekComplete()
const bool audioSeekComplete = IsAudioSeekComplete();
const bool videoSeekComplete = IsVideoSeekComplete();
- if (HasVideo() && !videoSeekComplete) {
+ if (!videoSeekComplete) {
// We haven't reached the target. Ensure we have requested another sample.
- if (NS_FAILED(EnsureVideoDecodeTaskQueued())) {
- DECODER_WARN("Failed to request video during seek");
- RejectIfExist(__func__);
- }
+ EnsureVideoDecodeTaskQueued();
}
SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d",
@@ -294,6 +271,7 @@ NextFrameSeekTask::OnAudioDecoded(MediaData* aAudioSample)
{
AssertOwnerThread();
MOZ_ASSERT(aAudioSample);
+ MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
// The MDSM::mDecodedAudioEndTime will be updated once the whole SeekTask is
// resolved.
@@ -303,11 +281,6 @@ NextFrameSeekTask::OnAudioDecoded(MediaData* aAudioSample)
aAudioSample->GetEndTime(),
aAudioSample->mDiscontinuity);
- if (!Exists()) {
- // We've received a sample from a previous decode. Discard it.
- return;
- }
-
// We accept any audio data here.
mSeekedAudioData = aAudioSample;
@@ -318,12 +291,9 @@ void
NextFrameSeekTask::OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
{
AssertOwnerThread();
- SAMPLE_LOG("OnAudioNotDecoded (aReason=%u)", aReason);
+ MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
- if (!Exists()) {
- // We've received a sample from a previous decode. Discard it.
- return;
- }
+ SAMPLE_LOG("OnAudioNotDecoded (aReason=%u)", aReason);
// We don't really handle audio deocde error here. Let MDSM to trigger further
// audio decoding tasks if it needs to play audio, and MDSM will then receive
@@ -337,6 +307,7 @@ NextFrameSeekTask::OnVideoDecoded(MediaData* aVideoSample)
{
AssertOwnerThread();
MOZ_ASSERT(aVideoSample);
+ MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
// The MDSM::mDecodedVideoEndTime will be updated once the whole SeekTask is
// resolved.
@@ -346,11 +317,6 @@ NextFrameSeekTask::OnVideoDecoded(MediaData* aVideoSample)
aVideoSample->GetEndTime(),
aVideoSample->mDiscontinuity);
- if (!Exists()) {
- // We've received a sample from a previous decode. Discard it.
- return;
- }
-
if (aVideoSample->mTime > mCurrentTimeBeforeSeek) {
mSeekedVideoData = aVideoSample;
}
@@ -362,12 +328,9 @@ void
NextFrameSeekTask::OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
{
AssertOwnerThread();
- SAMPLE_LOG("OnVideoNotDecoded (aReason=%u)", aReason);
+ MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
- if (!Exists()) {
- // We've received a sample from a previous decode. Discard it.
- return;
- }
+ SAMPLE_LOG("OnVideoNotDecoded (aReason=%u)", aReason);
if (aReason == MediaDecoderReader::DECODE_ERROR) {
if (mVideoQueue.GetSize() > 0) {
@@ -389,14 +352,7 @@ NextFrameSeekTask::OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReaso
// If the decoder is waiting for data, we tell it to call us back when the
// data arrives.
if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
- MOZ_ASSERT(mReader->IsWaitForDataSupported(),
- "Readers that send WAITING_FOR_DATA need to implement WaitForData");
mReader->WaitForData(MediaData::VIDEO_DATA);
-
- // We are out of data to decode and will enter buffering mode soon.
- // We want to play the frames we have already decoded, so we stop pre-rolling
- // and ensure that loadeddata is fired as required.
- mNeedToStopPrerollingVideo = true;
return;
}
diff --git a/dom/media/NextFrameSeekTask.h b/dom/media/NextFrameSeekTask.h
index 18ee1240169a..5ee281f3e517 100644
--- a/dom/media/NextFrameSeekTask.h
+++ b/dom/media/NextFrameSeekTask.h
@@ -43,13 +43,9 @@ public:
private:
~NextFrameSeekTask();
- bool HasAudio() const;
-
- bool HasVideo() const;
-
bool IsVideoDecoding() const;
- nsresult EnsureVideoDecodeTaskQueued();
+ void EnsureVideoDecodeTaskQueued();
const char* VideoRequestStatus();
@@ -87,8 +83,6 @@ private:
* Internal state.
*/
const int64_t mCurrentTimeBeforeSeek;
- const bool mHasAudio;
- const bool mHasVideo;
media::TimeUnit mDuration;
MediaEventListener mAudioCallback;
diff --git a/gfx/layers/Compositor.h b/gfx/layers/Compositor.h
index 31db7b2b5a41..8b189091505f 100644
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -200,7 +200,7 @@ public:
virtual already_AddRefed
CreateDataTextureSourceAround(gfx::DataSourceSurface* aSurface) { return nullptr; }
- virtual bool Initialize() = 0;
+ virtual bool Initialize(nsCString* const out_failureReason) = 0;
virtual void Destroy();
bool IsDestroyed() const { return mIsDestroyed; }
diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp
index 962f48a46fad..f941ca9e263c 100644
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -799,15 +799,13 @@ Layer::CalculateScissorRect(const RenderTargetIntRect& aCurrentScissorRect)
return currentClip;
}
- if (GetVisibleRegion().IsEmpty()) {
+ if (GetLocalVisibleRegion().IsEmpty() &&
+ !(AsLayerComposite() && AsLayerComposite()->NeedToDrawCheckerboarding())) {
// When our visible region is empty, our parent may not have created the
// intermediate surface that we would require for correct clipping; however,
// this does not matter since we are invisible.
- // Note that we do not use GetLocalVisibleRegion(), because that can be
- // empty for a layer whose rendered contents have been async-scrolled
- // completely offscreen, but for which we still need to draw a
- // checkerboarding backround color, and calculating an empty scissor rect
- // for such a layer would prevent that (see bug 1247452 comment 10).
+ // Make sure we still compute a clip rect if we want to draw checkboarding
+ // for this layer, since we want to do this even if the layer is invisible.
return RenderTargetIntRect(currentClip.TopLeft(), RenderTargetIntSize(0, 0));
}
diff --git a/gfx/layers/basic/BasicCompositor.cpp b/gfx/layers/basic/BasicCompositor.cpp
index 696b82fc15e2..e7caf61400ad 100644
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -96,7 +96,7 @@ BasicCompositor::~BasicCompositor()
}
bool
-BasicCompositor::Initialize()
+BasicCompositor::Initialize(nsCString* const out_failureReason)
{
return mWidget ? mWidget->InitCompositor(this) : false;
};
diff --git a/gfx/layers/basic/BasicCompositor.h b/gfx/layers/basic/BasicCompositor.h
index 06455447a0b2..4f607dcda6f0 100644
--- a/gfx/layers/basic/BasicCompositor.h
+++ b/gfx/layers/basic/BasicCompositor.h
@@ -50,7 +50,7 @@ public:
virtual BasicCompositor* AsBasicCompositor() override { return this; }
- virtual bool Initialize() override;
+ virtual bool Initialize(nsCString* const out_failureReason) override;
virtual void DetachWidget() override;
diff --git a/gfx/layers/composite/ContainerLayerComposite.cpp b/gfx/layers/composite/ContainerLayerComposite.cpp
index 2dc3123e2b5e..9e891ab693af 100755
--- a/gfx/layers/composite/ContainerLayerComposite.cpp
+++ b/gfx/layers/composite/ContainerLayerComposite.cpp
@@ -53,24 +53,6 @@ namespace layers {
using namespace gfx;
-static bool
-LayerHasCheckerboardingAPZC(Layer* aLayer, Color* aOutColor)
-{
- for (LayerMetricsWrapper i(aLayer, LayerMetricsWrapper::StartAt::BOTTOM); i; i = i.GetParent()) {
- if (!i.Metrics().IsScrollable()) {
- continue;
- }
- if (i.GetApzc() && i.GetApzc()->IsCurrentlyCheckerboarding()) {
- if (aOutColor) {
- *aOutColor = i.Metadata().GetBackgroundColor();
- }
- return true;
- }
- break;
- }
- return false;
-}
-
static void DrawLayerInfo(const RenderTargetIntRect& aClipRect,
LayerManagerComposite* aManager,
Layer* aLayer)
@@ -357,15 +339,6 @@ ContainerRenderVR(ContainerT* aContainer,
DUMP("<<< ContainerRenderVR [%p]\n", aContainer);
}
-static bool
-NeedToDrawCheckerboardingForLayer(Layer* aLayer, Color* aOutCheckerboardingColor)
-{
- return (aLayer->Manager()->AsyncPanZoomEnabled() &&
- aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE) &&
- aLayer->IsOpaqueForVisibility() &&
- LayerHasCheckerboardingAPZC(aLayer, aOutCheckerboardingColor);
-}
-
/* all of the prepared data that we need in RenderLayer() */
struct PreparedData
{
@@ -412,7 +385,7 @@ ContainerPrepare(ContainerT* aContainer,
// may be null which is not allowed.
if (!layerToRender->GetLayer()->AsContainerLayer()) {
if (!layerToRender->GetLayer()->IsVisible() &&
- !NeedToDrawCheckerboardingForLayer(layerToRender->GetLayer(), nullptr)) {
+ !layerToRender->NeedToDrawCheckerboarding(nullptr)) {
CULLING_LOG("Sublayer %p has no effective visible region\n", layerToRender->GetLayer());
continue;
}
@@ -635,7 +608,7 @@ RenderLayers(ContainerT* aContainer,
}
Color color;
- if (NeedToDrawCheckerboardingForLayer(layer, &color)) {
+ if (layerToRender->NeedToDrawCheckerboarding(&color)) {
if (gfxPrefs::APZHighlightCheckerboardedAreas()) {
color = Color(255 / 255.f, 188 / 255.f, 217 / 255.f, 1.f); // "Cotton Candy"
}
@@ -967,7 +940,6 @@ RefLayerComposite::Prepare(const RenderTargetIntRect& aClipRect)
ContainerPrepare(this, mCompositeManager, aClipRect);
}
-
void
RefLayerComposite::CleanupResources()
{
diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp
index d9e08bf1a21c..d3f35696ad87 100644
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -22,6 +22,7 @@
#include "TiledContentHost.h"
#include "Units.h" // for ScreenIntRect
#include "UnitTransforms.h" // for ViewAs
+#include "apz/src/AsyncPanZoomController.h" // for AsyncPanZoomController
#include "gfxPrefs.h" // for gfxPrefs
#ifdef XP_MACOSX
#include "gfxPlatformMac.h"
@@ -1603,6 +1604,35 @@ LayerComposite::HasStaleCompositor() const
return mCompositeManager->GetCompositor() != mCompositor;
}
+static bool
+LayerHasCheckerboardingAPZC(Layer* aLayer, Color* aOutColor)
+{
+ bool answer = false;
+ for (LayerMetricsWrapper i(aLayer, LayerMetricsWrapper::StartAt::BOTTOM); i; i = i.GetParent()) {
+ if (!i.Metrics().IsScrollable()) {
+ continue;
+ }
+ if (i.GetApzc() && i.GetApzc()->IsCurrentlyCheckerboarding()) {
+ if (aOutColor) {
+ *aOutColor = i.Metadata().GetBackgroundColor();
+ }
+ answer = true;
+ break;
+ }
+ break;
+ }
+ return answer;
+}
+
+bool
+LayerComposite::NeedToDrawCheckerboarding(gfx::Color* aOutCheckerboardingColor)
+{
+ return GetLayer()->Manager()->AsyncPanZoomEnabled() &&
+ (GetLayer()->GetContentFlags() & Layer::CONTENT_OPAQUE) &&
+ GetLayer()->IsOpaqueForVisibility() &&
+ LayerHasCheckerboardingAPZC(GetLayer(), aOutCheckerboardingColor);
+}
+
#ifndef MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS
/*static*/ bool
diff --git a/gfx/layers/composite/LayerManagerComposite.h b/gfx/layers/composite/LayerManagerComposite.h
index c324446d07da..7e68e398fa40 100644
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -560,6 +560,12 @@ public:
*/
virtual nsIntRegion GetFullyRenderedRegion();
+ /**
+ * Return true if a checkerboarding background color needs to be drawn
+ * for this layer.
+ */
+ bool NeedToDrawCheckerboarding(gfx::Color* aOutCheckerboardingColor = nullptr);
+
protected:
gfx::Matrix4x4 mShadowTransform;
LayerIntRegion mShadowVisibleRegion;
diff --git a/gfx/layers/d3d11/CompositorD3D11.cpp b/gfx/layers/d3d11/CompositorD3D11.cpp
index 018d817a6e61..e8ed9bf42cef 100644
--- a/gfx/layers/d3d11/CompositorD3D11.cpp
+++ b/gfx/layers/d3d11/CompositorD3D11.cpp
@@ -195,7 +195,7 @@ CompositorD3D11::~CompositorD3D11()
}
bool
-CompositorD3D11::Initialize()
+CompositorD3D11::Initialize(nsCString* const out_failureReason)
{
ScopedGfxFeatureReporter reporter("D3D11 Layers");
@@ -204,6 +204,7 @@ CompositorD3D11::Initialize()
HRESULT hr;
if (!gfxWindowsPlatform::GetPlatform()->GetD3D11Device(&mDevice)) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_NO_DEVICE";
return false;
}
@@ -211,6 +212,7 @@ CompositorD3D11::Initialize()
if (!mContext) {
gfxCriticalNote << "[D3D11] failed to get immediate context";
+ *out_failureReason = "FEATURE_FAILURE_D3D11_CONTEXT";
return false;
}
@@ -250,6 +252,7 @@ CompositorD3D11::Initialize()
getter_AddRefs(mAttachments->mInputLayout));
if (Failed(hr, "CreateInputLayout")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_INPUT_LAYOUT";
return false;
}
@@ -261,10 +264,12 @@ CompositorD3D11::Initialize()
hr = mDevice->CreateBuffer(&bufferDesc, &data, getter_AddRefs(mAttachments->mVertexBuffer));
if (Failed(hr, "create vertex buffer")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_VERTEX_BUFFER";
return false;
}
if (!mAttachments->CreateShaders()) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_CREATE_SHADERS";
return false;
}
@@ -275,12 +280,14 @@ CompositorD3D11::Initialize()
hr = mDevice->CreateBuffer(&cBufferDesc, nullptr, getter_AddRefs(mAttachments->mVSConstantBuffer));
if (Failed(hr, "create vs buffer")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_VS_BUFFER";
return false;
}
cBufferDesc.ByteWidth = sizeof(PixelShaderConstants);
hr = mDevice->CreateBuffer(&cBufferDesc, nullptr, getter_AddRefs(mAttachments->mPSConstantBuffer));
if (Failed(hr, "create ps buffer")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_PS_BUFFER";
return false;
}
@@ -290,18 +297,21 @@ CompositorD3D11::Initialize()
hr = mDevice->CreateRasterizerState(&rastDesc, getter_AddRefs(mAttachments->mRasterizerState));
if (Failed(hr, "create rasterizer")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_RASTERIZER";
return false;
}
CD3D11_SAMPLER_DESC samplerDesc(D3D11_DEFAULT);
hr = mDevice->CreateSamplerState(&samplerDesc, getter_AddRefs(mAttachments->mLinearSamplerState));
if (Failed(hr, "create linear sampler")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_LINEAR_SAMPLER";
return false;
}
samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
hr = mDevice->CreateSamplerState(&samplerDesc, getter_AddRefs(mAttachments->mPointSamplerState));
if (Failed(hr, "create point sampler")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_POINT_SAMPLER";
return false;
}
@@ -315,6 +325,7 @@ CompositorD3D11::Initialize()
blendDesc.RenderTarget[0] = rtBlendPremul;
hr = mDevice->CreateBlendState(&blendDesc, getter_AddRefs(mAttachments->mPremulBlendState));
if (Failed(hr, "create pm blender")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_PM_BLENDER";
return false;
}
@@ -327,6 +338,7 @@ CompositorD3D11::Initialize()
blendDesc.RenderTarget[0] = rtBlendNonPremul;
hr = mDevice->CreateBlendState(&blendDesc, getter_AddRefs(mAttachments->mNonPremulBlendState));
if (Failed(hr, "create npm blender")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_NPM_BLENDER";
return false;
}
@@ -344,6 +356,7 @@ CompositorD3D11::Initialize()
blendDesc.RenderTarget[0] = rtBlendComponent;
hr = mDevice->CreateBlendState(&blendDesc, getter_AddRefs(mAttachments->mComponentBlendState));
if (Failed(hr, "create component blender")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_COMP_BLENDER";
return false;
}
}
@@ -357,13 +370,15 @@ CompositorD3D11::Initialize()
blendDesc.RenderTarget[0] = rtBlendDisabled;
hr = mDevice->CreateBlendState(&blendDesc, getter_AddRefs(mAttachments->mDisabledBlendState));
if (Failed(hr, "create null blender")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_NULL_BLENDER";
return false;
}
if (!mAttachments->InitSyncObject()) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_OBJ_SYNC";
return false;
}
-
+
//
// VR additions
//
@@ -389,6 +404,7 @@ CompositorD3D11::Initialize()
cBufferDesc.ByteWidth = sizeof(gfx::VRDistortionConstants);
hr = mDevice->CreateBuffer(&cBufferDesc, nullptr, getter_AddRefs(mAttachments->mVRDistortionConstants));
if (Failed(hr, "create vr buffer ")) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_VR_BUFFER";
return false;
}
}
@@ -427,7 +443,8 @@ CompositorD3D11::Initialize()
*/
hr = dxgiFactory->CreateSwapChain(dxgiDevice, &swapDesc, getter_AddRefs(mSwapChain));
if (Failed(hr, "create swap chain")) {
- return false;
+ *out_failureReason = "FEATURE_FAILURE_D3D11_SWAP_CHAIN";
+ return false;
}
// We need this because we don't want DXGI to respond to Alt+Enter.
@@ -436,10 +453,12 @@ CompositorD3D11::Initialize()
}
if (!mWidget->InitCompositor(this)) {
+ *out_failureReason = "FEATURE_FAILURE_D3D11_INIT_COMPOSITOR";
return false;
}
reporter.SetSuccessful();
+
return true;
}
diff --git a/gfx/layers/d3d11/CompositorD3D11.h b/gfx/layers/d3d11/CompositorD3D11.h
index 581ed71c38b0..74ee4cf2730f 100644
--- a/gfx/layers/d3d11/CompositorD3D11.h
+++ b/gfx/layers/d3d11/CompositorD3D11.h
@@ -47,7 +47,7 @@ public:
virtual CompositorD3D11* AsCompositorD3D11() override { return this; }
- virtual bool Initialize() override;
+ virtual bool Initialize(nsCString* const out_failureReason) override;
virtual TextureFactoryIdentifier
GetTextureFactoryIdentifier() override;
diff --git a/gfx/layers/d3d9/CompositorD3D9.cpp b/gfx/layers/d3d9/CompositorD3D9.cpp
index 42671ff08a74..4362598f4098 100644
--- a/gfx/layers/d3d9/CompositorD3D9.cpp
+++ b/gfx/layers/d3d9/CompositorD3D9.cpp
@@ -38,25 +38,29 @@ CompositorD3D9::~CompositorD3D9()
}
bool
-CompositorD3D9::Initialize()
+CompositorD3D9::Initialize(nsCString* const out_failureReason)
{
ScopedGfxFeatureReporter reporter("D3D9 Layers");
mDeviceManager = gfxWindowsPlatform::GetPlatform()->GetD3D9DeviceManager();
if (!mDeviceManager) {
+ *out_failureReason = "FEATURE_FAILURE_D3D9_DEVICE_MANAGER";
return false;
}
mSwapChain = mDeviceManager->CreateSwapChain(mWidget->AsWindows()->GetHwnd());
if (!mSwapChain) {
+ *out_failureReason = "FEATURE_FAILURE_D3D9_SWAP_CHAIN";
return false;
}
if (!mWidget->InitCompositor(this)) {
+ *out_failureReason = "FEATURE_FAILURE_D3D9_INIT_COMPOSITOR";
return false;
}
reporter.SetSuccessful();
+
return true;
}
diff --git a/gfx/layers/d3d9/CompositorD3D9.h b/gfx/layers/d3d9/CompositorD3D9.h
index 5e3cf4d8fdca..5fca10994cd2 100644
--- a/gfx/layers/d3d9/CompositorD3D9.h
+++ b/gfx/layers/d3d9/CompositorD3D9.h
@@ -26,7 +26,7 @@ public:
virtual CompositorD3D9* AsCompositorD3D9() override { return this; }
- virtual bool Initialize() override;
+ virtual bool Initialize(nsCString* const out_failureReason) override;
virtual TextureFactoryIdentifier
GetTextureFactoryIdentifier() override;
diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp
index c7478932f78e..3d9aa203f696 100644
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -72,6 +72,7 @@
#include "mozilla/Hal.h"
#include "mozilla/HalTypes.h"
#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
#ifdef MOZ_ENABLE_PROFILER_SPS
#include "ProfilerMarkers.h"
#endif
@@ -1519,8 +1520,22 @@ CompositorBridgeParent::NewCompositor(const nsTArray& aBackendHin
compositor = new CompositorD3D9(this, mWidget);
#endif
}
-
- if (compositor && compositor->Initialize()) {
+ nsCString failureReason;
+ if (compositor && compositor->Initialize(&failureReason)) {
+ if (failureReason.IsEmpty()){
+ failureReason = "SUCCESS";
+ }
+ if (compositor->GetBackendType() == LayersBackend::LAYERS_OPENGL){
+ Telemetry::Accumulate(Telemetry::OPENGL_COMPOSITING_FAILURE_ID, failureReason);
+ }
+#ifdef XP_WIN
+ else if (compositor->GetBackendType() == LayersBackend::LAYERS_D3D9){
+ Telemetry::Accumulate(Telemetry::D3D9_COMPOSITING_FAILURE_ID, failureReason);
+ }
+ else if (compositor->GetBackendType() == LayersBackend::LAYERS_D3D11){
+ Telemetry::Accumulate(Telemetry::D3D11_COMPOSITING_FAILURE_ID, failureReason);
+ }
+#endif
compositor->SetCompositorID(mCompositorID);
return compositor;
}
diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp
index 1371ba9696b4..a3c49d22c05b 100644
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -227,7 +227,7 @@ CompositorOGL::CleanupResources()
}
bool
-CompositorOGL::Initialize()
+CompositorOGL::Initialize(nsCString* const out_failureReason)
{
ScopedGfxFeatureReporter reporter("GL Layers");
@@ -237,12 +237,16 @@ CompositorOGL::Initialize()
mGLContext = CreateContext();
#ifdef MOZ_WIDGET_ANDROID
- if (!mGLContext)
+ if (!mGLContext){
+ *out_failureReason = "FEATURE_FAILURE_OPENGL_NO_ANDROID_CONTEXT";
NS_RUNTIMEABORT("We need a context on Android");
+ }
#endif
- if (!mGLContext)
+ if (!mGLContext){
+ *out_failureReason = "FEATURE_FAILURE_OPENGL_CREATE_CONTEXT";
return false;
+ }
MakeCurrent();
@@ -258,6 +262,7 @@ CompositorOGL::Initialize()
RefPtr effect = new EffectSolidColor(Color(0, 0, 0, 0));
ShaderConfigOGL config = GetShaderConfigFor(effect);
if (!GetShaderProgramFor(config)) {
+ *out_failureReason = "FEATURE_FAILURE_OPENGL_COMPILE_SHADER";
return false;
}
@@ -332,6 +337,7 @@ CompositorOGL::Initialize()
if (mFBOTextureTarget == LOCAL_GL_NONE) {
/* Unable to find a texture target that works with FBOs and NPOT textures */
+ *out_failureReason = "FEATURE_FAILURE_OPENGL_NO_TEXTURE_TARGET";
return false;
}
} else {
@@ -349,6 +355,7 @@ CompositorOGL::Initialize()
* texture2DRect).
*/
if (!mGLContext->IsExtensionSupported(gl::GLContext::ARB_texture_rectangle))
+ *out_failureReason = "FEATURE_FAILURE_OPENGL_ARB_EXT";
return false;
}
@@ -425,6 +432,7 @@ CompositorOGL::Initialize()
}
reporter.SetSuccessful();
+
return true;
}
diff --git a/gfx/layers/opengl/CompositorOGL.h b/gfx/layers/opengl/CompositorOGL.h
index ebb8c06f92bd..567179b31a36 100644
--- a/gfx/layers/opengl/CompositorOGL.h
+++ b/gfx/layers/opengl/CompositorOGL.h
@@ -206,7 +206,7 @@ public:
virtual already_AddRefed
CreateDataTextureSource(TextureFlags aFlags = TextureFlags::NO_FLAGS) override;
- virtual bool Initialize() override;
+ virtual bool Initialize(nsCString* const out_failureReason) override;
virtual void Destroy() override;
diff --git a/gfx/tests/gtest/TestCompositor.cpp b/gfx/tests/gtest/TestCompositor.cpp
index 2a10b2b94b3a..10759f644c8c 100644
--- a/gfx/tests/gtest/TestCompositor.cpp
+++ b/gfx/tests/gtest/TestCompositor.cpp
@@ -138,8 +138,8 @@ static already_AddRefed CreateTestCompositor(LayersBackend backend,
MOZ_CRASH(); // No support yet
#endif
}
-
- if (!compositor || !compositor->Initialize()) {
+ nsCString failureReason;
+ if (!compositor || !compositor->Initialize(&failureReason)) {
printf_stderr("Failed to construct layer manager for the requested backend\n");
abort();
}
diff --git a/gfx/tests/gtest/TestTreeTraversal.cpp b/gfx/tests/gtest/TestTreeTraversal.cpp
index c05ed7389b15..27c17fc2222d 100644
--- a/gfx/tests/gtest/TestTreeTraversal.cpp
+++ b/gfx/tests/gtest/TestTreeTraversal.cpp
@@ -1,7 +1,17 @@
#include
#include "mozilla/RefPtr.h"
#include "gtest/gtest.h"
+#include "gtest/MozGTestBench.h"
+#include "nsRegion.h"
+#include "nsRect.h"
#include "TreeTraversal.h"
+#include
+#include
+
+const int PERFORMANCE_TREE_DEPTH = 20;
+const int PERFORMANCE_TREE_CHILD_COUNT = 2;
+const int PERFORMANCE_TREE_LEAF_COUNT = 1048576; // 2 ** 20
+const int PERFORMANCE_REGION_XWRAP = 1024;
using namespace mozilla::layers;
using namespace mozilla;
@@ -12,27 +22,38 @@ enum class ForEachNodeType {Continue, Skip};
template
class TestNodeBase {
public:
+ NS_INLINE_DECL_REFCOUNTING(TestNodeBase);
explicit TestNodeBase(T aType, int aExpectedTraversalRank = -1);
+ explicit TestNodeBase();
void SetActualTraversalRank(int aRank);
+ void SetValue(int aValue);
+ void SetType(T aType);
+ void SetRegion(nsRegion aRegion);
int GetExpectedTraversalRank();
int GetActualTraversalRank();
+ int GetValue();
T GetType();
+ nsRegion GetRegion();
+ virtual bool IsLeaf() = 0;
private:
int mExpectedTraversalRank;
int mActualTraversalRank;
+ int mValue;
+ nsRegion mRegion;
T mType;
protected:
- ~TestNodeBase() {};
+ virtual ~TestNodeBase() {};
};
template
class TestNodeReverse : public TestNodeBase {
public:
- NS_INLINE_DECL_REFCOUNTING(TestNodeReverse);
explicit TestNodeReverse(T aType, int aExpectedTraversalRank = -1);
+ explicit TestNodeReverse();
void AddChild(RefPtr> aNode);
TestNodeReverse* GetLastChild();
TestNodeReverse* GetPrevSibling();
+ bool IsLeaf();
private:
void SetPrevSibling(RefPtr> aNode);
void SetLastChild(RefPtr> aNode);
@@ -44,11 +65,12 @@ class TestNodeReverse : public TestNodeBase {
template
class TestNodeForward : public TestNodeBase {
public:
- NS_INLINE_DECL_REFCOUNTING(TestNodeForward);
explicit TestNodeForward(T aType, int aExpectedTraversalRank = -1);
+ explicit TestNodeForward();
void AddChild(RefPtr> aNode);
TestNodeForward* GetFirstChild();
TestNodeForward* GetNextSibling();
+ bool IsLeaf();
private:
void SetNextSibling(RefPtr> aNode);
void SetLastChild(RefPtr> aNode);
@@ -67,6 +89,13 @@ TestNodeReverse::TestNodeReverse(T aType, int aExpectedTraversalRank) :
}
+template
+TestNodeReverse::TestNodeReverse() :
+ TestNodeBase()
+{
+
+}
+
template
void TestNodeReverse::SetLastChild(RefPtr> aNode)
{
@@ -98,6 +127,12 @@ TestNodeReverse* TestNodeReverse::GetPrevSibling()
return mSiblingNode;
}
+template
+bool TestNodeReverse::IsLeaf()
+{
+ return !mLastChildNode;
+}
+
template
TestNodeForward::TestNodeForward(T aType, int aExpectedTraversalRank) :
TestNodeBase(aType, aExpectedTraversalRank)
@@ -105,6 +140,13 @@ TestNodeForward::TestNodeForward(T aType, int aExpectedTraversalRank) :
}
+template
+TestNodeForward::TestNodeForward() :
+ TestNodeBase()
+{
+
+}
+
template
void TestNodeForward::AddChild(RefPtr> aNode)
{
@@ -136,6 +178,12 @@ void TestNodeForward::SetNextSibling(RefPtr> aNode)
mSiblingNode = aNode;
}
+template
+bool TestNodeForward::IsLeaf()
+{
+ return !mFirstChildNode;
+}
+
template
TestNodeForward* TestNodeForward::GetFirstChild()
{
@@ -156,6 +204,11 @@ TestNodeBase::TestNodeBase(T aType, int aExpectedTraversalRank):
{
}
+template
+TestNodeBase::TestNodeBase()
+{
+}
+
template
int TestNodeBase::GetActualTraversalRank()
{
@@ -180,11 +233,64 @@ T TestNodeBase::GetType()
return mType;
}
+template
+void TestNodeBase::SetType(T aType)
+{
+ mType = aType;
+}
+
+template
+nsRegion TestNodeBase::GetRegion()
+{
+ return mRegion;
+}
+
+template
+void TestNodeBase::SetRegion(nsRegion aRegion)
+{
+ mRegion = aRegion;
+}
+
+template
+int TestNodeBase::GetValue()
+{
+ return mValue;
+}
+
+template
+void TestNodeBase::SetValue(int aValue)
+{
+ mValue = aValue;
+}
+
+typedef TestNodeBase SearchTestNode;
+typedef TestNodeBase ForEachTestNode;
typedef TestNodeReverse SearchTestNodeReverse;
typedef TestNodeReverse ForEachTestNodeReverse;
typedef TestNodeForward SearchTestNodeForward;
typedef TestNodeForward ForEachTestNodeForward;
+template
+void CreateBenchmarkTreeRecursive(RefPtr aNode, int aDepth, int aChildrenCount, Action aAction)
+{
+ if (aDepth > 0) {
+ for (int i = 0; i < aChildrenCount; i++) {
+ RefPtr newNode = new Node();
+ aNode->AddChild(newNode);
+ CreateBenchmarkTreeRecursive(newNode, aDepth-1, aChildrenCount, aAction);
+ }
+ }
+ aAction(aNode);
+}
+
+template
+RefPtr CreateBenchmarkTree(int aDepth, int aChildrenCount, Action aAction)
+{
+ RefPtr rootNode = new Node();
+ CreateBenchmarkTreeRecursive(rootNode, aDepth, aChildrenCount, aAction);
+ return rootNode;
+}
+
TEST(TreeTraversal, DepthFirstSearchNull)
{
RefPtr nullNode;
@@ -1150,3 +1256,972 @@ TEST(TreeTraversal, ForEachNodeLambdaReturnsVoid)
<< "Node at index " << i << " was hit out of order.";
}
}
+
+struct AssignSearchNodeTypesWithLastLeafAsNeedle {
+ RefPtr& node;
+ void operator()(SearchTestNodeForward* aNode) {
+ aNode->SetType(SearchNodeType::Hay);
+ if (aNode->IsLeaf()) {
+ node = aNode;
+ }
+ }
+};
+
+bool FindNeedle(SearchTestNode* aNode) {
+ return aNode->GetType() == SearchNodeType::Needle;
+}
+
+struct AssignSearchNodeTypesAllHay
+{
+ void operator()(SearchTestNode* aNode){
+ aNode->SetType(SearchNodeType::Hay);
+ }
+};
+
+struct AssignSearchNodeTypesWithFirstLeafAsNeedle
+{
+ RefPtr& needleNode;
+ void operator()(SearchTestNodeReverse* aNode){
+ if (!needleNode && aNode->IsLeaf()) {
+ needleNode = aNode;
+ }
+ aNode->SetType(SearchNodeType::Hay);
+ }
+};
+
+struct AssignSearchNodeValuesAllFalseValuesReverse
+{
+ int falseValue;
+ RefPtr& needleNode;
+ void operator()(SearchTestNodeReverse* aNode){
+ aNode->SetValue(falseValue);
+ if (!needleNode && aNode->IsLeaf()) {
+ needleNode = aNode;
+ }
+ }
+};
+
+struct AssignSearchNodeValuesAllFalseValuesForward
+{
+ int falseValue;
+ RefPtr& needleNode;
+ void operator()(SearchTestNodeForward* aNode){
+ aNode->SetValue(falseValue);
+ needleNode = aNode;
+ }
+};
+
+struct AllocateUnitRegionsToLeavesOnly
+{
+ int& xWrap;
+ int& squareCount;
+ void operator()(ForEachTestNode* aNode) {
+ if (aNode->IsLeaf()) {
+ int x = squareCount % xWrap;
+ int y = squareCount / xWrap;
+ aNode->SetRegion(nsRegion(nsRect(x, y, 1, 1)));
+ squareCount++;
+ }
+ }
+};
+
+void ForEachNodeDoNothing(ForEachTestNode* aNode) {}
+
+template
+static RefPtr DepthFirstSearchForwardRecursive(RefPtr aNode)
+{
+ if (aNode->GetType() == SearchNodeType::Needle) {
+ return aNode;
+ }
+ for (RefPtr node = aNode->GetFirstChild();
+ node != nullptr;
+ node = node->GetNextSibling()) {
+ if (RefPtr foundNode = DepthFirstSearchForwardRecursive(node)) {
+ return foundNode;
+ }
+ }
+ return nullptr;
+}
+
+static void Plain_ForwardDepthFirstSearchPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesWithLastLeafAsNeedle{needleNode});
+ needleNode->SetType(SearchNodeType::Needle);
+ RefPtr foundNode =
+ DepthFirstSearchForwardRecursive(root.get());
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardDepthFirstSearchPerformance, &Plain_ForwardDepthFirstSearchPerformance);
+
+static void TreeTraversal_ForwardDepthFirstSearchPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesWithLastLeafAsNeedle{needleNode});
+ needleNode->SetType(SearchNodeType::Needle);
+ RefPtr foundNode = DepthFirstSearch(root.get(), &FindNeedle);
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardDepthFirstSearchPerformance, &TreeTraversal_ForwardDepthFirstSearchPerformance);
+
+template
+static RefPtr DepthFirstSearchCaptureVariablesForwardRecursive(RefPtr aNode,
+ int a, int b, int c, int d, int e, int f,
+ int g, int h, int i, int j, int k, int l,
+ int m, int& n, int& o, int& p, int& q, int& r,
+ int& s, int& t, int& u, int& v, int& w, int& x,
+ int& y, int& z)
+{
+ if (aNode->GetValue() == a + b + c + d + e + f + g + h + i + j + k + l + m +
+ n + o + p + q + r + s + t + u + v + w + x + y + z) {
+ return aNode;
+ }
+ for (RefPtr node = aNode->GetFirstChild();
+ node != nullptr;
+ node = node->GetNextSibling()) {
+ if (RefPtr foundNode = DepthFirstSearchCaptureVariablesForwardRecursive(node,
+ a, b, c, d, e, f, g, h, i, j, k, l, m,
+ n, o, p, q, r, s, t, u, v, w, x, y, z)) {
+ return foundNode;
+ }
+ }
+ return nullptr;
+}
+
+static void Plain_ForwardDepthFirstSearchCaptureVariablesPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int a = 1; int b = 1; int c = 1; int d = 1; int e = 1; int f = 1;
+ int g = 1; int h = 1; int i = 1; int j = 1; int k = 1; int l = 1;
+ int m = 1; int n = 1; int o = 1; int p = 1; int q = 1; int r = 1;
+ int s = 1; int t = 1; int u = 1; int v = 1; int w = 1; int x = 1;
+ int y = 1; int z = 1;
+ int needleTotal = a + b + c + d + e + f + g + h + i + j + k + l + m +
+ n + o + p + q + r + s + t + u + v + w + x + y + z;
+ int hayTotal = 0;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeValuesAllFalseValuesForward{hayTotal, needleNode});
+ needleNode->SetValue(needleTotal);
+ RefPtr foundNode =
+ DepthFirstSearchCaptureVariablesForwardRecursive(root.get(),
+ a, b, c, d, e, f, g, h, i, j, k, l, m,
+ n, o, p, q, r, s, t, u, v, w, x, y, z);
+ ASSERT_EQ(foundNode->GetValue(), needleTotal);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardDepthFirstSearchCaptureVariablesPerformance, &Plain_ForwardDepthFirstSearchCaptureVariablesPerformance);
+
+static void TreeTraversal_ForwardDepthFirstSearchCaptureVariablesPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ int a = 1; int b = 1; int c = 1; int d = 1; int e = 1; int f = 1;
+ int g = 1; int h = 1; int i = 1; int j = 1; int k = 1; int l = 1;
+ int m = 1; int n = 1; int o = 1; int p = 1; int q = 1; int r = 1;
+ int s = 1; int t = 1; int u = 1; int v = 1; int w = 1; int x = 1;
+ int y = 1; int z = 1;
+ int needleTotal = a + b + c + d + e + f + g + h + i + j + k + l + m +
+ n + o + p + q + r + s + t + u + v + w + x + y + z;
+ int hayTotal = 0;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeValuesAllFalseValuesForward{hayTotal, needleNode});
+ needleNode->SetValue(needleTotal);
+ RefPtr foundNode = DepthFirstSearch(root.get(),
+ [a, b, c, d, e, f, g, h, i, j, k, l, m,
+ &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z]
+ (SearchTestNodeForward* aNode) {
+ return aNode->GetValue() == a + b + c + d + e + f + g + h + i + j + k + l + m +
+ n + o + p + q + r + s + t + u + v + w + x + y + z;
+ });
+ ASSERT_EQ(foundNode->GetValue(), needleTotal);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardDepthFirstSearchCaptureVariablesPerformance, &TreeTraversal_ForwardDepthFirstSearchCaptureVariablesPerformance);
+
+template
+static RefPtr DepthFirstSearchPostOrderForwardRecursive(RefPtr aNode)
+{
+ for (RefPtr node = aNode->GetFirstChild();
+ node != nullptr;
+ node = node->GetNextSibling()) {
+ if (RefPtr foundNode = DepthFirstSearchPostOrderForwardRecursive(node)) {
+ return foundNode;
+ }
+ }
+ if (aNode->GetType() == SearchNodeType::Needle) {
+ return aNode;
+ }
+ return nullptr;
+}
+
+static void Plain_ForwardDepthFirstSearchPostOrderPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesAllHay{});
+ root->SetType(SearchNodeType::Needle);
+ RefPtr foundNode =
+ DepthFirstSearchPostOrderForwardRecursive(root.get());
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(root, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardDepthFirstSearchPostOrderPerformance, &Plain_ForwardDepthFirstSearchPostOrderPerformance);
+
+static void TreeTraversal_ForwardDepthFirstSearchPostOrderPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesAllHay{});
+ root->SetType(SearchNodeType::Needle);
+ RefPtr foundNode = DepthFirstSearchPostOrder(root.get(), &FindNeedle);
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(root, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardDepthFirstSearchPostOrderPerformance, &TreeTraversal_ForwardDepthFirstSearchPostOrderPerformance);
+
+template
+static RefPtr BreadthFirstSearchForwardQueue(RefPtr aNode)
+{
+ RefPtr returnNode = nullptr;
+ queue> nodes;
+ nodes.push(aNode);
+ while(!nodes.empty()) {
+ RefPtr node = nodes.front();
+ nodes.pop();
+ if (node->GetType() == SearchNodeType::Needle) {
+ return node;
+ }
+ for (RefPtr childNode = node->GetFirstChild();
+ childNode != nullptr;
+ childNode = childNode->GetNextSibling()) {
+ nodes.push(childNode);
+ }
+ }
+ return nullptr;
+}
+
+static void Plain_ForwardBreadthFirstSearchPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesWithLastLeafAsNeedle{needleNode});
+ needleNode->SetType(SearchNodeType::Needle);
+ RefPtr foundNode =
+ BreadthFirstSearchForwardQueue(root.get());
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardBreadthFirstSearchPerformance, &Plain_ForwardBreadthFirstSearchPerformance);
+
+static void TreeTraversal_ForwardBreadthFirstSearchPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesWithLastLeafAsNeedle{needleNode});
+ needleNode->SetType(SearchNodeType::Needle);
+ RefPtr foundNode = BreadthFirstSearch(root.get(), &FindNeedle);
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardBreadthFirstSearchPerformance, &TreeTraversal_ForwardBreadthFirstSearchPerformance);
+
+// This test ((Plain|TreeTraversal)_ForwardForEachNodePostOrderPerformance)
+// uses the following benchmark:
+//
+// Starting with a tree whose leaves only are augmented with region data
+// (arranged as a series of 1x1 blocks stacked in rows of 100000), calculate
+// each ancestor's region as the union of its child regions.
+template
+static void ForEachNodePostOrderForwardRecursive(RefPtr aNode)
+{
+ if (!aNode->IsLeaf()) {
+ nsRegion newRegion;
+ for (RefPtr node = aNode->GetFirstChild();
+ node != nullptr;
+ node = node->GetNextSibling()) {
+ ForEachNodePostOrderForwardRecursive(node);
+ nsRegion childRegion = node->GetRegion();
+ newRegion.OrWith(childRegion);
+ }
+ aNode->SetRegion(newRegion);
+ }
+}
+
+static void Plain_ForwardForEachNodePostOrderPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ int squareCount = 0;
+ int xWrap = PERFORMANCE_REGION_XWRAP;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AllocateUnitRegionsToLeavesOnly{xWrap, squareCount});
+ ForEachNodePostOrderForwardRecursive(root);
+ ASSERT_EQ(root->GetRegion(), nsRegion(nsRect(0, 0, PERFORMANCE_REGION_XWRAP, PERFORMANCE_REGION_XWRAP)));
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardForEachNodePostOrderPerformance, &Plain_ForwardForEachNodePostOrderPerformance);
+
+static void TreeTraversal_ForwardForEachNodePostOrderPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ int squareCount = 0;
+ int xWrap = PERFORMANCE_REGION_XWRAP;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AllocateUnitRegionsToLeavesOnly{xWrap, squareCount});
+ ForEachNodePostOrder(root.get(),
+ [](ForEachTestNodeForward* aNode) {
+ if (!aNode->IsLeaf()) {
+ nsRegion newRegion;
+ for (RefPtr node = aNode->GetFirstChild();
+ node != nullptr;
+ node = node->GetNextSibling()) {
+ nsRegion childRegion = node->GetRegion();
+ newRegion.OrWith(childRegion);
+ }
+ aNode->SetRegion(newRegion);
+ }
+ });
+ ASSERT_EQ(root->GetRegion(), nsRegion(nsRect(0, 0, PERFORMANCE_REGION_XWRAP, PERFORMANCE_REGION_XWRAP)));
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardForEachNodePostOrderPerformance, &TreeTraversal_ForwardForEachNodePostOrderPerformance);
+
+// This test ((Plain|TreeTraversal)_ForwardForEachNodePerformance) uses the
+// following benchmark:
+//
+// Starting with a tree whose root has a rectangular region of size
+// PERFORMANCE_TREE_LEAF_COUNT x 1, for each node, split the region into
+// PERFORMANCE_TREE_CHILD_COUNT separate regions of equal width and assign to
+// each child left-to-right. In the end, every node's region should equal the
+// sum of its childrens' regions, and each level of depth's regions should sum
+// to the root's region.
+template
+static void ForEachNodeForwardRecursive(RefPtr aNode)
+{
+ if (!aNode->IsLeaf()) {
+ int nChildren = 0;
+ for (RefPtr node = aNode->GetFirstChild();
+ node != nullptr;
+ node = node->GetNextSibling()) {
+ nChildren++;
+ }
+ nsRect bounds = aNode->GetRegion().GetBounds();
+ int childWidth = bounds.width / nChildren;
+ int x = bounds.x;
+ for (RefPtr node = aNode->GetFirstChild();
+ node != nullptr;
+ node = node->GetNextSibling()) {
+ node->SetRegion(nsRegion(nsRect(x, 0, childWidth, 1)));
+ ForEachNodeForwardRecursive(node);
+ x += childWidth;
+ }
+ }
+}
+
+static void Plain_ForwardForEachNodePerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ &ForEachNodeDoNothing);
+ root->SetRegion(nsRegion(nsRect(0, 0, rectangleWidth, 1)));
+ ForEachNodeForwardRecursive(root);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardForEachNodePerformance, &Plain_ForwardForEachNodePerformance);
+
+static void TreeTraversal_ForwardForEachNodePerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ &ForEachNodeDoNothing);
+ root->SetRegion(nsRegion(nsRect(0, 0, rectangleWidth, 1)));
+ ForEachNode(root.get(),
+ [](ForEachTestNodeForward* aNode) {
+ if (!aNode->IsLeaf()) {
+ int nChildren = 0;
+ for (RefPtr node = aNode->GetFirstChild();
+ node != nullptr;
+ node = node->GetNextSibling()) {
+ nChildren++;
+ }
+ nsRect bounds = aNode->GetRegion().GetBounds();
+ int childWidth = bounds.width / nChildren;
+ int x = bounds.x;
+ for (RefPtr node = aNode->GetFirstChild();
+ node != nullptr;
+ node = node->GetNextSibling()) {
+ node->SetRegion(nsRegion(nsRect(x, 0, childWidth, 1)));
+ x += childWidth;
+ }
+ }
+ });
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardForEachNodePerformance, &TreeTraversal_ForwardForEachNodePerformance);
+
+// This test ((Plain|TreeTraversal)_ForwardForEachNodeStackPerformance) uses
+// the following benchmark:
+//
+// Starting with an unattached region equal to PERFORMANCE_TREE_LEAF_COUNT x 1,
+// a starting width of PERFORMANCE_TREE_LEAF_COUNT, and an empty tree, create a
+// tree with the same conditions as
+// ((Plain|TreeTraversal)_ForwardForEachNodePerformance) by assigning regions
+// of the current width, starting from the min x and min y coordinates. For
+// each level of depth, decrease the current width by a factor of
+// PERFORMANCE_TREE_CHILD_COUNT, and maintain a stack of ancestor regions.
+// Use the stack to track the portion of each region still available to assign
+// to children, which determines the aforementioned min x and min y coordinates.
+// Compare this to using the program stack.
+template
+static void ForEachNodeForwardStackRecursive(RefPtr aNode, int& aRectangleWidth, nsRegion aRegion, int aChildrenCount)
+{
+ nsRect parentRect = aRegion.GetBounds();
+ nsRect newRectangle(parentRect.x, parentRect.y, aRectangleWidth, 1);
+ nsRegion newRegion(newRectangle);
+ aNode->SetRegion(nsRegion(newRegion));
+
+ aRectangleWidth /= aChildrenCount;
+
+ for (RefPtr node = aNode->GetFirstChild();
+ node != nullptr;
+ node = node->GetNextSibling()) {
+ ForEachNodeForwardStackRecursive(node, aRectangleWidth, newRegion, aChildrenCount);
+ newRegion.SubOut(node->GetRegion());
+ }
+
+ // Handle case where rectangle width is truncated if power falls below 0,
+ // so we dont lose the regions in future iterations
+ if (aRectangleWidth == 0) {
+ aRectangleWidth = 1;
+ }
+ else {
+ aRectangleWidth *= aChildrenCount;
+ }
+}
+
+static void Plain_ForwardForEachNodeStackPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ &ForEachNodeDoNothing);
+ nsRegion startRegion(nsRect(0, 0, rectangleWidth, 1));
+ ForEachNodeForwardStackRecursive(root, rectangleWidth, startRegion, childrenCount);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ForwardForEachNodeStackPerformance, &Plain_ForwardForEachNodeStackPerformance);
+
+static void TreeTraversal_ForwardForEachNodeStackPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ int rectangleWidth = PERFORMANCE_TREE_LEAF_COUNT;
+ stack regionStack;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ &ForEachNodeDoNothing);
+ regionStack.push(nsRegion(nsRect(0, 0, rectangleWidth, 1)));
+ ForEachNode(root.get(),
+ [®ionStack, &rectangleWidth, childrenCount](ForEachTestNodeForward* aNode) {
+ nsRegion parentRegion = regionStack.top();
+ nsRect parentRect = parentRegion.GetBounds();
+ nsRect newRect(parentRect.x, parentRect.y, rectangleWidth, 1);
+ nsRegion newRegion(newRect);
+ aNode->SetRegion(newRegion);
+ regionStack.top().SubOut(newRegion);
+ regionStack.push(newRegion);
+ rectangleWidth /= childrenCount;
+ },
+ [®ionStack, &rectangleWidth, childrenCount](ForEachTestNodeForward* aNode) {
+ regionStack.pop();
+ // Handle case where rectangle width is truncated if power falls below 0,
+ // so we dont lose the regions in future iterations
+ if (rectangleWidth == 0) {
+ rectangleWidth = 1;
+ }
+ else {
+ rectangleWidth *= childrenCount;
+ }
+ });
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ForwardForEachNodeStackPerformance, &TreeTraversal_ForwardForEachNodeStackPerformance);
+
+template
+static RefPtr DepthFirstSearchReverseRecursive(RefPtr aNode)
+{
+ if (aNode->GetType() == SearchNodeType::Needle) {
+ return aNode;
+ }
+ for (RefPtr node = aNode->GetLastChild();
+ node != nullptr;
+ node = node->GetPrevSibling()) {
+ if (RefPtr foundNode = DepthFirstSearchReverseRecursive(node)) {
+ return foundNode;
+ }
+ }
+ return nullptr;
+}
+
+static void Plain_ReverseDepthFirstSearchPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesWithFirstLeafAsNeedle{needleNode});
+ needleNode->SetType(SearchNodeType::Needle);
+ RefPtr foundNode =
+ DepthFirstSearchReverseRecursive(root.get());
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseDepthFirstSearchPerformance, &Plain_ReverseDepthFirstSearchPerformance);
+
+static void TreeTraversal_ReverseDepthFirstSearchPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesWithFirstLeafAsNeedle{needleNode});
+ needleNode->SetType(SearchNodeType::Needle);
+ RefPtr foundNode = DepthFirstSearch(root.get(),
+ &FindNeedle);
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseDepthFirstSearchPerformance, &TreeTraversal_ReverseDepthFirstSearchPerformance);
+
+template
+static RefPtr DepthFirstSearchCaptureVariablesReverseRecursive(RefPtr aNode,
+ int a, int b, int c, int d, int e, int f,
+ int g, int h, int i, int j, int k, int l,
+ int m, int& n, int& o, int& p, int& q, int& r,
+ int& s, int& t, int& u, int& v, int& w, int& x,
+ int& y, int& z)
+{
+ if (aNode->GetValue() == a + b + c + d + e + f + g + h + i + j + k + l +
+ m + n + o + p + q + r + s + t + u + v + w + x + y + z) {
+ return aNode;
+ }
+ for (RefPtr node = aNode->GetLastChild();
+ node != nullptr;
+ node = node->GetPrevSibling()) {
+ if (RefPtr foundNode = DepthFirstSearchCaptureVariablesReverseRecursive(node,
+ a, b, c, d, e, f, g, h, i, j, k, l, m,
+ n, o, p, q, r, s, t, u, v, w, x, y, z)) {
+ return foundNode;
+ }
+ }
+ return nullptr;
+}
+
+static void Plain_ReverseDepthFirstSearchCaptureVariablesPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int a = 1; int b = 1; int c = 1; int d = 1; int e = 1; int f = 1;
+ int g = 1; int h = 1; int i = 1; int j = 1; int k = 1; int l = 1;
+ int m = 1; int n = 1; int o = 1; int p = 1; int q = 1; int r = 1;
+ int s = 1; int t = 1; int u = 1; int v = 1; int w = 1; int x = 1;
+ int y = 1; int z = 1;
+ int needleTotal = a + b + c + d + e + f + g + h + i + j + k + l + m +
+ n + o + p + q + r + s + t + u + v + w + x + y + z;
+ int hayTotal = 0;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeValuesAllFalseValuesReverse{hayTotal, needleNode});
+ needleNode->SetValue(needleTotal);
+ RefPtr foundNode =
+ DepthFirstSearchCaptureVariablesReverseRecursive(root.get(),
+ a, b, c, d, e, f, g, h, i, j, k, l, m,
+ n, o, p, q, r, s, t, u, v, w, x, y, z);
+ ASSERT_EQ(foundNode->GetValue(), needleTotal);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseDepthFirstSearchCaptureVariablesPerformance, &Plain_ReverseDepthFirstSearchCaptureVariablesPerformance);
+
+static void TreeTraversal_ReverseDepthFirstSearchCaptureVariablesPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ int a = 1; int b = 1; int c = 1; int d = 1; int e = 1; int f = 1;
+ int g = 1; int h = 1; int i = 1; int j = 1; int k = 1; int l = 1;
+ int m = 1; int n = 1; int o = 1; int p = 1; int q = 1; int r = 1;
+ int s = 1; int t = 1; int u = 1; int v = 1; int w = 1; int x = 1;
+ int y = 1; int z = 1;
+ int needleTotal = a + b + c + d + e + f + g + h + i + j + k + l + m +
+ n + o + p + q + r + s + t + u + v + w + x + y + z;
+ int hayTotal = 0;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeValuesAllFalseValuesReverse{hayTotal, needleNode});
+ needleNode->SetValue(needleTotal);
+ RefPtr foundNode = DepthFirstSearch(root.get(),
+ [a, b, c, d, e, f, g, h, i, j, k, l, m,
+ &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z] (SearchTestNodeReverse* aNode) {
+ return aNode->GetValue() == a + b + c + d + e + f + g + h + i + j + k + l +
+ m + n + o + p + q + r + s + t + u + v + w + x + y + z;
+ });
+ ASSERT_EQ(foundNode->GetValue(), needleTotal);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseDepthFirstSearchCaptureVariablesPerformance, &TreeTraversal_ReverseDepthFirstSearchCaptureVariablesPerformance);
+
+template
+static RefPtr DepthFirstSearchPostOrderReverseRecursive(RefPtr aNode)
+{
+ for (RefPtr node = aNode->GetLastChild();
+ node != nullptr;
+ node = node->GetPrevSibling()) {
+ if (RefPtr foundNode = DepthFirstSearchPostOrderReverseRecursive(node)) {
+ return foundNode;
+ }
+ }
+ if (aNode->GetType() == SearchNodeType::Needle) {
+ return aNode;
+ }
+ return nullptr;
+}
+
+static void Plain_ReverseDepthFirstSearchPostOrderPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesAllHay{});
+ root->SetType(SearchNodeType::Needle);
+ RefPtr foundNode =
+ DepthFirstSearchPostOrderReverseRecursive(root.get());
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(root, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseDepthFirstSearchPostOrderPerformance, &Plain_ReverseDepthFirstSearchPostOrderPerformance);
+
+static void TreeTraversal_ReverseDepthFirstSearchPostOrderPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesAllHay{});
+ root->SetType(SearchNodeType::Needle);
+ RefPtr foundNode = DepthFirstSearchPostOrder(root.get(), &FindNeedle);
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(root, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseDepthFirstSearchPostOrderPerformance, &TreeTraversal_ReverseDepthFirstSearchPostOrderPerformance);
+
+template
+static RefPtr BreadthFirstSearchReverseQueue(RefPtr aNode)
+{
+ RefPtr returnNode = nullptr;
+ queue> nodes;
+ nodes.push(aNode);
+ while(!nodes.empty()) {
+ RefPtr node = nodes.front();
+ nodes.pop();
+ if (node->GetType() == SearchNodeType::Needle) {
+ return node;
+ }
+ for (RefPtr childNode = node->GetLastChild();
+ childNode != nullptr;
+ childNode = childNode->GetPrevSibling()) {
+ nodes.push(childNode);
+ }
+ }
+ return nullptr;
+}
+
+static void Plain_ReverseBreadthFirstSearchPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesWithFirstLeafAsNeedle{needleNode});
+ needleNode->SetType(SearchNodeType::Needle);
+ RefPtr foundNode =
+ BreadthFirstSearchReverseQueue(root.get());
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseBreadthFirstSearchPerformance, &Plain_ReverseBreadthFirstSearchPerformance);
+
+static void TreeTraversal_ReverseBreadthFirstSearchPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ RefPtr needleNode;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AssignSearchNodeTypesWithFirstLeafAsNeedle{needleNode});
+ needleNode->SetType(SearchNodeType::Needle);
+ RefPtr foundNode = BreadthFirstSearch(root.get(), &FindNeedle);
+ ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle);
+ ASSERT_EQ(needleNode, foundNode);
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseBreadthFirstSearchPerformance, &TreeTraversal_ReverseBreadthFirstSearchPerformance);
+
+// This test ((Plain|TreeTraversal)_ReverseForEachNodePostOrderPerformance)
+// uses the following benchmark:
+//
+// Starting with a tree whose leaves only are augmented with region data
+// (arranged as a series of 1x1 blocks stacked in rows of 100000), calculate
+// each ancestor's region as the union of its child regions.
+template
+static void ForEachNodePostOrderReverseRecursive(RefPtr aNode)
+{
+ if (!aNode->IsLeaf()) {
+ nsRegion newRegion;
+ for (RefPtr node = aNode->GetLastChild();
+ node != nullptr;
+ node = node->GetPrevSibling()) {
+ ForEachNodePostOrderReverseRecursive(node);
+ nsRegion childRegion = node->GetRegion();
+ newRegion.OrWith(childRegion);
+ }
+ aNode->SetRegion(newRegion);
+ }
+}
+
+static void Plain_ReverseForEachNodePostOrderPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ int squareCount = 0;
+ int xWrap = PERFORMANCE_REGION_XWRAP;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AllocateUnitRegionsToLeavesOnly{xWrap, squareCount});
+ ForEachNodePostOrderReverseRecursive(root);
+ ASSERT_EQ(root->GetRegion(), nsRegion(nsRect(0, 0, PERFORMANCE_REGION_XWRAP, PERFORMANCE_REGION_XWRAP)));
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseForEachNodePostOrderPerformance, &Plain_ReverseForEachNodePostOrderPerformance);
+
+static void TreeTraversal_ReverseForEachNodePostOrderPerformance()
+{
+ int depth = PERFORMANCE_TREE_DEPTH;
+ int childrenCount = PERFORMANCE_TREE_CHILD_COUNT;
+ int squareCount = 0;
+ int xWrap = PERFORMANCE_REGION_XWRAP;
+ RefPtr root = CreateBenchmarkTree(depth, childrenCount,
+ AllocateUnitRegionsToLeavesOnly{xWrap, squareCount});
+ ForEachNodePostOrder(root.get(),
+ [](ForEachTestNodeReverse* aNode) {
+ if (!aNode->IsLeaf()) {
+ nsRegion newRegion;
+ for (RefPtr node = aNode->GetLastChild();
+ node != nullptr;
+ node = node->GetPrevSibling()) {
+ nsRegion childRegion = node->GetRegion();
+ newRegion.OrWith(childRegion);
+ }
+ aNode->SetRegion(newRegion);
+ }
+ });
+ ASSERT_EQ(root->GetRegion(), nsRegion(nsRect(0, 0, PERFORMANCE_REGION_XWRAP, PERFORMANCE_REGION_XWRAP)));
+}
+
+MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseForEachNodePostOrderPerformance, &TreeTraversal_ReverseForEachNodePostOrderPerformance);
+
+// This test ((Plain|TreeTraversal)_ReverseForEachNodePerformance) uses the
+// following benchmark:
+//
+// Starting with a tree whose root has a rectangular region of size
+// PERFORMANCE_TREE_LEAF_COUNT x 1, for each node, split the region into
+// PERFORMANCE_TREE_CHILD_COUNT separate regions of equal width and assign to
+// each child left-to-right. In the end, every node's region should equal the
+// sum of its childrens' regions, and each level of depth's regions should sum
+// to the root's region.
+template
+static void ForEachNodeReverseRecursive(RefPtr aNode)
+{
+ if (!aNode->IsLeaf()) {
+ int nChildren = 0;
+ for (RefPtr node = aNode->GetLastChild();
+ node != nullptr;
+ node = node->GetPrevSibling()) {
+ nChildren++;
+ }
+ nsRect bounds = aNode->GetRegion().GetBounds();
+ int childWidth = bounds.width / nChildren;
+ int x = bounds.x;
+ for (RefPtr