WebKit Bugzilla
Attachment 360029 Details for
Bug 193447
: Limit user-agent interactions based on the touch-action property on iOS
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch
bug-193447-20190124221711.patch (text/plain), 81.26 KB, created by
Antoine Quint
on 2019-01-24 13:17:13 PST
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Antoine Quint
Created:
2019-01-24 13:17:13 PST
Size:
81.26 KB
patch
obsolete
>Subversion Revision: 240446 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index bf22699d5bbe80430e55b680fae13a368dbf7b98..b3fb1ea5fd8014a716700c549ef9aa12543e8186 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,71 @@ >+2019-01-24 Antoine Quint <graouts@apple.com> >+ >+ Limit user-agent interactions based on the touch-action property >+ https://bugs.webkit.org/show_bug.cgi?id=193447 >+ <rdar://problem/47283874> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ We now compile a list of elements with a non-auto touch-action property that is updated whenever an element has its style changed >+ or is removed from its document. When the content of that list changes, we inform the scrolling coordinator such that it can compile >+ a list of TouchActionData structures which hold the touch-action value, the ID of the nearest scroll node and the Region containing >+ the bounds of each of those elements to send it up to the UI process along with touch regions. Computing the list of allowed touch >+ actions for a given element accounts for not only the value specified directly on that element's style, but also in its hierarchy, >+ crossing any frame boundary towards the top-level document's root node. >+ >+ Tests: pointerevents/ios/touch-action-none-in-overflow-scrolling-touch.html >+ pointerevents/ios/touch-action-none-on-iframe.html >+ pointerevents/ios/touch-action-none-on-parent.html >+ pointerevents/ios/touch-action-none.html >+ pointerevents/ios/touch-action-pan-x-pan-y.html >+ pointerevents/ios/touch-action-pan-x.html >+ pointerevents/ios/touch-action-pan-y.html >+ pointerevents/ios/touch-action-pinch-zoom-allows-zooming.html >+ pointerevents/ios/touch-action-pinch-zoom-prevents-scrolling.html >+ >+ * WebCore.xcodeproj/project.pbxproj: >+ * dom/Document.cpp: >+ (WebCore::Document::invalidateRenderingDependentRegions): >+ (WebCore::Document::nodeWillBeRemoved): Ensure a node that is being removed from this document is no longer listed in its >+ list of elements with a non-auto touch-action property. >+ (WebCore::Document::absoluteRegionForNode): >+ (WebCore::Document::absoluteRegionForEventTargets): >+ (WebCore::Document::updateTouchActionElements): Create a list of elements with a non-auto touch-action property if one doesn't >+ exist yet and update it to add the given element if it contains a non-auto touch-action, or remove it if it doesn't. If the contents >+ of that list changed as a result, the scrolling coordinator is informed. >+ * dom/Document.h: >+ (WebCore::Document:: const): >+ * dom/Element.cpp: >+ (WebCore::parentCrossingFrameBoundaries): >+ (WebCore::Element::computedTouchActions const): Provide the list of allowed touch actions accounting for the "touch-action" property >+ specified on this element and all of its hierarchy, crossing frame boundary. >+ (WebCore::Element::nearestScrollingNodeIDUsingTouchOverflowScrolling const): Provide the ScrollingNodeID, if any, for the nearest scrolling node >+ for that element. This will allow the UI process to identify which scroll view's behavior to customize to reflect the element's allowed >+ touch actions. >+ * dom/Element.h: >+ * page/scrolling/ScrollingCoordinator.cpp: >+ (WebCore::ScrollingCoordinator::absoluteEventTrackingRegionsForFrame const): Compute the region for all elements with a non-auto touch-action property >+ throughout the provided frame and all of its subframes. >+ * page/scrolling/ScrollingCoordinator.h: >+ (WebCore::ScrollableAreaParameters::operator== const): Deleted. >+ * page/scrolling/ScrollingCoordinatorTypes.h: Added. >+ (WebCore::ScrollableAreaParameters::operator== const): >+ * page/scrolling/ScrollingTree.cpp: >+ (WebCore::ScrollingTree::touchActionDataAtPoint const): Query the list of TouchActionData objects for a match based on the provided point. Right >+ now the logic is pretty crude, stopping at the first TouchActionData for which the region contains the provided point, but future patches will >+ account for overlap and nesting. >+ (WebCore::ScrollingTree::setTouchActionsForScrollingNodeID): >+ * page/scrolling/ScrollingTree.h: >+ * page/scrolling/ScrollingTreeNode.h: >+ (WebCore::ScrollingTreeNode::touchActions const): >+ (WebCore::ScrollingTreeNode::setTouchActions): >+ * platform/EventTrackingRegions.cpp: >+ (WebCore::operator==): >+ * platform/EventTrackingRegions.h: >+ (WebCore::operator!=): >+ * style/StyleTreeResolver.cpp: >+ (WebCore::Style::TreeResolver::resolveElement): Update the list of elements with a non-auto touch-action property when an element's style changes. >+ > 2019-01-24 Brent Fulgham <bfulgham@apple.com> > > Activate the WebResourceLoadStatisticsStore in the NetworkProcess and deactivate it in the UIProcess. >diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog >index 04c5c0497750fe84b67bef504bb5c0de3e23e12e..e0cd46e701b9331430e03806082a25f8be117880 100644 >--- a/Source/WebKit/ChangeLog >+++ b/Source/WebKit/ChangeLog >@@ -1,3 +1,51 @@ >+2019-01-24 Antoine Quint <graouts@apple.com> >+ >+ Limit user-agent interactions based on the touch-action property >+ https://bugs.webkit.org/show_bug.cgi?id=193447 >+ <rdar://problem/47283874> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Handle the "none", "pan-x", "pan-y" and "pinch-zoom" values for the touch-action property by querying the scrolling tree whenever a touch begins >+ to identify whether its point is contained within the region of an element with a non-auto touch-action property. If it is, we use the list of >+ permitted touch actions such to then customize the behavior of the nearest scroll view to pan or zoom only as instructed. >+ >+ * Shared/WebCoreArgumentCoders.cpp: >+ (IPC::ArgumentCoder<TouchActionData>::encode): >+ (IPC::ArgumentCoder<TouchActionData>::decode): >+ (IPC::ArgumentCoder<EventTrackingRegions>::encode): >+ (IPC::ArgumentCoder<EventTrackingRegions>::decode): >+ (IPC::ArgumentCoder<Region>::decode): >+ * Shared/WebCoreArgumentCoders.h: >+ * UIProcess/API/Cocoa/WKWebView.mm: >+ (-[WKWebView scrollViewWillEndDragging:withVelocity:targetContentOffset:]): Account for panning constraints set on the content view to prevent deceleration >+ to pan the view if it ought not. >+ (-[WKWebView _scrollView:adjustedOffsetForOffset:translation:startPoint:locationInView:horizontalVelocity:verticalVelocity:]): Implement an additional >+ UIScrollView delegation method to apply the panning constraints set on the content view while panning. >+ * UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp: >+ (WebKit::RemoteScrollingCoordinatorProxy::touchActionDataAtPoint const): >+ (WebKit::RemoteScrollingCoordinatorProxy::setTouchActionsForScrollingNodeID): >+ * UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h: >+ * UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm: >+ (-[WKScrollingNodeScrollViewDelegate scrollViewWillEndDragging:withVelocity:targetContentOffset:]): Apply the same logic as in WKWebView. >+ (-[WKScrollingNodeScrollViewDelegate scrollViewDidEndDragging:willDecelerate:]): Reset the touch actions for the associated scroll node to start the next >+ interaction in the default state. >+ (-[WKScrollingNodeScrollViewDelegate scrollViewDidEndDecelerating:]): Reset the touch actions for the associated scroll node to start the next interaction >+ in the default state. >+ (-[WKScrollingNodeScrollViewDelegate _scrollView:adjustedOffsetForOffset:translation:startPoint:locationInView:horizontalVelocity:verticalVelocity:]): Apply >+ the same logic as in WKWebView. >+ * UIProcess/WebPageProxy.h: >+ (WebKit::WebPageProxy::isScrollingOrZooming const): >+ * UIProcess/ios/WKContentViewInteraction.h: >+ * UIProcess/ios/WKContentViewInteraction.mm: >+ (-[WKContentView preventsPanningInXAxis]): >+ (-[WKContentView preventsPanningInYAxis]): >+ (-[WKContentView cleanupInteraction]): >+ (-[WKContentView _webTouchEventsRecognized:]): As we process touches, check whether there are touch actions set for this touch's points' locations. Based >+ on those touch actions, either setDefaultPrevented on the _touchEventGestureRecognizer if the touch action is "none" or selectively disable panning and zooming. >+ (-[WKContentView _resetPanningPreventionFlags]): >+ (-[WKContentView _didEndScrollingOrZooming]): >+ > 2019-01-24 Brent Fulgham <bfulgham@apple.com> > > Activate the WebResourceLoadStatisticsStore in the NetworkProcess and deactivate it in the UIProcess. >diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj >index c3d8fdb0456340c9ccf04a02f7af143988642c64..81137599ca43bee8d5ebe25469e61c526061bbaf 100644 >--- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj >+++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj >@@ -2082,6 +2082,7 @@ > 712BE4831FE865DD002031CC /* FillMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 712BE4811FE865D4002031CC /* FillMode.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 712BE4881FE8686A002031CC /* JSFillMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 712BE4851FE86818002031CC /* JSFillMode.h */; }; > 712BE4891FE86875002031CC /* JSPlaybackDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 712BE4861FE86859002031CC /* JSPlaybackDirection.h */; }; >+ 712DBA4921F8AD83008F36B2 /* ScrollingCoordinatorTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 712DBA4721F8AD79008F36B2 /* ScrollingCoordinatorTypes.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 713171341FBE78DB00F758DE /* CSSPropertyBlendingClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 713171321FBE78C500F758DE /* CSSPropertyBlendingClient.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 7132445120109DA500AE7FB2 /* WebAnimationUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 7132444F20109D9B00AE7FB2 /* WebAnimationUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 7134496E146941B300720312 /* SVGLengthContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 7134496C146941B300720312 /* SVGLengthContext.h */; settings = {ATTRIBUTES = (Private, ); }; }; >@@ -2404,7 +2405,7 @@ > 834476EF1DA5BC5E002B6ED2 /* JSScrollToOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E9B3011DA5A51E00FFE8F6 /* JSScrollToOptions.h */; }; > 8348BFAC1B85729800912F36 /* ClassCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 8348BFAA1B85729500912F36 /* ClassCollection.h */; }; > 834DFAD01F7DAE5D00C2725B /* SharedStringHash.h in Headers */ = {isa = PBXBuildFile; fileRef = 834DFACC1F7DAE5600C2725B /* SharedStringHash.h */; settings = {ATTRIBUTES = (Private, ); }; }; >- 83520C7E1A71BFCC006BD2AA /* CSSFontFamily.h in Headers */ = {isa = PBXBuildFile; fileRef = 83520C7D1A71BFCC006BD2AA /* CSSFontFamily.h */; }; >+ 83520C7E1A71BFCC006BD2AA /* CSSFontFamily.h in Headers */ = {isa = PBXBuildFile; fileRef = 83520C7D1A71BFCC006BD2AA /* CSSFontFamily.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 8358CB701C53277500E0C2D8 /* JSXMLDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F570AD1C53268E007FD6CB /* JSXMLDocument.h */; }; > 835D2D781F5F1FBD00141DED /* HTMLInputElementEntriesAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 835D2D751F5F1FB800141DED /* HTMLInputElementEntriesAPI.h */; }; > 835D363719FF6193004C93AB /* StyleBuilderCustom.h in Headers */ = {isa = PBXBuildFile; fileRef = 835D363619FF6193004C93AB /* StyleBuilderCustom.h */; }; >@@ -2890,7 +2891,7 @@ > 9920398318B95BC600B39AF9 /* UserInputBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = 9920398118B95BC600B39AF9 /* UserInputBridge.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 996E59DF1DF0128D006612B9 /* NavigatorWebDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 996E59DC1DF00D90006612B9 /* NavigatorWebDriver.h */; }; > 9A528E8417D7F52F00AA9518 /* FloatingObjects.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A528E8217D7F52F00AA9518 /* FloatingObjects.h */; settings = {ATTRIBUTES = (Private, ); }; }; >- 9AB1F38018E2489A00534743 /* CSSToLengthConversionData.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AB1F37E18E2489A00534743 /* CSSToLengthConversionData.h */; }; >+ 9AB1F38018E2489A00534743 /* CSSToLengthConversionData.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AB1F37E18E2489A00534743 /* CSSToLengthConversionData.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 9B24DE8E15194B9500C59C27 /* HTMLBDIElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B24DE8C15194B9500C59C27 /* HTMLBDIElement.h */; }; > 9B2D8A7914997CCF00ECEF3E /* UndoStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B2D8A7814997CCF00ECEF3E /* UndoStep.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 9B32CDA913DF7FA900F34D13 /* RenderedPosition.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B32CDA713DF7FA900F34D13 /* RenderedPosition.h */; }; >@@ -4767,7 +4768,7 @@ > E1E1BF00115FF6FB006F52CA /* WindowsKeyboardCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = E1E1BEFF115FF6FB006F52CA /* WindowsKeyboardCodes.h */; settings = {ATTRIBUTES = (Private, ); }; }; > E1E6EEA80B628DB3005F2F70 /* JSHTMLSelectElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E1E6EEA70B628DB3005F2F70 /* JSHTMLSelectElement.h */; }; > E1EC29A00BB04C6B00EA187B /* XPathNodeSet.h in Headers */ = {isa = PBXBuildFile; fileRef = E1EC299E0BB04C6B00EA187B /* XPathNodeSet.h */; settings = {ATTRIBUTES = (Private, ); }; }; >- E1ED8AC30CC49BE000BFC557 /* CSSPrimitiveValueMappings.h in Headers */ = {isa = PBXBuildFile; fileRef = E1ED8AC20CC49BE000BFC557 /* CSSPrimitiveValueMappings.h */; }; >+ E1ED8AC30CC49BE000BFC557 /* CSSPrimitiveValueMappings.h in Headers */ = {isa = PBXBuildFile; fileRef = E1ED8AC20CC49BE000BFC557 /* CSSPrimitiveValueMappings.h */; settings = {ATTRIBUTES = (Private, ); }; }; > E1F1E8300C3C2BB9006DB391 /* XSLTExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = E1F1E82E0C3C2BB9006DB391 /* XSLTExtensions.h */; }; > E1F80B8818317252007885C3 /* CryptoKeyPair.h in Headers */ = {isa = PBXBuildFile; fileRef = E1F80B8618317252007885C3 /* CryptoKeyPair.h */; settings = {ATTRIBUTES = (Private, ); }; }; > E1F80B8E183172B5007885C3 /* JSCryptoKeyPair.h in Headers */ = {isa = PBXBuildFile; fileRef = E1F80B8C183172B5007885C3 /* JSCryptoKeyPair.h */; }; >@@ -9286,6 +9287,7 @@ > 712BE4851FE86818002031CC /* JSFillMode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSFillMode.h; sourceTree = "<group>"; }; > 712BE4861FE86859002031CC /* JSPlaybackDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSPlaybackDirection.h; sourceTree = "<group>"; }; > 712BE4871FE8685A002031CC /* JSPlaybackDirection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSPlaybackDirection.cpp; sourceTree = "<group>"; }; >+ 712DBA4721F8AD79008F36B2 /* ScrollingCoordinatorTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollingCoordinatorTypes.h; sourceTree = "<group>"; }; > 7130141D1DC9C08600CA3A88 /* pip-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "pip-support.js"; sourceTree = "<group>"; }; > 713171321FBE78C500F758DE /* CSSPropertyBlendingClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSPropertyBlendingClient.h; sourceTree = "<group>"; }; > 7132444F20109D9B00AE7FB2 /* WebAnimationUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebAnimationUtilities.h; sourceTree = "<group>"; }; >@@ -16884,6 +16886,7 @@ > 0F605AEB15F94848004DF0C0 /* ScrollingConstraints.h */, > 1AF62EE414DA22A70041556C /* ScrollingCoordinator.cpp */, > 1AF62EE514DA22A70041556C /* ScrollingCoordinator.h */, >+ 712DBA4721F8AD79008F36B2 /* ScrollingCoordinatorTypes.h */, > 51C61B081DE536E7008A212D /* ScrollingMomentumCalculator.cpp */, > 51C61B091DE536E7008A212D /* ScrollingMomentumCalculator.h */, > 93C38BFC164473C700091EB2 /* ScrollingStateFixedNode.cpp */, >@@ -31373,6 +31376,7 @@ > 1AF62EE814DA22A70041556C /* ScrollingCoordinator.h in Headers */, > 0FC4E40D187F82E10045882C /* ScrollingCoordinatorIOS.h in Headers */, > 9391A991162746CB00297330 /* ScrollingCoordinatorMac.h in Headers */, >+ 712DBA4921F8AD83008F36B2 /* ScrollingCoordinatorTypes.h in Headers */, > 51C61B0B1DE536E7008A212D /* ScrollingMomentumCalculator.h in Headers */, > 517DEEE81DE94B0800B91644 /* ScrollingMomentumCalculatorMac.h in Headers */, > 93C38BFF164473C700091EB2 /* ScrollingStateFixedNode.h in Headers */, >diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp >index 5db71f516f6f88d7421149b19527f4e7ad8c1759..d299ef22c3b5bdb8d9cda215ff2f65fb71d95df2 100644 >--- a/Source/WebCore/dom/Document.cpp >+++ b/Source/WebCore/dom/Document.cpp >@@ -208,6 +208,7 @@ > #include "TextAutoSizing.h" > #include "TextEvent.h" > #include "TextNodeTraversal.h" >+#include "TouchAction.h" > #include "TransformSource.h" > #include "TreeWalker.h" > #include "UndoManager.h" >@@ -4151,6 +4152,14 @@ void Document::invalidateRenderingDependentRegions(AnnotationsAction annotations > #if PLATFORM(IOS_FAMILY) && ENABLE(TOUCH_EVENTS) > setTouchEventRegionsNeedUpdate(); > #endif >+#if ENABLE(POINTER_EVENTS) >+ if (auto* page = this->page()) { >+ if (auto* frameView = view()) { >+ if (auto* scrollingCoordinator = page->scrollingCoordinator()) >+ scrollingCoordinator->frameViewEventTrackingRegionsChanged(*frameView); >+ } >+ } >+#endif > } > > void Document::invalidateScrollbarDependentRegions() >@@ -4534,6 +4543,11 @@ void Document::nodeWillBeRemoved(Node& node) > > if (is<Text>(node)) > m_markers->removeMarkers(node); >+ >+#if ENABLE(POINTER_EVENTS) >+ if (m_touchActionElements) >+ m_touchActionElements->remove(&node); >+#endif > } > > static Node* fallbackFocusNavigationStartingNodeAfterRemoval(Node& node) >@@ -7096,6 +7110,34 @@ LayoutRect Document::absoluteEventHandlerBounds(bool& includesFixedPositionEleme > return LayoutRect(); > } > >+Document::RegionFixedPair Document::absoluteRegionForNode(Node* node) >+{ >+ Region region; >+ LayoutRect rootRelativeBounds; >+ bool insideFixedPosition = false; >+ >+ if (is<Document>(node)) { >+ Document* document = downcast<Document>(node); >+ if (document == this) >+ rootRelativeBounds = absoluteEventHandlerBounds(insideFixedPosition); >+ else if (Element* element = document->ownerElement()) >+ rootRelativeBounds = element->absoluteEventHandlerBounds(insideFixedPosition); >+ } else if (is<Element>(node)) { >+ Element* element = downcast<Element>(node); >+ if (is<HTMLBodyElement>(element)) { >+ // For the body, just use the document bounds. >+ // The body may not cover this whole area, but it's OK for this region to be an overestimate. >+ rootRelativeBounds = absoluteEventHandlerBounds(insideFixedPosition); >+ } else >+ rootRelativeBounds = element->absoluteEventHandlerBounds(insideFixedPosition); >+ } >+ >+ if (!rootRelativeBounds.isEmpty()) >+ region.unite(Region(enclosingIntRect(rootRelativeBounds))); >+ >+ return RegionFixedPair(region, insideFixedPosition); >+} >+ > Document::RegionFixedPair Document::absoluteRegionForEventTargets(const EventTargetSet* targets) > { > LayoutDisallowedScope layoutDisallowedScope(LayoutDisallowedScope::Reason::ReentrancyAvoidance); >@@ -7107,26 +7149,9 @@ Document::RegionFixedPair Document::absoluteRegionForEventTargets(const EventTar > bool insideFixedPosition = false; > > for (auto& keyValuePair : *targets) { >- LayoutRect rootRelativeBounds; >- >- if (is<Document>(keyValuePair.key)) { >- Document* document = downcast<Document>(keyValuePair.key); >- if (document == this) >- rootRelativeBounds = absoluteEventHandlerBounds(insideFixedPosition); >- else if (Element* element = document->ownerElement()) >- rootRelativeBounds = element->absoluteEventHandlerBounds(insideFixedPosition); >- } else if (is<Element>(keyValuePair.key)) { >- Element* element = downcast<Element>(keyValuePair.key); >- if (is<HTMLBodyElement>(element)) { >- // For the body, just use the document bounds. >- // The body may not cover this whole area, but it's OK for this region to be an overestimate. >- rootRelativeBounds = absoluteEventHandlerBounds(insideFixedPosition); >- } else >- rootRelativeBounds = element->absoluteEventHandlerBounds(insideFixedPosition); >- } >- >- if (!rootRelativeBounds.isEmpty()) >- targetRegion.unite(Region(enclosingIntRect(rootRelativeBounds))); >+ auto targetRegionFixedPair = absoluteRegionForNode(keyValuePair.key); >+ targetRegion.unite(targetRegionFixedPair.first); >+ insideFixedPosition |= targetRegionFixedPair.second; > } > > return RegionFixedPair(targetRegion, insideFixedPosition); >@@ -8651,4 +8676,33 @@ void Document::setPaintWorkletGlobalScopeForName(const String& name, Ref<PaintWo > } > #endif > >+#if ENABLE(POINTER_EVENTS) >+void Document::updateTouchActionElements(Node& node, RenderStyle* style) >+{ >+ if (!m_touchActionElements) >+ m_touchActionElements = std::make_unique<HashSet<Node*>>(); >+ >+ bool changed = false; >+ >+ if (style && style->touchActions() != TouchAction::Auto) >+ changed |= m_touchActionElements->add(&node).isNewEntry; >+ else >+ changed |= m_touchActionElements->remove(&node); >+ >+#if PLATFORM(IOS_FAMILY) >+ if (!changed) >+ return; >+ >+ Page* page = this->page(); >+ if (!page) >+ return; >+ >+ if (FrameView* frameView = view()) { >+ if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) >+ scrollingCoordinator->frameViewEventTrackingRegionsChanged(*frameView); >+ } >+#endif >+} >+#endif >+ > } // namespace WebCore >diff --git a/Source/WebCore/dom/Document.h b/Source/WebCore/dom/Document.h >index bb5c1c046654f19a1a869544b6333e726b0716d4..4a6a1aa290dc4eecb8976158a6491857f40c7ca5 100644 >--- a/Source/WebCore/dom/Document.h >+++ b/Source/WebCore/dom/Document.h >@@ -1271,6 +1271,7 @@ public: > #if ENABLE(TOUCH_EVENTS) > bool hasTouchEventHandlers() const { return (m_touchEventTargets.get()) ? m_touchEventTargets->size() : false; } > bool touchEventTargetsContain(Node& node) const { return m_touchEventTargets ? m_touchEventTargets->contains(&node) : false; } >+ void updateTouchActionElements(Node&, RenderStyle*); > #else > bool hasTouchEventHandlers() const { return false; } > bool touchEventTargetsContain(Node&) const { return false; } >@@ -1290,9 +1291,14 @@ public: > #endif > } > >+#if ENABLE(POINTER_EVENTS) >+ const HashSet<Node*>* touchActionElements() const { return m_touchActionElements.get(); } >+#endif >+ > const EventTargetSet* wheelEventTargets() const { return m_wheelEventTargets.get(); } > > typedef std::pair<Region, bool> RegionFixedPair; >+ RegionFixedPair absoluteRegionForNode(Node*); > RegionFixedPair absoluteRegionForEventTargets(const EventTargetSet*); > > LayoutRect absoluteEventHandlerBounds(bool&) final; >@@ -1873,6 +1879,9 @@ private: > > #if ENABLE(TOUCH_EVENTS) > std::unique_ptr<EventTargetSet> m_touchEventTargets; >+#endif >+#if ENABLE(POINTER_EVENTS) >+ std::unique_ptr<HashSet<Node*>> m_touchActionElements; > #endif > std::unique_ptr<EventTargetSet> m_wheelEventTargets; > >diff --git a/Source/WebCore/dom/Element.cpp b/Source/WebCore/dom/Element.cpp >index b2f13d022c09873252479cbeaa5086270a842bd6..e28a3af5c270b4a1386f12105d4432e65f85d395 100644 >--- a/Source/WebCore/dom/Element.cpp >+++ b/Source/WebCore/dom/Element.cpp >@@ -81,6 +81,8 @@ > #include "PointerLockController.h" > #include "RenderFragmentContainer.h" > #include "RenderLayer.h" >+#include "RenderLayerBacking.h" >+#include "RenderLayerCompositor.h" > #include "RenderListBox.h" > #include "RenderTheme.h" > #include "RenderTreeUpdater.h" >@@ -3416,7 +3418,7 @@ void Element::setContainsFullScreenElement(bool flag) > invalidateStyleAndLayerComposition(); > } > >-static Element* parentCrossingFrameBoundaries(Element* element) >+static Element* parentCrossingFrameBoundaries(const Element* element) > { > ASSERT(element); > return element->parentElement() ? element->parentElement() : element->document().ownerElement(); >@@ -4120,4 +4122,66 @@ void Element::setAttributeStyleMap(Ref<StylePropertyMap>&& map) > } > #endif > >+#if ENABLE(POINTER_EVENTS) >+OptionSet<TouchAction> Element::computedTouchActions() const >+{ >+ OptionSet<TouchAction> computedTouchActions = TouchAction::Auto; >+ for (auto* element = this; element; element = parentCrossingFrameBoundaries(element)) { >+ auto* renderer = element->renderer(); >+ if (!renderer) >+ continue; >+ >+ auto touchActions = renderer->style().touchActions(); >+ >+ // Once we've encountered touch-action: none, we know that this will be the computed value. >+ if (touchActions == TouchAction::None) >+ return touchActions; >+ >+ // If the computed touch-action so far was "auto", we can just use the current element's touch-action. >+ if (computedTouchActions == TouchAction::Auto) { >+ computedTouchActions = touchActions; >+ continue; >+ } >+ >+ // If the current element has touch-action: auto or the same touch-action as the computed touch-action, >+ // we need to keep going up the ancestry chain. >+ if (touchActions == TouchAction::Auto || touchActions == computedTouchActions) >+ continue; >+ >+ // Now, the element's touch-action and the computed touch-action are different and are neither "auto" nor "none". >+ if (computedTouchActions == TouchAction::Manipulation) { >+ // If the computed touch-action is "manipulation", we can take the current element's touch-action as the newly >+ // computed touch-action. >+ computedTouchActions = touchActions; >+ } else if (touchActions == TouchAction::Manipulation) { >+ // Otherwise, we have a restricted computed touch-action so far. If the current element's touch-action is "manipulation" >+ // then we can just keep going and leave the computed touch-action untouched. >+ continue; >+ } >+ >+ // In any other case, we have competing restrictive touch-action values that can only yield "none". >+ return TouchAction::None; >+ } >+ return computedTouchActions; >+} >+ >+#if ENABLE(ACCELERATED_OVERFLOW_SCROLLING) >+ScrollingNodeID Element::nearestScrollingNodeIDUsingTouchOverflowScrolling() const >+{ >+ if (!renderer()) >+ return 0; >+ >+ // We are not interested in the root, so check that we also have a valid parent. >+ for (auto* layer = renderer()->enclosingLayer(); layer && layer->parent(); layer = layer->parent()) { >+ if (layer->isComposited()) { >+ if (auto scrollingNodeID = layer->backing()->scrollingNodeIDForRole(ScrollCoordinationRole::Scrolling)) >+ return scrollingNodeID; >+ } >+ } >+ >+ return 0; >+} >+#endif >+#endif >+ > } // namespace WebCore >diff --git a/Source/WebCore/dom/Element.h b/Source/WebCore/dom/Element.h >index c5eb6cb8c448b099c5e0126ad5a1e7c4e8842b86..8c8866139f49fc8a854402c97fd22ae84324dd05 100644 >--- a/Source/WebCore/dom/Element.h >+++ b/Source/WebCore/dom/Element.h >@@ -31,6 +31,7 @@ > #include "KeyframeAnimationOptions.h" > #include "ScrollToOptions.h" > #include "ScrollTypes.h" >+#include "ScrollingCoordinator.h" > #include "ShadowRootMode.h" > #include "SimulatedClickOptions.h" > #include "StyleChange.h" >@@ -56,6 +57,7 @@ class PseudoElement; > class RenderTreePosition; > class StylePropertyMap; > class WebAnimation; >+enum class TouchAction : uint8_t; > struct ElementStyle; > struct ScrollIntoViewOptions; > >@@ -588,6 +590,13 @@ public: > ExceptionOr<Ref<WebAnimation>> animate(JSC::ExecState&, JSC::Strong<JSC::JSObject>&&, Optional<Variant<double, KeyframeAnimationOptions>>&&); > Vector<RefPtr<WebAnimation>> getAnimations(); > >+#if ENABLE(POINTER_EVENTS) >+ OptionSet<TouchAction> computedTouchActions() const; >+#if ENABLE(ACCELERATED_OVERFLOW_SCROLLING) >+ ScrollingNodeID nearestScrollingNodeIDUsingTouchOverflowScrolling() const; >+#endif >+#endif >+ > protected: > Element(const QualifiedName&, Document&, ConstructionType); > >diff --git a/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp b/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp >index c4faedd3e8f5485b300bf43d3aa6f1eab9301545..130b26112a920a7db60e1ded438e89b234a6aa6d 100644 >--- a/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp >+++ b/Source/WebCore/page/scrolling/ScrollingCoordinator.cpp >@@ -38,6 +38,7 @@ > #include "Region.h" > #include "RenderLayerCompositor.h" > #include "RenderView.h" >+#include "RuntimeEnabledFeatures.h" > #include "ScrollAnimator.h" > #include "Settings.h" > #include <wtf/MainThread.h> >@@ -107,7 +108,25 @@ EventTrackingRegions ScrollingCoordinator::absoluteEventTrackingRegionsForFrame( > auto* document = frame.document(); > if (!document) > return EventTrackingRegions(); >- return document->eventTrackingRegions(); >+ auto eventTrackingRegions = document->eventTrackingRegions(); >+ >+#if ENABLE(POINTER_EVENTS) >+ if (RuntimeEnabledFeatures::sharedFeatures().pointerEventsEnabled()) { >+ auto& touchActionData = eventTrackingRegions.touchActionData; >+ for (auto& node : *frame.document()->touchActionElements()) { >+ if (!is<Element>(node)) >+ continue; >+ auto& element = downcast<Element>(*node); >+ touchActionData.append({ >+ element.computedTouchActions(), >+ element.nearestScrollingNodeIDUsingTouchOverflowScrolling(), >+ node->document().absoluteRegionForNode(node).first >+ }); >+ } >+ } >+#endif >+ >+ return eventTrackingRegions; > #else > auto* frameView = frame.view(); > if (!frameView) >diff --git a/Source/WebCore/page/scrolling/ScrollingCoordinator.h b/Source/WebCore/page/scrolling/ScrollingCoordinator.h >index ac976e3bca226ab5b750eb600b07758c60961b1e..d4bb5f52d7c176716a9e2dd905cdac3dcafe0f3c 100644 >--- a/Source/WebCore/page/scrolling/ScrollingCoordinator.h >+++ b/Source/WebCore/page/scrolling/ScrollingCoordinator.h >@@ -30,6 +30,7 @@ > #include "PlatformWheelEvent.h" > #include "ScrollSnapOffsetsInfo.h" > #include "ScrollTypes.h" >+#include "ScrollingCoordinatorTypes.h" > #include <wtf/Forward.h> > #include <wtf/ThreadSafeRefCounted.h> > #include <wtf/TypeCasts.h> >@@ -51,27 +52,6 @@ class TextStream; > > namespace WebCore { > >-typedef unsigned SynchronousScrollingReasons; >-typedef uint64_t ScrollingNodeID; >- >-enum class ScrollingNodeType : uint8_t { >- MainFrame, >- Subframe, >- FrameHosting, >- Overflow, >- Fixed, >- Sticky >-}; >- >-enum ScrollingStateTreeAsTextBehaviorFlags { >- ScrollingStateTreeAsTextBehaviorNormal = 0, >- ScrollingStateTreeAsTextBehaviorIncludeLayerIDs = 1 << 0, >- ScrollingStateTreeAsTextBehaviorIncludeNodeIDs = 1 << 1, >- ScrollingStateTreeAsTextBehaviorIncludeLayerPositions = 1 << 2, >- ScrollingStateTreeAsTextBehaviorDebug = ScrollingStateTreeAsTextBehaviorIncludeLayerIDs | ScrollingStateTreeAsTextBehaviorIncludeNodeIDs | ScrollingStateTreeAsTextBehaviorIncludeLayerPositions >-}; >-typedef unsigned ScrollingStateTreeAsTextBehavior; >- > class Document; > class Frame; > class FrameView; >@@ -86,42 +66,6 @@ class ViewportConstraints; > class ScrollingTree; > #endif > >-enum class ScrollingLayerPositionAction { >- Set, >- SetApproximate, >- Sync >-}; >- >-struct ScrollableAreaParameters { >- ScrollElasticity horizontalScrollElasticity { ScrollElasticityNone }; >- ScrollElasticity verticalScrollElasticity { ScrollElasticityNone }; >- >- ScrollbarMode horizontalScrollbarMode { ScrollbarAuto }; >- ScrollbarMode verticalScrollbarMode { ScrollbarAuto }; >- >- bool hasEnabledHorizontalScrollbar { false }; >- bool hasEnabledVerticalScrollbar { false }; >- >- bool useDarkAppearanceForScrollbars { false }; >- >- bool operator==(const ScrollableAreaParameters& other) const >- { >- return horizontalScrollElasticity == other.horizontalScrollElasticity >- && verticalScrollElasticity == other.verticalScrollElasticity >- && horizontalScrollbarMode == other.horizontalScrollbarMode >- && verticalScrollbarMode == other.verticalScrollbarMode >- && hasEnabledHorizontalScrollbar == other.hasEnabledHorizontalScrollbar >- && hasEnabledVerticalScrollbar == other.hasEnabledVerticalScrollbar >- && useDarkAppearanceForScrollbars == other.useDarkAppearanceForScrollbars; >- } >-}; >- >-enum class ViewportRectStability { >- Stable, >- Unstable, >- ChangingObscuredInsetsInteractively // This implies Unstable. >-}; >- > class ScrollingCoordinator : public ThreadSafeRefCounted<ScrollingCoordinator> { > public: > static Ref<ScrollingCoordinator> create(Page*); >diff --git a/Source/WebCore/page/scrolling/ScrollingCoordinatorTypes.h b/Source/WebCore/page/scrolling/ScrollingCoordinatorTypes.h >new file mode 100644 >index 0000000000000000000000000000000000000000..137ebd2ffcc9c8fe400c0cec67d6016967914b15 >--- /dev/null >+++ b/Source/WebCore/page/scrolling/ScrollingCoordinatorTypes.h >@@ -0,0 +1,89 @@ >+/* >+ * Copyright (C) 2019 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' >+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, >+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS >+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR >+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS >+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN >+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) >+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF >+ * THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+#pragma once >+ >+#include "ScrollTypes.h" >+ >+namespace WebCore { >+ >+typedef unsigned SynchronousScrollingReasons; >+typedef uint64_t ScrollingNodeID; >+ >+enum class ScrollingNodeType : uint8_t { >+ MainFrame, >+ Subframe, >+ FrameHosting, >+ Overflow, >+ Fixed, >+ Sticky >+}; >+ >+enum ScrollingStateTreeAsTextBehaviorFlags { >+ ScrollingStateTreeAsTextBehaviorNormal = 0, >+ ScrollingStateTreeAsTextBehaviorIncludeLayerIDs = 1 << 0, >+ ScrollingStateTreeAsTextBehaviorIncludeNodeIDs = 1 << 1, >+ ScrollingStateTreeAsTextBehaviorIncludeLayerPositions = 1 << 2, >+ ScrollingStateTreeAsTextBehaviorDebug = ScrollingStateTreeAsTextBehaviorIncludeLayerIDs | ScrollingStateTreeAsTextBehaviorIncludeNodeIDs | ScrollingStateTreeAsTextBehaviorIncludeLayerPositions >+}; >+typedef unsigned ScrollingStateTreeAsTextBehavior; >+ >+enum class ScrollingLayerPositionAction { >+ Set, >+ SetApproximate, >+ Sync >+}; >+ >+struct ScrollableAreaParameters { >+ ScrollElasticity horizontalScrollElasticity { ScrollElasticityNone }; >+ ScrollElasticity verticalScrollElasticity { ScrollElasticityNone }; >+ >+ ScrollbarMode horizontalScrollbarMode { ScrollbarAuto }; >+ ScrollbarMode verticalScrollbarMode { ScrollbarAuto }; >+ >+ bool hasEnabledHorizontalScrollbar { false }; >+ bool hasEnabledVerticalScrollbar { false }; >+ >+ bool useDarkAppearanceForScrollbars { false }; >+ >+ bool operator==(const ScrollableAreaParameters& other) const >+ { >+ return horizontalScrollElasticity == other.horizontalScrollElasticity >+ && verticalScrollElasticity == other.verticalScrollElasticity >+ && horizontalScrollbarMode == other.horizontalScrollbarMode >+ && verticalScrollbarMode == other.verticalScrollbarMode >+ && hasEnabledHorizontalScrollbar == other.hasEnabledHorizontalScrollbar >+ && hasEnabledVerticalScrollbar == other.hasEnabledVerticalScrollbar >+ && useDarkAppearanceForScrollbars == other.useDarkAppearanceForScrollbars; >+ } >+}; >+ >+enum class ViewportRectStability { >+ Stable, >+ Unstable, >+ ChangingObscuredInsetsInteractively // This implies Unstable. >+}; >+ >+} >diff --git a/Source/WebCore/page/scrolling/ScrollingTree.cpp b/Source/WebCore/page/scrolling/ScrollingTree.cpp >index 524393dd16aa637ff161d675b80ffd94e7aa1500..6f73e0a98f61fba27744aaca6eb4a1a917c4206f 100644 >--- a/Source/WebCore/page/scrolling/ScrollingTree.cpp >+++ b/Source/WebCore/page/scrolling/ScrollingTree.cpp >@@ -403,6 +403,25 @@ String ScrollingTree::scrollingTreeAsText() > return ts.release(); > } > >+#if ENABLE(POINTER_EVENTS) >+Optional<TouchActionData> ScrollingTree::touchActionDataAtPoint(const IntPoint p) const >+{ >+ // FIXME: This does not handle the case where there are multiple regions matching this point. >+ for (auto& touchActionData : m_eventTrackingRegions.touchActionData) { >+ if (touchActionData.region.contains(p)) >+ return touchActionData; >+ } >+ >+ return WTF::nullopt; >+} >+ >+void ScrollingTree::setTouchActionsForScrollingNodeID(OptionSet<TouchAction> touchActions, ScrollingNodeID scrollingNodeID) >+{ >+ if (auto* scrollingTreeNode = nodeForID(scrollingNodeID)) >+ scrollingTreeNode->setTouchActions(touchActions); >+} >+#endif >+ > } // namespace WebCore > > #endif // ENABLE(ASYNC_SCROLLING) >diff --git a/Source/WebCore/page/scrolling/ScrollingTree.h b/Source/WebCore/page/scrolling/ScrollingTree.h >index efc68415c39430df7f27043fe982332775567632..8e786b6259a8bb83ac0653231f727fbde334a3f3 100644 >--- a/Source/WebCore/page/scrolling/ScrollingTree.h >+++ b/Source/WebCore/page/scrolling/ScrollingTree.h >@@ -30,6 +30,7 @@ > #include "PlatformWheelEvent.h" > #include "Region.h" > #include "ScrollingCoordinator.h" >+#include "TouchAction.h" > #include "WheelEventTestTrigger.h" > #include <wtf/HashMap.h> > #include <wtf/Lock.h> >@@ -104,7 +105,10 @@ public: > #endif > > WEBCORE_EXPORT TrackingType eventTrackingTypeForPoint(const AtomicString& eventName, IntPoint); >- >+#if ENABLE(POINTER_EVENTS) >+ WEBCORE_EXPORT Optional<TouchActionData> touchActionDataAtPoint(const IntPoint) const; >+#endif >+ > #if PLATFORM(MAC) > virtual void handleWheelEventPhase(PlatformWheelEventPhase) = 0; > virtual void setActiveScrollSnapIndices(ScrollingNodeID, unsigned /*horizontalIndex*/, unsigned /*verticalIndex*/) { } >@@ -151,6 +155,9 @@ public: > } > > WEBCORE_EXPORT String scrollingTreeAsText(); >+#if ENABLE(POINTER_EVENTS) >+ WEBCORE_EXPORT void setTouchActionsForScrollingNodeID(OptionSet<TouchAction>, ScrollingNodeID); >+#endif > > protected: > void setMainFrameScrollPosition(FloatPoint); >diff --git a/Source/WebCore/page/scrolling/ScrollingTreeNode.h b/Source/WebCore/page/scrolling/ScrollingTreeNode.h >index 2f4415ac8852669c730d28a65751131e5ba2c993..af630b12973dffbaaf6fae3f0a3839b9ff53c401 100644 >--- a/Source/WebCore/page/scrolling/ScrollingTreeNode.h >+++ b/Source/WebCore/page/scrolling/ScrollingTreeNode.h >@@ -31,6 +31,7 @@ > #include "ScrollTypes.h" > #include "ScrollingCoordinator.h" > #include "ScrollingStateNode.h" >+#include "TouchAction.h" > #include <wtf/RefCounted.h> > #include <wtf/TypeCasts.h> > >@@ -71,6 +72,11 @@ public: > > WEBCORE_EXPORT void dump(WTF::TextStream&, ScrollingStateTreeAsTextBehavior) const; > >+#if ENABLE(POINTER_EVENTS) >+ OptionSet<TouchAction> touchActions() const { return m_touchActions; } >+ void setTouchActions(OptionSet<TouchAction> touchActions) { m_touchActions = touchActions; } >+#endif >+ > protected: > ScrollingTreeNode(ScrollingTree&, ScrollingNodeType, ScrollingNodeID); > ScrollingTree& scrollingTree() const { return m_scrollingTree; } >@@ -86,6 +92,9 @@ private: > const ScrollingNodeID m_nodeID; > > ScrollingTreeNode* m_parent; >+#if ENABLE(POINTER_EVENTS) >+ OptionSet<TouchAction> m_touchActions; >+#endif > }; > > } // namespace WebCore >diff --git a/Source/WebCore/platform/EventTrackingRegions.cpp b/Source/WebCore/platform/EventTrackingRegions.cpp >index 7e763c8953dca8e6dd7cacad6252407c70c5f863..437780098528bed10962f4862071b28671010944 100644 >--- a/Source/WebCore/platform/EventTrackingRegions.cpp >+++ b/Source/WebCore/platform/EventTrackingRegions.cpp >@@ -73,7 +73,19 @@ void EventTrackingRegions::unite(const EventTrackingRegions& eventTrackingRegion > bool operator==(const EventTrackingRegions& a, const EventTrackingRegions& b) > { > return a.asynchronousDispatchRegion == b.asynchronousDispatchRegion >+#if ENABLE(POINTER_EVENTS) >+ && a.touchActionData == b.touchActionData >+#endif > && a.eventSpecificSynchronousDispatchRegions == b.eventSpecificSynchronousDispatchRegions; > } > >+#if ENABLE(POINTER_EVENTS) >+bool operator==(const TouchActionData& a, const TouchActionData& b) >+{ >+ return a.touchActions == b.touchActions >+ && a.scrollingNodeID == b.scrollingNodeID >+ && a.region == b.region; >+} >+#endif >+ > } // namespace WebCore >diff --git a/Source/WebCore/platform/EventTrackingRegions.h b/Source/WebCore/platform/EventTrackingRegions.h >index 3d58ba55efd63ed9bae40501e309b861fc9e9c05..c2a206301af17dc16ce8fd79ee53bcbf1ca126d1 100644 >--- a/Source/WebCore/platform/EventTrackingRegions.h >+++ b/Source/WebCore/platform/EventTrackingRegions.h >@@ -30,6 +30,12 @@ > #include <wtf/text/StringHash.h> > #include <wtf/text/WTFString.h> > >+#if ENABLE(POINTER_EVENTS) >+#include "CSSPrimitiveValueMappings.h" >+#include "ScrollingCoordinatorTypes.h" >+#include "TouchAction.h" >+#endif >+ > namespace WebCore { > > enum class TrackingType : uint8_t { >@@ -38,6 +44,18 @@ enum class TrackingType : uint8_t { > Synchronous = 2 > }; > >+#if ENABLE(POINTER_EVENTS) >+typedef uint64_t ScrollingNodeID; >+struct TouchActionData { >+ OptionSet<TouchAction> touchActions { TouchAction::Auto }; >+ ScrollingNodeID scrollingNodeID; >+ Region region; >+}; >+ >+bool operator==(const TouchActionData&, const TouchActionData&); >+inline bool operator!=(const TouchActionData& a, const TouchActionData& b) { return !(a == b); } >+#endif >+ > struct EventTrackingRegions { > // Region for which events can be dispatched without blocking scrolling. > Region asynchronousDispatchRegion; >@@ -46,6 +64,10 @@ struct EventTrackingRegions { > // The key is the Event Name with an active handler. > HashMap<String, Region> eventSpecificSynchronousDispatchRegions; > >+#if ENABLE(POINTER_EVENTS) >+ Vector<TouchActionData> touchActionData; >+#endif >+ > bool isEmpty() const; > > void translate(IntSize); >diff --git a/Source/WebCore/style/StyleTreeResolver.cpp b/Source/WebCore/style/StyleTreeResolver.cpp >index 1b134dfea3a250e52cfd864c8bb1573cb854c21f..a6177f92a77dff0294e1ff97fa215115abf34ace 100644 >--- a/Source/WebCore/style/StyleTreeResolver.cpp >+++ b/Source/WebCore/style/StyleTreeResolver.cpp >@@ -236,6 +236,11 @@ ElementUpdates TreeResolver::resolveElement(Element& element) > auto beforeUpdate = resolvePseudoStyle(element, update, PseudoId::Before); > auto afterUpdate = resolvePseudoStyle(element, update, PseudoId::After); > >+#if ENABLE(POINTER_EVENTS) >+ if (RuntimeEnabledFeatures::sharedFeatures().pointerEventsEnabled()) >+ m_document.updateTouchActionElements(element, update.style.get()); >+#endif >+ > return { WTFMove(update), descendantsToResolve, WTFMove(beforeUpdate), WTFMove(afterUpdate) }; > } > >diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.cpp b/Source/WebKit/Shared/WebCoreArgumentCoders.cpp >index 3b0529a4a959a23bd5d040f5fa008ad7b0fec71f..54d199e25d84b25183fd9bf9f02cb95b5ac8ca77 100644 >--- a/Source/WebKit/Shared/WebCoreArgumentCoders.cpp >+++ b/Source/WebKit/Shared/WebCoreArgumentCoders.cpp >@@ -329,10 +329,40 @@ Optional<DOMCacheEngine::Record> ArgumentCoder<DOMCacheEngine::Record>::decode(D > return {{ WTFMove(identifier), WTFMove(updateResponseCounter), WTFMove(requestHeadersGuard), WTFMove(request), WTFMove(options.value()), WTFMove(referrer), WTFMove(responseHeadersGuard), WTFMove(response), WTFMove(responseBody), responseBodySize }}; > } > >+#if ENABLE(POINTER_EVENTS) >+void ArgumentCoder<TouchActionData>::encode(Encoder& encoder, const TouchActionData& touchActionData) >+{ >+ encoder << touchActionData.touchActions << touchActionData.scrollingNodeID << touchActionData.region; >+} >+ >+Optional<TouchActionData> ArgumentCoder<TouchActionData>::decode(Decoder& decoder) >+{ >+ Optional<OptionSet<TouchAction>> touchActions; >+ decoder >> touchActions; >+ if (!touchActions) >+ return WTF::nullopt; >+ >+ Optional<ScrollingNodeID> scrollingNodeID; >+ decoder >> scrollingNodeID; >+ if (!scrollingNodeID) >+ return WTF::nullopt; >+ >+ Optional<Region> region; >+ decoder >> region; >+ if (!region) >+ return WTF::nullopt; >+ >+ return {{ WTFMove(*touchActions), WTFMove(*scrollingNodeID), WTFMove(*region) }}; >+} >+#endif >+ > void ArgumentCoder<EventTrackingRegions>::encode(Encoder& encoder, const EventTrackingRegions& eventTrackingRegions) > { > encoder << eventTrackingRegions.asynchronousDispatchRegion; > encoder << eventTrackingRegions.eventSpecificSynchronousDispatchRegions; >+#if ENABLE(POINTER_EVENTS) >+ encoder << eventTrackingRegions.touchActionData; >+#endif > } > > bool ArgumentCoder<EventTrackingRegions>::decode(Decoder& decoder, EventTrackingRegions& eventTrackingRegions) >@@ -343,8 +373,16 @@ bool ArgumentCoder<EventTrackingRegions>::decode(Decoder& decoder, EventTracking > HashMap<String, Region> eventSpecificSynchronousDispatchRegions; > if (!decoder.decode(eventSpecificSynchronousDispatchRegions)) > return false; >+#if ENABLE(POINTER_EVENTS) >+ Vector<TouchActionData> touchActionData; >+ if (!decoder.decode(touchActionData)) >+ return false; >+#endif > eventTrackingRegions.asynchronousDispatchRegion = WTFMove(asynchronousDispatchRegion); > eventTrackingRegions.eventSpecificSynchronousDispatchRegions = WTFMove(eventSpecificSynchronousDispatchRegions); >+#if ENABLE(POINTER_EVENTS) >+ eventTrackingRegions.touchActionData = WTFMove(touchActionData); >+#endif > return true; > } > >@@ -915,6 +953,15 @@ bool ArgumentCoder<Region>::decode(Decoder& decoder, Region& region) > return true; > } > >+Optional<Region> ArgumentCoder<Region>::decode(Decoder& decoder) >+{ >+ Region region; >+ if (!decode(decoder, region)) >+ return WTF::nullopt; >+ >+ return region; >+} >+ > void ArgumentCoder<Length>::encode(Encoder& encoder, const Length& length) > { > SimpleArgumentCoder<Length>::encode(encoder, length); >diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.h b/Source/WebKit/Shared/WebCoreArgumentCoders.h >index 3ae7e475d8336efc27dcf2c51f722cc2db9845a6..2ef5e43adb48951819c9a713d69e013d20dd6e9b 100644 >--- a/Source/WebKit/Shared/WebCoreArgumentCoders.h >+++ b/Source/WebKit/Shared/WebCoreArgumentCoders.h >@@ -121,6 +121,9 @@ struct ResourceLoadStatistics; > struct ScrollableAreaParameters; > struct TextCheckingResult; > struct TextIndicatorData; >+#if ENABLE(POINTER_EVENTS) >+struct TouchActionData; >+#endif > struct ViewportAttributes; > struct WindowFeatures; > >@@ -199,6 +202,13 @@ template<> struct ArgumentCoder<WebCore::DOMCacheEngine::Record> { > static Optional<WebCore::DOMCacheEngine::Record> decode(Decoder&); > }; > >+#if ENABLE(POINTER_EVENTS) >+template<> struct ArgumentCoder<WebCore::TouchActionData> { >+ static void encode(Encoder&, const WebCore::TouchActionData&); >+ static Optional<WebCore::TouchActionData> decode(Decoder&); >+}; >+#endif >+ > template<> struct ArgumentCoder<WebCore::EventTrackingRegions> { > static void encode(Encoder&, const WebCore::EventTrackingRegions&); > static bool decode(Decoder&, WebCore::EventTrackingRegions&); >@@ -316,6 +326,7 @@ template<> struct ArgumentCoder<WebCore::Path> { > template<> struct ArgumentCoder<WebCore::Region> { > static void encode(Encoder&, const WebCore::Region&); > static bool decode(Decoder&, WebCore::Region&); >+ static Optional<WebCore::Region> decode(Decoder&); > }; > > template<> struct ArgumentCoder<WebCore::Length> { >diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm >index 5f754aa55f86d401cd76bee65cd954ddd761f876..40ab367e1b964a947d7ea033e572bcba3b082ba6 100644 >--- a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm >+++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm >@@ -2581,6 +2581,14 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi > // zooming. We'll animate to the right place once the zoom finishes. > if ([scrollView isZooming]) > *targetContentOffset = [scrollView contentOffset]; >+#if ENABLE(POINTER_EVENTS) >+ else { >+ if ([_contentView preventsPanningInXAxis]) >+ targetContentOffset->x = scrollView.contentOffset.x; >+ if ([_contentView preventsPanningInYAxis]) >+ targetContentOffset->y = scrollView.contentOffset.y; >+ } >+#endif > #if ENABLE(CSS_SCROLL_SNAP) && ENABLE(ASYNC_SCROLLING) > if (WebKit::RemoteScrollingCoordinatorProxy* coordinator = _page->scrollingCoordinatorProxy()) { > // FIXME: Here, I'm finding the maximum horizontal/vertical scroll offsets. There's probably a better way to do this. >@@ -2618,6 +2626,20 @@ - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView > [self _didFinishScrolling]; > } > >+- (CGPoint)_scrollView:(UIScrollView *)scrollView adjustedOffsetForOffset:(CGPoint)offset translation:(CGPoint)translation startPoint:(CGPoint)start locationInView:(CGPoint)locationInView horizontalVelocity:(inout double *)hv verticalVelocity:(inout double *)vv >+{ >+ if (![_contentView preventsPanningInXAxis] && ![_contentView preventsPanningInYAxis]) >+ return offset; >+ >+ CGPoint adjustedContentOffset = CGPointMake(offset.x, offset.y); >+ if ([_contentView preventsPanningInXAxis]) >+ adjustedContentOffset.x = start.x; >+ if ([_contentView preventsPanningInYAxis]) >+ adjustedContentOffset.y = start.y; >+ >+ return adjustedContentOffset; >+} >+ > - (void)scrollViewDidScroll:(UIScrollView *)scrollView > { > if (![self usesStandardContentView] && [_customContentView respondsToSelector:@selector(web_scrollViewDidScroll:)]) >diff --git a/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp b/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp >index a959c93bc759d1c98668c81dada01e267f35b9b1..a8c6f7c35aeccb9380f914c19a4e337da15af8fe 100644 >--- a/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp >+++ b/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.cpp >@@ -206,6 +206,18 @@ String RemoteScrollingCoordinatorProxy::scrollingTreeAsText() const > return emptyString(); > } > >+#if ENABLE(POINTER_EVENTS) >+Optional<TouchActionData> RemoteScrollingCoordinatorProxy::touchActionDataAtPoint(IntPoint p) const >+{ >+ return m_scrollingTree->touchActionDataAtPoint(p); >+} >+ >+void RemoteScrollingCoordinatorProxy::setTouchActionsForScrollingNodeID(OptionSet<TouchAction> touchActions, ScrollingNodeID scrollingNodeID) >+{ >+ m_scrollingTree->setTouchActionsForScrollingNodeID(touchActions, scrollingNodeID); >+} >+#endif >+ > } // namespace WebKit > > #endif // ENABLE(ASYNC_SCROLLING) >diff --git a/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h b/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h >index b32d79fe9289b1457c8ead2d8d35c3b79daec96a..c0b02c19e302e952af1a99f226de5cbfd28bdade 100644 >--- a/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h >+++ b/Source/WebKit/UIProcess/RemoteLayerTree/RemoteScrollingCoordinatorProxy.h >@@ -98,6 +98,11 @@ public: > > String scrollingTreeAsText() const; > >+#if ENABLE(POINTER_EVENTS) >+ Optional<WebCore::TouchActionData> touchActionDataAtPoint(WebCore::IntPoint) const; >+ void setTouchActionsForScrollingNodeID(OptionSet<WebCore::TouchAction>, WebCore::ScrollingNodeID); >+#endif >+ > private: > void connectStateNodeLayers(WebCore::ScrollingStateTree&, const RemoteLayerTreeHost&); > #if ENABLE(CSS_SCROLL_SNAP) >diff --git a/Source/WebKit/UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm b/Source/WebKit/UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm >index 4cc7e1a87b85ac70637e15f44cc1e4f8f9219c4c..c9e6d122142468349b27cab6a33f6153eaf5b601 100644 >--- a/Source/WebKit/UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm >+++ b/Source/WebKit/UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm >@@ -67,9 +67,27 @@ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView > _scrollingTreeNodeDelegate->scrollWillStart(); > } > >-#if ENABLE(CSS_SCROLL_SNAP) > - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset > { >+#if ENABLE(POINTER_EVENTS) >+ if (![scrollView isZooming]) { >+ auto touchActions = _scrollingTreeNodeDelegate->scrollingNode().touchActions(); >+ if (touchActions != WebCore::TouchAction::Auto && touchActions != WebCore::TouchAction::Manipulation) { >+ bool canPanX = true; >+ bool canPanY = true; >+ if (!touchActions.contains(WebCore::TouchAction::PanX)) { >+ canPanX = false; >+ targetContentOffset->x = scrollView.contentOffset.x; >+ } >+ if (!touchActions.contains(WebCore::TouchAction::PanY)) { >+ canPanY = false; >+ targetContentOffset->y = scrollView.contentOffset.y; >+ } >+ } >+ } >+#endif >+ >+#if ENABLE(CSS_SCROLL_SNAP) > CGFloat horizontalTarget = targetContentOffset->x; > CGFloat verticalTarget = targetContentOffset->y; > >@@ -96,8 +114,8 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi > || originalVerticalSnapPosition != _scrollingTreeNodeDelegate->scrollingNode().currentVerticalSnapPointIndex()) { > _scrollingTreeNodeDelegate->currentSnapPointIndicesDidChange(_scrollingTreeNodeDelegate->scrollingNode().currentHorizontalSnapPointIndex(), _scrollingTreeNodeDelegate->scrollingNode().currentVerticalSnapPointIndex()); > } >-} > #endif >+} > > - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)willDecelerate > { >@@ -105,6 +123,9 @@ - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL > _inUserInteraction = NO; > _scrollingTreeNodeDelegate->scrollViewDidScroll(scrollView.contentOffset, _inUserInteraction); > _scrollingTreeNodeDelegate->scrollDidEnd(); >+#if ENABLE(POINTER_EVENTS) >+ _scrollingTreeNodeDelegate->scrollingNode().setTouchActions(WebCore::TouchAction::Auto); >+#endif > } > } > >@@ -114,9 +135,30 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView > _inUserInteraction = NO; > _scrollingTreeNodeDelegate->scrollViewDidScroll(scrollView.contentOffset, _inUserInteraction); > _scrollingTreeNodeDelegate->scrollDidEnd(); >+#if ENABLE(POINTER_EVENTS) >+ _scrollingTreeNodeDelegate->scrollingNode().setTouchActions(WebCore::TouchAction::Auto); >+#endif > } > } > >+#if ENABLE(POINTER_EVENTS) >+- (CGPoint)_scrollView:(UIScrollView *)scrollView adjustedOffsetForOffset:(CGPoint)offset translation:(CGPoint)translation startPoint:(CGPoint)start locationInView:(CGPoint)locationInView horizontalVelocity:(inout double *)hv verticalVelocity:(inout double *)vv >+{ >+ auto touchActions = _scrollingTreeNodeDelegate->scrollingNode().touchActions(); >+ if (touchActions == WebCore::TouchAction::Auto || touchActions == WebCore::TouchAction::Manipulation) >+ return offset; >+ >+ CGPoint adjustedContentOffset = CGPointMake(offset.x, offset.y); >+ >+ if (!touchActions.contains(WebCore::TouchAction::PanX)) >+ adjustedContentOffset.x = start.x; >+ if (!touchActions.contains(WebCore::TouchAction::PanY)) >+ adjustedContentOffset.y = start.y; >+ >+ return adjustedContentOffset; >+} >+#endif >+ > @end > > namespace WebKit { >diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h >index ad7d7dc9282e357f30ef24e289a113da63201d92..40dc30940a1d1d93af5d2b331d2cbe2abd4e4c3c 100644 >--- a/Source/WebKit/UIProcess/WebPageProxy.h >+++ b/Source/WebKit/UIProcess/WebPageProxy.h >@@ -684,6 +684,9 @@ public: > void requestStartDataInteraction(const WebCore::IntPoint& clientPosition, const WebCore::IntPoint& globalPosition); > void requestAdditionalItemsForDragSession(const WebCore::IntPoint& clientPosition, const WebCore::IntPoint& globalPosition); > void didConcludeEditDataInteraction(Optional<WebCore::TextIndicatorData>); >+#if ENABLE(POINTER_EVENTS) >+ bool isScrollingOrZooming() const { return m_isScrollingOrZooming; } >+#endif > #endif > #endif > #if ENABLE(DATA_DETECTION) >diff --git a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h >index d508a3e4ba7cd7ac82186e69a6c275a5a0c8437e..5d9eb696a69406502583b49eee14924cedb0ac42 100644 >--- a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h >+++ b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h >@@ -196,6 +196,10 @@ struct WKAutoCorrectionData { > RetainPtr<UIWebTouchEventsGestureRecognizer> _touchEventGestureRecognizer; > > BOOL _canSendTouchEventsAsynchronously; >+#if ENABLE(POINTER_EVENTS) >+ BOOL _preventsPanningInXAxis; >+ BOOL _preventsPanningInYAxis; >+#endif > > RetainPtr<WKSyntheticClickTapGestureRecognizer> _singleTapGestureRecognizer; > RetainPtr<_UIWebHighlightLongPressGestureRecognizer> _highlightLongPressGestureRecognizer; >@@ -349,6 +353,10 @@ struct WKAutoCorrectionData { > @property (nonatomic, readonly) const WebKit::FocusedElementInformation& focusedElementInformation; > @property (nonatomic, readonly) UIWebFormAccessory *formAccessoryView; > @property (nonatomic, readonly) UITextInputAssistantItem *inputAssistantItemForWebView; >+#if ENABLE(POINTER_EVENTS) >+@property (nonatomic, readonly) BOOL preventsPanningInXAxis; >+@property (nonatomic, readonly) BOOL preventsPanningInYAxis; >+#endif > > #if ENABLE(DATALIST_ELEMENT) > @property (nonatomic, strong) UIView <WKFormControl> *dataListTextSuggestionsInputView; >diff --git a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm >index b28f177ca1485f75e14cccf158284c2962b4b791..5838d2051e87e015b4b4ab4319e11c4de768dfc4 100644 >--- a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm >+++ b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm >@@ -129,6 +129,11 @@ > #import <WebKitAdditions/WKPlatformFileUploadPanel.mm> > #endif > >+#if ENABLE(POINTER_EVENTS) >+#import "RemoteScrollingCoordinatorProxy.h" >+#import <WebCore/TouchAction.h> >+#endif >+ > #if PLATFORM(WATCHOS) > > @interface WKContentView (WatchSupport) <WKFocusedFormControlViewDelegate, WKSelectMenuListViewControllerDelegate, WKTextInputListViewControllerDelegate> >@@ -608,6 +613,18 @@ static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedEl > return (focusedElementInformation.elementType != WebKit::InputType::None); > } > >+#if ENABLE(POINTER_EVENTS) >+- (BOOL)preventsPanningInXAxis >+{ >+ return _preventsPanningInXAxis; >+} >+ >+- (BOOL)preventsPanningInYAxis >+{ >+ return _preventsPanningInYAxis; >+} >+#endif >+ > - (WKFormInputSession *)_formInputSession > { > return _formInputSession.get(); >@@ -854,6 +871,10 @@ - (void)cleanupInteraction > _hasSetUpInteractions = NO; > _suppressSelectionAssistantReasons = { }; > _isZoomingToRevealFocusedElement = NO; >+ >+#if ENABLE(POINTER_EVENTS) >+ [self _resetPanningPreventionFlags]; >+#endif > } > > - (void)_removeDefaultGestureRecognizers >@@ -1121,16 +1142,54 @@ - (void)_webTouchEventsRecognized:(UIWebTouchEventsGestureRecognizer *)gestureRe > WebKit::NativeWebTouchEvent nativeWebTouchEvent(lastTouchEvent); > nativeWebTouchEvent.setCanPreventNativeGestures(!_canSendTouchEventsAsynchronously || [gestureRecognizer isDefaultPrevented]); > >+#if ENABLE(POINTER_EVENTS) >+ if (auto* scrollingCoordinator = _page->scrollingCoordinatorProxy()) { >+ for (const auto& touchPoint : nativeWebTouchEvent.touchPoints()) { >+ if (touchPoint.phase() != WebKit::WebPlatformTouchPoint::TouchPressed) >+ continue; >+ >+ if (auto touchActionData = scrollingCoordinator->touchActionDataAtPoint(touchPoint.location())) { >+ if (touchActionData->touchActions == WebCore::TouchAction::None) >+ [_touchEventGestureRecognizer setDefaultPrevented:YES]; >+ else if (!touchActionData->touchActions.contains(WebCore::TouchAction::Manipulation)) { >+ if (auto scrollingNodeID = touchActionData->scrollingNodeID) >+ scrollingCoordinator->setTouchActionsForScrollingNodeID(touchActionData->touchActions, touchActionData->scrollingNodeID); >+ else { >+ if (!touchActionData->touchActions.contains(WebCore::TouchAction::PinchZoom)) >+ _webView.scrollView.pinchGestureRecognizer.enabled = NO; >+ _preventsPanningInXAxis = !touchActionData->touchActions.contains(WebCore::TouchAction::PanX); >+ _preventsPanningInYAxis = !touchActionData->touchActions.contains(WebCore::TouchAction::PanY); >+ } >+ } >+ } >+ } >+ } >+#endif >+ > if (_canSendTouchEventsAsynchronously) > _page->handleTouchEventAsynchronously(nativeWebTouchEvent); > else > _page->handleTouchEventSynchronously(nativeWebTouchEvent); > >- if (nativeWebTouchEvent.allTouchPointsAreReleased()) >+ if (nativeWebTouchEvent.allTouchPointsAreReleased()) { > _canSendTouchEventsAsynchronously = NO; >+ >+#if ENABLE(POINTER_EVENTS) >+ if (!_page->isScrollingOrZooming()) >+ [self _resetPanningPreventionFlags]; >+#endif >+ } > #endif > } > >+#if ENABLE(POINTER_EVENTS) >+- (void)_resetPanningPreventionFlags >+{ >+ _preventsPanningInXAxis = NO; >+ _preventsPanningInYAxis = NO; >+} >+#endif >+ > - (void)_inspectorNodeSearchRecognized:(UIGestureRecognizer *)gestureRecognizer > { > ASSERT(_inspectorNodeSearchEnabled); >@@ -2195,6 +2254,10 @@ - (void)_didEndScrollingOrZooming > } > _page->setIsScrollingOrZooming(false); > >+#if ENABLE(POINTER_EVENTS) >+ [self _resetPanningPreventionFlags]; >+#endif >+ > #if PLATFORM(WATCHOS) > [_focusedFormControlView engageFocusedFormControlNavigation]; > #endif >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index c2abd7d67973662c17462d6053ea5cc87eca5d9a..c408e06040842deb82f52d05cc842b43939fa745 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,33 @@ >+2019-01-24 Antoine Quint <graouts@apple.com> >+ >+ Limit user-agent interactions based on the touch-action property >+ https://bugs.webkit.org/show_bug.cgi?id=193447 >+ <rdar://problem/47283874> >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Add a new series of tests that check that the "none", "pan-x", "pan-y" and "pinch-zoom" values have the expected >+ impact on page panning on iOS. >+ >+ * pointerevents/ios/touch-action-none-expected.txt: Added. >+ * pointerevents/ios/touch-action-none-in-overflow-scrolling-touch-expected.txt: Added. >+ * pointerevents/ios/touch-action-none-in-overflow-scrolling-touch.html: Added. >+ * pointerevents/ios/touch-action-none-on-iframe-expected.txt: Added. >+ * pointerevents/ios/touch-action-none-on-iframe.html: Added. >+ * pointerevents/ios/touch-action-none-on-parent-expected.txt: Added. >+ * pointerevents/ios/touch-action-none-on-parent.html: Added. >+ * pointerevents/ios/touch-action-none.html: Added. >+ * pointerevents/ios/touch-action-pan-x-expected.txt: Added. >+ * pointerevents/ios/touch-action-pan-x-pan-y-expected.txt: Added. >+ * pointerevents/ios/touch-action-pan-x-pan-y.html: Added. >+ * pointerevents/ios/touch-action-pan-x.html: Added. >+ * pointerevents/ios/touch-action-pan-y-expected.txt: Added. >+ * pointerevents/ios/touch-action-pan-y.html: Added. >+ * pointerevents/ios/touch-action-pinch-zoom-allows-zooming-expected.txt: Added. >+ * pointerevents/ios/touch-action-pinch-zoom-allows-zooming.html: Added. >+ * pointerevents/ios/touch-action-pinch-zoom-prevents-scrolling-expected.txt: Added. >+ * pointerevents/ios/touch-action-pinch-zoom-prevents-scrolling.html: Added. >+ > 2019-01-24 John Wilander <wilander@apple.com> > > Add Ad Click Attribution as an internal/experimental feature >diff --git a/LayoutTests/pointerevents/ios/touch-action-none-expected.txt b/LayoutTests/pointerevents/ios/touch-action-none-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..727e8af81bd984c8c34070551af5b61e9270ff81 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-none-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS Testing that setting touch-action: none on an element prevents page scrolling. >+ >diff --git a/LayoutTests/pointerevents/ios/touch-action-none-in-overflow-scrolling-touch-expected.txt b/LayoutTests/pointerevents/ios/touch-action-none-in-overflow-scrolling-touch-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..2c0072910245ef57587ebe9c52c9fabc0532d1c2 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-none-in-overflow-scrolling-touch-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS Testing that setting touch-action: none in content hosted in a scrollable container using '-webkit-overflow-scrolling: touch' prevents scrolling of that container. >+ >diff --git a/LayoutTests/pointerevents/ios/touch-action-none-in-overflow-scrolling-touch.html b/LayoutTests/pointerevents/ios/touch-action-none-in-overflow-scrolling-touch.html >new file mode 100644 >index 0000000000000000000000000000000000000000..9e9614ffefcefbeafe674174b581541c0f235141 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-none-in-overflow-scrolling-touch.html >@@ -0,0 +1,49 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<meta charset=utf-8> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+<style> >+ >+#scrolling-container { >+ position: absolute; >+ left: 0; >+ top: 0; >+ width: 400px; >+ height: 400px; >+ overflow: scroll; >+ -webkit-overflow-scrolling: touch; >+} >+ >+#scrolling-container > div { >+ position: absolute; >+ left: 0; >+ top: 0; >+ width: 200%; >+ height: 200%; >+ touch-action: none; >+} >+ >+</style> >+</head> >+<body> >+<div id="scrolling-container"><div></div></div> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="../utils.js"></script> >+<script> >+ >+'use strict'; >+ >+async_test(test => { >+ const scrollingContainer = document.body.firstElementChild; >+ ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => { >+ assert_equals(scrollingContainer.scrollLeft, 0, "The scrolling container was not scrolled in the x-axis."); >+ assert_equals(scrollingContainer.scrollTop, 0, "The scrolling container was not scrolled in the y-axis."); >+ test.done(); >+ }); >+}, "Testing that setting touch-action: none in content hosted in a scrollable container using '-webkit-overflow-scrolling: touch' prevents scrolling of that container."); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/pointerevents/ios/touch-action-none-on-iframe-expected.txt b/LayoutTests/pointerevents/ios/touch-action-none-on-iframe-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..46751bdd7c3462a1e086ffc73f353ed44daf7fbc >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-none-on-iframe-expected.txt >@@ -0,0 +1,4 @@ >+ >+ >+PASS Testing that setting touch-action: none on an iframe and interacting with its content prevents page scrolling. >+ >diff --git a/LayoutTests/pointerevents/ios/touch-action-none-on-iframe.html b/LayoutTests/pointerevents/ios/touch-action-none-on-iframe.html >new file mode 100644 >index 0000000000000000000000000000000000000000..d927ea8db46174341c51a0c4cc5253e5b6207a2d >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-none-on-iframe.html >@@ -0,0 +1,48 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<meta charset=utf-8> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+<style> >+ >+iframe { >+ position: absolute; >+ left: 0; >+ top: 0; >+ width: 400px; >+ height: 400px; >+ touch-action: none; >+} >+ >+</style> >+</head> >+<body> >+<iframe></iframe> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="../utils.js"></script> >+<script> >+ >+'use strict'; >+ >+async_test(test => { >+ const iframe = document.body.firstElementChild; >+ const target = iframe.contentDocument.body.appendChild(document.createElement("div")); >+ target.setAttribute("style", ` >+ position: absolute; >+ left: 0; >+ top: 0; >+ width: 200%; >+ height: 200%; >+ `); >+ >+ ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => { >+ assert_equals(window.pageXOffset, 0, "The page was not scrolled in the x-axis."); >+ assert_equals(window.pageYOffset, 0, "The page was not scrolled in the y-axis."); >+ test.done(); >+ }); >+}, "Testing that setting touch-action: none on an iframe and interacting with its content prevents page scrolling."); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/pointerevents/ios/touch-action-none-on-parent-expected.txt b/LayoutTests/pointerevents/ios/touch-action-none-on-parent-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..d02b1304968a291eadd13739526fc0d8cbe2a29b >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-none-on-parent-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS Testing that setting touch-action: none on a parent element prevents page scrolling. >+ >diff --git a/LayoutTests/pointerevents/ios/touch-action-none-on-parent.html b/LayoutTests/pointerevents/ios/touch-action-none-on-parent.html >new file mode 100644 >index 0000000000000000000000000000000000000000..9a480793f790a5c86241769f9931e8f480cf3b74 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-none-on-parent.html >@@ -0,0 +1,29 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<meta charset=utf-8> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+</head> >+<body> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="../utils.js"></script> >+<script> >+ >+'use strict'; >+ >+target_test({ width: "200px", height: "200px" }, (target, test) => { >+ document.body.style.width = "2000px"; >+ document.body.style.height = "2000px"; >+ document.body.style.touchAction = "none"; >+ >+ ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => { >+ assert_equals(window.pageXOffset, 0, "The page was not scrolled in the x-axis."); >+ assert_equals(window.pageYOffset, 0, "The page was not scrolled in the y-axis."); >+ test.done(); >+ }); >+}, "Testing that setting touch-action: none on a parent element prevents page scrolling."); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/pointerevents/ios/touch-action-none.html b/LayoutTests/pointerevents/ios/touch-action-none.html >new file mode 100644 >index 0000000000000000000000000000000000000000..ad00b83bb98e4ae5ddc9a59c98672a6a0329ce8b >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-none.html >@@ -0,0 +1,30 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<meta charset=utf-8> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+</head> >+<body> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="../utils.js"></script> >+<script> >+ >+'use strict'; >+ >+target_test({ width: "200px", height: "200px" }, (target, test) => { >+ document.body.style.width = "2000px"; >+ document.body.style.height = "2000px"; >+ >+ target.style.touchAction = "none"; >+ >+ ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => { >+ assert_equals(window.pageXOffset, 0, "The page was not scrolled in the x-axis."); >+ assert_equals(window.pageYOffset, 0, "The page was not scrolled in the y-axis."); >+ test.done(); >+ }); >+}, "Testing that setting touch-action: none on an element prevents page scrolling."); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/pointerevents/ios/touch-action-pan-x-expected.txt b/LayoutTests/pointerevents/ios/touch-action-pan-x-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..87e78379df45a7177476c8be7b5d64040f0aacde >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-pan-x-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS Testing that setting touch-action: pan-x on an element prevents page scrolling in the y-axis. >+ >diff --git a/LayoutTests/pointerevents/ios/touch-action-pan-x-pan-y-expected.txt b/LayoutTests/pointerevents/ios/touch-action-pan-x-pan-y-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..be97bde327fc29c3ae4b780a111647cd68fd5f82 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-pan-x-pan-y-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS Testing that setting 'touch-action: pan-x pan-y' on an element allows page scrolling in both axes. >+ >diff --git a/LayoutTests/pointerevents/ios/touch-action-pan-x-pan-y.html b/LayoutTests/pointerevents/ios/touch-action-pan-x-pan-y.html >new file mode 100644 >index 0000000000000000000000000000000000000000..52291a4d8c67c22151c229d837d71a8f3d00ab14 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-pan-x-pan-y.html >@@ -0,0 +1,30 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<meta charset=utf-8> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+</head> >+<body> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="../utils.js"></script> >+<script> >+ >+'use strict'; >+ >+target_test({ width: "200px", height: "200px" }, (target, test) => { >+ document.body.style.width = "2000px"; >+ document.body.style.height = "2000px"; >+ >+ target.style.touchAction = "pan-x pan-y"; >+ >+ ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => { >+ assert_not_equals(window.pageXOffset, 0, "The page was scrolled in the x-axis."); >+ assert_not_equals(window.pageYOffset, 0, "The page was scrolled in the y-axis."); >+ test.done(); >+ }); >+}, "Testing that setting 'touch-action: pan-x pan-y' on an element allows page scrolling in both axes."); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/pointerevents/ios/touch-action-pan-x.html b/LayoutTests/pointerevents/ios/touch-action-pan-x.html >new file mode 100644 >index 0000000000000000000000000000000000000000..cb9205f7ef5da2cab70019cfb8ea70f95d77c6f7 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-pan-x.html >@@ -0,0 +1,30 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<meta charset=utf-8> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+</head> >+<body> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="../utils.js"></script> >+<script> >+ >+'use strict'; >+ >+target_test({ width: "200px", height: "200px" }, (target, test) => { >+ document.body.style.width = "2000px"; >+ document.body.style.height = "2000px"; >+ >+ target.style.touchAction = "pan-x"; >+ >+ ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => { >+ assert_not_equals(window.pageXOffset, 0, "The page was scrolled in the x-axis."); >+ assert_equals(window.pageYOffset, 0, "The page was not scrolled in the y-axis."); >+ test.done(); >+ }); >+}, "Testing that setting touch-action: pan-x on an element prevents page scrolling in the y-axis."); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/pointerevents/ios/touch-action-pan-y-expected.txt b/LayoutTests/pointerevents/ios/touch-action-pan-y-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..3ff658c3864226715ae67ea42fbb928680692848 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-pan-y-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS Testing that setting touch-action: pan-x on an element prevents page scrolling in the x-axis. >+ >diff --git a/LayoutTests/pointerevents/ios/touch-action-pan-y.html b/LayoutTests/pointerevents/ios/touch-action-pan-y.html >new file mode 100644 >index 0000000000000000000000000000000000000000..9090d39601fbee17bfd1b95f243e9fabc522795e >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-pan-y.html >@@ -0,0 +1,30 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<meta charset=utf-8> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+</head> >+<body> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="../utils.js"></script> >+<script> >+ >+'use strict'; >+ >+target_test({ width: "200px", height: "200px" }, (target, test) => { >+ document.body.style.width = "2000px"; >+ document.body.style.height = "2000px"; >+ >+ target.style.touchAction = "pan-y"; >+ >+ ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => { >+ assert_equals(window.pageXOffset, 0, "The page was not scrolled in the x-axis."); >+ assert_not_equals(window.pageYOffset, 0, "The page was scrolled in the y-axis."); >+ test.done(); >+ }); >+}, "Testing that setting touch-action: pan-x on an element prevents page scrolling in the x-axis."); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-allows-zooming-expected.txt b/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-allows-zooming-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..094e67615e2a5998f3333f90fda30dd1dca5a468 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-allows-zooming-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS Testing that setting touch-action: pinch-zoom on an element allows page zooming. >+ >diff --git a/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-allows-zooming.html b/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-allows-zooming.html >new file mode 100644 >index 0000000000000000000000000000000000000000..2da63c9429e1aa139026ed4313cea6d5d4787976 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-allows-zooming.html >@@ -0,0 +1,29 @@ >+<!DOCTYPE html><!-- webkit-test-runner [ useFlexibleViewport=true ] --> >+<html> >+<head> >+<meta charset=utf-8> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+</head> >+<body> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="../utils.js"></script> >+<script> >+ >+'use strict'; >+ >+target_test({ width: "400px", height: "400px" }, (target, test) => { >+ document.body.style.width = "2000px"; >+ document.body.style.height = "2000px"; >+ >+ target.style.touchAction = "pinch-zoom"; >+ >+ ui.pinchOut({ x: 50, y: 50, width: 100, height: 100, scale: 0.5 }).then(() => { >+ assert_not_equals(window.internals.pageScaleFactor(), 1, "The page was scaled."); >+ test.done(); >+ }); >+}, "Testing that setting touch-action: pinch-zoom on an element allows page zooming."); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-prevents-scrolling-expected.txt b/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-prevents-scrolling-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..60c85b9aa552df9b71d9ae43974050e0c2899800 >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-prevents-scrolling-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS Testing that setting touch-action: pinch-zoom on an element prevents page scrolling. >+ >diff --git a/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-prevents-scrolling.html b/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-prevents-scrolling.html >new file mode 100644 >index 0000000000000000000000000000000000000000..02341c621507a3ee83734d27713d84bff559d7dd >--- /dev/null >+++ b/LayoutTests/pointerevents/ios/touch-action-pinch-zoom-prevents-scrolling.html >@@ -0,0 +1,30 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<meta charset=utf-8> >+<meta name="viewport" content="width=device-width, initial-scale=1"> >+</head> >+<body> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="../utils.js"></script> >+<script> >+ >+'use strict'; >+ >+target_test({ width: "200px", height: "200px" }, (target, test) => { >+ document.body.style.width = "2000px"; >+ document.body.style.height = "2000px"; >+ >+ target.style.touchAction = "pinch-zoom"; >+ >+ ui.swipe({ x: 150, y: 150 }, { x: 50, y: 50 }).then(() => { >+ assert_equals(window.pageXOffset, 0, "The page was not scrolled in the x-axis."); >+ assert_equals(window.pageYOffset, 0, "The page was not scrolled in the y-axis."); >+ test.done(); >+ }); >+}, "Testing that setting touch-action: pinch-zoom on an element prevents page scrolling."); >+ >+</script> >+</body> >+</html> >\ No newline at end of file
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 193447
:
359189
|
359208
|
359257
|
359258
|
359260
|
359363
|
359365
|
360028
|
360029
|
360031
|
360089
|
360090
|
360100
|
360338