Bug 1176019 - Force a paint when switching to a loaded tab r=mconley

This is fairly straightforward, other than the fact that the
nomenclature gets a bit awkward with the aForce parameter on
the ForcePaint methods. I'm not sure which direction to go with
this - "aForce" seems a fairly intuitive name for what we want,
and I'm kind of inclined to say the existing ForcePaint mechanic
should be renamed to something like PaintWithInterrupt, or
PaintWithPriority.

MozReview-Commit-ID: Bj9DROug1pC

--HG--
extra : rebase_source : a3d91fec940d83325d36bafb13fe892e9c9530e8
This commit is contained in:
Doug Thayer 2018-05-14 23:45:00 -07:00
parent 02388061cd
commit fc77954257
13 changed files with 159 additions and 90 deletions

View File

@ -979,6 +979,9 @@ class AsyncTabSwitcher {
this.requestedTab = tab;
if (tabState == this.STATE_LOADED) {
this.maybeVisibleTabs.clear();
if (tab.linkedBrowser.isRemoteBrowser) {
tab.linkedBrowser.forceRepaint();
}
}
tab.linkedBrowser.setAttribute("primary", "true");

View File

@ -35,6 +35,13 @@ interface nsITabParent : nsISupports
*/
readonly attribute boolean hasLayers;
/**
* Sends a message to the child ensuring that they paint as early as
* possible. This will send the message to paint even if renderLayers
* is already true.
*/
void forceRepaint();
/**
* As an optimisation, setting the docshell's active state to
* inactive also triggers a layer invalidation to free up some

View File

@ -5318,12 +5318,17 @@ ContentParent::SendGetFilesResponseAndForget(const nsID& aUUID,
}
void
ContentParent::ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch)
ContentParent::PaintTabWhileInterruptingJS(TabParent* aTabParent,
bool aForceRepaint,
uint64_t aLayerObserverEpoch)
{
if (!mHangMonitorActor) {
return;
}
ProcessHangMonitor::ForcePaint(mHangMonitorActor, aTabParent, aLayerObserverEpoch);
ProcessHangMonitor::PaintWhileInterruptingJS(mHangMonitorActor,
aTabParent,
aForceRepaint,
aLayerObserverEpoch);
}
void

View File

@ -639,7 +639,7 @@ public:
nsTArray<nsCString>* aResults) override;
// Use the PHangMonitor channel to ask the child to repaint a tab.
void ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch);
void PaintTabWhileInterruptingJS(TabParent* aTabParent, bool aForceRepaint, uint64_t aLayerObserverEpoch);
// This function is called when we are about to load a document from an
// HTTP(S), FTP or wyciwyg channel for a content process. It is a useful

View File

@ -560,7 +560,7 @@ parent:
/**
* Child informs the parent that the layer tree is already available.
*/
async ForcePaintNoOp(uint64_t aLayerObserverEpoch);
async PaintWhileInterruptingJSNoOp(uint64_t aLayerObserverEpoch);
/**
* Sent by the child to the parent to inform it that an update to the
@ -767,11 +767,15 @@ child:
* @param aEnabled
* True if the child should render and upload layers, false if the
* child should clear layers.
* @param aForceRepaint
* True if the child should force a paint even if it's already
* visible.
* @param aLayerObserverEpoch
* The layer observer epoch for this activation. This message should be
* ignored if this epoch has already been observed (via ForcePaint).
* ignored if this epoch has already been observed (via
* PaintWhileInterruptingJS).
*/
async RenderLayers(bool aEnabled, uint64_t aLayerObserverEpoch);
async RenderLayers(bool aEnabled, bool aForceRepaint, uint64_t aLayerObserverEpoch);
/**
* Notify the child that it shouldn't paint the offscreen displayport.

View File

@ -41,7 +41,7 @@ child:
async BeginStartingDebugger();
async EndStartingDebugger();
async ForcePaint(TabId tabId, uint64_t aLayerObserverEpoch);
async PaintWhileInterruptingJS(TabId tabId, bool forceRepaint, uint64_t aLayerObserverEpoch);
};
} // namespace mozilla

View File

@ -96,19 +96,21 @@ class HangMonitorChild
void ClearHang();
void ClearHangAsync();
void ClearForcePaint(uint64_t aLayerObserverEpoch);
void ClearPaintWhileInterruptingJS(uint64_t aLayerObserverEpoch);
// MaybeStartForcePaint will notify the background hang monitor of activity
// if this is the first time calling it since ClearForcePaint. It should be
// MaybeStartPaintWhileInterruptingJS will notify the background hang monitor of activity
// if this is the first time calling it since ClearPaintWhileInterruptingJS. It should be
// callable from any thread, but you must be holding mMonitor if using it off
// the main thread, since it could race with ClearForcePaint.
void MaybeStartForcePaint();
// the main thread, since it could race with ClearPaintWhileInterruptingJS.
void MaybeStartPaintWhileInterruptingJS();
mozilla::ipc::IPCResult RecvTerminateScript(const bool& aTerminateGlobal) override;
mozilla::ipc::IPCResult RecvBeginStartingDebugger() override;
mozilla::ipc::IPCResult RecvEndStartingDebugger() override;
mozilla::ipc::IPCResult RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch) override;
mozilla::ipc::IPCResult RecvPaintWhileInterruptingJS(const TabId& aTabId,
const bool& aForceRepaint,
const uint64_t& aLayerObserverEpoch) override;
void ActorDestroy(ActorDestroyReason aWhy) override;
@ -127,7 +129,7 @@ class HangMonitorChild
void ShutdownOnThread();
static Atomic<HangMonitorChild*> sInstance;
UniquePtr<BackgroundHangMonitor> mForcePaintMonitor;
UniquePtr<BackgroundHangMonitor> mPaintWhileInterruptingJSMonitor;
const RefPtr<ProcessHangMonitor> mHangMonitor;
Monitor mMonitor;
@ -140,9 +142,10 @@ class HangMonitorChild
bool mTerminateGlobal;
bool mStartDebugger;
bool mFinishedStartingDebugger;
bool mForcePaint;
TabId mForcePaintTab;
MOZ_INIT_OUTSIDE_CTOR uint64_t mForcePaintEpoch;
bool mPaintWhileInterruptingJS;
bool mPaintWhileInterruptingJSForce;
TabId mPaintWhileInterruptingJSTab;
MOZ_INIT_OUTSIDE_CTOR uint64_t mPaintWhileInterruptingJSEpoch;
JSContext* mContext;
bool mShutdownDone;
@ -227,7 +230,9 @@ public:
void Shutdown();
void ForcePaint(dom::TabParent* aTabParent, uint64_t aLayerObserverEpoch);
void PaintWhileInterruptingJS(dom::TabParent* aTabParent,
bool aForceRepaint,
uint64_t aLayerObserverEpoch);
void TerminateScript(bool aTerminateGlobal);
void BeginStartingDebugger();
@ -258,7 +263,7 @@ private:
void ClearHangNotification();
void ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch);
void PaintWhileInterruptingJSOnThread(TabId aTabId, bool aForceRepaint, uint64_t aLayerObserverEpoch);
void ShutdownOnThread();
@ -280,10 +285,10 @@ private:
Mutex mBrowserCrashDumpHashLock;
mozilla::ipc::TaskFactory<HangMonitorParent> mMainThreadTaskFactory;
static bool sShouldForcePaint;
static bool sShouldPaintWhileInterruptingJS;
};
bool HangMonitorParent::sShouldForcePaint = true;
bool HangMonitorParent::sShouldPaintWhileInterruptingJS = true;
} // namespace
@ -297,13 +302,14 @@ HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
mTerminateGlobal(false),
mStartDebugger(false),
mFinishedStartingDebugger(false),
mForcePaint(false),
mPaintWhileInterruptingJS(false),
mPaintWhileInterruptingJSForce(false),
mShutdownDone(false),
mIPCOpen(true)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
mContext = danger::GetJSContext();
mForcePaintMonitor =
mPaintWhileInterruptingJSMonitor =
MakeUnique<mozilla::BackgroundHangMonitor>("Gecko_Child_ForcePaint",
128, /* ms timeout for microhangs */
1024, /* ms timeout for permahangs */
@ -314,7 +320,7 @@ HangMonitorChild::~HangMonitorChild()
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
MOZ_ASSERT(sInstance == this);
mForcePaintMonitor = nullptr;
mPaintWhileInterruptingJSMonitor = nullptr;
sInstance = nullptr;
}
@ -323,24 +329,27 @@ HangMonitorChild::InterruptCallback()
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
bool forcePaint;
TabId forcePaintTab;
uint64_t forcePaintEpoch;
bool paintWhileInterruptingJS;
bool paintWhileInterruptingJSForce;
TabId paintWhileInterruptingJSTab;
uint64_t paintWhileInterruptingJSEpoch;
{
MonitorAutoLock lock(mMonitor);
forcePaint = mForcePaint;
forcePaintTab = mForcePaintTab;
forcePaintEpoch = mForcePaintEpoch;
paintWhileInterruptingJS = mPaintWhileInterruptingJS;
paintWhileInterruptingJSForce = mPaintWhileInterruptingJSForce;
paintWhileInterruptingJSTab = mPaintWhileInterruptingJSTab;
paintWhileInterruptingJSEpoch = mPaintWhileInterruptingJSEpoch;
mForcePaint = false;
mPaintWhileInterruptingJS = false;
}
if (forcePaint) {
RefPtr<TabChild> tabChild = TabChild::FindTabChild(forcePaintTab);
if (paintWhileInterruptingJS) {
RefPtr<TabChild> tabChild = TabChild::FindTabChild(paintWhileInterruptingJSTab);
if (tabChild) {
js::AutoAssertNoContentJS nojs(mContext);
tabChild->ForcePaint(forcePaintEpoch);
tabChild->PaintWhileInterruptingJS(paintWhileInterruptingJSEpoch,
paintWhileInterruptingJSForce);
}
}
}
@ -415,16 +424,19 @@ HangMonitorChild::RecvEndStartingDebugger()
}
mozilla::ipc::IPCResult
HangMonitorChild::RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch)
HangMonitorChild::RecvPaintWhileInterruptingJS(const TabId& aTabId,
const bool& aForceRepaint,
const uint64_t& aLayerObserverEpoch)
{
MOZ_RELEASE_ASSERT(IsOnThread());
{
MonitorAutoLock lock(mMonitor);
MaybeStartForcePaint();
mForcePaint = true;
mForcePaintTab = aTabId;
mForcePaintEpoch = aLayerObserverEpoch;
MaybeStartPaintWhileInterruptingJS();
mPaintWhileInterruptingJS = true;
mPaintWhileInterruptingJSForce = aForceRepaint;
mPaintWhileInterruptingJSTab = aTabId;
mPaintWhileInterruptingJSEpoch = aLayerObserverEpoch;
}
JS_RequestInterruptCallback(mContext);
@ -433,7 +445,7 @@ HangMonitorChild::RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObse
}
void
HangMonitorChild::MaybeStartForcePaint()
HangMonitorChild::MaybeStartPaintWhileInterruptingJS()
{
// See Bug 1449662. The body of this function other than assertions
// has been temporarily removed to diagnose a tab switch spinner
@ -444,7 +456,7 @@ HangMonitorChild::MaybeStartForcePaint()
}
void
HangMonitorChild::ClearForcePaint(uint64_t aLayerObserverEpoch)
HangMonitorChild::ClearPaintWhileInterruptingJS(uint64_t aLayerObserverEpoch)
{
// See Bug 1449662. The body of this function other than assertions
// has been temporarily removed to diagnose a tab switch spinner
@ -609,7 +621,7 @@ HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor)
static bool sInited = false;
if (!sInited) {
sInited = true;
Preferences::AddBoolVarCache(&sShouldForcePaint,
Preferences::AddBoolVarCache(&sShouldPaintWhileInterruptingJS,
"browser.tabs.remote.force-paint", true);
}
}
@ -665,27 +677,33 @@ HangMonitorParent::ShutdownOnThread()
}
void
HangMonitorParent::ForcePaint(dom::TabParent* aTab, uint64_t aLayerObserverEpoch)
HangMonitorParent::PaintWhileInterruptingJS(dom::TabParent* aTab,
bool aForceRepaint,
uint64_t aLayerObserverEpoch)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (sShouldForcePaint) {
if (sShouldPaintWhileInterruptingJS) {
TabId id = aTab->GetTabId();
Dispatch(NewNonOwningRunnableMethod<TabId, uint64_t>(
"HangMonitorParent::ForcePaintOnThread",
Dispatch(NewNonOwningRunnableMethod<TabId, bool, uint64_t>(
"HangMonitorParent::PaintWhileInterruptingJSOnThread",
this,
&HangMonitorParent::ForcePaintOnThread,
&HangMonitorParent::PaintWhileInterruptingJSOnThread,
id,
aForceRepaint,
aLayerObserverEpoch));
}
}
void
HangMonitorParent::ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch)
HangMonitorParent::PaintWhileInterruptingJSOnThread(TabId aTabId,
bool aForceRepaint,
uint64_t aLayerObserverEpoch)
{
MOZ_RELEASE_ASSERT(IsOnThread());
if (mIPCOpen) {
Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
Unused << SendPaintWhileInterruptingJS(aTabId, aForceRepaint,
aLayerObserverEpoch);
}
}
@ -1357,33 +1375,34 @@ ProcessHangMonitor::ClearHang()
}
/* static */ void
ProcessHangMonitor::ForcePaint(PProcessHangMonitorParent* aParent,
dom::TabParent* aTabParent,
uint64_t aLayerObserverEpoch)
ProcessHangMonitor::PaintWhileInterruptingJS(PProcessHangMonitorParent* aParent,
dom::TabParent* aTabParent,
bool aForceRepaint,
uint64_t aLayerObserverEpoch)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
auto parent = static_cast<HangMonitorParent*>(aParent);
parent->ForcePaint(aTabParent, aLayerObserverEpoch);
parent->PaintWhileInterruptingJS(aTabParent, aForceRepaint, aLayerObserverEpoch);
}
/* static */ void
ProcessHangMonitor::ClearForcePaint(uint64_t aLayerObserverEpoch)
ProcessHangMonitor::ClearPaintWhileInterruptingJS(uint64_t aLayerObserverEpoch)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
if (HangMonitorChild* child = HangMonitorChild::Get()) {
child->ClearForcePaint(aLayerObserverEpoch);
child->ClearPaintWhileInterruptingJS(aLayerObserverEpoch);
}
}
/* static */ void
ProcessHangMonitor::MaybeStartForcePaint()
ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS()
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
if (HangMonitorChild* child = HangMonitorChild::Get()) {
child->MaybeStartForcePaint();
child->MaybeStartPaintWhileInterruptingJS();
}
}

