Bug 797615 - Guard against isBrowserContentDocumentDisplayed returning true prematurely. r=Cwiiis

Prior to this change, isBrowserContentDocumentDisplayed returned false
from the time that the isFirstPaint flag was set in layout to the time
that layout handed off the rendered document to the compositor. However
the way the function is used meant that it needs to return false until
the compositor actually composites the "first-paint" rendering,
otherwise other events can sneak in and run before the compositor. This
patch moves the tracking for the flag into GeckoLayerClient so that it
can be queried and modified synchronously from both the Gecko thread in
browser.js and the compositor thread in setFirstPaintViewport.
This commit is contained in:
Kartikaya Gupta 2013-05-30 09:55:23 -04:00
parent 0b742ebed4
commit 059609fb24
7 changed files with 87 additions and 6 deletions

View File

@ -93,6 +93,15 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
private final LayerMarginsAnimator mMarginsAnimator; private final LayerMarginsAnimator mMarginsAnimator;
private LayerView mView; private LayerView mView;
/* This flag is true from the time that browser.js detects a first-paint is about to start,
* to the time that we receive the first-paint composite notification from the compositor.
* Note that there is a small race condition with this; if there are two paints that both
* have the first-paint flag set, and the second paint happens concurrently with the
* composite for the first paint, then this flag may be set to true prematurely. Fixing this
* is possible but risky; see https://bugzilla.mozilla.org/show_bug.cgi?id=797615#c751
*/
private volatile boolean mContentDocumentIsDisplayed;
public GeckoLayerClient(Context context, LayerView view, EventDispatcher eventDispatcher) { public GeckoLayerClient(Context context, LayerView view, EventDispatcher eventDispatcher) {
// we can fill these in with dummy values because they are always written // we can fill these in with dummy values because they are always written
// to before being read // to before being read
@ -120,6 +129,7 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
mMarginsAnimator = new LayerMarginsAnimator(this, view); mMarginsAnimator = new LayerMarginsAnimator(this, view);
mView = view; mView = view;
mView.setListener(this); mView.setListener(this);
mContentDocumentIsDisplayed = true;
} }
/** Attaches to root layer so that Gecko appears. */ /** Attaches to root layer so that Gecko appears. */
@ -413,6 +423,16 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
} }
} }
/* This is invoked by JNI on the gecko thread */
void contentDocumentChanged() {
mContentDocumentIsDisplayed = false;
}
/* This is invoked by JNI on the gecko thread */
boolean isContentDocumentDisplayed() {
return mContentDocumentIsDisplayed;
}
// This is called on the Gecko thread to determine if we're still interested // This is called on the Gecko thread to determine if we're still interested
// in the update of this display-port to continue. We can return true here // in the update of this display-port to continue. We can return true here
// to abort the current update and continue with any subsequent ones. This // to abort the current update and continue with any subsequent ones. This
@ -579,6 +599,8 @@ public class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
} }
DisplayPortCalculator.resetPageState(); DisplayPortCalculator.resetPageState();
mDrawTimingQueue.reset(); mDrawTimingQueue.reset();
mContentDocumentIsDisplayed = true;
} }
/** This function is invoked by Gecko via JNI; be careful when modifying signature. /** This function is invoked by Gecko via JNI; be careful when modifying signature.

View File

@ -637,7 +637,7 @@ var BrowserApp = {
// off to the compositor. // off to the compositor.
isBrowserContentDocumentDisplayed: function() { isBrowserContentDocumentDisplayed: function() {
try { try {
if (window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).isFirstPaint) if (!getBridge().isContentDocumentDisplayed())
return false; return false;
} catch (e) { } catch (e) {
return false; return false;
@ -649,8 +649,9 @@ var BrowserApp = {
return tab.contentDocumentIsDisplayed; return tab.contentDocumentIsDisplayed;
}, },
displayedDocumentChanged: function() { contentDocumentChanged: function() {
window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).isFirstPaint = true; window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).isFirstPaint = true;
getBridge().contentDocumentChanged();
}, },
get tabs() { get tabs() {
@ -677,7 +678,7 @@ var BrowserApp = {
Tabs.touch(aTab); Tabs.touch(aTab);
aTab.setActive(true); aTab.setActive(true);
aTab.setResolution(aTab._zoom, true); aTab.setResolution(aTab._zoom, true);
this.displayedDocumentChanged(); this.contentDocumentChanged();
this.deck.selectedPanel = aTab.browser; this.deck.selectedPanel = aTab.browser;
// Focus the browser so that things like selection will be styled correctly. // Focus the browser so that things like selection will be styled correctly.
aTab.browser.focus(); aTab.browser.focus();
@ -1442,7 +1443,7 @@ var BrowserApp = {
break; break;
case "Viewport:Flush": case "Viewport:Flush":
this.displayedDocumentChanged(); this.contentDocumentChanged();
break; break;
case "Passwords:Init": { case "Passwords:Init": {
@ -3840,7 +3841,7 @@ Tab.prototype = {
let contentDocument = aSubject; let contentDocument = aSubject;
if (contentDocument == this.browser.contentDocument) { if (contentDocument == this.browser.contentDocument) {
if (BrowserApp.selectedTab == this) { if (BrowserApp.selectedTab == this) {
BrowserApp.displayedDocumentChanged(); BrowserApp.contentDocumentChanged();
} }
this.contentDocumentIsDisplayed = true; this.contentDocumentIsDisplayed = true;

View File

@ -2193,6 +2193,20 @@ NS_IMETHODIMP nsAndroidBridge::GetDisplayPort(bool aPageSizeUpdate, bool aIsBrow
return NS_OK; return NS_OK;
} }
/* void displayedDocumentChanged(); */
NS_IMETHODIMP nsAndroidBridge::ContentDocumentChanged()
{
AndroidBridge::Bridge()->ContentDocumentChanged();
return NS_OK;
}
/* boolean isContentDocumentDisplayed(); */
NS_IMETHODIMP nsAndroidBridge::IsContentDocumentDisplayed(bool *aRet)
{
*aRet = AndroidBridge::Bridge()->IsContentDocumentDisplayed();
return NS_OK;
}
void void
AndroidBridge::NotifyDefaultPrevented(bool aDefaultPrevented) AndroidBridge::NotifyDefaultPrevented(bool aDefaultPrevented)
{ {
@ -2697,6 +2711,26 @@ AndroidBridge::GetDisplayPort(bool aPageSizeUpdate, bool aIsBrowserContentDispla
mLayerClient->GetDisplayPort(&jniFrame, aPageSizeUpdate, aIsBrowserContentDisplayed, tabId, metrics, displayPort); mLayerClient->GetDisplayPort(&jniFrame, aPageSizeUpdate, aIsBrowserContentDisplayed, tabId, metrics, displayPort);
} }
void
AndroidBridge::ContentDocumentChanged()
{
JNIEnv* env = GetJNIEnv();
if (!env || !mLayerClient)
return;
AutoLocalJNIFrame jniFrame(env, 0);
mLayerClient->ContentDocumentChanged(&jniFrame);
}
bool
AndroidBridge::IsContentDocumentDisplayed()
{
JNIEnv* env = GetJNIEnv();
if (!env || !mLayerClient)
return false;
AutoLocalJNIFrame jniFrame(env, 0);
return mLayerClient->IsContentDocumentDisplayed(&jniFrame);
}
bool bool
AndroidBridge::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution, bool aDrawingCritical, gfx::Rect& aViewport, float& aScaleX, float& aScaleY) AndroidBridge::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution, bool aDrawingCritical, gfx::Rect& aViewport, float& aScaleX, float& aScaleY)
{ {

View File

@ -191,6 +191,8 @@ public:
nsresult CaptureThumbnail(nsIDOMWindow *window, int32_t bufW, int32_t bufH, int32_t tabId, jobject buffer); nsresult CaptureThumbnail(nsIDOMWindow *window, int32_t bufW, int32_t bufH, int32_t tabId, jobject buffer);
void SendThumbnail(jobject buffer, int32_t tabId, bool success); void SendThumbnail(jobject buffer, int32_t tabId, bool success);
void GetDisplayPort(bool aPageSizeUpdate, bool aIsBrowserContentDisplayed, int32_t tabId, nsIAndroidViewport* metrics, nsIAndroidDisplayport** displayPort); void GetDisplayPort(bool aPageSizeUpdate, bool aIsBrowserContentDisplayed, int32_t tabId, nsIAndroidViewport* metrics, nsIAndroidDisplayport** displayPort);
void ContentDocumentChanged();
bool IsContentDocumentDisplayed();
bool ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution, bool aDrawingCritical, gfx::Rect& aViewport, float& aScaleX, float& aScaleY); bool ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution, bool aDrawingCritical, gfx::Rect& aViewport, float& aScaleX, float& aScaleY);

