diff --git a/js/3p-filters.js b/js/3p-filters.js
index 3eb49c332..0f1cb583d 100644
--- a/js/3p-filters.js
+++ b/js/3p-filters.js
@@ -323,7 +323,7 @@ var updateWidgets = function() {
/******************************************************************************/
var onListCheckboxChanged = function() {
- var href = uDom(this).parent().find('a').first().attr('href');
+ var href = uDom(this).parent().descendants('a').first().attr('href');
if ( typeof href !== 'string' ) {
return;
}
@@ -349,13 +349,13 @@ var onListLinkClicked = function(ev) {
var onPurgeClicked = function() {
var button = uDom(this);
var li = button.parent();
- var href = li.find('a').first().attr('href');
+ var href = li.descendants('a').first().attr('href');
if ( !href ) {
return;
}
messaging.tell({ what: 'purgeCache', path: href });
button.remove();
- if ( li.find('input').first().prop('checked') ) {
+ if ( li.descendants('input').first().prop('checked') ) {
cacheWasPurged = true;
updateWidgets();
}
@@ -377,16 +377,16 @@ var reloadAll = function(update) {
// Reload blacklists
var switches = [];
var lis = uDom('#lists .listDetails');
- var i = lis.length();
+ var i = lis.length;
var path;
while ( i-- ) {
path = lis
- .subset(i)
- .find('a')
+ .subset(i, 1)
+ .descendants('a')
.attr('href');
switches.push({
location: path,
- off: lis.subset(i).find('input').prop('checked') === false
+ off: lis.subset(i, 1).descendants('input').prop('checked') === false
});
}
messaging.tell({
diff --git a/js/dashboard-common.js b/js/dashboard-common.js
index 6a7bebf2e..858501221 100644
--- a/js/dashboard-common.js
+++ b/js/dashboard-common.js
@@ -28,7 +28,7 @@ uDom.onLoad(function() {
uDom('.whatisthis').on('click', function() {
uDom(this)
.parent()
- .find('.whatisthis-expandable')
+ .descendants('.whatisthis-expandable')
.toggleClass('whatisthis-expanded');
});
});
diff --git a/js/i18n.js b/js/i18n.js
index a16b63a48..bfbe3b345 100644
--- a/js/i18n.js
+++ b/js/i18n.js
@@ -22,19 +22,19 @@
// Helper to deal with the i18n'ing of HTML files.
uDom.onLoad(function() {
- uDom('[data-i18n]').forEach(function() {
- this.innerHTML = chrome.i18n.getMessage(this.getAttribute('data-i18n'));
+ uDom('[data-i18n]').forEach(function(elem) {
+ elem.html(chrome.i18n.getMessage(elem.attr('data-i18n')));
});
- uDom('[title]').forEach(function() {
- var title = chrome.i18n.getMessage(this.getAttribute('title'));
+ uDom('[title]').forEach(function(elem) {
+ var title = chrome.i18n.getMessage(elem.attr('title'));
if ( title ) {
- this.setAttribute('title', title);
+ elem.attr('title', title);
}
});
- uDom('[data-i18n-tip]').forEach(function() {
- this.setAttribute(
+ uDom('[data-i18n-tip]').forEach(function(elem) {
+ elem.attr(
'data-tip',
- chrome.i18n.getMessage(this.getAttribute('data-i18n-tip')).replace(/
/g, '')
+ chrome.i18n.getMessage(elem.attr('data-i18n-tip')).replace(/
/g, '')
);
});
});
diff --git a/js/udom.js b/js/udom.js
index 3f1a1c70a..996b8f84d 100644
--- a/js/udom.js
+++ b/js/udom.js
@@ -38,6 +38,18 @@ var DOMList = function() {
/******************************************************************************/
+Object.defineProperty(
+ DOMList.prototype,
+ 'length',
+ {
+ get: function() {
+ return this.nodes.length;
+ }
+ }
+);
+
+/******************************************************************************/
+
var DOMListFactory = function(selector, context) {
var r = new DOMList();
if ( typeof selector === 'string' ) {
@@ -100,8 +112,8 @@ var addListToList = function(list, other) {
var addSelectorToList = function(list, selector, context) {
var p = context || document;
var r = p.querySelectorAll(selector);
- var i = r.length;
- while ( i-- ) {
+ var n = r.length;
+ for ( var i = 0; i < n; i++ ) {
list.nodes.push(r[i]);
}
return list;
@@ -114,6 +126,8 @@ var pTagOfChildTag = {
'option': 'select'
};
+// TODO: documentFragment
+
var addHTMLToList = function(list, html) {
var matches = html.match(/^<([a-z]+)/);
if ( !matches || matches.length !== 2 ) {
@@ -134,15 +148,99 @@ var addHTMLToList = function(list, html) {
/******************************************************************************/
+var isChildOf = function(child, parent) {
+ return child !== null && parent !== null && child.parentNode === parent;
+};
+
+/******************************************************************************/
+
+var isDescendantOf = function(descendant, ancestor) {
+ while ( descendant.parentNode !== null ) {
+ if ( descendant.parentNode === ancestor ) {
+ return true;
+ }
+ descendant = descendant.parentNode;
+ }
+ return false;
+};
+
+/******************************************************************************/
+
+var nodeInNodeList = function(node, nodeList) {
+ var i = nodeList.length;
+ while ( i-- ) {
+ if ( nodeList[i] === node ) {
+ return true;
+ }
+ }
+ return false;
+};
+
+/******************************************************************************/
+
+var doesMatchSelector = function(node, selector) {
+ if ( !node ) {
+ return false;
+ }
+ if ( node.nodeType !== 1 ) {
+ return false;
+ }
+ if ( selector === undefined ) {
+ return true;
+ }
+ var parentNode = node.parentNode;
+ if ( !parentNode || !parentNode.setAttribute ) {
+ return false;
+ }
+ var doesMatch = false;
+ parentNode.setAttribute('uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO', '');
+ var grandpaNode = parentNode.parentNode || document;
+ var nl = grandpaNode.querySelectorAll('[uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO] > ' + selector);
+ var i = nl.length;
+ while ( doesMatch === false && i-- ) {
+ doesMatch = nl[i] === node;
+ }
+ parentNode.removeAttribute('uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO');
+ return doesMatch;
+};
+
+/******************************************************************************/
+
DOMList.prototype.length = function() {
return this.nodes.length;
};
/******************************************************************************/
+DOMList.prototype.nodeAt = function(i) {
+ return this.nodes[i];
+};
+
+DOMList.prototype.at = function(i) {
+ return addNodeToList(new DOMList(), this.nodes[i]);
+};
+
+/******************************************************************************/
+
+DOMList.prototype.toArray = function() {
+ return this.nodes.slice();
+};
+
+/******************************************************************************/
+
+DOMList.prototype.forEach = function(fn) {
+ var n = this.nodes.length;
+ for ( var i = 0; i < n; i++ ) {
+ fn(this.at(i), i);
+ }
+ return this;
+};
+
+/******************************************************************************/
+
DOMList.prototype.subset = function(i, l) {
var r = new DOMList();
- var n = l !== undefined ? l : 1;
+ var n = l !== undefined ? l : this.nodes.length;
var j = Math.min(i + n, this.nodes.length);
if ( i < j ) {
r.nodes = this.nodes.slice(i, j);
@@ -153,13 +251,30 @@ DOMList.prototype.subset = function(i, l) {
/******************************************************************************/
DOMList.prototype.first = function() {
- return this.subset(0);
+ return this.subset(0, 1);
};
/******************************************************************************/
-DOMList.prototype.node = function(i) {
- return this.nodes[i];
+DOMList.prototype.next = function(selector) {
+ var r = new DOMList();
+ var n = this.nodes.length;
+ var node;
+ for ( var i = 0; i < n; i++ ) {
+ node = this.nodes[i];
+ while ( node.nextSibling !== null ) {
+ node = node.nextSibling;
+ if ( node.nodeType !== 1 ) {
+ continue;
+ }
+ if ( doesMatchSelector(node, selector) === false ) {
+ continue;
+ }
+ addNodeToList(r, node);
+ break;
+ }
+ }
+ return r;
};
/******************************************************************************/
@@ -174,7 +289,54 @@ DOMList.prototype.parent = function() {
/******************************************************************************/
-DOMList.prototype.find = function(selector) {
+DOMList.prototype.filter = function(filter) {
+ var r = new DOMList();
+ var filterFunc;
+ if ( typeof filter === 'string' ) {
+ filterFunc = function() {
+ return doesMatchSelector(this, filter);
+ };
+ } else if ( typeof filter === 'function' ) {
+ filterFunc = filter;
+ } else {
+ filterFunc = function(){
+ return true;
+ };
+ }
+ var n = this.nodes.length;
+ var node;
+ for ( var i = 0; i < n; i++ ) {
+ node = this.nodes[i];
+ if ( filterFunc.apply(node) ) {
+ addNodeToList(r, node);
+ }
+ }
+ return r;
+};
+
+/******************************************************************************/
+
+// TODO: Avoid possible duplicates
+
+DOMList.prototype.ancestors = function(selector) {
+ var r = new DOMList();
+ var n = this.nodes.length;
+ var node;
+ for ( var i = 0; i < n; i++ ) {
+ node = this.nodes[i].parentNode;
+ while ( node ) {
+ if ( doesMatchSelector(node, selector) ) {
+ addNodeToList(r, node);
+ }
+ node = node.parentNode;
+ }
+ }
+ return r;
+};
+
+/******************************************************************************/
+
+DOMList.prototype.descendants = function(selector) {
var r = new DOMList();
var n = this.nodes.length;
var nl;
@@ -187,28 +349,36 @@ DOMList.prototype.find = function(selector) {
/******************************************************************************/
-DOMList.prototype.forEach = function(callback) {
+DOMList.prototype.contents = function() {
+ var r = new DOMList();
+ var cnodes, cn, ci;
var n = this.nodes.length;
for ( var i = 0; i < n; i++ ) {
- callback.bind(this.nodes[i]).call();
+ cnodes = this.nodes[i].childNodes;
+ cn = cnodes.length;
+ for ( ci = 0; ci < cn; ci++ ) {
+ addNodeToList(r, cnodes.item(ci));
+ }
}
- return this;
+ return r;
};
/******************************************************************************/
DOMList.prototype.remove = function() {
- var n = this.nodes.length;
- var c, p;
- for ( var i = 0; i < n; i++ ) {
- c = this.nodes[i];
- if ( p = c.parentNode ) {
- p.removeChild(c);
+ var cn, p;
+ var i = this.nodes.length;
+ while ( i-- ) {
+ cn = this.nodes[i];
+ if ( p = cn.parentNode ) {
+ p.removeChild(cn);
}
}
return this;
};
+DOMList.prototype.detach = DOMList.prototype.remove;
+
/******************************************************************************/
DOMList.prototype.empty = function() {
@@ -254,12 +424,10 @@ DOMList.prototype.prepend = function(selector, context) {
/******************************************************************************/
DOMList.prototype.appendTo = function(selector, context) {
- var p = DOMListFactory(selector, context);
- if ( p.length ) {
- var n = this.nodes.length;
- for ( var i = 0; i < n; i++ ) {
- p.nodes[0].appendChild(this.nodes[i]);
- }
+ var p = selector instanceof DOMListFactory ? selector : DOMListFactory(selector, context);
+ var n = p.length;
+ for ( var i = 0; i < n; i++ ) {
+ p.nodes[0].appendChild(this.nodes[i]);
}
return this;
};
@@ -284,6 +452,28 @@ DOMList.prototype.insertAfter = function(selector, context) {
/******************************************************************************/
+DOMList.prototype.insertBefore = function(selector, context) {
+ if ( this.nodes.length === 0 ) {
+ return this;
+ }
+ var referenceNodes = DOMListFactory(selector, context);
+ if ( referenceNodes.nodes.length === 0 ) {
+ return this;
+ }
+ var referenceNode = referenceNodes.nodes[0];
+ var parentNode = referenceNode.parentNode;
+ if ( !parentNode ) {
+ return this;
+ }
+ var n = this.nodes.length;
+ for ( var i = 0; i < n; i++ ) {
+ parentNode.insertBefore(this.nodes[i], referenceNode);
+ }
+ return this;
+};
+
+/******************************************************************************/
+
DOMList.prototype.clone = function(notDeep) {
var r = new DOMList();
var n = this.nodes.length;
@@ -324,7 +514,7 @@ DOMList.prototype.attr = function(attr, value) {
DOMList.prototype.prop = function(prop, value) {
var i = this.nodes.length;
if ( value === undefined ) {
- return i ? this.nodes[0][prop] : undefined;
+ return i !== 0 ? this.nodes[0][prop] : undefined;
}
while ( i-- ) {
this.nodes[i][prop] = value;
@@ -379,13 +569,33 @@ DOMList.prototype.text = function(text) {
/******************************************************************************/
-DOMList.prototype.hasClassName = function(className) {
+var toggleClass = function(node, className, targetState) {
+ var tokenList = node.classList;
+ if ( tokenList instanceof DOMTokenList === false ) {
+ return;
+ }
+ var currentState = tokenList.contains(className);
+ var newState = targetState;
+ if ( newState === undefined ) {
+ newState = !currentState;
+ }
+ if ( newState === currentState ) {
+ return;
+ }
+ tokenList.toggle(className, newState)
+};
+
+/******************************************************************************/
+
+DOMList.prototype.hasClass = function(className) {
if ( !this.nodes.length ) {
return false;
}
- var re = new RegExp('(^| )' + className + '( |$)');
- return re.test(this.nodes[0].className);
+ var tokenList = this.nodes[0].classList;
+ return tokenList instanceof DOMTokenList &&
+ tokenList.contains(className);
};
+DOMList.prototype.hasClassName = DOMList.prototype.hasClass;
DOMList.prototype.addClass = function(className) {
return this.toggleClass(className, true);
@@ -402,49 +612,46 @@ DOMList.prototype.removeClass = function(className) {
return this;
};
+/******************************************************************************/
+
DOMList.prototype.toggleClass = function(className, targetState) {
- var re = new RegExp('(^| )' + className + '( |$)');
- var n = this.nodes.length;
- var node, currentState, newState, newClassName;
- for ( var i = 0; i < n; i++ ) {
- node = this.nodes[i];
- currentState = re.test(node.className);
- newState = targetState;
- if ( newState === undefined ) {
- newState = !currentState;
- }
- if ( newState === currentState ) {
- continue;
- }
- newClassName = node.className;
- if ( newState ) {
- newClassName += ' ' + className;
- } else {
- newClassName = newClassName.replace(re, ' ');
- }
- node.className = newClassName.trim();
+ if ( className.indexOf(' ') !== -1 ) {
+ return this.toggleClasses(className, true);
+ }
+ var i = this.nodes.length;
+ while ( i-- ) {
+ toggleClass(this.nodes[i], className, targetState);
}
return this;
};
/******************************************************************************/
-var makeEventHandler = function(context, selector, callback) {
+DOMList.prototype.toggleClasses = function(classNames, targetState) {
+ var tokens = classNames.split(/\s+/);
+ var i = this.nodes.length;
+ var node, j;
+ while ( i-- ) {
+ node = this.nodes[i];
+ j = tokens.length;
+ while ( j-- ) {
+ toggleClass(node, tokens[j], targetState);
+ }
+ }
+ return this;
+};
+
+/******************************************************************************/
+
+var makeEventHandler = function(selector, callback) {
return function(event) {
- var candidates = context.querySelectorAll(selector);
- if ( !candidates.length ) {
+ var dispatcher = event.currentTarget;
+ if ( !dispatcher || typeof dispatcher.querySelectorAll !== 'function' ) {
return;
}
- var node = event.target;
- var i;
- while ( node && node !== context ) {
- i = candidates.length;
- while ( i-- ) {
- if ( candidates[i] === node ) {
- return callback.call(node, event);
- }
- }
- node = node.parentNode;
+ var receiver = event.target;
+ if ( nodeInNodeList(receiver, dispatcher.querySelectorAll(selector)) ) {
+ callback.call(receiver, event);
}
};
};
@@ -453,14 +660,13 @@ DOMList.prototype.on = function(etype, selector, callback) {
if ( typeof selector === 'function' ) {
callback = selector;
selector = undefined;
+ } else {
+ callback = makeEventHandler(selector, callback);
}
+
var i = this.nodes.length;
while ( i-- ) {
- if ( selector !== undefined ) {
- this.nodes[i].addEventListener(etype, makeEventHandler(this.nodes[i], selector, callback), true);
- } else {
- this.nodes[i].addEventListener(etype, callback);
- }
+ this.nodes[i].addEventListener(etype, callback, selector !== undefined);
}
return this;
};