fune/mobile/android/components/geckoview/GeckoViewPrompt.js
Kris Maglione 8ad0bfd9c0 Bug 1484496: Follow-up: Fix more busted JS enumerators. r=bustage CLOSED TREE
--HG--
extra : amend_source : b6753494cab68add325af09442239e72ea0c4501
2018-08-24 17:10:29 -07:00

999 lines
29 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
EventDispatcher: "resource://gre/modules/Messaging.jsm",
FileUtils: "resource://gre/modules/FileUtils.jsm",
GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
});
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGen",
"@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
function PromptFactory() {
this.wrappedJSObject = this;
}
PromptFactory.prototype = {
classID: Components.ID("{076ac188-23c1-4390-aa08-7ef1f78ca5d9}"),
QueryInterface: ChromeUtils.generateQI([
Ci.nsIPromptFactory, Ci.nsIPromptService]),
handleEvent: function(aEvent) {
switch (aEvent.type) {
case "click":
this._handleClick(aEvent);
break;
case "contextmenu":
this._handleContextMenu(aEvent);
break;
}
},
_handleClick: function(aEvent) {
let target = aEvent.composedTarget;
if (aEvent.defaultPrevented || target.isContentEditable ||
target.disabled || target.readOnly || !target.willValidate) {
// target.willValidate is false when any associated fieldset is disabled,
// in which case this element is treated as disabled per spec.
return;
}
let win = target.ownerGlobal;
if (target instanceof win.HTMLSelectElement) {
this._handleSelect(target);
aEvent.preventDefault();
} else if (target instanceof win.HTMLInputElement) {
let type = target.type;
if (type === "date" || type === "month" || type === "week" ||
type === "time" || type === "datetime-local") {
this._handleDateTime(target, type);
aEvent.preventDefault();
}
}
},
_handleSelect: function(aElement) {
let win = aElement.ownerGlobal;
let id = 0;
let map = {};
let items = (function enumList(elem, disabled) {
let items = [];
let children = elem.children;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (win.getComputedStyle(child).display === "none") {
continue;
}
let item = {
id: String(id),
disabled: disabled || child.disabled,
};
if (child instanceof win.HTMLOptGroupElement) {
item.label = child.label;
item.items = enumList(child, item.disabled);
} else if (child instanceof win.HTMLOptionElement) {
item.label = child.label || child.text;
item.selected = child.selected;
} else {
continue;
}
items.push(item);
map[id++] = child;
}
return items;
})(aElement);
let prompt = new PromptDelegate(win);
prompt.asyncShowPrompt({
type: "choice",
mode: aElement.multiple ? "multiple" : "single",
choices: items,
}, result => {
// OK: result
// Cancel: !result
if (!result || result.choices === undefined) {
return;
}
let dispatchEvents = false;
if (!aElement.multiple) {
let elem = map[result.choices[0]];
if (elem && elem instanceof win.HTMLOptionElement) {
dispatchEvents = !elem.selected;
elem.selected = true;
} else {
Cu.reportError("Invalid id for select result: " + result.choices[0]);
}
} else {
for (let i = 0; i < id; i++) {
let elem = map[i];
let index = result.choices.indexOf(String(i));
if (elem instanceof win.HTMLOptionElement &&
elem.selected !== (index >= 0)) {
// Current selected is not the same as the new selected state.
dispatchEvents = true;
elem.selected = !elem.selected;
}
result.choices[index] = undefined;
}
for (let i = 0; i < result.choices.length; i++) {
if (result.choices[i] !== undefined && result.choices[i] !== null) {
Cu.reportError("Invalid id for select result: " + result.choices[i]);
break;
}
}
}
if (dispatchEvents) {
this._dispatchEvents(aElement);
}
});
},
_handleDateTime: function(aElement, aType) {
let prompt = new PromptDelegate(aElement.ownerGlobal);
prompt.asyncShowPrompt({
type: "datetime",
mode: aType,
value: aElement.value,
min: aElement.min,
max: aElement.max,
}, result => {
// OK: result
// Cancel: !result
if (!result || result.datetime === undefined || result.datetime === aElement.value) {
return;
}
aElement.value = result.datetime;
this._dispatchEvents(aElement);
});
},
_dispatchEvents: function(aElement) {
// Fire both "input" and "change" events for <select> and <input>.
aElement.dispatchEvent(new aElement.ownerGlobal.Event("input", { bubbles: true }));
aElement.dispatchEvent(new aElement.ownerGlobal.Event("change", { bubbles: true }));
},
_handleContextMenu: function(aEvent) {
let target = aEvent.composedTarget;
if (aEvent.defaultPrevented || target.isContentEditable) {
return;
}
// Look through all ancestors for a context menu per spec.
let parent = target;
let menu = target.contextMenu;
while (!menu && parent) {
menu = parent.contextMenu;
parent = parent.parentElement;
}
if (!menu) {
return;
}
let builder = {
_cursor: undefined,
_id: 0,
_map: {},
_stack: [],
items: [],
// nsIMenuBuilder
openContainer: function(aLabel) {
if (!this._cursor) {
// Top-level
this._cursor = this;
return;
}
let newCursor = {
id: String(this._id++),
items: [],
label: aLabel,
};
this._cursor.items.push(newCursor);
this._stack.push(this._cursor);
this._cursor = newCursor;
},
addItemFor: function(aElement, aCanLoadIcon) {
this._cursor.items.push({
disabled: aElement.disabled,
icon: aCanLoadIcon && aElement.icon &&
aElement.icon.length ? aElement.icon : null,
id: String(this._id),
label: aElement.label,
selected: aElement.checked,
});
this._map[this._id++] = aElement;
},
addSeparator: function() {
this._cursor.items.push({
disabled: true,
id: String(this._id++),
separator: true,
});
},
undoAddSeparator: function() {
let sep = this._cursor.items[this._cursor.items.length - 1];
if (sep && sep.separator) {
this._cursor.items.pop();
}
},
closeContainer: function() {
let childItems = (this._cursor.label === "") ? this._cursor.items : null;
this._cursor = this._stack.pop();
if (childItems !== null && this._cursor && this._cursor.items.length === 1) {
// Merge a single nameless child container into the parent container.
// This lets us build an HTML contextmenu within a submenu.
this._cursor.items = childItems;
}
},
toJSONString: function() {
return JSON.stringify(this.items);
},
click: function(aId) {
let item = this._map[aId];
if (item) {
item.click();
}
},
};
// XXX the "show" event is not cancelable but spec says it should be.
menu.sendShowEvent();
menu.build(builder);
let prompt = new PromptDelegate(target.ownerGlobal);
prompt.asyncShowPrompt({
type: "choice",
mode: "menu",
choices: builder.items,
}, result => {
// OK: result
// Cancel: !result
if (result && result.choices !== undefined) {
builder.click(result.choices[0]);
}
});
aEvent.preventDefault();
},
/* ---------- nsIPromptFactory ---------- */
getPrompt: function(aDOMWin, aIID) {
// Delegated to login manager here, which in turn calls back into us via nsIPromptService.
if (aIID.equals(Ci.nsIAuthPrompt2) || aIID.equals(Ci.nsIAuthPrompt)) {
try {
let pwmgr = Cc["@mozilla.org/passwordmanager/authpromptfactory;1"].getService(Ci.nsIPromptFactory);
return pwmgr.getPrompt(aDOMWin, aIID);
} catch (e) {
Cu.reportError("Delegation to password manager failed: " + e);
}
}
let p = new PromptDelegate(aDOMWin);
p.QueryInterface(aIID);
return p;
},
/* ---------- private memebers ---------- */
// nsIPromptService methods proxy to our Prompt class
callProxy: function(aMethod, aArguments) {
let prompt = new PromptDelegate(aArguments[0]);
return prompt[aMethod].apply(prompt, Array.prototype.slice.call(aArguments, 1));
},
/* ---------- nsIPromptService ---------- */
alert: function() {
return this.callProxy("alert", arguments);
},
alertCheck: function() {
return this.callProxy("alertCheck", arguments);
},
confirm: function() {
return this.callProxy("confirm", arguments);
},
confirmCheck: function() {
return this.callProxy("confirmCheck", arguments);
},
confirmEx: function() {
return this.callProxy("confirmEx", arguments);
},
prompt: function() {
return this.callProxy("prompt", arguments);
},
promptUsernameAndPassword: function() {
return this.callProxy("promptUsernameAndPassword", arguments);
},
promptPassword: function() {
return this.callProxy("promptPassword", arguments);
},
select: function() {
return this.callProxy("select", arguments);
},
promptAuth: function() {
return this.callProxy("promptAuth", arguments);
},
asyncPromptAuth: function() {
return this.callProxy("asyncPromptAuth", arguments);
}
};
function PromptDelegate(aDomWin) {
this._domWin = aDomWin;
if (aDomWin) {
this._dispatcher = GeckoViewUtils.getDispatcherForWindow(aDomWin);
}
if (!this._dispatcher) {
[this._dispatcher, this._domWin] =
GeckoViewUtils.getActiveDispatcherAndWindow();
}
}
PromptDelegate.prototype = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
BUTTON_TYPE_POSITIVE: 0,
BUTTON_TYPE_NEUTRAL: 1,
BUTTON_TYPE_NEGATIVE: 2,
/* ---------- internal methods ---------- */
_changeModalState: function(aEntering) {
if (!this._domWin) {
// Allow not having a DOM window.
return true;
}
// Accessing the document object can throw if this window no longer exists. See bug 789888.
try {
let winUtils = this._domWin.windowUtils;
if (!aEntering) {
winUtils.leaveModalState();
}
let event = this._domWin.document.createEvent("Events");
event.initEvent(aEntering ? "DOMWillOpenModalDialog" : "DOMModalDialogClosed",
true, true);
winUtils.dispatchEventToChromeOnly(this._domWin, event);
if (aEntering) {
winUtils.enterModalState();
}
return true;
} catch (ex) {
Cu.reportError("Failed to change modal state: " + ex);
}
return false;
},
/**
* Shows a native prompt, and then spins the event loop for this thread while we wait
* for a response
*/
_showPrompt: function(aMsg) {
let result = undefined;
if (!this._domWin || !this._changeModalState(/* aEntering */ true)) {
return;
}
try {
this.asyncShowPrompt(aMsg, res => result = res);
// Spin this thread while we wait for a result
Services.tm.spinEventLoopUntil(() =>
this._domWin.closed || result !== undefined);
} finally {
this._changeModalState(/* aEntering */ false);
}
return result;
},
asyncShowPrompt: function(aMsg, aCallback) {
let handled = false;
let onResponse = response => {
if (handled) {
return;
}
aCallback(response);
// This callback object is tied to the Java garbage collector because
// it is invoked from Java. Manually release the target callback
// here; otherwise we may hold onto resources for too long, because
// we would be relying on both the Java and the JS garbage collectors
// to run.
aMsg = undefined;
aCallback = undefined;
handled = true;
};
if (!this._dispatcher) {
onResponse(null);
return;
}
this._dispatcher.dispatch("GeckoView:Prompt", aMsg, {
onSuccess: onResponse,
onError: error => {
Cu.reportError("Prompt error: " + error);
onResponse(null);
},
});
},
_addText: function(aTitle, aText, aMsg) {
return Object.assign(aMsg, {
title: aTitle,
msg: aText,
});
},
_addCheck: function(aCheckMsg, aCheckState, aMsg) {
return Object.assign(aMsg, {
hasCheck: !!aCheckMsg,
checkMsg: aCheckMsg,
checkValue: aCheckState && aCheckState.value,
});
},
/* ---------- nsIPrompt ---------- */
alert: function(aTitle, aText) {
this.alertCheck(aTitle, aText);
},
alertCheck: function(aTitle, aText, aCheckMsg, aCheckState) {
let result = this._showPrompt(this._addText(aTitle, aText,
this._addCheck(aCheckMsg, aCheckState, {
type: "alert",
})));
if (result && aCheckState) {
aCheckState.value = !!result.checkValue;
}
},
confirm: function(aTitle, aText) {
// Button 0 is OK.
return this.confirmCheck(aTitle, aText) == 0;
},
confirmCheck: function(aTitle, aText, aCheckMsg, aCheckState) {
// Button 0 is OK.
return this.confirmEx(aTitle, aText, Ci.nsIPrompt.STD_OK_CANCEL_BUTTONS,
/* aButton0 */ null, /* aButton1 */ null, /* aButton2 */ null,
aCheckMsg, aCheckState) == 0;
},
confirmEx: function(aTitle, aText, aButtonFlags, aButton0,
aButton1, aButton2, aCheckMsg, aCheckState) {
let btnMap = Array(3).fill(null);
let btnTitle = Array(3).fill(null);
let btnCustomTitle = Array(3).fill(null);
let savedButtonId = [];
for (let i = 0; i < 3; i++) {
let btnFlags = aButtonFlags >> (i * 8);
switch (btnFlags & 0xff) {
case Ci.nsIPrompt.BUTTON_TITLE_OK :
btnMap[this.BUTTON_TYPE_POSITIVE] = i;
btnTitle[this.BUTTON_TYPE_POSITIVE] = "ok";
break;
case Ci.nsIPrompt.BUTTON_TITLE_CANCEL :
btnMap[this.BUTTON_TYPE_NEGATIVE] = i;
btnTitle[this.BUTTON_TYPE_NEGATIVE] = "cancel";
break;
case Ci.nsIPrompt.BUTTON_TITLE_YES :
btnMap[this.BUTTON_TYPE_POSITIVE] = i;
btnTitle[this.BUTTON_TYPE_POSITIVE] = "yes";
break;
case Ci.nsIPrompt.BUTTON_TITLE_NO :
btnMap[this.BUTTON_TYPE_NEGATIVE] = i;
btnTitle[this.BUTTON_TYPE_NEGATIVE] = "no";
break;
case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING :
// We don't know if this is positive/negative/neutral, so save for later.
savedButtonId.push(i);
break;
case Ci.nsIPrompt.BUTTON_TITLE_SAVE :
case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE :
case Ci.nsIPrompt.BUTTON_TITLE_REVERT :
// Not supported; fall-through.
default:
break;
}
}
// Put saved buttons into available slots.
for (let i = 0; i < 3 && savedButtonId.length; i++) {
if (btnMap[i] === null) {
btnMap[i] = savedButtonId.shift();
btnTitle[i] = "custom";
btnCustomTitle[i] = [aButton0, aButton1, aButton2][btnMap[i]];
}
}
let result = this._showPrompt(this._addText(aTitle, aText,
this._addCheck(aCheckMsg, aCheckState, {
type: "button",
btnTitle: btnTitle,
btnCustomTitle: btnCustomTitle,
})));
if (result && aCheckState) {
aCheckState.value = !!result.checkValue;
}
return (result && result.button in btnMap) ? btnMap[result.button] : -1;
},
prompt: function(aTitle, aText, aValue, aCheckMsg, aCheckState) {
let result = this._showPrompt(this._addText(aTitle, aText,
this._addCheck(aCheckMsg, aCheckState, {
type: "text",
value: aValue.value,
})));
// OK: result && result.text !== undefined
// Cancel: result && result.text === undefined
// Error: !result
if (result && aCheckState) {
aCheckState.value = !!result.checkValue;
}
if (!result || result.text === undefined) {
return false;
}
aValue.value = result.text || "";
return true;
},
promptPassword: function(aTitle, aText, aPassword, aCheckMsg, aCheckState) {
return this._promptUsernameAndPassword(aTitle, aText, /* aUsername */ undefined,
aPassword, aCheckMsg, aCheckState);
},
promptUsernameAndPassword: function(aTitle, aText, aUsername, aPassword,
aCheckMsg, aCheckState) {
let msg = {
type: "auth",
mode: aUsername ? "auth" : "password",
options: {
flags: aUsername ? 0 : Ci.nsIAuthInformation.ONLY_PASSWORD,
username: aUsername ? aUsername.value : undefined,
password: aPassword.value,
},
};
let result = this._showPrompt(this._addText(aTitle, aText,
this._addCheck(aCheckMsg, aCheckState, msg)));
// OK: result && result.password !== undefined
// Cancel: result && result.password === undefined
// Error: !result
if (result && aCheckState) {
aCheckState.value = !!result.checkValue;
}
if (!result || result.password === undefined) {
return false;
}
if (aUsername) {
aUsername.value = result.username || "";
}
aPassword.value = result.password || "";
return true;
},
select: function(aTitle, aText, aCount, aSelectList, aOutSelection) {
let choices = Array.prototype.map.call(aSelectList, (item, index) => ({
id: String(index),
label: item,
disabled: false,
selected: false,
}));
let result = this._showPrompt(this._addText(aTitle, aText, {
type: "choice",
mode: "single",
choices: choices,
}));
// OK: result
// Cancel: !result
if (!result || result.choices === undefined) {
return false;
}
aOutSelection.value = Number(result.choices[0]);
return true;
},
_getAuthMsg: function(aChannel, aLevel, aAuthInfo) {
let username;
if ((aAuthInfo.flags & Ci.nsIAuthInformation.NEED_DOMAIN) && aAuthInfo.domain) {
username = aAuthInfo.domain + "\\" + aAuthInfo.username;
} else {
username = aAuthInfo.username;
}
return this._addText(/* title */ null, this._getAuthText(aChannel, aAuthInfo), {
type: "auth",
mode: aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD ? "password" : "auth",
options: {
flags: aAuthInfo.flags,
uri: aChannel && aChannel.URI.displaySpec,
level: aLevel,
username: username,
password: aAuthInfo.password,
},
});
},
_fillAuthInfo: function(aAuthInfo, aCheckState, aResult) {
if (aResult && aCheckState) {
aCheckState.value = !!aResult.checkValue;
}
if (!aResult || aResult.password === undefined) {
return false;
}
aAuthInfo.password = aResult.password || "";
if (aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD) {
return true;
}
let username = aResult.username || "";
if (aAuthInfo.flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
// Domain is separated from username by a backslash
var idx = username.indexOf("\\");
if (idx >= 0) {
aAuthInfo.domain = username.substring(0, idx);
aAuthInfo.username = username.substring(idx + 1);
return true;
}
}
aAuthInfo.username = username;
return true;
},
promptAuth: function(aChannel, aLevel, aAuthInfo, aCheckMsg, aCheckState) {
let result = this._showPrompt(this._addCheck(aCheckMsg, aCheckState,
this._getAuthMsg(aChannel, aLevel, aAuthInfo)));
// OK: result && result.password !== undefined
// Cancel: result && result.password === undefined
// Error: !result
return this._fillAuthInfo(aAuthInfo, aCheckState, result);
},
asyncPromptAuth: function(aChannel, aCallback, aContext, aLevel, aAuthInfo,
aCheckMsg, aCheckState) {
let responded = false;
let callback = result => {
// OK: result && result.password !== undefined
// Cancel: result && result.password === undefined
// Error: !result
if (responded) {
return;
}
responded = true;
if (this._fillAuthInfo(aAuthInfo, aCheckState, result)) {
aCallback.onAuthAvailable(aContext, aAuthInfo);
} else {
aCallback.onAuthCancelled(aContext, /* userCancel */ true);
}
};
this.asyncShowPrompt(this._addCheck(aCheckMsg, aCheckState,
this._getAuthMsg(aChannel, aLevel, aAuthInfo)), callback);
return {
QueryInterface: ChromeUtils.generateQI([Ci.nsICancelable]),
cancel: function() {
if (responded) {
return;
}
responded = true;
aCallback.onAuthCancelled(aContext, /* userCancel */ false);
}
};
},
_getAuthText: function(aChannel, aAuthInfo) {
let isProxy = (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY);
let isPassOnly = (aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD);
let isCrossOrig = (aAuthInfo.flags & Ci.nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE);
let username = aAuthInfo.username;
let [displayHost, realm] = this._getAuthTarget(aChannel, aAuthInfo);
// Suppress "the site says: $realm" when we synthesized a missing realm.
if (!aAuthInfo.realm && !isProxy) {
realm = "";
}
// Trim obnoxiously long realms.
if (realm.length > 50) {
realm = realm.substring(0, 50) + "\u2026";
}
let bundle = Services.strings.createBundle(
"chrome://global/locale/commonDialogs.properties");
let text;
if (isProxy) {
text = bundle.formatStringFromName("EnterLoginForProxy3", [realm, displayHost], 2);
} else if (isPassOnly) {
text = bundle.formatStringFromName("EnterPasswordFor", [username, displayHost], 2);
} else if (isCrossOrig) {
text = bundle.formatStringFromName("EnterUserPasswordForCrossOrigin2", [displayHost], 1);
} else if (!realm) {
text = bundle.formatStringFromName("EnterUserPasswordFor2", [displayHost], 1);
} else {
text = bundle.formatStringFromName("EnterLoginForRealm3", [realm, displayHost], 2);
}
return text;
},
_getAuthTarget: function(aChannel, aAuthInfo) {
// If our proxy is demanding authentication, don't use the
// channel's actual destination.
if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
if (!(aChannel instanceof Ci.nsIProxiedChannel)) {
throw "proxy auth needs nsIProxiedChannel";
}
let info = aChannel.proxyInfo;
if (!info) {
throw "proxy auth needs nsIProxyInfo";
}
// Proxies don't have a scheme, but we'll use "moz-proxy://"
// so that it's more obvious what the login is for.
let idnService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
let hostname = "moz-proxy://" + idnService.convertUTF8toACE(info.host) + ":" + info.port;
let realm = aAuthInfo.realm;
if (!realm) {
realm = hostname;
}
return [hostname, realm];
}
let hostname = aChannel.URI.scheme + "://" + aChannel.URI.displayHostPort;
// If a HTTP WWW-Authenticate header specified a realm, that value
// will be available here. If it wasn't set or wasn't HTTP, we'll use
// the formatted hostname instead.
let realm = aAuthInfo.realm;
if (!realm) {
realm = hostname;
}
return [hostname, realm];
},
};
function FilePickerDelegate() {
}
FilePickerDelegate.prototype = {
classID: Components.ID("{e4565e36-f101-4bf5-950b-4be0887785a9}"),
QueryInterface: ChromeUtils.generateQI([Ci.nsIFilePicker]),
/* ---------- nsIFilePicker ---------- */
init: function(aParent, aTitle, aMode) {
if (aMode === Ci.nsIFilePicker.modeGetFolder ||
aMode === Ci.nsIFilePicker.modeSave) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
}
this._prompt = new PromptDelegate(aParent);
this._msg = {
type: "file",
title: aTitle,
mode: (aMode === Ci.nsIFilePicker.modeOpenMultiple) ? "multiple" : "single",
};
this._mode = aMode;
this._mimeTypes = [];
this._extensions = [];
},
get mode() {
return this._mode;
},
appendFilters: function(aFilterMask) {
if (aFilterMask & Ci.nsIFilePicker.filterAll) {
this._mimeTypes.push("*/*");
}
if (aFilterMask & Ci.nsIFilePicker.filterAudio) {
this._mimeTypes.push("audio/*");
}
if (aFilterMask & Ci.nsIFilePicker.filterImages) {
this._mimeTypes.push("image/*");
}
if (aFilterMask & Ci.nsIFilePicker.filterVideo) {
this._mimeTypes.push("video/*");
}
if (aFilterMask & Ci.nsIFilePicker.filterHTML) {
this._mimeTypes.push("text/html");
}
if (aFilterMask & Ci.nsIFilePicker.filterText) {
this._mimeTypes.push("text/plain");
}
if (aFilterMask & Ci.nsIFilePicker.filterXML) {
this._mimeTypes.push("text/xml");
}
if (aFilterMask & Ci.nsIFilePicker.filterXUL) {
this._mimeTypes.push("application/vnd.mozilla.xul+xml");
}
},
appendFilter: function(aTitle, aFilter) {
// Only include filter that specify extensions (i.e. exclude generic ones like "*").
let filters = aFilter.split(/[\s,;]+/).filter(filter => filter.includes("."));
Array.prototype.push.apply(this._extensions, filters);
},
show: function() {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
open: function(aFilePickerShownCallback) {
this._msg.mimeTypes = this._mimeTypes;
if (this._extensions.length) {
this._msg.extensions = this._extensions;
}
this._prompt.asyncShowPrompt(this._msg, result => {
// OK: result
// Cancel: !result
if (!result || !result.files || !result.files.length) {
aFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel);
} else {
this._files = result.files;
aFilePickerShownCallback.done(Ci.nsIFilePicker.returnOK);
}
});
},
get file() {
if (!this._files) {
throw Cr.NS_ERROR_NOT_AVAILABLE;
}
return new FileUtils.File(this._files[0]);
},
get fileURL() {
return Services.io.newFileURI(this.file);
},
_getEnumerator(aDOMFile) {
if (!this._files) {
throw Cr.NS_ERROR_NOT_AVAILABLE;
}
return {
QueryInterface: ChromeUtils.generateQI([Ci.nsISimpleEnumerator]),
_owner: this,
_index: 0,
* [Symbol.iterator]() {
for (let file of this._owner._files) {
if (aDOMFile) {
yield this._owner._getDOMFile(file);
}
yield new FileUtils.File(file);
}
},
hasMoreElements: function() {
return this._index < this._owner._files.length;
},
getNext: function() {
let files = this._owner._files;
if (this._index >= files.length) {
throw Cr.NS_ERROR_FAILURE;
}
if (aDOMFile) {
return this._owner._getDOMFile(files[this._index++]);
}
return new FileUtils.File(files[this._index++]);
}
};
},
get files() {
return this._getEnumerator(/* aDOMFile */ false);
},
_getDOMFile(aPath) {
if (this._prompt._domWin) {
return this._prompt._domWin.File.createFromFileName(aPath);
}
return File.createFromFileName(aPath);
},
get domFileOrDirectory() {
if (!this._files) {
throw Cr.NS_ERROR_NOT_AVAILABLE;
}
return this._getDOMFile(this._files[0]);
},
get domFileOrDirectoryEnumerator() {
return this._getEnumerator(/* aDOMFile */ true);
},
get defaultString() {
return "";
},
set defaultString(aValue) {
},
get defaultExtension() {
return "";
},
set defaultExtension(aValue) {
},
get filterIndex() {
return 0;
},
set filterIndex(aValue) {
},
get displayDirectory() {
return null;
},
set displayDirectory(aValue) {
},
get displaySpecialDirectory() {
return "";
},
set displaySpecialDirectory(aValue) {
},
get addToRecentDocs() {
return false;
},
set addToRecentDocs(aValue) {
},
get okButtonLabel() {
return "";
},
set okButtonLabel(aValue) {
},
};
function ColorPickerDelegate() {
}
ColorPickerDelegate.prototype = {
classID: Components.ID("{aa0dd6fc-73dd-4621-8385-c0b377e02cee}"),
QueryInterface: ChromeUtils.generateQI([Ci.nsIColorPicker]),
init: function(aParent, aTitle, aInitialColor) {
this._prompt = new PromptDelegate(aParent);
this._msg = {
type: "color",
title: aTitle,
value: aInitialColor,
};
},
open: function(aColorPickerShownCallback) {
this._prompt.asyncShowPrompt(this._msg, result => {
// OK: result
// Cancel: !result
aColorPickerShownCallback.done((result && result.color) || "");
});
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
ColorPickerDelegate, FilePickerDelegate, PromptFactory]);