Part2 of: Extend -webkit-user-select with new value "all"
authorenrica@apple.com <enrica@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Nov 2012 20:54:20 +0000 (20:54 +0000)
committerenrica@apple.com <enrica@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Nov 2012 20:54:20 +0000 (20:54 +0000)
<rdar://problem/10161404>
https://bugs.webkit.org/show_bug.cgi?id=91912

Reviewed by Ryosuke Niwa.

Source/WebCore:

The new value "all" for -webkit-user-select property gives content none-or-all selection option.
The patch was originally prepared by Alice Cheng but the approach has been changed.
The idea is to treat these elements like non editable, meaning that we should skip over them entirely
when moving the cursor and a deletion should delete the element and all its descentants at once.
The key change is in Node::rendererIsEditable where we now return false if the element style is
userSelect: all. The other change is in the way we create the selection on mouse click and dragging
over the element. In both cases we force the selection to extend over the entire element with
the user-select: all attribute.
This is currently enabled only for the Mac port.

Test: editing/selection/user-select-all-selection.html

* dom/Node.cpp: Added a parameter to isContentEditable to behave differently
when called from JavaScript. Internally isContentEditable returns false on
nodes with user-select: all style.
(WebCore::Node::isContentEditable):
(WebCore::Node::isContentRichlyEditable):
(WebCore::Node::rendererIsEditable):
(WebCore::Node::shouldUseInputMethod):
(WebCore::Node::willRespondToMouseClickEvents):
* dom/Node.h:
(WebCore::Node::rendererIsEditable):
(WebCore::Node::rendererIsRichlyEditable):
* dom/Position.cpp:
(WebCore::Position::nodeIsUserSelectAll): Added.
(WebCore::Position::rootUserSelectAllForNode): Added.
* dom/Position.h: Added static functions described above.
* editing/ApplyStyleCommand.cpp:
(WebCore::ApplyStyleCommand::removeInlineStyleFromElement): Added parameter to
isContentEditable() call.
(WebCore::ApplyStyleCommand::surroundNodeRangeWithElement): Added parameter to
isContentEditable() call.
* editing/DeleteFromTextNodeCommand.cpp:
(WebCore::DeleteFromTextNodeCommand::doApply): Added parameter to
isContentEditable() call.
* editing/FrameSelection.cpp:
(WebCore::adjustForwardPositionForUserSelectAll): New helper function.
(WebCore::adjustBackwardPositionForUserSelectAll): New helper function.
(WebCore::FrameSelection::modifyExtendingRight):
(WebCore::FrameSelection::modifyExtendingForward):
(WebCore::FrameSelection::modifyExtendingLeft):
(WebCore::FrameSelection::modifyExtendingBackward):
(WebCore::FrameSelection::modify):
(WebCore::CaretBase::invalidateCaretRect): Added parameter to
isContentEditable() call.
* editing/InsertNodeBeforeCommand.cpp:
(WebCore::InsertNodeBeforeCommand::doApply): Ditto.
(WebCore::InsertNodeBeforeCommand::doUnapply): Ditto.
* editing/RemoveNodeCommand.cpp:
(WebCore::RemoveNodeCommand::doApply): Ditto.
* editing/visible_units.cpp:
(WebCore::startOfParagraph): We should not consider a paragraph break and element
with user-select: all style, like we do at the border of editability.
(WebCore::endOfParagraph): Ditto.
* page/EventHandler.cpp:
(WebCore::EventHandler::updateSelectionForMouseDownDispatchingSelectStart): Create a selection
around the element whose style is user-select: all.
(WebCore::EventHandler::updateSelectionForMouseDrag): Ditto.
* rendering/RootInlineBox.cpp:
(WebCore::RootInlineBox::selectionState): Fixed a bug uncovered during this work.
If the selection starts in one of the leaf boxes and after we encounter one with SelectionNone,
we should return the selection state as SelectionBoth, assuming we went past the end selection.
This avoids doing an incorrect gap filling for the selection highlighting.

LayoutTests:

Testing moving and extending selections with the new CSS propertyvalue, with mouse movements
and with the keyboard.
Updated test expectations for all the platforms, since this is only enabled for the Mac port.

* editing/selection/user-select-all-selection-expected.txt: Added.
* editing/selection/user-select-all-selection.html: Added.
* platform/chromium/TestExpectations:
* platform/qt/TestExpectations:
* platform/gtk/TestExpectations:
* platform/win/TestExpectations:
* platform/efl/TestExpectations:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@133224 268f45cc-cd09-0410-ab3c-d52691b4dbfc

