Bug 1401706 - Move ownership of editor anon content to nsTextControlFrame. r=Ehsan, a=sledru
authorEmilio Cobos Álvarez <emilio@crisal.io>
Fri, 22 Sep 2017 02:18:30 +0200
changeset 434256 4d33cf91c8bd34351510604e5032b610fa4afa02
parent 434255 fd78928b00d87444d1aefe942d0489e8682e0981
child 434257 f1e83779aee97be6a183bda6aed19ec04ade5af3
push id1567
push userjlorenzo@mozilla.com
push dateThu, 02 Nov 2017 12:36:05 +0000
treeherdermozilla-release@e512c14a0406 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersEhsan, sledru
bugs1401706
milestone57.0
Bug 1401706 - Move ownership of editor anon content to nsTextControlFrame. r=Ehsan, a=sledru MozReview-Commit-ID: 4QpbarX5dvf Signed-off-by: Emilio Cobos Álvarez <emilio@crisal.io>
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/HTMLTextAreaElement.cpp
dom/html/HTMLTextAreaElement.h
dom/html/nsITextControlElement.h
dom/html/nsTextEditorState.cpp
dom/html/nsTextEditorState.h
layout/forms/nsTextControlFrame.cpp
layout/forms/nsTextControlFrame.h
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2631,27 +2631,16 @@ HTMLInputElement::GetRootEditorNode()
   nsTextEditorState* state = GetEditorState();
   if (state) {
     return state->GetRootNode();
   }
   return nullptr;
 }
 
 NS_IMETHODIMP_(Element*)
-HTMLInputElement::CreatePlaceholderNode()
-{
-  nsTextEditorState* state = GetEditorState();
-  if (state) {
-    NS_ENSURE_SUCCESS(state->CreatePlaceholderNode(), nullptr);
-    return state->GetPlaceholderNode();
-  }
-  return nullptr;
-}
-
-NS_IMETHODIMP_(Element*)
 HTMLInputElement::GetPlaceholderNode()
 {
   nsTextEditorState* state = GetEditorState();
   if (state) {
     return state->GetPlaceholderNode();
   }
   return nullptr;
 }
@@ -2672,27 +2661,16 @@ HTMLInputElement::GetPlaceholderVisibili
   if (!state) {
     return false;
   }
 
   return state->GetPlaceholderVisibility();
 }
 
 NS_IMETHODIMP_(Element*)
-HTMLInputElement::CreatePreviewNode()
-{
-  nsTextEditorState* state = GetEditorState();
-  if (state) {
-    NS_ENSURE_SUCCESS(state->CreatePreviewNode(), nullptr);
-    return state->GetPreviewNode();
-  }
-  return nullptr;
-}
-
-NS_IMETHODIMP_(Element*)
 HTMLInputElement::GetPreviewNode()
 {
   nsTextEditorState* state = GetEditorState();
   if (state) {
     return state->GetPreviewNode();
   }
   return nullptr;
 }
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -242,19 +242,17 @@ public:
   NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
   NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() override;
   NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
   NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
   NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD CreateEditor() override;
   NS_IMETHOD_(Element*) GetRootEditorNode() override;
-  NS_IMETHOD_(Element*) CreatePlaceholderNode() override;
   NS_IMETHOD_(Element*) GetPlaceholderNode() override;
-  NS_IMETHOD_(Element*) CreatePreviewNode() override;
   NS_IMETHOD_(Element*) GetPreviewNode() override;
   NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override;
   NS_IMETHOD_(void) SetPreviewValue(const nsAString& aValue) override;
   NS_IMETHOD_(void) GetPreviewValue(nsAString& aValue) override;
   NS_IMETHOD_(void) EnablePreview() override;
   NS_IMETHOD_(bool) IsPreviewEnabled() override;
   NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
   NS_IMETHOD_(bool) GetPreviewVisibility() override;
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -288,23 +288,16 @@ HTMLTextAreaElement::CreateEditor()
 
 NS_IMETHODIMP_(Element*)
 HTMLTextAreaElement::GetRootEditorNode()
 {
   return mState.GetRootNode();
 }
 
 NS_IMETHODIMP_(Element*)
