Bug 1518339 - Make user-select: auto behave like user-select: text for editing roots. r=mats
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 08 Jan 2019 20:21:12 +0000
changeset 510058 0bacb9f000cc21c69c519c5d1ad78757230fa088
parent 510057 9da0a9e7fef371221c355be429ec4ca5fa26821b
child 510059 a894d406f4ae900c0dc0f199cfee12082a389def
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmats
bugs1518339
milestone66.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 1518339 - Make user-select: auto behave like user-select: text for editing roots. r=mats This is the closest to the spec behavior, I think, and less likely to cause interop issues, but if you prefer me to stop the 'inheritance' chain at contenteditable elements or what not I can also do that. Differential Revision: https://phabricator.services.mozilla.com/D15963
dom/html/nsGenericHTMLElement.h
layout/base/tests/bug1518339-1-ref.html
layout/base/tests/bug1518339-1.html
layout/base/tests/bug1518339-2-ref.html
layout/base/tests/bug1518339-2.html
layout/base/tests/mochitest.ini
layout/base/tests/test_reftests_with_caret.html
layout/generic/nsFrame.cpp
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -844,16 +844,17 @@ class nsGenericHTMLElement : public nsGe
         kNameSpaceID_None, nsGkAtoms::contenteditable, values, eIgnoreCase);
 
     return value > 0 ? eTrue : (value == 0 ? eFalse : eInherit);
   }
 
   // Used by A, AREA, LINK, and STYLE.
   already_AddRefed<nsIURI> GetHrefURIForAnchors() const;
 
+ public:
   /**
    * Returns whether this element is an editable root. There are two types of
    * editable roots:
    *   1) the documentElement if the whole document is editable (for example for
    *      desginMode=on)
    *   2) an element that is marked editable with contentEditable=true and that
    *      doesn't have a parent or whose parent is not editable.
    * Note that this doesn't return input and textarea elements that haven't been
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1518339-1-ref.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Test reference</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div contenteditable="true">
+  Can you edit <b>me</b>?
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+  const editable = document.querySelector('div[contenteditable="true"]');
+  editable.focus();
+  synthesizeMouseAtCenter(editable.querySelector("b"), {});
+  setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1518339-1.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>contenteditable is selectable by default, even with a user-select: none ancestor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+  :root {
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    user-select: none;
+  }
+</style>
+<div contenteditable="true">
+  Can you edit <b>me</b>?
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+  const editable = document.querySelector('div[contenteditable="true"]');
+  editable.focus();
+  synthesizeMouseAtCenter(editable.querySelector("b"), {});
+  setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1518339-2-ref.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Test reference</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+  div {
+    -webkit-user-select: all;
+    -moz-user-select: all;
+    user-select: all;
+  }
+</style>
+<div contenteditable="true">
+  <div>
+    You should be able to select <b>all</b> of me.
+  </div>
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+  const editable = document.querySelector('div[contenteditable="true"]');
+  editable.focus();
+  synthesizeMouseAtCenter(editable.querySelector("b"), {});
+  setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1518339-2.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>user-select can be overriden on a contenteditable element</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+  div {
+    -webkit-user-select: all;
+    -moz-user-select: all;
+    user-select: all;
+  }
+</style>
+<div contenteditable="true">
+  You should be able to select <b>all</b> of me.
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+  const editable = document.querySelector('div[contenteditable="true"]');
+  editable.focus();
+  synthesizeMouseAtCenter(editable.querySelector("b"), {});
+  setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -359,16 +359,20 @@ support-files =
   bug1506547-1.html
   bug1506547-2.html
   bug1506547-3.html
   bug1506547-4.html
   bug1506547-5.html
   bug1506547-6.html
   bug1506547-4-ref.html
   bug1506547-5-ref.html
+  bug1518339-1.html
+  bug1518339-1-ref.html
+  bug1518339-2.html
+  bug1518339-2-ref.html
 
 [test_remote_frame.html]
 [test_resize_flush.html]
 support-files = resize_flush_iframe.html
 [test_scroll_event_ordering.html]
 [test_scroll_per_page.html]
 support-files = window_empty_document.html
 [test_scroll_selection_into_view.html]
--- a/layout/base/tests/test_reftests_with_caret.html
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -208,16 +208,18 @@ var tests = [
     [ 'bug1484094-2.html' , 'bug1484094-2-ref.html' ] ,
     [ 'bug1506547-1.html' , 'bug1506547-2.html' ] ,
     [ 'bug1506547-2.html' , 'bug1506547-3.html' ] ,
     [ 'bug1506547-4.html' , 'bug1506547-4-ref.html' ] ,
     [ 'bug1506547-5.html' , 'bug1506547-5-ref.html' ] ,
     [ 'bug1506547-6.html' , 'bug1506547-5-ref.html' ] ,
     [ 'bug1510942-1.html' , 'bug1510942-1-ref.html' ] ,
     [ 'bug1510942-2.html' , 'bug1510942-2-ref.html' ] ,
+    [ 'bug1518339-1.html' , 'bug1518339-1-ref.html' ] ,
+    [ 'bug1518339-2.html' , 'bug1518339-2-ref.html' ] ,
     function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.enabled']]}, nextTest);} ,
 ];
 
 if (!navigator.appVersion.includes("Android")) {
   tests.push([ 'bug512295-1.html' , 'bug512295-1-ref.html' ]);
   tests.push([ 'bug512295-2.html' , 'bug512295-2-ref.html' ]);
   tests.push([ 'bug923376.html'   , 'bug923376-ref.html'   ]);
   tests.push(function() {SpecialPowers.pushPrefEnv({'set': [['layout.css.overflow-clip-box.enabled', true]]}, nextTest);});
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -3926,26 +3926,51 @@ nsresult nsFrame::GetDataForTableSelecti
   if (foundCell)
     *aTarget = TableSelection::Cell;
   else if (foundTable)
     *aTarget = TableSelection::Table;
 
   return NS_OK;
 }
 
+static bool IsEditingHost(const nsIFrame* aFrame) {
+  auto* element = nsGenericHTMLElement::FromNodeOrNull(aFrame->GetContent());
+  return element && element->IsEditableRoot();
+}
+
 static StyleUserSelect UsedUserSelect(const nsIFrame* aFrame) {
   if (aFrame->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT)) {
     return StyleUserSelect::None;
   }
 
+  // Per https://drafts.csswg.org/css-ui-4/#content-selection:
+  //
+  // The computed value is the specified value, except:
+  //
+  //   1 - on editable elements where the computed value is always 'contain'
+  //       regardless of the specified value.
+  //   2 - when the specified value is auto, which computes to one of the other
+  //       values [...]
+  //
+  // See https://github.com/w3c/csswg-drafts/issues/3344 to see why we do this
+  // at used-value time instead of at computed-value time.
+  //
+  // Also, we check for auto first to allow explicitly overriding the value for
+  // the editing host.
   auto style = aFrame->StyleUIReset()->mUserSelect;
   if (style != StyleUserSelect::Auto) {
     return style;
   }
 
+  if (IsEditingHost(aFrame)) {
+    // We don't implement 'contain' itself, but we make 'text' behave as
+    // 'contain' for contenteditable elements anyway so this is ok.
+    return StyleUserSelect::Text;
+  }
+
   auto* parent = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
   return parent ? UsedUserSelect(parent) : StyleUserSelect::Text;
 }
 
 bool nsIFrame::IsSelectable(StyleUserSelect* aSelectStyle) const {
   auto style = UsedUserSelect(this);
   if (aSelectStyle) {
     *aSelectStyle = style;