W3C

Selectors API

W3C Working Draft 21 December 2007

This Version:
http://www.w3.org/TR/2007/WD-selectors-api-20071221/
Latest Version:
http://www.w3.org/TR/selectors-api/
Previous Versions:
http://www.w3.org/TR/2007/WD-selectors-api-20071019/
http://www.w3.org/TR/2006/WD-selectors-api-20060926/
http://www.w3.org/TR/2006/WD-selectors-api-20060525/
Editors:
Lachlan Hunt (Opera Software ASA) <lachlan.hunt@lachy.id.au>
Anne van Kesteren (Opera Software ASA) <annevk@opera.com>

Abstract

Selectors, which are widely used in CSS, are patterns that match against elements in a tree structure [Selectors] [CSS21]. The Selectors API specification defines methods for retrieving Element nodes from the DOM by matching against a group of selectors. It is often desirable to perform DOM operations on a specific set of elements in a document. These methods simplify the process of aquiring specific elements, especially compared with the more verbose techniques defined and used in the past.

Status of this Document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at http://www.w3.org/TR/.

This is a Last Call Working Draft of "Selectors API". The W3C Membership and other interested parties are invited to review the document and send comments to public-webapi@w3.org (public archive) with [selectors-api] in the subject, through 06 January 2008.

Web content and browser developers are encouraged to review this draft. This draft is considered relatively stable and is expected to progress to Candidate Recommendation after the review period. The editor’s copy of this specification is available in W3C CVS. A detailed list of changes is also available from the CVS server.

This document was developed by the Web API Working Group. The Working Group expects to advance this Working Draft to Recommendation Status.

Publication as a Working Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

Table of Contents

1. Introduction

This section is non-normative.

This specification introduces two methods that take a group of selectors (often simply referred to as a selector) as an argument and return the matching elements [Selectors]. With these methods, it is easier to match a set of Element nodes based on specific criteria. So instead of having to filter the result of a getElementsByTagName() call, authors can directly “filter” in the query.

1.1. Examples

This section is non-normative.

Some ECMAScript [ECMAScript] examples:

This is an example HTML table.

<table id="score">
  <thead>
    <tr>
      <th>Test
      <th>Result
  <tfoot>
    <tr>
      <th>Average
      <td>82%
  <tbody>
    <tr>
      <td>A
      <td>87%
    <tr>
      <td>B
      <td>78%
    <tr>
      <td>C
      <td>81%
</table>

In order to obtain the cells containing the results in the table, which might be done, for example, to plot the values on a graph, there are at least two approaches that may be taken. Using only the APIs from DOM Level 2, it requires a script like the following that iterates through each tr within each tbody in the table to find the second cell of each row.

var table = document.getElementById("score");
var groups = table.tBodies;
var rows = null;
var cells = [];

for (var i = 0; i < groups.length; i++) {
  rows = groups[i].rows;
  for (var j = 0; j < rows.length; j++) {
    cells.push(rows[j].cells[1]);
  }
}

Alternatively, using the querySelectorAll() method, that script becomes much more concise.

var cells = document.querySelectorAll("#score>tbody>td:nth-of-type(2)");

1.2. Conformance Requirements

All diagrams, examples and notes in this specification are non-normative, as are all sections explicitly marked non-normative. Everything else in this specification is normative.

The key words must, should, and may in the normative parts of this document are to be interpreted as described in RFC 2119 [RFC2119].

The following conformance classes are defined (and considered) by this specification:

conforming user agent
A user agent that implements the DocumentSelector, ElementSelector and StaticNodeList interfaces described in this specification and conforms to all must-level critera that apply to user agents.
conforming namespace resolver
An object implementing the NSResolver interface which conforms to all must-level critera that apply to the NSResolver interface.
conforming application
An application that uses the interfaces defined in this specification and conforms to all must-level critera that apply to authors.

1.2.1. Terminology and Conventions

The terminology used in this specification is that from Selectors [Selectors].

Conformance requirements phrased as algorithms or specific steps may be implemented in any manner, so long as the end result is equivalent. In doing so, user agents may assume that the object implementing the NSResolver interface (or ECMAScript Function) returns consistent results when its lookupNamespaceURI() method is invoked.

The construction "Foo object", where Foo is actually an interface, is sometimes used instead of the more accurate "object implementing the Foo interface".

1.2.2. Interoperability Considerations

This section is non-normative.

Since user agents may optimise the algorithms described in this specification, and because some may invoke the NSResolver object more than others, interoperability concerns may arise if the the NSResolver object (or ECMAScript Function) causes side effects or returns inconsistent results each time it is invoked.