-HTMLTextAreaElement::CreatePlaceholderNode()
-{
-  NS_ENSURE_SUCCESS(mState.CreatePlaceholderNode(), nullptr);
-  return mState.GetPlaceholderNode();
-}
-
-NS_IMETHODIMP_(Element*)
 HTMLTextAreaElement::GetPlaceholderNode()
 {
   return mState.GetPlaceholderNode();
 }
 
 NS_IMETHODIMP_(void)
 HTMLTextAreaElement::UpdateOverlayTextVisibility(bool aNotify)
 {
@@ -313,23 +306,16 @@ HTMLTextAreaElement::UpdateOverlayTextVi
 
 NS_IMETHODIMP_(bool)
 HTMLTextAreaElement::GetPlaceholderVisibility()
 {
   return mState.GetPlaceholderVisibility();
 }
 
 NS_IMETHODIMP_(Element*)
-HTMLTextAreaElement::CreatePreviewNode()
-{
-  NS_ENSURE_SUCCESS(mState.CreatePreviewNode(), nullptr);
-  return mState.GetPreviewNode();
-}
-
-NS_IMETHODIMP_(Element*)
 HTMLTextAreaElement::GetPreviewNode()
 {
   return mState.GetPreviewNode();
 }
 
 NS_IMETHODIMP_(void)
 HTMLTextAreaElement::SetPreviewValue(const nsAString& aValue)
 {
--- a/dom/html/HTMLTextAreaElement.h
+++ b/dom/html/HTMLTextAreaElement.h
@@ -98,19 +98,17 @@ public:
   NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
   NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() override;
   NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
   NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
   NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
   NS_IMETHOD CreateEditor() override;
   NS_IMETHOD_(Element*) GetRootEditorNode() override;
-  NS_IMETHOD_(Element*) CreatePlaceholderNode() override;
   NS_IMETHOD_(Element*) GetPlaceholderNode() override;
-  NS_IMETHOD_(Element*) CreatePreviewNode() override;
   NS_IMETHOD_(Element*) GetPreviewNode() override;
   NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override;
   NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
   NS_IMETHOD_(bool) GetPreviewVisibility() override;
   NS_IMETHOD_(void) SetPreviewValue(const nsAString& aValue) override;
   NS_IMETHOD_(void) GetPreviewValue(nsAString& aValue) override;
   NS_IMETHOD_(void) EnablePreview() override;
   NS_IMETHOD_(bool) IsPreviewEnabled() override;
--- a/dom/html/nsITextControlElement.h
+++ b/dom/html/nsITextControlElement.h
@@ -134,31 +134,21 @@ public:
   NS_IMETHOD CreateEditor() = 0;
 
   /**
    * Get the anonymous root node for the text control.
    */
   NS_IMETHOD_(mozilla::dom::Element*) GetRootEditorNode() = 0;
 
   /**
-   * Create the placeholder anonymous node for the text control and returns it.
-   */
-  NS_IMETHOD_(mozilla::dom::Element*) CreatePlaceholderNode() = 0;
-
-  /**
    * Get the placeholder anonymous node for the text control.
    */
   NS_IMETHOD_(mozilla::dom::Element*) GetPlaceholderNode() = 0;
 
   /**
-   * Create the preview anonymous node for the text control and returns it.
-   */
-  NS_IMETHOD_(mozilla::dom::Element*) CreatePreviewNode() = 0;
-
-  /**
    * Get the preview anonymous node for the text control.
    */
   NS_IMETHOD_(mozilla::dom::Element*) GetPreviewNode() = 0;
 
   /**
    * Update preview value for the text control.
    */
   NS_IMETHOD_(void) SetPreviewValue(const nsAString& aValue) = 0;
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -1181,31 +1181,48 @@ nsTextEditorState::Construct(nsITextCont
     state->mEditorInitialized = false;
     state->mInitializing = false;
     state->mValueTransferInProgress = false;
     state->mSelectionCached = true;
     state->mSelectionRestoreEagerInit = false;
     state->mPlaceholderVisibility = false;
     state->mPreviewVisibility = false;
     state->mIsCommittingComposition = false;
-    state->ClearValueCache();
     // When adding more member variable initializations here, add the same
     // also to the constructor.
     return state;
   }
 
   return new nsTextEditorState(aOwningElement);
 }
 
 nsTextEditorState::~nsTextEditorState()
 {
   MOZ_COUNT_DTOR(nsTextEditorState);
   Clear();
 }
 
+Element*
+nsTextEditorState::GetRootNode()
+{
+  return mBoundFrame ? mBoundFrame->GetRootNode() : nullptr;
+}
+
+Element*
+nsTextEditorState::GetPlaceholderNode()
+{
+  return mBoundFrame ? mBoundFrame->GetPlaceholderNode() : nullptr;
+}
+
+Element*
+nsTextEditorState::GetPreviewNode()
+{
+  return mBoundFrame ? mBoundFrame->GetPreviewNode() : nullptr;
+}
+
 void
 nsTextEditorState::Clear()
 {
   if (mBoundFrame) {
     // Oops, we still have a frame!
     // This should happen when the type of a text input control is being changed
     // to something which is not a text control.  In this case, we should pretend
     // that a frame is being destroyed, and clean up after ourselves properly.
@@ -1220,29 +1237,23 @@ nsTextEditorState::Clear()
 }
 
 void nsTextEditorState::Unlink()
 {
   nsTextEditorState* tmp = this;
   tmp->Clear();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextEditor)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootNode)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaceholderDiv)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreviewDiv)
 }
 
 void nsTextEditorState::Traverse(nsCycleCollectionTraversalCallback& cb)
 {
   nsTextEditorState* tmp = this;
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextEditor)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootNode)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaceholderDiv)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreviewDiv)
 }
 
 nsFrameSelection*
 nsTextEditorState::GetConstFrameSelection() {
   if (mSelCon)
     return mSelCon->GetConstFrameSelection();
   return nullptr;
 }