21 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/selection/user-select-all-selection-expected.txt [new file with mode: 0644]
LayoutTests/editing/selection/user-select-all-selection.html [new file with mode: 0644]
LayoutTests/platform/chromium/TestExpectations
LayoutTests/platform/efl/TestExpectations
LayoutTests/platform/gtk/TestExpectations
LayoutTests/platform/qt/TestExpectations
LayoutTests/platform/win/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/dom/Node.cpp
Source/WebCore/dom/Node.h
Source/WebCore/dom/Position.cpp
Source/WebCore/dom/Position.h
Source/WebCore/editing/ApplyStyleCommand.cpp
Source/WebCore/editing/DeleteFromTextNodeCommand.cpp
Source/WebCore/editing/FrameSelection.cpp
Source/WebCore/editing/InsertNodeBeforeCommand.cpp
Source/WebCore/editing/RemoveNodeCommand.cpp
Source/WebCore/editing/visible_units.cpp
Source/WebCore/page/EventHandler.cpp
Source/WebCore/rendering/RootInlineBox.cpp

index 60bb8b2..640da2e 100644 (file)
@@ -1,3 +1,24 @@
+2012-11-01  Enrica Casucci  <enrica@apple.com>
+
+        Part2 of: Extend -webkit-user-select with new value "all"
+        <rdar://problem/10161404>
+        https://bugs.webkit.org/show_bug.cgi?id=91912
+
+        Reviewed by Ryosuke Niwa.
+
+        Testing moving and extending selections with the new CSS propertyvalue, with mouse movements
+        and with the keyboard.
+        Updated test expectations for all the platforms, since this is only enabled for the Mac port.
+
+        * editing/selection/user-select-all-selection-expected.txt: Added.
+        * editing/selection/user-select-all-selection.html: Added.
+        * platform/chromium/TestExpectations:
+        * platform/qt/TestExpectations:
+        * platform/gtk/TestExpectations:
+        * platform/win/TestExpectations:
+        * platform/efl/TestExpectations:
+
+
 2012-11-01  David Barton  <dbarton@mathscribe.com>
 
         REGRESSION (r128837): mathml/presentation/subsup.xhtml became flaky
