Surfin' Safari

Using the System Font in Web Content

Posted by Myles Maxfield on Monday, July 27th, 2015 at 9:00 am

Web content is sometimes designed to fit in with the overall aesthetic of the underlying platform which it is being rendered on. One of the ways to achieve this is by using the platform’s system font, which is possible on iOS and OS X by using the “-apple-system” CSS value for the “font-family” CSS property. On iOS 9 and OS X 10.11, doing this allows you to use Apple’s new system font, San Francisco. Using “-apple-system” also correctly interacts with the font-weight CSS property to choose the correct font on Apple’s latest operating systems.

Mac OS X 10.11 El Capitan System Font

On platforms which do not support “-apple-system,” the browser will simply fall back to the next item in the font-family fallback list. This provides a great way to make sure all your users get a great experience, regardless of which platform they are using.

There are currently discussions in the w3c regarding standardizing this value so authors could simply specify “system.” However, these discussions have not reached consensus, so WebKit prefixes this value.

Using any other mechanism to specify the system font is not guaranteed to behave as you might expect. This includes using any implementation details such as period-prefixed family names.

Going beyond the system font, iOS has dynamic type behavior, which can provide an additional level of fit and finish to your content. These text styles identify more than simply a particular font family; instead, they represent an entire style, including size and weight. These styles are therefore characterized by values given to the more-general “font” CSS property. The supported values are:

font: -apple-system-body
font: -apple-system-headline
font: -apple-system-subheadline
font: -apple-system-caption1
font: -apple-system-caption2
font: -apple-system-footnote
font: -apple-system-short-body
font: -apple-system-short-headline
font: -apple-system-short-subheadline
font: -apple-system-short-caption1
font: -apple-system-short-footnote
font: -apple-system-tall-body

For more information, please see Antonio Cavedoni’s fantastic WWDC presentation or contact @jonathandavis.

Announcing JetStream 1.1

Posted by Filip Pizlo on Monday, July 13th, 2015 at 3:03 pm

JetStream is the benchmark suite that WebKit developers use to tune JavaScript engine performance. It comprises latency benchmarks, which measure the start-up and worst-case execution times of the JavaScript engine, and throughput benchmarks, which measure average throughput in steady state. We want to make JetStream accurately reflect our performance goals — a change that improves the score should also improve the user experience. Today we are happy to announce JetStream 1.1, which improves the accuracy of the latency component of the suite. JetStream 1.1 fixes bugs in two existing tests and adds a new test to replace the oldest and smallest test in the latency component.

A sound approach to responsiveness scores

The latency component of JetStream is mostly made up of tests that measure the latency incurred when executing code for the first time. But it also includes two tests, splay-latency and mandreel-latency whose scores reflect how close a browser’s worst-case performance is to its average-case. A relatively good score on splay-latency indicates that a browser’s garbage collector is less likely to interfere with a web app’s responsiveness. Mandreel-latency is similar, but due to its larger code base and lower allocation rate, it primarily tests whether a browser’s just-in-time compiler interferes with responsiveness. Both of these tests originate from the Octane version 2 benchmark suite.

Splay-latency and mandreel-latency are intended to reward responsive browsers. A responsive browser will always execute the task quickly, rather than usually executing it quickly but sometimes having a hiccup with very bad performance. But these tests use the wrong metric for computing a responsiveness score: they first compute the root mean squared (RMS) of thousands of samples, and then report a score that is the reciprocal of the RMS. It turns out that this number doesn’t adequately reward browsers that have great responsiveness, and in some cases, it gives a high score to browsers that are less responsive.

For example, consider what would happen if Browser A always ran a task in 20 milliseconds, while Browser B ran the same task in either 10 milliseconds or 5 milliseconds at random with equal probability. Clearly, Browser B would appear to be more responsive – it always completes the task at least twice as fast as browser A. But, using RMS as the latency metric means that Browser A gets a higher score: since its performance is always 20 milliseconds, it will have an RMS of zero, which leads to a latency score of 1/0, i.e. infinity. Browser B will have an RMS around 3.5, leading to a score of 1/3.5, i.e. less than infinity. Setting aside the absurdity of a browser getting an infinite score — that’s also something we want to prevent — it’s clear that Browser A has a higher score on these tests despite always being less responsive. We’d like to be able to use these tests to tune the responsiveness of WebKit: we want to accept changes that improve the score and reject those that degrade it. But we can’t do this if the score rewards bad behavior.

