зеркало из https://github.com/mozilla/pjs.git
Bug 585991 - Show a popup listing possible completions; r=rcampbell,dtownsend sr=neil
This commit is contained in:
Родитель
c6784a3d8a
Коммит
76116cd872
|
@ -0,0 +1,395 @@
|
||||||
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||||
|
/* ***** BEGIN LICENSE BLOCK *****
|
||||||
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||||||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
* http://www.mozilla.org/MPL/
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||||
|
* for the specific language governing rights and limitations under the
|
||||||
|
* License.
|
||||||
|
*
|
||||||
|
* The Original Code is Autocomplete Popup.
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is
|
||||||
|
* The Mozilla Foundation.
|
||||||
|
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
* Mihai Sucan <mihai.sucan@gmail.com> (original author)
|
||||||
|
*
|
||||||
|
* Alternatively, the contents of this file may be used under the terms of
|
||||||
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||||
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||||
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||||
|
* of those above. If you wish to allow use of your version of this file only
|
||||||
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||||
|
* use your version of this file under the terms of the MPL, indicate your
|
||||||
|
* decision by deleting the provisions above and replace them with the notice
|
||||||
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||||
|
* the provisions above, a recipient may use your version of this file under
|
||||||
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||||
|
*
|
||||||
|
* ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
|
const Cu = Components.utils;
|
||||||
|
|
||||||
|
// The XUL and XHTML namespace.
|
||||||
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||||
|
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||||
|
|
||||||
|
const HUD_STRINGS_URI = "chrome://global/locale/headsUpDisplay.properties";
|
||||||
|
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "stringBundle", function () {
|
||||||
|
return Services.strings.createBundle(HUD_STRINGS_URI);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["AutocompletePopup"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autocomplete popup UI implementation.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param nsIDOMDocument aDocument
|
||||||
|
* The document you want the popup attached to.
|
||||||
|
*/
|
||||||
|
function AutocompletePopup(aDocument)
|
||||||
|
{
|
||||||
|
this._document = aDocument;
|
||||||
|
|
||||||
|
// Reuse the existing popup elements.
|
||||||
|
this._panel = this._document.getElementById("webConsole_autocompletePopup");
|
||||||
|
if (!this._panel) {
|
||||||
|
this._panel = this._document.createElementNS(XUL_NS, "panel");
|
||||||
|
this._panel.setAttribute("id", "webConsole_autocompletePopup");
|
||||||
|
this._panel.setAttribute("label",
|
||||||
|
stringBundle.GetStringFromName("Autocomplete.label"));
|
||||||
|
this._panel.setAttribute("noautofocus", "true");
|
||||||
|
this._panel.setAttribute("ignorekeys", "true");
|
||||||
|
|
||||||
|
let mainPopupSet = this._document.getElementById("mainPopupSet");
|
||||||
|
if (mainPopupSet) {
|
||||||
|
mainPopupSet.appendChild(this._panel);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._document.documentElement.appendChild(this._panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._list = this._document.createElementNS(XUL_NS, "richlistbox");
|
||||||
|
this._list.flex = 1;
|
||||||
|
this._panel.appendChild(this._list);
|
||||||
|
|
||||||
|
// Open and hide the panel, so we initialize the API of the richlistbox.
|
||||||
|
this._panel.width = 1;
|
||||||
|
this._panel.height = 1;
|
||||||
|
this._panel.openPopup(null, "overlap", 0, 0, false, false);
|
||||||
|
this._panel.hidePopup();
|
||||||
|
this._panel.width = "";
|
||||||
|
this._panel.height = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._list = this._panel.firstChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AutocompletePopup.prototype = {
|
||||||
|
_document: null,
|
||||||
|
_panel: null,
|
||||||
|
_list: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the autocomplete popup panel.
|
||||||
|
*
|
||||||
|
* @param nsIDOMNode aAnchor
|
||||||
|
* Optional node to anchor the panel to.
|
||||||
|
*/
|
||||||
|
openPopup: function AP_openPopup(aAnchor)
|
||||||
|
{
|
||||||
|
this._panel.openPopup(aAnchor, "after_start", 0, 0, false, false);
|
||||||
|
|
||||||
|
if (this.onSelect) {
|
||||||
|
this._list.addEventListener("select", this.onSelect, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.onClick) {
|
||||||
|
this._list.addEventListener("click", this.onClick, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateSize();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the autocomplete popup panel.
|
||||||
|
*/
|
||||||
|
hidePopup: function AP_hidePopup()
|
||||||
|
{
|
||||||
|
this._panel.hidePopup();
|
||||||
|
|
||||||
|
if (this.onSelect) {
|
||||||
|
this._list.removeEventListener("select", this.onSelect, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.onClick) {
|
||||||
|
this._list.removeEventListener("click", this.onClick, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the autocomplete popup is open.
|
||||||
|
*/
|
||||||
|
get isOpen() {
|
||||||
|
return this._panel.state == "open";
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the object instance. Please note that the panel DOM elements remain
|
||||||
|
* in the DOM, because they might still be in use by other instances of the
|
||||||
|
* same code. It is the responsability of the client code to perform DOM
|
||||||
|
* cleanup.
|
||||||
|
*/
|
||||||
|
destroy: function AP_destroy()
|
||||||
|
{
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.hidePopup();
|
||||||
|
}
|
||||||
|
this.clearItems();
|
||||||
|
|
||||||
|
this._document = null;
|
||||||
|
this._list = null;
|
||||||
|
this._panel = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the autocomplete items array.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* The array of autocomplete items.
|
||||||
|
*/
|
||||||
|
getItems: function AP_getItems()
|
||||||
|
{
|
||||||
|
let items = [];
|
||||||
|
|
||||||
|
Array.forEach(this._list.childNodes, function(aItem) {
|
||||||
|
items.push(aItem._autocompleteItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the autocomplete items list, in one go.
|
||||||
|
*
|
||||||
|
* @param array aItems
|
||||||
|
* The list of items you want displayed in the popup list.
|
||||||
|
*/
|
||||||
|
setItems: function AP_setItems(aItems)
|
||||||
|
{
|
||||||
|
this.clearItems();
|
||||||
|
aItems.forEach(this.appendItem, this);
|
||||||
|
|
||||||
|
// Make sure that the new content is properly fitted by the XUL richlistbox.
|
||||||
|
if (this.isOpen) {
|
||||||
|
// We need the timeout to allow the content to reflow. Attempting to
|
||||||
|
// update the richlistbox size too early does not work.
|
||||||
|
this._document.defaultView.setTimeout(this._updateSize.bind(this), 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the panel size to fit the content.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_updateSize: function AP__updateSize()
|
||||||
|
{
|
||||||
|
this._list.width = this._panel.clientWidth +
|
||||||
|
this._scrollbarWidth;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all the items from the autocomplete list.
|
||||||
|
*/
|
||||||
|
clearItems: function AP_clearItems()
|
||||||
|
{
|
||||||
|
while (this._list.hasChildNodes()) {
|
||||||
|
this._list.removeChild(this._list.firstChild);
|
||||||
|
}
|
||||||
|
this._list.width = "";
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the index of the selected item.
|
||||||
|
*
|
||||||
|
* @type number
|
||||||
|
*/
|
||||||
|
get selectedIndex() {
|
||||||
|
return this._list.selectedIndex;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for the selected index.
|
||||||
|
*
|
||||||
|
* @param number aIndex
|
||||||
|
* The number (index) of the item you want to select in the list.
|
||||||
|
*/
|
||||||
|
set selectedIndex(aIndex) {
|
||||||
|
this._list.selectedIndex = aIndex;
|
||||||
|
this._list.ensureIndexIsVisible(this._list.selectedIndex);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the selected item.
|
||||||
|
* @type object
|
||||||
|
*/
|
||||||
|
get selectedItem() {
|
||||||
|
return this._list.selectedItem ?
|
||||||
|
this._list.selectedItem._autocompleteItem : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for the selected item.
|
||||||
|
*
|
||||||
|
* @param object aItem
|
||||||
|
* The object you want selected in the list.
|
||||||
|
*/
|
||||||
|
set selectedItem(aItem) {
|
||||||
|
this._list.selectedItem = this._findListItem(aItem);
|
||||||
|
this._list.ensureIndexIsVisible(this._list.selectedIndex);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append an item into the autocomplete list.
|
||||||
|
*
|
||||||
|
* @param object aItem
|
||||||
|
* The item you want appended to the list. The object must have a
|
||||||
|
* "label" property which is used as the displayed value.
|
||||||
|
*/
|
||||||
|
appendItem: function AP_appendItem(aItem)
|
||||||
|
{
|
||||||
|
let description = this._document.createElementNS(XUL_NS, "description");
|
||||||
|
description.textContent = aItem.label;
|
||||||
|
|
||||||
|
let listItem = this._document.createElementNS(XUL_NS, "richlistitem");
|
||||||
|
listItem.appendChild(description);
|
||||||
|
listItem._autocompleteItem = aItem;
|
||||||
|
|
||||||
|
this._list.appendChild(listItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the richlistitem element that belongs to an item.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param object aItem
|
||||||
|
* The object you want found in the list.
|
||||||
|
*
|
||||||
|
* @return nsIDOMNode|null
|
||||||
|
* The nsIDOMNode that belongs to the given item object. This node is
|
||||||
|
* the richlistitem element.
|
||||||
|
*/
|
||||||
|
_findListItem: function AP__findListItem(aItem)
|
||||||
|
{
|
||||||
|
for (let i = 0; i < this._list.childNodes.length; i++) {
|
||||||
|
let child = this._list.childNodes[i];
|
||||||
|
if (child._autocompleteItem == aItem) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an item from the popup list.
|
||||||
|
*
|
||||||
|
* @param object aItem
|
||||||
|
* The item you want removed.
|
||||||
|
*/
|
||||||
|
removeItem: function AP_removeItem(aItem)
|
||||||
|
{
|
||||||
|
let item = this._findListItem(aItem);
|
||||||
|
if (!item) {
|
||||||
|
throw new Error("Item not found!");
|
||||||
|
}
|
||||||
|
this._list.removeChild(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the number of items in the popup.
|
||||||
|
* @type number
|
||||||
|
*/
|
||||||
|
get itemCount() {
|
||||||
|
return this._list.childNodes.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the next item in the list.
|
||||||
|
*
|
||||||
|
* @return object
|
||||||
|
* The newly selected item object.
|
||||||
|
*/
|
||||||
|
selectNextItem: function AP_selectNextItem()
|
||||||
|
{
|
||||||
|
if (this.selectedIndex < (this.itemCount - 1)) {
|
||||||
|
this.selectedIndex++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.selectedIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.selectedItem;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the previous item in the list.
|
||||||
|
*
|
||||||
|
* @return object
|
||||||
|
* The newly selected item object.
|
||||||
|
*/
|
||||||
|
selectPreviousItem: function AP_selectPreviousItem()
|
||||||
|
{
|
||||||
|
if (this.selectedIndex > -1) {
|
||||||
|
this.selectedIndex--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.selectedIndex = this.itemCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.selectedItem;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the scrollbar width in the current document.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
get _scrollbarWidth()
|
||||||
|
{
|
||||||
|
if (this.__scrollbarWidth) {
|
||||||
|
return this.__scrollbarWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hbox = this._document.createElementNS(XUL_NS, "hbox");
|
||||||
|
hbox.setAttribute("style", "height: 0%; overflow: hidden");
|
||||||
|
|
||||||
|
let scrollbar = this._document.createElementNS(XUL_NS, "scrollbar");
|
||||||
|
scrollbar.setAttribute("orient", "vertical");
|
||||||
|
hbox.appendChild(scrollbar);
|
||||||
|
|
||||||
|
this._document.documentElement.appendChild(hbox);
|
||||||
|
this.__scrollbarWidth = scrollbar.clientWidth;
|
||||||
|
this._document.documentElement.removeChild(hbox);
|
||||||
|
|
||||||
|
return this.__scrollbarWidth;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -86,6 +86,17 @@ XPCOMUtils.defineLazyGetter(this, "PropertyPanel", function () {
|
||||||
return obj.PropertyPanel;
|
return obj.PropertyPanel;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "AutocompletePopup", function () {
|
||||||
|
var obj = {};
|
||||||
|
try {
|
||||||
|
Cu.import("resource://gre/modules/AutocompletePopup.jsm", obj);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
Cu.reportError(err);
|
||||||
|
}
|
||||||
|
return obj.AutocompletePopup;
|
||||||
|
});
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "namesAndValuesOf", function () {
|
XPCOMUtils.defineLazyGetter(this, "namesAndValuesOf", function () {
|
||||||
var obj = {};
|
var obj = {};
|
||||||
Cu.import("resource:///modules/PropertyPanel.jsm", obj);
|
Cu.import("resource:///modules/PropertyPanel.jsm", obj);
|
||||||
|
@ -524,8 +535,10 @@ ResponseListener.prototype =
|
||||||
function createElement(aDocument, aTag, aAttributes)
|
function createElement(aDocument, aTag, aAttributes)
|
||||||
{
|
{
|
||||||
let node = aDocument.createElement(aTag);
|
let node = aDocument.createElement(aTag);
|
||||||
for (var attr in aAttributes) {
|
if (aAttributes) {
|
||||||
node.setAttribute(attr, aAttributes[attr]);
|
for (let attr in aAttributes) {
|
||||||
|
node.setAttribute(attr, aAttributes[attr]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -1763,6 +1776,8 @@ HUD_SERVICE.prototype =
|
||||||
ownerDoc = outputNode.ownerDocument;
|
ownerDoc = outputNode.ownerDocument;
|
||||||
ownerDoc.getElementById(id).parentNode.removeChild(outputNode);
|
ownerDoc.getElementById(id).parentNode.removeChild(outputNode);
|
||||||
|
|
||||||
|
this.hudReferences[id].jsterm.autocompletePopup.destroy();
|
||||||
|
|
||||||
this.hudReferences[id].consoleWindowUnregisterOnHide = false;
|
this.hudReferences[id].consoleWindowUnregisterOnHide = false;
|
||||||
|
|
||||||
// remove the HeadsUpDisplay object from memory
|
// remove the HeadsUpDisplay object from memory
|
||||||
|
@ -1791,6 +1806,12 @@ HUD_SERVICE.prototype =
|
||||||
Services.obs.notifyObservers(id, "web-console-destroyed", null);
|
Services.obs.notifyObservers(id, "web-console-destroyed", null);
|
||||||
|
|
||||||
if (Object.keys(this.hudReferences).length == 0) {
|
if (Object.keys(this.hudReferences).length == 0) {
|
||||||
|
let autocompletePopup = outputNode.ownerDocument.
|
||||||
|
getElementById("webConsole_autocompletePopup");
|
||||||
|
if (autocompletePopup) {
|
||||||
|
autocompletePopup.parentNode.removeChild(autocompletePopup);
|
||||||
|
}
|
||||||
|
|
||||||
this.suspend();
|
this.suspend();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -4158,24 +4179,23 @@ function JSPropertyProvider(aScope, aInputValue)
|
||||||
let properties = completionPart.split('.');
|
let properties = completionPart.split('.');
|
||||||
let matchProp;
|
let matchProp;
|
||||||
if (properties.length > 1) {
|
if (properties.length > 1) {
|
||||||
matchProp = properties[properties.length - 1].trimLeft();
|
matchProp = properties.pop().trimLeft();
|
||||||
properties.pop();
|
for (let i = 0; i < properties.length; i++) {
|
||||||
for each (var prop in properties) {
|
let prop = properties[i].trim();
|
||||||
prop = prop.trim();
|
|
||||||
|
|
||||||
// If obj is undefined or null, then there is no change to run
|
// If obj is undefined or null, then there is no change to run completion
|
||||||
// completion on it. Exit here.
|
// on it. Exit here.
|
||||||
if (typeof obj === "undefined" || obj === null) {
|
if (typeof obj === "undefined" || obj === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
// Check if prop is a getter function on obj. Functions can change other
|
|
||||||
// stuff so we can't execute them to get the next object. Stop here.
|
|
||||||
if (obj.__lookupGetter__(prop)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
obj = obj[prop];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if prop is a getter function on obj. Functions can change other
|
||||||
|
// stuff so we can't execute them to get the next object. Stop here.
|
||||||
|
if (obj.__lookupGetter__(prop)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
obj = obj[prop];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
matchProp = properties[0].trimLeft();
|
matchProp = properties[0].trimLeft();
|
||||||
|
@ -4193,17 +4213,15 @@ function JSPropertyProvider(aScope, aInputValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
let matches = [];
|
let matches = [];
|
||||||
for (var prop in obj) {
|
for (let prop in obj) {
|
||||||
matches.push(prop);
|
if (prop.indexOf(matchProp) == 0) {
|
||||||
|
matches.push(prop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
matches = matches.filter(function(item) {
|
|
||||||
return item.indexOf(matchProp) == 0;
|
|
||||||
}).sort();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
matchProp: matchProp,
|
matchProp: matchProp,
|
||||||
matches: matches
|
matches: matches.sort(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4465,6 +4483,9 @@ function JSTerm(aContext, aParentNode, aMixin, aConsole)
|
||||||
this.historyIndex = 0;
|
this.historyIndex = 0;
|
||||||
this.historyPlaceHolder = 0; // this.history.length;
|
this.historyPlaceHolder = 0; // this.history.length;
|
||||||
this.log = LogFactory("*** JSTerm:");
|
this.log = LogFactory("*** JSTerm:");
|
||||||
|
this.autocompletePopup = new AutocompletePopup(aParentNode.ownerDocument);
|
||||||
|
this.autocompletePopup.onSelect = this.onAutocompleteSelect.bind(this);
|
||||||
|
this.autocompletePopup.onClick = this.acceptProposedCompletion.bind(this);
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4603,6 +4624,7 @@ JSTerm.prototype = {
|
||||||
this.historyIndex++;
|
this.historyIndex++;
|
||||||
this.historyPlaceHolder = this.history.length;
|
this.historyPlaceHolder = this.history.length;
|
||||||
this.setInputValue("");
|
this.setInputValue("");
|
||||||
|
this.clearCompletion();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4957,53 +4979,56 @@ JSTerm.prototype = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let inputUpdated = false;
|
||||||
|
|
||||||
switch(aEvent.keyCode) {
|
switch(aEvent.keyCode) {
|
||||||
|
case Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE:
|
||||||
|
if (this.autocompletePopup.isOpen) {
|
||||||
|
this.clearCompletion();
|
||||||
|
aEvent.preventDefault();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case Ci.nsIDOMKeyEvent.DOM_VK_RETURN:
|
case Ci.nsIDOMKeyEvent.DOM_VK_RETURN:
|
||||||
this.execute();
|
if (this.autocompletePopup.isOpen) {
|
||||||
|
this.acceptProposedCompletion();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.execute();
|
||||||
|
}
|
||||||
aEvent.preventDefault();
|
aEvent.preventDefault();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Ci.nsIDOMKeyEvent.DOM_VK_UP:
|
case Ci.nsIDOMKeyEvent.DOM_VK_UP:
|
||||||
// history previous
|
if (this.autocompletePopup.isOpen) {
|
||||||
if (this.canCaretGoPrevious()) {
|
inputUpdated = this.complete(this.COMPLETE_BACKWARD);
|
||||||
let updated = this.historyPeruse(HISTORY_BACK);
|
}
|
||||||
if (updated && aEvent.cancelable) {
|
else if (this.canCaretGoPrevious()) {
|
||||||
aEvent.preventDefault();
|
inputUpdated = this.historyPeruse(HISTORY_BACK);
|
||||||
}
|
}
|
||||||
|
if (inputUpdated) {
|
||||||
|
aEvent.preventDefault();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
|
case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
|
||||||
// history next
|
if (this.autocompletePopup.isOpen) {
|
||||||
if (this.canCaretGoNext()) {
|
inputUpdated = this.complete(this.COMPLETE_FORWARD);
|
||||||
let updated = this.historyPeruse(HISTORY_FORWARD);
|
}
|
||||||
if (updated && aEvent.cancelable) {
|
else if (this.canCaretGoNext()) {
|
||||||
aEvent.preventDefault();
|
inputUpdated = this.historyPeruse(HISTORY_FORWARD);
|
||||||
}
|
}
|
||||||
|
if (inputUpdated) {
|
||||||
|
aEvent.preventDefault();
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case Ci.nsIDOMKeyEvent.DOM_VK_RIGHT:
|
|
||||||
// accept proposed completion
|
|
||||||
this.acceptProposedCompletion();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Ci.nsIDOMKeyEvent.DOM_VK_TAB:
|
case Ci.nsIDOMKeyEvent.DOM_VK_TAB:
|
||||||
// If there are more than one possible completion, pressing tab
|
// Generate a completion and accept the first proposed value.
|
||||||
// means taking the next completion, shift_tab means taking
|
if (this.complete(this.COMPLETE_HINT_ONLY) &&
|
||||||
// the previous completion.
|
this.lastCompletion &&
|
||||||
var completionResult;
|
this.acceptProposedCompletion()) {
|
||||||
if (aEvent.shiftKey) {
|
aEvent.preventDefault();
|
||||||
completionResult = this.complete(this.COMPLETE_BACKWARD);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
completionResult = this.complete(this.COMPLETE_FORWARD);
|
|
||||||
}
|
|
||||||
if (completionResult) {
|
|
||||||
if (aEvent.cancelable) {
|
|
||||||
aEvent.preventDefault();
|
|
||||||
}
|
|
||||||
aEvent.target.focus();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -5147,96 +5172,127 @@ JSTerm.prototype = {
|
||||||
let inputValue = inputNode.value;
|
let inputValue = inputNode.value;
|
||||||
// If the inputNode has no value, then don't try to complete on it.
|
// If the inputNode has no value, then don't try to complete on it.
|
||||||
if (!inputValue) {
|
if (!inputValue) {
|
||||||
this.lastCompletion = null;
|
this.clearCompletion();
|
||||||
this.updateCompleteNode("");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only complete if the selection is empty and at the end of the input.
|
// Only complete if the selection is empty and at the end of the input.
|
||||||
if (inputNode.selectionStart == inputNode.selectionEnd &&
|
if (inputNode.selectionStart == inputNode.selectionEnd &&
|
||||||
inputNode.selectionEnd != inputValue.length) {
|
inputNode.selectionEnd != inputValue.length) {
|
||||||
// TODO: shouldnt we do this in the other 'bail' cases?
|
this.clearCompletion();
|
||||||
this.lastCompletion = null;
|
|
||||||
this.updateCompleteNode("");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let matches;
|
let popup = this.autocompletePopup;
|
||||||
let matchIndexToUse;
|
|
||||||
let matchOffset;
|
|
||||||
|
|
||||||
// If there is a saved completion from last time and the used value for
|
if (!this.lastCompletion || this.lastCompletion.value != inputValue) {
|
||||||
// completion stayed the same, then use the stored completion.
|
let properties = this.propertyProvider(this.sandbox.window, inputValue);
|
||||||
if (this.lastCompletion && inputValue == this.lastCompletion.value) {
|
if (!properties || !properties.matches.length) {
|
||||||
matches = this.lastCompletion.matches;
|
this.clearCompletion();
|
||||||
matchOffset = this.lastCompletion.matchOffset;
|
|
||||||
if (type === this.COMPLETE_BACKWARD) {
|
|
||||||
this.lastCompletion.index --;
|
|
||||||
}
|
|
||||||
else if (type === this.COMPLETE_FORWARD) {
|
|
||||||
this.lastCompletion.index ++;
|
|
||||||
}
|
|
||||||
matchIndexToUse = this.lastCompletion.index;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Look up possible completion values.
|
|
||||||
let completion = this.propertyProvider(this.sandbox.window, inputValue);
|
|
||||||
if (!completion) {
|
|
||||||
this.updateCompleteNode("");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
matches = completion.matches;
|
|
||||||
matchIndexToUse = 0;
|
|
||||||
matchOffset = completion.matchProp.length;
|
|
||||||
// Store this match;
|
|
||||||
this.lastCompletion = {
|
|
||||||
index: 0,
|
|
||||||
value: inputValue,
|
|
||||||
matches: matches,
|
|
||||||
matchOffset: matchOffset
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type != this.COMPLETE_HINT_ONLY && matches.length == 1) {
|
let items = properties.matches.map(function(aMatch) {
|
||||||
this.acceptProposedCompletion();
|
return {label: aMatch};
|
||||||
return true;
|
});
|
||||||
}
|
popup.setItems(items);
|
||||||
else if (matches.length != 0) {
|
this.lastCompletion = {value: inputValue,
|
||||||
// Ensure that the matchIndexToUse is always a valid array index.
|
matchProp: properties.matchProp};
|
||||||
if (matchIndexToUse < 0) {
|
|
||||||
matchIndexToUse = matches.length + (matchIndexToUse % matches.length);
|
if (items.length > 1 && !popup.isOpen) {
|
||||||
if (matchIndexToUse == matches.length) {
|
popup.openPopup(this.inputNode);
|
||||||
matchIndexToUse = 0;
|
}
|
||||||
|
else if (items.length < 2 && popup.isOpen) {
|
||||||
|
popup.hidePopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.length > 0) {
|
||||||
|
popup.selectedIndex = 0;
|
||||||
|
if (items.length == 1) {
|
||||||
|
// onSelect is not fired when the popup is not open.
|
||||||
|
this.onAutocompleteSelect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
matchIndexToUse = matchIndexToUse % matches.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
let completionStr = matches[matchIndexToUse].substring(matchOffset);
|
let accepted = false;
|
||||||
this.updateCompleteNode(completionStr);
|
|
||||||
return completionStr ? true : false;
|
if (type != this.COMPLETE_HINT_ONLY && popup.itemCount == 1) {
|
||||||
|
this.acceptProposedCompletion();
|
||||||
|
accepted = true;
|
||||||
|
}
|
||||||
|
else if (type == this.COMPLETE_BACKWARD) {
|
||||||
|
this.autocompletePopup.selectPreviousItem();
|
||||||
|
}
|
||||||
|
else if (type == this.COMPLETE_FORWARD) {
|
||||||
|
this.autocompletePopup.selectNextItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
return accepted || popup.itemCount > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
onAutocompleteSelect: function JSTF_onAutocompleteSelect()
|
||||||
|
{
|
||||||
|
let currentItem = this.autocompletePopup.selectedItem;
|
||||||
|
if (currentItem && this.lastCompletion) {
|
||||||
|
let suffix = currentItem.label.substring(this.lastCompletion.
|
||||||
|
matchProp.length);
|
||||||
|
this.updateCompleteNode(suffix);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.updateCompleteNode("");
|
this.updateCompleteNode("");
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the current completion information and close the autocomplete popup,
|
||||||
|
* if needed.
|
||||||
|
*/
|
||||||
|
clearCompletion: function JSTF_clearCompletion()
|
||||||
|
{
|
||||||
|
this.autocompletePopup.clearItems();
|
||||||
|
this.lastCompletion = null;
|
||||||
|
this.updateCompleteNode("");
|
||||||
|
if (this.autocompletePopup.isOpen) {
|
||||||
|
this.autocompletePopup.hidePopup();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept the proposed input completion.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
* True if there was a selected completion item and the input value
|
||||||
|
* was updated, false otherwise.
|
||||||
|
*/
|
||||||
acceptProposedCompletion: function JSTF_acceptProposedCompletion()
|
acceptProposedCompletion: function JSTF_acceptProposedCompletion()
|
||||||
{
|
{
|
||||||
this.setInputValue(this.inputNode.value + this.completionValue);
|
let updated = false;
|
||||||
this.updateCompleteNode("");
|
|
||||||
|
let currentItem = this.autocompletePopup.selectedItem;
|
||||||
|
if (currentItem && this.lastCompletion) {
|
||||||
|
let suffix = currentItem.label.substring(this.lastCompletion.
|
||||||
|
matchProp.length);
|
||||||
|
this.setInputValue(this.inputNode.value + suffix);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clearCompletion();
|
||||||
|
|
||||||
|
return updated;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCompleteNode: function JSTF_updateCompleteNode(suffix)
|
/**
|
||||||
|
* Update the node that displays the currently selected autocomplete proposal.
|
||||||
|
*
|
||||||
|
* @param string aSuffix
|
||||||
|
* The proposed suffix for the inputNode value.
|
||||||
|
*/
|
||||||
|
updateCompleteNode: function JSTF_updateCompleteNode(aSuffix)
|
||||||
{
|
{
|
||||||
this.completionValue = suffix;
|
|
||||||
|
|
||||||
// completion prefix = input, with non-control chars replaced by spaces
|
// completion prefix = input, with non-control chars replaced by spaces
|
||||||
let prefix = this.inputNode.value.replace(/[\S]/g, " ");
|
let prefix = aSuffix ? this.inputNode.value.replace(/[\S]/g, " ") : "";
|
||||||
this.completeNode.value = prefix + this.completionValue;
|
this.completeNode.value = prefix + aSuffix;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ include $(DEPTH)/config/autoconf.mk
|
||||||
EXTRA_JS_MODULES = HUDService.jsm \
|
EXTRA_JS_MODULES = HUDService.jsm \
|
||||||
PropertyPanel.jsm \
|
PropertyPanel.jsm \
|
||||||
NetworkHelper.jsm \
|
NetworkHelper.jsm \
|
||||||
|
AutocompletePopup.jsm \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
ifdef ENABLE_TESTS
|
ifdef ENABLE_TESTS
|
||||||
|
|
|
@ -138,6 +138,8 @@ _BROWSER_TEST_FILES = \
|
||||||
browser_webconsole_bug_646025_console_file_location.js \
|
browser_webconsole_bug_646025_console_file_location.js \
|
||||||
browser_webconsole_position_ui.js \
|
browser_webconsole_position_ui.js \
|
||||||
browser_webconsole_bug_642615_autocomplete.js \
|
browser_webconsole_bug_642615_autocomplete.js \
|
||||||
|
browser_webconsole_bug_585991_autocomplete_popup.js \
|
||||||
|
browser_webconsole_bug_585991_autocomplete_keys.js \
|
||||||
head.js \
|
head.js \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||||
|
/* ***** BEGIN LICENSE BLOCK *****
|
||||||
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||||||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
* http://www.mozilla.org/MPL/
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||||
|
* for the specific language governing rights and limitations under the
|
||||||
|
* License.
|
||||||
|
*
|
||||||
|
* The Original Code is Web Console test suite.
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is
|
||||||
|
* The Mozilla Foundation.
|
||||||
|
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
* Mihai Sucan <mihai.sucan@gmail.com>
|
||||||
|
*
|
||||||
|
* Alternatively, the contents of this file may be used under the terms of
|
||||||
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||||
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||||
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||||
|
* of those above. If you wish to allow use of your version of this file only
|
||||||
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||||
|
* use your version of this file under the terms of the MPL, indicate your
|
||||||
|
* decision by deleting the provisions above and replace them with the notice
|
||||||
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||||
|
* the provisions above, a recipient may use your version of this file under
|
||||||
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||||
|
*
|
||||||
|
* ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
|
const TEST_URI = "data:text/html,<p>bug 585991 - autocomplete popup keyboard usage test";
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
addTab(TEST_URI);
|
||||||
|
browser.addEventListener("load", tabLoaded, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tabLoaded() {
|
||||||
|
browser.removeEventListener("load", tabLoaded, true);
|
||||||
|
openConsole();
|
||||||
|
|
||||||
|
content.wrappedJSObject.foobarBug585991 = {
|
||||||
|
"item0": "value0",
|
||||||
|
"item1": "value1",
|
||||||
|
"item2": "value2",
|
||||||
|
"item3": "value3",
|
||||||
|
};
|
||||||
|
|
||||||
|
let hudId = HUDService.getHudIdByWindow(content);
|
||||||
|
HUD = HUDService.hudReferences[hudId];
|
||||||
|
let jsterm = HUD.jsterm;
|
||||||
|
let popup = jsterm.autocompletePopup;
|
||||||
|
let completeNode = jsterm.completeNode;
|
||||||
|
|
||||||
|
ok(!popup.isOpen, "popup is not open");
|
||||||
|
|
||||||
|
popup._panel.addEventListener("popupshown", function() {
|
||||||
|
popup._panel.removeEventListener("popupshown", arguments.callee, false);
|
||||||
|
|
||||||
|
ok(popup.isOpen, "popup is open");
|
||||||
|
|
||||||
|
is(popup.itemCount, 4, "popup.itemCount is correct");
|
||||||
|
|
||||||
|
let sameItems = popup.getItems();
|
||||||
|
is(sameItems.every(function(aItem, aIndex) {
|
||||||
|
return aItem.label == "item" + aIndex;
|
||||||
|
}), true, "getItems returns back the same items");
|
||||||
|
|
||||||
|
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||||
|
is(popup.selectedItem.label, "item0", "item0 is selected");
|
||||||
|
is(completeNode.value, prefix + "item0", "completeNode.value holds item0");
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 1, "index 1 is selected");
|
||||||
|
is(popup.selectedItem.label, "item1", "item1 is selected");
|
||||||
|
is(completeNode.value, prefix + "item1", "completeNode.value holds item1");
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("VK_UP", {});
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||||
|
is(popup.selectedItem.label, "item0", "item0 is selected");
|
||||||
|
is(completeNode.value, prefix + "item0", "completeNode.value holds item0");
|
||||||
|
|
||||||
|
popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("VK_TAB", {});
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
jsterm.setInputValue("window.foobarBug585991");
|
||||||
|
EventUtils.synthesizeKey(".", {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function autocompletePopupHidden()
|
||||||
|
{
|
||||||
|
let jsterm = HUD.jsterm;
|
||||||
|
let popup = jsterm.autocompletePopup;
|
||||||
|
let completeNode = jsterm.completeNode;
|
||||||
|
let inputNode = jsterm.inputNode;
|
||||||
|
|
||||||
|
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
|
||||||
|
|
||||||
|
ok(!popup.isOpen, "popup is not open");
|
||||||
|
|
||||||
|
is(inputNode.value, "window.foobarBug585991.item0",
|
||||||
|
"completion was successful after VK_TAB");
|
||||||
|
|
||||||
|
ok(!completeNode.value, "completeNode is empty");
|
||||||
|
|
||||||
|
popup._panel.addEventListener("popupshown", function() {
|
||||||
|
popup._panel.removeEventListener("popupshown", arguments.callee, false);
|
||||||
|
|
||||||
|
ok(popup.isOpen, "popup is open");
|
||||||
|
|
||||||
|
is(popup.itemCount, 4, "popup.itemCount is correct");
|
||||||
|
|
||||||
|
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||||
|
is(popup.selectedItem.label, "item0", "item0 is selected");
|
||||||
|
is(completeNode.value, prefix + "item0", "completeNode.value holds item0");
|
||||||
|
|
||||||
|
popup._panel.addEventListener("popuphidden", function() {
|
||||||
|
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
|
||||||
|
|
||||||
|
ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
|
||||||
|
|
||||||
|
is(inputNode.value, "window.foobarBug585991.",
|
||||||
|
"completion was cancelled");
|
||||||
|
|
||||||
|
ok(!completeNode.value, "completeNode is empty");
|
||||||
|
|
||||||
|
executeSoon(testReturnKey);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
executeSoon(function() {
|
||||||
|
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||||
|
});
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
executeSoon(function() {
|
||||||
|
jsterm.setInputValue("window.foobarBug585991");
|
||||||
|
EventUtils.synthesizeKey(".", {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testReturnKey()
|
||||||
|
{
|
||||||
|
let jsterm = HUD.jsterm;
|
||||||
|
let popup = jsterm.autocompletePopup;
|
||||||
|
let completeNode = jsterm.completeNode;
|
||||||
|
let inputNode = jsterm.inputNode;
|
||||||
|
|
||||||
|
popup._panel.addEventListener("popupshown", function() {
|
||||||
|
popup._panel.removeEventListener("popupshown", arguments.callee, false);
|
||||||
|
|
||||||
|
ok(popup.isOpen, "popup is open");
|
||||||
|
|
||||||
|
is(popup.itemCount, 4, "popup.itemCount is correct");
|
||||||
|
|
||||||
|
let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 0, "index 0 is selected");
|
||||||
|
is(popup.selectedItem.label, "item0", "item0 is selected");
|
||||||
|
is(completeNode.value, prefix + "item0", "completeNode.value holds item0");
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 1, "index 1 is selected");
|
||||||
|
is(popup.selectedItem.label, "item1", "item1 is selected");
|
||||||
|
is(completeNode.value, prefix + "item1", "completeNode.value holds item1");
|
||||||
|
|
||||||
|
popup._panel.addEventListener("popuphidden", function() {
|
||||||
|
popup._panel.removeEventListener("popuphidden", arguments.callee, false);
|
||||||
|
|
||||||
|
ok(!popup.isOpen, "popup is not open after VK_RETURN");
|
||||||
|
|
||||||
|
is(inputNode.value, "window.foobarBug585991.item1",
|
||||||
|
"completion was successful after VK_RETURN");
|
||||||
|
|
||||||
|
ok(!completeNode.value, "completeNode is empty");
|
||||||
|
|
||||||
|
executeSoon(finishTest);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
executeSoon(function() {
|
||||||
|
jsterm.setInputValue("window.foobarBug58599");
|
||||||
|
EventUtils.synthesizeKey("1", {});
|
||||||
|
EventUtils.synthesizeKey(".", {});
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||||
|
/* ***** BEGIN LICENSE BLOCK *****
|
||||||
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||||
|
*
|
||||||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||||||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
* http://www.mozilla.org/MPL/
|
||||||
|
*
|
||||||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||||
|
* for the specific language governing rights and limitations under the
|
||||||
|
* License.
|
||||||
|
*
|
||||||
|
* The Original Code is Web Console test suite.
|
||||||
|
*
|
||||||
|
* The Initial Developer of the Original Code is
|
||||||
|
* The Mozilla Foundation.
|
||||||
|
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
* Mihai Sucan <mihai.sucan@gmail.com>
|
||||||
|
*
|
||||||
|
* Alternatively, the contents of this file may be used under the terms of
|
||||||
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||||
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||||
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||||
|
* of those above. If you wish to allow use of your version of this file only
|
||||||
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||||
|
* use your version of this file under the terms of the MPL, indicate your
|
||||||
|
* decision by deleting the provisions above and replace them with the notice
|
||||||
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||||
|
* the provisions above, a recipient may use your version of this file under
|
||||||
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||||
|
*
|
||||||
|
* ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
|
const TEST_URI = "data:text/html,<p>bug 585991 - autocomplete popup test";
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
addTab(TEST_URI);
|
||||||
|
browser.addEventListener("load", tabLoaded, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tabLoaded() {
|
||||||
|
browser.removeEventListener("load", tabLoaded, true);
|
||||||
|
openConsole();
|
||||||
|
|
||||||
|
let items = [
|
||||||
|
{label: "item0", value: "value0"},
|
||||||
|
{label: "item1", value: "value1"},
|
||||||
|
{label: "item2", value: "value2"},
|
||||||
|
];
|
||||||
|
|
||||||
|
let hudId = HUDService.getHudIdByWindow(content);
|
||||||
|
let HUD = HUDService.hudReferences[hudId];
|
||||||
|
let popup = HUD.jsterm.autocompletePopup;
|
||||||
|
|
||||||
|
ok(!popup.isOpen, "popup is not open");
|
||||||
|
|
||||||
|
popup._panel.addEventListener("popupshown", function() {
|
||||||
|
popup._panel.removeEventListener("popupshown", arguments.callee, false);
|
||||||
|
|
||||||
|
ok(popup.isOpen, "popup is open");
|
||||||
|
|
||||||
|
is(popup.itemCount, 0, "no items");
|
||||||
|
|
||||||
|
popup.setItems(items);
|
||||||
|
|
||||||
|
is(popup.itemCount, items.length, "items added");
|
||||||
|
|
||||||
|
let sameItems = popup.getItems();
|
||||||
|
is(sameItems.every(function(aItem, aIndex) {
|
||||||
|
return aItem === items[aIndex];
|
||||||
|
}), true, "getItems returns back the same items");
|
||||||
|
|
||||||
|
is(popup.selectedIndex, -1, "no index is selected");
|
||||||
|
ok(!popup.selectedItem, "no item is selected");
|
||||||
|
|
||||||
|
popup.selectedIndex = 1;
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 1, "index 1 is selected");
|
||||||
|
is(popup.selectedItem, items[1], "item1 is selected");
|
||||||
|
|
||||||
|
popup.selectedItem = items[2];
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 2, "index 2 is selected");
|
||||||
|
is(popup.selectedItem, items[2], "item2 is selected");
|
||||||
|
|
||||||
|
is(popup.selectPreviousItem(), items[1], "selectPreviousItem() works");
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 1, "index 1 is selected");
|
||||||
|
is(popup.selectedItem, items[1], "item1 is selected");
|
||||||
|
|
||||||
|
is(popup.selectNextItem(), items[2], "selectPreviousItem() works");
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 2, "index 2 is selected");
|
||||||
|
is(popup.selectedItem, items[2], "item2 is selected");
|
||||||
|
|
||||||
|
ok(!popup.selectNextItem(), "selectPreviousItem() works");
|
||||||
|
|
||||||
|
is(popup.selectedIndex, -1, "no index is selected");
|
||||||
|
ok(!popup.selectedItem, "no item is selected");
|
||||||
|
|
||||||
|
items.push({label: "label3", value: "value3"});
|
||||||
|
popup.appendItem(items[3]);
|
||||||
|
|
||||||
|
is(popup.itemCount, items.length, "item3 appended");
|
||||||
|
|
||||||
|
popup.selectedIndex = 3;
|
||||||
|
is(popup.selectedItem, items[3], "item3 is selected");
|
||||||
|
|
||||||
|
popup.removeItem(items[2]);
|
||||||
|
|
||||||
|
is(popup.selectedIndex, 2, "index2 is selected");
|
||||||
|
is(popup.selectedItem, items[3], "item3 is still selected");
|
||||||
|
is(popup.itemCount, items.length - 1, "item2 removed");
|
||||||
|
|
||||||
|
popup.clearItems();
|
||||||
|
is(popup.itemCount, 0, "items cleared");
|
||||||
|
|
||||||
|
popup.hidePopup();
|
||||||
|
finishTest();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
popup.openPopup();
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ function tabLoad(aEvent) {
|
||||||
|
|
||||||
jsterm.clearOutput();
|
jsterm.clearOutput();
|
||||||
|
|
||||||
ok(!jsterm.completionValue, "no completionValue");
|
ok(!jsterm.completeNode.value, "no completeNode.value");
|
||||||
|
|
||||||
jsterm.setInputValue("doc");
|
jsterm.setInputValue("doc");
|
||||||
|
|
||||||
|
@ -28,28 +28,28 @@ function tabLoad(aEvent) {
|
||||||
jsterm.inputNode.addEventListener("keyup", function() {
|
jsterm.inputNode.addEventListener("keyup", function() {
|
||||||
jsterm.inputNode.removeEventListener("keyup", arguments.callee, false);
|
jsterm.inputNode.removeEventListener("keyup", arguments.callee, false);
|
||||||
|
|
||||||
let completionValue = jsterm.completionValue;
|
let completionValue = jsterm.completeNode.value;
|
||||||
ok(completionValue, "we have a completionValue");
|
ok(completionValue, "we have a completeNode.value");
|
||||||
|
|
||||||
// wait for paste
|
// wait for paste
|
||||||
jsterm.inputNode.addEventListener("input", function() {
|
jsterm.inputNode.addEventListener("input", function() {
|
||||||
jsterm.inputNode.removeEventListener("input", arguments.callee, false);
|
jsterm.inputNode.removeEventListener("input", arguments.callee, false);
|
||||||
|
|
||||||
ok(!jsterm.completionValue, "no completionValue after clipboard paste");
|
ok(!jsterm.completeNode.value, "no completeNode.value after clipboard paste");
|
||||||
|
|
||||||
// wait for undo
|
// wait for undo
|
||||||
jsterm.inputNode.addEventListener("input", function() {
|
jsterm.inputNode.addEventListener("input", function() {
|
||||||
jsterm.inputNode.removeEventListener("input", arguments.callee, false);
|
jsterm.inputNode.removeEventListener("input", arguments.callee, false);
|
||||||
|
|
||||||
is(jsterm.completionValue, completionValue,
|
is(jsterm.completeNode.value, completionValue,
|
||||||
"same completionValue after undo");
|
"same completeNode.value after undo");
|
||||||
|
|
||||||
// wait for paste (via keyboard event)
|
// wait for paste (via keyboard event)
|
||||||
jsterm.inputNode.addEventListener("keyup", function() {
|
jsterm.inputNode.addEventListener("keyup", function() {
|
||||||
jsterm.inputNode.removeEventListener("keyup", arguments.callee, false);
|
jsterm.inputNode.removeEventListener("keyup", arguments.callee, false);
|
||||||
|
|
||||||
ok(!jsterm.completionValue,
|
ok(!jsterm.completeNode.value,
|
||||||
"no completionValue after clipboard paste (via keyboard event)");
|
"no completeNode.value after clipboard paste (via keyboard event)");
|
||||||
|
|
||||||
executeSoon(finishTest);
|
executeSoon(finishTest);
|
||||||
}, false);
|
}, false);
|
||||||
|
|
|
@ -139,6 +139,10 @@ webConsolePositionWindow=Window
|
||||||
# title.
|
# title.
|
||||||
webConsoleOwnWindowTitle=Web Console
|
webConsoleOwnWindowTitle=Web Console
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (Autocomplete.label):
|
||||||
|
# The autocomplete popup panel label/title.
|
||||||
|
Autocomplete.label=Autocomplete popup
|
||||||
|
|
||||||
# LOCALIZATION NOTE (stacktrace.anonymousFunction):
|
# LOCALIZATION NOTE (stacktrace.anonymousFunction):
|
||||||
# This string is used to display JavaScript functions that have no given name -
|
# This string is used to display JavaScript functions that have no given name -
|
||||||
# they are said to be anonymous. See stacktrace.outputMessage.
|
# they are said to be anonymous. See stacktrace.outputMessage.
|
||||||
|
|
Загрузка…
Ссылка в новой задаче