mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-13 13:25:37 +00:00
Bug 1465539 - Introduce accessibility test for editable node. r=jchen
--HG-- rename : mobile/android/geckoview/src/androidTest/assets/www/selectionAction.html => mobile/android/geckoview/src/androidTest/assets/www/inputs.html
This commit is contained in:
parent
6e9c4e588f
commit
04cfa9d84b
@ -1,5 +1,5 @@
|
||||
<html>
|
||||
<head><title>SelectionActionDelegate Test</title></head>
|
||||
<head><title>Inputs</title></head>
|
||||
<body>
|
||||
<div id="text">lorem</div>
|
||||
<input id="input" value="ipsum">
|
@ -0,0 +1,143 @@
|
||||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.geckoview.test
|
||||
|
||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
|
||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
|
||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
|
||||
|
||||
import android.support.test.filters.MediumTest
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import android.view.accessibility.AccessibilityNodeProvider
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.accessibility.AccessibilityRecord
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
import android.widget.FrameLayout
|
||||
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.junit.Test
|
||||
import org.junit.Before
|
||||
import org.junit.After
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@MediumTest
|
||||
@WithDisplay(width = 480, height = 640)
|
||||
@WithDevToolsAPI
|
||||
class AccessibilityTest : BaseSessionTest() {
|
||||
lateinit var view: View
|
||||
val provider: AccessibilityNodeProvider get() = view.getAccessibilityNodeProvider()
|
||||
|
||||
// Given a child ID, return the virtual descendent ID.
|
||||
private fun getVirtualDescendantId(childId: Long): Int {
|
||||
try {
|
||||
var getVirtualDescendantIdMethod =
|
||||
AccessibilityNodeInfo::class.java.getMethod("getVirtualDescendantId", Long::class.java)
|
||||
return getVirtualDescendantIdMethod.invoke(null, childId) as Int
|
||||
} catch (ex: Exception) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the virtual descendent ID of the event's source.
|
||||
private fun getSourceId(event: AccessibilityEvent): Int {
|
||||
try {
|
||||
var getSourceIdMethod =
|
||||
AccessibilityRecord::class.java.getMethod("getSourceNodeId")
|
||||
return getVirtualDescendantId(getSourceIdMethod.invoke(event) as Long)
|
||||
} catch (ex: Exception) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private interface EventDelegate {
|
||||
fun onAccessibilityFocused(event: AccessibilityEvent) { }
|
||||
fun onFocused(event: AccessibilityEvent) { }
|
||||
}
|
||||
|
||||
@Before fun setup() {
|
||||
// We initialize a view with a parent and grandparent so that the
|
||||
// accessibility events propogate up at least to the parent.
|
||||
view = FrameLayout(InstrumentationRegistry.getTargetContext())
|
||||
FrameLayout(InstrumentationRegistry.getTargetContext()).addView(view)
|
||||
FrameLayout(InstrumentationRegistry.getTargetContext()).addView(view.parent as View)
|
||||
|
||||
// Force on accessibility and assign the session's accessibility
|
||||
// object a view.
|
||||
sessionRule.setPrefsUntilTestEnd(mapOf("accessibility.force_disabled" to -1))
|
||||
mainSession.accessibility.view = view
|
||||
|
||||
// Set up an external delegate that will intercept accessibility events.
|
||||
sessionRule.addExternalDelegateUntilTestEnd(
|
||||
EventDelegate::class,
|
||||
{ newDelegate -> (view.parent as View).setAccessibilityDelegate(object : View.AccessibilityDelegate() {
|
||||
override fun onRequestSendAccessibilityEvent(host: ViewGroup, child: View, event: AccessibilityEvent): Boolean {
|
||||
when (event.getEventType()) {
|
||||
AccessibilityEvent.TYPE_VIEW_FOCUSED -> newDelegate.onFocused(event)
|
||||
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED -> newDelegate.onAccessibilityFocused(event)
|
||||
else -> {}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}) },
|
||||
{ (view.parent as View).setAccessibilityDelegate(null) },
|
||||
object : EventDelegate { })
|
||||
}
|
||||
|
||||
@After fun teardown() {
|
||||
sessionRule.session.accessibility.view = null
|
||||
}
|
||||
|
||||
@Test fun testRootNode() {
|
||||
assertThat("provider is not null", provider, notNullValue())
|
||||
var node = provider.createAccessibilityNodeInfo(AccessibilityNodeProvider.HOST_VIEW_ID)
|
||||
assertThat("Root node should have WebView class name",
|
||||
node.getClassName().toString(), equalTo("android.webkit.WebView"))
|
||||
}
|
||||
|
||||
@Test fun testPageLoad() {
|
||||
sessionRule.session.loadTestPath(INPUTS_PATH)
|
||||
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onFocused(event: AccessibilityEvent) { }
|
||||
})
|
||||
}
|
||||
|
||||
@Test fun testAccessibilityFocus() {
|
||||
var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
|
||||
sessionRule.session.loadTestPath(INPUTS_PATH)
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
|
||||
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
|
||||
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAccessibilityFocused(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
var node = provider.createAccessibilityNodeInfo(nodeId)
|
||||
assertThat("Text node should not be focusable", node.isFocusable(), equalTo(false))
|
||||
}
|
||||
})
|
||||
|
||||
provider.performAction(nodeId,
|
||||
AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
|
||||
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAccessibilityFocused(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
var node = provider.createAccessibilityNodeInfo(nodeId)
|
||||
assertThat("Entry node should be focusable", node.isFocusable(), equalTo(true))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -30,10 +30,10 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
|
||||
const val DOWNLOAD_HTML_PATH = "/assets/www/download.html"
|
||||
const val HELLO_HTML_PATH = "/assets/www/hello.html"
|
||||
const val HELLO2_HTML_PATH = "/assets/www/hello2.html"
|
||||
const val INPUTS_PATH = "/assets/www/inputs.html"
|
||||
const val INVALID_URI = "http://www.test.invalid/"
|
||||
const val NEW_SESSION_HTML_PATH = "/assets/www/newSession.html"
|
||||
const val NEW_SESSION_CHILD_HTML_PATH = "/assets/www/newSession_child.html"
|
||||
const val SELECTION_ACTION_PATH = "/assets/www/selectionAction.html"
|
||||
const val TITLE_CHANGE_HTML_PATH = "/assets/www/titleChange.html"
|
||||
const val TRACKERS_PATH = "/assets/www/trackers.html"
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ class SelectionActionDelegateTest : BaseSessionTest() {
|
||||
sessionRule.setPrefsUntilTestEnd(mapOf(
|
||||
"layout.accessiblecaret.enabled_on_touch" to true))
|
||||
|
||||
mainSession.loadTestPath(SELECTION_ACTION_PATH)
|
||||
mainSession.loadTestPath(INPUTS_PATH)
|
||||
mainSession.waitForPageStop()
|
||||
|
||||
content.focus()
|
||||
|
@ -50,7 +50,7 @@ class TextInputDelegateTest : BaseSessionTest() {
|
||||
|
||||
@Test fun restartInput() {
|
||||
// Check that restartInput is called on focus and blur.
|
||||
mainSession.loadTestPath(SELECTION_ACTION_PATH)
|
||||
mainSession.loadTestPath(INPUTS_PATH)
|
||||
mainSession.waitForPageStop()
|
||||
|
||||
mainSession.evaluateJS("$('$id').focus()")
|
||||
@ -85,7 +85,7 @@ class TextInputDelegateTest : BaseSessionTest() {
|
||||
// Our user action trick doesn't work for design-mode, so we can't test that here.
|
||||
assumeThat("Not in designmode", id, not(equalTo("#designmode")))
|
||||
|
||||
mainSession.loadTestPath(SELECTION_ACTION_PATH)
|
||||
mainSession.loadTestPath(INPUTS_PATH)
|
||||
mainSession.waitForPageStop()
|
||||
|
||||
// Focus the input once here and once below, but we should only get a
|
||||
@ -119,7 +119,7 @@ class TextInputDelegateTest : BaseSessionTest() {
|
||||
// Our user action trick doesn't work for design-mode, so we can't test that here.
|
||||
assumeThat("Not in designmode", id, not(equalTo("#designmode")))
|
||||
|
||||
mainSession.loadTestPath(SELECTION_ACTION_PATH)
|
||||
mainSession.loadTestPath(INPUTS_PATH)
|
||||
mainSession.waitForPageStop()
|
||||
|
||||
// Simulate a user action so we're allowed to show/hide the keyboard.
|
||||
@ -154,7 +154,7 @@ class TextInputDelegateTest : BaseSessionTest() {
|
||||
// Our user action trick doesn't work for design-mode, so we can't test that here.
|
||||
assumeThat("Not in designmode", id, not(equalTo("#designmode")))
|
||||
|
||||
mainSession.loadTestPath(SELECTION_ACTION_PATH)
|
||||
mainSession.loadTestPath(INPUTS_PATH)
|
||||
mainSession.waitForPageStop()
|
||||
|
||||
// Simulate a user action so we're allowed to show/hide the keyboard.
|
||||
|
@ -7,6 +7,7 @@ package org.mozilla.geckoview;
|
||||
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
@ -109,7 +110,10 @@ public class SessionAccessibility {
|
||||
// as a child. It is a source for events,
|
||||
// but not a member of the tree you
|
||||
// can get to by traversing down.
|
||||
onInitializeAccessibilityNodeInfo(mView, info);
|
||||
if (mView.getDisplay() != null) {
|
||||
// When running junit tests we don't have a display
|
||||
onInitializeAccessibilityNodeInfo(mView, info);
|
||||
}
|
||||
info.setClassName("android.webkit.WebView"); // TODO: WTF
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
Bundle bundle = info.getExtras();
|
||||
@ -244,19 +248,14 @@ public class SessionAccessibility {
|
||||
});
|
||||
}
|
||||
|
||||
public static class Settings {
|
||||
private static class Settings {
|
||||
private static final Settings INSTANCE = new Settings();
|
||||
private boolean mEnabled;
|
||||
private static final String FORCE_ACCESSIBILITY_PREF = "accessibility.force_disabled";
|
||||
|
||||
private volatile boolean mEnabled;
|
||||
/* package */ volatile boolean mForceEnabled;
|
||||
|
||||
public Settings() {
|
||||
EventDispatcher.getInstance().registerUiThreadListener(new BundleEventListener() {
|
||||
@Override
|
||||
public void handleMessage(final String event, final GeckoBundle message,
|
||||
final EventCallback callback) {
|
||||
updateAccessibilitySettings();
|
||||
}
|
||||
}, "GeckoView:AccessibilityReady", null);
|
||||
|
||||
final Context context = GeckoAppShell.getApplicationContext();
|
||||
AccessibilityManager accessibilityManager =
|
||||
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
@ -283,6 +282,17 @@ public class SessionAccessibility {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
PrefsHelper.PrefHandler prefHandler = new PrefsHelper.PrefHandlerBase() {
|
||||
@Override
|
||||
public void prefValue(String pref, int value) {
|
||||
if (pref.equals(FORCE_ACCESSIBILITY_PREF)) {
|
||||
mForceEnabled = value < 0;
|
||||
dispatch();
|
||||
}
|
||||
}
|
||||
};
|
||||
PrefsHelper.addObserver(new String[]{ FORCE_ACCESSIBILITY_PREF }, prefHandler);
|
||||
}
|
||||
|
||||
public static Settings getInstance() {
|
||||
@ -290,26 +300,20 @@ public class SessionAccessibility {
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return INSTANCE.mEnabled;
|
||||
return INSTANCE.mEnabled || INSTANCE.mForceEnabled;
|
||||
}
|
||||
|
||||
private void updateAccessibilitySettings() {
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final AccessibilityManager accessibilityManager = (AccessibilityManager)
|
||||
GeckoAppShell.getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
mEnabled = accessibilityManager.isEnabled() &&
|
||||
accessibilityManager.isTouchExplorationEnabled();
|
||||
final AccessibilityManager accessibilityManager = (AccessibilityManager)
|
||||
GeckoAppShell.getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
mEnabled = accessibilityManager.isEnabled() && accessibilityManager.isTouchExplorationEnabled();
|
||||
|
||||
dispatch();
|
||||
}
|
||||
});
|
||||
dispatch();
|
||||
}
|
||||
|
||||
private void dispatch() {
|
||||
final GeckoBundle ret = new GeckoBundle(1);
|
||||
ret.putBoolean("enabled", mEnabled);
|
||||
ret.putBoolean("enabled", mEnabled || mForceEnabled);
|
||||
// "GeckoView:AccessibilitySettings" is dispatched to the Gecko thread.
|
||||
EventDispatcher.getInstance().dispatch("GeckoView:AccessibilitySettings", ret);
|
||||
// "GeckoView:AccessibilityEnabled" is dispatched to the UI thread.
|
||||
@ -358,7 +362,9 @@ public class SessionAccessibility {
|
||||
node.setPassword(message.getBoolean("password"));
|
||||
node.setFocusable(message.getBoolean("focusable"));
|
||||
node.setFocused(message.getBoolean("focused"));
|
||||
node.setEditable(message.getBoolean("editable"));
|
||||
if (Build.VERSION.SDK_INT >= 18) {
|
||||
node.setEditable(message.getBoolean("editable"));
|
||||
}
|
||||
|
||||
final String[] textArray = message.getStringArray("text");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@ -374,9 +380,6 @@ public class SessionAccessibility {
|
||||
if (message.getBoolean("clickable")) {
|
||||
node.setClickable(true);
|
||||
node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||
} else {
|
||||
node.setClickable(false);
|
||||
node.removeAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||
}
|
||||
|
||||
final GeckoBundle bounds = message.getBundle("bounds");
|
||||
@ -427,9 +430,7 @@ public class SessionAccessibility {
|
||||
eventType == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER)) {
|
||||
// In Jelly Bean we populate an AccessibilityNodeInfo with the minimal amount of data to have
|
||||
// it work with TalkBack.
|
||||
if (mVirtualContentNode == null) {
|
||||
mVirtualContentNode = AccessibilityNodeInfo.obtain(mView, eventSource);
|
||||
}
|
||||
mVirtualContentNode = AccessibilityNodeInfo.obtain(mView, eventSource);
|
||||
populateNodeInfoFromJSON(mVirtualContentNode, message);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
|
||||
class GeckoViewAccessibility extends GeckoViewModule {
|
||||
onInit() {
|
||||
EventDispatcher.instance.dispatch("GeckoView:AccessibilityReady");
|
||||
EventDispatcher.instance.registerListener((aEvent, aData, aCallback) => {
|
||||
if (aData.enabled) {
|
||||
AccessFu.enable();
|
||||
|
Loading…
Reference in New Issue
Block a user