In the following example, the NSResolver causes a side effect when executed.

function resolver(prefix) {
  sideEffect();
  if (prefix == "test") {
    return "http://example.org/test";
  }
}

var x = document.querySelectorAll("test|*:empty > test|p", resolver)

Some user agents may not need to resolve the namespace prefix in order to determine that the selector cannot match any elements, in which case sideEffect() will not be invoked.

The following example could return inconsistent results each time it is invoked, depending on the order in which namespace prefixes are resolved and the number of times the resolver is invoked to for the foo prefix.

var i = 0;
function resolver(prefix) {

  var ns = ["http://example.org/foo",
            "http://example.org/bar",
            "http://example.org/baz"];
  return ns[i++];
}

var x = document.querySelectorAll("foo|x, foo|y, bar|z", resolver);

This could result in selecting x, y and z elements from almost any combination of namespaces that may be returned. The result is unpredictable and different results may occur in different user agents.

1.2.3. Extensibility

This section is non-normative.

Extensions of the APIs defined in this specification are strongly discouraged. User agents, Working Groups and other interested parties should discuss extensions on a relevant public forum, such as public-webapi@w3.org.

1.3. Security Considerations

It is expected that implementing this specification introduces no new security risks for users.

User agents should ensure they remain stable when facing a hostile NSResolver object. Potentially hostile behaviour includes:

1.4. Privacy Considerations

History theft is a potential privacy issue because the :visited pseudo-class in Selectors [Selectors] allows authors to query which links have been visited.

This is not a new problem, as it can already be exploited using existing CSS and DOM APIs, such as getComputedStyle() [DOM2Style].

In this example, vlinks will aquire a list of links that the user has visited. The author can then obtain the URIs and potentially exploit this knowledge.

var vlinks = document.querySelectorAll(":visited");
for (var i = 0; i < vlinks.length; i++) {
  doSomethingEvil(vlinks[i].href);
}

As defined in Selectors ([Selectors], section 6.6.1), user agents may treat all links as unvisited links, or implement other measures to preserve the user’s privacy.

2. The querySelector() and querySelectorAll() Methods

Objects implementing the Document interface must also implement the DocumentSelector interface. Likewise objects implementing the Element interface must also implement the ElementSelector interface. [DOM3Core]

interface DocumentSelector {
  Element         querySelector(in DOMString selectors);
  Element         querySelector(in DOMString selectors, in NSResolver nsresolver);
  StaticNodeList  querySelectorAll(in DOMString selectors);
  StaticNodeList  querySelectorAll(in DOMString selectors, in NSResolver nsresolver);
};

interface ElementSelector {
  Element         querySelector(in DOMString selectors);
  Element         querySelector(in DOMString selectors, in NSResolver nsresolver);
  StaticNodeList  querySelectorAll(in DOMString selectors);
  StaticNodeList  querySelectorAll(in DOMString selectors, in NSResolver nsresolver);
}

The querySelector() methods on the DocumentSelector interface must, when invoked, return the first Element node within the document, in document order (using depth-first pre-order traversal), that matches the group of selectors (selectors), if any. Otherwise it must return null.

The querySelector() methods on the ElementSelector interface must, when invoked, return the first Element node in document order that is a descendant of the element on which the method was invoked and matches the group of selectors (selectors), if any. Otherwise it must return null.

The querySelectorAll() methods on the DocumentSelector interface must, when invoked, return a StaticNodeList of all the Element nodes within the document, in document order, that match the group of selectors (selectors) in document order, if any. Otherwise it must return an empty StaticNodeList.

The querySelectorAll() methods on the ElementSelector interface must, when invoked, return a StaticNodeList of all the Element nodes, in document order, that are descendants of the element on which the method was invoked and matches the group of selectors (selectors), if any. Otherwise it must return an empty StaticNodeList.

Both querySelector() and querySelectorAll() take a group of selectors (selectors) as the first argument and optionally an NSResolver (nsresolver) as the second. User agents must use the nsresolver argument to resolve namespace prefixes to namespaces or to get the default namespace. When the nsresolver argument is null user agents must ignore it. [Selectors]

When using namespace prefixes within selectors or if there needs to be a default namespace, authors must pass an NSResolver object, or a Function in the case of ECMAScript, as the argument for nsresolver. Otherwise, authors may set the argument to null or omit it if the language binding permits.

If the given group of selectors (selectors) is invalid, the user agent must raise a SYNTAX_ERR exception ([DOM3Core], section 1.4).

