Bug 795785 part.1 Editor should scroll the selection into view after edit even when the editor is specified overflow: hidden; r=ehsan,smaug, sr=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 08 Oct 2012 03:45:51 +0900
changeset 109574 3d8d4f10eb627d972c57205924e76c909cd80b3f
parent 109573 b57278dc0172d3b82d75eeb39790524c343a597a
child 109575 0c48cbf5e4c53347816db0446df512db6e356858
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersehsan, smaug, smaug
bugs795785
milestone18.0a1
Bug 795785 part.1 Editor should scroll the selection into view after edit even when the editor is specified overflow: hidden; r=ehsan,smaug, sr=smaug
content/base/public/nsISelectionController.idl
editor/libeditor/base/nsEditor.cpp
editor/libeditor/base/tests/Makefile.in
editor/libeditor/base/tests/test_bug795785.html
layout/base/nsPresShell.cpp
layout/generic/Selection.h
layout/generic/nsSelection.cpp
--- a/content/base/public/nsISelectionController.idl
+++ b/content/base/public/nsISelectionController.idl
@@ -61,29 +61,32 @@ interface nsISelectionController : nsISe
    * @param aType will hold the type of selection //SelectionType
    * @param _return will hold the return value
    */
     nsISelection getSelection(in short type);
 
    const short SCROLL_SYNCHRONOUS = 1<<1;
    const short SCROLL_FIRST_ANCESTOR_ONLY = 1<<2;
    const short SCROLL_CENTER_VERTICALLY = 1<<4;