diff --git a/LayoutTests/editing/selection/user-select-all-selection-expected.txt b/LayoutTests/editing/selection/user-select-all-selection-expected.txt
new file mode 100644 (file)
index 0000000..0291d50
--- /dev/null
@@ -0,0 +1,34 @@
+Test -webkit-user-select all user select all area Test -webkit-user-select all
+Test -webkit-user-select all selection movements and extensions (left right forward backward)
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+placeCaretBeforeUserSelectAllElement()
+window.getSelection().modify('extend', 'forward', 'character')
+PASS Selection is the entire user-select-all element
+window.getSelection().modify('extend', 'backward', 'character')
+PASS Selection is right before user-select-all element
+window.getSelection().modify('extend', 'right', 'character')
+PASS Selection is the entire user-select-all element
+window.getSelection().modify('extend', 'left', 'character')
+PASS Selection is right before user-select-all element
+window.getSelection().modify('move', 'forward', 'character')
+PASS Selection is right after user-select-all element
+window.getSelection().modify('move', 'backward', 'character')
+PASS Selection is right before user-select-all element
+window.getSelection().modify('move', 'right', 'character')
+PASS Selection is right after user-select-all element
+window.getSelection().modify('move', 'left', 'character')
+PASS Selection is right before user-select-all element
+clickAt(descendant.offsetLeft + 10 , descendant.offsetTop + 10)
+PASS Selection is the entire user-select-all element
+mouseMoveFromTo(leftTarget.offsetLeft, descendant.offsetLeft + 20)
+PASS Selection is the entire user-select-all element plus everything on its left
+mouseMoveFromTo(userSelectAllElement.offsetLeft + userSelectAllElement.offsetWidth + rightTarget.offsetWidth, descendant.offsetLeft + 10)
+PASS Selection is the entire user-select-all element plus everything on its right
+PASS Selection is only the text in bold
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/selection/user-select-all-selection.html b/LayoutTests/editing/selection/user-select-all-selection.html
new file mode 100644 (file)
index 0000000..d65cb77
--- /dev/null
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<head>
+<style>
+.userSelectAll {-webkit-user-select: all; }
+</style>
+<script src="../editing.js"></script>
+<script src="../../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/js-test-selection-shared.js"></script>
+<script>
+function log(str) {
+    var div = document.createElement("div");
+    div.appendChild(document.createTextNode(str));
+    document.getElementById("console").appendChild(div);
+}
+
+function testSelectionAt(anchorNode, anchorOffset, optFocusNode, optFocusOffset, str) {
+    var focusNode = optFocusNode || anchorNode;
+    var focusOffset = (optFocusOffset === undefined) ? anchorOffset : optFocusOffset;
+                
+    var sel = window.getSelection();
+    if (sel.anchorNode == anchorNode
+        && sel.focusNode == focusNode
+        && sel.anchorOffset == anchorOffset
+        && sel.focusOffset == focusOffset) {
+        testPassed("Selection is " + str);
+    } else {
+        testFailed("Selection should be " + str +
+            " at anchorNode: " + sel.anchorNode + " anchorOffset: " + sel.anchorOffset +
+            " focusNode: " + sel.focusNode + " focusOffset: " + sel.focusOffset);
+    }
+}
+
+function selectionShouldBeRelativeToUserSelectAllElement(str) {
+    var userSelectAllElement = document.getElementById("allArea");
+    var userSelectAllNodeIndex = 1;
+    if (str == "around")
+        testSelectionAt(userSelectAllElement.previousSibling.firstChild, userSelectAllElement.previousSibling.textContent.length,
+            userSelectAllElement.nextSibling, 0,
+            "the entire user-select-all element");
+    else if ( str == "before")
+        testSelectionAt(userSelectAllElement.previousSibling.firstChild, userSelectAllElement.previousSibling.textContent.length,
+            userSelectAllElement.previousSibling.firstChild, userSelectAllElement.previousSibling.textContent.length,
+            "right before user-select-all element");
+    else if (str == "after")
+        testSelectionAt(userSelectAllElement.nextSibling.firstChild, 0,
+            userSelectAllElement.nextSibling.firstChild, 0,
+            "right after user-select-all element");
+}
+
+function placeCaretBeforeUserSelectAllElement(){
+    var userSelectAllElement = document.getElementById("allArea");
+    execSetSelectionCommand(userSelectAllElement.previousSibling.firstChild, userSelectAllElement.previousSibling.textContent.length, userSelectAllElement.previousSibling, userSelectAllElement.previousSibling.textContent.length);
+}
+
+function mouseMoveFromTo(fromX, toX){
+    var userSelectAllElement = document.getElementById("allArea");
+    var y = userSelectAllElement.offsetTop + 10;
+    eventSender.dragMode = false;
+    // Clear click count
+    eventSender.mouseMoveTo(0, 0);
+    eventSender.mouseDown();
+    eventSender.mouseUp();
+                
+    eventSender.mouseMoveTo(fromX, y);
+    eventSender.mouseDown();
+    eventSender.mouseMoveTo(toX, y);
+    eventSender.mouseUp();
+}
+
+function testKeyboard(){
+    var userSelectAllElement = document.getElementById("allArea");
+                
+    evalAndLog("placeCaretBeforeUserSelectAllElement()");
+    evalAndLog("window.getSelection().modify('extend', 'forward', 'character')");
+    selectionShouldBeRelativeToUserSelectAllElement("around");
+     
+    evalAndLog("window.getSelection().modify('extend', 'backward', 'character')");
+    selectionShouldBeRelativeToUserSelectAllElement("before");
+     
+    evalAndLog("window.getSelection().modify('extend', 'right', 'character')");
+    selectionShouldBeRelativeToUserSelectAllElement("around");
+                
+    evalAndLog("window.getSelection().modify('extend', 'left', 'character')");
+    selectionShouldBeRelativeToUserSelectAllElement("before");
+                
+    evalAndLog("window.getSelection().modify('move', 'forward', 'character')");
+    selectionShouldBeRelativeToUserSelectAllElement("after");
+                
+    evalAndLog("window.getSelection().modify('move', 'backward', 'character')");
+    selectionShouldBeRelativeToUserSelectAllElement("before");
+                
+    evalAndLog("window.getSelection().modify('move', 'right', 'character')");
+    selectionShouldBeRelativeToUserSelectAllElement("after");
+     
+    evalAndLog("window.getSelection().modify('move', 'left', 'character')");
+    selectionShouldBeRelativeToUserSelectAllElement("before");
+}
+
+function testMouse(){
+    var userSelectAllElement = document.getElementById("allArea");
+    var descendant = document.getElementById("descendant");
+    evalAndLog("clickAt(descendant.offsetLeft + 10 , descendant.offsetTop + 10)");
+    selectionShouldBeRelativeToUserSelectAllElement("around");
+                
+    // mouse extending from left
+    var leftTarget = userSelectAllElement.previousSibling;
+    log("mouseMoveFromTo(leftTarget.offsetLeft, descendant.offsetLeft + 20)");
+    mouseMoveFromTo(leftTarget.offsetLeft, descendant.offsetLeft + 20);
+    testSelectionAt(leftTarget.firstChild, 0, userSelectAllElement.nextSibling, 0, "the entire user-select-all element plus everything on its left");
+    
+    // mouse extending from right
+    var rightTarget = userSelectAllElement.nextSibling;
+    var textLength = rightTarget.textContent.length;
+    log("mouseMoveFromTo(userSelectAllElement.offsetLeft + userSelectAllElement.offsetWidth + rightTarget.offsetWidth, descendant.offsetLeft + 10)");
+    mouseMoveFromTo(userSelectAllElement.offsetLeft + userSelectAllElement.offsetWidth + rightTarget.offsetWidth, descendant.offsetLeft + 10);
+    testSelectionAt(rightTarget.firstChild, textLength, leftTarget.firstChild, leftTarget.textContent.textLength, "the entire user-select-all element plus everything on its right");
+}
+    
+function testProgrammaticSelection(){
+    var boldElement = document.querySelector('b');
+    getSelection().selectAllChildren(boldElement);
+    testSelectionAt(boldElement.firstChild, 0, boldElement.firstChild, 10, "only the text in bold");
+}
+</script>
+</head>
+<body><div contenteditable><span>Test -webkit-user-select all </span><span class="userSelectAll" id="allArea"><span style="border: solid red 1px" id="descendant">user <b>select all</b> area</span></span><span> Test -webkit-user-select all</span></div>
+<div id="console"></div>
+<script>
+description(" Test -webkit-user-select all selection movements and extensions (left right forward backward) ");
+testKeyboard();
+testMouse();
+testProgrammaticSelection();
+</script>
+<script src="../../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
index 4f0d8bf..7660b78 100644 (file)
@@ -4141,6 +4141,7 @@ webkit.org/b/100023 [ Mac ] fast/sub-pixel/file-upload-control-at-fractional-off
 
 webkit.org/b/100478  [ Mac ] css3/filters/custom/effect-custom.html [ Pass ImageOnlyFailure ]
 
