Bug 612128 - document.execCommand("inserthtml") may insert HTML inside non-editable parts of a document; r=roc,bz a=bz
authorEhsan Akhgari <ehsan@mozilla.com>
Tue, 16 Nov 2010 15:45:49 -0500
changeset 58925 7cd873b260dafe8797fc094dc4cfa9ae4e74988a
parent 58924 f2f4415b3f1bcb74dfde9850a0ccb5f99d21cbfc
child 58926 3aaa0ec60f8fda0bd9671d3f5e8fbaeb7f1f08ee
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersroc, bz, bz
bugs612128
milestone2.0b8pre
Bug 612128 - document.execCommand("inserthtml") may insert HTML inside non-editable parts of a document; r=roc,bz a=bz
content/html/content/src/nsGenericHTMLElement.cpp
content/html/content/src/nsTextEditorState.cpp
editor/libeditor/base/crashtests/382527-1.html
editor/libeditor/base/nsEditor.h
editor/libeditor/html/nsHTMLDataTransfer.cpp
editor/libeditor/html/nsHTMLEditor.cpp
editor/libeditor/html/nsHTMLEditor.h
editor/libeditor/html/tests/Makefile.in
editor/libeditor/html/tests/test_bug612128.html
layout/base/nsCSSFrameConstructor.cpp
layout/generic/nsIAnonymousContentCreator.h
layout/reftests/editor/readonly-editable-ref.html
layout/reftests/editor/readonly-editable.html
layout/reftests/editor/readonly-non-editable-ref.html
layout/reftests/editor/readonly-non-editable.html
layout/reftests/editor/readwrite-editable-ref.html
layout/reftests/editor/readwrite-editable.html
layout/reftests/editor/readwrite-non-editable-ref.html
layout/reftests/editor/readwrite-non-editable.html
layout/reftests/editor/reftest.list
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -1674,43 +1674,27 @@ nsGenericHTMLElement::MapCommonAttribute
       }
     }
   }
 }
 
 void
 nsGenericHTMLFormElement::UpdateEditableFormControlState()
 {
-  // nsCSSFrameConstructor::MaybeConstructLazily is based on the logic of this
-  // function, so should be kept in sync with that.
-
   ContentEditableTristate value = GetContentEditableValue();
   if (value != eInherit) {
     SetEditableFlag(!!value);
 
     return;
   }
 
   nsIContent *parent = GetParent();
 
-  if (parent && parent->HasFlag(NODE_IS_EDITABLE)) {
-    SetEditableFlag(PR_TRUE);
-    return;
-  }
-
-  if (!IsTextControl(PR_FALSE)) {
-    SetEditableFlag(PR_FALSE);
-    return;
-  }
-
-  // If not contentEditable we still need to check the readonly attribute.
-  PRBool roState;
-  GetBoolAttr(nsGkAtoms::readonly, &roState);
-
-  SetEditableFlag(!roState);
+  PRBool inEditableSubtree = parent && parent->HasFlag(NODE_IS_EDITABLE);
+  SetEditableFlag(inEditableSubtree);
 }
 
 
 /* static */ const nsGenericHTMLElement::MappedAttributeEntry
 nsGenericHTMLElement::sCommonAttributeMap[] = {
   { &nsGkAtoms::contenteditable },
   { &nsGkAtoms::lang },
   { &nsGkAtoms::hidden },
@@ -2792,16 +2776,28 @@ nsGenericHTMLFormElement::IntrinsicState
   
   if (mForm && mForm->IsDefaultSubmitElement(this)) {
       NS_ASSERTION(IsSubmitControl(),
                    "Default submit element that isn't a submit control.");
       // We are the default submit element (:default)
       state |= NS_EVENT_STATE_DEFAULT;
   }
 
+  // Make the text controls read-write
+  if (!state.HasState(NS_EVENT_STATE_MOZ_READWRITE) &&
+      IsTextControl(PR_FALSE)) {
+    PRBool roState;
+    GetBoolAttr(nsGkAtoms::readonly, &roState);
+
+    if (!roState) {
+      state |= NS_EVENT_STATE_MOZ_READWRITE;
+      state &= ~NS_EVENT_STATE_MOZ_READONLY;
+    }
+  }
+
   return state;
 }
 
 nsGenericHTMLFormElement::FocusTristate
 nsGenericHTMLFormElement::FocusState()
 {
   // We can't be focused if we aren't in a document
   nsIDocument* doc = GetCurrentDoc();
--- a/content/html/content/src/nsTextEditorState.cpp
+++ b/content/html/content/src/nsTextEditorState.cpp
@@ -1520,16 +1520,19 @@ nsTextEditorState::CreateRootNode()
   nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nsnull,
                                                  kNameSpaceID_XHTML);
   NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
 
   nsresult rv = NS_NewHTMLElement(getter_AddRefs(mRootNode), nodeInfo.forget(),
                                   NOT_FROM_PARSER);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // 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");
   PRInt32 wrapCols = GetWrapCols();
   if (wrapCols >= 0) {
     classValue.AppendLiteral(" wrap");
--- a/editor/libeditor/base/crashtests/382527-1.html
+++ b/editor/libeditor/base/crashtests/382527-1.html
@@ -31,17 +31,19 @@ function init2()
 
 function init3()
 {
   var rng = targetDocument.createRange(); 
   rng.setStart(textNode, 1);
   rng.setEnd(textNode, 1);
   targetWindow.getSelection().addRange(rng);
 
-  targetDocument.execCommand("inserthtml", false, "<p>");
+  try {
+    targetDocument.execCommand("inserthtml", false, "<p>");
+  } catch(e) {}
   
   document.documentElement.removeAttribute("class");
 }
 
 
 </script>
 
 </head>
--- a/editor/libeditor/base/nsEditor.h
+++ b/editor/libeditor/base/nsEditor.h
@@ -508,17 +508,17 @@ public:
 
   /** returns PR_TRUE if aNode is a descendant of our root node */
   PRBool IsDescendantOfBody(nsIDOMNode *inNode);
 
   /** returns PR_TRUE if aNode is a container */
   virtual PRBool IsContainer(nsIDOMNode *aNode);
 
   /** returns PR_TRUE if aNode is an editable node */
-  PRBool IsEditable(nsIDOMNode *aNode);
+  virtual PRBool IsEditable(nsIDOMNode *aNode);
 
   virtual PRBool IsTextInDirtyFrameVisible(nsIDOMNode *aNode);
 
   /** returns PR_TRUE if aNode is a MozEditorBogus node */
   PRBool IsMozEditorBogusNode(nsIDOMNode *aNode);
 
   /** counts number of editable child nodes */
   nsresult CountEditableChildren(nsIDOMNode *aNode, PRUint32 &outCount);
--- a/editor/libeditor/html/nsHTMLDataTransfer.cpp
+++ b/editor/libeditor/html/nsHTMLDataTransfer.cpp
@@ -310,17 +310,19 @@ nsHTMLEditor::DoInsertHTMLWithContext(co
   nsCOMPtr<nsIDOMNode> targetNode, tempNode;
   PRInt32 targetOffset=0;
 
   if (!aDestNode)
   {
     // if caller didn't provide the destination/target node,
     // fetch the paste insertion point from our selection
     res = GetStartNodeAndOffset(selection, getter_AddRefs(targetNode), &targetOffset);
-    if (!targetNode) res = NS_ERROR_FAILURE;
+    if (!targetNode || !IsEditable(targetNode)) {
+      res = NS_ERROR_FAILURE;
+    }
     NS_ENSURE_SUCCESS(res, res);
   }
   else
   {
     targetNode = aDestNode;
     targetOffset = aDestOffset;
   }
 
--- a/editor/libeditor/html/nsHTMLEditor.cpp
+++ b/editor/libeditor/html/nsHTMLEditor.cpp
@@ -6025,8 +6025,27 @@ nsHTMLEditor::GetPreferredIMEState(PRUin
     *aState = nsIContent::IME_STATUS_DISABLE;
     return NS_OK;
   }
 
   // HTML editor don't prefer the CSS ime-mode because IE didn't do so too.
   *aState = nsIContent::IME_STATUS_ENABLE;
   return NS_OK;
 }
+
+PRBool
+nsHTMLEditor::IsEditable(nsIDOMNode* aNode) {
+  if (!nsPlaintextEditor::IsEditable(aNode)) {
+    return PR_FALSE;
+  }
+  nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
+  if (!node) {
+    // If what we're dealing with is not a node, then it's not editable!
+    return PR_FALSE;
+  }
+  if (node->IsElement()) {
+    // If we're dealing with an element, then ask it whether it's editable.
+    return node->IsEditable();
+  }
+  // We might be dealing with a text node for example, which we always consider
+  // to be editable.
+  return PR_TRUE;
+}
--- a/editor/libeditor/html/nsHTMLEditor.h
+++ b/editor/libeditor/html/nsHTMLEditor.h
@@ -150,16 +150,17 @@ public:
   NS_IMETHOD GetIsDocumentEditable(PRBool *aIsDocumentEditable);
   NS_IMETHOD BeginningOfDocument();
   virtual nsresult HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent);
   virtual already_AddRefed<nsIContent> GetFocusedContent();
   virtual PRBool IsActiveInDOMWindow();
   virtual already_AddRefed<nsPIDOMEventTarget> GetPIDOMEventTarget();
   virtual already_AddRefed<nsIContent> FindSelectionRoot(nsINode *aNode);
   virtual PRBool IsAcceptableInputEvent(nsIDOMEvent* aEvent);
