Bug 1670531 - Only select selectable element if it isn't editable. r=masayuki

When caret is [] in the following html then we use [shift] + [arrow right],
Gecko select non-editable text and "C" character. This behaviour is different
of Blink and WebKit. They select only non-editable element by this operation.

```
<div contenteditable>A[]<span contenteditable=false><B</span>C</div>
```

Another example is `<img>` element with `contenteditable=false`. If this
`<img>` element is editable, [shift] + [arrow right] doesn't select "C"
character, but if this `<img>` element isn't editable, [shift] + [arrow right]
selects additional "C" character on Gecko.

```
<div contenteditable>A[]<img contenteditable=false src=... />C</div>
```

So I would like to change this behaviour to Blink/Webkit way.

`PeekOffsetForCharacter` is looking for selection end. When it is selecting
elements, if traversed frame/content has non-select frame/content, we select
first character of editable text.  But when we already have selected element,
it is unnecessary to select editable text.

Also, bug1524266-4.html is unfortunately work now and we don't support white
space compression for this situation (bug 1670518). Even if inner span is
editable, we don't compress white space. So we need a workaround for this
test.

Differential Revision: https://phabricator.services.mozilla.com/D93300
This commit is contained in:
Makoto Kato 2020-10-21 07:08:09 +00:00
Родитель bdc3f86e85
Коммит dfdccd0200
10 изменённых файлов: 153 добавлений и 5 удалений

Просмотреть файл

