Bug 773347 - GCLI hidden commands don't execute properly; r=harth
authorJoe Walker <jwalker@mozilla.com>
Fri, 24 Aug 2012 11:05:04 +0100
changeset 105418 6b00d1edb2e717b0d658dc3408fae6f38add9b02
parent 105417 828067f7c340b7a62b17eeac5a0a2ba2cd059486
child 105419 7bf846af58d35674c1b02f8dbfa260f6012a78d5
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersharth
bugs773347
milestone17.0a1
Bug 773347 - GCLI hidden commands don't execute properly; r=harth
browser/devtools/commandline/CmdCmd.jsm
browser/devtools/commandline/gcli.jsm
browser/devtools/commandline/test/browser_cmd_commands.js
browser/devtools/commandline/test/browser_cmd_jsb.js
browser/devtools/commandline/test/browser_gcli_web.js
--- a/browser/devtools/commandline/CmdCmd.jsm
+++ b/browser/devtools/commandline/CmdCmd.jsm
@@ -15,16 +15,18 @@ XPCOMUtils.defineLazyGetter(this, "prefB
   return prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2);
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "console",
                                   "resource:///modules/devtools/Console.jsm");
 
+const PREF_DIR = "devtools.commands.dir";
+
 /**
  * A place to store the names of the commands that we have added as a result of
  * calling refreshAutoCommands(). Used by refreshAutoCommands to remove the
  * added commands.
  */
 let commands = [];
 
 /**
@@ -38,18 +40,18 @@ let CmdCommands = {
    * we eval the script from the .mozcmd file. This should be a chrome window.
    */
   refreshAutoCommands: function GC_refreshAutoCommands(aSandboxPrincipal) {
     // First get rid of the last set of commands
     commands.forEach(function(name) {
       gcli.removeCommand(name);
     });
 
-    let dirName = prefBranch.getComplexValue("devtools.commands.dir",
-                                             Ci.nsISupportsString).data;
+    let dirName = prefBranch.getComplexValue(PREF_DIR,
+                                             Ci.nsISupportsString).data.trim();
     if (dirName == "") {
       return;
     }
 
     let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
     dir.initWithPath(dirName);
     if (!dir.exists() || !dir.isDirectory()) {
       throw new Error('\'' + dirName + '\' is not a directory.');
@@ -104,23 +106,24 @@ function loadCommandFile(aFile, aSandbox
   }.bind(this));
 }
 
 /**
  * 'cmd' command
  */
 gcli.addCommand({
   name: "cmd",
-  description: gcli.lookup("cmdDesc"),
-  hidden: true
+  get hidden() { return !prefBranch.prefHasUserValue(PREF_DIR); },
+  description: gcli.lookup("cmdDesc")
 });
 
 /**
  * 'cmd refresh' command
  */
 gcli.addCommand({
   name: "cmd refresh",
   description: gcli.lookup("cmdRefreshDesc"),
-  hidden: true,
+  get hidden() { return !prefBranch.prefHasUserValue(PREF_DIR); },
   exec: function Command_cmdRefresh(args, context) {
-    GcliCmdCommands.refreshAutoCommands(context.environment.chromeDocument.defaultView);
+    let chromeWindow = context.environment.chromeDocument.defaultView;
+    GcliCommands.refreshAutoCommands(chromeWindow);
   }
 });
--- a/browser/devtools/commandline/gcli.jsm
+++ b/browser/devtools/commandline/gcli.jsm
@@ -1792,29 +1792,40 @@ SelectionType.prototype._findPredictions
   // Cache lower case versions of all the option names
   for (i = 0; i < lookup.length; i++) {
     option = lookup[i];
     if (option._gcliLowerName == null) {
       option._gcliLowerName = option.name.toLowerCase();
     }
   }
 
+  // Exact hidden matches. If 'hidden: true' then we only allow exact matches
+  // All the tests after here check that !option.value.hidden
+  for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+    option = lookup[i];
+    if (option.name === arg.text) {
+      this._addToPredictions(predictions, option, arg);
+    }
+  }
+
   // Start with prefix matching
   for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
     option = lookup[i];
-    if (option._gcliLowerName.indexOf(match) === 0) {
-      this._addToPredictions(predictions, option, arg);
+    if (option._gcliLowerName.indexOf(match) === 0 && !option.value.hidden) {
+      if (predictions.indexOf(option) === -1) {
+        this._addToPredictions(predictions, option, arg);
+      }
     }
   }
 
   // Try infix matching if we get less half max matched
   if (predictions.length < (maxPredictions / 2)) {
     for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
       option = lookup[i];
-      if (option._gcliLowerName.indexOf(match) !== -1) {
+      if (option._gcliLowerName.indexOf(match) !== -1 && !option.value.hidden) {
         if (predictions.indexOf(option) === -1) {
           this._addToPredictions(predictions, option, arg);
         }
       }
     }
   }
 
   // Try fuzzy matching if we don't get a prefix match