An unresolvable namespace is a namespace that cannot be resolved because there was no NSResolver provided or the given NSResolver does not return a namespace for the namespace prefix. When an unresolvable namespace is encountered, the user agent must raise a NAMESPACE_ERR exception ([DOM3Core], section 1.4).

The default namespace does not need to be defined.

If an exception is raised by the NSResolver while resolving namespaces, processing must be aborted and the exception propagated to the caller.

In languages that support optional arguments for methods, like ECMAScript, if the nsresolver argument is omitted, the user agent must act as if the nsresolver argument was set to null.

Using pseudo-elements in one of the selectors could mean that nothing is returned for that particular selector when it doesn’t resolve in one or more Element nodes.

The methods accept a group of selectors (comma separated) as the argument. The following example would select all p elements in the document that have a class of either "error" or "warning".

var alerts = document.querySelectorAll("p.warning, p.error");

The querySelector() method also accepts a group of selectors and it will return the first element that matches either selector in the group (if any).

var x = document.querySelector("#foo, #bar");

x would contain the first element in the document with an ID of either foo or bar (or both).

The methods can also be invoked on elements. In this example, the method is invoked on an element that is the target of an event listener.

function handle(evt) {
  var x = evt.target.querySelector("span");
  ...
  // Do something with x
}

2.1. The NSResolver Interface

The NSResolver interface allows prefixes to be bound to a namespace URI. The object implementing this interface is expected to be implemented by authors.

Other specifications may introduce methods that return an object implementing NSResolver in which case it would be implemented by the user agent.

interface NSResolver {
  DOMString       lookupNamespaceURI(in DOMString prefix);
};

The lookupNamespaceURI() method must, when invoked, return the namespace represented by the prefix (prefix) or the default namespace if the prefix argument is an empty string.

When the lookupNamespaceURI() method is invoked with a prefix as the argument, it must return the namespace URI for the given prefix. When the lookupNamespaceURI() method is invoked with an empty string as the argument (representing the default namespace), it must do either of the following:

The lookupNamespaceURI() must return consistent results each time it is invoked.

Authors are strongly discouraged from writing an NSResolver that returns inconsistent results.

In ECMAScript (and other languages that allow similar constructs), this object may be implemented as a Function. In such languages, user agents must support NSResolver being implemented as a Function.

To resolve namespace prefixes, if there was an NSResolver object provided, user agents must pass the prefix, preserving case, to the lookupNamespaceURI() method. User agents must handle prefixes case sensitively. If there was no NSResolver object provided, or if the method returns an empty string or does not return a DOMString, the prefix represents an unresolvable namespace. Otherwise, the return value is the namespace for the given prefix.

The case sensitivity of namespace prefixes is effectively determined by the implementation of the NSResolver object that is used to resolve the namespaces.

In Unicode, caseless matching requires both strings that are being compared, to be case folded prior to performing a binary comparison [CaseMap]. However, since case folding is not the same as simply uppercasing or lowercasing both strings and because the comparison is being performed by the NSResolver object implemented by the author, this specification cannot require case insensitive namespace prefixes.

To get the default namespace, if there was an NSResolver object provided, user agents must invoke the lookupNamespaceURI() method with the empty string as the argument. If there is no NSResolver object provided, or if the method returns an empty string, null, undefined, or equivalent, then there is no default namespace. Otherwise, if the method does not return a DOMString, the default namespace is an unresolvable namespace. Otherwise, the return value is the default namespace.

The number of times that a user agent may invoke the lookupNamespaceURI() method with each prefix and the order in which prefixes are resolved is not defined by this specification.

The follwoing examples make use of this sample document.

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:svg="http://www.w3.org/2000/svg"
      xml:lang="en">
  <head>
    <title>Selectors API Example</title>
  </head>
  <body>
    <div><svg:svg … > … </svg:svg></div>
    <p><svg:svg … > … </svg:svg></p>
    <p><svg xmlns="http://www.w3.org/2000/svg" … > … </svg></p>
  </body>
</html> 

The NSResolver may be implemented as an object with a lookupNamespaceURI method.

var resolver = {
  lookupNamespaceURI: function(prefix) {
    var ns = {
      "xh"  :"http://www.w3.org/1999/xhtml",
      "xbl" :"http://www.w3.org/ns/xbl",
      "svg" :"http://www.w3.org/2000/svg",
      "math":"http://www.w3.org/1998/Math/MathML"
    };
    return ns[prefix];
  }
}