The reason is that the RMS will increase (and the 1/RMS score will decrease) whenever there is an outlier in either direction. Both good outliers (the browser sometimes finishes a task much faster than normal) and bad outliers (the browser sometimes takes a long time) increase the RMS by the same amount. Instead of RMS, we want a one-sided metric, which punishes bad outliers while ignoring the good ones — we are fine with browsers sometimes being much faster than the average so long as the worst case is still great.

The simplest solution would be to compute a score based on the slowest execution out of the thousands of executions that these tests do. It’s tempting to do this, but it carries a cost: the worst case can be a noisy number. We want to reduce the measurement noise, since we want to be able to confidently measure small differences in performance between different versions of our code. An easy way to get most of the benefit of a worst-case measurement while reducing noise is to take the 0.5% worst of all samples and report their average.

Using the average of the worst samples never punishes browsers for sometimes running faster than normal. It zeroes in on exactly what we mean by responsiveness: it’s how fast the browser will run in the worst case. Consider how this affects splay-latency. The samples above the 99.5 percentile — roughly 10 samples in a typical run of splay — are exactly those where WebKit’s garbage collector has to do a full scan of the heap. Browsers that can split the heap scan into smaller chunks of work that never incur one long pause will score better on this test. It’s easy to intuit about the splay-latency and mandreel-latency scores: if one browser scores 2× higher than another, it means that this browser’s occasional performance hiccups will be 2× less severe.

The changes to improve the splay-latency and mandreel-latency tests were tracked by WebKit bugs 145762 and 146378.

Introducing CDjs

JetStream includes many legacy benchmarks from older benchmark suites like SunSpider and V8. Most of those tests are still relevant, since they feature non-trivial workloads that are representative of what we think developers want to be able to do efficiently in JavaScript. But we can further improve the benchmark suite by incrementally replacing the smallest and most trivial tests with larger and more complex ones. We are taking one small step in this direction in JetStream 1.1 by replacing cordic, previously the smallest latency test with just 80 non-comment lines of code, with a new test called CDjs, which is much more sophisticated.

CDjs is our JavaScript implementation of the CDx real-time benchmark. CDx is a collision detection benchmark in which aircraft fly around in a simulated airspace and collisions are reported by interpolating the flight paths between recorded frames. CDx has previously been implemented in Java and C, and was used for worst-case performance comparisons between those languages. CDx is usually run for hundreds, or even thousands, of iterations and performance results are computed based on the worst-case execution. This makes CDx a natural candidate for the latency component of JetStream. CDjs is a direct port of the Java implementation of CDx (also known as CDj), except for two changes:

  1. CDjs uses red-black trees rather than hashtables for its various mappings. This makes CDjs slightly more faithful to the concept of real-time, since hashtables may have random worst-case pathologies due to collisions. Also, while we were tempted to use the new Map API in ES6, we weren’t able to do so in CDjs because it needs to use objects’ values as keys rather than object identity. The red-black tree implementation is a port of WebKit’s existing red-black tree.
  2. CDjs doesn’t attempt to reuse objects quite as aggressively as CDj did. This makes CDjs more faithful to the experimental question that CDx was trying to ask: how much slower do you run when you write a real-time workload in a high-level language? The answer is more meaningful if the high-level program fully utilizes the features of the high-level language, like garbage collection.

We run CDjs as a benchmark by simulating 1,000 aircraft that occasionally collide. We run the benchmark for 200 frames. This makes the benchmark run for around one second on a modern browser and typical notebook computer. The score is the inverse of the average of the 10 worst execution times from those 200 frames. The addition of this benchmark doesn’t change the overall weighting of JetStream between latency and throughput, though it does shift the definition of latency in the direction of worst-case of many samples rather than the cost of cold start.

The addition of CDjs to JetStream was tracked by WebKit bug 146156.

Conclusion

We are happy to introduce this significant update to the JetStream benchmark suite. This update makes JetStream’s latency metrics more accurate than they were before. The new suite is now available to run at browserbench.org/JetStream/. As always, file bugs at bugs.webkit.org if you find any issues in this benchmark.

Web Inspector Console Improvements

Posted by Joseph Pecoraro on Wednesday, June 24th, 2015 at 2:00 pm