+webkit.org/b/100424 editing/selection/user-select-all-selection.html [ Failure ]
 webkit.org/b/100475 compositing/tiling/tile-cache-zoomed.html [ Failure ]
 webkit.org/b/100475 platform/chromium/virtual/softwarecompositing/tiling/tile-cache-zoomed.html [ Failure ]
 webkit.org/b/100477 [ Win ] platform/chromium/fast/forms/suggestion-picker/date-suggestion-picker-key-operations.html [ Failure ]
index 0e5a534..28c7a38 100644 (file)
@@ -1670,6 +1670,8 @@ webkit.org/b/99691 gamepad/gamepad-out-of-range-crasher.html [ Failure ]
 # Resource Timing is not enable yet, skip it.
 webkit.org/b/61138 http/tests/w3c/webperf/submission/Intel/resource-timing [ Skip ]
 
+webkit.org/b/100424 editing/selection/user-select-all-selection.html [ Failure ]
+
 # Entering into full screen playback mode fails when triggered by context menu
 Bug(EFL) media/context-menu-actions.html [ Failure ]
 
index 7625fa0..5956b33 100644 (file)
@@ -1429,6 +1429,8 @@ webkit.org/b/99825 accessibility/title-ui-element-correctness.html [ Failure ]
 
 webkit.org/b/98718 svg/animations/animate-css-xml-attributeType.html [ Failure ]
 
+webkit.org/b/100424 editing/selection/user-select-all-selection.html [ Failure ]
+
 webkit.org/b/100846 inspector-protocol/debugger-pause-dedicated-worker.html [ Skip ]
 
 webkit.org/b/95299 fast/images/exif-orientation.html [ Failure ]
index 0dbfc42..fda680d 100644 (file)
@@ -2508,6 +2508,8 @@ webkit.org/b/100117 fast/xmlhttprequest/xmlhttprequest-recursive-sync-event.html
 # [Qt] New http/tests/inspector/network/image-as-text-loading-data-url.html fails
 webkit.org/b/100196 http/tests/inspector/network/image-as-text-loading-data-url.html
 
+webkit.org/b/100424 editing/selection/user-select-all-selection.html [ Failure ]
+
 # REGRESSION(r132757): It made 2 jquery tests assert
 webkit.org/b/100636 jquery/manipulation.html
 webkit.org/b/100636 jquery/traversing.html
index b2b7b6b..43a5ce9 100644 (file)
@@ -2378,6 +2378,8 @@ webkit.org/b/84893 http/tests/w3c/webperf/submission/Intel/user-timing
 # Resource Timing is not enable yet, skip it.
 webkit.org/b/61138 http/tests/w3c/webperf/submission/Intel/resource-timing
 
+webkit.org/b/100424 editing/selection/user-select-all-selection.html [ Failure ]
+
 # https://bugs.webkit.org/show_bug.cgi?id=99567
 # Not supported on Mac or Windows ports
 inspector/elements/update-shadowdom.html
