Bug Summary

File:Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
Warning:line 2588, column 15
Local variable 'cachedImage' is uncounted and unsafe

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple arm64-apple-ios26.2.0-simulator -Os -Wundef-prefix=TARGET_OS_ -Werror=undef-prefix -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -Werror=implicit-function-declaration -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name UnifiedSource76-nonARC.mm -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=osx -analyzer-checker=security.insecureAPI.decodeValueOfObjCType -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -mframe-pointer=non-leaf -ffp-contract=on -fno-rounding-math -target-sdk-version=26.2 -fdepfile-entry=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/SDKSettings.json -fcompatibility-qualified-id-block-type-checking -fvisibility-inlines-hidden-static-local-var -fno-modulemap-allow-subdirectory-search -fdefine-target-os-macros -enable-tlsdesc -target-cpu apple-m1 -target-feature +v8.4a -target-feature +aes -target-feature +altnzcv -target-feature +ccdp -target-feature +ccpp -target-feature +complxnum -target-feature +crc -target-feature +dotprod -target-feature +flagm -target-feature +fp-armv8 -target-feature +fp16fml -target-feature +fptoint -target-feature +fullfp16 -target-feature +jsconv -target-feature +lse -target-feature +neon -target-feature +pauth -target-feature +perfmon -target-feature +predres -target-feature +ras -target-feature +rcpc -target-feature +rdm -target-feature +sb -target-feature +sha2 -target-feature +sha3 -target-feature +specrestrict -target-feature +ssbs -target-abi darwinpcs -dwarf-ext-refs -fmodule-format=obj -debug-info-kind=standalone -dwarf-version=5 -debugger-tuning=lldb -fdebug-compilation-dir=/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebKit -target-linker-version 1230.1 -fcoverage-compilation-dir=/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebKit -resource-dir /Users/buildbot/Library/Developer/Toolchains/swift-webkit.xctoolchain/usr/lib/clang/21 -dependency-file /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/scan-build-output/StaticAnalyzer/WebKit/WebKit/normal/arm64/UnifiedSource76-nonARC.d -skip-unused-modulemap-deps -MT dependencies -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk -iquote /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebKit.build/Release-iphonesimulator/WebKit.build/WebKit-generated-files.hmap -iquote /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebKit.build/Release-iphonesimulator/WebKit.build/WebKit-project-headers.hmap -isystem /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitLibraries/SDKs/iphonesimulator26.0-additions.sdk/usr/local/include -isystem /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/SDKAdditions/usr/include -iframework /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/SDKAdditions/System/Library/Frameworks -iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/PrivateFrameworks -isystem /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitLibraries/SDKs/iphonesimulator26.0-additions.sdk/usr/local/include -include-pch /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/PrecompiledHeaders/SharedPrecompiledHeaders/3938699508861868627/WebKit2Prefix.h.gch -D _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE -D NDEBUG -D HAVE_CORE_PREDICTION -D ENABLE_LOWER_FORMATREADERBUNDLE_CODESIGNING_REQUIREMENTS -D WEBKIT_BUNDLE_VERSION="625.1.10+" -D FRAMEWORK_NAME=WebKit -D OBJC_OLD_DISPATCH_PROTOTYPES=0 -D __clang_analyzer__ -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebKit.build/Release-iphonesimulator/WebKit.build/WebKit-own-target-headers.hmap -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebKit.build/Release-iphonesimulator/WebKit.build/WebKit-all-target-headers.hmap -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/WebCore.framework/PrivateHeaders/ForwardingHeaders -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebKit -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebKit/IPC -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/pal/graphics/WebGPU -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/pal/graphics/WebGPU -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/WebKitAdditions -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/WebKitAdditions -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/webrtc -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/webrtc -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/webm -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/webm -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebKit -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebKit.build/Release-iphonesimulator/WebKit.build/DerivedSources-normal/arm64 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebKit.build/Release-iphonesimulator/WebKit.build/DerivedSources/arm64 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebKit.build/Release-iphonesimulator/WebKit.build/DerivedSources -F/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator -F/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitLibraries/SDKs/iphonesimulator26.0-additions.sdk/System/Library/PrivateFrameworks -F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/PrivateFrameworks -D __STDC_WANT_LIB_EXT1__=1 -I/usr/local/include -stdlib=libc++ -internal-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include/c++/v1 -internal-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include -internal-isystem /Users/buildbot/Library/Developer/Toolchains/swift-webkit.xctoolchain/usr/lib/clang/21/include -internal-externc-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/Frameworks -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/SubFrameworks -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/Library/Frameworks -Wno-trigraphs -Werror -Wno-missing-field-initializers -Wmissing-prototypes -Wunreachable-code -Wno-implicit-atomic-properties -Wno-objc-interface-ivars -Wno-arc-repeated-use-of-weak -Wimplicit-retain-self -Wnon-virtual-dtor -Wno-overloaded-virtual -Wno-exit-time-destructors -Wduplicate-method-match -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wno-float-conversion -Wnon-literal-null-conversion -Wobjc-literal-conversion -Wsign-compare -Wno-shorten-64-to-32 -Wnewline-eof -Wno-selector -Wno-strict-selector-match -Wundeclared-selector -Wdeprecated-implementations -Wno-c++11-extensions -Wno-implicit-fallthrough -Wprotocol -Wdeprecated-declarations -Winvalid-offsetof -Wno-sign-conversion -Winfinite-recursion -Wmove -Wcomma -Wblock-capture-autoreleasing -Wstrict-prototypes -Wrange-loop-analysis -Wno-semicolon-before-method-body -Wall -Wc99-designator -Wconditional-uninitialized -Wextra -Wdeprecated-enum-enum-conversion -Wdeprecated-enum-float-conversion -Wenum-float-conversion -Wfinal-dtor-non-final-class -Wformat=2 -Wmisleading-indentation -Wpointer-to-int-cast -Wreorder-init-list -Wundef -Wunused-but-set-variable -Wvla -Werror=unguarded-availability -Wno-elaborated-enum-base -Wno-unused-parameter -Wcast-qual -Wchar-subscripts -Wextra-tokens -Winit-self -Wmissing-noreturn -Wpacked -Wpointer-arith -Wredundant-decls -Wwrite-strings -Wexit-time-destructors -Wglobal-constructors -Wtautological-compare -Wimplicit-fallthrough -Wvla -Wliteral-conversion -Wthread-safety -Wno-profile-instr-out-of-date -Wno-profile-instr-unprofiled -Wno-backend-plugin -Wunsafe-buffer-usage -Wno-unknown-warning-option -Wno-missing-field-initializers -Wno-nullability-completeness -std=c++2b -fdeprecated-macro -ferror-limit 19 -fmacro-backtrace-limit=0 -fvisibility=hidden -fvisibility-inlines-hidden -stack-protector 1 -fblocks -fencode-extended-block-signature -fno-rtti -fregister-global-dtors-with-atexit -fgnuc-version=4.2.1 -fno-cxx-modules -fno-implicit-modules -fskip-odr-check-in-gmf -fobjc-runtime=ios-26.2.0 -fobjc-weak -fobjc-exceptions -fexceptions -fpascal-strings -fmax-type-align=16 -fdiagnostics-show-note-include-stack -vectorize-loops -vectorize-slp -fsafe-buffer-usage-suggestions -analyzer-output=html -analyzer-config report-in-main-source-file=true -analyzer-config nullability:NoDiagnoseCallsToSystemHeaders=true -analyzer-checker optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker security.insecureAPI.UncheckedReturn -analyzer-checker security.insecureAPI.getpw -analyzer-checker security.insecureAPI.gets -analyzer-checker security.insecureAPI.mkstemp -analyzer-checker security.insecureAPI.mktemp -analyzer-disable-checker security.insecureAPI.rand -analyzer-disable-checker security.insecureAPI.strcpy -analyzer-checker security.insecureAPI.vfork -analyzer-disable-checker alpha,apiModeling,core,cplusplus,deadcode,debug,fuchsia,nullability,optin,osx,security,unix,webkit -analyzer-checker alpha.webkit.ForwardDeclChecker,alpha.webkit.MemoryUnsafeCastChecker,alpha.webkit.NoDeleteChecker,alpha.webkit.NoUncheckedPtrMemberChecker,alpha.webkit.NoUnretainedMemberChecker,alpha.webkit.RetainPtrCtorAdoptChecker,alpha.webkit.UncheckedCallArgsChecker,alpha.webkit.UncheckedLocalVarsChecker,alpha.webkit.UncountedCallArgsChecker,alpha.webkit.UncountedLocalVarsChecker,alpha.webkit.UnretainedCallArgsChecker,alpha.webkit.UnretainedLambdaCapturesChecker,alpha.webkit.UnretainedLocalVarsChecker,webkit.NoUncountedMemberChecker,webkit.RefCntblBaseVirtualDtor,webkit.UncountedLambdaCapturesChecker -analyzer-config max-nodes=10000000 -analyzer-config verbose-report-filename=true -fdwarf2-cfi-asm -o /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/scan-build-output/StaticAnalyzer/WebKit/WebKit/normal/arm64/UnifiedSource76-nonARC.plist -x objective-c++ /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebKit/unified-sources/UnifiedSource76-nonARC.mm
1/*
2 * Copyright (C) 2012-2025 Apple Inc. All rights reserved.
3 * Copyright (C) 2026 Samuel Weinig <sam@webkit.org>
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#import "config.h"
28#import "WebPage.h"
29
30#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
31
32#import "DocumentEditingContext.h"
33#import "DragInitiationResult.h"
34#import "DrawingArea.h"
35#import "EditingRange.h"
36#import "EditorState.h"
37#import "InteractionInformationAtPosition.h"
38#import "KeyEventInterpretationContext.h"
39#import "Logging.h"
40#import "MessageSenderInlines.h"
41#import "NativeWebKeyboardEvent.h"
42#import "PDFPluginBase.h"
43#import "PluginView.h"
44#import "PrintInfo.h"
45#import "RemoteLayerTreeDrawingArea.h"
46#import "RemoteRenderingBackendProxy.h"
47#import "RemoteScrollingCoordinator.h"
48#import "RemoteSnapshotRecorderProxy.h"
49#import "RemoteWebTouchEvent.h"
50#import "RevealItem.h"
51#import "SandboxUtilities.h"
52#import "ShareableBitmapUtilities.h"
53#import "SharedBufferReference.h"
54#import "SyntheticEditingCommandType.h"
55#import "TapHandlingResult.h"
56#import "TextCheckingControllerProxy.h"
57#import "UIKitSPI.h"
58#import "UnifiedPDFPlugin.h"
59#import "UserData.h"
60#import "ViewGestureGeometryCollector.h"
61#import "VisibleContentRectUpdateInfo.h"
62#import "WKAccessibilityWebPageObjectIOS.h"
63#import "WebAutocorrectionContext.h"
64#import "WebAutocorrectionData.h"
65#import "WebChromeClient.h"
66#import "WebEventConversion.h"
67#import "WebFrame.h"
68#import "WebImage.h"
69#import "WebPageInternals.h"
70#import "WebPageMessages.h"
71#import "WebPageProxyMessages.h"
72#import "WebPreviewLoaderClient.h"
73#import "WebProcess.h"
74#import "WebTouchEvent.h"
75#import <CoreText/CTFont.h>
76#import <WebCore/AXObjectCache.h>
77#import <WebCore/AXRemoteTokenIOS.h>
78#import <WebCore/AccessibilityObject.h>
79#import <WebCore/Autofill.h>
80#import <WebCore/AutofillElements.h>
81#import <WebCore/BoundaryPointInlines.h>
82#import <WebCore/Chrome.h>
83#import <WebCore/ContainerNodeInlines.h>
84#import <WebCore/ContentChangeObserver.h>
85#import <WebCore/DOMTimerHoldingTank.h>
86#import <WebCore/DataDetection.h>
87#import <WebCore/DataDetectionResultsStorage.h>
88#import <WebCore/DiagnosticLoggingClient.h>
89#import <WebCore/DiagnosticLoggingKeys.h>
90#import <WebCore/DocumentLoader.h>
91#import <WebCore/DocumentMarkerController.h>
92#import <WebCore/DocumentMarkers.h>
93#import <WebCore/DocumentQuirks.h>
94#import <WebCore/DragController.h>
95#import <WebCore/EditingHTMLConverter.h>
96#import <WebCore/EditingInlines.h>
97#import <WebCore/Editor.h>
98#import <WebCore/EditorClient.h>
99#import <WebCore/Element.h>
100#import <WebCore/ElementAncestorIteratorInlines.h>
101#import <WebCore/ElementAnimationContext.h>
102#import <WebCore/EventHandler.h>
103#import <WebCore/EventNames.h>
104#import <WebCore/EventTargetInlines.h>
105#import <WebCore/File.h>
106#import <WebCore/FloatQuad.h>
107#import <WebCore/FrameDestructionObserverInlines.h>
108#import <WebCore/FocusController.h>
109#import <WebCore/FocusControllerTypes.h>
110#import <WebCore/FontCache.h>
111#import <WebCore/FontCacheCoreText.h>
112#import <WebCore/GeometryUtilities.h>
113#import <WebCore/GraphicsLayer.h>
114#import <WebCore/HTMLAreaElement.h>
115#import <WebCore/HTMLAttachmentElement.h>
116#import <WebCore/HTMLBodyElement.h>
117#import <WebCore/HTMLElement.h>
118#import <WebCore/HTMLElementTypeHelpers.h>
119#import <WebCore/HTMLFormElement.h>
120#import <WebCore/HTMLHRElement.h>
121#import <WebCore/HTMLIFrameElement.h>
122#import <WebCore/HTMLImageElement.h>
123#import <WebCore/HTMLInputElement.h>
124#import <WebCore/HTMLLabelElement.h>
125#import <WebCore/HTMLModelElement.h>
126#import <WebCore/HTMLOptGroupElement.h>
127#import <WebCore/HTMLOptionElement.h>
128#import <WebCore/HTMLPlugInElement.h>
129#import <WebCore/HTMLSelectElement.h>
130#import <WebCore/HTMLSummaryElement.h>
131#import <WebCore/HTMLTextAreaElement.h>
132#import <WebCore/HTMLVideoElement.h>
133#import <WebCore/HandleUserInputEventResult.h>
134#import <WebCore/HistoryItem.h>
135#import <WebCore/HitTestResult.h>
136#import <WebCore/HitTestSource.h>
137#import <WebCore/Image.h>
138#import <WebCore/ImageOverlay.h>
139#import <WebCore/InputMode.h>
140#import <WebCore/KeyboardEvent.h>
141#import <WebCore/LibWebRTCProvider.h>
142#import <WebCore/LocalDOMWindow.h>
143#import <WebCore/LocalFrame.h>
144#import <WebCore/LocalFrameLoaderClient.h>
145#import <WebCore/LocalFrameView.h>
146#import <WebCore/MediaSessionManagerIOS.h>
147#import <WebCore/Node.h>
148#import <WebCore/NodeInlines.h>
149#import <WebCore/NodeList.h>
150#import <WebCore/NodeRenderStyle.h>
151#import <WebCore/NotImplemented.h>
152#import <WebCore/Page.h>
153#import <WebCore/PagePasteboardContext.h>
154#import <WebCore/Pasteboard.h>
155#import <WebCore/PlatformKeyboardEvent.h>
156#import <WebCore/PlatformMediaSessionManager.h>
157#import <WebCore/PlatformMouseEvent.h>
158#import <WebCore/PluginDocument.h>
159#import <WebCore/PluginViewBase.h>
160#import <WebCore/PointerCaptureController.h>
161#import <WebCore/PointerCharacteristics.h>
162#import <WebCore/PrintContext.h>
163#import <WebCore/Quirks.h>
164#import <WebCore/Range.h>
165#import <WebCore/RemoteFrame.h>
166#import <WebCore/RemoteFrameGeometryTransformer.h>
167#import <WebCore/RemoteFrameView.h>
168#import <WebCore/RenderBlock.h>
169#import <WebCore/RenderBoxInlines.h>
170#import <WebCore/RenderImage.h>
171#import <WebCore/RenderLayer.h>
172#import <WebCore/RenderLayerBacking.h>
173#import <WebCore/RenderLayerScrollableArea.h>
174#import <WebCore/RenderObjectInlines.h>
175#import <WebCore/RenderTheme.h>
176#import <WebCore/RenderVideoInlines.h>
177#import <WebCore/RenderView.h>
178#import <WebCore/RenderedDocumentMarker.h>
179#import <WebCore/ScrollableArea.h>
180#import <WebCore/Settings.h>
181#import <WebCore/ShadowRoot.h>
182#import <WebCore/SharedBuffer.h>
183#import <WebCore/SharedMemory.h>
184#import <WebCore/StylePrimitiveNumericTypes+Evaluation.h>
185#import <WebCore/StyleProperties.h>
186#import <WebCore/TextIndicator.h>
187#import <WebCore/TextIterator.h>
188#import <WebCore/UserAgent.h>
189#import <WebCore/UserGestureIndicator.h>
190#import <WebCore/ViewportArguments.h>
191#import <WebCore/VisibleUnits.h>
192#import <WebCore/WebEvent.h>
193#import <WebCore/WritingSuggestionData.h>
194#import <pal/system/ios/UserInterfaceIdiom.h>
195#import <wtf/CoroutineUtilities.h>
196#import <wtf/MathExtras.h>
197#import <wtf/MemoryPressureHandler.h>
198#import <wtf/RuntimeApplicationChecks.h>
199#import <wtf/Scope.h>
200#import <wtf/SetForScope.h>
201#import <wtf/cocoa/Entitlements.h>
202#import <wtf/cocoa/SpanCocoa.h>
203#import <wtf/text/MakeString.h>
204#import <wtf/text/StringToIntegerConversion.h>
205#import <wtf/text/TextBreakIterator.h>
206#import <wtf/text/TextStream.h>
207#import <wtf/text/WTFString.h>
208
209#if ENABLE(ATTACHMENT_ELEMENT)(defined 1 && 1)
210#import <WebCore/PromisedAttachmentInfo.h>
211#endif
212
213#import <pal/cocoa/RevealSoftLink.h>
214
215#define WEBPAGE_RELEASE_LOG(channel, fmt, ...) RELEASE_LOG(channel, "%p - WebPage::" fmt, this, ##__VA_ARGS__)clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2Logchannel
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("%p - WebPage::" fmt), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG34"
) = "%p - WebPage::" fmt; uint8_t _Alignas(16) __attribute__(
(uninitialized)) _os_fmt_buf[__builtin_os_log_format_buffer_size
("%p - WebPage::" fmt, this, ##__VA_ARGS__)]; _os_log_impl(&
__dso_handle, _log_tmp, _type_tmp, _os_fmt_str, (uint8_t *)__builtin_os_log_format
(_os_fmt_buf, "%p - WebPage::" fmt, this, ##__VA_ARGS__), (uint32_t
)sizeof(_os_fmt_buf)) clang diagnostic pop ; }); } }) clang diagnostic
pop
216#define WEBPAGE_RELEASE_LOG_ERROR(channel, fmt, ...) RELEASE_LOG_ERROR(channel, "%p - WebPage::" fmt, this, ##__VA_ARGS__)clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2Logchannel
.osLogChannel); os_log_type_t _type_tmp = OS_LOG_TYPE_ERROR; if
(os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__({
clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("%p - WebPage::" fmt), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG35"
) = "%p - WebPage::" fmt; uint8_t _Alignas(16) __attribute__(
(uninitialized)) _os_fmt_buf[__builtin_os_log_format_buffer_size
("%p - WebPage::" fmt, this, ##__VA_ARGS__)]; _os_log_error_impl
(&__dso_handle, _log_tmp, _type_tmp, _os_fmt_str, (uint8_t
*)__builtin_os_log_format(_os_fmt_buf, "%p - WebPage::" fmt,
this, ##__VA_ARGS__), (uint32_t)sizeof(_os_fmt_buf)) clang diagnostic
pop ; }); } }) clang diagnostic pop
217
218namespace WebKit {
219using namespace WebCore;
220
221static void adjustCandidateAutocorrectionInFrame(const String& correction, LocalFrame& frame)
222{
223#if HAVE(AUTOCORRECTION_ENHANCEMENTS)(defined 1 && 1)
224 auto startPosition = frame.selection().selection().start();
225 auto endPosition = frame.selection().selection().end();
226
227 auto firstPositionInEditableContent = startOfEditableContent(startPosition);
228
229 auto referenceRange = makeSimpleRange(firstPositionInEditableContent, endPosition);
230 if (!referenceRange)
231 return;
232
233 auto correctedRange = findPlainText(*referenceRange, correction, { FindOption::Backwards });
234 if (correctedRange.collapsed())
235 return;
236
237 addMarker(correctedRange, WebCore::DocumentMarkerType::CorrectionIndicator);
238#else
239 UNUSED_PARAM(frame)(void)frame;
240#endif
241}
242
243// WebCore stores the page scale factor as float instead of double. When we get a scale from WebCore,
244// we need to ignore differences that are within a small rounding error, with enough leeway
245// to handle rounding differences that may result from round-tripping through UIScrollView.
246bool scalesAreEssentiallyEqual(float a, float b)
247{
248 const auto scaleFactorEpsilon = 0.01f;
249 return WTF::areEssentiallyEqual(a, b, scaleFactorEpsilon);
250}
251
252void WebPage::platformDetach()
253{
254 [m_mockAccessibilityElement setWebPage:nullptr];
255}
256
257void WebPage::platformInitializeAccessibility(ShouldInitializeNSAccessibility)
258{
259 m_mockAccessibilityElement = adoptNS([[WKAccessibilityWebPageObject alloc] init]);
260 [m_mockAccessibilityElement setWebPage:this];
261
262 RefPtr localMainFrame = protect(*m_page)->localMainFrame();
263 if (localMainFrame)
264 accessibilityTransferRemoteToken(accessibilityRemoteTokenData());
265}
266
267void WebPage::platformReinitializeAccessibilityToken()
268{
269 RefPtr frame = m_page->focusController().focusedOrMainFrame();
270 if (!frame)
271 return;
272
273 accessibilityTransferRemoteToken(accessibilityRemoteTokenData());
274}
275
276RetainPtr<NSData> WebPage::accessibilityRemoteTokenData() const
277{
278 return [[[NSUUID UUID] UUIDString] dataUsingEncoding:NSUTF8StringEncoding];
279}
280
281void WebPage::relayAccessibilityNotification(String&& notificationName, RetainPtr<NSData>&& notificationData)
282{
283 send(Messages::WebPageProxy::RelayAccessibilityNotification(WTF::move(notificationName), span(notificationData.get())));
284}
285
286void WebPage::relayAriaNotifyNotification(WebCore::AriaNotifyData&& notificationData)
287{
288 send(Messages::WebPageProxy::RelayAriaNotifyNotification(WTF::move(notificationData)));
289}
290
291void WebPage::relayLiveRegionNotification(WebCore::LiveRegionAnnouncementData&& notificationData)
292{
293 send(Messages::WebPageProxy::RelayLiveRegionNotification(WTF::move(notificationData)));
294}
295
296static void computeEditableRootHasContentAndPlainText(const VisibleSelection& selection, EditorState::PostLayoutData& data)
297{
298 data.hasContent = false;
299 data.hasPlainText = false;
300 if (!selection.isContentEditable())
301 return;
302
303 if (data.selectedTextLength || data.characterAfterSelection || data.characterBeforeSelection || data.twoCharacterBeforeSelection) {
304 // If any of these variables have been previously set, the editable root must have plain text content, so we can bail from the remainder of the check.
305 data.hasContent = true;
306 data.hasPlainText = true;
307 return;
308 }
309
310 RefPtr root = selection.rootEditableElement();
311 if (!root || editingIgnoresContent(*root))
312 return;
313
314 auto startInEditableRoot = firstPositionInNode(*root);
315 data.hasContent = root->hasChildNodes() && !isEndOfEditableOrNonEditableContent(startInEditableRoot);
316 if (data.hasContent) {
317 auto range = makeSimpleRange(
318 VisiblePosition { startInEditableRoot },
319 VisiblePosition { lastPositionInNode(*root) }
320 );
321 data.hasPlainText = range && hasAnyPlainText(*range);
322 }
323}
324
325bool WebPage::requiresPostLayoutDataForEditorState(const LocalFrame& frame) const
326{
327 // If we have a composition or are using a hardware keyboard then we need to send the full
328 // editor so that the UIProcess can update UI, including the position of the caret.
329 bool needsLayout = frame.editor().hasComposition();
330#if !PLATFORM(MACCATALYST)(defined WTF_PLATFORM_MACCATALYST && WTF_PLATFORM_MACCATALYST
)
331 needsLayout |= m_keyboardIsAttached;
332#endif
333 return needsLayout;
334}
335
336static void convertContentToRootView(const LocalFrameView& view, Vector<SelectionGeometry>& geometries)
337{
338 for (auto& geometry : geometries)
339 geometry.setQuad(view.contentsToRootView(geometry.quad()));
340}
341
342void WebPage::getPlatformEditorState(LocalFrame& frame, EditorState& result) const
343{
344 getPlatformEditorStateCommon(frame, result);
345
346 if (!result.hasPostLayoutAndVisualData())
347 return;
348
349 auto& postLayoutData = *result.postLayoutData;
350 auto& visualData = *result.visualData;
351
352 Ref view = *frame.view();
353
354 if (frame.editor().hasComposition()) {
355 if (auto compositionRange = protect(frame.editor())->compositionRange()) {
356 visualData.markedTextRects = RenderObject::collectSelectionGeometries(*compositionRange).geometries;
357 convertContentToRootView(view, visualData.markedTextRects);
358
359 postLayoutData.markedText = plainTextForContext(*compositionRange);
360 VisibleSelection compositionSelection(*compositionRange);
361 visualData.markedTextCaretRectAtStart = view->contentsToRootView(compositionSelection.visibleStart().absoluteCaretBounds(nullptr /* insideFixed */));
362 visualData.markedTextCaretRectAtEnd = view->contentsToRootView(compositionSelection.visibleEnd().absoluteCaretBounds(nullptr /* insideFixed */));
363 }
364 }
365
366 const auto& selection = frame.selection().selection();
367 std::optional<SimpleRange> selectedRange;
368 postLayoutData.isStableStateUpdate = m_isInStableState;
369
370 if (selection.isCaret()) {
371 // FIXME: The following check should take into account writing direction.
372 postLayoutData.isReplaceAllowed = result.isContentEditable && atBoundaryOfGranularity(selection.start(), TextGranularity::WordGranularity, SelectionDirection::Forward);
373
374 selectedRange = wordRangeFromPosition(selection.start());
375 postLayoutData.wordAtSelection = plainTextForContext(selectedRange);
376
377 if (selection.isContentEditable())
378 charactersAroundPosition(selection.start(), postLayoutData.characterAfterSelection, postLayoutData.characterBeforeSelection, postLayoutData.twoCharacterBeforeSelection);
379 } else if (selection.isRange()) {
380 selectedRange = selection.toNormalizedRange();
381 String selectedText;
382 if (selectedRange) {
383 selectedText = plainTextForDisplay(*selectedRange);
384 postLayoutData.selectedTextLength = selectedText.length();
385 const int maxSelectedTextLength = 200;
386 postLayoutData.wordAtSelection = selectedText.left(maxSelectedTextLength);
387 auto findSelectedEditableImageElement = [&] {
388 RefPtr<HTMLImageElement> foundImage;
389 if (!result.isContentEditable)
390 return foundImage;
391
392 for (TextIterator iterator { *selectedRange, { } }; !iterator.atEnd(); iterator.advance()) {
393 RefPtr imageElement = dynamicDowncast<HTMLImageElement>(iterator.node());
394 if (!imageElement)
395 continue;
396
397 if (foundImage) {
398 foundImage = nullptr;
399 break;
400 }
401
402 foundImage = imageElement;
403 }
404 return foundImage;
405 };
406
407 if (auto imageElement = findSelectedEditableImageElement())
408 postLayoutData.selectedEditableImage = contextForElement(*imageElement);
409 }
410 // FIXME: We should disallow replace when the string contains only CJ characters.
411 postLayoutData.isReplaceAllowed = result.isContentEditable && !result.isInPasswordField && !selectedText.containsOnly<isASCIIWhitespace>();
412 }
413
414#if USE(DICTATION_ALTERNATIVES)(defined 1 && 1)
415 if (selectedRange) {
416 auto markers = protect(protect(frame.document())->markers())->markersInRange(*selectedRange, DocumentMarkerType::DictationAlternatives);
417 postLayoutData.dictationContextsForSelection = WTF::map(markers, [] (auto& marker) {
418 return std::get<DocumentMarker::DictationData>(marker->data()).context;
419 });
420 }
421#endif
422
423 if (!selection.isNone()) {
424 bool selectionIsEditable = selection.hasEditableStyle();
425 if (selectionIsEditable) {
426 // FIXME: The caret color style should be computed using the selection caret's container
427 // rather than the focused element. This causes caret colors in editable children to be
428 // ignored in favor of the editing host's caret color. See: <https://webkit.org/b/229809>.
429 if (RefPtr editableRoot = selection.rootEditableElement(); editableRoot && editableRoot->renderer()) {
430 CheckedRef style = protect(editableRoot->renderer())->style();
431 postLayoutData.caretColor = CaretBase::computeCaretColor(style, editableRoot.get());
432 postLayoutData.hasCaretColorAuto = style->caretColor().isAuto();
433 postLayoutData.hasGrammarDocumentMarkers = protect(protect(editableRoot->document())->markers())->hasMarkers(makeRangeSelectingNodeContents(*editableRoot), DocumentMarkerType::Grammar);
434 }
435 }
436
437 computeEditableRootHasContentAndPlainText(selection, postLayoutData);
438 postLayoutData.selectionStartIsAtParagraphBoundary = atBoundaryOfGranularity(selection.visibleStart(), TextGranularity::ParagraphGranularity, SelectionDirection::Backward);
439 postLayoutData.selectionEndIsAtParagraphBoundary = atBoundaryOfGranularity(selection.visibleEnd(), TextGranularity::ParagraphGranularity, SelectionDirection::Forward);
440
441 bool shouldComputeEnclosingLayerID = [&] {
442 if (!m_page->settings().selectionHonorsOverflowScrolling())
443 return false;
444
445 if (selection.isCaret() && !postLayoutData.hasCaretColorAuto && !postLayoutData.caretColor.isVisible())
446 return false;
447
448 return true;
449 }();
450
451 if (shouldComputeEnclosingLayerID)
452 computeEnclosingLayerID(result, selection);
453 }
454}
455
456void WebPage::platformWillPerformEditingCommand()
457{
458#if ENABLE(CONTENT_CHANGE_OBSERVER)(defined 1 && 1)
459 RefPtr frame = m_page->focusController().focusedOrMainFrame();
460 if (!frame)
461 return;
462
463 if (RefPtr document = frame->document()) {
464 if (CheckedPtr holdingTank = document->domTimerHoldingTankIfExists())
465 holdingTank->removeAll();
466 }
467#endif
468}
469
470FloatSize WebPage::screenSize() const
471{
472 return m_screenSize;
473}
474
475FloatSize WebPage::availableScreenSize() const
476{
477 return m_availableScreenSize;
478}
479
480FloatSize WebPage::overrideScreenSize() const
481{
482 return m_overrideScreenSize;
483}
484
485FloatSize WebPage::overrideAvailableScreenSize() const
486{
487 return m_overrideAvailableScreenSize;
488}
489
490void WebPage::didReceiveMobileDocType(bool isMobileDoctype)
491{
492 m_isMobileDoctype = isMobileDoctype;
493 resetViewportDefaultConfiguration(m_mainFrame.ptr(), isMobileDoctype);
494}
495
496void WebPage::savePageState(HistoryItem& historyItem)
497{
498 historyItem.setScaleIsInitial(!m_userHasChangedPageScaleFactor);
499 historyItem.setMinimumLayoutSizeInScrollViewCoordinates(m_viewportConfiguration.minimumLayoutSize());
500 historyItem.setContentSize(m_viewportConfiguration.contentsSize());
501}
502
503static double scaleAfterViewportWidthChange(double currentScale, bool scaleToFitContent, const ViewportConfiguration& viewportConfiguration, float unobscuredWidthInScrollViewCoordinates, const IntSize& newContentSize, const IntSize& oldContentSize, float visibleHorizontalFraction)
504{
505 double scale;
506 if (!scaleToFitContent) {
507 scale = viewportConfiguration.initialScale();
508 LOG(VisibleRects, "scaleAfterViewportWidthChange using initial scale: %.2f", scale)((void)0);
509 return scale;
510 }
511
512 // When the content size changes, we keep the same relative horizontal content width in view, otherwise we would
513 // end up zoomed too far in landscape->portrait, and too close in portrait->landscape.
514 double widthToKeepInView = visibleHorizontalFraction * newContentSize.width();
515 double newScale = unobscuredWidthInScrollViewCoordinates / widthToKeepInView;
516 scale = std::max(std::min(newScale, viewportConfiguration.maximumScale()), viewportConfiguration.minimumScale());
517 LOG(VisibleRects, "scaleAfterViewportWidthChange scaling content to fit: %.2f", scale)((void)0);
518 return scale;
519}
520
521static FloatPoint relativeCenterAfterContentSizeChange(const FloatRect& originalContentRect, IntSize oldContentSize, IntSize newContentSize)
522{
523 // If the content size has changed, keep the same relative position.
524 FloatPoint oldContentCenter = originalContentRect.center();
525 float relativeHorizontalPosition = oldContentCenter.x() / oldContentSize.width();
526 float relativeVerticalPosition = oldContentCenter.y() / oldContentSize.height();
527 return FloatPoint(relativeHorizontalPosition * newContentSize.width(), relativeVerticalPosition * newContentSize.height());
528}
529
530static inline FloatRect adjustExposedRectForNewScale(const FloatRect& exposedRect, double exposedRectScale, double newScale)
531{
532 if (exposedRectScale == newScale)
533 return exposedRect;
534
535 float horizontalChange = exposedRect.width() * exposedRectScale / newScale - exposedRect.width();
536 float verticalChange = exposedRect.height() * exposedRectScale / newScale - exposedRect.height();
537
538 auto adjustedRect = exposedRect;
539 adjustedRect.inflate({ horizontalChange / 2, verticalChange / 2 });
540 return adjustedRect;
541}
542
543void WebPage::restorePageState(const HistoryItem& historyItem)
544{
545 // When a HistoryItem is cleared, its scale factor and scroll point are set to zero. We should not try to restore the other
546 // parameters in those conditions.
547 if (!historyItem.pageScaleFactor()) {
548 send(Messages::WebPageProxy::CouldNotRestorePageState());
549 return;
550 }
551
552 // We can restore the exposed rect and scale, but we cannot touch the scroll position since the obscured insets
553 // may be changing in the UIProcess. The UIProcess can update the position from the information we send and will then
554 // scroll to the correct position through a regular VisibleContentRectUpdate.
555
556 m_userHasChangedPageScaleFactor = !historyItem.scaleIsInitial();
557 RefPtr localMainFrame = protect(*m_page)->localMainFrame();
558 if (!localMainFrame)
559 return;
560 Ref frameView = *localMainFrame->view();
561
562 FloatSize currentMinimumLayoutSizeInScrollViewCoordinates = m_viewportConfiguration.minimumLayoutSize();
563 if (historyItem.minimumLayoutSizeInScrollViewCoordinates() == currentMinimumLayoutSizeInScrollViewCoordinates) {
564 float boundedScale = historyItem.scaleIsInitial() ? m_viewportConfiguration.initialScale() : historyItem.pageScaleFactor();
565 boundedScale = std::min<float>(m_viewportConfiguration.maximumScale(), std::max<float>(m_viewportConfiguration.minimumScale(), boundedScale));
566 scalePage(boundedScale, IntPoint());
567
568 std::optional<FloatPoint> scrollPosition;
569 if (historyItem.shouldRestoreScrollPosition()) {
570 protect(m_drawingArea)->setExposedContentRect(historyItem.exposedContentRect());
571 m_hasRestoredExposedContentRectAfterDidCommitLoad = true;
572 scrollPosition = FloatPoint(historyItem.scrollPosition());
573 }
574
575 RELEASE_LOG(Scrolling, "WebPage::restorePageState with matching minimumLayoutSize; historyItem.shouldRestoreScrollPosition %d, scrollPosition.y %d", historyItem.shouldRestoreScrollPosition(), historyItem.scrollPosition().y())clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2LogScrolling
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("WebPage::restorePageState with matching minimumLayoutSize; historyItem.shouldRestoreScrollPosition %d, scrollPosition.y %d"
), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG36"
) = "WebPage::restorePageState with matching minimumLayoutSize; historyItem.shouldRestoreScrollPosition %d, scrollPosition.y %d"
; uint8_t _Alignas(16) __attribute__((uninitialized)) _os_fmt_buf
[__builtin_os_log_format_buffer_size("WebPage::restorePageState with matching minimumLayoutSize; historyItem.shouldRestoreScrollPosition %d, scrollPosition.y %d"
, historyItem.shouldRestoreScrollPosition(), historyItem.scrollPosition
().y())]; _os_log_impl(&__dso_handle, _log_tmp, _type_tmp
, _os_fmt_str, (uint8_t *)__builtin_os_log_format(_os_fmt_buf
, "WebPage::restorePageState with matching minimumLayoutSize; historyItem.shouldRestoreScrollPosition %d, scrollPosition.y %d"
, historyItem.shouldRestoreScrollPosition(), historyItem.scrollPosition
().y()), (uint32_t)sizeof(_os_fmt_buf)) clang diagnostic pop
; }); } }) clang diagnostic pop
;
576 send(Messages::WebPageProxy::RestorePageState(scrollPosition, frameView->scrollOrigin(), historyItem.obscuredInsets(), boundedScale));
577 } else {
578 IntSize oldContentSize = historyItem.contentSize();
579 IntSize newContentSize = frameView->contentsSize();
580 double visibleHorizontalFraction = static_cast<float>(historyItem.unobscuredContentRect().width()) / oldContentSize.width();
581
582 double newScale = scaleAfterViewportWidthChange(historyItem.pageScaleFactor(), !historyItem.scaleIsInitial(), m_viewportConfiguration, currentMinimumLayoutSizeInScrollViewCoordinates.width(), newContentSize, oldContentSize, visibleHorizontalFraction);
583
584 std::optional<FloatPoint> newCenter;
585 if (historyItem.shouldRestoreScrollPosition()) {
586 if (!oldContentSize.isEmpty() && !newContentSize.isEmpty() && newContentSize != oldContentSize)
587 newCenter = relativeCenterAfterContentSizeChange(historyItem.unobscuredContentRect(), oldContentSize, newContentSize);
588 else
589 newCenter = FloatRect(historyItem.unobscuredContentRect()).center();
590 }
591
592 RELEASE_LOG(Scrolling, "WebPage::restorePageState with mismatched minimumLayoutSize; historyItem.shouldRestoreScrollPosition %d, unobscured rect top %d, scale %.2f", historyItem.shouldRestoreScrollPosition(), historyItem.unobscuredContentRect().y(), newScale)clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2LogScrolling
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("WebPage::restorePageState with mismatched minimumLayoutSize; historyItem.shouldRestoreScrollPosition %d, unobscured rect top %d, scale %.2f"
), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG37"
) = "WebPage::restorePageState with mismatched minimumLayoutSize; historyItem.shouldRestoreScrollPosition %d, unobscured rect top %d, scale %.2f"
; uint8_t _Alignas(16) __attribute__((uninitialized)) _os_fmt_buf
[__builtin_os_log_format_buffer_size("WebPage::restorePageState with mismatched minimumLayoutSize; historyItem.shouldRestoreScrollPosition %d, unobscured rect top %d, scale %.2f"
, historyItem.shouldRestoreScrollPosition(), historyItem.unobscuredContentRect
().y(), newScale)]; _os_log_impl(&__dso_handle, _log_tmp,
_type_tmp, _os_fmt_str, (uint8_t *)__builtin_os_log_format(_os_fmt_buf
, "WebPage::restorePageState with mismatched minimumLayoutSize; historyItem.shouldRestoreScrollPosition %d, unobscured rect top %d, scale %.2f"
, historyItem.shouldRestoreScrollPosition(), historyItem.unobscuredContentRect
().y(), newScale), (uint32_t)sizeof(_os_fmt_buf)) clang diagnostic
pop ; }); } }) clang diagnostic pop
;
593 scalePage(newScale, IntPoint());
594 send(Messages::WebPageProxy::RestorePageCenterAndScale(newCenter, newScale));
595 }
596}
597
598double WebPage::minimumPageScaleFactor() const
599{
600 if (!m_viewportConfiguration.allowsUserScaling())
601 return m_page->pageScaleFactor();
602 return m_viewportConfiguration.minimumScale();
603}
604
605double WebPage::maximumPageScaleFactor() const
606{
607 if (!m_viewportConfiguration.allowsUserScaling())
608 return m_page->pageScaleFactor();
609 return m_viewportConfiguration.maximumScale();
610}
611
612double WebPage::maximumPageScaleFactorIgnoringAlwaysScalable() const
613{
614 if (!m_viewportConfiguration.allowsUserScalingIgnoringAlwaysScalable())
615 return m_page->pageScaleFactor();
616 return m_viewportConfiguration.maximumScaleIgnoringAlwaysScalable();
617}
618
619bool WebPage::allowsUserScaling() const
620{
621 return m_viewportConfiguration.allowsUserScaling();
622}
623
624bool WebPage::handleEditingKeyboardEvent(KeyboardEvent& event)
625{
626 auto* platformEvent = event.underlyingPlatformEvent();
627 if (!platformEvent)
628 return false;
629
630 // Don't send synthetic events to the UIProcess. They are only
631 // used for interacting with JavaScript.
632 if (platformEvent->isSyntheticEvent())
633 return false;
634
635 if (handleKeyEventByRelinquishingFocusToChrome(event))
636 return true;
637
638 updateLastNodeBeforeWritingSuggestions(event);
639
640 auto scrollingNodeID = [&] -> std::optional<WebCore::ScrollingNodeID> {
641 RefPtr frame = m_page->focusController().focusedOrMainFrame();
642 if (!frame)
643 return std::nullopt;
644
645 CheckedPtr scrollableArea = frame->eventHandler().focusedScrollableArea();
646 if (!scrollableArea)
647 return std::nullopt;
648
649 return scrollableArea->scrollingNodeID();
650 }();
651
652 auto isCharEvent = platformEvent->type() == PlatformKeyboardEvent::Type::Char;
653 auto context = KeyEventInterpretationContext { isCharEvent, scrollingNodeID };
654
655 // FIXME: Interpret the event immediately upon receiving it in UI process, without sending to WebProcess first.
656 auto sendResult = protect(WebProcess::singleton().parentProcessConnection())->sendSync(Messages::WebPageProxy::InterpretKeyEvent(editorState(ShouldPerformLayout::Yes), context), m_identifier);
657 auto [eventWasHandled] = sendResult.takeReplyOr(false);
658 return eventWasHandled;
659}
660
661static bool disableServiceWorkerEntitlementTestingOverride;
662
663bool WebPage::parentProcessHasServiceWorkerEntitlement() const
664{
665 if (disableServiceWorkerEntitlementTestingOverride)
666 return false;
667
668 RetainPtr xpcConnection = WebProcess::singleton().parentProcessConnection()->xpcConnection();
669 static bool hasEntitlement = WTF::hasEntitlement(xpcConnection.get(), "com.apple.developer.WebKit.ServiceWorkers"_s) || WTF::hasEntitlement(xpcConnection.get(), "com.apple.developer.web-browser"_s);
670 return hasEntitlement;
671}
672
673void WebPage::disableServiceWorkerEntitlement()
674{
675 disableServiceWorkerEntitlementTestingOverride = true;
676}
677
678void WebPage::clearServiceWorkerEntitlementOverride(CompletionHandler<void()>&& completionHandler)
679{
680 disableServiceWorkerEntitlementTestingOverride = false;
681 completionHandler();
682}
683
684bool WebPage::performNonEditingBehaviorForSelector(const String&, WebCore::KeyboardEvent*)
685{
686 notImplemented()((void)0);
687 return false;
688}
689
690void WebPage::getSelectionContext(CompletionHandler<void(const String&, const String&, const String&)>&& completionHandler)
691{
692 RefPtr frame = m_page->focusController().focusedOrMainFrame();
693 if (!frame)
694 return;
695
696 static constexpr auto selectionExtendedContextLength = 350;
697
698#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
699 if (RefPtr pluginView = focusedPluginViewForFrame(*frame)) {
700 auto [textBefore, textAfter] = pluginView->stringsBeforeAndAfterSelection(selectionExtendedContextLength);
701 return completionHandler(pluginView->selectionString(), WTF::move(textBefore), WTF::move(textAfter));
702 }
703#endif
704
705 if (!frame->selection().isRange())
706 return completionHandler({ }, { }, { });
707
708 auto& selection = frame->selection().selection();
709 String selectedText = plainTextForContext(selection.firstRange());
710 String textBefore = plainTextForDisplay(rangeExpandedByCharactersInDirectionAtWordBoundary(selection.start(), selectionExtendedContextLength, SelectionDirection::Backward));
711 String textAfter = plainTextForDisplay(rangeExpandedByCharactersInDirectionAtWordBoundary(selection.end(), selectionExtendedContextLength, SelectionDirection::Forward));
712
713 completionHandler(selectedText, textBefore, textAfter);
714}
715
716void WebPage::updateRemotePageAccessibilityOffset(WebCore::FrameIdentifier, WebCore::IntPoint offset)
717{
718#if ENABLE(ACCESSIBILITY_LOCAL_FRAME)(defined 0 && 0)
719 // With local frame support, position data is sent from the UI process in frameScreenPosition.
720 UNUSED_PARAM(offset)(void)offset;
721#else
722 [protect(accessibilityRemoteObject()) setRemoteFrameOffset:offset];
723#endif
724}
725
726static RetainPtr<NSDictionary> createAccessibillityTokenDictionary(WebCore::AccessibilityRemoteToken elementToken)
727{
728 RetainPtr uuid = elementToken.uuid.createNSUUID();
729 return @{ @"ax-pid" : @(elementToken.pid), @"ax-uuid" : [uuid UUIDString], @"ax-register" : @YES__objc_yes };
730}
731
732void WebPage::registerRemoteFrameAccessibilityTokens(pid_t pid, WebCore::AccessibilityRemoteToken elementToken, WebCore::FrameIdentifier frameID)
733{
734 createMockAccessibilityElement(pid);
735 if ([m_mockAccessibilityElement respondsToSelector:@selector(setRemoteTokenDictionary:)])
736 [m_mockAccessibilityElement setRemoteTokenDictionary:createAccessibillityTokenDictionary(elementToken).get()];
737 [m_mockAccessibilityElement setFrameIdentifier:frameID];
738}
739
740void WebPage::createMockAccessibilityElement(pid_t pid)
741{
742 auto mockAccessibilityElement = adoptNS([[WKAccessibilityWebPageObject alloc] init]);
743
744 [mockAccessibilityElement setWebPage:this];
745 m_mockAccessibilityElement = WTF::move(mockAccessibilityElement);
746}
747
748void WebPage::registerUIProcessAccessibilityTokens(WebCore::AccessibilityRemoteToken elementToken, WebCore::AccessibilityRemoteToken)
749{
750 if ([m_mockAccessibilityElement respondsToSelector:@selector(setRemoteTokenDictionary:)])
751 [m_mockAccessibilityElement setRemoteTokenDictionary:createAccessibillityTokenDictionary(elementToken).get()];
752}
753
754void WebPage::getStringSelectionForPasteboard(CompletionHandler<void(String&&)>&& completionHandler)
755{
756 notImplemented()((void)0);
757 completionHandler({ });
758}
759
760void WebPage::getDataSelectionForPasteboard(const String, CompletionHandler<void(RefPtr<SharedBuffer>&&)>&& completionHandler)
761{
762 notImplemented()((void)0);
763 completionHandler({ });
764}
765
766WebCore::IntPoint WebPage::accessibilityRemoteFrameOffset()
767{
768 return [m_mockAccessibilityElement accessibilityRemoteFrameOffset];
769}
770
771bool WebPage::platformCanHandleRequest(const WebCore::ResourceRequest& request)
772{
773 RetainPtr nsRequest = request.nsURLRequest(HTTPBodyUpdatePolicy::DoNotUpdateHTTPBody);
774 if (!nsRequest.get().URL)
775 return false;
776
777 return [NSURLConnection canHandleRequest:nsRequest.get()];
778}
779
780void WebPage::shouldDelayWindowOrderingEvent(const WebKit::WebMouseEvent&, CompletionHandler<void(bool)>&& completionHandler)
781{
782 notImplemented()((void)0);
783 completionHandler(false);
784}
785
786void WebPage::advanceToNextMisspelling(bool)
787{
788 notImplemented()((void)0);
789}
790
791IntRect WebPage::rectForElementAtInteractionLocation() const
792{
793 constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::AllowVisibleChildFrameContentOnly };
794 RefPtr localMainFrame = protect(*m_page)->localMainFrame();
795 if (!localMainFrame)
796 return IntRect();
797 HitTestResult result = localMainFrame->eventHandler().hitTestResultAtPoint(flooredIntPoint(m_lastInteractionLocation), hitType);
798 RefPtr hitNode = result.innerNode();
799 if (!hitNode || !hitNode->renderer())
800 return IntRect();
801 return protect(result.innerNodeFrame()->view())->contentsToRootView(protect(hitNode->renderer())->absoluteBoundingBoxRect(true));
802}
803
804void WebPage::updateSelectionAppearance()
805{
806 RefPtr frame = m_page->focusController().focusedOrMainFrame();
807 if (!frame)
808 return;
809
810 Ref editor = frame->editor();
811 if (editor->ignoreSelectionChanges())
812 return;
813
814 if (editor->client() && !protect(editor->client())->shouldRevealCurrentSelectionAfterInsertion())
815 return;
816
817 if (!editor->hasComposition() && frame->selection().selection().isNone())
818 return;
819
820 didChangeSelection(*frame);
821}
822
823void WebPage::generateSyntheticEditingCommand(SyntheticEditingCommandType command)
824{
825 PlatformKeyboardEvent keyEvent;
826 RefPtr frame = m_page->focusController().focusedOrMainFrame();
827 if (!frame)
828 return;
829
830 OptionSet<PlatformEvent::Modifier> modifiers;
831 modifiers.add(PlatformEvent::Modifier::MetaKey);
832
833 switch (command) {
834 case SyntheticEditingCommandType::Undo:
835 keyEvent = PlatformKeyboardEvent(PlatformEvent::Type::KeyDown, "z"_s, "z"_s,
836 "z"_s, "KeyZ"_s,
837 "U+005A"_s, 90, false, false, false, modifiers, MonotonicTime::now());
838 break;
839 case SyntheticEditingCommandType::Redo:
840 keyEvent = PlatformKeyboardEvent(PlatformEvent::Type::KeyDown, "y"_s, "y"_s,
841 "y"_s, "KeyY"_s,
842 "U+0059"_s, 89, false, false, false, modifiers, MonotonicTime::now());
843 break;
844 case SyntheticEditingCommandType::ToggleBoldface:
845 keyEvent = PlatformKeyboardEvent(PlatformEvent::Type::KeyDown, "b"_s, "b"_s,
846 "b"_s, "KeyB"_s,
847 "U+0042"_s, 66, false, false, false, modifiers, MonotonicTime::now());
848 break;
849 case SyntheticEditingCommandType::ToggleItalic:
850 keyEvent = PlatformKeyboardEvent(PlatformEvent::Type::KeyDown, "i"_s, "i"_s,
851 "i"_s, "KeyI"_s,
852 "U+0049"_s, 73, false, false, false, modifiers, MonotonicTime::now());
853 break;
854 case SyntheticEditingCommandType::ToggleUnderline:
855 keyEvent = PlatformKeyboardEvent(PlatformEvent::Type::KeyDown, "u"_s, "u"_s,
856 "u"_s, "KeyU"_s,
857 "U+0055"_s, 85, false, false, false, modifiers, MonotonicTime::now());
858 break;
859 default:
860 break;
861 }
862
863 keyEvent.setIsSyntheticEvent();
864
865 PlatformKeyboardEvent::setCurrentModifierState(modifiers);
866
867 frame->eventHandler().keyEvent(keyEvent);
868}
869
870void WebPage::attemptSyntheticClick(const IntPoint& point, OptionSet<WebEventModifier> modifiers, TransactionID lastLayerTreeTransactionId)
871{
872 FloatPoint adjustedPoint;
873 RefPtr localMainFrame = protect(*m_page)->localMainFrame();
874 RefPtr nodeRespondingToClick = localMainFrame ? localMainFrame->nodeRespondingToClickEvents(point, adjustedPoint) : nullptr;
875 RefPtr frameRespondingToClick = nodeRespondingToClick ? nodeRespondingToClick->document().frame() : nullptr;
876 IntPoint adjustedIntPoint = roundedIntPoint(adjustedPoint);
877
878 bool didNotHandleTapAsClick = !frameRespondingToClick;
879 if (frameRespondingToClick) {
880 auto firstTransactionID = WebFrame::fromCoreFrame(*frameRespondingToClick)->firstLayerTreeTransactionIDAfterDidCommitLoad();
881 didNotHandleTapAsClick = !firstTransactionID || lastLayerTreeTransactionId.lessThanSameProcess(*firstTransactionID);
882 }
883
884 if (didNotHandleTapAsClick)
885 send(Messages::WebPageProxy::DidNotHandleTapAsClick(adjustedIntPoint));
886 else if (m_interactionNode == nodeRespondingToClick)
887 completeSyntheticClick(std::nullopt, *nodeRespondingToClick, adjustedPoint, modifiers, WebCore::SyntheticClickType::OneFingerTap);
888 else
889 handleSyntheticClick(std::nullopt, *nodeRespondingToClick, adjustedPoint, modifiers);
890}
891
892static RefPtr<LocalDOMWindow> windowWithDoubleClickEventListener(RefPtr<LocalFrame> frame)
893{
894 if (!frame)
895 return nullptr;
896
897 RefPtr window = frame->window();
898 if (!window || !window->hasEventListeners(WebCore::eventNames().dblclickEvent))
899 return nullptr;
900
901 return window;
902}
903
904void WebPage::handleDoubleTapForDoubleClickAtPoint(const IntPoint& point, OptionSet<WebEventModifier> modifiers, TransactionID lastLayerTreeTransactionId)
905{
906 FloatPoint adjustedPoint;
907 RefPtr localMainFrame = protect(*m_page)->localMainFrame();
908 RefPtr nodeRespondingToDoubleClick = localMainFrame ? localMainFrame->nodeRespondingToDoubleClickEvent(point, adjustedPoint) : nullptr;
909
910 RefPtr windowListeningToDoubleClickEvents = windowWithDoubleClickEventListener(localMainFrame);
911
912 if (!nodeRespondingToDoubleClick && !windowListeningToDoubleClickEvents)
913 return;
914
915 RefPtr<LocalFrame> frameRespondingToDoubleClick;
916 if (nodeRespondingToDoubleClick)
917 frameRespondingToDoubleClick = nodeRespondingToDoubleClick->document().frame();
918 else if (windowListeningToDoubleClickEvents) {
919 RefPtr document = windowListeningToDoubleClickEvents->documentIfLocal();
920 frameRespondingToDoubleClick = document ? document->frame() : nullptr;
921 }
922
923 if (!frameRespondingToDoubleClick)
924 return;
925
926 auto firstTransactionID = WebFrame::fromCoreFrame(*frameRespondingToDoubleClick)->firstLayerTreeTransactionIDAfterDidCommitLoad();
927 if (!firstTransactionID || lastLayerTreeTransactionId.lessThanSameProcess(*firstTransactionID))
928 return;
929
930 SetForScope userIsInteractingChange { m_userIsInteracting, true };
931
932 auto platformModifiers = platform(modifiers);
933 auto roundedAdjustedPoint = roundedIntPoint(adjustedPoint);
934 frameRespondingToDoubleClick->eventHandler().handleMousePressEvent(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, MouseButton::Left, PlatformEvent::Type::MousePressed, 2, platformModifiers, MonotonicTime::now(), 0, WebCore::SyntheticClickType::OneFingerTap, WebCore::MouseEventInputSource::UserDriven));
935 if (m_isClosed)
936 return;
937 frameRespondingToDoubleClick->eventHandler().handleMouseReleaseEvent(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, MouseButton::Left, PlatformEvent::Type::MouseReleased, 2, platformModifiers, MonotonicTime::now(), 0, WebCore::SyntheticClickType::OneFingerTap, WebCore::MouseEventInputSource::UserDriven));
938}
939
940void WebPage::requestFocusedElementInformation(CompletionHandler<void(const std::optional<FocusedElementInformation>&)>&& completionHandler)
941{
942 std::optional<FocusedElementInformation> information;
943 if (m_focusedElement)
944 information = focusedElementInformation();
945
946 completionHandler(information);
947}
948
949void WebPage::updateFocusedElementInformation()
950{
951 m_updateFocusedElementInformationTimer.stop();
952
953 if (!m_focusedElement)
954 return;
955
956 auto information = focusedElementInformation();
957 if (!information)
958 return;
959
960 send(Messages::WebPageProxy::UpdateFocusedElementInformation(*information));
961}
962
963#if ENABLE(DRAG_SUPPORT)(defined 1 && 1)
964Awaitable<DragInitiationResult> WebPage::requestDragStart(std::optional<WebCore::FrameIdentifier> remoteFrameID, IntPoint clientPosition, IntPoint globalPosition, OptionSet<WebCore::DragSourceAction> allowedActionsMask)
965{
966 SetForScope allowedActionsForScope(m_allowedDragSourceActions, allowedActionsMask);
967 RefPtr localRootFrame = this->localRootFrame(remoteFrameID);
968 if (!localRootFrame)
969 co_return { false };
970
971 auto handledOrTransformer = co_await AwaitableFromCompletionHandler<Expected<bool, RemoteFrameGeometryTransformer>> { [=] (auto completionHandler) {
972 localRootFrame->eventHandler().tryToBeginDragAtPoint(clientPosition, globalPosition, WTF::move(completionHandler));
973 } };
974 if (handledOrTransformer)
975 co_return { *handledOrTransformer };
976 auto& transformer = handledOrTransformer.error();
977 co_return { DragInitiationResult::RemoteFrameData {
978 transformer.remoteFrameID(),
979 transformer.transformToRemoteFrameCoordinates(clientPosition),
980 transformer.transformToRemoteFrameCoordinates(globalPosition)
981 } };
982}
983
984Awaitable<DragInitiationResult> WebPage::requestAdditionalItemsForDragSession(std::optional<WebCore::FrameIdentifier> rootFrameID, IntPoint clientPosition, IntPoint globalPosition, OptionSet<WebCore::DragSourceAction> allowedActionsMask)
985{
986 SetForScope allowedActionsForScope(m_allowedDragSourceActions, allowedActionsMask);
987 // To augment the platform drag session with additional items, end the current drag session and begin a new drag session with the new drag item.
988 // This process is opaque to the UI process, which still maintains the old drag item in its drag session. Similarly, this persistent drag session
989 // is opaque to the web process, which only sees that the current drag has ended, and that a new one is beginning.
990 PlatformMouseEvent event(clientPosition, globalPosition, MouseButton::Left, PlatformEvent::Type::MouseMoved, 0, { }, MonotonicTime::now(), 0, WebCore::SyntheticClickType::NoTap, WebCore::MouseEventInputSource::UserDriven);
991 m_page->dragController().dragEnded();
992 RefPtr localMainFrame = protect(*m_page)->localMainFrame();
993 if (!localMainFrame)
994 co_return { false };
995
996 localMainFrame->eventHandler().dragSourceEndedAt(event, { }, MayExtendDragSession::Yes);
997
998 auto handledOrTransformer = co_await AwaitableFromCompletionHandler<Expected<bool, RemoteFrameGeometryTransformer>> { [=] (auto completionHandler) {
999 localMainFrame->eventHandler().tryToBeginDragAtPoint(clientPosition, globalPosition, WTF::move(completionHandler));
1000 } };
1001 if (handledOrTransformer)
1002 co_return { *handledOrTransformer };
1003 auto& transformer = handledOrTransformer.error();
1004 co_return { DragInitiationResult::RemoteFrameData {
1005 transformer.remoteFrameID(),
1006 transformer.transformToRemoteFrameCoordinates(clientPosition),
1007 transformer.transformToRemoteFrameCoordinates(globalPosition)
1008 } };
1009}
1010
1011void WebPage::insertDroppedImagePlaceholders(const Vector<IntSize>& imageSizes, CompletionHandler<void(const Vector<IntRect>&, RefPtr<WebCore::TextIndicator>)>&& reply)
1012{
1013 m_page->dragController().insertDroppedImagePlaceholdersAtCaret(imageSizes);
1014 auto placeholderRects = m_page->dragController().droppedImagePlaceholders().map([&] (auto& element) {
1015 return rootViewBounds(element);
1016 });
1017
1018 auto imagePlaceholderRange = m_page->dragController().droppedImagePlaceholderRange();
1019 if (placeholderRects.size() != imageSizes.size()) {
1020 RELEASE_LOG(DragAndDrop, "Failed to insert dropped image placeholders: placeholder rect count (%tu) does not match image size count (%tu).", placeholderRects.size(), imageSizes.size())clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2LogDragAndDrop
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("Failed to insert dropped image placeholders: placeholder rect count (%tu) does not match image size count (%tu)."
), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG38"
) = "Failed to insert dropped image placeholders: placeholder rect count (%tu) does not match image size count (%tu)."
; uint8_t _Alignas(16) __attribute__((uninitialized)) _os_fmt_buf
[__builtin_os_log_format_buffer_size("Failed to insert dropped image placeholders: placeholder rect count (%tu) does not match image size count (%tu)."
, placeholderRects.size(), imageSizes.size())]; _os_log_impl(
&__dso_handle, _log_tmp, _type_tmp, _os_fmt_str, (uint8_t
*)__builtin_os_log_format(_os_fmt_buf, "Failed to insert dropped image placeholders: placeholder rect count (%tu) does not match image size count (%tu)."
, placeholderRects.size(), imageSizes.size()), (uint32_t)sizeof
(_os_fmt_buf)) clang diagnostic pop ; }); } }) clang diagnostic
pop
;
1021 reply({ }, nullptr);
1022 return;
1023 }
1024
1025 if (!imagePlaceholderRange) {
1026 RELEASE_LOG(DragAndDrop, "Failed to insert dropped image placeholders: no image placeholder range.")clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2LogDragAndDrop
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("Failed to insert dropped image placeholders: no image placeholder range."
), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG39"
) = "Failed to insert dropped image placeholders: no image placeholder range."
; uint8_t _Alignas(16) __attribute__((uninitialized)) _os_fmt_buf
[__builtin_os_log_format_buffer_size("Failed to insert dropped image placeholders: no image placeholder range."
)]; _os_log_impl(&__dso_handle, _log_tmp, _type_tmp, _os_fmt_str
, (uint8_t *)__builtin_os_log_format(_os_fmt_buf, "Failed to insert dropped image placeholders: no image placeholder range."
), (uint32_t)sizeof(_os_fmt_buf)) clang diagnostic pop ; }); }
}) clang diagnostic pop
;
1027 reply({ }, nullptr);
1028 return;
1029 }
1030
1031 constexpr OptionSet<TextIndicatorOption> textIndicatorOptions {
1032 TextIndicatorOption::IncludeSnapshotOfAllVisibleContentWithoutSelection,
1033 TextIndicatorOption::ExpandClipBeyondVisibleRect,
1034 TextIndicatorOption::PaintAllContent,
1035 TextIndicatorOption::UseSelectionRectForSizing
1036 };
1037
1038 RefPtr textIndicator = TextIndicator::createWithRange(*imagePlaceholderRange, textIndicatorOptions, TextIndicatorPresentationTransition::None, { });
1039
1040 reply(WTF::move(placeholderRects), WTF::move(textIndicator));
1041}
1042
1043void WebPage::didConcludeDrop()
1044{
1045 m_rangeForDropSnapshot = std::nullopt;
1046 m_pendingImageElementsForDropSnapshot.clear();
1047}
1048
1049void WebPage::didConcludeEditDrag()
1050{
1051 send(Messages::WebPageProxy::WillReceiveEditDragSnapshot());
1052
1053 layoutIfNeeded();
1054
1055 m_pendingImageElementsForDropSnapshot.clear();
1056
1057 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1058 if (!frame)
1059 return;
1060
1061 if (auto selectionRange = frame->selection().selection().toNormalizedRange()) {
1062 m_pendingImageElementsForDropSnapshot = visibleImageElementsInRangeWithNonLoadedImages(*selectionRange);
1063 protect(frame->selection())->setSelectedRange(makeSimpleRange(selectionRange->end), Affinity::Downstream, FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
1064 m_rangeForDropSnapshot = WTF::move(selectionRange);
1065 }
1066
1067 if (m_pendingImageElementsForDropSnapshot.isEmpty())
1068 computeAndSendEditDragSnapshot();
1069}
1070
1071void WebPage::didFinishLoadingImageForElement(WebCore::HTMLImageElement& element)
1072{
1073 if (!m_pendingImageElementsForDropSnapshot.remove(&element))
1074 return;
1075
1076 bool shouldSendSnapshot = m_pendingImageElementsForDropSnapshot.isEmpty();
1077 m_page->dragController().finalizeDroppedImagePlaceholder(element, [protectedThis = Ref { *this }, shouldSendSnapshot] {
1078 if (shouldSendSnapshot)
1079 protectedThis->computeAndSendEditDragSnapshot();
1080 });
1081}
1082
1083void WebPage::computeAndSendEditDragSnapshot()
1084{
1085 RefPtr<WebCore::TextIndicator> textIndicator;
1086 constexpr OptionSet<TextIndicatorOption> defaultTextIndicatorOptionsForEditDrag {
1087 TextIndicatorOption::IncludeSnapshotOfAllVisibleContentWithoutSelection,
1088 TextIndicatorOption::ExpandClipBeyondVisibleRect,
1089 TextIndicatorOption::PaintAllContent,
1090 TextIndicatorOption::IncludeMarginIfRangeMatchesSelection,
1091 TextIndicatorOption::PaintBackgrounds,
1092 TextIndicatorOption::ComputeEstimatedBackgroundColor,
1093 TextIndicatorOption::UseSelectionRectForSizing,
1094 TextIndicatorOption::IncludeSnapshotWithSelectionHighlight
1095 };
1096 if (auto range = std::exchange(m_rangeForDropSnapshot, std::nullopt))
1097 textIndicator = TextIndicator::createWithRange(*range, defaultTextIndicatorOptionsForEditDrag, TextIndicatorPresentationTransition::None, { });
1098
1099 send(Messages::WebPageProxy::DidReceiveEditDragSnapshot(WTF::move(textIndicator)));
1100}
1101
1102#endif
1103
1104void WebPage::handleTwoFingerTapAtPoint(const WebCore::IntPoint& point, OptionSet<WebKit::WebEventModifier> modifiers, WebKit::TapIdentifier requestID)
1105{
1106 FloatPoint adjustedPoint;
1107 RefPtr localMainFrame = protect(*m_page)->localMainFrame();
1108 RefPtr nodeRespondingToClick = localMainFrame ? localMainFrame->nodeRespondingToClickEvents(point, adjustedPoint) : nullptr;
1109 if (!nodeRespondingToClick || !nodeRespondingToClick->renderer()) {
1110 send(Messages::WebPageProxy::DidNotHandleTapAsClick(roundedIntPoint(adjustedPoint)));
1111 return;
1112 }
1113 sendTapHighlightForNodeIfNecessary(requestID, nodeRespondingToClick, point);
1114 completeSyntheticClick(std::nullopt, *nodeRespondingToClick, adjustedPoint, modifiers, WebCore::SyntheticClickType::TwoFingerTap);
1115}
1116
1117void WebPage::clearSelectionAfterTapIfNeeded()
1118{
1119 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1120 if (!frame)
1121 return;
1122
1123#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
1124 if (RefPtr pluginView = pluginViewForFrame(frame.get())) {
1125 pluginView->clearSelection();
1126 return;
1127 }
1128#endif
1129
1130 if (frame->selection().selection().isContentEditable())
1131 return;
1132
1133 clearSelection();
1134}
1135
1136void WebPage::didRecognizeLongPress()
1137{
1138 if (RefPtr localMainFrame = protect(*m_page)->localMainFrame())
1139 ContentChangeObserver::didRecognizeLongPress(*localMainFrame);
1140}
1141
1142void WebPage::tapHighlightAtPosition(WebKit::TapIdentifier requestID, const FloatPoint& position)
1143{
1144 RefPtr localMainFrame = protect(*m_page)->localMainFrame();
1145 if (!localMainFrame)
1146 return;
1147 FloatPoint adjustedPoint;
1148 sendTapHighlightForNodeIfNecessary(requestID, localMainFrame->nodeRespondingToClickEvents(position, adjustedPoint), position);
1149}
1150
1151void WebPage::inspectorNodeSearchMovedToPosition(const FloatPoint& position)
1152{
1153 RefPtr localMainFrame = protect(*m_page)->localMainFrame();
1154 if (!localMainFrame)
1155 return;
1156 IntPoint adjustedPoint = roundedIntPoint(position);
1157
1158 localMainFrame->eventHandler().mouseMoved(PlatformMouseEvent(adjustedPoint, adjustedPoint, MouseButton::None, PlatformEvent::Type::MouseMoved, 0, { }, { }, 0, WebCore::SyntheticClickType::NoTap, WebCore::MouseEventInputSource::UserDriven));
1159 protect(localMainFrame->document())->updateStyleIfNeeded();
1160}
1161
1162void WebPage::inspectorNodeSearchEndedAtPosition(const FloatPoint& position)
1163{
1164 RefPtr localMainFrame = protect(*m_page)->localMainFrame();
1165 if (RefPtr node = localMainFrame ? localMainFrame->deepestNodeAtLocation(position) : nullptr)
1166 node->inspect();
1167}
1168
1169void WebPage::updateInputContextAfterBlurringAndRefocusingElementIfNeeded(Element& element)
1170{
1171 if (m_recentlyBlurredElement != &element || !m_isShowingInputViewForFocusedElement)
1172 return;
1173
1174 m_hasPendingInputContextUpdateAfterBlurringAndRefocusingElement = true;
1175 callOnMainRunLoop([this, protectedThis = Ref { *this }] {
1176 if (m_hasPendingInputContextUpdateAfterBlurringAndRefocusingElement)
1177 send(Messages::WebPageProxy::UpdateInputContextAfterBlurringAndRefocusingElement());
1178 m_hasPendingInputContextUpdateAfterBlurringAndRefocusingElement = false;
1179 });
1180}
1181
1182void WebPage::didProgrammaticallyClearTextFormControl(const HTMLTextFormControlElement& element)
1183{
1184 if (!m_isShowingInputViewForFocusedElement)
1185 return;
1186
1187 if (m_focusedElement != &element && m_recentlyBlurredElement != &element)
1188 return;
1189
1190 callOnMainRunLoop([protectedThis = Ref { *this }, element = Ref { element }] {
1191 if (protectedThis->m_focusedElement != element.ptr())
1192 return;
1193
1194 auto context = protectedThis->contextForElement(element);
1195 if (!context)
1196 return;
1197
1198 protectedThis->send(Messages::WebPageProxy::DidProgrammaticallyClearFocusedElement(WTF::move(*context)));
1199 });
1200}
1201
1202void WebPage::blurFocusedElement()
1203{
1204 if (!m_focusedElement)
1205 return;
1206
1207 protect(m_focusedElement)->blur();
1208}
1209
1210void WebPage::setFocusedElementValue(const WebCore::ElementContext& context, const String& value)
1211{
1212 // FIXME: should also handle the case of HTMLSelectElement.
1213 if (RefPtr input = dynamicDowncast<HTMLInputElement>(elementForContext(context)))
1214 input->setValue(value, DispatchInputAndChangeEvent);
1215}
1216
1217void WebPage::setFocusedElementSelectedIndex(const WebCore::ElementContext& context, uint32_t index, bool allowMultipleSelection)
1218{
1219 if (RefPtr select = dynamicDowncast<HTMLSelectElement>(elementForContext(context)))
1220 select->optionSelectedByUser(index, true, allowMultipleSelection);
1221}
1222
1223void WebPage::setSelectElementIsOpen(const WebCore::ElementContext& context, bool isOpen)
1224{
1225 if (RefPtr select = dynamicDowncast<HTMLSelectElement>(elementForContext(context)))
1226 select->setPopupIsVisible(isOpen);
1227}
1228
1229void WebPage::setIsShowingInputViewForFocusedElement(bool showingInputView)
1230{
1231 m_isShowingInputViewForFocusedElement = showingInputView;
1232 if (!showingInputView)
1233 protect(*m_page)->clearIsShowingInputView();
1234}
1235
1236void WebPage::showInspectorHighlight(const WebCore::InspectorOverlay::Highlight& highlight)
1237{
1238 send(Messages::WebPageProxy::ShowInspectorHighlight(highlight));
1239}
1240
1241void WebPage::hideInspectorHighlight()
1242{
1243 send(Messages::WebPageProxy::HideInspectorHighlight());
1244}
1245
1246void WebPage::showInspectorIndication()
1247{
1248 send(Messages::WebPageProxy::ShowInspectorIndication());
1249}
1250
1251void WebPage::hideInspectorIndication()
1252{
1253 send(Messages::WebPageProxy::HideInspectorIndication());
1254}
1255
1256void WebPage::enableInspectorNodeSearch()
1257{
1258 send(Messages::WebPageProxy::EnableInspectorNodeSearch());
1259}
1260
1261void WebPage::disableInspectorNodeSearch()
1262{
1263 send(Messages::WebPageProxy::DisableInspectorNodeSearch());
1264}
1265
1266void WebPage::setForceAlwaysUserScalable(bool userScalable)
1267{
1268 m_forceAlwaysUserScalable = userScalable;
1269 m_viewportConfiguration.setForceAlwaysUserScalable(userScalable);
1270}
1271
1272static bool insideImageOverlay(const VisiblePosition& position)
1273{
1274 RefPtr container = position.deepEquivalent().containerNode();
1275 return container && ImageOverlay::isInsideOverlay(*container);
1276}
1277
1278static std::optional<SimpleRange> expandForImageOverlay(const SimpleRange& range)
1279{
1280 VisiblePosition expandedStart(makeContainerOffsetPosition(protect(range.startContainer()), range.startOffset()));
1281 VisiblePosition expandedEnd(makeContainerOffsetPosition(protect(range.endContainer()), range.endOffset()));
1282
1283 for (auto start = expandedStart; insideImageOverlay(start); start = start.previous()) {
1284 if (RefPtr container = start.deepEquivalent().containerNode(); is<Text>(container)) {
1285 expandedStart = firstPositionInNode(*container).downstream();
1286 break;
1287 }
1288 }
1289
1290 for (auto end = expandedEnd; insideImageOverlay(end); end = end.next()) {
1291 if (RefPtr container = end.deepEquivalent().containerNode(); is<Text>(container)) {
1292 expandedEnd = lastPositionInNode(*container).upstream();
1293 break;
1294 }
1295 }
1296
1297 return makeSimpleRange({ expandedStart, expandedEnd });
1298}
1299
1300static std::pair<std::optional<SimpleRange>, SelectionWasFlipped> rangeForPointInRootViewCoordinates(LocalFrame& frame, const IntPoint& pointInRootViewCoordinates, bool baseIsStart, bool selectionFlippingEnabled)
1301{
1302 VisibleSelection existingSelection = frame.selection().selection();
1303 VisiblePosition selectionStart = existingSelection.visibleStart();
1304 VisiblePosition selectionEnd = existingSelection.visibleEnd();
1305 RefPtr localMainFrame = frame.localMainFrame();
1306 if (!localMainFrame)
1307 return { std::nullopt, SelectionWasFlipped::No };
1308
1309 auto pointInDocument = protect(frame.view())->rootViewToContents(pointInRootViewCoordinates.constrainedWithin(protect(localMainFrame->view())->unobscuredContentRect()));
1310
1311 if (!selectionFlippingEnabled) {
1312 RefPtr node = selectionStart.deepEquivalent().containerNode();
1313 if (node && node->renderStyle() && node->renderStyle()->writingMode().isVertical()) {
1314 if (baseIsStart) {
1315 int startX = selectionStart.absoluteCaretBounds().center().x();
1316 if (pointInDocument.x() > startX)
1317 pointInDocument.setX(startX);
1318 } else {
1319 int endX = selectionEnd.absoluteCaretBounds().center().x();
1320 if (pointInDocument.x() < endX)
1321 pointInDocument.setX(endX);
1322 }
1323 } else {
1324 if (baseIsStart) {
1325 int startY = selectionStart.absoluteCaretBounds().center().y();
1326 if (pointInDocument.y() < startY)
1327 pointInDocument.setY(startY);
1328 } else {
1329 int endY = selectionEnd.absoluteCaretBounds().center().y();
1330 if (pointInDocument.y() > endY)
1331 pointInDocument.setY(endY);
1332 }
1333 }
1334 }
1335
1336 auto hitTest = frame.eventHandler().hitTestResultAtPoint(pointInDocument, {
1337 HitTestRequest::Type::ReadOnly,
1338 HitTestRequest::Type::Active,
1339 HitTestRequest::Type::AllowVisibleChildFrameContentOnly,
1340 });
1341
1342 RefPtr targetNode = hitTest.targetNode();
1343 if (targetNode && !HTMLElement::shouldExtendSelectionToTargetNode(*targetNode, existingSelection))
1344 return { std::nullopt, SelectionWasFlipped::No };
1345
1346 VisiblePosition result;
1347 std::optional<SimpleRange> range;
1348 SelectionWasFlipped selectionFlipped = SelectionWasFlipped::No;
1349
1350 auto shouldUseOldExtentAsNewBase = [&] {
1351 return frame.settings().visuallyContiguousBidiTextSelectionEnabled()
1352 && crossesBidiTextBoundaryInSameLine(result, baseIsStart ? selectionEnd : selectionStart);
1353 };
1354
1355 if (targetNode)
1356 result = frame.eventHandler().selectionExtentRespectingEditingBoundary(frame.selection().selection(), hitTest.localPoint(), targetNode.get()).deepEquivalent();
1357 else
1358 result = frame.visiblePositionForPoint(pointInDocument).deepEquivalent();
1359
1360 if (baseIsStart) {
1361 bool flipSelection = result < selectionStart;
1362
1363 if ((flipSelection && !selectionFlippingEnabled) || result == selectionStart)
1364 result = selectionStart.next();
1365 else if (RefPtr containerNode = selectionStart.deepEquivalent().containerNode(); containerNode && targetNode && &containerNode->treeScope() != &targetNode->treeScope())
1366 result = VisibleSelection::adjustPositionForEnd(result.deepEquivalent(), containerNode.get());
1367
1368 if (selectionFlippingEnabled && flipSelection) {
1369 range = makeSimpleRange(result, shouldUseOldExtentAsNewBase() ? selectionEnd : selectionStart);
1370 selectionFlipped = SelectionWasFlipped::Yes;
1371 } else
1372 range = makeSimpleRange(selectionStart, result);
1373 } else {
1374 bool flipSelection = selectionEnd < result;
1375
1376 if ((flipSelection && !selectionFlippingEnabled) || result == selectionEnd)
1377 result = selectionEnd.previous();
1378 else if (RefPtr containerNode = selectionEnd.deepEquivalent().containerNode(); containerNode && targetNode && &containerNode->treeScope() != &targetNode->treeScope())
1379 result = VisibleSelection::adjustPositionForStart(result.deepEquivalent(), containerNode.get());
1380
1381 if (selectionFlippingEnabled && flipSelection) {
1382 range = makeSimpleRange(shouldUseOldExtentAsNewBase() ? selectionStart : selectionEnd, result);
1383 selectionFlipped = SelectionWasFlipped::Yes;
1384 } else
1385 range = makeSimpleRange(result, selectionEnd);
1386 }
1387
1388 if (range && ImageOverlay::isInsideOverlay(*range))
1389 return { expandForImageOverlay(*range), SelectionWasFlipped::No };
1390
1391 return { range, selectionFlipped };
1392}
1393
1394static std::optional<SimpleRange> rangeAtWordBoundaryForPosition(LocalFrame* frame, const VisiblePosition& position, bool baseIsStart)
1395{
1396 SelectionDirection sameDirection = baseIsStart ? SelectionDirection::Forward : SelectionDirection::Backward;
1397 SelectionDirection oppositeDirection = baseIsStart ? SelectionDirection::Backward : SelectionDirection::Forward;
1398 VisiblePosition base = baseIsStart ? frame->selection().selection().visibleStart() : frame->selection().selection().visibleEnd();
1399 VisiblePosition extent = baseIsStart ? frame->selection().selection().visibleEnd() : frame->selection().selection().visibleStart();
1400 VisiblePosition initialExtent = position;
1401
1402 if (atBoundaryOfGranularity(extent, TextGranularity::WordGranularity, sameDirection)) {
1403 // This is a word boundary. Leave selection where it is.
1404 return std::nullopt;
1405 }
1406
1407 if (atBoundaryOfGranularity(extent, TextGranularity::WordGranularity, oppositeDirection)) {
1408 // This is a word boundary in the wrong direction. Nudge the selection to a character before proceeding.
1409 extent = baseIsStart ? extent.previous() : extent.next();
1410 }
1411
1412 // Extend to the boundary of the word.
1413
1414 VisiblePosition wordBoundary = positionOfNextBoundaryOfGranularity(extent, TextGranularity::WordGranularity, sameDirection);
1415 if (wordBoundary.isNotNull()
1416 && atBoundaryOfGranularity(wordBoundary, TextGranularity::WordGranularity, sameDirection)
1417 && initialExtent != wordBoundary) {
1418 extent = wordBoundary;
1419 if (!(base < extent))
1420 std::swap(base, extent);
1421 return makeSimpleRange(base, extent);
1422 }
1423 // Conversely, if the initial extent equals the current word boundary, then
1424 // run the rest of this function to see if the selection should extend
1425 // the other direction to the other word.
1426
1427 // If this is where the extent was initially, then iterate in the other direction in the document until we hit the next word.
1428 while (extent.isNotNull()
1429 && !atBoundaryOfGranularity(extent, TextGranularity::WordGranularity, sameDirection)
1430 && extent != base
1431 && !atBoundaryOfGranularity(extent, TextGranularity::LineGranularity, sameDirection)
1432 && !atBoundaryOfGranularity(extent, TextGranularity::LineGranularity, oppositeDirection)) {
1433 extent = baseIsStart ? extent.next() : extent.previous();
1434 }
1435
1436 // Don't let the smart extension make the extent equal the base.
1437 // Expand out to word boundary.
1438 if (extent.isNull() || extent == base)
1439 extent = wordBoundary;
1440 if (extent.isNull())
1441 return std::nullopt;
1442
1443 if (!(base < extent))
1444 std::swap(base, extent);
1445 return makeSimpleRange(base, extent);
1446}
1447
1448IntRect WebPage::rootViewBounds(const Node& node)
1449{
1450 RefPtr frame = node.document().frame();
1451 if (!frame)
1452 return { };
1453
1454 RefPtr view = frame->view();
1455 if (!view)
1456 return { };
1457
1458 CheckedPtr renderer = node.renderer();
1459 if (!renderer)
1460 return { };
1461
1462 return view->contentsToRootView(renderer->absoluteBoundingBoxRect());
1463}
1464
1465void WebPage::clearSelection()
1466{
1467 m_startingGestureRange = std::nullopt;
1468 RefPtr focusedOrMainFrame = protect(*m_page)->focusController().focusedOrMainFrame();
1469 protect(focusedOrMainFrame->selection())->clear();
1470#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
1471 if (RefPtr pluginView = pluginViewForFrame(focusedOrMainFrame.get()))
1472 pluginView->clearSelection();
1473#endif
1474}
1475
1476void WebPage::dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch touch, const IntPoint& point)
1477{
1478 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1479 if (!frame)
1480 return;
1481
1482 if (!frame->selection().selection().isContentEditable())
1483 return;
1484
1485 IntRect focusedElementRect;
1486 if (m_focusedElement)
1487 focusedElementRect = rootViewInteractionBounds(*protect(m_focusedElement));
1488
1489 if (focusedElementRect.isEmpty())
1490 return;
1491
1492 RefPtr localMainFrame = protect(m_page)->localMainFrame();
1493 if (!localMainFrame)
1494 return;
1495
1496 auto adjustedPoint = point.constrainedBetween(focusedElementRect.minXMinYCorner(), focusedElementRect.maxXMaxYCorner());
1497 auto& eventHandler = localMainFrame->eventHandler();
1498 switch (touch) {
1499 case SelectionTouch::Started:
1500 eventHandler.handleMousePressEvent({ adjustedPoint, adjustedPoint, MouseButton::Left, PlatformEvent::Type::MousePressed, 1, { }, MonotonicTime::now(), WebCore::ForceAtClick, WebCore::SyntheticClickType::NoTap, WebCore::MouseEventInputSource::UserDriven });
1501 break;
1502 case SelectionTouch::Moved:
1503 eventHandler.dispatchSyntheticMouseMove({ adjustedPoint, adjustedPoint, MouseButton::Left, PlatformEvent::Type::MouseMoved, 0, { }, MonotonicTime::now(), WebCore::ForceAtClick, WebCore::SyntheticClickType::NoTap, WebCore::MouseEventInputSource::UserDriven });
1504 break;
1505 case SelectionTouch::Ended:
1506 case SelectionTouch::EndedMovingForward:
1507 case SelectionTouch::EndedMovingBackward:
1508 case SelectionTouch::EndedNotMoving:
1509 eventHandler.handleMouseReleaseEvent({ adjustedPoint, adjustedPoint, MouseButton::Left, PlatformEvent::Type::MouseReleased, 1, { }, MonotonicTime::now(), WebCore::ForceAtClick, WebCore::SyntheticClickType::NoTap, WebCore::MouseEventInputSource::UserDriven });
1510 break;
1511 }
1512}
1513
1514void WebPage::clearSelectionAfterTappingSelectionHighlightIfNeeded(WebCore::FloatPoint location)
1515{
1516 RefPtr localMainFrame = protect(m_page)->localMainFrame();
1517 if (!localMainFrame)
1518 return;
1519
1520 auto result = localMainFrame->eventHandler().hitTestResultAtPoint(LayoutPoint { location }, {
1521 HitTestRequest::Type::ReadOnly,
1522 HitTestRequest::Type::AllowVisibleChildFrameContentOnly,
1523 HitTestRequest::Type::IncludeAllElementsUnderPoint,
1524 HitTestRequest::Type::CollectMultipleElements,
1525 });
1526
1527 bool tappedVideo = false;
1528 bool tappedLiveText = false;
1529 for (Ref node : result.listBasedTestResult()) {
1530 if (is<HTMLVideoElement>(node))
1531 tappedVideo = true;
1532 else if (ImageOverlay::isOverlayText(node))
1533 tappedLiveText = true;
1534 }
1535
1536 if (tappedVideo && !tappedLiveText)
1537 clearSelection();
1538}
1539
1540bool WebPage::shouldDrawVisuallyContiguousBidiSelection() const
1541{
1542 return m_page->settings().visuallyContiguousBidiTextSelectionEnabled() && m_activeTextInteractionSources;
1543}
1544
1545void WebPage::addTextInteractionSources(OptionSet<TextInteractionSource> sources)
1546{
1547 if (sources.isEmpty())
1548 return;
1549
1550 bool wasEmpty = m_activeTextInteractionSources.isEmpty();
1551 m_activeTextInteractionSources.add(sources);
1552 if (!wasEmpty)
1553 return;
1554
1555 if (m_page->settings().visuallyContiguousBidiTextSelectionEnabled())
1556 scheduleFullEditorStateUpdate();
1557}
1558
1559void WebPage::removeTextInteractionSources(OptionSet<TextInteractionSource> sources)
1560{
1561 if (m_activeTextInteractionSources.isEmpty())
1562 return;
1563
1564 m_activeTextInteractionSources.remove(sources);
1565
1566 if (!m_activeTextInteractionSources.isEmpty())
1567 return;
1568
1569 if (!m_page->settings().visuallyContiguousBidiTextSelectionEnabled())
1570 return;
1571
1572 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1573 if (!frame)
1574 return;
1575
1576 auto originalSelection = frame->selection().selection();
1577 if (!originalSelection.isNonOrphanedRange())
1578 return;
1579
1580 auto originalSelectedRange = originalSelection.toNormalizedRange();
1581 if (!originalSelectedRange)
1582 return;
1583
1584 auto adjustedRange = adjustToVisuallyContiguousRange(*originalSelectedRange);
1585 if (originalSelectedRange == adjustedRange)
1586 return;
1587
1588 protect(frame->selection())->setSelectedRange(adjustedRange, originalSelection.affinity(), FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
1589}
1590
1591void WebPage::updateSelectionWithTouches(const IntPoint& point, SelectionTouch selectionTouch, bool baseIsStart, CompletionHandler<void(const WebCore::IntPoint&, SelectionTouch, OptionSet<SelectionFlags>)>&& completionHandler)
1592{
1593 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1594 if (!frame)
1595 return completionHandler(point, selectionTouch, { });
1596
1597#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
1598 if (RefPtr pluginView = focusedPluginViewForFrame(*frame)) {
1599 OptionSet<SelectionFlags> resultFlags;
1600 auto startOrEnd = baseIsStart ? SelectionEndpoint::End : SelectionEndpoint::Start;
1601 if (pluginView->moveSelectionEndpoint(point, startOrEnd) == SelectionWasFlipped::Yes)
1602 resultFlags.add(SelectionFlags::SelectionFlipped);
1603 return completionHandler(point, selectionTouch, resultFlags);
1604 }
1605#endif
1606
1607 if (selectionTouch == SelectionTouch::Moved
1608 && m_bidiSelectionFlippingState != BidiSelectionFlippingState::NotFlipping
1609 && baseIsStart == (m_bidiSelectionFlippingState == BidiSelectionFlippingState::FlippingToStart)) {
1610 // The last selection update caused the selection base and extent to swap. Ignore any in-flight selection
1611 // update that's still trying to adjust the new selection base (after flipping).
1612 return completionHandler(point, selectionTouch, { });
1613 }
1614
1615 if (selectionTouch == SelectionTouch::Started)
1616 addTextInteractionSources(TextInteractionSource::Touch);
1617
1618 IntPoint pointInDocument = RefPtr(frame->view())->rootViewToContents(point);
1619 VisiblePosition position = frame->visiblePositionForPoint(pointInDocument);
1620 if (position.isNull())
1621 return completionHandler(point, selectionTouch, { });
1622
1623 if (shouldDispatchSyntheticMouseEventsWhenModifyingSelection())
1624 dispatchSyntheticMouseEventsForSelectionGesture(selectionTouch, point);
1625
1626 std::optional<SimpleRange> range;
1627 OptionSet<SelectionFlags> flags;
1628 SelectionWasFlipped selectionFlipped = SelectionWasFlipped::No;
1629
1630 switch (selectionTouch) {
1631 case SelectionTouch::Started:
1632 case SelectionTouch::EndedNotMoving:
1633 break;
1634
1635 case SelectionTouch::Ended:
1636 if (frame->selection().selection().isContentEditable())
1637 range = makeSimpleRange(closestWordBoundaryForPosition(position));
1638 else
1639 std::tie(range, selectionFlipped) = rangeForPointInRootViewCoordinates(*frame, point, baseIsStart, selectionFlippingEnabled());
1640 break;
1641
1642 case SelectionTouch::EndedMovingForward:
1643 case SelectionTouch::EndedMovingBackward:
1644 range = rangeAtWordBoundaryForPosition(frame.get(), position, baseIsStart);
1645 break;
1646
1647 case SelectionTouch::Moved:
1648 std::tie(range, selectionFlipped) = rangeForPointInRootViewCoordinates(*frame, point, baseIsStart, selectionFlippingEnabled());
1649 break;
1650 }
1651
1652 if (range)
1653 protect(frame->selection())->setSelectedRange(range, position.affinity(), WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
1654
1655 switch (selectionTouch) {
1656 case SelectionTouch::Started:
1657 case SelectionTouch::Moved:
1658 break;
1659 case SelectionTouch::Ended:
1660 case SelectionTouch::EndedMovingForward:
1661 case SelectionTouch::EndedMovingBackward:
1662 case SelectionTouch::EndedNotMoving:
1663 removeTextInteractionSources(TextInteractionSource::Touch);
1664 break;
1665 }
1666
1667 if (selectionFlipped == SelectionWasFlipped::Yes) {
1668 flags.add(SelectionFlags::SelectionFlipped);
1669 m_bidiSelectionFlippingState = baseIsStart ? BidiSelectionFlippingState::FlippingToStart : BidiSelectionFlippingState::FlippingToEnd;
1670 } else
1671 m_bidiSelectionFlippingState = BidiSelectionFlippingState::NotFlipping;
1672
1673 completionHandler(point, selectionTouch, flags);
1674}
1675
1676void WebPage::selectWithTwoTouches(const WebCore::IntPoint& from, const WebCore::IntPoint& to, GestureType gestureType, GestureRecognizerState gestureState, CompletionHandler<void(const WebCore::IntPoint&, GestureType, GestureRecognizerState, OptionSet<SelectionFlags>)>&& completionHandler)
1677{
1678 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1679 if (!frame)
1680 return;
1681
1682 RefPtr view = frame->view();
1683 auto fromPosition = frame->visiblePositionForPoint(view->rootViewToContents(from));
1684 auto toPosition = frame->visiblePositionForPoint(view->rootViewToContents(to));
1685 if (auto range = makeSimpleRange(fromPosition, toPosition)) {
1686 if (!(fromPosition < toPosition))
1687 std::swap(range->start, range->end);
1688 protect(frame->selection())->setSelectedRange(range, fromPosition.affinity(), WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
1689 }
1690
1691 // We can use the same callback for the gestures with one point.
1692 completionHandler(from, gestureType, gestureState, { });
1693}
1694
1695void WebPage::extendSelectionForReplacement(CompletionHandler<void()>&& completion)
1696{
1697 auto callCompletionHandlerOnExit = makeScopeExit(WTF::move(completion));
1698
1699 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1700 if (!frame)
1701 return;
1702
1703 RefPtr document = frame->document();
1704 if (!document)
1705 return;
1706
1707 auto selectedRange = frame->selection().selection().range();
1708 if (!selectedRange || !selectedRange->collapsed())
1709 return;
1710
1711 VisiblePosition position = frame->selection().selection().start();
1712 RefPtr container = position.deepEquivalent().containerNode();
1713 if (!container)
1714 return;
1715
1716 auto markerRanges = protect(document->markers())->markersFor(*container, { DocumentMarkerType::DictationAlternatives, DocumentMarkerType::CorrectionIndicator }).map([&](auto& marker) {
1717 return makeSimpleRange(*container, *marker);
1718 });
1719
1720 std::optional<SimpleRange> rangeToSelect;
1721 for (auto& markerRange : markerRanges) {
1722 if (contains(makeVisiblePositionRange(markerRange), position)) {
1723 // In practice, dictation markers should only span a single text node, so it's sufficient to
1724 // grab the first matching range (instead of taking the union of all intersecting ranges).
1725 rangeToSelect = markerRange;
1726 break;
1727 }
1728 }
1729
1730 if (!rangeToSelect)
1731 rangeToSelect = wordRangeFromPosition(position);
1732
1733 if (!rangeToSelect)
1734 return;
1735
1736 if (auto lastRange = std::exchange(m_lastSelectedReplacementRange, { }); lastRange && makeSimpleRange(*lastRange) == rangeToSelect)
1737 return;
1738
1739 setSelectedRangeDispatchingSyntheticMouseEventsIfNeeded(*rangeToSelect, position.affinity());
1740 m_lastSelectedReplacementRange = rangeToSelect->makeWeakSimpleRange();
1741}
1742
1743void WebPage::resetLastSelectedReplacementRangeIfNeeded()
1744{
1745 if (!m_lastSelectedReplacementRange)
1746 return;
1747
1748 auto replacementRange = makeSimpleRange(*m_lastSelectedReplacementRange);
1749 if (!replacementRange) {
1750 m_lastSelectedReplacementRange = { };
1751 return;
1752 }
1753
1754 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1755 if (!frame) {
1756 m_lastSelectedReplacementRange = { };
1757 return;
1758 }
1759
1760 auto selectedRange = frame->selection().selection().toNormalizedRange();
1761 if (!selectedRange) {
1762 m_lastSelectedReplacementRange = { };
1763 return;
1764 }
1765
1766 if (!contains(ComposedTree, *replacementRange, *selectedRange))
1767 m_lastSelectedReplacementRange = { };
1768}
1769
1770void WebPage::extendSelection(WebCore::TextGranularity granularity, CompletionHandler<void()>&& completionHandler)
1771{
1772 auto callCompletionHandlerOnExit = makeScopeExit(WTF::move(completionHandler));
1773
1774 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1775 if (!frame)
1776 return;
1777
1778 // For the moment we handle only TextGranularity::WordGranularity.
1779 if (granularity != TextGranularity::WordGranularity || !frame->selection().isCaret())
1780 return;
1781
1782 VisiblePosition position = frame->selection().selection().start();
1783 auto wordRange = wordRangeFromPosition(position);
1784 if (!wordRange)
1785 return;
1786
1787 setSelectedRangeDispatchingSyntheticMouseEventsIfNeeded(*wordRange, position.affinity());
1788}
1789
1790void WebPage::setSelectedRangeDispatchingSyntheticMouseEventsIfNeeded(const SimpleRange& range, Affinity affinity)
1791{
1792 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1793 if (!frame)
1794 return;
1795
1796 IntPoint endLocationForSyntheticMouseEvents;
1797 bool shouldDispatchMouseEvents = shouldDispatchSyntheticMouseEventsWhenModifyingSelection();
1798 if (shouldDispatchMouseEvents) {
1799 RefPtr view = frame->view();
1800 auto startLocationForSyntheticMouseEvents = view->contentsToRootView(VisiblePosition(makeDeprecatedLegacyPosition(range.start)).absoluteCaretBounds()).center();
1801 endLocationForSyntheticMouseEvents = view->contentsToRootView(VisiblePosition(makeDeprecatedLegacyPosition(range.end)).absoluteCaretBounds()).center();
1802 dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Started, startLocationForSyntheticMouseEvents);
1803 dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Moved, endLocationForSyntheticMouseEvents);
1804 }
1805
1806 protect(frame->selection())->setSelectedRange(range, affinity, WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
1807
1808 if (shouldDispatchMouseEvents)
1809 dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Ended, endLocationForSyntheticMouseEvents);
1810}
1811
1812void WebPage::platformDidSelectAll()
1813{
1814 if (!shouldDispatchSyntheticMouseEventsWhenModifyingSelection())
1815 return;
1816
1817 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1818 if (!frame)
1819 return;
1820
1821 RefPtr view = frame->view();
1822 auto startCaretRect = view->contentsToRootView(VisiblePosition(frame->selection().selection().start()).absoluteCaretBounds());
1823 auto endCaretRect = view->contentsToRootView(VisiblePosition(frame->selection().selection().end()).absoluteCaretBounds());
1824 dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Started, startCaretRect.center());
1825 dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Moved, endCaretRect.center());
1826 dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Ended, endCaretRect.center());
1827}
1828
1829void WebPage::selectWordBackward()
1830{
1831 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1832 if (!frame)
1833 return;
1834
1835 if (!frame->selection().isCaret())
1836 return;
1837
1838 auto position = frame->selection().selection().visibleStart();
1839 auto startPosition = positionOfNextBoundaryOfGranularity(position, TextGranularity::WordGranularity, SelectionDirection::Backward);
1840 if (startPosition.isNull() || startPosition == position)
1841 return;
1842
1843 protect(frame->selection())->setSelectedRange(makeSimpleRange(startPosition, position), position.affinity(), WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
1844}
1845
1846void WebPage::moveSelectionByOffset(int32_t offset, CompletionHandler<void()>&& completionHandler)
1847{
1848 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1849 if (!frame)
1850 return;
1851
1852 VisiblePosition startPosition = frame->selection().selection().end();
1853 if (startPosition.isNull())
1854 return;
1855 SelectionDirection direction = offset < 0 ? SelectionDirection::Backward : SelectionDirection::Forward;
1856 VisiblePosition position = startPosition;
1857 for (int i = 0; i < std::abs(offset); ++i) {
1858 position = positionOfNextBoundaryOfGranularity(position, TextGranularity::CharacterGranularity, direction);
1859 if (position.isNull())
1860 break;
1861 }
1862 if (position.isNotNull() && startPosition != position)
1863 protect(frame->selection())->setSelectedRange(makeSimpleRange(position), position.affinity(), WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
1864 completionHandler();
1865}
1866
1867void WebPage::startAutoscrollAtPosition(const WebCore::FloatPoint& positionInWindow)
1868{
1869 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1870 if (!frame)
1871 return;
1872
1873 if (m_focusedElement && m_focusedElement->renderer()) {
1874 frame->eventHandler().startSelectionAutoscroll(protect(m_focusedElement->renderer()), positionInWindow);
1875 return;
1876 }
1877
1878 auto& selection = frame->selection().selection();
1879 if (!selection.isRange())
1880 return;
1881
1882 auto range = selection.toNormalizedRange();
1883 if (!range)
1884 return;
1885
1886 CheckedPtr renderer = range->start.container->renderer();
1887 if (!renderer)
1888 return;
1889
1890 frame->eventHandler().startSelectionAutoscroll(renderer, positionInWindow);
1891}
1892
1893void WebPage::cancelAutoscroll()
1894{
1895 if (RefPtr frame = m_page->focusController().focusedOrMainFrame())
1896 frame->eventHandler().cancelSelectionAutoscroll();
1897}
1898
1899void WebPage::requestEvasionRectsAboveSelection(CompletionHandler<void(const Vector<FloatRect>&)>&& reply)
1900{
1901 RefPtr frame = m_page->focusController().focusedOrMainFrame();
1902 if (!frame)
1903 return reply({ });
1904
1905 RefPtr frameView = frame->view();
1906 if (!frameView) {
1907 reply({ });
1908 return;
1909 }
1910
1911 auto selection = frame->selection().selection();
1912 if (selection.isNone()) {
1913 reply({ });
1914 return;
1915 }
1916
1917 auto selectedRange = selection.toNormalizedRange();
1918 if (!selectedRange) {
1919 reply({ });
1920 return;
1921 }
1922
1923 if (!m_focusedElement || !m_focusedElement->renderer() || isTransparentOrFullyClipped(*protect(m_focusedElement))) {
1924 reply({ });
1925 return;
1926 }
1927
1928 float scaleFactor = pageScaleFactor();
1929 const double factorOfContentArea = 0.5;
1930 RefPtr localMainFrame = protect(m_page)->localMainFrame();
1931 if (!localMainFrame) {
1932 reply({ });
1933 return;
1934 }
1935
1936 auto unobscuredContentArea = RefPtr(localMainFrame->view())->unobscuredContentRect().area();
1937 if (unobscuredContentArea.hasOverflowed()) {
1938 reply({ });
1939 return;
1940 }
1941
1942 double contextMenuAreaLimit = factorOfContentArea * scaleFactor * unobscuredContentArea.value();
1943
1944 FloatRect selectionBoundsInRootViewCoordinates;
1945 if (selection.isRange())
1946 selectionBoundsInRootViewCoordinates = frameView->contentsToRootView(unionRect(RenderObject::absoluteTextRects(*selectedRange)));
1947 else
1948 selectionBoundsInRootViewCoordinates = frameView->contentsToRootView(protect(frame->selection())->absoluteCaretBounds());
1949
1950 auto centerOfTargetBounds = selectionBoundsInRootViewCoordinates.center();
1951 FloatPoint centerTopInRootViewCoordinates { centerOfTargetBounds.x(), selectionBoundsInRootViewCoordinates.y() };
1952
1953 auto clickableNonEditableNode = [&](const FloatPoint& locationInRootViewCoordinates) -> RefPtr<Node> {
1954 RefPtr localMainFrame = protect(m_page)->localMainFrame();
1955 if (!localMainFrame)
1956 return nullptr;
1957
1958 FloatPoint adjustedPoint;
1959 RefPtr hitNode = Ref(*localMainFrame)->nodeRespondingToClickEvents(locationInRootViewCoordinates, adjustedPoint);
1960 if (!hitNode || is<HTMLBodyElement>(hitNode) || is<Document>(hitNode) || hitNode->hasEditableStyle())
1961 return nullptr;
1962
1963 return hitNode;
1964 };
1965
1966 // This heuristic attempts to find a list of rects to avoid when showing the callout menu on iOS.
1967 // First, hit-test several points above the bounds of the selection rect in search of clickable nodes that are not editable.
1968 // Secondly, hit-test several points around the edges of the selection rect and exclude any nodes found in the first round of
1969 // hit-testing if these nodes are also reachable by moving outwards from the left, right, or bottom edges of the selection.
1970 // Additionally, exclude any hit-tested nodes that are either very large relative to the size of the root view, or completely
1971 // encompass the selection bounds. The resulting rects are the bounds of these hit-tested nodes in root view coordinates.
1972 HashSet<Ref<Node>> hitTestedNodes;
1973 Vector<FloatRect> rectsToAvoidInRootViewCoordinates;
1974 const Vector<FloatPoint, 5> offsetsForHitTesting { { -30, -50 }, { 30, -50 }, { -60, -35 }, { 60, -35 }, { 0, -20 } };
1975 for (auto offset : offsetsForHitTesting) {
1976 offset.scale(1 / scaleFactor);
1977 if (RefPtr hitNode = clickableNonEditableNode(centerTopInRootViewCoordinates + offset))
1978 hitTestedNodes.add(*hitNode);
1979 }
1980
1981 const float marginForHitTestingSurroundingNodes = 80 / scaleFactor;
1982 Vector<FloatPoint, 3> exclusionHitTestLocations {
1983 { selectionBoundsInRootViewCoordinates.x() - marginForHitTestingSurroundingNodes, centerOfTargetBounds.y() },
1984 { centerOfTargetBounds.x(), selectionBoundsInRootViewCoordinates.maxY() + marginForHitTestingSurroundingNodes },
1985 { selectionBoundsInRootViewCoordinates.maxX() + marginForHitTestingSurroundingNodes, centerOfTargetBounds.y() }
1986 };
1987
1988 for (auto& location : exclusionHitTestLocations) {
1989 if (RefPtr nodeToExclude = clickableNonEditableNode(location))
1990 hitTestedNodes.remove(*nodeToExclude);
1991 }
1992
1993 for (auto& node : hitTestedNodes) {
1994 RefPtr frameView = node->document().view();
1995 CheckedPtr renderer = node->renderer();
1996 if (!renderer || !frameView)
1997 continue;
1998
1999 auto bounds = frameView->contentsToRootView(renderer->absoluteBoundingBoxRect());
2000 auto area = bounds.area();
2001 if (area.hasOverflowed() || area > contextMenuAreaLimit)
2002 continue;
2003
2004 if (bounds.contains(enclosingIntRect(selectionBoundsInRootViewCoordinates)))
2005 continue;
2006
2007 rectsToAvoidInRootViewCoordinates.append(WTF::move(bounds));
2008 }
2009
2010 reply(WTF::move(rectsToAvoidInRootViewCoordinates));
2011}
2012
2013void WebPage::getRectsForGranularityWithSelectionOffset(WebCore::TextGranularity granularity, int32_t offset, CompletionHandler<void(const Vector<WebCore::SelectionGeometry>&)>&& completionHandler)
2014{
2015 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2016 if (!frame)
2017 return completionHandler({ });
2018
2019 auto selection = m_internals->storedSelectionForAccessibility.isNone() ? frame->selection().selection() : m_internals->storedSelectionForAccessibility;
2020 auto position = visiblePositionForPositionWithOffset(selection.visibleStart(), offset);
2021 auto direction = offset < 0 ? SelectionDirection::Backward : SelectionDirection::Forward;
2022 auto range = enclosingTextUnitOfGranularity(position, granularity, direction);
2023 if (!range || range->collapsed()) {
2024 completionHandler({ });
2025 return;
2026 }
2027
2028 auto selectionGeometries = RenderObject::collectSelectionGeometriesWithoutUnionInteriorLines(*range);
2029 RefPtr view = frame->view();
2030 convertContentToRootView(*view, selectionGeometries);
2031 completionHandler(selectionGeometries);
2032}
2033
2034void WebPage::storeSelectionForAccessibility(bool shouldStore)
2035{
2036 if (!shouldStore)
2037 m_internals->storedSelectionForAccessibility = VisibleSelection();
2038 else {
2039 if (RefPtr frame = m_page->focusController().focusedOrMainFrame())
2040 m_internals->storedSelectionForAccessibility = frame->selection().selection();
2041 }
2042}
2043
2044static std::optional<SimpleRange> rangeNearPositionMatchesText(const VisiblePosition& position, const String& matchText, const VisibleSelection& selection)
2045{
2046 auto liveRange = selection.firstRange();
2047 if (!liveRange)
2048 return std::nullopt;
2049 SimpleRange range { *liveRange };
2050 auto boundaryPoint = makeBoundaryPoint(position);
2051 if (!boundaryPoint)
2052 return std::nullopt;
2053 return findClosestPlainText(range, matchText, { }, characterCount({ range.start, *boundaryPoint }, TextIteratorBehavior::EmitsCharactersBetweenAllVisiblePositions));
2054}
2055
2056void WebPage::getRectsAtSelectionOffsetWithText(int32_t offset, const String& text, CompletionHandler<void(const Vector<WebCore::SelectionGeometry>&)>&& completionHandler)
2057{
2058 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2059 if (!frame)
2060 return completionHandler({ });
2061 auto& selection = m_internals->storedSelectionForAccessibility.isNone() ? frame->selection().selection() : m_internals->storedSelectionForAccessibility;
2062 auto startPosition = visiblePositionForPositionWithOffset(selection.visibleStart(), offset);
2063 auto range = makeSimpleRange(startPosition, visiblePositionForPositionWithOffset(startPosition, text.length()));
2064 if (!range || range->collapsed()) {
2065 completionHandler({ });
2066 return;
2067 }
2068
2069 if (plainTextForDisplay(*range) != text) {
2070 // Try to search for a range which is the closest to the position within the selection range that matches the passed in text.
2071 if (auto wordRange = rangeNearPositionMatchesText(startPosition, text, selection)) {
2072 if (!wordRange->collapsed())
2073 range = *wordRange;
2074 }
2075 }
2076
2077 auto selectionGeometries = RenderObject::collectSelectionGeometriesWithoutUnionInteriorLines(*range);
2078 RefPtr view = frame->view();
2079 convertContentToRootView(*view, selectionGeometries);
2080 completionHandler(selectionGeometries);
2081}
2082
2083void WebPage::selectPositionAtBoundaryWithDirection(const WebCore::IntPoint& point, WebCore::TextGranularity granularity, WebCore::SelectionDirection direction, bool isInteractingWithFocusedElement, CompletionHandler<void()>&& completionHandler)
2084{
2085 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2086 if (!frame)
2087 return completionHandler();
2088
2089 VisiblePosition position = visiblePositionInFocusedNodeForPoint(*frame, point, isInteractingWithFocusedElement);
2090
2091 if (position.isNotNull()) {
2092 position = positionOfNextBoundaryOfGranularity(position, granularity, direction);
2093 if (position.isNotNull())
2094 protect(frame->selection())->setSelectedRange(makeSimpleRange(position), Affinity::Upstream, WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
2095 }
2096 completionHandler();
2097}
2098
2099void WebPage::moveSelectionAtBoundaryWithDirection(WebCore::TextGranularity granularity, WebCore::SelectionDirection direction, CompletionHandler<void()>&& completionHandler)
2100{
2101 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2102 if (!frame)
2103 return completionHandler();
2104
2105 if (!frame->selection().selection().isNone()) {
2106 bool isForward = (direction == SelectionDirection::Forward || direction == SelectionDirection::Right);
2107 VisiblePosition position = (isForward) ? frame->selection().selection().visibleEnd() : frame->selection().selection().visibleStart();
2108 position = positionOfNextBoundaryOfGranularity(position, granularity, direction);
2109 if (position.isNotNull())
2110 protect(frame->selection())->setSelectedRange(makeSimpleRange(position), isForward? Affinity::Upstream : Affinity::Downstream, WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
2111 }
2112 completionHandler();
2113}
2114
2115void WebPage::beginSelectionInDirection(WebCore::SelectionDirection direction, CompletionHandler<void(bool)>&& completionHandler)
2116{
2117 m_selectionAnchor = direction == SelectionDirection::Left ? SelectionAnchor::Start : SelectionAnchor::End;
2118 completionHandler(m_selectionAnchor == SelectionAnchor::Start);
2119}
2120
2121void WebPage::didReleaseAllTouchPoints()
2122{
2123 m_hasAnyActiveTouchPoints = false;
2124 removeTextInteractionSources(TextInteractionSource::Touch);
2125}
2126
2127#if ENABLE(REVEAL)(defined 1 && 1)
2128RevealItem WebPage::revealItemForCurrentSelection()
2129{
2130 RefPtr frame = protect(*m_page)->focusController().focusedOrMainFrame();
2131 if (!frame)
2132 return { };
2133
2134 auto selection = frame->selection().selection();
2135 if (!selection.isNone()) {
2136 std::optional<SimpleRange> fullCharacterRange;
2137 if (selection.isRange()) {
2138 auto selectionStart = selection.visibleStart();
2139 auto selectionEnd = selection.visibleEnd();
2140
2141 // As context, we are going to use the surrounding paragraphs of text.
2142 fullCharacterRange = makeSimpleRange(startOfParagraph(selectionStart), endOfParagraph(selectionEnd));
2143 if (!fullCharacterRange)
2144 fullCharacterRange = makeSimpleRange(selectionStart, selectionEnd);
2145
2146 if (fullCharacterRange) {
2147 auto selectionRange = NSMakeRange(characterCount(*makeSimpleRange(fullCharacterRange->start, selectionStart)), characterCount(*makeSimpleRange(selectionStart, selectionEnd)));
2148 String itemString = plainText(*fullCharacterRange);
2149 return { itemString, selectionRange };
2150 }
2151 }
2152 }
2153 return { };
2154}
2155
2156void WebPage::requestRVItemInCurrentSelectedRange(CompletionHandler<void(const WebKit::RevealItem&)>&& completionHandler)
2157{
2158 completionHandler(RevealItem(revealItemForCurrentSelection()));
2159}
2160
2161void WebPage::prepareSelectionForContextMenuWithLocationInView(IntPoint point, CompletionHandler<void(bool, const RevealItem&)>&& completionHandler)
2162{
2163 constexpr OptionSet hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::AllowVisibleChildFrameContentOnly };
2164 RefPtr localMainFrame = protect(m_page)->localMainFrame();
2165 if (!localMainFrame)
2166 return completionHandler(false, { });
2167 Ref frame = *localMainFrame;
2168 auto result = frame->eventHandler().hitTestResultAtPoint(point, hitType);
2169 RefPtr hitNode = result.innerNonSharedNode();
2170 if (!hitNode)
2171 return completionHandler(false, { });
2172
2173 if (RefPtr view = frame->view()) {
2174 auto pointInContents = view->rootViewToContents(point);
2175
2176 if (protect(frame->selection())->contains(pointInContents))
2177 return completionHandler(true, revealItemForCurrentSelection());
2178 }
2179
2180 auto sendEditorStateAndCallCompletionHandler = [this, protectedThis = Ref { *this }, completionHandler = WTF::move(completionHandler)](RevealItem&& item) mutable {
2181 layoutIfNeeded();
2182 sendEditorStateUpdate();
2183 completionHandler(true, WTF::move(item));
2184 };
2185
2186 if (is<HTMLImageElement>(*hitNode) && hitNode->hasEditableStyle()) {
2187 auto range = makeRangeSelectingNode(*hitNode);
2188 if (range && protect(frame->selection())->setSelectedRange(range, Affinity::Upstream, FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes))
2189 return sendEditorStateAndCallCompletionHandler({ });
2190 }
2191
2192 frame->eventHandler().selectClosestContextualWordOrLinkFromHitTestResult(result, DontAppendTrailingWhitespace);
2193 sendEditorStateAndCallCompletionHandler(revealItemForCurrentSelection());
2194}
2195#endif
2196
2197void WebPage::replaceSelectedText(const String& oldText, const String& newText)
2198{
2199 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2200 if (!frame)
2201 return;
2202
2203 auto wordRange = frame->selection().isCaret() ? wordRangeFromPosition(frame->selection().selection().start()) : frame->selection().selection().toNormalizedRange();
2204 if (plainTextForContext(wordRange) != oldText)
2205 return;
2206
2207 IgnoreSelectionChangeForScope ignoreSelectionChanges { *frame };
2208 protect(frame->selection())->setSelectedRange(wordRange, Affinity::Upstream, WebCore::FrameSelection::ShouldCloseTyping::Yes);
2209 protect(frame->editor())->insertText(newText, 0);
2210}
2211
2212void WebPage::replaceDictatedText(const String& oldText, const String& newText)
2213{
2214 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2215 if (!frame)
2216 return;
2217
2218 if (frame->selection().isNone())
2219 return;
2220
2221 if (frame->selection().isRange()) {
2222 protect(frame->editor())->deleteSelectionWithSmartDelete(false);
2223 return;
2224 }
2225
2226 auto range = findDictatedTextRangeBeforeCursor(*frame, oldText);
2227 if (!range)
2228 return;
2229
2230 // We don't want to notify the client that the selection has changed until we are done inserting the new text.
2231 IgnoreSelectionChangeForScope ignoreSelectionChanges { *frame };
2232 protect(frame->selection())->setSelectedRange(*range, Affinity::Upstream, WebCore::FrameSelection::ShouldCloseTyping::Yes);
2233 protect(frame->editor())->insertText(newText, 0);
2234}
2235
2236void WebPage::willInsertFinalDictationResult()
2237{
2238 RefPtr frame = protect(*m_page)->focusController().focusedOrMainFrame();
2239 if (!frame)
2240 return;
2241
2242 if (frame->selection().isNone())
2243 return;
2244
2245 m_ignoreSelectionChangeScopeForDictation = makeUnique<IgnoreSelectionChangeForScope>(*frame);
2246}
2247
2248bool WebPage::shouldRemoveDictationAlternativesAfterEditing() const
2249{
2250 return !m_ignoreSelectionChangeScopeForDictation;
2251}
2252
2253void WebPage::didInsertFinalDictationResult()
2254{
2255 m_ignoreSelectionChangeScopeForDictation = nullptr;
2256 scheduleFullEditorStateUpdate();
2257}
2258
2259void WebPage::requestAutocorrectionData(const String& textForAutocorrection, CompletionHandler<void(WebAutocorrectionData)>&& reply)
2260{
2261 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2262 if (!frame)
2263 return reply({ });
2264
2265 if (!frame->selection().isCaret()) {
2266 reply({ });
2267 return;
2268 }
2269
2270 auto range = wordRangeFromPosition(frame->selection().selection().visibleStart());
2271 if (!range) {
2272 reply({ });
2273 return;
2274 }
2275
2276 auto textForRange = plainTextForContext(range);
2277 const unsigned maxSearchAttempts = 5;
2278 for (size_t i = 0; i < maxSearchAttempts && textForRange != textForAutocorrection; ++i) {
2279 auto position = makeDeprecatedLegacyPosition(range->start).previous();
2280 if (position.isNull() || position == makeDeprecatedLegacyPosition(range->start))
2281 break;
2282 auto startRange = wordRangeFromPosition(position);
2283 if (!startRange)
2284 continue;
2285 range = { { startRange->start, range->end } };
2286 textForRange = plainTextForContext(range);
2287 }
2288
2289 Vector<SelectionGeometry> selectionGeometries;
2290 if (textForRange == textForAutocorrection)
2291 selectionGeometries = RenderObject::collectSelectionGeometries(*range).geometries;
2292
2293 auto rootViewSelectionRects = selectionGeometries.map([&](const auto& selectionGeometry) -> FloatRect {
2294 return frame->view()->contentsToRootView(selectionGeometry.rect());
2295 });
2296
2297 bool multipleFonts = false;
2298 CTFontRef font = nilnullptr;
2299 if (auto coreFont = protect(frame->editor())->fontForSelection(multipleFonts))
2300 font = coreFont->ctFont();
2301
2302 reply({ WTF::move(rootViewSelectionRects) , (__bridge UIFont *)font });
2303}
2304
2305void WebPage::applyAutocorrection(const String& correction, const String& originalText, bool isCandidate, CompletionHandler<void(const String&)>&& callback)
2306{
2307 callback(applyAutocorrectionInternal(correction, originalText, isCandidate) ? correction : String());
2308}
2309
2310Seconds WebPage::eventThrottlingDelay() const
2311{
2312 auto behaviorOverride = m_page->eventThrottlingBehaviorOverride();
2313 if (behaviorOverride) {
2314 switch (behaviorOverride.value()) {
2315 case EventThrottlingBehavior::Responsive:
2316 return 0_s;
2317 case EventThrottlingBehavior::Unresponsive:
2318 return 1_s;
2319 }
2320 }
2321
2322 if (m_isInStableState || m_estimatedLatency <= Seconds(1.0 / 60))
2323 return 0_s;
2324
2325 return std::min(m_estimatedLatency * 2, 1_s);
2326}
2327
2328void WebPage::syncApplyAutocorrection(const String& correction, const String& originalText, bool isCandidate, CompletionHandler<void(bool)>&& reply)
2329{
2330 reply(applyAutocorrectionInternal(correction, originalText, isCandidate));
2331}
2332
2333bool WebPage::applyAutocorrectionInternal(const String& correction, const String& originalText, bool isCandidate)
2334{
2335 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2336 if (!frame)
2337 return false;
2338
2339 if (!frame->selection().isCaretOrRange())
2340 return false;
2341
2342 if (correction == originalText)
2343 return false;
2344
2345 std::optional<SimpleRange> range;
2346 String textForRange;
2347 auto originalTextWithFoldedQuoteMarks = foldQuoteMarks(originalText);
2348
2349 if (frame->selection().isCaret()) {
2350 auto position = frame->selection().selection().visibleStart();
2351 range = wordRangeFromPosition(position);
2352 textForRange = plainTextForContext(range);
2353
2354 // If 'originalText' is not the same as 'textForRange' we need to move 'range'
2355 // forward such that it matches the original selection as much as possible.
2356 if (foldQuoteMarks(textForRange) != originalTextWithFoldedQuoteMarks) {
2357 // Search for the original text near the selection caret.
2358 auto characterCount = numGraphemeClusters(originalText);
2359 if (!characterCount) {
2360 textForRange = emptyString();
2361 range = makeSimpleRange(position);
2362 } else if (auto searchRange = rangeExpandedAroundPositionByCharacters(position, characterCount)) {
2363 if (auto foundRange = findPlainText(*searchRange, originalTextWithFoldedQuoteMarks, { FindOption::DoNotSetSelection, FindOption::DoNotRevealSelection }); !foundRange.collapsed()) {
2364 textForRange = plainTextForContext(foundRange);
2365 range = foundRange;
2366 }
2367 }
2368 } else if (textForRange.isEmpty() && range && !range->collapsed()) {
2369 // If 'range' does not include any text but it is not collapsed, we need to set
2370 // 'range' to match the selection. Otherwise non-text nodes will be removed.
2371 range = makeSimpleRange(position);
2372 if (!range)
2373 return false;
2374 }
2375 } else {
2376 // Range selection.
2377 range = frame->selection().selection().toNormalizedRange();
2378 if (!range)
2379 return false;
2380
2381 textForRange = plainTextForContext(range);
2382 }
2383
2384 if (foldQuoteMarks(textForRange) != originalTextWithFoldedQuoteMarks)
2385 return false;
2386
2387 // Correctly determine affinity, using logic currently only present in VisiblePosition
2388 auto affinity = Affinity::Downstream;
2389 if (range && range->collapsed())
2390 affinity = VisiblePosition(makeDeprecatedLegacyPosition(range->start), Affinity::Upstream).affinity();
2391
2392 protect(frame->selection())->setSelectedRange(range, affinity, WebCore::FrameSelection::ShouldCloseTyping::Yes);
2393 Ref editor = frame->editor();
2394 if (correction.length()) {
2395 editor->insertText(correction, 0, originalText.isEmpty() ? TextEventInputKeyboard : TextEventInputAutocompletion);
2396 if (isCandidate)
2397 adjustCandidateAutocorrectionInFrame(correction, *frame);
2398 } else if (originalText.length())
2399 editor->deleteWithDirection(SelectionDirection::Backward, TextGranularity::CharacterGranularity, false, true);
2400 return true;
2401}
2402
2403WebAutocorrectionContext WebPage::autocorrectionContext()
2404{
2405 if (!m_focusedElement)
2406 return { };
2407
2408 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2409 if (!frame)
2410 return { };
2411
2412 if (!frame->selection().selection().hasEditableStyle())
2413 return { };
2414
2415 String contextBefore;
2416 String markedText;
2417 String selectedText;
2418 String contextAfter;
2419 EditingRange selectedRangeInMarkedText;
2420
2421 VisiblePosition startPosition = frame->selection().selection().start();
2422 VisiblePosition endPosition = frame->selection().selection().end();
2423 const unsigned minContextWordCount = 10;
2424 const unsigned minContextLength = 40;
2425 const unsigned maxContextLength = 100;
2426
2427 if (frame->selection().isRange())
2428 selectedText = plainTextForContext(frame->selection().selection().toNormalizedRange());
2429
2430 if (auto compositionRange = protect(frame->editor())->compositionRange()) {
2431 auto markedTextBefore = plainTextForContext(makeSimpleRange(compositionRange->start, startPosition));
2432 auto markedTextAfter = plainTextForContext(makeSimpleRange(endPosition, compositionRange->end));
2433 markedText = makeString(markedTextBefore, selectedText, markedTextAfter);
2434 if (!markedText.isEmpty()) {
2435 selectedRangeInMarkedText.location = markedTextBefore.length();
2436 selectedRangeInMarkedText.length = selectedText.length();
2437 }
2438 } else {
2439 auto firstPositionInEditableContent = startOfEditableContent(startPosition);
2440 if (startPosition != firstPositionInEditableContent) {
2441 VisiblePosition contextStartPosition = startPosition;
2442 VisiblePosition previousPosition;
2443 unsigned totalContextLength = 0;
2444 for (unsigned i = 0; i < minContextWordCount; ++i) {
2445 if (contextBefore.length() >= minContextLength)
2446 break;
2447 previousPosition = startOfWord(positionOfNextBoundaryOfGranularity(contextStartPosition, TextGranularity::WordGranularity, SelectionDirection::Backward));
2448 if (previousPosition.isNull())
2449 break;
2450 String currentWord = plainTextForContext(makeSimpleRange(previousPosition, contextStartPosition));
2451 totalContextLength += currentWord.length();
2452 if (totalContextLength >= maxContextLength)
2453 break;
2454 contextStartPosition = previousPosition;
2455 }
2456 VisiblePosition sentenceContextStartPosition = startOfSentence(startPosition);
2457 if (sentenceContextStartPosition.isNotNull() && sentenceContextStartPosition < contextStartPosition)
2458 contextStartPosition = sentenceContextStartPosition;
2459 if (contextStartPosition.isNotNull() && contextStartPosition < startPosition) {
2460 contextBefore = plainTextForContext(makeSimpleRange(contextStartPosition, startPosition));
2461 if (atBoundaryOfGranularity(contextStartPosition, TextGranularity::ParagraphGranularity, SelectionDirection::Backward) && firstPositionInEditableContent != contextStartPosition)
2462 contextBefore = makeString("\n "_s, contextBefore);
2463 }
2464 }
2465
2466 if (endPosition != endOfEditableContent(endPosition)) {
2467 VisiblePosition nextPosition = endOfSentence(endPosition);
2468 if (nextPosition.isNotNull() && nextPosition > endPosition)
2469 contextAfter = plainTextForContext(makeSimpleRange(endPosition, nextPosition));
2470 }
2471 }
2472
2473 WebAutocorrectionContext correction;
2474 correction.contextBefore = WTF::move(contextBefore);
2475 correction.markedText = WTF::move(markedText);
2476 correction.selectedText = WTF::move(selectedText);
2477 correction.contextAfter = WTF::move(contextAfter);
2478 correction.selectedRangeInMarkedText = WTF::move(selectedRangeInMarkedText);
2479 return correction;
2480}
2481
2482void WebPage::preemptivelySendAutocorrectionContext()
2483{
2484 send(Messages::WebPageProxy::HandleAutocorrectionContext(autocorrectionContext()));
2485}
2486
2487void WebPage::handleAutocorrectionContextRequest()
2488{
2489 send(Messages::WebPageProxy::HandleAutocorrectionContext(autocorrectionContext()));
2490}
2491
2492void WebPage::prepareToRunModalJavaScriptDialog()
2493{
2494 // When a modal dialog is presented while an editable element is focused, UIKit will attempt to request a
2495 // WebAutocorrectionContext, which triggers synchronous IPC back to the web process, resulting in deadlock.
2496 // To avoid this deadlock, we preemptively compute and send autocorrection context data to the UI process,
2497 // such that the UI process can immediately respond to UIKit without synchronous IPC to the web process.
2498 preemptivelySendAutocorrectionContext();
2499}
2500
2501static inline bool isObscuredElement(Element& element)
2502{
2503 RefPtr mainFrameDocument = protect(element.document())->mainFrameDocument();
2504 if (!mainFrameDocument) {
2505 LOG_ONCE(SiteIsolation, "Unable to properly perform isObscuredElement() without access to the main frame document ")((void)0);
2506 return false;
2507 }
2508
2509 auto elementRectInMainFrame = element.boundingBoxInRootViewCoordinates();
2510
2511 constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::AllowChildFrameContent, HitTestRequest::Type::DisallowUserAgentShadowContent, HitTestRequest::Type::IgnoreClipping };
2512 HitTestResult result(elementRectInMainFrame.center());
2513
2514 mainFrameDocument->hitTest(hitType, result);
2515 result.setToNonUserAgentShadowAncestor();
2516
2517 if (result.targetElement() == &element)
2518 return false;
2519
2520 return true;
2521}
2522
2523void WebPage::startInteractionWithElementContextOrPosition(std::optional<WebCore::ElementContext>&& elementContext, WebCore::IntPoint&& point)
2524{
2525 if (elementContext) {
2526 m_interactionNode = elementForContext(*elementContext);
2527 if (m_interactionNode)
2528 return;
2529 }
2530
2531 FloatPoint adjustedPoint;
2532 if (RefPtr localMainFrame = protect(m_page)->localMainFrame())
2533 m_interactionNode = localMainFrame->nodeRespondingToInteraction(point, adjustedPoint);
2534}
2535
2536void WebPage::stopInteraction()
2537{
2538 m_interactionNode = nullptr;
2539}
2540
2541static void handleAnimationActions(Element& element, uint32_t action)
2542{
2543#if ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)(defined 1 && 1)
2544 if (static_cast<SheetAction>(action) == SheetAction::PlayAnimation) {
2545 if (RefPtr imageElement = dynamicDowncast<HTMLImageElement>(element))
2546 imageElement->setAllowsAnimation(true);
2547 } else if (static_cast<SheetAction>(action) == SheetAction::PauseAnimation) {
2548 if (RefPtr imageElement = dynamicDowncast<HTMLImageElement>(element))
2549 imageElement->setAllowsAnimation(false);
2550 }
2551#endif // ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)
2552}
2553
2554void WebPage::performActionOnElement(uint32_t action, const String& authorizationToken, CompletionHandler<void()>&& completionHandler)
2555{
2556 CompletionHandlerCallingScope callCompletionHandler(WTF::move(completionHandler));
2557
2558 RefPtr element = dynamicDowncast<HTMLElement>(m_interactionNode);
2559 if (!element || !element->renderer())
2560 return;
2561
2562 if (static_cast<SheetAction>(action) == SheetAction::Copy) {
2563 Ref interactionNodeEditor = protect(m_interactionNode->document())->editor();
2564 Ref elementDocument = element->document();
2565 if (is<RenderImage>(*element->renderer())) {
2566 URL urlToCopy;
2567 String titleToCopy;
2568 if (RefPtr linkElement = containingLinkAnchorElement(*element)) {
2569 if (auto url = linkElement->href(); !url.isEmpty() && !url.protocolIsJavaScript()) {
2570 urlToCopy = url;
2571 titleToCopy = linkElement->attributeWithoutSynchronization(HTMLNames::titleAttr);
2572 if (!titleToCopy.length())
2573 titleToCopy = linkElement->textContent();
2574 titleToCopy = titleToCopy.trim(isASCIIWhitespace);
2575 }
2576 }
2577 interactionNodeEditor->writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(elementDocument->pageID())), *element, urlToCopy, titleToCopy);
2578 } else if (element->isLink())
2579 interactionNodeEditor->copyURL(elementDocument->completeURL(element->attributeWithoutSynchronization(HTMLNames::hrefAttr)), element->textContent());
2580#if ENABLE(ATTACHMENT_ELEMENT)(defined 1 && 1)
2581 else if (auto attachmentInfo = protect(elementDocument->editor())->promisedAttachmentInfo(*element))
2582 send(Messages::WebPageProxy::WritePromisedAttachmentToPasteboard(WTF::move(attachmentInfo), authorizationToken));
2583#endif
2584 } else if (static_cast<SheetAction>(action) == SheetAction::SaveImage) {
2585 CheckedPtr renderImage = dynamicDowncast<RenderImage>(*element->renderer());
2586 if (!renderImage)
2587 return;
2588 auto* cachedImage = renderImage->cachedImage();
Local variable 'cachedImage' is uncounted and unsafe
2589 if (!cachedImage)
2590 return;
2591 RefPtr<FragmentedSharedBuffer> buffer = cachedImage->resourceBuffer();
2592 if (!buffer)
2593 return;
2594 std::optional<SharedMemory::Handle> handle;
2595 {
2596 auto sharedMemoryBuffer = SharedMemory::copyBuffer(*buffer);
2597 if (!sharedMemoryBuffer)
2598 return;
2599 handle = sharedMemoryBuffer->createHandle(SharedMemory::Protection::ReadOnly);
2600 }
2601 if (!handle)
2602 return;
2603 send(Messages::WebPageProxy::SaveImageToLibrary(WTF::move(*handle), authorizationToken));
2604 }
2605#if ENABLE(SPATIAL_IMAGE_DETECTION)(defined ENABLE_SPATIAL_IMAGE_DETECTION && ENABLE_SPATIAL_IMAGE_DETECTION
)
2606 else if (static_cast<SheetAction>(action) == SheetAction::ViewSpatial)
2607 element->webkitRequestFullscreen();
2608#endif
2609
2610 handleAnimationActions(*element, action);
2611}
2612
2613void WebPage::performActionOnElements(uint32_t action, const Vector<WebCore::ElementContext>& elements)
2614{
2615 for (const auto& elementContext : elements) {
2616 if (RefPtr element = elementForContext(elementContext))
2617 handleAnimationActions(*element, action);
2618 }
2619}
2620
2621static inline RefPtr<Element> nextAssistableElement(Node* startNode, Page& page, bool isForward)
2622{
2623 RefPtr nextElement = dynamicDowncast<Element>(startNode);
2624 if (!nextElement)
2625 return nullptr;
2626
2627 CheckedRef focusController { page.focusController() };
2628 do {
2629 auto result = isForward ? focusController->nextFocusableElement(*nextElement) : focusController->previousFocusableElement(*nextElement);
2630 if (!result.element && result.continuedSearchInRemoteFrame == ContinuedSearchInRemoteFrame::Yes)
2631 RELEASE_LOG(SiteIsolation, "Crossing site isolation process barrier searching for `nextAssistableElement` is not yet supported")clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2LogSiteIsolation
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("Crossing site isolation process barrier searching for `nextAssistableElement` is not yet supported"
), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG40"
) = "Crossing site isolation process barrier searching for `nextAssistableElement` is not yet supported"
; uint8_t _Alignas(16) __attribute__((uninitialized)) _os_fmt_buf
[__builtin_os_log_format_buffer_size("Crossing site isolation process barrier searching for `nextAssistableElement` is not yet supported"
)]; _os_log_impl(&__dso_handle, _log_tmp, _type_tmp, _os_fmt_str
, (uint8_t *)__builtin_os_log_format(_os_fmt_buf, "Crossing site isolation process barrier searching for `nextAssistableElement` is not yet supported"
), (uint32_t)sizeof(_os_fmt_buf)) clang diagnostic pop ; }); }
}) clang diagnostic pop
;
2632
2633 nextElement = result.element;
2634 } while (nextElement && (!WebPage::isAssistableElement(*nextElement) || isObscuredElement(*nextElement)));
2635
2636 return nextElement;
2637}
2638
2639void WebPage::focusNextFocusedElement(bool isForward, CompletionHandler<void()>&& completionHandler)
2640{
2641 auto nextElement = nextAssistableElement(m_focusedElement.get(), *protect(m_page), isForward);
2642 m_userIsInteracting = true;
2643 if (nextElement)
2644 nextElement->focus();
2645 m_userIsInteracting = false;
2646 completionHandler();
2647}
2648
2649std::optional<FocusedElementInformation> WebPage::focusedElementInformation()
2650{
2651 Ref page = *m_page;
2652 RefPtr focusedOrMainFrame = page->focusController().focusedOrMainFrame();
2653 if (!focusedOrMainFrame)
2654 return std::nullopt;
2655 RefPtr<Document> document = focusedOrMainFrame->document();
2656 if (!document || !document->view())
2657 return std::nullopt;
2658
2659 auto focusedElement = m_focusedElement.copyRef();
2660 layoutIfNeeded();
2661
2662 // Layout may have detached the document or caused a change of focus.
2663 if (!document->view() || focusedElement != m_focusedElement)
2664 return std::nullopt;
2665
2666 scheduleFullEditorStateUpdate();
2667
2668 FocusedElementInformation information;
2669
2670 if (RefPtr webFrame = WebProcess::singleton().webFrame(focusedOrMainFrame->frameID()))
2671 information.frame = webFrame->info(WithCertificateInfo::Yes);
2672
2673 information.lastInteractionLocation = flooredIntPoint(m_lastInteractionLocation);
2674 if (auto elementContext = contextForElement(*focusedElement))
2675 information.elementContext = WTF::move(*elementContext);
2676
2677 if (CheckedPtr renderer = focusedElement->renderer()) {
2678 information.interactionRect = rootViewInteractionBounds(*focusedElement);
2679 information.nodeFontSize = protect(renderer->style())->fontDescription().computedSize();
2680
2681 bool inFixed = false;
2682 renderer->localToContainerPoint(FloatPoint(), nullptr, UseTransforms, &inFixed);
2683 information.insideFixedPosition = inFixed;
2684 information.isRTL = renderer->writingMode().isBidiRTL();
2685
2686 if (RefPtr scrollingCoordinator = this->scrollingCoordinator())
2687 information.containerScrollingNodeID = scrollingCoordinator->scrollableContainerNodeID(*renderer);
2688 } else
2689 information.interactionRect = { };
2690
2691 RefPtr htmlElement = dynamicDowncast<HTMLElement>(focusedElement);
2692 if (htmlElement)
2693 information.isSpellCheckingEnabled = htmlElement->spellcheck();
2694
2695 information.isWritingSuggestionsEnabled = focusedElement->isWritingSuggestionsEnabled();
2696
2697 if (RefPtr formControlElement = dynamicDowncast<HTMLFormControlElement>(focusedElement))
2698 information.isFocusingWithValidationMessage = formControlElement->isFocusingWithValidationMessage();
2699
2700 information.minimumScaleFactor = minimumPageScaleFactor();
2701 information.maximumScaleFactor = maximumPageScaleFactor();
2702 information.maximumScaleFactorIgnoringAlwaysScalable = maximumPageScaleFactorIgnoringAlwaysScalable();
2703 information.allowsUserScaling = m_viewportConfiguration.allowsUserScaling();
2704 information.allowsUserScalingIgnoringAlwaysScalable = m_viewportConfiguration.allowsUserScalingIgnoringAlwaysScalable();
2705 if (auto nextElement = nextAssistableElement(focusedElement.get(), page, true)) {
2706 information.nextNodeRect = rootViewBounds(*nextElement);
2707 information.hasNextNode = true;
2708 }
2709 if (auto previousElement = nextAssistableElement(focusedElement.get(), page, false)) {
2710 information.previousNodeRect = rootViewBounds(*previousElement);
2711 information.hasPreviousNode = true;
2712 }
2713 information.identifier = m_internals->lastFocusedElementInformationIdentifier.increment();
2714
2715 if (htmlElement) {
2716 if (auto labels = htmlElement->labels()) {
2717 Vector<Ref<Element>> associatedLabels;
2718 for (unsigned index = 0; index < labels->length(); ++index) {
2719 if (RefPtr label = dynamicDowncast<Element>(labels->item(index)); label && label->renderer())
2720 associatedLabels.append(*label);
2721 }
2722 for (auto& labelElement : associatedLabels) {
2723 auto text = labelElement->innerText();
2724 if (!text.isEmpty()) {
2725 information.label = WTF::move(text);
2726 break;
2727 }
2728 }
2729 }
2730 }
2731
2732 information.title = focusedElement->title();
2733 information.ariaLabel = focusedElement->attributeWithoutSynchronization(HTMLNames::aria_labelAttr);
2734
2735 if (RefPtr element = dynamicDowncast<HTMLSelectElement>(*focusedElement)) {
2736#if USE(UICONTEXTMENU)(defined 1 && 1)
2737 static bool selectPickerUsesMenu = linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::HasUIContextMenuInteraction);
2738#else
2739 bool selectPickerUsesMenu = false;
2740#endif
2741
2742 information.elementType = InputType::Select;
2743
2744 RefPtr<ContainerNode> parentGroup;
2745 int parentGroupID = 0;
2746 for (auto& item : element->listItems()) {
2747 if (RefPtr optionElement = dynamicDowncast<HTMLOptionElement>(item.get())) {
2748 if (parentGroup && optionElement->parentNode() != parentGroup) {
2749 parentGroupID++;
2750 parentGroup = nullptr;
2751 information.selectOptions.append(OptionItem(emptyString(), true, false, false, parentGroupID));
2752 }
2753
2754 information.selectOptions.append(OptionItem(optionElement->label(), false, optionElement->selected(), optionElement->hasAttributeWithoutSynchronization(WebCore::HTMLNames::disabledAttr), parentGroupID));
2755 } else if (RefPtr optGroupElement = dynamicDowncast<HTMLOptGroupElement>(item.get())) {
2756 if (selectPickerUsesMenu)
2757 parentGroup = optGroupElement;
2758
2759 parentGroupID++;
2760 information.selectOptions.append(OptionItem(optGroupElement->groupLabelText(), true, false, optGroupElement->hasAttributeWithoutSynchronization(WebCore::HTMLNames::disabledAttr), parentGroupID));
2761 } else if (selectPickerUsesMenu && is<HTMLHRElement>(item.get())) {
2762 parentGroupID++;
2763 parentGroup = nullptr;
2764 information.selectOptions.append(OptionItem(emptyString(), true, false, false, parentGroupID));
2765 }
2766 }
2767 information.selectedIndex = element->selectedIndex();
2768 information.isMultiSelect = element->multiple();
2769 information.usesBaseAppearancePicker = element->usesBaseAppearancePicker();
2770 } else if (RefPtr element = dynamicDowncast<HTMLTextAreaElement>(*focusedElement)) {
2771 information.autocapitalizeType = element->autocapitalizeType();
2772 information.isAutocorrect = element->shouldAutocorrect();
2773 information.elementType = InputType::TextArea;
2774 information.isReadOnly = element->isReadOnly();
2775 information.value = element->value();
2776 information.hasPlainText = !information.value.isEmpty();
2777 information.autofillFieldName = WebCore::toAutofillFieldName(element->autofillData().fieldName);
2778 information.nonAutofillCredentialType = element->autofillData().nonAutofillCredentialType;
2779 information.placeholder = element->attributeWithoutSynchronization(HTMLNames::placeholderAttr);
2780 information.inputMode = element->canonicalInputMode();
2781 information.enterKeyHint = element->canonicalEnterKeyHint();
2782 } else if (RefPtr element = dynamicDowncast<HTMLInputElement>(*focusedElement)) {
2783 RefPtr form = element->form();
2784 if (form)
2785 information.formAction = form->getURLAttribute(WebCore::HTMLNames::actionAttr).string();
2786 if (auto autofillElements = WebCore::AutofillElements::computeAutofillElements(*element)) {
2787 information.acceptsAutofilledLoginCredentials = true;
2788 information.isAutofillableUsernameField = autofillElements->username() == focusedElement;
2789 }
2790 information.representingPageURL = protect(element->document())->urlForBindings();
2791 information.autocapitalizeType = element->autocapitalizeType();
2792 information.isAutocorrect = element->shouldAutocorrect();
2793 information.placeholder = element->attributeWithoutSynchronization(HTMLNames::placeholderAttr);
2794 information.hasEverBeenPasswordField = element->hasEverBeenPasswordField();
2795 if (element->isPasswordField())
2796 information.elementType = InputType::Password;
2797 else if (element->isSearchField())
2798 information.elementType = InputType::Search;
2799 else if (element->isEmailField())
2800 information.elementType = InputType::Email;
2801 else if (element->isTelephoneField())
2802 information.elementType = InputType::Phone;
2803 else if (element->isNumberField())
2804 information.elementType = element->getAttribute(HTMLNames::patternAttr) == "\\d*"_s || element->getAttribute(HTMLNames::patternAttr) == "[0-9]*"_s ? InputType::NumberPad : InputType::Number;
2805 else if (element->isDateTimeLocalField())
2806 information.elementType = InputType::DateTimeLocal;
2807 else if (element->isDateField())
2808 information.elementType = InputType::Date;
2809 else if (element->isTimeField())
2810 information.elementType = InputType::Time;
2811 else if (element->isWeekField())
2812 information.elementType = InputType::Week;
2813 else if (element->isMonthField())
2814 information.elementType = InputType::Month;
2815 else if (element->isURLField())
2816 information.elementType = InputType::URL;
2817 else if (element->isText()) {
2818 const AtomString& pattern = element->attributeWithoutSynchronization(HTMLNames::patternAttr);
2819 if (pattern == "\\d*"_s || pattern == "[0-9]*"_s)
2820 information.elementType = InputType::NumberPad;
2821 else {
2822 information.elementType = InputType::Text;
2823 if (!information.formAction.isEmpty()
2824 && (element->getNameAttribute().contains("search"_s) || element->getIdAttribute().contains("search"_s) || element->attributeWithoutSynchronization(HTMLNames::titleAttr).contains("search"_s)))
2825 information.elementType = InputType::Search;
2826 }
2827 }
2828 else if (element->isColorControl()) {
2829 information.elementType = InputType::Color;
2830 information.colorValue = element->valueAsColor();
2831 information.supportsAlpha = element->alpha() ? WebKit::ColorControlSupportsAlpha::Yes : WebKit::ColorControlSupportsAlpha::No;
2832 information.suggestedColors = element->suggestedColors();
2833 }
2834
2835 information.isFocusingWithDataListDropdown = element->isFocusingWithDataListDropdown();
2836 information.hasSuggestions = !!element->list();
2837 information.inputMode = element->canonicalInputMode();
2838 information.enterKeyHint = element->canonicalEnterKeyHint();
2839 information.isReadOnly = element->isReadOnly();
2840 information.value = element->value();
2841 information.hasPlainText = !information.value.isEmpty();
2842 information.valueAsNumber = element->valueAsNumber();
2843 information.autofillFieldName = WebCore::toAutofillFieldName(element->autofillData().fieldName);
2844 information.nonAutofillCredentialType = element->autofillData().nonAutofillCredentialType;
2845 } else if (focusedElement->hasEditableStyle()) {
2846 information.elementType = InputType::ContentEditable;
2847 if (RefPtr focusedHTMLElement = dynamicDowncast<HTMLElement>(*focusedElement)) {
2848 information.isAutocorrect = focusedHTMLElement->shouldAutocorrect();
2849 information.autocapitalizeType = focusedHTMLElement->autocapitalizeType();
2850 information.inputMode = focusedHTMLElement->canonicalInputMode();
2851 information.enterKeyHint = focusedHTMLElement->canonicalEnterKeyHint();
2852 information.shouldSynthesizeKeyEventsForEditing = true;
2853 } else {
2854 information.isAutocorrect = true;
2855 information.autocapitalizeType = WebCore::AutocapitalizeType::Default;
2856 }
2857 information.isReadOnly = false;
2858 information.hasPlainText = hasAnyPlainText(makeRangeSelectingNodeContents(*focusedElement));
2859 }
2860
2861 Ref focusedElementDocument = focusedElement->document();
2862
2863 if (focusedElementDocument->quirks().shouldSuppressAutocorrectionAndAutocapitalizationInHiddenEditableAreas() && isTransparentOrFullyClipped(*focusedElement)) {
2864 information.autocapitalizeType = WebCore::AutocapitalizeType::None;
2865 information.isAutocorrect = false;
2866 }
2867
2868 auto& quirks = focusedElementDocument->quirks();
2869 information.shouldAvoidResizingWhenInputViewBoundsChange = quirks.shouldAvoidResizingWhenInputViewBoundsChange();
2870 information.shouldAvoidScrollingWhenFocusedContentIsVisible = quirks.shouldAvoidScrollingWhenFocusedContentIsVisible();
2871 information.shouldUseLegacySelectPopoverDismissalBehaviorInDataActivation = quirks.shouldUseLegacySelectPopoverDismissalBehaviorInDataActivation();
2872 information.shouldHideSoftTopScrollEdgeEffect = quirks.shouldHideSoftTopScrollEdgeEffectDuringFocus(*focusedElement);
2873
2874 return information;
2875}
2876
2877void WebPage::autofillLoginCredentials(const String& username, const String& password)
2878{
2879 if (RefPtr input = dynamicDowncast<HTMLInputElement>(m_focusedElement)) {
2880 if (auto autofillElements = AutofillElements::computeAutofillElements(*input))
2881 autofillElements->autofill(username, password);
2882 }
2883}
2884
2885void WebPage::setViewportConfigurationViewLayoutSize(const FloatSize& size, double layoutSizeScaleFactorFromClient, double minimumEffectiveDeviceWidth)
2886{
2887 LOG_WITH_STREAM(VisibleRects, stream << "WebPage " << m_identifier << " setViewportConfigurationViewLayoutSize " << size << " layoutSizeScaleFactorFromClient " << layoutSizeScaleFactorFromClient << " minimumEffectiveDeviceWidth " << minimumEffectiveDeviceWidth)((void)0);
2888
2889 if (!m_viewportConfiguration.isKnownToLayOutWiderThanViewport())
2890 m_viewportConfiguration.setMinimumEffectiveDeviceWidthForShrinkToFit(0);
2891
2892 if (size.isZero() && mainFramePlugInRejectsZeroViewLayoutSizeUpdates())
2893 return;
2894
2895 bool mainFramePluginOverridesViewScale = mainFramePlugInDefersScalingToViewport();
2896
2897 m_baseViewportLayoutSizeScaleFactor = [&] {
2898 if (!m_page->settings().automaticallyAdjustsViewScaleUsingMinimumEffectiveDeviceWidth())
2899 return 1.0;
2900
2901 if (!minimumEffectiveDeviceWidth)
2902 return 1.0;
2903
2904 if (minimumEffectiveDeviceWidth >= size.width())
2905 return 1.0;
2906
2907 if (mainFramePluginOverridesViewScale)
2908 return 1.0;
2909
2910 return size.width() / minimumEffectiveDeviceWidth;
2911 }();
2912
2913 double layoutSizeScaleFactor = mainFramePluginOverridesViewScale ? 1.0 : layoutSizeScaleFactorFromClient * m_baseViewportLayoutSizeScaleFactor;
2914
2915 auto previousLayoutSizeScaleFactor = m_viewportConfiguration.layoutSizeScaleFactor();
2916 if (!m_viewportConfiguration.setViewLayoutSize(size, layoutSizeScaleFactor, minimumEffectiveDeviceWidth))
2917 return;
2918
2919 auto zoomToInitialScale = ZoomToInitialScale::No;
2920 auto newInitialScale = m_viewportConfiguration.initialScale();
2921 auto currentPageScaleFactor = pageScaleFactor();
2922 if (layoutSizeScaleFactor > previousLayoutSizeScaleFactor && newInitialScale > currentPageScaleFactor)
2923 zoomToInitialScale = ZoomToInitialScale::Yes;
2924 else if (layoutSizeScaleFactor < previousLayoutSizeScaleFactor && newInitialScale < currentPageScaleFactor)
2925 zoomToInitialScale = ZoomToInitialScale::Yes;
2926
2927 viewportConfigurationChanged(zoomToInitialScale);
2928}
2929
2930void WebPage::setDeviceOrientation(IntDegrees deviceOrientation)
2931{
2932 if (deviceOrientation == m_deviceOrientation)
2933 return;
2934 m_deviceOrientation = deviceOrientation;
2935#if ENABLE(ORIENTATION_EVENTS)(defined 1 && 1)
2936 if (RefPtr localMainFrame = protect(m_page)->localMainFrame())
2937 localMainFrame->orientationChanged();
2938#endif
2939}
2940
2941void WebPage::setOverrideViewportArguments(const std::optional<WebCore::ViewportArguments>& arguments)
2942{
2943 protect(m_page)->setOverrideViewportArguments(arguments);
2944}
2945
2946void WebPage::dynamicViewportSizeUpdate(const DynamicViewportSizeUpdate& target)
2947{
2948 RefPtr localMainFrame = protect(m_page)->localMainFrame();
2949 if (!localMainFrame)
2950 return;
2951
2952 SetForScope dynamicSizeUpdateGuard(m_inDynamicSizeUpdate, true);
2953 // FIXME: this does not handle the cases where the content would change the content size or scroll position from JavaScript.
2954 // To handle those cases, we would need to redo this computation on every change until the next visible content rect update.
2955 LOG_WITH_STREAM(VisibleRects, stream << "\nWebPage::dynamicViewportSizeUpdate - viewLayoutSize " << target.viewLayoutSize << " targetUnobscuredRect " << target.unobscuredRect << " targetExposedContentRect " << target.exposedContentRect << " targetScale " << target.scale)((void)0);
2956
2957 Ref frameView = *localMainFrame->view();
2958 IntSize oldContentSize = frameView->contentsSize();
2959 float oldPageScaleFactor = m_page->pageScaleFactor();
2960 auto oldUnobscuredContentRect = frameView->unobscuredContentRect();
2961 bool wasAtInitialScale = scalesAreEssentiallyEqual(oldPageScaleFactor, m_viewportConfiguration.initialScale());
2962
2963 m_internals->dynamicSizeUpdateHistory.add(std::make_pair(oldContentSize, oldPageScaleFactor), frameView->scrollPosition());
2964
2965 RefPtr<Node> oldNodeAtCenter;
2966 double visibleHorizontalFraction = 1;
2967 float relativeHorizontalPositionInNodeAtCenter = 0;
2968 float relativeVerticalPositionInNodeAtCenter = 0;
2969 if (!shouldEnableViewportBehaviorsForResizableWindows()) {
2970 visibleHorizontalFraction = frameView->unobscuredContentSize().width() / oldContentSize.width();
2971 IntPoint unobscuredContentRectCenter = frameView->unobscuredContentRect().center();
2972
2973 HitTestResult hitTestResult = HitTestResult(unobscuredContentRectCenter);
2974
2975 if (RefPtr document = frameView->frame().document())
2976 document->hitTest(HitTestRequest(), hitTestResult);
2977
2978 if (RefPtr node = hitTestResult.innerNode()) {
2979 if (CheckedPtr renderer = node->renderer()) {
2980 Ref containingView = *node->document().frame()->view();
2981 FloatRect boundingBox = containingView->contentsToRootView(renderer->absoluteBoundingBoxRect(true));
2982 relativeHorizontalPositionInNodeAtCenter = (unobscuredContentRectCenter.x() - boundingBox.x()) / boundingBox.width();
2983 relativeVerticalPositionInNodeAtCenter = (unobscuredContentRectCenter.y() - boundingBox.y()) / boundingBox.height();
2984 oldNodeAtCenter = node;
2985 }
2986 }
2987 }
2988
2989 LOG_WITH_STREAM(VisibleRects, stream << "WebPage::dynamicViewportSizeUpdate setting view layout size to " << target.viewLayoutSize)((void)0);
2990 bool viewportChanged = m_viewportConfiguration.setIsKnownToLayOutWiderThanViewport(false);
2991 viewportChanged |= m_viewportConfiguration.setViewLayoutSize(target.viewLayoutSize, std::nullopt, target.minimumEffectiveDeviceWidth);
2992 if (viewportChanged)
2993 viewportConfigurationChanged();
2994
2995 IntSize newLayoutSize = m_viewportConfiguration.layoutSize();
2996
2997 if (setFixedLayoutSize(newLayoutSize))
2998 resetTextAutosizing();
2999
3000 setDefaultUnobscuredSize(target.maximumUnobscuredSize);
3001 setMinimumUnobscuredSize(target.minimumUnobscuredSize);
3002 setMaximumUnobscuredSize(target.maximumUnobscuredSize);
3003 protect(m_page)->setUnobscuredSafeAreaInsets(target.unobscuredSafeAreaInsets);
3004
3005 frameView->updateLayoutAndStyleIfNeededRecursive();
3006
3007 IntSize newContentSize = frameView->contentsSize();
3008
3009 bool scaleToFitContent = (!shouldEnableViewportBehaviorsForResizableWindows() || !wasAtInitialScale) && m_userHasChangedPageScaleFactor;
3010 double scale = scaleAfterViewportWidthChange(target.scale, scaleToFitContent, m_viewportConfiguration, target.unobscuredRectInScrollViewCoordinates.width(), newContentSize, oldContentSize, visibleHorizontalFraction);
3011 FloatRect newUnobscuredContentRect = target.unobscuredRect;
3012 FloatRect newExposedContentRect = target.exposedContentRect;
3013
3014 bool scaleChanged = !scalesAreEssentiallyEqual(scale, target.scale);
3015 if (scaleChanged) {
3016 // The target scale the UI is using cannot be reached by the content. We need to compute new targets based
3017 // on the viewport constraint and report everything back to the UIProcess.
3018
3019 // 1) Compute a new unobscured rect centered around the original one.
3020 double scaleDifference = target.scale / scale;
3021 double newUnobscuredRectWidth = target.unobscuredRect.width() * scaleDifference;
3022 double newUnobscuredRectHeight = target.unobscuredRect.height() * scaleDifference;
3023 double newUnobscuredRectX;
3024 double newUnobscuredRectY;
3025 if (shouldEnableViewportBehaviorsForResizableWindows()) {
3026 newUnobscuredRectX = oldUnobscuredContentRect.x();
3027 newUnobscuredRectY = oldUnobscuredContentRect.y();
3028 } else {
3029 newUnobscuredRectX = target.unobscuredRect.x() - (newUnobscuredRectWidth - target.unobscuredRect.width()) / 2;
3030 newUnobscuredRectY = target.unobscuredRect.y() - (newUnobscuredRectHeight - target.unobscuredRect.height()) / 2;
3031 }
3032 newUnobscuredContentRect = FloatRect(newUnobscuredRectX, newUnobscuredRectY, newUnobscuredRectWidth, newUnobscuredRectHeight);
3033
3034 // 2) Extend our new unobscuredRect by the obscured margins to get a new exposed rect.
3035 double obscuredTopMargin = (target.unobscuredRect.y() - target.exposedContentRect.y()) * scaleDifference;
3036 double obscuredLeftMargin = (target.unobscuredRect.x() - target.exposedContentRect.x()) * scaleDifference;
3037 double obscuredBottomMargin = (target.exposedContentRect.maxY() - target.unobscuredRect.maxY()) * scaleDifference;
3038 double obscuredRightMargin = (target.exposedContentRect.maxX() - target.unobscuredRect.maxX()) * scaleDifference;
3039 newExposedContentRect = FloatRect(newUnobscuredRectX - obscuredLeftMargin,
3040 newUnobscuredRectY - obscuredTopMargin,
3041 newUnobscuredRectWidth + obscuredLeftMargin + obscuredRightMargin,
3042 newUnobscuredRectHeight + obscuredTopMargin + obscuredBottomMargin);
3043 }
3044
3045 if (oldContentSize != newContentSize || scaleChanged) {
3046 // Snap the new unobscured rect back into the content rect.
3047 newUnobscuredContentRect.setWidth(std::min(static_cast<float>(newContentSize.width()), newUnobscuredContentRect.width()));
3048 newUnobscuredContentRect.setHeight(std::min(static_cast<float>(newContentSize.height()), newUnobscuredContentRect.height()));
3049
3050 bool positionWasRestoredFromSizeUpdateHistory = false;
3051 const auto& previousPosition = m_internals->dynamicSizeUpdateHistory.find(std::pair<IntSize, float>(newContentSize, scale));
3052 if (previousPosition != m_internals->dynamicSizeUpdateHistory.end()) {
3053 IntPoint restoredPosition = previousPosition->value;
3054 FloatPoint deltaPosition(restoredPosition.x() - newUnobscuredContentRect.x(), restoredPosition.y() - newUnobscuredContentRect.y());
3055 newUnobscuredContentRect.moveBy(deltaPosition);
3056 newExposedContentRect.moveBy(deltaPosition);
3057 positionWasRestoredFromSizeUpdateHistory = true;
3058 } else if (oldContentSize != newContentSize) {
3059 FloatPoint newRelativeContentCenter;
3060
3061 if (CheckedPtr renderer = oldNodeAtCenter ? oldNodeAtCenter->renderer() : nullptr) {
3062 Ref containingView = *oldNodeAtCenter->document().frame()->view();
3063 auto newBoundingBox = containingView->contentsToRootView(renderer->absoluteBoundingBoxRect(true));
3064 newRelativeContentCenter = FloatPoint(newBoundingBox.x() + relativeHorizontalPositionInNodeAtCenter * newBoundingBox.width(), newBoundingBox.y() + relativeVerticalPositionInNodeAtCenter * newBoundingBox.height());
3065 } else
3066 newRelativeContentCenter = relativeCenterAfterContentSizeChange(target.unobscuredRect, oldContentSize, newContentSize);
3067
3068 FloatPoint newUnobscuredContentRectCenter = newUnobscuredContentRect.center();
3069 FloatPoint positionDelta(newRelativeContentCenter.x() - newUnobscuredContentRectCenter.x(), newRelativeContentCenter.y() - newUnobscuredContentRectCenter.y());
3070 newUnobscuredContentRect.moveBy(positionDelta);
3071 newExposedContentRect.moveBy(positionDelta);
3072 }
3073
3074 // Make the top/bottom edges "sticky" within 1 pixel.
3075 if (!positionWasRestoredFromSizeUpdateHistory) {
3076 if (target.unobscuredRect.maxY() > oldContentSize.height() - 1) {
3077 float bottomVerticalPosition = newContentSize.height() - newUnobscuredContentRect.height();
3078 newUnobscuredContentRect.setY(bottomVerticalPosition);
3079 newExposedContentRect.setY(bottomVerticalPosition);
3080 }
3081 if (target.unobscuredRect.y() < 1) {
3082 newUnobscuredContentRect.setY(0);
3083 newExposedContentRect.setY(0);
3084 }
3085
3086 bool likelyResponsiveDesignViewport = newLayoutSize.width() == target.viewLayoutSize.width() && scalesAreEssentiallyEqual(scale, 1);
3087 bool contentBleedsOutsideLayoutWidth = newContentSize.width() > newLayoutSize.width();
3088 bool originalScrollPositionWasOnTheLeftEdge = target.unobscuredRect.x() <= 0;
3089 if (likelyResponsiveDesignViewport && contentBleedsOutsideLayoutWidth && originalScrollPositionWasOnTheLeftEdge) {
3090 // This is a special heuristics for "responsive" design with odd layout. It is quite common for responsive design
3091 // to have content "bleeding" outside of the minimal layout width, usually from an image or table larger than expected.
3092 // In those cases, the design usually does not adapt to the new width and remain at the newLayoutSize except for the
3093 // large boxes.
3094 // It is worth revisiting this special case as web developers get better with responsive design.
3095 newExposedContentRect.setX(0);
3096 newUnobscuredContentRect.setX(0);
3097 }
3098 }
3099
3100 float horizontalAdjustment = 0;
3101 if (newUnobscuredContentRect.maxX() > newContentSize.width())
3102 horizontalAdjustment -= newUnobscuredContentRect.maxX() - newContentSize.width();
3103 float verticalAdjustment = 0;
3104 if (newUnobscuredContentRect.maxY() > newContentSize.height())
3105 verticalAdjustment -= newUnobscuredContentRect.maxY() - newContentSize.height();
3106 if (newUnobscuredContentRect.x() < 0)
3107 horizontalAdjustment += - newUnobscuredContentRect.x();
3108 if (newUnobscuredContentRect.y() < 0)
3109 verticalAdjustment += - newUnobscuredContentRect.y();
3110
3111 FloatPoint adjustmentDelta(horizontalAdjustment, verticalAdjustment);
3112 newUnobscuredContentRect.moveBy(adjustmentDelta);
3113 newExposedContentRect.moveBy(adjustmentDelta);
3114 }
3115
3116 frameView->setScrollVelocity({ 0, 0, 0, MonotonicTime::now() });
3117
3118 IntPoint roundedUnobscuredContentRectPosition = roundedIntPoint(newUnobscuredContentRect.location());
3119 frameView->setUnobscuredContentSize(newUnobscuredContentRect.size());
3120 protect(m_drawingArea)->setExposedContentRect(newExposedContentRect);
3121
3122 scalePage(scale, roundedUnobscuredContentRectPosition);
3123
3124 frameView->updateLayoutAndStyleIfNeededRecursive();
3125
3126 // FIXME: Move settings from Frame to Frame and remove this check.
3127 auto& settings = frameView->frame().settings();
3128 LayoutRect documentRect = IntRect(frameView->scrollOrigin(), frameView->contentsSize());
3129 double heightExpansionFactor = m_disallowLayoutViewportHeightExpansionReasons.isEmpty() ? settings.layoutViewportHeightExpansionFactor() : 0;
3130 auto layoutViewportSize = LocalFrameView::expandedLayoutViewportSize(frameView->baseLayoutViewportSize(), LayoutSize(documentRect.size()), heightExpansionFactor);
3131 LayoutRect layoutViewportRect = LocalFrameView::computeUpdatedLayoutViewportRect(frameView->layoutViewportRect(), documentRect, LayoutSize(newUnobscuredContentRect.size()), LayoutRect(newUnobscuredContentRect), layoutViewportSize, frameView->minStableLayoutViewportOrigin(), frameView->maxStableLayoutViewportOrigin(), LayoutViewportConstraint::ConstrainedToDocumentRect);
3132 frameView->setLayoutViewportOverrideRect(layoutViewportRect);
3133 frameView->layoutOrVisualViewportChanged();
3134
3135 frameView->setCustomSizeForResizeEvent(expandedIntSize(target.unobscuredRectInScrollViewCoordinates.size()));
3136 setDeviceOrientation(target.deviceOrientation);
3137 frameView->setScrollOffset(roundedUnobscuredContentRectPosition);
3138
3139 protect(m_page)->isolatedUpdateRendering();
3140
3141#if ENABLE(VIEWPORT_RESIZING)(defined (defined 1 && 1) && (defined 1 &&
1))
3142 shrinkToFitContent();
3143#endif
3144
3145 protect(m_drawingArea)->triggerRenderingUpdate();
3146
3147 m_pendingDynamicViewportSizeUpdateID = target.identifier;
3148}
3149
3150void WebPage::resetViewportDefaultConfiguration(WebFrame* frame, bool hasMobileDocType)
3151{
3152 LOG_WITH_STREAM(VisibleRects, stream << "WebPage " << m_identifier << " resetViewportDefaultConfiguration")((void)0);
3153 if (m_useTestingViewportConfiguration) {
3154 m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::testingParameters());
3155 return;
3156 }
3157
3158 auto parametersForStandardFrame = [&] {
3159#if ENABLE(FULLSCREEN_API)(defined 1 && 1)
3160 if (m_isInFullscreenMode == IsInFullscreenMode::Yes)
3161 return m_viewportConfiguration.nativeWebpageParameters();
3162#endif
3163 if (shouldIgnoreMetaViewport())
3164 return m_viewportConfiguration.nativeWebpageParameters();
3165 return ViewportConfiguration::webpageParameters();
3166 };
3167
3168 RefPtr localFrame = frame->coreLocalFrame() ? frame->coreLocalFrame() : frame->provisionalFrame();
3169 ASSERT(localFrame)((void)0);
3170 if (!frame || !localFrame) {
3171 m_viewportConfiguration.setDefaultConfiguration(parametersForStandardFrame());
3172 return;
3173 }
3174
3175 RefPtr document = localFrame->document();
3176
3177 auto updateViewportConfigurationForMobileDocType = [this, protectedThis = Ref { *this }, document] {
3178 m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::xhtmlMobileParameters());
3179
3180 // Do not update the viewport arguments if they are already configured by the website.
3181 if (m_viewportConfiguration.viewportArguments().type == ViewportArguments::Type::ViewportMeta)
3182 return;
3183
3184 if (!document || !document->isViewportDocument())
3185 return;
3186
3187 // https://www.w3.org/TR/2016/WD-css-device-adapt-1-20160329/#intro
3188 // Certain DOCTYPEs (for instance XHTML Mobile Profile) are used to recognize mobile documents which are assumed
3189 // to be designed for handheld devices, hence using the viewport size as the initial containing block size.
3190 ViewportArguments viewportArguments { ViewportArguments::Type::ViewportMeta };
3191 viewportArguments.width = ViewportArguments::ValueDeviceWidth;
3192 viewportArguments.height = ViewportArguments::ValueDeviceHeight;
3193 viewportArguments.zoom = 1;
3194 document->setViewportArguments(viewportArguments);
3195 viewportPropertiesDidChange(viewportArguments);
3196 };
3197
3198 if (hasMobileDocType) {
3199 return updateViewportConfigurationForMobileDocType();
3200 }
3201
3202 bool configureWithParametersForStandardFrame = !document;
3203
3204 if (document) {
3205 if (document->isImageDocument())
3206 m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::imageDocumentParameters());
3207 else if (document->isTextDocument())
3208 m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::textDocumentParameters());
3209#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3210 else if (m_page && m_page->settings().unifiedPDFEnabled() && document->isPluginDocument())
3211 m_viewportConfiguration.setDefaultConfiguration(UnifiedPDFPlugin::viewportParameters());
3212#endif
3213 else
3214 configureWithParametersForStandardFrame = true;
3215 }
3216
3217 if (configureWithParametersForStandardFrame)
3218 return m_viewportConfiguration.setDefaultConfiguration(parametersForStandardFrame());
3219}
3220
3221#if ENABLE(TEXT_AUTOSIZING)(defined 1 && 1)
3222
3223void WebPage::updateTextAutosizingEnablementFromInitialScale(double initialScale)
3224{
3225 if (protect(*m_page)->settings().textAutosizingEnabledAtLargeInitialScale())
3226 return;
3227
3228 bool shouldEnable = initialScale <= 1;
3229 if (shouldEnable == m_page->settings().textAutosizingEnabled())
3230 return;
3231
3232 m_page->settings().setTextAutosizingEnabled(shouldEnable);
3233}
3234
3235void WebPage::resetIdempotentTextAutosizingIfNeeded(double previousInitialScale)
3236{
3237 if (!m_page->settings().textAutosizingEnabled() || !m_page->settings().textAutosizingUsesIdempotentMode())
3238 return;
3239
3240 const float minimumScaleChangeBeforeRecomputingTextAutosizing = 0.01;
3241 if (std::abs(previousInitialScale - m_page->initialScaleIgnoringContentSize()) < minimumScaleChangeBeforeRecomputingTextAutosizing)
3242 return;
3243
3244 if (m_page->initialScaleIgnoringContentSize() >= 1 && previousInitialScale >= 1)
3245 return;
3246
3247 RefPtr localMainFrame = protect(m_page)->localMainFrame();
3248 if (!localMainFrame)
3249 return;
3250
3251 if (!localMainFrame->view())
3252 return;
3253
3254 auto textAutoSizingDelay = [&] {
3255 Ref frameView = *localMainFrame->view();
3256 if (!frameView->isVisuallyNonEmpty()) {
3257 // We don't anticipate any painting after the next upcoming layout.
3258 const Seconds longTextAutoSizingDelayOnViewportChange = 100_ms;
3259 return longTextAutoSizingDelayOnViewportChange;
3260 }
3261 const Seconds defaultTextAutoSizingDelayOnViewportChange = 80_ms;
3262 return defaultTextAutoSizingDelayOnViewportChange;
3263 };
3264
3265 // We don't need to update text sizing eagerly. There might be multiple incoming dynamic viewport changes.
3266 m_textAutoSizingAdjustmentTimer.startOneShot(textAutoSizingDelay());
3267}
3268#endif // ENABLE(TEXT_AUTOSIZING)
3269
3270void WebPage::resetTextAutosizing()
3271{
3272#if ENABLE(TEXT_AUTOSIZING)(defined 1 && 1)
3273 for (RefPtr frame = &m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
3274 RefPtr localFrame = dynamicDowncast<LocalFrame>(frame.get());
3275 if (!localFrame)
3276 continue;
3277 RefPtr document = localFrame->document();
3278 if (!document || !document->renderView())
3279 continue;
3280 protect(document->renderView())->resetTextAutosizing();
3281 }
3282#endif
3283}
3284
3285#if ENABLE(VIEWPORT_RESIZING)(defined (defined 1 && 1) && (defined 1 &&
1))
3286
3287void WebPage::shrinkToFitContent(ZoomToInitialScale zoomToInitialScale)
3288{
3289 if (m_isClosed)
3290 return;
3291
3292 if (!m_page->settings().allowViewportShrinkToFitContent())
3293 return;
3294
3295 if (m_useTestingViewportConfiguration)
3296 return;
3297
3298 if (!shouldIgnoreMetaViewport())
3299 return;
3300
3301 if (!m_viewportConfiguration.viewportArguments().shrinkToFit)
3302 return;
3303
3304 if (m_viewportConfiguration.canIgnoreScalingConstraints())
3305 return;
3306
3307 RefPtr mainFrame = m_mainFrame->coreLocalFrame();
3308 if (!mainFrame)
3309 return;
3310
3311 RefPtr view = mainFrame->view();
3312 if (!view)
3313 return;
3314
3315 RefPtr mainDocument = mainFrame->document();
3316 if (!mainDocument)
3317 return;
3318
3319 mainDocument->updateLayout();
3320
3321 static const int toleratedHorizontalScrollingDistance = 20;
3322 static const int maximumExpandedLayoutWidth = 1280;
3323 static const int maximumContentWidthBeforeAvoidingShrinkToFit = 1920;
3324
3325 auto scaledViewWidth = [&] () -> int {
3326 return std::round(m_viewportConfiguration.viewLayoutSize().width() / m_viewportConfiguration.initialScale());
3327 };
3328
3329 int originalContentWidth = view->contentsWidth();
3330 int originalViewWidth = scaledViewWidth();
3331 int originalLayoutWidth = m_viewportConfiguration.layoutWidth();
3332 int originalHorizontalOverflowAmount = originalContentWidth - originalViewWidth;
3333 if (originalHorizontalOverflowAmount <= toleratedHorizontalScrollingDistance || originalLayoutWidth >= maximumExpandedLayoutWidth || originalContentWidth <= originalViewWidth || originalContentWidth > maximumContentWidthBeforeAvoidingShrinkToFit)
3334 return;
3335
3336 auto changeMinimumEffectiveDeviceWidth = [this, protectedThis = Ref { *this }, mainDocument] (int targetLayoutWidth) -> bool {
3337 if (m_viewportConfiguration.setMinimumEffectiveDeviceWidthForShrinkToFit(targetLayoutWidth)) {
3338 viewportConfigurationChanged();
3339 mainDocument->updateLayout();
3340 return true;
3341 }
3342 return false;
3343 };
3344
3345 m_viewportConfiguration.setIsKnownToLayOutWiderThanViewport(true);
3346 double originalMinimumDeviceWidth = m_viewportConfiguration.minimumEffectiveDeviceWidth();
3347 if (changeMinimumEffectiveDeviceWidth(std::min(maximumExpandedLayoutWidth, originalContentWidth)) && view->contentsWidth() - scaledViewWidth() > originalHorizontalOverflowAmount) {
3348 changeMinimumEffectiveDeviceWidth(originalMinimumDeviceWidth);
3349 m_viewportConfiguration.setIsKnownToLayOutWiderThanViewport(false);
3350 }
3351
3352 // FIXME (197429): Consider additionally logging an error message to the console if a responsive meta viewport tag was used.
3353 RELEASE_LOG(ViewportSizing, "Shrink-to-fit: content width %d => %d; layout width %d => %d", originalContentWidth, view->contentsWidth(), originalLayoutWidth, m_viewportConfiguration.layoutWidth())clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2LogViewportSizing
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("Shrink-to-fit: content width %d => %d; layout width %d => %d"
), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG41"
) = "Shrink-to-fit: content width %d => %d; layout width %d => %d"
; uint8_t _Alignas(16) __attribute__((uninitialized)) _os_fmt_buf
[__builtin_os_log_format_buffer_size("Shrink-to-fit: content width %d => %d; layout width %d => %d"
, originalContentWidth, view->contentsWidth(), originalLayoutWidth
, m_viewportConfiguration.layoutWidth())]; _os_log_impl(&
__dso_handle, _log_tmp, _type_tmp, _os_fmt_str, (uint8_t *)__builtin_os_log_format
(_os_fmt_buf, "Shrink-to-fit: content width %d => %d; layout width %d => %d"
, originalContentWidth, view->contentsWidth(), originalLayoutWidth
, m_viewportConfiguration.layoutWidth()), (uint32_t)sizeof(_os_fmt_buf
)) clang diagnostic pop ; }); } }) clang diagnostic pop
;
3354 viewportConfigurationChanged(zoomToInitialScale);
3355}
3356
3357#endif // ENABLE(VIEWPORT_RESIZING)
3358
3359bool WebPage::shouldIgnoreMetaViewport() const
3360{
3361 if (!m_page)
3362 return false;
3363
3364 RefPtr localMainFrame = protect(m_page)->localMainFrame();
3365 if (RefPtr mainDocument = localMainFrame ? localMainFrame->document() : nullptr) {
3366 RefPtr loader = mainDocument->loader();
3367 if (loader && loader->metaViewportPolicy() == WebCore::MetaViewportPolicy::Ignore)
3368 return true;
3369 }
3370
3371 return m_page->settings().shouldIgnoreMetaViewport();
3372}
3373
3374bool WebPage::mainFramePlugInDefersScalingToViewport() const
3375{
3376#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3377 if (RefPtr plugin = mainFramePlugIn(); plugin && !plugin->pluginHandlesPageScaleFactor())
3378 return true;
3379#endif
3380 return false;
3381}
3382
3383bool WebPage::mainFramePlugInRejectsZeroViewLayoutSizeUpdates() const
3384{
3385#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3386 if (RefPtr plugin = mainFramePlugIn(); plugin && !plugin->pluginHandlesPageScaleFactor())
3387 return true;
3388#endif
3389 return false;
3390}
3391
3392bool WebPage::shouldEnableViewportBehaviorsForResizableWindows() const
3393{
3394#if HAVE(UIKIT_RESIZABLE_WINDOWS)(defined 1 && 1)
3395 return shouldIgnoreMetaViewport() && m_isWindowResizingEnabled && !mainFramePlugInDefersScalingToViewport();
3396#else
3397 return false;
3398#endif
3399}
3400
3401void WebPage::viewportConfigurationChanged(ZoomToInitialScale zoomToInitialScale)
3402{
3403 double initialScale = m_viewportConfiguration.initialScale();
3404 double initialScaleIgnoringContentSize = m_viewportConfiguration.initialScaleIgnoringContentSize();
3405#if ENABLE(TEXT_AUTOSIZING)(defined 1 && 1)
3406 double previousInitialScaleIgnoringContentSize = m_page->initialScaleIgnoringContentSize();
3407 protect(m_page)->setInitialScaleIgnoringContentSize(initialScaleIgnoringContentSize);
3408 resetIdempotentTextAutosizingIfNeeded(previousInitialScaleIgnoringContentSize);
3409 updateTextAutosizingEnablementFromInitialScale(initialScale);
3410#endif
3411 if (setFixedLayoutSize(m_viewportConfiguration.layoutSize()))
3412 resetTextAutosizing();
3413
3414 auto minimumScale = m_viewportConfiguration.minimumScale();
3415 auto previousMinimumScale = m_previousViewportConfigurationMinimumScale.value_or(minimumScale);
3416 auto updatePreviousMinimumScale = makeScopeExit([&] {
3417 m_previousViewportConfigurationMinimumScale = minimumScale;
3418 });
3419
3420 double scale = [&] {
3421 if (!m_userHasChangedPageScaleFactor || zoomToInitialScale == ZoomToInitialScale::Yes)
3422 return initialScale;
3423
3424 auto currentScale = pageScaleFactor();
3425 if (scalesAreEssentiallyEqual(previousMinimumScale, currentScale))
3426 return minimumScale;
3427
3428 return std::max(std::min(currentScale, m_viewportConfiguration.maximumScale()), minimumScale);
3429 }();
3430
3431 LOG_WITH_STREAM(VisibleRects, stream << "WebPage " << m_identifier << " viewportConfigurationChanged - setting zoomedOutPageScaleFactor to " << minimumScale << " and scale to " << scale)((void)0);
3432
3433 protect(m_page)->setZoomedOutPageScaleFactor(minimumScale);
3434
3435 updateSizeForCSSDefaultViewportUnits();
3436 updateSizeForCSSSmallViewportUnits();
3437 updateSizeForCSSLargeViewportUnits();
3438
3439 RefPtr mainFrameView = this->localMainFrameView();
3440 if (!mainFrameView) {
3441 // FIXME: This is hit in some site isolation tests on iOS. Investigate and fix. <rdar://116201382>
3442 return;
3443 }
3444
3445 Ref frameView = *mainFrameView;
3446 IntPoint scrollPosition = frameView->scrollPosition();
3447 if (!m_hasReceivedVisibleContentRectsAfterDidCommitLoad) {
3448 FloatSize minimumLayoutSizeInScrollViewCoordinates = m_viewportConfiguration.viewLayoutSize();
3449 minimumLayoutSizeInScrollViewCoordinates.scale(1 / scale);
3450 IntSize minimumLayoutSizeInDocumentCoordinates = roundedIntSize(minimumLayoutSizeInScrollViewCoordinates);
3451 frameView->setUnobscuredContentSize(minimumLayoutSizeInDocumentCoordinates);
3452 frameView->setScrollVelocity({ 0, 0, 0, MonotonicTime::now() });
3453
3454 // FIXME: We could send down the obscured margins to find a better exposed rect and unobscured rect.
3455 // It is not a big deal at the moment because the tile coverage will always extend past the obscured bottom inset.
3456 if (!m_hasRestoredExposedContentRectAfterDidCommitLoad)
3457 protect(m_drawingArea)->setExposedContentRect(FloatRect(scrollPosition, minimumLayoutSizeInDocumentCoordinates));
3458 }
3459 scalePage(scale, scrollPosition);
3460
3461 if (!m_hasReceivedVisibleContentRectsAfterDidCommitLoad) {
3462 // This takes scale into account, so do after the scale change.
3463 frameView->setCustomFixedPositionLayoutRect(enclosingIntRect(frameView->viewportConstrainedObjectsRect()));
3464
3465 frameView->setCustomSizeForResizeEvent(expandedIntSize(m_viewportConfiguration.minimumLayoutSize()));
3466 }
3467}
3468
3469void WebPage::applicationWillResignActive()
3470{
3471 [[NSNotificationCenter defaultCenter] postNotificationName:protect(WebUIApplicationWillResignActiveNotification).get() object:nilnullptr];
3472
3473 // FIXME(224775): Move to WebProcess
3474 if (RefPtr manager = mediaSessionManagerIfExists())
3475 manager->applicationWillBecomeInactive();
3476
3477 if (m_page)
3478 protect(m_page)->applicationWillResignActive();
3479}
3480
3481void WebPage::applicationDidEnterBackground(bool isSuspendedUnderLock)
3482{
3483 [[NSNotificationCenter defaultCenter] postNotificationName:protect(WebUIApplicationDidEnterBackgroundNotification).get() object:nilnullptr userInfo:@{@"isSuspendedUnderLock": @(isSuspendedUnderLock)}];
3484
3485 m_isSuspendedUnderLock = isSuspendedUnderLock;
3486 if (!m_backgroundTextExtractionEnabled)
3487 freezeLayerTree(LayerTreeFreezeReason::BackgroundApplication);
3488
3489 // FIXME(224775): Move to WebProcess
3490 if (RefPtr manager = mediaSessionManagerIfExists())
3491 manager->applicationDidEnterBackground(isSuspendedUnderLock);
3492
3493 if (m_page)
3494 protect(m_page)->applicationDidEnterBackground();
3495}
3496
3497void WebPage::applicationDidFinishSnapshottingAfterEnteringBackground()
3498{
3499 markLayersVolatile();
3500}
3501
3502void WebPage::applicationWillEnterForeground(bool isSuspendedUnderLock)
3503{
3504 m_isSuspendedUnderLock = false;
3505 cancelMarkLayersVolatile();
3506
3507 if (!m_backgroundTextExtractionEnabled)
3508 unfreezeLayerTree(LayerTreeFreezeReason::BackgroundApplication);
3509
3510 [[NSNotificationCenter defaultCenter] postNotificationName:protect(WebUIApplicationWillEnterForegroundNotification).get() object:nilnullptr userInfo:@{@"isSuspendedUnderLock": @(isSuspendedUnderLock)}];
3511
3512 // FIXME(224775): Move to WebProcess
3513 if (RefPtr manager = mediaSessionManagerIfExists())
3514 manager->applicationWillEnterForeground(isSuspendedUnderLock);
3515
3516 if (m_page)
3517 protect(m_page)->applicationWillEnterForeground();
3518}
3519
3520void WebPage::applicationDidBecomeActive()
3521{
3522 [[NSNotificationCenter defaultCenter] postNotificationName:protect(WebUIApplicationDidBecomeActiveNotification).get() object:nilnullptr];
3523
3524 // FIXME(224775): Move to WebProcess
3525 if (RefPtr manager = mediaSessionManagerIfExists())
3526 manager->applicationDidBecomeActive();
3527
3528 if (m_page)
3529 protect(m_page)->applicationDidBecomeActive();
3530}
3531
3532void WebPage::applicationDidEnterBackgroundForMedia(bool isSuspendedUnderLock)
3533{
3534 if (RefPtr manager = mediaSessionManagerIfExists())
3535 manager->applicationDidEnterBackground(isSuspendedUnderLock);
3536}
3537
3538void WebPage::applicationWillEnterForegroundForMedia(bool isSuspendedUnderLock)
3539{
3540 if (RefPtr manager = mediaSessionManagerIfExists())
3541 manager->applicationWillEnterForeground(isSuspendedUnderLock);
3542}
3543
3544static inline void adjustVelocityDataForBoundedScale(VelocityData& velocityData, double exposedRectScale, double minimumScale, double maximumScale)
3545{
3546 if (velocityData.scaleChangeRate) {
3547 velocityData.horizontalVelocity = 0;
3548 velocityData.verticalVelocity = 0;
3549 }
3550
3551 if (exposedRectScale >= maximumScale || exposedRectScale <= minimumScale || scalesAreEssentiallyEqual(exposedRectScale, minimumScale) || scalesAreEssentiallyEqual(exposedRectScale, maximumScale))
3552 velocityData.scaleChangeRate = 0;
3553}
3554
3555std::optional<float> WebPage::scaleFromUIProcess(const VisibleContentRectUpdateInfo& visibleContentRectUpdateInfo) const
3556{
3557 auto transactionIDForLastScaleFromUIProcess = visibleContentRectUpdateInfo.lastLayerTreeTransactionID();
3558 if (m_internals->lastTransactionIDWithScaleChange && m_internals->lastTransactionIDWithScaleChange->greaterThanSameProcess(transactionIDForLastScaleFromUIProcess))
3559 return std::nullopt;
3560
3561 float scaleFromUIProcess = visibleContentRectUpdateInfo.scale();
3562 float currentScale = m_page->pageScaleFactor();
3563
3564 double scaleNoiseThreshold = 0.005;
3565 if (!m_isInStableState && std::abs(scaleFromUIProcess - currentScale) < scaleNoiseThreshold) {
3566 // Tiny changes of scale during interactive zoom cause content to jump by one pixel, creating
3567 // visual noise. We filter those useless updates.
3568 scaleFromUIProcess = currentScale;
3569 }
3570
3571 scaleFromUIProcess = std::min<float>(m_viewportConfiguration.maximumScale(), std::max<float>(m_viewportConfiguration.minimumScale(), scaleFromUIProcess));
3572 if (scalesAreEssentiallyEqual(currentScale, scaleFromUIProcess))
3573 return std::nullopt;
3574
3575 return scaleFromUIProcess;
3576}
3577
3578static bool selectionIsInsideFixedPositionContainer(LocalFrame& frame)
3579{
3580 auto& selection = frame.selection().selection();
3581 if (selection.isNone())
3582 return false;
3583
3584 bool isInsideFixedPosition = false;
3585 if (selection.isCaret()) {
3586 protect(frame.selection())->absoluteCaretBounds(&isInsideFixedPosition);
3587 return isInsideFixedPosition;
3588 }
3589
3590 selection.visibleStart().absoluteCaretBounds(&isInsideFixedPosition);
3591 if (isInsideFixedPosition)
3592 return true;
3593
3594 selection.visibleEnd().absoluteCaretBounds(&isInsideFixedPosition);
3595 return isInsideFixedPosition;
3596}
3597
3598void WebPage::updateVisibleContentRects(const VisibleContentRectUpdateInfo& visibleContentRectUpdateInfo, MonotonicTime oldestTimestamp)
3599{
3600 LOG_WITH_STREAM(VisibleRects, stream << "\nWebPage " << m_identifier << " updateVisibleContentRects " << visibleContentRectUpdateInfo)((void)0);
3601
3602 // Skip any VisibleContentRectUpdate that have been queued before DidCommitLoad suppresses the updates in the UIProcess.
3603 if (m_mainFrame->firstLayerTreeTransactionIDAfterDidCommitLoad() && visibleContentRectUpdateInfo.lastLayerTreeTransactionID().lessThanSameProcess(*m_mainFrame->firstLayerTreeTransactionIDAfterDidCommitLoad()) && !visibleContentRectUpdateInfo.isFirstUpdateForNewViewSize())
3604 return;
3605
3606 m_hasReceivedVisibleContentRectsAfterDidCommitLoad = true;
3607 m_isInStableState = visibleContentRectUpdateInfo.inStableState();
3608
3609 auto scaleFromUIProcess = this->scaleFromUIProcess(visibleContentRectUpdateInfo);
3610
3611 // Skip progressively redrawing tiles if pinch-zooming while the system is under memory pressure.
3612 if (scaleFromUIProcess && !m_isInStableState && MemoryPressureHandler::singleton().isUnderMemoryPressure())
3613 return;
3614
3615 if (m_isInStableState)
3616 m_hasStablePageScaleFactor = true;
3617 else {
3618 if (!m_oldestNonStableUpdateVisibleContentRectsTimestamp)
3619 m_oldestNonStableUpdateVisibleContentRectsTimestamp = oldestTimestamp;
3620 }
3621
3622 float scaleToUse = scaleFromUIProcess.value_or(m_page->pageScaleFactor());
3623 FloatRect exposedContentRect = visibleContentRectUpdateInfo.exposedContentRect();
3624 FloatRect adjustedExposedContentRect = adjustExposedRectForNewScale(exposedContentRect, visibleContentRectUpdateInfo.scale(), scaleToUse);
3625 protect(m_drawingArea)->setExposedContentRect(adjustedExposedContentRect);
3626 RefPtr localMainFrame = protect(m_page)->localMainFrame();
3627 if (!localMainFrame)
3628 return;
3629 RefPtr frameView = *localMainFrame->view();
3630
3631 if (RefPtr scrollingCoordinator = this->scrollingCoordinator()) {
3632 Ref remoteScrollingCoordinator = downcast<RemoteScrollingCoordinator>(*scrollingCoordinator);
3633 if (auto mainFrameScrollingNodeID = frameView->scrollingNodeID()) {
3634 if (visibleContentRectUpdateInfo.viewStability().contains(ViewStabilityFlag::ScrollViewRubberBanding))
3635 remoteScrollingCoordinator->addNodeWithActiveRubberBanding(*mainFrameScrollingNodeID);
3636 else
3637 remoteScrollingCoordinator->removeNodeWithActiveRubberBanding(*mainFrameScrollingNodeID);
3638 }
3639 }
3640
3641 auto layoutViewportRect = visibleContentRectUpdateInfo.layoutViewportRect();
3642 auto unobscuredContentRect = visibleContentRectUpdateInfo.unobscuredContentRect();
3643 auto scrollPosition = roundedIntPoint(unobscuredContentRect.location());
3644
3645 // Computation of layoutViewportRect is done in LayoutUnits which loses some precision, so test with an epsilon.
3646 // FIXME (302123): The loss of precision when converting floating point values to LayoutUnit does not, by itself, explain
3647 // the differences between the `layoutViewportRect` and `unobscuredContentRect`'s locations. While scrolling on iOS,
3648 // the absolute differences can sometimes exceed 3px, which is well over this fractional error threshold.
3649 // For now, we maintain behavior shipped in iOS 26 by snapping to the unobscured content rect location as long as
3650 // the difference is fairly small (~45 px).
3651 static constexpr auto maxEpsilon = 45.0;
3652 static constexpr auto epsilonRatio = 1.0 / (2 * kFixedPointDenominator);
3653 auto unobscuredContentRectLocation = unobscuredContentRect.location();
3654 auto epsilonX = std::min(maxEpsilon, epsilonRatio * std::abs(unobscuredContentRectLocation.x()));
3655 auto epsilonY = std::min(maxEpsilon, epsilonRatio * std::abs(unobscuredContentRectLocation.y()));
3656 auto layoutViewportRectLocation = layoutViewportRect.location();
3657 if (std::abs(unobscuredContentRectLocation.x() - layoutViewportRectLocation.x()) <= epsilonX && std::abs(unobscuredContentRectLocation.y() - layoutViewportRectLocation.y()) <= epsilonY)
3658 layoutViewportRect.setLocation(scrollPosition);
3659
3660 bool pageHasBeenScaledSinceLastLayerTreeCommitThatChangedPageScale = ([&] {
3661 if (!m_internals->lastLayerTreeTransactionIdAndPageScaleBeforeScalingPage)
3662 return false;
3663
3664 if (scalesAreEssentiallyEqual(scaleToUse, m_page->pageScaleFactor()))
3665 return false;
3666
3667 auto [transactionIdBeforeScalingPage, scaleBeforeScalingPage] = *m_internals->lastLayerTreeTransactionIdAndPageScaleBeforeScalingPage;
3668 if (!scalesAreEssentiallyEqual(scaleBeforeScalingPage, scaleToUse))
3669 return false;
3670
3671 return transactionIdBeforeScalingPage.greaterThanOrEqualSameProcess( visibleContentRectUpdateInfo.lastLayerTreeTransactionID());
3672 })();
3673
3674 if (!pageHasBeenScaledSinceLastLayerTreeCommitThatChangedPageScale) {
3675 bool shouldSetCorePageScale = [this, protectedThis = Ref { *this }] {
3676#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3677 RefPtr pluginView = mainFramePlugIn();
3678 if (!pluginView)
3679 return true;
3680 return !pluginView->pluginHandlesPageScaleFactor();
3681#else
3682 UNUSED_PARAM(this)(void)this;
3683 return true;
3684#endif
3685 }();
3686
3687 auto setCorePageScaleFactor = [this, protectedThis = Ref { *this }](float scale, const auto& origin, bool inStableState) {
3688 m_page->setPageScaleFactor(scale, origin, inStableState);
3689#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3690 if (RefPtr pluginView = mainFramePlugIn())
3691 pluginView->mainFramePageScaleFactorDidChange();
3692#endif
3693 };
3694
3695 bool hasSetPageScale = false;
3696 if (scaleFromUIProcess) {
3697 m_scaleWasSetByUIProcess = true;
3698 m_hasStablePageScaleFactor = m_isInStableState;
3699
3700 m_internals->dynamicSizeUpdateHistory.clear();
3701
3702 if (shouldSetCorePageScale)
3703 setCorePageScaleFactor(scaleFromUIProcess.value(), scrollPosition, m_isInStableState);
3704
3705 hasSetPageScale = true;
3706 send(Messages::WebPageProxy::PageScaleFactorDidChange(scaleFromUIProcess.value()));
3707 }
3708
3709 if (!hasSetPageScale && m_isInStableState && shouldSetCorePageScale)
3710 setCorePageScaleFactor(scaleToUse, scrollPosition, true);
3711 }
3712
3713 if (scrollPosition != frameView->scrollPosition())
3714 m_internals->dynamicSizeUpdateHistory.clear();
3715
3716 if (m_viewportConfiguration.setCanIgnoreScalingConstraints(visibleContentRectUpdateInfo.allowShrinkToFit()))
3717 viewportConfigurationChanged();
3718
3719 double minimumEffectiveDeviceWidthWhenIgnoringScalingConstraints = ([&] {
3720 RefPtr document = localMainFrame->document();
3721 if (!document)
3722 return 0;
3723
3724 if (!document->quirks().shouldLayOutAtMinimumWindowWidthWhenIgnoringScalingConstraints())
3725 return 0;
3726
3727 // This value is chosen to be close to the minimum width of a Safari window on macOS.
3728 return 500;
3729 })();
3730
3731 if (m_viewportConfiguration.setMinimumEffectiveDeviceWidthWhenIgnoringScalingConstraints(minimumEffectiveDeviceWidthWhenIgnoringScalingConstraints))
3732 viewportConfigurationChanged();
3733
3734 frameView->clearObscuredInsetsAdjustmentsIfNeeded();
3735 frameView->setUnobscuredContentSize(unobscuredContentRect.size());
3736 Ref page = *m_page;
3737 page->setContentInsets(visibleContentRectUpdateInfo.contentInsets());
3738 page->setObscuredInsets(visibleContentRectUpdateInfo.obscuredInsets());
3739 page->setUnobscuredSafeAreaInsets(visibleContentRectUpdateInfo.unobscuredSafeAreaInsets());
3740 page->setEnclosedInScrollableAncestorView(visibleContentRectUpdateInfo.enclosedInScrollableAncestorView());
3741
3742 VelocityData scrollVelocity = visibleContentRectUpdateInfo.scrollVelocity();
3743 adjustVelocityDataForBoundedScale(scrollVelocity, visibleContentRectUpdateInfo.scale(), m_viewportConfiguration.minimumScale(), m_viewportConfiguration.maximumScale());
3744 frameView->setScrollVelocity(scrollVelocity);
3745
3746 bool visualViewportChanged = unobscuredContentRect != visibleContentRectUpdateInfo.unobscuredContentRectRespectingInputViewBounds();
3747 if (visualViewportChanged)
3748 frameView->setVisualViewportOverrideRect(LayoutRect(visibleContentRectUpdateInfo.unobscuredContentRectRespectingInputViewBounds()));
3749 else if (m_isInStableState) {
3750 frameView->setVisualViewportOverrideRect(std::nullopt);
3751 visualViewportChanged = true;
3752 }
3753
3754 bool isChangingObscuredInsetsInteractively = visibleContentRectUpdateInfo.viewStability().contains(ViewStabilityFlag::ChangingObscuredInsetsInteractively);
3755 bool shouldPerformLayout = m_isInStableState && !isChangingObscuredInsetsInteractively;
3756
3757 LOG_WITH_STREAM(VisibleRects, stream << "WebPage::updateVisibleContentRects - setLayoutViewportOverrideRect " << layoutViewportRect)((void)0);
3758 frameView->setLayoutViewportOverrideRect(LayoutRect(layoutViewportRect), shouldPerformLayout ? LocalFrameView::TriggerLayoutOrNot::Yes : LocalFrameView::TriggerLayoutOrNot::No);
3759
3760 if (m_isInStableState) {
3761 if (selectionIsInsideFixedPositionContainer(*localMainFrame)) {
3762 // Ensure that the next layer tree commit contains up-to-date caret/selection rects.
3763 frameView->frame().selection().setCaretRectNeedsUpdate();
3764 scheduleFullEditorStateUpdate();
3765 }
3766 }
3767
3768 if (visualViewportChanged)
3769 frameView->layoutOrVisualViewportChanged();
3770
3771 if (!isChangingObscuredInsetsInteractively)
3772 frameView->setCustomSizeForResizeEvent(expandedIntSize(visibleContentRectUpdateInfo.unobscuredRectInScrollViewCoordinates().size()));
3773
3774 if (RefPtr scrollingCoordinator = this->scrollingCoordinator()) {
3775 auto viewportStability = ViewportRectStability::Stable;
3776 auto layerAction = ScrollingLayerPositionAction::Sync;
3777
3778 if (isChangingObscuredInsetsInteractively) {
3779 viewportStability = ViewportRectStability::ChangingObscuredInsetsInteractively;
3780 layerAction = ScrollingLayerPositionAction::SetApproximate;
3781 } else if (!m_isInStableState) {
3782 viewportStability = ViewportRectStability::Unstable;
3783 layerAction = ScrollingLayerPositionAction::SetApproximate;
3784 }
3785
3786 auto mainFrameScrollingNodeID = frameView->scrollingNodeID();
3787 if (!mainFrameScrollingNodeID) {
3788 ASSERT_NOT_REACHED()((void)0);
3789 return;
3790 }
3791
3792 auto scrollUpdate = ScrollUpdate {
3793 .nodeID = *mainFrameScrollingNodeID,
3794 .scrollPosition = scrollPosition,
3795 .data = ScrollUpdateData {
3796 .updateType = ScrollUpdateType::PositionUpdate,
3797 .updateLayerPositionAction = layerAction,
3798 .layoutViewportOrigin = visibleContentRectUpdateInfo.layoutViewportRect().location()
3799 }
3800 };
3801
3802 // We don't actually know that these are user scrolls; we get here for all kinds of state changes.
3803 scrollingCoordinator->applyScrollUpdate(WTF::move(scrollUpdate), ScrollType::User, viewportStability);
3804
3805 if (visibleContentRectUpdateInfo.needsScrollend() && frameView->scrollingNodeID()) {
3806 auto scrollUpdate = ScrollUpdate {
3807 .nodeID = *frameView->scrollingNodeID(),
3808 .scrollPosition = { },
3809 .data = ScrollUpdateData {
3810 .updateType = ScrollUpdateType::WheelEventScrollDidEnd,
3811 }
3812 };
3813 scrollingCoordinator->applyScrollUpdate(WTF::move(scrollUpdate), ScrollType::User);
3814 }
3815 }
3816}
3817
3818void WebPage::scheduleLayoutViewportHeightExpansionUpdate()
3819{
3820 if (!m_page->settings().layoutViewportHeightExpansionFactor())
3821 return;
3822
3823 if (m_updateLayoutViewportHeightExpansionTimer.isActive()) {
3824 m_shouldRescheduleLayoutViewportHeightExpansionTimer = true;
3825 return;
3826 }
3827
3828 m_updateLayoutViewportHeightExpansionTimer.restart();
3829}
3830
3831void WebPage::updateLayoutViewportHeightExpansionTimerFired()
3832{
3833 if (m_disallowLayoutViewportHeightExpansionReasons.contains(DisallowLayoutViewportHeightExpansionReason::LargeContainer))
3834 return;
3835
3836 RefPtr mainFrame = m_mainFrame->coreLocalFrame();
3837 if (!mainFrame)
3838 return;
3839
3840 RefPtr document = mainFrame->document();
3841 if (!document)
3842 return;
3843
3844 RefPtr view = mainFrame->view();
3845 if (!view)
3846 return;
3847
3848 FloatRect viewportRect = view->viewportConstrainedObjectsRect();
3849
3850 bool hitTestedToLargeViewportConstrainedElement = [&] {
3851 if (!view->hasViewportConstrainedObjects())
3852 return false;
3853
3854 HashSet<Ref<Element>> largeViewportConstrainedElements;
3855 for (CheckedRef renderer : *view->viewportConstrainedObjects()) {
3856 RefPtr element = renderer->element();
3857 if (!element)
3858 continue;
3859
3860 auto bounds = renderer->absoluteBoundingBoxRect();
3861 if (intersection(viewportRect, bounds).height() > 0.9 * viewportRect.height())
3862 largeViewportConstrainedElements.add(element.releaseNonNull());
3863 }
3864
3865 if (largeViewportConstrainedElements.isEmpty())
3866 return false;
3867
3868 auto hitTestResult = HitTestResult { LayoutRect { viewportRect } };
3869 if (!document->hitTest({ HitTestSource::User, { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::CollectMultipleElements } }, hitTestResult))
3870 return false;
3871
3872 auto& hitTestedNodes = hitTestResult.listBasedTestResult();
3873 HashSet<Ref<Element>> elementsOutsideOfAnyLargeViewportConstrainedContainers;
3874 for (auto& node : hitTestedNodes) {
3875 RefPtr firstParentOrSelf = dynamicDowncast<Element>(node.get()) ?: node->parentElementInComposedTree();
3876 Vector<Ref<Element>> ancestorsForHitTestedNode;
3877 for (RefPtr parent = firstParentOrSelf; parent; parent = parent->parentElementInComposedTree()) {
3878 if (largeViewportConstrainedElements.contains(*parent))
3879 return true;
3880
3881 if (elementsOutsideOfAnyLargeViewportConstrainedContainers.contains(*parent))
3882 break;
3883
3884 ancestorsForHitTestedNode.append(*parent);
3885 }
3886 for (auto& ancestor : ancestorsForHitTestedNode)
3887 elementsOutsideOfAnyLargeViewportConstrainedContainers.add(ancestor);
3888 }
3889 return false;
3890 }();
3891
3892 if (hitTestedToLargeViewportConstrainedElement) {
3893 RELEASE_LOG(ViewportSizing, "Shrinking viewport down to normal height (found large fixed-position container)")clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2LogViewportSizing
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("Shrinking viewport down to normal height (found large fixed-position container)"
), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG42"
) = "Shrinking viewport down to normal height (found large fixed-position container)"
; uint8_t _Alignas(16) __attribute__((uninitialized)) _os_fmt_buf
[__builtin_os_log_format_buffer_size("Shrinking viewport down to normal height (found large fixed-position container)"
)]; _os_log_impl(&__dso_handle, _log_tmp, _type_tmp, _os_fmt_str
, (uint8_t *)__builtin_os_log_format(_os_fmt_buf, "Shrinking viewport down to normal height (found large fixed-position container)"
), (uint32_t)sizeof(_os_fmt_buf)) clang diagnostic pop ; }); }
}) clang diagnostic pop
;
3894 addReasonsToDisallowLayoutViewportHeightExpansion(DisallowLayoutViewportHeightExpansionReason::LargeContainer);
3895 } else if (m_shouldRescheduleLayoutViewportHeightExpansionTimer) {
3896 m_shouldRescheduleLayoutViewportHeightExpansionTimer = false;
3897 m_updateLayoutViewportHeightExpansionTimer.restart();
3898 }
3899}
3900
3901void WebPage::willStartUserTriggeredZooming()
3902{
3903#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3904 if (RefPtr pluginView = mainFramePlugIn())
3905 pluginView->didBeginMagnificationGesture();
3906#endif
3907
3908 protect(protect(*m_page)->diagnosticLoggingClient())->logDiagnosticMessage(DiagnosticLoggingKeys::webViewKey(), DiagnosticLoggingKeys::userZoomActionKey(), ShouldSample::No);
3909 m_userHasChangedPageScaleFactor = true;
3910}
3911
3912void WebPage::didEndUserTriggeredZooming()
3913{
3914#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3915 if (RefPtr pluginView = mainFramePlugIn())
3916 pluginView->didEndMagnificationGesture();
3917#endif
3918}
3919
3920#if ENABLE(IOS_TOUCH_EVENTS)(defined 0 && 0)
3921static std::optional<RemoteWebTouchEvent> transformEventIfNecessary(const Expected<bool, WebCore::RemoteFrameGeometryTransformer>& transformer, WebTouchEvent&& event)
3922{
3923 if (transformer)
3924 return std::nullopt;
3925 event.transformToRemoteFrameCoordinates(transformer.error());
3926 return RemoteWebTouchEvent { transformer.error().remoteFrameID(), WTF::move(event) };
3927}
3928
3929void WebPage::dispatchAsynchronousTouchEvents(UniqueRef<EventDispatcher::TouchEventQueue>&& queue)
3930{
3931 for (auto& touchEventData : queue.get()) {
3932 auto handleTouchEventResult = dispatchTouchEvent(touchEventData.frameID, touchEventData.event);
3933 bool handled = handleTouchEventResult.value_or(false);
3934
3935 // Invoke coalesced-away completion handlers first. These events were never
3936 // dispatched to JS, but their handlers must still be invoked so that state
3937 // transitions they carry (e.g. m_touchMovePreventionState) are resolved.
3938 for (auto&& completionHandler : touchEventData.completionHandlers | std::views::take(touchEventData.completionHandlers.size() - 1))
3939 completionHandler(handled, std::nullopt);
3940
3941 // The last handler corresponds to the event that was actually dispatched.
3942 touchEventData.completionHandlers.last()(handled, transformEventIfNecessary(handleTouchEventResult, WTF::move(touchEventData.event)));
3943 }
3944}
3945
3946void WebPage::cancelAsynchronousTouchEvents(UniqueRef<EventDispatcher::TouchEventQueue>&& queue)
3947{
3948 for (auto& touchEventData : queue.get()) {
3949 for (auto& completionHandler : touchEventData.completionHandlers)
3950 completionHandler(true, std::nullopt);
3951 }
3952}
3953#endif
3954
3955void WebPage::computePagesForPrintingiOS(WebCore::FrameIdentifier frameID, const PrintInfo& printInfo, CompletionHandler<void(uint64_t)>&& reply)
3956{
3957 ASSERT_WITH_MESSAGE(!printInfo.snapshotFirstPage, "If we are just snapshotting the first page, we don't need a synchronous message to determine the page count, which is 1.")((void)0);
3958
3959 Vector<WebCore::IntRect> pageRects;
3960 double totalScaleFactor;
3961 auto margin = printInfo.margin;
3962 computePagesForPrintingImpl(frameID, printInfo, pageRects, totalScaleFactor, margin);
3963
3964 RELEASE_LOG(Printing, "Computing pages for printing. Page rects size = %zu", pageRects.size())clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2LogPrinting
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("Computing pages for printing. Page rects size = %zu"), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG43"
) = "Computing pages for printing. Page rects size = %zu"; uint8_t
_Alignas(16) __attribute__((uninitialized)) _os_fmt_buf[__builtin_os_log_format_buffer_size
("Computing pages for printing. Page rects size = %zu", pageRects
.size())]; _os_log_impl(&__dso_handle, _log_tmp, _type_tmp
, _os_fmt_str, (uint8_t *)__builtin_os_log_format(_os_fmt_buf
, "Computing pages for printing. Page rects size = %zu", pageRects
.size()), (uint32_t)sizeof(_os_fmt_buf)) clang diagnostic pop
; }); } }) clang diagnostic pop
;
3965
3966 ASSERT(pageRects.size() >= 1)((void)0);
3967 reply(pageRects.size());
3968}
3969
3970void WebPage::drawToImage(WebCore::FrameIdentifier frameID, const PrintInfo& printInfo, CompletionHandler<void(std::optional<WebCore::ShareableBitmap::Handle>&&)>&& reply)
3971{
3972 Vector<WebCore::IntRect> pageRects;
3973 double totalScaleFactor;
3974 auto margin = printInfo.margin;
3975 computePagesForPrintingImpl(frameID, printInfo, pageRects, totalScaleFactor, margin);
3976
3977 size_t pageCount = pageRects.size();
3978
3979 RELEASE_LOG(Printing, "Drawing to image. Page rects size = %zu", pageCount)clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2LogPrinting
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("Drawing to image. Page rects size = %zu"), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG44"
) = "Drawing to image. Page rects size = %zu"; uint8_t _Alignas
(16) __attribute__((uninitialized)) _os_fmt_buf[__builtin_os_log_format_buffer_size
("Drawing to image. Page rects size = %zu", pageCount)]; _os_log_impl
(&__dso_handle, _log_tmp, _type_tmp, _os_fmt_str, (uint8_t
*)__builtin_os_log_format(_os_fmt_buf, "Drawing to image. Page rects size = %zu"
, pageCount), (uint32_t)sizeof(_os_fmt_buf)) clang diagnostic
pop ; }); } }) clang diagnostic pop
;
3980
3981 ASSERT(pageCount >= 1)((void)0);
3982
3983 if (!m_printContext) {
3984 reply({ });
3985 endPrinting();
3986 return;
3987 }
3988
3989 Checked<int> pageWidth = pageRects[0].width();
3990 Checked<int> pageHeight = pageRects[0].height();
3991
3992 // The thumbnail images are always a maximum of 500 x 500.
3993 static constexpr float maximumPrintPreviewDimensionSize = 500.0;
3994
3995 // If the sizes are too large, the bitmap will not be able to be created,
3996 // so scale them down.
3997 float scaleFactor = maximumPrintPreviewDimensionSize / static_cast<int>(std::max(pageWidth, pageHeight));
3998 if (scaleFactor < 1.0) {
3999 pageWidth = static_cast<int>(std::floorf(static_cast<int>(pageWidth) * scaleFactor));
4000 pageHeight = static_cast<int>(std::floorf(static_cast<int>(pageHeight) * scaleFactor));
4001 }
4002
4003 int imageHeight;
4004 if (!WTF::safeMultiply(pageHeight.value<size_t>(), pageCount, imageHeight)) {
4005 reply({ });
4006 endPrinting();
4007 return;
4008 }
4009
4010 auto bitmap = ShareableBitmap::create({ IntSize(pageWidth, imageHeight) });
4011 if (!bitmap) {
4012 reply({ });
4013 endPrinting();
4014 return;
4015 }
4016
4017 auto graphicsContext = bitmap->createGraphicsContext();
4018 if (!graphicsContext) {
4019 reply({ });
4020 endPrinting();
4021 return;
4022 }
4023
4024 for (size_t pageIndex = 0; pageIndex < pageCount; ++pageIndex) {
4025 if (pageIndex >= m_printContext->pageCount())
4026 break;
4027 graphicsContext->save();
4028 graphicsContext->translate(0, pageHeight * static_cast<int>(pageIndex));
4029 protect(m_printContext)->spoolPage(*graphicsContext, pageIndex, pageWidth);
4030 graphicsContext->restore();
4031 }
4032
4033 auto handle = bitmap->createHandle(SharedMemory::Protection::ReadOnly);
4034 reply(WTF::move(handle));
4035 endPrinting();
4036}
4037
4038void WebPage::drawPrintingToSnapshotiOS(RemoteSnapshotIdentifier snapshotIdentifier, WebCore::FrameIdentifier frameID, const PrintInfo& printInfo, CompletionHandler<void(std::optional<WebCore::FloatSize>)>&& completionHandler)
4039{
4040 RefPtr frame = WebProcess::singleton().webFrame(frameID);
4041 if (!frame) {
4042 completionHandler({ });
4043 endPrinting();
4044 return;
4045 }
4046
4047 RefPtr coreFrame = frame->coreLocalFrame();
4048 if (!coreFrame) {
4049 completionHandler({ });
4050 endPrinting();
4051 return;
4052 }
4053
4054 if (pdfDocumentForPrintingFrame(coreFrame.get())) {
4055 // Can't do this remotely.
4056 completionHandler({ });
4057 endPrinting();
4058 return;
4059 }
4060
4061 if (!m_printContext) {
4062 completionHandler({ });
4063 endPrinting();
4064 return;
4065 }
4066
4067 PrintContextAccessScope scope { *this };
4068 ASSERT(coreFrame->document()->printing())((void)0);
4069
4070 Vector<WebCore::IntRect> pageRects;
4071 double totalScaleFactor;
4072 auto margin = printInfo.margin;
4073 computePagesForPrintingImpl(frameID, printInfo, pageRects, totalScaleFactor, margin);
4074
4075 size_t pageCount = pageRects.size();
4076
4077 RELEASE_LOG(Printing, "Drawing to image. Page rects size = %zu", pageCount)clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (WebKit2LogPrinting
.osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("Drawing to image. Page rects size = %zu"), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG45"
) = "Drawing to image. Page rects size = %zu"; uint8_t _Alignas
(16) __attribute__((uninitialized)) _os_fmt_buf[__builtin_os_log_format_buffer_size
("Drawing to image. Page rects size = %zu", pageCount)]; _os_log_impl
(&__dso_handle, _log_tmp, _type_tmp, _os_fmt_str, (uint8_t
*)__builtin_os_log_format(_os_fmt_buf, "Drawing to image. Page rects size = %zu"
, pageCount), (uint32_t)sizeof(_os_fmt_buf)) clang diagnostic
pop ; }); } }) clang diagnostic pop
;
4078
4079 Checked<int> pageWidth = pageRects[0].width();
4080 Checked<int> pageHeight = pageRects[0].height();
4081
4082 // The thumbnail images are always a maximum of 500 x 500.
4083 static constexpr float maximumPrintPreviewDimensionSize = 500.0;
4084
4085 // If the sizes are too large, the bitmap will not be able to be created,
4086 // so scale them down.
4087 float scaleFactor = maximumPrintPreviewDimensionSize / static_cast<int>(std::max(pageWidth, pageHeight));
4088 if (scaleFactor < 1.0) {
4089 pageWidth = static_cast<int>(std::floorf(static_cast<int>(pageWidth) * scaleFactor));
4090 pageHeight = static_cast<int>(std::floorf(static_cast<int>(pageHeight) * scaleFactor));
4091 }
4092
4093 int imageHeight;
4094 if (!WTF::safeMultiply(pageHeight.value<size_t>(), pageCount, imageHeight)) {
4095 completionHandler({ });
4096 endPrinting();
4097 return;
4098 }
4099
4100 auto mediaBox = IntRect { 0, 0, pageWidth, imageHeight };
4101
4102 Ref remoteRenderingBackend = ensureRemoteRenderingBackendProxy();
4103 m_remoteSnapshotState = {
4104 snapshotIdentifier,
4105 remoteRenderingBackend->createSnapshotRecorder(snapshotIdentifier),
4106 MainRunLoopSuccessCallbackAggregator::create([completionHandler = WTF::move(completionHandler), snapshotSize = mediaBox.size()] (bool success) mutable {
4107 completionHandler(success ? std::optional<FloatSize>(snapshotSize) : std::nullopt);
4108 })
4109 };
4110
4111 GraphicsContext& context = m_remoteSnapshotState->recorder.get();
4112
4113 for (size_t pageIndex = 0; pageIndex < pageCount; ++pageIndex) {
4114 if (pageIndex >= m_printContext->pageCount())
4115 break;
4116 context.save();
4117 context.translate(0, pageHeight * static_cast<int>(pageIndex));
4118 protect(m_printContext)->spoolPage(context, pageIndex, pageWidth);
4119 context.restore();
4120 }
4121
4122 remoteRenderingBackend->sinkSnapshotRecorderIntoSnapshotFrame(WTF::move(m_remoteSnapshotState->recorder), frameID, protect(m_remoteSnapshotState->callback)->chain());
4123 m_remoteSnapshotState = std::nullopt;
4124}
4125
4126void WebPage::drawToPDFiOS(FrameIdentifier frameID, const PrintInfo& printInfo, uint64_t pageCount, CompletionHandler<void(RefPtr<SharedBuffer>&&)>&& reply)
4127{
4128 if (printInfo.snapshotFirstPage) {
4129 RefPtr localMainFrame = protect(m_page)->localMainFrame();
4130 if (!localMainFrame)
4131 return;
4132
4133 auto snapshotRect = IntRect { FloatRect { { }, FloatSize { printInfo.availablePaperWidth, printInfo.availablePaperHeight } } };
4134
4135 RefPtr buffer = ImageBuffer::create(snapshotRect.size(), RenderingMode::PDFDocument, RenderingPurpose::Snapshot, 1, DestinationColorSpace::SRGB(), PixelFormat::BGRA8);
4136 if (!buffer)
4137 return;
4138
4139 Ref frameView = *localMainFrame->view();
4140 auto originalLayoutViewportOverrideRect = frameView->layoutViewportOverrideRect();
4141 frameView->setLayoutViewportOverrideRect(LayoutRect(snapshotRect));
4142
4143 pdfSnapshotAtSize(*localMainFrame, buffer->context(), snapshotRect, { });
4144
4145 frameView->setLayoutViewportOverrideRect(originalLayoutViewportOverrideRect);
4146 reply(buffer->sinkIntoPDFDocument());
4147 return;
4148 }
4149
4150 RefPtr<SharedBuffer> pdfPageData;
4151 drawPagesToPDFImpl(frameID, printInfo, 0, pageCount, pdfPageData);
4152 reply(WTF::move(pdfPageData));
4153
4154 endPrinting();
4155}
4156
4157void WebPage::drawPrintingPagesToSnapshotiOS(RemoteSnapshotIdentifier snapshotIdentifier, WebCore::FrameIdentifier frameID, const PrintInfo& printInfo, uint64_t pageCount, CompletionHandler<void(std::optional<WebCore::FloatSize>)>&& completionHandler)
4158{
4159 RefPtr frame = WebProcess::singleton().webFrame(frameID);
4160 if (!frame) {
4161 completionHandler({ });
4162 return;
4163 }
4164
4165 RefPtr coreFrame = frame->coreLocalFrame();
4166 if (!coreFrame) {
4167 completionHandler({ });
4168 return;
4169 }
4170
4171 if (pdfDocumentForPrintingFrame(coreFrame.get())) {
4172 // Can't do this remotely.
4173 completionHandler({ });
4174 return;
4175 }
4176
4177 if (!m_printContext) {
4178 completionHandler({ });
4179 return;
4180 }
4181
4182 PrintContextAccessScope scope { *this };
4183 ASSERT(coreFrame->document()->printing())((void)0);
4184
4185 auto mediaBox = FloatRect { 0, 0, printInfo.availablePaperWidth, printInfo.availablePaperHeight };
4186 if (!printInfo.snapshotFirstPage && m_printContext && m_printContext->pageCount())
4187 mediaBox = m_printContext->pageRect(0);
4188
4189 Ref remoteRenderingBackend = ensureRemoteRenderingBackendProxy();
4190 m_remoteSnapshotState = {
4191 snapshotIdentifier,
4192 remoteRenderingBackend->createSnapshotRecorder(snapshotIdentifier),
4193 MainRunLoopSuccessCallbackAggregator::create([completionHandler = WTF::move(completionHandler), snapshotSize = mediaBox.size()] (bool success) mutable {
4194 completionHandler(success ? std::optional<FloatSize>(snapshotSize) : std::nullopt);
4195 })
4196 };
4197
4198 GraphicsContext& context = m_remoteSnapshotState->recorder.get();
4199
4200 if (printInfo.snapshotFirstPage) {
4201 Ref frameView = *coreFrame->view();
4202 auto originalLayoutViewportOverrideRect = frameView->layoutViewportOverrideRect();
4203 frameView->setLayoutViewportOverrideRect(LayoutRect(mediaBox));
4204
4205 pdfSnapshotAtSize(*coreFrame, context, IntRect { mediaBox }, { });
4206
4207 frameView->setLayoutViewportOverrideRect(originalLayoutViewportOverrideRect);
4208 } else
4209 drawPrintContextPagesToGraphicsContext(context, mediaBox, 0, pageCount);
4210
4211 remoteRenderingBackend->sinkSnapshotRecorderIntoSnapshotFrame(WTF::move(m_remoteSnapshotState->recorder), frameID, protect(m_remoteSnapshotState->callback)->chain());
4212 m_remoteSnapshotState = std::nullopt;
4213}
4214
4215void WebPage::contentSizeCategoryDidChange(const String& contentSizeCategory)
4216{
4217 setContentSizeCategory(contentSizeCategory);
4218 FontCache::invalidateAllFontCaches();
4219}
4220
4221String WebPage::platformUserAgent(const URL&) const
4222{
4223 return String();
4224}
4225
4226static bool isMousePrimaryPointingDevice()
4227{
4228#if PLATFORM(MACCATALYST)(defined WTF_PLATFORM_MACCATALYST && WTF_PLATFORM_MACCATALYST
)
4229 return true;
4230#else
4231 return false;
4232#endif
4233}
4234
4235bool WebPage::hasAccessoryMousePointingDevice() const
4236{
4237 if (isMousePrimaryPointingDevice())
4238 return true;
4239
4240#if HAVE(MOUSE_DEVICE_OBSERVATION)(defined 1 && 1)
4241 if (WebProcess::singleton().hasMouseDevice())
4242 return true;
4243#endif
4244
4245 return false;
4246}
4247
4248static bool hasAccessoryStylusPointingDevice()
4249{
4250#if HAVE(STYLUS_DEVICE_OBSERVATION)(defined 1 && 1)
4251 if (WebProcess::singleton().hasStylusDevice())
4252 return true;
4253#endif
4254
4255 return false;
4256}
4257
4258bool WebPage::hoverSupportedByPrimaryPointingDevice() const
4259{
4260 return isMousePrimaryPointingDevice();
4261}
4262
4263bool WebPage::hoverSupportedByAnyAvailablePointingDevice() const
4264{
4265 return hasAccessoryMousePointingDevice();
4266}
4267
4268std::optional<PointerCharacteristics> WebPage::pointerCharacteristicsOfPrimaryPointingDevice() const
4269{
4270 return isMousePrimaryPointingDevice() ? PointerCharacteristics::Fine : PointerCharacteristics::Coarse;
4271}
4272
4273OptionSet<PointerCharacteristics> WebPage::pointerCharacteristicsOfAllAvailablePointingDevices() const
4274{
4275 OptionSet<PointerCharacteristics> result;
4276 if (auto pointerCharacteristicsOfPrimaryPointingDevice = this->pointerCharacteristicsOfPrimaryPointingDevice())
4277 result.add(*pointerCharacteristicsOfPrimaryPointingDevice);
4278 if (hasAccessoryMousePointingDevice() || hasAccessoryStylusPointingDevice())
4279 result.add(PointerCharacteristics::Fine);
4280 return result;
4281}
4282
4283void WebPage::hardwareKeyboardAvailabilityChanged(HardwareKeyboardState state)
4284{
4285 m_keyboardIsAttached = state.isAttached;
4286 setHardwareKeyboardState(state);
4287
4288 protect(m_page)->didUpdateHardwareKeyboardAttachment(m_keyboardIsAttached);
4289
4290 if (RefPtr focusedFrame = m_page->focusController().focusedLocalFrame())
4291 focusedFrame->eventHandler().capsLockStateMayHaveChanged();
4292}
4293
4294void WebPage::updateStringForFind(const String& findString)
4295{
4296 send(Messages::WebPageProxy::UpdateStringForFind(findString));
4297}
4298
4299bool WebPage::platformPrefersTextLegibilityBasedZoomScaling() const
4300{
4301#if PLATFORM(WATCHOS)(defined WTF_PLATFORM_WATCHOS && WTF_PLATFORM_WATCHOS
)
4302 return true;
4303#else
4304 return false;
4305#endif
4306}
4307
4308void WebPage::updateSelectionWithDelta(int64_t locationDelta, int64_t lengthDelta, CompletionHandler<void()>&& completionHandler)
4309{
4310 RefPtr frame = m_page->focusController().focusedOrMainFrame();
4311 if (!frame)
4312 return completionHandler();
4313
4314 RefPtr root = protect(frame->selection())->rootEditableElementOrDocumentElement();
4315 auto selectionRange = frame->selection().selection().toNormalizedRange();
4316 if (!root || !selectionRange) {
4317 completionHandler();
4318 return;
4319 }
4320
4321 auto scope = makeRangeSelectingNodeContents(*root);
4322 auto selectionCharacterRange = characterRange(scope, *selectionRange);
4323 CheckedInt64 newSelectionLocation { selectionCharacterRange.location };
4324 CheckedInt64 newSelectionLength { selectionCharacterRange.length };
4325 newSelectionLocation += locationDelta;
4326 newSelectionLength += lengthDelta;
4327 if (newSelectionLocation.hasOverflowed() || newSelectionLength.hasOverflowed()) {
4328 completionHandler();
4329 return;
4330 }
4331
4332 auto newSelectionRange = CharacterRange(newSelectionLocation, newSelectionLength);
4333 auto updatedSelectionRange = resolveCharacterRange(makeRangeSelectingNodeContents(*root), newSelectionRange);
4334 protect(frame->selection())->setSelectedRange(updatedSelectionRange, Affinity::Downstream, WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
4335 completionHandler();
4336}
4337
4338static VisiblePosition moveByGranularityRespectingWordBoundary(const VisiblePosition& position, TextGranularity granularity, uint64_t granularityCount, SelectionDirection direction)
4339{
4340 ASSERT(granularityCount)((void)0);
4341 ASSERT(position.isNotNull())((void)0);
4342 bool backwards = direction == SelectionDirection::Backward;
4343 auto farthestPositionInDirection = backwards ? startOfEditableContent(position) : endOfEditableContent(position);
4344 if (position == farthestPositionInDirection)
4345 return backwards ? startOfWord(position) : endOfWord(position);
4346 VisiblePosition currentPosition = position;
4347 VisiblePosition nextPosition;
4348 do {
4349 nextPosition = positionOfNextBoundaryOfGranularity(currentPosition, granularity, direction);
4350 if (nextPosition.isNull())
4351 break;
4352 currentPosition = nextPosition;
4353 if (atBoundaryOfGranularity(currentPosition, granularity, direction))
4354 --granularityCount;
4355 } while (granularityCount);
4356 if (granularity == TextGranularity::SentenceGranularity)
4357 return currentPosition;
4358 // Note that this rounds to the nearest word, which may cross a line boundary when using line granularity.
4359 // For example, suppose the text is laid out as follows and the insertion point is at |:
4360 // |This is the first sen
4361 // tence in a paragraph.
4362 // Then moving 1 line of granularity forward will return the postion after the 'e' in sentence.
4363 return backwards ? startOfWord(currentPosition) : endOfWord(currentPosition);
4364}
4365
4366static VisiblePosition visiblePositionForPointInRootViewCoordinates(LocalFrame& frame, FloatPoint pointInRootViewCoordinates)
4367{
4368 auto pointInDocument = protect(frame.view())->rootViewToContents(roundedIntPoint(pointInRootViewCoordinates));
4369 return frame.visiblePositionForPoint(pointInDocument);
4370}
4371
4372static VisiblePositionRange constrainRangeToSelection(const VisiblePositionRange& selection, const VisiblePositionRange& range)
4373{
4374 if (intersects(selection, range))
4375 return intersection(selection, range);
4376 auto rangeMidpoint = midpoint(range);
4377 auto position = startOfWord(rangeMidpoint);
4378 if (!contains(range, position)) {
4379 position = endOfWord(rangeMidpoint);
4380 if (!contains(range, position))
4381 position = rangeMidpoint;
4382 }
4383 return { position, position };
4384}
4385
4386void WebPage::requestDocumentEditingContext(DocumentEditingContextRequest&& request, CompletionHandler<void(DocumentEditingContext&&)>&& completionHandler)
4387{
4388 bool wantsAttributedText = request.options.contains(DocumentEditingContextRequest::Options::AttributedText);
4389 if (!request.options.contains(DocumentEditingContextRequest::Options::Text) && !wantsAttributedText) {
4390 completionHandler({ });
4391 return;
4392 }
4393
4394 RefPtr frame = m_page->focusController().focusedOrMainFrame();
4395 if (!frame)
4396 return completionHandler({ });
4397
4398#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
4399 if (RefPtr pluginView = focusedPluginViewForFrame(*frame))
4400 return completionHandler(pluginView->documentEditingContext(WTF::move(request)));
4401#endif
4402
4403 RefPtr view = frame->view();
4404 if (!view)
4405 return completionHandler({ });
4406
4407 protect(frame->document())->updateLayout(LayoutOptions::IgnorePendingStylesheets);
4408
4409 VisibleSelection selection = frame->selection().selection();
4410
4411 VisiblePositionRange rangeOfInterest;
4412 auto selectionRange = VisiblePositionRange { selection.visibleStart(), selection.visibleEnd() };
4413
4414 bool isSpatialRequest = request.options.containsAny({ DocumentEditingContextRequest::Options::Spatial, DocumentEditingContextRequest::Options::SpatialAndCurrentSelection });
4415 bool isSpatialRequestWithCurrentSelection = request.options.contains(DocumentEditingContextRequest::Options::SpatialAndCurrentSelection);
4416 bool wantsRects = request.options.contains(DocumentEditingContextRequest::Options::Rects);
4417 bool wantsMarkedTextRects = request.options.contains(DocumentEditingContextRequest::Options::MarkedTextRects);
4418
4419 if (auto textInputContext = request.textInputContext) {
4420 auto element = elementForContext(*textInputContext);
4421 if (!element) {
4422 completionHandler({ });
4423 return;
4424 }
4425
4426 if (!request.rect.isEmpty()) {
4427 rangeOfInterest.start = closestEditablePositionInElementForAbsolutePoint(*element, roundedIntPoint(request.rect.minXMinYCorner()));
4428 rangeOfInterest.end = closestEditablePositionInElementForAbsolutePoint(*element, roundedIntPoint(request.rect.maxXMaxYCorner()));
4429 } else if (RefPtr textFormControlElement = dynamicDowncast<HTMLTextFormControlElement>(element)) {
4430 rangeOfInterest.start = textFormControlElement->visiblePositionForIndex(0);
4431 rangeOfInterest.end = textFormControlElement->visiblePositionForIndex(textFormControlElement->value()->length());
4432 } else {
4433 rangeOfInterest.start = firstPositionInOrBeforeNode(element.get());
4434 rangeOfInterest.end = lastPositionInOrAfterNode(element.get());
4435 }
4436 } else if (isSpatialRequest) {
4437 // FIXME: We might need to be a bit more careful that we get something useful (test the other corners?).
4438 rangeOfInterest.start = visiblePositionForPointInRootViewCoordinates(*frame, request.rect.minXMinYCorner());
4439 rangeOfInterest.end = visiblePositionForPointInRootViewCoordinates(*frame, request.rect.maxXMaxYCorner());
4440
4441 if (isSpatialRequestWithCurrentSelection && !selection.isNoneOrOrphaned()) {
4442 static constexpr auto maximumNumberOfLines = 10;
4443 auto intersectsSpatialRect = [&](const VisiblePosition& position) {
4444 FloatRect caretInRootView = view->contentsToRootView(position.absoluteCaretBounds());
4445 return caretInRootView.intersects(request.rect);
4446 };
4447
4448 auto startPositionNearSelection = [&] {
4449 auto start = selectionRange.start;
4450 unsigned lineCount = 0;
4451 do {
4452 auto previous = previousLinePosition(start, start.lineDirectionPointForBlockDirectionNavigation());
4453 if (previous.isNull() || previous == start)
4454 break;
4455 start = WTF::move(previous);
4456 lineCount++;
4457 } while (intersectsSpatialRect(start) && lineCount < maximumNumberOfLines);
4458 return start;
4459 }();
4460
4461 auto endPositionNearSelection = [&] {
4462 auto end = selectionRange.end;
4463 unsigned lineCount = 0;
4464 do {
4465 auto next = nextLinePosition(end, end.lineDirectionPointForBlockDirectionNavigation());
4466 if (next.isNull() || next == end)
4467 break;
4468 end = WTF::move(next);
4469 lineCount++;
4470 } while (intersectsSpatialRect(end) && lineCount < maximumNumberOfLines);
4471 return end;
4472 }();
4473
4474 if (startPositionNearSelection <= endPositionNearSelection) {
4475 rangeOfInterest.start = std::clamp(rangeOfInterest.start, startPositionNearSelection, endPositionNearSelection);
4476 rangeOfInterest.end = std::clamp(rangeOfInterest.end, startPositionNearSelection, endPositionNearSelection);
4477 }
4478 }
4479 if (request.options.contains(DocumentEditingContextRequest::Options::SpatialAndCurrentSelection)) {
4480 if (RefPtr rootEditableElement = selection.rootEditableElement()) {
4481 VisiblePosition startOfEditableRoot { firstPositionInOrBeforeNode(rootEditableElement.get()) };
4482 VisiblePosition endOfEditableRoot { lastPositionInOrAfterNode(rootEditableElement.get()) };
4483 if (startOfEditableRoot <= endOfEditableRoot) {
4484 rangeOfInterest.start = std::clamp(rangeOfInterest.start, startOfEditableRoot, endOfEditableRoot);
4485 rangeOfInterest.end = std::clamp(rangeOfInterest.end, startOfEditableRoot, endOfEditableRoot);
4486 }
4487 }
4488 }
4489 } else if (!selection.isNone())
4490 rangeOfInterest = selectionRange;
4491
4492 if (rangeOfInterest.end < rangeOfInterest.start)
4493 std::exchange(rangeOfInterest.start, rangeOfInterest.end);
4494
4495 if (request.options.contains(DocumentEditingContextRequest::Options::SpatialAndCurrentSelection)) {
4496 if (selectionRange.start < rangeOfInterest.start)
4497 rangeOfInterest.start = selectionRange.start;
4498 if (selectionRange.end > rangeOfInterest.end)
4499 rangeOfInterest.end = selectionRange.end;
4500 }
4501
4502 if (rangeOfInterest.start.isNull() || rangeOfInterest.start.isOrphan() || rangeOfInterest.end.isNull() || rangeOfInterest.end.isOrphan()) {
4503 completionHandler({ });
4504 return;
4505 }
4506
4507 // The subset of the selection that is inside the range of interest.
4508 auto rangeOfInterestInSelection = constrainRangeToSelection(selection, rangeOfInterest);
4509 if (rangeOfInterestInSelection.isNull()) {
4510 completionHandler({ });
4511 return;
4512 }
4513
4514 VisiblePosition contextBeforeStart;
4515 VisiblePosition contextAfterEnd;
4516 auto compositionRange = protect(frame->editor())->compositionRange();
4517 if (request.granularityCount) {
4518 contextBeforeStart = moveByGranularityRespectingWordBoundary(rangeOfInterest.start, request.surroundingGranularity, request.granularityCount, SelectionDirection::Backward);
4519 contextAfterEnd = moveByGranularityRespectingWordBoundary(rangeOfInterest.end, request.surroundingGranularity, request.granularityCount, SelectionDirection::Forward);
4520 } else {
4521 contextBeforeStart = rangeOfInterest.start;
4522 contextAfterEnd = rangeOfInterest.end;
4523 if (wantsMarkedTextRects && compositionRange) {
4524 // In the case where the client has requested marked text rects make sure that the context
4525 // range encompasses the entire marked text range so that we don't return a truncated result.
4526 auto compositionStart = makeDeprecatedLegacyPosition(compositionRange->start);
4527 auto compositionEnd = makeDeprecatedLegacyPosition(compositionRange->end);
4528 if (contextBeforeStart > compositionStart)
4529 contextBeforeStart = compositionStart;
4530 if (contextAfterEnd < compositionEnd)
4531 contextAfterEnd = compositionEnd;
4532 }
4533 }
4534
4535 auto isTextObscured = [] (const VisiblePosition& visiblePosition) {
4536 if (RefPtr textControl = enclosingTextFormControl(visiblePosition.deepEquivalent())) {
4537 if (RefPtr input = dynamicDowncast<HTMLInputElement>(textControl.get())) {
4538 if (input->autofilledAndObscured())
4539 return true;
4540 }
4541 }
4542 return false;
4543 };
4544
4545 auto makeString = [&] (const VisiblePosition& start, const VisiblePosition& end) -> AttributedString {
4546 auto range = makeSimpleRange(start, end);
4547 if (!range || range->collapsed())
4548 return { };
4549
4550 auto isObscured = isTextObscured(start);
4551 TextIteratorBehaviors textBehaviors = { };
4552 if (!isObscured)
4553 textBehaviors = TextIteratorBehavior::EmitsOriginalText;
4554
4555 if (wantsAttributedText)
4556 return editingAttributedStringReplacingNoBreakSpace(*range, textBehaviors, { IncludedElement::Images, IncludedElement::Attachments });
4557
4558 return AttributedString::fromNSAttributedString(adoptNS([[NSAttributedString alloc] initWithString:plainTextReplacingNoBreakSpace(*range, textBehaviors).createNSString().get()]));
4559 };
4560
4561 DocumentEditingContext context;
4562 context.contextBefore = makeString(contextBeforeStart, rangeOfInterestInSelection.start);
4563 context.selectedText = makeString(rangeOfInterestInSelection.start, rangeOfInterestInSelection.end);
4564 context.contextAfter = makeString(rangeOfInterestInSelection.end, contextAfterEnd);
4565 if (auto compositionVisiblePositionRange = makeVisiblePositionRange(compositionRange); intersects(rangeOfInterest, compositionVisiblePositionRange)) {
4566 context.markedText = makeString(compositionVisiblePositionRange.start, compositionVisiblePositionRange.end);
4567 auto markedTextLength = context.markedText.string.length();
4568
4569 ptrdiff_t distanceToSelectionStart = distanceBetweenPositions(rangeOfInterestInSelection.start, compositionVisiblePositionRange.start);
4570 ptrdiff_t distanceToSelectionEnd = distanceToSelectionStart + [context.selectedText.string.createNSString() length];
4571
4572 distanceToSelectionStart = clampTo<ptrdiff_t>(distanceToSelectionStart, 0, markedTextLength);
4573 distanceToSelectionEnd = clampTo<ptrdiff_t>(distanceToSelectionEnd, 0, markedTextLength);
4574 RELEASE_ASSERT(distanceToSelectionStart <= distanceToSelectionEnd)do { if (__builtin_expect(!!(!(distanceToSelectionStart <=
distanceToSelectionEnd)), 0)) do { WTF::isIntegralOrPointerType
(); compilerFenceForCrash(); WTFCrashWithInfo(4574, "/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm"
, __PRETTY_FUNCTION__ ); } while (false); } while (0)
;
4575
4576 context.selectedRangeInMarkedText = {
4577 static_cast<uint64_t>(distanceToSelectionStart),
4578 static_cast<uint64_t>(distanceToSelectionEnd - distanceToSelectionStart)
4579 };
4580 } else if (auto* suggestion = frame->editor().writingSuggestionData()) {
4581 if (auto suffix = suggestion->content(); !suffix.isEmpty()) {
4582 context.markedText = AttributedString::fromNSAttributedString(adoptNS([[NSAttributedString alloc] initWithString:suffix.createNSString().get()]));
4583 context.selectedRangeInMarkedText = { 0, 0 };
4584 }
4585 }
4586
4587 auto characterRectsForRange = [](const SimpleRange& range, unsigned startOffset) {
4588 Vector<DocumentEditingContext::TextRectAndRange> rects;
4589 unsigned offsetSoFar = startOffset;
4590 std::optional<SimpleRange> lastTextRange;
4591 for (TextIterator iterator { range }; !iterator.atEnd(); iterator.advance()) {
4592 if (iterator.text().isEmpty())
4593 continue;
4594
4595 if (lastTextRange == iterator.range())
4596 continue;
4597
4598 Vector<IntRect> absoluteRects;
4599 if (iterator.range().collapsed())
4600 absoluteRects = { VisiblePosition(makeContainerOffsetPosition(iterator.range().start)).absoluteCaretBounds() };
4601 else {
4602 absoluteRects = RenderObject::absoluteTextRects(iterator.range(), {
4603 RenderObject::BoundingRectBehavior::IgnoreEmptyTextSelections,
4604 RenderObject::BoundingRectBehavior::ComputeIndividualCharacterRects,
4605 });
4606 if (absoluteRects.isEmpty())
4607 absoluteRects.append({ });
4608 }
4609
4610 for (auto& absoluteRect : absoluteRects)
4611 rects.append({ protect(iterator.range().start.document().view())->contentsToRootView(absoluteRect), { offsetSoFar++, 1 } });
4612
4613 lastTextRange = iterator.range();
4614 }
4615 return rects;
4616 };
4617
4618 if (wantsRects) {
4619 if (auto contextRange = makeSimpleRange(contextBeforeStart, contextAfterEnd)) {
4620 // FIXME (257828): We should ideally ASSERT() here that context.textRects.size() is equal to
4621 // the combined length of the context and selection strings; however, there are some corner
4622 // cases in which this isn't true. In particular, such an assertion would be hit when running
4623 // the layout test editing/selection/ios/update-selection-after-overflow-scroll.html.
4624 // See also: <https://bugs.webkit.org/show_bug.cgi?id=257828>.
4625 context.textRects = characterRectsForRange(*contextRange, 0);
4626 }
4627 } else if (wantsMarkedTextRects && compositionRange) {
4628 unsigned compositionStartOffset = 0;
4629 if (auto range = makeSimpleRange(contextBeforeStart, compositionRange->start))
4630 compositionStartOffset = characterCount(*range);
4631 context.textRects = characterRectsForRange(*compositionRange, compositionStartOffset);
4632 }
4633
4634#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)(defined 0 && 0)
4635 if (request.options.contains(DocumentEditingContextRequest::Options::Annotation))
4636 context.annotatedText = m_textCheckingControllerProxy->annotatedSubstringBetweenPositions(contextBeforeStart, contextAfterEnd);
4637#endif
4638
4639 if (request.options.contains(DocumentEditingContextRequest::Options::AutocorrectedRanges)) {
4640 if (auto contextRange = makeSimpleRange(contextBeforeStart, contextAfterEnd)) {
4641 auto ranges = protect(protect(frame->document())->markers())->rangesForMarkersInRange(*contextRange, DocumentMarkerType::CorrectionIndicator);
4642 context.autocorrectedRanges = ranges.map([&] (auto& range) {
4643 auto characterRangeInContext = characterRange(*contextRange, range);
4644 return DocumentEditingContext::Range { characterRangeInContext.location, characterRangeInContext.length };
4645 });
4646 }
4647 }
4648
4649 completionHandler(WTF::move(context));
4650}
4651
4652void WebPage::setShouldRevealCurrentSelectionAfterInsertion(bool shouldRevealCurrentSelectionAfterInsertion)
4653{
4654 if (m_shouldRevealCurrentSelectionAfterInsertion == shouldRevealCurrentSelectionAfterInsertion)
4655 return;
4656 m_shouldRevealCurrentSelectionAfterInsertion = shouldRevealCurrentSelectionAfterInsertion;
4657 if (!shouldRevealCurrentSelectionAfterInsertion)
4658 return;
4659 protect(m_page)->revealCurrentSelection();
4660 scheduleFullEditorStateUpdate();
4661}
4662
4663void WebPage::setScreenIsBeingCaptured(bool captured)
4664{
4665 m_screenIsBeingCaptured = captured;
4666}
4667
4668void WebPage::setInsertionPointColor(WebCore::Color color)
4669{
4670 RenderThemeIOS::setInsertionPointColor(color);
4671}
4672
4673void WebPage::textInputContextsInRect(FloatRect searchRect, CompletionHandler<void(const Vector<ElementContext>&)>&& completionHandler)
4674{
4675 auto contexts = protect(m_page)->editableElementsInRect(searchRect).map([&] (const auto& element) {
4676 Ref document = element->document();
4677
4678 ElementContext context;
4679 context.webPageIdentifier = m_identifier;
4680 context.documentIdentifier = document->identifier();
4681 context.nodeIdentifier = element->nodeIdentifier();
4682 context.boundingRect = element->boundingBoxInRootViewCoordinates();
4683 return context;
4684 });
4685 completionHandler(contexts);
4686#if ENABLE(EDITABLE_REGION)(defined 1 && 1)
4687 protect(m_page)->setEditableRegionEnabled();
4688#endif
4689}
4690
4691void WebPage::focusTextInputContextAndPlaceCaret(const ElementContext& elementContext, const IntPoint& point, CompletionHandler<void(bool)>&& completionHandler)
4692{
4693 RefPtr target = elementForContext(elementContext);
4694 if (!target) {
4695 completionHandler(false);
4696 return;
4697 }
4698
4699 ASSERT(target->document().frame())((void)0);
4700 Ref targetFrame = *target->document().frame();
4701
4702 protect(targetFrame->document())->updateLayout(LayoutOptions::IgnorePendingStylesheets);
4703
4704 // Performing layout could have could torn down the element's renderer. Check that we still
4705 // have one. Otherwise, bail out as this function only focuses elements that have a visual
4706 // representation.
4707 if (!target->renderer() || !target->isFocusable()) {
4708 completionHandler(false);
4709 return;
4710 }
4711
4712 // FIXME: Do not focus an element if it moved or the caret point is outside its bounds
4713 // because we only want to do so if the caret can be placed.
4714 UserGestureIndicator gestureIndicator { IsProcessingUserGesture::Yes, &target->document() };
4715 SetForScope userIsInteractingChange { m_userIsInteracting, true };
4716 protect(corePage())->focusController().setFocusedElement(target.get(), targetFrame.ptr());
4717
4718 // Setting the focused element could tear down the element's renderer. Check that we still have one.
4719 if (m_focusedElement != target || !target->renderer()) {
4720 completionHandler(false);
4721 return;
4722 }
4723
4724 ASSERT(targetFrame->view())((void)0);
4725 auto position = closestEditablePositionInElementForAbsolutePoint(*target, protect(targetFrame->view())->rootViewToContents(point));
4726 if (position.isNull()) {
4727 completionHandler(false);
4728 return;
4729 }
4730 protect(targetFrame->selection())->setSelectedRange(makeSimpleRange(position), position.affinity(), WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
4731 completionHandler(true);
4732}
4733
4734void WebPage::platformDidScalePage()
4735{
4736 auto transactionID = downcast<RemoteLayerTreeDrawingArea>(*m_drawingArea).lastCommittedTransactionID();
4737 m_internals->lastLayerTreeTransactionIdAndPageScaleBeforeScalingPage = { { transactionID, m_lastTransactionPageScaleFactor } };
4738}
4739
4740#if USE(QUICK_LOOK)(defined 1 && 1)
4741
4742void WebPage::didStartLoadForQuickLookDocumentInMainFrame(const String& fileName, const String& uti)
4743{
4744 send(Messages::WebPageProxy::DidStartLoadForQuickLookDocumentInMainFrame(fileName, uti));
4745}
4746
4747void WebPage::didFinishLoadForQuickLookDocumentInMainFrame(const FragmentedSharedBuffer& buffer)
4748{
4749 ASSERT(!buffer.isEmpty())((void)0);
4750
4751 // FIXME: In some cases, buffer contains a single segment that wraps an existing ShareableResource.
4752 // If we could create a handle from that existing resource then we could avoid this extra
4753 // allocation and copy.
4754
4755 auto sharedMemory = SharedMemory::copyBuffer(buffer);
4756 if (!sharedMemory)
4757 return;
4758
4759 auto shareableResource = ShareableResource::create(sharedMemory.releaseNonNull(), 0, buffer.size());
4760 if (!shareableResource)
4761 return;
4762
4763 auto handle = shareableResource->createHandle();
4764 if (!handle)
4765 return;
4766
4767 send(Messages::WebPageProxy::DidFinishLoadForQuickLookDocumentInMainFrame(WTF::move(*handle)));
4768}
4769
4770void WebPage::requestPasswordForQuickLookDocumentInMainFrame(const String& fileName, CompletionHandler<void(const String&)>&& completionHandler)
4771{
4772 sendWithAsyncReply(Messages::WebPageProxy::RequestPasswordForQuickLookDocumentInMainFrame(fileName), WTF::move(completionHandler));
4773}
4774
4775#endif
4776
4777void WebPage::animationDidFinishForElement(const Element& animatedElement)
4778{
4779 scheduleEditorStateUpdateAfterAnimationIfNeeded(animatedElement);
4780
4781 if (!m_page->settings().layoutViewportHeightExpansionFactor())
4782 return;
4783
4784 if (!protect(animatedElement.document())->isTopDocument())
4785 return;
4786
4787 if (CheckedPtr renderer = animatedElement.renderer(); renderer && renderer->isFixedPositioned())
4788 scheduleLayoutViewportHeightExpansionUpdate();
4789}
4790
4791void WebPage::scheduleEditorStateUpdateAfterAnimationIfNeeded(const Element& animatedElement)
4792{
4793 RefPtr frame = m_page->focusController().focusedOrMainFrame();
4794 if (!frame)
4795 return;
4796
4797 auto& selection = frame->selection().selection();
4798 if (selection.isNoneOrOrphaned())
4799 return;
4800
4801 if (selection.isCaret() && !selection.hasEditableStyle())
4802 return;
4803
4804 auto scheduleEditorStateUpdateForStartOrEndContainerNodeIfNeeded = [&](const Node* container) {
4805 if (!animatedElement.isShadowIncludingInclusiveAncestorOf(container))
4806 return false;
4807
4808 frame->selection().setCaretRectNeedsUpdate();
4809 scheduleFullEditorStateUpdate();
4810 return true;
4811 };
4812
4813 RefPtr startContainer = selection.start().containerNode();
4814 if (scheduleEditorStateUpdateForStartOrEndContainerNodeIfNeeded(startContainer.get()))
4815 return;
4816
4817 RefPtr endContainer = selection.end().containerNode();
4818 if (startContainer != endContainer)
4819 scheduleEditorStateUpdateForStartOrEndContainerNodeIfNeeded(endContainer.get());
4820}
4821
4822FloatSize WebPage::screenSizeForFingerprintingProtections(const LocalFrame&, FloatSize defaultSize) const
4823{
4824 if (!PAL::currentUserInterfaceIdiomIsSmallScreen())
4825 return m_viewportConfiguration.minimumLayoutSize();
4826
4827 static constexpr std::array fixedSizes {
4828 FloatSize { 320, 568 },
4829 FloatSize { 375, 667 },
4830 FloatSize { 390, 844 },
4831 FloatSize { 414, 896 },
4832 };
4833
4834 for (auto fixedSize : fixedSizes) {
4835 if (defaultSize.width() <= fixedSize.width())
4836 return fixedSize;
4837 }
4838
4839 return std::get<std::tuple_size_v<decltype(fixedSizes)> - 1>(fixedSizes);
4840}
4841
4842void WebPage::shouldDismissKeyboardAfterTapAtPoint(FloatPoint point, CompletionHandler<void(bool)>&& completion)
4843{
4844 RefPtr localMainFrame = protect(m_page)->localMainFrame();
4845 if (!localMainFrame)
4846 return completion(false);
4847
4848 RefPtr mainFrameView = localMainFrame->view();
4849 if (!mainFrameView)
4850 return completion(false);
4851
4852 FloatPoint adjustedPoint;
4853 RefPtr target = localMainFrame->nodeRespondingToClickEvents(point, adjustedPoint);
4854 if (!target)
4855 return completion(true);
4856
4857 if (target->hasEditableStyle())
4858 return completion(false);
4859
4860 if (RefPtr element = dynamicDowncast<Element>(*target); element && element->isFormControlElement())
4861 return completion(false);
4862
4863 auto minimumSizeForDismissal = FloatSize { 0.9f * mainFrameView->unobscuredContentSize().width(), 150.f };
4864
4865 bool isReplaced;
4866 FloatSize targetSize = target->absoluteBoundingRect(&isReplaced).size();
4867 completion(targetSize.width() >= minimumSizeForDismissal.width() && targetSize.height() >= minimumSizeForDismissal.height());
4868}
4869
4870void WebPage::computeEnclosingLayerID(EditorState& state, const VisibleSelection& selection) const
4871{
4872 auto selectionRange = selection.range();
4873
4874 if (!selectionRange)
4875 return;
4876
4877 auto [startLayer, endLayer, enclosingLayer, graphicsLayer, enclosingGraphicsLayerID] = computeEnclosingLayer(*selectionRange);
4878
4879 state.visualData->enclosingLayerID = WTF::move(enclosingGraphicsLayerID);
4880
4881 if (!state.visualData->enclosingLayerID)
4882 return;
4883
4884 auto nextScroller = [](RenderLayer& layer, IncludeSelfOrNot includeSelf) {
4885 return layer.enclosingScrollableLayer(includeSelf, CrossFrameBoundaries::Yes);
4886 };
4887
4888 auto scrollOffsetAndNodeIDForLayer = [](RenderLayer* layer) -> std::pair<ScrollOffset, std::optional<ScrollingNodeID>> {
4889 CheckedRef renderer = layer->renderer();
4890 WeakPtr scrollableArea = [&] -> ScrollableArea* {
4891 if (renderer->isRenderView())
4892 return protect(renderer->document())->isTopDocument() ? nullptr : renderer->frame().view();
4893
4894 return layer->scrollableArea();
4895 }();
4896
4897 if (!scrollableArea)
4898 return { };
4899
4900 auto scrollingNodeID = scrollableArea->scrollingNodeID();
4901 if (!scrollingNodeID)
4902 return { };
4903
4904 return { scrollableArea->scrollOffset(), WTF::move(scrollingNodeID) };
4905 };
4906
4907 CheckedPtr<RenderLayer> scrollableLayer;
4908 for (CheckedPtr layer = nextScroller(*protect(enclosingLayer), IncludeSelfOrNot::IncludeSelf); layer; layer = nextScroller(*layer, IncludeSelfOrNot::ExcludeSelf)) {
4909 if (auto [scrollOffset, scrollingNodeID] = scrollOffsetAndNodeIDForLayer(layer.get()); scrollingNodeID) {
4910 state.visualData->enclosingScrollOffset = scrollOffset;
4911 state.visualData->enclosingScrollingNodeID = WTF::move(scrollingNodeID);
4912 scrollableLayer = WTF::move(layer);
4913 break;
4914 }
4915 }
4916
4917 ASSERT_IMPLIES(state.visualData->enclosingLayerID, graphicsLayer)((void)0);
4918 state.visualData->enclosingLayerUsesContentsLayer = graphicsLayer && protect(graphicsLayer)->usesContentsLayer();
4919
4920 if (selection.isCaret()) {
4921 state.visualData->scrollingNodeIDAtStart = state.visualData->enclosingScrollingNodeID;
4922 state.visualData->scrollingNodeIDAtEnd = state.visualData->enclosingScrollingNodeID;
4923 return;
4924 }
4925
4926 auto scrollingNodeIDForEndpoint = [&](RenderLayer* endpointLayer) {
4927 for (CheckedPtr layer = endpointLayer; layer && layer != scrollableLayer; layer = nextScroller(*layer, IncludeSelfOrNot::ExcludeSelf)) {
4928 if (auto scrollingNodeID = scrollOffsetAndNodeIDForLayer(layer.get()).second)
4929 return scrollingNodeID;
4930 }
4931 return state.visualData->enclosingScrollingNodeID;
4932 };
4933
4934 state.visualData->scrollingNodeIDAtStart = scrollingNodeIDForEndpoint(startLayer.get());
4935 state.visualData->scrollingNodeIDAtEnd = scrollingNodeIDForEndpoint(endLayer.get());
4936}
4937
4938void WebPage::callAfterPendingSyntheticClick(CompletionHandler<void(SyntheticClickResult)>&& completion)
4939{
4940 if (m_pendingSyntheticClickCallback)
4941 return completion(SyntheticClickResult::Failed);
4942
4943 sendWithAsyncReply(Messages::WebPageProxy::IsPotentialTapInProgress(), [weakPage = WeakPtr { *this }, completion = WTF::move(completion)](bool isTapping) mutable {
4944 RefPtr page = weakPage.get();
4945 if (!page || page->m_isClosed)
4946 return completion(SyntheticClickResult::PageInvalid);
4947
4948 if (!isTapping)
4949 return completion(SyntheticClickResult::Failed);
4950
4951 if (!page->m_potentialTapNode)
4952 return completion(SyntheticClickResult::Failed);
4953
4954 page->m_pendingSyntheticClickCallback = WTF::move(completion);
4955 });
4956}
4957
4958#if ENABLE(IOS_TOUCH_EVENTS)(defined 0 && 0)
4959
4960void WebPage::didDispatchClickEvent(const PlatformMouseEvent& event, Node& node)
4961{
4962 if (!m_userIsInteracting)
4963 return;
4964
4965 if (event.type() != PlatformEventType::MouseReleased)
4966 return;
4967
4968 if (event.syntheticClickType() != SyntheticClickType::NoTap)
4969 return;
4970
4971 RefPtr element = dynamicDowncast<Element>(node) ?: node.parentElementInComposedTree();
4972 if (!element)
4973 return;
4974
4975 Ref document = node.document();
4976 if (!document->quirks().shouldSynthesizeTouchEventsAfterNonSyntheticClick(*element))
4977 return;
4978
4979 bool isReplaced = false;
4980 auto bounds = element->absoluteBoundingRect(&isReplaced);
4981 if (bounds.isEmpty())
4982 return;
4983
4984 callOnMainRunLoop([bounds, document = WTF::move(document)] mutable {
4985 if (RefPtr frame = document->frame())
4986 frame->eventHandler().dispatchSimulatedTouchEvent(roundedIntPoint(bounds.center()));
4987 });
4988}
4989
4990#endif // ENABLE(IOS_TOUCH_EVENTS)
4991
4992#if USE(UICONTEXTMENU)(defined 1 && 1)
4993
4994void WebPage::willBeginContextMenuInteraction()
4995{
4996 m_hasActiveContextMenuInteraction = true;
4997}
4998
4999void WebPage::didEndContextMenuInteraction()
5000{
5001 m_hasActiveContextMenuInteraction = false;
5002}
5003
5004#endif // USE(UICONTEXTMENU)
5005
5006#if ENABLE(PDF_PAGE_NUMBER_INDICATOR)(defined 1 && 1)
5007
5008void WebPage::createPDFPageNumberIndicator(PDFPluginBase& plugin, const IntRect& boundingBox, size_t pageCount)
5009{
5010 ASSERT(!m_pdfPlugInWithPageNumberIndicator.first || m_pdfPlugInWithPageNumberIndicator.first == plugin.identifier())((void)0);
5011 if (m_pdfPlugInWithPageNumberIndicator.first == plugin.identifier())
5012 return;
5013 m_pdfPlugInWithPageNumberIndicator = std::make_pair(plugin.identifier(), WeakPtr { plugin });
5014 send(Messages::WebPageProxy::CreatePDFPageNumberIndicator(plugin.identifier(), boundingBox, pageCount));
5015}
5016
5017void WebPage::updatePDFPageNumberIndicatorLocation(PDFPluginBase& plugin, const IntRect& boundingBox)
5018{
5019 if (m_pdfPlugInWithPageNumberIndicator.first == plugin.identifier())
5020 send(Messages::WebPageProxy::UpdatePDFPageNumberIndicatorLocation(plugin.identifier(), boundingBox));
5021}
5022
5023void WebPage::updatePDFPageNumberIndicatorCurrentPage(PDFPluginBase& plugin, size_t pageIndex)
5024{
5025 if (m_pdfPlugInWithPageNumberIndicator.first == plugin.identifier())
5026 send(Messages::WebPageProxy::UpdatePDFPageNumberIndicatorCurrentPage(plugin.identifier(), pageIndex));
5027}
5028
5029void WebPage::removePDFPageNumberIndicator(PDFPluginBase& plugin)
5030{
5031 if (m_pdfPlugInWithPageNumberIndicator.first == plugin.identifier()) {
5032 m_pdfPlugInWithPageNumberIndicator = std::make_pair(Markable<PDFPluginIdentifier> { }, nullptr);
5033 send(Messages::WebPageProxy::RemovePDFPageNumberIndicator(plugin.identifier()));
5034 }
5035}
5036
5037#endif
5038
5039#if ENABLE(UNIFIED_PDF)(defined 1 && 1)
5040
5041void WebPage::setPDFDisplayMode(PDFDisplayMode mode)
5042{
5043 send(Messages::WebPageProxy::SetPDFDisplayMode(mode));
5044}
5045
5046void WebPage::requestPDFDisplayMode(PDFDisplayMode mode)
5047{
5048 if (RefPtr pluginView = mainFramePlugIn())
5049 return pluginView->setPDFDisplayMode(mode);
5050}
5051
5052#endif
5053
5054} // namespace WebKit
5055
5056#undef WEBPAGE_RELEASE_LOG
5057#undef WEBPAGE_RELEASE_LOG_ERROR
5058
5059#endif // PLATFORM(IOS_FAMILY)