Skip to content

Commit

Permalink
Add support for writingsuggestions attribute
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=266824
rdar://114989563

Reviewed by Aditya Keerthi.

Implement support for the new DOM `writingsuggstions` web API attribute, as defined by the
corresponding spec PR (whatwg/html#10018).

* Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml:
* Source/WTF/wtf/PlatformEnable.h:
* Source/WebCore/dom/Element.cpp:
(WebCore::Element::isWritingSuggestionsEnabled const):
* Source/WebCore/dom/Element.h:
* Source/WebCore/editing/VisibleSelection.cpp:
(WebCore::VisibleSelection::canEnableWritingSuggestions const):
* Source/WebCore/editing/VisibleSelection.h:
* Source/WebCore/html/HTMLAttributeNames.in:
* Source/WebCore/html/HTMLElement.cpp:
(WebCore::HTMLElement::writingsuggestions const):
(WebCore::HTMLElement::setWritingsuggestions):
* Source/WebCore/html/HTMLElement.h:
* Source/WebCore/html/HTMLElement.idl:
* Source/WebCore/html/HTMLInputElement.cpp:
(WebCore::HTMLInputElement::supportsWritingSuggestions const):
* Source/WebCore/html/HTMLInputElement.h:
* Source/WebKit/Shared/EditorState.cpp:
(WebKit::operator<<):
* Source/WebKit/Shared/EditorState.h:
* Source/WebKit/Shared/EditorState.serialization.in:
* Source/WebKit/Shared/FocusedElementInformation.h:
* Source/WebKit/Shared/FocusedElementInformation.serialization.in:
* Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView _updateTextInputTraits:]):
* Source/WebKit/UIProcess/mac/WebViewImpl.mm:
(WebKit::WebViewImpl::postLayoutDataForContentEditable):
(WebKit::WebViewImpl::allowsInlinePredictions const):
* Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm:
(WebKit::WebPage::getPlatformEditorStateCommon const):
* Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::focusedElementInformation):
* Tools/TestWebKitAPI/SourcesCocoa.txt:
* Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WritingSuggestions.mm: Added.
(-[WritingSuggestionsWebAPIWKWebView initWithHTMLString:]):
(-[WritingSuggestionsWebAPIWKWebView focusElementAndEnsureEditorStateUpdate:]):
(TEST):

Canonical link: https://commits.webkit.org/274912@main
  • Loading branch information
rr-codes committed Feb 17, 2024
1 parent 40fc435 commit e1b69e3
Show file tree
Hide file tree
Showing 24 changed files with 258 additions and 3 deletions.
15 changes: 15 additions & 0 deletions Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8326,6 +8326,21 @@ WriteRichTextDataWhenCopyingOrDragging:
WebCore:
default: true

WritingSuggestionsAttributeEnabled:
type: bool
status: stable
category: dom
humanReadableName: "Writing Suggestions"
humanReadableDescription: "Enable the writingsuggestions attribute"
condition: ENABLE(WRITING_SUGGESTIONS)
defaultValue:
WebKitLegacy:
default: true
WebKit:
default: true
WebCore:
default: true

ZoomOnDoubleTapWhenRoot:
type: bool
status: internal
Expand Down
5 changes: 5 additions & 0 deletions Source/WTF/wtf/PlatformEnable.h
Original file line number Diff line number Diff line change
Expand Up @@ -995,3 +995,8 @@
&& USE(LINEARMEDIAKIT)
#define ENABLE_LINEAR_MEDIA_PLAYER 0
#endif

#if !defined(ENABLE_WRITING_SUGGESTIONS) \
&& (PLATFORM(COCOA) && HAVE(INLINE_PREDICTIONS))
#define ENABLE_WRITING_SUGGESTIONS 1
#endif
49 changes: 49 additions & 0 deletions Source/WebCore/dom/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@
#include "HTMLScriptElement.h"
#include "HTMLSelectElement.h"
#include "HTMLTemplateElement.h"
#include "HTMLTextAreaElement.h"
#include "IdChangeInvalidation.h"
#include "IdTargetObserverRegistry.h"
#include "InputType.h"
#include "InspectorInstrumentation.h"
#include "JSDOMPromiseDeferred.h"
#include "JSLazyEventListener.h"
Expand Down Expand Up @@ -4752,6 +4754,53 @@ bool Element::isSpellCheckingEnabled() const
return true;
}

