Bug 1499430 - Make PresShell::PageMove() use result of nsFrameSelection::GetFrameToPageSelect() when PresShell::GetScrollableFrameToScroll() returns nullptr r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 10 Jan 2019 05:02:56 +0000
changeset 453210 87e21b003b079e91d7e833853116a343fcae8cee
parent 453209 460f0359741bd0026b7da0426836e1c0084a5be3
child 453211 2f4920fa4ac7c36c79057fa4c1a7e5a212d025aa
push id35349
push userbtara@mozilla.com
push dateThu, 10 Jan 2019 17:19:27 +0000
treeherdermozilla-central@a51746f37520 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1499430
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1499430 - Make PresShell::PageMove() use result of nsFrameSelection::GetFrameToPageSelect() when PresShell::GetScrollableFrameToScroll() returns nullptr r=smaug If there is no scrollable frame, PresShell::GetScrollableFrameToScroll() returns nullptr. However, even when we don't expand selection, we need to move caret in current selection root. Therefore, it should call nsFrameSelection::CommonPageMove() with the result of nsFrameSelection::GetFrameToPageSelect() to move caret. Differential Revision: https://phabricator.services.mozilla.com/D16020
layout/base/PresShell.cpp
layout/base/tests/mochitest.ini
layout/base/tests/test_expanding_selection_per_page.html
layout/base/tests/test_moving_and_expanding_selection_per_page.html
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -2261,24 +2261,26 @@ PresShell::LineMove(bool aForward, bool 
 NS_IMETHODIMP
 PresShell::IntraLineMove(bool aForward, bool aExtend) {
   RefPtr<nsFrameSelection> frameSelection = mSelection;
   return frameSelection->IntraLineMove(aForward, aExtend);
 }
 
 NS_IMETHODIMP
 PresShell::PageMove(bool aForward, bool aExtend) {
-  nsIFrame* frame;
+  nsIFrame* frame = nullptr;
   if (!aExtend) {
     frame = do_QueryFrame(GetScrollableFrameToScroll(nsIPresShell::eVertical));
-  } else {
-    frame = mSelection->GetFrameToPageSelect();
+    // If there is no scrollable frame, get the frame to move caret instead.
   }
   if (!frame) {
-    return NS_OK;
+    frame = mSelection->GetFrameToPageSelect();
+    if (!frame) {
+      return NS_OK;
+    }
   }
   RefPtr<nsFrameSelection> frameSelection = mSelection;
   frameSelection->CommonPageMove(aForward, aExtend, frame);
   // After ScrollSelectionIntoView(), the pending notifications might be
   // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
   return ScrollSelectionIntoView(
       nsISelectionController::SELECTION_NORMAL,
       nsISelectionController::SELECTION_FOCUS_REGION,
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -146,28 +146,28 @@ support-files = bug1226904.html
 [test_bug1246622.html]
 [test_bug1278021.html]
 [test_emulateMedium.html]
 [test_event_target_iframe_oop.html]
 skip-if = e10s # bug 1020135, nested oop iframes not supported
 support-files = bug921928_event_target_iframe_apps_oop.html
 [test_event_target_radius.html]
 skip-if = toolkit == 'android' # Bug 1355836
-[test_expanding_selection_per_page.html]
-support-files = window_empty_document.html
 [test_flush_on_paint.html]
 skip-if = true # Bug 688128
 [test_frame_reconstruction_for_pseudo_elements.html]
 [test_frame_reconstruction_for_svg_transforms.html]
 [test_frame_reconstruction_scroll_restore.html]
 [test_getBoxQuads_convertPointRectQuad.html]
 support-files =
   file_getBoxQuads_convertPointRectQuad_frame1.html
   file_getBoxQuads_convertPointRectQuad_frame2.html
 [test_getClientRects_emptytext.html]
+[test_moving_and_expanding_selection_per_page.html]
+support-files = window_empty_document.html
 [test_mozPaintCount.html]
 skip-if = toolkit == 'android' # android: Requires plugin support
 [test_preserve3d_sorting_hit_testing.html]
 support-files = preserve3d_sorting_hit_testing_iframe.html
 [test_preserve3d_sorting_hit_testing2.html]
 support-files = preserve3d_sorting_hit_testing2_iframe.html
 [test_reftests_with_caret.html]
 skip-if = toolkit == 'android' # Bug 1355842
rename from layout/base/tests/test_expanding_selection_per_page.html
rename to layout/base/tests/test_moving_and_expanding_selection_per_page.html
--- a/layout/base/tests/test_expanding_selection_per_page.html
+++ b/layout/base/tests/test_moving_and_expanding_selection_per_page.html
@@ -18,24 +18,21 @@ addLoadEvent(() => {
 async function doTests(aWindow) {
   const IS_WIN = navigator.platform.includes("Win");
   // On macOS and Linux, Shift + PageUp/PageDown requires native event to
   // resolve default action of PageDown and PageUp. Although macOS widget has
   // nsIWidget::AttachNativeKeyEvent(), we cannot use synthesizeKey() for the
   // following tests.  So, use nsISelectionController.pageMove() instead on
   // non-Windows platforms.
   const kUseKeyboardEvent = IS_WIN;
-  let selectionController;
-  if (!kUseKeyboardEvent) {
-    selectionController = SpecialPowers.wrap(aWindow)
-                                       .docShell
-                                       .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
-                                       .getInterface(SpecialPowers.Ci.nsISelectionDisplay)
-                                       .QueryInterface(SpecialPowers.Ci.nsISelectionController);
-  }
+  let selectionController = SpecialPowers.wrap(aWindow)
+                                         .docShell
+                                         .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+                                         .getInterface(SpecialPowers.Ci.nsISelectionDisplay)
+                                         .QueryInterface(SpecialPowers.Ci.nsISelectionController);
   // On Windows, per-page selection to start or end expands selection to same
   // column of first or last line.  On the other platforms, it expands selection
   // to start or end of first or last line.
   const kSelectToStartOrEnd = !IS_WIN;
 
   await SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false]]});
   await SimpleTest.promiseFocus(aWindow);
 
@@ -54,258 +51,307 @@ async function doTests(aWindow) {
         return `text node in ${getElementDescription(aNode.parentElement)}`;
       case aNode.ELEMENT_NODE:
         return getElementDescription(aNode);
       default:
         return "unknown node";
     }
   }
 
-  function doSelectPageDown() {
-    if (kUseKeyboardEvent) {
-      synthesizeKey("KEY_PageDown", {shiftKey: true}, aWindow);
+  function doTest(aExpandSelection) {
+    // Note that when neither editor has focus nor in caret mode, key navigation
+    // does not call nsISelectionController::PageMove().  Therefore, in such
+    // cases, you need to call doPageDown() and doPageUp() with setting true
+    // to aUseSelectionController.
+    function doPageDown(aUseSelectionController) {
+      if (kUseKeyboardEvent && !aUseSelectionController) {
+        synthesizeKey("KEY_PageDown", {shiftKey: aExpandSelection}, aWindow);
+      } else {
+        selectionController.pageMove(true, aExpandSelection);
+      }
+    }
+
+    function doPageUp(aUseSelectionController) {
+      if (kUseKeyboardEvent && !aUseSelectionController) {
+        synthesizeKey("KEY_PageUp", {shiftKey: aExpandSelection}, aWindow);
+      } else {
+        selectionController.pageMove(false, aExpandSelection);
+      }
+    }
+
+    let doc = aWindow.document;
+    let body = doc.body;
+    let selection = doc.getSelection();
+    let container;
+
+    body.innerHTML = '<span id="s1">first line</span><br>' +
+                     '<span id="s2">second line</span><br>' +
+                     '<span id="s3">last line</span>';
+    container = doc.documentElement;
+
+    let description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in non-scrollable body: `;
+    is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
+    selection.collapse(doc.getElementById("s1").firstChild, 3);
+    doPageDown(!aExpandSelection);
+    is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
+    let range = selection.getRangeAt(0);
+    if (aExpandSelection) {
+      is(range.startContainer, doc.getElementById("s1").firstChild,
+         `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
+      is(range.startOffset, 3,
+         `${description} selection should be expanded from the first line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+    is(range.endContainer, doc.getElementById("s3").firstChild,
+       `${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.endOffset, range.endContainer.length,
+         `${description} selection should be expanded to end of the last line`);
     } else {
-      selectionController.pageMove(true, true);
+      isfuzzy(range.endOffset, 3, 2,
+              `${description} selection should be expanded to around the last line's 3rd insertion point`);
+    }
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in non-scrollable body: `;
+    selection.collapse(doc.getElementById("s3").firstChild, 3);
+    doPageUp(!aExpandSelection);
+    is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
+    range = selection.getRangeAt(0);
+    is(range.startContainer, doc.getElementById("s1").firstChild,
+       `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.startOffset, 0,
+         `${description} selection should be expanded to start of the first line`);
+    } else {
+      isfuzzy(range.startOffset, 3, 2,
+              `${description} selection should be expanded to around the first line's 3rd insertion point`);
+    }
+    if (aExpandSelection) {
+      is(range.endContainer, doc.getElementById("s3").firstChild,
+         `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
+      is(range.endOffset, 3,
+         `${description} selection should be expanded from the last line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+
+    body.innerHTML = '<span id="s1">first line in the body</span>' +
+                       '<div id="d1" style="height: 2em; line-height: 1em; overflow: auto;">' +
+                         '<span id="s2">first line</span><br>' +
+                         '<span id="s3">second line</span><br>' +
+                         '<span id="s4">third line</span><br>' +
+                         '<span id="s5">last line</span>' +
+                       "</div>" +
+                     '<span id="s6">last line in the body</span>';
+    container = doc.getElementById("d1");
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in scrollable area in the body: `;
+    is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
+    selection.collapse(doc.getElementById("s2").firstChild, 3);
+    doPageDown(!aExpandSelection);
+    isnot(container.scrollTop, 0, description + "should be scrolled down");
+    range = selection.getRangeAt(0);
+    if (aExpandSelection) {
+      is(range.startContainer, doc.getElementById("s2").firstChild,
+         `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
+      is(range.startOffset, 3,
+         `${description} selection should be expanded from the first line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+    is(range.endContainer, doc.getElementById("s4").firstChild,
+       `${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
+    isfuzzy(range.endOffset, 3, 2,
+            `${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in scrollable area in the body: `;
+    selection.collapse(doc.getElementById("s4").firstChild, 3);
+    let previousScrollTop = container.scrollTop;
+    doPageUp(!aExpandSelection);
+    ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
+    range = selection.getRangeAt(0);
+    is(range.startContainer, doc.getElementById("s2").firstChild,
+       `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
+    isfuzzy(range.startOffset, 3, 2,
+            `${description} selection should be expanded to around the first line's 3rd insertion point`);
+    if (aExpandSelection) {
+      is(range.endContainer, doc.getElementById("s4").firstChild,
+         `${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
+      is(range.endOffset, 3,
+         `${description} selection should be expanded from the 3rd line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
     }
-  }
+
+    body.innerHTML = '<span id="s1">first line in the body</span>' +
+                       '<div id="d1" contenteditable style="height: 2em; line-height: 1em; overflow: auto;">' +
+                         '<span id="s2">first line</span><br>' +
+                         '<span id="s3">second line</span><br>' +
+                         '<span id="s4">third line</span><br>' +
+                         '<span id="s5">last line</span>' +
+                       "</div>" +
+                     '<span id="s6">last line in the body</span>';
+    container = doc.getElementById("d1");
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in scrollable editable div in the body: `;
+    is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
+    selection.collapse(doc.getElementById("s2").firstChild, 3);
+    doPageDown();
+    isnot(container.scrollTop, 0, description + "should be scrolled down");
+    range = selection.getRangeAt(0);
+    if (aExpandSelection) {
+      is(range.startContainer, doc.getElementById("s2").firstChild,
+         `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
+      is(range.startOffset, 3,
+         `${description} selection should be expanded from the first line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+    is(range.endContainer, doc.getElementById("s4").firstChild,
+       `${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
+    isfuzzy(range.endOffset, 3, 2,
+            `${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in scrollable editable div in the body: `;
+    selection.collapse(doc.getElementById("s4").firstChild, 3);
+    previousScrollTop = container.scrollTop;
+    doPageUp();
+    ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
+    range = selection.getRangeAt(0);
+    is(range.startContainer, doc.getElementById("s2").firstChild,
+       `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
+    isfuzzy(range.startOffset, 3, 2,
+            `${description} selection should be expanded to around the first line's 3rd insertion point`);
+    if (aExpandSelection) {
+      is(range.endContainer, doc.getElementById("s4").firstChild,
+         `${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
+      is(range.endOffset, 3,
+         `${description} selection should be expanded from the 3rd line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+
+    body.innerHTML = '<span id="s1">first line in the body</span>' +
+                       '<div id="d1" contenteditable>' +
+                         '<span id="s2">first line</span><br>' +
+                         '<span id="s3">second line</span><br>' +
+                         '<span id="s4">third line</span><br>' +
+                         '<span id="s5">last line</span>' +
+                       "</div>" +
+                     '<span id="s6">last line in the body</span>';
+    container = doc.getElementById("d1");
 
-  function doSelectPageUp() {
-    if (kUseKeyboardEvent) {
-      synthesizeKey("KEY_PageUp", {shiftKey: true}, aWindow);
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in non-scrollable editable div in the body: `;
+    is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
+    selection.collapse(doc.getElementById("s2").firstChild, 3);
+    doPageDown();
+    is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
+    range = selection.getRangeAt(0);
+    if (aExpandSelection) {
+      is(range.startContainer, doc.getElementById("s2").firstChild,
+         `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
+      is(range.startOffset, 3,
+         `${description} selection should be expanded from the first line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+    is(range.endContainer, doc.getElementById("s5").firstChild,
+       `${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.endOffset, range.endContainer.length,
+         `${description} selection should be expanded to end of the last line`);
+    } else {
+      isfuzzy(range.endOffset, 3, 2,
+              `${description} selection should be expanded to around the last line's 3rd insertion point`);
+    }
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in non-scrollable editable div in the body: `;
+    selection.collapse(doc.getElementById("s5").firstChild, 3);
+    doPageUp();
+    is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
+    range = selection.getRangeAt(0);
+    is(range.startContainer, doc.getElementById("s2").firstChild,
+       `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.startOffset, 0,
+         `${description} selection should be expanded to start of the first line`);
+    } else {
+      isfuzzy(range.startOffset, 3, 2,
+              `${description} selection should be expanded to around the first line's 3rd insertion point`);
+    }
+    if (aExpandSelection) {
+      is(range.endContainer, doc.getElementById("s5").firstChild,
+         `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
+      is(range.endOffset, 3,
+         `${description} selection should be expanded from the last line's 3rd insertion point`);
     } else {
-      selectionController.pageMove(false, true);
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+
+    body.innerHTML = '<span id="s1">first line in the body</span>' +
+                       '<div id="d1" contenteditable>' +
+                         '<span id="s2">first editable line</span><br>' +
+                         '<div id="d2" style="height: 3em; line-height: 1em; overflow: auto;">' +
+                           '<span id="s3">first line</span><br>' +
+                           '<span id="s4">second line</span>' +
+                         "</div>" +
+                         '<span id="s5">last editable line</span>' +
+                       "</div>" +
+                     '<span id="s6">last line in the body</span>';
+    container = doc.getElementById("d2");
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to forward in scrollable div (but not scrollable along y-axis) in the editable div: `;
+    is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
+    selection.collapse(doc.getElementById("s3").firstChild, 3);
+    doPageDown();
+    is(container.scrollTop, 0, description + "scrollable div in the editable div (but not scrollable along y-axis) shouldn't be scrollable");
+    range = selection.getRangeAt(0);
+    if (aExpandSelection) {
+      is(range.startContainer, doc.getElementById("s3").firstChild,
+         `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
+      is(range.startOffset, 3,
+         `${description} selection should be expanded from the first line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
+    }
+    is(range.endContainer, doc.getElementById("s5").firstChild,
+       `${description} selection should be expanded into the last editable line (got: ${getNodeDescription(range.endContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.endOffset, range.endContainer.length,
+         `${description} selection should be expanded to end of the last editable line`);
+    } else {
+      isfuzzy(range.endOffset, 3, 2,
+              `${description} selection should be expanded to around the last editable line's 3rd insertion point`);
+    }
+
+    description = `${aExpandSelection ? "Expanding selection" : "Moving caret"} to backward in scrollable div (but not scrollable along y-axis) in the editable div: `;
+    selection.collapse(doc.getElementById("s4").firstChild, 3);
+    doPageUp();
+    is(container.scrollTop, 0, description + "scrollable div (but not scrollable along y-axis) in the editable div shouldn't be scrollable");
+    range = selection.getRangeAt(0);
+    is(range.startContainer, doc.getElementById("s2").firstChild,
+       `${description} selection should be expanded into the first editable line (got: ${getNodeDescription(range.startContainer)})`);
+    if (kSelectToStartOrEnd) {
+      is(range.startOffset, 0,
+         `${description} selection should be expanded to start of the first editable line`);
+    } else {
+      isfuzzy(range.startOffset, 3, 2,
+              `${description} selection should be expanded to around the first editable line's 3rd insertion point`);
+    }
+    if (aExpandSelection) {
+      is(range.endContainer, doc.getElementById("s4").firstChild,
+         `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
+      is(range.endOffset, 3,
+         `${description} selection should be expanded from the last line's 3rd insertion point`);
+    } else {
+      ok(range.collapsed, `${description} selection should be collapsed`);
     }
   }
 
-  let doc = aWindow.document;
-  let body = doc.body;
-  let selection = doc.getSelection();
-  let container;
-
-  body.innerHTML = '<span id="s1">first line</span><br>' +
-                   '<span id="s2">second line</span><br>' +
-                   '<span id="s3">last line</span>';
-  container = doc.documentElement;
-
-  let description = "Expanding selection to forward in non-scrollable body: ";
-  is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
-  selection.collapse(doc.getElementById("s1").firstChild, 3);
-  doSelectPageDown();
-  is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
-  let range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s1").firstChild,
-     `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
-  is(range.startOffset, 3,
-     `${description} selection should be expanded from the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s3").firstChild,
-     `${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.endOffset, range.endContainer.length,
-       `${description} selection should be expanded to end of the last line`);
-  } else {
-    isfuzzy(range.endOffset, 3, 2,
-            `${description} selection should be expanded to around the last line's 3rd insertion point`);
-  }
-
-  description = "Expanding selection to backward in non-scrollable body: ";
-  selection.collapse(doc.getElementById("s3").firstChild, 3);
-  doSelectPageUp();
-  is(container.scrollTop, 0, description + "this test shouldn't create scrollable document");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s1").firstChild,
-     `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.startOffset, 0,
-       `${description} selection should be expanded to start of the first line`);
-  } else {
-    isfuzzy(range.startOffset, 3, 2,
-            `${description} selection should be expanded to around the first line's 3rd insertion point`);
-  }
-  is(range.endContainer, doc.getElementById("s3").firstChild,
-     `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
-  is(range.endOffset, 3,
-     `${description} selection should be expanded from the last line's 3rd insertion point`);
-
-  body.innerHTML = '<span id="s1">first line in the body</span>' +
-                     '<div id="d1" style="height: 2em; line-height: 1em; overflow: auto;">' +
-                       '<span id="s2">first line</span><br>' +
-                       '<span id="s3">second line</span><br>' +
-                       '<span id="s4">third line</span><br>' +
-                       '<span id="s5">last line</span>' +
-                     "</div>" +
-                   '<span id="s6">last line in the body</span>';
-  container = doc.getElementById("d1");
-
-  description = "Expanding selection to forward in scrollable area in the body: ";
-  is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
-  selection.collapse(doc.getElementById("s2").firstChild, 3);
-  doSelectPageDown();
-  isnot(container.scrollTop, 0, description + "should be scrolled down");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
-  is(range.startOffset, 3,
-     `${description} selection should be expanded from the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s4").firstChild,
-     `${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
-  isfuzzy(range.endOffset, 3, 2,
-          `${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
-
-  description = "Expanding selection to backward in scrollable area in the body: ";
-  selection.collapse(doc.getElementById("s4").firstChild, 3);
-  let previousScrollTop = container.scrollTop;
-  doSelectPageUp();
-  ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
-  isfuzzy(range.startOffset, 3, 2,
-          `${description} selection should be expanded to around the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s4").firstChild,
-     `${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
-  is(range.endOffset, 3,
-     `${description} selection should be expanded from the 3rd line's 3rd insertion point`);
-
-  body.innerHTML = '<span id="s1">first line in the body</span>' +
-                     '<div id="d1" contenteditable style="height: 2em; line-height: 1em; overflow: auto;">' +
-                       '<span id="s2">first line</span><br>' +
-                       '<span id="s3">second line</span><br>' +
-                       '<span id="s4">third line</span><br>' +
-                       '<span id="s5">last line</span>' +
-                     "</div>" +
-                   '<span id="s6">last line in the body</span>';
-  container = doc.getElementById("d1");
-
-  description = "Expanding selection to forward in scrollable editable div in the body: ";
-  is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
-  selection.collapse(doc.getElementById("s2").firstChild, 3);
-  doSelectPageDown();
-  isnot(container.scrollTop, 0, description + "should be scrolled down");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
-  is(range.startOffset, 3,
-     `${description} selection should be expanded from the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s4").firstChild,
-     `${description} selection should be expanded into the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
-  isfuzzy(range.endOffset, 3, 2,
-          `${description} selection should be expanded to around the 3rd line's 3rd insertion point`);
-
-  description = "Expanding selection to backward in scrollable editable div in the body: ";
-  selection.collapse(doc.getElementById("s4").firstChild, 3);
-  previousScrollTop = container.scrollTop;
-  doSelectPageUp();
-  ok(container.scrollTop < previousScrollTop, description + "should be scrolled up");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
-  isfuzzy(range.startOffset, 3, 2,
-          `${description} selection should be expanded to around the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s4").firstChild,
-     `${description} selection should be expanded from the 3rd line (got: ${getNodeDescription(range.endContainer)})`);
-  is(range.endOffset, 3,
-     `${description} selection should be expanded from the 3rd line's 3rd insertion point`);
-
-  body.innerHTML = '<span id="s1">first line in the body</span>' +
-                     '<div id="d1" contenteditable>' +
-                       '<span id="s2">first line</span><br>' +
-                       '<span id="s3">second line</span><br>' +
-                       '<span id="s4">third line</span><br>' +
-                       '<span id="s5">last line</span>' +
-                     "</div>" +
-                   '<span id="s6">last line in the body</span>';
-  container = doc.getElementById("d1");
-
-  description = "Expanding selection to forward in non-scrollable editable div in the body: ";
-  is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
-  selection.collapse(doc.getElementById("s2").firstChild, 3);
-  doSelectPageDown();
-  is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
-  is(range.startOffset, 3,
-     `${description} selection should be expanded from the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s5").firstChild,
-     `${description} selection should be expanded into the last line (got: ${getNodeDescription(range.endContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.endOffset, range.endContainer.length,
-       `${description} selection should be expanded to end of the last line`);
-  } else {
-    isfuzzy(range.endOffset, 3, 2,
-            `${description} selection should be expanded to around the last line's 3rd insertion point`);
-  }
-
-  description = "Expanding selection to backward in non-scrollable editable div in the body: ";
-  selection.collapse(doc.getElementById("s5").firstChild, 3);
-  doSelectPageUp();
-  is(container.scrollTop, 0, description + "editable div shouldn't be scrollable");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded into the first line (got: ${getNodeDescription(range.startContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.startOffset, 0,
-       `${description} selection should be expanded to start of the first line`);
-  } else {
-    isfuzzy(range.startOffset, 3, 2,
-            `${description} selection should be expanded to around the first line's 3rd insertion point`);
-  }
-  is(range.endContainer, doc.getElementById("s5").firstChild,
-     `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
-  is(range.endOffset, 3,
-     `${description} selection should be expanded from the last line's 3rd insertion point`);
-
-  body.innerHTML = '<span id="s1">first line in the body</span>' +
-                     '<div id="d1" contenteditable>' +
-                       '<span id="s2">first editable line</span><br>' +
-                       '<div id="d2" style="height: 3em; line-height: 1em; overflow: auto;">' +
-                         '<span id="s3">first line</span><br>' +
-                         '<span id="s4">second line</span>' +
-                       "</div>" +
-                       '<span id="s5">last editable line</span>' +
-                     "</div>" +
-                   '<span id="s6">last line in the body</span>';
-  container = doc.getElementById("d2");
-
-  description = "Expanding selection to forward in scrollable div (but not scrollable along y-axis) in the editable div: ";
-  is(container.scrollTop, 0, description + "scrollTop should be 0 at initialization");
-  selection.collapse(doc.getElementById("s3").firstChild, 3);
-  doSelectPageDown();
-  is(container.scrollTop, 0, description + "scrollable div in the editable div (but not scrollable along y-axis) shouldn't be scrollable");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s3").firstChild,
-     `${description} selection should be expanded from the first line (got: ${getNodeDescription(range.startContainer)})`);
-  is(range.startOffset, 3,
-     `${description} selection should be expanded from the first line's 3rd insertion point`);
-  is(range.endContainer, doc.getElementById("s5").firstChild,
-     `${description} selection should be expanded into the last editable line (got: ${getNodeDescription(range.endContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.endOffset, range.endContainer.length,
-       `${description} selection should be expanded to end of the last editable line`);
-  } else {
-    isfuzzy(range.endOffset, 3, 2,
-            `${description} selection should be expanded to around the last editable line's 3rd insertion point`);
-  }
-
-  description = "Expanding selection to backward in scrollable div (but not scrollable along y-axis) in the editable div: ";
-  selection.collapse(doc.getElementById("s4").firstChild, 3);
-  doSelectPageUp();
-  is(container.scrollTop, 0, description + "scrollable div (but not scrollable along y-axis) in the editable div shouldn't be scrollable");
-  range = selection.getRangeAt(0);
-  is(range.startContainer, doc.getElementById("s2").firstChild,
-     `${description} selection should be expanded into the first editable line (got: ${getNodeDescription(range.startContainer)})`);
-  if (kSelectToStartOrEnd) {
-    is(range.startOffset, 0,
-       `${description} selection should be expanded to start of the first editable line`);
-  } else {
-    isfuzzy(range.startOffset, 3, 2,
-            `${description} selection should be expanded to around the first editable line's 3rd insertion point`);
-  }
-  is(range.endContainer, doc.getElementById("s4").firstChild,
-     `${description} selection should be expanded from the last line (got: ${getNodeDescription(range.endContainer)})`);
-  is(range.endOffset, 3,
-     `${description} selection should be expanded from the last line's 3rd insertion point`);
+  doTest(false);
+  doTest(true);
 
   aWindow.close();
   SimpleTest.finish();
 }
 </script>
 </html>