[Extra zoom mode] Implement additional SPI for adjusting viewport shrink-to-fit behavior
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 27 Feb 2018 17:16:31 +0000 (17:16 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 27 Feb 2018 17:16:31 +0000 (17:16 +0000)
https://bugs.webkit.org/show_bug.cgi?id=183100
<rdar://problem/37840987>

Reviewed by Tim Horton.

Source/WebCore:

Introduce new customization options to ViewportConfiguration. The first is m_forceHorizontalShrinkToFit, which
(when set to true) forces the viewport to scale using shrink-to-fit heuristics, regardless of whether
"shrink-to-fit=no" is specified via viewport parameters or if content width did not exceed minimum layout size.
The second is m_viewSize, which reflects the true size of the viewport. See WebKit ChangeLog for more details.

Tests: ViewportSizingTests.ForceShrinkToFitViewportOverridesViewportParameters
       ViewportSizingTests.ShrinkToFitViewportWithMinimumAllowedLayoutWidth

* page/ViewportConfiguration.cpp:
(WebCore::ViewportConfiguration::ViewportConfiguration):
(WebCore::ViewportConfiguration::setMinimumLayoutSize):

Plumb the real size of the view alongside the minimum layout size when updating the minimum layout size.

(WebCore::ViewportConfiguration::setForceHorizontalShrinkToFit):
(WebCore::ViewportConfiguration::shouldIgnoreHorizontalScalingConstraints const):

Bail early and return true if forceHorizontalShrinkToFit is set. This forces shrink-to-fit even in cases where
"shrink-to-fit" is set to "no", or content dimensions don't exceed layout dimensions.

(WebCore::ViewportConfiguration::initialScaleFromSize const):

Use view dimensions rather than minimum layout dimensions when computing the initial scale. Minimum layout size
is no longer always equal to the size of the view if the client has specified a minimum allowed layout width. As
such, when computing the initial scale, to ensure that the content (which was laid out using the minimum layout
size) fits within the real viewport, we need to divide real viewport dimensions by content dimensions.

(WebCore::ViewportConfiguration::minimumScale const):

Similarly, use view size instead of minimum layout size to compute minimum scale.

(WebCore::ViewportConfiguration::description const):
* page/ViewportConfiguration.h:

Source/WebKit:

Add new SPI hooks to provide finer control over certain aspects of the shrink-to-fit viewport heuristic.
Currently, in certain cases of iPad multitasking, Safari allows shrinking content to fit by default. This means
that even when "width=device-width" is used, if the contents of the page are too wide to fit within the
viewport's width, we'll adjust the initial scale such that the viewport can fit all of the content.

However, in certain viewport dimensions, this heuristic is insufficient to ensure that pages are laid out and
displayed properly within the viewport. Namely, one could imagine that an element with a hard-coded width that
is larger than the real viewport width would cause all other elements with dimensions relative to the body to be
excessively shrunk down once shrink-to-fit is applied, so the page would still look broken even if the contents
of the page all fit within the viewport.

To mitigate this, we decouple the notions of minimum layout size from the size of the actual viewport (which we
simply refer to as "view size"). This allows us to introduce a mechanism where we lay out the page at a given
minimum layout size that is larger than the size of the view; later, when we determine the initial scale, we
then apply shrink-to-fit scaling using the view size rather than the minimum layout size. This grants us the
ability to lay out content as if our view were large, but still ensure that the contents of the page fit within
the actual view.

* Shared/VisibleContentRectUpdateInfo.cpp:
(WebKit::VisibleContentRectUpdateInfo::encode const):
(WebKit::VisibleContentRectUpdateInfo::decode):
(WebKit::operator<<):
* Shared/VisibleContentRectUpdateInfo.h:
(WebKit::VisibleContentRectUpdateInfo::VisibleContentRectUpdateInfo):
(WebKit::VisibleContentRectUpdateInfo::forceHorizontalShrinkToFit const):
(WebKit::operator==):

Plumb the forceHorizontalShrinkToFit flag through VisibleContentRectUpdateInfo.

* Shared/WebPageCreationParameters.cpp:
(WebKit::WebPageCreationParameters::encode const):
(WebKit::WebPageCreationParameters::decode):
* Shared/WebPageCreationParameters.h:

Plumb viewSize through IPC to WebPage.

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _initializeWithConfiguration:]):

Start off WKWebView flags at their initial values.

(-[WKWebView _minimumAllowedLayoutWidth]):
(-[WKWebView _setMinimumAllowedLayoutWidth:]):

This provides the minimum width at which the page will lay out, such that if the view width dips below this
value, we'll use this minimum allowed layout width instead. 0 by default.

(-[WKWebView activeMinimumLayoutSizes:]):

Refactor this from a static function to a helper method on WKWebView that computes both the minimum layout size
(which takes minimum allowed layout width into account) as well as the real view size. Refactor all call sites
to use this new method, and also propagate the view size down via IPC, alongside the minimum layout size.

(-[WKWebView _dispatchSetMinimumLayoutSize:viewSize:]):
(-[WKWebView _frameOrBoundsChanged]):
(-[WKWebView _setMinimumLayoutSizeOverride:]):
(-[WKWebView _setForceHorizontalViewportShrinkToFit:]):
(-[WKWebView _forceHorizontalViewportShrinkToFit]):

Setting this flag to YES forces us to always shrink-to-fit in the horizontal axis. NO by default.

(-[WKWebView _beginAnimatedResizeWithUpdates:]):
(-[WKWebView _endAnimatedResize]):
(activeMinimumLayoutSize): Deleted.

More refactoring to replace activeMinimumLayoutSize() with -activeMinimumLayoutSizes:.

(-[WKWebView _dispatchSetMinimumLayoutSize:]): Deleted.
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::creationParameters):
* UIProcess/WebPageProxy.h:
* UIProcess/ios/WKContentView.mm:
(-[WKContentView didUpdateVisibleRect:unobscuredRect:unobscuredRectInScrollViewCoordinates:obscuredInsets:unobscuredSafeAreaInsets:inputViewBounds:scale:minimumScale:inStableState:isChangingObscuredInsetsInteractively:enclosedInScrollableAncestorView:]):

Pass _forceHorizontalViewportShrinkToFit into the visible content rect update.

* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::dynamicViewportSizeUpdate):
(WebKit::WebPageProxy::setViewportConfigurationMinimumLayoutSize):

Plumb viewSize alongside the existing minimumLayoutSize.

* WebProcess/WebPage/WebPage.cpp:
(WebKit::m_credentialsMessenger):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::setViewportConfigurationMinimumLayoutSize):
(WebKit::WebPage::dynamicViewportSizeUpdate):
(WebKit::WebPage::updateVisibleContentRects):

Set forceHorizontalShrinkToFit on the viewport configuration here.

Tools:

Add API tests that exercise -_setMinimumAllowedLayoutWidth: and -_setForceHorizontalViewportShrinkToFit:. See
WebKit ChangeLog for more detail.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/ios/ViewportSizingTests.mm: Added.
(TestWebKitAPI::while):
(viewportTestPageMarkup):
(TestWebKitAPI::TEST):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@229063 268f45cc-cd09-0410-ab3c-d52691b4dbfc

21 files changed:
Source/WebCore/ChangeLog
Source/WebCore/page/ViewportConfiguration.cpp
Source/WebCore/page/ViewportConfiguration.h
Source/WebKit/ChangeLog
Source/WebKit/Shared/VisibleContentRectUpdateInfo.cpp
Source/WebKit/Shared/VisibleContentRectUpdateInfo.h
Source/WebKit/Shared/WebPageCreationParameters.cpp
Source/WebKit/Shared/WebPageCreationParameters.h
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/ios/WKContentView.mm
Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/ios/ViewportSizingTests.mm [new file with mode: 0644]

index 012d44c..1c3b6ad 100644 (file)
@@ -1,5 +1,47 @@
 2018-02-27  Wenson Hsieh  <wenson_hsieh@apple.com>
 
