Bug 1547869 - Make IsCommandEnabled() of HTML editor command classes return false if given editor is TextEditor r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 03 May 2019 02:15:18 +0000
changeset 531242 dfce0c346eeb8e9fbf28eb661714a243c137f293
parent 531241 68923cea77726b66ea8a7214e8307d62e5f77865
child 531243 21a63a10a2a91311ea3817ff72c2c93e242afeeb
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1547869
milestone68.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 1547869 - Make IsCommandEnabled() of HTML editor command classes return false if given editor is TextEditor r=smaug Currently, this bug does not occur actually because nobody has not accessed these command classes directly and they are registered only in command table for HTML editor. However, once rewriting `nsHTMLDocument::ExecCommand()` with these classes, its `IsCommandEnabled()` should return false if given editor is `TextEditor`. The reason why we need this fix is, when we make `ExecCommand()` call `IsCommandEnabled()` and it returns `true`, `ExecCommand()` needs to call `DoCommand()`. Then, it throws exception if given editor is not an `HTMLEditor` but the command class is only for `HTMLEditor`. This patch adds new WPT for testing whether `document.execCommand()` works with `<input>` and `<textarea>`. The behavior has not been standardized, but Chromium handles some commands even in it. So, I write the expectations from the point of view of web developers. (Chrome fails in "cut", "copy" and "removeformat" cases.) Differential Revision: https://phabricator.services.mozilla.com/D29473
editor/libeditor/HTMLEditorCommands.cpp
editor/libeditor/HTMLEditorDocumentCommands.cpp
testing/web-platform/meta/editing/other/exec-command-with-text-editor.tentative.html.ini
testing/web-platform/tests/editing/other/exec-command-with-text-editor.tentative.html
--- a/editor/libeditor/HTMLEditorCommands.cpp
+++ b/editor/libeditor/HTMLEditorCommands.cpp
@@ -46,22 +46,25 @@ static nsresult GetListState(HTMLEditor*
  * mozilla::StateUpdatingCommandBase
  *****************************************************************************/
 
 bool StateUpdatingCommandBase::IsCommandEnabled(Command aCommand,
                                                 TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
-  if (!aTextEditor->IsSelectionEditable()) {
+  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
+  if (!htmlEditor) {
+    return false;
+  }
+  if (!htmlEditor->IsSelectionEditable()) {
     return false;
   }
   if (aCommand == Command::FormatAbsolutePosition) {
-    HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
-    return htmlEditor && htmlEditor->IsAbsolutePositionEditorEnabled();
+    return htmlEditor->IsAbsolutePositionEditorEnabled();
   }
   return true;
 }
 
 nsresult StateUpdatingCommandBase::DoCommand(Command aCommand,
                                              TextEditor& aTextEditor) const {
   HTMLEditor* htmlEditor = aTextEditor.AsHTMLEditor();
   if (NS_WARN_IF(!htmlEditor)) {
@@ -103,20 +106,16 @@ nsresult StateUpdatingCommandBase::GetCo
 
 StaticRefPtr<PasteNoFormattingCommand> PasteNoFormattingCommand::sInstance;
 
 bool PasteNoFormattingCommand::IsCommandEnabled(Command aCommand,
                                                 TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
-
-  // This command is only implemented by nsIHTMLEditor, since
-  //  pasting in a plaintext editor automatically only supplies
-  //  "unformatted" text
   HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
   if (!htmlEditor) {
     return false;
   }
   return htmlEditor->CanPaste(nsIClipboard::kGlobalClipboard);
 }
 
 nsresult PasteNoFormattingCommand::DoCommand(Command aCommand,
@@ -369,39 +368,39 @@ nsresult ListItemCommand::ToggleState(ns
 StaticRefPtr<RemoveListCommand> RemoveListCommand::sInstance;
 
 bool RemoveListCommand::IsCommandEnabled(Command aCommand,
                                          TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
 
-  if (!aTextEditor->IsSelectionEditable()) {
+  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
+  if (!htmlEditor) {
+    return false;
+  }
+
+  if (!htmlEditor->IsSelectionEditable()) {
     return false;
   }
 
   // It is enabled if we are in any list type
-  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
-  if (NS_WARN_IF(!htmlEditor)) {
-    return false;
-  }
-
   bool bMixed;
   nsAutoString localName;
   nsresult rv = GetListState(MOZ_KnownLive(htmlEditor), &bMixed, localName);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
   return bMixed || !localName.IsEmpty();
 }
 
 nsresult RemoveListCommand::DoCommand(Command aCommand,
                                       TextEditor& aTextEditor) const {
   HTMLEditor* htmlEditor = aTextEditor.AsHTMLEditor();
-  if (!htmlEditor) {
+  if (NS_WARN_IF(!htmlEditor)) {
     return NS_OK;
   }
   // This removes any list type
   return htmlEditor->RemoveList(EmptyString());
 }
 
 nsresult RemoveListCommand::DoCommandParams(Command aCommand,
                                             nsCommandParams* aParams,
@@ -422,23 +421,27 @@ nsresult RemoveListCommand::GetCommandSt
 
 StaticRefPtr<IndentCommand> IndentCommand::sInstance;
 
 bool IndentCommand::IsCommandEnabled(Command aCommand,
                                      TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
-  return aTextEditor->IsSelectionEditable();
+  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
+  if (!htmlEditor) {
+    return false;
+  }
+  return htmlEditor->IsSelectionEditable();
 }
 
 nsresult IndentCommand::DoCommand(Command aCommand,
                                   TextEditor& aTextEditor) const {
   HTMLEditor* htmlEditor = aTextEditor.AsHTMLEditor();
-  if (!htmlEditor) {
+  if (NS_WARN_IF(!htmlEditor)) {
     return NS_OK;
   }
   nsresult rv = htmlEditor->IndentAsAction();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
@@ -462,23 +465,27 @@ nsresult IndentCommand::GetCommandStateP
 
 StaticRefPtr<OutdentCommand> OutdentCommand::sInstance;
 
 bool OutdentCommand::IsCommandEnabled(Command aCommand,
                                       TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
-  return aTextEditor->IsSelectionEditable();
+  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
+  if (!htmlEditor) {
+    return false;
+  }
+  return htmlEditor->IsSelectionEditable();
 }
 
 nsresult OutdentCommand::DoCommand(Command aCommand,
                                    TextEditor& aTextEditor) const {
   HTMLEditor* htmlEditor = aTextEditor.AsHTMLEditor();
-  if (!htmlEditor) {
+  if (NS_WARN_IF(!htmlEditor)) {
     return NS_OK;
   }
   nsresult rv = htmlEditor->OutdentAsAction();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
@@ -500,18 +507,22 @@ nsresult OutdentCommand::GetCommandState
  * mozilla::MultiStateCommandBase
  *****************************************************************************/
 
 bool MultiStateCommandBase::IsCommandEnabled(Command aCommand,
                                              TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
+  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
+  if (!htmlEditor) {
+    return false;
+  }
   // should be disabled sometimes, like if the current selection is an image
-  return aTextEditor->IsSelectionEditable();
+  return htmlEditor->IsSelectionEditable();
 }
 
 nsresult MultiStateCommandBase::DoCommand(Command aCommand,
                                           TextEditor& aTextEditor) const {
   NS_WARNING(
       "who is calling MultiStateCommandBase::DoCommand (no implementation)?");
   return NS_OK;
 }
@@ -1036,24 +1047,28 @@ nsresult IncreaseZIndexCommand::GetComma
 
 StaticRefPtr<RemoveStylesCommand> RemoveStylesCommand::sInstance;
 
 bool RemoveStylesCommand::IsCommandEnabled(Command aCommand,
                                            TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
+  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
+  if (!htmlEditor) {
+    return false;
+  }
   // test if we have any styles?
-  return aTextEditor->IsSelectionEditable();
+  return htmlEditor->IsSelectionEditable();
 }
 
 nsresult RemoveStylesCommand::DoCommand(Command aCommand,
                                         TextEditor& aTextEditor) const {
   HTMLEditor* htmlEditor = aTextEditor.AsHTMLEditor();
-  if (!htmlEditor) {
+  if (NS_WARN_IF(!htmlEditor)) {
     return NS_OK;
   }
   return MOZ_KnownLive(htmlEditor)->RemoveAllInlineProperties();
 }
 
 nsresult RemoveStylesCommand::DoCommandParams(Command aCommand,
                                               nsCommandParams* aParams,
                                               TextEditor& aTextEditor) const {
@@ -1073,24 +1088,28 @@ nsresult RemoveStylesCommand::GetCommand
 
 StaticRefPtr<IncreaseFontSizeCommand> IncreaseFontSizeCommand::sInstance;
 
 bool IncreaseFontSizeCommand::IsCommandEnabled(Command aCommand,
                                                TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
+  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
+  if (!htmlEditor) {
+    return false;
+  }
   // test if we are at max size?
-  return aTextEditor->IsSelectionEditable();
+  return htmlEditor->IsSelectionEditable();
 }
 
 nsresult IncreaseFontSizeCommand::DoCommand(Command aCommand,
                                             TextEditor& aTextEditor) const {
   HTMLEditor* htmlEditor = aTextEditor.AsHTMLEditor();
-  if (!htmlEditor) {
+  if (NS_WARN_IF(!htmlEditor)) {
     return NS_OK;
   }
   return MOZ_KnownLive(htmlEditor)->IncreaseFontSize();
 }
 
 nsresult IncreaseFontSizeCommand::DoCommandParams(
     Command aCommand, nsCommandParams* aParams, TextEditor& aTextEditor) const {
   return DoCommand(aCommand, aTextEditor);
@@ -1109,24 +1128,28 @@ nsresult IncreaseFontSizeCommand::GetCom
 
 StaticRefPtr<DecreaseFontSizeCommand> DecreaseFontSizeCommand::sInstance;
 
 bool DecreaseFontSizeCommand::IsCommandEnabled(Command aCommand,
                                                TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
+  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
+  if (!htmlEditor) {
+    return false;
+  }
   // test if we are at min size?
-  return aTextEditor->IsSelectionEditable();
+  return htmlEditor->IsSelectionEditable();
 }
 
 nsresult DecreaseFontSizeCommand::DoCommand(Command aCommand,
                                             TextEditor& aTextEditor) const {
   HTMLEditor* htmlEditor = aTextEditor.AsHTMLEditor();
-  if (!htmlEditor) {
+  if (NS_WARN_IF(!htmlEditor)) {
     return NS_OK;
   }
   return MOZ_KnownLive(htmlEditor)->DecreaseFontSize();
 }
 
 nsresult DecreaseFontSizeCommand::DoCommandParams(
     Command aCommand, nsCommandParams* aParams, TextEditor& aTextEditor) const {
   return DoCommand(aCommand, aTextEditor);
@@ -1145,17 +1168,21 @@ nsresult DecreaseFontSizeCommand::GetCom
 
 StaticRefPtr<InsertHTMLCommand> InsertHTMLCommand::sInstance;
 
 bool InsertHTMLCommand::IsCommandEnabled(Command aCommand,
                                          TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
-  return aTextEditor->IsSelectionEditable();
+  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
+  if (!htmlEditor) {
+    return false;
+  }
+  return htmlEditor->IsSelectionEditable();
 }
 
 nsresult InsertHTMLCommand::DoCommand(Command aCommand,
                                       TextEditor& aTextEditor) const {
   // If nsInsertHTMLCommand is called with no parameters, it was probably called
   // with an empty string parameter ''. In this case, it should act the same as
   // the delete command
   HTMLEditor* htmlEditor = aTextEditor.AsHTMLEditor();
@@ -1200,17 +1227,21 @@ nsresult InsertHTMLCommand::GetCommandSt
 
 StaticRefPtr<InsertTagCommand> InsertTagCommand::sInstance;
 
 bool InsertTagCommand::IsCommandEnabled(Command aCommand,
                                         TextEditor* aTextEditor) const {
   if (!aTextEditor) {
     return false;
   }
-  return aTextEditor->IsSelectionEditable();
+  HTMLEditor* htmlEditor = aTextEditor->AsHTMLEditor();
+  if (!htmlEditor) {
+    return false;
+  }
+  return htmlEditor->IsSelectionEditable();
 }
 
 // corresponding STATE_ATTRIBUTE is: src (img) and href (a)
 nsresult InsertTagCommand::DoCommand(Command aCommand,
                                      TextEditor& aTextEditor) const {
   nsAtom* tagName = GetTagName(aCommand);
   if (NS_WARN_IF(tagName != nsGkAtoms::hr)) {
     return NS_ERROR_NOT_IMPLEMENTED;
--- a/editor/libeditor/HTMLEditorDocumentCommands.cpp
+++ b/editor/libeditor/HTMLEditorDocumentCommands.cpp
@@ -34,31 +34,35 @@ namespace mozilla {
  *    for more than one of this type of command
  *    We check the input command param for different behavior
  *****************************************************************************/
 
 StaticRefPtr<SetDocumentStateCommand> SetDocumentStateCommand::sInstance;
 
 bool SetDocumentStateCommand::IsCommandEnabled(Command aCommand,
                                                TextEditor* aTextEditor) const {
-  // These commands are always enabled
-  return true;
+  // These commands are always enabled if given editor is an HTMLEditor.
+  return aTextEditor && aTextEditor->AsHTMLEditor();
 }
 
 nsresult SetDocumentStateCommand::DoCommand(Command aCommand,
                                             TextEditor& aTextEditor) const {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 nsresult SetDocumentStateCommand::DoCommandParams(
     Command aCommand, nsCommandParams* aParams, TextEditor& aTextEditor) const {
   if (NS_WARN_IF(!aParams)) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  if (NS_WARN_IF(!aTextEditor.AsHTMLEditor())) {
+    return NS_ERROR_FAILURE;
+  }
+
   switch (aCommand) {
     case Command::SetDocumentModified: {
       ErrorResult error;
       bool modified = aParams->GetBool(STATE_ATTRIBUTE, error);
       // Should we fail if this param wasn't set?
       // I'm not sure we should be that strict
       if (NS_WARN_IF(error.Failed())) {
         return error.StealNSResult();
@@ -212,16 +216,21 @@ nsresult SetDocumentStateCommand::GetCom
   // If the result is set to STATE_ATTRIBUTE as CString value,
   // queryCommandValue() returns the string value.
   // Otherwise, ignored.
 
   // The base editor owns most state info
   if (NS_WARN_IF(!aTextEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
+
+  if (NS_WARN_IF(!aTextEditor->AsHTMLEditor())) {
+    return NS_ERROR_FAILURE;
+  }
+
   // Always get the enabled state
   nsresult rv =
       aParams.SetBool(STATE_ENABLED, IsCommandEnabled(aCommand, aTextEditor));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   switch (aCommand) {
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/editing/other/exec-command-with-text-editor.tentative.html.ini
@@ -0,0 +1,198 @@
+[exec-command-with-text-editor.tentative.html]
+  [In <input>, execCommand("cut", false, null), ab\[\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("cut", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("copy", false, null), abc\[\]d): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("copy", false, null), a\[bc\]d): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("delete", false, null), ab\[\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("delete", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("forwarddelete", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("forwarddelete", false, null), a\[\]bc): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("selectall", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("undo", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("redo", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("inserthtml", false, <b>inserted</b>), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("inserttext", false, **inserted**), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("insertparagraph", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input>, execCommand("insertlinebreak", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("cut", false, null), ab\[\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("cut", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("copy", false, null), abc\[\]d): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("copy", false, null), a\[bc\]d): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("delete", false, null), ab\[\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("delete", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("forwarddelete", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("forwarddelete", false, null), a\[\]bc): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("selectall", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("undo", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("redo", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("inserthtml", false, <b>inserted</b>), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("inserttext", false, **inserted**), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("insertparagraph", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea>, execCommand("insertlinebreak", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("bold", false, bold), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("italic", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("underline", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("strikethrough", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("superscript", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("cut", false, null), ab\[\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("cut", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("copy", false, null), abc\[\]d): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("copy", false, null), a\[bc\]d): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("delete", false, null), ab\[\]c): checking value and selection after execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("delete", false, null), ab\[\]c): checking input event]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("delete", false, null), a\[b\]c): checking value and selection after execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("delete", false, null), a\[b\]c): checking input event]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("forwarddelete", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("forwarddelete", false, null), a\[\]bc): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("selectall", false, null), a\[b\]c): checking value and selection after execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("redo", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("inserthtml", false, <b>inserted</b>), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("inserttext", false, **inserted**), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("insertparagraph", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <input> in contenteditable, execCommand("insertlinebreak", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("cut", false, null), ab\[\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("cut", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("copy", false, null), abc\[\]d): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("copy", false, null), a\[bc\]d): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("delete", false, null), ab\[\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("delete", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("forwarddelete", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("forwarddelete", false, null), a\[\]bc): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("selectall", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("undo", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("redo", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("inserthtml", false, <b>inserted</b>), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("inserttext", false, **inserted**), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("insertparagraph", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
+
+  [In <textarea> in contenteditable, execCommand("insertlinebreak", false, null), a\[b\]c): calling execCommand()]
+    expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/editing/other/exec-command-with-text-editor.tentative.html
@@ -0,0 +1,341 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test that execCommand with &lt;input&gt; or &lt;textarea&gt;</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id="container"></div>
+<script>
+"use strict";
+
+setup({explicit_done: true});
+
+/**
+ * This test checks whether document.execCommand() does something expected or
+ * not in <input> and <textarea> with/without contenteditable parent.  Although
+ * this is not standardized even by any drafts.  So, this test uses expected
+ * values which may be expected by web developers.
+ */
+function runTests() {
+  let container = document.getElementById("container");
+  container.innerHTML = "<input id=\"target\">";
+  runTest(document.getElementById("target"), "In <input>");
+  container.innerHTML = "<textarea id=\"target\"></textarea>";
+  runTest(document.getElementById("target"), "In <textarea>");
+  container.setAttribute("contenteditable", "true");
+  container.innerHTML = "<input id=\"target\">";
+  runTest(document.getElementById("target"), "In <input> in contenteditable");
+  container.innerHTML = "<textarea id=\"target\"></textarea>";
+  runTest(document.getElementById("target"), "In <textarea> in contenteditable");
+
+  done();
+}
+
+function runTest(aTarget, aDescription) {
+  const kIsTextArea = aTarget.tagName === "TEXTAREA";
+  const kTests = [
+    /**
+     * command: command name of execCommand().
+     * param: param for the command.  i.e., the 3rd param of execCommand().
+     * value: initial value of <input> or <textarea>.  must have a pair of
+     *        "[" and "]" for specifying selection range.
+     * expectedValue: expected value of <input> or <textarea> after calling
+     *                execCommand() with command and param.  must have a
+     *                pair of "[" and "]" for specifying selection range.
+     * expectedExecCommandResult: expected bool result of execCommand().
+     * beforeinputExpected: if "beforeinput" event shouldn't be fired, set
+     *                      false.  otherwise, expected inputType value.
+     * inputExpected: if "input" event shouldn't be fired, set false.
+     *                otherwise, expected inputType value.
+     */
+    {command: "bold", param: "bold",
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "italic", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "underline", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "strikethrough", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "superscript", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    // Should return true for web apps implementing custom editor.
+    {command: "cut", param: null,
+     value: "ab[]c", expectedValue: "ab[]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "cut", param: null,
+     value: "a[b]c", expectedValue: "a[]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "deleteByCut",
+    },
+    // Should return true for web apps implementing custom editor.
+    {command: "copy", param: null,
+     value: "abc[]d", expectedValue: "abc[]d",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "copy", param: null,
+     value: "a[bc]d", expectedValue: "a[bc]d",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "paste", param: null,
+     value: "a[]c", expectedValue: "a[bc]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "insertFromPaste",
+    },
+    {command: "delete", param: null,
+     value: "ab[]c", expectedValue: "a[]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "deleteContentBackward",
+    },
+    {command: "delete", param: null,
+     value: "a[b]c", expectedValue: "a[]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "deleteContentBackward",
+    },
+    {command: "forwarddelete", param: null,
+     value: "a[b]c", expectedValue: "a[]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "deleteContentForward",
+    },
+    {command: "forwarddelete", param: null,
+     value: "a[]bc", expectedValue: "a[]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "deleteContentForward",
+    },
+    {command: "selectall", param: null,
+     value: "a[b]c", expectedValue: "[abc]",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    // Setting value should forget any transactions.
+    {command: "undo", param: null,
+     value: "[a]bc", expectedValue: "[a]bc",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "undo", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     initFunc: () => {
+       document.execCommand("delete", false, null);
+     },
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "historyUndo",
+    },
+    // Setting value should forget any transactions.
+    {command: "redo", param: null,
+     value: "[a]bc", expectedValue: "[a]bc",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "redo", param: null,
+     value: "a[b]c", expectedValue: "a[]c",
+     initFunc: () => {
+       document.execCommand("delete", false, null);
+       document.execCommand("undo", false, null);
+     },
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "historyRedo",
+    },
+    {command: "indent", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "outdent", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "backcolor", param: "#000000",
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "forecolor", param: "#000000",
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "hilitecolor", param: "#000000",
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "fontname", param: "DummyFont",
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "fontsize", param: "5",
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "increasefontsize", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "decreasefontsize", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "inserthorizontalrule", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "createlink", param: "foo.html",
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "insertimage", param: "no-image.png",
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "inserthtml", param: "<b>inserted</b>",
+     value: "a[b]c", expectedValue: "ainserted[]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "insertText",
+    },
+    {command: "inserttext", param: "**inserted**",
+     value: "a[b]c", expectedValue: "a**inserted**[]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "insertText",
+    },
+    {command: "justifyleft", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "justifyright", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "justifycenter", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "justifyfull", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "removeformat", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "unlink", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "insertorderedlist", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "insertunorderedlist", param: null,
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "insertparagraph", param: null,
+     value: "a[b]c", expectedValue: kIsTextArea ? "a\n[]c" : "a[b]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "insertParagraph",
+    },
+    {command: "insertlinebreak", param: null,
+     value: "a[b]c", expectedValue: kIsTextArea ? "a\n[]c" : "a[b]c",
+     expectedExecCommandResult: true,
+     beforeinputExpected: false, inputExpected: "insertLineBreak",
+    },
+    {command: "formatblock", param: "div",
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+    {command: "heading", param: "h1",
+     value: "a[b]c", expectedValue: "a[b]c",
+     expectedExecCommandResult: false,
+     beforeinputExpected: false, inputExpected: false,
+    },
+  ];
+
+  for (const kTest of kTests) {
+    const kDescription =
+        `${aDescription}, execCommand("${kTest.command}", false, ${kTest.param}), ${kTest.value})`;
+    if (!document.queryCommandSupported(kTest.command)) {
+      continue;
+    }
+    let value = kTest.value.replace(/[\[\]]/g, "");
+    aTarget.value = value;
+    aTarget.focus();
+    aTarget.selectionStart = kTest.value.indexOf("[");
+    aTarget.selectionEnd = kTest.value.indexOf("]") - 1;
+
+    if (kTest.initFunc) {
+      kTest.initFunc();
+    }
+
+    let beforeinput = false;
+    function onBeforeinput(event) {
+      beforeinput = event.inputType;
+    }
+    window.addEventListener("beforeinput", onBeforeinput, {capture: true});
+    let input = false;
+    function onInput(event) {
+      input = event.inputType;
+    }
+    window.addEventListener("input", onInput, {capture: true});
+    let ret;
+    test(function () {
+      ret = document.execCommand(kTest.command, false, kTest.param);
+      assert_equals(ret, kTest.expectedExecCommandResult);
+    }, `${kDescription}: calling execCommand()`);
+    if (ret == kTest.expectedExecCommandResult) {
+      test(function () {
+        let value = aTarget.value.substring(0, aTarget.selectionStart) +
+                        "[" +
+                        aTarget.value.substring(aTarget.selectionStart, aTarget.selectionEnd) +
+                        "]" +
+                        aTarget.value.substring(aTarget.selectionEnd);
+        assert_equals(value, kTest.expectedValue);
+      }, `${kDescription}: checking value and selection after execCommand()`);
+      test(function () {
+        assert_equals(beforeinput, kTest.beforeinputExpected);
+      }, `${kDescription}: checking beforeinput event`);
+      test(function () {
+        assert_equals(input, kTest.inputExpected);
+      }, `${kDescription}: checking input event`);
+    }
+    window.removeEventListener("beforeinput", onBeforeinput, {capture: true});
+    window.removeEventListener("input", onInput, {capture: true});
+  }
+}
+
+window.addEventListener("load", runTests, {once: true});
+</script>