Skip to content
Permalink
Browse files
:focus pseudo class fails to repaint on a shadow host when focus move…
…s from outside the page inside a shadow tree

https://bugs.webkit.org/show_bug.cgi?id=205531

Reviewed by Simon Fraser, Darin Adler and Wenson Hsieh.

The bug was caused by FrameSelection::focusedOrActiveStateChanged only invalidating the style of the focused element.
Invalidate each outer shadow host when the focus status changes to fix the bug.

* Source/WebCore/editing/FrameSelection.cpp:
(WebCore::FrameSelection::focusedOrActiveStateChanged): Fixed the bug.
* Tools/WebKitTestRunner/cocoa/UIScriptControllerCocoa.h:
* Tools/WebKitTestRunner/cocoa/UIScriptControllerCocoa.mm:
(WTR::UIScriptControllerCocoa::becomeFirstResponder): Moved to UIScriptControllerIOS.
(WTR::UIScriptControllerCocoa::resignFirstResponder): Ditto.
* Tools/WebKitTestRunner/ios/UIScriptControllerIOS.h:
* Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptControllerIOS::becomeFirstResponder): Moved from UIScriptControllerCocoa.
(WTR::UIScriptControllerIOS::resignFirstResponder): Ditto.
* Tools/WebKitTestRunner/mac/UIScriptControllerMac.h:
* Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm:
(WTR::UIScriptControllerMac::becomeFirstResponder): Added.
(WTR::UIScriptControllerMac::resignFirstResponder): Added.

* LayoutTests/fast/shadow-dom/focus-ring-on-shadow-host-expected.html: Added.
* LayoutTests/fast/shadow-dom/focus-ring-on-shadow-host.html: Added.
* LayoutTests/resources/ui-helper.js:
(window.UIHelper.resignFirstResponder): Added the support for WebKit1.
(window.UIHelper.becomeFirstResponder): Ditto.
* LayoutTests/platform/gtk/TestExpectations: Skip the test as resignFirstResponder is not implemented on GTK+.
* LayoutTests/platform/win/TestExpectations: Ditto on Windows.

Canonical link: https://commits.webkit.org/252261@main
  • Loading branch information
rniwa committed Jul 8, 2022
1 parent 56cbe67 commit e92078a985a7af424a36ba53b36ff89412cfa4e8
Showing 12 changed files with 104 additions and 18 deletions.
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<body>
<style>
#host {
width: 100px;
height: 100px;
background: green;
}
</style>
<div id="host"></div>
</body>
</html>
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<body>
<style>
#host:focus {
width: 50px;
height: 50px;
background: green;
overflow: hidden;
}
</style>
<div id="host"></div>
<script src="../../resources/ui-helper.js"></script>
<script>

const shadowRoot = host.attachShadow({mode: 'closed', delegatesFocus: true});
shadowRoot.innerHTML = `<div tabindex="0"></div>
<style>
div { width: 100px; height: 100px; background: green; }
</style>`;
const foucsableElement = shadowRoot.querySelector('div');

onload = runTest;

async function runTest() {
if (!window.testRunner)
return;

testRunner.waitUntilDone();

eventSender.mouseMoveTo(host.offsetLeft + 5, host.offsetTop + 5);
eventSender.mouseDown();
eventSender.mouseUp();

await new Promise((resolve) => requestAnimationFrame(() => setTimeout(resolve, 0)));

if (UIHelper.isWebKit2())
await UIHelper.resignFirstResponder();
else
window.testRunner.setMainFrameIsFirstResponder(false);

await new Promise((resolve) => requestAnimationFrame(() => setTimeout(resolve, 0)));

testRunner.notifyDone();
}

</script>
</body>
</html>
@@ -910,6 +910,9 @@ webkit.org/b/239750 media/media-source/media-mp4-xhe-aac.html [ Skip ]
webkit.org/b/242145 media/mediacapabilities/mediacapabilities-allowed-codecs.html [ Skip ]
webkit.org/b/242145 media/mediacapabilities/mediacapabilities-allowed-containers.html [ Skip ]

# resignFirstResponder is not implemented on GTK+
fast/shadow-dom/focus-ring-on-shadow-host.html

#////////////////////////////////////////////////////////////////////////////////////////
# End of Expected failures.
#////////////////////////////////////////////////////////////////////////////////////////
@@ -988,6 +988,9 @@ webkit.org/b/77568 fast/text/locale-shaping-complex.html [ ImageOnlyFailure ]
# WebGL doesn't seem to work on windows
fast/canvas/toDataURL-alpha-permutation.html [ ImageOnlyFailure ]