For convenience, in ECMAScript, the NSResolver may also be implemented as a function. In the above example, namespace prefixes are resolved case sensitively. If case insensitve namespace prefixes are desired, it is possible to implement this in the resolver by altering the case of the prefix before comparison.

function resolver(prefix) {
  var ns = {
    "xh"  :"http://www.w3.org/1999/xhtml",
    "xbl" :"http://www.w3.org/ns/xbl",
    "svg" :"http://www.w3.org/2000/svg",
    "math":"http://www.w3.org/1998/Math/MathML"
  };
  return ns[prefix.toLowerCase()];
}

var svgImages = document.querySelectorAll("xh|p SVG|svg", resolver);

Using the case insensitive namespace resolver, the prefix xh will be resolved as http://www.w3.org/1999/xhtml and SVG as http://www.w3.org/2000/svg. The svgImages variable will be a StaticNodeList containing the two svg elements that are descendents of the p elements.

The default namespace can be specified so that prefixes do not need to be used for selecting elements in a specific namespace. In the following example, the default namespace has been set to the XHTML namespace.

function resolver(prefix) {
  var ns = {
    ""    :"http://www.w3.org/1999/xhtml", // Default namespace
    "xbl" :"http://www.w3.org/ns/xbl",
    "svg" :"http://www.w3.org/2000/svg",
    "math":"http://www.w3.org/1998/Math/MathML"
  };
  return ns[prefix];
}

Using this resolver, the previous example can be rewritten without explicitly declaring the XHTML namespace using the xh prefix.

var svgImages = document.querySelectorAll("p svg|svg", resolver);

In the following example, the foo namespace is undefined and will result in an unresolvable namespace. The NAMESPACE_ERR exception can be handled using a try/catch block.

try {
  document.querySelectorAll("foo|bar", resolver);
} catch (e) {
  switch (e.code) {
  case DOMException.NAMESPACE_ERR:
    // Namespace error
    break;
  case DOMException.SYNTAX_ERR:
    // Syntax error
    break;
  default:
    // Unknown error
  }
}

2.2. The StaticNodeList Interface

typedef StaticNodeList NodeList;

The StaticNodeList must be implemented identically to the NodeList interface as defined in DOM Level 3 Core [DOM3Core] with the exception that the interface, as the name suggests, is static and not live.

This example demonstrates how to iterate through the items in a StaticNodeList.

var lis = document.querySelectorAll("ul.nav>li");
for (var i = 0; i < lis.length; i++) {
  process(lis.item(i));
}

In ECMAScript, the language binding also allows NodeLists and StaticNodeLists to be addressed using the array notation, so that loop could be rewritten like this:

for (var i = 0; i < lis.length; i++) {
  process(lis[i]);
}

Since a StaticNodeList is not live, changes to the DOM do not affect the content of the list. Consider the process() function called in the previous examples is defined as follows:

funtction process(elmt) {
  elmt.parentNode.removeChild(elmt);
}

This would cause each selected element to be removed from the DOM, but each element will remain in the StaticNodeList. If the list were a live NodeList, removing an item from the DOM would also remove the element from the list and adjust the indexes of subsequent elements. That would have adverse effects upon the loop because not all selected elements would be processed.

References

[CaseMap]
(Non-normative) Unicode Standard Annex #21 Case Mappings, M. Davis, editor. Unicode Consortium, March 2001.
[CSS21]
(Non-normative) Cascading Style Sheets, level 2 revision 1, B. Bos, T. Çelik, I. Hickson, H. Wium Lie, editors. World Wide Web Consortium, June 2005.
[DOM2Style]
(Non-normative) Document Object Model (DOM) Level 2 Style Specification, C. Wilson, P. Le Hégaret, V. Apparao, editors. World Wide Web Consortium, November 2000.
[DOM3Core]
Document Object Model (DOM) Level 3 Core Specification, A. Le Hors, P. Le Hégaret, L. Wood, G. Nicol, J. Robie, M. Champion, S. Byrne, editors. World Wide Web Consortium, April 2004.
[ECMAScript]
ECMAScript Language Specification, Third Edition. ECMA, December 1999.
[RFC2119]
RFC 2119: Key words for use in RFCs to Indicate Requirement Levels, S. Bradner. IETF, March 1997.
[Selectors]
Selectors, D. Glazman, T. Çelik, I. Hickson, P. Linss, J. Williams, editors. World Wide Web Consortium, December 2005.

Acknowledgements

The editor would like to thank to the following people who have contributed to this specification (ordered on first name):

Thanks to all those who have helped to improve this specification by sending suggestions and corrections. (Please, keep bugging us with your issues!)