--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -76,16 +76,18 @@ AccessibleCaretManager::sCaretsExtendedV
/*static*/ bool
AccessibleCaretManager::sCaretsAlwaysTilt = false;
/*static*/ bool
AccessibleCaretManager::sCaretsScriptUpdates = false;
/*static*/ bool
AccessibleCaretManager::sCaretsAllowDraggingAcrossOtherCaret = true;
/*static*/ bool
AccessibleCaretManager::sHapticFeedback = false;
+/*static*/ bool
+AccessibleCaretManager::sExtendSelectionForPhoneNumber = false;
AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
: mPresShell(aPresShell)
{
if (!mPresShell) {
return;
}
@@ -105,16 +107,18 @@ AccessibleCaretManager::AccessibleCaretM
Preferences::AddBoolVarCache(&sCaretsAlwaysTilt,
"layout.accessiblecaret.always_tilt");
Preferences::AddBoolVarCache(&sCaretsScriptUpdates,
"layout.accessiblecaret.allow_script_change_updates");
Preferences::AddBoolVarCache(&sCaretsAllowDraggingAcrossOtherCaret,
"layout.accessiblecaret.allow_dragging_across_other_caret", true);
Preferences::AddBoolVarCache(&sHapticFeedback,
"layout.accessiblecaret.hapticfeedback");
+ Preferences::AddBoolVarCache(&sExtendSelectionForPhoneNumber,
+ "layout.accessiblecaret.extend_selection_for_phone_number");
addedPrefs = true;
}
}
AccessibleCaretManager::~AccessibleCaretManager()
{
}
@@ -815,16 +819,21 @@ AccessibleCaretManager::SelectWord(nsIFr
SetSelectionDragState(true);
nsFrame* frame = static_cast<nsFrame*>(aFrame);
nsresult rs = frame->SelectByTypeAtPoint(mPresShell->GetPresContext(), aPoint,
eSelectWord, eSelectWord, 0);
SetSelectionDragState(false);
ClearMaintainedSelection();
+ // Smart-select phone numbers if possible.
+ if (sExtendSelectionForPhoneNumber) {
+ SelectMoreIfPhoneNumber();
+ }
+
return rs;
}
void
AccessibleCaretManager::SetSelectionDragState(bool aState) const
{
RefPtr<nsFrameSelection> fs = GetFrameSelection();
if (fs) {
@@ -837,16 +846,63 @@ AccessibleCaretManager::SetSelectionDrag
nsIDocument* doc = mPresShell->GetDocument();
MOZ_ASSERT(doc);
nsIWidget* widget = nsContentUtils::WidgetForDocument(doc);
static_cast<nsWindow*>(widget)->SetSelectionDragState(aState);
#endif
}
void
+AccessibleCaretManager::SelectMoreIfPhoneNumber() const
+{
+ SetSelectionDirection(eDirNext);
+ ExtendPhoneNumberSelection(NS_LITERAL_STRING("forward"));
+
+ SetSelectionDirection(eDirPrevious);
+ ExtendPhoneNumberSelection(NS_LITERAL_STRING("backward"));
+}
+
+void
+AccessibleCaretManager::ExtendPhoneNumberSelection(const nsAString& aDirection) const
+{
+ nsIDocument* doc = mPresShell->GetDocument();
+
+ // Extend the phone number selection until we find a boundary.
+ Selection* selection = GetSelection();
+
+ while (selection) {
+ // Save current Focus position, and extend the selection one char.
+ nsINode* focusNode = selection->GetFocusNode();
+ uint32_t focusOffset = selection->FocusOffset();
+ selection->Modify(NS_LITERAL_STRING("extend"),
+ aDirection,
+ NS_LITERAL_STRING("character"));
+
+ // If the selection didn't change, (can't extend further), we're done.
+ if (selection->GetFocusNode() == focusNode &&
+ selection->FocusOffset() == focusOffset) {
+ return;
+ }
+
+ // If the changed selection isn't a valid phone number, we're done.
+ nsAutoString selectedText;
+ selection->Stringify(selectedText);
+ nsAutoString phoneRegex(NS_LITERAL_STRING("(^\\+)?[0-9\\s,\\-.()*#pw]{1,30}$"));
+
+ if (!nsContentUtils::IsPatternMatching(selectedText, phoneRegex, doc)) {
+ // Backout the undesired selection extend, (collapse to original
+ // Anchor, extend to original Focus), before exit.
+ selection->Collapse(selection->GetAnchorNode(), selection->AnchorOffset());
+ selection->Extend(focusNode, focusOffset);
+ return;
+ }
+ }
+}
+
+void
AccessibleCaretManager::SetSelectionDirection(nsDirection aDir) const
{
Selection* selection = GetSelection();
if (selection) {
selection->AdjustAnchorFocusForMultiRange(aDir);
}
}
--- a/mobile/android/tests/browser/robocop/testAccessibleCarets.js
+++ b/mobile/android/tests/browser/robocop/testAccessibleCarets.js
@@ -55,43 +55,43 @@ function isInputOrTextarea(element) {
*/
function elementSelection(element) {
return (isInputOrTextarea(element)) ?
element.editor.selection :
element.ownerDocument.defaultView.getSelection();
}
/**
- * Select the first character of a target element, w/o affecting focus.
+ * Select the requested character of a target element, w/o affecting focus.
*/
-function selectElementFirstChar(doc, element) {
+function selectElementChar(doc, element, char) {
if (isInputOrTextarea(element)) {
- element.setSelectionRange(0, 1);
+ element.setSelectionRange(char, char + 1);
return;
}
// Simple test cases designed firstChild == #text node.
let range = doc.createRange();
- range.setStart(element.firstChild, 0);
- range.setEnd(element.firstChild, 1);
+ range.setStart(element.firstChild, char);
+ range.setEnd(element.firstChild, char + 1);
let selection = elementSelection(element);
selection.removeAllRanges();
selection.addRange(range);
}
/**
- * Get longpress point. Determine the midpoint in the first character of
+ * Get longpress point. Determine the midpoint in the requested character of
* the content in the element. X will be midpoint from left to right.
* Y will be 1/3 of the height up from the bottom to account for both
* LTR and smaller RTL characters. ie: |X| vs. |א|
*/
-function getFirstCharPressPoint(doc, element, expected) {
+function getCharPressPoint(doc, element, char, expected) {
// Select the first char in the element.
- selectElementFirstChar(doc, element);
+ selectElementChar(doc, element, char);
// Reality check selected char to expected.
let selection = elementSelection(element);
is(selection.toString(), expected, "Selected char should match expected char.");
// Return a point where long press should select entire word.
let rect = selection.getRangeAt(0).getBoundingClientRect();
let r = new Point(rect.left + (rect.width / 2), rect.bottom - (rect.height / 3));
@@ -157,27 +157,32 @@ add_task(function* testAccessibleCarets(
let i_LTR_elem = doc.getElementById("LTRinput");
let ta_LTR_elem = doc.getElementById("LTRtextarea");
let ce_RTL_elem = doc.getElementById("RTLcontenteditable");
let tc_RTL_elem = doc.getElementById("RTLtextContent");
let i_RTL_elem = doc.getElementById("RTLinput");
let ta_RTL_elem = doc.getElementById("RTLtextarea");
- // Locate longpress midpoints for test elements, ensure expactations.
- let ce_LTR_midPoint = getFirstCharPressPoint(doc, ce_LTR_elem, "F");
- let tc_LTR_midPoint = getFirstCharPressPoint(doc, tc_LTR_elem, "O");
- let i_LTR_midPoint = getFirstCharPressPoint(doc, i_LTR_elem, "T");
- let ta_LTR_midPoint = getFirstCharPressPoint(doc, ta_LTR_elem, "W");
+ let ip_LTR_elem = doc.getElementById("LTRphone");
+ let ip_RTL_elem = doc.getElementById("RTLphone");
- let ce_RTL_midPoint = getFirstCharPressPoint(doc, ce_RTL_elem, "א");
- let tc_RTL_midPoint = getFirstCharPressPoint(doc, tc_RTL_elem, "ת");
- let i_RTL_midPoint = getFirstCharPressPoint(doc, i_RTL_elem, "ל");
- let ta_RTL_midPoint = getFirstCharPressPoint(doc, ta_RTL_elem, "ה");
+ // Locate longpress midpoints for test elements, ensure expactations.
+ let ce_LTR_midPoint = getCharPressPoint(doc, ce_LTR_elem, 0, "F");
+ let tc_LTR_midPoint = getCharPressPoint(doc, tc_LTR_elem, 0, "O");
+ let i_LTR_midPoint = getCharPressPoint(doc, i_LTR_elem, 0, "T");
+ let ta_LTR_midPoint = getCharPressPoint(doc, ta_LTR_elem, 0, "W");
+ let ce_RTL_midPoint = getCharPressPoint(doc, ce_RTL_elem, 0, "א");
+ let tc_RTL_midPoint = getCharPressPoint(doc, tc_RTL_elem, 0, "ת");
+ let i_RTL_midPoint = getCharPressPoint(doc, i_RTL_elem, 0, "ל");
+ let ta_RTL_midPoint = getCharPressPoint(doc, ta_RTL_elem, 0, "ה");
+
+ let ip_LTR_midPoint = getCharPressPoint(doc, ip_LTR_elem, 8, "2");
+ let ip_RTL_midPoint = getCharPressPoint(doc, ip_RTL_elem, 9, "2");
// Longpress various LTR content elements. Test focused element against
// expected, and selected text against expected.
let result = getLongPressResult(browser, ce_LTR_midPoint);
is(result.focusedElement, ce_LTR_elem, "Focused element should match expected.");
is(result.text, "Find", "Selected text should match expected text.");
result = getLongPressResult(browser, tc_LTR_midPoint);
@@ -187,16 +192,23 @@ add_task(function* testAccessibleCarets(
result = getLongPressResult(browser, i_LTR_midPoint);
is(result.focusedElement, i_LTR_elem, "Focused element should match expected.");
is(result.text, "Type", "Selected text should match expected text.");
result = getLongPressResult(browser, ta_LTR_midPoint);
is(result.focusedElement, ta_LTR_elem, "Focused element should match expected.");
is(result.text, "Words", "Selected text should match expected text.");
+ result = getLongPressResult(browser, ip_LTR_midPoint);
+ is(result.focusedElement, ip_LTR_elem, "Focused element should match expected.");
+ is(result.text, "09876543210 .-.)(wp#*103410341",
+ "Selected phone number should match expected text.");
+ is(result.text.length, 30,
+ "Selected phone number length should match expected maximum.");
+
// Longpress various RTL content elements. Test focused element against
// expected, and selected text against expected.
result = getLongPressResult(browser, ce_RTL_midPoint);
is(result.focusedElement, ce_RTL_elem, "Focused element should match expected.");
is(result.text, "איפה", "Selected text should match expected text.");
result = getLongPressResult(browser, tc_RTL_midPoint);
is(result.focusedElement, null, "No focused element is expected.");
@@ -205,12 +217,17 @@ add_task(function* testAccessibleCarets(
result = getLongPressResult(browser, i_RTL_midPoint);
is(result.focusedElement, i_RTL_elem, "Focused element should match expected.");
is(result.text, "לרוץ", "Selected text should match expected text.");
result = getLongPressResult(browser, ta_RTL_midPoint);
is(result.focusedElement, ta_RTL_elem, "Focused element should match expected.");
is(result.text, "הספר", "Selected text should match expected text.");
+ result = getLongPressResult(browser, ip_RTL_midPoint);
+ is(result.focusedElement, ip_RTL_elem, "Focused element should match expected.");
+ is(result.text, "+972 3 7347514 ",
+ "Selected phone number should match expected text.");
+
ok(true, "Finished all tests.");
});
run_next_test();