mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1921146 - Make SelectionMovementUtils::GetFrameForNodeOffset
use last frame when the loop ended with invisible content r=emilio a=RyanVM
The editor in ChatGPT has only invisible `<br>` (`display: none`) and text content as `::after`. `SelectionMovementUtils::GetFrameForNodeOffset` assumes that at least one child has a primary frame. Therefore, it may return `nullptr` and `nsCaret` fails to paint the caret due to no frame. So, it should use a parent frame in such case. However, doing it may cause an assertion failure [1] when `IMEContentObserver::UpdateSelectionCache()` is called while an editor gets focus and the editor does not have visible children. Therefore, this patch makes it allow to flush pending notifications when it gets current selection range in the flattened text. Fortunately, this change does not make damage to the performance. I guess the reason is, sending focus notification synchronously will calling `ContentCacheInChild::CacheAll` soon. Then, it collects all information which should be cached in the parent process with flushing the pending layout. Therefore, this change should just do that a little bit faster than before. 1. https://searchfox.org/mozilla-central/rev/7e0ae4372c52b8183d1178132dd6493edb576738/layout/generic/nsIFrame.cpp#2039 Differential Revision: https://phabricator.services.mozilla.com/D224561
This commit is contained in:
parent
93fa74e203
commit
4d7835983e
@ -1953,16 +1953,13 @@ void IMEContentObserver::IMENotificationSender::SendFocusSet() {
|
||||
}
|
||||
|
||||
observer->mIMEHasFocus = true;
|
||||
// Initialize selection cache with the first selection data.
|
||||
#ifdef XP_MACOSX
|
||||
// We need to flush layout only on macOS because character coordinates are
|
||||
// cached by cocoa with this call, but we don't have a way to update them
|
||||
// after that. Therefore, we need the latest layout information right now.
|
||||
// Initialize selection cache with the first selection data. However, this
|
||||
// may be handled synchronously when the editor gets focus. In that case,
|
||||
// some frames may be dirty and they may be required to get caret frame in
|
||||
// ContentEventHandler::Init() to get the nearest widget from the selection.
|
||||
// Therefore, we need to update selection cache with flushing the pending
|
||||
// notifications.
|
||||
observer->UpdateSelectionCache(true);
|
||||
#else
|
||||
// We avoid flushing for focus in the general case.
|
||||
observer->UpdateSelectionCache(false);
|
||||
#endif // #ifdef XP_MACOSX #else
|
||||
MOZ_LOG(sIMECOLog, LogLevel::Info,
|
||||
("0x%p IMENotificationSender::SendFocusSet(), sending "
|
||||
"NOTIFY_IME_OF_FOCUS...",
|
||||
|
@ -206,11 +206,15 @@ nsIFrame* SelectionMovementUtils::GetFrameForNodeOffset(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsIFrame* returnFrame = nullptr;
|
||||
nsIFrame *returnFrame = nullptr, *lastFrame = aNode->GetPrimaryFrame();
|
||||
nsCOMPtr<nsIContent> theNode;
|
||||
uint32_t offsetInFrameContent;
|
||||
uint32_t offsetInFrameContent, offsetInLastFrameContent = 0;
|
||||
|
||||
while (true) {
|
||||
if (returnFrame) {
|
||||
lastFrame = returnFrame;
|
||||
offsetInLastFrameContent = offsetInFrameContent;
|
||||
}
|
||||
offsetInFrameContent = aOffset;
|
||||
|
||||
theNode = aNode;
|
||||
@ -322,7 +326,11 @@ nsIFrame* SelectionMovementUtils::GetFrameForNodeOffset(
|
||||
} // end while
|
||||
|
||||
if (!returnFrame) {
|
||||
return nullptr;
|
||||
if (!lastFrame) {
|
||||
return nullptr;
|
||||
}
|
||||
returnFrame = lastFrame;
|
||||
offsetInFrameContent = offsetInLastFrameContent;
|
||||
}
|
||||
|
||||
// If we ended up here and were asked to position the caret after a visible
|
||||
|
@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html class="reftest-wait">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Caret should be rendered in the editing host which has no visible non-anonymous content</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
div[contenteditable]::after {
|
||||
content: "placeholder text";
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
addEventListener("load", async () => {
|
||||
const editingHost = document.querySelector("div[contenteditable]");
|
||||
document.activeElement?.blur();
|
||||
const waitForFocus = new Promise(resolve =>
|
||||
editingHost.addEventListener("focus", resolve, {once: true})
|
||||
);
|
||||
editingHost.focus();
|
||||
await waitForFocus;
|
||||
document.documentElement.removeAttribute("class");
|
||||
}, {once: true});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div contenteditable></div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html class="reftest-wait">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Caret should be rendered in the editing host which has no visible non-anonymous content</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link rel="match" href="editing-host-has-only-invisible-br-ref.html">
|
||||
<style>
|
||||
div[contenteditable]::after {
|
||||
content: "placeholder text";
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
addEventListener("load", async () => {
|
||||
const editingHost = document.querySelector("div[contenteditable]");
|
||||
document.activeElement?.blur();
|
||||
const waitForFocus = new Promise(resolve =>
|
||||
editingHost.addEventListener("focus", resolve, {once: true})
|
||||
);
|
||||
editingHost.focus();
|
||||
await waitForFocus;
|
||||
document.documentElement.removeAttribute("class");
|
||||
}, {once: true});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div contenteditable><br style="display:none"></div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user