+        [Extra zoom mode] Implement additional SPI for adjusting viewport shrink-to-fit behavior
+        https://bugs.webkit.org/show_bug.cgi?id=183100
+        <rdar://problem/37840987>
+
+        Reviewed by Tim Horton.
+
+        Introduce new customization options to ViewportConfiguration. The first is m_forceHorizontalShrinkToFit, which
+        (when set to true) forces the viewport to scale using shrink-to-fit heuristics, regardless of whether
+        "shrink-to-fit=no" is specified via viewport parameters or if content width did not exceed minimum layout size.
+        The second is m_viewSize, which reflects the true size of the viewport. See WebKit ChangeLog for more details.
+
+        Tests: ViewportSizingTests.ForceShrinkToFitViewportOverridesViewportParameters
+               ViewportSizingTests.ShrinkToFitViewportWithMinimumAllowedLayoutWidth
+
+        * page/ViewportConfiguration.cpp:
+        (WebCore::ViewportConfiguration::ViewportConfiguration):
+        (WebCore::ViewportConfiguration::setMinimumLayoutSize):
+
+        Plumb the real size of the view alongside the minimum layout size when updating the minimum layout size.
+
+        (WebCore::ViewportConfiguration::setForceHorizontalShrinkToFit):
+        (WebCore::ViewportConfiguration::shouldIgnoreHorizontalScalingConstraints const):
+
+        Bail early and return true if forceHorizontalShrinkToFit is set. This forces shrink-to-fit even in cases where
+        "shrink-to-fit" is set to "no", or content dimensions don't exceed layout dimensions.
+
+        (WebCore::ViewportConfiguration::initialScaleFromSize const):
+
+        Use view dimensions rather than minimum layout dimensions when computing the initial scale. Minimum layout size
+        is no longer always equal to the size of the view if the client has specified a minimum allowed layout width. As
+        such, when computing the initial scale, to ensure that the content (which was laid out using the minimum layout
+        size) fits within the real viewport, we need to divide real viewport dimensions by content dimensions.
+
+        (WebCore::ViewportConfiguration::minimumScale const):
+
+        Similarly, use view size instead of minimum layout size to compute minimum scale.
+
+        (WebCore::ViewportConfiguration::description const):
+        * page/ViewportConfiguration.h:
+
+2018-02-27  Wenson Hsieh  <wenson_hsieh@apple.com>
+
         Unreviewed, fix the debug build after r228877.
 
         In the case where CAN_DISALLOW_USER_INSTALLED_FONTS is enabled, this function doesn't return anything when
index ae3c9bb..7087d72 100644 (file)
@@ -49,6 +49,7 @@ ViewportConfiguration::ViewportConfiguration()
     : m_minimumLayoutSize(1024, 768)
     , m_canIgnoreScalingConstraints(false)
     , m_forceAlwaysUserScalable(false)
+    , m_forceHorizontalShrinkToFit(false)
 {
     // Setup a reasonable default configuration to avoid computing infinite scale/sizes.
     // Those are the original iPhone configuration.
@@ -82,12 +83,14 @@ bool ViewportConfiguration::setContentsSize(const IntSize& contentSize)
     return true;
 }
 
-bool ViewportConfiguration::setMinimumLayoutSize(const FloatSize& minimumLayoutSize)
+bool ViewportConfiguration::setMinimumLayoutSize(const FloatSize& minimumLayoutSize, const FloatSize& viewSize)
 {
-    if (m_minimumLayoutSize == minimumLayoutSize)
+    if (m_minimumLayoutSize == minimumLayoutSize && m_viewSize == viewSize)
         return false;
 
     m_minimumLayoutSize = minimumLayoutSize;
+    m_viewSize = viewSize;
+
     updateConfiguration();
     return true;
 }
@@ -103,6 +106,15 @@ bool ViewportConfiguration::setViewportArguments(const ViewportArguments& viewpo
     return true;
 }
 
+bool ViewportConfiguration::setForceHorizontalShrinkToFit(bool forceHorizontalShrinkToFit)
+{
+    if (m_forceHorizontalShrinkToFit == forceHorizontalShrinkToFit)
+        return false;
+
+    m_forceHorizontalShrinkToFit = forceHorizontalShrinkToFit;
+    return true;
+}
+
 bool ViewportConfiguration::setCanIgnoreScalingConstraints(bool canIgnoreScalingConstraints)
 {
     if (canIgnoreScalingConstraints == m_canIgnoreScalingConstraints)
@@ -123,6 +135,9 @@ bool ViewportConfiguration::shouldIgnoreHorizontalScalingConstraints() const
     if (!m_canIgnoreScalingConstraints)
         return false;
 
+    if (m_forceHorizontalShrinkToFit)
+        return true;
+
     if (!m_configuration.allowsShrinkToFit)
         return false;
 
@@ -167,14 +182,14 @@ double ViewportConfiguration::initialScaleFromSize(double width, double height,
 
     // If not, it is up to us to determine the initial scale.
     // We want a scale small enough to fit the document width-wise.
-    const FloatSize& minimumLayoutSize = m_minimumLayoutSize;
     double initialScale = 0;
     if (width > 0 && !shouldIgnoreVerticalScalingConstraints())
-        initialScale = minimumLayoutSize.width() / width;
+        initialScale = m_viewSize.width() / width;
 
     // Prevent the initial scale from shrinking to a height smaller than our view's minimum height.
-    if (height > 0 && height * initialScale < minimumLayoutSize.height() && !shouldIgnoreHorizontalScalingConstraints())
-        initialScale = minimumLayoutSize.height() / height;
+    if (height > 0 && height * initialScale < m_viewSize.height() && !shouldIgnoreHorizontalScalingConstraints())
+        initialScale = m_viewSize.height() / height;
+
     return std::min(std::max(initialScale, shouldIgnoreScalingConstraints ? m_defaultConfiguration.minimumScale : m_configuration.minimumScale), m_configuration.maximumScale);
 }
 
@@ -200,14 +215,13 @@ double ViewportConfiguration::minimumScale() const
     if (m_forceAlwaysUserScalable)
         minimumScale = std::min(minimumScale, forceAlwaysUserScalableMinimumScale);
 
-    const FloatSize& minimumLayoutSize = m_minimumLayoutSize;
     double contentWidth = m_contentSize.width();
-    if (contentWidth > 0 && contentWidth * minimumScale < minimumLayoutSize.width() && !shouldIgnoreVerticalScalingConstraints())
-        minimumScale = minimumLayoutSize.width() / contentWidth;
+    if (contentWidth > 0 && contentWidth * minimumScale < m_viewSize.width() && !shouldIgnoreVerticalScalingConstraints())
+        minimumScale = m_viewSize.width() / contentWidth;
 
     double contentHeight = m_contentSize.height();
-    if (contentHeight > 0 && contentHeight * minimumScale < minimumLayoutSize.height() && !shouldIgnoreHorizontalScalingConstraints())
-        minimumScale = minimumLayoutSize.height() / contentHeight;
+    if (contentHeight > 0 && contentHeight * minimumScale < m_viewSize.height() && !shouldIgnoreHorizontalScalingConstraints())
+        minimumScale = m_viewSize.height() / contentHeight;
 
     minimumScale = std::min(std::max(minimumScale, m_configuration.minimumScale), m_configuration.maximumScale);
 
@@ -490,6 +504,7 @@ String ViewportConfiguration::description() const
     ts.dumpProperty("ignoring horizontal scaling constraints", shouldIgnoreHorizontalScalingConstraints() ? "true" : "false");
     ts.dumpProperty("ignoring vertical scaling constraints", shouldIgnoreVerticalScalingConstraints() ? "true" : "false");
     ts.dumpProperty("avoids unsafe area", avoidsUnsafeArea() ? "true" : "false");
+    ts.dumpProperty("force horizontal shrink to fit", m_forceHorizontalShrinkToFit ? "true" : "false");
     
     ts.endGroup();
 
index 3ed7678..eeed64c 100644 (file)
@@ -75,11 +75,13 @@ public:
     WEBCORE_EXPORT bool setContentsSize(const IntSize&);
 
     const FloatSize& minimumLayoutSize() const { return m_minimumLayoutSize; }
-    WEBCORE_EXPORT bool setMinimumLayoutSize(const FloatSize&);
+    WEBCORE_EXPORT bool setMinimumLayoutSize(const FloatSize&, const FloatSize& viewSize);
 
     const ViewportArguments& viewportArguments() const { return m_viewportArguments; }
     WEBCORE_EXPORT bool setViewportArguments(const ViewportArguments&);
 
+    WEBCORE_EXPORT bool setForceHorizontalShrinkToFit(bool);
+
     WEBCORE_EXPORT bool setCanIgnoreScalingConstraints(bool);
     void setForceAlwaysUserScalable(bool forceAlwaysUserScalable) { m_forceAlwaysUserScalable = forceAlwaysUserScalable; }
 
@@ -120,10 +122,12 @@ private:
     Parameters m_defaultConfiguration;
     IntSize m_contentSize;
     FloatSize m_minimumLayoutSize;
+    FloatSize m_viewSize;
     ViewportArguments m_viewportArguments;
 
     bool m_canIgnoreScalingConstraints;
     bool m_forceAlwaysUserScalable;
+    bool m_forceHorizontalShrinkToFit;
 };
 
 WTF::TextStream& operator<<(WTF::TextStream&, const ViewportConfiguration::Parameters&);