+   const short SCROLL_OVERFLOW_HIDDEN = 1<<5;
 
    /**
    * ScrollSelectionIntoView scrolls a region of the selection,
    * so that it is visible in the scrolled view.
    *
    * @param aType the selection to scroll into view. //SelectionType
    * @param aRegion the region inside the selection to scroll into view. //SelectionRegion
    * @param aFlags the scroll flags.  Valid bits include:
    * SCROLL_SYNCHRONOUS: when set, scrolls the selection into view
    * before returning. If not set, posts a request which is processed
    * at some point after the method returns.
    * SCROLL_FIRST_ANCESTOR_ONLY: if set, only the first ancestor will be scrolled
    * into view.
+   * SCROLL_OVERFLOW_HIDDEN: if set, scrolls even if the overflow is specified
+   * as hidden.
    *
    * Note that if isSynchronous is true, then this might flush the pending
    * reflow. It's dangerous for some objects. See bug 418470 comment 12.
    */
     void scrollSelectionIntoView(in short type, in short region, in short flags);
 
    /**
    * RepaintSelection repaints the selection specified by aType.
--- a/editor/libeditor/base/nsEditor.cpp
+++ b/editor/libeditor/base/nsEditor.cpp
@@ -2316,17 +2316,17 @@ NS_IMETHODIMP nsEditor::ScrollSelectionI
   if (NS_SUCCEEDED(GetSelectionController(getter_AddRefs(selCon))) && selCon)
   {
     int16_t region = nsISelectionController::SELECTION_FOCUS_REGION;
 
     if (aScrollToAnchor)
       region = nsISelectionController::SELECTION_ANCHOR_REGION;
 
     selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
-                                    region, 0);
+      region, nsISelectionController::SCROLL_OVERFLOW_HIDDEN);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP nsEditor::InsertTextImpl(const nsAString& aStringToInsert, 
                                           nsCOMPtr<nsIDOMNode> *aInOutNode, 
                                           int32_t *aInOutOffset,
--- a/editor/libeditor/base/tests/Makefile.in
+++ b/editor/libeditor/base/tests/Makefile.in
@@ -16,16 +16,17 @@ MOCHITEST_FILES = \
 		test_bug502673.html \
 		test_bug514156.html \
 		test_bug567213.html \
 		file_bug586662.html \
 		test_bug586662.html \
 		test_bug599983.html \
 		test_bug742261.html \
 		test_bug773262.html \
+		test_bug795785.html \
 		$(NULL)
 
 MOCHITEST_CHROME_FILES = \
 		test_selection_move_commands.xul \
                 test_bug46555.html \
 		test_bug646194.xul \
 		test_dragdrop.html \
 		test_bug599983.xul \
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/base/tests/test_bug795785.html
@@ -0,0 +1,128 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=795785
+-->
+<head>
+  <title>Test for Bug 795785</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795785">Mozilla Bug 795785</a>
+<div id="display">
+  <textarea id="textarea" style="overflow: hidden; height: 3em; width: 5em;"></textarea>
+  <div id="div" contenteditable style="overflow: hidden; height: 3em; width: 5em;"></div>
+</div>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+var textarea = document.getElementById("textarea");
+var div = document.getElementById("div");
+
+function hitEventLoop(aFunc, aTimes)
+{
+  if (--aTimes) {
+    setTimeout(hitEventLoop, 0, aFunc, aTimes);
+  } else {
+    setTimeout(aFunc, 20);
+  }
+}
+
+function doEnterKeyTest(aElement, aElementDescription, aCallback)
+{
+  aElement.focus();
+  aElement.scrollTop = 0;
+  hitEventLoop(function () {
+    is(aElement.scrollTop, 0,
+       aElementDescription + "'s scrollTop isn't 0");
+    synthesizeKey("VK_RETURN", { });
+    synthesizeKey("VK_RETURN", { });
+    synthesizeKey("VK_RETURN", { });
+    synthesizeKey("VK_RETURN", { });
+    synthesizeKey("VK_RETURN", { });
+    synthesizeKey("VK_RETURN", { });
+    hitEventLoop(function () {
+      isnot(aElement.scrollTop, 0,
+            aElementDescription + " was not scrolled by inserting line breaks");
+      aCallback();
+    }, 20);
+  }, 20);
+}
+
+function doCompositionTest(aElement, aElementDescription, aCallback)
+{
+  aElement.focus();
+  aElement.scrollTop = 0;
+  hitEventLoop(function () {
+    is(aElement.scrollTop, 0,
+       aElementDescription + "'s scrollTop isn't 0");
+    synthesizeComposition({ type: "compositionstart" });
+    var str = "Web \u958b\u767a\u8005\u306e\u7686\u3055\u3093\u306f\u3001" +
+              "Firefox \u306b\u5b9f\u88c5\u3055\u308c\u3066\u3044\u308b HTML5" +
+              " \u3084 CSS \u306e\u65b0\u6a5f\u80fd\u3092\u6d3b\u7528\u3059" +
+              "\u308b\u3053\u3068\u3067\u3001\u9b45\u529b\u3042\u308b Web " +
+              "\u30b5\u30a4\u30c8\u3084\u9769\u65b0\u7684\u306a Web \u30a2" +
+              "\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u3088\u308a" +
+              "\u77ed\u6642\u9593\u3067\u7c21\u5358\u306b\u4f5c\u6210\u3067" +
+              "\u304d\u307e\u3059\u3002";
+    synthesizeComposition({ type: "compositionupdate", data: str });
+    synthesizeText({
+        composition: {
+          string: str,
+          clauses: [
+            { length: str.length, attr: COMPOSITION_ATTR_RAWINPUT }
+          ]
+        },
+        caret: { start: str.length, length: 0 }
+      });
+    hitEventLoop(function () {
+      isnot(aElement.scrollTop, 0,
+            aElementDescription + " was not scrolled by composition");
+      synthesizeComposition({ type: "compositionupdate", data: "" });
+      synthesizeText({
+        composition: { string: "", clauses: [ { length: 0, attr: 0 } ] },
+        caret: { start: 0, length: 0 }
+      });
+      synthesizeComposition({ type: "compositionend", data: "" });
+      hitEventLoop(function () {
+        is(aElement.scrollTop, 0,
+           aElementDescription + " was not scrolled back to the top by canceling composition");
+        aCallback();
+      }, 20);
+    }, 20);
+  }, 20);
+}
+
+function runTests()
+{
+  doEnterKeyTest(textarea, "textarea",
+    function () {
+      textarea.value = "";
+      doEnterKeyTest(div, "div (contenteditable)",
+        function () {
+          div.innerHTML = "";
+          doCompositionTest(textarea, "textarea",
+            function () {
+              doCompositionTest(div, "div (contenteditable)",
+                function () {
+                  SimpleTest.finish();
+                });
+            });
+        });
+    });
+}
+
+</script>
+</body>
+
+</html>
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -175,17 +175,18 @@
 #include "mozilla/Telemetry.h"
 #include "sampler.h"
 #include "mozilla/css/ImageLoader.h"
 
 #include "Layers.h"
 #include "LayerTreeInvalidation.h"
 #include "nsAsyncDOMEvent.h"
 
-#define ANCHOR_SCROLL_FLAGS (SCROLL_OVERFLOW_HIDDEN | SCROLL_NO_PARENT_FRAMES)
+#define ANCHOR_SCROLL_FLAGS \
+  (nsIPresShell::SCROLL_OVERFLOW_HIDDEN | nsIPresShell::SCROLL_NO_PARENT_FRAMES)
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 
 CapturingContentInfo nsIPresShell::gCaptureInfo =
   { false /* mAllowed */, false /* mPointerLock */, false /* mRetargetToElement */,
     false /* mPreventDrag */, nullptr /* mContent */ };
