How Safari Content Blockers Hide DOM Elements with CSS

This article explains the technical mechanism behind element hiding in the Safari web browser. It covers how Safari Content Blockers utilize a declarative JSON syntax to define rules, how the css-display-none action targets specific Document Object Model (DOM) elements, and how WebKit compiles and applies these rules to remove unwanted page elements without compromising user privacy or browser performance.

The Declarative JSON Architecture

Unlike traditional browser extensions that use JavaScript to actively read and modify a webpage's DOM, Safari Content Blockers operate on a declarative model. Developers provide Safari with a JSON array of rules. Safari compiles these rules into efficient bytecode before a webpage even loads. This ensures that the content blocker never sees the user's browsing activity, while WebKit handles the actual element hiding natively at the engine level.

Every content blocking rule consists of two main parts:

  1. Trigger: Defines the conditions (such as the URL pattern) under which the rule should be activated.
  2. Action: Defines what Safari should do when the trigger conditions are met.

Using the css-display-none Action

To hide specific DOM elements on a page, Safari provides the css-display-none action type. This action requires a companion selector field, which accepts a comma-separated string of standard CSS selectors.

When you specify css-display-none, you are instructing WebKit to find any HTML elements matching your CSS selectors and prevent them from rendering on the screen.

Here is an example of a valid Safari Content Blocker JSON rule that hides specific DOM elements:

[
  {
    "trigger": {
      "url-filter": ".*",
      "if-domain": ["example.com", "www.example.com"]
    },
    "action": {
      "type": "css-display-none",
      "selector": "#top-banner, .sidebar-ad, div[data-ad-unit]"
    }
  }
]

Breakdown of the Rule

  • "url-filter": ".*": This regular expression tells Safari to evaluate this rule on any URL string.
  • "if-domain": This restricts the trigger so the CSS selectors are only applied when the user visits example.com or www.example.com. Leaving this out applies the selector globally to all websites.
  • "type": "css-display-none": This tells WebKit that the action is strictly cosmetic and meant to hide elements rather than block a network request.
  • "selector": This contains the exact CSS selectors targeting the DOM elements. In the example above, it targets an element with the ID top-banner, any element with the class sidebar-ad, and any div containing the data-ad-unit attribute.

How WebKit Applies the Styles

When a user navigates to a webpage that matches the trigger criteria, WebKit intercepts the page rendering process. Before the DOM is painted to the screen, WebKit takes the string provided in the selector field and injects a high-priority user stylesheet directly into the page context.

Behind the scenes, the JSON action is translated into the following CSS rule:

#top-banner, .sidebar-ad, div[data-ad-unit] {
    display: none !important;
}

Because WebKit applies this at the browser engine level using the !important flag, it overrides the webpage's native stylesheets and inline styles. The targeted DOM elements remain in the HTML source code, but they collapse to zero width and height, rendering them completely invisible to the user and preventing them from taking up layout space.

Supported CSS Selectors

The selector field supports most standard CSS3 selectors that WebKit natively understands. You can use:

  • ID selectors: #newsletter-popup
  • Class selectors: .sponsored-post
  • Attribute selectors: a[href^="https://tracker.example.com"]
  • Combinators: div.main-content > aside.promo
  • Pseudo-classes: div:not(.essential-content)

By combining specific triggers with precise CSS selectors, Safari Content Blockers efficiently strip away unwanted DOM elements natively, avoiding the memory overhead and privacy risks of injected JavaScript.