index f5c6ea1..2d49744 100644 (file)
@@ -1,3 +1,75 @@
+2012-11-01  Enrica Casucci  <enrica@apple.com>
+
+        Part2 of: Extend -webkit-user-select with new value "all"
+        <rdar://problem/10161404>
+        https://bugs.webkit.org/show_bug.cgi?id=91912
+
+        Reviewed by Ryosuke Niwa.
+
+        The new value "all" for -webkit-user-select property gives content none-or-all selection option.
+        The patch was originally prepared by Alice Cheng but the approach has been changed.
+        The idea is to treat these elements like non editable, meaning that we should skip over them entirely
+        when moving the cursor and a deletion should delete the element and all its descentants at once.
+        The key change is in Node::rendererIsEditable where we now return false if the element style is
+        userSelect: all. The other change is in the way we create the selection on mouse click and dragging
+        over the element. In both cases we force the selection to extend over the entire element with
+        the user-select: all attribute.
+        This is currently enabled only for the Mac port.
+
+        Test: editing/selection/user-select-all-selection.html
+
+        * dom/Node.cpp: Added a parameter to isContentEditable to behave differently
+        when called from JavaScript. Internally isContentEditable returns false on
+        nodes with user-select: all style.
+        (WebCore::Node::isContentEditable):
+        (WebCore::Node::isContentRichlyEditable):
+        (WebCore::Node::rendererIsEditable):
+        (WebCore::Node::shouldUseInputMethod):
+        (WebCore::Node::willRespondToMouseClickEvents):
+        * dom/Node.h:
+        (WebCore::Node::rendererIsEditable):
+        (WebCore::Node::rendererIsRichlyEditable):
+        * dom/Position.cpp:
+        (WebCore::Position::nodeIsUserSelectAll): Added.
+        (WebCore::Position::rootUserSelectAllForNode): Added.
+        * dom/Position.h: Added static functions described above.
+        * editing/ApplyStyleCommand.cpp:
+        (WebCore::ApplyStyleCommand::removeInlineStyleFromElement): Added parameter to
+        isContentEditable() call.
+        (WebCore::ApplyStyleCommand::surroundNodeRangeWithElement): Added parameter to
+        isContentEditable() call.
+        * editing/DeleteFromTextNodeCommand.cpp:
+        (WebCore::DeleteFromTextNodeCommand::doApply): Added parameter to
+        isContentEditable() call.
+        * editing/FrameSelection.cpp:
+        (WebCore::adjustForwardPositionForUserSelectAll): New helper function.
+        (WebCore::adjustBackwardPositionForUserSelectAll): New helper function.
+        (WebCore::FrameSelection::modifyExtendingRight):
+        (WebCore::FrameSelection::modifyExtendingForward):
+        (WebCore::FrameSelection::modifyExtendingLeft):
+        (WebCore::FrameSelection::modifyExtendingBackward):
+        (WebCore::FrameSelection::modify):
+        (WebCore::CaretBase::invalidateCaretRect): Added parameter to
+        isContentEditable() call.
+        * editing/InsertNodeBeforeCommand.cpp:
+        (WebCore::InsertNodeBeforeCommand::doApply): Ditto.
+        (WebCore::InsertNodeBeforeCommand::doUnapply): Ditto.
+        * editing/RemoveNodeCommand.cpp:
+        (WebCore::RemoveNodeCommand::doApply): Ditto.
+        * editing/visible_units.cpp:
+        (WebCore::startOfParagraph): We should not consider a paragraph break and element
+        with user-select: all style, like we do at the border of editability.
+        (WebCore::endOfParagraph): Ditto.
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::updateSelectionForMouseDownDispatchingSelectStart): Create a selection
+        around the element whose style is user-select: all.
+        (WebCore::EventHandler::updateSelectionForMouseDrag): Ditto.
+        * rendering/RootInlineBox.cpp:
+        (WebCore::RootInlineBox::selectionState): Fixed a bug uncovered during this work.
+        If the selection starts in one of the leaf boxes and after we encounter one with SelectionNone,
+        we should return the selection state as SelectionBoth, assuming we went past the end selection.
+        This avoids doing an incorrect gap filling for the selection highlighting.
+
 2012-11-01  Alec Flett  <alecflett@chromium.org>
 
         IndexedDB: Fix Windows build by re-adding a #include
index 55375a6..1d71ab0 100644 (file)
@@ -717,16 +717,16 @@ const AtomicString& Node::virtualNamespaceURI() const
     return nullAtom;
 }
 
-bool Node::isContentEditable()
+bool Node::isContentEditable(UserSelectAllTreatment treatment)
 {
     document()->updateStyleIfNeeded();
-    return rendererIsEditable(Editable);
+    return rendererIsEditable(Editable, treatment);
 }
 
 bool Node::isContentRichlyEditable()
 {
     document()->updateStyleIfNeeded();
-    return rendererIsEditable(RichlyEditable);
+    return rendererIsEditable(RichlyEditable, UserSelectAllIsAlwaysNonEditable);
 }
 
 void Node::inspect()
@@ -737,7 +737,7 @@ void Node::inspect()
 #endif
 }
 