@@ -1314,25 +1325,21 @@ nsTextEditorState::BindToFrame(nsTextCon
   // binding to the frame.
   nsAutoString currentValue;
   if (mTextEditor) {
     GetValue(currentValue, true);
   }
 
   mBoundFrame = aFrame;
 
-  nsresult rv = CreateRootNode();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsIContent *rootNode = GetRootNode();
-  rv = InitializeRootNode();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell();
-  NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
+  Element* rootNode = aFrame->GetRootNode();
+  MOZ_ASSERT(rootNode);
+
+  nsIPresShell* shell = aFrame->PresContext()->GetPresShell();
+  MOZ_ASSERT(shell);
 
   // Create selection
   RefPtr<nsFrameSelection> frameSel = new nsFrameSelection();
 
   // Create a SelectionController
   mSelCon = new nsTextInputSelectionImpl(frameSel, shell, rootNode);
   MOZ_ASSERT(!mTextListener, "Should not overwrite the object");
   mTextListener = new nsTextInputListener(mTextCtrlElement);
@@ -1408,18 +1415,16 @@ nsTextEditorState::PrepareEditor(const n
   AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection());
 
   // Don't attempt to initialize recursively!
   InitializationGuard guard(*this);
   if (guard.IsInitializingRecursively()) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
-  ClearValueCache();
-
   // Note that we don't check mTextEditor here, because we might already have
   // one around, in which case we don't create a new one, and we'll just tie
   // the required machinery to it.
 
   nsPresContext *presContext = mBoundFrame->PresContext();
   nsIPresShell *shell = presContext->GetPresShell();
 
   // Setup the editor flags
@@ -2096,26 +2101,25 @@ nsTextEditorState::DestroyEditor()
   // notify the editor that we are going away
   if (mEditorInitialized) {
     if (mTextListener) {
       mTextEditor->RemoveEditorObserver(mTextListener);
     }
     mTextEditor->PreDestroy(true);
     mEditorInitialized = false;
   }
-  ClearValueCache();
 }
 
 void
 nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame)
 {
   NS_ENSURE_TRUE_VOID(mBoundFrame);
 
   // If it was, however, it should be unbounded from the same frame.
-  NS_ASSERTION(!aFrame || aFrame == mBoundFrame, "Unbinding from the wrong frame");
+  MOZ_ASSERT(aFrame == mBoundFrame, "Unbinding from the wrong frame");
   NS_ENSURE_TRUE_VOID(!aFrame || aFrame == mBoundFrame);
 
   // If the editor is modified but nsIEditorObserver::EditAction() hasn't been
   // called yet, we need to notify it here because editor may be destroyed
   // before EditAction() is called if selection listener causes flushing layout.
   if (mTextListener && mTextEditor && mEditorInitialized &&
       mTextEditor->IsInEditAction()) {
     mTextListener->EditAction();
@@ -2141,17 +2145,16 @@ nsTextEditorState::UnbindFromFrame(nsTex
     uint32_t start = 0, end = 0;
     IgnoredErrorResult rangeRv;
     GetSelectionRange(&start, &end, rangeRv);
 
     IgnoredErrorResult dirRv;
     nsITextControlFrame::SelectionDirection direction =
       GetSelectionDirection(dirRv);
 
-    MOZ_ASSERT(aFrame == mBoundFrame);
     SelectionProperties& props = GetSelectionProperties();
     props.SetStart(start);
     props.SetEnd(end);
     props.SetDirection(direction);
     HTMLInputElement* number = GetParentNumberControl(aFrame);
     if (number) {
       // If we are inside a number control, cache the selection on the
       // parent control, because this text editor state will be destroyed
@@ -2236,176 +2239,24 @@ nsTextEditorState::UnbindFromFrame(nsTex
         NS_LITERAL_STRING("keyup"),
         TrustedEventsAtSystemGroupBubble());
     }
 
     mTextListener = nullptr;
   }
 
   mBoundFrame = nullptr;
-  // Clear mRootNode so that we don't unexpectedly notify below.
-  nsCOMPtr<Element> rootNode = mRootNode.forget();
 
   // Now that we don't have a frame any more, store the value in the text buffer.
   // The only case where we don't do this is if a value transfer is in progress.
   if (!mValueTransferInProgress) {
     bool success = SetValue(value, eSetValue_Internal);
     // TODO Find something better to do if this fails...
     NS_ENSURE_TRUE_VOID(success);
   }
-
-  if (rootNode && mMutationObserver) {
-    rootNode->RemoveMutationObserver(mMutationObserver);
-    mMutationObserver = nullptr;
-  }
-
-  // Unbind the anonymous content from the tree.
-  // We actually hold a reference to the content nodes so that
-  // they're not actually destroyed.
-  aFrame->DestroyAnonymousContent(rootNode.forget());
-  aFrame->DestroyAnonymousContent(mPlaceholderDiv.forget());
-  aFrame->DestroyAnonymousContent(mPreviewDiv.forget());
-}
-
-nsresult
-nsTextEditorState::CreateRootNode()
-{
-  MOZ_ASSERT(!mRootNode);
-  MOZ_ASSERT(mBoundFrame);
-
-  nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell();
-  NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
-
-  nsIDocument *doc = shell->GetDocument();
-  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
-
-  // Now create a DIV and add it to the anonymous content child list.
-  RefPtr<mozilla::dom::NodeInfo> nodeInfo;
-  nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
-                                                 kNameSpaceID_XHTML,
-                                                 nsIDOMNode::ELEMENT_NODE);
-
-  nsresult rv = NS_NewHTMLElement(getter_AddRefs(mRootNode), nodeInfo.forget(),
-                                  NOT_FROM_PARSER);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mMutationObserver = new nsAnonDivObserver(this);
-  mRootNode->AddMutationObserver(mMutationObserver);
-  return rv;
-}
-
-nsresult
-nsTextEditorState::InitializeRootNode()
-{
-  // Make our root node editable
-  mRootNode->SetFlags(NODE_IS_EDITABLE);
-
-  // Set the necessary classes on the text control. We use class values
-  // instead of a 'style' attribute so that the style comes from a user-agent
-  // style sheet and is still applied even if author styles are disabled.
-  nsAutoString classValue;
-  classValue.AppendLiteral("anonymous-div");
-  int32_t wrapCols = GetWrapCols();
-  if (wrapCols > 0) {
-    classValue.AppendLiteral(" wrap");
-  }
-  if (!IsSingleLineTextControl()) {
-    // We can't just inherit the overflow because setting visible overflow will
-    // crash when the number of lines exceeds the height of the textarea and
-    // setting -moz-hidden-unscrollable overflow (NS_STYLE_OVERFLOW_CLIP)
-    // doesn't paint the caret for some reason.
-    const nsStyleDisplay* disp = mBoundFrame->StyleDisplay();
-    if (disp->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE &&
-        disp->mOverflowX != NS_STYLE_OVERFLOW_CLIP) {
-      classValue.AppendLiteral(" inherit-overflow");
-    }
-    classValue.AppendLiteral(" inherit-scroll-behavior");
-  }
-  nsresult rv = mRootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
-                                   classValue, false);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return mBoundFrame->UpdateValueDisplay(false);
-}
-
-Element*
-nsTextEditorState::CreateEmptyDivNode()
-{
-  MOZ_ASSERT(mBoundFrame);
-
-  nsIPresShell *shell = mBoundFrame->PresContext()->GetPresShell();
-  MOZ_ASSERT(shell);
-
-  nsIDocument *doc = shell->GetDocument();
-  MOZ_ASSERT(doc);
-
-  nsNodeInfoManager* pNodeInfoManager = doc->NodeInfoManager();
-  MOZ_ASSERT(pNodeInfoManager);
-
-  Element *element;
-
-  // Create a DIV
-  RefPtr<mozilla::dom::NodeInfo> nodeInfo;
-  nodeInfo = pNodeInfoManager->GetNodeInfo(nsGkAtoms::div, nullptr,
-                                           kNameSpaceID_XHTML,
-                                           nsIDOMNode::ELEMENT_NODE);
-
-  element = NS_NewHTMLDivElement(nodeInfo.forget());
-
-  // Create the text node for DIV
-  RefPtr<nsTextNode> textNode = new nsTextNode(pNodeInfoManager);
-  textNode->MarkAsMaybeModifiedFrequently();
-
-  element->AppendChildTo(textNode, false);
-
-  return element;
-}
-
-nsresult
-nsTextEditorState::CreatePlaceholderNode()
-{
-#ifdef DEBUG
-  {
-    nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
-    if (content) {
-      nsAutoString placeholderTxt;
-      content->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder,
-                       placeholderTxt);
-      nsContentUtils::RemoveNewlines(placeholderTxt);
-      NS_ASSERTION(!placeholderTxt.IsEmpty(), "CreatePlaceholderNode() shouldn't \
-be called if @placeholder is the empty string when trimmed from line breaks");
-    }
-  }
-#endif // DEBUG
-
-  NS_ENSURE_TRUE(!mPlaceholderDiv, NS_ERROR_UNEXPECTED);
-
-  // Create a DIV for the placeholder
-  // and add it to the anonymous content child list
-  mPlaceholderDiv = CreateEmptyDivNode();
-
-  // initialize the text
-  UpdatePlaceholderText(false);
-
-  return NS_OK;
-}
-
-nsresult
-nsTextEditorState::CreatePreviewNode()
-{
-  NS_ENSURE_TRUE(!mPreviewDiv, NS_ERROR_UNEXPECTED);
-
-  // Create a DIV for the preview
-  // and add it to the anonymous content child list
-  mPreviewDiv = CreateEmptyDivNode();
-
-  mPreviewDiv->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
-                       NS_LITERAL_STRING("preview-div"), false);
-
-  return NS_OK;
 }
 
 int32_t
 nsTextEditorState::GetMaxLength()
 {
   nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
   nsGenericHTMLElement* element =
     nsGenericHTMLElement::FromContentOrNull(content);
@@ -2430,18 +2281,18 @@ nsTextEditorState::GetValue(nsAString& a
   // been set to the editor yet.
   if (mIsCommittingComposition) {
     aValue = mValueBeingSet;
     return;
   }
 
   if (mTextEditor && mBoundFrame &&
       (mEditorInitialized || !IsSingleLineTextControl())) {
-    if (aIgnoreWrap && !mCachedValue.IsVoid()) {
-      aValue = mCachedValue;
+    if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) {
+      aValue = mBoundFrame->CachedValue();
       return;
     }
 
     aValue.Truncate(); // initialize out param
 
     uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
                       nsIDocumentEncoder::OutputPreformatted |
                       nsIDocumentEncoder::OutputPersistNBSP |
@@ -2472,19 +2323,19 @@ nsTextEditorState::GetValue(nsAString& a
       AutoNoJSAPI nojsapi;
 
       mTextEditor->OutputToString(NS_LITERAL_STRING("text/plain"), flags,
                                   aValue);
     }
     // Only when the result doesn't include line breaks caused by hard-wrap,
     // mCacheValue should cache the value.
     if (!(flags & nsIDocumentEncoder::OutputWrap)) {
-      const_cast<nsTextEditorState*>(this)->SetValueCache(aValue);
+      mBoundFrame->CacheValue(aValue);
     } else {
-      const_cast<nsTextEditorState*>(this)->ClearValueCache();
+      mBoundFrame->ClearCachedValue();
     }
   } else {
     if (!mTextCtrlElement->ValueChanged() || !mValue) {
       mTextCtrlElement->GetDefaultValueFromContent(aValue);
     } else {
       aValue = *mValue;
     }
   }
@@ -2717,17 +2568,17 @@ nsTextEditorState::SetValue(const nsAStr
           if (!mBoundFrame) {
             return SetValue(newValue, aFlags & eSetValue_Notify);
           }
           return true;
         }
 
         // The new value never includes line breaks caused by hard-wrap.
         // So, mCachedValue can always cache the new value.
-        if (!SetValueCache(newValue, fallible)) {
+        if (!mBoundFrame->CacheValue(newValue, fallible)) {
           return false;
         }
       }
     }
   } else {
     if (!mValue) {
       mValue.emplace();
     }
@@ -2766,20 +2617,20 @@ nsTextEditorState::SetValue(const nsAStr
       if (IsSelectionCached()) {
         SelectionProperties& props = GetSelectionProperties();
         props.SetIsDirty();
       }
     }
 
     // If we've reached the point where the root node has been created, we
     // can assume that it's safe to notify.
-    ValueWasChanged(!!mRootNode);
+    ValueWasChanged(!!mBoundFrame);
   }
 
