Frameworks • iOS, macOS, tvOS • 28:08
The Web Inspector makes introspection and debugging simpler than ever. Discover how you can use debugger stepping, editing cookies, and overriding network loaded resources to provide you with powerful development capabilities and help you create faster, more efficient websites.
Speaker: Devin Rousso
Downloads from Apple
Transcript
Hello and welcome to WWDC. Hi, my name is Devin, and I'm thrilled to be able to show you some of the many improvements and new features added to Web Inspector in the last year. To start, let's enable Web Inspector in Safari. First, click on the Safari menu bar item and select "Preferences" to show Safari's Preferences. Go to the Advanced tab and enable "Show Develop menu in menu bar". In the now-visible Develop menu, click "Show Web Inspector".
And there it is. Web Inspector is your one-stop shop for all introspection and debugging tools for Web content, whether that be in Safari, a WKWebView in an app or even a JSContext. Before we dive into individual features, let's take a look at some of the changes that have been made to the overall interface.
We've merged the toolbar and dashboard into the tab bar to save on vertical space. Additionally, we've tightened the spacing around many controls throughout the interface, allowing you to see more content in Web Inspector without having to sacrifice on visibility of content in the inspected page. These changes also are not limited to when docked to the bottom as they also apply when docked to the side, and even when undocked into a separate window.
We've also improved accessibility support for navigating around Web Inspector when using a screen reader. For those of you that prefer Dark Mode, we've created Dark Mode variants for all of the icons throughout Web Inspector, as well as added a way to toggle Dark Mode independently from the rest of the system in the Settings tab.
Because there's no longer a dashboard, the Network tab now shows the inspected page statistics below the main table, and has been expanded to include the number of unique domains, the total transfer size, and a count of all redirects. This year, we've added new features to almost every tab, as well as adding a few entirely new tabs. So, let's go through each, starting with the new Sources tab.
For those of you who've used Web Inspector in the past, the Sources tab takes all of the best features of the Resources tab and the Debugger tab, and combines them into a single tab. The Sources tab lists all resources loaded by the inspected page since Web Inspector was opened, including long-lived things like WebSockets, or more ephemeral things like XHRs or Fetches.
The Sources tab is also the primary place for JavaScript debugging, containing all of the JavaScript debugger stepping controls and many of the breakpoint-adding capabilities provided by Web Inspector. And new this year, the Sources tab is home to the network-overriding capabilities provided by Web Inspector, which we'll see in a minute. So, let's take a look.
When viewing resources in the Sources tab, Web Inspector will provide alternate representations for applicable content, giving you another way to explore and examine the content to understand what's going on, or fix any issues. Right now, we're looking at the response content of this resource, which we can see is HTML text.
In this case, the head is filled with a lot of meta and link tags, meaning that we'd have to scroll down to see any of the content of the body. Additionally, it's harder to see where tags begin and end when they're all scrunched up like this in the text.
It would be a lot easier to navigate if we could visualize this response content as a DOM tree instead, just like in the Elements tab. Clicking on the Response breadcrumb item will show a drop-down with all of the alternate representations Web Inspector provides for this resource. Selecting the response "DOM Tree" will visualize the response content as a DOM tree, just like we wanted. Similar alternate representations are also provided for when the content is valid JSON, showing an object tree as if it were logged in the Console. These alternate representations are also available when content is sent as part of a request.
In situations that you do find an issue in the resource's response, it's not always easy to make a change to the source code that the resource was loaded from as it may exist on a faraway server, or involve complicated preprocessing steps. I am very excited to tell you that this year Web Inspector has added a new feature to solve this exact problem. We call them "local overrides".
Creating one is as simple as clicking the "Create Override" button when viewing any resource, which will automatically copy the current contents of that resource into a new local override for that resource. They are fully editable and are preserved by Web Inspector across sessions of both the inspected page and Web Inspector itself. So, let's try making a simple change to see a local override in action.
Scrolling down in the content area, we can see the WebKit text in the header. We can change this WebKit text to something like "Hello WWDC" instead. If we take a look at the inspected page and reload, we can see that the WebKit text in the header of the inspected page changes to match the edits we just made.
Even better, local overrides support more than just modifying the response's content. If you right-click on the local override in Web Inspector and select the "Edit Local Override" action, it's also possible to modify the HTTP status code or any HTTP headers. When the inspected page loads anything over the network that matches any local override's URL, the entire response, including the HTTP status code and any HTTP headers, will be completely replaced by the local override as it's been configured. Whenever this happens, the icon of the overridden resource will change, indicating that it was replaced. And this overridden icon is shown anywhere that the resource can be seen throughout Web Inspector.
As further proof, if we select the overridden resource and scroll down, the same modifications we made to the content are also present. All resources, including the main resource like we just saw, and even non-text resources like images, can be overridden and fully configured from within Web Inspector. Sometimes, however, it's not necessarily a particular resource that you wanna change, but really a JavaScript API provided by WebKit. In these cases, you'll want to use the new Inspector Bootstrap Script, which can be added from the "Add Resource" button in the bottom corner of the navigation sidebar in the Sources tab by selecting the "Inspector Bootstrap Script" action.
Similar to how local overrides allow you to modify resources loaded over the network, the Inspector Bootstrap Script can be thought of as a way to modify the JavaScript API surface itself. The contents of the Inspector Bootstrap Script are guaranteed to run before anything else in the inspected page, hence the "bootstrap" name.
This allows you to do things like swizzle built-in functions to see where they get called, or set up some global state that causes your code to log additional debugging information. Just like local overrides, the Inspector Bootstrap Script is preserved by Web Inspector across sessions of both the inspected page and Web Inspector itself. Speaking of debugging JavaScript, we've also added a number of new global breakpoints.
The Debugger Statements breakpoint controls whether or not JavaScript execution is paused at debugger statements. Previously, the only way to control this was to completely disable all breakpoints. We felt that this was unnecessarily restrictive, so we created this separate control to allow you to use other types of breakpoints while simultaneously disabling debugger statements specifically. The other new breakpoints, since they relate to specific APIs, must be enabled by clicking on the "Add Breakpoint" button, which will show a menu with actions that each enable one of these new different breakpoints. So, let's go through each of them one by one.
The All Microtasks breakpoint will pause JavaScript execution whenever any microtask is about to be executed, such as from Promise objects or the queueMicrotask global function. Along these lines, the All Animation Frames breakpoint does the same for requestAnimationFrame callbacks, the All Timeouts breakpoint does the same for setTimeout callbacks, and the All Intervals breakpoint does the same for setInterval callbacks.
Lastly, the All Events breakpoint will pause before any callback is invoked for any eventListener, even those unrelated to the DOM. But sometimes these global breakpoints are almost too global in the sense that they pause more often than desired. Let's take a look at an example using the All Events breakpoint.
I have a simple page that uses jQuery to add an EventListener to a button element. Because I've enabled the All Events breakpoint, I would expect to pause inside this EventListener. But in reality, jQuery wraps my EventListener callback with its own logic, so instead we pause in jQuery's EventListener wrapper, which is not really ideal as none of this is my code. Really, what I want to do is have this breakpoint pause in my code after all of jQuery's code has run.
Script blackboxing is a new feature that allows you to do just that, instructing the JavaScript debugger to defer any pause that would occur in a script to pause at the first expression to execute outside of that script. We can do this to jQuery by hovering over the jQuery resource in the navigation sidebar and clicking on the button that appears. Now, if we trigger this breakpoint again, we should pause in our EventListener instead.
If, for some reason, you still do want to dig into the library or framework code, Web Inspector will still show any call frames from blackboxed script, graying them out to indicate their blackbox nature. For larger libraries and frameworks, it's also possible to blackbox multiple scripts at once by configuring regular expression patterns to match against the script's URL in the Settings tab.
In addition to some of the global breakpoints we've seen so far, Web Inspector also allows you to set breakpoints on specific lines of JavaScript, even after pretty-printing. Clicking on any line in the gutter area of the content preview will add a JavaScript breakpoint that will pause right before that line executes. Because JavaScript is so flexible with its syntax, however, it's not uncommon to see lots of operations combined onto a single line, using a mix of various operators. In this case, the logical "or" operator.
Previously, if I wanted to pause inside the "c" function call and I didn't know where "c" was declared to set a breakpoint, the only way to do this would be to use the Step In JavaScript debugger action to step in to "a" and then step out to move past "a", then step in again to go inside "b" and finally, step back out again to arrive at "c". This is a very tedious process with lots of opportunity for mistakes, which can be incredibly frustrating, especially in cases where the starting state is hard to reach. This has led us to introduce a new JavaScript debugger action which we call "Step".
Unlike the Step Over JavaScript debugger action, which resumes and re-pauses execution before the next statement in the current call frame, which, in this case, would be after the entire foo variable declaration, this new Step debugger action will resume and re-pause JavaScript execution before the next expression in the current call frame. And just like that, we've resumed, executed "a" and re-paused on the same line without ever having to step in or step out. Once more, we can Step to resume, execute "b", and re-pause, arriving at the call for "c".
The Step JavaScript debugger action provides more fine-grained control over how the JavaScript debugger execution head moves through the program, allowing for fewer stepping mistakes, and even less actions in total. But sometimes scripts are inlined into HTML. In the past, there was no way to pretty-print this inlined script content, or even any of the surrounding HTML. As a result, it was often difficult to set breakpoints, or even step through code in a nicely readable way.
I'm very happy to say that in the latest Safari, Web Inspector now supports formatting HTML and other XML-like content, including inline scripts and style sheets, allowing support for all of the existing JavaScript debugger and stepping capabilities too. Simply click on the "Pretty-Print" button in the top right of any text-based content preview to toggle between the original content and the pretty-printed version. Web Inspector will also attempt to detect when HTML has been minified and automatically toggle pretty-printing, just like it has always done for JavaScript and CSS content.
So, speaking of Web Inspector functionality, there's so much more than just viewing resources. So, let's move on to some of the other powerful or more specialized features, starting with the Timelines tab. The Timelines tab contains the majority of performance-profiling tools offered by Web Inspector. It captures activity in the inspected page during what we call "a recording", which organizes and plots the data into one of a handful of different timelines based on the type of data. Timeline recordings can also be exported and imported, allowing you to share them with others, or save them for later use.
The main change this year in the Timelines tab is an introduction of an entirely new timeline, which you can activate by clicking the "Edit" button in the main graph area. This will put us in Edit mode, where we can enable or disable any of the various timelines. The new timeline we've added this year is the Media and Animations timeline, which captures events related to media elements, like video or audio, and also captures the life cycle of CSS animations and CSS transitions.
After checking the checkbox to enable the Media and Animations timeline, and clicking the "Done" button to leave Edit mode, if we reload the inspected page, we can see a list of all media elements, CSS animations and CSS transitions that have had any activity during the recorded interval. Each individual media element, CSS animation or CSS transition has its own row in the table, with links to any related DOM node, or information about the CSS animation name, or CSS transition property.
The Media and Animations timeline can also be used to correlate activity captured in other timelines with state changes in any media elements, such as a video being paused, or the creation or destruction of any CSS animations or CSS transitions, to figure out what's going on and understand why.
And that's a quick look at the Media and Animations timeline in the Timelines tab. Let's move on to the Storage tab. So, the Storage tab contains a list of all of the data stored by the inspected page in the browser, via things like cookies or localStorage or IndexedDB. This year, we've added filtering capabilities for all storage types, and improved editing and deleting capabilities, especially for cookies.
The new filtering capabilities take the form of an always-visible filter bar shown at the top of most storage views. As an example, filtering cookies is as simple as typing some text into the filter bar, which is compared against the various fields of each cookie, hiding any rows that had no matches.
The second major feature is the ability to edit cookies. Double-clicking on one of a cookie's cells will show a popover with inputs for each field of that cookie. All modifications made in this popover will be applied as soon as the popover is dismissed. It is also possible to add entirely new cookies by clicking on the "Add Cookie" button, which will show a similar popover, but this time all of the inputs will be empty. Note that a name is required in order for a cookie to be added. Just like HTTP cookies or those set by document.cookie, they will persist beyond Web Inspector, so be careful what you add.
But many other features of the Web are not persistent, so let's take a look at tooling for some of those, starting with the all-new Graphics tab. For those of you who've used Web Inspector in the past few years, you may remember using the Canvas tab. The Graphics tab expands the scope and functionality of the Canvas tab, still showing all canvas contexts and shaders, and supporting taking recordings of JavaScript API calls made with canvas contexts, but now also listing all Web Animations, including those created by CSS animations and CSS transitions, providing in-depth information about each.
The previously available information about all canvas contexts in the inspected page is still shown in the Canvases section at the top, followed by the new sections for the various types of animations created by the Web Animations API, separated by how they're created, such as from JavaScript, CSS animations or CSS transitions.
Clicking on one of these animations will expand the Details sidebar, which contains all of the information about the selected animation, such as the ID, which can be thought of as the animation's name, the associated node of the animation, the configured timing parameters, like the number of iterations or the total duration, the keyframes list, each item containing a list of CSS properties and values that are interpolated between as the animation progresses, and finally, for animations that are created by the JavaScript API, a backtrace for how the animation was created.
Clicking the "markup" icon in the header of any animation will show a context menu with a number of utility actions, such as "Log Animation", which will save the JavaScript object for that Web Animation in a temporary variable in the Console, in this case "dollar sign one", allowing us to have easy access for manipulating it using the powerful Web Animations API.
For more information about the new Web Animations API, be sure to check out the "What's New for Web Developers" session in your Developer App. But sometimes animations are configured incorrectly, which causes performance problems. So, let's take a look at the new Layers tab to see how it can help. The Layers tab shows a live view of the layer tree of the inspected page, listing the memory cost and the paint count for each layer.
While it's not exactly one-to-one, it's generally a good idea to keep the number of unintended layers as low as possible, as a greater number of layers can be associated with increased memory use and worse performance. The key word there, though, is "unintended," which really requires you, the developer, to know your layout and when things should and should not create a layer.
To assist with this, clicking on a layer, either in the visualization itself or in the Details sidebar, will show information about that layer, such as why it was created. In the case of the currently selected layer, it was created because the associated node was being animated by CSS and had a CSS willChange property.
The entire layer tree visualization can also be manipulated using common orbit controls, such as rotating vertically by clicking and dragging up and down... rotating horizontally by clicking and dragging left and right... zooming by scrolling in and out... and panning by right-clicking and dragging, making it easy to find layers visually to find out more about them.
So, now let's move on to our final tab, the Console. The Console tab lists all of the various messages logged by the inspected page, and allows arbitrary JavaScript evaluation in the inspected page. Special functionality is also exposed while evaluating JavaScript, intended to make inspecting and debugging JavaScript easier by providing more information previously known only to WebKit.
There are a whole bunch of these Console-only, specially-exposed bits of functionality that are new this year, but there are two that are particularly noteworthy. The first is called QueryInstances. So, let's imagine a simple JavaScript class hierarchy of an Animal base class with a Pet subclass. Now, let's say that we want to find all instances of this Pet subclass. One way to do it would be to examine a captured heap snapshot in the Timelines tab, but that includes a lot of other information that is likely not relevant.
So instead, QueryInstances, when given a constructor, will scan the entire JavaScript heap for instances of objects that inherit from that given constructor, returning them all in an array that can be further manipulated in the Console. As we can see here, there is one instance of this Pet class, which is held in the variable with the name "Buddy".
Note, however, that due to the nature of JavaScript inheritance, the term "instance" also applies to subclass objects as well. As an example, if we call QueryInstances with the Animal constructor, we will not only see the previously seen "Buddy" variable, but we will also see an instance of Animal held by the "cat" variable, and even the entire Pet class, as it also inherits from the Animal class. For more flexibility, it's also possible to provide a prototype instead of a constructor, which will have the exact same result we saw earlier with the related constructor.
The second noteworthy function is called queryHolders. So, again, let's imagine a simple JavaScript class called Person. Similar to reality, each Person can have a Parent. This is represented in JavaScript as a Parent property, whose value is the other Person object. This is called a strong reference and is a common cause of memory leaks in JavaScript programs. If we wanted to find all objects that have a strong reference to a given object, we could examine a captured heap snapshot in the Timelines tab, but again, this may include other information that is likely not relevant.
Instead, we can use the queryHolders Console function, which, when given an object, will scan the entire JavaScript heap for all other objects that have a strong reference to that given object. As we can see in this example, Alice has a strong reference to John through its Parent property, which is why Alice appears in the array returned by queryHolders.
Now let's talk about our last feature for today. I'm very happy to announce that in the latest Safari, so long as Web Inspector is already open, enabling "Intelligent Tracking Prevention Debug Mode" from Safari's Develop menu will cause all debug logging from Safari's Intelligent Tracking Prevention to also appear in the Web Inspector Console, as well as in the macOS system Console.app.
Additionally, logs for Ad Click Attribution Debug Mode will also be shown in the Web Inspector Console, which can be enabled from the Experimental Features submenu of Safari's Develop menu. This way, you'll never have to switch away from Safari to see all of the logs you might find useful. And with that, we've reached the end of our feature highlights. There is so much more coming in the latest Safari, including a ton of bug fixes and minor tweaks throughout Web Inspector. Before we finish up, there's a few general tips I'd like to leave you with.
Tooltips are your friend. If you're unsure of what something does, simply hover over it with your cursor for a few seconds and voilà, the answer will appear. Every icon in Web Inspector has a tooltip. All links have a tooltip. In fact, almost every component of the interface has a tooltip. Web Inspector makes heavy use of iconography as there's a ton of varying functionality that has to fit into a very limited amount of space. As such, it relies on tooltips to provide additional explanation and description where there isn't enough room.
For a similar reason, context menus are everywhere and they often contain additional functionality that can't be accessed anywhere else. Many icons have a context menu. Every link has a context menu. Again, almost every component of the interface has a context menu. And, in fact, most of what we've done so far could've also been done via a context menu action.
For much of the same reason as tooltips, Web Inspector puts a lot of functionality into context menu actions as there isn't enough room for each action to have its own component in the interface. For more tips like those, there's an all-new section of the WebKit website that talks specifically about Web Inspector.
It contains a bunch of reference pages that each explore both the breadth and the depth of various features, parts of the interface and more. In fact, many of the features we talked about today already have reference pages, and we're working on writing even more for many of the others. Also, be sure to follow @webkit on Twitter as we post weekly tips about various features in Web Inspector.
And I'd also highly recommend using Safari Technology Preview for your daily development as it gets biweekly updates with bug fixes and new features both to Web Inspector and to the rest of WebKit. Be sure to check out this session's related resources or other sessions about some of these new features, as well as links to our documentation. I really hope you've enjoyed learning about what's new in Web Inspector, and I hope you find the things we've discussed useful for understanding and debugging Web content in the future. Thank you so much for listening, and I hope you have a wonderful WWDC.