+  virtual PRBool IsEditable(nsIDOMNode *aNode);
 
   /* ------------ nsStubMutationObserver overrides --------- */
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
 
   /* ------------ nsIEditorIMESupport overrides ------------ */
   NS_IMETHOD GetPreferredIMEState(PRUint32 *aState);
--- a/editor/libeditor/html/tests/Makefile.in
+++ b/editor/libeditor/html/tests/Makefile.in
@@ -63,16 +63,17 @@ include $(topsrcdir)/config/rules.mk
 		test_bug537046.html \
 		test_bug550434.html \
 		test_bug551704.html \
 		test_bug592592.html \
 		test_bug597784.html \
 		test_bug599322.html \
 		test_bug607584.html \
 		test_bug611182.html \
+		test_bug612128.html \
 		test_bug612447.html \
 		test_CF_HTML_clipboard.html \
 		test_contenteditable_focus.html \
 		test_contenteditable_text_input_handling.html \
 		test_htmleditor_keyevent_handling.html \
 		test_select_all_without_body.html \
 		file_select_all_without_body.html \
 		test_root_element_replacement.html \
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/html/tests/test_bug612128.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=612128
+-->
+<head>
+  <title>Test for Bug 612128</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/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=612128">Mozilla Bug 612128</a>
+<p id="display"></p>
+<div id="content">
+<input>
+<div contenteditable></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 612128 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+  document.querySelector("input").focus();
+  var threw = false;
+  try {
+    document.execCommand("inserthtml", null, "<span>f" + "oo</span>");
+  } catch (e) {
+    threw = true;
+  }
+  ok(threw, "The inserthtml command should fail");
+  is(document.querySelectorAll("span").length, 0, "No span element should be injected inside the page");
+  is(document.body.innerHTML.indexOf("f" + "oo"), -1, "No text should be injected inside the page");
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -3952,17 +3952,23 @@ nsCSSFrameConstructor::GetAnonymousConte
         aParent->NodeInfo()->Equals(nsGkAtoms::use, kNameSpaceID_SVG)) {
       content->SetFlags(NODE_IS_ANONYMOUS);
     } else
 #endif
     {
       content->SetNativeAnonymous();
     }
 