bool Element::isWritingSuggestionsEnabled() const
{
// If none of the following conditions are true, then return `false`.

// `element` is an `input` element whose `type` attribute is in either the
// `Text`, `Search`, `URL`, `Email` state and is `mutable`.
auto isEligibleInputElement = [&] {
RefPtr input = dynamicDowncast<HTMLInputElement>(*this);
if (!input)
return false;

return !input->isDisabledFormControl() && input->supportsWritingSuggestions();
};

// `element` is a `textarea` element that is `mutable`.
auto isEligibleTextArea = [&] {
RefPtr textArea = dynamicDowncast<HTMLTextAreaElement>(*this);
if (!textArea)
return false;

return !textArea->isDisabledFormControl();
};

// `element` is an `editing host` or is `editable`.

if (!isEligibleInputElement() && !isEligibleTextArea() && !hasEditableStyle())
return false;

// If `element` has an 'inclusive ancestor' with a `writingsuggestions` content attribute that's
// not in the `default` state and the nearest such ancestor's `writingsuggestions` content attribute
// is in the `false` state, then return `false`.

for (auto* ancestor = this; ancestor; ancestor = ancestor->parentElementInComposedTree()) {
auto& value = ancestor->attributeWithoutSynchronization(HTMLNames::writingsuggestionsAttr);

if (value.isNull())
continue;
if (value.isEmpty() || equalLettersIgnoringASCIICase(value, "true"_s))
return true;
if (equalLettersIgnoringASCIICase(value, "false"_s))
return false;
}

// Otherwise, return `true`.
return true;
}

