Backed out 3 changesets (bug 1434376)for failing browser chrome at browser/base/content/test/performance/browser_urlbar_search_reflows.js on a CLOSED TREE

Backed out changeset b636251b75ab (bug 1434376)
Backed out changeset fccbba9cb959 (bug 1434376)
Backed out changeset b5128504011c (bug 1434376)
This commit is contained in:
Andreea Pavel 2018-02-25 12:44:28 +02:00
parent 56da525c38
commit 1aac7df383
14 changed files with 128 additions and 518 deletions

View File

@ -8010,7 +8010,7 @@ var gIdentityHandler = {
classes += " in-use";
// Synchronize control center and identity block blinking animations.
window.promiseDocumentFlushed(() => {}).then(() => {
BrowserUtils.promiseLayoutFlushed(document, "style", () => {
let sharingIconBlink = document.getElementById("sharing-icon").getAnimations()[0];
if (sharingIconBlink) {
let startTime = sharingIconBlink.startTime;

View File

@ -283,7 +283,7 @@
<parameter name="aTab"/>
<body>
<![CDATA[
aTab.ownerGlobal.promiseDocumentFlushed(() => {
BrowserUtils.promiseLayoutFlushed(aTab.ownerDocument, "style", () => {
if (!aTab.parentNode) {
return;
}

View File

@ -82,8 +82,9 @@ async function withReflowObserver(testFn, expectedReflows = [], win = window) {
},
reflowInterruptible(start, end) {
// Interruptible reflows are the reflows caused by the refresh
// driver ticking. These are fine.
// We're not interested in interruptible reflows, but might as well take the
// opportuntiy to dirty the root frame.
dirtyFrameFn();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,

View File

@ -569,7 +569,11 @@ CustomizeMode.prototype = {
}
// Wait until the next frame before setting the class to ensure
// we do start the animation.
// we do start the animation. We cannot use promiseLayoutFlushed
// here because callback is only invoked when any actual reflow
// happens, while that may not happen soonish enough. If we have
// an observer for style flush, we may be able to replace the
// nested rAFs with that.
this.window.requestAnimationFrame(() => {
this.window.requestAnimationFrame(() => {
animationNode.classList.add("animate-out");
@ -2417,8 +2421,7 @@ CustomizeMode.prototype = {
panelOnTheLeft = true;
}
} else {
await this.window.promiseDocumentFlushed(() => {});
await BrowserUtils.promiseLayoutFlushed(doc, "display", () => {});
if (!this._customizing || !this._wantToBeInCustomizeMode) {
return;
}

View File

@ -495,8 +495,8 @@ var PanelMultiView = class extends this.AssociatedToNode {
// binding for the <panelmultiview> element may still be disconnected.
// In this case, give the layout code a chance to run.
if (!this.connected) {
await this.window.promiseDocumentFlushed(() => {});
await BrowserUtils.promiseLayoutFlushed(this.document, "layout",
() => {});
// The XBL binding must be connected at this point. If this is not the
// case, the calling code should be updated to unhide the panel.
if (!this.connected) {
@ -788,7 +788,7 @@ var PanelMultiView = class extends this.AssociatedToNode {
return;
}
const { window } = this;
const {window, document} = this;
let nextPanelView = PanelView.forNode(viewNode);
let prevPanelView = PanelView.forNode(previousViewNode);
@ -847,7 +847,7 @@ var PanelMultiView = class extends this.AssociatedToNode {
// description elements it contains.
nextPanelView.descriptionHeightWorkaround();
viewRect = await window.promiseDocumentFlushed(() => {
viewRect = await BrowserUtils.promiseLayoutFlushed(this.document, "layout", () => {
return this._dwu.getBoundsWithoutFlushing(viewNode);
});
@ -894,7 +894,7 @@ var PanelMultiView = class extends this.AssociatedToNode {
// sliding animation with smaller views.
viewNode.style.width = viewRect.width + "px";
await window.promiseDocumentFlushed(() => {});
await BrowserUtils.promiseLayoutFlushed(document, "layout", () => {});
// Kick off the transition!
details.phase = TRANSITION_PHASES.TRANSITION;
@ -1000,7 +1000,7 @@ var PanelMultiView = class extends this.AssociatedToNode {
// We force 'display: none' on the previous view node to make sure that it
// doesn't cause an annoying flicker whilst resetting the styles above.
previousViewNode.style.display = "none";
await this.window.promiseDocumentFlushed(() => {});
await BrowserUtils.promiseLayoutFlushed(this.document, "layout", () => {});
previousViewNode.style.removeProperty("display");
}
}

View File

@ -1608,8 +1608,10 @@ PlacesToolbar.prototype = {
notify: function PT_notify(aTimer) {
if (aTimer == this._updateNodesVisibilityTimer) {
this._updateNodesVisibilityTimer = null;
// Bug 1440070: This should use promiseDocumentFlushed, so that
// _updateNodesVisibilityTimerCallback can use getBoundsWithoutFlush.
// TODO: This should use promiseLayoutFlushed("layout"), so that
// _updateNodesVisibilityTimerCallback can use getBoundsWithoutFlush. But
// for yet unknown reasons doing that causes intermittent failures,
// apparently due the flush not happening in a meaningful time.
window.requestAnimationFrame(this._updateNodesVisibilityTimerCallback.bind(this));
} else if (aTimer == this._ibTimer) {
// * Timer to turn off indicator bar.

View File

@ -856,41 +856,6 @@ nsGlobalWindowInner::IsBackgroundInternal() const
return !mOuterWindow || mOuterWindow->IsBackground();
}
class PromiseDocumentFlushedResolver final {
public:
PromiseDocumentFlushedResolver(Promise* aPromise,
PromiseDocumentFlushedCallback& aCallback)
: mPromise(aPromise)
, mCallback(&aCallback)
{
}
virtual ~PromiseDocumentFlushedResolver() = default;
void Call()
{
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
ErrorResult error;
JS::Rooted<JS::Value> returnVal(RootingCx());
mCallback->Call(&returnVal, error);
if (error.Failed()) {
mPromise->MaybeReject(error);
} else {
mPromise->MaybeResolve(returnVal);
}
}
void Cancel()
{
mPromise->MaybeReject(NS_ERROR_ABORT);
}
RefPtr<Promise> mPromise;
RefPtr<PromiseDocumentFlushedCallback> mCallback;
};
//*****************************************************************************
//*** nsGlobalWindowInner: Object Management
//*****************************************************************************
@ -924,8 +889,6 @@ nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter *aOuterWindow)
mCleanedUp(false),
mDialogAbuseCount(0),
mAreDialogsEnabled(true),
mObservingDidRefresh(false),
mIteratingDocumentFlushedResolvers(false),
mCanSkipCCGeneration(0),
mBeforeUnloadListenerCount(0)
{
@ -1330,13 +1293,6 @@ nsGlobalWindowInner::FreeInnerObjects()
while (mDoc->EventHandlingSuppressed()) {
mDoc->UnsuppressEventHandlingAndFireEvents(false);
}
if (mObservingDidRefresh) {
nsIPresShell* shell = mDoc->GetShell();
if (shell) {
Unused << shell->RemovePostRefreshObserver(this);
}
}
}
// Remove our reference to the document and the document principal.
@ -1378,11 +1334,6 @@ nsGlobalWindowInner::FreeInnerObjects()
}
mBeforeUnloadListenerCount = 0;
}
// If we have any promiseDocumentFlushed callbacks, fire them now so
// that the Promises can resolve.
CallDocumentFlushedResolvers();
mObservingDidRefresh = false;
}
//*****************************************************************************
@ -1538,12 +1489,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mGroupMessageManagers)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPromises)
for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mPromise);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mCallback);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner)
@ -1638,11 +1583,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner)
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPromises)
for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) {
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mPromise);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mCallback);
}
tmp->mDocumentFlushedResolvers.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -7383,125 +7323,6 @@ nsGlobalWindowInner::BeginWindowMove(Event& aMouseDownEvent, Element* aPanel,
aError = widget->BeginMoveDrag(mouseEvent);
}
already_AddRefed<Promise>
nsGlobalWindowInner::PromiseDocumentFlushed(PromiseDocumentFlushedCallback& aCallback,
ErrorResult& aError)
{
MOZ_RELEASE_ASSERT(IsChromeWindow());
if (!IsCurrentInnerWindow()) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (mIteratingDocumentFlushedResolvers) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (!mDoc) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsIPresShell* shell = mDoc->GetShell();
if (!shell) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
// We need to associate the lifetime of the Promise to the lifetime
// of the caller's global. That way, if the window we're observing
// refresh driver ticks on goes away before our observer is fired,
// we can still resolve the Promise.
nsIGlobalObject* global = GetIncumbentGlobal();
if (!global) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
RefPtr<Promise> resultPromise = Promise::Create(global, aError);
if (aError.Failed()) {
return nullptr;
}
UniquePtr<PromiseDocumentFlushedResolver> flushResolver(
new PromiseDocumentFlushedResolver(resultPromise, aCallback));
if (!shell->NeedFlush(FlushType::Style)) {
flushResolver->Call();
return resultPromise.forget();
}
if (!mObservingDidRefresh) {
bool success = shell->AddPostRefreshObserver(this);
if (!success) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
mObservingDidRefresh = true;
}
mDocumentFlushedResolvers.AppendElement(Move(flushResolver));
return resultPromise.forget();
}
void
nsGlobalWindowInner::CallDocumentFlushedResolvers()
{
MOZ_ASSERT(!mIteratingDocumentFlushedResolvers);
mIteratingDocumentFlushedResolvers = true;
for (const auto& documentFlushedResolver : mDocumentFlushedResolvers) {
documentFlushedResolver->Call();
}
mDocumentFlushedResolvers.Clear();
mIteratingDocumentFlushedResolvers = false;
}
void
nsGlobalWindowInner::CancelDocumentFlushedResolvers()
{
MOZ_ASSERT(!mIteratingDocumentFlushedResolvers);
mIteratingDocumentFlushedResolvers = true;
for (const auto& documentFlushedResolver : mDocumentFlushedResolvers) {
documentFlushedResolver->Cancel();
}
mDocumentFlushedResolvers.Clear();
mIteratingDocumentFlushedResolvers = false;
}
void
nsGlobalWindowInner::DidRefresh()
{
auto rejectionGuard = MakeScopeExit([&] {
CancelDocumentFlushedResolvers();
mObservingDidRefresh = false;
});
MOZ_ASSERT(mDoc);
nsIPresShell* shell = mDoc->GetShell();
MOZ_ASSERT(shell);
if (shell->NeedStyleFlush() || shell->HasPendingReflow()) {
// By the time our observer fired, something has already invalidated
// style and maybe layout. We'll wait until the next refresh driver
// tick instead.
rejectionGuard.release();
return;
}
bool success = shell->RemovePostRefreshObserver(this);
if (!success) {
return;
}
rejectionGuard.release();
CallDocumentFlushedResolvers();
mObservingDidRefresh = false;
}
already_AddRefed<nsWindowRoot>
nsGlobalWindowInner::GetWindowRoot(mozilla::ErrorResult& aError)
{

View File

@ -57,7 +57,6 @@
#include "nsCheapSets.h"
#include "mozilla/dom/ImageBitmapSource.h"
#include "mozilla/UniquePtr.h"
#include "nsRefreshDriver.h"
class nsIArray;
class nsIBaseWindow;
@ -90,8 +89,6 @@ class IdleRequestExecutor;
class DialogValueHolder;
class PromiseDocumentFlushedResolver;
namespace mozilla {
class AbstractThread;
class ThrottledEventQueue;
@ -214,8 +211,7 @@ class nsGlobalWindowInner : public mozilla::dom::EventTarget,
public nsIScriptObjectPrincipal,
public nsSupportsWeakReference,
public nsIInterfaceRequestor,
public PRCListStr,
public nsAPostRefreshObserver
public PRCListStr
{
public:
typedef mozilla::TimeStamp TimeStamp;
@ -948,12 +944,6 @@ public:
mozilla::dom::Element* aPanel,
mozilla::ErrorResult& aError);
already_AddRefed<mozilla::dom::Promise>
PromiseDocumentFlushed(mozilla::dom::PromiseDocumentFlushedCallback& aCallback,
mozilla::ErrorResult& aError);
void DidRefresh() override;
void GetDialogArgumentsOuter(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
nsIPrincipal& aSubjectPrincipal,
mozilla::ErrorResult& aError);
@ -1279,9 +1269,6 @@ private:
mChromeFields.mGroupMessageManagers.Clear();
}
void CallDocumentFlushedResolvers();
void CancelDocumentFlushedResolvers();
public:
// Dispatch a runnable related to the global.
virtual nsresult Dispatch(mozilla::TaskCategory aCategory,
@ -1428,14 +1415,6 @@ protected:
// currently enabled on this window.
bool mAreDialogsEnabled;
// This flag keeps track of whether this window is currently
// observing DidRefresh notifications from the refresh driver.
bool mObservingDidRefresh;
// This flag keeps track of whether or not we're going through
// promiseDocumentFlushed resolvers. When true, promiseDocumentFlushed
// cannot be called.
bool mIteratingDocumentFlushedResolvers;
nsTArray<uint32_t> mEnabledSensors;
#if defined(MOZ_WIDGET_ANDROID)
@ -1462,8 +1441,6 @@ protected:
nsTArray<RefPtr<mozilla::dom::Promise>> mPendingPromises;
nsTArray<mozilla::UniquePtr<PromiseDocumentFlushedResolver>> mDocumentFlushedResolvers;
static InnerWindowByIdTable* sInnerWindowsById;
// Members in the mChromeFields member should only be used in chrome windows.

View File

@ -48,7 +48,6 @@ skip-if = !e10s # this only makes sense with e10s-multi
[browser_messagemanager_unload.js]
[browser_pagehide_on_tab_close.js]
skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s.
[browser_promiseDocumentFlushed.js]
[browser_state_notifications.js]
skip-if = true # Bug 1271028
[browser_use_counters.js]

View File

@ -1,246 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Dirties style and layout on the current browser window.
*
* @param {Number} Optional factor by which to modify the DOM. Useful for
* when multiple calls to dirtyTheDOM may occur, and you need them
* to dirty the DOM differently from one another. If you only need
* to dirty the DOM once, this can be omitted.
*/
function dirtyStyleAndLayout(factor = 1) {
gNavToolbox.style.padding = factor + "px";
}
/**
* Dirties style of the current browser window, but NOT layout.
*/
function dirtyStyle() {
gNavToolbox.style.color = "red";
}
const gWindowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
/**
* Asserts that no style or layout flushes are required by the
* current window.
*/
function assertNoFlushesRequired() {
Assert.ok(!gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_STYLE),
"No flushes are required.");
}
/**
* Asserts that the DOM has been dirtied, and so style and layout flushes
* are required.
*/
function assertFlushesRequired() {
Assert.ok(gWindowUtils.needsFlush(Ci.nsIDOMWindowUtils.FLUSH_LAYOUT),
"Style and layout flushes are required.");
}
/**
* Removes style changes from dirtyTheDOM() from the browser window,
* and resolves once the refresh driver ticks.
*/
async function cleanTheDOM() {
gNavToolbox.style.padding = "";
gNavToolbox.style.color = "";
await window.promiseDocumentFlushed(() => {});
}
add_task(async function setup() {
registerCleanupFunction(cleanTheDOM);
});
/**
* Tests that if the DOM is dirty, that promiseDocumentFlushed will
* resolve once layout and style have been flushed.
*/
add_task(async function test_basic() {
dirtyStyleAndLayout();
assertFlushesRequired();
await window.promiseDocumentFlushed(() => {});
assertNoFlushesRequired();
dirtyStyle();
assertFlushesRequired();
await window.promiseDocumentFlushed(() => {});
assertNoFlushesRequired();
// The DOM should be clean already, but we'll do this anyway to isolate
// failures from other tests.
await cleanTheDOM();
});
/**
* Test that values returned by the callback passed to promiseDocumentFlushed
* get passed down through the Promise resolution.
*/
add_task(async function test_can_get_results_from_callback() {
const NEW_PADDING = "2px";
gNavToolbox.style.padding = NEW_PADDING;
assertFlushesRequired();
let paddings = await window.promiseDocumentFlushed(() => {
let style = window.getComputedStyle(gNavToolbox);
return {
left: style.paddingLeft,
right: style.paddingRight,
top: style.paddingTop,
bottom: style.paddingBottom,
};
});
for (let prop in paddings) {
Assert.equal(paddings[prop], NEW_PADDING,
"Got expected padding");
}
await cleanTheDOM();
gNavToolbox.style.padding = NEW_PADDING;
assertFlushesRequired();
let rect = await window.promiseDocumentFlushed(() => {
let observer = {
reflow() {
Assert.ok(false, "A reflow should not have occurred.");
},
reflowInterruptible() {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
Ci.nsISupportsWeakReference])
};
let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
docShell.addWeakReflowObserver(observer);
let toolboxRect = gNavToolbox.getBoundingClientRect();
docShell.removeWeakReflowObserver(observer);
return toolboxRect;
});
// The actual values of this rect aren't super important for
// the purposes of this test - we just want to know that a valid
// rect was returned, so checking for properties being greater than
// 0 is sufficient.
for (let property of ["width", "height"]) {
Assert.ok(rect[property] > 0, `Rect property ${property} > 0 (${rect[property]})`);
}
await cleanTheDOM();
});
/**
* Test that if promiseDocumentFlushed is requested on a window
* that closes before it gets a chance to do a refresh driver
* tick, the promiseDocumentFlushed Promise is still resolved, and
* the callback is still called.
*/
add_task(async function test_resolved_in_window_close() {
let win = await BrowserTestUtils.openNewBrowserWindow();
await win.promiseDocumentFlushed(() => {});
let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
docShell.contentViewer.pausePainting();
win.gNavToolbox.style.padding = "5px";
const EXPECTED = 1234;
let promise = win.promiseDocumentFlushed(() => {
// Despite the window not painting before closing, this
// callback should be fired when the window gets torn
// down and should stil be able to return a result.
return EXPECTED;
});
await BrowserTestUtils.closeWindow(win);
Assert.equal(await promise, EXPECTED);
});
/**
* Test that re-entering promiseDocumentFlushed is not possible
* from within a promiseDocumentFlushed callback. Doing so will
* result in the outer Promise rejecting with NS_ERROR_FAILURE.
*/
add_task(async function test_reentrancy() {
dirtyStyleAndLayout();
assertFlushesRequired();
let promise = window.promiseDocumentFlushed(() => {
return window.promiseDocumentFlushed(() => {
Assert.ok(false, "Should never run this.");
});
});
await Assert.rejects(promise, ex => ex.result == Cr.NS_ERROR_FAILURE);
});
/**
* Tests the expected execution order of a series of promiseDocumentFlushed
* calls, their callbacks, and the resolutions of their Promises.
*
* When multiple promiseDocumentFlushed callbacks are queued, the callbacks
* should always been run first before any of the Promises are resolved.
*
* The callbacks should run in the order that they were queued in via
* promiseDocumentFlushed. The Promise resolutions should similarly run
* in the order that promiseDocumentFlushed was called in.
*/
add_task(async function test_execution_order() {
let result = [];
dirtyStyleAndLayout(1);
let promise1 = window.promiseDocumentFlushed(() => {
result.push(0);
}).then(() => {
result.push(2);
});
let promise2 = window.promiseDocumentFlushed(() => {
result.push(1);
}).then(() => {
result.push(3);
});
await Promise.all([promise1, promise2]);
Assert.equal(result.length, 4,
"Should have run all callbacks and Promises.");
let promise3 = window.promiseDocumentFlushed(() => {
result.push(4);
}).then(() => {
result.push(6);
});
let promise4 = window.promiseDocumentFlushed(() => {
result.push(5);
}).then(() => {
result.push(7);
});
await Promise.all([promise3, promise4]);
Assert.equal(result.length, 8,
"Should have run all callbacks and Promises.");
for (let i = 0; i < result.length; ++i) {
Assert.equal(result[i], i,
"Callbacks and Promises should have run in the expected order.");
}
});

View File

@ -372,8 +372,6 @@ partial interface Window {
};
#endif
callback PromiseDocumentFlushedCallback = any ();
// Mozilla extensions for Chrome windows.
partial interface Window {
// The STATE_* constants need to match the corresponding enum in nsGlobalWindow.cpp.
@ -446,47 +444,6 @@ partial interface Window {
[Throws, Func="nsGlobalWindowInner::IsPrivilegedChromeWindow"]
void beginWindowMove(Event mouseDownEvent, optional Element? panel = null);
/**
* Calls the given function as soon as a style or layout flush for the
* top-level document is not necessary, and returns a Promise which
* resolves to the callback's return value after it executes.
*
* In the event that the window goes away before a flush can occur, the
* callback will still be called and the Promise resolved as the window
* tears itself down.
*
* Note that the callback can be called either synchronously or asynchronously
* depending on whether or not flushes are pending:
*
* The callback will be called synchronously when calling
* promiseDocumentFlushed when NO flushes are already pending. This is
* to ensure that no script has a chance to dirty the DOM before the callback
* is called.
*
* The callback will be called asynchronously if a flush is pending.
*
* The expected execution order is that all pending callbacks will
* be fired first (and in the order that they were queued) and then the
* Promise resolution handlers will all be invoked later on during the
* next microtask checkpoint.
*
* promiseDocumentFlushed does not support re-entrancy - so calling it from
* within a promiseDocumentFlushed callback will result in the inner call
* throwing an NS_ERROR_FAILURE exception, and the outer Promise rejecting
* with that exception.
*
* The callback function *must not make any changes which would require
* a style or layout flush*.
*
* Also throws NS_ERROR_FAILURE if the window is not in a state where flushes
* can be waited for (for example, the PresShell has not yet been created).
*
* @param {function} callback
* @returns {Promise}
*/
[Throws, Func="nsGlobalWindowInner::IsPrivilegedChromeWindow"]
Promise<any> promiseDocumentFlushed(PromiseDocumentFlushedCallback callback);
[Func="IsChromeOrXBL"]
readonly attribute boolean isChromeWindow;
};

View File

@ -2330,13 +2330,13 @@ public:
mFillRule = aFillRule;
}
mozilla::Position& GetPosition() {
Position& GetPosition() {
MOZ_ASSERT(mType == StyleBasicShapeType::Circle ||
mType == StyleBasicShapeType::Ellipse,
"expected circle or ellipse");
return mPosition;
}
const mozilla::Position& GetPosition() const {
const Position& GetPosition() const {
MOZ_ASSERT(mType == StyleBasicShapeType::Circle ||
mType == StyleBasicShapeType::Ellipse,
"expected circle or ellipse");
@ -2396,7 +2396,7 @@ private:
// (top, right, bottom, left) for inset
nsTArray<nsStyleCoord> mCoordinates;
// position of center for ellipse or circle
mozilla::Position mPosition;
Position mPosition;
// corner radii for inset (0 if not set)
nsStyleCorners mRadius;
};

View File

@ -14,6 +14,48 @@ ChromeUtils.defineModuleGetter(this, "PlacesUtils",
Cu.importGlobalProperties(["URL"]);
let reflowObservers = new WeakMap();
function ReflowObserver(doc) {
this._doc = doc;
doc.docShell.addWeakReflowObserver(this);
reflowObservers.set(this._doc, this);
this.callbacks = [];
}
ReflowObserver.prototype = {
QueryInterface: XPCOMUtils.generateQI(["nsIReflowObserver", "nsISupportsWeakReference"]),
_onReflow() {
reflowObservers.delete(this._doc);
this._doc.docShell.removeWeakReflowObserver(this);
for (let callback of this.callbacks) {
try {
callback();
} catch (e) {
Cu.reportError(e);
}
}
},
reflow() {
this._onReflow();
},
reflowInterruptible() {
this._onReflow();
},
};
const FLUSH_TYPES = {
"style": Ci.nsIDOMWindowUtils.FLUSH_STYLE,
"layout": Ci.nsIDOMWindowUtils.FLUSH_LAYOUT,
"display": Ci.nsIDOMWindowUtils.FLUSH_DISPLAY,
};
var BrowserUtils = {
/**
@ -386,7 +428,8 @@ var BrowserUtils = {
}
let bounds = dwu.getBoundsWithoutFlushing(toolbarItem);
if (!bounds.height) {
await window.promiseDocumentFlushed(() => {
let document = element.ownerDocument;
await BrowserUtils.promiseLayoutFlushed(document, "layout", () => {
bounds = dwu.getBoundsWithoutFlushing(toolbarItem);
});
}
@ -644,6 +687,65 @@ var BrowserUtils = {
return [url, postData];
},
/**
* Calls the given function when the given document has just reflowed,
* and returns a promise which resolves to its return value after it
* has been called.
*
* The function *must not trigger any reflows*, or make any changes
* which would require a layout flush.
*
* @param {Document} doc
* @param {function} callback
* @returns {Promise}
*/
promiseReflowed(doc, callback) {
let observer = reflowObservers.get(doc);
if (!observer) {
observer = new ReflowObserver(doc);
reflowObservers.set(doc, observer);
}
return new Promise((resolve, reject) => {
observer.callbacks.push(() => {
try {
resolve(callback());
} catch (e) {
reject(e);
}
});
});
},
/**
* Calls the given function as soon as a layout flush of the given
* type is not necessary, and returns a promise which resolves to the
* callback's return value after it executes.
*
* The function *must not trigger any reflows*, or make any changes
* which would require a layout flush.
*
* @param {Document} doc
* @param {string} flushType
* The flush type required. Must be one of:
*
* - "style"
* - "layout"
* - "display"
* @param {function} callback
* @returns {Promise}
*/
async promiseLayoutFlushed(doc, flushType, callback) {
let utils = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
if (!utils.needsFlush(FLUSH_TYPES[flushType])) {
return callback();
}
return this.promiseReflowed(doc, callback);
},
/**
* Generate a document fragment for a localized string that has DOM
* node replacements. This avoids using getFormattedString followed

View File

@ -10,12 +10,6 @@
#include "mozilla/TimeStamp.h"
#include "mozilla/TypeTraits.h"
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
// GetTickCount().
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif
namespace mozilla {
// Utility class that converts time values represented as an unsigned integral