HTML5 Rocks

HTML5 Rocks

What's the CSS :scope pseudo-class for?

By Eric Bidelman at

:scope is defined in CSS Selectors 4 as:

A pseudo-class which represents any element that is in the contextual reference element set. This is is a (potentially empty) explicitly-specified set of elements, such as that specified by the querySelector(), or the parent element of a <style scoped> element, which is used to "scope" a selector so that it only matches within a subtree.

An example of using this guy is within a <style scoped> (more info):

<style>
  li {
    color: blue;
  }
</style>

<ul>
  <style scoped>
    li {
      color: red;
    } 
    :scope {
      border: 1px solid red;
    }
  </style>
  <li>abc</li>
  <li>def</li>
  <li>efg</li>
</ul>

<ul>
  <li>hij</li>
  <li>klm</li>
  <li>nop</li>
</ul>

Note: <style scoped> can be enabled in Chrome using the "Enable experimental WebKit features" flag in about:flags.

This colors the li elements in the first ul red and, because of the :scope rule, puts a border around the ul. That's because in the context of this <style scoped>, the ul matches :scope. It's the local context. If we were to add a :scope rule in the outer <style> it would match the entire document. Essentially, equivalent to :root.

Contextual elements

You're probably aware of the Element version of querySelector() and querySelectorAll(). Instead of querying the entire document, you can restrict the result set to a contextual element:

<ul>
 <li id="scope"><a>abc</a></li>
 <li>def</li>
 <li><a>efg</a></li>
</ul>
<script>
  document.querySelectorAll('ul a').length; // 2

  var scope = document.querySelector('#scope');
  scope.querySelectorAll('a').length; // 1
</script>

When these are called, the browser returns a NodeList that's filtered to only include the set of nodes that a.) match the selector and b.) which are also descendants of the context element. So in the the second example, the browser finds all a elements, then filters out the ones not in the scope element. This works, but it can lead to some bizarre behavior if you're not careful. Read on.

When querySelector goes wrong

There's a really important point in the Selectors spec that people often overlook. Even when querySelector[All]() is invoked on an element, selectors still evaluate in the context of the entire document. This means unanticipated things can happen:

scope.querySelectorAll('ul a').length); // 1
scope.querySelectorAll('body ul a').length); // 1

WTF! In the first example, ul is my element, yet I'm still able to use it and matches nodes. In the second, body isn't even a descendant of my element, but "body ul a" still matches. Both of these are confusing and not what you'd expect.

It's worth making the comparison to jQuery here, which takes the right approach and does what you'd expect:

$(scope).find('ul a').length // 0
$(scope).find('body ul a').length // 0

...enter :scope to solve these semantic shenanigans.

Fixing querySelector with :scope

WebKit recently landed support for using the :scope pseudo-class in querySelector[All](). You can test it in Chrome Canary 27.

You can use it restrict selectors to a context element. Let's see an example. In the following, :scope is used to "scope" the selector to the scope element's subtree. That's right, I said scope three times!

scope.querySelectorAll(':scope ul a').length); // 0
scope.querySelectorAll(':scope body ul a').length); // 0
scope.querySelectorAll(':scope a').length); // 1

Using :scope makes the semantics of the querySelector() methods a little more predictable and inline with what others like jQuery are already doing.

Performance win?

