Bug 1279086 - Allow painting for tab switch when JS is running (r=dvander,mconley,mrbkap)

This commit is contained in:
Bill McCloskey 2016-07-22 16:36:45 -07:00
parent 95c5d71549
commit 3799faa926
31 changed files with 559 additions and 390 deletions

View File

@ -3300,6 +3300,7 @@ var PrintPreviewListener = {
this._simplifyPageTab = null; this._simplifyPageTab = null;
} }
gBrowser.removeTab(this._printPreviewTab); gBrowser.removeTab(this._printPreviewTab);
gBrowser.deactivatePrintPreviewBrowsers();
this._printPreviewTab = null; this._printPreviewTab = null;
}, },
_toggleAffectedChrome: function () { _toggleAffectedChrome: function () {
@ -3355,7 +3356,11 @@ var PrintPreviewListener = {
if (this._chromeState.sidebarOpen) if (this._chromeState.sidebarOpen)
SidebarUI.show(this._sidebarCommand); SidebarUI.show(this._sidebarCommand);
} },
activateBrowser(browser) {
gBrowser.activateBrowserForPrintPreview(browser);
},
} }
function getMarkupDocumentViewer() function getMarkupDocumentViewer()

View File

@ -1634,8 +1634,7 @@
// As frameLoaders start out with an active docShell we have to // As frameLoaders start out with an active docShell we have to
// deactivate it if this is not the selected tab's browser or the // deactivate it if this is not the selected tab's browser or the
// browser window is minimized. // browser window is minimized.
aBrowser.docShellIsActive = (aBrowser == this.selectedBrowser && aBrowser.docShellIsActive = this.shouldActivateDocShell(aBrowser);
window.windowState != window.STATE_MINIMIZED);
// Create a new tab progress listener for the new browser we just injected, // Create a new tab progress listener for the new browser we just injected,
// since tab progress listeners have logic for handling the initial about:blank // since tab progress listeners have logic for handling the initial about:blank
@ -2804,8 +2803,11 @@
remoteBrowser._outerWindowIDBrowserMap.delete(aOtherBrowser.outerWindowID); remoteBrowser._outerWindowIDBrowserMap.delete(aOtherBrowser.outerWindowID);
} }
aOtherBrowser.docShellIsActive = (ourBrowser == this.selectedBrowser && // If switcher is active, it will intercept swap events and
window.windowState != window.STATE_MINIMIZED); // react as needed.
if (!this._switcher) {
aOtherBrowser.docShellIsActive = this.shouldActivateDocShell(ourBrowser);
}
// Swap the docshells // Swap the docshells
ourBrowser.swapDocShells(aOtherBrowser); ourBrowser.swapDocShells(aOtherBrowser);
@ -3310,6 +3312,56 @@
</body> </body>
</method> </method>
<!--
List of browsers whose docshells must be active in order for print preview
to work.
-->
<field name="_printPreviewBrowsers">
new Set()
</field>
<method name="activateBrowserForPrintPreview">
<parameter name="aBrowser"/>
<body>
<![CDATA[
this._printPreviewBrowsers.add(aBrowser);
if (this._switcher) {
this._switcher.activateBrowserForPrintPreview(aBrowser);
}
aBrowser.docShellIsActive = true;
]]>
</body>
</method>
<method name="deactivatePrintPreviewBrowsers">
<body>
<![CDATA[
let browsers = this._printPreviewBrowsers;
this._printPreviewBrowsers = new Set();
for (let browser of browsers) {
browser.docShellIsActive = this.shouldActivateDocShell(browser);
}
]]>
</body>
</method>
<!--
Returns true if a given browser's docshell should be active.
-->
<method name="shouldActivateDocShell">
<parameter name="aBrowser"/>
<body>
<![CDATA[
if (this._switcher) {
return this._switcher.shouldActivateDocShell(aBrowser);
}
return (aBrowser == this.selectedBrowser &&
window.windowState != window.STATE_MINIMIZED) ||
this._printPreviewBrowsers.has(aBrowser);
]]>
</body>
</method>
<!-- <!--
The tab switcher is responsible for asynchronously switching The tab switcher is responsible for asynchronously switching
tabs in e10s. It waits until the new tab is ready (i.e., the tabs in e10s. It waits until the new tab is ready (i.e., the
@ -3341,35 +3393,12 @@
It's important that we always show either the spinner or a tab It's important that we always show either the spinner or a tab
whose layers are available. Otherwise the compositor will draw whose layers are available. Otherwise the compositor will draw
an entirely black frame, which is very jarring. To ensure this an entirely black frame, which is very jarring. To ensure this
never happens, we do the following: never happens when switching away from a tab, we assume the
old tab might still be drawn until a MozAfterPaint event
1. When switching away from a tab, we assume the old tab might occurs. Because layout and compositing happen asynchronously,
still be drawn until a MozAfterPaint event occurs. Because we don't have any other way of knowing when the switch
layout and compositing happen asynchronously, we don't have actually takes place. Therefore, we don't unload the old tab
any other way of knowing when the switch actually takes until the next MozAfterPaint event.
place. Therefore, we don't unload the old tab until the next
MozAfterPaint event.
2. Suppose that the user switches from tab A to B and then
back to A. Suppose that we ask for tab A's layers to be
unloaded via message M1 after the first switch and then
request them again via message M2 once the second switch
happens. Both loading and unloading of layers happens
asynchronously, and this can cause problems. It's possible
that the content process publishes one last layer tree before
M1 is received. The parent process doesn't know that this
layer tree was published before M1 and not after M2, so it
will display the tab. However, once M1 arrives, the content
process will destroy the layer tree for A and now we will
display black for it.
To counter this problem, we keep tab A in a separate
"unloading" state until the layer tree is actually dropped in
the compositor thread. While the tab is in the "unloading"
state, we're not allowed to request layers for it. Once the
layers are dropped in the compositor, an event will fire and
we will transition the tab to the "unloaded" state. Then we
are free to request the tab's layers again.
--> -->
<field name="_switcher">null</field> <field name="_switcher">null</field>
<method name="_getSwitcher"> <method name="_getSwitcher">
@ -3416,11 +3445,6 @@
// True if we're in the midst of switching tabs. // True if we're in the midst of switching tabs.
switchInProgress: false, switchInProgress: false,
// Keep an exact list of content processes (tabParent) in which
// we're actively suppressing the display port. This gives a robust
// way to make sure we don't forget to un-suppress.
activeSuppressDisplayport: new Set(),
// Set of tabs that might be visible right now. We maintain // Set of tabs that might be visible right now. We maintain
// this set because we can't be sure when a tab is actually // this set because we can't be sure when a tab is actually
// drawn. A tab is added to this set when we ask to make it // drawn. A tab is added to this set when we ask to make it
@ -3462,26 +3486,37 @@
return state; return state;
}, },
setTabState: function(tab, state) { setTabStateNoAction(tab, state) {
if (state == this.STATE_UNLOADED) { if (state == this.STATE_UNLOADED) {
this.tabState.delete(tab); this.tabState.delete(tab);
} else { } else {
this.tabState.set(tab, state); this.tabState.set(tab, state);
} }
},
setTabState: function(tab, state) {
this.setTabStateNoAction(tab, state);
let browser = tab.linkedBrowser; let browser = tab.linkedBrowser;
let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader; let {tabParent} = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
if (state == this.STATE_LOADING) { if (state == this.STATE_LOADING) {
// Ask for a MozLayerTreeReady event. this.assert(!this.minimized);
fl.requestNotifyLayerTreeReady();
browser.docShellIsActive = true; browser.docShellIsActive = true;
if (!tabParent) {
this.onLayersReady(browser);
}
} else if (state == this.STATE_UNLOADING) { } else if (state == this.STATE_UNLOADING) {
// Ask for MozLayerTreeCleared event.
fl.requestNotifyLayerTreeCleared();
browser.docShellIsActive = false; browser.docShellIsActive = false;
if (!tabParent) {
this.onLayersCleared(browser);
}
} }
}, },
get minimized() {
return window.windowState == window.STATE_MINIMIZED;
},
init: function() { init: function() {
this.log("START"); this.log("START");
@ -3489,7 +3524,12 @@
window.addEventListener("MozLayerTreeReady", this); window.addEventListener("MozLayerTreeReady", this);
window.addEventListener("MozLayerTreeCleared", this); window.addEventListener("MozLayerTreeCleared", this);
window.addEventListener("TabRemotenessChange", this); window.addEventListener("TabRemotenessChange", this);
this.setTabState(this.requestedTab, this.STATE_LOADED); window.addEventListener("sizemodechange", this);
window.addEventListener("SwapDocShells", this, true);
window.addEventListener("EndSwapDocShells", this, true);
if (!this.minimized) {
this.setTabState(this.requestedTab, this.STATE_LOADED);
}
}, },
destroy: function() { destroy: function() {
@ -3506,13 +3546,11 @@
window.removeEventListener("MozLayerTreeReady", this); window.removeEventListener("MozLayerTreeReady", this);
window.removeEventListener("MozLayerTreeCleared", this); window.removeEventListener("MozLayerTreeCleared", this);
window.removeEventListener("TabRemotenessChange", this); window.removeEventListener("TabRemotenessChange", this);
window.removeEventListener("sizemodechange", this);
window.removeEventListener("SwapDocShells", this, true);
window.removeEventListener("EndSwapDocShells", this, true);
this.tabbrowser._switcher = null; this.tabbrowser._switcher = null;
this.activeSuppressDisplayport.forEach(function(tabParent) {
tabParent.suppressDisplayport(false);
});
this.activeSuppressDisplayport.clear();
}, },
finish: function() { finish: function() {
@ -3524,7 +3562,7 @@
this.assert(!this.loadTimer); this.assert(!this.loadTimer);
this.assert(!this.loadingTab); this.assert(!this.loadingTab);
this.assert(this.lastVisibleTab === this.requestedTab); this.assert(this.lastVisibleTab === this.requestedTab);
this.assert(this.getTabState(this.requestedTab) == this.STATE_LOADED); this.assert(this.minimized || this.getTabState(this.requestedTab) == this.STATE_LOADED);
this.destroy(); this.destroy();
@ -3564,7 +3602,7 @@
} }
// Show or hide the spinner as needed. // Show or hide the spinner as needed.
let needSpinner = this.getTabState(showTab) != this.STATE_LOADED; let needSpinner = this.getTabState(showTab) != this.STATE_LOADED && !this.minimized;
if (!needSpinner && this.spinnerTab) { if (!needSpinner && this.spinnerTab) {
this.spinnerHidden(); this.spinnerHidden();
this.tabbrowser.removeAttribute("pendingpaint"); this.tabbrowser.removeAttribute("pendingpaint");
@ -3619,14 +3657,15 @@
// We've decided to try to load requestedTab. // We've decided to try to load requestedTab.
loadRequestedTab: function() { loadRequestedTab: function() {
this.assert(!this.loadTimer); this.assert(!this.loadTimer);
this.assert(!this.minimized);
// loadingTab can be non-null here if we timed out loading the current tab. // loadingTab can be non-null here if we timed out loading the current tab.
// In that case we just overwrite it with a different tab; it's had its chance. // In that case we just overwrite it with a different tab; it's had its chance.
this.loadingTab = this.requestedTab; this.loadingTab = this.requestedTab;
this.log("Loading tab " + this.tinfo(this.loadingTab)); this.log("Loading tab " + this.tinfo(this.loadingTab));
this.setTabState(this.requestedTab, this.STATE_LOADING);
this.loadTimer = this.setTimer(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT); this.loadTimer = this.setTimer(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
this.setTabState(this.requestedTab, this.STATE_LOADING);
}, },
// This function runs before every event. It fixes up the state // This function runs before every event. It fixes up the state
@ -3671,13 +3710,21 @@
this.assert(!this.loadingTab || this.loadTimer); this.assert(!this.loadingTab || this.loadTimer);
// If we're not loading anything, try loading the requested tab. // If we're not loading anything, try loading the requested tab.
if (!this.loadTimer && this.getTabState(this.requestedTab) == this.STATE_UNLOADED) { let requestedState = this.getTabState(this.requestedTab);
if (!this.loadTimer && !this.minimized &&
(requestedState == this.STATE_UNLOADED ||
requestedState == this.STATE_UNLOADING)) {
this.loadRequestedTab(); this.loadRequestedTab();
} }
// See how many tabs still have work to do. // See how many tabs still have work to do.
let numPending = 0; let numPending = 0;
for (let [tab, state] of this.tabState) { for (let [tab, state] of this.tabState) {
// Skip print preview browsers since they shouldn't affect tab switching.
if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
continue;
}
if (state == this.STATE_LOADED && tab !== this.requestedTab) { if (state == this.STATE_LOADED && tab !== this.requestedTab) {
numPending++; numPending++;
} }
@ -3712,6 +3759,10 @@
// Unload any tabs that can be unloaded. // Unload any tabs that can be unloaded.
for (let [tab, state] of this.tabState) { for (let [tab, state] of this.tabState) {
if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
continue;
}
if (state == this.STATE_LOADED && if (state == this.STATE_LOADED &&
!this.maybeVisibleTabs.has(tab) && !this.maybeVisibleTabs.has(tab) &&
tab !== this.lastVisibleTab && tab !== this.lastVisibleTab &&
@ -3745,9 +3796,11 @@
// Fires when the layers become available for a tab. // Fires when the layers become available for a tab.
onLayersReady: function(browser) { onLayersReady: function(browser) {
this.logState("onLayersReady");
let tab = this.tabbrowser.getTabForBrowser(browser); let tab = this.tabbrowser.getTabForBrowser(browser);
this.logState(`onLayersReady(${tab._tPos})`);
this.assert(this.getTabState(tab) == this.STATE_LOADING ||
this.getTabState(tab) == this.STATE_LOADED);
this.setTabState(tab, this.STATE_LOADED); this.setTabState(tab, this.STATE_LOADED);
this.maybeFinishTabSwitch(); this.maybeFinishTabSwitch();
@ -3769,10 +3822,11 @@
// Called when we're done clearing the layers for a tab. // Called when we're done clearing the layers for a tab.
onLayersCleared: function(browser) { onLayersCleared: function(browser) {
this.logState("onLayersCleared");
let tab = this.tabbrowser.getTabForBrowser(browser); let tab = this.tabbrowser.getTabForBrowser(browser);
if (tab) { if (tab) {
this.logState(`onLayersCleared(${tab._tPos})`);
this.assert(this.getTabState(tab) == this.STATE_UNLOADING ||
this.getTabState(tab) == this.STATE_UNLOADED);
this.setTabState(tab, this.STATE_UNLOADED); this.setTabState(tab, this.STATE_UNLOADED);
} }
}, },
@ -3781,7 +3835,7 @@
// a MozLayerTreeReady notification that we requested may never fire, // a MozLayerTreeReady notification that we requested may never fire,
// so we need to simulate it. // so we need to simulate it.
onRemotenessChange: function(tab) { onRemotenessChange: function(tab) {
this.logState("onRemotenessChange"); this.logState(`onRemotenessChange(${tab._tPos}, ${tab.linkedBrowser.isRemoteBrowser})`);
if (!tab.linkedBrowser.isRemoteBrowser) { if (!tab.linkedBrowser.isRemoteBrowser) {
if (this.getTabState(tab) == this.STATE_LOADING) { if (this.getTabState(tab) == this.STATE_LOADING) {
this.onLayersReady(tab.linkedBrowser); this.onLayersReady(tab.linkedBrowser);
@ -3804,34 +3858,98 @@
} }
}, },
onSizeModeChange() {
if (this.minimized) {
for (let [tab, state] of this.tabState) {
// Skip print preview browsers since they shouldn't affect tab switching.
if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
continue;
}
if (state == this.STATE_LOADING || state == this.STATE_LOADED) {
this.setTabState(tab, this.STATE_UNLOADING);
}
if (this.loadTimer) {
this.clearTimer(this.loadTimer);
this.loadTimer = null;
}
this.loadingTab = null;
}
} else {
// Do nothing. We'll automatically start loading the requested tab in
// postActions.
}
},
onSwapDocShells(ourBrowser, otherBrowser) {
// This event fires before the swap. ourBrowser is from
// our window. We save the state of otherBrowser since ourBrowser
// needs to take on that state at the end of the swap.
let otherTabbrowser = otherBrowser.ownerDocument.defaultView.gBrowser;
let otherState;
if (otherTabbrowser && otherTabbrowser._switcher) {
let otherTab = otherTabbrowser.getTabForBrowser(otherBrowser);
otherState = otherTabbrowser._switcher.getTabState(otherTab);
} else {
otherState = (otherBrowser.docShellIsActive
? this.STATE_LOADED
: this.STATE_UNLOADED);
}
if (!this.swapMap) {
this.swapMap = new WeakMap();
}
this.swapMap.set(otherBrowser, otherState);
},
onEndSwapDocShells(ourBrowser, otherBrowser) {
// The swap has happened. We reset the loadingTab in
// case it has been swapped. We also set ourBrowser's state
// to whatever otherBrowser's state was before the swap.
if (this.loadTimer) {
// Clearing the load timer means that we will
// immediately display a spinner if ourBrowser isn't
// ready yet. Typically it will already be ready
// though. If it's not, we're probably in a new window,
// in which case we have no other tabs to display anyway.
this.clearTimer(this.loadTimer);
this.loadTimer = null;
}
this.loadingTab = null;
let otherState = this.swapMap.get(otherBrowser);
this.swapMap.delete(otherBrowser);
let ourTab = this.tabbrowser.getTabForBrowser(ourBrowser);
if (ourTab) {
this.setTabStateNoAction(ourTab, otherState);
}
},
shouldActivateDocShell(browser) {
let tab = this.tabbrowser.getTabForBrowser(browser);
let state = this.getTabState(tab);
return state == this.STATE_LOADING || state == this.STATE_LOADED;
},
activateBrowserForPrintPreview(browser) {
let tab = this.tabbrowser.getTabForBrowser(browser);
this.setTabState(tab, this.STATE_LOADING);
},
// Called when the user asks to switch to a given tab. // Called when the user asks to switch to a given tab.
requestTab: function(tab) { requestTab: function(tab) {
if (tab === this.requestedTab) { if (tab === this.requestedTab) {
return; return;
} }
// Instrumentation to figure out bug 1166351 - if the binding
// on the browser we're switching to has gone away, try to find out
// why. We should remove this and the checkBrowserBindingAlive
// method once bug 1166351 has been closed.
if (this.tabbrowser.AppConstants.E10S_TESTING_ONLY &&
!this.checkBrowserBindingAlive(tab)) {
Cu.reportError("Please report the above errors in bug 1166351.");
return;
}
this.logState("requestTab " + this.tinfo(tab)); this.logState("requestTab " + this.tinfo(tab));
this.startTabSwitch(); this.startTabSwitch();
this.requestedTab = tab; this.requestedTab = tab;
let browser = this.requestedTab.linkedBrowser;
let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) {
fl.tabParent.suppressDisplayport(true);
this.activeSuppressDisplayport.add(fl.tabParent);
}
this.preActions(); this.preActions();
if (this.unloadTimer) { if (this.unloadTimer) {
@ -3863,6 +3981,12 @@
this.onLayersCleared(event.originalTarget); this.onLayersCleared(event.originalTarget);
} else if (event.type == "TabRemotenessChange") { } else if (event.type == "TabRemotenessChange") {
this.onRemotenessChange(event.target); this.onRemotenessChange(event.target);
} else if (event.type == "sizemodechange") {
this.onSizeModeChange();
} else if (event.type == "SwapDocShells") {
this.onSwapDocShells(event.originalTarget, event.detail);
} else if (event.type == "EndSwapDocShells") {
this.onEndSwapDocShells(event.originalTarget, event.detail);
} }
this.postActions(); this.postActions();
@ -3946,42 +4070,6 @@
return this._shouldLog; return this._shouldLog;
}, },
// Instrumentation for bug 1166351
checkBrowserBindingAlive: function(tab) {
let err = Cu.reportError;
if (!tab.linkedBrowser) {
err("Attempting to switch to tab that has no linkedBrowser.");
return false;
}
let b = tab.linkedBrowser;
if (!b._alive) {
// The browser binding has been removed. Dump a bunch of
// diagnostic information to the browser console.
let utils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
let results = utils.getBindingURLs(b);
let urls = [];
for (let i = 0; i < results.length; ++i) {
urls.push(results.queryElementAt(i, Ci.nsIURI).spec);
}
err("The browser has the following bindings:");
err(urls);
err("MozBinding is currently: " +
window.getComputedStyle(b).MozBinding);
if (!b.parentNode) {
err("Browser was removed from the DOM.");
} else {
err("Parent is: " + b.parentNode.outerHTML);
}
return false;
}
return true;
},
tinfo: function(tab) { tinfo: function(tab) {
if (tab) { if (tab) {
return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")"; return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
@ -4350,7 +4438,7 @@
case "sizemodechange": case "sizemodechange":
if (aEvent.target == window) { if (aEvent.target == window) {
this.mCurrentBrowser.setDocShellIsActiveAndForeground( this.mCurrentBrowser.setDocShellIsActiveAndForeground(
window.windowState != window.STATE_MINIMIZED); this.shouldActivateDocShell(this.mCurrentBrowser));
} }
break; break;
} }

View File

@ -1021,8 +1021,6 @@ nsFrameLoader::SwapWithOtherRemoteLoader(nsFrameLoader* aOther,
return rv; return rv;
} }
mRemoteBrowser->SwapLayerTreeObservers(aOther->mRemoteBrowser);
nsCOMPtr<nsIBrowserDOMWindow> otherBrowserDOMWindow = nsCOMPtr<nsIBrowserDOMWindow> otherBrowserDOMWindow =
aOther->mRemoteBrowser->GetBrowserDOMWindow(); aOther->mRemoteBrowser->GetBrowserDOMWindow();
nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow = nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
@ -3203,46 +3201,6 @@ nsFrameLoader::RequestNotifyAfterRemotePaint()
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
nsFrameLoader::RequestNotifyLayerTreeReady()
{
if (mRemoteBrowser) {
return mRemoteBrowser->RequestNotifyLayerTreeReady() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
if (!mOwnerContent) {
return NS_ERROR_NOT_AVAILABLE;
}
RefPtr<AsyncEventDispatcher> event =
new AsyncEventDispatcher(mOwnerContent,
NS_LITERAL_STRING("MozLayerTreeReady"),
true, false);
event->PostDOMEvent();
return NS_OK;
}
NS_IMETHODIMP
nsFrameLoader::RequestNotifyLayerTreeCleared()
{
if (mRemoteBrowser) {
return mRemoteBrowser->RequestNotifyLayerTreeCleared() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
if (!mOwnerContent) {
return NS_ERROR_NOT_AVAILABLE;
}
RefPtr<AsyncEventDispatcher> event =
new AsyncEventDispatcher(mOwnerContent,
NS_LITERAL_STRING("MozLayerTreeCleared"),
true, false);
event->PostDOMEvent();
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
nsFrameLoader::Print(uint64_t aOuterWindowID, nsFrameLoader::Print(uint64_t aOuterWindowID,
nsIPrintSettings* aPrintSettings, nsIPrintSettings* aPrintSettings,

View File

@ -138,14 +138,6 @@ interface nsIFrameLoader : nsISupports
*/ */
void requestNotifyAfterRemotePaint(); void requestNotifyAfterRemotePaint();
/**
* Request an event when the layer tree from the remote tab becomes
* available or unavailable. When this happens, a mozLayerTreeReady
* or mozLayerTreeCleared event is fired.
*/
void requestNotifyLayerTreeReady();
void requestNotifyLayerTreeCleared();
/** /**
* Print the current document. * Print the current document.
* *

View File

@ -5501,3 +5501,12 @@ ContentParent::SendGetFilesResponseAndForget(const nsID& aUUID,
Unused << SendGetFilesResponse(aUUID, aResult); Unused << SendGetFilesResponse(aUUID, aResult);
} }
} }
void
ContentParent::ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch)
{
if (!mHangMonitorActor) {
return;
}
ProcessHangMonitor::ForcePaint(mHangMonitorActor, aTabParent, aLayerObserverEpoch);
}

View File

@ -563,6 +563,9 @@ public:
virtual int32_t Pid() const override; virtual int32_t Pid() const override;
// Use the PHangMonitor channel to ask the child to repaint a tab.
void ForceTabPaint(TabParent* aTabParent, uint64_t aLayerObserverEpoch);
protected: protected:
void OnChannelConnected(int32_t pid) override; void OnChannelConnected(int32_t pid) override;

View File

@ -550,6 +550,11 @@ parent:
*/ */
async RemotePaintIsReady(); async RemotePaintIsReady();
/**
* Child informs the parent that the layer tree is already available.
*/
async ForcePaintNoOp(uint64_t aLayerObserverEpoch);
/** /**
* Sent by the child to the parent to inform it that an update to the * Sent by the child to the parent to inform it that an update to the
* dimensions has been requested, likely through win.moveTo or resizeTo * dimensions has been requested, likely through win.moveTo or resizeTo
@ -721,7 +726,7 @@ child:
/** /**
* Update the child side docShell active (resource use) state. * Update the child side docShell active (resource use) state.
*/ */
async SetDocShellIsActive(bool aIsActive, bool aIsHidden); async SetDocShellIsActive(bool aIsActive, bool aIsHidden, uint64_t aLayerObserverEpoch);
/** /**
* Notify the child that it shouldn't paint the offscreen displayport. * Notify the child that it shouldn't paint the offscreen displayport.

View File

@ -40,6 +40,8 @@ child:
async BeginStartingDebugger(); async BeginStartingDebugger();
async EndStartingDebugger(); async EndStartingDebugger();
async ForcePaint(TabId tabId, uint64_t aLayerObserverEpoch);
}; };
} // namespace mozilla } // namespace mozilla

View File

@ -7,9 +7,13 @@
#include "mozilla/ProcessHangMonitor.h" #include "mozilla/ProcessHangMonitor.h"
#include "mozilla/ProcessHangMonitorIPC.h" #include "mozilla/ProcessHangMonitorIPC.h"
#include "jsapi.h"
#include "js/GCAPI.h"
#include "mozilla/Atomics.h" #include "mozilla/Atomics.h"
#include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Element.h" #include "mozilla/dom/Element.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/TabChild.h" #include "mozilla/dom/TabChild.h"
#include "mozilla/dom/TabParent.h" #include "mozilla/dom/TabParent.h"
#include "mozilla/Monitor.h" #include "mozilla/Monitor.h"
@ -96,8 +100,11 @@ class HangMonitorChild
virtual bool RecvBeginStartingDebugger() override; virtual bool RecvBeginStartingDebugger() override;
virtual bool RecvEndStartingDebugger() override; virtual bool RecvEndStartingDebugger() override;
virtual bool RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch) override;
virtual void ActorDestroy(ActorDestroyReason aWhy) override; virtual void ActorDestroy(ActorDestroyReason aWhy) override;
void InterruptCallback();
void Shutdown(); void Shutdown();
static HangMonitorChild* Get() { return sInstance; } static HangMonitorChild* Get() { return sInstance; }
@ -119,6 +126,10 @@ class HangMonitorChild
bool mTerminateScript; bool mTerminateScript;
bool mStartDebugger; bool mStartDebugger;
bool mFinishedStartingDebugger; bool mFinishedStartingDebugger;
bool mForcePaint;
TabId mForcePaintTab;
uint64_t mForcePaintEpoch;
JSContext* mContext;
bool mShutdownDone; bool mShutdownDone;
// This field is only accessed on the hang thread. // This field is only accessed on the hang thread.
@ -208,6 +219,8 @@ public:
void Shutdown(); void Shutdown();
void ForcePaint(dom::TabParent* aTabParent, uint64_t aLayerObserverEpoch);
void TerminateScript(); void TerminateScript();
void BeginStartingDebugger(); void BeginStartingDebugger();
void EndStartingDebugger(); void EndStartingDebugger();
@ -224,6 +237,9 @@ public:
private: private:
bool TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId); bool TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId);
void ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch);
void ShutdownOnThread(); void ShutdownOnThread();
const RefPtr<ProcessHangMonitor> mHangMonitor; const RefPtr<ProcessHangMonitor> mHangMonitor;
@ -255,10 +271,12 @@ HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor)
mTerminateScript(false), mTerminateScript(false),
mStartDebugger(false), mStartDebugger(false),
mFinishedStartingDebugger(false), mFinishedStartingDebugger(false),
mForcePaint(false),
mShutdownDone(false), mShutdownDone(false),
mIPCOpen(true) mIPCOpen(true)
{ {
MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_RELEASE_ASSERT(NS_IsMainThread());
mContext = danger::GetJSContext();
} }
HangMonitorChild::~HangMonitorChild() HangMonitorChild::~HangMonitorChild()
@ -268,6 +286,33 @@ HangMonitorChild::~HangMonitorChild()
sInstance = nullptr; sInstance = nullptr;
} }
void
HangMonitorChild::InterruptCallback()
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
bool forcePaint;
TabId forcePaintTab;
uint64_t forcePaintEpoch;
{
MonitorAutoLock lock(mMonitor);
forcePaint = mForcePaint;
forcePaintTab = mForcePaintTab;
forcePaintEpoch = mForcePaintEpoch;
mForcePaint = false;
}
if (forcePaint) {
RefPtr<TabChild> tabChild = TabChild::FindTabChild(forcePaintTab);
if (tabChild) {
JS::AutoAssertOnGC aaogc(mContext);
tabChild->ForcePaint(forcePaintEpoch);
}
}
}
void void
HangMonitorChild::Shutdown() HangMonitorChild::Shutdown()
{ {
@ -331,6 +376,23 @@ HangMonitorChild::RecvEndStartingDebugger()
return true; return true;
} }
bool
HangMonitorChild::RecvForcePaint(const TabId& aTabId, const uint64_t& aLayerObserverEpoch)
{
MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
{
MonitorAutoLock lock(mMonitor);
mForcePaint = true;
mForcePaintTab = aTabId;
mForcePaintEpoch = aLayerObserverEpoch;
}
JS_RequestInterruptCallback(mContext);
return true;
}
void void
HangMonitorChild::Open(Transport* aTransport, ProcessId aPid, HangMonitorChild::Open(Transport* aTransport, ProcessId aPid,
MessageLoop* aIOLoop) MessageLoop* aIOLoop)
@ -524,6 +586,25 @@ HangMonitorParent::ShutdownOnThread()
mMonitor.Notify(); mMonitor.Notify();
} }
void
HangMonitorParent::ForcePaint(dom::TabParent* aTab, uint64_t aLayerObserverEpoch)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
TabId id = aTab->GetTabId();
MonitorLoop()->PostTask(NewNonOwningRunnableMethod<TabId, uint64_t>(
this, &HangMonitorParent::ForcePaintOnThread, id, aLayerObserverEpoch));
}
void
HangMonitorParent::ForcePaintOnThread(TabId aTabId, uint64_t aLayerObserverEpoch)
{
MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
if (mIPCOpen) {
Unused << SendForcePaint(aTabId, aLayerObserverEpoch);
}
}
void void
HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy) HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy)
{ {
@ -959,6 +1040,16 @@ HangMonitoredProcess::UserCanceled()
return NS_OK; return NS_OK;
} }
static bool
InterruptCallback(JSContext* cx)
{
if (HangMonitorChild* child = HangMonitorChild::Get()) {
child->InterruptCallback();
}
return true;
}
ProcessHangMonitor* ProcessHangMonitor::sInstance; ProcessHangMonitor* ProcessHangMonitor::sInstance;
ProcessHangMonitor::ProcessHangMonitor() ProcessHangMonitor::ProcessHangMonitor()
@ -1094,6 +1185,9 @@ mozilla::CreateHangMonitorChild(mozilla::ipc::Transport* aTransport,
{ {
MOZ_RELEASE_ASSERT(NS_IsMainThread()); MOZ_RELEASE_ASSERT(NS_IsMainThread());
JSContext* cx = danger::GetJSContext();
JS_AddInterruptCallback(cx, InterruptCallback);
ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate();
HangMonitorChild* child = new HangMonitorChild(monitor); HangMonitorChild* child = new HangMonitorChild(monitor);
@ -1142,3 +1236,13 @@ ProcessHangMonitor::ClearHang()
child->ClearHang(); child->ClearHang();
} }
} }
/* static */ void
ProcessHangMonitor::ForcePaint(PProcessHangMonitorParent* aParent,
dom::TabParent* aTabParent,
uint64_t aLayerObserverEpoch)
{
MOZ_RELEASE_ASSERT(NS_IsMainThread());
auto parent = static_cast<HangMonitorParent*>(aParent);
parent->ForcePaint(aTabParent, aLayerObserverEpoch);
}

View File

@ -22,6 +22,7 @@ namespace mozilla {
namespace dom { namespace dom {
class ContentParent; class ContentParent;
class TabParent;
} // namespace dom } // namespace dom
class PProcessHangMonitorParent; class PProcessHangMonitorParent;
@ -45,6 +46,10 @@ class ProcessHangMonitor final
static void ClearHang(); static void ClearHang();
static void ForcePaint(PProcessHangMonitorParent* aParent,
dom::TabParent* aTab,
uint64_t aLayerObserverEpoch);
enum SlowScriptAction { enum SlowScriptAction {
Continue, Continue,
Terminate, Terminate,

View File

@ -83,6 +83,7 @@
#include "nsLayoutUtils.h" #include "nsLayoutUtils.h"
#include "nsPrintfCString.h" #include "nsPrintfCString.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "nsViewManager.h"
#include "nsWeakReference.h" #include "nsWeakReference.h"
#include "nsWindowWatcher.h" #include "nsWindowWatcher.h"
#include "PermissionMessageUtils.h" #include "PermissionMessageUtils.h"
@ -544,6 +545,7 @@ TabChild::TabChild(nsIContentChild* aManager,
, mDidSetRealShowInfo(false) , mDidSetRealShowInfo(false)
, mDidLoadURLInit(false) , mDidLoadURLInit(false)
, mAPZChild(nullptr) , mAPZChild(nullptr)
, mLayerObserverEpoch(0)
{ {
// In the general case having the TabParent tell us if APZ is enabled or not // In the general case having the TabParent tell us if APZ is enabled or not
// doesn't really work because the TabParent itself may not have a reference // doesn't really work because the TabParent itself may not have a reference
@ -2583,19 +2585,75 @@ TabChild::RecvSetUpdateHitRegion(const bool& aEnabled)
} }
bool bool
TabChild::RecvSetDocShellIsActive(const bool& aIsActive, const bool& aIsHidden) TabChild::RecvSetDocShellIsActive(const bool& aIsActive, const bool& aIsHidden, const uint64_t& aLayerObserverEpoch)
{ {
// docshell is consider prerendered only if not active yet // Since SetDocShellIsActive requests come in from both the hang monitor
mIsPrerendered &= !aIsActive; // channel and the PContent channel, we have an ordering problem. This code
nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); // ensures that we respect the order in which the requests were made and
if (docShell) { // ignore stale requests.
if (aIsHidden) { if (mLayerObserverEpoch > aLayerObserverEpoch) {
docShell->SetIsActive(aIsActive); return true;
} else { }
docShell->SetIsActiveAndForeground(aIsActive); mLayerObserverEpoch = aLayerObserverEpoch;
}
if (aIsActive && WebWidget()->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.
if (IPCOpen()) {
Unused << SendForcePaintNoOp(aLayerObserverEpoch);
} }
return true; return true;
}
MOZ_ASSERT(mPuppetWidget);
MOZ_ASSERT(mPuppetWidget->GetLayerManager());
MOZ_ASSERT(mPuppetWidget->GetLayerManager()->GetBackendType() ==
LayersBackend::LAYERS_CLIENT);
// We send the current layer observer epoch to the compositor so that
// TabParent knows whether a layer update notification corresponds to the
// latest SetDocShellIsActive request that was made.
ClientLayerManager *manager = mPuppetWidget->GetLayerManager()->AsClientLayerManager();
manager->SetLayerObserverEpoch(aLayerObserverEpoch);
// docshell is consider prerendered only if not active yet
mIsPrerendered &= !aIsActive;
nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
if (docShell) {
if (aIsHidden) {
docShell->SetIsActive(aIsActive);
} else {
docShell->SetIsActiveAndForeground(aIsActive);
}
}
if (aIsActive) {
// We don't use GetPresShell() here because that would create a content viewer
// if one doesn't exist yet. Creating a content viewer can cause JS to run,
// which we want to avoid. nsIDocShell::GetPresShell returns null if no
// content viewer exists yet.
if (nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell()) {
// If we need to repaint, let's do that right away. No sense waiting until
// we get back to the event loop again. We suppress the display port so that
// we only paint what's visible. This ensures that the tab we're switching
// to paints as quickly as possible.
APZCCallbackHelper::SuppressDisplayport(true, presShell);
if (nsContentUtils::IsSafeToRunScript()) {
WebWidget()->PaintNowIfNeeded();
} else {
RefPtr<nsViewManager> vm = presShell->GetViewManager();
if (nsView* view = vm->GetRootView()) {
presShell->Paint(view, view->GetBounds(),
nsIPresShell::PAINT_LAYERS |
nsIPresShell::PAINT_SYNC_DECODE_IMAGES);
}
}
APZCCallbackHelper::SuppressDisplayport(false, presShell);
}
}
return true;
} }
bool bool
@ -2816,6 +2874,10 @@ TabChild::NotifyPainted()
void void
TabChild::MakeVisible() TabChild::MakeVisible()
{ {
if (mPuppetWidget && mPuppetWidget->IsVisible()) {
return;
}
if (mPuppetWidget) { if (mPuppetWidget) {
mPuppetWidget->Show(true); mPuppetWidget->Show(true);
} }
@ -2824,6 +2886,10 @@ TabChild::MakeVisible()
void void
TabChild::MakeHidden() TabChild::MakeHidden()
{ {
if (mPuppetWidget && !mPuppetWidget->IsVisible()) {
return;
}
CompositorBridgeChild* compositor = CompositorBridgeChild::Get(); CompositorBridgeChild* compositor = CompositorBridgeChild::Get();
// Clear cached resources directly. This avoids one extra IPC // Clear cached resources directly. This avoids one extra IPC
@ -3182,6 +3248,13 @@ TabChild::GetOuterRect()
return ViewAs<ScreenPixel>(outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims); return ViewAs<ScreenPixel>(outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
} }
void
TabChild::ForcePaint(uint64_t aLayerObserverEpoch)
{
nsAutoScriptBlocker scriptBlocker;
RecvSetDocShellIsActive(true, false, aLayerObserverEpoch);
}
TabChildGlobal::TabChildGlobal(TabChildBase* aTabChild) TabChildGlobal::TabChildGlobal(TabChildBase* aTabChild)
: mTabChild(aTabChild) : mTabChild(aTabChild)
{ {

View File

@ -646,6 +646,9 @@ public:
mAPZChild = aAPZChild; mAPZChild = aAPZChild;
} }
// Request that the docshell be marked as active.
void ForcePaint(uint64_t aLayerObserverEpoch);
protected: protected:
virtual ~TabChild(); virtual ~TabChild();
@ -658,7 +661,8 @@ protected:
virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) override; virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) override;
virtual bool RecvSetDocShellIsActive(const bool& aIsActive, virtual bool RecvSetDocShellIsActive(const bool& aIsActive,
const bool& aIsHidden) override; const bool& aIsHidden,
const uint64_t& aLayerObserverEpoch) override;
virtual bool RecvNavigateByKey(const bool& aForward, virtual bool RecvNavigateByKey(const bool& aForward,
const bool& aForDocumentNavigation) override; const bool& aForDocumentNavigation) override;
@ -778,6 +782,9 @@ private:
// dangling pointer. // dangling pointer.
layers::APZChild* mAPZChild; layers::APZChild* mAPZChild;
// The most recently seen layer observer epoch in RecvSetDocShellIsActive.
uint64_t mLayerObserverEpoch;
DISALLOW_EVIL_CONSTRUCTORS(TabChild); DISALLOW_EVIL_CONSTRUCTORS(TabChild);
}; };

View File

@ -292,13 +292,13 @@ TabParent::TabParent(nsIContentParent* aManager,
, mInitedByParent(false) , mInitedByParent(false)
, mTabId(aTabId) , mTabId(aTabId)
, mCreatingWindow(false) , mCreatingWindow(false)
, mNeedLayerTreeReadyNotification(false)
, mCursor(nsCursor(-1)) , mCursor(nsCursor(-1))
, mTabSetsCursor(false) , mTabSetsCursor(false)
, mHasContentOpener(false) , mHasContentOpener(false)
#ifdef DEBUG #ifdef DEBUG
, mActiveSupressDisplayportCount(0) , mActiveSupressDisplayportCount(0)
#endif #endif
, mLayerTreeEpoch(0)
{ {
MOZ_ASSERT(aManager); MOZ_ASSERT(aManager);
} }
@ -493,13 +493,6 @@ TabParent::DestroyInternal()
if (RenderFrameParent* frame = GetRenderFrame()) { if (RenderFrameParent* frame = GetRenderFrame()) {
RemoveTabParentFromTable(frame->GetLayersId()); RemoveTabParentFromTable(frame->GetLayersId());
frame->Destroy(); frame->Destroy();
// Notify our layer tree update observer that we're going away. It's
// possible that we race with a notification and there can be an
// LayerTreeUpdateRunnable on the main thread's event queue with a pointer
// to us. However, our actual destruction won't be until yet another event
// *after* that one is processed, so this should be safe.
mLayerUpdateObserver->TabParentDestroyed();
} }
// Let all PluginWidgets know we are tearing down. Prevents // Let all PluginWidgets know we are tearing down. Prevents
@ -2280,12 +2273,10 @@ TabParent::GetTabIdFrom(nsIDocShell *docShell)
RenderFrameParent* RenderFrameParent*
TabParent::GetRenderFrame() TabParent::GetRenderFrame()
{ {
if (!mLayerUpdateObserver) {
mLayerUpdateObserver = new LayerTreeUpdateObserver(this);
}
PRenderFrameParent* p = LoneManagedOrNullAsserts(ManagedPRenderFrameParent()); PRenderFrameParent* p = LoneManagedOrNullAsserts(ManagedPRenderFrameParent());
return static_cast<RenderFrameParent*>(p); RenderFrameParent* frame = static_cast<RenderFrameParent*>(p);
return frame;
} }
bool bool
@ -2636,11 +2627,6 @@ TabParent::SetRenderFrame(PRenderFrameParent* aRFParent)
uint64_t layersId = renderFrame->GetLayersId(); uint64_t layersId = renderFrame->GetLayersId();
AddTabParentToTable(layersId, this); AddTabParentToTable(layersId, this);
if (mNeedLayerTreeReadyNotification) {
RequestNotifyLayerTreeReady();
mNeedLayerTreeReadyNotification = false;
}
return true; return true;
} }
@ -2885,10 +2871,22 @@ TabParent::GetUseAsyncPanZoom(bool* useAsyncPanZoom)
NS_IMETHODIMP NS_IMETHODIMP
TabParent::SetDocShellIsActive(bool isActive) TabParent::SetDocShellIsActive(bool isActive)
{ {
// Increment the epoch so that layer tree updates from previous
// SetDocShellIsActive requests are ignored.
mLayerTreeEpoch++;
// docshell is consider prerendered only if not active yet // docshell is consider prerendered only if not active yet
mIsPrerendered &= !isActive; mIsPrerendered &= !isActive;
mDocShellIsActive = isActive; mDocShellIsActive = isActive;
Unused << SendSetDocShellIsActive(isActive, true); Unused << SendSetDocShellIsActive(isActive, true, mLayerTreeEpoch);
// Ask the child to repaint using the PHangMonitor channel/thread (which may
// be less congested).
if (isActive) {
ContentParent* cp = Manager()->AsContentParent();
cp->ForceTabPaint(this, mLayerTreeEpoch);
}
return NS_OK; return NS_OK;
} }
@ -2912,7 +2910,7 @@ TabParent::SetDocShellIsActiveAndForeground(bool isActive)
// docshell is consider prerendered only if not active yet // docshell is consider prerendered only if not active yet
mIsPrerendered &= !isActive; mIsPrerendered &= !isActive;
mDocShellIsActive = isActive; mDocShellIsActive = isActive;
Unused << SendSetDocShellIsActive(isActive, false); Unused << SendSetDocShellIsActive(isActive, false, mLayerTreeEpoch);
return NS_OK; return NS_OK;
} }
@ -2973,12 +2971,13 @@ TabParent::NavigateByKey(bool aForward, bool aForDocumentNavigation)
class LayerTreeUpdateRunnable final class LayerTreeUpdateRunnable final
: public mozilla::Runnable : public mozilla::Runnable
{ {
RefPtr<LayerTreeUpdateObserver> mUpdateObserver; uint64_t mLayersId;
uint64_t mEpoch;
bool mActive; bool mActive;
public: public:
explicit LayerTreeUpdateRunnable(LayerTreeUpdateObserver* aObs, bool aActive) explicit LayerTreeUpdateRunnable(uint64_t aLayersId, uint64_t aEpoch, bool aActive)
: mUpdateObserver(aObs), mActive(aActive) : mLayersId(aLayersId), mEpoch(aEpoch), mActive(aActive)
{ {
MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!NS_IsMainThread());
} }
@ -2986,59 +2985,37 @@ public:
private: private:
NS_IMETHOD Run() override { NS_IMETHOD Run() override {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
if (RefPtr<TabParent> tabParent = mUpdateObserver->GetTabParent()) { if (RefPtr<TabParent> tabParent = TabParent::GetTabParentFromLayersId(mLayersId)) {
tabParent->LayerTreeUpdate(mActive); tabParent->LayerTreeUpdate(mEpoch, mActive);
} }
return NS_OK; return NS_OK;
} }
}; };
void /* static */ void
LayerTreeUpdateObserver::ObserveUpdate(uint64_t aLayersId, bool aActive) TabParent::ObserveLayerUpdate(uint64_t aLayersId, uint64_t aEpoch, bool aActive)
{ {
MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!NS_IsMainThread());
RefPtr<LayerTreeUpdateRunnable> runnable = RefPtr<LayerTreeUpdateRunnable> runnable =
new LayerTreeUpdateRunnable(this, aActive); new LayerTreeUpdateRunnable(aLayersId, aEpoch, aActive);
NS_DispatchToMainThread(runnable); NS_DispatchToMainThread(runnable);
} }
void
bool TabParent::LayerTreeUpdate(uint64_t aEpoch, bool aActive)
TabParent::RequestNotifyLayerTreeReady()
{ {
RenderFrameParent* frame = GetRenderFrame(); // Ignore updates from old epochs. They might tell us that layers are
if (!frame || !frame->IsInitted()) { // available when we've already sent a message to clear them. We can't trust
mNeedLayerTreeReadyNotification = true; // the update in that case since layers could disappear anytime after that.
} else { if (aEpoch != mLayerTreeEpoch || mIsDestroyed) {
GPUProcessManager::Get()->RequestNotifyLayerTreeReady( return;
frame->GetLayersId(),
mLayerUpdateObserver);
}
return true;
}
bool
TabParent::RequestNotifyLayerTreeCleared()
{
RenderFrameParent* frame = GetRenderFrame();
if (!frame) {
return false;
} }
GPUProcessManager::Get()->RequestNotifyLayerTreeCleared(
frame->GetLayersId(),
mLayerUpdateObserver);
return true;
}
bool
TabParent::LayerTreeUpdate(bool aActive)
{
nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement); nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement);
if (!target) { if (!target) {
NS_WARNING("Could not locate target for layer tree message."); NS_WARNING("Could not locate target for layer tree message.");
return true; return;
} }
RefPtr<Event> event = NS_NewDOMEvent(mFrameElement, nullptr, nullptr); RefPtr<Event> event = NS_NewDOMEvent(mFrameElement, nullptr, nullptr);
@ -3051,33 +3028,16 @@ TabParent::LayerTreeUpdate(bool aActive)
event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
bool dummy; bool dummy;
mFrameElement->DispatchEvent(event, &dummy); mFrameElement->DispatchEvent(event, &dummy);
return true;
} }
void bool
TabParent::SwapLayerTreeObservers(TabParent* aOther) TabParent::RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch)
{ {
if (IsDestroyed() || aOther->IsDestroyed()) { // We sent a ForcePaint message when layers were already visible. In this
return; // case, we should act as if an update occurred even though we already have
} // the layers.
LayerTreeUpdate(aLayerObserverEpoch, true);
RenderFrameParent* rfp = GetRenderFrame(); return true;
RenderFrameParent* otherRfp = aOther->GetRenderFrame();
if (!rfp || !otherRfp) {
return;
}
// The swap that happens for the observers in GPUProcessManager has to
// happen in a lock so that an update being processed on the compositor thread
// can't grab the layer update observer for the wrong tab parent.
GPUProcessManager::Get()->SwapLayerTreeObservers(
rfp->GetLayersId(),
otherRfp->GetLayersId());
// No need for a lock, destruction can only happen on the main thread and we
// only read mLayerUpdateObserver::mTabParent on the main thread.
Swap(mLayerUpdateObserver, aOther->mLayerUpdateObserver);
mLayerUpdateObserver->SwapTabParent(aOther->mLayerUpdateObserver);
} }
bool bool

View File

@ -80,39 +80,6 @@ namespace ipc {
class StructuredCloneData; class StructuredCloneData;
} // ipc namespace } // ipc namespace
// This observer runs on the compositor thread, so we dispatch a runnable to the
// main thread to actually dispatch the event.
class LayerTreeUpdateObserver : public layers::CompositorUpdateObserver
{
public:
explicit LayerTreeUpdateObserver(TabParent* aTabParent)
: mTabParent(aTabParent)
{
MOZ_ASSERT(NS_IsMainThread());
}
virtual void ObserveUpdate(uint64_t aLayersId, bool aActive) override;
virtual void SwapTabParent(LayerTreeUpdateObserver* aOther) {
MOZ_ASSERT(NS_IsMainThread());
Swap(mTabParent, aOther->mTabParent);
}
void TabParentDestroyed() {
MOZ_ASSERT(NS_IsMainThread());
mTabParent = nullptr;
}
TabParent* GetTabParent() {
MOZ_ASSERT(NS_IsMainThread());
return mTabParent;
}
private:
// NB: Should never be touched off the main thread!
TabParent* mTabParent;
};
class TabParent final : public PBrowserParent class TabParent final : public PBrowserParent
, public nsIDOMEventListener , public nsIDOMEventListener
, public nsITabParent , public nsITabParent
@ -587,14 +554,8 @@ public:
bool SendLoadRemoteScript(const nsString& aURL, bool SendLoadRemoteScript(const nsString& aURL,
const bool& aRunInGlobalScope); const bool& aRunInGlobalScope);
// See nsIFrameLoader requestNotifyLayerTreeReady. static void ObserveLayerUpdate(uint64_t aLayersId, uint64_t aEpoch, bool aActive);
bool RequestNotifyLayerTreeReady(); void LayerTreeUpdate(uint64_t aEpoch, bool aActive);
bool RequestNotifyLayerTreeCleared();
bool LayerTreeUpdate(bool aActive);
void SwapLayerTreeObservers(TabParent* aOther);
virtual bool virtual bool
RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers, RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
@ -644,6 +605,8 @@ protected:
virtual bool RecvRemotePaintIsReady() override; virtual bool RecvRemotePaintIsReady() override;
virtual bool RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch) override;
virtual bool RecvSetDimensions(const uint32_t& aFlags, virtual bool RecvSetDimensions(const uint32_t& aFlags,
const int32_t& aX, const int32_t& aY, const int32_t& aX, const int32_t& aY,
const int32_t& aCx, const int32_t& aCy) override; const int32_t& aCx, const int32_t& aCy) override;
@ -761,11 +724,6 @@ private:
// CreateWindow response. Then TabChild loads them immediately. // CreateWindow response. Then TabChild loads them immediately.
nsTArray<FrameScriptInfo> mDelayedFrameScripts; nsTArray<FrameScriptInfo> mDelayedFrameScripts;
// If the user called RequestNotifyLayerTreeReady and the RenderFrameParent
// wasn't ready yet, we set this flag and call RequestNotifyLayerTreeReady
// again once the RenderFrameParent arrives.
bool mNeedLayerTreeReadyNotification;
// Cached cursor setting from TabChild. When the cursor is over the tab, // Cached cursor setting from TabChild. When the cursor is over the tab,
// it should take this appearance. // it should take this appearance.
nsCursor mCursor; nsCursor mCursor;
@ -796,7 +754,7 @@ private:
static void RemoveTabParentFromTable(uint64_t aLayersId); static void RemoveTabParentFromTable(uint64_t aLayersId);
RefPtr<LayerTreeUpdateObserver> mLayerUpdateObserver; uint64_t mLayerTreeEpoch;
public: public:
static TabParent* GetTabParentFromLayersId(uint64_t aLayersId); static TabParent* GetTabParentFromLayersId(uint64_t aLayersId);

View File

@ -511,24 +511,6 @@ GPUProcessManager::DeallocateLayerTreeId(uint64_t aLayersId)
CompositorBridgeParent::DeallocateLayerTreeId(aLayersId); CompositorBridgeParent::DeallocateLayerTreeId(aLayersId);
} }
void
GPUProcessManager::RequestNotifyLayerTreeReady(uint64_t aLayersId, CompositorUpdateObserver* aObserver)
{
CompositorBridgeParent::RequestNotifyLayerTreeReady(aLayersId, aObserver);
}
void
GPUProcessManager::RequestNotifyLayerTreeCleared(uint64_t aLayersId, CompositorUpdateObserver* aObserver)
{
CompositorBridgeParent::RequestNotifyLayerTreeCleared(aLayersId, aObserver);
}
void
GPUProcessManager::SwapLayerTreeObservers(uint64_t aLayer, uint64_t aOtherLayer)
{
CompositorBridgeParent::SwapLayerTreeObservers(aLayer, aOtherLayer);
}
bool bool
GPUProcessManager::UpdateRemoteContentController(uint64_t aLayersId, GPUProcessManager::UpdateRemoteContentController(uint64_t aLayersId,
dom::ContentParent* aContentParent, dom::ContentParent* aContentParent,

View File

@ -104,10 +104,6 @@ public:
// Must run on the content main thread. // Must run on the content main thread.
void DeallocateLayerTreeId(uint64_t aLayersId); void DeallocateLayerTreeId(uint64_t aLayersId);
void RequestNotifyLayerTreeReady(uint64_t aLayersId, CompositorUpdateObserver* aObserver);
void RequestNotifyLayerTreeCleared(uint64_t aLayersId, CompositorUpdateObserver* aObserver);
void SwapLayerTreeObservers(uint64_t aLayer, uint64_t aOtherLayer);
// Creates a new RemoteContentController for aTabId. Should only be called on // Creates a new RemoteContentController for aTabId. Should only be called on
// the main thread. // the main thread.
// //

View File

@ -835,6 +835,12 @@ ClientLayerManager::SetNextPaintSyncId(int32_t aSyncId)
mForwarder->SetPaintSyncId(aSyncId); mForwarder->SetPaintSyncId(aSyncId);
} }
void
ClientLayerManager::SetLayerObserverEpoch(uint64_t aLayerObserverEpoch)
{
mForwarder->SetLayerObserverEpoch(aLayerObserverEpoch);
}
void void
ClientLayerManager::AddDidCompositeObserver(DidCompositeObserver* aObserver) ClientLayerManager::AddDidCompositeObserver(DidCompositeObserver* aObserver)
{ {

View File

@ -238,6 +238,8 @@ public:
void SetNextPaintSyncId(int32_t aSyncId); void SetNextPaintSyncId(int32_t aSyncId);
void SetLayerObserverEpoch(uint64_t aLayerObserverEpoch);
class DidCompositeObserver { class DidCompositeObserver {
public: public:
virtual void DidComposite() = 0; virtual void DidComposite() = 0;

View File

@ -26,6 +26,7 @@
#include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown #include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown
#include "mozilla/DebugOnly.h" // for DebugOnly #include "mozilla/DebugOnly.h" // for DebugOnly
#include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/TabParent.h"
#include "mozilla/gfx/2D.h" // for DrawTarget #include "mozilla/gfx/2D.h" // for DrawTarget
#include "mozilla/gfx/Point.h" // for IntSize #include "mozilla/gfx/Point.h" // for IntSize
#include "mozilla/gfx/Rect.h" // for IntSize #include "mozilla/gfx/Rect.h" // for IntSize
@ -1819,19 +1820,6 @@ CompositorBridgeParent::DeallocateLayerTreeId(uint64_t aId)
CompositorLoop()->PostTask(NewRunnableFunction(&EraseLayerState, aId)); CompositorLoop()->PostTask(NewRunnableFunction(&EraseLayerState, aId));
} }
/* static */ void
CompositorBridgeParent::SwapLayerTreeObservers(uint64_t aLayerId, uint64_t aOtherLayerId)
{
EnsureLayerTreeMapReady();
MonitorAutoLock lock(*sIndirectLayerTreesLock);
NS_ASSERTION(sIndirectLayerTrees.find(aLayerId) != sIndirectLayerTrees.end(),
"SwapLayerTrees missing layer 1");
NS_ASSERTION(sIndirectLayerTrees.find(aOtherLayerId) != sIndirectLayerTrees.end(),
"SwapLayerTrees missing layer 2");
std::swap(sIndirectLayerTrees[aLayerId].mLayerTreeReadyObserver,
sIndirectLayerTrees[aOtherLayerId].mLayerTreeReadyObserver);
}
static void static void
UpdateControllerForLayersId(uint64_t aLayersId, UpdateControllerForLayersId(uint64_t aLayersId,
GeckoContentController* aController) GeckoContentController* aController)
@ -1908,22 +1896,6 @@ CompositorBridgeParent::PostInsertVsyncProfilerMarker(TimeStamp aVsyncTimestamp)
} }
} }
/* static */ void
CompositorBridgeParent::RequestNotifyLayerTreeReady(uint64_t aLayersId, CompositorUpdateObserver* aObserver)
{
EnsureLayerTreeMapReady();
MonitorAutoLock lock(*sIndirectLayerTreesLock);
sIndirectLayerTrees[aLayersId].mLayerTreeReadyObserver = aObserver;
}
/* static */ void
CompositorBridgeParent::RequestNotifyLayerTreeCleared(uint64_t aLayersId, CompositorUpdateObserver* aObserver)
{
EnsureLayerTreeMapReady();
MonitorAutoLock lock(*sIndirectLayerTreesLock);
sIndirectLayerTrees[aLayersId].mLayerTreeClearedObserver = aObserver;
}
widget::PCompositorWidgetParent* widget::PCompositorWidgetParent*
CompositorBridgeParent::AllocPCompositorWidgetParent(const CompositorWidgetInitData& aInitData) CompositorBridgeParent::AllocPCompositorWidgetParent(const CompositorWidgetInitData& aInitData)
{ {
@ -2528,10 +2500,8 @@ CrossProcessCompositorBridgeParent::ShadowLayersUpdated(
mNotifyAfterRemotePaint = false; mNotifyAfterRemotePaint = false;
} }
if (state->mLayerTreeReadyObserver) { if (aLayerTree->ShouldParentObserveEpoch()) {
RefPtr<CompositorUpdateObserver> observer = state->mLayerTreeReadyObserver; dom::TabParent::ObserveLayerUpdate(id, aLayerTree->GetChildEpoch(), true);
state->mLayerTreeReadyObserver = nullptr;
observer->ObserveUpdate(id, true);
} }
aLayerTree->SetPendingTransactionId(aTransactionId); aLayerTree->SetPendingTransactionId(aTransactionId);
@ -2760,15 +2730,7 @@ CrossProcessCompositorBridgeParent::NotifyClearCachedResources(LayerTransactionP
uint64_t id = aLayerTree->GetId(); uint64_t id = aLayerTree->GetId();
MOZ_ASSERT(id != 0); MOZ_ASSERT(id != 0);
RefPtr<CompositorUpdateObserver> observer; dom::TabParent::ObserveLayerUpdate(id, aLayerTree->GetChildEpoch(), false);
{ // scope lock
MonitorAutoLock lock(*sIndirectLayerTreesLock);
observer = sIndirectLayerTrees[id].mLayerTreeClearedObserver;
sIndirectLayerTrees[id].mLayerTreeClearedObserver = nullptr;
}
if (observer) {
observer->ObserveUpdate(id, false);
}
} }
bool bool

View File

@ -191,17 +191,6 @@ private:
#endif #endif
}; };
class CompositorUpdateObserver
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorUpdateObserver);
virtual void ObserveUpdate(uint64_t aLayersId, bool aActive) = 0;
protected:
virtual ~CompositorUpdateObserver() {}
};
class CompositorBridgeParent final : public PCompositorBridgeParent, class CompositorBridgeParent final : public PCompositorBridgeParent,
public ShadowLayersManager, public ShadowLayersManager,
public CompositorBridgeParentIPCAllocator, public CompositorBridgeParentIPCAllocator,
@ -440,8 +429,6 @@ public:
LayerTransactionParent* mLayerTree; LayerTransactionParent* mLayerTree;
nsTArray<PluginWindowData> mPluginData; nsTArray<PluginWindowData> mPluginData;
bool mUpdatedPluginDataAvailable; bool mUpdatedPluginDataAvailable;
RefPtr<CompositorUpdateObserver> mLayerTreeReadyObserver;
RefPtr<CompositorUpdateObserver> mLayerTreeClearedObserver;
// Number of times the compositor has been reset without having been // Number of times the compositor has been reset without having been
// acknowledged by the child. // acknowledged by the child.
@ -515,10 +502,6 @@ private:
*/ */
static void DeallocateLayerTreeId(uint64_t aId); static void DeallocateLayerTreeId(uint64_t aId);
static void RequestNotifyLayerTreeReady(uint64_t aLayersId, CompositorUpdateObserver* aObserver);
static void RequestNotifyLayerTreeCleared(uint64_t aLayersId, CompositorUpdateObserver* aObserver);
static void SwapLayerTreeObservers(uint64_t aLayer, uint64_t aOtherLayer);
/** /**
* Creates a new RemoteContentController for aTabId. Should only be called on * Creates a new RemoteContentController for aTabId. Should only be called on
* the main thread. * the main thread.

View File

@ -149,6 +149,8 @@ LayerTransactionParent::LayerTransactionParent(LayerManagerComposite* aManager,
: mLayerManager(aManager) : mLayerManager(aManager)
, mShadowLayersManager(aLayersManager) , mShadowLayersManager(aLayersManager)
, mId(aId) , mId(aId)
, mChildEpoch(0)
, mParentEpoch(0)
, mPendingTransaction(0) , mPendingTransaction(0)
, mPendingCompositorUpdates(0) , mPendingCompositorUpdates(0)
, mDestroyed(false) , mDestroyed(false)
@ -711,6 +713,24 @@ LayerTransactionParent::RecvUpdate(InfallibleTArray<Edit>&& cset,
return true; return true;
} }
bool
LayerTransactionParent::RecvSetLayerObserverEpoch(const uint64_t& aLayerObserverEpoch)
{
mChildEpoch = aLayerObserverEpoch;
return true;
}
bool
LayerTransactionParent::ShouldParentObserveEpoch()
{
if (mParentEpoch == mChildEpoch) {
return false;
}
mParentEpoch = mChildEpoch;
return true;
}
bool bool
LayerTransactionParent::RecvSetTestSampleTime(const TimeStamp& aTime) LayerTransactionParent::RecvSetTestSampleTime(const TimeStamp& aTime)
{ {

View File

@ -60,6 +60,9 @@ public:
uint64_t GetId() const { return mId; } uint64_t GetId() const { return mId; }
Layer* GetRoot() const { return mRoot; } Layer* GetRoot() const { return mRoot; }
uint64_t GetChildEpoch() const { return mChildEpoch; }
bool ShouldParentObserveEpoch();
virtual ShmemAllocator* AsShmemAllocator() override { return this; } virtual ShmemAllocator* AsShmemAllocator() override { return this; }
virtual bool AllocShmem(size_t aSize, virtual bool AllocShmem(size_t aSize,
@ -139,6 +142,8 @@ protected:
const mozilla::TimeStamp& aTransactionStart, const mozilla::TimeStamp& aTransactionStart,
const int32_t& aPaintSyncId) override; const int32_t& aPaintSyncId) override;
virtual bool RecvSetLayerObserverEpoch(const uint64_t& aLayerObserverEpoch) override;
virtual bool RecvClearCachedResources() override; virtual bool RecvClearCachedResources() override;
virtual bool RecvForceComposite() override; virtual bool RecvForceComposite() override;
virtual bool RecvSetTestSampleTime(const TimeStamp& aTime) override; virtual bool RecvSetTestSampleTime(const TimeStamp& aTime) override;
@ -196,6 +201,13 @@ private:
// because the "real tree" is owned by the compositor. // because the "real tree" is owned by the compositor.
uint64_t mId; uint64_t mId;
// These fields keep track of the latest epoch values in the child and the
// parent. mChildEpoch is the latest epoch value received from the child.
// mParentEpoch is the latest epoch value that we have told TabParent about
// (via ObserveLayerUpdate).
uint64_t mChildEpoch;
uint64_t mParentEpoch;
uint64_t mPendingTransaction; uint64_t mPendingTransaction;
// Number of compositor updates we're waiting for the child to // Number of compositor updates we're waiting for the child to

View File

@ -70,6 +70,8 @@ parent:
bool isRepeatTransaction, TimeStamp transactionStart, bool isRepeatTransaction, TimeStamp transactionStart,
int32_t paintSyncId); int32_t paintSyncId);
async SetLayerObserverEpoch(uint64_t layerObserverEpoch);
// Testing APIs // Testing APIs
// Enter test mode, set the sample time to sampleTime, and resample // Enter test mode, set the sample time to sampleTime, and resample

View File

@ -797,6 +797,15 @@ ShadowLayerForwarder::EndTransaction(InfallibleTArray<EditReply>* aReplies,
return true; return true;
} }
void
ShadowLayerForwarder::SetLayerObserverEpoch(uint64_t aLayerObserverEpoch)
{
if (!HasShadowManager() || !mShadowManager->IPCOpen()) {
return;
}
Unused << mShadowManager->SendSetLayerObserverEpoch(aLayerObserverEpoch);
}
bool bool
ShadowLayerForwarder::AllocUnsafeShmem(size_t aSize, ShadowLayerForwarder::AllocUnsafeShmem(size_t aSize,
ipc::SharedMemory::SharedMemoryType aShmType, ipc::SharedMemory::SharedMemoryType aShmType,

View File

@ -376,6 +376,8 @@ public:
void SetPaintSyncId(int32_t aSyncId) { mPaintSyncId = aSyncId; } void SetPaintSyncId(int32_t aSyncId) { mPaintSyncId = aSyncId; }
void SetLayerObserverEpoch(uint64_t aLayerObserverEpoch);
static void PlatformSyncBeforeUpdate(); static void PlatformSyncBeforeUpdate();
virtual bool AllocSurfaceDescriptor(const gfx::IntSize& aSize, virtual bool AllocSurfaceDescriptor(const gfx::IntSize& aSize,

View File

@ -360,6 +360,9 @@ js::RunScript(JSContext* cx, RunState& state)
{ {
JS_CHECK_RECURSION(cx, return false); JS_CHECK_RECURSION(cx, return false);
// Since any script can conceivably GC, make sure it's safe to do so.
JS::AutoAssertOnGC::VerifyIsSafeToGC(cx->runtime());
if (!Debugger::checkNoExecute(cx, state.script())) if (!Debugger::checkNoExecute(cx, state.script()))
return false; return false;

View File

@ -572,7 +572,11 @@ var PrintUtils = {
// Set the original window as an active window so any mozPrintCallbacks can // Set the original window as an active window so any mozPrintCallbacks can
// run without delayed setTimeouts. // run without delayed setTimeouts.
this._sourceBrowser.docShellIsActive = true; if (this._listener.activateBrowser) {
this._listener.activateBrowser(this._sourceBrowser);
} else {
this._sourceBrowser.docShellIsActive = true;
}
// show the toolbar after we go into print preview mode so // show the toolbar after we go into print preview mode so
// that we can initialize the toolbar with total num pages // that we can initialize the toolbar with total num pages

View File

@ -747,6 +747,10 @@ var PrintPreviewListener = {
gBrowser.collapsed = false; gBrowser.collapsed = false;
document.getElementById("viewSource-toolbox").hidden = false; document.getElementById("viewSource-toolbox").hidden = false;
}, },
activateBrowser(browser) {
browser.docShellIsActive = true;
},
}; };
// viewZoomOverlay.js uses this // viewZoomOverlay.js uses this

View File

@ -1278,6 +1278,11 @@
if (aOtherBrowser._remoteFinder) if (aOtherBrowser._remoteFinder)
aOtherBrowser._remoteFinder.swapBrowser(aOtherBrowser); aOtherBrowser._remoteFinder.swapBrowser(aOtherBrowser);
} }
event = new CustomEvent("EndSwapDocShells", {"detail": aOtherBrowser});
this.dispatchEvent(event);
event = new CustomEvent("EndSwapDocShells", {"detail": this});
aOtherBrowser.dispatchEvent(event);
]]> ]]>
</body> </body>
</method> </method>
@ -1323,9 +1328,6 @@
]]> ]]>
</body> </body>
</method> </method>
<!-- This will go away if the binding has been removed for some reason. -->
<field name="_alive">true</field>
</implementation> </implementation>
<handlers> <handlers>

View File

@ -1107,6 +1107,14 @@ PuppetWidget::PaintTask::Run()
return NS_OK; return NS_OK;
} }
void
PuppetWidget::PaintNowIfNeeded()
{
if (IsVisible() && mPaintTask.IsPending()) {
Paint();
}
}
NS_IMPL_ISUPPORTS(PuppetWidget::MemoryPressureObserver, nsIObserver) NS_IMPL_ISUPPORTS(PuppetWidget::MemoryPressureObserver, nsIObserver)
NS_IMETHODIMP NS_IMETHODIMP

View File

@ -200,6 +200,9 @@ public:
virtual bool NeedsPaint() override; virtual bool NeedsPaint() override;
// Paint the widget immediately if any paints are queued up.
void PaintNowIfNeeded();
virtual TabChild* GetOwningTabChild() override { return mTabChild; } virtual TabChild* GetOwningTabChild() override { return mTabChild; }
void UpdateBackingScaleCache(float aDpi, double aScale) void UpdateBackingScaleCache(float aDpi, double aScale)