-bool Node::rendererIsEditable(EditableLevel editableLevel) const
+bool Node::rendererIsEditable(EditableLevel editableLevel, UserSelectAllTreatment treatment) const
 {
     if (document()->frame() && document()->frame()->page() && document()->frame()->page()->isEditable() && !shadowRoot())
         return true;
@@ -748,6 +748,12 @@ bool Node::rendererIsEditable(EditableLevel editableLevel) const
 
     for (const Node* node = this; node; node = node->parentNode()) {
         if ((node->isHTMLElement() || node->isDocumentNode()) && node->renderer()) {
+#if ENABLE(USERSELECT_ALL)
+            // Elements with user-select: all style are considered atomic
+            // therefore non editable.
+            if (node->renderer()->style()->userSelect() == SELECT_ALL && treatment == UserSelectAllIsAlwaysNonEditable)
+                return false;
+#endif
             switch (node->renderer()->style()->userModify()) {
             case READ_ONLY:
                 return false;
@@ -785,7 +791,7 @@ bool Node::isEditableToAccessibility(EditableLevel editableLevel) const
 
 bool Node::shouldUseInputMethod()
 {
-    return isContentEditable();
+    return isContentEditable(UserSelectAllIsAlwaysNonEditable);
 }
 
 RenderBox* Node::renderBox() const
@@ -2746,7 +2752,7 @@ bool Node::willRespondToMouseClickEvents()
 {
     if (disabled())
         return false;
-    return isContentEditable() || hasEventListeners(eventNames().mouseupEvent) || hasEventListeners(eventNames().mousedownEvent) || hasEventListeners(eventNames().clickEvent) || hasEventListeners(eventNames().DOMActivateEvent);
+    return isContentEditable(UserSelectAllIsAlwaysNonEditable) || hasEventListeners(eventNames().mouseupEvent) || hasEventListeners(eventNames().mousedownEvent) || hasEventListeners(eventNames().clickEvent) || hasEventListeners(eventNames().DOMActivateEvent);
 }
 
 bool Node::willRespondToTouchEvents()
index f99ae2a..169c1c1 100644 (file)
@@ -375,16 +375,20 @@ public:
     virtual bool isMouseFocusable() const;
     virtual Node* focusDelegate();
 
-    bool isContentEditable();
+    enum UserSelectAllTreatment {
+        UserSelectAllDoesNotAffectEditability,
+        UserSelectAllIsAlwaysNonEditable
+    };
+    bool isContentEditable(UserSelectAllTreatment = UserSelectAllDoesNotAffectEditability);
     bool isContentRichlyEditable();
 
     void inspect();
 
-    bool rendererIsEditable(EditableType editableType = ContentIsEditable) const
+    bool rendererIsEditable(EditableType editableType = ContentIsEditable, UserSelectAllTreatment treatment = UserSelectAllIsAlwaysNonEditable) const
     {
         switch (editableType) {
         case ContentIsEditable:
-            return rendererIsEditable(Editable);
+            return rendererIsEditable(Editable, treatment);
         case HasEditableAXRole:
             return isEditableToAccessibility(Editable);
         }
@@ -396,7 +400,7 @@ public:
     {
         switch (editableType) {
         case ContentIsEditable:
-            return rendererIsEditable(RichlyEditable);
+            return rendererIsEditable(RichlyEditable, UserSelectAllIsAlwaysNonEditable);
         case HasEditableAXRole:
             return isEditableToAccessibility(RichlyEditable);
         }
@@ -768,7 +772,7 @@ private:
     void setDocument(Document*);
 
     enum EditableLevel { Editable, RichlyEditable };
-    bool rendererIsEditable(EditableLevel) const;
+    bool rendererIsEditable(EditableLevel, UserSelectAllTreatment = UserSelectAllIsAlwaysNonEditable) const;
     bool isEditableToAccessibility(EditableLevel) const;
 
     void setStyleChange(StyleChangeType);
index 9fd2c01..cb60700 100644 (file)
@@ -865,6 +865,35 @@ ContainerNode* Position::findParent(const Node* node)
     return node->nonShadowBoundaryParentNode();
 }
 
+#if ENABLE(USERSELECT_ALL)
+bool Position::nodeIsUserSelectAll(const Node* node)
+{
+    return node && node->renderer() && node->renderer()->style()->userSelect() == SELECT_ALL;
+}
+
+Node* Position::rootUserSelectAllForNode(Node* node)
+{
+    if (!node || !nodeIsUserSelectAll(node))
+        return 0;
+    Node* parent = node->parentNode();
+    if (!parent)
+        return node;
+
+    Node* candidateRoot = node;
+    while (parent) {
+        if (!parent->renderer()) {
+            parent = parent->parentNode();
+            continue;
+        }
+        if (!nodeIsUserSelectAll(parent))
+            break;
+        candidateRoot = parent;
+        parent = candidateRoot->parentNode();
+    }
+    return candidateRoot;
+}
+#endif
+
 bool Position::isCandidate() const
 {
     if (isNull())
index 27c3f42..fa2aba7 100644 (file)
@@ -189,7 +189,10 @@ public:
 
     static bool hasRenderedNonAnonymousDescendantsWithHeight(RenderObject*);
     static bool nodeIsUserSelectNone(Node*);
-
+#if ENABLE(USERSELECT_ALL)
+    static bool nodeIsUserSelectAll(const Node*);
+    static Node* rootUserSelectAllForNode(Node*);
+#endif
     static ContainerNode* findParent(const Node*);
     
     void debugPosition(const char* msg = "") const;
index 71fb60d..943f6b2 100644 (file)
@@ -825,7 +825,7 @@ bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRe
 {
     ASSERT(element);
 
-    if (!element->parentNode() || !element->parentNode()->isContentEditable())
+    if (!element->parentNode() || !element->parentNode()->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))
         return false;
 
     if (isStyledInlineElementToRemove(element.get())) {
@@ -1288,7 +1288,7 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStar
     RefPtr<Node> node = startNode;
     while (node) {
         RefPtr<Node> next = node->nextSibling();
-        if (node->isContentEditable()) {
+        if (node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) {
             removeNode(node);
             appendNode(node, element);
         }
index 5708c86..25444ac 100644 (file)
@@ -47,7 +47,7 @@ void DeleteFromTextNodeCommand::doApply()
 {
     ASSERT(m_node);
 
-    if (!m_node->isContentEditable())
+    if (!m_node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))
         return;
 
     ExceptionCode ec = 0;
index f0e249a..563a066 100644 (file)
@@ -556,6 +556,14 @@ VisiblePosition FrameSelection::endForPlatform() const
     return positionForPlatform(false);
 }
 
+#if ENABLE(USERSELECT_ALL)
+static void adjustPositionForUserSelectAll(VisiblePosition& pos, bool isForward)
+{
+    if (Node* rootUserSelectAll = Position::rootUserSelectAllForNode(pos.deepEquivalent().anchorNode()))
+        pos = isForward ? positionAfterNode(rootUserSelectAll).downstream(CanCrossEditingBoundary) : positionBeforeNode(rootUserSelectAll).upstream(CanCrossEditingBoundary);
+}
+#endif
+
 VisiblePosition FrameSelection::modifyExtendingRight(TextGranularity granularity)
 {
     VisiblePosition pos(m_selection.extent(), m_selection.affinity());
@@ -594,6 +602,9 @@ VisiblePosition FrameSelection::modifyExtendingRight(TextGranularity granularity
         pos = modifyExtendingForward(granularity);
         break;
     }
+#if ENABLE(USERSELECT_ALL)
+    adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == LTR);
+#endif
     return pos;
 }
 
@@ -633,7 +644,9 @@ VisiblePosition FrameSelection::modifyExtendingForward(TextGranularity granulari
             pos = endOfDocument(pos);
         break;
     }
-    
+#if ENABLE(USERSELECT_ALL)
+     adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == LTR);
+#endif
     return pos;
 }
 
@@ -760,6 +773,9 @@ VisiblePosition FrameSelection::modifyExtendingLeft(TextGranularity granularity)
         pos = modifyExtendingBackward(granularity);
         break;
     }
+#if ENABLE(USERSELECT_ALL)
+    adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == LTR));
+#endif
     return pos;
 }
        
@@ -804,6 +820,9 @@ VisiblePosition FrameSelection::modifyExtendingBackward(TextGranularity granular
             pos = startOfDocument(pos);
         break;
     }
+#if ENABLE(USERSELECT_ALL)
+    adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == LTR));
+#endif
     return pos;
 }
 