@@ -6781,17 +6782,17 @@ PresShell::PrepareToUseCaretPosition(nsI
     // the top of the window. This is arguably better behavior anyway.
     rv = ScrollContentIntoView(content,
                                nsIPresShell::ScrollAxis(
                                  nsIPresShell::SCROLL_MINIMUM,
                                  nsIPresShell::SCROLL_IF_NOT_VISIBLE),
                                nsIPresShell::ScrollAxis(
                                  nsIPresShell::SCROLL_MINIMUM,
                                  nsIPresShell::SCROLL_IF_NOT_VISIBLE),
-                               SCROLL_OVERFLOW_HIDDEN);
+                               nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
     NS_ENSURE_SUCCESS(rv, false);
     frame = content->GetPrimaryFrame();
     NS_WARN_IF_FALSE(frame, "No frame for focused content?");
   }
 
   // Actually scroll the selection (ie caret) into view. Note that this must
   // be synchronous since we will be checking the caret position on the screen.
   //
@@ -6846,17 +6847,17 @@ PresShell::GetCurrentItemAndPositionForE
                                                nsIContent** aTargetToUse,
                                                nsIntPoint& aTargetPt,
                                                nsIWidget *aRootWidget)
 {
   nsCOMPtr<nsIContent> focusedContent(do_QueryInterface(aCurrentEl));
   ScrollContentIntoView(focusedContent,
                         ScrollAxis(),
                         ScrollAxis(),
-                        SCROLL_OVERFLOW_HIDDEN);
+                        nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
 
   nsPresContext* presContext = GetPresContext();
 
   bool istree = false, checkLineHeight = true;
   nscoord extraTreeY = 0;
 
 #ifdef MOZ_XUL
   // Set the position to just underneath the current item for multi-select
--- a/layout/generic/Selection.h
+++ b/layout/generic/Selection.h
@@ -62,23 +62,24 @@ public:
   nsIFrame*     GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect *aRect);
   // Returns the position of the region (SELECTION_ANCHOR_REGION or
   // SELECTION_FOCUS_REGION only), and frame that that position is relative to.
   // The 'position' is a zero-width rectangle.
   nsIFrame*     GetSelectionEndPointGeometry(SelectionRegion aRegion, nsRect *aRect);
 
   nsresult      PostScrollSelectionIntoViewEvent(
                                         SelectionRegion aRegion,
-                                        bool aFirstAncestorOnly,
+                                        int32_t aFlags,
                                         nsIPresShell::ScrollAxis aVertical,
                                         nsIPresShell::ScrollAxis aHorizontal);
   enum {
     SCROLL_SYNCHRONOUS = 1<<1,
     SCROLL_FIRST_ANCESTOR_ONLY = 1<<2,
-    SCROLL_DO_FLUSH = 1<<3
+    SCROLL_DO_FLUSH = 1<<3,
+    SCROLL_OVERFLOW_HIDDEN = 1<<5
   };
   // aDoFlush only matters if aIsSynchronous is true.  If not, we'll just flush
   // when the scroll event fires so we make sure to scroll to the right place.
   nsresult      ScrollIntoView(SelectionRegion aRegion,
                                nsIPresShell::ScrollAxis aVertical =
                                  nsIPresShell::ScrollAxis(),
                                nsIPresShell::ScrollAxis aHorizontal =
                                  nsIPresShell::ScrollAxis(),
@@ -148,31 +149,31 @@ private:
 
   class ScrollSelectionIntoViewEvent : public nsRunnable {
   public:
     NS_DECL_NSIRUNNABLE
     ScrollSelectionIntoViewEvent(Selection* aSelection,
                                  SelectionRegion aRegion,
                                  nsIPresShell::ScrollAxis aVertical,
                                  nsIPresShell::ScrollAxis aHorizontal,
-                                 bool aFirstAncestorOnly)
+                                 int32_t aFlags)
       : mSelection(aSelection),
         mRegion(aRegion),
         mVerticalScroll(aVertical),
         mHorizontalScroll(aHorizontal),
-        mFirstAncestorOnly(aFirstAncestorOnly) {
+        mFlags(aFlags) {
       NS_ASSERTION(aSelection, "null parameter");
     }
     void Revoke() { mSelection = nullptr; }
   private:
     Selection *mSelection;
     SelectionRegion mRegion;
     nsIPresShell::ScrollAxis mVerticalScroll;
     nsIPresShell::ScrollAxis mHorizontalScroll;
-    bool mFirstAncestorOnly;
+    int32_t mFlags;
   };
 
   void setAnchorFocusRange(int32_t aIndex); // pass in index into mRanges;
                                             // negative value clears
                                             // mAnchorFocusRange
   nsresult     SelectAllFramesForContent(nsIContentIterator *aInnerIter,
                                nsIContent *aContent,
                                bool aSelected);
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -1707,16 +1707,19 @@ nsFrameSelection::ScrollSelectionIntoVie
 
   nsIPresShell::ScrollAxis verticalScroll = nsIPresShell::ScrollAxis();
   int32_t flags = Selection::SCROLL_DO_FLUSH;
   if (aFlags & nsISelectionController::SCROLL_SYNCHRONOUS) {
     flags |= Selection::SCROLL_SYNCHRONOUS;
   } else if (aFlags & nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY) {
     flags |= Selection::SCROLL_FIRST_ANCESTOR_ONLY;
   }
+  if (aFlags & nsISelectionController::SCROLL_OVERFLOW_HIDDEN) {
+    flags |= Selection::SCROLL_OVERFLOW_HIDDEN;
+  }
   if (aFlags & nsISelectionController::SCROLL_CENTER_VERTICALLY) {
     verticalScroll = nsIPresShell::ScrollAxis(
       nsIPresShell::SCROLL_CENTER, nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE);
   }
 
   // After ScrollSelectionIntoView(), the pending notifications might be
   // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
   return mDomSelections[index]->ScrollIntoView(aRegion,
@@ -5228,42 +5231,39 @@ Selection::GetSelectionEndPointGeometry(
 NS_IMETHODIMP
 Selection::ScrollSelectionIntoViewEvent::Run()
 {
   if (!mSelection)
     return NS_OK;  // event revoked
 
   int32_t flags = Selection::SCROLL_DO_FLUSH |
                   Selection::SCROLL_SYNCHRONOUS;
-  if (mFirstAncestorOnly) {
-    flags |= Selection::SCROLL_FIRST_ANCESTOR_ONLY;
-  }
 
   mSelection->mScrollEvent.Forget();
   mSelection->ScrollIntoView(mRegion, mVerticalScroll,
-                             mHorizontalScroll, flags);
+                             mHorizontalScroll, mFlags | flags);
   return NS_OK;
 }
 
 nsresult
 Selection::PostScrollSelectionIntoViewEvent(
                                          SelectionRegion aRegion,
-                                         bool aFirstAncestorOnly,
+                                         int32_t aFlags,
                                          nsIPresShell::ScrollAxis aVertical,
                                          nsIPresShell::ScrollAxis aHorizontal)
 {
   // If we've already posted an event, revoke it and place a new one at the
   // end of the queue to make sure that any new pending reflow events are
   // processed before we scroll. This will insure that we scroll to the
   // correct place on screen.
   mScrollEvent.Revoke();
 
   nsRefPtr<ScrollSelectionIntoViewEvent> ev =
       new ScrollSelectionIntoViewEvent(this, aRegion, aVertical, aHorizontal,
-                                       aFirstAncestorOnly);
+                                       aFlags);
   nsresult rv = NS_DispatchToCurrentThread(ev);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mScrollEvent = ev;
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -5294,18 +5294,17 @@ Selection::ScrollIntoView(SelectionRegio
   nsresult result;
   if (!mFrameSelection)
     return NS_OK;//nothing to do
 
   if (mFrameSelection->GetBatching())
     return NS_OK;
 
   if (!(aFlags & Selection::SCROLL_SYNCHRONOUS))
-    return PostScrollSelectionIntoViewEvent(aRegion,
-      !!(aFlags & Selection::SCROLL_FIRST_ANCESTOR_ONLY),
+    return PostScrollSelectionIntoViewEvent(aRegion, aFlags,
       aVertical, aHorizontal);
 
   //
   // Shut the caret off before scrolling to avoid
   // leaving caret turds on the screen!
   //
   nsCOMPtr<nsIPresShell> presShell;
   result = GetPresShell(getter_AddRefs(presShell));
@@ -5339,19 +5338,27 @@ Selection::ScrollIntoView(SelectionRegio
     if (!frame)
       return NS_ERROR_FAILURE;
 
     // Scroll vertically to get the caret into view, but only if the container
     // is perceived to be scrollable in that direction (i.e. there is a visible
     // vertical scrollbar or the scroll range is at least one device pixel)
     aVertical.mOnlyIfPerceivedScrollableDirection = true;
 
+
+    uint32_t flags = 0;
+    if (aFlags & Selection::SCROLL_FIRST_ANCESTOR_ONLY) {
+      flags |= nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY;
+    }
+    if (aFlags & Selection::SCROLL_OVERFLOW_HIDDEN) {
+      flags |= nsIPresShell::SCROLL_OVERFLOW_HIDDEN;
+    }
+
     presShell->ScrollFrameRectIntoView(frame, rect, aVertical, aHorizontal,
-      (aFlags & Selection::SCROLL_FIRST_ANCESTOR_ONLY) ?
-       nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY : 0);
+      flags);
     return NS_OK;
   }
   return result;
 }
 
 
 
 NS_IMETHODIMP