+    PRBool anonContentIsEditable = content->HasFlag(NODE_IS_EDITABLE);
     rv = content->BindToTree(mDocument, aParent, aParent, PR_TRUE);
+    // If the anonymous content creator requested that the content should be
+    // editable, honor its request.
+    if (anonContentIsEditable) {
+      content->SetFlags(NODE_IS_EDITABLE);
+    }
     if (NS_FAILED(rv)) {
       content->UnbindFromTree();
       return rv;
     }
   }
 
   return NS_OK;
 }
@@ -6191,59 +6197,40 @@ nsCSSFrameConstructor::ReframeTextIfNeed
     // NS_CREATE_FRAME_IF_NON_WHITESPACE flag)
     return;
   }
   NS_ASSERTION(!aContent->GetPrimaryFrame(),
                "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
   ContentInserted(aParentContent, aContent, nsnull, PR_FALSE);
 }
 
-// We want to disable lazy frame construction for nodes that are under an
-// editor. We use nsINode::IsEditable, but that includes inputs with type text
-// and password and textareas, which are common and aren't really editable (the
-// native anonymous content under them is what is actually editable) so we want
-// to construct frames for those lazily.
-// The logic for this check is based on
-// nsGenericHTMLFormElement::UpdateEditableFormControlState and so must be kept
-// in sync with that. The presence of the NODE_MAY_HAVE_CONTENT_EDITABLE_ATTR
-// flag only indicates a contenteditable attribute, it doesn't indicate if it
-// is true or false, so we force eager construction in some cases when the node
-// is not editable, but that should be rare.
-static inline PRBool
-IsActuallyEditable(nsIContent* aContainer, nsIContent* aChild)
-{
-  return (aChild->IsEditable() &&
-          (aContainer->IsEditable() ||
-           aChild->HasFlag(NODE_MAY_HAVE_CONTENT_EDITABLE_ATTR)));
-}
-
 // For inserts aChild should be valid, for appends it should be null.
 // Returns true if this operation can be lazy, false if not.
 PRBool
 nsCSSFrameConstructor::MaybeConstructLazily(Operation aOperation,
                                             nsIContent* aContainer,
                                             nsIContent* aChild)
 {
   if (mPresShell->GetPresContext()->IsChrome() || !aContainer ||
       aContainer->IsInNativeAnonymousSubtree() || aContainer->IsXUL()) {
     return PR_FALSE;
   }
 
   if (aOperation == CONTENTINSERT) {
     if (aChild->IsRootOfAnonymousSubtree() ||
-        aChild->IsXUL() || IsActuallyEditable(aContainer, aChild)) {
+        aChild->IsEditable() || aChild->IsXUL()) {
       return PR_FALSE;
     }
   } else { // CONTENTAPPEND
     NS_ASSERTION(aOperation == CONTENTAPPEND,
                  "operation should be either insert or append");
     for (nsIContent* child = aChild; child; child = child->GetNextSibling()) {
       NS_ASSERTION(!child->IsRootOfAnonymousSubtree(),
                    "Should be coming through the CONTENTAPPEND case");
-      if (child->IsXUL() || IsActuallyEditable(aContainer, child)) {
+      if (child->IsXUL() || child->IsEditable()) {
         return PR_FALSE;
       }
     }
   }
 
   // We can construct lazily; just need to set suitable bits in the content
   // tree.
 
--- a/layout/generic/nsIAnonymousContentCreator.h
+++ b/layout/generic/nsIAnonymousContentCreator.h
@@ -59,16 +59,20 @@ class nsIAnonymousContentCreator
 {
 public:
   NS_DECL_QUERYFRAME_TARGET(nsIAnonymousContentCreator)
 
   /**
    * Creates "native" anonymous content and adds the created content to
    * the aElements array. None of the returned elements can be nsnull.
    *
+   * If the anonymous content creator sets the editable flag on some
+   * of the elements that it creates, the flag will be applied to the node
+   * upon being bound to the document.
+   *
    * @note The returned elements are owned by this object. This object is
    *       responsible for calling UnbindFromTree on the elements it returned
    *       from CreateAnonymousContent when appropriate (i.e. before releasing
    *       them).
    */
   virtual nsresult CreateAnonymousContent(nsTArray<nsIContent*>& aElements)=0;
 
   /**
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/readonly-editable-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input>
+    <input readonly>
+    <input type=password>
+    <input type=password readonly>
+    <input type=email>
+    <input type=email readonly>
+    <textarea></textarea>
+    <textarea readonly></textarea>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/readonly-editable.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-read-write + span {
+        display: none;
+      }
+      span {
+        color: transparent; /* workaround for bug 617524 */
+        outline: 1px solid green;
+      }
+    </style>
+  </head>
+  <body contenteditable>
+    <input><span>hide me</span>
+    <input readonly><span>hide me</span>
+    <input type=password><span>hide me</span>
+    <input type=password readonly><span>hide me</span>
+    <input type=email><span>hide me</span>
+    <input type=email readonly><span>hide me</span>
+    <textarea></textarea><span>hide me</span>
+    <textarea readonly></textarea><span>hide me</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/readonly-non-editable-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span {
+        color: transparent; /* workaround for bug 617524 */
+        outline: 1px solid green;
+      }
+    </style>
+  </head>
+  <body>
+    <input><span>hide me</span>
+    <input readonly>
+    <input type=password><span>hide me</span>
+    <input type=password readonly>
+    <input type=email><span>hide me</span>
+    <input type=email readonly>
+    <textarea></textarea><span>hide me</span>
+    <textarea readonly></textarea>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/readonly-non-editable.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-read-only + span {
+        display: none;
+      }
+      span {
+        color: transparent; /* workaround for bug 617524 */
+        outline: 1px solid green;
+      }
+    </style>
+  </head>
+  <body>
+    <input><span>hide me</span>
+    <input readonly><span>hide me</span>
+    <input type=password><span>hide me</span>
+    <input type=password readonly><span>hide me</span>
+    <input type=email><span>hide me</span>
+    <input type=email readonly><span>hide me</span>
+    <textarea></textarea><span>hide me</span>
+    <textarea readonly></textarea><span>hide me</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/readwrite-editable-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input>
+    <input readonly>
+    <input type=password>
+    <input type=password readonly>
+    <input type=email>
+    <input type=email readonly>
+    <textarea></textarea>
+    <textarea readonly></textarea>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/readwrite-editable.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-read-write + span {
+        display: none;
+      }
+      span {
+        color: transparent; /* workaround for bug 617524 */
+        outline: 1px solid green;
+      }
+    </style>
+  </head>
+  <body contenteditable>
+    <input><span>hide me</span>
+    <input readonly><span>hide me</span>
+    <input type=password><span>hide me</span>
+    <input type=password readonly><span>hide me</span>
+    <input type=email><span>hide me</span>
+    <input type=email readonly><span>hide me</span>
+    <textarea></textarea><span>hide me</span>
+    <textarea readonly></textarea><span>hide me</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/readwrite-non-editable-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span {
+        color: transparent; /* workaround for bug 617524 */
+        outline: 1px solid green;
+      }
+    </style>
+  </head>
+  <body>
+    <input>
+    <input readonly><span>hide me</span>
+    <input type=password>
+    <input type=password readonly><span>hide me</span>
+    <input type=email>
+    <input type=email readonly><span>hide me</span>
+    <textarea></textarea>
+    <textarea readonly></textarea><span>hide me</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/editor/readwrite-non-editable.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      :-moz-read-write + span {
+        display: none;
+      }
+      span {
+        color: transparent; /* workaround for bug 617524 */
+        outline: 1px solid green;
+      }
+    </style>
+  </head>
+  <body>
+    <input><span>hide me</span>
+    <input readonly><span>hide me</span>
+    <input type=password><span>hide me</span>
+    <input type=password readonly><span>hide me</span>
+    <input type=email><span>hide me</span>
+    <input type=email readonly><span>hide me</span>
+    <textarea></textarea><span>hide me</span>
+    <textarea readonly></textarea><span>hide me</span>
+  </body>
+</html>
--- a/layout/reftests/editor/reftest.list
+++ b/layout/reftests/editor/reftest.list
@@ -42,8 +42,12 @@ asserts(1) == passwd-4.html passwd-ref.h
 != spellcheck-textarea-property-dynamic-override-inherit.html spellcheck-textarea-ref.html
 == caret_on_focus.html caret_on_focus-ref.html
 != caret_on_textarea_lastline.html caret_on_textarea_lastline-ref.html
 == input-text-onfocus-reframe.html input-text-onfocus-reframe-ref.html
 == input-text-notheme-onfocus-reframe.html input-text-notheme-onfocus-reframe-ref.html
 == caret_after_reframe.html caret_after_reframe-ref.html
 == nobogusnode-1.html nobogusnode-ref.html
 == nobogusnode-2.html nobogusnode-ref.html
+== readwrite-non-editable.html readwrite-non-editable-ref.html
+== readwrite-editable.html readwrite-editable-ref.html
+== readonly-non-editable.html readonly-non-editable-ref.html
+== readonly-editable.html readonly-editable-ref.html