index a709d83..81f860b 100644 (file)
@@ -1,3 +1,105 @@
+2018-02-27  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Extra zoom mode] Implement additional SPI for adjusting viewport shrink-to-fit behavior
+        https://bugs.webkit.org/show_bug.cgi?id=183100
+        <rdar://problem/37840987>
+
+        Reviewed by Tim Horton.
+
+        Add new SPI hooks to provide finer control over certain aspects of the shrink-to-fit viewport heuristic.
+        Currently, in certain cases of iPad multitasking, Safari allows shrinking content to fit by default. This means
+        that even when "width=device-width" is used, if the contents of the page are too wide to fit within the
+        viewport's width, we'll adjust the initial scale such that the viewport can fit all of the content.
+
+        However, in certain viewport dimensions, this heuristic is insufficient to ensure that pages are laid out and
+        displayed properly within the viewport. Namely, one could imagine that an element with a hard-coded width that
+        is larger than the real viewport width would cause all other elements with dimensions relative to the body to be
+        excessively shrunk down once shrink-to-fit is applied, so the page would still look broken even if the contents
+        of the page all fit within the viewport.
+
+        To mitigate this, we decouple the notions of minimum layout size from the size of the actual viewport (which we
+        simply refer to as "view size"). This allows us to introduce a mechanism where we lay out the page at a given
+        minimum layout size that is larger than the size of the view; later, when we determine the initial scale, we
+        then apply shrink-to-fit scaling using the view size rather than the minimum layout size. This grants us the
+        ability to lay out content as if our view were large, but still ensure that the contents of the page fit within
+        the actual view.
+
+        * Shared/VisibleContentRectUpdateInfo.cpp:
+        (WebKit::VisibleContentRectUpdateInfo::encode const):
+        (WebKit::VisibleContentRectUpdateInfo::decode):
+        (WebKit::operator<<):
+        * Shared/VisibleContentRectUpdateInfo.h:
+        (WebKit::VisibleContentRectUpdateInfo::VisibleContentRectUpdateInfo):
+        (WebKit::VisibleContentRectUpdateInfo::forceHorizontalShrinkToFit const):
+        (WebKit::operator==):
+
+        Plumb the forceHorizontalShrinkToFit flag through VisibleContentRectUpdateInfo.
+
+        * Shared/WebPageCreationParameters.cpp:
+        (WebKit::WebPageCreationParameters::encode const):
+        (WebKit::WebPageCreationParameters::decode):
+        * Shared/WebPageCreationParameters.h:
+
+        Plumb viewSize through IPC to WebPage.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _initializeWithConfiguration:]):
+
+        Start off WKWebView flags at their initial values.
+
+        (-[WKWebView _minimumAllowedLayoutWidth]):
+        (-[WKWebView _setMinimumAllowedLayoutWidth:]):
+
+        This provides the minimum width at which the page will lay out, such that if the view width dips below this
+        value, we'll use this minimum allowed layout width instead. 0 by default.
+
+        (-[WKWebView activeMinimumLayoutSizes:]):
+
+        Refactor this from a static function to a helper method on WKWebView that computes both the minimum layout size
+        (which takes minimum allowed layout width into account) as well as the real view size. Refactor all call sites
+        to use this new method, and also propagate the view size down via IPC, alongside the minimum layout size.
+
+        (-[WKWebView _dispatchSetMinimumLayoutSize:viewSize:]):
+        (-[WKWebView _frameOrBoundsChanged]):
+        (-[WKWebView _setMinimumLayoutSizeOverride:]):
+        (-[WKWebView _setForceHorizontalViewportShrinkToFit:]):
+        (-[WKWebView _forceHorizontalViewportShrinkToFit]):
+
+        Setting this flag to YES forces us to always shrink-to-fit in the horizontal axis. NO by default.
+
+        (-[WKWebView _beginAnimatedResizeWithUpdates:]):
+        (-[WKWebView _endAnimatedResize]):
+        (activeMinimumLayoutSize): Deleted.
+
+        More refactoring to replace activeMinimumLayoutSize() with -activeMinimumLayoutSizes:.
+
+        (-[WKWebView _dispatchSetMinimumLayoutSize:]): Deleted.
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::creationParameters):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/ios/WKContentView.mm:
+        (-[WKContentView didUpdateVisibleRect:unobscuredRect:unobscuredRectInScrollViewCoordinates:obscuredInsets:unobscuredSafeAreaInsets:inputViewBounds:scale:minimumScale:inStableState:isChangingObscuredInsetsInteractively:enclosedInScrollableAncestorView:]):
+
+        Pass _forceHorizontalViewportShrinkToFit into the visible content rect update.
+
+        * UIProcess/ios/WebPageProxyIOS.mm:
+        (WebKit::WebPageProxy::dynamicViewportSizeUpdate):
+        (WebKit::WebPageProxy::setViewportConfigurationMinimumLayoutSize):
+
+        Plumb viewSize alongside the existing minimumLayoutSize.
+
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::m_credentialsMessenger):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::setViewportConfigurationMinimumLayoutSize):
+        (WebKit::WebPage::dynamicViewportSizeUpdate):
+        (WebKit::WebPage::updateVisibleContentRects):
+
+        Set forceHorizontalShrinkToFit on the viewport configuration here.
+
 2018-02-27  Tim Horton  <timothy_horton@apple.com>
 
         Stop using deprecated CADisplay SPI
index 863d71c..8185bca 100644 (file)
@@ -53,6 +53,7 @@ void VisibleContentRectUpdateInfo::encode(IPC::Encoder& encoder) const
     encoder << m_isFirstUpdateForNewViewSize;
     encoder << m_isChangingObscuredInsetsInteractively;
     encoder << m_allowShrinkToFit;
+    encoder << m_forceHorizontalShrinkToFit;
     encoder << m_enclosedInScrollableAncestorView;
 }
 