The console is an essential part of Web Inspector. Evaluating expressions in the quick console is one of the primary ways of interacting with the inspected page. Logs, errors, and warnings emitted from the page show up in the console and exploring or interacting with these objects is a given while debugging.

We recently improved both the Console and Object views in Web Inspector to make it more powerful and fun to use. Our main focus was getting quicker access to useful data and modernizing it to work better with the new changes in JavaScript.

Basics – Object Previews, Trees, and $n

Object previews allow you to see the first few properties without needing to expand them. You’ll notice that each evaluation provides you with a “$n” debugger variable to refer back to that object later. These special variables are known only to the tools, so you won’t be cluttering the page with temporary variables. $0 still exists and refers to the current selected node in the DOM Tree.

Object Preview

When expanded, the full object tree view cleanly separates properties and API. Again, we use object previews where possible to reveal more data at a glance. The icons for each property correspond to the type of the value for that property. For example, in the image below you’ll see properties with number values have a blue N icon, strings with a red S, functions a green F, etc. The icons give objects a visual pattern, and makes it easy to visually find a particular property or an unexpected change in the normal data an object holds.

Object Tree

Supporting New Types

Web Inspector has always had great support for inspecting certain built-in JavaScript types such as Arrays and DOM types like Nodes. Web Inspector has improved those views and now has comprehensive support for all of the built-in JavaScript types. This including the new ES6 types (Symbol, Set, Map, WeakSet, WeakMap, Promises, Classes, Iterators).

Array, Set, and Map object trees

WebKit’s tools are most useful when they show internal state of objects, known only to the engine, that is otherwise inaccessible. For example, showing the current status of Promises:

Promises

Or upcoming values of native Iterators:

Iterators

Other interesting cases are showing values in WeakSets and WeakMaps, or showing the original target function and bound arguments for bound functions.

API View

When expanding an object’s prototype you get a great API view showing what methods you can call on the object. The API view always provides parameter names for user functions and even provides curated versions for native functions. The API view makes it really convenient to lookup or discover the ways that you can interact with objects already available to you in the console.

Array API ViewLocal Storage Object Tree

As an added bonus, if you are working with ES6 Classes and log a class by its name or its constructor you immediately get the API view for that class.

Interactivity

Object trees are more interactive. Hover a property icon to see the property’s descriptor attributes. Hover the property name to see the exact path you can use to access the property. Getters can be invoked, and their results can be further explored.

Property Descriptor tooltipProperty Path tooltip

Context menus also provide more options. One of the most powerful features is that with any value in an Object tree you can use the context menu and select “Log Value” to re-log the value to the Console. This immediately creates a $n reference to the live object, letting you interact with it or easily reference it again later.

Console Messages

Console messages have also had a UI refresh, making logs, errors, warnings, and their location links stand out more:

Console Messages

Feedback

These enhancements are available to use in WebKit Nightly Builds. We would love to hear your feedback! You can send us quick feedback on Twitter (@JosephPecoraro, @xeenon), file a bug report, or even consider contributing your own enhancements!

Introduction to WebKit Content Blockers

Posted by Benjamin Poulain on Friday, June 12th, 2015 at 4:26 pm

Browser extensions have been a big part of modern browsers for a while now. With extensions, everyone can change their browser to their preferences.

Today, there are several models to extend browsers. Most extensions are written in JavaScript and loaded by the browser, following a model introduced by Mozilla over a decade ago. That model is also used with WebKit. It is the classical way to write extensions for OS X Safari, Web/Epiphany, and other browsers.

On OS X and iOS, there is also the concept of App Extensions with a different approach to security and performance. They are essentially little sandboxed applications that are launched on demand to extend some specific piece of functionality, known as an extension point.

The JavaScript extensions model has been great for many use cases, but there is one category of extensions where members of the WebKit project felt we should do better: the content blocking extensions. Such extensions are the most popular kind; they let users decide what should load and not load, who can track them, what should be visible on pages, etc.

The reason we are unhappy about the JavaScript-based content blocking extensions is they have significant performance drawbacks. The current model uses a lot of energy, reducing battery life, and increases page load time by adding latency for each resource. Certain kinds of extensions also reduce the runtime performance of webpages. Sometimes, they can allocate tremendous amounts of memory, which goes against our efforts to reduce WebKit’s memory footprint.

