summaryrefslogtreecommitdiffstats
path: root/comm/suite/chatzilla/xul/content/handlers.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/suite/chatzilla/xul/content/handlers.js')
-rw-r--r--comm/suite/chatzilla/xul/content/handlers.js3960
1 files changed, 3960 insertions, 0 deletions
diff --git a/comm/suite/chatzilla/xul/content/handlers.js b/comm/suite/chatzilla/xul/content/handlers.js
new file mode 100644
index 0000000000..74e0f1c856
--- /dev/null
+++ b/comm/suite/chatzilla/xul/content/handlers.js
@@ -0,0 +1,3960 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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/. */
+
+window.onresize =
+function onresize()
+{
+ for (var i = 0; i < client.deck.childNodes.length; i++)
+ scrollDown(client.deck.childNodes[i], true);
+}
+
+function onInputFocus()
+{
+}
+
+function showErrorDlg(message)
+{
+ const XUL_MIME = "application/vnd.mozilla.xul+xml";
+ const XUL_KEY = "http://www.mozilla.org/keymaster/" +
+ "gatekeeper/there.is.only.xul";
+
+ const TITLE = "ChatZilla run-time error";
+ const HEADER = "There was a run-time error with ChatZilla. " +
+ "Please report the following information:";
+
+ const OL_JS = "document.getElementById('tb').value = '%S';";
+ const TB_STYLE = ' multiline="true" readonly="true"' +
+ ' style="width: 60ex; height: 20em;"';
+
+ const ERROR_DLG = '<?xml version="1.0"?>' +
+ '<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>' +
+ '<dialog xmlns="' + XUL_KEY + '" buttons="accept" ' +
+ 'title="' + TITLE + '" onload="' + OL_JS + '">' +
+ '<label>' + HEADER + '</label><textbox' + TB_STYLE + ' id="tb"/>' +
+ '</dialog>';
+
+ var content = message.replace(/\n/g, "\\n");
+ content = content.replace(/'/g, "\\'").replace(/"/g, "&quot;");
+ content = content.replace(/</g, "&lt;").replace(/>/g, "&gt;");
+ content = ERROR_DLG.replace("%S", content);
+ content = encodeURIComponent(content);
+ content = "data:" + XUL_MIME + "," + content;
+
+ setTimeout(function() {
+ window.openDialog(content, "_blank", "chrome,modal");
+ }, 100);
+}
+
+function onLoad()
+{
+ dd ("Initializing ChatZilla {");
+ try
+ {
+ init();
+ }
+ catch (ex)
+ {
+ dd("caught exception while initializing:\n" + dumpObjectTree(ex));
+ var exception = formatException(ex) + (ex.stack && "\n" + ex.stack);
+ showErrorDlg(exception + "\n" + dumpObjectTree(ex));
+ }
+
+ dd("}");
+ mainStep();
+}
+
+function initHandlers()
+{
+ var node;
+ node = document.getElementById("input");
+ node.addEventListener("keypress", onInputKeyPress, false);
+ node = document.getElementById("multiline-input");
+ node.addEventListener("keypress", onMultilineInputKeyPress, false);
+ node.active = false;
+
+ window.onkeypress = onWindowKeyPress;
+
+ window.isFocused = false;
+ window.addEventListener("focus", onWindowFocus, true);
+ window.addEventListener("blur", onWindowBlue, true);
+
+ client.inputPopup = null;
+
+ // Should fail silently pre-moz1.4
+ doCommandWithParams("cmd_clipboardDragDropHook",
+ {addhook: CopyPasteHandler});
+}
+
+function onClose()
+{
+ // Assume close needs authorization from user.
+ var close = false;
+
+ // Close has already been authorized.
+ if ("userClose" in client && client.userClose)
+ close = true;
+
+ // Not connected, no need for authorization.
+ if (!("getConnectionCount" in client) || (client.getConnectionCount() == 0))
+ close = true;
+
+ if (!close)
+ {
+ // Authorization needed from user.
+ client.wantToQuit();
+ return false;
+ }
+
+ return true;
+}
+
+function onUnload()
+{
+ dd("Shutting down ChatZilla.");
+
+ /* Disable every loaded & enabled plugin to give them all a chance to
+ * clean up anything beyond the ChatZilla window (e.g. libraries). All
+ * errors are disregarded as there's nothing we can do at this point.
+ * Wipe the plugin list afterwards for safety.
+ */
+ for (var k in client.plugins)
+ {
+ if ((client.plugins[k].API > 0) && client.plugins[k].enabled)
+ {
+ try
+ {
+ client.plugins[k].disable();
+ }
+ catch(ex) {}
+ }
+ }
+ client.plugins = new Object();
+
+ // Close all dialogs.
+ if ("joinDialog" in client)
+ client.joinDialog.close();
+ if ("configWindow" in client)
+ client.configWindow.close();
+ if ("installPluginDialog" in client)
+ client.installPluginDialog.close();
+ if ("aboutDialog" in client)
+ client.aboutDialog.close();
+
+ // We don't trust anybody.
+ client.hiddenDocument = null;
+ uninitOfflineIcon();
+ uninitIdleAutoAway(client.prefs["awayIdleTime"]);
+ destroy();
+}
+
+function onNotImplemented()
+{
+ alert (getMsg("onNotImplementedMsg"));
+}
+
+/* tab click */
+function onTabClick(e, id)
+{
+ if (e.which != 2)
+ return;
+
+ var tbi = document.getElementById(id);
+ var view = client.viewsArray[tbi.getAttribute("viewKey")];
+
+ if (e.which == 2)
+ {
+ dispatch("hide", { view: view.source, source: "mouse" });
+ return;
+ }
+}
+
+function onTabSelect(e)
+{
+ var tabs = e.target;
+
+ /* Hackaround, bug 314230. XBL likes focusing a tab before onload has fired.
+ * That means the tab we're trying to select here will be the hidden one,
+ * which doesn't have a viewKey. We catch that case.
+ */
+ if (!tabs.selectedItem.hasAttribute("viewKey"))
+ return;
+
+ var key = tabs.selectedItem.getAttribute("viewKey");
+ var view = client.viewsArray[key];
+ dispatch("set-current-view", {view:view.source});
+}
+
+function onMessageViewClick(e)
+{
+ if ((e.which != 1) && (e.which != 2))
+ return true;
+
+ var cx = getMessagesContext(null, e.target);
+ cx.source = "mouse";
+ cx.shiftKey = e.shiftKey;
+ var command = getEventCommand(e);
+ if (!client.commandManager.isCommandSatisfied(cx, command))
+ return false;
+
+ dispatch(command, cx);
+ dispatch("focus-input");
+ e.preventDefault();
+ return true;
+}
+
+function onMessageViewMouseDown(e)
+{
+ if ((typeof startScrolling != "function") ||
+ ((e.which != 1) && (e.which != 2)))
+ {
+ return true;
+ }
+
+ var cx = getMessagesContext(null, e.target);
+ var command = getEventCommand(e);
+ if (!client.commandManager.isCommandSatisfied(cx, command))
+ startScrolling(e);
+ return true;
+}
+
+function onMessageViewContextMenu(e)
+{
+ var elem = e.target;
+ var menu = document.getElementById("context:messages");
+ while (elem)
+ {
+ if (elem.localName && elem.localName.toLowerCase() == "input")
+ {
+ menu = document.getElementById("context:edit");
+ break;
+ }
+ elem = elem.parentNode;
+ }
+ document.popupNode = e.target;
+ if ("openPopupAtScreen" in menu)
+ menu.openPopupAtScreen(e.screenX, e.screenY, true);
+ else
+ menu.showPopup(null, e.screenX + 2, e.screenY + 2, "context", "", "");
+ e.stopPropagation();
+ e.preventDefault();
+}
+
+function getEventCommand(e)
+{
+ let where = Services.prefs.getIntPref("browser.link.open_newwindow");
+ if ((where != 3) && ((e.which == 2) || e.ctrlKey) &&
+ Services.prefs.getBoolPref("browser.tabs.opentabfor.middleclick", true))
+ where = 3;
+
+ if (where == 2)
+ return "goto-url-newwin";
+ if (where == 3)
+ return "goto-url-newtab";
+ return "goto-url";
+}
+
+function onMouseOver (e)
+{
+ var i = 0;
+ var target = e.target;
+ var status = "";
+ while (!status && target && i < 5)
+ {
+ if ("getAttribute" in target)
+ {
+ status = target.getAttribute("href");
+ if (!status)
+ status = target.getAttribute("status-text");
+ }
+ ++i;
+ target = target.parentNode;
+ }
+
+ // Setting client.status to "" will revert it to the default automatically.
+ client.status = status;
+}
+
+function onMultilineInputKeyPress (e)
+{
+ if ((e.ctrlKey || e.metaKey) && e.keyCode == 13)
+ {
+ /* meta-enter, execute buffer */
+ onMultilineSend(e);
+ }
+ else
+ {
+ if ((e.ctrlKey || e.metaKey) && e.keyCode == 40)
+ {
+ /* ctrl/meta-down, switch to single line mode */
+ dispatch ("pref multiline false");
+ }
+ }
+}
+
+function onMultilineSend(e)
+{
+ var multiline = document.getElementById("multiline-input");
+ e.line = multiline.value;
+ if (e.line.search(/\S/) == -1)
+ return;
+ onInputCompleteLine (e);
+ multiline.value = "";
+ if (("multiLineForPaste" in client) && client.multiLineForPaste)
+ client.prefs["multiline"] = false;
+}
+
+function onTooltip(event)
+{
+ const XLinkNS = "http://www.w3.org/1999/xlink";
+
+ var tipNode = event.originalTarget;
+ var titleText = null;
+ var XLinkTitleText = null;
+
+ var element = document.tooltipNode;
+ while (element && (element != document.documentElement))
+ {
+ if (element.nodeType == Node.ELEMENT_NODE)
+ {
+ var text;
+ if (element.hasAttribute("title"))
+ text = element.getAttribute("title");
+ else if (element.hasAttributeNS(XLinkNS, "title"))
+ text = element.getAttributeNS(XLinkNS, "title");
+
+ if (text)
+ {
+ tipNode.setAttribute("label", text);
+ return true;
+ }
+ }
+
+ element = element.parentNode;
+ }
+
+ return false;
+}
+
+function onInputKeyPress (e)
+{
+ if (client.prefs["outgoing.colorCodes"])
+ setTimeout(onInputKeyPressCallback, 100, e.target);
+
+ switch (e.keyCode)
+ {
+ case 9: /* tab */
+ if (!e.ctrlKey && !e.metaKey)
+ {
+ onTabCompleteRequest(e);
+ e.preventDefault();
+ }
+ return;
+
+ case 77: /* Hackaround for carbon on mac sending us this instead of 13
+ * for ctrl+enter. 77 = "M", and ctrl+M was originally used
+ * to send a CR in a terminal. */
+ // Fallthrough if ctrl was pressed, otherwise break out to default:
+ if (!e.ctrlKey)
+ break;
+
+ case 13: /* CR */
+ e.line = e.target.value;
+ e.target.value = "";
+ if (e.line.search(/\S/) == -1)
+ return;
+ if (e.ctrlKey)
+ e.line = client.COMMAND_CHAR + "say " + e.line;
+ onInputCompleteLine (e);
+ return;
+
+ case 37: /* left */
+ if (e.altKey && e.metaKey)
+ cycleView(-1);
+ return;
+
+ case 38: /* up */
+ if (e.ctrlKey || e.metaKey)
+ {
+ /* ctrl/meta-up, switch to multi line mode */
+ dispatch ("pref multiline true");
+ }
+ else
+ {
+ if (client.lastHistoryReferenced == -2)
+ {
+ client.lastHistoryReferenced = -1;
+ e.target.value = client.incompleteLine;
+ }
+ else if (client.lastHistoryReferenced <
+ client.inputHistory.length - 1)
+ {
+ e.target.value =
+ client.inputHistory[++client.lastHistoryReferenced];
+ }
+ }
+ e.preventDefault();
+ return;
+
+ case 39: /* right */
+ if (e.altKey && e.metaKey)
+ cycleView(+1);
+ return;
+
+ case 40: /* down */
+ if (client.lastHistoryReferenced > 0)
+ e.target.value =
+ client.inputHistory[--client.lastHistoryReferenced];
+ else if (client.lastHistoryReferenced == -1)
+ {
+ e.target.value = "";
+ client.lastHistoryReferenced = -2;
+ }
+ else
+ {
+ client.lastHistoryReferenced = -1;
+ e.target.value = client.incompleteLine;
+ }
+ e.preventDefault();
+ return;
+ }
+ client.lastHistoryReferenced = -1;
+ client.incompleteLine = e.target.value;
+}
+
+function onTabCompleteRequest (e)
+{
+ var elem = document.commandDispatcher.focusedElement;
+ var singleInput = document.getElementById("input");
+ if (document.getBindingParent(elem) != singleInput)
+ return;
+
+ var selStart = singleInput.selectionStart;
+ var selEnd = singleInput.selectionEnd;
+ var line = singleInput.value;
+
+ if (!line)
+ {
+ if ("defaultCompletion" in client.currentObject)
+ singleInput.value = client.currentObject.defaultCompletion;
+ // If there was nothing to complete, help the user:
+ if (!singleInput.value)
+ display(MSG_LEAVE_INPUTBOX, MT_INFO);
+ return;
+ }
+
+ if (selStart != selEnd)
+ {
+ /* text is highlighted, just move caret to end and exit */
+ singleInput.selectionStart = singleInput.selectionEnd = line.length;
+ return;
+ }
+
+ var wordStart = line.substr(0, selStart).search(/\s\S*$/);
+ if (wordStart == -1)
+ wordStart = 0;
+ else
+ ++wordStart;
+
+ var wordEnd = line.substr(selStart).search(/\s/);
+ if (wordEnd == -1)
+ wordEnd = line.length;
+ else
+ wordEnd += selStart;
+
+ // Double tab on nothing, inform user how to get out of the input box
+ if (wordEnd == wordStart)
+ {
+ display(MSG_LEAVE_INPUTBOX, MT_INFO);
+ return;
+ }
+
+ if ("performTabMatch" in client.currentObject)
+ {
+ var word = line.substring(wordStart, wordEnd);
+ var wordLower = word.toLowerCase();
+ var d = getObjectDetails(client.currentObject);
+ if (d.server)
+ wordLower = d.server.toLowerCase(word);
+
+ var co = client.currentObject;
+
+ // We need some special knowledge of how to lower-case strings.
+ var lcFn;
+ if ("getLCFunction" in co)
+ lcFn = co.getLCFunction();
+
+ var matches = co.performTabMatch(line, wordStart, wordEnd, wordLower,
+ selStart, lcFn);
+ /* if we get null back, we're supposed to fail silently */
+ if (!matches)
+ return;
+
+ var doubleTab = false;
+ var date = new Date();
+ if ((date - client.lastTabUp) <= client.DOUBLETAB_TIME)
+ doubleTab = true;
+ else
+ client.lastTabUp = date;
+
+ if (doubleTab)
+ {
+ /* if the user hit tab twice quickly, */
+ if (matches.length > 0)
+ {
+ /* then list possible completions, */
+ display(getMsg(MSG_FMT_MATCHLIST,
+ [matches.length, word,
+ matches.sort().join(", ")]));
+ }
+ else
+ {
+ /* or display an error if there are none. */
+ display(getMsg(MSG_ERR_NO_MATCH, word), MT_ERROR);
+ }
+ }
+ else if (matches.length >= 1)
+ {
+ var match;
+ if (matches.length == 1)
+ match = matches[0];
+ else
+ match = getCommonPfx(matches, lcFn);
+ singleInput.value = line.substr(0, wordStart) + match +
+ line.substr(wordEnd);
+ if (wordEnd < line.length)
+ {
+ /* if the word we completed was in the middle if the line
+ * then move the cursor to the end of the completed word. */
+ var newpos = wordStart + match.length;
+ if (matches.length == 1)
+ {
+ /* word was fully completed, move one additional space */
+ ++newpos;
+ }
+ singleInput.selectionEnd = e.target.selectionStart = newpos;
+ }
+ }
+ }
+
+}
+
+function onWindowKeyPress(e)
+{
+ var code = Number(e.keyCode);
+ var w;
+ var newOfs;
+ var userList = document.getElementById("user-list");
+ var elemFocused = document.commandDispatcher.focusedElement;
+
+ const isMac = client.platform == "Mac";
+ const isLinux = client.platform == "Linux";
+ const isWindows = client.platform == "Windows";
+ const isOS2 = client.platform == "OS/2";
+ const isUnknown = !(isMac || isLinux || isWindows || isOS2);
+
+ switch (code)
+ {
+ case 9: /* Tab */
+ // Control-Tab => next tab (all platforms)
+ // Control-Shift-Tab => previous tab (all platforms)
+ if (e.ctrlKey && !e.altKey && !e.metaKey)
+ {
+ cycleView(e.shiftKey ? -1: 1);
+ e.preventDefault();
+ }
+ break;
+
+ case 33: /* Page Up */
+ case 34: /* Page Down */
+ // Control-Page Up => previous tab (all platforms)
+ // Control-Page Down => next tab (all platforms)
+ if ((e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey) ||
+ (e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey))
+ {
+ cycleView(2 * code - 67);
+ e.preventDefault();
+ break;
+ }
+
+ if (!e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey &&
+ (elemFocused != userList))
+ {
+ w = client.currentFrame;
+ newOfs = w.pageYOffset + (w.innerHeight * 0.75) *
+ (2 * code - 67);
+ if (newOfs > 0)
+ w.scrollTo(w.pageXOffset, newOfs);
+ else
+ w.scrollTo(w.pageXOffset, 0);
+ e.preventDefault();
+ }
+ break;
+
+ case 37: /* Left Arrow */
+ case 39: /* Right Arrow */
+ // Command-Alt-Left Arrow => previous tab (Mac only)
+ // Command-Alt-Right Arrow => next tab (Mac only)
+ if (isMac && e.metaKey && e.altKey && !e.ctrlKey && !e.shiftKey)
+ {
+ cycleView(code - 38);
+ e.preventDefault();
+ }
+ break;
+
+ case 219: /* [ */
+ case 221: /* ] */
+ // Command-Shift-[ => previous tab (Mac only)
+ // Command-Shift-] => next tab (Mac only)
+ if (isMac && e.metaKey && e.shiftKey && !e.altKey && !e.ctrlKey)
+ {
+ cycleView(code - 220);
+ e.preventDefault();
+ }
+ break;
+
+ case 117: /* F6 */
+ // F6 => focus next (all platforms)
+ // Shift-F6 => focus previous (all platforms)
+ if (!e.altKey && !e.ctrlKey && !e.metaKey)
+ {
+ advanceKeyboardFocus(e.shiftKey ? -1 : 1);
+ e.preventDefault();
+ }
+ break;
+ }
+
+ // Code is zero if we have a typeable character triggering the event.
+ if (code != 0)
+ return;
+
+ // OS X only: Command-{ and Command-}
+ // Newer geckos seem to only provide these keys in charCode, not keyCode
+ if (isMac && e.metaKey && e.shiftKey && !e.altKey && !e.ctrlKey)
+ {
+ if (e.charCode == 123 || e.charCode == 125)
+ {
+ cycleView(e.charCode - 124);
+ e.preventDefault();
+ return;
+ }
+ }
+
+ // Numeric shortcuts
+
+ // The following code is copied from:
+ // /mozilla/browser/base/content/browser.js
+ // Revision: 1.748
+ // Lines: 1397-1421
+
+ // \d in a RegExp will find any Unicode character with the "decimal digit"
+ // property (Nd)
+ var regExp = /\d/;
+ if (!regExp.test(String.fromCharCode(e.charCode)))
+ return;
+
+ // Some Unicode decimal digits are in the range U+xxx0 - U+xxx9 and some are
+ // in the range U+xxx6 - U+xxxF. Find the digit 1 corresponding to our
+ // character.
+ var digit1 = (e.charCode & 0xFFF0) | 1;
+ if (!regExp.test(String.fromCharCode(digit1)))
+ digit1 += 6;
+
+ var idx = e.charCode - digit1;
+
+ if ((0 <= idx) && (idx <= 8))
+ {
+ var modifier = (e.altKey ? 0x1 : 0) |
+ (e.ctrlKey ? 0x2 : 0) |
+ (e.shiftKey ? 0x4 : 0) |
+ (e.metaKey ? 0x8 : 0);
+
+ var modifierMask;
+ if (client.prefs["tabGotoKeyModifiers"])
+ modifierMask = client.prefs["tabGotoKeyModifiers"];
+ else
+ modifierMask = 0x1; // alt
+
+ if ((modifier & modifierMask) == modifierMask)
+ {
+ // Pressing 1-8 takes you to that tab, while pressing 9 takes you
+ // to the last tab always.
+ if (idx == 8)
+ idx = client.viewsArray.length - 1;
+
+ if ((idx in client.viewsArray) && client.viewsArray[idx].source)
+ {
+ var newView = client.viewsArray[idx].source;
+ dispatch("set-current-view", { view: newView });
+ }
+ e.preventDefault();
+ return;
+ }
+ }
+}
+
+function onWindowFocus(e)
+{
+ window.isFocused = true;
+}
+
+function onWindowBlue(e)
+{
+ window.isFocused = false;
+
+ // If we're tracking last read lines, set a mark on the current view
+ // when losing focus.
+ if (client.currentObject && client.currentObject.prefs["autoMarker"])
+ client.currentObject.dispatch("marker-set");
+}
+
+function onInputCompleteLine(e)
+{
+ if (!client.inputHistory.length || client.inputHistory[0] != e.line)
+ {
+ client.inputHistory.unshift(e.line);
+ if (client.inputHistoryLogger)
+ client.inputHistoryLogger.append(e.line);
+ }
+
+ if (client.inputHistory.length > client.MAX_HISTORY)
+ client.inputHistory.pop();
+
+ client.lastHistoryReferenced = -1;
+ client.incompleteLine = "";
+
+ if (e.line[0] == client.COMMAND_CHAR)
+ {
+ if (client.prefs["outgoing.colorCodes"])
+ e.line = replaceColorCodes(e.line);
+ dispatch(e.line.substr(1), null, true);
+ }
+ else /* plain text */
+ {
+ /* color codes */
+ if (client.prefs["outgoing.colorCodes"])
+ e.line = replaceColorCodes(e.line);
+ client.sayToCurrentTarget(e.line, true);
+ }
+}
+
+function onNotifyTimeout()
+{
+ for (var n in client.networks)
+ {
+ var net = client.networks[n];
+ if (net.isConnected()) {
+ if ((net.prefs["notifyList"].length > 0) &&
+ (!net.primServ.supports["monitor"])) {
+ let isonList = net.prefs["notifyList"];
+ net.primServ.sendData ("ISON " + isonList.join(" ") + "\n");
+ } else {
+ /* Just send a ping to see if we're alive. */
+ net.primServ.sendData ("PING :ALIVECHECK\n");
+ }
+ }
+ }
+}
+
+function onWhoTimeout()
+{
+ function checkWho()
+ {
+ var checkNext = (net.lastWhoCheckChannel == null);
+ for (var c in net.primServ.channels)
+ {
+ var chan = net.primServ.channels[c];
+
+ if (checkNext && chan.active &&
+ chan.getUsersLength() < client.prefs["autoAwayCap"])
+ {
+ net.primServ.LIGHTWEIGHT_WHO = true;
+ net.primServ.who(chan.unicodeName);
+ net.lastWhoCheckChannel = chan;
+ net.lastWhoCheckTime = Number(new Date());
+ return;
+ }
+
+ if (chan == net.lastWhoCheckChannel)
+ checkNext = true;
+ }
+ if (net.lastWhoCheckChannel)
+ {
+ net.lastWhoCheckChannel = null;
+ checkWho();
+ }
+ };
+
+ for (var n in client.networks)
+ {
+ var net = client.networks[n];
+ var period = net.prefs["autoAwayPeriod"];
+ // The time since the last check, with a 5s error margin to
+ // stop us from not checking because the timer fired a tad early:
+ var waited = Number(new Date()) - net.lastWhoCheckTime + 5000;
+ if (net.isConnected() && (period != 0) && (period * 60000 < waited) &&
+ !net.primServ.caps["away-notify"])
+ checkWho();
+ }
+}
+
+function onInputKeyPressCallback (el)
+{
+ function doPopup(popup)
+ {
+ if (client.inputPopup && client.inputPopup != popup)
+ client.inputPopup.hidePopup();
+
+ client.inputPopup = popup;
+ if (popup)
+ {
+ if (el.nodeName == "textbox")
+ {
+ popup.showPopup(el, -1, -1, "tooltip", "topleft", "bottomleft");
+ }
+ else
+ {
+ var box = el.ownerDocument.getBoxObjectFor(el);
+ var pos = { x: client.mainWindow.screenX + box.screenX + 5,
+ y: client.mainWindow.screenY + box.screenY + box.height + 25 };
+ popup.moveTo(pos.x, pos.y);
+ popup.showPopup(el, 0, 0, "tooltip");
+ }
+ }
+ }
+
+ var text = " " + el.value.substr(0, el.selectionStart);
+ if (el.selectionStart != el.selectionEnd)
+ text = "";
+
+ if (text.match(/[^%]%C[0-9]{0,2},?[0-9]{0,2}$/))
+ doPopup(document.getElementById("colorTooltip"));
+ else if (text.match(/[^%]%$/))
+ doPopup(document.getElementById("percentTooltip"));
+ else
+ doPopup(null);
+}
+
+function onUserDoubleClick(event)
+{
+ if ((event.button != 0) ||
+ event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
+ {
+ return;
+ }
+ var userList = document.getElementById("user-list");
+ if (!userList.view || !userList.view.selection)
+ return;
+ var currentIndex = userList.view.selection.currentIndex;
+ if (currentIndex < 0)
+ return;
+ var nickname = getNicknameForUserlistRow(currentIndex);
+ dispatch("query", {nickname: nickname, source: "mouse"});
+}
+
+client.onFindEnd =
+CIRCNetwork.prototype.onFindEnd =
+CIRCChannel.prototype.onFindEnd =
+CIRCUser.prototype.onFindEnd =
+CIRCDCCChat.prototype.onFindEnd =
+CIRCDCCFileTransfer.prototype.onFindEnd =
+function this_onfindend(e)
+{
+ this.scrollToElement("selection", "inview");
+}
+
+CIRCChannel.prototype._updateConferenceMode =
+function my_updateconfmode()
+{
+ const minDiff = client.CONFERENCE_LOW_PASS;
+
+ var enabled = this.prefs["conference.enabled"];
+ var userLimit = this.prefs["conference.limit"];
+ var userCount = this.getUsersLength();
+
+ if (userLimit == 0)
+ {
+ // userLimit == 0 --> always off.
+ if (enabled)
+ this.prefs["conference.enabled"] = false;
+ }
+ else if (userLimit == 1)
+ {
+ // userLimit == 1 --> always on.
+ if (!enabled)
+ this.prefs["conference.enabled"] = true;
+ }
+ else if (enabled && (userCount < userLimit - minDiff))
+ {
+ this.prefs["conference.enabled"] = false;
+ }
+ else if (!enabled && (userCount > userLimit + minDiff))
+ {
+ this.prefs["conference.enabled"] = true;
+ }
+}
+
+CIRCServer.prototype.CTCPHelpClientinfo =
+function serv_ccinfohelp()
+{
+ return MSG_CTCPHELP_CLIENTINFO;
+}
+
+CIRCServer.prototype.CTCPHelpAction =
+function serv_ccinfohelp()
+{
+ return MSG_CTCPHELP_ACTION;
+}
+
+CIRCServer.prototype.CTCPHelpTime =
+function serv_ccinfohelp()
+{
+ return MSG_CTCPHELP_TIME;
+}
+
+CIRCServer.prototype.CTCPHelpVersion =
+function serv_ccinfohelp()
+{
+ return MSG_CTCPHELP_VERSION;
+}
+
+CIRCServer.prototype.CTCPHelpSource =
+function serv_csrchelp()
+{
+ return MSG_CTCPHELP_SOURCE;
+}
+
+CIRCServer.prototype.CTCPHelpOs =
+function serv_oshelp()
+{
+ return MSG_CTCPHELP_OS;
+}
+
+CIRCServer.prototype.CTCPHelpHost =
+function serv_hosthelp()
+{
+ return MSG_CTCPHELP_HOST;
+}
+
+CIRCServer.prototype.CTCPHelpPing =
+function serv_ccinfohelp()
+{
+ return MSG_CTCPHELP_PING;
+}
+
+CIRCServer.prototype.CTCPHelpDcc =
+function serv_ccinfohelp()
+{
+ return MSG_CTCPHELP_DCC;
+}
+
+/**
+ * Calculates delay before the next automatic connection attempt.
+ *
+ * If the number of connection attempts is limited, use fixed interval
+ * MIN_RECONNECT_MS. For unlimited attempts (-1), use exponential backoff: the
+ * interval between connection attempts to the network (not individual
+ * servers) is doubled after each attempt, up to MAX_RECONNECT_MS.
+ */
+CIRCNetwork.prototype.getReconnectDelayMs =
+function my_getReconnectDelayMs()
+{
+ var nServers = this.serverList.length;
+
+ if ((-1 != this.MAX_CONNECT_ATTEMPTS) ||
+ (0 != this.connectCandidate % nServers))
+ {
+ return this.MIN_RECONNECT_MS;
+ }
+
+ var networkRound = Math.ceil(this.connectCandidate / nServers);
+
+ var rv = this.MIN_RECONNECT_MS * Math.pow(2, networkRound - 1);
+
+ // clamp rv between MIN/MAX_RECONNECT_MS
+ rv = Math.min(Math.max(rv, this.MIN_RECONNECT_MS), this.MAX_RECONNECT_MS);
+
+ return rv;
+}
+
+CIRCNetwork.prototype.onInit =
+function net_oninit ()
+{
+ this.logFile = null;
+ this.lastServer = null;
+}
+
+CIRCNetwork.prototype.onInfo =
+function my_netinfo (e)
+{
+ this.display(e.msg, "INFO", undefined, undefined, e.tags);
+}
+
+CIRCNetwork.prototype.onUnknown =
+function my_unknown (e)
+{
+ if ("pendingWhoisLines" in e.server)
+ {
+ /* whois lines always have the nick in param 2 */
+ e.user = new CIRCUser(e.server, null, e.params[2]);
+
+ e.destMethod = "onUnknownWhois";
+ e.destObject = this;
+ return;
+ }
+
+ e.params.shift(); /* remove the code */
+ e.params.shift(); /* and the dest. nick (always me) */
+
+ // Handle random IRC numerics automatically.
+ var msg = getMsg("msg.irc." + e.code, null, "");
+ if (msg)
+ {
+ if (arrayIndexOf(e.server.channelTypes, e.params[0][0]) != -1)
+ {
+ // Message about a channel (e.g. join failed).
+ e.channel = new CIRCChannel(e.server, null, e.params[0]);
+ }
+
+ var targetDisplayObj = this;
+ if (e.channel && ("messages" in e.channel))
+ targetDisplayObj = e.channel;
+
+ // Check for /knock support for the +i message.
+ if (((e.code == 471) || (e.code == 473) || (e.code == 475)) &&
+ ("knock" in e.server.servCmds))
+ {
+ var args = [msg, e.channel.unicodeName,
+ "knock " + e.channel.unicodeName];
+ msg = getMsg("msg.irc." + e.code + ".knock", args, "");
+ client.munger.getRule(".inline-buttons").enabled = true;
+ targetDisplayObj.display(msg, undefined, undefined, undefined,
+ e.tags);
+ client.munger.getRule(".inline-buttons").enabled = false;
+ }
+ else
+ {
+ targetDisplayObj.display(msg, undefined, undefined, undefined,
+ e.tags);
+ }
+
+ if (e.channel)
+ {
+ if (e.channel.busy)
+ {
+ e.channel.busy = false;
+ updateProgress();
+ }
+ }
+ else
+ {
+ // Network type error?
+ if (this.busy)
+ {
+ this.busy = false;
+ updateProgress();
+ }
+ }
+ return;
+ }
+
+ /* if it looks like some kind of "end of foo" code, and we don't
+ * already have a mapping for it, make one up */
+ var length = e.params.length;
+ if (!(e.code in client.responseCodeMap) &&
+ (e.params[length - 1].search (/^end of/i) != -1))
+ {
+ client.responseCodeMap[e.code] = "---";
+ }
+
+ this.display(toUnicode(e.params.join(" "), this), e.code.toUpperCase(),
+ undefined, undefined, e.tags);
+}
+
+CIRCNetwork.prototype.lastWhoCheckChannel = null;
+CIRCNetwork.prototype.lastWhoCheckTime = 0;
+CIRCNetwork.prototype.on001 = /* Welcome! */
+CIRCNetwork.prototype.on002 = /* your host is */
+CIRCNetwork.prototype.on003 = /* server born-on date */
+CIRCNetwork.prototype.on004 = /* server id */
+CIRCNetwork.prototype.on005 = /* server features */
+CIRCNetwork.prototype.on250 = /* highest connection count */
+CIRCNetwork.prototype.on251 = /* users */
+CIRCNetwork.prototype.on252 = /* opers online (in params[2]) */
+CIRCNetwork.prototype.on254 = /* channels found (in params[2]) */
+CIRCNetwork.prototype.on255 = /* link info */
+CIRCNetwork.prototype.on265 = /* local user details */
+CIRCNetwork.prototype.on266 = /* global user details */
+CIRCNetwork.prototype.on375 = /* start of MOTD */
+CIRCNetwork.prototype.on372 = /* MOTD line */
+CIRCNetwork.prototype.on376 = /* end of MOTD */
+CIRCNetwork.prototype.on422 = /* no MOTD */
+CIRCNetwork.prototype.on670 = /* STARTTLS Success */
+CIRCNetwork.prototype.on691 = /* STARTTLS Failure */
+CIRCNetwork.prototype.on902 = /* SASL Nick locked */
+CIRCNetwork.prototype.on903 = /* SASL Auth success */
+CIRCNetwork.prototype.on904 = /* SASL Auth failed */
+CIRCNetwork.prototype.on905 = /* SASL Command too long */
+CIRCNetwork.prototype.on906 = /* SASL Aborted */
+CIRCNetwork.prototype.on907 = /* SASL Already authenticated */
+CIRCNetwork.prototype.on908 = /* SASL Mechanisms */
+function my_showtonet (e)
+{
+ var p = (3 in e.params) ? e.params[2] + " " : "";
+ var str = "";
+
+ switch (e.code)
+ {
+ case "004":
+ case "005":
+ str = e.params.slice(3).join(" ");
+ break;
+
+ case "001":
+ // Code moved to lower down to speed this bit up. :)
+ var c, u;
+ // If we've switched servers, *first* we must rehome our objects.
+ if (this.lastServer && (this.lastServer != this.primServ))
+ {
+ for (c in this.lastServer.channels)
+ this.lastServer.channels[c].rehome(this.primServ);
+ for (u in this.lastServer.users)
+ this.lastServer.users[u].rehome(this.primServ);
+
+ // This makes sure we have the *right* me object.
+ this.primServ.me.rehome(this.primServ);
+ }
+
+ // Update the list of ignored users from the prefs:
+ var ignoreAry = this.prefs["ignoreList"];
+ for (var j = 0; j < ignoreAry.length; ++j)
+ this.ignoreList[ignoreAry[j]] = getHostmaskParts(ignoreAry[j]);
+
+ // Update everything.
+ // Welcome to history.
+ addURLToHistory(this.getURL());
+ updateTitle(this);
+ this.updateHeader();
+ client.updateHeader();
+ updateSecurityIcon();
+ updateStalkExpression(this);
+
+ client.ident.removeNetwork(this);
+
+ // Figure out what nick we *really* want:
+ if (this.prefs["away"] && this.prefs["awayNick"])
+ this.preferredNick = this.prefs["awayNick"];
+ else
+ this.preferredNick = this.prefs["nickname"];
+
+ // Pretend this never happened.
+ delete this.pendingNickChange;
+
+ str = e.decodeParam(2);
+
+ break;
+
+ case "251": /* users */
+ this.doAutoPerform();
+
+ // Set our initial monitor list
+ if ((this.primServ.supports["monitor"]) &&
+ (this.prefs["notifyList"].length > 0))
+ {
+ this.primServ.sendMonitorList(this.prefs["notifyList"], true);
+ }
+
+ this.isIdleAway = client.isIdleAway;
+ if (this.prefs["away"])
+ this.dispatch("away", { reason: this.prefs["away"] });
+
+ if (this.lastServer)
+ {
+ // Re-join channels from previous connection.
+ for (c in this.primServ.channels)
+ {
+ var chan = this.primServ.channels[c];
+ if (chan.joined)
+ chan.join(chan.mode.key);
+ }
+ }
+ this.lastServer = this.primServ;
+
+ if ("pendingURLs" in this)
+ {
+ var target = this.pendingURLs.pop();
+ while (target)
+ {
+ gotoIRCURL(target.url, target.e);
+ target = this.pendingURLs.pop();
+ }
+ delete this.pendingURLs;
+ }
+
+ // Do this after the JOINs, so they are quicker.
+ // This is not time-critical code.
+ if (client.prefs["dcc.enabled"] && this.prefs["dcc.useServerIP"])
+ {
+ var delayFn = function(t) {
+ // This is the quickest way to get out host/IP.
+ t.pendingUserhostReply = true;
+ t.primServ.sendData("USERHOST " +
+ t.primServ.me.encodedName + "\n");
+ };
+ setTimeout(delayFn, 1000 * Math.random(), this);
+ }
+
+ // Had some collision during connect.
+ if (this.primServ.me.unicodeName != this.preferredNick)
+ {
+ this.reclaimLeft = this.RECLAIM_TIMEOUT;
+ this.reclaimName();
+ }
+
+ if ("onLogin" in this)
+ {
+ ev = new CEvent("network", "login", this, "onLogin");
+ client.eventPump.addEvent(ev);
+ }
+
+ str = e.decodeParam(e.params.length - 1);
+ break;
+
+ case "376": /* end of MOTD */
+ case "422": /* no MOTD */
+ this.busy = false;
+ updateProgress();
+
+ /* Some servers (wrongly) dont send 251, so try
+ auto-perform after the MOTD as well */
+ this.doAutoPerform();
+ /* no break */
+
+ case "372":
+ case "375":
+ case "376":
+ if (this.IGNORE_MOTD)
+ return;
+ /* no break */
+
+ default:
+ str = e.decodeParam(e.params.length - 1);
+ break;
+ }
+
+ this.displayHere(p + str, e.code.toUpperCase(), undefined, undefined,
+ e.tags);
+}
+
+CIRCNetwork.prototype.onUnknownCTCPReply =
+function my_ctcprunk (e)
+{
+ this.display(getMsg(MSG_FMT_CTCPREPLY,
+ [toUnicode(e.CTCPCode, this),
+ toUnicode(e.CTCPData, this), e.user.unicodeName]),
+ "CTCP_REPLY", e.user, e.server.me, e.tags);
+}
+
+CIRCNetwork.prototype.onNotice =
+function my_notice(e)
+{
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ this.display(e.decodeParam(2), "NOTICE", this, e.server.me, e.tags);
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+CIRCNetwork.prototype.onPrivmsg =
+function my_privmsg(e)
+{
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ this.display(e.decodeParam(2), "PRIVMSG", this, e.server.me, e.tags);
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+/* userhost reply */
+CIRCNetwork.prototype.on302 =
+function my_302(e)
+{
+ if (client.prefs["dcc.enabled"] && this.prefs["dcc.useServerIP"] &&
+ ("pendingUserhostReply" in this))
+ {
+ var me = new RegExp("^" + this.primServ.me.encodedName + "\\*?=", "i");
+ if (e.params[2].match(me))
+ client.dcc.addHost(this.primServ.me.host, true);
+
+ delete this.pendingUserhostReply;
+ return true;
+ }
+
+ e.destMethod = "onUnknown";
+ e.destObject = this;
+
+ return true;
+}
+
+CIRCNetwork.prototype.on303 = /* ISON (aka notify) reply */
+function my_303 (e)
+{
+ function lower(text)
+ {
+ return e.server.toLowerCase(text);
+ };
+
+ var onList = new Array();
+ // split() gives an array of one item ("") when splitting "", which we
+ // don't want, so only do the split if there's something to split.
+ if (e.params[2])
+ onList = stringTrim(e.server.toLowerCase(e.params[2])).split(/\s+/);
+ var offList = new Array();
+ var newArrivals = new Array();
+ var newDepartures = new Array();
+ var o = getObjectDetails(client.currentObject);
+ var displayTab;
+ var i;
+
+ if ("network" in o && o.network == this && client.currentObject != this)
+ displayTab = client.currentObject;
+
+ for (i = 0; i < this.prefs["notifyList"].length; i++)
+ {
+ if (!arrayContains(onList, lower(this.prefs["notifyList"][i])))
+ /* user is not on */
+ offList.push(lower(this.prefs["notifyList"][i]));
+ }
+
+ if ("onList" in this)
+ {
+ for (i in onList)
+ if (!arrayContains(this.onList, onList[i]))
+ /* we didn't know this person was on */
+ newArrivals.push(onList[i]);
+ }
+ else
+ this.onList = newArrivals = onList;
+
+ if ("offList" in this)
+ {
+ for (i in offList)
+ if (!arrayContains(this.offList, offList[i]))
+ /* we didn't know this person was off */
+ newDepartures.push(offList[i]);
+ }
+ else
+ this.offList = newDepartures = offList;
+
+ if (newArrivals.length > 0)
+ {
+ this.displayHere (arraySpeak (newArrivals, "is", "are") +
+ " online.", "NOTIFY-ON", undefined, undefined,
+ e.tags);
+ if (displayTab)
+ displayTab.displayHere (arraySpeak (newArrivals, "is", "are") +
+ " online.", "NOTIFY-ON", undefined,
+ undefined, e.tags);
+ }
+
+ if (newDepartures.length > 0)
+ {
+ this.displayHere (arraySpeak (newDepartures, "is", "are") +
+ " offline.", "NOTIFY-OFF", undefined, undefined,
+ e.tags);
+ if (displayTab)
+ displayTab.displayHere (arraySpeak (newDepartures, "is", "are") +
+ " offline.", "NOTIFY-OFF", undefined,
+ undefined, e.tags);
+ }
+
+ this.onList = onList;
+ this.offList = offList;
+
+}
+
+CIRCNetwork.prototype.on730 = /* RPL_MONONLINE */
+CIRCNetwork.prototype.on731 = /* RPL_MONOFFLINE */
+function my_monnotice(e)
+{
+ var userList = e.params[2].split(",");
+ var nickList = [];
+ var o = getObjectDetails(client.currentObject);
+ var displayTab;
+ var i;
+ var msg;
+
+ if ("network" in o && o.network == this && client.currentObject != this)
+ displayTab = client.currentObject;
+
+ for (i = 0; i < userList.length; i++)
+ {
+ var nick = e.server.toLowerCase(userList[i].split("!")[0]);
+
+ // Make sure this nick is in the notify list.
+ if (this.prefs["notifyList"].indexOf(nick) < 0)
+ {
+ this.prefs["notifyList"].push(nick);
+ this.prefs["notifyList"].update();
+ }
+ nickList.push(nick);
+ }
+
+ if (e.code == "730") // RPL_MONONLINE
+ msg = arraySpeak (nickList, "is", "are") + " online.";
+ else // RPL_MONOFFLINE
+ msg = arraySpeak (nickList, "is", "are") + " offline.";
+ this.displayHere(msg, e.code, undefined, undefined, e.tags);
+ if (displayTab)
+ displayTab.displayHere(msg, e.code, undefined, undefined, e.tags);
+}
+
+CIRCNetwork.prototype.on732 = /* RPL_MONLIST */
+function my_732(e)
+{
+ if (!this.pendingNotifyList)
+ this.pendingNotifyList = [];
+ var nickList = e.server.toLowerCase(e.params[2]).split(",")
+ this.pendingNotifyList = this.pendingNotifyList.concat(nickList);
+}
+
+CIRCNetwork.prototype.on733 = /* RPL_ENDOFMONLIST */
+function my_733(e)
+{
+ if (this.pendingNotifyList)
+ {
+ this.prefs["notifyList"] = this.pendingNotifyList;
+ this.prefs["notifyList"].update();
+ this.display(getMsg(MSG_NOTIFY_LIST, arraySpeak(this.pendingNotifyList)));
+ delete this.pendingNotifyList;
+ if (e.params[2])
+ this.display(e.params[2], e.code, undefined, undefined, e.tags);
+ }
+ else
+ {
+ this.prefs["notifyList"] = [];
+ this.prefs["notifyList"].update();
+ display(MSG_NO_NOTIFY_LIST, e.code, undefined, undefined, e.tags);
+ }
+}
+
+CIRCNetwork.prototype.on734 = /* ERR_MONLISTFULL */
+function my_734(e)
+{
+ var nickList = e.server.toLowerCase(e.params[3]).split(",")
+ var i;
+ var msgname;
+
+ for (i = 0; i < nickList.length; i++)
+ {
+ var j = this.prefs["notifyList"].indexOf(nickList[i]);
+ if (j >= 0)
+ arrayRemoveAt(this.prefs["notifyList"], j);
+ }
+ this.prefs["notifyList"].update();
+
+ if (e.params[4])
+ this.display(e.params[4], e.code, undefined, undefined, e.tags)
+ else
+ this.display(MSG_NOTIFY_FULL);
+
+ msgname = (nickList.length == 1) ? MSG_NOTIFY_DELONE :
+ MSG_NOTIFY_DELSOME;
+ this.display(getMsg(msgname, arraySpeak(nickList)));
+}
+
+/* away off reply */
+CIRCNetwork.prototype.on305 =
+function my_305(e)
+{
+ this.display(MSG_AWAY_OFF, e.code, undefined, undefined, e.tags);
+
+ return true;
+}
+
+/* away on reply */
+CIRCNetwork.prototype.on306 =
+function my_306(e)
+{
+ var idleMsgParams = [this.prefs["away"], client.prefs["awayIdleTime"]];
+ if (!this.isIdleAway)
+ this.display(getMsg(MSG_AWAY_ON, this.prefs["away"]), e.code,
+ undefined, undefined, e.tags);
+ else
+ this.display(getMsg(MSG_IDLE_AWAY_ON, idleMsgParams), e.code,
+ undefined, undefined, e.tags);
+
+ return true;
+}
+
+
+CIRCNetwork.prototype.on263 = /* 'try again' */
+function my_263 (e)
+{
+ /* Urgh, this one's a pain. We need to abort whatever we tried, and start
+ * it again if appropriate.
+ *
+ * Known causes of this message:
+ * - LIST, with or without a parameter.
+ */
+
+ if (("_list" in this) && !this._list.done && (this._list.count == 0))
+ {
+ // We attempted a LIST, and we think it failed. :)
+ this._list.done = true;
+ this._list.error = e.decodeParam(2);
+ // Return early for this one if we're saving it.
+ if ("file" in this._list)
+ return true;
+ }
+
+ e.destMethod = "onUnknown";
+ e.destObject = this;
+ return true;
+}
+
+CIRCNetwork.prototype.isRunningList =
+function my_running_list()
+{
+ /* The list is considered "running" when a cancel is effective. This means
+ * that even if _list.done is true (finished recieving data), we will still
+ * be "running" whilst we have undisplayed items.
+ */
+ return (("_list" in this) &&
+ (!this._list.done || (this._list.length > this._list.displayed)) &&
+ !this._list.cancelled);
+}
+
+CIRCNetwork.prototype.list =
+function my_list(word, file)
+{
+ const NORMAL_FILE_TYPE = Components.interfaces.nsIFile.NORMAL_FILE_TYPE;
+
+ if (("_list" in this) && !this._list.done)
+ return false;
+
+ this._list = new Array();
+ this._list.string = word;
+ this._list.regexp = null;
+ this._list.done = false;
+ this._list.count = 0;
+ if (file)
+ {
+ var lfile = new LocalFile(file);
+ if (!lfile.localFile.exists())
+ {
+ // futils.umask may be 0022. Result is 0644.
+ lfile.localFile.create(NORMAL_FILE_TYPE, 0o666 & ~futils.umask);
+ }
+ this._list.file = new LocalFile(lfile.localFile, ">");
+ }
+
+ if (isinstance(word, RegExp))
+ {
+ this._list.regexp = word;
+ this._list.string = "";
+ word = "";
+ }
+
+ if (word)
+ this.primServ.sendData("LIST " + fromUnicode(word, this) + "\n");
+ else
+ this.primServ.sendData("LIST\n");
+
+ return true;
+}
+
+CIRCNetwork.prototype.listInit =
+function my_list_init ()
+{
+ function checkEndList (network)
+ {
+ if (network._list.count == network._list.lastLength)
+ {
+ network.on323();
+ }
+ else
+ {
+ network._list.lastLength = network._list.count;
+ network._list.endTimeout =
+ setTimeout(checkEndList, 5000, network);
+ }
+ }
+
+ function outputList (network)
+ {
+ const CHUNK_SIZE = 5;
+ var list = network._list;
+ if (list.cancelled)
+ {
+ if (list.done)
+ {
+ /* The server is no longer throwing stuff at us, so now
+ * we can safely kill the list.
+ */
+ network.display(getMsg(MSG_LIST_END,
+ [list.displayed, list.count]));
+ delete network._list;
+ }
+ else
+ {
+ /* We cancelled the list, but we're still getting data.
+ * Handle that data, but don't display, and do it more
+ * slowly, so we cause less lag.
+ */
+ setTimeout(outputList, 1000, network);
+ }
+ return;
+ }
+ if (list.length > list.displayed)
+ {
+ var start = list.displayed;
+ var end = list.length;
+ if (end - start > CHUNK_SIZE)
+ end = start + CHUNK_SIZE;
+ for (var i = start; i < end; ++i)
+ network.displayHere(getMsg(MSG_FMT_CHANLIST, list[i]), "322",
+ undefined, undefined, list[i][3]);
+ list.displayed = end;
+ }
+ if (list.done && (list.displayed == list.length))
+ {
+ if (list.event323)
+ {
+ var length = list.event323.params.length;
+ network.displayHere(list.event323.params[length - 1], "323",
+ undefined, undefined, list.event323.tags);
+ }
+ network.displayHere(getMsg(MSG_LIST_END,
+ [list.displayed, list.count]));
+ }
+ else
+ {
+ setTimeout(outputList, 250, network);
+ }
+ }
+
+ if (!("_list" in this))
+ {
+ this._list = new Array();
+ this._list.string = MSG_UNKNOWN;
+ this._list.regexp = null;
+ this._list.done = false;
+ this._list.count = 0;
+ }
+
+ if (!("file" in this._list))
+ {
+ this._list.displayed = 0;
+ if (client.currentObject != this)
+ display (getMsg(MSG_LIST_REROUTED, this.unicodeName));
+ setTimeout(outputList, 250, this);
+ }
+ this._list.lastLength = 0;
+ this._list.endTimeout = setTimeout(checkEndList, 5000, this);
+}
+
+CIRCNetwork.prototype.abortList =
+function my_abortList()
+{
+ this._list.cancelled = true;
+}
+
+CIRCNetwork.prototype.on321 = /* LIST reply header */
+function my_321 (e)
+{
+ this.listInit();
+
+ if (!("file" in this._list))
+ this.displayHere (e.params[2] + " " + e.params[3], "321");
+}
+
+CIRCNetwork.prototype.on323 = /* end of LIST reply */
+function my_323 (e)
+{
+ if (this._list.endTimeout)
+ {
+ clearTimeout(this._list.endTimeout);
+ delete this._list.endTimeout;
+ }
+ if (("file" in this._list))
+ this._list.file.close();
+
+ this._list.done = true;
+ this._list.event323 = e;
+}
+
+CIRCNetwork.prototype.on322 = /* LIST reply */
+function my_listrply (e)
+{
+ if (!("_list" in this) || !("lastLength" in this._list))
+ this.listInit();
+
+ ++this._list.count;
+
+ /* If the list has been cancelled, don't bother adding all this info
+ * anymore. Do increase the count (above), otherwise we never truly notice
+ * the list being finished.
+ */
+ if (this._list.cancelled)
+ return;
+
+ var chanName = e.decodeParam(2);
+ var topic = e.decodeParam(4);
+ if (!this._list.regexp || chanName.match(this._list.regexp) ||
+ topic.match(this._list.regexp))
+ {
+ if (!("file" in this._list))
+ {
+ this._list.push([chanName, e.params[3], topic, e.tags]);
+ }
+ else
+ {
+ this._list.file.write(fromUnicode(chanName, "UTF-8") + " " +
+ e.params[3] + " " +
+ fromUnicode(topic, "UTF-8") + "\n");
+ }
+ }
+}
+
+CIRCNetwork.prototype.on401 = /* ERR_NOSUCHNICK */
+CIRCNetwork.prototype.on402 = /* ERR_NOSUCHSERVER */
+CIRCNetwork.prototype.on403 = /* ERR_NOSUCHCHANNEL */
+function my_401(e)
+{
+ var server, channel, user;
+
+ /* Note that servers generally only send 401 and 402, sharing the former
+ * between nicknames and channels, but we're ready for anything.
+ */
+ if (e.code == 402)
+ server = e.decodeParam(2);
+ else if (arrayIndexOf(e.server.channelTypes, e.params[2][0]) != -1)
+ channel = new CIRCChannel(e.server, null, e.params[2]);
+ else
+ user = new CIRCUser(e.server, null, e.params[2]);
+
+ if (user && this.whoisList && (user.collectionKey in this.whoisList))
+ {
+ // If this is from a /whois, send a /whowas and don't display anything.
+ this.primServ.whowas(user.unicodeName, 1);
+ this.whoisList[user.collectionKey] = false;
+ return;
+ }
+
+ if (user)
+ user.display(getMsg(MSG_IRC_401, [user.unicodeName]), e.code,
+ undefined, undefined, e.tags);
+ else if (server)
+ this.display(getMsg(MSG_IRC_402, [server]), e.code,
+ undefined, undefined, e.tags);
+ else if (channel)
+ channel.display(getMsg(MSG_IRC_403, [channel.unicodeName]), e.code,
+ undefined, undefined, e.tags);
+ else
+ dd("on401: unreachable code.");
+}
+
+/* 464; "invalid or missing password", occurs as a reply to both OPER and
+ * sometimes initially during user registration. */
+CIRCNetwork.prototype.on464 =
+function my_464(e)
+{
+ if (this.state == NET_CONNECTING)
+ {
+ // If we are in the process of connecting we are needing a login
+ // password, subtly different from after user registration.
+ this.display(MSG_IRC_464_LOGIN, e.code, undefined, undefined, e.tags);
+ }
+ else
+ {
+ e.destMethod = "onUnknown";
+ e.destObject = this;
+ }
+}
+
+/* end of WHO */
+CIRCNetwork.prototype.on315 =
+function my_315 (e)
+{
+ var matches;
+ if ("whoMatches" in this)
+ matches = this.whoMatches;
+ else
+ matches = 0;
+
+ if ("pendingWhoReply" in this)
+ this.display(getMsg(MSG_WHO_END, [e.params[2], matches]), e.code,
+ undefined, undefined, e.tags);
+
+ if ("whoUpdates" in this)
+ {
+ var userlist = document.getElementById("user-list");
+ for (var c in this.whoUpdates)
+ {
+ for (var i = 0; i < this.whoUpdates[c].length; i++)
+ {
+ var index = this.whoUpdates[c][i].chanListEntry.childIndex;
+ userlist.treeBoxObject.invalidateRow(index);
+ }
+ this.primServ.channels[c].updateUsers(this.whoUpdates[c]);
+ }
+ delete this.whoUpdates;
+ }
+
+ delete this.pendingWhoReply;
+ delete this.whoMatches;
+}
+
+CIRCNetwork.prototype.on352 =
+function my_352 (e)
+{
+ //0-352 1-sender 2-channel 3-ident 4-host
+ //5-server 6-nick 7-H/G 8-hops and realname
+ if ("pendingWhoReply" in this)
+ {
+ var status;
+ if (e.user.isAway)
+ status = MSG_GONE;
+ else
+ status = MSG_HERE;
+
+ this.display(getMsg(MSG_WHO_MATCH,
+ [e.params[6], e.params[3], e.params[4],
+ e.user.desc, status, e.decodeParam(2),
+ e.params[5], e.user.hops]), e.code, e.user,
+ undefined, e.tags);
+ }
+
+ updateTitle(e.user);
+ if ("whoMatches" in this)
+ ++this.whoMatches;
+ else
+ this.whoMatches = 1;
+
+ if (!("whoUpdates" in this))
+ this.whoUpdates = new Object();
+
+ if (e.userHasChanges)
+ {
+ for (var c in e.server.channels)
+ {
+ var chan = e.server.channels[c];
+ if (chan.active && (e.user.collectionKey in chan.users))
+ {
+ if (!(c in this.whoUpdates))
+ this.whoUpdates[c] = new Array();
+ this.whoUpdates[c].push(chan.users[e.user.collectionKey]);
+ }
+ }
+ }
+}
+
+CIRCNetwork.prototype.on354 =
+function my_354(e)
+{
+ //0-352 1-sender 2-type 3-channel 4-ident 5-host
+ //6-server 7-nick 8-H/G 9-hops 10-account 11-realname
+ if ("pendingWhoReply" in this)
+ {
+ var status;
+ if (e.user.isAway)
+ status = MSG_GONE;
+ else
+ status = MSG_HERE;
+
+ this.display(getMsg(MSG_WHO_MATCH,
+ [e.params[7], e.params[4], e.params[5],
+ e.user.desc, status, e.decodeParam(3),
+ e.params[6], e.user.hops]), e.code, e.user,
+ undefined, e.tags);
+ }
+
+ updateTitle(e.user);
+ if ("whoMatches" in this)
+ ++this.whoMatches;
+ else
+ this.whoMatches = 1;
+
+ if (!("whoUpdates" in this))
+ this.whoUpdates = new Object();
+
+ if (e.userHasChanges)
+ {
+ for (var c in e.server.channels)
+ {
+ var chan = e.server.channels[c];
+ if (chan.active && (e.user.collectionKey in chan.users))
+ {
+ if (!(c in this.whoUpdates))
+ this.whoUpdates[c] = new Array();
+ this.whoUpdates[c].push(chan.users[e.user.collectionKey]);
+ }
+ }
+ }
+}
+
+CIRCNetwork.prototype.on301 = /* user away message */
+function my_301(e)
+{
+ if (e.user.awayMessage != e.user.lastShownAwayMessage)
+ {
+ var params = [e.user.unicodeName, e.user.awayMessage];
+ e.user.display(getMsg(MSG_WHOIS_AWAY, params), e.code,
+ undefined, undefined, e.tags);
+ e.user.lastShownAwayMessage = e.user.awayMessage;
+ }
+}
+
+CIRCNetwork.prototype.on311 = /* whois name */
+CIRCNetwork.prototype.on319 = /* whois channels */
+CIRCNetwork.prototype.on312 = /* whois server */
+CIRCNetwork.prototype.on317 = /* whois idle time */
+CIRCNetwork.prototype.on318 = /* whois end of whois*/
+CIRCNetwork.prototype.on330 = /* ircu's 330 numeric ("X is logged in as Y") */
+CIRCNetwork.prototype.onUnknownWhois = /* misc whois line */
+function my_whoisreply (e)
+{
+ var text = "egads!";
+ var nick = e.params[2];
+ var lowerNick = this.primServ.toLowerCase(nick);
+ var user;
+
+ if (this.whoisList && (e.code != 318) && (lowerNick in this.whoisList))
+ this.whoisList[lowerNick] = true;
+
+ if (e.user)
+ {
+ user = e.user;
+ nick = user.unicodeName;
+ }
+
+ switch (Number(e.code))
+ {
+ case 311:
+ // Clear saved away message so it appears and can be reset.
+ if (e.user)
+ e.user.lastShownAwayMessage = "";
+
+ text = getMsg(MSG_WHOIS_NAME,
+ [nick, e.params[3], e.params[4],
+ e.decodeParam(6)]);
+ break;
+
+ case 319:
+ var ary = stringTrim(e.decodeParam(3)).split(" ");
+ text = getMsg(MSG_WHOIS_CHANNELS, [nick, arraySpeak(ary)]);
+ break;
+
+ case 312:
+ text = getMsg(MSG_WHOIS_SERVER,
+ [nick, e.params[3], e.params[4]]);
+ break;
+
+ case 317:
+ text = getMsg(MSG_WHOIS_IDLE,
+ [nick, formatDateOffset(Number(e.params[3])),
+ new Date(Number(e.params[4]) * 1000)]);
+ break;
+
+ case 318:
+ // If the user isn't here, then we sent a whowas in on401.
+ // Don't display the "end of whois" message.
+ if (this.whoisList && (lowerNick in this.whoisList) &&
+ !this.whoisList[lowerNick])
+ {
+ delete this.whoisList[lowerNick];
+ return;
+ }
+ if (this.whoisList)
+ delete this.whoisList[lowerNick];
+
+ text = getMsg(MSG_WHOIS_END, nick);
+ if (user)
+ user.updateHeader();
+ break;
+
+ case 330:
+ text = getMsg(MSG_FMT_LOGGED_ON, [e.decodeParam(2), e.params[3]]);
+ break;
+
+ default:
+ text = toUnicode(e.params.splice(2, e.params.length).join(" "),
+ this);
+ }
+
+ if (e.user)
+ e.user.display(text, e.code, undefined, undefined, e.tags);
+ else
+ this.display(text, e.code, undefined, undefined, e.tags);
+}
+
+CIRCNetwork.prototype.on341 = /* invite reply */
+function my_341 (e)
+{
+ this.display (getMsg(MSG_YOU_INVITE, [e.decodeParam(2), e.decodeParam(3)]),
+ "341", undefined, undefined, e.tags);
+}
+
+CIRCNetwork.prototype.onInvite = /* invite message */
+function my_invite (e)
+{
+ var invitee = e.params[1];
+ if (invitee == e.server.me.unicodeName)
+ {
+ client.munger.getRule(".inline-buttons").enabled = true;
+ this.display(getMsg(MSG_INVITE_YOU, [e.user.unicodeName, e.user.name,
+ e.user.host,
+ e.channel.unicodeName,
+ e.channel.unicodeName,
+ e.channel.getURL()]),
+ "INVITE", undefined, undefined, e.tags);
+ client.munger.getRule(".inline-buttons").enabled = false;
+
+ if ("messages" in e.channel)
+ e.channel.join();
+ }
+ else
+ {
+ this.display(getMsg(MSG_INVITE_SOMEONE, [e.user.unicodeName,
+ invitee,
+ e.channel.unicodeName]),
+ "INVITE", undefined, undefined, e.tags);
+ }
+}
+
+CIRCNetwork.prototype.on433 = /* nickname in use */
+function my_433 (e)
+{
+ var nick = toUnicode(e.params[2], this);
+
+ if ("pendingReclaimCheck" in this)
+ {
+ delete this.pendingReclaimCheck;
+ return;
+ }
+
+ if (this.state == NET_CONNECTING)
+ {
+ // Force a number, thanks.
+ var nickIndex = 1 * arrayIndexOf(this.prefs["nicknameList"], nick);
+ var newnick = null;
+
+ dd("433: failed with " + nick + " (" + nickIndex + ")");
+
+ var tryList = true;
+
+ if ((("_firstNick" in this) && (this._firstNick == -1)) ||
+ (this.prefs["nicknameList"].length == 0) ||
+ ((nickIndex != -1) && (this.prefs["nicknameList"].length < 2)))
+ {
+ tryList = false;
+ }
+
+ if (tryList)
+ {
+ nickIndex = (nickIndex + 1) % this.prefs["nicknameList"].length;
+
+ if (("_firstNick" in this) && (this._firstNick == nickIndex))
+ {
+ // We're back where we started. Give up with this method.
+ this._firstNick = -1;
+ tryList = false;
+ }
+ }
+
+ if (tryList)
+ {
+ newnick = this.prefs["nicknameList"][nickIndex];
+ dd(" trying " + newnick + " (" + nickIndex + ")");
+
+ // Save first index we've tried.
+ if (!("_firstNick" in this))
+ this._firstNick = nickIndex;
+ }
+ else if (this.NICK_RETRIES > 0)
+ {
+ newnick = this.INITIAL_NICK + "_";
+ this.NICK_RETRIES--;
+ dd(" trying " + newnick);
+ }
+
+ if (newnick)
+ {
+ this.INITIAL_NICK = newnick;
+ this.display(getMsg(MSG_RETRY_NICK, [nick, newnick]), "433",
+ undefined, undefined, e.tags);
+ this.primServ.changeNick(newnick);
+ }
+ else
+ {
+ this.display(getMsg(MSG_NICK_IN_USE, nick), "433",
+ undefined, undefined, e.tags);
+ }
+ }
+ else
+ {
+ this.display(getMsg(MSG_NICK_IN_USE, nick), "433",
+ undefined, undefined, e.tags);
+ }
+}
+
+CIRCNetwork.prototype.onStartConnect =
+function my_sconnect (e)
+{
+ this.busy = true;
+ updateProgress();
+ if ("_firstNick" in this)
+ delete this._firstNick;
+
+ client.munger.getRule(".inline-buttons").enabled = true;
+ this.display(getMsg(MSG_CONNECTION_ATTEMPT,
+ [this.getURL(), e.server.getURL(), this.unicodeName,
+ "cancel"]), "INFO");
+ client.munger.getRule(".inline-buttons").enabled = false;
+
+ if (this.prefs["identd.enabled"])
+ {
+ try
+ {
+ client.ident.addNetwork(this, e.server);
+ }
+ catch (ex)
+ {
+ display(getMsg(MSG_IDENT_ERROR, formatException(ex)), MT_ERROR);
+ }
+ }
+
+ this.NICK_RETRIES = this.prefs["nicknameList"].length + 3;
+
+ // When connection begins, autoperform has not been sent
+ this.autoPerformSent = false;
+}
+
+CIRCNetwork.prototype.onError =
+function my_neterror (e)
+{
+ var msg;
+ var type = MT_ERROR;
+
+ if (typeof e.errorCode != "undefined")
+ {
+ switch (e.errorCode)
+ {
+ case JSIRC_ERR_NO_SOCKET:
+ msg = MSG_ERR_NO_SOCKET;
+ break;
+
+ case JSIRC_ERR_EXHAUSTED:
+ // error already displayed in onDisconnect
+ break;
+
+ case JSIRC_ERR_OFFLINE:
+ msg = MSG_ERR_OFFLINE;
+ break;
+
+ case JSIRC_ERR_NO_SECURE:
+ msg = getMsg(MSG_ERR_NO_SECURE, this.unicodeName);
+ break;
+
+ case JSIRC_ERR_CANCELLED:
+ msg = MSG_ERR_CANCELLED;
+ type = MT_INFO;
+ break;
+
+ case JSIRC_ERR_PAC_LOADING:
+ msg = MSG_WARN_PAC_LOADING;
+ type = MT_WARN;
+ break;
+ }
+ }
+ else
+ {
+ msg = e.params[e.params.length - 1];
+ }
+
+ dispatch("sync-header");
+ updateTitle();
+
+ if (this.state == NET_OFFLINE)
+ {
+ this.busy = false;
+ updateProgress();
+ }
+
+ client.ident.removeNetwork(this);
+
+ if (msg)
+ this.display(msg, type);
+
+ if (e.errorCode == JSIRC_ERR_PAC_LOADING)
+ return;
+
+ if (this.deleteWhenDone)
+ this.dispatch("delete-view");
+
+ delete this.deleteWhenDone;
+}
+
+
+CIRCNetwork.prototype.onDisconnect =
+function my_netdisconnect (e)
+{
+ var msg, msgNetwork;
+ var msgType = MT_ERROR;
+ var retrying = true;
+
+ if (typeof e.disconnectStatus != "undefined")
+ {
+ switch (e.disconnectStatus)
+ {
+ case 0:
+ msg = getMsg(MSG_CONNECTION_CLOSED,
+ [this.getURL(), e.server.getURL()]);
+ break;
+
+ case NS_ERROR_CONNECTION_REFUSED:
+ msg = getMsg(MSG_CONNECTION_REFUSED,
+ [this.getURL(), e.server.getURL()]);
+ break;
+
+ case NS_ERROR_NET_TIMEOUT:
+ msg = getMsg(MSG_CONNECTION_TIMEOUT,
+ [this.getURL(), e.server.getURL()]);
+ break;
+
+ case NS_ERROR_NET_RESET:
+ msg = getMsg(MSG_CONNECTION_RESET,
+ [this.getURL(), e.server.getURL()]);
+ break;
+
+ case NS_ERROR_NET_INTERRUPT:
+ msg = getMsg(MSG_CONNECTION_INTERRUPT,
+ [this.getURL(), e.server.getURL()]);
+ break;
+
+ case NS_ERROR_UNKNOWN_HOST:
+ msg = getMsg(MSG_UNKNOWN_HOST,
+ [e.server.hostname, this.getURL(),
+ e.server.getURL()]);
+ break;
+
+ case NS_ERROR_UNKNOWN_PROXY_HOST:
+ msg = getMsg(MSG_UNKNOWN_PROXY_HOST,
+ [this.getURL(), e.server.getURL()]);
+ break;
+
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ msg = MSG_PROXY_CONNECTION_REFUSED;
+ break;
+
+ case NS_ERROR_OFFLINE:
+ msg = MSG_ERR_OFFLINE;
+ retrying = false;
+ break;
+
+ case NS_ERROR_ABORT:
+ if (Services.io.offline)
+ {
+ msg = getMsg(MSG_CONNECTION_ABORT_OFFLINE,
+ [this.getURL(), e.server.getURL()]);
+ }
+ else
+ {
+ msg = getMsg(MSG_CONNECTION_ABORT_UNKNOWN,
+ [this.getURL(), e.server.getURL(),
+ formatException(e.exception)]);
+ }
+ retrying = false;
+ break;
+
+ default:
+ var errClass = getNSSErrorClass(e.disconnectStatus);
+ // Check here if it's a cert error.
+ // The exception adding dialog will explain the reasons.
+ if (errClass == ERROR_CLASS_BAD_CERT)
+ {
+ var cmd = "ssl-exception";
+ cmd += " " + e.server.hostname + " " + e.server.port;
+ cmd += " true";
+ msg = getMsg(MSG_INVALID_CERT, [this.getURL(), cmd]);
+ retrying = false;
+ break;
+ }
+
+ // If it's a protocol error, we can still display a useful message.
+ var statusMsg = e.disconnectStatus;
+ if (errClass == ERROR_CLASS_SSL_PROTOCOL)
+ {
+ var nssErrSvc = getService("@mozilla.org/nss_errors_service;1",
+ "nsINSSErrorsService");
+ var errMsg = nssErrSvc.getErrorMessage(e.disconnectStatus);
+ errMsg = errMsg.replace(/\.$/, "");
+ statusMsg += " (" + errMsg + ")";
+ }
+
+ msg = getMsg(MSG_CLOSE_STATUS,
+ [this.getURL(), e.server.getURL(),
+ statusMsg]);
+ break;
+ }
+ }
+ else
+ {
+ msg = getMsg(MSG_CONNECTION_CLOSED,
+ [this.getURL(), e.server.getURL()]);
+ }
+
+ // e.quitting signals the disconnect was intended: don't use "ERROR".
+ if (e.quitting)
+ {
+ msgType = "DISCONNECT";
+ msg = getMsg(MSG_CONNECTION_QUIT,
+ [this.getURL(), e.server.getURL(), this.unicodeName,
+ "reconnect"]);
+ msgNetwork = msg;
+ }
+ // We won't reconnect if the error was really bad, or if the user doesn't
+ // want us to do so.
+ else if (!retrying || !this.stayingPower)
+ {
+ msgNetwork = msg;
+ }
+ else
+ {
+ var delayStr = formatDateOffset(this.getReconnectDelayMs() / 1000);
+ if (this.MAX_CONNECT_ATTEMPTS == -1)
+ {
+ msgNetwork = getMsg(MSG_RECONNECTING_IN,
+ [msg, delayStr, this.unicodeName, "cancel"]);
+ }
+ else if (this.connectAttempt < this.MAX_CONNECT_ATTEMPTS)
+ {
+ var left = this.MAX_CONNECT_ATTEMPTS - this.connectAttempt;
+ if (left == 1)
+ {
+ msgNetwork = getMsg(MSG_RECONNECTING_IN_LEFT1,
+ [msg, delayStr, this.unicodeName,
+ "cancel"]);
+ }
+ else
+ {
+ msgNetwork = getMsg(MSG_RECONNECTING_IN_LEFT,
+ [msg, left, delayStr, this.unicodeName,
+ "cancel"]);
+ }
+ }
+ else
+ {
+ msgNetwork = getMsg(MSG_CONNECTION_EXHAUSTED, msg);
+ }
+ }
+
+ /* If we were connected ok, put an error on all tabs. If we were only
+ * /trying/ to connect, and failed, just put it on the network tab.
+ */
+ client.munger.getRule(".inline-buttons").enabled = true;
+ if (this.state == NET_ONLINE)
+ {
+ for (var v in client.viewsArray)
+ {
+ var obj = client.viewsArray[v].source;
+ if (obj == this)
+ {
+ obj.displayHere(msgNetwork, msgType);
+ }
+ else if (obj != client)
+ {
+ var details = getObjectDetails(obj);
+ if ("server" in details && details.server == e.server)
+ obj.displayHere(msg, msgType);
+ }
+ }
+ }
+ else
+ {
+ this.busy = false;
+ updateProgress();
+
+ // Don't do anything if we're cancelling.
+ if (this.state != NET_CANCELLING)
+ {
+ this.displayHere(msgNetwork, msgType);
+ }
+ }
+ client.munger.getRule(".inline-buttons").enabled = false;
+
+ for (var c in this.primServ.channels)
+ {
+ var channel = this.primServ.channels[c];
+ channel._clearUserList();
+ }
+
+ dispatch("sync-header");
+ updateTitle();
+ updateProgress();
+ updateSecurityIcon();
+
+ client.ident.removeNetwork(this);
+
+ if ("userClose" in client && client.userClose &&
+ client.getConnectionCount() == 0)
+ window.close();
+
+ // Renew the STS policy.
+ if (e.server.isSecure && ("sts" in e.server.caps) && client.sts.ENABLED)
+ {
+ var policy = client.sts.parseParameters(e.server.capvals["sts"]);
+ client.sts.setPolicy(e.server.hostname, e.server.port, policy.duration);
+ }
+
+ if (("reconnect" in this) && this.reconnect)
+ {
+ if ("stsUpgradePort" in this)
+ {
+ e.server.port = this.stsUpgradePort;
+ e.server.isSecure = true;
+ delete this.stsUpgradePort;
+ }
+ this.connect(this.requireSecurity);
+ delete this.reconnect;
+ }
+}
+
+CIRCNetwork.prototype.onCTCPReplyPing =
+function my_replyping (e)
+{
+ // see bug 326523
+ if (stringTrim(e.CTCPData).length != 13)
+ {
+ this.display(getMsg(MSG_PING_REPLY_INVALID, e.user.unicodeName),
+ "INFO", e.user, "ME!", e.tags);
+ return;
+ }
+
+ var delay = formatDateOffset((new Date() - new Date(Number(e.CTCPData))) /
+ 1000);
+ this.display(getMsg(MSG_PING_REPLY, [e.user.unicodeName, delay]), "INFO",
+ e.user, "ME!", e.tags);
+}
+
+CIRCNetwork.prototype.on221 =
+CIRCNetwork.prototype.onUserMode =
+function my_umode (e)
+{
+ if ("user" in e && e.user)
+ {
+ e.user.updateHeader();
+ this.display(getMsg(MSG_USER_MODE, [e.user.unicodeName, e.params[2]]),
+ MT_MODE, undefined, undefined, e.tags);
+ }
+ else
+ {
+ this.display(getMsg(MSG_USER_MODE, [e.params[1], e.params[2]]),
+ MT_MODE, undefined, undefined, e.tags);
+ }
+}
+
+CIRCNetwork.prototype.onNick =
+function my_cnick (e)
+{
+ if (!ASSERT(userIsMe(e.user), "network nick event for third party"))
+ return;
+
+ if (("pendingNickChange" in this) &&
+ (this.pendingNickChange == e.user.unicodeName))
+ {
+ this.prefs["nickname"] = e.user.unicodeName;
+ this.preferredNick = e.user.unicodeName;
+ delete this.pendingNickChange;
+ }
+
+ if (getTabForObject(this))
+ {
+ this.displayHere(getMsg(MSG_NEWNICK_YOU, e.user.unicodeName),
+ "NICK", "ME!", e.user, e.tags);
+ }
+
+ this.updateHeader();
+ updateStalkExpression(this);
+}
+
+CIRCNetwork.prototype.onPing =
+function my_netping (e)
+{
+ this.updateHeader(this);
+}
+
+CIRCNetwork.prototype.onPong =
+function my_netpong (e)
+{
+ this.updateHeader(this);
+}
+
+CIRCNetwork.prototype.onWallops =
+function my_netwallops(e)
+{
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ if (e.user)
+ this.display(e.msg, "WALLOPS/WALLOPS", e.user, this, e.tags);
+ else
+ this.display(e.msg, "WALLOPS/WALLOPS", undefined, this, e.tags);
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+/* unknown command reply */
+CIRCNetwork.prototype.on421 =
+function my_421(e)
+{
+ this.display(getMsg(MSG_IRC_421, e.decodeParam(2)), MT_ERROR, undefined,
+ undefined, e.tags);
+ return true;
+}
+
+/* cap reply */
+CIRCNetwork.prototype.onCap =
+function my_cap(e)
+{
+ if (e.params[2] == "LS")
+ {
+ // Handle the STS upgrade policy if we have one.
+ if (e.server.pendingCapNegotiation && e.stsUpgradePort)
+ {
+ this.display(getMsg(MSG_STS_UPGRADE, e.stsUpgradePort));
+ this.reconnect = true;
+ this.stsUpgradePort = e.stsUpgradePort;
+ this.quit(MSG_RECONNECTING);
+ return true;
+ }
+
+ // Don't show the raw message until we've registered.
+ if (this.state == NET_ONLINE)
+ {
+
+ var listCaps = new Array();
+ for (var cap in e.server.caps)
+ {
+ var value = e.server.capvals[cap];
+ if (value)
+ cap += "=" + value;
+ listCaps.push(cap);
+ }
+ if (listCaps.length > 0)
+ {
+ listCaps.sort();
+ this.display(getMsg(MSG_SUPPORTS_CAPS, listCaps.join(", ")));
+ }
+ }
+
+ // Update the STS duration policy.
+ if (e.server.isSecure && ("sts" in e.server.caps) && client.sts.ENABLED)
+ {
+ var policy = client.sts.parseParameters(e.server.capvals["sts"]);
+ client.sts.setPolicy(e.server.hostname, e.server.port, policy.duration);
+ }
+ }
+ else if (e.params[2] == "LIST")
+ {
+ var listCapsEnabled = new Array();
+ for (var cap in e.server.caps)
+ {
+ if (e.server.caps[cap])
+ {
+ listCapsEnabled.push(cap);
+ }
+ }
+ if (listCapsEnabled.length > 0)
+ {
+ listCapsEnabled.sort();
+ this.display(getMsg(MSG_SUPPORTS_CAPSON,
+ listCapsEnabled.join(", ")));
+ }
+ }
+ else if (e.params[2] == "ACK")
+ {
+ if (e.capsOn.length)
+ this.display(getMsg(MSG_CAPS_ON, e.capsOn.join(", ")));
+ if (e.capsOff.length)
+ this.display(getMsg(MSG_CAPS_OFF, e.capsOff.join(", ")));
+ }
+ else if (e.params[2] == "NAK")
+ {
+ this.display(getMsg(MSG_CAPS_ERROR, e.caps.join(", ")));
+ }
+ else if (e.params[2] == "NEW")
+ {
+ // Handle a new STS policy
+ if (client.sts.ENABLED && (arrayContains(e.newcaps, "sts")))
+ {
+ var policy = client.sts.parseParameters(e.server.capvals["sts"]);
+ if (!e.server.isSecure && policy.port)
+ {
+ // Inform the user of the new upgrade policy and
+ // offer an option to reconnect.
+ client.munger.getRule(".inline-buttons").enabled = true;
+ this.display(getMsg(MSG_STS_UPGRADE_NEW, [this.unicodeName, "reconnect"]));
+ client.munger.getRule(".inline-buttons").enabled = false;
+ }
+ else if (e.server.isSecure && policy.duration)
+ {
+ // Renew the policy's duration.
+ client.sts.setPolicy(e.server.hostname, e.server.port, policy.duration);
+ }
+ }
+ }
+ return true;
+}
+
+// Notify the user of received CTCP requests.
+CIRCNetwork.prototype.onReceiveCTCP =
+function my_ccrecv(e)
+{
+ // Do nothing if we receive these.
+ if ((e.type == "ctcp-action") ||
+ (e.type == "ctcp-dcc") ||
+ (e.type == "unk-ctcp"))
+ return true;
+
+ this.display(getMsg(MSG_FMT_CTCPRECV,
+ [toUnicode(e.CTCPCode, this),
+ toUnicode(e.CTCPData, this), e.user.unicodeName]),
+ "CTCP_REQUEST", e.user, e.server.me, e.tags);
+
+ return true;
+}
+
+/* SASL authentication start */
+CIRCNetwork.prototype.onSASLStart =
+function my_saslstart(e)
+{
+ if (!e.mechs || e.mechs.indexOf("plain") !== -1)
+ e.server.sendData("AUTHENTICATE PLAIN\n");
+}
+
+/* SASL authentication response */
+CIRCNetwork.prototype.onAuthenticate =
+function my_auth(e)
+{
+ if (e.params[1] !== "+")
+ return;
+
+ var username = e.server.me.encodedName;
+ var password = client.tryToGetLogin(e.server.parent.getURL(), "sasl",
+ e.server.me.name, null, true,
+ getMsg(MSG_SASL_PASSWORD, username));
+ if (!password)
+ {
+ // Abort authentication.
+ e.server.sendAuthAbort();
+ return;
+ }
+
+ var auth = username + '\0' + username + '\0' + password;
+ e.server.sendAuthResponse(auth);
+}
+
+CIRCNetwork.prototype.onNetsplitBatch =
+function my_netsplit_batch(e)
+{
+ for (var c in this.primServ.channels)
+ {
+ if (e.starting)
+ {
+ this.startMsgGroup(e.reftag, getMsg(MSG_BATCH_NETSPLIT_START,
+ [e.params[3],
+ e.params[4]]),
+ e.batchtype);
+ }
+ else
+ {
+ this.display(MSG_BATCH_NETSPLIT_END, e.batchtype);
+ this.endMsgGroup();
+ }
+ }
+}
+
+CIRCNetwork.prototype.onNetjoinBatch =
+function my_netjoin_batch(e)
+{
+ for (var c in this.primServ.channels)
+ {
+ if (e.starting)
+ {
+ this.startMsgGroup(e.reftag, getMsg(MSG_BATCH_NETJOIN_START,
+ [e.params[3],
+ e.params[4]]),
+ e.batchtype);
+ }
+ else
+ {
+ this.display(MSG_BATCH_NETJOIN_END, e.batchtype);
+ this.endMsgGroup();
+ }
+ }
+}
+
+CIRCChannel.prototype.onChathistoryBatch =
+function my_chathistory_batch(e)
+{
+ if (e.starting)
+ {
+ this.startMsgGroup(e.reftag, getMsg(MSG_BATCH_CHATHISTORY_START,
+ [e.params[3]]),
+ e.batchtype);
+ }
+ else
+ {
+ this.display(MSG_BATCH_CHATHISTORY_END, e.batchtype);
+ this.endMsgGroup();
+ }
+}
+
+CIRCNetwork.prototype.onUnknownBatch =
+CIRCChannel.prototype.onUnknownBatch =
+CIRCUser.prototype.onUnknownBatch =
+function my_unknown_batch(e)
+{
+ if (e.starting)
+ {
+ this.startMsgGroup(e.reftag, getMsg(MSG_BATCH_UNKNOWN,
+ [e.batchtype,
+ e.params.slice(3)]),
+ "BATCH");
+ }
+ else
+ {
+ this.display(MSG_BATCH_UNKNOWN_END, e.batchtype);
+ this.endMsgGroup();
+ }
+}
+
+/* user away status */
+CIRCNetwork.prototype.onAway =
+function my_away(e)
+{
+ var userlist = document.getElementById("user-list");
+ for (var c in e.server.channels)
+ {
+ var chan = e.server.channels[c];
+ if (chan.active && (e.user.collectionKey in chan.users))
+ {
+ let index = chan.users[e.user.collectionKey].chanListEntry.childIndex;
+ userlist.treeBoxObject.invalidateRow(index);
+ e.server.channels[c].updateUsers([e.user.collectionKey]);
+ }
+ }
+}
+
+/* user host changed */
+CIRCNetwork.prototype.onChghost =
+function my_chghost(e)
+{
+ e.user.updateHeader();
+}
+
+CIRCNetwork.prototype.reclaimName =
+function my_reclaimname()
+{
+ var network = this;
+
+ function callback() {
+ network.reclaimName();
+ };
+
+ if ("pendingReclaimCheck" in this)
+ delete this.pendingReclaimCheck;
+
+ // Function to attempt to get back the nickname the user wants.
+ if ((this.state != NET_ONLINE) || !this.primServ)
+ return false;
+
+ if (this.primServ.me.unicodeName == this.preferredNick)
+ return false;
+
+ this.reclaimLeft -= this.RECLAIM_WAIT;
+
+ if (this.reclaimLeft <= 0)
+ return false;
+
+ this.pendingReclaimCheck = true;
+ this.INITIAL_NICK = this.preferredNick;
+ this.primServ.changeNick(this.preferredNick);
+
+ setTimeout(callback, this.RECLAIM_WAIT);
+
+ return true;
+}
+
+CIRCNetwork.prototype.doAutoPerform =
+function net_autoperform()
+{
+ if (("autoPerformSent" in this) && (this.autoPerformSent == false))
+ {
+ var cmdary = client.prefs["autoperform.network"].concat(this.prefs["autoperform"]);
+ for (var i = 0; i < cmdary.length; ++i)
+ {
+ if (cmdary[i][0] == "/")
+ this.dispatch(cmdary[i].substr(1));
+ else
+ this.dispatch(cmdary[i]);
+ }
+ this.autoPerformSent = true;
+ }
+}
+
+
+/* We want to override the base implementations. */
+CIRCChannel.prototype._join = CIRCChannel.prototype.join;
+CIRCChannel.prototype._part = CIRCChannel.prototype.part;
+
+CIRCChannel.prototype.join =
+function chan_join(key)
+{
+ var joinFailedFn = function _joinFailedFn(t)
+ {
+ delete t.joinTimer;
+ t.busy = false;
+ updateProgress();
+ }
+ if (!this.joined)
+ {
+ this.joinTimer = setTimeout(joinFailedFn, 30000, this);
+ this.busy = true;
+ updateProgress();
+ }
+ this._join(key);
+}
+
+CIRCChannel.prototype.part =
+function chan_part(reason)
+{
+ var partFailedFn = function _partFailedFn(t)
+ {
+ delete t.partTimer;
+ t.busy = false;
+ updateProgress();
+ }
+ this.partTimer = setTimeout(partFailedFn, 30000, this);
+ this.busy = true;
+ updateProgress();
+ this._part(reason);
+}
+
+client.setActivityMarker =
+CIRCNetwork.prototype.setActivityMarker =
+CIRCChannel.prototype.setActivityMarker =
+CIRCUser.prototype.setActivityMarker =
+CIRCDCCChat.prototype.setActivityMarker =
+CIRCDCCFileTransfer.prototype.setActivityMarker =
+function view_setactivitymarker(state)
+{
+ if (!client.initialized)
+ return;
+
+ // Always clear the activity marker first.
+ var markedRow = this.getActivityMarker();
+ if (markedRow)
+ {
+ markedRow.classList.remove("chatzilla-line-marker");
+ }
+
+ if (state)
+ {
+ // Mark the last row.
+ var target = this.messages.firstChild.lastChild;
+ if (!target)
+ return;
+ target.classList.add("chatzilla-line-marker");
+ }
+}
+
+client.getActivityMarker =
+CIRCNetwork.prototype.getActivityMarker =
+CIRCChannel.prototype.getActivityMarker =
+CIRCUser.prototype.getActivityMarker =
+CIRCDCCChat.prototype.getActivityMarker =
+CIRCDCCFileTransfer.prototype.getActivityMarker =
+function view_getactivitymarker()
+{
+ return this.messages.querySelector(".chatzilla-line-marker");
+}
+CIRCChannel.prototype.onInit =
+function chan_oninit ()
+{
+ this.logFile = null;
+ this.pendingNamesReply = false;
+ this.importantMessages = 0;
+}
+
+CIRCChannel.prototype.onPrivmsg =
+function my_cprivmsg (e)
+{
+ var msg = e.decodeParam(2);
+ var msgtype = "PRIVMSG";
+ if ("msgPrefix" in e)
+ msgtype += "/" + e.msgPrefix.symbol;
+
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ this.display(msg, msgtype, e.user, this, e.tags);
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+/* end of names */
+CIRCChannel.prototype.on366 =
+function my_366 (e)
+{
+ // First clear up old users:
+ var removals = new Array();
+ while (this.userList.childData.childData.length > 0)
+ {
+ var userToRemove = this.userList.childData.childData[0]._userObj;
+ this.removeFromList(userToRemove);
+ removals.push(userToRemove);
+ }
+ this.removeUsers(removals);
+
+ var entries = new Array(), updates = new Array();
+ for (var u in this.users)
+ {
+ entries.push(new UserEntry(this.users[u], this.userListShare));
+ updates.push(this.users[u]);
+ }
+ this.addUsers(updates);
+
+ this.userList.childData.appendChildren(entries);
+
+ if (this.pendingNamesReply)
+ {
+ this.parent.parent.display (e.channel.unicodeName + ": " +
+ e.params[3], "366", undefined, undefined,
+ e.tags);
+ }
+ this.pendingNamesReply = false;
+
+ // Update conference mode now we have a complete user list.
+ this._updateConferenceMode();
+}
+
+CIRCChannel.prototype.onTopic = /* user changed topic */
+CIRCChannel.prototype.on332 = /* TOPIC reply */
+function my_topic (e)
+{
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ if (e.code == "TOPIC")
+ this.display (getMsg(MSG_TOPIC_CHANGED, [this.topicBy, this.topic]),
+ "TOPIC", undefined, undefined, e.tags);
+
+ if (e.code == "332")
+ {
+ if (this.topic)
+ {
+ this.display (getMsg(MSG_TOPIC,
+ [this.unicodeName, this.topic]),
+ "TOPIC", undefined, undefined, e.tags);
+ }
+ else
+ {
+ this.display(getMsg(MSG_NO_TOPIC, this.unicodeName), "TOPIC",
+ undefined, undefined, e.tags);
+ }
+ }
+
+ this.updateHeader();
+ updateTitle(this);
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+CIRCChannel.prototype.on333 = /* Topic setter information */
+function my_topicinfo (e)
+{
+ this.display (getMsg(MSG_TOPIC_DATE, [this.unicodeName, this.topicBy,
+ this.topicDate]), "TOPIC",
+ undefined, undefined, e.tags);
+}
+
+CIRCChannel.prototype.on353 = /* names reply */
+function my_topic (e)
+{
+ if (this.pendingNamesReply)
+ {
+ this.parent.parent.display (e.channel.unicodeName + ": " +
+ e.params[4], "NAMES", undefined, undefined,
+ e.tags);
+ }
+}
+
+CIRCChannel.prototype.on367 = /* channel ban stuff */
+function my_bans(e)
+{
+ if ("pendingBanList" in this)
+ return;
+
+ var msg = getMsg(MSG_BANLIST_ITEM,
+ [e.user.unicodeName, e.ban, this.unicodeName, e.banTime]);
+ if (this.iAmHalfOp() || this.iAmOp())
+ msg += " " + getMsg(MSG_BANLIST_BUTTON, "mode -b " + e.ban);
+
+ client.munger.getRule(".inline-buttons").enabled = true;
+ this.display(msg, "BAN", undefined, undefined, e.tags);
+ client.munger.getRule(".inline-buttons").enabled = false;
+}
+
+CIRCChannel.prototype.on368 =
+function my_endofbans(e)
+{
+ if ("pendingBanList" in this)
+ return;
+
+ this.display(getMsg(MSG_BANLIST_END, this.unicodeName), "BAN", undefined,
+ undefined, e.tags);
+}
+
+CIRCChannel.prototype.on348 = /* channel except stuff */
+function my_excepts(e)
+{
+ if ("pendingExceptList" in this)
+ return;
+
+ var msg = getMsg(MSG_EXCEPTLIST_ITEM, [e.user.unicodeName, e.except,
+ this.unicodeName, e.exceptTime]);
+ if (this.iAmHalfOp() || this.iAmOp())
+ msg += " " + getMsg(MSG_EXCEPTLIST_BUTTON, "mode -e " + e.except);
+
+ client.munger.getRule(".inline-buttons").enabled = true;
+ this.display(msg, "EXCEPT", undefined, undefined, e.tags);
+ client.munger.getRule(".inline-buttons").enabled = false;
+}
+
+CIRCChannel.prototype.on349 =
+function my_endofexcepts(e)
+{
+ if ("pendingExceptList" in this)
+ return;
+
+ this.display(getMsg(MSG_EXCEPTLIST_END, this.unicodeName), "EXCEPT",
+ undefined, undefined, e.tags);
+}
+
+CIRCChannel.prototype.on482 =
+function my_needops(e)
+{
+ if ("pendingExceptList" in this)
+ return;
+
+ this.display(getMsg(MSG_CHANNEL_NEEDOPS, this.unicodeName), MT_ERROR,
+ undefined, undefined, e.tags);
+}
+
+CIRCChannel.prototype.onNotice =
+function my_notice (e)
+{
+ var msgtype = "NOTICE";
+ if ("msgPrefix" in e)
+ msgtype += "/" + e.msgPrefix.symbol;
+
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ this.display(e.decodeParam(2), msgtype, e.user, this, e.tags);
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+CIRCChannel.prototype.onCTCPAction =
+function my_caction (e)
+{
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ this.display(e.CTCPData, "ACTION", e.user, this, e.tags);
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+CIRCChannel.prototype.onUnknownCTCP =
+function my_unkctcp (e)
+{
+ this.display (getMsg(MSG_UNKNOWN_CTCP, [e.CTCPCode, e.CTCPData,
+ e.user.unicodeName]),
+ "BAD-CTCP", e.user, this, e.tags);
+}
+
+CIRCChannel.prototype.onJoin =
+function my_cjoin (e)
+{
+ dispatch("create-tab-for-view", { view: e.channel });
+
+ if (userIsMe(e.user))
+ {
+ var params = [e.user.unicodeName, e.channel.unicodeName];
+ this.display(getMsg(MSG_YOU_JOINED, params), "JOIN",
+ e.server.me, this, e.tags);
+ /* Tell the user that conference mode is on, lest they forget (if it
+ * subsequently turns itself off, they'll get a message anyway).
+ */
+ if (this.prefs["conference.enabled"])
+ this.display(MSG_CONF_MODE_STAYON);
+ addURLToHistory(this.getURL());
+
+ if ("joinTimer" in this)
+ {
+ clearTimeout(this.joinTimer);
+ delete this.joinTimer;
+ this.busy = false;
+ updateProgress();
+ }
+
+ /* !-channels are "safe" channels, and get a server-generated prefix.
+ * For this reason, creating the channel is delayed until this point.
+ */
+ if (e.channel.unicodeName[0] == "!")
+ dispatch("set-current-view", { view: e.channel });
+
+ this.doAutoPerform();
+ }
+ else
+ {
+ if (!this.prefs["conference.enabled"])
+ {
+ this.display(getMsg(MSG_SOMEONE_JOINED,
+ [e.user.unicodeName, e.user.name, e.user.host,
+ e.channel.unicodeName]),
+ "JOIN", e.user, this, e.tags);
+ }
+
+ /* Only do this for non-me joins so us joining doesn't reset it (when
+ * we join the usercount is always 1). Also, do this after displaying
+ * the join message so we don't get cryptic effects such as a user
+ * joining causes *only* a "Conference mode enabled" message.
+ */
+ this._updateConferenceMode();
+ }
+
+ /* We don't want to add ourself here, since the names reply we'll be
+ * getting right after the join will include us as well! (FIXME)
+ */
+ if (!userIsMe(e.user))
+ {
+ this.addUsers([e.user]);
+ var entry = new UserEntry(e.user, this.userListShare);
+ this.userList.childData.appendChild(entry);
+ this.userList.childData.reSort();
+ }
+ this.updateHeader();
+}
+
+CIRCChannel.prototype.onPart =
+function my_cpart(e)
+{
+ this.removeUsers([e.user]);
+ this.updateHeader();
+
+ if (userIsMe(e.user))
+ {
+ var msg = e.reason ? MSG_YOU_LEFT_REASON : MSG_YOU_LEFT;
+ var params = [e.user.unicodeName, e.channel.unicodeName, e.reason];
+ this.display(getMsg(msg, params), "PART", e.user, this, e.tags);
+ this._clearUserList();
+
+ if ("partTimer" in this)
+ {
+ clearTimeout(this.partTimer);
+ delete this.partTimer;
+ this.busy = false;
+ updateProgress();
+ }
+
+ if (this.deleteWhenDone)
+ this.dispatch("delete-view");
+
+ delete this.deleteWhenDone;
+ }
+ else
+ {
+ /* We're ok to update this before the message, because the only thing
+ * that can happen is *disabling* of conference mode.
+ */
+ this._updateConferenceMode();
+
+ if (!this.prefs["conference.enabled"])
+ {
+ var msg = e.reason ? MSG_SOMEONE_LEFT_REASON : MSG_SOMEONE_LEFT;
+ var params = [e.user.unicodeName, e.channel.unicodeName, e.reason];
+ this.display(getMsg(msg, params), "PART", e.user, this, e.tags);
+ }
+
+ this.removeFromList(e.user);
+ }
+}
+
+CIRCChannel.prototype.onKick =
+function my_ckick (e)
+{
+ if (userIsMe (e.lamer))
+ {
+ if (e.user)
+ {
+ this.display (getMsg(MSG_YOURE_GONE,
+ [e.lamer.unicodeName, e.channel.unicodeName,
+ e.user.unicodeName, e.reason]),
+ "KICK", e.user, this, e.tags);
+ }
+ else
+ {
+ this.display (getMsg(MSG_YOURE_GONE,
+ [e.lamer.unicodeName, e.channel.unicodeName,
+ MSG_SERVER, e.reason]),
+ "KICK", (void 0), this, e.tags);
+ }
+
+ this._clearUserList();
+ /* Try 1 re-join attempt if allowed. */
+ if (this.prefs["autoRejoin"])
+ this.join(this.mode.key);
+ }
+ else
+ {
+ var enforcerProper, enforcerNick;
+ if (e.user && userIsMe(e.user))
+ {
+ enforcerProper = "YOU";
+ enforcerNick = "ME!";
+ }
+ else if (e.user)
+ {
+ enforcerProper = e.user.unicodeName;
+ enforcerNick = e.user.encodedName;
+ }
+ else
+ {
+ enforcerProper = MSG_SERVER;
+ enforcerNick = MSG_SERVER;
+ }
+
+ this.display(getMsg(MSG_SOMEONE_GONE,
+ [e.lamer.unicodeName, e.channel.unicodeName,
+ enforcerProper, e.reason]),
+ "KICK", e.user, this, e.tags);
+
+ this.removeFromList(e.lamer);
+ }
+
+ this.removeUsers([e.lamer]);
+ this.updateHeader();
+}
+
+CIRCChannel.prototype.removeFromList =
+function my_removeFromList(user)
+{
+ // Remove the user from the list and 'disconnect' the user from their entry:
+ var idx = user.chanListEntry.childIndex;
+ this.userList.childData.removeChildAtIndex(idx);
+
+ delete user.chanListEntry._userObj;
+ delete user.chanListEntry;
+}
+
+CIRCChannel.prototype.onChanMode =
+function my_cmode (e)
+{
+ if (e.code == "MODE")
+ {
+ var msg = e.decodeParam(1);
+ for (var i = 2; i < e.params.length; i++)
+ msg += " " + e.decodeParam(i);
+
+ var source = e.user ? e.user.unicodeName : e.source;
+ this.display(getMsg(MSG_MODE_CHANGED, [msg, source]),
+ "MODE", (e.user || null), this, e.tags);
+ }
+ else if ("pendingModeReply" in this)
+ {
+ var msg = e.decodeParam(3);
+ for (var i = 4; i < e.params.length; i++)
+ msg += " " + e.decodeParam(i);
+
+ var view = ("messages" in this && this.messages) ? this : e.network;
+ view.display(getMsg(MSG_MODE_ALL, [this.unicodeName, msg]), "MODE",
+ undefined, undefined, e.tags);
+ delete this.pendingModeReply;
+ }
+ var updates = new Array();
+ for (var u in e.usersAffected)
+ updates.push(e.usersAffected[u]);
+ this.updateUsers(updates);
+
+ this.updateHeader();
+ updateTitle(this);
+ if (client.currentObject == this)
+ updateUserList();
+}
+
+CIRCChannel.prototype.onNick =
+function my_cnick (e)
+{
+ if (userIsMe (e.user))
+ {
+ if (getTabForObject(this))
+ {
+ this.displayHere(getMsg(MSG_NEWNICK_YOU, e.user.unicodeName),
+ "NICK", "ME!", e.user, e.tags);
+ }
+ this.parent.parent.updateHeader();
+ }
+ else if (!this.prefs["conference.enabled"])
+ {
+ this.display(getMsg(MSG_NEWNICK_NOTYOU, [e.oldNick,
+ e.user.unicodeName]),
+ "NICK", e.user, this, e.tags);
+ }
+
+ this.updateUsers([e.user]);
+ if (client.currentObject == this)
+ updateUserList();
+}
+
+CIRCChannel.prototype.onQuit =
+function my_cquit (e)
+{
+ if (userIsMe(e.user))
+ {
+ /* I dont think this can happen */
+ var pms = [e.user.unicodeName, e.server.parent.unicodeName, e.reason];
+ this.display(getMsg(MSG_YOU_QUIT, pms),"QUIT", e.user, this, e.tags);
+ this._clearUserList();
+ }
+ else
+ {
+ // See onPart for why this is ok before the message.
+ this._updateConferenceMode();
+
+ if (!this.prefs["conference.enabled"])
+ {
+ this.display(getMsg(MSG_SOMEONE_QUIT,
+ [e.user.unicodeName,
+ e.server.parent.unicodeName, e.reason]),
+ "QUIT", e.user, this, e.tags);
+ }
+ }
+
+ this.removeUsers([e.user]);
+ this.removeFromList(e.user);
+
+ this.updateHeader();
+}
+
+CIRCChannel.prototype.doAutoPerform =
+function my_cautoperform()
+{
+ var cmdary = client.prefs["autoperform.channel"].concat(this.prefs["autoperform"]);
+ for (var i = 0; i < cmdary.length; ++i)
+ {
+ if (cmdary[i][0] == "/")
+ this.dispatch(cmdary[i].substr(1));
+ else
+ this.dispatch(cmdary[i]);
+ }
+}
+
+CIRCChannel.prototype._clearUserList =
+function _my_clearuserlist()
+{
+ if (this.userList && this.userList.childData &&
+ this.userList.childData.childData)
+ {
+ this.userList.freeze();
+ var len = this.userList.childData.childData.length;
+ while (len > 0)
+ {
+ var entry = this.userList.childData.childData[--len];
+ this.userList.childData.removeChildAtIndex(len);
+ delete entry._userObj.chanListEntry;
+ delete entry._userObj;
+ }
+ this.userList.thaw();
+ }
+}
+
+CIRCUser.prototype.onInit =
+function user_oninit ()
+{
+ this.logFile = null;
+ this.lastShownAwayMessage = "";
+}
+
+CIRCUser.prototype.onPrivmsg =
+function my_cprivmsg(e)
+{
+ var sourceObj = e.user;
+ var destObj = e.server.me;
+ var displayObj = this;
+
+ if (!("messages" in this))
+ {
+ var limit = client.prefs["newTabLimit"];
+ if (limit == 0 || client.viewsArray.length < limit)
+ {
+ if (e.user != e.server.me)
+ {
+ openQueryTab(e.server, e.user.unicodeName);
+ }
+ else
+ {
+ // This is a self-message, i.e. we received a message that
+ // looks like it came from us. Display it accordingly.
+ sourceObj = e.server.me;
+ destObj = openQueryTab(e.server, e.params[1]);
+ displayObj = destObj;
+ }
+ }
+ }
+
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ displayObj.display(e.decodeParam(2), "PRIVMSG", sourceObj, destObj, e.tags);
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+CIRCUser.prototype.onNick =
+function my_unick (e)
+{
+ if (userIsMe(e.user))
+ {
+ this.parent.parent.updateHeader();
+ updateTitle();
+ }
+ else if ("messages" in this && this.messages)
+ {
+ this.display(getMsg(MSG_NEWNICK_NOTYOU, [e.oldNick, e.user.unicodeName]),
+ "NICK", e.user, this, e.tags);
+ }
+
+ this.updateHeader();
+ var tab = getTabForObject(this);
+ if (tab)
+ tab.setAttribute("label", this.unicodeName);
+}
+
+CIRCUser.prototype.onNotice =
+function my_notice (e)
+{
+ var msg = e.decodeParam(2);
+ var displayMailto = client.prefs["munger.mailto"];
+
+ var ary = msg.match(/^\[([^ ]+)\]\s+/);
+ if (ary)
+ {
+ var channel = e.server.getChannel(ary[1]);
+ if (channel)
+ {
+ client.munger.getRule(".mailto").enabled = displayMailto;
+ channel.display(msg, "NOTICE", this, e.server.me, e.tags);
+ client.munger.getRule(".mailto").enabled = false;
+ return;
+ }
+ }
+
+ var sourceObj = this;
+ var destObj = e.server.me;
+ var displayObj = this;
+
+ if (e.user == e.server.me)
+ {
+ // This is a self-message, i.e. we received a message that
+ // looks like it came from us. Display it accordingly.
+ var sourceObj = e.server.me;
+ var destObj = e.server.addTarget(e.params[1]);
+ var displayObj = e.server.parent;
+ }
+
+ client.munger.getRule(".mailto").enabled = displayMailto;
+ displayObj.display(msg, "NOTICE", sourceObj, destObj, e.tags);
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+CIRCUser.prototype.onCTCPAction =
+function my_uaction(e)
+{
+ if (!("messages" in this))
+ {
+ var limit = client.prefs["newTabLimit"];
+ if (limit == 0 || client.viewsArray.length < limit)
+ openQueryTab(e.server, e.user.unicodeName);
+ }
+
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ this.display(e.CTCPData, "ACTION", this, e.server.me, e.tags);
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+CIRCUser.prototype.onUnknownCTCP =
+function my_unkctcp (e)
+{
+ this.parent.parent.display (getMsg(MSG_UNKNOWN_CTCP,
+ [e.CTCPCode, e.CTCPData,
+ e.user.unicodeName]),
+ "BAD-CTCP", this, e.server.me, e.tags);
+}
+
+function onDCCAutoAcceptTimeout(o, folder)
+{
+ // user may have already accepted or declined
+ if (o.state.state != DCC_STATE_REQUESTED)
+ return;
+
+ if (o.TYPE == "IRCDCCChat")
+ {
+ o.accept();
+ display(getMsg(MSG_DCCCHAT_ACCEPTED, o._getParams()), "DCC-CHAT");
+ }
+ else
+ {
+ var dest, leaf, tries = 0;
+ while (true)
+ {
+ leaf = escapeFileName(o.filename);
+ if (++tries > 1)
+ {
+ // A file with the same name as the offered file already exists
+ // in the user's download folder. Add [x] before the extension.
+ // The extension is the last dot to the end of the string,
+ // unless it is one of the special-cased compression extensions,
+ // in which case the second to last dot is used. The second
+ // extension can only contain letters, to avoid mistakes like
+ // "patch-version1[2].0.gz". If no file extension is present,
+ // the [x] is just appended to the filename.
+ leaf = leaf.replace(/(\.[a-z]*\.(gz|bz2|z)|\.[^\.]*|)$/i,
+ "[" + tries + "]$&");
+ }
+
+ dest = getFileFromURLSpec(folder);
+ dest.append(leaf);
+ if (!dest.exists())
+ break;
+ }
+ o.accept(dest);
+ display(getMsg(MSG_DCCFILE_ACCEPTED, o._getParams()), "DCC-FILE");
+ }
+}
+
+CIRCUser.prototype.onDCCChat =
+function my_dccchat(e)
+{
+ if (!client.prefs["dcc.enabled"])
+ return;
+
+ var u = client.dcc.addUser(e.user, e.host);
+ var c = client.dcc.addChat(u, e.port);
+
+ var str = MSG_DCCCHAT_GOT_REQUEST;
+ var cmds = getMsg(MSG_DCC_COMMAND_ACCEPT, "dcc-accept " + c.id) + " " +
+ getMsg(MSG_DCC_COMMAND_DECLINE, "dcc-decline " + c.id);
+
+ var allowList = this.parent.parent.prefs["dcc.autoAccept.list"];
+ for (var m = 0; m < allowList.length; ++m)
+ {
+ if (hostmaskMatches(e.user, getHostmaskParts(allowList[m])))
+ {
+ var acceptDelay = client.prefs["dcc.autoAccept.delay"];
+ if (acceptDelay == 0)
+ {
+ str = MSG_DCCCHAT_ACCEPTING_NOW;
+ }
+ else
+ {
+ str = MSG_DCCCHAT_ACCEPTING;
+ cmds = [(acceptDelay / 1000), cmds];
+ }
+ setTimeout(onDCCAutoAcceptTimeout, acceptDelay, c);
+ break;
+ }
+ }
+
+ client.munger.getRule(".inline-buttons").enabled = true;
+ this.parent.parent.display(getMsg(str, c._getParams().concat(cmds)),
+ "DCC-CHAT", undefined, undefined, e.tags);
+ client.munger.getRule(".inline-buttons").enabled = false;
+
+ // Pass the event over to the DCC Chat object.
+ e.set = "dcc-chat";
+ e.destObject = c;
+ e.destMethod = "onGotRequest";
+}
+
+CIRCUser.prototype.onDCCSend =
+function my_dccsend(e)
+{
+ if (!client.prefs["dcc.enabled"])
+ return;
+
+ var u = client.dcc.addUser(e.user, e.host);
+ var f = client.dcc.addFileTransfer(u, e.port, e.file, e.size);
+
+ var str = MSG_DCCFILE_GOT_REQUEST;
+ var cmds = getMsg(MSG_DCC_COMMAND_ACCEPT, "dcc-accept " + f.id) + " " +
+ getMsg(MSG_DCC_COMMAND_DECLINE, "dcc-decline " + f.id);
+
+ var allowList = this.parent.parent.prefs["dcc.autoAccept.list"];
+ for (var m = 0; m < allowList.length; ++m)
+ {
+ if (hostmaskMatches(e.user, getHostmaskParts(allowList[m]),
+ this.parent))
+ {
+ var acceptDelay = client.prefs["dcc.autoAccept.delay"];
+ if (acceptDelay == 0)
+ {
+ str = MSG_DCCFILE_ACCEPTING_NOW;
+ }
+ else
+ {
+ str = MSG_DCCFILE_ACCEPTING;
+ cmds = [(acceptDelay / 1000), cmds];
+ }
+ setTimeout(onDCCAutoAcceptTimeout, acceptDelay,
+ f, this.parent.parent.prefs["dcc.downloadsFolder"]);
+ break;
+ }
+ }
+
+ client.munger.getRule(".inline-buttons").enabled = true;
+ this.parent.parent.display(getMsg(str,[e.user.unicodeName,
+ e.host, e.port, e.file,
+ getSISize(e.size)].concat(cmds)),
+ "DCC-FILE", undefined, undefined, e.tags);
+ client.munger.getRule(".inline-buttons").enabled = false;
+
+ // Pass the event over to the DCC File object.
+ e.set = "dcc-file";
+ e.destObject = f;
+ e.destMethod = "onGotRequest";
+}
+
+CIRCUser.prototype.onDCCReject =
+function my_dccreject(e)
+{
+ if (!client.prefs["dcc.enabled"])
+ return;
+
+ //FIXME: Uh... cope. //
+
+ // Pass the event over to the DCC Chat object.
+ //e.set = "dcc-file";
+ //e.destObject = f;
+ //e.destMethod = "onGotReject";
+}
+
+CIRCUser.prototype.doAutoPerform =
+function my_autoperform()
+{
+ var cmdary = client.prefs["autoperform.user"].concat(this.prefs["autoperform"]);
+ for (var i = 0; i < cmdary.length; ++i)
+ {
+ if (cmdary[i][0] == "/")
+ this.dispatch(cmdary[i].substr(1));
+ else
+ this.dispatch(cmdary[i]);
+ }
+}
+
+CIRCDCCChat.prototype.onInit =
+function my_dccinit(e)
+{
+}
+
+CIRCDCCChat.prototype._getParams =
+function my_dccgetparams()
+{
+ return [this.unicodeName, this.remoteIP, this.port];
+}
+
+CIRCDCCChat.prototype.onPrivmsg =
+function my_dccprivmsg(e)
+{
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ this.displayHere(toUnicode(e.line, this), "PRIVMSG", e.user, "ME!");
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+CIRCDCCChat.prototype.onCTCPAction =
+function my_uaction(e)
+{
+ client.munger.getRule(".mailto").enabled = client.prefs["munger.mailto"];
+ this.displayHere(e.CTCPData, "ACTION", e.user, "ME!");
+ client.munger.getRule(".mailto").enabled = false;
+}
+
+CIRCDCCChat.prototype.onUnknownCTCP =
+function my_unkctcp(e)
+{
+ this.displayHere(getMsg(MSG_UNKNOWN_CTCP, [e.CTCPCode, e.CTCPData,
+ e.user.unicodeName]),
+ "BAD-CTCP", e.user, "ME!");
+}
+
+CIRCDCCChat.prototype.onConnect =
+function my_dccconnect(e)
+{
+ playEventSounds("dccchat", "connect");
+ this.displayHere(getMsg(MSG_DCCCHAT_OPENED, this._getParams()), "DCC-CHAT");
+}
+
+CIRCDCCChat.prototype.onAbort =
+function my_dccabort(e)
+{
+ this.display(getMsg(MSG_DCCCHAT_ABORTED, this._getParams()), "DCC-CHAT");
+}
+
+CIRCDCCChat.prototype.onFail =
+function my_dccfail(e)
+{
+ this.display(getMsg(MSG_DCCCHAT_FAILED, this._getParams()), "DCC-CHAT");
+}
+
+CIRCDCCChat.prototype.onDisconnect =
+function my_dccdisconnect(e)
+{
+ playEventSounds("dccchat", "disconnect");
+ this.display(getMsg(MSG_DCCCHAT_CLOSED, this._getParams()), "DCC-CHAT");
+}
+
+
+CIRCDCCFileTransfer.prototype.onInit =
+function my_dccfileinit(e)
+{
+ this.busy = false;
+ updateProgress();
+}
+
+CIRCDCCFileTransfer.prototype._getParams =
+function my_dccfilegetparams()
+{
+ var dir = MSG_UNKNOWN;
+
+ if (this.state.dir == DCC_DIR_GETTING)
+ dir = MSG_DCCLIST_FROM;
+
+ if (this.state.dir == DCC_DIR_SENDING)
+ dir = MSG_DCCLIST_TO;
+
+ return [this.filename, dir, this.unicodeName,
+ this.remoteIP, this.port];
+}
+
+CIRCDCCFileTransfer.prototype.onConnect =
+function my_dccfileconnect(e)
+{
+ this.displayHere(getMsg(MSG_DCCFILE_OPENED, this._getParams()), "DCC-FILE");
+ this.busy = true;
+ this.speed = 0;
+ updateProgress();
+ this._lastUpdate = new Date();
+ this._lastPosition = 0;
+ this._lastSpeedTime = new Date();
+}
+
+CIRCDCCFileTransfer.prototype.onProgress =
+function my_dccfileprogress(e)
+{
+ var now = new Date();
+ var pcent = this.progress;
+
+ var tab = getTabForObject(this);
+
+ // If we've moved 100KiB or waited 10s, update the progress bar.
+ if ((this.position > this._lastPosition + 102400) ||
+ (now - this._lastUpdate > 10000))
+ {
+ updateProgress();
+ updateTitle();
+
+ if (tab)
+ tab.setAttribute("label", this.viewName + " (" + pcent + "%)");
+
+ var change = (this.position - this._lastPosition);
+ var speed = change / ((now - this._lastSpeedTime) / 1000); // B/s
+ this._lastSpeedTime = now;
+
+ /* Use an average of the last speed, and this speed, so we get a little
+ * smoothing to it.
+ */
+ this.speed = (this.speed + speed) / 2;
+ this.updateHeader();
+ this._lastPosition = this.position;
+ }
+
+ // If it's also been 10s or more since we last displayed a msg...
+ if (now - this._lastUpdate > 10000)
+ {
+ this._lastUpdate = now;
+
+ var args = [pcent, getSISize(this.position), getSISize(this.size),
+ getSISpeed(this.speed)];
+
+ // We supress this message if the view is hidden.
+ if (tab)
+ this.displayHere(getMsg(MSG_DCCFILE_PROGRESS, args), "DCC-FILE");
+ }
+}
+
+CIRCDCCFileTransfer.prototype.onAbort =
+function my_dccfileabort(e)
+{
+ this.busy = false;
+ updateProgress();
+ updateTitle();
+ this.display(getMsg(MSG_DCCFILE_ABORTED, this._getParams()), "DCC-FILE");
+}
+
+CIRCDCCFileTransfer.prototype.onFail =
+function my_dccfilefail(e)
+{
+ this.busy = false;
+ updateProgress();
+ updateTitle();
+ this.display(getMsg(MSG_DCCFILE_FAILED, this._getParams()), "DCC-FILE");
+}
+
+CIRCDCCFileTransfer.prototype.onDisconnect =
+function my_dccfiledisconnect(e)
+{
+ this.busy = false;
+ updateProgress();
+ this.updateHeader();
+ updateTitle();
+
+ var msg, tab = getTabForObject(this);
+ if (tab)
+ tab.setAttribute("label", this.viewName + " (DONE)");
+
+ if (this.state.dir == DCC_DIR_GETTING)
+ {
+ var localURL = getURLSpecFromFile(this.localPath);
+ var cmd = "dcc-show-file " + localURL;
+ var msgId = (client.platform == "Mac") ? MSG_DCCFILE_CLOSED_SAVED_MAC :
+ MSG_DCCFILE_CLOSED_SAVED;
+ msg = getMsg(msgId, this._getParams().concat(localURL, cmd));
+ }
+ else
+ {
+ msg = getMsg(MSG_DCCFILE_CLOSED_SENT, this._getParams());
+ }
+ client.munger.getRule(".inline-buttons").enabled = true;
+ this.display(msg, "DCC-FILE");
+ client.munger.getRule(".inline-buttons").enabled = false;
+}
+
+var CopyPasteHandler = new Object();
+
+CopyPasteHandler.allowDrop =
+CopyPasteHandler.allowStartDrag =
+CopyPasteHandler.onCopyOrDrag =
+function phand_bogus()
+{
+ return true;
+}
+
+CopyPasteHandler.onPasteOrDrop =
+function phand_onpaste(e, data)
+{
+ // XXXbug 329487: The effect of onPasteOrDrop's return value is actually the
+ // exact opposite of the definition in the IDL.
+
+ // Don't mess with the multiline box at all.
+ if (client.prefs["multiline"])
+ return true;
+
+ var str = new Object();
+ var strlen = new Object();
+ data.getTransferData("text/unicode", str, strlen);
+ str.value.QueryInterface(Components.interfaces.nsISupportsString);
+ str.value.data = str.value.data.replace(/(^\s*[\r\n]+|[\r\n]+\s*$)/g, "");
+
+ // XXX part of what follows is a very ugly hack to make links (with a title)
+ // not open the multiline box. We 'should' be able to ask the transferable
+ // what flavours it supports, but testing showed that by the time we can ask
+ // for that info, it's forgotten about everything apart from text/unicode.
+ var lines = str.value.data.split("\n");
+ var m = lines[0].match(client.linkRE);
+
+ if ((str.value.data.indexOf("\n") == -1) ||
+ (m && (m[0] == lines[0]) && (lines.length == 2)))
+ {
+ // If, after stripping leading/trailing empty lines, the string is a
+ // single line, or it's a link with a title, put it back in
+ // the transferable and return.
+ data.setTransferData("text/unicode", str.value,
+ str.value.data.length * 2);
+ return true;
+ }
+
+ // If it's a drop, move the text cursor to the mouse position.
+ if (e && ("rangeOffset" in e))
+ client.input.setSelectionRange(e.rangeOffset, e.rangeOffset);
+
+ str = client.input.value.substr(0, client.input.selectionStart) +
+ str.value.data + client.input.value.substr(client.input.selectionEnd);
+ client.prefs["multiline"] = true;
+ // We want to auto-collapse after send, so the user is not thrown off by the
+ // "strange" input box if they didn't specifically ask for it:
+ client.multiLineForPaste = true;
+ client.input.value = str;
+ return false;
+}
+
+CopyPasteHandler.QueryInterface =
+function phand_qi(iid)
+{
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIClipboardDragDropHooks))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+}
+
+function UserEntry(userObj, channelListShare)
+{
+ var self = this;
+ function getUName()
+ {
+ return userObj.unicodeName;
+ };
+ function getSortFn()
+ {
+ if (client.prefs["sortUsersByMode"])
+ return ule_sortByMode;
+ return ule_sortByName;
+ };
+
+ // This object is used to represent a user in the userlist. To work with our
+ // JS tree view, it needs a bunch of stuff that is set through the
+ // constructor and the prototype (see also a couple of lines down). Here we
+ // call the original constructor to do some work for us:
+ XULTreeViewRecord.call(this, channelListShare);
+
+ // This magic function means the unicodeName is used for display:
+ this.setColumnPropertyName("usercol", getUName);
+
+ // We need this for sorting by mode (op, hop, voice, etc.)
+ this._userObj = userObj;
+
+ // When the user leaves, we need to have the entry so we can remove it:
+ userObj.chanListEntry = this;
+
+ // Gross hack: we set up the sort function by getter so we don't have to go
+ // back (array sort -> xpc -> our pref lib -> xpc -> pref interfaces) for
+ // every bloody compare. Now it will be a function that doesn't need prefs
+ // after being retrieved, which is much much faster.
+ this.__defineGetter__("sortCompare", getSortFn);
+}
+
+// See explanation in the constructor.
+UserEntry.prototype = XULTreeViewRecord.prototype;
+
+function ule_sortByName(a, b)
+{
+ if (a._userObj.unicodeName == b._userObj.unicodeName)
+ return 0;
+ var aName = a._userObj.unicodeName.toLowerCase();
+ var bName = b._userObj.unicodeName.toLowerCase();
+ return (aName < bName ? -1 : 1);
+}
+
+function ule_sortByMode(a, b)
+{
+ if (a._userObj.sortName == b._userObj.sortName)
+ return 0;
+ var aName = a._userObj.sortName.toLowerCase();
+ var bName = b._userObj.sortName.toLowerCase();
+ return (aName < bName ? -1 : 1);
+}