Bug 1263357 - When the caret is placed after visible line break, associate caret with frame on the next line instead. r=mats

--HG--
extra : amend_source : a0068b0c841189204e07cc6c0a19f83d5dac8da3
This commit is contained in:
Jorg K 2016-10-14 14:21:00 -04:00
Родитель e6f4f137d8
Коммит 4bb58198d9
16 изменённых файлов: 423 добавлений и 85 удалений

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

@ -49,9 +49,7 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/EncodingUtils.h"
#include "nsContainerFrame.h"
#include "nsBlockFrame.h"
#include "nsComputedDOMStyle.h"
#include "nsLayoutUtils.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -324,85 +322,6 @@ nsDocumentEncoder::IncludeInContext(nsINode *aNode)
return false;
}
static
bool
LineHasNonEmptyContentWorker(nsIFrame* aFrame)
{
// Look for non-empty frames, but ignore inline and br frames.
// For inline frames, descend into the children, if any.
if (aFrame->GetType() == nsGkAtoms::inlineFrame) {
for (nsIFrame* child : aFrame->PrincipalChildList()) {
if (LineHasNonEmptyContentWorker(child)) {
return true;
}
}
} else {
if (aFrame->GetType() != nsGkAtoms::brFrame &&
!aFrame->IsEmpty()) {
return true;
}
}
return false;
}
static
bool
LineHasNonEmptyContent(nsLineBox* aLine)
{
int32_t count = aLine->GetChildCount();
for (nsIFrame* frame = aLine->mFirstChild; count > 0;
--count, frame = frame->GetNextSibling()) {
if (LineHasNonEmptyContentWorker(frame)) {
return true;
}
}
return false;
}
static
bool
IsInvisibleBreak(nsINode *aNode)
{
if (!aNode->IsElement() || !aNode->IsEditable()) {
return false;
}
nsIFrame* frame = aNode->AsElement()->GetPrimaryFrame();
if (!frame || frame->GetType() != nsGkAtoms::brFrame) {
return false;
}
nsContainerFrame* f = frame->GetParent();
while (f && f->IsFrameOfType(nsBox::eLineParticipant)) {
f = f->GetParent();
}
nsBlockFrame* blockAncestor = do_QueryFrame(f);
if (!blockAncestor) {
// The container frame doesn't support line breaking.
return false;
}
bool valid = false;
nsBlockInFlowLineIterator iter(blockAncestor, frame, &valid);
if (!valid) {
return false;
}
bool lineNonEmpty = LineHasNonEmptyContent(iter.GetLine());
while (iter.Next()) {
auto currentLine = iter.GetLine();
// Completely skip empty lines.
if (!currentLine->IsEmpty()) {
// If we come across an inline line, the BR has caused a visible line break.
if (currentLine->IsInline()) {
return false;
}
}
}
return lineNonEmpty;
}
nsresult
nsDocumentEncoder::SerializeNodeStart(nsINode* aNode,
int32_t aStartOffset,
@ -437,7 +356,7 @@ nsDocumentEncoder::SerializeNodeStart(nsINode* aNode,
if (node->IsElement()) {
if ((mFlags & (nsIDocumentEncoder::OutputPreformatted |
nsIDocumentEncoder::OutputDropInvisibleBreak)) &&
IsInvisibleBreak(node)) {
nsLayoutUtils::IsInvisibleBreak(node)) {
return NS_OK;
}
Element* originalElement =

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

@ -9370,3 +9370,92 @@ nsLayoutUtils::SupportsServoStyleBackend(nsIDocument* aDocument)
aDocument->IsHTMLOrXHTML() &&
static_cast<nsDocument*>(aDocument)->IsContentDocument();
}
static
bool
LineHasNonEmptyContentWorker(nsIFrame* aFrame)
{
// Look for non-empty frames, but ignore inline and br frames.
// For inline frames, descend into the children, if any.
if (aFrame->GetType() == nsGkAtoms::inlineFrame) {
for (nsIFrame* child : aFrame->PrincipalChildList()) {
if (LineHasNonEmptyContentWorker(child)) {
return true;
}
}
} else {
if (aFrame->GetType() != nsGkAtoms::brFrame &&
!aFrame->IsEmpty()) {
return true;
}
}
return false;
}
static
bool
LineHasNonEmptyContent(nsLineBox* aLine)
{
int32_t count = aLine->GetChildCount();
for (nsIFrame* frame = aLine->mFirstChild; count > 0;
--count, frame = frame->GetNextSibling()) {
if (LineHasNonEmptyContentWorker(frame)) {
return true;
}
}
return false;
}
/* static */ bool
nsLayoutUtils::IsInvisibleBreak(nsINode* aNode, nsIFrame** aNextLineFrame)
{
if (aNextLineFrame) {
*aNextLineFrame = nullptr;
}
if (!aNode->IsElement() || !aNode->IsEditable()) {
return false;
}
nsIFrame* frame = aNode->AsElement()->GetPrimaryFrame();
if (!frame || frame->GetType() != nsGkAtoms::brFrame) {
return false;
}
nsContainerFrame* f = frame->GetParent();
while (f && f->IsFrameOfType(nsBox::eLineParticipant)) {
f = f->GetParent();
}
nsBlockFrame* blockAncestor = do_QueryFrame(f);
if (!blockAncestor) {
// The container frame doesn't support line breaking.
return false;
}
bool valid = false;
nsBlockInFlowLineIterator iter(blockAncestor, frame, &valid);
if (!valid) {
return false;
}
bool lineNonEmpty = LineHasNonEmptyContent(iter.GetLine());
if (!lineNonEmpty) {
return false;
}
while (iter.Next()) {
auto currentLine = iter.GetLine();
// Completely skip empty lines.
if (!currentLine->IsEmpty()) {
// If we come across an inline line, the BR has caused a visible line break.
if (currentLine->IsInline()) {
if (aNextLineFrame) {
*aNextLineFrame = currentLine->mFirstChild;
}
return false;
}
break;
}
}
return lineNonEmpty;
}

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

@ -2856,6 +2856,19 @@ public:
*/
static bool SupportsServoStyleBackend(nsIDocument* aDocument);
/*
* Checks whether a node is an invisible break.
* If not, returns the first frame on the next line if such a next line exists.
*
* @return true if the node is an invisible break.
* aNextLineFrame is returned null in this case.
* false if the node causes a visible break or if the node is no break.
*
* @param aNextLineFrame assigned to first frame on the next line if such a
* next line exists, null otherwise.
*/
static bool IsInvisibleBreak(nsINode* aNode, nsIFrame** aNextLineFrame = nullptr);
private:
static uint32_t sFontSizeInflationEmPerLine;
static uint32_t sFontSizeInflationMinTwips;

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

@ -0,0 +1,28 @@
<!DOCTYPE HTML><html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
-->
<head>
<meta charset="utf-8">
<title>Testcase #1 for bug 1263357</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p id="theP"><tt>xyz<br></tt><br></p></div>
<script>
function start() {
var sel = window.getSelection();
// Focus on editable block.
theDiv = document.getElementById("editable");
theP = document.getElementById("theP");
theDiv.focus();
sel.collapse(theP, 1);
}
SimpleTest.waitForFocus(start);
</script>
</body>
</html>

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

@ -0,0 +1,34 @@
<!DOCTYPE HTML><html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
-->
<head>
<meta charset="utf-8">
<title>Testcase #1 for bug1263357</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p><tt>xyz</tt><br></p></div>
<script>
function start() {
var sel = window.getSelection();
// Focus on editable block.
theDiv = document.getElementById("editable");
theDiv.focus();
sel.collapse(theDiv, 0);
if (navigator.platform.indexOf("Win") == 0) {
synthesizeKey("VK_END", {});
} else {
// End key doesn't work as expected on Mac and Linux.
sel.modify("move", "right", "lineboundary");
}
synthesizeKey("VK_RETURN", {shiftKey: true});
}
SimpleTest.waitForFocus(start);
</script>
</body>
</html>

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

@ -0,0 +1,28 @@
<!DOCTYPE HTML><html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
-->
<head>
<meta charset="utf-8">
<title>Testcase #2 for bug 1263357</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p id="theP"><font color=red><span>xyz<br></span></font><br></p></div>
<script>
function start() {
var sel = window.getSelection();
// Focus on editable block.
theDiv = document.getElementById("editable");
theP = document.getElementById("theP");
theDiv.focus();
sel.collapse(theP, 1);
}
SimpleTest.waitForFocus(start);
</script>
</body>
</html>

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

@ -0,0 +1,34 @@
<!DOCTYPE HTML><html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
-->
<head>
<meta charset="utf-8">
<title>Testcase #2 for bug 1263357</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p><font color=red><span>xyz</span></font><br></p></div>
<script>
function start() {
var sel = window.getSelection();
// Focus on editable block.
theDiv = document.getElementById("editable");
theDiv.focus();
sel.collapse(theDiv, 0);
if (navigator.platform.indexOf("Win") == 0) {
synthesizeKey("VK_END", {});
} else {
// End key doesn't work as expected on Mac and Linux.
sel.modify("move", "right", "lineboundary");
}
synthesizeKey("VK_RETURN", {shiftKey: true});
}
SimpleTest.waitForFocus(start);
</script>
</body>
</html>

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

@ -0,0 +1,27 @@
<!DOCTYPE HTML><html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
-->
<head>
<meta charset="utf-8">
<title>Testcase #3 for bug 1263357</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span><br>more</div>
<script>
function start() {
var sel = window.getSelection();
// Focus on editable block.
theDiv = document.getElementById("editable");
theDiv.focus();
sel.collapse(theDiv, 1);
}
SimpleTest.waitForFocus(start);
</script>
</body>
</html>

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

@ -0,0 +1,28 @@
<!DOCTYPE HTML><html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
-->
<head>
<meta charset="utf-8">
<title>Testcase #3 for bug1263357</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span><br>more</div>
<script>
function start() {
var sel = window.getSelection();
// Focus on editable block.
theDiv = document.getElementById("editable");
theSpan = document.getElementById("theSpan");
theDiv.focus();
sel.collapse(theSpan, 2);
}
SimpleTest.waitForFocus(start);
</script>
</body>
</html>

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

@ -0,0 +1,27 @@
<!DOCTYPE HTML><html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
-->
<head>
<meta charset="utf-8">
<title>Testcase #4 for bug 1263357</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span>more</div>
<script>
function start() {
var sel = window.getSelection();
// Focus on editable block.
theDiv = document.getElementById("editable");
theDiv.focus();
sel.collapse(theDiv, 1);
}
SimpleTest.waitForFocus(start);
</script>
</body>
</html>

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

@ -0,0 +1,28 @@
<!DOCTYPE HTML><html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
-->
<head>
<meta charset="utf-8">
<title>Testcase #4 for bug1263357</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span>more</div>
<script>
function start() {
var sel = window.getSelection();
// Focus on editable block.
theDiv = document.getElementById("editable");
theSpan = document.getElementById("theSpan");
theDiv.focus();
sel.collapse(theSpan, 2);
}
SimpleTest.waitForFocus(start);
</script>
</body>
</html>

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

@ -0,0 +1,27 @@
<!DOCTYPE HTML><html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
-->
<head>
<meta charset="utf-8">
<title>Testcase #5 for bug 1263357</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span> more</div>
<script>
function start() {
var sel = window.getSelection();
// Focus on editable block.
theDiv = document.getElementById("editable");
theDiv.focus();
sel.collapse(theDiv, 1);
}
SimpleTest.waitForFocus(start);
</script>
</body>
</html>

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

@ -0,0 +1,28 @@
<!DOCTYPE HTML><html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
-->
<head>
<meta charset="utf-8">
<title>Testcase #5 for bug1263357</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
</head>
<body>
<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span> more</div>
<script>
function start() {
var sel = window.getSelection();
// Focus on editable block.
theDiv = document.getElementById("editable");
theSpan = document.getElementById("theSpan");
theDiv.focus();
sel.collapse(theSpan, 2);
}
SimpleTest.waitForFocus(start);
</script>
</body>
</html>

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

@ -173,6 +173,16 @@ support-files =
bug1082486-1-ref.html
bug1082486-2.html
bug1082486-2-ref.html
bug1263357-1.html
bug1263357-1-ref.html
bug1263357-2.html
bug1263357-2-ref.html
bug1263357-3.html
bug1263357-3-ref.html
bug1263357-4.html
bug1263357-4-ref.html
bug1263357-5.html
bug1263357-5-ref.html
input-maxlength-valid-before-change.html
input-maxlength-valid-change.html
input-maxlength-invalid-change.html

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

@ -181,6 +181,11 @@ var tests = [
[ 'bug1259949-1.html' , 'bug1259949-1-ref.html'] ,
[ 'bug1259949-2.html' , 'bug1259949-2-ref.html'] ,
[ 'bug1263288.html' , 'bug1263288-ref.html'] ,
[ 'bug1263357-1.html' , 'bug1263357-1-ref.html'] ,
[ 'bug1263357-2.html' , 'bug1263357-2-ref.html'] ,
[ 'bug1263357-3.html' , 'bug1263357-3-ref.html'] ,
[ 'bug1263357-4.html' , 'bug1263357-4-ref.html'] ,
[ 'bug1263357-5.html' , 'bug1263357-5-ref.html'] ,
function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.enabled']]}, nextTest);} ,
];

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

@ -1992,7 +1992,7 @@ nsFrameSelection::RepaintSelection(SelectionType aSelectionType)
#endif
return mDomSelections[index]->Repaint(mShell->GetPresContext());
}
nsIFrame*
nsFrameSelection::GetFrameForNodeOffset(nsIContent* aNode,
int32_t aOffset,
@ -2011,11 +2011,12 @@ nsFrameSelection::GetFrameForNodeOffset(nsIContent* aNode,
}
nsIFrame* returnFrame = nullptr;
nsCOMPtr<nsIContent> theNode;
while (true) {
*aReturnOffset = aOffset;
nsCOMPtr<nsIContent> theNode = aNode;
theNode = aNode;
if (aNode->IsElement()) {
int32_t childIndex = 0;
@ -2135,6 +2136,18 @@ nsFrameSelection::GetFrameForNodeOffset(nsIContent* aNode,
if (!returnFrame)
return nullptr;
// If we ended up here and were asked to position the caret after a visible
// break, let's return the frame on the next line instead if it exists.
if (aOffset > 0 && (uint32_t) aOffset >= aNode->Length() &&
theNode == aNode->GetLastChild()) {
nsIFrame* newFrame;
nsLayoutUtils::IsInvisibleBreak(theNode, &newFrame);
if (newFrame) {
returnFrame = newFrame;
*aReturnOffset = 0;
}
}
// find the child frame containing the offset we want
returnFrame->GetChildFrameContainingOffset(*aReturnOffset, aHint == CARET_ASSOCIATE_AFTER,
&aOffset, &returnFrame);