diff --git a/lib/demo/index.js b/lib/demo/index.js
index f5d39d92..e08fdc26 100644
--- a/lib/demo/index.js
+++ b/lib/demo/index.js
@@ -26,6 +26,7 @@ require('gcli/commands/help').startup();
require('gcli/commands/intro').startup();
require('gcli/commands/pref').startup();
require('gcli/commands/pref_list').startup();
+require('gcli/commands/qsa').startup();
require('demo/commands/basic').startup();
require('demo/commands/bugs').startup();
diff --git a/lib/gcli/commands/qsa.css b/lib/gcli/commands/qsa.css
new file mode 100644
index 00000000..26905d7a
--- /dev/null
+++ b/lib/gcli/commands/qsa.css
@@ -0,0 +1,23 @@
+.qsa-node-list, .qsa-node-attr-list, .qsa-node-attr-list .qsa-node-attr {
+ margin: 0;
+ padding: 0;
+ display: inline;
+}
+.qsa-node-list {
+ color: #999;
+}
+.qsa-node-list .qsa-node {
+ display: inline-block;
+ background: #F2F2F2;
+ border: 1px solid #D6D6D6;
+ border-radius: .2em;
+ margin: .2em;
+ padding: .5em;
+ cursor: pointer;
+}
+.qsa-node-attr-name {
+ color: #7ED3E8;
+}
+.qsa-node-attr-value {
+ color: #F20505;
+}
diff --git a/lib/gcli/commands/qsa.html b/lib/gcli/commands/qsa.html
new file mode 100644
index 00000000..0e14082a
--- /dev/null
+++ b/lib/gcli/commands/qsa.html
@@ -0,0 +1,13 @@
+
+
${l10n.lookupFormat('qsaResultIntroText', [nodes.length, selector])}${l10n.lookup('qsaResultsTooMany')}
+
+ -
+ <${node.tagName.toLowerCase()}
+
+ -
+ ${attr.name}="${attr.value}"
+
+
/>
+
+
+
diff --git a/lib/gcli/commands/qsa.js b/lib/gcli/commands/qsa.js
new file mode 100644
index 00000000..ee8eb351
--- /dev/null
+++ b/lib/gcli/commands/qsa.js
@@ -0,0 +1,105 @@
+define(function(require, exports, module) {
+
+'use strict';
+
+var util = require('util/util');
+var l10n = require('util/l10n');
+var gcli = require('gcli/index');
+
+var qsaViewHtml = require('text!gcli/commands/qsa.html');
+var qsaViewCss = require('text!gcli/commands/qsa.css');
+
+/**
+ * Transform a nodeList into an array.
+ * Useful for forEach'ing for example.
+ * @param nodeList The nodeList as returned by querySelectorAll
+ * @return The transformed array
+ */
+function nodesToArray(nodeList) {
+ return Array.prototype.slice.call(nodeList);
+}
+
+/**
+ * QuerySelectorAll (qsa) command spec
+ */
+var qsaCmdSpec = {
+ name: 'qsa',
+ description: l10n.lookup('qsaDesc'),
+ manual: l10n.lookup('qsaManual'),
+ params: [{
+ name: 'query',
+ type: 'string',
+ defaultValue: '*',
+ description: l10n.lookup('qsaQueryDesc'),
+ }],
+ returnType: 'qsaCmdOutput',
+ exec: function(args, context) {
+ var out = {
+ nodes: [],
+ selector: args.query
+ };
+
+ // An invalid selector may lead to a SyntaxError exception e.g. '3' or ''
+ try {
+ out.nodes = nodesToArray(context.document.querySelectorAll(args.query));
+ } catch(e) {}
+
+ // Converting the nodes and attributes to plain objects to allow JSONing
+ out.nodes.forEach(function(node, index, nodeList) {
+ var attributes = nodesToArray(node.attributes);
+ attributes.forEach(function(attr, index, attrList) {
+ attrList[index] = {
+ name: attr.name,
+ value: attr.value
+ };
+ });
+
+ nodeList[index] = {
+ tagName: node.tagName,
+ attributes: attributes,
+ selector: util.findCssSelector(node)
+ };
+ });
+
+ return out;
+ }
+};
+
+/**
+ * QuerySelectorAll (qsa) command output converter
+ * qsaCmdOutput -> View
+ */
+var qsaConverterSpec = {
+ from: 'qsaCmdOutput',
+ to: 'view',
+ exec: function(qsaCmdOutput, context) {
+ return context.createView({
+ html: qsaViewHtml,
+ css: qsaViewCss,
+ options: {allowEval: true, stack: 'qsa.html'},
+ data: {
+ onclick: function(ev) {
+ context.exec({
+ command: 'qsa',
+ args: {query: ev.currentTarget.dataset.selector}
+ });
+ },
+ selector: qsaCmdOutput.selector,
+ nodes: qsaCmdOutput.nodes,
+ maxDisplayedNodes: 100,
+ l10n: l10n
+ }
+ });
+ }
+};
+
+exports.startup = function() {
+ gcli.addCommand(qsaCmdSpec);
+ gcli.addConverter(qsaConverterSpec);
+};
+exports.shutdown = function() {
+ gcli.removeCommand(qsaCmdSpec);
+ gcli.removeConverter(qsaConverterSpec);
+};
+
+});
diff --git a/lib/gcli/nls/strings.js b/lib/gcli/nls/strings.js
index e0a48dd0..e0368019 100644
--- a/lib/gcli/nls/strings.js
+++ b/lib/gcli/nls/strings.js
@@ -433,7 +433,25 @@ var i18n = {
// Short description of the 'allowSetDesc' setting. Displayed when the user
// asks for help on the settings.
- allowSetDesc: 'Has the user enabled the \'pref set\' command?'
+ allowSetDesc: 'Has the user enabled the \'pref set\' command?',
+
+ // A very short description of the 'qsa' command.
+ qsaDesc: 'Execute querySelectorAll given a selector and show matches',
+
+ // A fuller description of the 'qsa' command.
+ qsaManual: 'Execute querySelectorAll on the current document and show the number of matched elements as well as the elements themselves (provided the selector returns less than 100 nodes)',
+
+ // A very short description of the 'query' parameter to the 'qsa' command.
+ qsaQueryDesc: 'The CSS selector to use, or several selectors seperated by commas',
+
+ // The intro text to the 'qsa' command shows after the command has been run and gives the number of nodes matched
+ qsaResultIntroText: '%1$S node(s) found for selector %2$S',
+
+ // The too many results message of the 'qsa' command shows when there are more than X nodes matched by the query, to request the user to refine it
+ qsaResultsTooMany: ', please refine your selector to display the resulting nodes.',
+
+ // The tooltip text is displayed on hovering over a matched node, to inform the user of the action executed on click
+ qsaNodeTooltip: 'Click to run qsa with selector %S'
}
};
exports.root = i18n.root;