@ -10,10 +10,7 @@
</style>
<div contenteditable="true" spellcheck="false">
xx
<span contenteditable="false">
NOT EDITABLE
</span>
xxx
<span contenteditable="false">NOT EDITABLE</span>xxx
</div>
<script>
SimpleTest.waitForFocus(function() {

Просмотреть файл

@ -0,0 +1,27 @@
<!doctype html>
<html class="reftest-wait">
<title>Select non-editable content in an editor</title>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<style>
div:focus {
outline: 3px solid blue;
}
</style>
<div contenteditable="true" spellcheck="false">
xx<span contenteditable="false">NOT EDITABLE</span>xxx
</div>
<script>
SimpleTest.waitForFocus(function() {
document.querySelector('[contenteditable="true"]').focus();
requestAnimationFrame(function() {
// Move after the two x
for (let i = 0; i < 2; ++i) {
synthesizeKey("KEY_ArrowRight");
}
// Select <span>
synthesizeKey("KEY_ArrowRight", { shiftKey: true });
document.documentElement.removeAttribute("class");
});
});
</script>

Просмотреть файл

@ -0,0 +1,27 @@
<!doctype html>
<html class="reftest-wait">
<title>Select non-editable content in an editor</title>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<style>
div:focus {
outline: 3px solid blue;
}
</style>
<div contenteditable="true" spellcheck="false">
xx<span contenteditable="false">NOT EDITABLE</span>xxx
</div>
<script>
SimpleTest.waitForFocus(function() {
document.querySelector('[contenteditable="true"]').focus();
requestAnimationFrame(function() {
// Move before the three x
for (let i = 0; i < 3; ++i) {
synthesizeKey("KEY_ArrowRight");
}
// Select <span>
synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
document.documentElement.removeAttribute("class");
});
});
</script>

Просмотреть файл

@ -0,0 +1,27 @@
<!doctype html>
<html class="reftest-wait">
<title>Select non-editable content in an editor</title>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<style>
div:focus {
outline: 3px solid blue;
}
</style>
<div contenteditable="true" spellcheck="false">
xx<img src="image_rgrg-256x256.png">xxx
</div>
<script>
SimpleTest.waitForFocus(function() {
document.querySelector('[contenteditable="true"]').focus();
requestAnimationFrame(function() {
// Move after the two x
for (let i = 0; i < 2; ++i) {
synthesizeKey("KEY_ArrowRight");
}
// Select <img>
synthesizeKey("KEY_ArrowRight", { shiftKey: true });
document.documentElement.removeAttribute("class");
});
});
</script>

Просмотреть файл

@ -0,0 +1,27 @@
<!doctype html>
<html class="reftest-wait">
<title>Select non-editable content in an editor</title>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<style>
div:focus {
outline: 3px solid blue;
}
</style>
<div contenteditable="true" spellcheck="false">
xx<img contenteditable="false" src="image_rgrg-256x256.png">xxx
</div>
<script>
SimpleTest.waitForFocus(function() {
document.querySelector('[contenteditable="true"]').focus();
requestAnimationFrame(function() {
// Move after the two x
for (let i = 0; i < 2; ++i) {
synthesizeKey("KEY_ArrowRight");
}
// Select <img>
synthesizeKey("KEY_ArrowRight", { shiftKey: true });
document.documentElement.removeAttribute("class");
});
});
</script>

Просмотреть файл

@ -0,0 +1,27 @@
<!doctype html>
<html class="reftest-wait">
<title>Select non-editable content in an editor</title>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<style>
div:focus {
outline: 3px solid blue;
}
</style>
<div contenteditable="true" spellcheck="false">
xx<img contenteditable="false" src="image_rgrg-256x256.png">xxx
</div>
<script>
SimpleTest.waitForFocus(function() {
document.querySelector('[contenteditable="true"]').focus();
requestAnimationFrame(function() {
// Move before the three x
for (let i = 0; i < 3; ++i) {
synthesizeKey("KEY_ArrowRight");
}
// Select <img>
synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
document.documentElement.removeAttribute("class");
});
});
</script>

Просмотреть файл

@ -375,6 +375,11 @@ support-files =
bug1637476-2-ref.html
bug1637476-3.html
bug1637476-3-ref.html
bug1670531-1.html
bug1670531-2.html
bug1670531-3.html
bug1670531-3-ref.html
bug1670531-4.html
image_rgrg-256x256.png
input-invalid-ref.html
input-maxlength-invalid-change.html

Просмотреть файл

@ -274,6 +274,10 @@ var tests = [
[ 'bug1637476-1.html' , 'bug1637476-1-ref.html' ] ,
[ 'bug1637476-2.html' , 'bug1637476-2-ref.html' ] ,
[ 'bug1637476-3.html' , 'bug1637476-3-ref.html' ] ,
// shift+arrow key should select non-editable only
[ 'bug1670531-1.html' , 'bug1670531-2.html' ] ,
[ 'bug1670531-3.html' , 'bug1670531-3-ref.html' ] ,
[ 'bug1670531-4.html' , 'bug1670531-3-ref.html' ] ,
function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.enabled_on_touch']]}, nextTest);} ,
function() {SpecialPowers.pushPrefEnv({'set': [['accessibility.browsewithcaret', true]]}, nextTest);} ,
[ 'bug1529492-1.html' , 'bug1529492-1-ref.html' ] ,

Просмотреть файл

@ -8448,6 +8448,7 @@ nsresult nsIFrame::PeekOffsetForCharacter(nsPeekOffsetStruct* aPos,
}
next.mJumpedLine |= current.mJumpedLine;
next.mMovedOverNonSelectableText |= current.mMovedOverNonSelectableText;
next.mHasSelectableFrame |= current.mHasSelectableFrame;
current = next;
}
@ -8455,7 +8456,7 @@ nsresult nsIFrame::PeekOffsetForCharacter(nsPeekOffsetStruct* aPos,
// the offset to be at the frame edge. Note that if we are extending the
// selection, this doesn't matter.
if (peekSearchState == FOUND && current.mMovedOverNonSelectableText &&
!aPos->mExtend) {
(!aPos->mExtend || current.mHasSelectableFrame)) {
int32_t start, end;
current.mFrame->GetOffsets(start, end);
current.mOffset = aPos->mDirection == eDirNext ? 0 : end - start;
@ -9017,6 +9018,9 @@ nsIFrame::SelectablePeekReport nsIFrame::GetFrameFromDirection(
selectable = IsSelectable(traversedFrame);
if (!selectable) {
if (traversedFrame->IsSelectable(nullptr)) {
result.mHasSelectableFrame = true;
}
result.mMovedOverNonSelectableText = true;
}
} // while (!selectable)

Просмотреть файл

@ -3773,6 +3773,9 @@ class nsIFrame : public nsQueryFrame {
bool mJumpedHardBreak = false;
/** whether we jumped over a non-selectable frame during the search */
bool mMovedOverNonSelectableText = false;
/** whether we met selectable text frame that isn't editable during the
* search */
bool mHasSelectableFrame = false;
FrameSearchResult PeekOffsetNoAmount(bool aForward) {
return mFrame->PeekOffsetNoAmount(aForward, &mOffset);