-  mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mRootNode,
+  mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mBoundFrame,
                                    /* aWasInteractiveUserChange = */ false);
 
   return true;
 }
 
 bool
 nsTextEditorState::HasNonEmptyValue()
 {
@@ -2820,59 +2671,42 @@ nsTextEditorState::InitializeKeyboardEve
 
 void
 nsTextEditorState::ValueWasChanged(bool aNotify)
 {
   UpdateOverlayTextVisibility(aNotify);
 }
 
 void
-nsTextEditorState::UpdatePlaceholderText(bool aNotify)
-{
-  NS_ASSERTION(mPlaceholderDiv, "This function should not be called if "
-                                "mPlaceholderDiv isn't set");
-
-  // If we don't have a placeholder div, there's nothing to do.
-  if (!mPlaceholderDiv)
-    return;
-
-  nsAutoString placeholderValue;
-
-  nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
-  content->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholderValue);
-  nsContentUtils::RemoveNewlines(placeholderValue);
-  NS_ASSERTION(mPlaceholderDiv->GetFirstChild(), "placeholder div has no child");
-  mPlaceholderDiv->GetFirstChild()->SetText(placeholderValue, aNotify);
-}
-
-void
 nsTextEditorState::SetPreviewText(const nsAString& aValue, bool aNotify)
 {
   // If we don't have a preview div, there's nothing to do.
-  if (!mPreviewDiv)
+  Element* previewDiv = GetPreviewNode();
+  if (!previewDiv)
     return;
 
   nsAutoString previewValue(aValue);
 
   nsContentUtils::RemoveNewlines(previewValue);
-  MOZ_ASSERT(mPreviewDiv->GetFirstChild(), "preview div has no child");
-  mPreviewDiv->GetFirstChild()->SetText(previewValue, aNotify);
+  MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
+  previewDiv->GetFirstChild()->SetText(previewValue, aNotify);
 
   UpdateOverlayTextVisibility(aNotify);
 }
 
 void
 nsTextEditorState::GetPreviewText(nsAString& aValue)
 {
   // If we don't have a preview div, there's nothing to do.
-  if (!mPreviewDiv)
+  Element* previewDiv = GetPreviewNode();
+  if (!previewDiv)
     return;
 
-  MOZ_ASSERT(mPreviewDiv->GetFirstChild(), "preview div has no child");
-  const nsTextFragment *text = mPreviewDiv->GetFirstChild()->GetText();
+  MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
+  const nsTextFragment *text = previewDiv->GetFirstChild()->GetText();
 
   aValue.Truncate();
   text->AppendTo(aValue);
 }
 
 void
 nsTextEditorState::UpdateOverlayTextVisibility(bool aNotify)
 {
@@ -2904,46 +2738,8 @@ nsTextEditorState::HideSelectionIfBlurre
   }
 }
 
 bool
 nsTextEditorState::EditorHasComposition()
 {
   return mTextEditor && mTextEditor->IsIMEComposing();
 }
-
-NS_IMPL_ISUPPORTS(nsAnonDivObserver, nsIMutationObserver)
-
-void
-nsAnonDivObserver::CharacterDataChanged(nsIDocument*             aDocument,
-                                        nsIContent*              aContent,
-                                        CharacterDataChangeInfo* aInfo)
-{
-  mTextEditorState->ClearValueCache();
-}
-
-void
-nsAnonDivObserver::ContentAppended(nsIDocument* aDocument,
-                                   nsIContent*  aContainer,
-                                   nsIContent*  aFirstNewContent,
-                                   int32_t      /* unused */)
-{
-  mTextEditorState->ClearValueCache();
-}
-
-void
-nsAnonDivObserver::ContentInserted(nsIDocument* aDocument,
-                                   nsIContent*  aContainer,
-                                   nsIContent*  aChild,
-                                   int32_t      /* unused */)
-{
-  mTextEditorState->ClearValueCache();
-}
-
-void
-nsAnonDivObserver::ContentRemoved(nsIDocument* aDocument,
-                                  nsIContent*  aContainer,
-                                  nsIContent*  aChild,
-                                  int32_t      aIndexInContainer,
-                                  nsIContent*  aPreviousSibling)
-{
-  mTextEditorState->ClearValueCache();
-}
--- a/dom/html/nsTextEditorState.h
+++ b/dom/html/nsTextEditorState.h
@@ -142,20 +142,18 @@ public:
 
   void Traverse(nsCycleCollectionTraversalCallback& cb);
   void Unlink();
 
   void PrepareForReuse()
   {
     Unlink();
     mValue.reset();
-    mCachedValue.Truncate();
     mValueBeingSet.Truncate();
     mTextCtrlElement = nullptr;
-    MOZ_ASSERT(!mMutationObserver);
   }
 
   mozilla::TextEditor* GetTextEditor();
   nsISelectionController* GetSelectionController() const;
   nsFrameSelection* GetConstFrameSelection();
   nsresult BindToFrame(nsTextControlFrame* aFrame);
   void UnbindFromFrame(nsTextControlFrame* aFrame);
   nsresult PrepareEditor(const nsAString *aValue = nullptr);