It is an area were we want to do better. We are working on new tools to enable content blocking at a fraction of the cost.

One new feature, we are developing allows describing content blocking rules in a structured format ahead-of-time, declaratively, rather than running extension-provided code at the moment a decision about blocking needs to be made. This model allows for WebKit to compile the ruleset into a format that’s very efficient to apply to loads and page content.

Content blockers in action

Before I dive into the details, let’s see what a declarative extension look like in the new format.

In essence, each content blocker extension is a list of rules that tells the engine how to act when loading a resource.

The rules are written in JSON format. For example, here is an extension with two rules:


    [
        {
            "trigger": {
                "url-filter": "evil-tracker.js"
            },
            "action": {
                "type": "block"
            }
        },
        {
            "trigger": {
                "url-filter": ".*",
                "resource-type": ["image", "style-sheet"]
                "unless-domain": ["reputable-content-server.com"]
            },
            "action": {
                "type": "block-cookies"
            }
        }
    ]

The first rule activates for any URL that contains the string “evil-tracker.js”. When the first rule is activated, the load is blocked.

The second rule activates for any resource loaded as an image or a style sheet on any domain except “reputable-content-server.com”. When the rule is activated, cookies are stripped from the request before sending it to the server.

The rules are passed to the engine by the browser. In iOS Safari, it is done through the native app extension mechanism. In OS X Safari, browser extensions can provide their rules through a new API. If you hack on WebKit, MiniBrowser also lets you load rule sets directly from the Debug menu.

Once the rules are passed to WebKit, they are compiled into an efficient bytecode format. The engine then executes this bytecode for each resource request, and uses the result to modify the request or inject CSS.

The bytecode is executed for each resource in the network subsystem. The goal is to reduce the latency between a request being created by the page and the request being actually dispatched over the network.

Content blocker format

The content blocker rules are passed in JSON format. The top level object is an array containing every rule that needs to be loaded.

Each rule of the content blocker is a dictionary with two parts: a trigger which activates the rule, and an action defining what to do when the rule is activated.

A typical rule set looks like this:


    [
        {
            "trigger": {
                …
            },
            "action": {
                …
            }
        },
        {
            "trigger": {
                …
            },
            "action": {
                …
            }
        }
    ]

The order of the rules is important. For every extension, the actions are applied in order. There is an action that skip all the rules that appears before the current one: “ignore-previous-rules”.

Let’s dive into the “trigger” and “action” objects.

Trigger definition

The “trigger” defines what properties activate a rule. When the rule is activated, its action is queued for execution. When all the triggers have been evaluated, the actions are applied in order.

Currently, the triggers are based on resource load information: the url and type of each resource, the domain of the document, and the relation of the resource to the document.

The valid fields in the trigger are:

  • “url-filter” (string, mandatory): matches the resource’s URL.
  • “url-filter-is-case-sensitive”: (boolean, optional): changes the “url-filter” case-sensitivity.
  • “resource-type”: (array of strings, optional): matches how the resource will be used.
  • “load-type”: (array of strings, optional): matches the relation to the main resource.
  • “if-domain”/”unless-domain” (array of strings, optional): matches the domain of the document.

The most important field, and the only mandatory one, is “url-filter”. In this field, you define a regular expression that will be evaluated against the URL of each resource. It is possible to match every URL by matching every character (e.g. “.*”) but in general it is better to be as precise as possible to avoid unforeseen side effects.

The syntax of the regular expression is a strict subset of JavaScript regular expressions. It is introduced later in this post.

It is possible to change the case-sensitivity of “url-filter” with the field “url-filter-is-case-sensitive”. By default, the matching is case-insensitive.

The optional field “resource-type” specifies the type of load to match. The content of this field is an array with all the types of load that can activate the trigger. The possible values are:

  • “document”
  • “image”
  • “style-sheet”
  • “script”
  • “font”
  • “raw” (any untyped load, like XMLHttpRequest)
  • “svg-document”
  • “media”
  • “popup”

Since the triggers are evaluated before the load starts, these types defines how the engine intends to use the resource, not necessarily the type of the resource itself (for example, <img src=”something.css”> is identified as an image.)

If “resource-type” is not specified, the default is to match all types of resources.

The field “load-type” defines the relation between the domain of the resource being loaded and the domain of the document. The two possible values are:

  • “first-party”
  • “third-party”

