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 109573 3d8d4f10eb627d972c57205924e76c909cd80b3f
parent 109572 b57278dc0172d3b82d75eeb39790524c343a597a
child 109574 0c48cbf5e4c53347816db0446df512db6e356858
push id16083
push usermasayuki@d-toybox.com
push dateSun, 07 Oct 2012 18:46:10 +0000
treeherdermozilla-inbound@0c48cbf5e4c5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, smaug, smaug
bugs795785
milestone18.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 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