@@ -195,29 +193,19 @@ public:
   bool HasNonEmptyValue();
   // The following methods are for textarea element to use whether default
   // value or not.
   // XXX We might have to add assertion when it is into editable,
   // or reconsider fixing bug 597525 to remove these.
   void EmptyValue() { if (mValue) mValue->Truncate(); }
   bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; }
 
-  nsresult CreatePlaceholderNode();
-  nsresult CreatePreviewNode();
-  mozilla::dom::Element* CreateEmptyDivNode();
-
-  mozilla::dom::Element* GetRootNode() {
-    return mRootNode;
-  }
-  mozilla::dom::Element* GetPlaceholderNode() {
-    return mPlaceholderDiv;
-  }
-  mozilla::dom::Element* GetPreviewNode() {
-    return mPreviewDiv;
-  }
+  mozilla::dom::Element* GetRootNode();
+  mozilla::dom::Element* GetPlaceholderNode();
+  mozilla::dom::Element* GetPreviewNode();
 
   bool IsSingleLineTextControl() const {
     return mTextCtrlElement->IsSingleLineTextControl();
   }
   bool IsTextArea() const {
     return mTextCtrlElement->IsTextArea();
   }
   bool IsPasswordTextControl() const {
@@ -234,54 +222,31 @@ public:
   }
 
   void UpdateOverlayTextVisibility(bool aNotify);
 
   // placeholder methods
   bool GetPlaceholderVisibility() {
     return mPlaceholderVisibility;
   }
-  void UpdatePlaceholderText(bool aNotify);
 
   // preview methods
   void SetPreviewText(const nsAString& aValue, bool aNotify);
   void GetPreviewText(nsAString& aValue);
   bool GetPreviewVisibility() {
     return mPreviewVisibility;
   }
 
   /**
    * Get the maxlength attribute
    * @param aMaxLength the value of the max length attr
    * @returns false if attr not defined
    */
   int32_t GetMaxLength();
 
-  void ClearValueCache()
-  {
-    mCachedValue.SetIsVoid(true);
-    MOZ_ASSERT(mCachedValue.IsEmpty());
-  }
-  void SetValueCache(const nsAString& aValue)
-  {
-    mCachedValue.Assign(aValue);
-    MOZ_ASSERT(!mCachedValue.IsVoid());
-  }
-  MOZ_MUST_USE bool
-  SetValueCache(const nsAString& aValue,
-                const mozilla::fallible_t& aFallible)
-  {
-    if (!mCachedValue.Assign(aValue, aFallible)) {
-      ClearValueCache();
-      return false;
-    }
-    MOZ_ASSERT(!mCachedValue.IsVoid());
-    return true;
-  }
-
   void HideSelectionIfBlurred();
 
   struct SelectionProperties {
     public:
       SelectionProperties() : mStart(0), mEnd(0),
         mDirection(nsITextControlFrame::eForward) {}
       bool IsDefault() const
       {
@@ -406,31 +371,29 @@ public:
                     uint32_t aEnd, mozilla::dom::SelectionMode aSelectMode,
                     mozilla::ErrorResult& aRv,
                     const mozilla::Maybe<uint32_t>& aSelectionStart =
                       mozilla::Nothing(),
                     const mozilla::Maybe<uint32_t>& aSelectionEnd =
                       mozilla::Nothing());
 
   void UpdateEditableState(bool aNotify) {
-    if (mRootNode) {
-      mRootNode->UpdateEditableState(aNotify);
+    if (auto* root = GetRootNode()) {
+      root->UpdateEditableState(aNotify);
     }
   }
 
 private:
   friend class RestoreSelectionState;
 
   // not copy constructible
   nsTextEditorState(const nsTextEditorState&);
   // not assignable
   void operator= (const nsTextEditorState&);
 
-  nsresult CreateRootNode();
-
   void ValueWasChanged(bool aNotify);
 
   void DestroyEditor();
   void Clear();
 
   nsresult InitializeRootNode();
 
   void FinishedRestoringSelection();
@@ -463,33 +426,22 @@ private:
     bool mGuardSet;
   };
   friend class InitializationGuard;
   friend class PrepareEditorEvent;
 
   // The text control element owns this object, and ensures that this object
   // has a smaller lifetime.
   nsITextControlElement* MOZ_NON_OWNING_REF mTextCtrlElement;
-  // mSelCon is non-null while we have an mBoundFrame.
   RefPtr<nsTextInputSelectionImpl> mSelCon;
   RefPtr<RestoreSelectionState> mRestoringSelection;
   RefPtr<mozilla::TextEditor> mTextEditor;
-  nsCOMPtr<mozilla::dom::Element> mRootNode;
-  nsCOMPtr<mozilla::dom::Element> mPlaceholderDiv;
-  nsCOMPtr<mozilla::dom::Element> mPreviewDiv;
   nsTextControlFrame* mBoundFrame;
   RefPtr<nsTextInputListener> mTextListener;
   mozilla::Maybe<nsString> mValue;
-  RefPtr<nsAnonDivObserver> mMutationObserver;
-  // Cache of the |.value| of <input> or <textarea> element without hard-wrap.
-  // If its IsVoid() returns true, it doesn't cache |.value|.
-  // Otherwise, it's cached when setting specific value or getting value from
-  // TextEditor.  Additionally, when contents in the anonymous <div> element
-  // is modified, this is cleared.
-  nsString mCachedValue;
   // mValueBeingSet is available only while SetValue() is requesting to commit
   // composition.  I.e., this is valid only while mIsCommittingComposition is
   // true.  While active composition is being committed, GetValue() needs
   // the latest value which is set by SetValue().  So, this is cache for that.
   nsString mValueBeingSet;
   SelectionProperties mSelectionProperties;
   bool mEverInited; // Have we ever been initialized?
   bool mEditorInitialized;
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -47,16 +47,17 @@
 #include "mozilla/dom/HTMLTextAreaElement.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/MathAlgorithms.h"
 #include "nsFrameSelection.h"
 
 #define DEFAULT_COLUMN_WIDTH 20
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 nsIFrame*
 NS_NewTextControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsTextControlFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsTextControlFrame)
@@ -96,27 +97,44 @@ public:
   }
   bool EnteredMoreThanOnce() const { return !mFirstEntry; }
 private:
   nsTextControlFrame &mFrame;
   bool mFirstEntry;
 };
 #endif
 
