The AMP Project and Igalia working together to improve WebKit and the Web Platform
TL;DR
The AMP Project and Igalia have recently been collaborating to improve WebKit’s implementation of the Web platform. Both teams are committed to make the Web better and we expect that all developers and users will benefit from this effort. In this blog post, we review some of the bug fixes and features currently being considered:
-
Frame sandboxing: Implementing sandbox values to allow trusted third-party resources to open unsandboxed popups or restrict unsafe operations of malicious ones.
-
Frame scrolling on iOS: Trying to move to a more standard and interoperable approach via
iframe
elements; addressing miscellaneous issues with scrollable nodes (e.g. visual artifacts while scrolling, view not scrolled when using “Find Text”…). -
Root scroller: Finding a solution to the old interoperability issue about how to scroll the main frame; considering a new rootScroller API.
Some demo pages for frame sandboxing and scrolling are also available if you wish to test features discussed in this blog post.
Introduction
AMP is an open-source project to enable websites and ads that are consistently fast, beautiful and high-performing across devices and distribution platforms. Several interoperability bugs and missing features in WebKit have caused problems to AMP users and to Web developers in general. Although it is possible to add platform-specific workarounds to AMP, the best way to help the Web Platform community is to directly fix these issues in WebKit, so that everybody can benefit from these improvements.
Igalia is a consulting company with a team dedicated to Web Platform developments in all open-source Web Engines (Chromium, WebKit, Servo, Gecko) working in the implementation and standardization of miscellaneous technologies (CSS Grid/flexbox, ECMAScript, WebRTC, WebVR, ARIA, MathML, etc). Given this expertise, the AMP Project sponsored Igalia so that they can lead these developments in WebKit. It is worth noting that this project aligns with the Web Predictability effort supported by both Google and Igalia, which aims at making the Web more predictable for developers. In particular, the following aspects are considered:
- Interoperability: Effort is made to write Web Platform Tests (WPT), to follow Web standards and ensure consistent behaviors between web engines or operating systems.
- Compatibility: Changes are carefully analyzed using telemetry techniques or user feedback in order to avoid breaking compatibility with previous versions of WebKit.
- Reducing footguns: Removals of non-standard features (e.g. CSS vendor prefixes) are attempted while new features are carefully introduced.
Below we provide further description of the WebKit improvements, showing concretely how the above principles are followed.
Frame sandboxing
A sandbox attribute can
be specified on the iframe
element in order to enable a set of
restrictions on any content it hosts.
These conditions can be relaxed by specifying a list of values such as
allow-scripts
(to allow javascript execution in the frame) or allow-popups
(to allow the frame to open popups). By default, the same restrictions apply
to a popup opened by a sandboxed frame.
However, sometimes this behavior is not wanted. Consider for example the case of an advertisement inside a sandboxed frame. If a popup is opened from this frame then it is likely that a non-sandboxed context is desired on the landing page. In order to handle this use case, a new allow-popups-to-escape-sandbox value has been introduced. The value is now supported in Safari Technology Preview 34.
While performing that work, it was noticed that some WPT tests for the sandbox attribute were still failing. It turns out that WebKit does not really follow the rules to allow navigation. More specifically, navigating a top context is never allowed when such context corresponds to an opened popup. We have made some changes to WebKit so that it behaves more closely to the specification. This is integrated into Safari Technology Preview 35 and you can for example try this W3C test. Note that this test requires to change preferences to allow popups.
It is worth noting that web engines may slightly depart from the specification regarding the previously mentioned rules. In particular, WebKit checks a same-origin condition to be sure that one frame is allowed to navigate another one. WebKit always has contained a special case to ignore this condition when a sandboxed frame with the allow-top-navigation flag tries and navigate its top frame. This feature, sometimes known as “frame busting,” has been used by third-party resources to perform malicious auto-redirecting. As a consequence, Chromium developers proposed to restrict frame busting to the case where the navigation is triggered by a user gesture.
According to Chromium’s telemetry frame busting without a user gesture is very rare.
But when experimenting with the behavior change of allow-top-navigation
several regressions were reported.
Hence it was instead decided to introduce the
allow-top-navigation-by-user-activation flag in order to provide this improved
safety context while still preserving backward compatibility.
We implemented this feature in WebKit and it is now available in Safari Technology Preview 37.
Finally, another proposed security improvement is to use an
allow-modals
flag
to explicitly allow sandboxed frames to display modal dialogs (with alert
,
prompt
, etc). That is, the default behavior for sandboxed frames will be
to forbid such modal dialogs. Again, such a change of behavior must be done with
care. Experiments in Chromium showed that the
usage of modal dialogs in sandboxed frames is very low and no users complained.
Hence we
implemented that behavior in WebKit
and the feature should arrive in Safari Technology Preview soon.
Check out the
frame sandboxing demos if
if you want to test the new allow-popup-to-escape-sandbox
,
allow-top-navigation-without-user-activation
and allow-modals
flags.
Frame scrolling on iOS
Apple’s UI choice was to (almost) always “flatten” (expand) frames so that users do not require to scroll them. The rationale for this is that it avoids to be trapped into hierarchy of nested frames. Changing that behavior is likely to cause a big backward compatibility issue on iOS so for now we proposed a less radical solution: Add a heuristic to support the case of “fullscreen” iframes used by the AMP Project. Note that such exceptions already exist in WebKit, e.g. to avoid making offscreen content visible.
We thus added the following heuristic into
WebKit Nightly: do not flatten
out-of-flow iframes (e.g. position: absolute
) that have viewport units
(e.g. vw
and vh
). This includes the case of the “fullscreen” iframe previously
mentioned. For now it is still under a developer flag so that WebKit developers
can control when they want to enable it. Of course, if this is successful we
might consider more advanced heuristics.
The fact that frames are never scrollable in iOS is an obvious interoperability
issue. As a workaround, it is possible to emulate such “scrollable nodes”
behavior using
overflow: scroll
nodes with the -webkit-overflow-scrolling: touch
property set. This is not really ideal for our Web Predictability goal as we
would like to get rid of browser vendor prefixes. Also, in practice such
workarounds lead to even more problems in AMP as explained in these
blog
posts. That’s why implementing scrolling of frames is
one of the main goals of this project and
significant steps have already been made in that direction.
The (relatively complex) class hierarchy involved in frame scrolling is
summarized in Figure 2. The frame flattening heuristic mentioned above is
handled in the WebCore::RenderIFrame
class (in purple).
The WebCore::ScrollingTreeFrameScrollingNodeIOS
and
WebCore::ScrollingTreeOverflowScrollingNodeIOS
classes from the
scrolling tree (in blue) are used to scroll, respectively,
the main frame and overflow nodes on iOS. Scrolling of
non-main frames will obviously have some code to share with the former, but
it will also have some parts in common with the latter. For example,
passing an extra UIScrollView
layer is needed instead of relying on the one
contained in the WKWebView
of the main frame. An important
step is thus to introduce a special class for scrolling inner frames that
would share some logic from the two other classes and
some refactoring to ensure optimal code reuse.
Similar refactoring has been done for scrolling node states (in red)
to move the scrolling layer parameter into WebCore::ScrollingStateNode
instead of having separate members
for WebCore::ScrollingStateOverflowScrollingNode
and WebCore::ScrollingStateFrameScrollingNode
.
The scrolling coordinator classes (in green) are also important, for example to
handle hit testing. At the moment, this is not really implemented for
overflow nodes but it might be important to have it for scrollable frames.
Again, one sees that some logic is shared for asynchronous scrolling on macOS (WebCore::ScrollingCoordinatorMac
) and iOS (WebCore::ScrollingCoordinatorIOS
)
in ancestor classes. Indeed, our effort to make frames scrollable on iOS
is also opening the possibility of
asynchronous scrolling of frames on macOS, something that is currently not implemented.
Finally, some more work is necessary in the render classes (purple) to ensure
that the layer hierarchies are correctly built. Patches
have been uploaded
and you can view the result on the video of Figure 4.
Notice that this work has not been reviewed yet and
there are known bugs, for example with
overlapping elements (hit testing not implemented) or position: fixed
elements.
Various other scrolling bugs were reported, analyzed and sometimes fixed by
Apple. The switch from overflow nodes to scrollable iframes is unlikely to
address them. For example, the “Find Text” operation in iOS has
advanced features done by the UI process (highlight, smart magnification) but
the scrolling operation needed only works for the main frame. It looks like this could be fixed
by unifying a bit the scrolling code path with macOS. There are also
several jump and flickering bugs with position: fixed
nodes. Finally, Apple fixed
inconsistent scrolling inertia
used for the main frame and the one used for inner scrollable nodes by
making the former the same as the latter.
Root Scroller
The CSSOM View specification extends the DOM element with some scrolling properties.
That specification indicates that the element to consider to scroll the main view
is document.body
in quirks mode while it is document.documentElement
in no-quirks mode. This is the behavior that has always been followed
by browsers like Firefox or Interner Explorer. However, WebKit-based browsers
always treat document.body
as the root scroller. This interoperability issue
has been a big problem for web developers.
One convenient workaround was to introduce the
document.scrollingElement which returns the element to use for scrolling the main
view (document.body
or document.documentElement
) and was recently
implemented in WebKit.
Use this
test page
to verify whether your browser supports the
document.scrollingElement
property and which DOM element is used to scroll
the main view in no-quirks mode.
Nevertheless, this does not solve the issue with existing web pages.
Chromium’s Web Platform Predictability team has made a huge communication effort
with Web authors and developers which has
drastically reduced the use of document.body
in no-quirks mode. For instance,
Chromium’s telemetry on Figure 3 indicates that the percentage of
document.body.scrollTop
in no-quirks pages has gone
from 18% down to 0.0003% during the past three years. Hence the Chromium team
is now considering shipping the standard behavior.
In WebKit, the issue has been known for a long time and an old attempt to fix it was reverted for causing regressions. For now, we imported the CSSOM View tests and just marked the one related to the scrolling element as failing. An analysis of the situation has been left on WebKit’s bug; Depending on how things evolve on Chromium’s side we could consider the discussion and implementation work in WebKit.
Related to that work, a new API is being proposed to set the root scroller to an arbitrary scrolling element, giving more flexibility to authors of Web applications. Today, this is unfortunately not possible without losing some of the special features of the main view (e.g. on iOS, Safari’s URL bar is hidden when scrolling the main view to maximize the screen space). Such API is currently being experimented in Chromium and we plan to investigate whether this can be implemented in WebKit too.
Conclusion
In the past months, The AMP Project and Igalia have worked on analyzing some interoperability issue and fixing them in WebKit. Many improvements for frame sandboxing are going to be available soon. Significant progress has also been made for frame scrolling on iOS and collaboration continues with Apple reviewers to ensure that the work will be integrated in future versions of WebKit. Improvements to “root scrolling” are also being considered although they are pending on the evolution of the issues on Chromium’s side. All these efforts are expected to be useful for WebKit users and the Web platform in general.
Last but not least, I would like to thank Apple engineers Simon Fraser, Chris Dumez, and Youenn Fablet for their reviews and help, as well as Google and the AMP team for supporting that project.