Bug 1763570 - Wait for APZ state to set autofill information. r=geckoview-reviewers,owlish

When setting focus to input element, Gecko sets focused element to central via
`zoomToFocusedInput`. So when we receives `focusin` event, content may be
scrolled and zoomed. To pass correct element rectangle, we have to wait until
it is completed.

Fennec added `PanZoom:StateChange` event to listen APZ state. So GV should use
same way.

Differential Revision: https://phabricator.services.mozilla.com/D150453
This commit is contained in:
Makoto Kato 2022-07-14 18:32:47 +00:00
Родитель f81ba5c480
Коммит c69c9f0882
4 изменённых файлов: 98 добавлений и 6 удалений

Просмотреть файл

@ -1473,13 +1473,19 @@ bool BrowserChild::NotifyAPZStateChange(
const layers::GeckoContentController::APZStateChange& aChange,
const int& aArg) {
mAPZEventState->ProcessAPZStateChange(aViewId, aChange, aArg);
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (aChange ==
layers::GeckoContentController::APZStateChange::eTransformEnd) {
// This is used by tests to determine when the APZ is done doing whatever
// it's doing. XXX generify this as needed when writing additional tests.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
observerService->NotifyObservers(nullptr, "APZ:TransformEnd", nullptr);
observerService->NotifyObservers(nullptr, "PanZoom:StateChange",
u"NOTHING");
} else if (aChange ==
layers::GeckoContentController::APZStateChange::eTransformBegin) {
observerService->NotifyObservers(nullptr, "PanZoom:StateChange",
u"PANNING");
}
return true;
}

Просмотреть файл

@ -47,11 +47,22 @@ class GeckoViewAutoFillChild extends GeckoViewActorChild {
break;
}
case "focusin": {
if (
this.contentWindow.HTMLInputElement.isInstance(aEvent.composedTarget)
) {
this.onFocus(aEvent.composedTarget);
const element = aEvent.composedTarget;
if (!this.contentWindow.HTMLInputElement.isInstance(element)) {
break;
}
GeckoViewUtils.waitForPanZoomState(this.contentWindow).finally(() => {
if (Cu.isDeadWrapper(element)) {
// Focus element is removed or document is navigated to new page.
return;
}
const focusedElement =
Services.focus.focusedElement ||
element.ownerDocument?.activeElement;
if (element == focusedElement) {
this.onFocus(focusedElement);
}
});
break;
}
case "focusout": {

Просмотреть файл

@ -6,6 +6,7 @@ package org.mozilla.geckoview.test
import androidx.test.filters.MediumTest
import android.util.SparseArray
import android.view.KeyEvent
import android.view.View
import org.hamcrest.Matchers.*
import org.junit.Test
@ -13,6 +14,7 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mozilla.geckoview.Autofill
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.TextInputDelegate
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.*
@RunWith(Parameterized::class)
@ -510,4 +512,26 @@ class AutofillDelegateTest : BaseSessionTest() {
assertThat("autofill hint count",
checkAutofillChild(root), equalTo(6))
}
@WithDisplay(width = 100, height = 100)
@Test fun autofillWaitForKeyboard() {
// Wait for the accessibility nodes to populate.
mainSession.loadUri(pageUrl)
mainSession.waitForPageStop()
mainSession.pressKey(KeyEvent.KEYCODE_CTRL_LEFT)
mainSession.evaluateJS("document.querySelector('#pass2').focus()")
sessionRule.waitUntilCalled(object : Autofill.Delegate, TextInputDelegate {
@AssertCalled(order = [2])
override fun onNodeFocus(session: GeckoSession,
node: Autofill.Node,
data: Autofill.NodeData) {
assertThat("ID should be valid", node, notNullValue())
}
@AssertCalled(order = [1])
override fun showSoftInput(session: GeckoSession) {}
})
}
}

Просмотреть файл

@ -7,6 +7,9 @@ const { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
const { Log } = ChromeUtils.import("resource://gre/modules/Log.jsm");
const { clearTimeout, setTimeout } = ChromeUtils.import(
"resource://gre/modules/Timer.jsm"
);
const lazy = {};
@ -325,6 +328,54 @@ var GeckoViewUtils = {
return null;
},
/**
* Return promise for waiting for finishing PanZoomState.
*
* @param aWindow a DOM window.
* @return promise
*/
waitForPanZoomState(aWindow) {
return new Promise((resolve, reject) => {
if (
!aWindow?.windowUtils.asyncPanZoomEnabled ||
!Services.prefs.getBoolPref("apz.zoom-to-focused-input.enabled")
) {
// No zoomToFocusedInput.
resolve();
return;
}
let timerId = 0;
const panZoomState = (aSubject, aTopic, aData) => {
if (timerId != 0) {
// aWindow may be dead object now.
try {
clearTimeout(timerId);
} catch (e) {}
timerId = 0;
}
if (aData === "NOTHING") {
Services.obs.removeObserver(panZoomState, "PanZoom:StateChange");
resolve();
}
};
Services.obs.addObserver(panZoomState, "PanZoom:StateChange");
// "GeckoView:ZoomToInput" has the timeout as 500ms when window isn't
// resized (it means on-screen-keyboard is already shown).
// So after up to 500ms, APZ event is sent. So we need to wait for more
// 500ms.
timerId = setTimeout(() => {
// PanZoom state isn't changed. zoomToFocusedInput will return error.
Services.obs.removeObserver(panZoomState, "PanZoom:StateChange");
reject();
}, 600);
});
},
/**
* Add logging functions to the specified scope that forward to the given
* Log.jsm logger. Currently "debug" and "warn" functions are supported. To