@@ -92,6 +93,8 @@ bool VisibleContentRectUpdateInfo::decode(IPC::Decoder& decoder, VisibleContentR
         return false;
     if (!decoder.decode(result.m_allowShrinkToFit))
         return false;
+    if (!decoder.decode(result.m_forceHorizontalShrinkToFit))
+        return false;
     if (!decoder.decode(result.m_enclosedInScrollableAncestorView))
         return false;
 
@@ -130,6 +133,8 @@ TextStream& operator<<(TextStream& ts, const VisibleContentRectUpdateInfo& info)
         ts.dumpProperty("enclosedInScrollableAncestorView", info.enclosedInScrollableAncestorView());
 
     ts.dumpProperty("timestamp", info.timestamp().secondsSinceEpoch().value());
+    ts.dumpProperty("allowShrinkToFit", info.allowShrinkToFit());
+    ts.dumpProperty("forceHorizontalShrinkToFit", info.forceHorizontalShrinkToFit());
     if (info.horizontalVelocity())
         ts.dumpProperty("horizontalVelocity", info.horizontalVelocity());
     if (info.verticalVelocity())
index 5f07c2b..d22abaa 100644 (file)
@@ -45,7 +45,7 @@ class VisibleContentRectUpdateInfo {
 public:
     VisibleContentRectUpdateInfo() = default;
 
-    VisibleContentRectUpdateInfo(const WebCore::FloatRect& exposedContentRect, const WebCore::FloatRect& unobscuredContentRect, const WebCore::FloatRect& unobscuredRectInScrollViewCoordinates, const WebCore::FloatRect& unobscuredContentRectRespectingInputViewBounds, const WebCore::FloatRect& customFixedPositionRect, const WebCore::FloatBoxExtent& obscuredInsets, const WebCore::FloatBoxExtent& unobscuredSafeAreaInsets, double scale, bool inStableState, bool isFirstUpdateForNewViewSize, bool isChangingObscuredInsetsInteractively, bool allowShrinkToFit, bool enclosedInScrollableAncestorView, MonotonicTime timestamp, double horizontalVelocity, double verticalVelocity, double scaleChangeRate, uint64_t lastLayerTreeTransactionId)
+    VisibleContentRectUpdateInfo(const WebCore::FloatRect& exposedContentRect, const WebCore::FloatRect& unobscuredContentRect, const WebCore::FloatRect& unobscuredRectInScrollViewCoordinates, const WebCore::FloatRect& unobscuredContentRectRespectingInputViewBounds, const WebCore::FloatRect& customFixedPositionRect, const WebCore::FloatBoxExtent& obscuredInsets, const WebCore::FloatBoxExtent& unobscuredSafeAreaInsets, double scale, bool inStableState, bool isFirstUpdateForNewViewSize, bool isChangingObscuredInsetsInteractively, bool allowShrinkToFit, bool forceHorizontalShrinkToFit, bool enclosedInScrollableAncestorView, MonotonicTime timestamp, double horizontalVelocity, double verticalVelocity, double scaleChangeRate, uint64_t lastLayerTreeTransactionId)
         : m_exposedContentRect(exposedContentRect)
         , m_unobscuredContentRect(unobscuredContentRect)
         , m_unobscuredContentRectRespectingInputViewBounds(unobscuredContentRectRespectingInputViewBounds)
@@ -63,6 +63,7 @@ public:
         , m_isFirstUpdateForNewViewSize(isFirstUpdateForNewViewSize)
         , m_isChangingObscuredInsetsInteractively(isChangingObscuredInsetsInteractively)
         , m_allowShrinkToFit(allowShrinkToFit)
+        , m_forceHorizontalShrinkToFit(forceHorizontalShrinkToFit)
         , m_enclosedInScrollableAncestorView(enclosedInScrollableAncestorView)
     {
     }
@@ -80,6 +81,7 @@ public:
     bool isFirstUpdateForNewViewSize() const { return m_isFirstUpdateForNewViewSize; }
     bool isChangingObscuredInsetsInteractively() const { return m_isChangingObscuredInsetsInteractively; }
     bool allowShrinkToFit() const { return m_allowShrinkToFit; }
+    bool forceHorizontalShrinkToFit() const { return m_forceHorizontalShrinkToFit; }
     bool enclosedInScrollableAncestorView() const { return m_enclosedInScrollableAncestorView; }
 
     MonotonicTime timestamp() const { return m_timestamp; }
@@ -112,6 +114,7 @@ private:
     bool m_isFirstUpdateForNewViewSize { false };
     bool m_isChangingObscuredInsetsInteractively { false };
     bool m_allowShrinkToFit { false };
+    bool m_forceHorizontalShrinkToFit { false };
     bool m_enclosedInScrollableAncestorView { false };
 };
 
@@ -130,6 +133,7 @@ inline bool operator==(const VisibleContentRectUpdateInfo& a, const VisibleConte
         && a.inStableState() == b.inStableState()
         && a.isFirstUpdateForNewViewSize() == b.isFirstUpdateForNewViewSize()
         && a.allowShrinkToFit() == b.allowShrinkToFit()
+        && a.forceHorizontalShrinkToFit() == b.forceHorizontalShrinkToFit()
         && a.enclosedInScrollableAncestorView() == b.enclosedInScrollableAncestorView();
 }
 
index feed2fb..78f60e9 100644 (file)
@@ -88,6 +88,7 @@ void WebPageCreationParameters::encode(IPC::Encoder& encoder) const
     encoder << textAutosizingWidth;
     encoder << ignoresViewportScaleLimits;
     encoder << viewportConfigurationMinimumLayoutSize;
+    encoder << viewportConfigurationViewSize;
     encoder << maximumUnobscuredSize;
 #endif
 #if PLATFORM(COCOA)
@@ -244,6 +245,8 @@ std::optional<WebPageCreationParameters> WebPageCreationParameters::decode(IPC::
         return std::nullopt;
     if (!decoder.decode(parameters.viewportConfigurationMinimumLayoutSize))
         return std::nullopt;
+    if (!decoder.decode(parameters.viewportConfigurationViewSize))
+        return std::nullopt;
     if (!decoder.decode(parameters.maximumUnobscuredSize))
         return std::nullopt;
 #endif
index e76ee34..04fe8c8 100644 (file)
@@ -145,6 +145,7 @@ struct WebPageCreationParameters {
     float textAutosizingWidth;
     bool ignoresViewportScaleLimits;
     WebCore::FloatSize viewportConfigurationMinimumLayoutSize;
+    WebCore::FloatSize viewportConfigurationViewSize;
     WebCore::FloatSize maximumUnobscuredSize;
 #endif
 #if PLATFORM(COCOA)
index 42a1648..c5be5a2 100644 (file)
@@ -189,6 +189,11 @@ enum class DynamicViewportUpdateMode {
     ResizingWithDocumentHidden,
 };
 
+struct ActiveViewportLayoutSizes {
+    WebCore::FloatSize minimumLayoutSize;
+    WebCore::FloatSize viewSize;
+};
+
 #endif // PLATFORM(IOS)
 
 #if PLATFORM(IOS)
@@ -302,6 +307,8 @@ static std::optional<WebCore::ScrollbarOverlayStyle> toCoreScrollbarStyle(_WKOve
     std::optional<int32_t> _lastSentDeviceOrientation;
 
     BOOL _allowsViewportShrinkToFit;
+    BOOL _forceHorizontalViewportShrinkToFit;
+    CGFloat _minimumAllowedLayoutWidth;
 
     BOOL _hasCommittedLoadForMainFrame;
     BOOL _needsResetViewStateAfterCommitLoadForMainFrame;
@@ -698,7 +705,16 @@ static void validate(WKWebViewConfiguration *configuration)
 
 #if PLATFORM(IOS)
     _dragInteractionPolicy = _WKDragInteractionPolicyDefault;
+#if ENABLE(EXTRA_ZOOM_MODE)
+    _minimumAllowedLayoutWidth = 320;
+    _allowsViewportShrinkToFit = YES;
+    _forceHorizontalViewportShrinkToFit = YES;
+#else
+    _minimumAllowedLayoutWidth = 0;
+    _allowsViewportShrinkToFit = NO;
+    _forceHorizontalViewportShrinkToFit = NO;
 #endif
+#endif // PLATFORM(IOS)
 }
 
 - (void)_setUpSQLiteDatabaseTrackerClient
@@ -2481,25 +2497,45 @@ static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOff
     return UIEdgeInsetsAdd([_scrollView _contentScrollInset], self.safeAreaInsets, [_scrollView _edgesApplyingSafeAreaInsetsToContentInset]);
 }
 
-static WebCore::FloatSize activeMinimumLayoutSize(WKWebView *webView, const CGRect& bounds)
+- (CGFloat)_minimumAllowedLayoutWidth
+{
+    return _minimumAllowedLayoutWidth;
+}
+
+- (void)_setMinimumAllowedLayoutWidth:(CGFloat)minimumAllowedLayoutWidth
+{
+    if (_minimumAllowedLayoutWidth == minimumAllowedLayoutWidth)
+        return;
+
+    _minimumAllowedLayoutWidth = minimumAllowedLayoutWidth;
+
+    auto sizes = [self activeMinimumLayoutSizes:self.bounds];
+    [self _dispatchSetMinimumLayoutSize:sizes.minimumLayoutSize viewSize:sizes.viewSize];
+}
+
+- (ActiveViewportLayoutSizes)activeMinimumLayoutSizes:(const CGRect&)bounds
 {
-    if (webView->_overridesMinimumLayoutSize)
-        return WebCore::FloatSize(webView->_minimumLayoutSizeOverride);
+    if (_overridesMinimumLayoutSize)
+        return { WebCore::FloatSize(_minimumLayoutSizeOverride), WebCore::FloatSize(_minimumLayoutSizeOverride) };
 
+    ActiveViewportLayoutSizes sizes;
 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
-    return WebCore::FloatSize(UIEdgeInsetsInsetRect(CGRectMake(0, 0, bounds.size.width, bounds.size.height), webView._scrollViewSystemContentInset).size);
+    sizes.viewSize = WebCore::FloatSize(UIEdgeInsetsInsetRect(CGRectMake(0, 0, bounds.size.width, bounds.size.height), self._scrollViewSystemContentInset).size);
 #else
-    return WebCore::FloatSize(bounds.size);
+    sizes.viewSize = WebCore::FloatSize { bounds.size };
 #endif
+
+    sizes.minimumLayoutSize = { std::max<float>(sizes.viewSize.width(), self._minimumAllowedLayoutWidth), sizes.viewSize.height() };
+    return sizes;
 }
 
-- (void)_dispatchSetMinimumLayoutSize:(WebCore::FloatSize)minimumLayoutSize
+- (void)_dispatchSetMinimumLayoutSize:(WebCore::FloatSize)minimumLayoutSize viewSize:(WebCore::FloatSize)viewSize
 {
     if (_lastSentMinimumLayoutSize && CGSizeEqualToSize(_lastSentMinimumLayoutSize.value(), minimumLayoutSize))
         return;
 
-    LOG_WITH_STREAM(VisibleRects, stream << "-[WKWebView " << _page->pageID() << " _dispatchSetMinimumLayoutSize:] " << minimumLayoutSize << " contentZoomScale " << contentZoomScale(self));
-    _page->setViewportConfigurationMinimumLayoutSize(minimumLayoutSize);
+    LOG_WITH_STREAM(VisibleRects, stream << "-[WKWebView " << _page->pageID() << " _dispatchSetMinimumLayoutSize:] " << minimumLayoutSize << " viewSize " << viewSize << " contentZoomScale " << contentZoomScale(self));
+    _page->setViewportConfigurationMinimumLayoutSize(minimumLayoutSize, viewSize);
     _lastSentMinimumLayoutSize = minimumLayoutSize;
 }
 
@@ -2527,8 +2563,10 @@ static WebCore::FloatSize activeMinimumLayoutSize(WKWebView *webView, const CGRe
     [_scrollView setFrame:bounds];
 
     if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing) {
-        if (!_overridesMinimumLayoutSize)
-            [self _dispatchSetMinimumLayoutSize:activeMinimumLayoutSize(self, self.bounds)];
+        if (!_overridesMinimumLayoutSize) {
+            auto sizes = [self activeMinimumLayoutSizes:self.bounds];
+            [self _dispatchSetMinimumLayoutSize:sizes.minimumLayoutSize viewSize:sizes.viewSize];
+        }
         if (!_overridesMaximumUnobscuredSize)
             [self _dispatchSetMaximumUnobscuredSize:WebCore::FloatSize(bounds.size)];
 
@@ -4902,7 +4940,7 @@ static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
     _minimumLayoutSizeOverride = minimumLayoutSizeOverride;
 
     if (_dynamicViewportUpdateMode == DynamicViewportUpdateMode::NotResizing)
-        [self _dispatchSetMinimumLayoutSize:WebCore::FloatSize(minimumLayoutSizeOverride)];
+        [self _dispatchSetMinimumLayoutSize:WebCore::FloatSize(minimumLayoutSizeOverride) viewSize:WebCore::FloatSize(minimumLayoutSizeOverride)];
 
 }
 
@@ -5009,6 +5047,20 @@ static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
         [self _dispatchSetMaximumUnobscuredSize:WebCore::FloatSize(size)];
 }
 
+- (void)_setForceHorizontalViewportShrinkToFit:(BOOL)forceHorizontalViewportShrinkToFit
+{
+    if (_forceHorizontalViewportShrinkToFit == forceHorizontalViewportShrinkToFit)
+        return;
+
+    _forceHorizontalViewportShrinkToFit = forceHorizontalViewportShrinkToFit;
+    [self _scheduleVisibleContentRectUpdate];
+}
+
+- (BOOL)_forceHorizontalViewportShrinkToFit
+{
+    return _forceHorizontalViewportShrinkToFit;
+}
+
 - (void)_setAllowsViewportShrinkToFit:(BOOL)allowShrinkToFit
 {
     _allowsViewportShrinkToFit = allowShrinkToFit;
@@ -5050,27 +5102,27 @@ static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
 
     _dynamicViewportUpdateMode = DynamicViewportUpdateMode::ResizingWithAnimation;
 
-    WebCore::FloatSize oldMinimumLayoutSize = activeMinimumLayoutSize(self, oldBounds);
-    WebCore::FloatSize oldMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, oldBounds);
+    auto oldSizes = [self activeMinimumLayoutSizes:self.bounds];
+    auto oldMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, oldBounds);
     int32_t oldOrientation = activeOrientation(self);
     UIEdgeInsets oldObscuredInsets = _obscuredInsets;
 
     updateBlock();
 
     CGRect newBounds = self.bounds;
