Bug 1449564 - part 1: Disable object resizer and inline table editor in default r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 02 Apr 2018 17:26:46 +0900
changeset 830009 641d0c0f8190fc7329a47450c16901621491eb20
parent 830008 0ed4fce8ca6071a2fa61245e39deb1844558a68e
child 830010 d21676503e6642937f54219f0e3c5a36682cd426
push id118808
push usermasayuki@d-toybox.com
push dateSat, 18 Aug 2018 08:51:19 +0000
reviewersm_kato
bugs1449564
milestone63.0a1
Bug 1449564 - part 1: Disable object resizer and inline table editor in default r?m_kato Gecko supports resizers of <img> elements and <table>, <td>, <th> elements and has UI to remove existing table row or column in default. However, the other browsers don't have such UI and web apps need to disable this feature with calling both: document.execCommand("enableObjectResizing", false, false); document.execCommand("enableInlineTableEditing", false, false); for avoiding conflicting with their own features to edit such elements. Therefore, it doesn't make sense to keep enabling them in default only on Gecko. If web apps want to keep using these features, they should call: document.execCommand("enableObjectResizing", false, true); document.execCommand("enableInlineTableEditing", false, true); at initializing the editor. And also this patch fixes bugs of document.queryCommandState("enableObjectResizing") and document.queryCommandState("enableInlineTableEditing"). They always return false even after calling document.execCommand(..., false, true) since nsSetDocumentStateCommand::GetCommandStateParams() sets bool value as STATE_ATTRIBUTE. However, nsHTMLDocument::QueryCommandValue() which is the caller referring STATE_ATTRIBUTE doesn't treat it as bool value. And also those commands are related to state of document. Therefore, they should be return as bool value of STATE_ALL instead. Then, nsHTMLDocument::QueryCommandState() returns the state as expected. Note that those commands are supported only by Gecko. So, we don't need to worry about the compatibility. Finally, this patch rewrites 2 existing tests to check basic behavior of resizers and appearance of resizers. Note that this patch does not add new tests to test inline table editor since it's difficult to test the behavior with current API. Perhaps, we should add an API to nsIHTMLEditor to retrieve each anonymous elements in another bug since it requires to add wrapping API of SpecialPowers. MozReview-Commit-ID: 1FhYo5vcV60
editor/libeditor/HTMLAnonymousNodeEditor.cpp
editor/libeditor/HTMLEditor.cpp
editor/libeditor/HTMLEditor.h
editor/libeditor/HTMLEditorDocumentCommands.cpp
editor/libeditor/HTMLEditorObjectResizer.cpp
editor/libeditor/HTMLInlineTableEditor.cpp
editor/libeditor/tests/mochitest.ini
editor/libeditor/tests/test_bug640321.html
editor/libeditor/tests/test_objectResizing.html
editor/libeditor/tests/test_resizers_appearance.html
editor/libeditor/tests/test_resizers_resizing_elements.html
layout/base/tests/bug558663.html
layout/generic/test/test_image_selection.html
--- a/editor/libeditor/HTMLAnonymousNodeEditor.cpp
+++ b/editor/libeditor/HTMLAnonymousNodeEditor.cpp
@@ -289,19 +289,21 @@ HTMLEditor::DeleteRefToAnonymousNode(Man
 NS_IMETHODIMP
 HTMLEditor::CheckSelectionStateForAnonymousButtons(Selection* aSelection)
 {
   if (NS_WARN_IF(!aSelection)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // early way out if all contextual UI extensions are disabled
-  NS_ENSURE_TRUE(mIsObjectResizingEnabled ||
-      mIsAbsolutelyPositioningEnabled ||
-      mIsInlineTableEditingEnabled, NS_OK);
+  if (NS_WARN_IF(!IsObjectResizerEnabled() &&
+                 !mIsAbsolutelyPositioningEnabled &&
+                 !IsInlineTableEditorEnabled())) {
+    return NS_OK;
+  }
 
   // Don't change selection state if we're moving.
   if (mIsMoving) {
     return NS_OK;
   }
 
   // let's get the containing element of the selection
   RefPtr<Element> focusElement = GetSelectionContainerElement(*aSelection);
@@ -320,24 +322,24 @@ HTMLEditor::CheckSelectionStateForAnonym
   RefPtr<Element> absPosElement;
   if (mIsAbsolutelyPositioningEnabled) {
     // Absolute Positioning support is enabled, is the selection contained
     // in an absolutely positioned element ?
     absPosElement = GetAbsolutelyPositionedSelectionContainer();
   }
 
   RefPtr<Element> cellElement;
-  if (mIsObjectResizingEnabled || mIsInlineTableEditingEnabled) {
+  if (IsObjectResizerEnabled() || IsInlineTableEditorEnabled()) {
     // Resizing or Inline Table Editing is enabled, we need to check if the
     // selection is contained in a table cell
     cellElement =
       GetElementOrParentByTagNameAtSelection(*aSelection, *nsGkAtoms::td);
   }
 
-  if (mIsObjectResizingEnabled && cellElement) {
+  if (IsObjectResizerEnabled() && cellElement) {
     // we are here because Resizing is enabled AND selection is contained in
     // a cell
 
     // get the enclosing table
     if (nsGkAtoms::img != focusTagAtom) {
       // the element container of the selection is not an image, so we'll show
       // the resizers around the table
       focusElement = GetEnclosingTable(cellElement);
@@ -360,34 +362,34 @@ HTMLEditor::CheckSelectionStateForAnonym
   // side effects while this code runs (bug 420439).
 
   if (mIsAbsolutelyPositioningEnabled && mAbsolutelyPositionedObject &&
       absPosElement != mAbsolutelyPositionedObject) {
     HideGrabber();
     NS_ASSERTION(!mAbsolutelyPositionedObject, "HideGrabber failed");
   }
 
-  if (mIsObjectResizingEnabled && mResizedObject &&
+  if (IsObjectResizerEnabled() && mResizedObject &&
       mResizedObject != focusElement) {
     nsresult rv = HideResizers();
     NS_ENSURE_SUCCESS(rv, rv);
     NS_ASSERTION(!mResizedObject, "HideResizers failed");
   }
 
   if (mIsInlineTableEditingEnabled && mInlineEditedCell &&
       mInlineEditedCell != cellElement) {
     nsresult rv = HideInlineTableEditingUI();
     NS_ENSURE_SUCCESS(rv, rv);
     NS_ASSERTION(!mInlineEditedCell, "HideInlineTableEditingUI failed");
   }
 
   // now, let's display all contextual UI for good
   nsIContent* hostContent = GetActiveEditingHost();
 
-  if (mIsObjectResizingEnabled && focusElement &&
+  if (IsObjectResizerEnabled() && focusElement &&
       IsModifiableNode(*focusElement) && focusElement != hostContent) {
     if (nsGkAtoms::img == focusTagAtom) {
       mResizedObjectIsAnImage = true;
     }
     if (mResizedObject) {
       nsresult rv = RefreshResizers();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -111,28 +111,28 @@ HTMLEditor::InsertNodeIntoProperAncestor
               const EditorRawDOMPoint& aPointToInsert,
               SplitAtEdges aSplitAtEdges);
 
 HTMLEditor::HTMLEditor()
   : mCRInParagraphCreatesParagraph(false)
   , mCSSAware(false)
   , mSelectedCellIndex(0)
   , mHasShownResizers(false)
-  , mIsObjectResizingEnabled(true)
+  , mIsObjectResizingEnabled(false)
   , mIsResizing(false)
   , mPreserveRatio(false)
   , mResizedObjectIsAnImage(false)
   , mIsAbsolutelyPositioningEnabled(true)
   , mResizedObjectIsAbsolutelyPositioned(false)
   , mHasShownGrabber(false)
   , mGrabberClicked(false)
   , mIsMoving(false)
   , mSnapToGridEnabled(false)
   , mHasShownInlineTableEditor(false)
-  , mIsInlineTableEditingEnabled(true)
+  , mIsInlineTableEditingEnabled(false)
   , mOriginalX(0)
   , mOriginalY(0)
   , mResizedObjectX(0)
   , mResizedObjectY(0)
   , mResizedObjectWidth(0)
   , mResizedObjectHeight(0)
   , mResizedObjectMarginLeft(0)
   , mResizedObjectMarginTop(0)
@@ -3075,18 +3075,20 @@ HTMLEditor::AddOverrideStyleSheetInterna
     return rv;
   }
 
   // We MUST ONLY load synchronous local files (no @import)
   // XXXbz Except this will actually try to load remote files
   // synchronously, of course..
   RefPtr<StyleSheet> sheet;
   // Editor override style sheets may want to style Gecko anonymous boxes
-  rv = presShell->GetDocument()->CSSLoader()->
-    LoadSheetSync(uaURI, css::eAgentSheetFeatures, true, &sheet);
+  DebugOnly<nsresult> ignoredRv =
+    presShell->GetDocument()->CSSLoader()->
+      LoadSheetSync(uaURI, css::eAgentSheetFeatures, true, &sheet);
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(ignoredRv), "LoadSheetSync() failed");
 
   // Synchronous loads should ALWAYS return completed
   if (NS_WARN_IF(!sheet)) {
     return NS_ERROR_FAILURE;
   }
 
   // Add the override style sheet
   // (This checks if already exists)
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -216,16 +216,42 @@ public:
   nsresult OnMouseUp(int32_t aX, int32_t aY, Element* aTarget);
 
   /**
    * event callback when the mouse pointer is moved
    * @param aMouseEvent [IN] the event
    */
   nsresult OnMouseMove(dom::MouseEvent* aMouseEvent);
 
+  /**
+   * Enable/disable object resizers for <img> elements, <table> elements,
+   * absolute positioned elements (required absolute position editor enabled).
+   */
+  void EnableObjectResizer(bool aEnable)
+  {
+    mIsObjectResizingEnabled = aEnable;
+  }
+  bool IsObjectResizerEnabled() const
+  {
+    return mIsObjectResizingEnabled;
+  }
+
+  /**
+   * Enable/disable inline table editor, e.g., adding new row or column,
+   * removing existing row or column.
+   */
+  void EnableInlineTableEditor(bool aEnable)
+  {
+    mIsInlineTableEditingEnabled = aEnable;
+  }
+  bool IsInlineTableEditorEnabled() const
+  {
+    return mIsInlineTableEditingEnabled;
+  }
+
   // non-virtual methods of interface methods
   bool AbsolutePositioningEnabled() const
   {
     return mIsAbsolutelyPositioningEnabled;
   }
 
   /**
    * returns the deepest absolutely positioned container of the selection
--- a/editor/libeditor/HTMLEditorDocumentCommands.cpp
+++ b/editor/libeditor/HTMLEditorDocumentCommands.cpp
@@ -339,52 +339,52 @@ SetDocumentStateCommand::DoCommandParams
     if (NS_WARN_IF(!htmlEditor)) {
       return NS_ERROR_INVALID_ARG;
     }
     ErrorResult error;
     bool enabled = params->GetBool(STATE_ATTRIBUTE, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
-    nsresult rv = htmlEditor->SetObjectResizingEnabled(enabled);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+    htmlEditor->EnableObjectResizer(enabled);
     return NS_OK;
   }
 
   if (!nsCRT::strcmp(aCommandName, "cmd_enableInlineTableEditing")) {
     HTMLEditor* htmlEditor = textEditor->AsHTMLEditor();
     if (NS_WARN_IF(!htmlEditor)) {
       return NS_ERROR_INVALID_ARG;
     }
     ErrorResult error;
     bool enabled = params->GetBool(STATE_ATTRIBUTE, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
-    nsresult rv = htmlEditor->SetInlineTableEditingEnabled(enabled);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+    htmlEditor->EnableInlineTableEditor(enabled);
     return NS_OK;
   }
 
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 SetDocumentStateCommand::GetCommandStateParams(const char* aCommandName,
                                                nsICommandParams* aParams,
                                                nsISupports* refCon)
 {
   if (NS_WARN_IF(!aParams) || NS_WARN_IF(!refCon)) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  // If the result is set to STATE_ALL as bool value, queryCommandState()
+  // returns the bool value.
+  // 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
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (NS_WARN_IF(!editor)) {
     return NS_ERROR_INVALID_ARG;
   }
   TextEditor* textEditor = editor->AsTextEditor();
   MOZ_ASSERT(textEditor);
 
@@ -393,65 +393,74 @@ SetDocumentStateCommand::GetCommandState
   // Always get the enabled state
   bool outCmdEnabled = false;
   IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
   nsresult rv = params->SetBool(STATE_ENABLED, outCmdEnabled);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  // cmd_setDocumentModified is an internal command.
   if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentModified")) {
     bool modified;
     rv = textEditor->GetDocumentModified(&modified);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
+    // XXX Nobody refers this result due to wrong type.
     rv = params->SetBool(STATE_ATTRIBUTE, modified);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
+  // cmd_setDocumentReadOnly is a Gecko specific command, "contentReadOnly".
   if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentReadOnly")) {
+    // XXX Nobody refers this result due to wrong type.
     rv = params->SetBool(STATE_ATTRIBUTE, textEditor->IsReadonly());
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
+  // cmd_setDocumentUseCSS is a common command, "styleWithCSS".
   if (!nsCRT::strcmp(aCommandName, "cmd_setDocumentUseCSS")) {
     HTMLEditor* htmlEditor = textEditor->AsHTMLEditor();
     if (NS_WARN_IF(!htmlEditor)) {
       return NS_ERROR_INVALID_ARG;
     }
     bool isCSS;
     htmlEditor->GetIsCSSEnabled(&isCSS);
     rv = params->SetBool(STATE_ALL, isCSS);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
+  // cmd_insertBrOnReturn is a Gecko specific command, "insertBrOrReturn".
   if (!nsCRT::strcmp(aCommandName, "cmd_insertBrOnReturn")) {
     HTMLEditor* htmlEditor = textEditor->AsHTMLEditor();
     if (NS_WARN_IF(!htmlEditor)) {
       return NS_ERROR_INVALID_ARG;
     }
     bool createPOnReturn;
     htmlEditor->GetReturnInParagraphCreatesNewParagraph(&createPOnReturn);
+    // XXX Nobody refers this result due to wrong type.
     rv = params->SetBool(STATE_ATTRIBUTE, !createPOnReturn);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
+  // cmd_defaultParagraphSeparator is a common command,
+  // "defaultParagraphSeparator".
   if (!nsCRT::strcmp(aCommandName, "cmd_defaultParagraphSeparator")) {
     HTMLEditor* htmlEditor = textEditor->AsHTMLEditor();
     if (NS_WARN_IF(!htmlEditor)) {
       return NS_ERROR_INVALID_ARG;
     }
 
     switch (htmlEditor->GetDefaultParagraphSeparator()) {
       case ParagraphSeparator::div: {
@@ -476,38 +485,44 @@ SetDocumentStateCommand::GetCommandState
         return NS_OK;
       }
       default:
         MOZ_ASSERT_UNREACHABLE("Invalid paragraph separator value");
         return NS_ERROR_UNEXPECTED;
     }
   }
 
+  // cmd_enableObjectResizing is a Gecko specific command,
+  // "enableObjectResizing".
   if (!nsCRT::strcmp(aCommandName, "cmd_enableObjectResizing")) {
     HTMLEditor* htmlEditor = textEditor->AsHTMLEditor();
     if (NS_WARN_IF(!htmlEditor)) {
       return NS_ERROR_INVALID_ARG;
     }
-    bool enabled;
-    htmlEditor->GetObjectResizingEnabled(&enabled);
-    rv = params->SetBool(STATE_ATTRIBUTE, enabled);
+    // We returned the result as STATE_ATTRIBUTE with bool value 60 or earlier.
+    // So, the result was ignored by both nsHTMLDocument::QueryCommandValue()
+    // and nsHTMLDocument::QueryCommandState().
+    rv = params->SetBool(STATE_ALL, htmlEditor->IsObjectResizerEnabled());
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
+  // cmd_enableInlineTableEditing is a Gecko specific command,
+  // "enableInlineTableEditing".
   if (!nsCRT::strcmp(aCommandName, "cmd_enableInlineTableEditing")) {
     HTMLEditor* htmlEditor = textEditor->AsHTMLEditor();
     if (NS_WARN_IF(!htmlEditor)) {
       return NS_ERROR_INVALID_ARG;
     }
-    bool enabled;
-    htmlEditor->GetInlineTableEditingEnabled(&enabled);
-    rv = params->SetBool(STATE_ATTRIBUTE, enabled);
+    // We returned the result as STATE_ATTRIBUTE with bool value 60 or earlier.
+    // So, the result was ignored by both nsHTMLDocument::QueryCommandValue()
+    // and nsHTMLDocument::QueryCommandState().
+    rv = params->SetBool(STATE_ALL, htmlEditor->IsInlineTableEditorEnabled());
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
   return NS_ERROR_NOT_IMPLEMENTED;
 }
--- a/editor/libeditor/HTMLEditorObjectResizer.cpp
+++ b/editor/libeditor/HTMLEditorObjectResizer.cpp
@@ -941,20 +941,20 @@ HTMLEditor::GetResizedObject(Element** a
   RefPtr<Element> ret = mResizedObject;
   ret.forget(aResizedObject);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLEditor::GetObjectResizingEnabled(bool* aIsObjectResizingEnabled)
 {
-  *aIsObjectResizingEnabled = mIsObjectResizingEnabled;
+  *aIsObjectResizingEnabled = IsObjectResizerEnabled();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLEditor::SetObjectResizingEnabled(bool aObjectResizingEnabled)
 {
-  mIsObjectResizingEnabled = aObjectResizingEnabled;
+  EnableObjectResizer(aObjectResizingEnabled);
   return NS_OK;
 }
 
 } // namespace mozilla
--- a/editor/libeditor/HTMLInlineTableEditor.cpp
+++ b/editor/libeditor/HTMLInlineTableEditor.cpp
@@ -23,24 +23,24 @@ namespace mozilla {
 
 // Uncomment the following line if you want to disable
 // table deletion when the only column/row is removed
 // #define DISABLE_TABLE_DELETION 1
 
 NS_IMETHODIMP
 HTMLEditor::SetInlineTableEditingEnabled(bool aIsEnabled)
 {
-  mIsInlineTableEditingEnabled = aIsEnabled;
+  EnableInlineTableEditor(aIsEnabled);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLEditor::GetInlineTableEditingEnabled(bool* aIsEnabled)
 {
-  *aIsEnabled = mIsInlineTableEditingEnabled;
+  *aIsEnabled = IsInlineTableEditorEnabled();
   return NS_OK;
 }
 
 nsresult
 HTMLEditor::ShowInlineTableEditingUI(Element* aCell)
 {
   // do nothing if aCell is not a table cell...
   if (!aCell || !HTMLEditUtils::IsTableCell(aCell)) {
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -132,18 +132,16 @@ skip-if = toolkit == 'android' #TIMED_OU
 skip-if = toolkit == 'android' #bug 957797
 [test_bug625452.html]
 [test_bug629845.html]
 [test_bug635636.html]
 skip-if = os == 'android'
 [test_bug636465.html]
 skip-if = os == 'android'
 [test_bug638596.html]
-[test_bug640321.html]
-skip-if = android_version == '18' || (verify && debug && os == 'win') # bug 1147989
 [test_bug641466.html]
 [test_bug645914.html]
 [test_bug646194.html]
 [test_bug668599.html]
 [test_bug674770-1.html]
 subsuite = clipboard
 skip-if = toolkit == 'android' || verify
 [test_bug674770-2.html]
@@ -282,17 +280,19 @@ skip-if = android_version == '24'
 subsuite = clipboard
 skip-if = android_version == '24'
 [test_nsIHTMLEditor_getSelectedElement.html]
 skip-if = toolkit == 'android' && debug # bug 1480702, causes permanent failure of non-related test
 [test_nsIHTMLEditor_selectElement.html]
 skip-if = toolkit == 'android' && debug # bug 1480702, causes permanent failure of non-related test
 [test_nsIHTMLEditor_setCaretAfterElement.html]
 skip-if = toolkit == 'android' && debug # bug 1480702, causes permanent failure of non-related test
-[test_objectResizing.html]
+[test_resizers_appearance.html]
+[test_resizers_resizing_elements.html]
+skip-if = android_version == '18' || (verify && debug && os == 'win') # bug 1147989
 [test_root_element_replacement.html]
 [test_select_all_without_body.html]
 [test_spellcheck_pref.html]
 skip-if = toolkit == 'android'
 [test_undo_after_spellchecker_replaces_word.html]
 skip-if = toolkit == 'android'
 [test_undo_redo_stack_after_setting_value.html]
 [test_backspace_vs.html]
rename from editor/libeditor/tests/test_objectResizing.html
rename to editor/libeditor/tests/test_resizers_appearance.html
--- a/editor/libeditor/tests/test_objectResizing.html
+++ b/editor/libeditor/tests/test_resizers_appearance.html
@@ -1,51 +1,95 @@
 <!DOCTYPE html>
 <html>
 <head>
+  <title>Test for resizers appearance</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none;">
 
 </div>
 
-<div id="editable1" contenteditable="true">
-<table id="table1">
-<tr><td>ABCDEFG</td><td>HIJKLMN</td></tr>
-<tr><td>ABCDEFG</td><td>HIJKLMN</td></tr>
-</table>
-</div>
+<div id="editor" contenteditable></div>
+<div id="clickaway" style="width: 3px; height: 3px;"></div>
+<img src="green.png"><!-- for ensuring to load the image at first test of <img> case -->
 <pre id="test">
 
 <script class="testbody" type="application/javascript">
-SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
-  document.execCommand("enableObjectResizing", false, "true");
+"use strict";
 
-  let tableNode = document.getElementById("table1");
-  synthesizeMouseAtCenter(tableNode, {});
-  is(tableNode.getAttribute("_moz_resizing"), "true",
-     "_moz_resizing attribute should be true with object resizing");
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async function() {
+  async function waitForSelectionChange() {
+    return new Promise(resolve => {
+      document.addEventListener("selectionchange", () => {
+        resolve();
+      }, {once: true});
+    });
+  }
+
+  let editor = document.getElementById("editor");
+  let outOfEditor = document.getElementById("clickaway");
 
-  let originalHeight = tableNode.clientHeight;
-  synthesizeMouse(tableNode, 0, originalHeight, { type: "mousedown" });
-  synthesizeMouse(tableNode, 0, originalHeight + 100, { type: "mousemove" });
-  synthesizeMouse(tableNode, 0, originalHeight + 100, { type: "mouseup" });
-  isnot(originalHeight, tableNode.clientHeight,
-        "table height should be changed by dragging a resizer grip");
+  const kTests = [
+    { description: "<img>",
+      innerHTML: "<img id=\"target\" src=\"green.png\" width=\"100\" height=\"100\">",
+      resizable: true,
+    },
+    { description: "<table>",
+      innerHTML: "<table id=\"target\" border><tr><td>1-1</td><td>1-2</td></tr><tr><td>2-1</td><td>2-2</td></tr></table>",
+      resizable: true,
+    },
+    { description: "absolute positioned <div>",
+      innerHTML: "<div id=\"target\" style=\"position: absolute; top: 50px; left: 50px;\">positioned</div>",
+      resizable: true,
+    },
+    { description: "fixed positioned <div>",
+      innerHTML: "<div id=\"target\" style=\"position: fixed; top: 50px; left: 50px;\">positioned</div>",
+      resizable: false,
+    },
+    { description: "relative positioned <div>",
+      innerHTML: "<div id=\"target\" style=\"position: relative; top: 50px; left: 50px;\">positioned</div>",
+      resizable: false,
+    },
+  ];
 
-  let originalWidth = tableNode.clientWidth;
-  synthesizeMouse(tableNode, originalWidth, 0, { type: "mousedown" });
-  synthesizeMouse(tableNode, originalWidth + 100, 0, { type: "mousemove" });
-  synthesizeMouse(tableNode, originalWidth + 100, 0, { type: "mouseup" });
-  isnot(originalWidth, tableNode.clientWidth,
-        "table width should be changed by dragging a resizer grip");
+  for (const kTest of kTests) {
+    const kDescription = kTest.description + ": ";
+    editor.innerHTML = kTest.innerHTML;
+    let target = document.getElementById("target");
+
+    document.execCommand("enableObjectResizing", false, false);
+    ok(!document.queryCommandState("enableObjectResizing"),
+       kDescription + "Object resizer should be disabled by the call of execCommand");
+
+    synthesizeMouseAtCenter(outOfEditor, {});
+    let promiseSelectionChangeEvent1 = waitForSelectionChange();
+    synthesizeMouseAtCenter(target, {});
+    await promiseSelectionChangeEvent1;
+
+    ok(!target.hasAttribute("_moz_resizing"),
+       kDescription + ": While enableObjectResizing is disabled, resizers shouldn't appear");
+
+    document.execCommand("enableObjectResizing", false, true);
+    ok(document.queryCommandState("enableObjectResizing"),
+       kDescription + "Object resizer should be enabled by the call of execCommand");
+
+    synthesizeMouseAtCenter(outOfEditor, {});
+    let promiseSelectionChangeEvent2 = waitForSelectionChange();
+    synthesizeMouseAtCenter(target, {});
+    await promiseSelectionChangeEvent2;
+
+    is(target.hasAttribute("_moz_resizing"), kTest.resizable,
+       kDescription + (kTest.resizable ? "While enableObjectResizing is enabled, resizers should appear" :
+                                         "Even while enableObjectResizing is enabled, resizers shouldn't appear"));
+  }
 
   SimpleTest.finish();
 });
 </script>
 </pre>
 </body>
 </html>
rename from editor/libeditor/tests/test_bug640321.html
rename to editor/libeditor/tests/test_resizers_resizing_elements.html
--- a/editor/libeditor/tests/test_bug640321.html
+++ b/editor/libeditor/tests/test_resizers_resizing_elements.html
@@ -1,190 +1,230 @@
 <!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=640321
--->
 <head>
-  <title>Test for Bug 640321</title>
+  <title>Test for resizers of some elements</title>
   <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"/>
+  <style>
+  #target {
+    background-color: green;
+  }
+  </style>
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=640321">Mozilla Bug 640321</a>
 <p id="display"></p>
-<div id="content" contenteditable style="text-align: center">
-  <img src="green.png">
-</div>
+<div id="content" contenteditable style="width: 200px; height: 200px;"></div>
 <div id="clickaway" style="width: 10px; height: 10px"></div>
+<img src="green.png"><!-- for ensuring to load the image at first test of <img> case -->
 <pre id="test">
 <script type="application/javascript">
+"use strict";
 
-/** Test for Bug 640321 **/
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
-  var img = document.querySelector("img");
+SimpleTest.waitForFocus(async function() {
+  document.execCommand("enableObjectResizing", false, true);
+  ok(document.queryCommandState("enableObjectResizing"),
+     "Object resizer should be enabled by the call of execCommand");
+
+  let outOfEditor = document.getElementById("clickaway");
 
   function cancel(e) { e.stopPropagation(); }
-  var content = document.getElementById("content");
+  let content = document.getElementById("content");
   content.addEventListener("mousedown", cancel);
   content.addEventListener("mousemove", cancel);
   content.addEventListener("mouseup", cancel);
 
-  /**
-   * This function is a generic resizer test.
-   * We have 8 resizers that we'd like to test, and each can be moved in 8 different directions.
-   * In specifying baseX, W can be considered to be the width of the image, and for baseY, H
-   * can be considered to be the height of the image. deltaX and deltaY are regular pixel values
-   * which can be positive or negative.
-   */
-  const W = 1;
-  const H = 1;
-  function testResizer(baseX, baseY, deltaX, deltaY, expectedDeltaX, expectedDeltaY) {
-    ok(true, "testResizer(" + [baseX, baseY, deltaX, deltaY, expectedDeltaX, expectedDeltaY].join(", ") + ")");
-
-    // Reset the dimensions of the image
-    img.style.width = "100px";
-    img.style.height = "100px";
-    var rect = img.getBoundingClientRect();
-    is(rect.width, 100, "Sanity check the length");
-    is(rect.height, 100, "Sanity check the height");
-
-    // Click on the image to show the resizers
-    synthesizeMouseAtCenter(img, {});
-
-    // Determine which resizer we're dealing with
-    var basePosX = rect.width * baseX;
-    var basePosY = rect.height * baseY;
-
-    // Click on the correct resizer
-    synthesizeMouse(img, basePosX, basePosY, {type: "mousedown"});
-    // Drag it delta pixels to the right and bottom (or maybe left and top!)
-    synthesizeMouse(img, basePosX + deltaX, basePosY + deltaY, {type: "mousemove"});
-    // Release the mouse button
-    synthesizeMouse(img, basePosX + deltaX, basePosY + deltaY, {type: "mouseup"});
-    // Move the mouse delta more pixels to the same direction to make sure that the
-    // resize operation has stopped.
-    synthesizeMouse(img, basePosX + deltaX * 2, basePosY + deltaY * 2, {type: "mousemove"});
-    // Click outside of the image to hide the resizers
-    synthesizeMouseAtCenter(document.getElementById("clickaway"), {});
-
-    // Get the new dimensions for the image
-    var newRect = img.getBoundingClientRect();
-    is(newRect.width, rect.width + expectedDeltaX, "The width should be increased by " + expectedDeltaX + " pixels");
-    is(newRect.height, rect.height + expectedDeltaY, "The height should be increased by " + expectedDeltaY + "pixels");
+  async function waitForSelectionChange() {
+    return new Promise(resolve => {
+      document.addEventListener("selectionchange", () => {
+        resolve();
+      }, {once: true});
+    });
   }
 
-  function runTests(preserveRatio) {
+  async function doTest(aDescription, aPreserveRatio, aInnerHTML) {
+    let description = aDescription;
+    if (SpecialPowers.getBoolPref("editor.resizing.preserve_ratio")) {
+      description += " (preserve ratio pref is true)";
+    }
+    description += ": ";
+    content.innerHTML = aInnerHTML;
+    let target = document.getElementById("target");
+
+    /**
+     * This function is a generic resizer test.
+     * We have 8 resizers that we'd like to test, and each can be moved in 8 different directions.
+     * In specifying baseX, W can be considered to be the width of the image, and for baseY, H
+     * can be considered to be the height of the image. deltaX and deltaY are regular pixel values
+     * which can be positive or negative.
+     */
+    const W = 1;
+    const H = 1;
+    async function testResizer(baseX, baseY, deltaX, deltaY, expectedDeltaX, expectedDeltaY) {
+      ok(true, description + "testResizer(" + [baseX, baseY, deltaX, deltaY, expectedDeltaX, expectedDeltaY].join(", ") + ")");
+
+      // Reset the dimensions of the target.
+      target.style.width = "150px";
+      target.style.height = "150px";
+      let rect = target.getBoundingClientRect();
+      is(rect.width, 150, description + "Sanity check the width");
+      is(rect.height, 150, description + "Sanity check the height");
+
+      // Click on the target to show the resizers
+      let promiseSelectionChangeEvent = waitForSelectionChange();
+      synthesizeMouseAtCenter(target, {});
+      await promiseSelectionChangeEvent;
+
+      // Determine which resizer we're dealing with.
+      let basePosX = rect.width * baseX;
+      let basePosY = rect.height * baseY;
+
+      // Click on the correct resizer
+      synthesizeMouse(target, basePosX, basePosY, {type: "mousedown"});
+      // Drag it delta pixels to the right and bottom (or maybe left and top!)
+      synthesizeMouse(target, basePosX + deltaX, basePosY + deltaY, {type: "mousemove"});
+      // Release the mouse button
+      synthesizeMouse(target, basePosX + deltaX, basePosY + deltaY, {type: "mouseup"});
+      // Move the mouse delta more pixels to the same direction to make sure that the
+      // resize operation has stopped.
+      synthesizeMouse(target, basePosX + deltaX * 2, basePosY + deltaY * 2, {type: "mousemove"});
+      // Click outside of the editor to hide the resizers
+      synthesizeMouseAtCenter(outOfEditor, {});
+
+      // Get the new dimensions for the target
+      let newRect = target.getBoundingClientRect();
+      isfuzzy(newRect.width, rect.width + expectedDeltaX, 1, description + "The width should be increased by " + expectedDeltaX + " pixels");
+      isfuzzy(newRect.height, rect.height + expectedDeltaY, 1, description + "The height should be increased by " + expectedDeltaY + "pixels");
+    }
+
     // Account for changes in the resizing behavior when we're trying to preserve
-    // the aspect ration.
+    // the aspect ration of image.
     // ignoredGrowth means we don't change the size of a dimension because otherwise
     // the aspect ratio would change undesirably.
     // needlessGrowth means that we change the size of a dimension perpendecular to
     // the mouse movement axis in order to preserve the aspect ratio.
     // reversedGrowth means that we change the size of a dimension in the opposite
     // direction to the mouse movement in order to maintain the aspect ratio.
-    const ignoredGrowth = preserveRatio ? 0 : 1;
-    const needlessGrowth = preserveRatio ? 1 : 0;
-    const reversedGrowth = preserveRatio ? -1 : 1;
+    const ignoredGrowth = aPreserveRatio ? 0 : 1;
+    const needlessGrowth = aPreserveRatio ? 1 : 0;
+    const reversedGrowth = aPreserveRatio ? -1 : 1;
 
     // top resizer
-    testResizer(W/2,   0, -10, -10,   0,  10);
-    testResizer(W/2,   0, -10,   0,   0,   0);
-    testResizer(W/2,   0, -10,  10,   0, -10);
-    testResizer(W/2,   0,   0, -10,   0,  10);
-    testResizer(W/2,   0,   0,   0,   0,   0);
-    testResizer(W/2,   0,   0,  10,   0, -10);
-    testResizer(W/2,   0,  10, -10,   0,  10);
-    testResizer(W/2,   0,  10,   0,   0,   0);
-    testResizer(W/2,   0,  10,  10,   0, -10);
+    await testResizer(W/2,   0, -10, -10,   0,  10);
+    await testResizer(W/2,   0, -10,   0,   0,   0);
+    await testResizer(W/2,   0, -10,  10,   0, -10);
+    await testResizer(W/2,   0,   0, -10,   0,  10);
+    await testResizer(W/2,   0,   0,   0,   0,   0);
+    await testResizer(W/2,   0,   0,  10,   0, -10);
+    await testResizer(W/2,   0,  10, -10,   0,  10);
+    await testResizer(W/2,   0,  10,   0,   0,   0);
+    await testResizer(W/2,   0,  10,  10,   0, -10);
 
     // top right resizer
-    testResizer(  W,   0, -10, -10, -10 * reversedGrowth, 10);
-    testResizer(  W,   0, -10,   0, -10 * ignoredGrowth,   0);
-    testResizer(  W,   0, -10,  10, -10, -10);
-    testResizer(  W,   0,   0, -10,  10 * needlessGrowth,  10);
-    testResizer(  W,   0,   0,   0,   0,   0);
-    testResizer(  W,   0,   0,  10,   0, -10 * ignoredGrowth);
-    testResizer(  W,   0,  10, -10,  10,  10);
-    testResizer(  W,   0,  10,   0,  10,  10 * needlessGrowth);
-    testResizer(  W,   0,  10,  10,  10, -10 * reversedGrowth);
+    await testResizer(  W,   0, -10, -10, -10 * reversedGrowth, 10);
+    await testResizer(  W,   0, -10,   0, -10 * ignoredGrowth,   0);
+    await testResizer(  W,   0, -10,  10, -10, -10);
+    await testResizer(  W,   0,   0, -10,  10 * needlessGrowth,  10);
+    await testResizer(  W,   0,   0,   0,   0,   0);
+    await testResizer(  W,   0,   0,  10,   0, -10 * ignoredGrowth);
+    await testResizer(  W,   0,  10, -10,  10,  10);
+    await testResizer(  W,   0,  10,   0,  10,  10 * needlessGrowth);
+    await testResizer(  W,   0,  10,  10,  10, -10 * reversedGrowth);
 
     // right resizer
-    testResizer(  W, H/2, -10, -10, -10,   0);
-    testResizer(  W, H/2, -10,   0, -10,   0);
-    testResizer(  W, H/2, -10,  10, -10,   0);
-    testResizer(  W, H/2,   0, -10,   0,   0);
-    testResizer(  W, H/2,   0,   0,   0,   0);
-    testResizer(  W, H/2,   0,  10,   0,   0);
-    testResizer(  W, H/2,  10, -10,  10,   0);
-    testResizer(  W, H/2,  10,   0,  10,   0);
-    testResizer(  W, H/2,  10,  10,  10,   0);
+    await testResizer(  W, H/2, -10, -10, -10,   0);
+    await testResizer(  W, H/2, -10,   0, -10,   0);
+    await testResizer(  W, H/2, -10,  10, -10,   0);
+    await testResizer(  W, H/2,   0, -10,   0,   0);
+    await testResizer(  W, H/2,   0,   0,   0,   0);
+    await testResizer(  W, H/2,   0,  10,   0,   0);
+    await testResizer(  W, H/2,  10, -10,  10,   0);
+    await testResizer(  W, H/2,  10,   0,  10,   0);
+    await testResizer(  W, H/2,  10,  10,  10,   0);
 
     // bottom right resizer
-    testResizer(  W,   H, -10, -10, -10, -10);
-    testResizer(  W,   H, -10,   0, -10 * ignoredGrowth,   0);
-    testResizer(  W,   H, -10,  10, -10 * reversedGrowth,  10);
-    testResizer(  W,   H,   0, -10,   0, -10 * ignoredGrowth);
-    testResizer(  W,   H,   0,   0,   0,   0);
-    testResizer(  W,   H,   0,  10,  10 * needlessGrowth,  10);
-    testResizer(  W,   H,  10, -10,  10, -10 * reversedGrowth);
-    testResizer(  W,   H,  10,   0,  10,  10 * needlessGrowth);
-    testResizer(  W,   H,  10,  10,  10,  10);
+    await testResizer(  W,   H, -10, -10, -10, -10);
+    await testResizer(  W,   H, -10,   0, -10 * ignoredGrowth,   0);
+    await testResizer(  W,   H, -10,  10, -10 * reversedGrowth,  10);
+    await testResizer(  W,   H,   0, -10,   0, -10 * ignoredGrowth);
+    await testResizer(  W,   H,   0,   0,   0,   0);
+    await testResizer(  W,   H,   0,  10,  10 * needlessGrowth,  10);
+    await testResizer(  W,   H,  10, -10,  10, -10 * reversedGrowth);
+    await testResizer(  W,   H,  10,   0,  10,  10 * needlessGrowth);
+    await testResizer(  W,   H,  10,  10,  10,  10);
 
     // bottom resizer
-    testResizer(W/2,   H, -10, -10,   0, -10);
-    testResizer(W/2,   H, -10,   0,   0,   0);
-    testResizer(W/2,   H, -10,  10,   0,  10);
-    testResizer(W/2,   H,   0, -10,   0, -10);
-    testResizer(W/2,   H,   0,   0,   0,   0);
-    testResizer(W/2,   H,   0,  10,   0,  10);
-    testResizer(W/2,   H,  10, -10,   0, -10);
-    testResizer(W/2,   H,  10,   0,   0,   0);
-    testResizer(W/2,   H,  10,  10,   0,  10);
+    await testResizer(W/2,   H, -10, -10,   0, -10);
+    await testResizer(W/2,   H, -10,   0,   0,   0);
+    await testResizer(W/2,   H, -10,  10,   0,  10);
+    await testResizer(W/2,   H,   0, -10,   0, -10);
+    await testResizer(W/2,   H,   0,   0,   0,   0);
+    await testResizer(W/2,   H,   0,  10,   0,  10);
+    await testResizer(W/2,   H,  10, -10,   0, -10);
+    await testResizer(W/2,   H,  10,   0,   0,   0);
+    await testResizer(W/2,   H,  10,  10,   0,  10);
 
     // bottom left resizer
-    testResizer(  0,   H, -10, -10,  10, -10 * reversedGrowth);
-    testResizer(  0,   H, -10,   0,  10,  10 * needlessGrowth);
-    testResizer(  0,   H, -10,  10,  10,  10);
-    testResizer(  0,   H,   0, -10,   0, -10 * ignoredGrowth);
-    testResizer(  0,   H,   0,   0,   0,   0);
-    testResizer(  0,   H,   0,  10,  10 * needlessGrowth,  10);
-    testResizer(  0,   H,  10, -10, -10, -10);
-    testResizer(  0,   H,  10,   0, -10 * ignoredGrowth,   0);
-    testResizer(  0,   H,  10,  10, -10 * reversedGrowth,  10);
+    await testResizer(  0,   H, -10, -10,  10, -10 * reversedGrowth);
+    await testResizer(  0,   H, -10,   0,  10,  10 * needlessGrowth);
+    await testResizer(  0,   H, -10,  10,  10,  10);
+    await testResizer(  0,   H,   0, -10,   0, -10 * ignoredGrowth);
+    await testResizer(  0,   H,   0,   0,   0,   0);
+    await testResizer(  0,   H,   0,  10,  10 * needlessGrowth,  10);
+    await testResizer(  0,   H,  10, -10, -10, -10);
+    await testResizer(  0,   H,  10,   0, -10 * ignoredGrowth,   0);
+    await testResizer(  0,   H,  10,  10, -10 * reversedGrowth,  10);
 
     // left resizer
-    testResizer(  0, H/2, -10, -10,  10,   0);
-    testResizer(  0, H/2, -10,   0,  10,   0);
-    testResizer(  0, H/2, -10,  10,  10,   0);
-    testResizer(  0, H/2,   0, -10,   0,   0);
-    testResizer(  0, H/2,   0,   0,   0,   0);
-    testResizer(  0, H/2,   0,  10,   0,   0);
-    testResizer(  0, H/2,  10, -10, -10,   0);
-    testResizer(  0, H/2,  10,   0, -10,   0);
-    testResizer(  0, H/2,  10,  10, -10,   0);
+    await testResizer(  0, H/2, -10, -10,  10,   0);
+    await testResizer(  0, H/2, -10,   0,  10,   0);
+    await testResizer(  0, H/2, -10,  10,  10,   0);
+    await testResizer(  0, H/2,   0, -10,   0,   0);
+    await testResizer(  0, H/2,   0,   0,   0,   0);
+    await testResizer(  0, H/2,   0,  10,   0,   0);
+    await testResizer(  0, H/2,  10, -10, -10,   0);
+    await testResizer(  0, H/2,  10,   0, -10,   0);
+    await testResizer(  0, H/2,  10,  10, -10,   0);
 
     // top left resizer
-    testResizer(  0,   0, -10, -10,  10,  10);
-    testResizer(  0,   0, -10,   0,  10,  10 * needlessGrowth);
-    testResizer(  0,   0, -10,  10,  10, -10 * reversedGrowth);
-    testResizer(  0,   0,   0, -10,  10 * needlessGrowth,  10);
-    testResizer(  0,   0,   0,   0,   0,   0);
-    testResizer(  0,   0,   0,  10,   0, -10 * ignoredGrowth);
-    testResizer(  0,   0,  10, -10, -10 * reversedGrowth,  10);
-    testResizer(  0,   0,  10,   0, -10 * ignoredGrowth,   0);
-    testResizer(  0,   0,  10,  10, -10, -10);
- }
- SpecialPowers.pushPrefEnv({"set": [["editor.resizing.preserve_ratio", false]]}, function() {
-     runTests(false);
-     SpecialPowers.pushPrefEnv({"set": [["editor.resizing.preserve_ratio", true]]}, function() {
-       runTests(true);
-       SimpleTest.finish();
-     });
-   });
- });
+    await testResizer(  0,   0, -10, -10,  10,  10);
+    await testResizer(  0,   0, -10,   0,  10,  10 * needlessGrowth);
+    await testResizer(  0,   0, -10,  10,  10, -10 * reversedGrowth);
+    await testResizer(  0,   0,   0, -10,  10 * needlessGrowth,  10);
+    await testResizer(  0,   0,   0,   0,   0,   0);
+    await testResizer(  0,   0,   0,  10,   0, -10 * ignoredGrowth);
+    await testResizer(  0,   0,  10, -10, -10 * reversedGrowth,  10);
+    await testResizer(  0,   0,  10,   0, -10 * ignoredGrowth,   0);
+    await testResizer(  0,   0,  10,  10, -10, -10);
+  }
 
+  const kTests = [
+    { description: "Resiziers for <img>",
+      innerHTML: "<img id=\"target\" src=\"green.png\">",
+      mayPreserveRatio: true,
+    },
+    { description: "Resiziers for <table>",
+      innerHTML: "<table id=\"target\" border><tr><td>cell</td><td>cell</td></tr></table>",
+      mayPreserveRatio: false,
+    },
+    { description: "Resiziers for absolute positioned <div>",
+      innerHTML: "<div id=\"target\" style=\"position: absolute; top: 50px; left: 50px;\">positioned</div>",
+      mayPreserveRatio: false,
+    },
+  ];
+
+  await SpecialPowers.pushPrefEnv({"set": [["editor.resizing.preserve_ratio", false]]});
+  for (const kTest of kTests) {
+    await doTest(kTest.description, false, kTest.innerHTML);
+  }
+  await SpecialPowers.pushPrefEnv({"set": [["editor.resizing.preserve_ratio", true]]});
+  for (const kTest of kTests) {
+    await doTest(kTest.description, kTest.mayPreserveRatio, kTest.innerHTML);
+  }
+  content.innerHTML = "";
+  SimpleTest.finish();
+});
 </script>
 </pre>
 </body>
 </html>
--- a/layout/base/tests/bug558663.html
+++ b/layout/base/tests/bug558663.html
@@ -30,17 +30,19 @@ function checkSnapshots(s1, s2, shouldBe
     ok(true, testName + " snapshots compare correctly");
   } else {
     ok(false, testName + " snapshots compare incorrectly. snapshot 1: " +
               res[1] + " snapshot 2: " + res[2]); 
   }
 }
 
 function runTest() {
-  document.getElementById("iframe").contentWindow.document.designMode = "on";
+  var contentDocument = document.getElementById("iframe").contentDocument;
+  contentDocument.designMode = "on";
+  contentDocument.execCommand("enableObjectResizing", false, true);
 
   // The editor requires the event loop to spin after you turn on design mode
   // before it takes effect.
   setTimeout(continueTest, 100);
 }
 
 function continueTest() {
   var win = document.getElementById("iframe").contentWindow;
--- a/layout/generic/test/test_image_selection.html
+++ b/layout/generic/test/test_image_selection.html
@@ -68,24 +68,35 @@ function step3() {
   gImage.src = src;
 }
 
 function step4() {
   gImage.removeEventListener("load", step4);
 
   gFuchsiaSelected = snapshotWindow(gIframe.contentWindow, false);
 
-  assert_different(gBlueNotSelected, gBlueSelected,
-                   "selecting image should add drag points");
+  if (gIframe.contentDocument.queryCommandState("enableObjectResizing")) {
+    assert_different(gBlueNotSelected, gBlueSelected,
+                     "selecting image should add drag points");
+  } else {
+    assert_equal(gBlueNotSelected, gBlueSelected,
+                 "selecting image should not change anything visually");
+  }
   assert_different(gBlueSelected, gFuchsiaSelected,
                    "different images should appear different");
 
   SimpleTest.finish();
 }
 
+function assert_equal(shot1, shot2, desc)
+{
+  var [correct, s1, s2] = compareSnapshots(shot1, shot2, true);
+  ok(correct, desc + (correct ? "" : "\nRESULT: " + s2));
+}
+
 function assert_different(shot1, shot2, desc)
 {
   var [correct, s1, s2] = compareSnapshots(shot1, shot2, false);
   ok(correct, desc);
 }
 
 </script>
 </pre>