+class nsTextControlFrame::nsAnonDivObserver final : public nsStubMutationObserver
+{
+public:
+  explicit nsAnonDivObserver(nsTextControlFrame& aFrame)
+   : mFrame(aFrame) {}
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+private:
+  ~nsAnonDivObserver() {}
+  nsTextControlFrame& mFrame;
+};
+
 nsTextControlFrame::nsTextControlFrame(nsStyleContext* aContext)
   : nsContainerFrame(aContext, kClassID)
   , mFirstBaseline(NS_INTRINSIC_WIDTH_UNKNOWN)
   , mEditorHasBeenInitialized(false)
   , mIsProcessing(false)
   , mUsePlaceholder(false)
   , mUsePreview(false)
 #ifdef DEBUG
   , mInEditorInitialization(false)
 #endif
 {
+  ClearCachedValue();
 }
 
 nsTextControlFrame::~nsTextControlFrame()
 {
 }
 
 void
 nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
@@ -128,16 +146,26 @@ nsTextControlFrame::DestroyFrom(nsIFrame
   // Unbind the text editor state object from the frame.  The editor will live
   // on, but things like controllers will be released.
   nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
   NS_ASSERTION(txtCtrl, "Content not a text control element");
   txtCtrl->UnbindFromFrame(this);
 
   nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
 
+  if (mMutationObserver) {
+    mRootNode->RemoveMutationObserver(mMutationObserver);
+    mMutationObserver = nullptr;
+  }
+
+  // FIXME(emilio, bug 1400618): Do this after the child frames are destroyed.
+  DestroyAnonymousContent(mRootNode.forget());
+  DestroyAnonymousContent(mPlaceholderDiv.forget());
+  DestroyAnonymousContent(mPreviewDiv.forget());
+
   nsContainerFrame::DestroyFrom(aDestructRoot);
 }
 
 LogicalSize
 nsTextControlFrame::CalcIntrinsicSize(gfxContext* aRenderingContext,
                                       WritingMode aWM,
                                       float aFontSizeInflation) const
 {
@@ -307,130 +335,210 @@ nsTextControlFrame::EnsureEditorInitiali
 
       SetSelectionEndPoints(position, position);
     }
   }
   NS_ENSURE_STATE(weakFrame.IsAlive());
   return NS_OK;
 }
 