-    WebCore::FloatSize newMinimumLayoutSize = activeMinimumLayoutSize(self, newBounds);
-    WebCore::FloatSize newMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, newBounds);
+    auto newSizes = [self activeMinimumLayoutSizes:newBounds];
+    auto newMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, newBounds);
     int32_t newOrientation = activeOrientation(self);
     UIEdgeInsets newObscuredInsets = _obscuredInsets;
     CGRect futureUnobscuredRectInSelfCoordinates = UIEdgeInsetsInsetRect(newBounds, _obscuredInsets);
     CGRect contentViewBounds = [_contentView bounds];
 
-    ASSERT_WITH_MESSAGE(!(_overridesMinimumLayoutSize && newMinimumLayoutSize.isEmpty()), "Clients controlling the layout size should maintain a valid layout size to minimize layouts.");
-    if (CGRectIsEmpty(newBounds) || newMinimumLayoutSize.isEmpty() || CGRectIsEmpty(futureUnobscuredRectInSelfCoordinates) || CGRectIsEmpty(contentViewBounds)) {
+    ASSERT_WITH_MESSAGE(!(_overridesMinimumLayoutSize && newSizes.minimumLayoutSize.isEmpty()), "Clients controlling the layout size should maintain a valid layout size to minimize layouts.");
+    if (CGRectIsEmpty(newBounds) || newSizes.minimumLayoutSize.isEmpty() || CGRectIsEmpty(futureUnobscuredRectInSelfCoordinates) || CGRectIsEmpty(contentViewBounds)) {
         _dynamicViewportUpdateMode = DynamicViewportUpdateMode::NotResizing;
         [self _frameOrBoundsChanged];
         if (_overridesMinimumLayoutSize)
-            [self _dispatchSetMinimumLayoutSize:WebCore::FloatSize(newMinimumLayoutSize)];
+            [self _dispatchSetMinimumLayoutSize:newSizes.minimumLayoutSize viewSize:newSizes.viewSize];
         if (_overridesMaximumUnobscuredSize)
             [self _dispatchSetMaximumUnobscuredSize:WebCore::FloatSize(newMaximumUnobscuredSize)];
         if (_overridesInterfaceOrientation)
@@ -5081,7 +5133,7 @@ static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
     }
 
     if (CGRectEqualToRect(oldBounds, newBounds)
-        && oldMinimumLayoutSize == newMinimumLayoutSize
+        && oldSizes.minimumLayoutSize == newSizes.minimumLayoutSize
         && oldMaximumUnobscuredSize == newMaximumUnobscuredSize
         && oldOrientation == newOrientation
         && UIEdgeInsetsEqualToEdgeInsets(oldObscuredInsets, newObscuredInsets)) {
@@ -5101,13 +5153,13 @@ static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
     [_resizeAnimationView addSubview:[_contentView unscaledView]];
 
     CGSize contentSizeInContentViewCoordinates = contentViewBounds.size;
-    [_scrollView setMinimumZoomScale:std::min(newMinimumLayoutSize.width() / contentSizeInContentViewCoordinates.width, [_scrollView minimumZoomScale])];
-    [_scrollView setMaximumZoomScale:std::max(newMinimumLayoutSize.width() / contentSizeInContentViewCoordinates.width, [_scrollView maximumZoomScale])];
+    [_scrollView setMinimumZoomScale:std::min(newSizes.minimumLayoutSize.width() / contentSizeInContentViewCoordinates.width, [_scrollView minimumZoomScale])];
+    [_scrollView setMaximumZoomScale:std::max(newSizes.minimumLayoutSize.width() / contentSizeInContentViewCoordinates.width, [_scrollView maximumZoomScale])];
 
     // Compute the new scale to keep the current content width in the scrollview.
     CGFloat oldWebViewWidthInContentViewCoordinates = oldUnobscuredContentRect.width();
     CGFloat visibleContentViewWidthInContentCoordinates = std::min(contentSizeInContentViewCoordinates.width, oldWebViewWidthInContentViewCoordinates);
-    CGFloat targetScale = newMinimumLayoutSize.width() / visibleContentViewWidthInContentCoordinates;
+    CGFloat targetScale = newSizes.minimumLayoutSize.width() / visibleContentViewWidthInContentCoordinates;
     CGFloat resizeAnimationViewAnimationScale = targetScale / contentZoomScale(self);
     [_resizeAnimationView setTransform:CGAffineTransformMakeScale(resizeAnimationViewAnimationScale, resizeAnimationViewAnimationScale)];
 
@@ -5147,11 +5199,11 @@ static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
     UIEdgeInsets unobscuredSafeAreaInsets = [self _computedUnobscuredSafeAreaInset];
     WebCore::FloatBoxExtent unobscuredSafeAreaInsetsExtent(unobscuredSafeAreaInsets.top, unobscuredSafeAreaInsets.right, unobscuredSafeAreaInsets.bottom, unobscuredSafeAreaInsets.left);
 
-    _lastSentMinimumLayoutSize = newMinimumLayoutSize;
+    _lastSentMinimumLayoutSize = newSizes.minimumLayoutSize;
     _lastSentMaximumUnobscuredSize = newMaximumUnobscuredSize;
     _lastSentDeviceOrientation = newOrientation;
 
-    _page->dynamicViewportSizeUpdate(newMinimumLayoutSize, newMaximumUnobscuredSize, visibleRectInContentCoordinates, unobscuredRectInContentCoordinates, futureUnobscuredRectInSelfCoordinates, unobscuredSafeAreaInsetsExtent, targetScale, newOrientation);
+    _page->dynamicViewportSizeUpdate(newSizes.minimumLayoutSize, newSizes.viewSize, newMaximumUnobscuredSize, visibleRectInContentCoordinates, unobscuredRectInContentCoordinates, futureUnobscuredRectInSelfCoordinates, unobscuredSafeAreaInsetsExtent, targetScale, newOrientation);
     if (WebKit::DrawingAreaProxy* drawingArea = _page->drawingArea())
         drawingArea->setSize(WebCore::IntSize(newBounds.size));
 }
@@ -5216,12 +5268,12 @@ static inline WebKit::FindOptions toFindOptions(_WKFindOptions wkFindOptions)
     [self _scheduleVisibleContentRectUpdate];
 
     CGRect newBounds = self.bounds;
-    WebCore::FloatSize newMinimumLayoutSize = activeMinimumLayoutSize(self, newBounds);
-    WebCore::FloatSize newMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, newBounds);
+    auto newSizes = [self activeMinimumLayoutSizes:newBounds];
+    auto newMaximumUnobscuredSize = activeMaximumUnobscuredSize(self, newBounds);
     int32_t newOrientation = activeOrientation(self);
 
