Bug 492394 part.1 NS_QUERY_CHARACTER_AT_POINT should also return tentative caret offset for the point r=smaug, sr=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 14 Apr 2015 14:27:37 +0900
changeset 268808 3cf74485ea6a93ab7b492e21723c9326fd977c37
parent 268807 7cb645a5d38ebb414447ff477827832e74cf7705
child 268809 b617cfc2df875ae85147b6e956fffffe60dc1b24
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-beta@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, smaug
bugs492394
milestone40.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 492394 part.1 NS_QUERY_CHARACTER_AT_POINT should also return tentative caret offset for the point r=smaug, sr=smaug
dom/base/nsQueryContentEventResult.cpp
dom/base/nsQueryContentEventResult.h
dom/events/ContentEventHandler.cpp
dom/interfaces/base/nsIQueryContentEventResult.idl
widget/BasicEvents.h
widget/TextEvents.h
widget/nsGUIEventIPC.h
widget/tests/window_composition_text_querycontent.xul
--- a/dom/base/nsQueryContentEventResult.cpp
+++ b/dom/base/nsQueryContentEventResult.cpp
@@ -33,16 +33,31 @@ nsQueryContentEventResult::GetOffset(uin
   bool notFound;
   nsresult rv = GetNotFound(&notFound);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(!notFound, NS_ERROR_NOT_AVAILABLE);
   *aOffset = mOffset;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsQueryContentEventResult::GetTentativeCaretOffset(uint32_t* aOffset)
+{
+  bool notFound;
+  nsresult rv = GetTentativeCaretOffsetNotFound(&notFound);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  if (NS_WARN_IF(notFound)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  *aOffset = mTentativeCaretOffset;
+  return NS_OK;
+}
+
 static bool IsRectEnabled(uint32_t aEventID)
 {
   return aEventID == NS_QUERY_CARET_RECT ||
          aEventID == NS_QUERY_TEXT_RECT ||
          aEventID == NS_QUERY_EDITOR_RECT ||
          aEventID == NS_QUERY_CHARACTER_AT_POINT;
 }
 
@@ -121,25 +136,39 @@ nsQueryContentEventResult::GetNotFound(b
   NS_ENSURE_TRUE(mSucceeded, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_TRUE(mEventID == NS_QUERY_SELECTED_TEXT ||
                  mEventID == NS_QUERY_CHARACTER_AT_POINT,
                  NS_ERROR_NOT_AVAILABLE);
   *aNotFound = (mOffset == WidgetQueryContentEvent::NOT_FOUND);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsQueryContentEventResult::GetTentativeCaretOffsetNotFound(bool* aNotFound)
+{
+  if (NS_WARN_IF(!mSucceeded)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  if (NS_WARN_IF(mEventID != NS_QUERY_CHARACTER_AT_POINT)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  *aNotFound = (mTentativeCaretOffset == WidgetQueryContentEvent::NOT_FOUND);
+  return NS_OK;
+}
+
 void
 nsQueryContentEventResult::SetEventResult(nsIWidget* aWidget,
                                           const WidgetQueryContentEvent &aEvent)
 {
   mEventID = aEvent.message;
   mSucceeded = aEvent.mSucceeded;
   mReversed = aEvent.mReply.mReversed;
   mRect = aEvent.mReply.mRect;
   mOffset = aEvent.mReply.mOffset;
+  mTentativeCaretOffset = aEvent.mReply.mTentativeCaretOffset;
   mString = aEvent.mReply.mString;
 
   if (!IsRectEnabled(mEventID) || !aWidget || !mSucceeded) {
     return;
   }
 
   nsIWidget* topWidget = aWidget->GetTopLevelWidget();
   if (!topWidget || topWidget == aWidget) {
--- a/dom/base/nsQueryContentEventResult.h
+++ b/dom/base/nsQueryContentEventResult.h
@@ -25,16 +25,17 @@ public:
                       const mozilla::WidgetQueryContentEvent &aEvent);
 
 protected:
   ~nsQueryContentEventResult();
 
   uint32_t mEventID;
 
   uint32_t mOffset;
+  uint32_t mTentativeCaretOffset;
   nsString mString;
   mozilla::LayoutDeviceIntRect mRect;
 
   bool mSucceeded;
   bool mReversed;
 };
 
 #endif // mozilla_dom_nsQueryContentEventResult_h
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -1130,16 +1130,19 @@ ContentEventHandler::OnQuerySelectionAsT
 nsresult
 ContentEventHandler::OnQueryCharacterAtPoint(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  aEvent->mReply.mOffset = aEvent->mReply.mTentativeCaretOffset =
+    WidgetQueryContentEvent::NOT_FOUND;
+
   nsIFrame* rootFrame = mPresShell->GetRootFrame();
   NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE);
   nsIWidget* rootWidget = rootFrame->GetNearestWidget();
   NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
 
   // The root frame's widget might be different, e.g., the event was fired on
   // a popup but the rootFrame is the document root.
   if (rootWidget != aEvent->widget) {
@@ -1159,30 +1162,57 @@ ContentEventHandler::OnQueryCharacterAtP
   if (rootWidget != aEvent->widget) {
     eventOnRoot.refPoint += aEvent->widget->WidgetToScreenOffset() -
       rootWidget->WidgetToScreenOffset();
   }
   nsPoint ptInRoot =
     nsLayoutUtils::GetEventCoordinatesRelativeTo(&eventOnRoot, rootFrame);
 
   nsIFrame* targetFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot);
-  if (!targetFrame || targetFrame->GetType() != nsGkAtoms::textFrame ||
-      !targetFrame->GetContent() ||
+  if (!targetFrame || !targetFrame->GetContent() ||
       !nsContentUtils::ContentIsDescendantOf(targetFrame->GetContent(),
                                              mRootContent)) {
-    // there is no character at the point.
-    aEvent->mReply.mOffset = WidgetQueryContentEvent::NOT_FOUND;
+    // There is no character at the point.
     aEvent->mSucceeded = true;
     return NS_OK;
   }
   nsPoint ptInTarget = ptInRoot + rootFrame->GetOffsetToCrossDoc(targetFrame);
   int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel();
   int32_t targetAPD = targetFrame->PresContext()->AppUnitsPerDevPixel();
   ptInTarget = ptInTarget.ScaleToOtherAppUnits(rootAPD, targetAPD);
 
+  nsIFrame::ContentOffsets tentativeCaretOffsets =
+    targetFrame->GetContentOffsetsFromPoint(ptInTarget);
+  if (!tentativeCaretOffsets.content ||
+      !nsContentUtils::ContentIsDescendantOf(tentativeCaretOffsets.content,
+                                             mRootContent)) {
+    // There is no character nor tentative caret point at the point.
+    aEvent->mSucceeded = true;
+    return NS_OK;
+  }
+
+  rv = GetFlatTextOffsetOfRange(mRootContent, tentativeCaretOffsets.content,
+                                tentativeCaretOffsets.offset,
+                                &aEvent->mReply.mTentativeCaretOffset,
+                                GetLineBreakType(aEvent));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (targetFrame->GetType() != nsGkAtoms::textFrame) {
+    // There is no character at the point but there is tentative caret point.
+    aEvent->mSucceeded = true;
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(
+    aEvent->mReply.mTentativeCaretOffset != WidgetQueryContentEvent::NOT_FOUND,
+    "The point is inside a character bounding box.  Why tentative caret point "
+    "hasn't been found?");
+
   nsTextFrame* textframe = static_cast<nsTextFrame*>(targetFrame);
   nsIFrame::ContentOffsets contentOffsets =
     textframe->GetCharacterOffsetAtFramePoint(ptInTarget);
   NS_ENSURE_TRUE(contentOffsets.content, NS_ERROR_FAILURE);
   uint32_t offset;
   rv = GetFlatTextOffsetOfRange(mRootContent, contentOffsets.content,
                                 contentOffsets.offset, &offset,
                                 GetLineBreakType(aEvent));
--- a/dom/interfaces/base/nsIQueryContentEventResult.idl
+++ b/dom/interfaces/base/nsIQueryContentEventResult.idl
@@ -6,23 +6,25 @@
 #include "nsISupports.idl"
 
 /**
  * The result of query content events.  succeeded propery can be used always.
  * Whether other properties can be used or not depends on the event.
  * See nsIDOMWindowUtils.idl, which properites can be used was documented.
  */
 
-[scriptable, uuid(4b4ba266-b51e-4f0f-8d0e-9f13cb2a0056)]
+[scriptable, uuid(e2c39e0e-345f-451a-a7b2-e0230d555847)]
 interface nsIQueryContentEventResult : nsISupports
 {
   readonly attribute unsigned long offset;
+  readonly attribute unsigned long tentativeCaretOffset;
   readonly attribute boolean reversed;
 
   readonly attribute long left;
   readonly attribute long top;
   readonly attribute long width;
   readonly attribute long height;
   readonly attribute AString text;
 
   readonly attribute boolean succeeded;
   readonly attribute boolean notFound;
+  readonly attribute boolean tentativeCaretOffsetNotFound;
 };
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -253,18 +253,19 @@
 // Query for the bounding rect of the current focused frame. Result is relative
 // to top level widget coordinates
 #define NS_QUERY_EDITOR_RECT            (NS_QUERY_CONTENT_EVENT_START + 5)
 // Query for the current state of the content. The particular members of
 // mReply that are set for each query content event will be valid on success.
 #define NS_QUERY_CONTENT_STATE          (NS_QUERY_CONTENT_EVENT_START + 6)
 // Query for the selection in the form of a nsITransferable.
 #define NS_QUERY_SELECTION_AS_TRANSFERABLE (NS_QUERY_CONTENT_EVENT_START + 7)
-// Query for character at a point.  This returns the character offset and its
-// rect.  The point is specified by Event::refPoint.
+// Query for character at a point.  This returns the character offset, its
+// rect and also tentative caret point if the point is clicked.  The point is
+// specified by Event::refPoint.
 #define NS_QUERY_CHARACTER_AT_POINT     (NS_QUERY_CONTENT_EVENT_START + 8)
 // Query if the DOM element under Event::refPoint belongs to our widget
 // or not.
 #define NS_QUERY_DOM_WIDGET_HITTEST     (NS_QUERY_CONTENT_EVENT_START + 9)
 
 // Video events
 #define NS_MEDIA_EVENT_START            3300
 #define NS_LOADSTART           (NS_MEDIA_EVENT_START)
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -549,16 +549,19 @@ public:
   {
     uint32_t mOffset;
     uint32_t mLength;
   } mInput;
   struct
   {
     void* mContentsRoot;
     uint32_t mOffset;
+    // mTentativeCaretOffset is used by only NS_QUERY_CHARACTER_AT_POINT.
+    // This is the offset where caret would be if user clicked at the refPoint.
+    uint32_t mTentativeCaretOffset;
     nsString mString;
     // Finally, the coordinates is system coordinates.
     mozilla::LayoutDeviceIntRect mRect;
     // The return widget has the caret. This is set at all query events.
     nsIWidget* mFocusedWidget;
     // true if selection is reversed (end < start)
     bool mReversed;
     // true if the selection exists
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -582,16 +582,17 @@ struct ParamTraits<mozilla::WidgetQueryC
   {
     WriteParam(aMsg, static_cast<mozilla::WidgetGUIEvent>(aParam));
     WriteParam(aMsg, aParam.mSucceeded);
     WriteParam(aMsg, aParam.mUseNativeLineBreak);
     WriteParam(aMsg, aParam.mWithFontRanges);
     WriteParam(aMsg, aParam.mInput.mOffset);
     WriteParam(aMsg, aParam.mInput.mLength);
     WriteParam(aMsg, aParam.mReply.mOffset);
+    WriteParam(aMsg, aParam.mReply.mTentativeCaretOffset);
     WriteParam(aMsg, aParam.mReply.mString);
     WriteParam(aMsg, aParam.mReply.mRect);
     WriteParam(aMsg, aParam.mReply.mReversed);
     WriteParam(aMsg, aParam.mReply.mHasSelection);
     WriteParam(aMsg, aParam.mReply.mWidgetIsHit);
     WriteParam(aMsg, aParam.mReply.mFontRanges);
   }
 
@@ -601,16 +602,17 @@ struct ParamTraits<mozilla::WidgetQueryC
     return ReadParam(aMsg, aIter,
                      static_cast<mozilla::WidgetGUIEvent*>(aResult)) &&
            ReadParam(aMsg, aIter, &aResult->mSucceeded) &&
            ReadParam(aMsg, aIter, &aResult->mUseNativeLineBreak) &&
            ReadParam(aMsg, aIter, &aResult->mWithFontRanges) &&
            ReadParam(aMsg, aIter, &aResult->mInput.mOffset) &&
            ReadParam(aMsg, aIter, &aResult->mInput.mLength) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mOffset) &&
+           ReadParam(aMsg, aIter, &aResult->mReply.mTentativeCaretOffset) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mString) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mRect) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mReversed) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mHasSelection) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mWidgetIsHit) &&
            ReadParam(aMsg, aIter, &aResult->mReply.mFontRanges);
   }
 };
--- a/widget/tests/window_composition_text_querycontent.xul
+++ b/widget/tests/window_composition_text_querycontent.xul
@@ -14,23 +14,23 @@
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
 
   <panel id="panel" hidden="true"
          orient="vertical"
          onpopupshown="onPanelShown(event);"
          onpopuphidden="onPanelHidden(event);">
     <vbox id="vbox">
       <textbox id="textbox" onfocus="onFocusPanelTextbox(event);"
-               multiline="true" cols="20" rows="4"/>
+               multiline="true" cols="20" rows="4" style="font-size: 36px;"/>
     </vbox>
   </panel>
 
 <body  xmlns="http://www.w3.org/1999/xhtml">
 <p id="display">
-<div id="div" style="margin: 0; padding: 0; font-size: 24px;">Here is a text frame.</div>
+<div id="div" style="margin: 0; padding: 0; font-size: 36px;">Here is a text frame.</div>
 <textarea style="margin: 0;" id="textarea" cols="20" rows="4"></textarea><br/>
 <iframe id="iframe" width="300" height="150"
         src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe><br/>
 <iframe id="iframe2" width="300" height="150"
         src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
 <iframe id="iframe3" width="300" height="150"
         src="data:text/html,&lt;body onload='document.designMode=%22on%22'&gt;body content&lt;/body&gt;"></iframe><br/>
 <input id="input" type="text"/><br/>
@@ -2164,19 +2164,21 @@ function runCharAtPointTest(aFocusedEdit
 {
   aFocusedEditor.value = "This is a test of the\nContent Events";
                        // 012345678901234567890  12345678901234
                        // 0         1         2           3    
 
   aFocusedEditor.focus();
 
   const kNone = -1;
-  const kTestingOffset   = [     0, 10,    20, 21 + kLFLen, 34 + kLFLen];
-  const kLeftSideOffset  = [ kNone,  9,    19,       kNone, 33 + kLFLen];
-  const kRightSideOffset = [     1, 11, kNone, 22 + kLFLen,       kNone];
+  const kTestingOffset             = [     0, 10,    20, 21 + kLFLen, 34 + kLFLen];
+  const kLeftSideOffset            = [ kNone,  9,    19,       kNone, 33 + kLFLen];
+  const kRightSideOffset           = [     1, 11, kNone, 22 + kLFLen,       kNone];
+  const kLeftTentativeCaretOffset  = [     0, 10,    20, 21 + kLFLen, 34 + kLFLen];
+  const kRightTentativeCaretOffset = [     1, 11,    21, 22 + kLFLen, 35 + kLFLen];
 
   var editorRect = synthesizeQueryEditorRect();
   if (!checkQueryContentResult(editorRect,
         "runCharAtPointTest (" + aTargetName + "): editorRect")) {
     return;
   }
 
   for (var i = 0; i < kTestingOffset.length; i++) {
@@ -2198,61 +2200,91 @@ function runCharAtPointTest(aFocusedEdit
       ok(!charAtPt1.notFound,
          "runCharAtPointTest (" + aTargetName + "): charAtPt1 isn't found: i=" + i);
       if (!charAtPt1.notFound) {
         is(charAtPt1.offset, kTestingOffset[i],
            "runCharAtPointTest (" + aTargetName + "): charAtPt1 offset is wrong: i=" + i);
         checkRect(charAtPt1, textRect, "runCharAtPointTest (" + aTargetName +
                   "): charAtPt1 left is wrong: i=" + i);
       }
+      ok(!charAtPt1.tentativeCaretOffsetNotFound,
+         "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 isn't found: i=" + i);
+      if (!charAtPt1.tentativeCaretOffsetNotFound) {
+        is(charAtPt1.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
+           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt1 is wrong: i=" + i);
+      }
     }
 
     // Test #2, getting same character rect by the point near the bottom-right.
     var charAtPt2 = synthesizeCharAtPoint(textRect.left + textRect.width - 2,
                                           textRect.top + textRect.height - 2);
     if (checkQueryContentResult(charAtPt2,
           "runCharAtPointTest (" + aTargetName + "): charAtPt2: i=" + i)) {
       ok(!charAtPt2.notFound,
          "runCharAtPointTest (" + aTargetName + "): charAtPt2 isn't found: i=" + i);
       if (!charAtPt2.notFound) {
         is(charAtPt2.offset, kTestingOffset[i],
            "runCharAtPointTest (" + aTargetName + "): charAtPt2 offset is wrong: i=" + i);
         checkRect(charAtPt2, textRect, "runCharAtPointTest (" + aTargetName +
                   "): charAtPt1 left is wrong: i=" + i);
       }
+      ok(!charAtPt2.tentativeCaretOffsetNotFound,
+         "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 isn't found: i=" + i);
+      if (!charAtPt2.tentativeCaretOffsetNotFound) {
+        is(charAtPt2.tentativeCaretOffset, kRightTentativeCaretOffset[i],
+           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt2 is wrong: i=" + i);
+      }
     }
 
     // Test #3, getting left character offset.
     var charAtPt3 = synthesizeCharAtPoint(textRect.left - 2,
                                           textRect.top + 1);
     if (checkQueryContentResult(charAtPt3,
           "runCharAtPointTest (" + aTargetName + "): charAtPt3: i=" + i)) {
       is(charAtPt3.notFound, kLeftSideOffset[i] == kNone,
          kLeftSideOffset[i] == kNone ?
            "runCharAtPointTest (" + aTargetName + "): charAtPt3 is found: i=" + i :
            "runCharAtPointTest (" + aTargetName + "): charAtPt3 isn't found: i=" + i);
       if (!charAtPt3.notFound) {
         is(charAtPt3.offset, kLeftSideOffset[i],
            "runCharAtPointTest (" + aTargetName + "): charAtPt3 offset is wrong: i=" + i);
       }
+      if (kLeftSideOffset[i] == kNone) {
+        // There may be no enough padding-left (depends on platform)
+        todo(false,
+             "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't tested: i=" + i);
+      } else {
+        ok(!charAtPt3.tentativeCaretOffsetNotFound,
+           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 isn't found: i=" + i);
+        if (!charAtPt3.tentativeCaretOffsetNotFound) {
+          is(charAtPt3.tentativeCaretOffset, kLeftTentativeCaretOffset[i],
+             "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt3 is wrong: i=" + i);
+        }
+      }
     }
 
     // Test #4, getting right character offset.
     var charAtPt4 = synthesizeCharAtPoint(textRect.left + textRect.width + 1,
                                           textRect.top + textRect.height - 2);
     if (checkQueryContentResult(charAtPt4,
           "runCharAtPointTest (" + aTargetName + "): charAtPt4: i=" + i)) {
       is(charAtPt4.notFound, kRightSideOffset[i] == kNone,
          kRightSideOffset[i] == kNone ?
            "runCharAtPointTest (" + aTargetName + "): charAtPt4 is found: i=" + i :
            "runCharAtPointTest (" + aTargetName + "): charAtPt4 isn't found: i=" + i);
       if (!charAtPt4.notFound) {
         is(charAtPt4.offset, kRightSideOffset[i],
            "runCharAtPointTest (" + aTargetName + "): charAtPt4 offset is wrong: i=" + i);
       }
+      ok(!charAtPt4.tentativeCaretOffsetNotFound,
+         "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 isn't found: i=" + i);
+      if (!charAtPt4.tentativeCaretOffsetNotFound) {
+        is(charAtPt4.tentativeCaretOffset, kRightTentativeCaretOffset[i],
+           "runCharAtPointTest (" + aTargetName + "): tentative caret offset for charAtPt4 is wrong: i=" + i);
+      }
     }
   }
 }
 
 function runCharAtPointAtOutsideTest()
 {
   textarea.focus();
   textarea.value = "some text";
@@ -2263,16 +2295,18 @@ function runCharAtPointAtOutsideTest()
   }
   // Check on a text node which is at the outside of editor.
   var charAtPt = synthesizeCharAtPoint(editorRect.left + 20,
                                        editorRect.top - 10);
   if (checkQueryContentResult(charAtPt,
         "runCharAtPointAtOutsideTest: charAtPt")) {
     ok(charAtPt.notFound,
        "runCharAtPointAtOutsideTest: charAtPt is found on outside of editor");
+    ok(charAtPt.tentativeCaretOffsetNotFound,
+       "runCharAtPointAtOutsideTest: tentative caret offset for charAtPt is found on outside of editor");
   }
 }
 
 function runBug722639Test()
 {
   textarea.focus();
   textarea.value = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
   textarea.value += textarea.value;