View File

@ -45,11 +45,12 @@ class ProcessHangMonitor final
static void ClearHang();
static void ForcePaint(PProcessHangMonitorParent* aParent,
dom::TabParent* aTab,
uint64_t aLayerObserverEpoch);
static void ClearForcePaint(uint64_t aLayerObserverEpoch);
static void MaybeStartForcePaint();
static void PaintWhileInterruptingJS(PProcessHangMonitorParent* aParent,
dom::TabParent* aTab,
bool aForceRepaint,
uint64_t aLayerObserverEpoch);
static void ClearPaintWhileInterruptingJS(uint64_t aLayerObserverEpoch);
static void MaybeStartPaintWhileInterruptingJS();
enum SlowScriptAction {
Continue,

View File

@ -2536,7 +2536,9 @@ TabChild::RemovePendingDocShellBlocker()
}
if (!mPendingDocShellBlockers && mPendingRenderLayersReceivedMessage) {
mPendingRenderLayersReceivedMessage = false;
RecvRenderLayers(mPendingRenderLayers, mPendingLayerObserverEpoch);
RecvRenderLayers(mPendingRenderLayers,
false /* aForceRepaint */,
mPendingLayerObserverEpoch);
}
}
@ -2567,7 +2569,7 @@ TabChild::RecvSetDocShellIsActive(const bool& aIsActive)
}
mozilla::ipc::IPCResult
TabChild::RecvRenderLayers(const bool& aEnabled, const uint64_t& aLayerObserverEpoch)
TabChild::RecvRenderLayers(const bool& aEnabled, const bool& aForceRepaint, const uint64_t& aLayerObserverEpoch)
{
if (mPendingDocShellBlockers > 0) {
mPendingRenderLayersReceivedMessage = true;
@ -2585,19 +2587,19 @@ TabChild::RecvRenderLayers(const bool& aEnabled, const uint64_t& aLayerObserverE
}
mLayerObserverEpoch = aLayerObserverEpoch;
auto clearForcePaint = MakeScopeExit([&] {
auto clearPaintWhileInterruptingJS = MakeScopeExit([&] {
// We might force a paint, or we might already have painted and this is a
// no-op. In either case, once we exit this scope, we need to alert the
// ProcessHangMonitor that we've finished responding to what might have
// been a request to force paint. This is so that the BackgroundHangMonitor
// for force painting can be made to wait again.
if (aEnabled) {
ProcessHangMonitor::ClearForcePaint(mLayerObserverEpoch);
ProcessHangMonitor::ClearPaintWhileInterruptingJS(mLayerObserverEpoch);
}
});
if (aEnabled) {
ProcessHangMonitor::MaybeStartForcePaint();
ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS();
}
if (mCompositorOptions) {
@ -2612,12 +2614,12 @@ TabChild::RecvRenderLayers(const bool& aEnabled, const uint64_t& aLayerObserverE
}
if (aEnabled) {
if (IsVisible()) {
if (!aForceRepaint && IsVisible()) {
// This request is a no-op. In this case, we still want a MozLayerTreeReady
// notification to fire in the parent (so that it knows that the child has
// updated its epoch). ForcePaintNoOp does that.
// updated its epoch). PaintWhileInterruptingJSNoOp does that.
if (IPCOpen()) {
Unused << SendForcePaintNoOp(mLayerObserverEpoch);
Unused << SendPaintWhileInterruptingJSNoOp(mLayerObserverEpoch);
return IPC_OK();
}
}
@ -3446,7 +3448,8 @@ TabChild::GetOuterRect()
}
void
TabChild::ForcePaint(uint64_t aLayerObserverEpoch)
TabChild::PaintWhileInterruptingJS(uint64_t aLayerObserverEpoch,
bool aForceRepaint)
{
if (!IPCOpen() || !mPuppetWidget || !mPuppetWidget->HasLayerManager()) {
// Don't bother doing anything now. Better to wait until we receive the
@ -3455,7 +3458,7 @@ TabChild::ForcePaint(uint64_t aLayerObserverEpoch)
}
nsAutoScriptBlocker scriptBlocker;
RecvRenderLayers(true, aLayerObserverEpoch);
RecvRenderLayers(true /* aEnabled */, aForceRepaint, aLayerObserverEpoch);
}
void

View File

@ -655,7 +655,8 @@ public:
const uint32_t& aFlags);
// Request that the docshell be marked as active.
void ForcePaint(uint64_t aLayerObserverEpoch);
void PaintWhileInterruptingJS(uint64_t aLayerObserverEpoch,
bool aForceRepaint);
#if defined(XP_WIN) && defined(ACCESSIBILITY)
uintptr_t GetNativeWindowHandle() const { return mNativeWindowHandle; }
@ -728,7 +729,7 @@ protected:
virtual mozilla::ipc::IPCResult RecvSetDocShellIsActive(const bool& aIsActive) override;
virtual mozilla::ipc::IPCResult RecvRenderLayers(const bool& aEnabled, const uint64_t& aLayerObserverEpoch) override;
virtual mozilla::ipc::IPCResult RecvRenderLayers(const bool& aEnabled, const bool& aForce, const uint64_t& aLayerObserverEpoch) override;
virtual mozilla::ipc::IPCResult RecvNavigateByKey(const bool& aForward,
const bool& aForDocumentNavigation) override;

View File

@ -2931,19 +2931,7 @@ TabParent::SetRenderLayers(bool aEnabled)
mRenderLayers = aEnabled;
// Increment the epoch so that layer tree updates from previous
// RenderLayers requests are ignored.
mLayerTreeEpoch++;
Unused << SendRenderLayers(aEnabled, mLayerTreeEpoch);
// Ask the child to repaint using the PHangMonitor channel/thread (which may
// be less congested).
if (aEnabled) {
ContentParent* cp = Manager()->AsContentParent();
cp->ForceTabPaint(this, mLayerTreeEpoch);
}
SetRenderLayersInternal(aEnabled, false /* aForceRepaint */);
return NS_OK;
}
@ -2961,6 +2949,31 @@ TabParent::GetHasLayers(bool* aResult)
return NS_OK;
}
NS_IMETHODIMP
TabParent::ForceRepaint()
{
SetRenderLayersInternal(true /* aEnabled */,
true /* aForceRepaint */);
return NS_OK;
}
void
TabParent::SetRenderLayersInternal(bool aEnabled, bool aForceRepaint)
{
// Increment the epoch so that layer tree updates from previous
// RenderLayers requests are ignored.
mLayerTreeEpoch++;
Unused << SendRenderLayers(aEnabled, aForceRepaint, mLayerTreeEpoch);
// Ask the child to repaint using the PHangMonitor channel/thread (which may
// be less congested).
if (aEnabled) {
ContentParent* cp = Manager()->AsContentParent();
cp->PaintTabWhileInterruptingJS(this, aForceRepaint, mLayerTreeEpoch);
}
}
NS_IMETHODIMP
TabParent::PreserveLayers(bool aPreserveLayers)
{
@ -3079,9 +3092,9 @@ TabParent::LayerTreeUpdate(uint64_t aEpoch, bool aActive)
}
mozilla::ipc::IPCResult
TabParent::RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch)
TabParent::RecvPaintWhileInterruptingJSNoOp(const uint64_t& aLayerObserverEpoch)
{
// We sent a ForcePaint message when layers were already visible. In this
// We sent a PaintWhileInterruptingJS message when layers were already visible. In this
// case, we should act as if an update occurred even though we already have
// the layers.
LayerTreeUpdate(aLayerObserverEpoch, true);

View File

@ -625,7 +625,7 @@ protected:
virtual mozilla::ipc::IPCResult RecvRemoteIsReadyToHandleInputEvents() override;
virtual mozilla::ipc::IPCResult RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch) override;
virtual mozilla::ipc::IPCResult RecvPaintWhileInterruptingJSNoOp(const uint64_t& aLayerObserverEpoch) override;
virtual mozilla::ipc::IPCResult RecvSetDimensions(const uint32_t& aFlags,
const int32_t& aX, const int32_t& aY,
@ -651,6 +651,8 @@ protected:
private:
void DestroyInternal();
void SetRenderLayersInternal(bool aEnabled, bool aForceRepaint);
already_AddRefed<nsFrameLoader>
GetFrameLoader(bool aUseCachedFrameLoaderAfterDestroy = false) const;

View File

@ -266,6 +266,17 @@
</setter>
</property>
<method name="forceRepaint">
<body>
<![CDATA[
let {frameLoader} = this;
if (frameLoader && frameLoader.tabParent) {
frameLoader.tabParent.forceRepaint();
}
]]>
</body>
</method>
<property name="hasLayers" readonly="true">
<getter><![CDATA[
let {frameLoader} = this;