-    if (!_lastSentMinimumLayoutSize || newMinimumLayoutSize != _lastSentMinimumLayoutSize.value())
-        [self _dispatchSetMinimumLayoutSize:WebCore::FloatSize(newMinimumLayoutSize)];
+    if (!_lastSentMinimumLayoutSize || newSizes.minimumLayoutSize != _lastSentMinimumLayoutSize.value())
+        [self _dispatchSetMinimumLayoutSize:newSizes.minimumLayoutSize viewSize:newSizes.viewSize];
     if (!_lastSentMaximumUnobscuredSize || newMaximumUnobscuredSize != _lastSentMaximumUnobscuredSize.value())
         [self _dispatchSetMaximumUnobscuredSize:WebCore::FloatSize(newMaximumUnobscuredSize)];
     if (!_lastSentDeviceOrientation || newOrientation != _lastSentDeviceOrientation.value())
index 4191a2e..a1ccce4 100644 (file)
@@ -204,6 +204,8 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 @property (nonatomic, setter=_setInterfaceOrientationOverride:) UIInterfaceOrientation _interfaceOrientationOverride;
 
 @property (nonatomic, setter=_setAllowsViewportShrinkToFit:) BOOL _allowsViewportShrinkToFit;
+@property (nonatomic, setter=_setForceHorizontalViewportShrinkToFit:) BOOL _forceHorizontalViewportShrinkToFit WK_API_AVAILABLE(ios(WK_IOS_TBA));
+@property (nonatomic, setter=_setMinimumAllowedLayoutWidth:) CGFloat _minimumAllowedLayoutWidth WK_API_AVAILABLE(ios(WK_IOS_TBA));
 
 // FIXME: Remove these three properties once we expose WKWebViewContentProvider as API.
 @property (nonatomic, readonly, getter=_isDisplayingPDF) BOOL _displayingPDF;
index 6edb632..22f8465 100644 (file)
@@ -5853,6 +5853,7 @@ WebPageCreationParameters WebPageProxy::creationParameters()
     parameters.mimeTypesWithCustomContentProviders = m_pageClient.mimeTypesWithCustomContentProviders();
     parameters.ignoresViewportScaleLimits = m_forceAlwaysUserScalable;
     parameters.viewportConfigurationMinimumLayoutSize = m_viewportConfigurationMinimumLayoutSize;
+    parameters.viewportConfigurationViewSize = m_viewportConfigurationViewSize;
     parameters.maximumUnobscuredSize = m_maximumUnobscuredSize;
 #endif
 
index c409d59..d28dc99 100644 (file)
@@ -532,10 +532,10 @@ public:
     void overflowScrollWillStartScroll();
     void overflowScrollDidEndScroll();
 
-    void dynamicViewportSizeUpdate(const WebCore::FloatSize& minimumLayoutSize, const WebCore::FloatSize& maximumUnobscuredSize, const WebCore::FloatRect& targetExposedContentRect, const WebCore::FloatRect& targetUnobscuredRect, const WebCore::FloatRect& targetUnobscuredRectInScrollViewCoordinates, const WebCore::FloatBoxExtent& unobscuredSafeAreaInsets, double targetScale, int32_t deviceOrientation);
+    void dynamicViewportSizeUpdate(const WebCore::FloatSize& minimumLayoutSize, const WebCore::FloatSize& viewSize, const WebCore::FloatSize& maximumUnobscuredSize, const WebCore::FloatRect& targetExposedContentRect, const WebCore::FloatRect& targetUnobscuredRect, const WebCore::FloatRect& targetUnobscuredRectInScrollViewCoordinates, const WebCore::FloatBoxExtent& unobscuredSafeAreaInsets, double targetScale, int32_t deviceOrientation);
     void synchronizeDynamicViewportUpdate();
 
-    void setViewportConfigurationMinimumLayoutSize(const WebCore::FloatSize&);
+    void setViewportConfigurationMinimumLayoutSize(const WebCore::FloatSize&, const WebCore::FloatSize& viewSize);
     void setMaximumUnobscuredSize(const WebCore::FloatSize&);
     void setDeviceOrientation(int32_t);
     int32_t deviceOrientation() const { return m_deviceOrientation; }
@@ -2088,6 +2088,7 @@ private:
     std::unique_ptr<NodeAssistanceArguments> m_deferredNodeAssistanceArguments;
     bool m_forceAlwaysUserScalable { false };
     WebCore::FloatSize m_viewportConfigurationMinimumLayoutSize;
+    WebCore::FloatSize m_viewportConfigurationViewSize;
     WebCore::FloatSize m_maximumUnobscuredSize;
 #endif
 
index be1fc99..db902b4 100644 (file)
@@ -425,6 +425,7 @@ private:
         _sizeChangedSinceLastVisibleContentRectUpdate,
         isChangingObscuredInsetsInteractively,
         _webView._allowsViewportShrinkToFit,
+        _webView._forceHorizontalViewportShrinkToFit,
         enclosedInScrollableAncestorView,
         timestamp,
         velocityData.horizontalVelocity,
index a8b32ce..b88c124 100644 (file)
@@ -304,7 +304,7 @@ void WebPageProxy::overflowScrollDidEndScroll()
     m_pageClient.overflowScrollDidEndScroll();
 }
 
-void WebPageProxy::dynamicViewportSizeUpdate(const FloatSize& minimumLayoutSize, const WebCore::FloatSize& maximumUnobscuredSize, const FloatRect& targetExposedContentRect, const FloatRect& targetUnobscuredRect, const FloatRect& targetUnobscuredRectInScrollViewCoordinates, const WebCore::FloatBoxExtent& unobscuredSafeAreaInsets, double targetScale, int32_t deviceOrientation)
+void WebPageProxy::dynamicViewportSizeUpdate(const FloatSize& minimumLayoutSize, const FloatSize& viewSize, const WebCore::FloatSize& maximumUnobscuredSize, const FloatRect& targetExposedContentRect, const FloatRect& targetUnobscuredRect, const FloatRect& targetUnobscuredRectInScrollViewCoordinates, const WebCore::FloatBoxExtent& unobscuredSafeAreaInsets, double targetScale, int32_t deviceOrientation)
 {
     if (!isValid())
         return;
@@ -313,7 +313,7 @@ void WebPageProxy::dynamicViewportSizeUpdate(const FloatSize& minimumLayoutSize,
 
     m_dynamicViewportSizeUpdateWaitingForTarget = true;
     m_dynamicViewportSizeUpdateWaitingForLayerTreeCommit = true;
-    m_process->send(Messages::WebPage::DynamicViewportSizeUpdate(minimumLayoutSize, maximumUnobscuredSize, targetExposedContentRect, targetUnobscuredRect, targetUnobscuredRectInScrollViewCoordinates, unobscuredSafeAreaInsets, targetScale, deviceOrientation, ++m_currentDynamicViewportSizeUpdateID), m_pageID);
+    m_process->send(Messages::WebPage::DynamicViewportSizeUpdate(minimumLayoutSize, viewSize, maximumUnobscuredSize, targetExposedContentRect, targetUnobscuredRect, targetUnobscuredRectInScrollViewCoordinates, unobscuredSafeAreaInsets, targetScale, deviceOrientation, ++m_currentDynamicViewportSizeUpdateID), m_pageID);
 }
 
 void WebPageProxy::synchronizeDynamicViewportUpdate()
@@ -353,12 +353,13 @@ void WebPageProxy::synchronizeDynamicViewportUpdate()
     m_dynamicViewportSizeUpdateWaitingForLayerTreeCommit = false;
 }
 
-void WebPageProxy::setViewportConfigurationMinimumLayoutSize(const WebCore::FloatSize& size)
+void WebPageProxy::setViewportConfigurationMinimumLayoutSize(const WebCore::FloatSize& size, const WebCore::FloatSize& viewSize)
 {
     m_viewportConfigurationMinimumLayoutSize = size;
+    m_viewportConfigurationViewSize = viewSize;
 
     if (isValid())
-        m_process->send(Messages::WebPage::SetViewportConfigurationMinimumLayoutSize(size), m_pageID);
+        m_process->send(Messages::WebPage::SetViewportConfigurationMinimumLayoutSize(size, viewSize), m_pageID);
 }
 
 void WebPageProxy::setForceAlwaysUserScalable(bool userScalable)
