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 node = aNode->GetLastChild(); + node != nullptr; + node = node->GetPrevSibling()) { + node->SetRegion(nsRegion(nsRect(x, 0, childWidth, 1))); + ForEachNodeReverseRecursive(node); + x += childWidth; + } + } +} + +static void Plain_ReverseForEachNodePerformance() +{ + 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))); + ForEachNodeReverseRecursive(root); +} + +MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseForEachNodePerformance, &Plain_ReverseForEachNodePerformance); + +static void TreeTraversal_ReverseForEachNodePerformance() +{ + 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(), + [](ForEachTestNodeReverse* 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 node = aNode->GetLastChild(); + node != nullptr; + node = node->GetPrevSibling()) { + node->SetRegion(nsRegion(nsRect(x, 0, childWidth, 1))); + x += childWidth; + } + } + }); +} + +MOZ_GTEST_BENCH(TreeTraversal, TreeTraversal_ReverseForEachNodePerformance, &TreeTraversal_ReverseForEachNodePerformance); + +// This test ((Plain|TreeTraversal)_ReverseForEachNodeStackPerformance) 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)_ReverseForEachNodePerformance) 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 ForEachNodeReverseStackRecursive(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->GetLastChild(); + node != nullptr; + node = node->GetPrevSibling()) { + ForEachNodeReverseStackRecursive(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_ReverseForEachNodeStackPerformance() +{ + 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)); + ForEachNodeReverseStackRecursive(root, rectangleWidth, startRegion, childrenCount); +} + +MOZ_GTEST_BENCH(TreeTraversal, Plain_ReverseForEachNodeStackPerformance, &Plain_ReverseForEachNodeStackPerformance); + +static void TreeTraversal_ReverseForEachNodeStackPerformance() +{ + 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](ForEachTestNodeReverse* 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](ForEachTestNodeReverse* 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_ReverseForEachNodeStackPerformance, &TreeTraversal_ReverseForEachNodeStackPerformance); diff --git a/testing/xpcshell/node-spdy/README.md b/testing/xpcshell/node-spdy/README.md index 9e9fc9eabd8e..2843bac791a9 100644 --- a/testing/xpcshell/node-spdy/README.md +++ b/testing/xpcshell/node-spdy/README.md @@ -31,7 +31,7 @@ var server = spdy.createServer(options, function(req, res) { res.end('hello world!'); }); -server.listen(443); +server.listen(3000); ``` Client: @@ -80,7 +80,7 @@ app.use(/* your favorite middleware */); var server = spdy.createServer(options, app); -server.listen(443); +server.listen(3000); ``` ## API @@ -120,9 +120,35 @@ spdy.createServer(options, function(req, res) { stream.end('alert("hello from push stream!");'); res.end(''); -}).listen(443); +}).listen(3000); ``` +The method is also avaliable when using SPDY with an Express server: + +```javascript +var app = express(); + +var server = spdy.createServer(options, app); + +app.get('/', function(req, res) { + var headers = { 'content-type': 'application/javascript' }; + res.push('/main.js', headers, function(err, stream) { + stream.on('acknowledge', function() { + }); + + stream.on('error', function() { + }); + + stream.end('alert("hello from push stream!");'); + }); + + res.end(''); +}); + +server.listen(3000); +``` + + Push is accomplished via the `push()` method invoked on the current response object (this works for express.js response objects as well). The format of the `push()` method is: diff --git a/testing/xpcshell/node-spdy/lib/spdy/client.js b/testing/xpcshell/node-spdy/lib/spdy/client.js index 345913646ad7..67ffe9f37145 100644 --- a/testing/xpcshell/node-spdy/lib/spdy/client.js +++ b/testing/xpcshell/node-spdy/lib/spdy/client.js @@ -171,7 +171,6 @@ proto.createConnection = function createConnection(options) { options.spdy.decompress }); state.id += 2; - state.connection._addStream(stream); return stream; }; diff --git a/testing/xpcshell/node-spdy/lib/spdy/connection.js b/testing/xpcshell/node-spdy/lib/spdy/connection.js index 718a53de8de3..2aa860ea3485 100644 --- a/testing/xpcshell/node-spdy/lib/spdy/connection.js +++ b/testing/xpcshell/node-spdy/lib/spdy/connection.js @@ -169,6 +169,9 @@ Connection.prototype._init = function init() { // for both legacy and non-legacy, when our socket is ready for writes again, // drain the sinks in case they were queuing due to socketBuffering. this.socket.on('drain', function () { + if (!state.socketBuffering) + return; + state.socketBuffering = false; Object.keys(state.streams).forEach(function(id) { state.streams[id]._drainSink(0); @@ -298,7 +301,6 @@ Connection.prototype._handleSynStream = function handleSynStream(frame) { } var stream = new Stream(this, frame); - this._addStream(stream); // Associate streams if (associated) { @@ -556,6 +558,8 @@ Connection.prototype._handlePing = function handlePing(id) { state.framer.pingFrame(id, function(err, frame) { if (err) return self.emit('error', err); + + self.emit('ping', id); self.write(frame); }); return; diff --git a/testing/xpcshell/node-spdy/lib/spdy/protocol/framer.js b/testing/xpcshell/node-spdy/lib/spdy/protocol/framer.js index ef394510b0dd..3cbf7bae8f6d 100644 --- a/testing/xpcshell/node-spdy/lib/spdy/protocol/framer.js +++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/framer.js @@ -4,6 +4,8 @@ var Buffer = require('buffer').Buffer; var EventEmitter = require('events').EventEmitter; var constants = require('./').constants; +var empty = new Buffer(0); + function Framer() { EventEmitter.call(this); @@ -310,7 +312,7 @@ Framer.prototype.dataFrame = function dataFrame(id, fin, data, callback) { } if (!fin && !data.length) - return callback(null, []); + return callback(null, empty); var frame = new Buffer(8 + data.length); diff --git a/testing/xpcshell/node-spdy/lib/spdy/protocol/parser.js b/testing/xpcshell/node-spdy/lib/spdy/protocol/parser.js index e2ca060c7a41..c60d0ea3907a 100644 --- a/testing/xpcshell/node-spdy/lib/spdy/protocol/parser.js +++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/parser.js @@ -59,6 +59,40 @@ parser.create = function create(connection) { Parser.prototype.destroy = function destroy() { }; +Parser.prototype._slice = function _slice(waiting) { + var buffer = new Buffer(waiting); + var sliced = 0; + var offset = 0; + + while (waiting > offset && sliced < this.buffer.length) { + var chunk = this.buffer[sliced++], + overmatched = false; + + // Copy chunk into `buffer` + if (chunk.length > waiting - offset) { + chunk.copy(buffer, offset, 0, waiting - offset); + + this.buffer[--sliced] = chunk.slice(waiting - offset); + this.buffered += this.buffer[sliced].length; + + overmatched = true; + } else { + chunk.copy(buffer, offset); + } + + // Move offset and decrease amount of buffered data + offset += chunk.length; + this.buffered -= chunk.length; + + if (overmatched) break; + } + + // Remove used buffers + this.buffer = this.buffer.slice(sliced); + + return buffer; +}; + // // ### function _write (data, encoding, cb) // #### @data {Buffer} chunk of data @@ -83,48 +117,36 @@ Parser.prototype._write = function write(data, encoding, cb) { return false; } + if (this.needDrain) { + // Mark parser as drained + this.needDrain = false; + this.emit('drain'); + } + // We shall not do anything until we get all expected data if (this.buffered < this.waiting) { - if (this.needDrain) { - // Mark parser as drained - this.needDrain = false; - this.emit('drain'); + // Emit DATA by chunks as they come + if (this.buffered !== 0 && + this.state.header && + !this.state.header.control) { + var buffer = this._slice(this.buffered); + this.waiting -= buffer.length; + + this.emit('frame', { + type: 'DATA', + id: this.state.header.id, + fin: false, + compressed: (this.state.header.flags & 0x02) === 0x02, + data: buffer + }); } cb(); return; } - var self = this, - buffer = new Buffer(this.waiting), - sliced = 0, - offset = 0; - - while (this.waiting > offset && sliced < this.buffer.length) { - var chunk = this.buffer[sliced++], - overmatched = false; - - // Copy chunk into `buffer` - if (chunk.length > this.waiting - offset) { - chunk.copy(buffer, offset, 0, this.waiting - offset); - - this.buffer[--sliced] = chunk.slice(this.waiting - offset); - this.buffered += this.buffer[sliced].length; - - overmatched = true; - } else { - chunk.copy(buffer, offset); - } - - // Move offset and decrease amount of buffered data - offset += chunk.length; - this.buffered -= chunk.length; - - if (overmatched) break; - } - - // Remove used buffers - this.buffer = this.buffer.slice(sliced); + var self = this; + var buffer = this._slice(this.waiting); // Executed parser for buffered data this.paused = true; @@ -232,6 +254,7 @@ Parser.prototype.execute = function execute(state, data, callback) { self.emit('frame', frame); state.type = 'frame-head'; + state.header = null; callback(null, 8); }; } diff --git a/testing/xpcshell/node-spdy/lib/spdy/response.js b/testing/xpcshell/node-spdy/lib/spdy/response.js index 469364f270f6..ee1cb82dbe1b 100644 --- a/testing/xpcshell/node-spdy/lib/spdy/response.js +++ b/testing/xpcshell/node-spdy/lib/spdy/response.js @@ -165,7 +165,6 @@ exports.push = function push(url, headers, priority, callback) { }); stream.associated = socket; - socket.connection._addStream(stream); socket._lock(function() { this._spdyState.framer.streamFrame( diff --git a/testing/xpcshell/node-spdy/lib/spdy/stream.js b/testing/xpcshell/node-spdy/lib/spdy/stream.js index bb50c72854bf..45b0b4c2ec31 100644 --- a/testing/xpcshell/node-spdy/lib/spdy/stream.js +++ b/testing/xpcshell/node-spdy/lib/spdy/stream.js @@ -7,6 +7,8 @@ var stream = require('stream'); var Buffer = require('buffer').Buffer; var constants = spdy.protocol.constants; +var empty = new Buffer(0); + if (!/^v(0\.10|0\.8|0\.9)\./.test(process.version)) var httpCommon = require('_http_common'); @@ -50,6 +52,15 @@ function Stream(connection, options) { state.id = options.id; state.associated = options.associated; state.headers = options.headers || {}; + + // Protocol standard compliance: + // Client does not have to send this header, we must assume, + // that it can handle compression + if (connection._spdyState.isServer && !state.associated && + !state.headers['accept-encoding']) { + state.headers['accept-encoding'] = 'gzip, deflate'; + } + if (connection._spdyState.xForward !== null) state.headers['x-forwarded-for'] = connection._spdyState.xForward; @@ -58,6 +69,7 @@ function Stream(connection, options) { connection._spdyState.counters.streamCount++; if (state.isPush) connection._spdyState.counters.pushCount++; + connection._addStream(this); // Useful for PUSH streams state.scheme = state.headers.scheme; @@ -111,8 +123,25 @@ exports.Stream = Stream; // Parser lookup methods Stream.prototype._parserHeadersComplete = function parserHeadersComplete() { + var method; if (this.parser) - return this.parser.onHeadersComplete || this.parser[1]; + method = this.parser.onHeadersComplete || this.parser[1]; + if (!method || !utils.isArgvParser) + return method; + + return function onHeadersCompleteWrap(info) { + // NOTE: shouldKeepAlive = false + return method.call(this, + info.versionMajor, + info.versionMinor, + info.headers, + info.method, + info.url, + info.statusCode, + info.statusMessage, + info.upgrade, + false); + }; }; Stream.prototype._parserBody = function parserBody() { @@ -154,7 +183,7 @@ Stream.prototype._init = function init() { return this.once('_chunkDone', onfinish); var self = this; - this._writeData(true, [], function() { + this._writeData(true, empty, function() { state.closedBy.us = true; if (state.sinkBuffer.length !== 0) return; @@ -716,7 +745,6 @@ Stream.prototype._parseClientRequest = function parseClientRequest(data, cb) { } connection.write(frame); self._unlock(); - connection._addStream(self); self.emit('_spdyRequest'); state.initialized = true; diff --git a/testing/xpcshell/node-spdy/lib/spdy/utils.js b/testing/xpcshell/node-spdy/lib/spdy/utils.js index 7be30bf21e2a..3d851ed1da90 100644 --- a/testing/xpcshell/node-spdy/lib/spdy/utils.js +++ b/testing/xpcshell/node-spdy/lib/spdy/utils.js @@ -12,6 +12,8 @@ if (utils.isLegacy) else utils.DuplexStream = stream.Duplex; +utils.isArgvParser = !/^v(0\.12|0\.11|0\.10|0\.8|0\.9)\./.test(process.version); + // // ### function createDeflate (version, compression) // #### @version {Number} SPDY version @@ -44,7 +46,7 @@ utils.createInflate = function createInflate(version) { var inflate = zlib.createInflate({ dictionary: spdy.protocol.dictionary[version], flush: zlib.Z_SYNC_FLUSH, - windowBits: 15 + windowBits: 0 }); // Define lock information early diff --git a/testing/xpcshell/node-spdy/package.json b/testing/xpcshell/node-spdy/package.json index 7ddfd7b78291..3ec0a6b78c45 100644 --- a/testing/xpcshell/node-spdy/package.json +++ b/testing/xpcshell/node-spdy/package.json @@ -1,6 +1,6 @@ { "name": "spdy", - "version": "1.29.1", + "version": "1.32.5", "description": "Implementation of the SPDY protocol on node.js.", "license": "MIT", "keywords": [ @@ -13,7 +13,7 @@ "homepage": "https://github.com/indutny/node-spdy", "bugs": { "email": "node-spdy+bugs@indutny.com", - "url": "https://github.com/indunty/node-spdy/issues" + "url": "https://github.com/indutny/node-spdy/issues" }, "author": "Fedor Indutny ", "contributors": [ diff --git a/testing/xpcshell/node-spdy/test/unit/connect-test.js b/testing/xpcshell/node-spdy/test/unit/connect-test.js index fef5a2218ccd..aecd3e2a66b6 100644 --- a/testing/xpcshell/node-spdy/test/unit/connect-test.js +++ b/testing/xpcshell/node-spdy/test/unit/connect-test.js @@ -17,6 +17,13 @@ suite('A SPDY Server / Connect', function() { req.url === '/deflate' ? zlib.createDeflate() : null; + if (req.url == '/headerTest') { + if (req.headers['accept-encoding'] == 'gzip, deflate') + return res.end(fox); + else + return res.end(); + } + // Terminate connection gracefully if (req.url === '/goaway') req.socket.connection.end(); @@ -202,4 +209,31 @@ suite('A SPDY Server / Connect', function() { done(); }); }); + + test('should add accept-encoding header to request headers, ' + + 'if none present', + function(done) { + var agent = spdy.createAgent({ + host: '127.0.0.1', + port: PORT, + rejectUnauthorized: false + }); + + var req = https.request({ + path: '/headerTest', + method: 'GET', + agent: agent, + }, function(res) { + var total = ''; + res.on('data', function(chunk){ + total += chunk; + }); + res.once('end', function() { + agent.close(); + assert.equal(total, fox); + done(); + }); + }); + req.end(); + }); }); diff --git a/testing/xpcshell/node-spdy/test/unit/stream-test.js b/testing/xpcshell/node-spdy/test/unit/stream-test.js index a9443a6d92c4..356b93922f0c 100644 --- a/testing/xpcshell/node-spdy/test/unit/stream-test.js +++ b/testing/xpcshell/node-spdy/test/unit/stream-test.js @@ -368,4 +368,44 @@ suite('A SPDY Server / Stream', function() { pair.client.req.end(); }); } + + test('it should not attempt to send empty arrays', function(done) { + var server = spdy.createServer(keys); + var agent; + + server.on('request', function(req, res) { + setTimeout(function() { + res.end(); + done(); + server.close(); + agent.close(); + }, 100); + }); + + server.listen(PORT, function() { + agent = spdy.createAgent({ + port: PORT, + rejectUnauthorized: false + }).on('error', function(err) { + done(err); + }); + + var body = new Buffer([1,2,3,4]); + + var opts = { + method: 'POST', + headers: { + 'Host': 'localhost', + 'Content-Length': body.length + }, + path: '/some-url', + agent: agent + }; + + var req = https.request(opts, function(res) { + }).on('error', function(err) { + }); + req.end(body); + }); + }); }); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 4922423b6537..5a6b3b4720ff 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -9966,6 +9966,30 @@ "bug_numbers": [1276714, 1276716], "description": "Number of times about:crashes has been opened.", "releaseChannelCollection": "opt-out" + }, + "D3D9_COMPOSITING_FAILURE_ID": { + "alert_emails": ["bgirard@mozilla.com"], + "expires_in_version": "never", + "kind": "count", + "keyed": true, + "description": "D3D9 compositor runtime and dynamic failure IDs. This will record a count for each context creation success or failure. Each failure id is a unique identifier that can be traced back to a particular failure branch or blocklist rule.", + "bug_numbers": [1002846] + }, + "D3D11_COMPOSITING_FAILURE_ID": { + "alert_emails": ["bgirard@mozilla.com"], + "expires_in_version": "never", + "kind": "count", + "keyed": true, + "description": "D3D11 compositor runtime and dynamic failure IDs. This will record a count for each context creation success or failure. Each failure id is a unique identifier that can be traced back to a particular failure branch or blocklist rule.", + "bug_numbers": [1002846] + }, + "OPENGL_COMPOSITING_FAILURE_ID": { + "alert_emails": ["bgirard@mozilla.com"], + "expires_in_version": "never", + "kind": "count", + "keyed": true, + "description": "OpenGL compositor runtime and dynamic failure IDs. This will record a count for each context creation success or failure. Each failure id is a unique identifier that can be traced back to a particular failure branch or blocklist rule.", + "bug_numbers": [1002846] }, "XHR_IN_WORKER": { "alert_emails": ["amarchesini@mozilla.com"], diff --git a/toolkit/modules/PopupNotifications.jsm b/toolkit/modules/PopupNotifications.jsm index c20e8cea1ed1..e0ba12acd057 100644 --- a/toolkit/modules/PopupNotifications.jsm +++ b/toolkit/modules/PopupNotifications.jsm @@ -955,6 +955,8 @@ PopupNotifications.prototype = { if (this._currentNotifications.length == 0) return; + event.stopPropagation(); + // Get the anchor that is the immediate child of the icon box let anchor = event.target; while (anchor && anchor.parentNode != this.iconBox) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 7b1a8dd3e400..988954a3f487 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -3923,6 +3923,9 @@ this.XPIProvider = { * same ID is already temporarily installed */ installTemporaryAddon: Task.async(function*(aFile) { + if (aFile.exists() && aFile.isFile()) { + flushJarCache(aFile); + } let addon = yield loadManifestFromFile(aFile, TemporaryInstallLocation); if (!addon.bootstrap) { diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js index e1ce28839257..d6f4a76800f2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js @@ -127,14 +127,13 @@ add_task(function*() { do_check_eq(addon.type, "extension"); do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED); - // test that an unpacked add-on works too let tempdir = gTmpD.clone(); + // test that an unpacked add-on works too writeInstallRDFToDir({ id: ID, - version: "2.0", + version: "3.0", bootstrap: true, - unpack: true, targetApplications: [{ id: "xpcshell@tests.mozilla.org", minVersion: "1", @@ -150,14 +149,14 @@ add_task(function*() { yield AddonManager.installTemporaryAddon(unpacked_addon); - BootstrapMonitor.checkAddonInstalled(ID, "2.0"); - BootstrapMonitor.checkAddonStarted(ID, "2.0"); + BootstrapMonitor.checkAddonInstalled(ID, "3.0"); + BootstrapMonitor.checkAddonStarted(ID, "3.0"); addon = yield promiseAddonByID(ID); // temporary add-on is installed and started do_check_neq(addon, null); - do_check_eq(addon.version, "2.0"); + do_check_eq(addon.version, "3.0"); do_check_eq(addon.name, "Test Bootstrap 1 (temporary)"); do_check_true(addon.isCompatible); do_check_false(addon.appDisabled); @@ -183,6 +182,130 @@ add_task(function*() { do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED); unpacked_addon.remove(true); + + // on Windows XPI files will be locked by the JAR cache, skip this test there. + if (!("nsIWindowsRegKey" in Components.interfaces)) { + // test that a packed (XPI) add-on works + writeInstallRDFToXPI({ + id: ID, + version: "2.0", + bootstrap: true, + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "1", + maxVersion: "1" + }], + name: "Test Bootstrap 1 (temporary)", + }, tempdir, "bootstrap1@tests.mozilla.org"); + + let packed_addon = tempdir.clone(); + packed_addon.append(ID + ".xpi"); + + yield AddonManager.installTemporaryAddon(packed_addon); + + addon = yield promiseAddonByID(ID); + + // temporary add-on is installed and started + do_check_neq(addon, null); + do_check_eq(addon.version, "2.0"); + do_check_eq(addon.name, "Test Bootstrap 1 (temporary)"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + restartManager(); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + addon = yield promiseAddonByID(ID); + + // existing add-on is back + do_check_neq(addon, null); + do_check_eq(addon.version, "1.0"); + do_check_eq(addon.name, "Test Bootstrap 1"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + packed_addon.remove(false); + + // test that a webextension works + let webext = createTempWebExtensionFile({ + manifest: { + version: "4.0", + name: "Test WebExtension 1 (temporary)", + applications: { + gecko: { + id: ID + } + } + } + }); + + yield AddonManager.installTemporaryAddon(webext); + addon = yield promiseAddonByID(ID); + + // temporary add-on is installed and started + do_check_neq(addon, null); + do_check_eq(addon.version, "4.0"); + do_check_eq(addon.name, "Test WebExtension 1 (temporary)"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + // test that re-loading a webextension works, using the same filename + webext.remove(false); + webext = createTempWebExtensionFile({ + manifest: { + version: "5.0", + name: "Test WebExtension 1 (temporary)", + applications: { + gecko: { + id: ID + } + } + } + }); + + yield AddonManager.installTemporaryAddon(webext); + addon = yield promiseAddonByID(ID); + + // temporary add-on is installed and started + do_check_neq(addon, null); + do_check_eq(addon.version, "5.0"); + do_check_eq(addon.name, "Test WebExtension 1 (temporary)"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + + restartManager(); + + BootstrapMonitor.checkAddonInstalled(ID, "1.0"); + BootstrapMonitor.checkAddonStarted(ID, "1.0"); + + addon = yield promiseAddonByID(ID); + + // existing add-on is back + do_check_neq(addon, null); + do_check_eq(addon.version, "1.0"); + do_check_eq(addon.name, "Test Bootstrap 1"); + do_check_true(addon.isCompatible); + do_check_false(addon.appDisabled); + do_check_true(addon.isActive); + do_check_eq(addon.type, "extension"); + do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED); + } + + // remove original add-on addon.uninstall(); BootstrapMonitor.checkAddonNotInstalled(ID); diff --git a/xpcom/base/nsSystemInfo.cpp b/xpcom/base/nsSystemInfo.cpp index 7a50ab935153..6ebd6c9c77cd 100644 --- a/xpcom/base/nsSystemInfo.cpp +++ b/xpcom/base/nsSystemInfo.cpp @@ -32,6 +32,9 @@ #ifdef MOZ_WIDGET_GTK #include #include +#endif + +#if defined (XP_LINUX) && !defined (ANDROID) #include #include #include "mozilla/Tokenizer.h" @@ -73,7 +76,7 @@ NS_EXPORT int android_sdk_version; // only happens well after that point. uint32_t nsSystemInfo::gUserUmask = 0; -#if defined (MOZ_WIDGET_GTK) +#if defined (XP_LINUX) && !defined (ANDROID) static void SimpleParseKeyValuePairs(const std::string& aFilename, std::map& aKeyValuePairs) @@ -497,7 +500,7 @@ nsSystemInfo::Init() } MOZ_ASSERT(sizeof(sysctlValue32) == len); -#elif defined (MOZ_WIDGET_GTK) +#elif defined (XP_LINUX) && !defined (ANDROID) // Get vendor, family, model, stepping, physical cores, L3 cache size // from /proc/cpuinfo file {