View File

@ -97,6 +97,8 @@ jmethodID AndroidGeckoLayerClient::jCreateFrameMethod = 0;
jmethodID AndroidGeckoLayerClient::jActivateProgramMethod = 0; jmethodID AndroidGeckoLayerClient::jActivateProgramMethod = 0;
jmethodID AndroidGeckoLayerClient::jDeactivateProgramMethod = 0; jmethodID AndroidGeckoLayerClient::jDeactivateProgramMethod = 0;
jmethodID AndroidGeckoLayerClient::jGetDisplayPort = 0; jmethodID AndroidGeckoLayerClient::jGetDisplayPort = 0;
jmethodID AndroidGeckoLayerClient::jContentDocumentChanged = 0;
jmethodID AndroidGeckoLayerClient::jIsContentDocumentDisplayed = 0;
jmethodID AndroidGeckoLayerClient::jViewportCtor = 0; jmethodID AndroidGeckoLayerClient::jViewportCtor = 0;
jfieldID AndroidGeckoLayerClient::jDisplayportPosition = 0; jfieldID AndroidGeckoLayerClient::jDisplayportPosition = 0;
jfieldID AndroidGeckoLayerClient::jDisplayportResolution = 0; jfieldID AndroidGeckoLayerClient::jDisplayportResolution = 0;
@ -359,6 +361,8 @@ AndroidGeckoLayerClient::InitGeckoLayerClientClass(JNIEnv *jEnv)
jActivateProgramMethod = getMethod("activateProgram", "()V"); jActivateProgramMethod = getMethod("activateProgram", "()V");
jDeactivateProgramMethod = getMethod("deactivateProgram", "()V"); jDeactivateProgramMethod = getMethod("deactivateProgram", "()V");
jGetDisplayPort = getMethod("getDisplayPort", "(ZZILorg/mozilla/gecko/gfx/ImmutableViewportMetrics;)Lorg/mozilla/gecko/gfx/DisplayPortMetrics;"); jGetDisplayPort = getMethod("getDisplayPort", "(ZZILorg/mozilla/gecko/gfx/ImmutableViewportMetrics;)Lorg/mozilla/gecko/gfx/DisplayPortMetrics;");
jContentDocumentChanged = getMethod("contentDocumentChanged", "()V");
jIsContentDocumentDisplayed = getMethod("isContentDocumentDisplayed", "()Z");
jViewportClass = GetClassGlobalRef(jEnv, "org/mozilla/gecko/gfx/ImmutableViewportMetrics"); jViewportClass = GetClassGlobalRef(jEnv, "org/mozilla/gecko/gfx/ImmutableViewportMetrics");
jViewportCtor = GetMethodID(jEnv, jViewportClass, "<init>", "(FFFFFFFFFFFFF)V"); jViewportCtor = GetMethodID(jEnv, jViewportClass, "<init>", "(FFFFFFFFFFFFF)V");
@ -1068,6 +1072,18 @@ AndroidGeckoLayerClient::GetDisplayPort(AutoLocalJNIFrame *jniFrame, bool aPageS
(*displayPort)->AddRef(); (*displayPort)->AddRef();
} }
void
AndroidGeckoLayerClient::ContentDocumentChanged(AutoLocalJNIFrame *jniFrame)
{
jniFrame->GetEnv()->CallVoidMethod(wrapped_obj, jContentDocumentChanged);
}
bool
AndroidGeckoLayerClient::IsContentDocumentDisplayed(AutoLocalJNIFrame *jniFrame)
{
return jniFrame->GetEnv()->CallBooleanMethod(wrapped_obj, jIsContentDocumentDisplayed);
}
bool bool
AndroidGeckoLayerClient::CreateFrame(AutoLocalJNIFrame *jniFrame, AndroidLayerRendererFrame& aFrame) AndroidGeckoLayerClient::CreateFrame(AutoLocalJNIFrame *jniFrame, AndroidLayerRendererFrame& aFrame)
{ {

View File

@ -281,6 +281,8 @@ public:
bool ActivateProgram(AutoLocalJNIFrame *jniFrame); bool ActivateProgram(AutoLocalJNIFrame *jniFrame);
bool DeactivateProgram(AutoLocalJNIFrame *jniFrame); bool DeactivateProgram(AutoLocalJNIFrame *jniFrame);
void GetDisplayPort(AutoLocalJNIFrame *jniFrame, bool aPageSizeUpdate, bool aIsBrowserContentDisplayed, int32_t tabId, nsIAndroidViewport* metrics, nsIAndroidDisplayport** displayPort); void GetDisplayPort(AutoLocalJNIFrame *jniFrame, bool aPageSizeUpdate, bool aIsBrowserContentDisplayed, int32_t tabId, nsIAndroidViewport* metrics, nsIAndroidDisplayport** displayPort);
void ContentDocumentChanged(AutoLocalJNIFrame *jniFrame);
bool IsContentDocumentDisplayed(AutoLocalJNIFrame *jniFrame);
protected: protected:
static jclass jGeckoLayerClientClass; static jclass jGeckoLayerClientClass;
@ -292,6 +294,8 @@ protected:
static jmethodID jActivateProgramMethod; static jmethodID jActivateProgramMethod;
static jmethodID jDeactivateProgramMethod; static jmethodID jDeactivateProgramMethod;
static jmethodID jGetDisplayPort; static jmethodID jGetDisplayPort;
static jmethodID jContentDocumentChanged;
static jmethodID jIsContentDocumentDisplayed;
static jmethodID jProgressiveUpdateCallbackMethod; static jmethodID jProgressiveUpdateCallbackMethod;
public: public:

View File

@ -41,10 +41,12 @@ interface nsIAndroidDisplayport : nsISupports {
attribute float resolution; attribute float resolution;
}; };
[scriptable, uuid(bbb8e0d7-5cca-4ad0-88be-538ce6d04f63)] [scriptable, uuid(5aa0cfa5-377c-4f5e-8dcf-59ebd9482d65)]
interface nsIAndroidBridge : nsISupports interface nsIAndroidBridge : nsISupports
{ {
AString handleGeckoMessage(in AString message); AString handleGeckoMessage(in AString message);
attribute nsIAndroidBrowserApp browserApp; attribute nsIAndroidBrowserApp browserApp;
nsIAndroidDisplayport getDisplayPort(in boolean aPageSizeUpdate, in boolean isBrowserContentDisplayed, in int32_t tabId, in nsIAndroidViewport metrics); nsIAndroidDisplayport getDisplayPort(in boolean aPageSizeUpdate, in boolean isBrowserContentDisplayed, in int32_t tabId, in nsIAndroidViewport metrics);
void contentDocumentChanged();
boolean isContentDocumentDisplayed();
}; };