A “first-party” load is any load where the URL has the same security origin as the document. Every other case is “third-party”.

Finally, it is possible to make a trigger conditional on the URL of the main document. In this case, the rule only applies for a particular domain, or only outside of a particular domain.

The domain filters are “if-domain” and “unless-domain”, those fields are exclusive. In those fields, you can provide a domain filter for the main document.

It is important to be careful with the trigger to avoid rules being unexpectedly activated. Since it is impossible to test the entire web to validate a trigger, it is advised to be as specific as possible.

Action definition

The “action” part of the dictionary defines what the engine should do when a resource is matched by a trigger.

Currently, the action object has only 2 valid fields:

  • “type” (string, mandatory): defines what to do when the rule is activated.
  • “selector” (string, mandatory for the “css-display-none” type): defines a selector list to apply on the page.

There are 3 types of actions that limit resources: “block”, “block-cookies”, “css-display-none”. There is an additional type that does not have any impact on the resource but changes how the content extension behaves: “ignore-previous-rules”.

The action “block” is the most powerful one. It tells the engine to abort loading the resource. If the resource was cached, the cache is ignored and the load will still fail.

The action “block-cookies” changes the way the resource is requested over the network. Before sending the request to the server, all cookies are stripped from the header. Safari has its own privacy policy that applies on top of this rule. It is only possible to block cookies that would otherwise be accepted by the privacy policy, combining “block-cookies” and “ignore-previous-rules” still follows the browser’s privacy settings.

The action “css-display-none” acts on the CSS subsystem. It lets you hide elements of the page based on selectors. When this action is set, there should be a second entry named “selector” containing a selector list. Any element matching the selector list has its “display” property set to “none” which hides it.

Every selector supported by WebKit is supported for content extensions, including compound selectors and the new selectors from CSS Selectors Level 4. For example, the following action definition is valid:


    "action": {
        "type": "css-display-none",
        "selector": "#newsletter, :matches(.main-page, .article) .annoying-overlay"
    }

Finally, there is the action type “ignore-previous-rules”. All it does is ignore every rule before the current one if the trigger is activated. Note that it is not possible to ignore the rules of an other extension. Each extension is isolated from the others.

The Regular expression format

Triggers support filtering the URLs of each resource based on regular expression.