index 7fb5f17..374d830 100644 (file)
@@ -604,7 +604,7 @@ WebPage::WebPage(uint64_t pageID, WebPageCreationParameters&& parameters)
 #endif
 
 #if PLATFORM(IOS)
-    setViewportConfigurationMinimumLayoutSize(parameters.viewportConfigurationMinimumLayoutSize);
+    setViewportConfigurationMinimumLayoutSize(parameters.viewportConfigurationMinimumLayoutSize, parameters.viewportConfigurationViewSize);
     setMaximumUnobscuredSize(parameters.maximumUnobscuredSize);
 #endif
 }
index a16c6ae..cc93510 100644 (file)
@@ -867,10 +867,10 @@ public:
     void updateVisibilityState(bool isInitialState = false);
 
 #if PLATFORM(IOS)
-    void setViewportConfigurationMinimumLayoutSize(const WebCore::FloatSize&);
+    void setViewportConfigurationMinimumLayoutSize(const WebCore::FloatSize&, const WebCore::FloatSize& viewSize);
     void setMaximumUnobscuredSize(const WebCore::FloatSize&);
     void setDeviceOrientation(int32_t);
-    void dynamicViewportSizeUpdate(const WebCore::FloatSize& minimumLayoutSize, const WebCore::FloatSize& maximumUnobscuredSize, const WebCore::FloatRect& targetExposedContentRect, const WebCore::FloatRect& targetUnobscuredRect, const WebCore::FloatRect& targetUnobscuredRectInScrollViewCoordinates, const WebCore::FloatBoxExtent& targetUnobscuredSafeAreaInsets, double scale, int32_t deviceOrientation, uint64_t dynamicViewportSizeUpdateID);
+    void dynamicViewportSizeUpdate(const WebCore::FloatSize& minimumLayoutSize, const WebCore::FloatSize& viewSize, const WebCore::FloatSize& maximumUnobscuredSize, const WebCore::FloatRect& targetExposedContentRect, const WebCore::FloatRect& targetUnobscuredRect, const WebCore::FloatRect& targetUnobscuredRectInScrollViewCoordinates, const WebCore::FloatBoxExtent& targetUnobscuredSafeAreaInsets, double scale, int32_t deviceOrientation, uint64_t dynamicViewportSizeUpdateID);
     void synchronizeDynamicViewportUpdate(double& newTargetScale, WebCore::FloatPoint& newScrollPosition, uint64_t& nextValidLayerTreeTransactionID);
     std::optional<float> scaleFromUIProcess(const VisibleContentRectUpdateInfo&) const;
     void updateVisibleContentRects(const VisibleContentRectUpdateInfo&, MonotonicTime oldestTimestamp);
index 3171b40..2b07f80 100644 (file)
@@ -41,10 +41,10 @@ messages -> WebPage LegacyReceiver {
     KeyEvent(WebKit::WebKeyboardEvent event)
     MouseEvent(WebKit::WebMouseEvent event)
 #if PLATFORM(IOS)
-    SetViewportConfigurationMinimumLayoutSize(WebCore::FloatSize size)
+    SetViewportConfigurationMinimumLayoutSize(WebCore::FloatSize size, WebCore::FloatSize viewSize)
     SetMaximumUnobscuredSize(WebCore::FloatSize size)
     SetDeviceOrientation(int32_t deviceOrientation)
-    DynamicViewportSizeUpdate(WebCore::FloatSize minimumLayoutSize, WebCore::FloatSize maximumUnobscuredSize, WebCore::FloatRect targetExposedContentRect, WebCore::FloatRect targetUnobscuredRect, WebCore::FloatRect targetUnobscuredRectInScrollViewCoordinates, WebCore::RectEdges<float> targetUnobscuredSafeAreaInsets, double scale, int32_t deviceOrientation, uint64_t dynamicViewportSizeUpdateID)
+    DynamicViewportSizeUpdate(WebCore::FloatSize minimumLayoutSize, WebCore::FloatSize viewSize, WebCore::FloatSize maximumUnobscuredSize, WebCore::FloatRect targetExposedContentRect, WebCore::FloatRect targetUnobscuredRect, WebCore::FloatRect targetUnobscuredRectInScrollViewCoordinates, WebCore::RectEdges<float> targetUnobscuredSafeAreaInsets, double scale, int32_t deviceOrientation, uint64_t dynamicViewportSizeUpdateID)
     SynchronizeDynamicViewportUpdate() -> (double newTargetScale, WebCore::FloatPoint newScrollPosition, uint64_t nextValidLayerTreeTransactionID)
 
     HandleTap(WebCore::IntPoint point, uint64_t lastLayerTreeTransactionId)
index 8140195..2987080 100644 (file)
@@ -2851,10 +2851,10 @@ void WebPage::autofillLoginCredentials(const String& username, const String& pas
     }
 }
 
-void WebPage::setViewportConfigurationMinimumLayoutSize(const FloatSize& size)
+void WebPage::setViewportConfigurationMinimumLayoutSize(const FloatSize& size, const FloatSize& viewSize)
 {
-    LOG_WITH_STREAM(VisibleRects, stream << "WebPage " << m_pageID << " setViewportConfigurationMinimumLayoutSize " << size);
-    if (m_viewportConfiguration.setMinimumLayoutSize(size))
+    LOG_WITH_STREAM(VisibleRects, stream << "WebPage " << m_pageID << " setViewportConfigurationMinimumLayoutSize " << size << " viewSize " << viewSize);
+    if (m_viewportConfiguration.setMinimumLayoutSize(size, viewSize))
         viewportConfigurationChanged();
 }
 
@@ -2889,12 +2889,12 @@ void WebPage::resetTextAutosizing()
     }
 }
 
-void WebPage::dynamicViewportSizeUpdate(const FloatSize& minimumLayoutSize, const WebCore::FloatSize& maximumUnobscuredSize, const FloatRect& targetExposedContentRect, const FloatRect& targetUnobscuredRect, const WebCore::FloatRect& targetUnobscuredRectInScrollViewCoordinates, const WebCore::FloatBoxExtent& targetUnobscuredSafeAreaInsets, double targetScale, int32_t deviceOrientation, uint64_t dynamicViewportSizeUpdateID)
+void WebPage::dynamicViewportSizeUpdate(const FloatSize& minimumLayoutSize, const FloatSize& viewSize, const WebCore::FloatSize& maximumUnobscuredSize, const FloatRect& targetExposedContentRect, const FloatRect& targetUnobscuredRect, const WebCore::FloatRect& targetUnobscuredRectInScrollViewCoordinates, const WebCore::FloatBoxExtent& targetUnobscuredSafeAreaInsets, double targetScale, int32_t deviceOrientation, uint64_t dynamicViewportSizeUpdateID)
 {
     SetForScope<bool> dynamicSizeUpdateGuard(m_inDynamicSizeUpdate, true);
     // FIXME: this does not handle the cases where the content would change the content size or scroll position from JavaScript.
     // To handle those cases, we would need to redo this computation on every change until the next visible content rect update.
-    LOG_WITH_STREAM(VisibleRects, stream << "\nWebPage::dynamicViewportSizeUpdate - minimumLayoutSize " << minimumLayoutSize << " targetUnobscuredRect " << targetUnobscuredRect << " targetExposedContentRect " << targetExposedContentRect << " targetScale " << targetScale);
+    LOG_WITH_STREAM(VisibleRects, stream << "\nWebPage::dynamicViewportSizeUpdate - minimumLayoutSize " << minimumLayoutSize << " viewSize " << viewSize << " targetUnobscuredRect " << targetUnobscuredRect << " targetExposedContentRect " << targetExposedContentRect << " targetScale " << targetScale);
 
     FrameView& frameView = *m_page->mainFrame().view();
     IntSize oldContentSize = frameView.contentsSize();
@@ -2927,7 +2927,7 @@ void WebPage::dynamicViewportSizeUpdate(const FloatSize& minimumLayoutSize, cons
     }
 
     LOG_WITH_STREAM(VisibleRects, stream << "WebPage::dynamicViewportSizeUpdate setting minimum layout size to " << minimumLayoutSize);
-    m_viewportConfiguration.setMinimumLayoutSize(minimumLayoutSize);
+    m_viewportConfiguration.setMinimumLayoutSize(minimumLayoutSize, viewSize);
     IntSize newLayoutSize = m_viewportConfiguration.layoutSize();
 
     if (setFixedLayoutSize(newLayoutSize))
@@ -3307,7 +3307,9 @@ void WebPage::updateVisibleContentRects(const VisibleContentRectUpdateInfo& visi
     if (scrollPosition != frameView.scrollPosition())
         m_dynamicSizeUpdateHistory.clear();
 
-    if (m_viewportConfiguration.setCanIgnoreScalingConstraints(m_ignoreViewportScalingConstraints && visibleContentRectUpdateInfo.allowShrinkToFit()))
+    bool didUpdateForceHorizontalShrinkToFit = m_viewportConfiguration.setForceHorizontalShrinkToFit(visibleContentRectUpdateInfo.forceHorizontalShrinkToFit());
+    bool didUpdateCanIgnoreViewportScalingConstraints = m_viewportConfiguration.setCanIgnoreScalingConstraints(m_ignoreViewportScalingConstraints && visibleContentRectUpdateInfo.allowShrinkToFit());
+    if (didUpdateForceHorizontalShrinkToFit || didUpdateCanIgnoreViewportScalingConstraints)
         viewportConfigurationChanged();
 
     frameView.setUnobscuredContentSize(visibleContentRectUpdateInfo.unobscuredContentRect().size());
index 38aa423..2d53236 100644 (file)
@@ -1,3 +1,20 @@
+2018-02-27  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Extra zoom mode] Implement additional SPI for adjusting viewport shrink-to-fit behavior
+        https://bugs.webkit.org/show_bug.cgi?id=183100
+        <rdar://problem/37840987>
+
+        Reviewed by Tim Horton.
+
+        Add API tests that exercise -_setMinimumAllowedLayoutWidth: and -_setForceHorizontalViewportShrinkToFit:. See
+        WebKit ChangeLog for more detail.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/ios/ViewportSizingTests.mm: Added.
+        (TestWebKitAPI::while):
+        (viewportTestPageMarkup):
+        (TestWebKitAPI::TEST):
+
 2018-02-27  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         Unreviewed, skip FTL tests if FTL is disabled
index 8341567..ea91f0a 100644 (file)
@@ -90,6 +90,7 @@
                2E14A5291D3FE96B0010F35B /* autoplaying-video-with-audio.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2E14A5281D3FE8B80010F35B /* autoplaying-video-with-audio.html */; };
                2E1B7B001D41ABA7007558B4 /* large-video-seek-after-ending.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2E1B7AFF1D41A95F007558B4 /* large-video-seek-after-ending.html */; };
                2E1B7B021D41B1B9007558B4 /* large-video-hides-controls-after-seek-to-end.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2E1B7B011D41B1B3007558B4 /* large-video-hides-controls-after-seek-to-end.html */; };
