Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions src/nwsapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@

// placeholder for global regexp
reOptimizer,
reSimpleId,
reValidator,

// special handling configuration flags
Expand Down Expand Up @@ -457,9 +458,11 @@
},

// find duplicate ids using iterative walk
// Walk 'context' in tree order collecting elements carrying 'id'. The
// walk can start at 'from', an element already known to be the first match.
byIdRaw =
function(id, context) {
var node = context, nodes = [ ], next = node.firstElementChild;
function(id, context, from) {
var node = context, nodes = [ ], next = from || node.firstElementChild;
while ((node = next)) {
node.id == id && (nodes[nodes.length] = node);
if ((next = node.firstElementChild || node.nextElementSibling)) continue;
Expand All @@ -473,7 +476,7 @@
// context agnostic getElementById
byId =
function(id, context) {
var e, i, l, nodes, api = method['#'];
var e, i, l, nodes, ownerDoc, api = method['#'];

// duplicates id allowed
if (Config.IDS_DUPES === false) {
Expand All @@ -493,6 +496,25 @@
}
}

// Without document.all, every '#id' used to walk the whole subtree,
// which measures 2.4ms against 43ns for getElementById on a
// 6300-element document. getElementById cannot answer on its own,
// because a document may carry the same id more than once and all of
// them match, but it does settle two things in constant time: whether
// the id exists anywhere, and where the first one is, since it returns
// the first in tree order and any duplicate has to follow it.
ownerDoc = context.nodeType == 9 ? context : context.ownerDocument;

if (ownerDoc && ownerDoc.getElementById &&
(context.nodeType == 9 || context.isConnected)) {
e = ownerDoc.getElementById(id);
// nothing in the document carries the id, so nothing under context does
if (!e) { return none; }
// scoped to an element, the first document-order match may sit
// outside it, and a match inside it would then be missed
if (context.nodeType == 9) { return byIdRaw(id, context, e); }
}

return byIdRaw(id, context);
},

Expand Down Expand Up @@ -924,6 +946,9 @@
// global
reValidator = RegExp(standardValidator, 'g');

// a lone '#id', the shape querySelector is asked for most often
reSimpleId = RegExp('^#(' + identifier + ')$');

Patterns.id = RegExp('^#(' + identifier + ')(.*)');
Patterns.tagName = RegExp('^(' + identifier + ')(.*)');
Patterns.className = RegExp('^\\.(' + identifier + ')(.*)');
Expand Down Expand Up @@ -1822,6 +1847,23 @@
// equivalent of w3c 'querySelector' method
first =
function _querySelector(selectors, context, callback) {
var element, match;

// A lone '#id' against a document is the id map's own question, and the
// first match in tree order is exactly what getElementById returns.
// Going through select() means building the whole candidate list first,
// and without document.all that list is built by walking the document:
// 2.4ms against 43ns here. Duplicate ids do not change the answer, only
// which of them comes first, and they cannot precede this one. Scoped
// to an element the first document-order match may sit outside it, so
// that case takes the ordinary path.
if (selectors && context && context.nodeType == 9 &&
context.getElementById && (match = reSimpleId.exec(selectors))) {
element = context.getElementById(unescapeIdentifier(match[1]));
if (element && typeof callback == 'function') { callback(element); }
return element || null;
}

return select(selectors, context,
typeof callback == 'function' ?
function firstMatch(element) {
Expand Down