+static already_AddRefed<Element>
+CreateEmptyDiv(const nsTextControlFrame& aOwnerFrame)
+{
+  nsIDocument* doc = aOwnerFrame.PresContext()->Document();
+  RefPtr<mozilla::dom::NodeInfo> nodeInfo =
+    doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
+                                        kNameSpaceID_XHTML,
+                                        nsIDOMNode::ELEMENT_NODE);
+
+  RefPtr<Element> element = NS_NewHTMLDivElement(nodeInfo.forget());
+  return element.forget();
+}
+
+static already_AddRefed<Element>
+CreateEmptyDivWithTextNode(const nsTextControlFrame& aOwnerFrame)
+{
+  RefPtr<Element> element = CreateEmptyDiv(aOwnerFrame);
+
+  // Create the text node for DIV
+  RefPtr<nsTextNode> textNode =
+    new nsTextNode(element->OwnerDoc()->NodeInfoManager());
+  textNode->MarkAsMaybeModifiedFrequently();
+
+  element->AppendChildTo(textNode, false);
+
+  return element.forget();
+}
+
 nsresult
 nsTextControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
 {
-  NS_ASSERTION(mContent, "We should have a content!");
+  MOZ_ASSERT(mContent, "We should have a content!");
 
   AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
 
   nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
-  NS_ASSERTION(txtCtrl, "Content not a text control element");
+  MOZ_ASSERT(txtCtrl, "Content not a text control element");
 
-  // Bind the frame to its text control
-  nsresult rv = txtCtrl->BindToFrame(this);
+  nsresult rv = CreateRootNode();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsIContent* rootNode = txtCtrl->GetRootEditorNode();
-  NS_ENSURE_TRUE(rootNode, NS_ERROR_OUT_OF_MEMORY);
-
-  if (!aElements.AppendElement(rootNode))
-    return NS_ERROR_OUT_OF_MEMORY;
+  // Bind the frame to its text control
+  rv = txtCtrl->BindToFrame(this);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  // Do we need a placeholder node?
-  nsAutoString placeholderTxt;
-  mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder,
-                    placeholderTxt);
-  nsContentUtils::RemoveNewlines(placeholderTxt);
-  mUsePlaceholder = !placeholderTxt.IsEmpty();
-
-  // Create the placeholder anonymous content if needed.
-  if (mUsePlaceholder) {
-    Element* placeholderNode = txtCtrl->CreatePlaceholderNode();
-    NS_ENSURE_TRUE(placeholderNode, NS_ERROR_OUT_OF_MEMORY);
-
-    // Associate ::placeholder pseudo-element with the placeholder node.
-    placeholderNode->SetPseudoElementType(CSSPseudoElementType::placeholder);
-    aElements.AppendElement(placeholderNode);
-
+  aElements.AppendElement(mRootNode);
+  CreatePlaceholderIfNeeded();
+  if (mPlaceholderDiv) {
     if (!IsSingleLineTextControl()) {
       // For textareas, UpdateValueDisplay doesn't initialize the visibility
       // status of the placeholder because it returns early, so we have to
       // do that manually here.
       txtCtrl->UpdateOverlayTextVisibility(true);
     }
+    aElements.AppendElement(mPlaceholderDiv);
   }
-
-  mUsePreview = txtCtrl->IsPreviewEnabled();
-
-  if (mUsePreview) {
-    // Create the preview anonymous content if needed.
-    Element* previewNode = txtCtrl->CreatePreviewNode();
-    NS_ENSURE_TRUE(previewNode, NS_ERROR_OUT_OF_MEMORY);
-
-    aElements.AppendElement(previewNode);
+  CreatePreviewIfNeeded();
+  if (mPreviewDiv) {
+    aElements.AppendElement(mPreviewDiv);
   }
 
   rv = UpdateValueDisplay(false);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // textareas are eagerly initialized
-  bool initEagerly = !IsSingleLineTextControl();
-  if (!initEagerly) {
-    // Also, input elements which have a cached selection should get eager
-    // editor initialization.
-    nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
-    NS_ASSERTION(txtCtrl, "Content not a text control element");
-    initEagerly = txtCtrl->HasCachedSelection();
+  InitializeEagerlyIfNeeded();
+  return NS_OK;
+}
+
+bool
+nsTextControlFrame::ShouldInitializeEagerly() const
+{
+  // textareas are eagerly initialized.
+  if (!IsSingleLineTextControl()) {
+    return true;
   }
-  if (!initEagerly) {
-    nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(txtCtrl);
-    if (element) {
-      // so are input text controls with spellcheck=true
-      element->GetSpellcheck(&initEagerly);
+
+  // Also, input elements which have a cached selection should get eager
+  // editor initialization.
+  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+  if (txtCtrl->HasCachedSelection()) {
+    return true;
+  }
+
+  // So do input text controls with spellcheck=true
+  if (auto* htmlElement = nsGenericHTMLElement::FromContent(mContent)) {
+    if (htmlElement->Spellcheck()) {
+      return true;
     }
   }
 
-  if (initEagerly) {
-    NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
-                 "Someone forgot a script blocker?");
-    EditorInitializer* initializer = new EditorInitializer(this);
-    SetProperty(TextControlInitializer(), initializer);
-    nsContentUtils::AddScriptRunner(initializer);
-  }
-
-  return NS_OK;
+  return false;
 }
 
 void
-nsTextControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
-                                             uint32_t aFilter)
+nsTextControlFrame::InitializeEagerlyIfNeeded()
+{
+  MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
+             "Someone forgot a script blocker?");
+  if (!ShouldInitializeEagerly()) {
+    return;
+  }
+
+  EditorInitializer* initializer = new EditorInitializer(this);
+  SetProperty(TextControlInitializer(), initializer);
+  nsContentUtils::AddScriptRunner(initializer);
+}
+
+nsresult
+nsTextControlFrame::CreateRootNode()
 {
-  // This can be called off-main-thread during Servo traversal, so we take care
-  // to avoid QI-ing the DOM node.
-  nsITextControlElement* txtCtrl = nullptr;
-  nsIContent* content = GetContent();
-  if (content->IsHTMLElement(nsGkAtoms::input)) {
-    txtCtrl = static_cast<HTMLInputElement*>(content);
-  } else if (content->IsHTMLElement(nsGkAtoms::textarea)) {
-    txtCtrl = static_cast<HTMLTextAreaElement*>(content);
-  } else {
-    MOZ_CRASH("Unexpected content type for nsTextControlFrame");
+  MOZ_ASSERT(!mRootNode);
+
+  mRootNode = CreateEmptyDiv(*this);
+
+  mMutationObserver = new nsAnonDivObserver(*this);
+  mRootNode->AddMutationObserver(mMutationObserver);
+
+  // Make our root node editable
+  mRootNode->SetFlags(NODE_IS_EDITABLE);
+
+  // Set the necessary classes on the text control. We use class values instead
+  // of a 'style' attribute so that the style comes from a user-agent style
+  // sheet and is still applied even if author styles are disabled.
+  nsAutoString classValue;
+  classValue.AppendLiteral("anonymous-div");
+  if (GetWrapCols() > 0) {
+    classValue.AppendLiteral(" wrap");
   }
 
-  nsIContent* root = txtCtrl->GetRootEditorNode();
-  if (root) {
-    aElements.AppendElement(root);
+  if (!IsSingleLineTextControl()) {
+    // We can't just inherit the overflow because setting visible overflow will
+    // crash when the number of lines exceeds the height of the textarea and
+    // setting -moz-hidden-unscrollable overflow (NS_STYLE_OVERFLOW_CLIP)
+    // doesn't paint the caret for some reason.
+    const nsStyleDisplay* disp = StyleDisplay();
+    if (disp->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE &&
+        disp->mOverflowX != NS_STYLE_OVERFLOW_CLIP) {
+      classValue.AppendLiteral(" inherit-overflow");
+    }
+    classValue.AppendLiteral(" inherit-scroll-behavior");
+  }
+  nsresult rv = mRootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+                                   classValue, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return UpdateValueDisplay(false);
+}
+
+void
+nsTextControlFrame::CreatePlaceholderIfNeeded()
+{
+  MOZ_ASSERT(!mPlaceholderDiv);
+
+  // Do we need a placeholder node?
+  nsAutoString placeholderTxt;
+  mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholderTxt);
+  nsContentUtils::RemoveNewlines(placeholderTxt);
+  mUsePlaceholder = !placeholderTxt.IsEmpty();
+
+  // Create the placeholder anonymous content if needed.
+  if (mUsePlaceholder) {
+    mPlaceholderDiv = CreateEmptyDivWithTextNode(*this);
+    // Associate ::placeholder pseudo-element with the placeholder node.
+    mPlaceholderDiv->SetPseudoElementType(CSSPseudoElementType::placeholder);
+    mPlaceholderDiv->GetFirstChild()->SetText(placeholderTxt, false);
+  }
+}
+
+void
+nsTextControlFrame::CreatePreviewIfNeeded()
+{
+  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+  mUsePreview = txtCtrl->IsPreviewEnabled();
+
+  if (mUsePreview) {
+    mPreviewDiv = CreateEmptyDivWithTextNode(*this);
+    mPreviewDiv->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+                         NS_LITERAL_STRING("preview-div"), false);
+  }
+}
+
+void
+nsTextControlFrame::AppendAnonymousContentTo(
+  nsTArray<nsIContent*>& aElements,
+  uint32_t aFilter)
+{
+  aElements.AppendElement(mRootNode);
+
+  if (mPlaceholderDiv && !(aFilter & nsIContent::eSkipPlaceholderContent)) {
+    aElements.AppendElement(mPlaceholderDiv);
   }
 
-  nsIContent* placeholder = txtCtrl->GetPlaceholderNode();
-  if (placeholder && !(aFilter & nsIContent::eSkipPlaceholderContent)) {
-    aElements.AppendElement(placeholder);
-  }
-
-  nsIContent* preview = txtCtrl->GetPreviewNode();
-  if (preview) {
-    aElements.AppendElement(preview);
+  if (mPreviewDiv) {
+    aElements.AppendElement(mPreviewDiv);
   }
 }
 
 nscoord
 nsTextControlFrame::GetPrefISize(gfxContext* aRenderingContext)
 {
   nscoord result = 0;
   DISPLAY_PREF_WIDTH(this, result);
@@ -1152,41 +1260,38 @@ nsTextControlFrame::SetValueChanged(bool
 nsresult
 nsTextControlFrame::UpdateValueDisplay(bool aNotify,
                                        bool aBeforeEditorInit,
                                        const nsAString *aValue)
 {
   if (!IsSingleLineTextControl()) // textareas don't use this
     return NS_OK;
 
-  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
-  NS_ASSERTION(txtCtrl, "Content not a text control element");
-  nsIContent* rootNode = txtCtrl->GetRootEditorNode();
-
-  NS_PRECONDITION(rootNode, "Must have a div content\n");
+  NS_PRECONDITION(mRootNode, "Must have a div content\n");
   NS_PRECONDITION(!mEditorHasBeenInitialized,
                   "Do not call this after editor has been initialized");
-  NS_ASSERTION(!mUsePlaceholder || txtCtrl->GetPlaceholderNode(),
+  NS_ASSERTION(!mUsePlaceholder || mPlaceholderDiv,
                "A placeholder div must exist");
 
-  nsIContent *textContent = rootNode->GetChildAt(0);
+  nsIContent* textContent = mRootNode->GetChildAt(0);
   if (!textContent) {
     // Set up a textnode with our value
     RefPtr<nsTextNode> textNode =
       new nsTextNode(mContent->NodeInfo()->NodeInfoManager());
     textNode->MarkAsMaybeModifiedFrequently();
 
-    NS_ASSERTION(textNode, "Must have textcontent!\n");
-
-    rootNode->AppendChildTo(textNode, aNotify);
+    mRootNode->AppendChildTo(textNode, aNotify);
     textContent = textNode;
   }
 
   NS_ENSURE_TRUE(textContent, NS_ERROR_UNEXPECTED);
 
+  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+  MOZ_ASSERT(txtCtrl);
+
   // Get the current value of the textfield from the content.
   nsAutoString value;
   if (aValue) {
     value = *aValue;
   } else {
     txtCtrl->GetTextEditorValue(value, true);
   }
 
@@ -1196,17 +1301,17 @@ nsTextControlFrame::UpdateValueDisplay(b
   if ((mUsePlaceholder || mUsePreview) && !aBeforeEditorInit)
   {
     AutoWeakFrame weakFrame(this);
     txtCtrl->UpdateOverlayTextVisibility(aNotify);
     NS_ENSURE_STATE(weakFrame.IsAlive());
   }
 
   if (aBeforeEditorInit && value.IsEmpty()) {
-    rootNode->RemoveChildAt(0, true);
+    mRootNode->RemoveChildAt(0, true);
     return NS_OK;
   }
 
   if (!value.IsEmpty() && IsPasswordTextControl()) {
     TextEditRules::FillBufWithPWChars(&value, value.Length());
   }
   return textContent->SetText(value, aNotify);
 }
@@ -1356,8 +1461,50 @@ nsTextControlFrame::EditorInitializer::R
   // bug 682684.
   if (!mFrame) {
     return NS_ERROR_FAILURE;
   }
 
   mFrame->FinishedInitializer();
   return NS_OK;
 }
+
+NS_IMPL_ISUPPORTS(nsTextControlFrame::nsAnonDivObserver, nsIMutationObserver)
+
+void
+nsTextControlFrame::nsAnonDivObserver::CharacterDataChanged(
+  nsIDocument* aDocument,
+  nsIContent* aContent,
+  CharacterDataChangeInfo* aInfo)
+{
+  mFrame.ClearCachedValue();
+}
+
+void
+nsTextControlFrame::nsAnonDivObserver::ContentAppended(
+  nsIDocument* aDocument,
+  nsIContent* aContainer,
+  nsIContent* aFirstNewContent,
+  int32_t /* unused */)
+{
+  mFrame.ClearCachedValue();
+}
+
+void
+nsTextControlFrame::nsAnonDivObserver::ContentInserted(
+  nsIDocument* aDocument,
+  nsIContent* aContainer,
+  nsIContent* aChild,
+  int32_t /* unused */)
+{
+  mFrame.ClearCachedValue();
+}
+
+void
+nsTextControlFrame::nsAnonDivObserver::ContentRemoved(
+  nsIDocument* aDocument,
+  nsIContent* aContainer,
+  nsIContent* aChild,
+  int32_t aIndexInContainer,
+  nsIContent* aPreviousSibling)
+{
+  mFrame.ClearCachedValue();
+}
--- a/layout/forms/nsTextControlFrame.h
+++ b/layout/forms/nsTextControlFrame.h
@@ -182,16 +182,28 @@ protected:
                               nsPresContext*           aPresContext,
                               const ReflowInput& aReflowInput,
                               nsReflowStatus&          aStatus,
                               ReflowOutput& aParentDesiredSize);
 
 public: //for methods who access nsTextControlFrame directly
   void SetValueChanged(bool aValueChanged);
 
+  mozilla::dom::Element* GetRootNode() const {
+    return mRootNode;
+  }
+
+  mozilla::dom::Element* GetPlaceholderNode() const {
+    return mPlaceholderDiv;
+  }
+
+  mozilla::dom::Element* GetPreviewNode() const {
+    return mPreviewDiv;
+  }
+
   // called by the focus listener
   nsresult MaybeBeginSecureKeyboardInput();
   void MaybeEndSecureKeyboardInput();
 
 #define DEFINE_TEXTCTRL_FORWARDER(type, name)                                  \
   type name() {                                                                \
     nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent()); \
     NS_ASSERTION(txtCtrl, "Content not a text control element");               \
@@ -328,17 +340,63 @@ private:
    * nsITextControlElement::GetRootEditorNode on our content if you need that.
    */
   nsresult GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement);
 
   void FinishedInitializer() {
     DeleteProperty(TextControlInitializer());
   }
 
+  const nsAString& CachedValue() const
+  {
+    return mCachedValue;
+  }
+
+  void ClearCachedValue()
+  {
+    mCachedValue.SetIsVoid(true);
+  }
+
+  void CacheValue(const nsAString& aValue)
+  {
+    mCachedValue.Assign(aValue);
+  }
+
+  MOZ_MUST_USE bool
+  CacheValue(const nsAString& aValue, const mozilla::fallible_t& aFallible)
+  {
+    if (!mCachedValue.Assign(aValue, aFallible)) {
+      ClearCachedValue();
+      return false;
+    }
+    return true;
+  }
+
 private:
+  class nsAnonDivObserver;
+
+  nsresult CreateRootNode();
+  void CreatePlaceholderIfNeeded();
+  void CreatePreviewIfNeeded();
+  bool ShouldInitializeEagerly() const;
+  void InitializeEagerlyIfNeeded();
+
+  RefPtr<mozilla::dom::Element> mRootNode;
+  RefPtr<mozilla::dom::Element> mPlaceholderDiv;
+  RefPtr<mozilla::dom::Element> mPreviewDiv;
+  RefPtr<nsAnonDivObserver> mMutationObserver;
+  // Cache of the |.value| of <input> or <textarea> element without hard-wrap.
+  // If its IsVoid() returns true, it doesn't cache |.value|.
+  // Otherwise, it's cached when setting specific value or getting value from
+  // TextEditor.  Additionally, when contents in the anonymous <div> element
+  // is modified, this is cleared.
+  //
+  // FIXME(bug 1402545): Consider using an nsAutoString here.
+  nsString mCachedValue;
+
   // Our first baseline, or NS_INTRINSIC_WIDTH_UNKNOWN if we have a pending
   // Reflow.
   nscoord mFirstBaseline;
 
   // these packed bools could instead use the high order bits on mState, saving 4 bytes
   bool mEditorHasBeenInitialized;
   bool mIsProcessing;
   // Keep track if we have asked a placeholder node creation.