All strings in “url-filter” are interpreted as regular expressions. You have to be careful to escape regular expression control characters. Typically the dot appears in filters and needs to be escaped (for example, “energy-waster.com” should appear as “energy-waster\.com”.

The format is a strict subset of JavaScript regular expressions. Syntactically, everything supported by JavaScript is reserved but only a subset will be accepted by the parser. An unsupported expression results in a parse error.

The following features are supported:

  • Matching any character with “.”.
  • Matching ranges with the range syntax [a-b].
  • Quantifying expressions with “?”, “+” and “*”.
  • Groups with parenthesis.

It is possible to use the beginning of line (“^”) and end of line (“$”) marker but they are restricted to be the first and last character of the expression. For example, a pattern like “^bar$” is perfectly valid, while “(foo)?^bar$” causes a syntax error.

All URL matching is done against the canonical version of the URL. As such, you can expect the URL to be completely ASCII. The domain will already be punycode encoded. Both the scheme and domain are already lowercase. The resource part of the URL is already percent encoded.

Since the URL is known to be ASCII, the url-filter is also restricted to ASCII. Patterns with non-ASCII characters result in a parse error.

Privacy

We have been building these features with a focus on providing better control over privacy. We wanted to enable better privacy filters, and that is what has been driving the feature set that exists today.

There is a whole universe of features that can take advantage of the content blocker API, around privacy or better user experience. We would love to hear your feedback about what works well, what needs improvement, and what is missing.

A major benefit of the declarative content blocking extension model is that the extension does not see the URLs of pages and resources the user browsed to or had a page request. WebKit itself does not keep track of what rules have been executed on which URLs; we do not track you by design.

Everything has been developed in the open; everyone is welcome to audit and improve the code. The main part of content blockers lives in Source/WebCore/contentextensions.

Performance advice

A big focus of this feature is performance. We are trying to have good scalability with minimal performance impact.

If the rule compiler detects that a set of rules would negatively impact user experience, it refuses to load them and returns an error.

There are parameters in your extensions that do impact performance. In this section, I will give some general rules to get good performance. There are a few big themes to maximize performance:

  • Avoid quantifiers (“*”, “+”, “?”) in “url-filter” as much as possible.
  • CSS rules are best defined before any “ignore-previous-rules”
  • Make the trigger as specific as possible.
  • Group rules with similar actions.

Minimize quantifiers in regular expressions

Avoiding quantifiers helps us optimize the triggers in the backend. Quantifiers are useful but they increase the matching possibilities which tends to reduce performance. We have found that many existing privacy extensions sometimes use quantifiers excessively which tends to be costly.

One particularly bad case is quantifier appearing in the middle of a string. Cases like:


    foo.*bar

tends to be slower than “foo” and “bar” separately. Using too many of them can cause the rule set to be rejected.

One exception to that rule is common prefixes. For example, if you have many rules such as those:


    https?://user-tracker.com
    https?://we-follow-you.com
    https?://etc.com

The rules are grouped by the prefix “https?://, and it only counts as one rule with quantifiers.

Have CSS rules before “ignore-previous-rules”

When compiling the rules, we group the CSS rules whenever we determine they will be used together. For example, if a set of rules applies on every page (by using the filter “.*”), a special stylesheet is prepared for them to be ready to use them instantly when the page has loaded.

When a “ignore-previous-rules” appears, it forces the compiler to break the stylesheet since the rules appearing before an action “ignore-previous-rules” are all dismissed when the action is activated.

Use specific triggers

Good triggers try to exclude everything that could be activated by accident. Regular expressions should be as specific as possible to achieve your desired goal. Specifying the resource types if possible, and using domain filter if the rule should only apply on certain domains.

Having specific rules is important to avoid changing pages inadvertently, but it is also useful for performance. Having few actions to execute is a good idea.

Grouping rules with the same actions

Finally, grouping rules with similar actions can simplify the execution. Ideally, your extension will have all the rules blocking loads, followed by all the rules blocking cookies, etc.

Since rules are evaluated in order, it is useful to have them grouped together since matching a trigger means all the following rules of the same action can be skipped.

For example, if all the “block” rules are together, as soon as the first one is activated, all the following block rules are skipped since they have the same actions. The trigger evaluation continues on the first following rule with a different action.

We want your feedback

We have been developing these capabilities with the goal of achieving better privacy without incurring an unreasonable performance cost. We have intentionally limited ourselves to just a few features that could be improved.

If you start building content blocker extensions, it would help us if you send us your JSON files. Having many different use cases will help us optimize the code better.

For short questions, you can contact me on Twitter. For longer questions, you can email webkit-help or file bug report. If you’re interested in hacking on the code, feel free to ask me, Alex Christensen, Brady Eidson and Sam Weinig for help.

For questions about Safari’s adoption of these APIs, contact Brian Weinstein or Jon Davis.

Building WebKit for iOS Simulator

Posted by Daniel Bates on Tuesday, January 27th, 2015 at 9:01 am

I am proud to formally announce that you can now build and run top-of-tree WebKit for iOS in the iOS Simulator. We have updated the pages on webkit.org with details on building for iOS Simulator. For your convenience, I have summarized the steps to get you up and running below:

  1. Install Xcode 6.1.1.
  2. Get the Code.
  3. Enable Xcode to build command line tools by running sudo Tools/Scripts/configure-xcode-for-ios-development in the Terminal.
  4. Build WebKit for iOS Simulator by running Tools/Scripts/build-webkit --ios-simulator.
  5. Launch Safari in the iOS Simulator with the WebKit version you built by running Tools/Scripts/run-safari --ios-simulator.

Early Warning System (EWS) bots for iOS are running to help contributors catch build breakage before a patch is landed. The EWS bots build 32-bit iOS WebKit for ARMv7 hardware. We chose to build this configuration because it will most likely reveal build errors that differ from the configuration built by the existing Mac EWS bots.

We are working to bring up support for running layout tests, build and test build bots and additional iOS EWS configurations to help contributors notice build issues and regressions in WebKit for iOS.

We have always encouraged you to file all WebKit bugs that you find. Since upstreaming iOS WebKit to open source in early 2014, we have tracked iOS WebKit bugs in bugs.webkit.org. Now that you are able to build and run iOS WebKit yourself, we invite you to help fix them!