#if ASSERT_ENABLED
bool Element::fastAttributeLookupAllowed(const QualifiedName& name) const
{
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/dom/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ class Element : public ContainerNode {
#endif

bool isSpellCheckingEnabled() const;
WEBCORE_EXPORT bool isWritingSuggestionsEnabled() const;

inline bool hasID() const;
inline bool hasClass() const;
Expand Down
15 changes: 15 additions & 0 deletions Source/WebCore/editing/VisibleSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,21 @@ bool VisibleSelection::isInPasswordField() const
return textControl && textControl->isPasswordField();
}

bool VisibleSelection::canEnableWritingSuggestions() const
{
RefPtr containerNode = start().containerNode();
if (!containerNode)
return false;

if (RefPtr element = dynamicDowncast<Element>(containerNode.get()))
return element->isWritingSuggestionsEnabled();

if (RefPtr element = containerNode->parentElement())
return element->isWritingSuggestionsEnabled();

return false;
}

bool VisibleSelection::isInAutoFilledAndViewableField() const
{
if (RefPtr input = dynamicDowncast<HTMLInputElement>(enclosingTextFormControl(start())))
Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/editing/VisibleSelection.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ class VisibleSelection {
WEBCORE_EXPORT bool isInPasswordField() const;
WEBCORE_EXPORT bool isInAutoFilledAndViewableField() const;

WEBCORE_EXPORT bool canEnableWritingSuggestions() const;

WEBCORE_EXPORT static Position adjustPositionForEnd(const Position& currentPosition, Node* startContainerNode);
WEBCORE_EXPORT static Position adjustPositionForStart(const Position& currentPosition, Node* startContainerNode);

Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/html/HTMLAttributeNames.in
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ webkitdirectory
webkitdropzone
width
wrap
writingsuggestions
x-apple-data-detectors
x-apple-data-detectors-result
x-apple-data-detectors-type
Expand Down
10 changes: 10 additions & 0 deletions Source/WebCore/html/HTMLElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,16 @@ void HTMLElement::setSpellcheck(bool enable)
setAttributeWithoutSynchronization(spellcheckAttr, enable ? trueAtom() : falseAtom());
}

bool HTMLElement::writingsuggestions() const
{
return isWritingSuggestionsEnabled();
}

void HTMLElement::setWritingsuggestions(bool enable)
{
setAttributeWithoutSynchronization(writingsuggestionsAttr, enable ? trueAtom() : falseAtom());
}

void HTMLElement::effectiveSpellcheckAttributeChanged(bool newValue)
{
for (auto it = descendantsOfType<HTMLElement>(*this).begin(); it;) {
Expand Down
3 changes: 3 additions & 0 deletions Source/WebCore/html/HTMLElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ class HTMLElement : public StyledElement {
WEBCORE_EXPORT bool spellcheck() const;
WEBCORE_EXPORT void setSpellcheck(bool);

WEBCORE_EXPORT bool writingsuggestions() const;
WEBCORE_EXPORT void setWritingsuggestions(bool);

WEBCORE_EXPORT bool translate() const;
WEBCORE_EXPORT void setTranslate(bool);

Expand Down
2 changes: 2 additions & 0 deletions Source/WebCore/html/HTMLElement.idl
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@

// Non-standard: We are the only browser to support this now that Blink dropped it (http://crbug.com/688943).
[CEReactions=Needed, Reflect] attribute DOMString webkitdropzone;

[Conditional=WRITING_SUGGESTIONS, CEReactions=Needed] attribute boolean writingsuggestions;
};

HTMLElement includes GlobalEventHandlers;
Expand Down
12 changes: 12 additions & 0 deletions Source/WebCore/html/HTMLInputElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,18 @@ bool HTMLInputElement::isTextType() const
return m_inputType->isTextType();
}

bool HTMLInputElement::supportsWritingSuggestions() const
{
static constexpr OptionSet<InputType::Type> allowedTypes = {
InputType::Type::Text,
InputType::Type::Search,
InputType::Type::URL,
InputType::Type::Email,
};

return allowedTypes.contains(m_inputType->type());
}

void HTMLInputElement::setDefaultCheckedState(bool isDefaultChecked)
{
if (m_isDefaultChecked == isDefaultChecked)
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/html/HTMLInputElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class HTMLInputElement : public HTMLTextFormControlElement {
// isTextField && !isPasswordField.
WEBCORE_EXPORT bool isText() const;
bool isTextType() const;
bool supportsWritingSuggestions() const;
WEBCORE_EXPORT bool isEmailField() const;
WEBCORE_EXPORT bool isFileUpload() const;
bool isImageButton() const;
Expand Down
2 changes: 2 additions & 0 deletions Source/WebKit/Shared/EditorState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ TextStream& operator<<(TextStream& ts, const EditorState& editorState)
ts.dumpProperty("enclosingListType", enumToUnderlyingType(editorState.postLayoutData->enclosingListType));
if (editorState.postLayoutData->baseWritingDirection != WebCore::WritingDirection::Natural)
ts.dumpProperty("baseWritingDirection", static_cast<uint8_t>(editorState.postLayoutData->baseWritingDirection));
if (editorState.postLayoutData->canEnableWritingSuggestions)
ts.dumpProperty("canEnableWritingSuggestions", editorState.postLayoutData->canEnableWritingSuggestions);
#endif // PLATFORM(COCOA)
#if PLATFORM(IOS_FAMILY)
if (editorState.postLayoutData->markedText.length())
Expand Down
1 change: 1 addition & 0 deletions Source/WebKit/Shared/EditorState.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ struct EditorState {
ListType enclosingListType { ListType::None };
WebCore::WritingDirection baseWritingDirection { WebCore::WritingDirection::Natural };
bool editableRootIsTransparentOrFullyClipped { false };
bool canEnableWritingSuggestions { false };
#endif
#if PLATFORM(IOS_FAMILY)
String markedText;
Expand Down
1 change: 1 addition & 0 deletions Source/WebKit/Shared/EditorState.serialization.in
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ struct WebKit::EditorState {
WebKit::ListType enclosingListType;
WebCore::WritingDirection baseWritingDirection;
bool editableRootIsTransparentOrFullyClipped;
bool canEnableWritingSuggestions;
#endif
#if PLATFORM(IOS_FAMILY)
String markedText;
Expand Down
1 change: 1 addition & 0 deletions Source/WebKit/Shared/FocusedElementInformation.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ struct FocusedElementInformation {
bool hasEverBeenPasswordField { false };
bool shouldSynthesizeKeyEventsForEditing { false };
bool isSpellCheckingEnabled { true };
bool isWritingSuggestionsEnabled { false };
bool shouldAvoidResizingWhenInputViewBoundsChange { false };
bool shouldAvoidScrollingWhenFocusedContentIsVisible { false };
bool shouldUseLegacySelectPopoverDismissalBehaviorInDataActivation { false };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ enum class WebKit::InputType : uint8_t {
bool hasEverBeenPasswordField;
bool shouldSynthesizeKeyEventsForEditing;
bool isSpellCheckingEnabled;
bool isWritingSuggestionsEnabled;
bool shouldAvoidResizingWhenInputViewBoundsChange;
bool shouldAvoidScrollingWhenFocusedContentIsVisible;
bool shouldUseLegacySelectPopoverDismissalBehaviorInDataActivation;
Expand Down
2 changes: 1 addition & 1 deletion Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Original file line number Diff line number Diff line change
Expand Up @@ -6915,7 +6915,7 @@ - (void)_updateTextInputTraits:(id<UITextInputTraits>)traits
privateTraits.shortcutConversionType = _focusedElementInformation.elementType == WebKit::InputType::Password ? UITextShortcutConversionTypeNo : UITextShortcutConversionTypeDefault;

#if HAVE(INLINE_PREDICTIONS)
traits.inlinePredictionType = (self.webView.configuration.allowsInlinePredictions || _page->preferences().inlinePredictionsInAllEditableElementsEnabled()) ? UITextInlinePredictionTypeDefault : UITextInlinePredictionTypeNo;
traits.inlinePredictionType = (self.webView.configuration.allowsInlinePredictions || _page->preferences().inlinePredictionsInAllEditableElementsEnabled() || _focusedElementInformation.isWritingSuggestionsEnabled) ? UITextInlinePredictionTypeDefault : UITextInlinePredictionTypeNo;
#endif

[self _updateTextInputTraitsForInteractionTintColor];
Expand Down
10 changes: 8 additions & 2 deletions Source/WebKit/UIProcess/mac/WebViewImpl.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3210,8 +3210,6 @@ static String commandNameForSelector(SEL selector)
std::optional<EditorState::PostLayoutData> WebViewImpl::postLayoutDataForContentEditable()
{
const EditorState& editorState = m_page->editorState();
if (!editorState.isContentEditable)
return std::nullopt;

// FIXME: It's pretty lame that we have to depend on the most recent EditorState having post layout data,
// and that we just bail if it is missing.
Expand Down Expand Up @@ -5195,6 +5193,14 @@ static BOOL shouldUseHighlightsForMarkedText(NSAttributedString *string)
#if HAVE(INLINE_PREDICTIONS)
bool WebViewImpl::allowsInlinePredictions() const
{
const EditorState& editorState = m_page->editorState();

if (editorState.hasPostLayoutData() && editorState.postLayoutData->canEnableWritingSuggestions)
return NSSpellChecker.isAutomaticInlineCompletionEnabled;

if (!editorState.isContentEditable)
return false;

if (!inlinePredictionsEnabled() && !m_page->preferences().inlinePredictionsInAllEditableElementsEnabled())
return false;

Expand Down
2 changes: 2 additions & 0 deletions Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,8 @@
}

postLayoutData.baseWritingDirection = frame.editor().baseWritingDirectionForSelectionStart();

postLayoutData.canEnableWritingSuggestions = selection.canEnableWritingSuggestions();
}

if (RefPtr editableRootOrFormControl = enclosingTextFormControl(selection.start()) ?: selection.rootEditableElement()) {
Expand Down
2 changes: 2 additions & 0 deletions Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3550,6 +3550,8 @@ static void handleAnimationActions(Element& element, uint32_t action)
if (htmlElement)
information.isSpellCheckingEnabled = htmlElement->spellcheck();

information.isWritingSuggestionsEnabled = focusedElement->isWritingSuggestionsEnabled();

if (RefPtr formControlElement = dynamicDowncast<HTMLFormControlElement>(focusedElement))
information.isFocusingWithValidationMessage = formControlElement->isFocusingWithValidationMessage();

Expand Down
1 change: 1 addition & 0 deletions Tools/TestWebKitAPI/SourcesCocoa.txt
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ Tests/WebKitCocoa/WebSQLBasics.mm
Tests/WebKitCocoa/WebSocket.mm
Tests/WebKitCocoa/WebsiteDataStoreCustomPaths.mm
Tests/WebKitCocoa/WebsitePolicies.mm
Tests/WebKitCocoa/WritingSuggestions.mm
Tests/WebKitCocoa/YoutubeReplacementPlugin.mm
Tests/WebKitCocoa/_WKInputDelegate.mm
Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm
Expand Down
2 changes: 2 additions & 0 deletions Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2013,6 +2013,7 @@
0711DF51226A95FB003DD2F7 /* AVFoundationSoftLinkTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AVFoundationSoftLinkTest.mm; sourceTree = "<group>"; };
07137049265320E500CA2C9A /* AudioBufferSize.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AudioBufferSize.mm; sourceTree = "<group>"; };
0721D4582838295400A95853 /* start-offset.ts */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.typescript; path = "start-offset.ts"; sourceTree = "<group>"; };
07338E042B7433A400F949EB /* WritingSuggestions.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = WritingSuggestions.mm; sourceTree = "<group>"; };
0738012E275EADAB000FA77C /* GetDisplayMediaWindowAndScreen.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = GetDisplayMediaWindowAndScreen.mm; sourceTree = "<group>"; };
0746645722FF62D000E3451A /* AccessibilityTestSupportProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AccessibilityTestSupportProtocol.h; sourceTree = "<group>"; };
0746645822FF630500E3451A /* AccessibilityTestPlugin.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AccessibilityTestPlugin.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4337,6 +4338,7 @@
9984FACA1CFFAEEE008D198C /* WKWebViewTextInput.mm */,
95A524942581A10D00461FE9 /* WKWebViewThemeColor.mm */,
953ABB3425C0D681004C8B73 /* WKWebViewUnderPageBackgroundColor.mm */,
07338E042B7433A400F949EB /* WritingSuggestions.mm */,
465A08FD280096F80028FD0E /* YoutubeReplacementPlugin.mm */,
);
name = "WebKit Cocoa";
Expand Down
Loading

0 comments on commit e1b69e3

Please sign in to comment.