@@ -956,6 +975,7 @@ bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, Tex
         moveTo(position, userTriggered);
         break;
     case AlterationExtend:
+
         if (!m_selection.isCaret()
             && (granularity == WordGranularity || granularity == ParagraphGranularity || granularity == LineGranularity)
             && m_frame && !m_frame->editor()->behavior().shouldExtendSelectionByWordOrLineAcrossCaret()) {
@@ -1365,7 +1385,7 @@ void CaretBase::invalidateCaretRect(Node* node, bool caretRectChanged)
 
     if (!caretRectChanged) {
         RenderView* view = toRenderView(node->document()->renderer());
-        if (view && shouldRepaintCaret(view, node->isContentEditable()))
+        if (view && shouldRepaintCaret(view, node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)))
             view->repaintRectangleInViewAndCompositedLayers(caretRepaintRect(node), false);
     }
 }
index bef4011..bf662a3 100644 (file)
@@ -48,7 +48,7 @@ InsertNodeBeforeCommand::InsertNodeBeforeCommand(PassRefPtr<Node> insertChild, P
 void InsertNodeBeforeCommand::doApply()
 {
     ContainerNode* parent = m_refChild->parentNode();
-    if (!parent || !parent->isContentEditable())
+    if (!parent || !parent->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))
         return;
 
     ExceptionCode ec;
@@ -60,7 +60,7 @@ void InsertNodeBeforeCommand::doApply()
 
 void InsertNodeBeforeCommand::doUnapply()
 {
-    if (!m_insertChild->isContentEditable())
+    if (!m_insertChild->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))
         return;
         
     // Need to notify this before actually deleting the text
index 19657ac..82cb1d1 100644 (file)
@@ -42,7 +42,7 @@ RemoveNodeCommand::RemoveNodeCommand(PassRefPtr<Node> node)
 void RemoveNodeCommand::doApply()
 {
     ContainerNode* parent = m_node->parentNode();
-    if (!parent || !parent->isContentEditable())
+    if (!parent || !parent->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))
         return;
 
     m_parent = parent;
index b0fb925..9508677 100644 (file)
@@ -1108,7 +1108,11 @@ VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossi
 
     Node* n = startNode;
     while (n) {
+#if ENABLE(USERSELECT_ALL)
+        if (boundaryCrossingRule == CannotCrossEditingBoundary && !Position::nodeIsUserSelectAll(n) && n->rendererIsEditable() != startNode->rendererIsEditable())
+#else
         if (boundaryCrossingRule == CannotCrossEditingBoundary && n->rendererIsEditable() != startNode->rendererIsEditable())
+#endif
             break;
         if (boundaryCrossingRule == CanSkipOverEditingBoundary) {
             while (n && n->rendererIsEditable() != startNode->rendererIsEditable())
@@ -1184,7 +1188,11 @@ VisiblePosition endOfParagraph(const VisiblePosition &c, EditingBoundaryCrossing
 
     Node* n = startNode;
     while (n) {
+#if ENABLE(USERSELECT_ALL)
+        if (boundaryCrossingRule == CannotCrossEditingBoundary && !Position::nodeIsUserSelectAll(n) && n->rendererIsEditable() != startNode->rendererIsEditable())
+#else
         if (boundaryCrossingRule == CannotCrossEditingBoundary && n->rendererIsEditable() != startNode->rendererIsEditable())
+#endif
             break;
         if (boundaryCrossingRule == CanSkipOverEditingBoundary) {
             while (n && n->rendererIsEditable() != startNode->rendererIsEditable())
index 87ed690..c5cb25d 100644 (file)
@@ -418,14 +418,21 @@ bool EventHandler::updateSelectionForMouseDownDispatchingSelectStart(Node* targe
     if (!dispatchSelectStart(targetNode))
         return false;
 
-    if (newSelection.isRange())
+    VisibleSelection selection(newSelection);
+#if ENABLE(USERSELECT_ALL)
+    if (Node* rootUserSelectAll = Position::rootUserSelectAllForNode(targetNode)) {
+        selection.setBase(positionBeforeNode(rootUserSelectAll).upstream(CanCrossEditingBoundary));
+        selection.setExtent(positionAfterNode(rootUserSelectAll).downstream(CanCrossEditingBoundary));
+    }
+#endif
+    if (selection.isRange())
         m_selectionInitiationState = ExtendedSelection;
     else {
         granularity = CharacterGranularity;
         m_selectionInitiationState = PlacedCaret;
     }
 
-    m_frame->selection()->setNonDirectionalSelectionIfNeeded(newSelection, granularity);
+    m_frame->selection()->setNonDirectionalSelectionIfNeeded(selection, granularity);
 
     return true;
 }
@@ -844,7 +851,25 @@ void EventHandler::updateSelectionForMouseDrag(const HitTestResult& hitTestResul
         newSelection = VisibleSelection(targetPosition);
     }
 
-    newSelection.setExtent(targetPosition);
+#if ENABLE(USERSELECT_ALL)
+    Node* rootUserSelectAllForMousePressNode = Position::rootUserSelectAllForNode(m_mousePressNode.get());
+    if (rootUserSelectAllForMousePressNode && rootUserSelectAllForMousePressNode == Position::rootUserSelectAllForNode(target)) {
+        newSelection.setBase(positionBeforeNode(rootUserSelectAllForMousePressNode).upstream(CanCrossEditingBoundary));
+        newSelection.setExtent(positionAfterNode(rootUserSelectAllForMousePressNode).downstream(CanCrossEditingBoundary));
+    } else {
+        // Reset base for user select all when base is inside user-select-all area and extent < base.
+        if (rootUserSelectAllForMousePressNode && comparePositions(target->renderer()->positionForPoint(hitTestResult.localPoint()), m_mousePressNode->renderer()->positionForPoint(m_dragStartPos)) < 0)
+            newSelection.setBase(positionAfterNode(rootUserSelectAllForMousePressNode).downstream(CanCrossEditingBoundary));
+        
+        Node* rootUserSelectAllForTarget = Position::rootUserSelectAllForNode(target);
+        if (rootUserSelectAllForTarget && m_mousePressNode->renderer() && comparePositions(target->renderer()->positionForPoint(hitTestResult.localPoint()), m_mousePressNode->renderer()->positionForPoint(m_dragStartPos)) < 0)
+            newSelection.setExtent(positionBeforeNode(rootUserSelectAllForTarget).upstream(CanCrossEditingBoundary));
+        else if (rootUserSelectAllForTarget && m_mousePressNode->renderer())
+            newSelection.setExtent(positionAfterNode(rootUserSelectAllForTarget).downstream(CanCrossEditingBoundary));
+        else
+            newSelection.setExtent(targetPosition);
+    }
+#endif
     if (m_frame->selection()->granularity() != CharacterGranularity)
         newSelection.expandUsingGranularity(m_frame->selection()->granularity());
 
index a260259..6c93303 100644 (file)
@@ -499,6 +499,10 @@ RenderObject::SelectionState RootInlineBox::selectionState()
                  ((boxState == RenderObject::SelectionStart || boxState == RenderObject::SelectionEnd) &&
                   (state == RenderObject::SelectionNone || state == RenderObject::SelectionInside)))
             state = boxState;
+        else if (boxState == RenderObject::SelectionNone && state == RenderObject::SelectionStart) {
+            // We are past the end of the selection.
+            state = RenderObject::SelectionBoth;
+        }
         if (state == RenderObject::SelectionBoth)
             break;
     }