# resignFirstResponder is not implemented on Windows
fast/shadow-dom/focus-ring-on-shadow-host.html

################################################################################
########### End Missing Functionality Prevents Testing ##############
################################################################################
@@ -1190,16 +1190,20 @@ window.UIHelper = class UIHelper {

static resignFirstResponder()
{
if (!this.isWebKit2())
if (!this.isWebKit2()) {
testRunner.setMainFrameIsFirstResponder(false);
return Promise.resolve();
}

return new Promise(resolve => testRunner.runUIScript(`uiController.resignFirstResponder()`, resolve));
}

static becomeFirstResponder()
{
if (!this.isWebKit2())
if (!this.isWebKit2()) {
testRunner.setMainFrameIsFirstResponder(true);
return Promise.resolve();
}

return new Promise(resolve => testRunner.runUIScript(`uiController.becomeFirstResponder()`, resolve));
}
@@ -2146,11 +2146,10 @@ void FrameSelection::focusedOrActiveStateChanged()
// Because Style::Resolver::checkOneSelector() and
// RenderTheme::isFocused() check if the frame is active, we have to
// update style and theme state that depended on those.
if (Element* element = m_document->focusedElement()) {
for (RefPtr element = m_document->focusedElement(); element; element = element->shadowHost()) {
element->invalidateStyleForSubtree();
if (RenderObject* renderer = element->renderer())
if (renderer && renderer->style().hasEffectiveAppearance())
renderer->theme().stateChanged(*renderer, ControlStates::States::Focused);
if (RenderObject* renderer = element->renderer(); renderer && renderer->style().hasEffectiveAppearance())
renderer->theme().stateChanged(*renderer, ControlStates::States::Focused);
}
#endif
}
@@ -42,8 +42,6 @@ class UIScriptControllerCocoa : public UIScriptControllerCommon {
void setViewScale(double) override;
void setMinimumEffectiveWidth(double) override;
void setWebViewEditable(bool) override;
void becomeFirstResponder() override;
void resignFirstResponder() override;
void removeViewFromWindow(JSValueRef) override;
void addViewToWindow(JSValueRef) override;
void overridePreference(JSStringRef, JSStringRef) override;
@@ -68,16 +68,6 @@ - (void)paste:(id)sender;
webView()._editable = editable;
}

void UIScriptControllerCocoa::becomeFirstResponder()
{
[webView() becomeFirstResponder];
}

void UIScriptControllerCocoa::resignFirstResponder()
{
[webView() resignFirstResponder];
}

void UIScriptControllerCocoa::doAsyncTask(JSValueRef callback)
{
unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent);
@@ -176,6 +176,8 @@ class UIScriptControllerIOS final : public UIScriptControllerCocoa {
JSObjectRef toObject(CGRect) const;

bool isWebContentFirstResponder() const override;
void becomeFirstResponder() override;
void resignFirstResponder() override;

void simulateRotation(DeviceOrientation, JSValueRef callback);
};
@@ -1391,6 +1391,16 @@ static UIDeviceOrientation toUIDeviceOrientation(DeviceOrientation orientation)
return [webView() _contentViewIsFirstResponder];
}

void UIScriptControllerIOS::becomeFirstResponder()
{
[webView() becomeFirstResponder];
}

void UIScriptControllerIOS::resignFirstResponder()
{
[webView() resignFirstResponder];
}

}

#endif // PLATFORM(IOS_FAMILY)
@@ -54,6 +54,8 @@ class UIScriptControllerMac final : public UIScriptControllerCocoa {
void firstResponderSuppressionForWebView(bool) override;
void makeWindowContentViewFirstResponder() override;
bool isWindowContentViewFirstResponder() const override;
void becomeFirstResponder() override;
void resignFirstResponder() override;
void toggleCapsLock(JSValueRef) override;
NSView *platformContentView() const override;
void clearAllCallbacks() override;
@@ -252,6 +252,19 @@ static void playBackEvents(WKWebView *webView, UIScriptContext *context, NSStrin
return [window firstResponder] == [window contentView];
}

void UIScriptControllerMac::becomeFirstResponder()
{
auto *webView = this->webView();
[webView.window makeFirstResponder:webView];
}

void UIScriptControllerMac::resignFirstResponder()
{
auto *webView = this->webView();
if (webView.window.firstResponder == webView)
[webView.window makeFirstResponder:nil];
}

void UIScriptControllerMac::toggleCapsLock(JSValueRef callback)
{
m_capsLockOn = !m_capsLockOn;

0 comments on commit e92078a

Please sign in to comment.