Not yet :(

I was curious if using :scope in qS/qSA gives a performance boost. So...like a good engineer I threw together a test. My rationale: less surface area for the browser to do selector matching means speedier lookups.

In my experiment, WebKit currently takes ~1.5-2x longer than not using :scope. Drats! When crbug.com/222028 gets fixed, using it should theoretically give you a slight performance boost over not using it.

Big boost to DOM performance - WebKit's innerHTML is 240% faster

By Sam Dutton at

We're very happy to see that some common DOM operations have just skyrocketed in speed. The changes were at the WebKit level, boosting performance for both Safari (JavaScriptCore) and Chrome (V8).

Chrome Engineer Kentaro Hara made seven code optimisations within WebKit; below are the results, which show just how much faster JavaScript DOM access has become:

DOM performance boosts summary

Below, Kentaro Hara gives details on some of the patches he made. The links are to WebKit bugs with test cases, so you can try out the tests for yourself. The changes were made between WebKit r109829 and r111133: Chrome 17 does not include them; Chrome 19 does.

Improve performance of div.innerHTML and div.outerHTML by 2.4x (V8, JavaScriptCore)

Previous behavior in WebKit:

  1. Create a string for each tag.
  2. Append a created string to Vector<string>, parsing the DOM tree.
  3. After the parsing, allocate a string whose size is the sum of all strings in the Vector<string>.
  4. Concatenate all strings in Vector<string>, and return it as innerHTML.

New behavior in WebKit:

  1. Allocate one string, say S.
  2. Concatenate a string for each tag to S, incrementally parsing the DOM tree.
  3. Return S as innerHTML.

In a nutshell, instead of creating a lot of strings and then concatenating them, the patch creates one string and then simply append strings incrementally.

Improve performance of div.innerText and div.outerText in Chromium/Mac by 4x (V8/Mac)

The patch just changed the initial buffer size for creating innerText. Changing the initial buffer size from 2^16 to 2^15 improved Chromium/Mac performance by 4x. This difference depends on the underlying malloc system.

Improve performance of CSS property accesses in JavaScriptCore by 35%

(Note: This is a change for Safari, not for Chrome.)

A CSS property string (e.g. .fontWeight, .backgroundColor) is converted to an integer ID in WebKit. This conversion is heavy. The patch caches the conversion results in a map (i.e. a property string => an integer ID), so that the conversion won't be conducted multiple times.

How do the tests work?

They measure the time of property accesses. In case of innerHTML (the performance test in bugs.webkit.org/show_bug.cgi?id=81214), the test just measures the time to run the following code:

for (var i = 0; i < 1000000; i++)
    document.body.innerHTML;

The performance test uses a large body copied from the HTML spec.

Similarly, the CSS property-accesses test measures the time of the following code:

var spanStyle = span.style;
for (var i = 0; i < 1000000; i++) {
    spanStyle.invalidFontWeight;
    spanStyle.invalidColor;
    spanStyle.invalidBackgroundColor;
    spanStyle.invalidDisplay;
}

The good news is that Kentaro Hara believes more performance improvements will be possible for other important DOM attributes and methods.

Bring it on!

Kudos to Haraken and the rest of the team.

A New Experimental Feature: scoped stylesheets

By Alex Danilo at

Chromium recently implemented a new feature from HTML5: scoped stylesheets, aka. <style scoped>. A web author can limit style rules to only apply to a part of a page by setting the ‘scoped’ attribute on a <style> element that is the direct child of the root element of the subtree you want the styles to be applied to. This limits the styles to affect just the element that is the parent of the <style> element and all of its descendants.

Example

Here’s a simple document that uses standard styling:

<html>
<body>
  <div>a div! <span>a span!</span></div>
    <div>
      <style>
        div { color: red; }
        span { color: green; }
      </style>
      a div! <span>a span!</span></div>
  <div>a div! <span>a span!</span></div>
</body>
</html>

The style rules specified will color text within any <div> red, and within any <span> green:

a div! a span!
a div! a span!
a div! a span!

However, if we set scoped on the <style> element:

<html>
<body>
  <div>a div! <span>a span!</span></div>
    <div>
      <style scoped>
        div { color: red; }
        span { color: green; }
      </style>
      a div! <span>a span!</span></div>
  <div>a div! <span>a span!</span></div>
</body>
</html>

then it restricts the style rules so that they’re applied to the enclosing <div> that is the parent of the <style scoped> element and anything inside just that <div>. We call this ‘scoped’ and the result looks like:

a div! a span!
a div! a span!
a div! a span!

This of course can be done anywhere in the markup. So if you’re adventurous, you could nest scoped styles within other scoped parts of the markup as much as you like to get fine-grained control over where styles get applied.

Use cases

Now what is this good for?

A common use case is syndicated content: when you as a web author would like to incorporate content from a third party, including all its styles, but do not want to risk those styles “polluting” other, unrelated parts of the page. A great advantage here is the ability to combine content from other sites like yelp, twitter, ebay, etc. into a single page without needing to isolate them using an <iframe> or on-the-fly editing the external content.

If you’re using a content management system (CMS) that sends you snippets of markup that are all mashed together into a final page display then this is a great feature to make sure each snippet gets styled in isolation from anything else on the page. This can be just as useful for a wiki as well.

When you want to author some nice demo code on a page, it’s easy to limit the styles to just the demo content. That lets you go wild with the CSS on the demo, yet nothing else on the page will get affected.

Another use case is simply encapsulation: for example, if your web page has a side menu, it makes sense to put styles that are specific to that menu into a <style scoped> section in that part of the markup. Those style rules won’t have any effect when rendering other parts of the page, which keeps them nicely separated from the main content!

Possibly one of the most compelling use cases is for the web component model. Web components are going to be a great way to build things like sliders, menus, date pickers, tab widgets, etc. By providing the scoped styles, a designer can build a widget and package it with their styles as a self-contained unit that others can grab and combine into a rich web application. We plan to use <style scoped> heavily with web components and the shadow DOM (that can already be enabled by setting the experimental “shadow DOM” flag in chrome://flags). Right now there’s no really good way to make sure that styles are limited to web components without resorting to bad practices like inline styling, so scoped styles are a perfect fit for this.

Why include the parent element?

The most natural way is to include the parent element so that the <style scoped> rules could, for example, set a common background color for the entire scope. It also allows scoped style sheets to be written “defensively” for browsers that don’t yet support <style scoped>, by prefixing rules with an ID or class selector as a fallback:

<div id=”menu”>
  <style scoped>
    #menu .main { … }
    #menu .sub { … }
  …

This mimics the effect of using styles when ‘scoped’ is implemented but with some run-time performance penalty due to the more complex selector. The nice thing about this approach is that it allows for a graceful fallback approach until the day when <style scoped> is widely supported and the ID selectors could simply be dropped.

Status

Given that the implementation of scoped style sheets is still new, they are currently hidden behind a run-time flag in Chrome. To enable them you need to get a version of Chrome that has a version number of 19 or higher (Chrome Canary right now), then locate the ‘Enable <style scoped>’ entry in chrome://flags (towards the end), click ‘Enable’ and then restart the browser.

There are currently no known bugs, but @global and scoped versions of @keyframes and @-webkit-region and are stil in the process of being implemented. Also, @font-face is ignored for the time being since there is a good chance that the spec will change.

We would like to encourage everyone interested in the feature to try it out and let us know about your experiences: the good, the bad and (maybe) the buggy.

Detect DOM changes with Mutation Observers

By Ernest Delgado at

Back in 2000, the Mutation Events API was specified to make it easy for developers to react to changes in a DOM (e.g. DOMNodeRemoved, DOMAttrModified, etc).

This feature wasn’t widely used by web developers but it presented a very convenient and popular use case for Chrome Extensions if they wanted to perform some action when something in the page changed.

Mutation Events are useful, but at the same time they create some performance issues. The Events are slow and they are fired too frequently in a synchronous way, which causes some undesired browser bugs.

Introduced in the DOM4 specification, DOM Mutation Observers will replace Mutation Events. Whereas Mutation Events fired slow events for every single change, Mutation Observers are faster using callback functions that can be delivered after multiple changes in the DOM.

You can manually handle the list of changes the API offers, or use a library such as Mutation Summary which makes this task easier and adds a layer of reliability about the changes that took place in the DOM.

You can start using Mutation Observers in Chrome Beta to detect changes in the DOM and be ready to use it when it comes to stable (Chrome 18). If you are currently using the deprecated Mutation Events, just migrate to Mutation Observers.

Here’s an example of listing inserted nodes with Mutation Events:

var insertedNodes = [];
document.addEventListener("DOMNodeInserted", function(e) {
  insertedNodes.push(e.target);
}, false);
console.log(insertedNodes);

And here’s how it looks with Mutation Observers:

var insertedNodes = [];
var observer = new MutationObserver(function(mutations) {
 mutations.forEach(function(mutation) {
   for (var i = 0; i < mutation.addedNodes.length; i++)
     insertedNodes.push(mutation.addedNodes[i]);
 })
});
observer.observe(document, { childList: true });
console.log(insertedNodes);

insertAdjacentHTML Everywhere

By Ernest Delgado at

If we want to insert content in a HTML document we have three ways to do it:

  • Using DOM methods like createNode and appendChild
  • Using Document Fragments
  • Using innerHTML

One can arguably say we also have document.write for few use cases.

innerHTML has been standarized in HTML5 and with it a brother method insertAdjacentHTML which works as innerHTML but allows us to define more specifically where we want to insert the HTML content: beforeBegin, afterBegin, beforeEnd and afterEnd.

var ul = document.getElementById("list");
ul.insertAdjacentHTML("beforeEnd", "<li>A new li on the list.</li>");

Back in 2008 John Resig wrote an article about insertAdjacentHTML with this conclusion:

Having browsers implement this method will dramatically reduce the amount of code needed to write a respectable JavaScript library. I'm looking forward to the day in which this method is more-widely available (along with querySelectorAll) so that we can really buckle down and do some serious code simplification.

Until now, the main issue with insertAdjacentHTML has been its lack of browser support. With Firefox implementing insertAdjacentHTML as of version 8, it will available in all major browsers including mobile browsers. If you want to use it now and make sure it works in Firefox versions earlier than 8 you can use this polyfill.