Surfin' Safari

Introducing the Rendering Frames Timeline

Posted by Matt Baker on Tuesday, September 8th, 2015 at 7:00 am

Being able to measure performance and identify bottlenecks is essential for web developers of modern web applications. Delivering a smooth user experience can be a challenge as complex scripts, dependencies on third-party JavaScript frameworks, and high performance animations become the norm. Most of today’s devices have displays which refresh at 60 Hz, which means our apps need to update at a consistent 60 frames per second.

Web Inspector includes a new tool to help locate and resolve rendering performance issues. In addition to the familiar event-based timelines, the Timelines tab now includes a Rendering Frames mode, a new timeline presenting a frame-based, task-specific view of existing profiler/instrumentation data.

Web Inspector Rendering Frames Timeline

The Event Loop

Each frame in the timeline represents a single cycle of the browser’s event processing loop, broken down into the tasks that executed during that time. During a single cycle of the event loop, the browser must:

  1. Handle events and timers
  2. Run JavaScript
  3. Perform style calculations and layout
  4. Paint and composite the page

If one or more of these tasks takes an excessive amount of time, frame rates can drop and page performance can suffer. To maintain a consistent 60 FPS frame rate, the time budget for each frame is about 16.667 ms, or 1 / 60 of a second.


Frame data is displayed in three panels: an overview graphrecords table, and summary chart. The graph panel (at the top of the view) shows a high-level overview of rendering times and long-running tasks. The records table (bottom half) shows details about what happened in every single frame, and the summary chart gives the aggregate time for the frame records currently selected in the timeline.

The Rendering Frames timeline plots discrete frames along the x-axis; each frame’s height corresponds to how long it took to render (lower is better). This is in contrast to other timelines in the Web Inspector, which plot events over time on the x-axis and don’t have a y-axis.

Rendering Frames Overview Graph

When there’s no script activity or layout changes to render, WebKit’s event loop remains idle. By default, the Rendering Frames timeline automatically hides these idle periods so you can easily compare periods of activity.

The above example shows a profile taken from a page that performed some work once per second with a call to setInterval. According to the Start Time column, each frame is about one second apart, and the interval between frames – idle time – is not shown.

Identifying Performance Problems

When web content has poor rendering performance, this shows up as tall bars in the timeline or frames with large values in the Total Time table column. Filters, which are discussed in the next section, can also aid in highlighting just those frames which consistently fail to meet our 60 FPS goal. The next step is determining what script or layouts are eating up our frame budget:

Filtering Data

When rendering up to 60 frames per second, complex web content can quickly generate large recordings that obscure infrequent, but annoying, frame drops. The Rendering Frames timeline adds two new filter types to help reduce the noise: a task filter and frame duration filter. Both filter options are located in the Timelines sidebar panel.

As with all timeline views, only the range of data selected in the timeline overview graph is shown in the records table.

Frame Filtering Options

All selected frames are shown in the overview graph and data grid by default. In the example below, this includes many frames well within the ~16 ms budget, and quite a few that are little more than a blip on the graph:

Unfiltered Frame Data

Duration filters are useful for hiding frames that don’t take a significant time to render. Below is the same data after filtering out frames which took less than 15 ms to render:

Filtered Frame Data

Filtered frames are de-emphasized in the overview graph, and hidden in the timeline records table. Applying filters reduces the amount of profile data which must be investigated. Data can be filtered even further by applying task filters:

Frame Task Filters

Final Thoughts

Knowing how your site spends its time is crucial for delivering smoothly rendered content and a snappy user experience. The Rendering Frames timeline is a great tool for identifying performance issues, and this is just the beginning. There are many enhancements and features planned for this and other areas of the Web Inspector, and we’re interested in hearing your feedback.

The Rendering Frames timeline is available to use in the Safari 9 beta, and the latest features and enhancements can be found in WebKit Nightly Builds. Help us make Web Inspector even better by sending us feedback on Twitter (@xeenon, @jonathandavis, @matt_a_baker), or by filing a bug.