@@ -2186,19 +2197,16 @@ CommandType.prototype.lookup = function(
     return { name: command.name, value: command };
   }, this);
 };
 
 /**
  * Add an option to our list of predicted options
  */
 CommandType.prototype._addToPredictions = function(predictions, option, arg) {
-  if (option.value.hidden) {
-    return;
-  }
   // The command type needs to exclude sub-commands when the CLI
   // is blank, but include them when we're filtering. This hack
   // excludes matches when the filter text is '' and when the
   // name includes a space.
   if (arg.text.length !== 0 || option.name.indexOf(' ') === -1) {
     predictions.push(option);
   }
 };
@@ -2505,16 +2513,26 @@ Object.defineProperty(Parameter.prototyp
 Object.defineProperty(Parameter.prototype, 'isDataRequired', {
   get: function() {
     return this.defaultValue === undefined;
   },
   enumerable: true
 });
 
 /**
+ * Reflect the paramSpec 'hidden' property (dynamically so it can change)
+ */
+Object.defineProperty(Parameter.prototype, 'hidden', {
+  get: function() {
+    return this.paramSpec.hidden;
+  },
+  enumerable: true
+});
+
+/**
  * Are we allowed to assign data to this parameter using positional
  * parameters?
  */
 Object.defineProperty(Parameter.prototype, 'isPositionalAllowed', {
   get: function() {
     return this.groupName == null;
   },
   enumerable: true
--- a/browser/devtools/commandline/test/browser_cmd_commands.js
+++ b/browser/devtools/commandline/test/browser_cmd_commands.js
@@ -8,23 +8,21 @@ Components.utils.import("resource:///mod
 
 const TEST_URI = "data:text/html;charset=utf-8,gcli-commands";
 
 function test() {
   DeveloperToolbarTest.test(TEST_URI, [ testEcho, testConsole ]);
 }
 
 function testEcho() {
-  /*
   DeveloperToolbarTest.exec({
     typed: "echo message",
     args: { message: "message" },
     outputMatch: /^message$/,
   });
-  */
 }
 
 function testConsole(browser, tab) {
   let hud = null;
   function onWebConsoleOpen(aSubject) {
     Services.obs.removeObserver(onWebConsoleOpen, "web-console-created");
 
     aSubject.QueryInterface(Ci.nsISupportsString);
--- a/browser/devtools/commandline/test/browser_cmd_jsb.js
+++ b/browser/devtools/commandline/test/browser_cmd_jsb.js
@@ -2,17 +2,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the jsb command works as it should
 
 const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" +
                  "test/browser_cmd_jsb_script.jsi";
 
 function test() {
-  DeveloperToolbarTest.test("about:blank", [ /*GJT_test*/ ]);
+  DeveloperToolbarTest.test("about:blank", [ GJT_test ]);
 }
 
 function GJT_test() {
   DeveloperToolbarTest.exec({
     typed: "jsb AAA",
     outputMatch: /valid/
   });
 
--- a/browser/devtools/commandline/test/browser_gcli_web.js
+++ b/browser/devtools/commandline/test/browser_gcli_web.js
@@ -1728,16 +1728,17 @@ exports.setup = function() {
   canon.addCommand(exports.tsnExtend);
   canon.addCommand(exports.tsnDeep);
   canon.addCommand(exports.tsnDeepDown);
   canon.addCommand(exports.tsnDeepDownNested);
   canon.addCommand(exports.tsnDeepDownNestedCmd);
   canon.addCommand(exports.tselarr);
   canon.addCommand(exports.tsm);
   canon.addCommand(exports.tsg);
+  canon.addCommand(exports.tshidden);
   canon.addCommand(exports.tscook);
 };
 
 exports.shutdown = function() {
   canon.removeCommand(exports.tsv);
   canon.removeCommand(exports.tsr);
   canon.removeCommand(exports.tse);
   canon.removeCommand(exports.tsj);
@@ -1752,16 +1753,17 @@ exports.shutdown = function() {
   canon.removeCommand(exports.tsnExtend);
   canon.removeCommand(exports.tsnDeep);
   canon.removeCommand(exports.tsnDeepDown);
   canon.removeCommand(exports.tsnDeepDownNested);
   canon.removeCommand(exports.tsnDeepDownNestedCmd);
   canon.removeCommand(exports.tselarr);
   canon.removeCommand(exports.tsm);
   canon.removeCommand(exports.tsg);
+  canon.removeCommand(exports.tshidden);
   canon.removeCommand(exports.tscook);
 
   types.deregisterType(exports.optionType);
   types.deregisterType(exports.optionValue);
 };
 
 
 exports.option1 = { type: types.getType('string') };
@@ -1906,16 +1908,48 @@ exports.tsnDeepDownNested = {
   name: 'tsn deep down nested',
 };
 
 exports.tsnDeepDownNestedCmd = {
   name: 'tsn deep down nested cmd',
   exec: createExec('tsnDeepDownNestedCmd')
 };
 
+exports.tshidden = {
+  name: 'tshidden',
+  hidden: true,
+  params: [
+    {
+      group: 'Options',
+      params: [
+        {
+          name: 'visible',
+          type: 'string',
+          defaultValue: null,
+          description: 'visible'
+        },
+        {
+          name: 'invisiblestring',
+          type: 'string',
+          description: 'invisiblestring',
+          defaultValue: null,
+          hidden: true
+        },
+        {
+          name: 'invisibleboolean',
+          type: 'boolean',
+          description: 'invisibleboolean',
+          hidden: true
+        }
+      ]
+    }
+  ],
+  exec: createExec('tshidden')
+};
+
 exports.tselarr = {
   name: 'tselarr',
   params: [
     { name: 'num', type: { name: 'selection', data: [ '1', '2', '3' ] } },
     { name: 'arr', type: { name: 'array', subtype: 'string' } },
   ],
   exec: createExec('tselarr')
 };
@@ -3005,16 +3039,132 @@ exports.testIncomplete = function(option
   });
   test.is(requisition._unassigned[0], requisition.getAssignmentAt(5),
           'unassigned -');
   test.is(requisition._unassigned.length, 1, 'single unassigned - tsg -');
   test.is(requisition._unassigned[0].param.type.isIncompleteName, true,
           'unassigned.isIncompleteName: tsg -');
 };
 
+exports.testHidden = function(options) {
+  helpers.setInput('tshidde');
+  helpers.check({
+    input:  'tshidde',
+    markup: 'EEEEEEE',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ],
+  });
+
+  helpers.setInput('tshidden');
+  helpers.check({
+    input:  'tshidden',
+    markup: 'VVVVVVVV',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'VALID',
+    emptyParameters: [ ],
+    args: {
+      visible: { value: undefined, status: 'VALID' },
+      invisiblestring: { value: undefined, status: 'VALID' },
+      invisibleboolean: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  helpers.setInput('tshidden --vis');
+  helpers.check({
+    input:  'tshidden --vis',
+    markup: 'VVVVVVVVVIIIII',
+    directTabText: 'ible',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ],
+    args: {
+      visible: { value: undefined, status: 'VALID' },
+      invisiblestring: { value: undefined, status: 'VALID' },
+      invisibleboolean: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  helpers.setInput('tshidden --invisiblestrin');
+  helpers.check({
+    input:  'tshidden --invisiblestrin',
+    markup: 'VVVVVVVVVEEEEEEEEEEEEEEEE',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ],
+    args: {
+      visible: { value: undefined, status: 'VALID' },
+      invisiblestring: { value: undefined, status: 'VALID' },
+      invisibleboolean: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  helpers.setInput('tshidden --invisiblestring');
+  helpers.check({
+    input:  'tshidden --invisiblestring',
+    markup: 'VVVVVVVVVIIIIIIIIIIIIIIIII',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ],
+    args: {
+      visible: { value: undefined, status: 'VALID' },
+      invisiblestring: { value: undefined, status: 'INCOMPLETE' },
+      invisibleboolean: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  helpers.setInput('tshidden --invisiblestring x');
+  helpers.check({
+    input:  'tshidden --invisiblestring x',
+    markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'VALID',
+    emptyParameters: [ ],
+    args: {
+      visible: { value: undefined, status: 'VALID' },
+      invisiblestring: { value: 'x', status: 'VALID' },
+      invisibleboolean: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  helpers.setInput('tshidden --invisibleboolea');
+  helpers.check({
+    input:  'tshidden --invisibleboolea',
+    markup: 'VVVVVVVVVEEEEEEEEEEEEEEEEE',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'ERROR',
+    emptyParameters: [ ],
+    args: {
+      visible: { value: undefined, status: 'VALID' },
+      invisiblestring: { value: undefined, status: 'VALID' },
+      invisibleboolean: { value: undefined, status: 'VALID' }
+    }
+  });
+
+  helpers.setInput('tshidden --invisibleboolean');
+  helpers.check({
+    input:  'tshidden --invisibleboolean',
+    markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVV',
+    directTabText: '',
+    arrowTabText: '',
+    status: 'VALID',
+    emptyParameters: [ ],
+    args: {
+      visible: { value: undefined, status: 'VALID' },
+      invisiblestring: { value: undefined, status: 'VALID' },
+      invisibleboolean: { value: true, status: 'VALID' }
+    }
+  });
+};
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at