Bug Summary

File:Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
Warning:line 2593, 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 1 && 1)
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 VisiblePosition position = frame->selection().selection().start();
2226 for (auto i = numGraphemeClusters(oldText); i; --i)
2227 position = position.previous();
2228 if (position.isNull())
2229 position = startOfDocument(protect(frame->document()));
2230 auto range = makeSimpleRange(position, frame->selection().selection().start());
2231
2232 if (plainTextForContext(range) != oldText)
2233 return;
2234
2235 // We don't want to notify the client that the selection has changed until we are done inserting the new text.
2236 IgnoreSelectionChangeForScope ignoreSelectionChanges { *frame };
2237 protect(frame->selection())->setSelectedRange(range, Affinity::Upstream, WebCore::FrameSelection::ShouldCloseTyping::Yes);
2238 protect(frame->editor())->insertText(newText, 0);
2239}
2240
2241void WebPage::willInsertFinalDictationResult()
2242{
2243 RefPtr frame = protect(*m_page)->focusController().focusedOrMainFrame();
2244 if (!frame)
2245 return;
2246
2247 if (frame->selection().isNone())
2248 return;
2249
2250 m_ignoreSelectionChangeScopeForDictation = makeUnique<IgnoreSelectionChangeForScope>(*frame);
2251}
2252
2253bool WebPage::shouldRemoveDictationAlternativesAfterEditing() const
2254{
2255 return !m_ignoreSelectionChangeScopeForDictation;
2256}
2257
2258void WebPage::didInsertFinalDictationResult()
2259{
2260 m_ignoreSelectionChangeScopeForDictation = nullptr;
2261 scheduleFullEditorStateUpdate();
2262}
2263
2264void WebPage::requestAutocorrectionData(const String& textForAutocorrection, CompletionHandler<void(WebAutocorrectionData)>&& reply)
2265{
2266 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2267 if (!frame)
2268 return reply({ });
2269
2270 if (!frame->selection().isCaret()) {
2271 reply({ });
2272 return;
2273 }
2274
2275 auto range = wordRangeFromPosition(frame->selection().selection().visibleStart());
2276 if (!range) {
2277 reply({ });
2278 return;
2279 }
2280
2281 auto textForRange = plainTextForContext(range);
2282 const unsigned maxSearchAttempts = 5;
2283 for (size_t i = 0; i < maxSearchAttempts && textForRange != textForAutocorrection; ++i) {
2284 auto position = makeDeprecatedLegacyPosition(range->start).previous();
2285 if (position.isNull() || position == makeDeprecatedLegacyPosition(range->start))
2286 break;
2287 auto startRange = wordRangeFromPosition(position);
2288 if (!startRange)
2289 continue;
2290 range = { { startRange->start, range->end } };
2291 textForRange = plainTextForContext(range);
2292 }
2293
2294 Vector<SelectionGeometry> selectionGeometries;
2295 if (textForRange == textForAutocorrection)
2296 selectionGeometries = RenderObject::collectSelectionGeometries(*range).geometries;
2297
2298 auto rootViewSelectionRects = selectionGeometries.map([&](const auto& selectionGeometry) -> FloatRect {
2299 return frame->view()->contentsToRootView(selectionGeometry.rect());
2300 });
2301
2302 bool multipleFonts = false;
2303 CTFontRef font = nilnullptr;
2304 if (auto coreFont = protect(frame->editor())->fontForSelection(multipleFonts))
2305 font = coreFont->ctFont();
2306
2307 reply({ WTF::move(rootViewSelectionRects) , (__bridge UIFont *)font });
2308}
2309
2310void WebPage::applyAutocorrection(const String& correction, const String& originalText, bool isCandidate, CompletionHandler<void(const String&)>&& callback)
2311{
2312 callback(applyAutocorrectionInternal(correction, originalText, isCandidate) ? correction : String());
2313}
2314
2315Seconds WebPage::eventThrottlingDelay() const
2316{
2317 auto behaviorOverride = m_page->eventThrottlingBehaviorOverride();
2318 if (behaviorOverride) {
2319 switch (behaviorOverride.value()) {
2320 case EventThrottlingBehavior::Responsive:
2321 return 0_s;
2322 case EventThrottlingBehavior::Unresponsive:
2323 return 1_s;
2324 }
2325 }
2326
2327 if (m_isInStableState || m_estimatedLatency <= Seconds(1.0 / 60))
2328 return 0_s;
2329
2330 return std::min(m_estimatedLatency * 2, 1_s);
2331}
2332
2333void WebPage::syncApplyAutocorrection(const String& correction, const String& originalText, bool isCandidate, CompletionHandler<void(bool)>&& reply)
2334{
2335 reply(applyAutocorrectionInternal(correction, originalText, isCandidate));
2336}
2337
2338bool WebPage::applyAutocorrectionInternal(const String& correction, const String& originalText, bool isCandidate)
2339{
2340 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2341 if (!frame)
2342 return false;
2343
2344 if (!frame->selection().isCaretOrRange())
2345 return false;
2346
2347 if (correction == originalText)
2348 return false;
2349
2350 std::optional<SimpleRange> range;
2351 String textForRange;
2352 auto originalTextWithFoldedQuoteMarks = foldQuoteMarks(originalText);
2353
2354 if (frame->selection().isCaret()) {
2355 auto position = frame->selection().selection().visibleStart();
2356 range = wordRangeFromPosition(position);
2357 textForRange = plainTextForContext(range);
2358
2359 // If 'originalText' is not the same as 'textForRange' we need to move 'range'
2360 // forward such that it matches the original selection as much as possible.
2361 if (foldQuoteMarks(textForRange) != originalTextWithFoldedQuoteMarks) {
2362 // Search for the original text near the selection caret.
2363 auto characterCount = numGraphemeClusters(originalText);
2364 if (!characterCount) {
2365 textForRange = emptyString();
2366 range = makeSimpleRange(position);
2367 } else if (auto searchRange = rangeExpandedAroundPositionByCharacters(position, characterCount)) {
2368 if (auto foundRange = findPlainText(*searchRange, originalTextWithFoldedQuoteMarks, { FindOption::DoNotSetSelection, FindOption::DoNotRevealSelection }); !foundRange.collapsed()) {
2369 textForRange = plainTextForContext(foundRange);
2370 range = foundRange;
2371 }
2372 }
2373 } else if (textForRange.isEmpty() && range && !range->collapsed()) {
2374 // If 'range' does not include any text but it is not collapsed, we need to set
2375 // 'range' to match the selection. Otherwise non-text nodes will be removed.
2376 range = makeSimpleRange(position);
2377 if (!range)
2378 return false;
2379 }
2380 } else {
2381 // Range selection.
2382 range = frame->selection().selection().toNormalizedRange();
2383 if (!range)
2384 return false;
2385
2386 textForRange = plainTextForContext(range);
2387 }
2388
2389 if (foldQuoteMarks(textForRange) != originalTextWithFoldedQuoteMarks)
2390 return false;
2391
2392 // Correctly determine affinity, using logic currently only present in VisiblePosition
2393 auto affinity = Affinity::Downstream;
2394 if (range && range->collapsed())
2395 affinity = VisiblePosition(makeDeprecatedLegacyPosition(range->start), Affinity::Upstream).affinity();
2396
2397 protect(frame->selection())->setSelectedRange(range, affinity, WebCore::FrameSelection::ShouldCloseTyping::Yes);
2398 Ref editor = frame->editor();
2399 if (correction.length()) {
2400 editor->insertText(correction, 0, originalText.isEmpty() ? TextEventInputKeyboard : TextEventInputAutocompletion);
2401 if (isCandidate)
2402 adjustCandidateAutocorrectionInFrame(correction, *frame);
2403 } else if (originalText.length())
2404 editor->deleteWithDirection(SelectionDirection::Backward, TextGranularity::CharacterGranularity, false, true);
2405 return true;
2406}
2407
2408WebAutocorrectionContext WebPage::autocorrectionContext()
2409{
2410 if (!m_focusedElement)
2411 return { };
2412
2413 RefPtr frame = m_page->focusController().focusedOrMainFrame();
2414 if (!frame)
2415 return { };
2416
2417 if (!frame->selection().selection().hasEditableStyle())
2418 return { };
2419
2420 String contextBefore;
2421 String markedText;
2422 String selectedText;
2423 String contextAfter;
2424 EditingRange selectedRangeInMarkedText;
2425
2426 VisiblePosition startPosition = frame->selection().selection().start();
2427 VisiblePosition endPosition = frame->selection().selection().end();
2428 const unsigned minContextWordCount = 10;
2429 const unsigned minContextLength = 40;
2430 const unsigned maxContextLength = 100;
2431
2432 if (frame->selection().isRange())
2433 selectedText = plainTextForContext(frame->selection().selection().toNormalizedRange());
2434
2435 if (auto compositionRange = protect(frame->editor())->compositionRange()) {
2436 auto markedTextBefore = plainTextForContext(makeSimpleRange(compositionRange->start, startPosition));
2437 auto markedTextAfter = plainTextForContext(makeSimpleRange(endPosition, compositionRange->end));
2438 markedText = makeString(markedTextBefore, selectedText, markedTextAfter);
2439 if (!markedText.isEmpty()) {
2440 selectedRangeInMarkedText.location = markedTextBefore.length();
2441 selectedRangeInMarkedText.length = selectedText.length();
2442 }
2443 } else {
2444 auto firstPositionInEditableContent = startOfEditableContent(startPosition);
2445 if (startPosition != firstPositionInEditableContent) {
2446 VisiblePosition contextStartPosition = startPosition;
2447 VisiblePosition previousPosition;
2448 unsigned totalContextLength = 0;
2449 for (unsigned i = 0; i < minContextWordCount; ++i) {
2450 if (contextBefore.length() >= minContextLength)
2451 break;
2452 previousPosition = startOfWord(positionOfNextBoundaryOfGranularity(contextStartPosition, TextGranularity::WordGranularity, SelectionDirection::Backward));
2453 if (previousPosition.isNull())
2454 break;
2455 String currentWord = plainTextForContext(makeSimpleRange(previousPosition, contextStartPosition));
2456 totalContextLength += currentWord.length();
2457 if (totalContextLength >= maxContextLength)
2458 break;
2459 contextStartPosition = previousPosition;
2460 }
2461 VisiblePosition sentenceContextStartPosition = startOfSentence(startPosition);
2462 if (sentenceContextStartPosition.isNotNull() && sentenceContextStartPosition < contextStartPosition)
2463 contextStartPosition = sentenceContextStartPosition;
2464 if (contextStartPosition.isNotNull() && contextStartPosition < startPosition) {
2465 contextBefore = plainTextForContext(makeSimpleRange(contextStartPosition, startPosition));
2466 if (atBoundaryOfGranularity(contextStartPosition, TextGranularity::ParagraphGranularity, SelectionDirection::Backward) && firstPositionInEditableContent != contextStartPosition)
2467 contextBefore = makeString("\n "_s, contextBefore);
2468 }
2469 }
2470
2471 if (endPosition != endOfEditableContent(endPosition)) {
2472 VisiblePosition nextPosition = endOfSentence(endPosition);
2473 if (nextPosition.isNotNull() && nextPosition > endPosition)
2474 contextAfter = plainTextForContext(makeSimpleRange(endPosition, nextPosition));
2475 }
2476 }
2477
2478 WebAutocorrectionContext correction;
2479 correction.contextBefore = WTF::move(contextBefore);
2480 correction.markedText = WTF::move(markedText);
2481 correction.selectedText = WTF::move(selectedText);
2482 correction.contextAfter = WTF::move(contextAfter);
2483 correction.selectedRangeInMarkedText = WTF::move(selectedRangeInMarkedText);
2484 return correction;
2485}
2486
2487void WebPage::preemptivelySendAutocorrectionContext()
2488{
2489 send(Messages::WebPageProxy::HandleAutocorrectionContext(autocorrectionContext()));
2490}
2491
2492void WebPage::handleAutocorrectionContextRequest()
2493{
2494 send(Messages::WebPageProxy::HandleAutocorrectionContext(autocorrectionContext()));
2495}
2496
2497void WebPage::prepareToRunModalJavaScriptDialog()
2498{
2499 // When a modal dialog is presented while an editable element is focused, UIKit will attempt to request a
2500 // WebAutocorrectionContext, which triggers synchronous IPC back to the web process, resulting in deadlock.
2501 // To avoid this deadlock, we preemptively compute and send autocorrection context data to the UI process,
2502 // such that the UI process can immediately respond to UIKit without synchronous IPC to the web process.
2503 preemptivelySendAutocorrectionContext();
2504}
2505
2506static inline bool isObscuredElement(Element& element)
2507{
2508 RefPtr mainFrameDocument = protect(element.document())->mainFrameDocument();
2509 if (!mainFrameDocument) {
2510 LOG_ONCE(SiteIsolation, "Unable to properly perform isObscuredElement() without access to the main frame document ")((void)0);
2511 return false;
2512 }
2513
2514 auto elementRectInMainFrame = element.boundingBoxInRootViewCoordinates();
2515
2516 constexpr OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::AllowChildFrameContent, HitTestRequest::Type::DisallowUserAgentShadowContent, HitTestRequest::Type::IgnoreClipping };
2517 HitTestResult result(elementRectInMainFrame.center());
2518
2519 mainFrameDocument->hitTest(hitType, result);
2520 result.setToNonUserAgentShadowAncestor();
2521
2522 if (result.targetElement() == &element)
2523 return false;
2524
2525 return true;
2526}
2527
2528void WebPage::startInteractionWithElementContextOrPosition(std::optional<WebCore::ElementContext>&& elementContext, WebCore::IntPoint&& point)
2529{
2530 if (elementContext) {
2531 m_interactionNode = elementForContext(*elementContext);
2532 if (m_interactionNode)
2533 return;
2534 }
2535
2536 FloatPoint adjustedPoint;
2537 if (RefPtr localMainFrame = protect(m_page)->localMainFrame())
2538 m_interactionNode = localMainFrame->nodeRespondingToInteraction(point, adjustedPoint);
2539}
2540
2541void WebPage::stopInteraction()
2542{
2543 m_interactionNode = nullptr;
2544}
2545
2546static void handleAnimationActions(Element& element, uint32_t action)
2547{
2548#if ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)(defined 1 && 1)
2549 if (static_cast<SheetAction>(action) == SheetAction::PlayAnimation) {
2550 if (RefPtr imageElement = dynamicDowncast<HTMLImageElement>(element))
2551 imageElement->setAllowsAnimation(true);
2552 } else if (static_cast<SheetAction>(action) == SheetAction::PauseAnimation) {
2553 if (RefPtr imageElement = dynamicDowncast<HTMLImageElement>(element))
2554 imageElement->setAllowsAnimation(false);
2555 }
2556#endif // ENABLE(ACCESSIBILITY_ANIMATION_CONTROL)
2557}
2558
2559void WebPage::performActionOnElement(uint32_t action, const String& authorizationToken, CompletionHandler<void()>&& completionHandler)
2560{
2561 CompletionHandlerCallingScope callCompletionHandler(WTF::move(completionHandler));
2562
2563 RefPtr element = dynamicDowncast<HTMLElement>(m_interactionNode);
2564 if (!element || !element->renderer())
2565 return;
2566
2567 if (static_cast<SheetAction>(action) == SheetAction::Copy) {
2568 Ref interactionNodeEditor = protect(m_interactionNode->document())->editor();
2569 Ref elementDocument = element->document();
2570 if (is<RenderImage>(*element->renderer())) {
2571 URL urlToCopy;
2572 String titleToCopy;
2573 if (RefPtr linkElement = containingLinkAnchorElement(*element)) {
2574 if (auto url = linkElement->href(); !url.isEmpty() && !url.protocolIsJavaScript()) {
2575 urlToCopy = url;
2576 titleToCopy = linkElement->attributeWithoutSynchronization(HTMLNames::titleAttr);
2577 if (!titleToCopy.length())
2578 titleToCopy = linkElement->textContent();
2579 titleToCopy = titleToCopy.trim(isASCIIWhitespace);
2580 }
2581 }
2582 interactionNodeEditor->writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(elementDocument->pageID())), *element, urlToCopy, titleToCopy);
2583 } else if (element->isLink())
2584 interactionNodeEditor->copyURL(elementDocument->completeURL(element->attributeWithoutSynchronization(HTMLNames::hrefAttr)), element->textContent());
2585#if ENABLE(ATTACHMENT_ELEMENT)(defined 1 && 1)
2586 else if (auto attachmentInfo = protect(elementDocument->editor())->promisedAttachmentInfo(*element))
2587 send(Messages::WebPageProxy::WritePromisedAttachmentToPasteboard(WTF::move(attachmentInfo), authorizationToken));
2588#endif
2589 } else if (static_cast<SheetAction>(action) == SheetAction::SaveImage) {
2590 CheckedPtr renderImage = dynamicDowncast<RenderImage>(*element->renderer());
2591 if (!renderImage)
2592 return;
2593 auto* cachedImage = renderImage->cachedImage();
Local variable 'cachedImage' is uncounted and unsafe
2594 if (!cachedImage)
2595 return;
2596 RefPtr<FragmentedSharedBuffer> buffer = cachedImage->resourceBuffer();
2597 if (!buffer)
2598 return;
2599 std::optional<SharedMemory::Handle> handle;
2600 {
2601 auto sharedMemoryBuffer = SharedMemory::copyBuffer(*buffer);
2602 if (!sharedMemoryBuffer)
2603 return;
2604 handle = sharedMemoryBuffer->createHandle(SharedMemory::Protection::ReadOnly);
2605 }
2606 if (!handle)
2607 return;
2608 send(Messages::WebPageProxy::SaveImageToLibrary(WTF::move(*handle), authorizationToken));
2609 }
2610#if ENABLE(SPATIAL_IMAGE_DETECTION)(defined ENABLE_SPATIAL_IMAGE_DETECTION && ENABLE_SPATIAL_IMAGE_DETECTION
)
2611 else if (static_cast<SheetAction>(action) == SheetAction::ViewSpatial)
2612 element->webkitRequestFullscreen();
2613#endif
2614
2615 handleAnimationActions(*element, action);
2616}
2617
2618void WebPage::performActionOnElements(uint32_t action, const Vector<WebCore::ElementContext>& elements)
2619{
2620 for (const auto& elementContext : elements) {
2621 if (RefPtr element = elementForContext(elementContext))
2622 handleAnimationActions(*element, action);
2623 }
2624}
2625
2626static inline RefPtr<Element> nextAssistableElement(Node* startNode, Page& page, bool isForward)
2627{
2628 RefPtr nextElement = dynamicDowncast<Element>(startNode);
2629 if (!nextElement)
2630 return nullptr;
2631
2632 CheckedRef focusController { page.focusController() };
2633 do {
2634 auto result = isForward ? focusController->nextFocusableElement(*nextElement) : focusController->previousFocusableElement(*nextElement);
2635 if (!result.element && result.continuedSearchInRemoteFrame == ContinuedSearchInRemoteFrame::Yes)
2636 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
;
2637
2638 nextElement = result.element;
2639 } while (nextElement && (!WebPage::isAssistableElement(*nextElement) || isObscuredElement(*nextElement)));
2640
2641 return nextElement;
2642}
2643
2644void WebPage::focusNextFocusedElement(bool isForward, CompletionHandler<void()>&& completionHandler)
2645{
2646 auto nextElement = nextAssistableElement(m_focusedElement.get(), *protect(m_page), isForward);
2647 m_userIsInteracting = true;
2648 if (nextElement)
2649 nextElement->focus();
2650 m_userIsInteracting = false;
2651 completionHandler();
2652}
2653
2654std::optional<FocusedElementInformation> WebPage::focusedElementInformation()
2655{
2656 Ref page = *m_page;
2657 RefPtr focusedOrMainFrame = page->focusController().focusedOrMainFrame();
2658 if (!focusedOrMainFrame)
2659 return std::nullopt;
2660 RefPtr<Document> document = focusedOrMainFrame->document();
2661 if (!document || !document->view())
2662 return std::nullopt;
2663
2664 auto focusedElement = m_focusedElement.copyRef();
2665 layoutIfNeeded();
2666
2667 // Layout may have detached the document or caused a change of focus.
2668 if (!document->view() || focusedElement != m_focusedElement)
2669 return std::nullopt;
2670
2671 scheduleFullEditorStateUpdate();
2672
2673 FocusedElementInformation information;
2674
2675 if (RefPtr webFrame = WebProcess::singleton().webFrame(focusedOrMainFrame->frameID()))
2676 information.frame = webFrame->info(WithCertificateInfo::Yes);
2677
2678 information.lastInteractionLocation = flooredIntPoint(m_lastInteractionLocation);
2679 if (auto elementContext = contextForElement(*focusedElement))
2680 information.elementContext = WTF::move(*elementContext);
2681
2682 if (CheckedPtr renderer = focusedElement->renderer()) {
2683 information.interactionRect = rootViewInteractionBounds(*focusedElement);
2684 information.nodeFontSize = protect(renderer->style())->fontDescription().computedSize();
2685
2686 bool inFixed = false;
2687 renderer->localToContainerPoint(FloatPoint(), nullptr, UseTransforms, &inFixed);
2688 information.insideFixedPosition = inFixed;
2689 information.isRTL = renderer->writingMode().isBidiRTL();
2690
2691 if (RefPtr scrollingCoordinator = this->scrollingCoordinator())
2692 information.containerScrollingNodeID = scrollingCoordinator->scrollableContainerNodeID(*renderer);
2693 } else
2694 information.interactionRect = { };
2695
2696 RefPtr htmlElement = dynamicDowncast<HTMLElement>(focusedElement);
2697 if (htmlElement)
2698 information.isSpellCheckingEnabled = htmlElement->spellcheck();
2699
2700 information.isWritingSuggestionsEnabled = focusedElement->isWritingSuggestionsEnabled();
2701
2702 if (RefPtr formControlElement = dynamicDowncast<HTMLFormControlElement>(focusedElement))
2703 information.isFocusingWithValidationMessage = formControlElement->isFocusingWithValidationMessage();
2704
2705 information.minimumScaleFactor = minimumPageScaleFactor();
2706 information.maximumScaleFactor = maximumPageScaleFactor();
2707 information.maximumScaleFactorIgnoringAlwaysScalable = maximumPageScaleFactorIgnoringAlwaysScalable();
2708 information.allowsUserScaling = m_viewportConfiguration.allowsUserScaling();
2709 information.allowsUserScalingIgnoringAlwaysScalable = m_viewportConfiguration.allowsUserScalingIgnoringAlwaysScalable();
2710 if (auto nextElement = nextAssistableElement(focusedElement.get(), page, true)) {
2711 information.nextNodeRect = rootViewBounds(*nextElement);
2712 information.hasNextNode = true;
2713 }
2714 if (auto previousElement = nextAssistableElement(focusedElement.get(), page, false)) {
2715 information.previousNodeRect = rootViewBounds(*previousElement);
2716 information.hasPreviousNode = true;
2717 }
2718 information.identifier = m_internals->lastFocusedElementInformationIdentifier.increment();
2719
2720 if (htmlElement) {
2721 if (auto labels = htmlElement->labels()) {
2722 Vector<Ref<Element>> associatedLabels;
2723 for (unsigned index = 0; index < labels->length(); ++index) {
2724 if (RefPtr label = dynamicDowncast<Element>(labels->item(index)); label && label->renderer())
2725 associatedLabels.append(*label);
2726 }
2727 for (auto& labelElement : associatedLabels) {
2728 auto text = labelElement->innerText();
2729 if (!text.isEmpty()) {
2730 information.label = WTF::move(text);
2731 break;
2732 }
2733 }
2734 }
2735 }
2736
2737 information.title = focusedElement->title();
2738 information.ariaLabel = focusedElement->attributeWithoutSynchronization(HTMLNames::aria_labelAttr);
2739
2740 if (RefPtr element = dynamicDowncast<HTMLSelectElement>(*focusedElement)) {
2741#if USE(UICONTEXTMENU)(defined 1 && 1)
2742 static bool selectPickerUsesMenu = linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::HasUIContextMenuInteraction);
2743#else
2744 bool selectPickerUsesMenu = false;
2745#endif
2746
2747 information.elementType = InputType::Select;
2748
2749 RefPtr<ContainerNode> parentGroup;
2750 int parentGroupID = 0;
2751 for (auto& item : element->listItems()) {
2752 if (RefPtr optionElement = dynamicDowncast<HTMLOptionElement>(item.get())) {
2753 if (parentGroup && optionElement->parentNode() != parentGroup) {
2754 parentGroupID++;
2755 parentGroup = nullptr;
2756 information.selectOptions.append(OptionItem(emptyString(), true, false, false, parentGroupID));
2757 }
2758
2759 information.selectOptions.append(OptionItem(optionElement->label(), false, optionElement->selected(), optionElement->hasAttributeWithoutSynchronization(WebCore::HTMLNames::disabledAttr), parentGroupID));
2760 } else if (RefPtr optGroupElement = dynamicDowncast<HTMLOptGroupElement>(item.get())) {
2761 if (selectPickerUsesMenu)
2762 parentGroup = optGroupElement;
2763
2764 parentGroupID++;
2765 information.selectOptions.append(OptionItem(optGroupElement->groupLabelText(), true, false, optGroupElement->hasAttributeWithoutSynchronization(WebCore::HTMLNames::disabledAttr), parentGroupID));
2766 } else if (selectPickerUsesMenu && is<HTMLHRElement>(item.get())) {
2767 parentGroupID++;
2768 parentGroup = nullptr;
2769 information.selectOptions.append(OptionItem(emptyString(), true, false, false, parentGroupID));
2770 }
2771 }
2772 information.selectedIndex = element->selectedIndex();
2773 information.isMultiSelect = element->multiple();
2774 information.usesBaseAppearancePicker = element->usesBaseAppearancePicker();
2775 } else if (RefPtr element = dynamicDowncast<HTMLTextAreaElement>(*focusedElement)) {
2776 information.autocapitalizeType = element->autocapitalizeType();
2777 information.isAutocorrect = element->shouldAutocorrect();
2778 information.elementType = InputType::TextArea;
2779 information.isReadOnly = element->isReadOnly();
2780 information.value = element->value();
2781 information.hasPlainText = !information.value.isEmpty();
2782 information.autofillFieldName = WebCore::toAutofillFieldName(element->autofillData().fieldName);
2783 information.nonAutofillCredentialType = element->autofillData().nonAutofillCredentialType;
2784 information.placeholder = element->attributeWithoutSynchronization(HTMLNames::placeholderAttr);
2785 information.inputMode = element->canonicalInputMode();
2786 information.enterKeyHint = element->canonicalEnterKeyHint();
2787 } else if (RefPtr element = dynamicDowncast<HTMLInputElement>(*focusedElement)) {
2788 RefPtr form = element->form();
2789 if (form)
2790 information.formAction = form->getURLAttribute(WebCore::HTMLNames::actionAttr).string();
2791 if (auto autofillElements = WebCore::AutofillElements::computeAutofillElements(*element)) {
2792 information.acceptsAutofilledLoginCredentials = true;
2793 information.isAutofillableUsernameField = autofillElements->username() == focusedElement;
2794 }
2795 information.representingPageURL = protect(element->document())->urlForBindings();
2796 information.autocapitalizeType = element->autocapitalizeType();
2797 information.isAutocorrect = element->shouldAutocorrect();
2798 information.placeholder = element->attributeWithoutSynchronization(HTMLNames::placeholderAttr);
2799 information.hasEverBeenPasswordField = element->hasEverBeenPasswordField();
2800 if (element->isPasswordField())
2801 information.elementType = InputType::Password;
2802 else if (element->isSearchField())
2803 information.elementType = InputType::Search;
2804 else if (element->isEmailField())
2805 information.elementType = InputType::Email;
2806 else if (element->isTelephoneField())
2807 information.elementType = InputType::Phone;
2808 else if (element->isNumberField())
2809 information.elementType = element->getAttribute(HTMLNames::patternAttr) == "\\d*"_s || element->getAttribute(HTMLNames::patternAttr) == "[0-9]*"_s ? InputType::NumberPad : InputType::Number;
2810 else if (element->isDateTimeLocalField())
2811 information.elementType = InputType::DateTimeLocal;
2812 else if (element->isDateField())
2813 information.elementType = InputType::Date;
2814 else if (element->isTimeField())
2815 information.elementType = InputType::Time;
2816 else if (element->isWeekField())
2817 information.elementType = InputType::Week;
2818 else if (element->isMonthField())
2819 information.elementType = InputType::Month;
2820 else if (element->isURLField())
2821 information.elementType = InputType::URL;
2822 else if (element->isText()) {
2823 const AtomString& pattern = element->attributeWithoutSynchronization(HTMLNames::patternAttr);
2824 if (pattern == "\\d*"_s || pattern == "[0-9]*"_s)
2825 information.elementType = InputType::NumberPad;
2826 else {
2827 information.elementType = InputType::Text;
2828 if (!information.formAction.isEmpty()
2829 && (element->getNameAttribute().contains("search"_s) || element->getIdAttribute().contains("search"_s) || element->attributeWithoutSynchronization(HTMLNames::titleAttr).contains("search"_s)))
2830 information.elementType = InputType::Search;
2831 }
2832 }
2833 else if (element->isColorControl()) {
2834 information.elementType = InputType::Color;
2835 information.colorValue = element->valueAsColor();
2836 information.supportsAlpha = element->alpha() ? WebKit::ColorControlSupportsAlpha::Yes : WebKit::ColorControlSupportsAlpha::No;
2837 information.suggestedColors = element->suggestedColors();
2838 }
2839
2840 information.isFocusingWithDataListDropdown = element->isFocusingWithDataListDropdown();
2841 information.hasSuggestions = !!element->list();
2842 information.inputMode = element->canonicalInputMode();
2843 information.enterKeyHint = element->canonicalEnterKeyHint();
2844 information.isReadOnly = element->isReadOnly();
2845 information.value = element->value();
2846 information.hasPlainText = !information.value.isEmpty();
2847 information.valueAsNumber = element->valueAsNumber();
2848 information.autofillFieldName = WebCore::toAutofillFieldName(element->autofillData().fieldName);
2849 information.nonAutofillCredentialType = element->autofillData().nonAutofillCredentialType;
2850 } else if (focusedElement->hasEditableStyle()) {
2851 information.elementType = InputType::ContentEditable;
2852 if (RefPtr focusedHTMLElement = dynamicDowncast<HTMLElement>(*focusedElement)) {
2853 information.isAutocorrect = focusedHTMLElement->shouldAutocorrect();
2854 information.autocapitalizeType = focusedHTMLElement->autocapitalizeType();
2855 information.inputMode = focusedHTMLElement->canonicalInputMode();
2856 information.enterKeyHint = focusedHTMLElement->canonicalEnterKeyHint();
2857 information.shouldSynthesizeKeyEventsForEditing = true;
2858 } else {
2859 information.isAutocorrect = true;
2860 information.autocapitalizeType = WebCore::AutocapitalizeType::Default;
2861 }
2862 information.isReadOnly = false;
2863 information.hasPlainText = hasAnyPlainText(makeRangeSelectingNodeContents(*focusedElement));
2864 }
2865
2866 Ref focusedElementDocument = focusedElement->document();
2867
2868 if (focusedElementDocument->quirks().shouldSuppressAutocorrectionAndAutocapitalizationInHiddenEditableAreas() && isTransparentOrFullyClipped(*focusedElement)) {
2869 information.autocapitalizeType = WebCore::AutocapitalizeType::None;
2870 information.isAutocorrect = false;
2871 }
2872
2873 auto& quirks = focusedElementDocument->quirks();
2874 information.shouldAvoidResizingWhenInputViewBoundsChange = quirks.shouldAvoidResizingWhenInputViewBoundsChange();
2875 information.shouldAvoidScrollingWhenFocusedContentIsVisible = quirks.shouldAvoidScrollingWhenFocusedContentIsVisible();
2876 information.shouldUseLegacySelectPopoverDismissalBehaviorInDataActivation = quirks.shouldUseLegacySelectPopoverDismissalBehaviorInDataActivation();
2877 information.shouldHideSoftTopScrollEdgeEffect = quirks.shouldHideSoftTopScrollEdgeEffectDuringFocus(*focusedElement);
2878
2879 return information;
2880}
2881
2882void WebPage::autofillLoginCredentials(const String& username, const String& password)
2883{
2884 if (RefPtr input = dynamicDowncast<HTMLInputElement>(m_focusedElement)) {
2885 if (auto autofillElements = AutofillElements::computeAutofillElements(*input))
2886 autofillElements->autofill(username, password);
2887 }
2888}
2889
2890void WebPage::setViewportConfigurationViewLayoutSize(const FloatSize& size, double layoutSizeScaleFactorFromClient, double minimumEffectiveDeviceWidth)
2891{
2892 LOG_WITH_STREAM(VisibleRects, stream << "WebPage " << m_identifier << " setViewportConfigurationViewLayoutSize " << size << " layoutSizeScaleFactorFromClient " << layoutSizeScaleFactorFromClient << " minimumEffectiveDeviceWidth " << minimumEffectiveDeviceWidth)((void)0);
2893
2894 if (!m_viewportConfiguration.isKnownToLayOutWiderThanViewport())
2895 m_viewportConfiguration.setMinimumEffectiveDeviceWidthForShrinkToFit(0);
2896
2897 if (size.isZero() && mainFramePlugInRejectsZeroViewLayoutSizeUpdates())
2898 return;
2899
2900 bool mainFramePluginOverridesViewScale = mainFramePlugInDefersScalingToViewport();
2901
2902 m_baseViewportLayoutSizeScaleFactor = [&] {
2903 if (!m_page->settings().automaticallyAdjustsViewScaleUsingMinimumEffectiveDeviceWidth())
2904 return 1.0;
2905
2906 if (!minimumEffectiveDeviceWidth)
2907 return 1.0;
2908
2909 if (minimumEffectiveDeviceWidth >= size.width())
2910 return 1.0;
2911
2912 if (mainFramePluginOverridesViewScale)
2913 return 1.0;
2914
2915 return size.width() / minimumEffectiveDeviceWidth;
2916 }();
2917
2918 double layoutSizeScaleFactor = mainFramePluginOverridesViewScale ? 1.0 : layoutSizeScaleFactorFromClient * m_baseViewportLayoutSizeScaleFactor;
2919
2920 auto previousLayoutSizeScaleFactor = m_viewportConfiguration.layoutSizeScaleFactor();
2921 if (!m_viewportConfiguration.setViewLayoutSize(size, layoutSizeScaleFactor, minimumEffectiveDeviceWidth))
2922 return;
2923
2924 auto zoomToInitialScale = ZoomToInitialScale::No;
2925 auto newInitialScale = m_viewportConfiguration.initialScale();
2926 auto currentPageScaleFactor = pageScaleFactor();
2927 if (layoutSizeScaleFactor > previousLayoutSizeScaleFactor && newInitialScale > currentPageScaleFactor)
2928 zoomToInitialScale = ZoomToInitialScale::Yes;
2929 else if (layoutSizeScaleFactor < previousLayoutSizeScaleFactor && newInitialScale < currentPageScaleFactor)
2930 zoomToInitialScale = ZoomToInitialScale::Yes;
2931
2932 viewportConfigurationChanged(zoomToInitialScale);
2933}
2934
2935void WebPage::setDeviceOrientation(IntDegrees deviceOrientation)
2936{
2937 if (deviceOrientation == m_deviceOrientation)
2938 return;
2939 m_deviceOrientation = deviceOrientation;
2940#if ENABLE(ORIENTATION_EVENTS)(defined 1 && 1)
2941 if (RefPtr localMainFrame = protect(m_page)->localMainFrame())
2942 localMainFrame->orientationChanged();
2943#endif
2944}
2945
2946void WebPage::setOverrideViewportArguments(const std::optional<WebCore::ViewportArguments>& arguments)
2947{
2948 protect(m_page)->setOverrideViewportArguments(arguments);
2949}
2950
2951void WebPage::dynamicViewportSizeUpdate(const DynamicViewportSizeUpdate& target)
2952{
2953 RefPtr localMainFrame = protect(m_page)->localMainFrame();
2954 if (!localMainFrame)
2955 return;
2956
2957 SetForScope dynamicSizeUpdateGuard(m_inDynamicSizeUpdate, true);
2958 // FIXME: this does not handle the cases where the content would change the content size or scroll position from JavaScript.
2959 // To handle those cases, we would need to redo this computation on every change until the next visible content rect update.
2960 LOG_WITH_STREAM(VisibleRects, stream << "\nWebPage::dynamicViewportSizeUpdate - viewLayoutSize " << target.viewLayoutSize << " targetUnobscuredRect " << target.unobscuredRect << " targetExposedContentRect " << target.exposedContentRect << " targetScale " << target.scale)((void)0);
2961
2962 Ref frameView = *localMainFrame->view();
2963 IntSize oldContentSize = frameView->contentsSize();
2964 float oldPageScaleFactor = m_page->pageScaleFactor();
2965 auto oldUnobscuredContentRect = frameView->unobscuredContentRect();
2966 bool wasAtInitialScale = scalesAreEssentiallyEqual(oldPageScaleFactor, m_viewportConfiguration.initialScale());
2967
2968 m_internals->dynamicSizeUpdateHistory.add(std::make_pair(oldContentSize, oldPageScaleFactor), frameView->scrollPosition());
2969
2970 RefPtr<Node> oldNodeAtCenter;
2971 double visibleHorizontalFraction = 1;
2972 float relativeHorizontalPositionInNodeAtCenter = 0;
2973 float relativeVerticalPositionInNodeAtCenter = 0;
2974 if (!shouldEnableViewportBehaviorsForResizableWindows()) {
2975 visibleHorizontalFraction = frameView->unobscuredContentSize().width() / oldContentSize.width();
2976 IntPoint unobscuredContentRectCenter = frameView->unobscuredContentRect().center();
2977
2978 HitTestResult hitTestResult = HitTestResult(unobscuredContentRectCenter);
2979
2980 if (RefPtr document = frameView->frame().document())
2981 document->hitTest(HitTestRequest(), hitTestResult);
2982
2983 if (RefPtr node = hitTestResult.innerNode()) {
2984 if (CheckedPtr renderer = node->renderer()) {
2985 Ref containingView = *node->document().frame()->view();
2986 FloatRect boundingBox = containingView->contentsToRootView(renderer->absoluteBoundingBoxRect(true));
2987 relativeHorizontalPositionInNodeAtCenter = (unobscuredContentRectCenter.x() - boundingBox.x()) / boundingBox.width();
2988 relativeVerticalPositionInNodeAtCenter = (unobscuredContentRectCenter.y() - boundingBox.y()) / boundingBox.height();
2989 oldNodeAtCenter = node;
2990 }
2991 }
2992 }
2993
2994 LOG_WITH_STREAM(VisibleRects, stream << "WebPage::dynamicViewportSizeUpdate setting view layout size to " << target.viewLayoutSize)((void)0);
2995 bool viewportChanged = m_viewportConfiguration.setIsKnownToLayOutWiderThanViewport(false);
2996 viewportChanged |= m_viewportConfiguration.setViewLayoutSize(target.viewLayoutSize, std::nullopt, target.minimumEffectiveDeviceWidth);
2997 if (viewportChanged)
2998 viewportConfigurationChanged();
2999
3000 IntSize newLayoutSize = m_viewportConfiguration.layoutSize();
3001
3002 if (setFixedLayoutSize(newLayoutSize))
3003 resetTextAutosizing();
3004
3005 setDefaultUnobscuredSize(target.maximumUnobscuredSize);
3006 setMinimumUnobscuredSize(target.minimumUnobscuredSize);
3007 setMaximumUnobscuredSize(target.maximumUnobscuredSize);
3008 protect(m_page)->setUnobscuredSafeAreaInsets(target.unobscuredSafeAreaInsets);
3009
3010 frameView->updateLayoutAndStyleIfNeededRecursive();
3011
3012 IntSize newContentSize = frameView->contentsSize();
3013
3014 bool scaleToFitContent = (!shouldEnableViewportBehaviorsForResizableWindows() || !wasAtInitialScale) && m_userHasChangedPageScaleFactor;
3015 double scale = scaleAfterViewportWidthChange(target.scale, scaleToFitContent, m_viewportConfiguration, target.unobscuredRectInScrollViewCoordinates.width(), newContentSize, oldContentSize, visibleHorizontalFraction);
3016 FloatRect newUnobscuredContentRect = target.unobscuredRect;
3017 FloatRect newExposedContentRect = target.exposedContentRect;
3018
3019 bool scaleChanged = !scalesAreEssentiallyEqual(scale, target.scale);
3020 if (scaleChanged) {
3021 // The target scale the UI is using cannot be reached by the content. We need to compute new targets based
3022 // on the viewport constraint and report everything back to the UIProcess.
3023
3024 // 1) Compute a new unobscured rect centered around the original one.
3025 double scaleDifference = target.scale / scale;
3026 double newUnobscuredRectWidth = target.unobscuredRect.width() * scaleDifference;
3027 double newUnobscuredRectHeight = target.unobscuredRect.height() * scaleDifference;
3028 double newUnobscuredRectX;
3029 double newUnobscuredRectY;
3030 if (shouldEnableViewportBehaviorsForResizableWindows()) {
3031 newUnobscuredRectX = oldUnobscuredContentRect.x();
3032 newUnobscuredRectY = oldUnobscuredContentRect.y();
3033 } else {
3034 newUnobscuredRectX = target.unobscuredRect.x() - (newUnobscuredRectWidth - target.unobscuredRect.width()) / 2;
3035 newUnobscuredRectY = target.unobscuredRect.y() - (newUnobscuredRectHeight - target.unobscuredRect.height()) / 2;
3036 }
3037 newUnobscuredContentRect = FloatRect(newUnobscuredRectX, newUnobscuredRectY, newUnobscuredRectWidth, newUnobscuredRectHeight);
3038
3039 // 2) Extend our new unobscuredRect by the obscured margins to get a new exposed rect.
3040 double obscuredTopMargin = (target.unobscuredRect.y() - target.exposedContentRect.y()) * scaleDifference;
3041 double obscuredLeftMargin = (target.unobscuredRect.x() - target.exposedContentRect.x()) * scaleDifference;
3042 double obscuredBottomMargin = (target.exposedContentRect.maxY() - target.unobscuredRect.maxY()) * scaleDifference;
3043 double obscuredRightMargin = (target.exposedContentRect.maxX() - target.unobscuredRect.maxX()) * scaleDifference;
3044 newExposedContentRect = FloatRect(newUnobscuredRectX - obscuredLeftMargin,
3045 newUnobscuredRectY - obscuredTopMargin,
3046 newUnobscuredRectWidth + obscuredLeftMargin + obscuredRightMargin,
3047 newUnobscuredRectHeight + obscuredTopMargin + obscuredBottomMargin);
3048 }
3049
3050 if (oldContentSize != newContentSize || scaleChanged) {
3051 // Snap the new unobscured rect back into the content rect.
3052 newUnobscuredContentRect.setWidth(std::min(static_cast<float>(newContentSize.width()), newUnobscuredContentRect.width()));
3053 newUnobscuredContentRect.setHeight(std::min(static_cast<float>(newContentSize.height()), newUnobscuredContentRect.height()));
3054
3055 bool positionWasRestoredFromSizeUpdateHistory = false;
3056 const auto& previousPosition = m_internals->dynamicSizeUpdateHistory.find(std::pair<IntSize, float>(newContentSize, scale));
3057 if (previousPosition != m_internals->dynamicSizeUpdateHistory.end()) {
3058 IntPoint restoredPosition = previousPosition->value;
3059 FloatPoint deltaPosition(restoredPosition.x() - newUnobscuredContentRect.x(), restoredPosition.y() - newUnobscuredContentRect.y());
3060 newUnobscuredContentRect.moveBy(deltaPosition);
3061 newExposedContentRect.moveBy(deltaPosition);
3062 positionWasRestoredFromSizeUpdateHistory = true;
3063 } else if (oldContentSize != newContentSize) {
3064 FloatPoint newRelativeContentCenter;
3065
3066 if (CheckedPtr renderer = oldNodeAtCenter ? oldNodeAtCenter->renderer() : nullptr) {
3067 Ref containingView = *oldNodeAtCenter->document().frame()->view();
3068 auto newBoundingBox = containingView->contentsToRootView(renderer->absoluteBoundingBoxRect(true));
3069 newRelativeContentCenter = FloatPoint(newBoundingBox.x() + relativeHorizontalPositionInNodeAtCenter * newBoundingBox.width(), newBoundingBox.y() + relativeVerticalPositionInNodeAtCenter * newBoundingBox.height());
3070 } else
3071 newRelativeContentCenter = relativeCenterAfterContentSizeChange(target.unobscuredRect, oldContentSize, newContentSize);
3072
3073 FloatPoint newUnobscuredContentRectCenter = newUnobscuredContentRect.center();
3074 FloatPoint positionDelta(newRelativeContentCenter.x() - newUnobscuredContentRectCenter.x(), newRelativeContentCenter.y() - newUnobscuredContentRectCenter.y());
3075 newUnobscuredContentRect.moveBy(positionDelta);
3076 newExposedContentRect.moveBy(positionDelta);
3077 }
3078
3079 // Make the top/bottom edges "sticky" within 1 pixel.
3080 if (!positionWasRestoredFromSizeUpdateHistory) {
3081 if (target.unobscuredRect.maxY() > oldContentSize.height() - 1) {
3082 float bottomVerticalPosition = newContentSize.height() - newUnobscuredContentRect.height();
3083 newUnobscuredContentRect.setY(bottomVerticalPosition);
3084 newExposedContentRect.setY(bottomVerticalPosition);
3085 }
3086 if (target.unobscuredRect.y() < 1) {
3087 newUnobscuredContentRect.setY(0);
3088 newExposedContentRect.setY(0);
3089 }
3090
3091 bool likelyResponsiveDesignViewport = newLayoutSize.width() == target.viewLayoutSize.width() && scalesAreEssentiallyEqual(scale, 1);
3092 bool contentBleedsOutsideLayoutWidth = newContentSize.width() > newLayoutSize.width();
3093 bool originalScrollPositionWasOnTheLeftEdge = target.unobscuredRect.x() <= 0;
3094 if (likelyResponsiveDesignViewport && contentBleedsOutsideLayoutWidth && originalScrollPositionWasOnTheLeftEdge) {
3095 // This is a special heuristics for "responsive" design with odd layout. It is quite common for responsive design
3096 // to have content "bleeding" outside of the minimal layout width, usually from an image or table larger than expected.
3097 // In those cases, the design usually does not adapt to the new width and remain at the newLayoutSize except for the
3098 // large boxes.
3099 // It is worth revisiting this special case as web developers get better with responsive design.
3100 newExposedContentRect.setX(0);
3101 newUnobscuredContentRect.setX(0);
3102 }
3103 }
3104
3105 float horizontalAdjustment = 0;
3106 if (newUnobscuredContentRect.maxX() > newContentSize.width())
3107 horizontalAdjustment -= newUnobscuredContentRect.maxX() - newContentSize.width();
3108 float verticalAdjustment = 0;
3109 if (newUnobscuredContentRect.maxY() > newContentSize.height())
3110 verticalAdjustment -= newUnobscuredContentRect.maxY() - newContentSize.height();
3111 if (newUnobscuredContentRect.x() < 0)
3112 horizontalAdjustment += - newUnobscuredContentRect.x();
3113 if (newUnobscuredContentRect.y() < 0)
3114 verticalAdjustment += - newUnobscuredContentRect.y();
3115
3116 FloatPoint adjustmentDelta(horizontalAdjustment, verticalAdjustment);
3117 newUnobscuredContentRect.moveBy(adjustmentDelta);
3118 newExposedContentRect.moveBy(adjustmentDelta);
3119 }
3120
3121 frameView->setScrollVelocity({ 0, 0, 0, MonotonicTime::now() });
3122
3123 IntPoint roundedUnobscuredContentRectPosition = roundedIntPoint(newUnobscuredContentRect.location());
3124 frameView->setUnobscuredContentSize(newUnobscuredContentRect.size());
3125 protect(m_drawingArea)->setExposedContentRect(newExposedContentRect);
3126
3127 scalePage(scale, roundedUnobscuredContentRectPosition);
3128
3129 frameView->updateLayoutAndStyleIfNeededRecursive();
3130
3131 // FIXME: Move settings from Frame to Frame and remove this check.
3132 auto& settings = frameView->frame().settings();
3133 LayoutRect documentRect = IntRect(frameView->scrollOrigin(), frameView->contentsSize());
3134 double heightExpansionFactor = m_disallowLayoutViewportHeightExpansionReasons.isEmpty() ? settings.layoutViewportHeightExpansionFactor() : 0;
3135 auto layoutViewportSize = LocalFrameView::expandedLayoutViewportSize(frameView->baseLayoutViewportSize(), LayoutSize(documentRect.size()), heightExpansionFactor);
3136 LayoutRect layoutViewportRect = LocalFrameView::computeUpdatedLayoutViewportRect(frameView->layoutViewportRect(), documentRect, LayoutSize(newUnobscuredContentRect.size()), LayoutRect(newUnobscuredContentRect), layoutViewportSize, frameView->minStableLayoutViewportOrigin(), frameView->maxStableLayoutViewportOrigin(), LayoutViewportConstraint::ConstrainedToDocumentRect);
3137 frameView->setLayoutViewportOverrideRect(layoutViewportRect);
3138 frameView->layoutOrVisualViewportChanged();
3139
3140 frameView->setCustomSizeForResizeEvent(expandedIntSize(target.unobscuredRectInScrollViewCoordinates.size()));
3141 setDeviceOrientation(target.deviceOrientation);
3142 frameView->setScrollOffset(roundedUnobscuredContentRectPosition);
3143
3144 protect(m_page)->isolatedUpdateRendering();
3145
3146#if ENABLE(VIEWPORT_RESIZING)(defined (defined 1 && 1) && (defined 1 &&
1))
3147 shrinkToFitContent();
3148#endif
3149
3150 protect(m_drawingArea)->triggerRenderingUpdate();
3151
3152 m_pendingDynamicViewportSizeUpdateID = target.identifier;
3153}
3154
3155void WebPage::resetViewportDefaultConfiguration(WebFrame* frame, bool hasMobileDocType)
3156{
3157 LOG_WITH_STREAM(VisibleRects, stream << "WebPage " << m_identifier << " resetViewportDefaultConfiguration")((void)0);
3158 if (m_useTestingViewportConfiguration) {
3159 m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::testingParameters());
3160 return;
3161 }
3162
3163 auto parametersForStandardFrame = [&] {
3164#if ENABLE(FULLSCREEN_API)(defined 1 && 1)
3165 if (m_isInFullscreenMode == IsInFullscreenMode::Yes)
3166 return m_viewportConfiguration.nativeWebpageParameters();
3167#endif
3168 if (shouldIgnoreMetaViewport())
3169 return m_viewportConfiguration.nativeWebpageParameters();
3170 return ViewportConfiguration::webpageParameters();
3171 };
3172
3173 RefPtr localFrame = frame->coreLocalFrame() ? frame->coreLocalFrame() : frame->provisionalFrame();
3174 ASSERT(localFrame)((void)0);
3175 if (!frame || !localFrame) {
3176 m_viewportConfiguration.setDefaultConfiguration(parametersForStandardFrame());
3177 return;
3178 }
3179
3180 RefPtr document = localFrame->document();
3181
3182 auto updateViewportConfigurationForMobileDocType = [this, protectedThis = Ref { *this }, document] {
3183 m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::xhtmlMobileParameters());
3184
3185 // Do not update the viewport arguments if they are already configured by the website.
3186 if (m_viewportConfiguration.viewportArguments().type == ViewportArguments::Type::ViewportMeta)
3187 return;
3188
3189 if (!document || !document->isViewportDocument())
3190 return;
3191
3192 // https://www.w3.org/TR/2016/WD-css-device-adapt-1-20160329/#intro
3193 // Certain DOCTYPEs (for instance XHTML Mobile Profile) are used to recognize mobile documents which are assumed
3194 // to be designed for handheld devices, hence using the viewport size as the initial containing block size.
3195 ViewportArguments viewportArguments { ViewportArguments::Type::ViewportMeta };
3196 viewportArguments.width = ViewportArguments::ValueDeviceWidth;
3197 viewportArguments.height = ViewportArguments::ValueDeviceHeight;
3198 viewportArguments.zoom = 1;
3199 document->setViewportArguments(viewportArguments);
3200 viewportPropertiesDidChange(viewportArguments);
3201 };
3202
3203 if (hasMobileDocType) {
3204 return updateViewportConfigurationForMobileDocType();
3205 }
3206
3207 bool configureWithParametersForStandardFrame = !document;
3208
3209 if (document) {
3210 if (document->isImageDocument())
3211 m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::imageDocumentParameters());
3212 else if (document->isTextDocument())
3213 m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::textDocumentParameters());
3214#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3215 else if (m_page && m_page->settings().unifiedPDFEnabled() && document->isPluginDocument())
3216 m_viewportConfiguration.setDefaultConfiguration(UnifiedPDFPlugin::viewportParameters());
3217#endif
3218 else
3219 configureWithParametersForStandardFrame = true;
3220 }
3221
3222 if (configureWithParametersForStandardFrame)
3223 return m_viewportConfiguration.setDefaultConfiguration(parametersForStandardFrame());
3224}
3225
3226#if ENABLE(TEXT_AUTOSIZING)(defined 1 && 1)
3227
3228void WebPage::updateTextAutosizingEnablementFromInitialScale(double initialScale)
3229{
3230 if (protect(*m_page)->settings().textAutosizingEnabledAtLargeInitialScale())
3231 return;
3232
3233 bool shouldEnable = initialScale <= 1;
3234 if (shouldEnable == m_page->settings().textAutosizingEnabled())
3235 return;
3236
3237 m_page->settings().setTextAutosizingEnabled(shouldEnable);
3238}
3239
3240void WebPage::resetIdempotentTextAutosizingIfNeeded(double previousInitialScale)
3241{
3242 if (!m_page->settings().textAutosizingEnabled() || !m_page->settings().textAutosizingUsesIdempotentMode())
3243 return;
3244
3245 const float minimumScaleChangeBeforeRecomputingTextAutosizing = 0.01;
3246 if (std::abs(previousInitialScale - m_page->initialScaleIgnoringContentSize()) < minimumScaleChangeBeforeRecomputingTextAutosizing)
3247 return;
3248
3249 if (m_page->initialScaleIgnoringContentSize() >= 1 && previousInitialScale >= 1)
3250 return;
3251
3252 RefPtr localMainFrame = protect(m_page)->localMainFrame();
3253 if (!localMainFrame)
3254 return;
3255
3256 if (!localMainFrame->view())
3257 return;
3258
3259 auto textAutoSizingDelay = [&] {
3260 Ref frameView = *localMainFrame->view();
3261 if (!frameView->isVisuallyNonEmpty()) {
3262 // We don't anticipate any painting after the next upcoming layout.
3263 const Seconds longTextAutoSizingDelayOnViewportChange = 100_ms;
3264 return longTextAutoSizingDelayOnViewportChange;
3265 }
3266 const Seconds defaultTextAutoSizingDelayOnViewportChange = 80_ms;
3267 return defaultTextAutoSizingDelayOnViewportChange;
3268 };
3269
3270 // We don't need to update text sizing eagerly. There might be multiple incoming dynamic viewport changes.
3271 m_textAutoSizingAdjustmentTimer.startOneShot(textAutoSizingDelay());
3272}
3273#endif // ENABLE(TEXT_AUTOSIZING)
3274
3275void WebPage::resetTextAutosizing()
3276{
3277#if ENABLE(TEXT_AUTOSIZING)(defined 1 && 1)
3278 for (RefPtr frame = &m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
3279 RefPtr localFrame = dynamicDowncast<LocalFrame>(frame.get());
3280 if (!localFrame)
3281 continue;
3282 RefPtr document = localFrame->document();
3283 if (!document || !document->renderView())
3284 continue;
3285 protect(document->renderView())->resetTextAutosizing();
3286 }
3287#endif
3288}
3289
3290#if ENABLE(VIEWPORT_RESIZING)(defined (defined 1 && 1) && (defined 1 &&
1))
3291
3292void WebPage::shrinkToFitContent(ZoomToInitialScale zoomToInitialScale)
3293{
3294 if (m_isClosed)
3295 return;
3296
3297 if (!m_page->settings().allowViewportShrinkToFitContent())
3298 return;
3299
3300 if (m_useTestingViewportConfiguration)
3301 return;
3302
3303 if (!shouldIgnoreMetaViewport())
3304 return;
3305
3306 if (!m_viewportConfiguration.viewportArguments().shrinkToFit)
3307 return;
3308
3309 if (m_viewportConfiguration.canIgnoreScalingConstraints())
3310 return;
3311
3312 RefPtr mainFrame = m_mainFrame->coreLocalFrame();
3313 if (!mainFrame)
3314 return;
3315
3316 RefPtr view = mainFrame->view();
3317 if (!view)
3318 return;
3319
3320 RefPtr mainDocument = mainFrame->document();
3321 if (!mainDocument)
3322 return;
3323
3324 mainDocument->updateLayout();
3325
3326 static const int toleratedHorizontalScrollingDistance = 20;
3327 static const int maximumExpandedLayoutWidth = 1280;
3328 static const int maximumContentWidthBeforeAvoidingShrinkToFit = 1920;
3329
3330 auto scaledViewWidth = [&] () -> int {
3331 return std::round(m_viewportConfiguration.viewLayoutSize().width() / m_viewportConfiguration.initialScale());
3332 };
3333
3334 int originalContentWidth = view->contentsWidth();
3335 int originalViewWidth = scaledViewWidth();
3336 int originalLayoutWidth = m_viewportConfiguration.layoutWidth();
3337 int originalHorizontalOverflowAmount = originalContentWidth - originalViewWidth;
3338 if (originalHorizontalOverflowAmount <= toleratedHorizontalScrollingDistance || originalLayoutWidth >= maximumExpandedLayoutWidth || originalContentWidth <= originalViewWidth || originalContentWidth > maximumContentWidthBeforeAvoidingShrinkToFit)
3339 return;
3340
3341 auto changeMinimumEffectiveDeviceWidth = [this, protectedThis = Ref { *this }, mainDocument] (int targetLayoutWidth) -> bool {
3342 if (m_viewportConfiguration.setMinimumEffectiveDeviceWidthForShrinkToFit(targetLayoutWidth)) {
3343 viewportConfigurationChanged();
3344 mainDocument->updateLayout();
3345 return true;
3346 }
3347 return false;
3348 };
3349
3350 m_viewportConfiguration.setIsKnownToLayOutWiderThanViewport(true);
3351 double originalMinimumDeviceWidth = m_viewportConfiguration.minimumEffectiveDeviceWidth();
3352 if (changeMinimumEffectiveDeviceWidth(std::min(maximumExpandedLayoutWidth, originalContentWidth)) && view->contentsWidth() - scaledViewWidth() > originalHorizontalOverflowAmount) {
3353 changeMinimumEffectiveDeviceWidth(originalMinimumDeviceWidth);
3354 m_viewportConfiguration.setIsKnownToLayOutWiderThanViewport(false);
3355 }
3356
3357 // FIXME (197429): Consider additionally logging an error message to the console if a responsive meta viewport tag was used.
3358 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
;
3359 viewportConfigurationChanged(zoomToInitialScale);
3360}
3361
3362#endif // ENABLE(VIEWPORT_RESIZING)
3363
3364bool WebPage::shouldIgnoreMetaViewport() const
3365{
3366 if (!m_page)
3367 return false;
3368
3369 RefPtr localMainFrame = protect(m_page)->localMainFrame();
3370 if (RefPtr mainDocument = localMainFrame ? localMainFrame->document() : nullptr) {
3371 RefPtr loader = mainDocument->loader();
3372 if (loader && loader->metaViewportPolicy() == WebCore::MetaViewportPolicy::Ignore)
3373 return true;
3374 }
3375
3376 return m_page->settings().shouldIgnoreMetaViewport();
3377}
3378
3379bool WebPage::mainFramePlugInDefersScalingToViewport() const
3380{
3381#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3382 if (RefPtr plugin = mainFramePlugIn(); plugin && !plugin->pluginHandlesPageScaleFactor())
3383 return true;
3384#endif
3385 return false;
3386}
3387
3388bool WebPage::mainFramePlugInRejectsZeroViewLayoutSizeUpdates() const
3389{
3390#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3391 if (RefPtr plugin = mainFramePlugIn(); plugin && !plugin->pluginHandlesPageScaleFactor())
3392 return true;
3393#endif
3394 return false;
3395}
3396
3397bool WebPage::shouldEnableViewportBehaviorsForResizableWindows() const
3398{
3399#if HAVE(UIKIT_RESIZABLE_WINDOWS)(defined 1 && 1)
3400 return shouldIgnoreMetaViewport() && m_isWindowResizingEnabled && !mainFramePlugInDefersScalingToViewport();
3401#else
3402 return false;
3403#endif
3404}
3405
3406void WebPage::viewportConfigurationChanged(ZoomToInitialScale zoomToInitialScale)
3407{
3408 double initialScale = m_viewportConfiguration.initialScale();
3409 double initialScaleIgnoringContentSize = m_viewportConfiguration.initialScaleIgnoringContentSize();
3410#if ENABLE(TEXT_AUTOSIZING)(defined 1 && 1)
3411 double previousInitialScaleIgnoringContentSize = m_page->initialScaleIgnoringContentSize();
3412 protect(m_page)->setInitialScaleIgnoringContentSize(initialScaleIgnoringContentSize);
3413 resetIdempotentTextAutosizingIfNeeded(previousInitialScaleIgnoringContentSize);
3414 updateTextAutosizingEnablementFromInitialScale(initialScale);
3415#endif
3416 if (setFixedLayoutSize(m_viewportConfiguration.layoutSize()))
3417 resetTextAutosizing();
3418
3419 auto minimumScale = m_viewportConfiguration.minimumScale();
3420 auto previousMinimumScale = m_previousViewportConfigurationMinimumScale.value_or(minimumScale);
3421 auto updatePreviousMinimumScale = makeScopeExit([&] {
3422 m_previousViewportConfigurationMinimumScale = minimumScale;
3423 });
3424
3425 double scale = [&] {
3426 if (!m_userHasChangedPageScaleFactor || zoomToInitialScale == ZoomToInitialScale::Yes)
3427 return initialScale;
3428
3429 auto currentScale = pageScaleFactor();
3430 if (scalesAreEssentiallyEqual(previousMinimumScale, currentScale))
3431 return minimumScale;
3432
3433 return std::max(std::min(currentScale, m_viewportConfiguration.maximumScale()), minimumScale);
3434 }();
3435
3436 LOG_WITH_STREAM(VisibleRects, stream << "WebPage " << m_identifier << " viewportConfigurationChanged - setting zoomedOutPageScaleFactor to " << minimumScale << " and scale to " << scale)((void)0);
3437
3438 protect(m_page)->setZoomedOutPageScaleFactor(minimumScale);
3439
3440 updateSizeForCSSDefaultViewportUnits();
3441 updateSizeForCSSSmallViewportUnits();
3442 updateSizeForCSSLargeViewportUnits();
3443
3444 RefPtr mainFrameView = this->localMainFrameView();
3445 if (!mainFrameView) {
3446 // FIXME: This is hit in some site isolation tests on iOS. Investigate and fix. <rdar://116201382>
3447 return;
3448 }
3449
3450 Ref frameView = *mainFrameView;
3451 IntPoint scrollPosition = frameView->scrollPosition();
3452 if (!m_hasReceivedVisibleContentRectsAfterDidCommitLoad) {
3453 FloatSize minimumLayoutSizeInScrollViewCoordinates = m_viewportConfiguration.viewLayoutSize();
3454 minimumLayoutSizeInScrollViewCoordinates.scale(1 / scale);
3455 IntSize minimumLayoutSizeInDocumentCoordinates = roundedIntSize(minimumLayoutSizeInScrollViewCoordinates);
3456 frameView->setUnobscuredContentSize(minimumLayoutSizeInDocumentCoordinates);
3457 frameView->setScrollVelocity({ 0, 0, 0, MonotonicTime::now() });
3458
3459 // FIXME: We could send down the obscured margins to find a better exposed rect and unobscured rect.
3460 // It is not a big deal at the moment because the tile coverage will always extend past the obscured bottom inset.
3461 if (!m_hasRestoredExposedContentRectAfterDidCommitLoad)
3462 protect(m_drawingArea)->setExposedContentRect(FloatRect(scrollPosition, minimumLayoutSizeInDocumentCoordinates));
3463 }
3464 scalePage(scale, scrollPosition);
3465
3466 if (!m_hasReceivedVisibleContentRectsAfterDidCommitLoad) {
3467 // This takes scale into account, so do after the scale change.
3468 frameView->setCustomFixedPositionLayoutRect(enclosingIntRect(frameView->viewportConstrainedObjectsRect()));
3469
3470 frameView->setCustomSizeForResizeEvent(expandedIntSize(m_viewportConfiguration.minimumLayoutSize()));
3471 }
3472}
3473
3474void WebPage::applicationWillResignActive()
3475{
3476 [[NSNotificationCenter defaultCenter] postNotificationName:protect(WebUIApplicationWillResignActiveNotification).get() object:nilnullptr];
3477
3478 // FIXME(224775): Move to WebProcess
3479 if (RefPtr manager = mediaSessionManagerIfExists())
3480 manager->applicationWillBecomeInactive();
3481
3482 if (m_page)
3483 protect(m_page)->applicationWillResignActive();
3484}
3485
3486void WebPage::applicationDidEnterBackground(bool isSuspendedUnderLock)
3487{
3488 [[NSNotificationCenter defaultCenter] postNotificationName:protect(WebUIApplicationDidEnterBackgroundNotification).get() object:nilnullptr userInfo:@{@"isSuspendedUnderLock": @(isSuspendedUnderLock)}];
3489
3490 m_isSuspendedUnderLock = isSuspendedUnderLock;
3491 if (!m_backgroundTextExtractionEnabled)
3492 freezeLayerTree(LayerTreeFreezeReason::BackgroundApplication);
3493
3494 // FIXME(224775): Move to WebProcess
3495 if (RefPtr manager = mediaSessionManagerIfExists())
3496 manager->applicationDidEnterBackground(isSuspendedUnderLock);
3497
3498 if (m_page)
3499 protect(m_page)->applicationDidEnterBackground();
3500}
3501
3502void WebPage::applicationDidFinishSnapshottingAfterEnteringBackground()
3503{
3504 markLayersVolatile();
3505}
3506
3507void WebPage::applicationWillEnterForeground(bool isSuspendedUnderLock)
3508{
3509 m_isSuspendedUnderLock = false;
3510 cancelMarkLayersVolatile();
3511
3512 if (!m_backgroundTextExtractionEnabled)
3513 unfreezeLayerTree(LayerTreeFreezeReason::BackgroundApplication);
3514
3515 [[NSNotificationCenter defaultCenter] postNotificationName:protect(WebUIApplicationWillEnterForegroundNotification).get() object:nilnullptr userInfo:@{@"isSuspendedUnderLock": @(isSuspendedUnderLock)}];
3516
3517 // FIXME(224775): Move to WebProcess
3518 if (RefPtr manager = mediaSessionManagerIfExists())
3519 manager->applicationWillEnterForeground(isSuspendedUnderLock);
3520
3521 if (m_page)
3522 protect(m_page)->applicationWillEnterForeground();
3523}
3524
3525void WebPage::applicationDidBecomeActive()
3526{
3527 [[NSNotificationCenter defaultCenter] postNotificationName:protect(WebUIApplicationDidBecomeActiveNotification).get() object:nilnullptr];
3528
3529 // FIXME(224775): Move to WebProcess
3530 if (RefPtr manager = mediaSessionManagerIfExists())
3531 manager->applicationDidBecomeActive();
3532
3533 if (m_page)
3534 protect(m_page)->applicationDidBecomeActive();
3535}
3536
3537void WebPage::applicationDidEnterBackgroundForMedia(bool isSuspendedUnderLock)
3538{
3539 if (RefPtr manager = mediaSessionManagerIfExists())
3540 manager->applicationDidEnterBackground(isSuspendedUnderLock);
3541}
3542
3543void WebPage::applicationWillEnterForegroundForMedia(bool isSuspendedUnderLock)
3544{
3545 if (RefPtr manager = mediaSessionManagerIfExists())
3546 manager->applicationWillEnterForeground(isSuspendedUnderLock);
3547}
3548
3549static inline void adjustVelocityDataForBoundedScale(VelocityData& velocityData, double exposedRectScale, double minimumScale, double maximumScale)
3550{
3551 if (velocityData.scaleChangeRate) {
3552 velocityData.horizontalVelocity = 0;
3553 velocityData.verticalVelocity = 0;
3554 }
3555
3556 if (exposedRectScale >= maximumScale || exposedRectScale <= minimumScale || scalesAreEssentiallyEqual(exposedRectScale, minimumScale) || scalesAreEssentiallyEqual(exposedRectScale, maximumScale))
3557 velocityData.scaleChangeRate = 0;
3558}
3559
3560std::optional<float> WebPage::scaleFromUIProcess(const VisibleContentRectUpdateInfo& visibleContentRectUpdateInfo) const
3561{
3562 auto transactionIDForLastScaleFromUIProcess = visibleContentRectUpdateInfo.lastLayerTreeTransactionID();
3563 if (m_internals->lastTransactionIDWithScaleChange && m_internals->lastTransactionIDWithScaleChange->greaterThanSameProcess(transactionIDForLastScaleFromUIProcess))
3564 return std::nullopt;
3565
3566 float scaleFromUIProcess = visibleContentRectUpdateInfo.scale();
3567 float currentScale = m_page->pageScaleFactor();
3568
3569 double scaleNoiseThreshold = 0.005;
3570 if (!m_isInStableState && std::abs(scaleFromUIProcess - currentScale) < scaleNoiseThreshold) {
3571 // Tiny changes of scale during interactive zoom cause content to jump by one pixel, creating
3572 // visual noise. We filter those useless updates.
3573 scaleFromUIProcess = currentScale;
3574 }
3575
3576 scaleFromUIProcess = std::min<float>(m_viewportConfiguration.maximumScale(), std::max<float>(m_viewportConfiguration.minimumScale(), scaleFromUIProcess));
3577 if (scalesAreEssentiallyEqual(currentScale, scaleFromUIProcess))
3578 return std::nullopt;
3579
3580 return scaleFromUIProcess;
3581}
3582
3583static bool selectionIsInsideFixedPositionContainer(LocalFrame& frame)
3584{
3585 auto& selection = frame.selection().selection();
3586 if (selection.isNone())
3587 return false;
3588
3589 bool isInsideFixedPosition = false;
3590 if (selection.isCaret()) {
3591 protect(frame.selection())->absoluteCaretBounds(&isInsideFixedPosition);
3592 return isInsideFixedPosition;
3593 }
3594
3595 selection.visibleStart().absoluteCaretBounds(&isInsideFixedPosition);
3596 if (isInsideFixedPosition)
3597 return true;
3598
3599 selection.visibleEnd().absoluteCaretBounds(&isInsideFixedPosition);
3600 return isInsideFixedPosition;
3601}
3602
3603void WebPage::updateVisibleContentRects(const VisibleContentRectUpdateInfo& visibleContentRectUpdateInfo, MonotonicTime oldestTimestamp)
3604{
3605 LOG_WITH_STREAM(VisibleRects, stream << "\nWebPage " << m_identifier << " updateVisibleContentRects " << visibleContentRectUpdateInfo)((void)0);
3606
3607 // Skip any VisibleContentRectUpdate that have been queued before DidCommitLoad suppresses the updates in the UIProcess.
3608 if (m_mainFrame->firstLayerTreeTransactionIDAfterDidCommitLoad() && visibleContentRectUpdateInfo.lastLayerTreeTransactionID().lessThanSameProcess(*m_mainFrame->firstLayerTreeTransactionIDAfterDidCommitLoad()) && !visibleContentRectUpdateInfo.isFirstUpdateForNewViewSize())
3609 return;
3610
3611 m_hasReceivedVisibleContentRectsAfterDidCommitLoad = true;
3612 m_isInStableState = visibleContentRectUpdateInfo.inStableState();
3613
3614 auto scaleFromUIProcess = this->scaleFromUIProcess(visibleContentRectUpdateInfo);
3615
3616 // Skip progressively redrawing tiles if pinch-zooming while the system is under memory pressure.
3617 if (scaleFromUIProcess && !m_isInStableState && MemoryPressureHandler::singleton().isUnderMemoryPressure())
3618 return;
3619
3620 if (m_isInStableState)
3621 m_hasStablePageScaleFactor = true;
3622 else {
3623 if (!m_oldestNonStableUpdateVisibleContentRectsTimestamp)
3624 m_oldestNonStableUpdateVisibleContentRectsTimestamp = oldestTimestamp;
3625 }
3626
3627 float scaleToUse = scaleFromUIProcess.value_or(m_page->pageScaleFactor());
3628 FloatRect exposedContentRect = visibleContentRectUpdateInfo.exposedContentRect();
3629 FloatRect adjustedExposedContentRect = adjustExposedRectForNewScale(exposedContentRect, visibleContentRectUpdateInfo.scale(), scaleToUse);
3630 protect(m_drawingArea)->setExposedContentRect(adjustedExposedContentRect);
3631 RefPtr localMainFrame = protect(m_page)->localMainFrame();
3632 if (!localMainFrame)
3633 return;
3634 RefPtr frameView = *localMainFrame->view();
3635
3636 if (RefPtr scrollingCoordinator = this->scrollingCoordinator()) {
3637 Ref remoteScrollingCoordinator = downcast<RemoteScrollingCoordinator>(*scrollingCoordinator);
3638 if (auto mainFrameScrollingNodeID = frameView->scrollingNodeID()) {
3639 if (visibleContentRectUpdateInfo.viewStability().contains(ViewStabilityFlag::ScrollViewRubberBanding))
3640 remoteScrollingCoordinator->addNodeWithActiveRubberBanding(*mainFrameScrollingNodeID);
3641 else
3642 remoteScrollingCoordinator->removeNodeWithActiveRubberBanding(*mainFrameScrollingNodeID);
3643 }
3644 }
3645
3646 auto layoutViewportRect = visibleContentRectUpdateInfo.layoutViewportRect();
3647 auto unobscuredContentRect = visibleContentRectUpdateInfo.unobscuredContentRect();
3648 auto scrollPosition = roundedIntPoint(unobscuredContentRect.location());
3649
3650 // Computation of layoutViewportRect is done in LayoutUnits which loses some precision, so test with an epsilon.
3651 // FIXME (302123): The loss of precision when converting floating point values to LayoutUnit does not, by itself, explain
3652 // the differences between the `layoutViewportRect` and `unobscuredContentRect`'s locations. While scrolling on iOS,
3653 // the absolute differences can sometimes exceed 3px, which is well over this fractional error threshold.
3654 // For now, we maintain behavior shipped in iOS 26 by snapping to the unobscured content rect location as long as
3655 // the difference is fairly small (~45 px).
3656 static constexpr auto maxEpsilon = 45.0;
3657 static constexpr auto epsilonRatio = 1.0 / (2 * kFixedPointDenominator);
3658 auto unobscuredContentRectLocation = unobscuredContentRect.location();
3659 auto epsilonX = std::min(maxEpsilon, epsilonRatio * std::abs(unobscuredContentRectLocation.x()));
3660 auto epsilonY = std::min(maxEpsilon, epsilonRatio * std::abs(unobscuredContentRectLocation.y()));
3661 auto layoutViewportRectLocation = layoutViewportRect.location();
3662 if (std::abs(unobscuredContentRectLocation.x() - layoutViewportRectLocation.x()) <= epsilonX && std::abs(unobscuredContentRectLocation.y() - layoutViewportRectLocation.y()) <= epsilonY)
3663 layoutViewportRect.setLocation(scrollPosition);
3664
3665 bool pageHasBeenScaledSinceLastLayerTreeCommitThatChangedPageScale = ([&] {
3666 if (!m_internals->lastLayerTreeTransactionIdAndPageScaleBeforeScalingPage)
3667 return false;
3668
3669 if (scalesAreEssentiallyEqual(scaleToUse, m_page->pageScaleFactor()))
3670 return false;
3671
3672 auto [transactionIdBeforeScalingPage, scaleBeforeScalingPage] = *m_internals->lastLayerTreeTransactionIdAndPageScaleBeforeScalingPage;
3673 if (!scalesAreEssentiallyEqual(scaleBeforeScalingPage, scaleToUse))
3674 return false;
3675
3676 return transactionIdBeforeScalingPage.greaterThanOrEqualSameProcess( visibleContentRectUpdateInfo.lastLayerTreeTransactionID());
3677 })();
3678
3679 if (!pageHasBeenScaledSinceLastLayerTreeCommitThatChangedPageScale) {
3680 bool shouldSetCorePageScale = [this, protectedThis = Ref { *this }] {
3681#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3682 RefPtr pluginView = mainFramePlugIn();
3683 if (!pluginView)
3684 return true;
3685 return !pluginView->pluginHandlesPageScaleFactor();
3686#else
3687 UNUSED_PARAM(this)(void)this;
3688 return true;
3689#endif
3690 }();
3691
3692 auto setCorePageScaleFactor = [this, protectedThis = Ref { *this }](float scale, const auto& origin, bool inStableState) {
3693 m_page->setPageScaleFactor(scale, origin, inStableState);
3694#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3695 if (RefPtr pluginView = mainFramePlugIn())
3696 pluginView->mainFramePageScaleFactorDidChange();
3697#endif
3698 };
3699
3700 bool hasSetPageScale = false;
3701 if (scaleFromUIProcess) {
3702 m_scaleWasSetByUIProcess = true;
3703 m_hasStablePageScaleFactor = m_isInStableState;
3704
3705 m_internals->dynamicSizeUpdateHistory.clear();
3706
3707 if (shouldSetCorePageScale)
3708 setCorePageScaleFactor(scaleFromUIProcess.value(), scrollPosition, m_isInStableState);
3709
3710 hasSetPageScale = true;
3711 send(Messages::WebPageProxy::PageScaleFactorDidChange(scaleFromUIProcess.value()));
3712 }
3713
3714 if (!hasSetPageScale && m_isInStableState && shouldSetCorePageScale)
3715 setCorePageScaleFactor(scaleToUse, scrollPosition, true);
3716 }
3717
3718 if (scrollPosition != frameView->scrollPosition())
3719 m_internals->dynamicSizeUpdateHistory.clear();
3720
3721 if (m_viewportConfiguration.setCanIgnoreScalingConstraints(visibleContentRectUpdateInfo.allowShrinkToFit()))
3722 viewportConfigurationChanged();
3723
3724 double minimumEffectiveDeviceWidthWhenIgnoringScalingConstraints = ([&] {
3725 RefPtr document = localMainFrame->document();
3726 if (!document)
3727 return 0;
3728
3729 if (!document->quirks().shouldLayOutAtMinimumWindowWidthWhenIgnoringScalingConstraints())
3730 return 0;
3731
3732 // This value is chosen to be close to the minimum width of a Safari window on macOS.
3733 return 500;
3734 })();
3735
3736 if (m_viewportConfiguration.setMinimumEffectiveDeviceWidthWhenIgnoringScalingConstraints(minimumEffectiveDeviceWidthWhenIgnoringScalingConstraints))
3737 viewportConfigurationChanged();
3738
3739 frameView->clearObscuredInsetsAdjustmentsIfNeeded();
3740 frameView->setUnobscuredContentSize(unobscuredContentRect.size());
3741 Ref page = *m_page;
3742 page->setContentInsets(visibleContentRectUpdateInfo.contentInsets());
3743 page->setObscuredInsets(visibleContentRectUpdateInfo.obscuredInsets());
3744 page->setUnobscuredSafeAreaInsets(visibleContentRectUpdateInfo.unobscuredSafeAreaInsets());
3745 page->setEnclosedInScrollableAncestorView(visibleContentRectUpdateInfo.enclosedInScrollableAncestorView());
3746
3747 VelocityData scrollVelocity = visibleContentRectUpdateInfo.scrollVelocity();
3748 adjustVelocityDataForBoundedScale(scrollVelocity, visibleContentRectUpdateInfo.scale(), m_viewportConfiguration.minimumScale(), m_viewportConfiguration.maximumScale());
3749 frameView->setScrollVelocity(scrollVelocity);
3750
3751 bool visualViewportChanged = unobscuredContentRect != visibleContentRectUpdateInfo.unobscuredContentRectRespectingInputViewBounds();
3752 if (visualViewportChanged)
3753 frameView->setVisualViewportOverrideRect(LayoutRect(visibleContentRectUpdateInfo.unobscuredContentRectRespectingInputViewBounds()));
3754 else if (m_isInStableState) {
3755 frameView->setVisualViewportOverrideRect(std::nullopt);
3756 visualViewportChanged = true;
3757 }
3758
3759 bool isChangingObscuredInsetsInteractively = visibleContentRectUpdateInfo.viewStability().contains(ViewStabilityFlag::ChangingObscuredInsetsInteractively);
3760 bool shouldPerformLayout = m_isInStableState && !isChangingObscuredInsetsInteractively;
3761
3762 LOG_WITH_STREAM(VisibleRects, stream << "WebPage::updateVisibleContentRects - setLayoutViewportOverrideRect " << layoutViewportRect)((void)0);
3763 frameView->setLayoutViewportOverrideRect(LayoutRect(layoutViewportRect), shouldPerformLayout ? LocalFrameView::TriggerLayoutOrNot::Yes : LocalFrameView::TriggerLayoutOrNot::No);
3764
3765 if (m_isInStableState) {
3766 if (selectionIsInsideFixedPositionContainer(*localMainFrame)) {
3767 // Ensure that the next layer tree commit contains up-to-date caret/selection rects.
3768 frameView->frame().selection().setCaretRectNeedsUpdate();
3769 scheduleFullEditorStateUpdate();
3770 }
3771 }
3772
3773 if (visualViewportChanged)
3774 frameView->layoutOrVisualViewportChanged();
3775
3776 if (!isChangingObscuredInsetsInteractively)
3777 frameView->setCustomSizeForResizeEvent(expandedIntSize(visibleContentRectUpdateInfo.unobscuredRectInScrollViewCoordinates().size()));
3778
3779 if (RefPtr scrollingCoordinator = this->scrollingCoordinator()) {
3780 auto viewportStability = ViewportRectStability::Stable;
3781 auto layerAction = ScrollingLayerPositionAction::Sync;
3782
3783 if (isChangingObscuredInsetsInteractively) {
3784 viewportStability = ViewportRectStability::ChangingObscuredInsetsInteractively;
3785 layerAction = ScrollingLayerPositionAction::SetApproximate;
3786 } else if (!m_isInStableState) {
3787 viewportStability = ViewportRectStability::Unstable;
3788 layerAction = ScrollingLayerPositionAction::SetApproximate;
3789 }
3790
3791 auto mainFrameScrollingNodeID = frameView->scrollingNodeID();
3792 if (!mainFrameScrollingNodeID) {
3793 ASSERT_NOT_REACHED()((void)0);
3794 return;
3795 }
3796
3797 auto scrollUpdate = ScrollUpdate {
3798 .nodeID = *mainFrameScrollingNodeID,
3799 .scrollPosition = scrollPosition,
3800 .data = ScrollUpdateData {
3801 .updateType = ScrollUpdateType::PositionUpdate,
3802 .updateLayerPositionAction = layerAction,
3803 .layoutViewportOrigin = visibleContentRectUpdateInfo.layoutViewportRect().location()
3804 }
3805 };
3806
3807 // We don't actually know that these are user scrolls; we get here for all kinds of state changes.
3808 scrollingCoordinator->applyScrollUpdate(WTF::move(scrollUpdate), ScrollType::User, viewportStability);
3809
3810 if (visibleContentRectUpdateInfo.needsScrollend() && frameView->scrollingNodeID()) {
3811 auto scrollUpdate = ScrollUpdate {
3812 .nodeID = *frameView->scrollingNodeID(),
3813 .scrollPosition = { },
3814 .data = ScrollUpdateData {
3815 .updateType = ScrollUpdateType::WheelEventScrollDidEnd,
3816 }
3817 };
3818 scrollingCoordinator->applyScrollUpdate(WTF::move(scrollUpdate), ScrollType::User);
3819 }
3820 }
3821}
3822
3823void WebPage::scheduleLayoutViewportHeightExpansionUpdate()
3824{
3825 if (!m_page->settings().layoutViewportHeightExpansionFactor())
3826 return;
3827
3828 if (m_updateLayoutViewportHeightExpansionTimer.isActive()) {
3829 m_shouldRescheduleLayoutViewportHeightExpansionTimer = true;
3830 return;
3831 }
3832
3833 m_updateLayoutViewportHeightExpansionTimer.restart();
3834}
3835
3836void WebPage::updateLayoutViewportHeightExpansionTimerFired()
3837{
3838 if (m_disallowLayoutViewportHeightExpansionReasons.contains(DisallowLayoutViewportHeightExpansionReason::LargeContainer))
3839 return;
3840
3841 RefPtr mainFrame = m_mainFrame->coreLocalFrame();
3842 if (!mainFrame)
3843 return;
3844
3845 RefPtr document = mainFrame->document();
3846 if (!document)
3847 return;
3848
3849 RefPtr view = mainFrame->view();
3850 if (!view)
3851 return;
3852
3853 FloatRect viewportRect = view->viewportConstrainedObjectsRect();
3854
3855 bool hitTestedToLargeViewportConstrainedElement = [&] {
3856 if (!view->hasViewportConstrainedObjects())
3857 return false;
3858
3859 HashSet<Ref<Element>> largeViewportConstrainedElements;
3860 for (CheckedRef renderer : *view->viewportConstrainedObjects()) {
3861 RefPtr element = renderer->element();
3862 if (!element)
3863 continue;
3864
3865 auto bounds = renderer->absoluteBoundingBoxRect();
3866 if (intersection(viewportRect, bounds).height() > 0.9 * viewportRect.height())
3867 largeViewportConstrainedElements.add(element.releaseNonNull());
3868 }
3869
3870 if (largeViewportConstrainedElements.isEmpty())
3871 return false;
3872
3873 auto hitTestResult = HitTestResult { LayoutRect { viewportRect } };
3874 if (!document->hitTest({ HitTestSource::User, { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::CollectMultipleElements } }, hitTestResult))
3875 return false;
3876
3877 auto& hitTestedNodes = hitTestResult.listBasedTestResult();
3878 HashSet<Ref<Element>> elementsOutsideOfAnyLargeViewportConstrainedContainers;
3879 for (auto& node : hitTestedNodes) {
3880 RefPtr firstParentOrSelf = dynamicDowncast<Element>(node.get()) ?: node->parentElementInComposedTree();
3881 Vector<Ref<Element>> ancestorsForHitTestedNode;
3882 for (RefPtr parent = firstParentOrSelf; parent; parent = parent->parentElementInComposedTree()) {
3883 if (largeViewportConstrainedElements.contains(*parent))
3884 return true;
3885
3886 if (elementsOutsideOfAnyLargeViewportConstrainedContainers.contains(*parent))
3887 break;
3888
3889 ancestorsForHitTestedNode.append(*parent);
3890 }
3891 for (auto& ancestor : ancestorsForHitTestedNode)
3892 elementsOutsideOfAnyLargeViewportConstrainedContainers.add(ancestor);
3893 }
3894 return false;
3895 }();
3896
3897 if (hitTestedToLargeViewportConstrainedElement) {
3898 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
;
3899 addReasonsToDisallowLayoutViewportHeightExpansion(DisallowLayoutViewportHeightExpansionReason::LargeContainer);
3900 } else if (m_shouldRescheduleLayoutViewportHeightExpansionTimer) {
3901 m_shouldRescheduleLayoutViewportHeightExpansionTimer = false;
3902 m_updateLayoutViewportHeightExpansionTimer.restart();
3903 }
3904}
3905
3906void WebPage::willStartUserTriggeredZooming()
3907{
3908#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3909 if (RefPtr pluginView = mainFramePlugIn())
3910 pluginView->didBeginMagnificationGesture();
3911#endif
3912
3913 protect(protect(*m_page)->diagnosticLoggingClient())->logDiagnosticMessage(DiagnosticLoggingKeys::webViewKey(), DiagnosticLoggingKeys::userZoomActionKey(), ShouldSample::No);
3914 m_userHasChangedPageScaleFactor = true;
3915}
3916
3917void WebPage::didEndUserTriggeredZooming()
3918{
3919#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
3920 if (RefPtr pluginView = mainFramePlugIn())
3921 pluginView->didEndMagnificationGesture();
3922#endif
3923}
3924
3925#if ENABLE(IOS_TOUCH_EVENTS)(defined 0 && 0)
3926static std::optional<RemoteWebTouchEvent> transformEventIfNecessary(const Expected<bool, WebCore::RemoteFrameGeometryTransformer>& transformer, WebTouchEvent&& event)
3927{
3928 if (transformer)
3929 return std::nullopt;
3930 event.transformToRemoteFrameCoordinates(transformer.error());
3931 return RemoteWebTouchEvent { transformer.error().remoteFrameID(), WTF::move(event) };
3932}
3933
3934void WebPage::dispatchAsynchronousTouchEvents(UniqueRef<EventDispatcher::TouchEventQueue>&& queue)
3935{
3936 for (auto& touchEventData : queue.get()) {
3937 auto handleTouchEventResult = dispatchTouchEvent(touchEventData.frameID, touchEventData.event);
3938 bool handled = handleTouchEventResult.value_or(false);
3939
3940 // Invoke coalesced-away completion handlers first. These events were never
3941 // dispatched to JS, but their handlers must still be invoked so that state
3942 // transitions they carry (e.g. m_touchMovePreventionState) are resolved.
3943 for (auto&& completionHandler : touchEventData.completionHandlers | std::views::take(touchEventData.completionHandlers.size() - 1))
3944 completionHandler(handled, std::nullopt);
3945
3946 // The last handler corresponds to the event that was actually dispatched.
3947 touchEventData.completionHandlers.last()(handled, transformEventIfNecessary(handleTouchEventResult, WTF::move(touchEventData.event)));
3948 }
3949}
3950
3951void WebPage::cancelAsynchronousTouchEvents(UniqueRef<EventDispatcher::TouchEventQueue>&& queue)
3952{
3953 for (auto& touchEventData : queue.get()) {
3954 for (auto& completionHandler : touchEventData.completionHandlers)
3955 completionHandler(true, std::nullopt);
3956 }
3957}
3958#endif
3959
3960void WebPage::computePagesForPrintingiOS(WebCore::FrameIdentifier frameID, const PrintInfo& printInfo, CompletionHandler<void(uint64_t)>&& reply)
3961{
3962 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);
3963
3964 Vector<WebCore::IntRect> pageRects;
3965 double totalScaleFactor;
3966 auto margin = printInfo.margin;
3967 computePagesForPrintingImpl(frameID, printInfo, pageRects, totalScaleFactor, margin);
3968
3969 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
;
3970
3971 ASSERT(pageRects.size() >= 1)((void)0);
3972 reply(pageRects.size());
3973}
3974
3975void WebPage::drawToImage(WebCore::FrameIdentifier frameID, const PrintInfo& printInfo, CompletionHandler<void(std::optional<WebCore::ShareableBitmap::Handle>&&)>&& reply)
3976{
3977 Vector<WebCore::IntRect> pageRects;
3978 double totalScaleFactor;
3979 auto margin = printInfo.margin;
3980 computePagesForPrintingImpl(frameID, printInfo, pageRects, totalScaleFactor, margin);
3981
3982 size_t pageCount = pageRects.size();
3983
3984 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
;
3985
3986 ASSERT(pageCount >= 1)((void)0);
3987
3988 if (!m_printContext) {
3989 reply({ });
3990 endPrinting();
3991 return;
3992 }
3993
3994 Checked<int> pageWidth = pageRects[0].width();
3995 Checked<int> pageHeight = pageRects[0].height();
3996
3997 // The thumbnail images are always a maximum of 500 x 500.
3998 static constexpr float maximumPrintPreviewDimensionSize = 500.0;
3999
4000 // If the sizes are too large, the bitmap will not be able to be created,
4001 // so scale them down.
4002 float scaleFactor = maximumPrintPreviewDimensionSize / static_cast<int>(std::max(pageWidth, pageHeight));
4003 if (scaleFactor < 1.0) {
4004 pageWidth = static_cast<int>(std::floorf(static_cast<int>(pageWidth) * scaleFactor));
4005 pageHeight = static_cast<int>(std::floorf(static_cast<int>(pageHeight) * scaleFactor));
4006 }
4007
4008 int imageHeight;
4009 if (!WTF::safeMultiply(pageHeight.value<size_t>(), pageCount, imageHeight)) {
4010 reply({ });
4011 endPrinting();
4012 return;
4013 }
4014
4015 auto bitmap = ShareableBitmap::create({ IntSize(pageWidth, imageHeight) });
4016 if (!bitmap) {
4017 reply({ });
4018 endPrinting();
4019 return;
4020 }
4021
4022 auto graphicsContext = bitmap->createGraphicsContext();
4023 if (!graphicsContext) {
4024 reply({ });
4025 endPrinting();
4026 return;
4027 }
4028
4029 for (size_t pageIndex = 0; pageIndex < pageCount; ++pageIndex) {
4030 if (pageIndex >= m_printContext->pageCount())
4031 break;
4032 graphicsContext->save();
4033 graphicsContext->translate(0, pageHeight * static_cast<int>(pageIndex));
4034 protect(m_printContext)->spoolPage(*graphicsContext, pageIndex, pageWidth);
4035 graphicsContext->restore();
4036 }
4037
4038 auto handle = bitmap->createHandle(SharedMemory::Protection::ReadOnly);
4039 reply(WTF::move(handle));
4040 endPrinting();
4041}
4042
4043void WebPage::drawPrintingToSnapshotiOS(RemoteSnapshotIdentifier snapshotIdentifier, WebCore::FrameIdentifier frameID, const PrintInfo& printInfo, CompletionHandler<void(std::optional<WebCore::FloatSize>)>&& completionHandler)
4044{
4045 RefPtr frame = WebProcess::singleton().webFrame(frameID);
4046 if (!frame) {
4047 completionHandler({ });
4048 endPrinting();
4049 return;
4050 }
4051
4052 RefPtr coreFrame = frame->coreLocalFrame();
4053 if (!coreFrame) {
4054 completionHandler({ });
4055 endPrinting();
4056 return;
4057 }
4058
4059 if (pdfDocumentForPrintingFrame(coreFrame.get())) {
4060 // Can't do this remotely.
4061 completionHandler({ });
4062 endPrinting();
4063 return;
4064 }
4065
4066 if (!m_printContext) {
4067 completionHandler({ });
4068 endPrinting();
4069 return;
4070 }
4071
4072 PrintContextAccessScope scope { *this };
4073 ASSERT(coreFrame->document()->printing())((void)0);
4074
4075 Vector<WebCore::IntRect> pageRects;
4076 double totalScaleFactor;
4077 auto margin = printInfo.margin;
4078 computePagesForPrintingImpl(frameID, printInfo, pageRects, totalScaleFactor, margin);
4079
4080 size_t pageCount = pageRects.size();
4081
4082 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
;
4083
4084 Checked<int> pageWidth = pageRects[0].width();
4085 Checked<int> pageHeight = pageRects[0].height();
4086
4087 // The thumbnail images are always a maximum of 500 x 500.
4088 static constexpr float maximumPrintPreviewDimensionSize = 500.0;
4089
4090 // If the sizes are too large, the bitmap will not be able to be created,
4091 // so scale them down.
4092 float scaleFactor = maximumPrintPreviewDimensionSize / static_cast<int>(std::max(pageWidth, pageHeight));
4093 if (scaleFactor < 1.0) {
4094 pageWidth = static_cast<int>(std::floorf(static_cast<int>(pageWidth) * scaleFactor));
4095 pageHeight = static_cast<int>(std::floorf(static_cast<int>(pageHeight) * scaleFactor));
4096 }
4097
4098 int imageHeight;
4099 if (!WTF::safeMultiply(pageHeight.value<size_t>(), pageCount, imageHeight)) {
4100 completionHandler({ });
4101 endPrinting();
4102 return;
4103 }
4104
4105 auto mediaBox = IntRect { 0, 0, pageWidth, imageHeight };
4106
4107 Ref remoteRenderingBackend = ensureRemoteRenderingBackendProxy();
4108 m_remoteSnapshotState = {
4109 snapshotIdentifier,
4110 remoteRenderingBackend->createSnapshotRecorder(snapshotIdentifier),
4111 MainRunLoopSuccessCallbackAggregator::create([completionHandler = WTF::move(completionHandler), snapshotSize = mediaBox.size()] (bool success) mutable {
4112 completionHandler(success ? std::optional<FloatSize>(snapshotSize) : std::nullopt);
4113 })
4114 };
4115
4116 GraphicsContext& context = m_remoteSnapshotState->recorder.get();
4117
4118 for (size_t pageIndex = 0; pageIndex < pageCount; ++pageIndex) {
4119 if (pageIndex >= m_printContext->pageCount())
4120 break;
4121 context.save();
4122 context.translate(0, pageHeight * static_cast<int>(pageIndex));
4123 protect(m_printContext)->spoolPage(context, pageIndex, pageWidth);
4124 context.restore();
4125 }
4126
4127 remoteRenderingBackend->sinkSnapshotRecorderIntoSnapshotFrame(WTF::move(m_remoteSnapshotState->recorder), frameID, protect(m_remoteSnapshotState->callback)->chain());
4128 m_remoteSnapshotState = std::nullopt;
4129}
4130
4131void WebPage::drawToPDFiOS(FrameIdentifier frameID, const PrintInfo& printInfo, uint64_t pageCount, CompletionHandler<void(RefPtr<SharedBuffer>&&)>&& reply)
4132{
4133 if (printInfo.snapshotFirstPage) {
4134 RefPtr localMainFrame = protect(m_page)->localMainFrame();
4135 if (!localMainFrame)
4136 return;
4137
4138 auto snapshotRect = IntRect { FloatRect { { }, FloatSize { printInfo.availablePaperWidth, printInfo.availablePaperHeight } } };
4139
4140 RefPtr buffer = ImageBuffer::create(snapshotRect.size(), RenderingMode::PDFDocument, RenderingPurpose::Snapshot, 1, DestinationColorSpace::SRGB(), PixelFormat::BGRA8);
4141 if (!buffer)
4142 return;
4143
4144 Ref frameView = *localMainFrame->view();
4145 auto originalLayoutViewportOverrideRect = frameView->layoutViewportOverrideRect();
4146 frameView->setLayoutViewportOverrideRect(LayoutRect(snapshotRect));
4147
4148 pdfSnapshotAtSize(*localMainFrame, buffer->context(), snapshotRect, { });
4149
4150 frameView->setLayoutViewportOverrideRect(originalLayoutViewportOverrideRect);
4151 reply(buffer->sinkIntoPDFDocument());
4152 return;
4153 }
4154
4155 RefPtr<SharedBuffer> pdfPageData;
4156 drawPagesToPDFImpl(frameID, printInfo, 0, pageCount, pdfPageData);
4157 reply(WTF::move(pdfPageData));
4158
4159 endPrinting();
4160}
4161
4162void WebPage::drawPrintingPagesToSnapshotiOS(RemoteSnapshotIdentifier snapshotIdentifier, WebCore::FrameIdentifier frameID, const PrintInfo& printInfo, uint64_t pageCount, CompletionHandler<void(std::optional<WebCore::FloatSize>)>&& completionHandler)
4163{
4164 RefPtr frame = WebProcess::singleton().webFrame(frameID);
4165 if (!frame) {
4166 completionHandler({ });
4167 return;
4168 }
4169
4170 RefPtr coreFrame = frame->coreLocalFrame();
4171 if (!coreFrame) {
4172 completionHandler({ });
4173 return;
4174 }
4175
4176 if (pdfDocumentForPrintingFrame(coreFrame.get())) {
4177 // Can't do this remotely.
4178 completionHandler({ });
4179 return;
4180 }
4181
4182 if (!m_printContext) {
4183 completionHandler({ });
4184 return;
4185 }
4186
4187 PrintContextAccessScope scope { *this };
4188 ASSERT(coreFrame->document()->printing())((void)0);
4189
4190 auto mediaBox = FloatRect { 0, 0, printInfo.availablePaperWidth, printInfo.availablePaperHeight };
4191 if (!printInfo.snapshotFirstPage && m_printContext && m_printContext->pageCount())
4192 mediaBox = m_printContext->pageRect(0);
4193
4194 Ref remoteRenderingBackend = ensureRemoteRenderingBackendProxy();
4195 m_remoteSnapshotState = {
4196 snapshotIdentifier,
4197 remoteRenderingBackend->createSnapshotRecorder(snapshotIdentifier),
4198 MainRunLoopSuccessCallbackAggregator::create([completionHandler = WTF::move(completionHandler), snapshotSize = mediaBox.size()] (bool success) mutable {
4199 completionHandler(success ? std::optional<FloatSize>(snapshotSize) : std::nullopt);
4200 })
4201 };
4202
4203 GraphicsContext& context = m_remoteSnapshotState->recorder.get();
4204
4205 if (printInfo.snapshotFirstPage) {
4206 Ref frameView = *coreFrame->view();
4207 auto originalLayoutViewportOverrideRect = frameView->layoutViewportOverrideRect();
4208 frameView->setLayoutViewportOverrideRect(LayoutRect(mediaBox));
4209
4210 pdfSnapshotAtSize(*coreFrame, context, IntRect { mediaBox }, { });
4211
4212 frameView->setLayoutViewportOverrideRect(originalLayoutViewportOverrideRect);
4213 } else
4214 drawPrintContextPagesToGraphicsContext(context, mediaBox, 0, pageCount);
4215
4216 remoteRenderingBackend->sinkSnapshotRecorderIntoSnapshotFrame(WTF::move(m_remoteSnapshotState->recorder), frameID, protect(m_remoteSnapshotState->callback)->chain());
4217 m_remoteSnapshotState = std::nullopt;
4218}
4219
4220void WebPage::contentSizeCategoryDidChange(const String& contentSizeCategory)
4221{
4222 setContentSizeCategory(contentSizeCategory);
4223 FontCache::invalidateAllFontCaches();
4224}
4225
4226String WebPage::platformUserAgent(const URL&) const
4227{
4228 return String();
4229}
4230
4231static bool isMousePrimaryPointingDevice()
4232{
4233#if PLATFORM(MACCATALYST)(defined WTF_PLATFORM_MACCATALYST && WTF_PLATFORM_MACCATALYST
)
4234 return true;
4235#else
4236 return false;
4237#endif
4238}
4239
4240bool WebPage::hasAccessoryMousePointingDevice() const
4241{
4242 if (isMousePrimaryPointingDevice())
4243 return true;
4244
4245#if HAVE(MOUSE_DEVICE_OBSERVATION)(defined 1 && 1)
4246 if (WebProcess::singleton().hasMouseDevice())
4247 return true;
4248#endif
4249
4250 return false;
4251}
4252
4253static bool hasAccessoryStylusPointingDevice()
4254{
4255#if HAVE(STYLUS_DEVICE_OBSERVATION)(defined 1 && 1)
4256 if (WebProcess::singleton().hasStylusDevice())
4257 return true;
4258#endif
4259
4260 return false;
4261}
4262
4263bool WebPage::hoverSupportedByPrimaryPointingDevice() const
4264{
4265 return isMousePrimaryPointingDevice();
4266}
4267
4268bool WebPage::hoverSupportedByAnyAvailablePointingDevice() const
4269{
4270 return hasAccessoryMousePointingDevice();
4271}
4272
4273std::optional<PointerCharacteristics> WebPage::pointerCharacteristicsOfPrimaryPointingDevice() const
4274{
4275 return isMousePrimaryPointingDevice() ? PointerCharacteristics::Fine : PointerCharacteristics::Coarse;
4276}
4277
4278OptionSet<PointerCharacteristics> WebPage::pointerCharacteristicsOfAllAvailablePointingDevices() const
4279{
4280 OptionSet<PointerCharacteristics> result;
4281 if (auto pointerCharacteristicsOfPrimaryPointingDevice = this->pointerCharacteristicsOfPrimaryPointingDevice())
4282 result.add(*pointerCharacteristicsOfPrimaryPointingDevice);
4283 if (hasAccessoryMousePointingDevice() || hasAccessoryStylusPointingDevice())
4284 result.add(PointerCharacteristics::Fine);
4285 return result;
4286}
4287
4288void WebPage::hardwareKeyboardAvailabilityChanged(HardwareKeyboardState state)
4289{
4290 m_keyboardIsAttached = state.isAttached;
4291 setHardwareKeyboardState(state);
4292
4293 protect(m_page)->didUpdateHardwareKeyboardAttachment(m_keyboardIsAttached);
4294
4295 if (RefPtr focusedFrame = m_page->focusController().focusedLocalFrame())
4296 focusedFrame->eventHandler().capsLockStateMayHaveChanged();
4297}
4298
4299void WebPage::updateStringForFind(const String& findString)
4300{
4301 send(Messages::WebPageProxy::UpdateStringForFind(findString));
4302}
4303
4304bool WebPage::platformPrefersTextLegibilityBasedZoomScaling() const
4305{
4306#if PLATFORM(WATCHOS)(defined WTF_PLATFORM_WATCHOS && WTF_PLATFORM_WATCHOS
)
4307 return true;
4308#else
4309 return false;
4310#endif
4311}
4312
4313void WebPage::updateSelectionWithDelta(int64_t locationDelta, int64_t lengthDelta, CompletionHandler<void()>&& completionHandler)
4314{
4315 RefPtr frame = m_page->focusController().focusedOrMainFrame();
4316 if (!frame)
4317 return completionHandler();
4318
4319 RefPtr root = protect(frame->selection())->rootEditableElementOrDocumentElement();
4320 auto selectionRange = frame->selection().selection().toNormalizedRange();
4321 if (!root || !selectionRange) {
4322 completionHandler();
4323 return;
4324 }
4325
4326 auto scope = makeRangeSelectingNodeContents(*root);
4327 auto selectionCharacterRange = characterRange(scope, *selectionRange);
4328 CheckedInt64 newSelectionLocation { selectionCharacterRange.location };
4329 CheckedInt64 newSelectionLength { selectionCharacterRange.length };
4330 newSelectionLocation += locationDelta;
4331 newSelectionLength += lengthDelta;
4332 if (newSelectionLocation.hasOverflowed() || newSelectionLength.hasOverflowed()) {
4333 completionHandler();
4334 return;
4335 }
4336
4337 auto newSelectionRange = CharacterRange(newSelectionLocation, newSelectionLength);
4338 auto updatedSelectionRange = resolveCharacterRange(makeRangeSelectingNodeContents(*root), newSelectionRange);
4339 protect(frame->selection())->setSelectedRange(updatedSelectionRange, Affinity::Downstream, WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
4340 completionHandler();
4341}
4342
4343static VisiblePosition moveByGranularityRespectingWordBoundary(const VisiblePosition& position, TextGranularity granularity, uint64_t granularityCount, SelectionDirection direction)
4344{
4345 ASSERT(granularityCount)((void)0);
4346 ASSERT(position.isNotNull())((void)0);
4347 bool backwards = direction == SelectionDirection::Backward;
4348 auto farthestPositionInDirection = backwards ? startOfEditableContent(position) : endOfEditableContent(position);
4349 if (position == farthestPositionInDirection)
4350 return backwards ? startOfWord(position) : endOfWord(position);
4351 VisiblePosition currentPosition = position;
4352 VisiblePosition nextPosition;
4353 do {
4354 nextPosition = positionOfNextBoundaryOfGranularity(currentPosition, granularity, direction);
4355 if (nextPosition.isNull())
4356 break;
4357 currentPosition = nextPosition;
4358 if (atBoundaryOfGranularity(currentPosition, granularity, direction))
4359 --granularityCount;
4360 } while (granularityCount);
4361 if (granularity == TextGranularity::SentenceGranularity)
4362 return currentPosition;
4363 // Note that this rounds to the nearest word, which may cross a line boundary when using line granularity.
4364 // For example, suppose the text is laid out as follows and the insertion point is at |:
4365 // |This is the first sen
4366 // tence in a paragraph.
4367 // Then moving 1 line of granularity forward will return the postion after the 'e' in sentence.
4368 return backwards ? startOfWord(currentPosition) : endOfWord(currentPosition);
4369}
4370
4371static VisiblePosition visiblePositionForPointInRootViewCoordinates(LocalFrame& frame, FloatPoint pointInRootViewCoordinates)
4372{
4373 auto pointInDocument = protect(frame.view())->rootViewToContents(roundedIntPoint(pointInRootViewCoordinates));
4374 return frame.visiblePositionForPoint(pointInDocument);
4375}
4376
4377static VisiblePositionRange constrainRangeToSelection(const VisiblePositionRange& selection, const VisiblePositionRange& range)
4378{
4379 if (intersects(selection, range))
4380 return intersection(selection, range);
4381 auto rangeMidpoint = midpoint(range);
4382 auto position = startOfWord(rangeMidpoint);
4383 if (!contains(range, position)) {
4384 position = endOfWord(rangeMidpoint);
4385 if (!contains(range, position))
4386 position = rangeMidpoint;
4387 }
4388 return { position, position };
4389}
4390
4391void WebPage::requestDocumentEditingContext(DocumentEditingContextRequest&& request, CompletionHandler<void(DocumentEditingContext&&)>&& completionHandler)
4392{
4393 bool wantsAttributedText = request.options.contains(DocumentEditingContextRequest::Options::AttributedText);
4394 if (!request.options.contains(DocumentEditingContextRequest::Options::Text) && !wantsAttributedText) {
4395 completionHandler({ });
4396 return;
4397 }
4398
4399 RefPtr frame = m_page->focusController().focusedOrMainFrame();
4400 if (!frame)
4401 return completionHandler({ });
4402
4403#if ENABLE(PDF_PLUGIN)(defined 1 && 1)
4404 if (RefPtr pluginView = focusedPluginViewForFrame(*frame))
4405 return completionHandler(pluginView->documentEditingContext(WTF::move(request)));
4406#endif
4407
4408 RefPtr view = frame->view();
4409 if (!view)
4410 return completionHandler({ });
4411
4412 protect(frame->document())->updateLayout(LayoutOptions::IgnorePendingStylesheets);
4413
4414 VisibleSelection selection = frame->selection().selection();
4415
4416 VisiblePositionRange rangeOfInterest;
4417 auto selectionRange = VisiblePositionRange { selection.visibleStart(), selection.visibleEnd() };
4418
4419 bool isSpatialRequest = request.options.containsAny({ DocumentEditingContextRequest::Options::Spatial, DocumentEditingContextRequest::Options::SpatialAndCurrentSelection });
4420 bool isSpatialRequestWithCurrentSelection = request.options.contains(DocumentEditingContextRequest::Options::SpatialAndCurrentSelection);
4421 bool wantsRects = request.options.contains(DocumentEditingContextRequest::Options::Rects);
4422 bool wantsMarkedTextRects = request.options.contains(DocumentEditingContextRequest::Options::MarkedTextRects);
4423
4424 if (auto textInputContext = request.textInputContext) {
4425 auto element = elementForContext(*textInputContext);
4426 if (!element) {
4427 completionHandler({ });
4428 return;
4429 }
4430
4431 if (!request.rect.isEmpty()) {
4432 rangeOfInterest.start = closestEditablePositionInElementForAbsolutePoint(*element, roundedIntPoint(request.rect.minXMinYCorner()));
4433 rangeOfInterest.end = closestEditablePositionInElementForAbsolutePoint(*element, roundedIntPoint(request.rect.maxXMaxYCorner()));
4434 } else if (RefPtr textFormControlElement = dynamicDowncast<HTMLTextFormControlElement>(element)) {
4435 rangeOfInterest.start = textFormControlElement->visiblePositionForIndex(0);
4436 rangeOfInterest.end = textFormControlElement->visiblePositionForIndex(textFormControlElement->value()->length());
4437 } else {
4438 rangeOfInterest.start = firstPositionInOrBeforeNode(element.get());
4439 rangeOfInterest.end = lastPositionInOrAfterNode(element.get());
4440 }
4441 } else if (isSpatialRequest) {
4442 // FIXME: We might need to be a bit more careful that we get something useful (test the other corners?).
4443 rangeOfInterest.start = visiblePositionForPointInRootViewCoordinates(*frame, request.rect.minXMinYCorner());
4444 rangeOfInterest.end = visiblePositionForPointInRootViewCoordinates(*frame, request.rect.maxXMaxYCorner());
4445
4446 if (isSpatialRequestWithCurrentSelection && !selection.isNoneOrOrphaned()) {
4447 static constexpr auto maximumNumberOfLines = 10;
4448 auto intersectsSpatialRect = [&](const VisiblePosition& position) {
4449 FloatRect caretInRootView = view->contentsToRootView(position.absoluteCaretBounds());
4450 return caretInRootView.intersects(request.rect);
4451 };
4452
4453 auto startPositionNearSelection = [&] {
4454 auto start = selectionRange.start;
4455 unsigned lineCount = 0;
4456 do {
4457 auto previous = previousLinePosition(start, start.lineDirectionPointForBlockDirectionNavigation());
4458 if (previous.isNull() || previous == start)
4459 break;
4460 start = WTF::move(previous);
4461 lineCount++;
4462 } while (intersectsSpatialRect(start) && lineCount < maximumNumberOfLines);
4463 return start;
4464 }();
4465
4466 auto endPositionNearSelection = [&] {
4467 auto end = selectionRange.end;
4468 unsigned lineCount = 0;
4469 do {
4470 auto next = nextLinePosition(end, end.lineDirectionPointForBlockDirectionNavigation());
4471 if (next.isNull() || next == end)
4472 break;
4473 end = WTF::move(next);
4474 lineCount++;
4475 } while (intersectsSpatialRect(end) && lineCount < maximumNumberOfLines);
4476 return end;
4477 }();
4478
4479 if (startPositionNearSelection <= endPositionNearSelection) {
4480 rangeOfInterest.start = std::clamp(rangeOfInterest.start, startPositionNearSelection, endPositionNearSelection);
4481 rangeOfInterest.end = std::clamp(rangeOfInterest.end, startPositionNearSelection, endPositionNearSelection);
4482 }
4483 }
4484 if (request.options.contains(DocumentEditingContextRequest::Options::SpatialAndCurrentSelection)) {
4485 if (RefPtr rootEditableElement = selection.rootEditableElement()) {
4486 VisiblePosition startOfEditableRoot { firstPositionInOrBeforeNode(rootEditableElement.get()) };
4487 VisiblePosition endOfEditableRoot { lastPositionInOrAfterNode(rootEditableElement.get()) };
4488 if (startOfEditableRoot <= endOfEditableRoot) {
4489 rangeOfInterest.start = std::clamp(rangeOfInterest.start, startOfEditableRoot, endOfEditableRoot);
4490 rangeOfInterest.end = std::clamp(rangeOfInterest.end, startOfEditableRoot, endOfEditableRoot);
4491 }
4492 }
4493 }
4494 } else if (!selection.isNone())
4495 rangeOfInterest = selectionRange;
4496
4497 if (rangeOfInterest.end < rangeOfInterest.start)
4498 std::exchange(rangeOfInterest.start, rangeOfInterest.end);
4499
4500 if (request.options.contains(DocumentEditingContextRequest::Options::SpatialAndCurrentSelection)) {
4501 if (selectionRange.start < rangeOfInterest.start)
4502 rangeOfInterest.start = selectionRange.start;
4503 if (selectionRange.end > rangeOfInterest.end)
4504 rangeOfInterest.end = selectionRange.end;
4505 }
4506
4507 if (rangeOfInterest.start.isNull() || rangeOfInterest.start.isOrphan() || rangeOfInterest.end.isNull() || rangeOfInterest.end.isOrphan()) {
4508 completionHandler({ });
4509 return;
4510 }
4511
4512 // The subset of the selection that is inside the range of interest.
4513 auto rangeOfInterestInSelection = constrainRangeToSelection(selection, rangeOfInterest);
4514 if (rangeOfInterestInSelection.isNull()) {
4515 completionHandler({ });
4516 return;
4517 }
4518
4519 VisiblePosition contextBeforeStart;
4520 VisiblePosition contextAfterEnd;
4521 auto compositionRange = protect(frame->editor())->compositionRange();
4522 if (request.granularityCount) {
4523 contextBeforeStart = moveByGranularityRespectingWordBoundary(rangeOfInterest.start, request.surroundingGranularity, request.granularityCount, SelectionDirection::Backward);
4524 contextAfterEnd = moveByGranularityRespectingWordBoundary(rangeOfInterest.end, request.surroundingGranularity, request.granularityCount, SelectionDirection::Forward);
4525 } else {
4526 contextBeforeStart = rangeOfInterest.start;
4527 contextAfterEnd = rangeOfInterest.end;
4528 if (wantsMarkedTextRects && compositionRange) {
4529 // In the case where the client has requested marked text rects make sure that the context
4530 // range encompasses the entire marked text range so that we don't return a truncated result.
4531 auto compositionStart = makeDeprecatedLegacyPosition(compositionRange->start);
4532 auto compositionEnd = makeDeprecatedLegacyPosition(compositionRange->end);
4533 if (contextBeforeStart > compositionStart)
4534 contextBeforeStart = compositionStart;
4535 if (contextAfterEnd < compositionEnd)
4536 contextAfterEnd = compositionEnd;
4537 }
4538 }
4539
4540 auto isTextObscured = [] (const VisiblePosition& visiblePosition) {
4541 if (RefPtr textControl = enclosingTextFormControl(visiblePosition.deepEquivalent())) {
4542 if (RefPtr input = dynamicDowncast<HTMLInputElement>(textControl.get())) {
4543 if (input->autofilledAndObscured())
4544 return true;
4545 }
4546 }
4547 return false;
4548 };
4549
4550 auto makeString = [&] (const VisiblePosition& start, const VisiblePosition& end) -> AttributedString {
4551 auto range = makeSimpleRange(start, end);
4552 if (!range || range->collapsed())
4553 return { };
4554
4555 auto isObscured = isTextObscured(start);
4556 TextIteratorBehaviors textBehaviors = { };
4557 if (!isObscured)
4558 textBehaviors = TextIteratorBehavior::EmitsOriginalText;
4559
4560 if (wantsAttributedText)
4561 return editingAttributedStringReplacingNoBreakSpace(*range, textBehaviors, { IncludedElement::Images, IncludedElement::Attachments });
4562
4563 return AttributedString::fromNSAttributedString(adoptNS([[NSAttributedString alloc] initWithString:plainTextReplacingNoBreakSpace(*range, textBehaviors).createNSString().get()]));
4564 };
4565
4566 DocumentEditingContext context;
4567 context.contextBefore = makeString(contextBeforeStart, rangeOfInterestInSelection.start);
4568 context.selectedText = makeString(rangeOfInterestInSelection.start, rangeOfInterestInSelection.end);
4569 context.contextAfter = makeString(rangeOfInterestInSelection.end, contextAfterEnd);
4570 if (auto compositionVisiblePositionRange = makeVisiblePositionRange(compositionRange); intersects(rangeOfInterest, compositionVisiblePositionRange)) {
4571 context.markedText = makeString(compositionVisiblePositionRange.start, compositionVisiblePositionRange.end);
4572 auto markedTextLength = context.markedText.string.length();
4573
4574 ptrdiff_t distanceToSelectionStart = distanceBetweenPositions(rangeOfInterestInSelection.start, compositionVisiblePositionRange.start);
4575 ptrdiff_t distanceToSelectionEnd = distanceToSelectionStart + [context.selectedText.string.createNSString() length];
4576
4577 distanceToSelectionStart = clampTo<ptrdiff_t>(distanceToSelectionStart, 0, markedTextLength);
4578 distanceToSelectionEnd = clampTo<ptrdiff_t>(distanceToSelectionEnd, 0, markedTextLength);
4579 RELEASE_ASSERT(distanceToSelectionStart <= distanceToSelectionEnd)do { if (__builtin_expect(!!(!(distanceToSelectionStart <=
distanceToSelectionEnd)), 0)) do { WTF::isIntegralOrPointerType
(); compilerFenceForCrash(); WTFCrashWithInfo(4579, "/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm"
, __PRETTY_FUNCTION__ ); } while (false); } while (0)
;
4580
4581 context.selectedRangeInMarkedText = {
4582 static_cast<uint64_t>(distanceToSelectionStart),
4583 static_cast<uint64_t>(distanceToSelectionEnd - distanceToSelectionStart)
4584 };
4585 } else if (auto* suggestion = frame->editor().writingSuggestionData()) {
4586 if (auto suffix = suggestion->content(); !suffix.isEmpty()) {
4587 context.markedText = AttributedString::fromNSAttributedString(adoptNS([[NSAttributedString alloc] initWithString:suffix.createNSString().get()]));
4588 context.selectedRangeInMarkedText = { 0, 0 };
4589 }
4590 }
4591
4592 auto characterRectsForRange = [](const SimpleRange& range, unsigned startOffset) {
4593 Vector<DocumentEditingContext::TextRectAndRange> rects;
4594 unsigned offsetSoFar = startOffset;
4595 std::optional<SimpleRange> lastTextRange;
4596 for (TextIterator iterator { range }; !iterator.atEnd(); iterator.advance()) {
4597 if (iterator.text().isEmpty())
4598 continue;
4599
4600 if (lastTextRange == iterator.range())
4601 continue;
4602
4603 Vector<IntRect> absoluteRects;
4604 if (iterator.range().collapsed())
4605 absoluteRects = { VisiblePosition(makeContainerOffsetPosition(iterator.range().start)).absoluteCaretBounds() };
4606 else {
4607 absoluteRects = RenderObject::absoluteTextRects(iterator.range(), {
4608 RenderObject::BoundingRectBehavior::IgnoreEmptyTextSelections,
4609 RenderObject::BoundingRectBehavior::ComputeIndividualCharacterRects,
4610 });
4611 if (absoluteRects.isEmpty())
4612 absoluteRects.append({ });
4613 }
4614
4615 for (auto& absoluteRect : absoluteRects)
4616 rects.append({ protect(iterator.range().start.document().view())->contentsToRootView(absoluteRect), { offsetSoFar++, 1 } });
4617
4618 lastTextRange = iterator.range();
4619 }
4620 return rects;
4621 };
4622
4623 if (wantsRects) {
4624 if (auto contextRange = makeSimpleRange(contextBeforeStart, contextAfterEnd)) {
4625 // FIXME (257828): We should ideally ASSERT() here that context.textRects.size() is equal to
4626 // the combined length of the context and selection strings; however, there are some corner
4627 // cases in which this isn't true. In particular, such an assertion would be hit when running
4628 // the layout test editing/selection/ios/update-selection-after-overflow-scroll.html.
4629 // See also: <https://bugs.webkit.org/show_bug.cgi?id=257828>.
4630 context.textRects = characterRectsForRange(*contextRange, 0);
4631 }
4632 } else if (wantsMarkedTextRects && compositionRange) {
4633 unsigned compositionStartOffset = 0;
4634 if (auto range = makeSimpleRange(contextBeforeStart, compositionRange->start))
4635 compositionStartOffset = characterCount(*range);
4636 context.textRects = characterRectsForRange(*compositionRange, compositionStartOffset);
4637 }
4638
4639#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)(defined 0 && 0)
4640 if (request.options.contains(DocumentEditingContextRequest::Options::Annotation))
4641 context.annotatedText = m_textCheckingControllerProxy->annotatedSubstringBetweenPositions(contextBeforeStart, contextAfterEnd);
4642#endif
4643
4644 if (request.options.contains(DocumentEditingContextRequest::Options::AutocorrectedRanges)) {
4645 if (auto contextRange = makeSimpleRange(contextBeforeStart, contextAfterEnd)) {
4646 auto ranges = protect(protect(frame->document())->markers())->rangesForMarkersInRange(*contextRange, DocumentMarkerType::CorrectionIndicator);
4647 context.autocorrectedRanges = ranges.map([&] (auto& range) {
4648 auto characterRangeInContext = characterRange(*contextRange, range);
4649 return DocumentEditingContext::Range { characterRangeInContext.location, characterRangeInContext.length };
4650 });
4651 }
4652 }
4653
4654 completionHandler(WTF::move(context));
4655}
4656
4657void WebPage::setShouldRevealCurrentSelectionAfterInsertion(bool shouldRevealCurrentSelectionAfterInsertion)
4658{
4659 if (m_shouldRevealCurrentSelectionAfterInsertion == shouldRevealCurrentSelectionAfterInsertion)
4660 return;
4661 m_shouldRevealCurrentSelectionAfterInsertion = shouldRevealCurrentSelectionAfterInsertion;
4662 if (!shouldRevealCurrentSelectionAfterInsertion)
4663 return;
4664 protect(m_page)->revealCurrentSelection();
4665 scheduleFullEditorStateUpdate();
4666}
4667
4668void WebPage::setScreenIsBeingCaptured(bool captured)
4669{
4670 m_screenIsBeingCaptured = captured;
4671}
4672
4673void WebPage::setInsertionPointColor(WebCore::Color color)
4674{
4675 RenderThemeIOS::setInsertionPointColor(color);
4676}
4677
4678void WebPage::textInputContextsInRect(FloatRect searchRect, CompletionHandler<void(const Vector<ElementContext>&)>&& completionHandler)
4679{
4680 auto contexts = protect(m_page)->editableElementsInRect(searchRect).map([&] (const auto& element) {
4681 Ref document = element->document();
4682
4683 ElementContext context;
4684 context.webPageIdentifier = m_identifier;
4685 context.documentIdentifier = document->identifier();
4686 context.nodeIdentifier = element->nodeIdentifier();
4687 context.boundingRect = element->boundingBoxInRootViewCoordinates();
4688 return context;
4689 });
4690 completionHandler(contexts);
4691#if ENABLE(EDITABLE_REGION)(defined 1 && 1)
4692 protect(m_page)->setEditableRegionEnabled();
4693#endif
4694}
4695
4696void WebPage::focusTextInputContextAndPlaceCaret(const ElementContext& elementContext, const IntPoint& point, CompletionHandler<void(bool)>&& completionHandler)
4697{
4698 RefPtr target = elementForContext(elementContext);
4699 if (!target) {
4700 completionHandler(false);
4701 return;
4702 }
4703
4704 ASSERT(target->document().frame())((void)0);
4705 Ref targetFrame = *target->document().frame();
4706
4707 protect(targetFrame->document())->updateLayout(LayoutOptions::IgnorePendingStylesheets);
4708
4709 // Performing layout could have could torn down the element's renderer. Check that we still
4710 // have one. Otherwise, bail out as this function only focuses elements that have a visual
4711 // representation.
4712 if (!target->renderer() || !target->isFocusable()) {
4713 completionHandler(false);
4714 return;
4715 }
4716
4717 // FIXME: Do not focus an element if it moved or the caret point is outside its bounds
4718 // because we only want to do so if the caret can be placed.
4719 UserGestureIndicator gestureIndicator { IsProcessingUserGesture::Yes, &target->document() };
4720 SetForScope userIsInteractingChange { m_userIsInteracting, true };
4721 protect(corePage())->focusController().setFocusedElement(target.get(), targetFrame.ptr());
4722
4723 // Setting the focused element could tear down the element's renderer. Check that we still have one.
4724 if (m_focusedElement != target || !target->renderer()) {
4725 completionHandler(false);
4726 return;
4727 }
4728
4729 ASSERT(targetFrame->view())((void)0);
4730 auto position = closestEditablePositionInElementForAbsolutePoint(*target, protect(targetFrame->view())->rootViewToContents(point));
4731 if (position.isNull()) {
4732 completionHandler(false);
4733 return;
4734 }
4735 protect(targetFrame->selection())->setSelectedRange(makeSimpleRange(position), position.affinity(), WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered::Yes);
4736 completionHandler(true);
4737}
4738
4739void WebPage::platformDidScalePage()
4740{
4741 auto transactionID = downcast<RemoteLayerTreeDrawingArea>(*m_drawingArea).lastCommittedTransactionID();
4742 m_internals->lastLayerTreeTransactionIdAndPageScaleBeforeScalingPage = { { transactionID, m_lastTransactionPageScaleFactor } };
4743}
4744
4745#if USE(QUICK_LOOK)(defined 1 && 1)
4746
4747void WebPage::didStartLoadForQuickLookDocumentInMainFrame(const String& fileName, const String& uti)
4748{
4749 send(Messages::WebPageProxy::DidStartLoadForQuickLookDocumentInMainFrame(fileName, uti));
4750}
4751
4752void WebPage::didFinishLoadForQuickLookDocumentInMainFrame(const FragmentedSharedBuffer& buffer)
4753{
4754 ASSERT(!buffer.isEmpty())((void)0);
4755
4756 // FIXME: In some cases, buffer contains a single segment that wraps an existing ShareableResource.
4757 // If we could create a handle from that existing resource then we could avoid this extra
4758 // allocation and copy.
4759
4760 auto sharedMemory = SharedMemory::copyBuffer(buffer);
4761 if (!sharedMemory)
4762 return;
4763
4764 auto shareableResource = ShareableResource::create(sharedMemory.releaseNonNull(), 0, buffer.size());
4765 if (!shareableResource)
4766 return;
4767
4768 auto handle = shareableResource->createHandle();
4769 if (!handle)
4770 return;
4771
4772 send(Messages::WebPageProxy::DidFinishLoadForQuickLookDocumentInMainFrame(WTF::move(*handle)));
4773}
4774
4775void WebPage::requestPasswordForQuickLookDocumentInMainFrame(const String& fileName, CompletionHandler<void(const String&)>&& completionHandler)
4776{
4777 sendWithAsyncReply(Messages::WebPageProxy::RequestPasswordForQuickLookDocumentInMainFrame(fileName), WTF::move(completionHandler));
4778}
4779
4780#endif
4781
4782void WebPage::animationDidFinishForElement(const Element& animatedElement)
4783{
4784 scheduleEditorStateUpdateAfterAnimationIfNeeded(animatedElement);
4785
4786 if (!m_page->settings().layoutViewportHeightExpansionFactor())
4787 return;
4788
4789 if (!protect(animatedElement.document())->isTopDocument())
4790 return;
4791
4792 if (CheckedPtr renderer = animatedElement.renderer(); renderer && renderer->isFixedPositioned())
4793 scheduleLayoutViewportHeightExpansionUpdate();
4794}
4795
4796void WebPage::scheduleEditorStateUpdateAfterAnimationIfNeeded(const Element& animatedElement)
4797{
4798 RefPtr frame = m_page->focusController().focusedOrMainFrame();
4799 if (!frame)
4800 return;
4801
4802 auto& selection = frame->selection().selection();
4803 if (selection.isNoneOrOrphaned())
4804 return;
4805
4806 if (selection.isCaret() && !selection.hasEditableStyle())
4807 return;
4808
4809 auto scheduleEditorStateUpdateForStartOrEndContainerNodeIfNeeded = [&](const Node* container) {
4810 if (!animatedElement.isShadowIncludingInclusiveAncestorOf(container))
4811 return false;
4812
4813 frame->selection().setCaretRectNeedsUpdate();
4814 scheduleFullEditorStateUpdate();
4815 return true;
4816 };
4817
4818 RefPtr startContainer = selection.start().containerNode();
4819 if (scheduleEditorStateUpdateForStartOrEndContainerNodeIfNeeded(startContainer.get()))
4820 return;
4821
4822 RefPtr endContainer = selection.end().containerNode();
4823 if (startContainer != endContainer)
4824 scheduleEditorStateUpdateForStartOrEndContainerNodeIfNeeded(endContainer.get());
4825}
4826
4827FloatSize WebPage::screenSizeForFingerprintingProtections(const LocalFrame&, FloatSize defaultSize) const
4828{
4829 if (!PAL::currentUserInterfaceIdiomIsSmallScreen())
4830 return m_viewportConfiguration.minimumLayoutSize();
4831
4832 static constexpr std::array fixedSizes {
4833 FloatSize { 320, 568 },
4834 FloatSize { 375, 667 },
4835 FloatSize { 390, 844 },
4836 FloatSize { 414, 896 },
4837 };
4838
4839 for (auto fixedSize : fixedSizes) {
4840 if (defaultSize.width() <= fixedSize.width())
4841 return fixedSize;
4842 }
4843
4844 return std::get<std::tuple_size_v<decltype(fixedSizes)> - 1>(fixedSizes);
4845}
4846
4847void WebPage::shouldDismissKeyboardAfterTapAtPoint(FloatPoint point, CompletionHandler<void(bool)>&& completion)
4848{
4849 RefPtr localMainFrame = protect(m_page)->localMainFrame();
4850 if (!localMainFrame)
4851 return completion(false);
4852
4853 RefPtr mainFrameView = localMainFrame->view();
4854 if (!mainFrameView)
4855 return completion(false);
4856
4857 FloatPoint adjustedPoint;
4858 RefPtr target = localMainFrame->nodeRespondingToClickEvents(point, adjustedPoint);
4859 if (!target)
4860 return completion(true);
4861
4862 if (target->hasEditableStyle())
4863 return completion(false);
4864
4865 if (RefPtr element = dynamicDowncast<Element>(*target); element && element->isFormControlElement())
4866 return completion(false);
4867
4868 auto minimumSizeForDismissal = FloatSize { 0.9f * mainFrameView->unobscuredContentSize().width(), 150.f };
4869
4870 bool isReplaced;
4871 FloatSize targetSize = target->absoluteBoundingRect(&isReplaced).size();
4872 completion(targetSize.width() >= minimumSizeForDismissal.width() && targetSize.height() >= minimumSizeForDismissal.height());
4873}
4874
4875void WebPage::computeEnclosingLayerID(EditorState& state, const VisibleSelection& selection) const
4876{
4877 auto selectionRange = selection.range();
4878
4879 if (!selectionRange)
4880 return;
4881
4882 auto [startLayer, endLayer, enclosingLayer, graphicsLayer, enclosingGraphicsLayerID] = computeEnclosingLayer(*selectionRange);
4883
4884 state.visualData->enclosingLayerID = WTF::move(enclosingGraphicsLayerID);
4885
4886 if (!state.visualData->enclosingLayerID)
4887 return;
4888
4889 auto nextScroller = [](RenderLayer& layer, IncludeSelfOrNot includeSelf) {
4890 return layer.enclosingScrollableLayer(includeSelf, CrossFrameBoundaries::Yes);
4891 };
4892
4893 auto scrollOffsetAndNodeIDForLayer = [](RenderLayer* layer) -> std::pair<ScrollOffset, std::optional<ScrollingNodeID>> {
4894 CheckedRef renderer = layer->renderer();
4895 WeakPtr scrollableArea = [&] -> ScrollableArea* {
4896 if (renderer->isRenderView())
4897 return protect(renderer->document())->isTopDocument() ? nullptr : renderer->frame().view();
4898
4899 return layer->scrollableArea();
4900 }();
4901
4902 if (!scrollableArea)
4903 return { };
4904
4905 auto scrollingNodeID = scrollableArea->scrollingNodeID();
4906 if (!scrollingNodeID)
4907 return { };
4908
4909 return { scrollableArea->scrollOffset(), WTF::move(scrollingNodeID) };
4910 };
4911
4912 CheckedPtr<RenderLayer> scrollableLayer;
4913 for (CheckedPtr layer = nextScroller(*protect(enclosingLayer), IncludeSelfOrNot::IncludeSelf); layer; layer = nextScroller(*layer, IncludeSelfOrNot::ExcludeSelf)) {
4914 if (auto [scrollOffset, scrollingNodeID] = scrollOffsetAndNodeIDForLayer(layer.get()); scrollingNodeID) {
4915 state.visualData->enclosingScrollOffset = scrollOffset;
4916 state.visualData->enclosingScrollingNodeID = WTF::move(scrollingNodeID);
4917 scrollableLayer = WTF::move(layer);
4918 break;
4919 }
4920 }
4921
4922 ASSERT_IMPLIES(state.visualData->enclosingLayerID, graphicsLayer)((void)0);
4923 state.visualData->enclosingLayerUsesContentsLayer = graphicsLayer && protect(graphicsLayer)->usesContentsLayer();
4924
4925 if (selection.isCaret()) {
4926 state.visualData->scrollingNodeIDAtStart = state.visualData->enclosingScrollingNodeID;
4927 state.visualData->scrollingNodeIDAtEnd = state.visualData->enclosingScrollingNodeID;
4928 return;
4929 }
4930
4931 auto scrollingNodeIDForEndpoint = [&](RenderLayer* endpointLayer) {
4932 for (CheckedPtr layer = endpointLayer; layer && layer != scrollableLayer; layer = nextScroller(*layer, IncludeSelfOrNot::ExcludeSelf)) {
4933 if (auto scrollingNodeID = scrollOffsetAndNodeIDForLayer(layer.get()).second)
4934 return scrollingNodeID;
4935 }
4936 return state.visualData->enclosingScrollingNodeID;
4937 };
4938
4939 state.visualData->scrollingNodeIDAtStart = scrollingNodeIDForEndpoint(startLayer.get());
4940 state.visualData->scrollingNodeIDAtEnd = scrollingNodeIDForEndpoint(endLayer.get());
4941}
4942
4943void WebPage::callAfterPendingSyntheticClick(CompletionHandler<void(SyntheticClickResult)>&& completion)
4944{
4945 if (m_pendingSyntheticClickCallback)
4946 return completion(SyntheticClickResult::Failed);
4947
4948 sendWithAsyncReply(Messages::WebPageProxy::IsPotentialTapInProgress(), [weakPage = WeakPtr { *this }, completion = WTF::move(completion)](bool isTapping) mutable {
4949 RefPtr page = weakPage.get();
4950 if (!page || page->m_isClosed)
4951 return completion(SyntheticClickResult::PageInvalid);
4952
4953 if (!isTapping)
4954 return completion(SyntheticClickResult::Failed);
4955
4956 if (!page->m_potentialTapNode)
4957 return completion(SyntheticClickResult::Failed);
4958
4959 page->m_pendingSyntheticClickCallback = WTF::move(completion);
4960 });
4961}
4962
4963#if ENABLE(IOS_TOUCH_EVENTS)(defined 0 && 0)
4964
4965void WebPage::didDispatchClickEvent(const PlatformMouseEvent& event, Node& node)
4966{
4967 if (!m_userIsInteracting)
4968 return;
4969
4970 if (event.type() != PlatformEventType::MouseReleased)
4971 return;
4972
4973 if (event.syntheticClickType() != SyntheticClickType::NoTap)
4974 return;
4975
4976 RefPtr element = dynamicDowncast<Element>(node) ?: node.parentElementInComposedTree();
4977 if (!element)
4978 return;
4979
4980 Ref document = node.document();
4981 if (!document->quirks().shouldSynthesizeTouchEventsAfterNonSyntheticClick(*element))
4982 return;
4983
4984 bool isReplaced = false;
4985 auto bounds = element->absoluteBoundingRect(&isReplaced);
4986 if (bounds.isEmpty())
4987 return;
4988
4989 callOnMainRunLoop([bounds, document = WTF::move(document)] mutable {
4990 if (RefPtr frame = document->frame())
4991 frame->eventHandler().dispatchSimulatedTouchEvent(roundedIntPoint(bounds.center()));
4992 });
4993}
4994
4995#endif // ENABLE(IOS_TOUCH_EVENTS)
4996
4997#if USE(UICONTEXTMENU)(defined 1 && 1)
4998
4999void WebPage::willBeginContextMenuInteraction()
5000{
5001 m_hasActiveContextMenuInteraction = true;
5002}
5003
5004void WebPage::didEndContextMenuInteraction()
5005{
5006 m_hasActiveContextMenuInteraction = false;
5007}
5008
5009#endif // USE(UICONTEXTMENU)
5010
5011#if ENABLE(PDF_PAGE_NUMBER_INDICATOR)(defined 1 && 1)
5012
5013void WebPage::createPDFPageNumberIndicator(PDFPluginBase& plugin, const IntRect& boundingBox, size_t pageCount)
5014{
5015 ASSERT(!m_pdfPlugInWithPageNumberIndicator.first || m_pdfPlugInWithPageNumberIndicator.first == plugin.identifier())((void)0);
5016 if (m_pdfPlugInWithPageNumberIndicator.first == plugin.identifier())
5017 return;
5018 m_pdfPlugInWithPageNumberIndicator = std::make_pair(plugin.identifier(), WeakPtr { plugin });
5019 send(Messages::WebPageProxy::CreatePDFPageNumberIndicator(plugin.identifier(), boundingBox, pageCount));
5020}
5021
5022void WebPage::updatePDFPageNumberIndicatorLocation(PDFPluginBase& plugin, const IntRect& boundingBox)
5023{
5024 if (m_pdfPlugInWithPageNumberIndicator.first == plugin.identifier())
5025 send(Messages::WebPageProxy::UpdatePDFPageNumberIndicatorLocation(plugin.identifier(), boundingBox));
5026}
5027
5028void WebPage::updatePDFPageNumberIndicatorCurrentPage(PDFPluginBase& plugin, size_t pageIndex)
5029{
5030 if (m_pdfPlugInWithPageNumberIndicator.first == plugin.identifier())
5031 send(Messages::WebPageProxy::UpdatePDFPageNumberIndicatorCurrentPage(plugin.identifier(), pageIndex));
5032}
5033
5034void WebPage::removePDFPageNumberIndicator(PDFPluginBase& plugin)
5035{
5036 if (m_pdfPlugInWithPageNumberIndicator.first == plugin.identifier()) {
5037 m_pdfPlugInWithPageNumberIndicator = std::make_pair(Markable<PDFPluginIdentifier> { }, nullptr);
5038 send(Messages::WebPageProxy::RemovePDFPageNumberIndicator(plugin.identifier()));
5039 }
5040}
5041
5042#endif
5043
5044#if ENABLE(UNIFIED_PDF)(defined 1 && 1)
5045
5046void WebPage::setPDFDisplayMode(PDFDisplayMode mode)
5047{
5048 send(Messages::WebPageProxy::SetPDFDisplayMode(mode));
5049}
5050
5051void WebPage::requestPDFDisplayMode(PDFDisplayMode mode)
5052{
5053 if (RefPtr pluginView = mainFramePlugIn())
5054 return pluginView->setPDFDisplayMode(mode);
5055}
5056
5057#endif
5058
5059} // namespace WebKit
5060
5061#undef WEBPAGE_RELEASE_LOG
5062#undef WEBPAGE_RELEASE_LOG_ERROR
5063
5064#endif // PLATFORM(IOS_FAMILY)