Styles Sidebar Refinements in Web Inspector

Posted by Devin Rousso on Monday, August 31st, 2015 at 7:00 am

For many users, the most important part of a webpage is how it looks. If a website doesn’t appeal to our senses we quickly move on. This is why design is so critical when making and debugging websites. It’s why we worked hard to improve the interface with thoughtful enhancements to help developers work more efficiently and effectively when they are designing webpages using Web Inspector.

Finding the Right Style

Due to the cascading nature of CSS, it can often be difficult to find styles in the Styles sidebar. You can always look at the Computed panel to see the list of styles that are applied to the selected element, but that won’t help you find the source of that style. To resolve this, we have added go-to arrows that will appear next to computed styles. When clicked you are switched to the Rules panel and the corresponding style is highlighted.

Computed to rules

If instead you want to see the list of declarations for a particular property or value, we have added a filter bar to the bottom of the Styles sidebar that will hide any style that does not match the filter term. As an example, you could filter for “background-color” and only the section that contain a “background-color” property will remain visible.

Filter bar

As an added bonus, the filter will also search selectors in the Rules panel, so you can search for a style specified by a particular selector, such as “#title h1”.

Filter selector

Intelligent Editing

There are over 200 different CSS properties, not even counting the prefixed ones, that developers have to take into account when designing websites and that can often get overwhelming. Did I remember to add all the prefixes for this property? Does this value exist for this property? Does this property even exist? These are all common questions that arise when editing CSS. In order to assist in answering these questions, Web Inspector now displays warning icons whenever a property has something wrong with it, whether that be from missing a prefix to not even existing. Simply hover over the icon to find out exactly what is wrong with the property and brief explanation of how to fix it.

Warning icon

In some cases, the fix is simple enough you can click on the warning icon and Web Inspector will automatically fix the issue, either by replacing or removing the problematic text.

Tabbing in the Styles sidebar has been functionally redesigned to provide an easier way to switch what text, whether that be a property name or value, is currently selected. In addition, Shift-Tabbing will perform the exact same selection change except in reverse, selecting the previous match. Once the last section of text is selected, tabbing again will highlight the next rule’s selector and start the entire process over in the next rule. This process wraps around from the last style to the first, allowing the user to completely tab through every editable property name and value in the Styles sidebar. While tabbing, any missing semicolons will also automatically be added for you.

Match Preview

Even if you know exactly what styles you want to apply to a particular element, finding the right selector to use for that style can be challenging. Previously, there was no way to tell if a selector would apply to an element other than actually creating a test style so that you could visually confirm the changes. Now, simply hovering over a selector will cause all matching nodes on the page to be highlighted.

Selector hovering

In addition, editing selectors will also apply highlights to all nodes matching the current text in the selector area. This way, you can preview what nodes will receive the styling without actually committing the selector.


What do you think? These enhancements are available in the WebKit Nightly Builds. Is there a feature you’d like to see added to the Styles sidebar? Let us know! Please feel free to submit any feedback through Twitter (@xeenon, @jonathandavis) or by filing a bug.

Type Profiling and Code Coverage Profiling for JavaScript

Posted by Saam Barati on Monday, August 24th, 2015 at 7:00 am

Web Inspector now has two great tools designed to make debugging JavaScript programs easier: the Code Coverage Profiler and the Type Profiler. The Code Coverage Profiler visually displays exactly which sections of your JavaScript program have executed. The Type Profiler visually annotates important variables with the collected set of type information for those variables. Both tools make understanding and debugging your JavaScript programs in Web Inspector better than ever.

Code Coverage Profiling

Understanding how a program works can be complex and complicated. Being able to grasp how a program works requires understanding which parts of the program execute based on a set of input data. In web applications, we’re often interested in knowing which parts of a program execute based on a user’s interaction with the web page. Web Inspector now has a way to show you exactly which parts of your program have run, and which haven’t.

Code Coverage Profiler