+               2E1B881F2040EE5300FFF6A9 /* ViewportSizingTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2E1B881E2040EE5300FFF6A9 /* ViewportSizingTests.mm */; };
                2E1DFDED1D42A51100714A00 /* large-videos-with-audio.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2E1DFDEC1D42A41C00714A00 /* large-videos-with-audio.html */; };
                2E1DFDEF1D42A6F200714A00 /* large-videos-with-audio-autoplay.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2E1DFDEE1D42A6EB00714A00 /* large-videos-with-audio-autoplay.html */; };
                2E1DFDF11D42E1E400714A00 /* large-video-seek-to-beginning-and-play-after-ending.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 2E1DFDF01D42E14400714A00 /* large-video-seek-to-beginning-and-play-after-ending.html */; };
                2E14A5281D3FE8B80010F35B /* autoplaying-video-with-audio.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "autoplaying-video-with-audio.html"; sourceTree = "<group>"; };
                2E1B7AFF1D41A95F007558B4 /* large-video-seek-after-ending.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "large-video-seek-after-ending.html"; sourceTree = "<group>"; };
                2E1B7B011D41B1B3007558B4 /* large-video-hides-controls-after-seek-to-end.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "large-video-hides-controls-after-seek-to-end.html"; sourceTree = "<group>"; };
+               2E1B881E2040EE5300FFF6A9 /* ViewportSizingTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ViewportSizingTests.mm; sourceTree = "<group>"; };
                2E1DFDEC1D42A41C00714A00 /* large-videos-with-audio.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "large-videos-with-audio.html"; sourceTree = "<group>"; };
                2E1DFDEE1D42A6EB00714A00 /* large-videos-with-audio-autoplay.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "large-videos-with-audio-autoplay.html"; sourceTree = "<group>"; };
                2E1DFDF01D42E14400714A00 /* large-video-seek-to-beginning-and-play-after-ending.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "large-video-seek-to-beginning-and-play-after-ending.html"; sourceTree = "<group>"; };
                                F4D4F3B71E4E36E400BB2767 /* DataInteractionTests.mm */,
                                7560917719259C59009EF06E /* MemoryCacheAddImageToCacheIOS.mm */,
                                F46849BD1EEF58E400B937FE /* UIPasteboardTests.mm */,
+                               2E1B881E2040EE5300FFF6A9 /* ViewportSizingTests.mm */,
                                514958BD1F7427AC00E87BAD /* WKWebViewAutofillTests.mm */,
                        );
                        path = ios;
                                7C83E03A1D0A602700FEBCF3 /* UtilitiesCocoa.mm in Sources */,
                                7C83E0C61D0A654E00FEBCF3 /* VideoControlsManager.mm in Sources */,
                                115EB3431EE0BA03003C2C0A /* ViewportSizeForViewportUnits.mm in Sources */,
+                               2E1B881F2040EE5300FFF6A9 /* ViewportSizingTests.mm in Sources */,
                                6356FB221EC4E0BA0044BF18 /* VisibleContentRect.mm in Sources */,
                                83779C381F82FECE007CDA8A /* VisitedLinkStore.mm in Sources */,
                                0F139E771A423A5B00F590F5 /* WeakObjCPtr.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/ios/ViewportSizingTests.mm b/Tools/TestWebKitAPI/Tests/ios/ViewportSizingTests.mm
new file mode 100644 (file)
index 0000000..9800d37
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#import "config.h"
+
+#if WK_API_ENABLED && PLATFORM(IOS)
+
+#import "PlatformUtilities.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKWebViewPrivate.h>
+
+using namespace TestWebKitAPI;
+
+@implementation TestWKWebView (ViewportTestingHelpers)
+
+- (BOOL)scaleIsApproximately:(CGFloat)expectedScale
+{
+    static const double maximumExpectedScaleDifference = 0.001;
+    return ABS(self.scrollView.zoomScale - expectedScale) < maximumExpectedScaleDifference;
+}
+
+- (void)expectScaleToBecome:(CGFloat)expectedScale
+{
+    while (![self scaleIsApproximately:expectedScale])
+        [self waitForNextPresentationUpdate];
+}
+
+@end
+
+static NSString *viewportTestPageMarkup(NSString *viewportMetaContent, int contentWidth, int contentHeight)
+{
+    return [NSString stringWithFormat:@"<meta name='viewport' content='%@'><body style='margin: 0'><div style='width: %dpx; height: %dpx; background-color: red'></div>", viewportMetaContent, contentWidth, contentHeight];
+}
+
+namespace TestWebKitAPI {
+
+TEST(ViewportSizingTests, ForceShrinkToFitViewportOverridesViewportParameters)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 400, 400)]);
+    [webView _setAllowsViewportShrinkToFit:YES];
+    [webView _setForceHorizontalViewportShrinkToFit:NO];
+    [webView synchronouslyLoadHTMLString:viewportTestPageMarkup(@"width=device-width, shrink-to-fit=no", 600, 100)];
+    [webView expectScaleToBecome:1];
+
+    [webView _setForceHorizontalViewportShrinkToFit:YES];
+    [webView expectScaleToBecome:0.667];
+
+    [webView _setForceHorizontalViewportShrinkToFit:NO];
+    [webView expectScaleToBecome:1];
+}
+
+TEST(ViewportSizingTests, ShrinkToFitViewportWithMinimumAllowedLayoutWidth)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)]);
+    [webView _setAllowsViewportShrinkToFit:YES];
+    [webView _setForceHorizontalViewportShrinkToFit:YES];
+    [webView _setMinimumAllowedLayoutWidth:0];
+    [webView synchronouslyLoadHTMLString:viewportTestPageMarkup(@"width=device-width, initial-scale=1", 400, 100)];
+    [webView expectScaleToBecome:0.5];
+    EXPECT_WK_STREQ("200", [webView stringByEvaluatingJavaScript:@"document.body.clientWidth"]);
+
+    [webView _setMinimumAllowedLayoutWidth:400];
+    [webView waitForNextPresentationUpdate];
+    EXPECT_WK_STREQ("400", [webView stringByEvaluatingJavaScript:@"document.body.clientWidth"]);
+    EXPECT_TRUE([webView scaleIsApproximately:0.5]);
+
+    [webView _setMinimumAllowedLayoutWidth:100];
+    [webView waitForNextPresentationUpdate];
+    EXPECT_WK_STREQ("200", [webView stringByEvaluatingJavaScript:@"document.body.clientWidth"]);
+    EXPECT_TRUE([webView scaleIsApproximately:0.5]);
+}
+
+} // namespace TestWebKitAPI
+
+#endif // WK_API_ENABLED && PLATFORM(IOS)