Knowing exactly which functions have executed, and even which if-then branches inside those functions have executed, is imperative in understanding the inner workings of your program’s control flow. Knowing when something has or has not executed gives you important insight into finding bugs and understanding how a program’s different pieces fit together. Web Inspector’s Code Coverage Profiler will tell you which portions of your program have executed up to basic block granularity.

One subtle detail you can see while watching this video is that the comments describing left and right are swapped. The Code Coverage Profiler a great tool for finding subtle mistakes in your program and your program’s documentation.

Type Profiling

One of the coolest new features in Web Inspector is the JavaScript Type Profiler. All values in JavaScript have a type, but, just by reading a JavaScript file or function, it’s hard to know what type everything will be. We created the Type Profiler because we no longer want to read JavaScript programs without knowing what type important variables have.

JavaScript is a dynamically typed language. This means that JavaScript programs are written without any type declarations. Any variable in any JavaScript program can have any type; expressions can have any type; any function can return any type; the list goes on. For example, the following is a legal JavaScript program:

let x = 20;
x = ["Hello", "World", "I am the Type Profiler"];
const identity = (x) => { return x; };
identity([1, 2, 3]);
identity("Saam Barati");

This is in contrast to a statically typed language like Swift or Haskell. Statically typed languages prevent you from mixing mismatched types. The compiler won’t let you run a program that fails its type checker. JavaScript doesn’t have this restriction. As long as a program doesn’t have a syntax error, it will run. For example, in a statically typed language, you can’t assign a number to a variable of type Array. JavaScript has no such restrictions.

This trivial example of a type mismatch seems easy enough to prevent. However, as your JavaScript program grows larger and the number of classes increases, it becomes untenable to keep track of all possible types and prevent their misuse. Even though JavaScript is not statically typed, variables in a JavaScript program are often intended to only have a particular type (this tendency towards monomorphism is a big part of why JIT compilers are successful at optimizing JavaScript programs). For example, consider this program:

function add(a, b) {
    return a + b;

The above function may have been written with the intention that a and b are both numbers. When a and b are both numbers, this function will behave as expected. For example:

add(10, 20) === 30

However, it begins to do weird things if it’s called with parameters that have an unintended type. For example:

add(10, "20") === "1020"
add([1, 2], undefined) === "[1,2]undefined"

The resulting values are weird and unexpected, but also totally valid according to the JavaScript spec. Even though the intention of the programmer is that the add function is to only be called with numbers, there is no easy and straightforward way to enforce this. Because type safety is hard to enforce, JavaScript programs often have bugs due to unexpected types leaking into places they were not meant to be. Web Inspector’s Type Profiler makes finding and debugging such problems easier than ever.

Sometimes type-related bugs are easy to diagnose, a mismatched type may throw a runtime TypeError. At other times, though, such problems won’t throw a TypeError and will result in subtle bugs that are difficult to diagnose and may only arise in rare situations.

Web Inspector’s Type Profiler helps you to find these subtle bugs. When using Web Inspector with the Type Profiler enabled, your JavaScript program will have type annotations next to important variables and function return types.

This allows you to visually inspect types in your JavaScript program without needing to insert console.log statements or pause in the debugger. console.log and the debugger are important debugging tools, but the Type Profiler is great for finding type-related bugs retroactively. The Type Profiler does what its name indicates: it profiles the types of values in your program. This means the information it shows you is only ever the types that have flowed through your program. It neither shows you the types something could be (which could be anything in JavaScript, this wouldn’t be helpful) nor does it infer what type something would be based on the information it already has.

The Type Profiler updates in real time. As new information is introduced in your program, the Type Profiler updates its annotations. As you see in the video, when the announceAnimal function is called a second time, the type displayed for the animal parameter updates from Dog to Animal. This happens because the Type Profiler tracks the inheritance chain for values it profiles. The Type Profiler also shows other aggregate types in an intelligent way. As you use the Type Profiler, you’ll see that the types it shows you are both intuitive and helpful.

The Type Profiler is also a great form of documentation. Using the Type Profiler will help you familiarize yourself with new code. Seeing type annotations while reading unfamiliar code makes understanding that code much easier. It also helps you better understand how your own code behaves.

You can see in the above image that the Type Profiler will display type names as String? and Number?. The Type Profiler is great at displaying optional parameters. Any <type> mixed with Undefined or Null will show up as <type>?. The Type Profiler will also recognize when a cohesive type name would be deceptive. This may happen when many unrelated types are assigned to the same variable, passed as an argument, or returned from a function. In this situation, the Type Profiler will display the type name as (many). When encountering (many), just hovering the type token will give you information about all the types that constitute that (many).

A Note on Compilation

Implementing a type profiler in a naive way would not be terribly difficult. You could imagine a naive implementation that works by rewriting JavaScript source code using abstract syntax tree transformations and wrapping the necessary language constructs with the required type profiling code. However, such an implementation would suffer from being unusably slow. It would probably incur a 20x slowdown if not more. When developing the Type Profiler, we set out to optimize its overhead as much as possible. On average, the Type Profiler will slow down JavaScript code by 2x.

This means that the Type Profiler is deeply integrated with JavaScriptCore’s JIT compilation infrastructure. Here is a quick overview of how JavaScriptCore’s (JSC) compiler pipeline works.

JSC first parses JavaScript text into an abstract syntax tree (AST). From this AST, JSC generates bytecode. This bytecode is a way of representing JavaScript operations at a lower level. The bytecode is not at such a low level that the original program is difficult to decipher from the bytecode. JSC will then interpret this bytecode and collect profiling information inside JSC’s Low Level Interpreter (LLInt). If a particular function has executed enough times, JSC will do a straightforward compilation of this bytecode into machine code inside JSC’s Baseline JIT. If this function continues to execute many times, JSC will compile it using its optimizing DFG and FTL JITs.

Let’s walk through an example of a JavaScript function and look at its corresponding bytecode representation:

    function add(a, b) { 
        return a + b 

JSC bytecode:

    function add: 10 m_instructions; 3 parameter(s); 1 variable(s)
    [ 0] enter             
    [ 1] get_scope       loc0
    [ 3] add             loc1, arg1, arg2
    [ 8] ret             loc1

This bytecode is straightforward. It says add the two parameters of the function together, store them in loc1, then return loc1. To see how the Type Profiler changes JSC’s bytecode, let’s see the same function’s bytecode with the Type Profiler enabled. (Comments added inline to describe what the Type Profiler is doing.)

    function add: 40 m_instructions; 3 parameter(s); 1 variable(s)
    [ 0] enter             
    [ 1] get_scope         loc0

    // Profile the parameters upon entering the function.
    [ 3] op_profile_type   arg1 
    [ 9] op_profile_type   arg2

    // Profile the operands to the add expression.
    [15] op_profile_type   arg1
    [21] op_profile_type   arg2
    [27] add               loc1, arg1, arg2

    // Profile the return statement to gather return type information for this function.
    [32] op_profile_type   loc1
    [38] ret               loc1

Because the type profiling machinery is compiled into JSC’s bytecode, JSC is then able to utilize its multi-tiered compiler infrastructure to optimize the overhead of type profiling. JSC’s DFG JIT is able to successfully optimize JavaScript code in large part due to the tendency that most JavaScript code is written with specific types in mind. Because of this, the DFG JIT can speculatively transform its IR based on gathered profiling information. Then, when executing this speculative code, if it finds that an assumption is broken at runtime, the DFG will OSR exit back into JSC’s baseline JIT. The bytecode operation op_profile_type is a very expensive operation and it appears with high frequency when the Type Profiler is enabled. When we transform this bytecode operation into DFG IR, we’re often able to optimize away the overhead of ProfileType by completely removing it from the executing code. We’re able to do this because by the time we decide to DFG compile the bytecode stream, it is likely that the types which were profiled in the Baseline JIT and the LLInt are the same types that the DFG will speculatively compile against. For example, if an op_profile_type operation has already observed a value with type Integer, it does not need to observe this type again because doing so adds no new information. If the DFG speculates that the type of a node is an Integer, and the type profiling information already associates this node with an Integer, then the DFG will remove the ProfileType node altogether. If this assumption is ever broken at runtime, the DFG will exit into the baseline JIT where this new type information will be recorded. As an example, here is the same add function represented using DFG IR at the time when bytecode IR is translated into DFG IR.

0:  SetArgument(this)
1:  SetArgument(arg1)
2:  SetArgument(arg2)
3:  JSConstant(JS|PureInt, Undefined)
4:  MovHint(@3, loc0)
5:  SetLocal(@3, loc0)
6:  JSConstant(JS|PureInt, Weak:Cell: 0x10f458ca0 Function)
7:  JSConstant(JS|PureInt, Weak:Cell: 0x10f443800 GlobalScopeObject)
8:  MovHint(@7, loc0)
9:  SetLocal(@7, loc0)
10: GetLocal(JS|MustGen|PureInt, arg1)
11: ProfileType(@10)
12: GetLocal(JS|MustGen|PureInt, arg2)
13: ProfileType(@12)
14: ProfileType(@10)
15: ProfileType(@12)
16: ValueAdd(@10, @12, JS|MustGen|PureInt)
17: MovHint(@16, loc1)
18: SetLocal(@16, loc1)
19: ProfileType(@16)
20: Return(@16)

There are still a lot of ProfileType operations in this IR. And these operations have the potential to be really expensive. However, the DFG speculates that the arguments to this function are integers. Because of this, the DFG IR at the time of generating machine code is able to remove all ProfileType operations under the speculative assumption that this function has integer arguments.

1:  SetArgument(arg1)
2:  SetArgument(arg2)
3:  JSConstant(JS|PureInt, Undefined)
4:  MovHint(@3, loc0)
7:  JSConstant(JS|PureInt, Weak:Cell: 0x10f443800 GlobalScopeObject)
8:  MovHint(@7, loc0)
10: GetLocal(@1, arg1)
12: GetLocal(@2, arg2)
16: ArithAdd(Int32:@10, Int32:@12)
17: MovHint(@16, loc1)
20: Return(@16)

Without optimizations like these, it would be unusably slow to use the Type Profiler while debugging your code.

If you’re interested in learning more about the Type Profiler, Code Coverage Profiler, or contributing to JSC or WebKit, please get in touch: @saambarati. There is a lot of interesting work still to be done and I’d be happy to point you in the right direction. You can also get in touch with @jonathandavis with any other questions and comments.

CSS Selectors Inside Selectors: Discover :matches(), :not() and :nth-child()

Posted by Benjamin Poulain on Monday, August 17th, 2015 at 7:00 am

Since last year, WebKit supports many selectors defined in the draft of CSS Selectors Level 4.

In this post, we will see a special subset of new selectors: new pseudo classes that take other selectors as arguments. Those are :matches(), :not(), :nth-child(), and :nth-last-child().

All the selectors discussed in this post work for styling with CSS, and for querying with JavaScript (querySelector[All](), closest(), etc).

Select Properties A or B with :matches()

The pseudo class :matches() takes a list of selector as argument. It matches an element if any of the selector in the list matches.

For example, if we have:

    :matches(.foo, .bar) {
        background-color: green;

Any element with the class “foo”, “bar”, or both will have a green background.

This new selector is used extensively inside WebKit itself to simplify stylesheets. For example, code like the following used to appear in Web Inspector:

    .syntax-highlighted .css-keyword,
    .syntax-highlighted .css-tag {
        color: rgb(170, 13, 145);

With :matches(), scoped selectors do not required any repetition:

    .syntax-highlighted :matches(.css-keyword, .css-tag) {
        color: rgb(170, 13, 145);

Full Selector Support

The argument inside :matches() does not have to be a list of simple selectors. Any selector is supported, including the use of combinators.

For example, the following rule removes the top margin whenever two title levels follow each other:

    :matches(h1+h2, h2+h3, h3+h4, h4+h5) {
        margin-top: 0;

There has been some confusion about the subject of the selector when using combinators with :matches(). For example, take the following rule:

    .icon:matches(:any-link img)

It matches every image with a class “icon” and that has a link among its ancestors. It does not match links containing image elements. In fact, it is strictly equivalent to:

    :any-link img.icon

If you are unsure what element :matches() would be applied to, you can just think of it as if the selector was invoking Element.matches() on the active element.

Deprecated :-webkit-any

WebKit already had a similar but more limited feature, the pseudo class :-webkit-any(). It is now deprecated.

The old :-webkit-any() has problems that cannot be fixed in a backward compatible way. It is best to avoid it. We removed all of its use inside WebKit.

Make sure to carefully test your pages when replacing :-webkit-any() by :matches() as they are not strictly equivalent. In particular, :-webkit-any() does not handle the specificity correctly.

Rejecting Complete Selectors with :not()

The pseudo class :not() has always been very popular in CSS. It selects an element if the element does not match a simple selector. For example:

    a:not(.internal) {
        color: red;

What is new is the argument you can pass to :not(). Just like :matches(), the new :not() supports any selector list as arguments.

Code that used to chain multiple :not() can now use comma separated selectors to achieve the same. For example:


Can also be written as:

    :not(i, em)

You can use complex selectors, which means you can exclude multiple properties. For example, it is now possible to select any element that does not have simultaneously the classes “.important” and “.dialog”:


And you can use combinators which is becoming one of my favorite features. For example, it is possible to select any image that is not part of a link:

    img:not(:any-link *)

Counting Elements with :nth-child() and :nth-last-child()

The pseudo class :nth-child() and :nth-last-child() are also being augmented with similar capabilities.

Previously, it was only possible to count elements indistinguishably with :nth-child(), or count elements having the same qualified name with :nth-of-type(). With the extended :nth-child(), you can specify what kind of element to count.

For example, if you want to select every other element that is not hidden, you can now do:

    :nth-child(odd of :not([hidden]))

The general syntax is of the form:

    :nth-child(An+B of selector list)

As for the previous selectors, the argument has full support for selectors, including complex selectors, combinators, etc.

Dynamic Specificity

Nesting selectors inside other selectors creates a new interesting question: what is the resulting specificity?

The answer is: it depends. The specificity varies with what is being matched. The final specificity is the largest of all the sub-selectors.

Let’s take a concrete example. If we take the following selector:

    :matches(div, #foo, .bar)

What is the specificity?

When the following element is selected:

    <div id="foo"></div>

The specificity is (1, 0, 0), the same as if we only had the selector “#foo”. The reason is for all the selector that match (“div” and “#foo”), the highest specificity of the two is the one of #foo.

This definition is great because it’s compatible with existing selectors. You can take any selector, wrap it inside a :matches(), and its specificity works exactly the same.

With complicated selectors, finding the specificity is not always this easy. Fortunately, the Web Inspector is there to help us.

In the Inspector, when hovering a selector in the “Rules” panel, the tooltip shows the real specificity of the selector for the current element. If the specificity varies with the element like in the example above, the Inspector also indicates that the specificity is dynamic:

The Web Inspector's tooltip show the dynamic specificity of any selector.

Final Word

There are many new possibilities created by the new selectors. Play with them, invent new combinations, and tell us what you think.

For short questions, you can contact me and Yusuke on Twitter. For longer questions, you can email webkit-help or file bug report.

Introducing Backdrop Filters

Posted by Brent Fulgham on Monday, August 10th, 2015 at 7:00 am

Our recent blog posts have focused on important performance and developer features added to WebKit. But WebKit is about more than just great developer tools; we also build features for authoring amazing web content.

In this post I’m excited to share a great new feature that designers have been demanding for some time: backdrop filters. Let’s start with a few words explaining why this feature is important, then we can delve into how you can start using it. If you are running a recent nightly build of WebKit, you can try out the example yourself!


The User Interface design language for iOS 7 and OS X Yosemite changed to incorporate some beautiful backdrop blur effects. This layering gives a sense of depth, while preventing detail from the content underneath from cluttering the foreground.

The following image shows the WebKit media controls on top of a video. Notice that you can see some of the background content through the frosted glass effect.

View of the media controls in the context of video playback.
It’s even easier to see in this close up, which was captured using a more vivid source video.

Detail view of the media controls, showing frosted glass backdrop effect.
We wanted the WebKit media controls to have this visual style on relevant platforms. However, the controls are implemented in HTML, CSS, and JavaScript.

Designers want to use these kind of beautiful effects in their web designs, but have been unable to because these effects were only available to native applications. This prevented embedded web views from looking as good as native controls. It also prevented these kinds of effects from being used for authoring websites.

Until recently, there was no standards-compliant method for producing these kinds of effects. Many designers were forced to create the illusion of blurred backdrops using pre-rendered background content and carefully clipping and positioning these assets to achieve the desired effect.

Unfortunately, as with most illusions, this approach doesn’t hold up to close scrutiny.

  • New artwork must be generated any time the background image or blur characteristics are changed.
  • The careful alignment and clipping required to maintain this illusion can lead to pixel cracks and other display gitches.
  • Different blurred images are needed for each targeted display resolution.
  • Dynamic layouts or animations require complicated and potentially costly calculations as the user interacts with the page.

In short, instead of focusing on their site design, developers were forced to take heroic measures to achieve the desired effect.

Backdrop Filter

We saw so many instances of these Sisyphean techniques being used that we created a new CSS style and proposed it as part of the CSS Filter Effects Module Level 2.

The backdrop-filter style allows us to style elements with backdrop effects that resemble those in iOS and OS X that motivated this discussion.

This new style allows the browser engine to do the complicated calculations and positioning needed to achieve this effect.

  1. WebKit starts with the content behind the styled element. Note that this is not the background of the element, but rather the content that would be drawn behind the element.
  2. WebKit then applies the blur effect to the content.
  3. Finally, the backdrop is composited with the other elements on the page to yield the final result.

Example: backdrop-filter: blur(10px);

Backdrop Filter Example
Since these blur operations are being done in the browser engine, we can take advantage of hardware support, resulting in very efficient operations. However, be warned! The nature of this backdrop effect forces the engine to perform more rendering passes, which will have an impact on performance. Make sure you only use this feature where it is most necessary.

We wanted to give developers the freedom to use all kinds of filters in their designs, so backdrop-filter supports the full range of effects provided by our CSS Filters implementation. This means we can do all kinds of exciting things with our backdrops:

Example: backdrop-filter: invert();

Inverted Color Backdrop Example

And we can combine multiple filters:

Example: backdrop-filter: blur(10px) grayscale(100%);

Mixed Filters on Backdrop Example
And best of all, this effect is completely dynamic — it can be used on top of HTML5 media, CSS animations, WebGL, and other dynamic content.

This is an amazing advancement of what you can do with your designs. Prior to this style, you could not achieve this kind of effect.


WebKit proposed this feature to the CSS Working Group last year, and it is currently in the Editor’s Draft of the CSS Filters Level 2 specification. We are currently prefixing this property to comply with the W3C requirements for features that have not completed the standardization process. Consequently, you will need to write -webkit-backdrop-filter when using it in your own CSS.


We hope you enjoy this new feature, and share your creations with us! If you find bugs, please report them at

As always, if you have suggestions or feedback on this (or other) Filter Effects, please share them on the mailing list.

For short questions, you can contact me or Dean Jackson on Twitter. For longer questions, you can email webkit-help.


The images and video of the International Space Station were obtained from NASA’s website, and are used under the Public Domain.