1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is ChatZilla.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998
21 * the Initial Developer. All Rights Reserved.
24 * Robert Ginda, <rginda@netscape.com>, original author
25 * Chiaki Koufugata chiaki@mozilla.gr.jp UI i18n
26 * Samuel Sieb, samuel@sieb.net, MIRC color codes, munger menu, and various
27 * James Ross, silver@warwickcompsoc.co.uk
29 * Alternatively, the contents of this file may be used under the terms of
30 * either the GNU General Public License Version 2 or later (the "GPL"), or
31 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 * in which case the provisions of the GPL or the LGPL are applicable instead
33 * of those above. If you wish to allow use of your version of this file only
34 * under the terms of either the GPL or the LGPL, and not to allow others to
35 * use your version of this file under the terms of the MPL, indicate your
36 * decision by deleting the provisions above and replace them with the notice
37 * and other provisions required by the GPL or the LGPL. If you do not delete
38 * the provisions above, a recipient may use your version of this file under
39 * the terms of any one of the MPL, the GPL or the LGPL.
41 * ***** END LICENSE BLOCK ***** */
43 const __cz_version = "0.9.81";
44 const __cz_condition = "green";
45 const __cz_suffix = "";
46 const __cz_guid = "59c81df5-4b7a-477b-912d-4e0fdf64e5f2";
47 const __cz_locale = "0.9.81";
56 warn = function (msg) { dumpln ("** WARNING " + msg + " **"); }
57 TEST = ASSERT = function _assert(expr, msg) {
59 dd("** ASSERTION FAILED: " + msg + " **\n" +
60 getStackTrace() + "\n");
68 dd = warn = TEST = ASSERT = function (){};
70 var client = new Object();
72 client.TYPE = "IRCClient";
73 client.COMMAND_CHAR = "/";
74 client.STEP_TIMEOUT = 500;
75 client.MAX_MESSAGES = 200;
76 client.MAX_HISTORY = 50;
77 /* longest nick to show in display before forcing the message to a block level
79 client.MAX_NICK_DISPLAY = 14;
80 /* longest word to show in display before abbreviating */
81 client.MAX_WORD_DISPLAY = 20;
83 client.NOTIFY_TIMEOUT = 5 * 60 * 1000; /* update notify list every 5 minutes */
85 // Check every minute which networks have away statuses that need an update.
86 client.AWAY_TIMEOUT = 60 * 1000;
88 client.SLOPPY_NETWORKS = true; /* true if msgs from a network can be displayed
89 * on the current object if it is related to
90 * the network (ie, /whois results will appear
91 * on the channel you're viewing, if that channel
92 * is on the network that the results came from)
94 client.DOUBLETAB_TIME = 500;
95 client.HIDE_CODES = true; /* true if you'd prefer to show numeric response
96 * codes as some default value (ie, "===") */
97 client.DEFAULT_RESPONSE_CODE = "===";
99 /* Maximum number of channels we'll try to list without complaining */
100 client.SAFE_LIST_COUNT = 500;
102 /* Minimum number of users above or below the conference limit the user count
103 * must go, before it is changed. This allows the user count to fluctuate
104 * around the limit without continously going on and off.
106 client.CONFERENCE_LOW_PASS = 10;
108 // Namespaces we happen to need:
109 const XHTML_NS = "http://www.w3.org/1999/xhtml";
111 client.viewsArray = new Array();
112 client.activityList = new Object();
113 client.hostCompat = new Object();
114 client.inputHistory = new Array();
115 client.lastHistoryReferenced = -1;
116 client.incompleteLine = "";
117 client.lastTabUp = new Date();
118 client.awayMsgs = new Array();
119 client.awayMsgCount = 5;
121 CIRCNetwork.prototype.INITIAL_CHANNEL = "";
122 CIRCNetwork.prototype.MAX_MESSAGES = 100;
123 CIRCNetwork.prototype.IGNORE_MOTD = false;
124 CIRCNetwork.prototype.RECLAIM_WAIT = 15000;
125 CIRCNetwork.prototype.RECLAIM_TIMEOUT = 400000;
126 CIRCNetwork.prototype.MIN_RECONNECT_MS = 15 * 1000; // 15s
127 CIRCNetwork.prototype.MAX_RECONNECT_MS = 2 * 60 * 60 * 1000; // 2h
129 CIRCServer.prototype.READ_TIMEOUT = 0;
130 CIRCServer.prototype.PRUNE_OLD_USERS = 0; // prune on user quit.
132 CIRCUser.prototype.MAX_MESSAGES = 200;
134 CIRCChannel.prototype.MAX_MESSAGES = 300;
136 CIRCChanUser.prototype.MAX_MESSAGES = 200;
140 if (("initialized" in client) && client.initialized)
143 client.initialized = false;
145 client.networks = new Object();
146 client.entities = new Object();
147 client.eventPump = new CEventPump (200);
151 /* hook all events EXCEPT server.poll and *.event-end types
152 * (the 4th param inverts the match) */
154 client.eventPump.addHook([{type: "poll", set:/^(server|dcc-chat)$/},
155 {type: "event-end"}], event_tracer,
156 "event-tracer", true /* negate */,
157 false /* disable */);
160 initApplicationCompatibility();
162 if (client.host == "")
163 showErrorDlg(getMsg(MSG_ERR_UNKNOWN_HOST, client.unknownUID));
173 // Create DCC handler.
174 client.dcc = new CIRCDCC(client);
176 client.ident = new IdentServer(client);
178 // start logging. nothing should call display() before this point.
179 if (client.prefs["log"])
180 client.openLogFile(client);
181 // kick-start a log-check interval to make sure we change logfiles in time:
182 // It will fire 2 seconds past the next full hour.
183 setTimeout("checkLogFiles()", 3602000 - (Number(new Date()) % 3600000));
185 // Make sure the userlist is on the correct side.
186 updateUserlistSide(client.prefs["userlistLeft"]);
188 client.display(MSG_WELCOME, "HELLO");
189 client.dispatch("set-current-view", { view: client });
191 importFromFrame("updateHeader");
192 importFromFrame("setHeaderState");
193 importFromFrame("changeCSS");
194 importFromFrame("updateMotifSettings");
195 importFromFrame("addUsers");
196 importFromFrame("updateUsers");
197 importFromFrame("removeUsers");
199 processStartupScripts();
201 client.commandManager.installKeys(document);
209 client.isIdleAway = false;
210 initIdleAutoAway(client.prefs["awayIdleTime"]);
212 client.initialized = true;
214 dispatch("help", { hello: true });
215 dispatch("networks");
217 initInstrumentation();
218 setTimeout("dispatch('focus-input')", 0);
219 setTimeout(processStartupURLs, 0);
222 function initStatic()
224 client.mainWindow = window;
228 var io = Components.classes['@mozilla.org/network/io-service;1'];
229 client.iosvc = io.getService(Components.interfaces.nsIIOService);
233 dd("IO service failed to initialize: " + ex);
236 // Need this for the userlist
237 client.atomSvc = getService("@mozilla.org/atom-service;1", "nsIAtomService");
241 const nsISound = Components.interfaces.nsISound;
243 Components.classes["@mozilla.org/sound;1"].createInstance(nsISound);
245 client.soundList = new Object();
249 dd("Sound failed to initialize: " + ex);
254 const nsIGlobalHistory = Components.interfaces.nsIGlobalHistory;
255 const GHIST_CONTRACTID = "@mozilla.org/browser/global-history;1";
256 client.globalHistory =
257 Components.classes[GHIST_CONTRACTID].getService(nsIGlobalHistory);
261 dd("Global History failed to initialize: " + ex);
266 const nsISDateFormat = Components.interfaces.nsIScriptableDateFormat;
267 const DTFMT_CID = "@mozilla.org/intl/scriptabledateformat;1";
269 Components.classes[DTFMT_CID].createInstance(nsISDateFormat);
271 // Mmmm, fun. This ONLY affects the ChatZilla window, don't worry!
272 Date.prototype.toStringInt = Date.prototype.toString;
273 Date.prototype.toString = function() {
274 var dtf = client.dtFormatter;
275 return dtf.FormatDateTime("", dtf.dateFormatLong,
276 dtf.timeFormatSeconds,
277 this.getFullYear(), this.getMonth() + 1,
278 this.getDate(), this.getHours(),
279 this.getMinutes(), this.getSeconds()
285 dd("Locale-correct date formatting failed to initialize: " + ex);
288 // XXX Bug 335998: See cmdHideView for usage of this.
289 client.hiddenDocument = document.implementation.createDocument(null, null, null);
291 multilineInputMode(client.prefs["multiline"]);
292 updateSpellcheck(client.prefs["inputSpellcheck"]);
293 if (client.prefs["showModeSymbols"])
294 setListMode("symbol");
296 setListMode("graphic");
298 var tree = document.getElementById('user-list');
299 tree.setAttribute("ondraggesture",
300 "nsDragAndDrop.startDrag(event, userlistDNDObserver);");
302 setDebugMode(client.prefs["debugMode"]);
304 var ver = __cz_version + (__cz_suffix ? "-" + __cz_suffix : "");
306 var ua = navigator.userAgent;
307 var app = getService("@mozilla.org/xre/app-info;1", "nsIXULAppInfo");
310 // Use the XUL host app info, and Gecko build ID.
311 if (app.ID == "{" + __cz_guid + "}")
313 // We ARE the app, in other words, we're running in XULrunner.
314 // Because of this, we must disregard app.(name|vendor|version).
315 // "XULRunner 1.7+/2005071506"
316 ua = "XULRunner " + app.platformVersion + "/" + app.platformBuildID;
318 // "XULRunner 1.7+/2005071506, Windows"
319 CIRCServer.prototype.HOST_RPLY = ua + ", " + client.platform;
323 // "Firefox 1.0+/2005071506"
324 ua = app.name + " " + app.version + "/";
325 if ("platformBuildID" in app) // 1.1 and up
326 ua += app.platformBuildID;
327 else if ("geckoBuildID" in app) // 1.0 - 1.1 trunk only
328 ua += app.geckoBuildID;
332 // "Mozilla Firefox 1.0+, Windows"
333 CIRCServer.prototype.HOST_RPLY = app.vendor + " " + app.name + " " +
334 app.version + ", " + client.platform;
339 // Extract the revision number, and Gecko build ID.
340 var ary = navigator.userAgent.match(/(rv:[^;)\s]+).*?Gecko\/(\d+)/);
343 if (navigator.vendor)
344 ua = navigator.vendor + " " + navigator.vendorSub; // FF 1.0
346 ua = client.entities.brandShortName + " " + ary[1]; // Suite
347 ua = ua + "/" + ary[2];
349 CIRCServer.prototype.HOST_RPLY = client.entities.brandShortName + ", " +
353 client.userAgent = getMsg(MSG_VERSION_REPLY, [ver, ua]);
354 CIRCServer.prototype.VERSION_RPLY = client.userAgent;
355 CIRCServer.prototype.SOURCE_RPLY = MSG_SOURCE_REPLY;
357 client.statusBar = new Object();
359 client.statusBar["server-nick"] = document.getElementById("server-nick");
361 client.tabs = document.getElementById("views-tbar-inner");
362 client.tabDragBar = document.getElementById("tabs-drop-indicator-bar");
363 client.tabDragMarker = document.getElementById("tabs-drop-indicator");
365 client.statusElement = document.getElementById("status-text");
366 client.defaultStatus = MSG_DEFAULT_STATUS;
368 client.progressPanel = document.getElementById("status-progress-panel");
369 client.progressBar = document.getElementById("status-progress-bar");
371 client.logFile = null;
372 setInterval("onNotifyTimeout()", client.NOTIFY_TIMEOUT);
373 // Call every minute, will check only the networks necessary.
374 setInterval("onWhoTimeout()", client.AWAY_TIMEOUT);
376 client.awayMsgs = [{ message: MSG_AWAY_DEFAULT }];
377 var awayFile = new nsLocalFile(client.prefs["profilePath"]);
378 awayFile.append("awayMsgs.txt");
379 if (awayFile.exists())
381 var awayLoader = new TextSerializer(awayFile);
382 if (awayLoader.open("<"))
384 // Load the first item from the file.
385 var item = awayLoader.deserialize();
386 if (isinstance(item, Array))
388 // If the first item is an array, it is the entire thing.
389 client.awayMsgs = item;
393 /* Not an array, so we have the old format of a single object
396 client.awayMsgs = [item];
397 while ((item = awayLoader.deserialize()))
398 client.awayMsgs.push(item);
404 client.defaultCompletion = client.COMMAND_CHAR + "help ";
406 client.deck = document.getElementById('output-deck');
409 function initApplicationCompatibility()
411 // This function does nothing more than tweak the UI based on the host
414 // Set up simple host and platform information.
415 client.host = "Unknown";
416 // Do we need to copy the icons? (not necessary on Gecko 1.8 and onwards,
417 // and install.js does it for us on SeaMonkey)
418 client.hostCompat.needToCopyIcons = false;
420 var app = getService("@mozilla.org/xre/app-info;1", "nsIXULAppInfo");
421 // nsIXULAppInfo wasn't implemented before 1.8...
424 // Use the XULAppInfo.ID to find out what host we run on.
427 case "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}":
428 client.host = "Firefox";
430 case "{" + __cz_guid + "}":
431 // We ARE the app, in other words, we're running in XULrunner.
432 client.host = "XULrunner";
434 case "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": // SeaMonkey
435 client.host = "Mozilla";
437 case "{a463f10c-3994-11da-9945-000d60ca027b}": // Flock
438 client.host = "Flock";
440 case "{3db10fab-e461-4c80-8b97-957ad5f8ea47}": // Netscape
441 client.host = "Netscape";
443 case "songbird@songbirdnest.com": // Songbird
444 client.host = "Songbird";
447 client.unknownUID = app.ID;
448 client.host = ""; // Unknown host, show an error later.
451 else if ("getBrowserURL" in window)
453 var url = getBrowserURL();
454 if (url == "chrome://navigator/content/navigator.xul")
456 client.host = "Mozilla";
458 else if (url == "chrome://browser/content/browser.xul")
460 client.hostCompat.needToCopyIcons = true;
461 client.host = "Firefox";
465 client.host = ""; // We don't know this host. Show an error later.
466 client.unknownUID = url;
470 client.platform = "Unknown";
471 if (navigator.platform.search(/mac/i) > -1)
472 client.platform = "Mac";
473 if (navigator.platform.search(/win/i) > -1)
474 client.platform = "Windows";
475 if (navigator.platform.search(/linux/i) > -1)
476 client.platform = "Linux";
477 if (navigator.platform.search(/os\/2/i) > -1)
478 client.platform = "OS/2";
480 client.hostPlatform = client.host + client.platform;
482 CIRCServer.prototype.OS_RPLY = navigator.oscpu + " (" +
483 navigator.platform + ")";
485 // Windows likes \r\n line endings, as wussy-notepad can't cope with just
487 if (client.platform == "Windows")
488 client.lineEnd = "\r\n";
490 client.lineEnd = "\n";
495 // Make sure we got the ChatZilla icon(s) in place first.
496 const iconName = "chatzilla-window";
497 const suffixes = [".ico", ".xpm", "16.xpm"];
499 /* when installing on Mozilla, the XPI has the power to put the icons where
500 * they are needed - in older versions of Firefox, it doesn't.
502 if (!client.hostCompat.needToCopyIcons)
505 var sourceDir = getSpecialDirectory("ProfD");
506 sourceDir.append("extensions");
507 sourceDir.append("{" + __cz_guid + "}");
508 sourceDir.append("chrome");
509 sourceDir.append("icons");
510 sourceDir.append("default");
512 var destDir = getSpecialDirectory("AChrom");
513 destDir.append("icons");
514 destDir.append("default");
515 if (!destDir.exists())
527 for (var i = 0; i < suffixes.length; i++)
529 var iconDest = destDir.clone();
530 iconDest.append(iconName + suffixes[i]);
531 var iconSrc = sourceDir.clone();
532 iconSrc.append(iconName + suffixes[i]);
534 if (iconSrc.exists() && !iconDest.exists())
538 iconSrc.copyTo(iconDest.parent, iconDest.leafName);
545 function initInstrumentation()
547 // Make sure we assign the user a random key - this is not used for
548 // anything except percentage chance of participation.
549 if (client.prefs["instrumentation.key"] == 0)
551 var rand = 1 + Math.round(Math.random() * 10000);
552 client.prefs["instrumentation.key"] = rand;
555 runInstrumentation("inst1");
558 function runInstrumentation(name, firstRun)
560 if (!/^inst\d+$/.test(name))
564 // 0 = not answered question
568 if (client.prefs["instrumentation." + name] == 0)
570 // We only want 1% of people to be asked here.
571 if (client.prefs["instrumentation.key"] > 100)
574 // User has not seen the info about this system. Show them the info.
575 var cmdYes = "allow-" + name;
576 var cmdNo = "deny-" + name;
577 var btnYes = getMsg(MSG_INST1_COMMAND_YES, cmdYes);
578 var btnNo = getMsg(MSG_INST1_COMMAND_NO, cmdNo);
579 client.munger.getRule(".inline-buttons").enabled = true;
580 client.display(getMsg("msg." + name + ".msg1", [btnYes, btnNo]));
581 client.display(getMsg("msg." + name + ".msg2", [cmdYes, cmdNo]));
582 client.munger.getRule(".inline-buttons").enabled = false;
584 // Don't hide *client* if we're asking the user about the startup ping.
585 client.lockView = true;
589 if (client.prefs["instrumentation." + name] != 1)
593 runInstrumentation1(firstRun);
596 function runInstrumentation1(firstRun)
598 function inst1onLoad()
600 if (/OK/.test(req.responseText))
601 client.display(MSG_INST1_MSGRPLY2);
603 client.display(getMsg(MSG_INST1_MSGRPLY1, MSG_UNKNOWN));
606 function inst1onError()
608 client.display(getMsg(MSG_INST1_MSGRPLY1, req.statusText));
613 const baseURI = "http://silver.warwickcompsoc.co.uk/" +
614 "mozilla/chatzilla/instrumentation/startup?";
618 // Do a first-run ping here.
619 var frReq = new XMLHttpRequest();
620 frReq.open("GET", baseURI + "first-run");
624 var data = new Array();
625 data.push("ver=" + encodeURIComponent(CIRCServer.prototype.VERSION_RPLY));
626 data.push("host=" + encodeURIComponent(client.hostPlatform));
627 data.push("chost=" + encodeURIComponent(CIRCServer.prototype.HOST_RPLY));
628 data.push("cos=" + encodeURIComponent(CIRCServer.prototype.OS_RPLY));
630 var url = baseURI + data.join("&");
632 var req = new XMLHttpRequest();
633 req.onload = inst1onLoad;
634 req.onerror = inst1onError;
635 req.open("GET", url);
640 client.display(getMsg(MSG_INST1_MSGRPLY1, formatException(ex)));
644 function getFindData(e)
646 var findData = new nsFindInstData();
647 findData.browser = e.sourceObject.frame;
648 findData.rootSearchWindow = getContentWindow(e.sourceObject.frame);
649 findData.currentSearchWindow = getContentWindow(e.sourceObject.frame);
651 /* Yay, evil hacks! findData.init doesn't care about the findService, it
652 * gets option settings from webBrowserFind. As we want the wrap option *on*
653 * when we use /find foo, we set it on the findService there. However,
654 * restoring the original value afterwards doesn't help, because init() here
655 * overrides that value. Unless we make .init do something else, of course:
657 findData._init = findData.init;
662 const FINDSVC_ID = "@mozilla.org/find/find_service;1";
663 var findService = getService(FINDSVC_ID, "nsIFindService");
664 this.webBrowserFind.wrapFind = findService.wrapFind;
670 function importFromFrame(method)
672 client.__defineGetter__(method, import_wrapper);
673 CIRCNetwork.prototype.__defineGetter__(method, import_wrapper);
674 CIRCChannel.prototype.__defineGetter__(method, import_wrapper);
675 CIRCUser.prototype.__defineGetter__(method, import_wrapper);
676 CIRCDCCChat.prototype.__defineGetter__(method, import_wrapper);
677 CIRCDCCFileTransfer.prototype.__defineGetter__(method, import_wrapper);
679 function import_wrapper()
681 var dummy = function(){};
683 if (!("frame" in this))
688 var window = getContentWindow(this.frame);
689 if (window && "initialized" in window && window.initialized &&
692 return function import_wrapper_apply()
694 window[method].apply(this, arguments);
700 ASSERT(0, "Caught exception calling: " + method + "\n" + ex);
707 function processStartupScripts()
709 client.plugins = new Array();
710 var scripts = client.prefs["initialScripts"];
711 for (var i = 0; i < scripts.length; ++i)
713 if (scripts[i].search(/^file:|chrome:/i) != 0)
715 display(getMsg(MSG_ERR_INVALID_SCHEME, scripts[i]), MT_ERROR);
719 var path = getFileFromURLSpec(scripts[i]);
723 display(getMsg(MSG_ERR_ITEM_NOT_FOUND, scripts[i]), MT_WARN);
727 if (path.isDirectory())
728 loadPluginDirectory(path);
734 function loadPluginDirectory(localPath, recurse)
736 if (typeof recurse == "undefined")
739 var initPath = localPath.clone();
740 initPath.append("init.js");
741 if (initPath.exists())
742 loadLocalFile(initPath);
747 var enumer = localPath.directoryEntries;
748 while (enumer.hasMoreElements())
750 var entry = enumer.getNext();
751 entry = entry.QueryInterface(Components.interfaces.nsILocalFile);
752 if (entry.isDirectory())
753 loadPluginDirectory(entry, recurse - 1);
757 function loadLocalFile(localFile)
759 var url = getURLSpecFromFile(localFile);
760 var glob = new Object();
761 dispatch("load", {url: url, scope: glob});
764 function getPluginById(id)
766 for (var i = 0; i < client.plugins.length; ++i)
768 if (client.plugins[i].id == id)
769 return client.plugins[i];
776 function getPluginIndexById(id)
778 for (var i = 0; i < client.plugins.length; ++i)
780 if (client.plugins[i].id == id)
788 function getPluginByURL(url)
790 for (var i = 0; i < client.plugins.length; ++i)
792 if (client.plugins[i].url == url)
793 return client.plugins[i];
800 function getPluginIndexByURL(url)
802 for (var i = 0; i < client.plugins.length; ++i)
804 if (client.plugins[i].url == url)
812 function processStartupURLs()
814 var wentSomewhere = false;
816 if ("arguments" in window &&
817 0 in window.arguments && typeof window.arguments[0] == "object" &&
818 "url" in window.arguments[0])
820 var url = window.arguments[0].url;
821 if (url.search(/^ircs?:\/?\/?\/?$/i) == -1)
823 /* if the url is not irc: irc:/, irc://, or ircs equiv then go to it. */
825 wentSomewhere = true;
828 /* check to see whether the URL has been passed via the command line
830 else if ("arguments" in window &&
831 0 in window.arguments && typeof window.arguments[0] == "string")
833 var url = window.arguments[0]
834 var urlMatches = url.match(/^ircs?:\/\/\/?(.*)$/)
839 /* if the url is not "irc://", "irc:///" or an ircs equiv then
842 wentSomewhere = true;
847 /* URL parameter is not blank, but does not not conform to the
849 display(getMsg(MSG_ERR_INVALID_SCHEME, url), MT_ERROR);
855 /* if we had nowhere else to go, connect to any default urls */
856 var ary = client.prefs["initialURLs"];
857 for (var i = 0; i < ary.length; ++i)
859 if (ary[i] && ary[i] == "irc:///")
861 // Clean out "default network" entries, which we don't
862 // support any more; replace with the harmless irc:// URL.
864 client.prefs["initialURLs"].update();
866 if (ary[i] && ary[i] != "irc://")
871 if (client.viewsArray.length > 1 && !isStartupURL("irc://"))
872 dispatch("delete-view", { view: client });
874 /* XXX: If we have the "stop XBL breaking" hidden tab, remove it, to
875 * stop XBL breaking later. Oh, the irony.
877 if (client.tabs.firstChild.hidden)
879 client.tabs.removeChild(client.tabs.firstChild);
880 updateTabAttributes();
889 function setStatus (str)
891 client.statusElement.setAttribute ("label", str);
895 client.__defineSetter__ ("status", setStatus);
897 function getStatus ()
899 return client.statusElement.getAttribute ("label");
902 client.__defineGetter__ ("status", getStatus);
904 function isVisible (id)
906 var e = document.getElementById(id);
908 if (!ASSERT(e,"Bogus id ``" + id + "'' passed to isVisible() **"))
911 return (e.getAttribute ("collapsed") != "true");
914 client.getConnectedNetworks =
915 function getConnectedNetworks()
918 for (var n in client.networks)
920 if (client.networks[n].isConnected())
921 rv.push(client.networks[n]);
926 function combineNicks(nickList, max)
931 var combinedList = [];
933 for (var i = 0; i < nickList.length; i += max)
935 count = Math.min(max, nickList.length - i);
936 var nicks = nickList.slice(i, i + count);
937 var str = new String(nicks.join(" "));
939 combinedList.push(str);
945 function updateAllStalkExpressions()
947 var list = client.prefs["stalkWords"];
949 for (var name in client.networks)
951 if ("stalkExpression" in client.networks[name])
952 updateStalkExpression(client.networks[name], list);
956 function updateStalkExpression(network)
958 function escapeChar(ch)
963 var list = client.prefs["stalkWords"];
965 var ary = new Array();
967 ary.push(network.primServ.me.unicodeName.replace(/[^\w\d]/g, escapeChar));
969 for (var i = 0; i < list.length; ++i)
970 ary.push(list[i].replace(/[^\w\d]/g, escapeChar));
973 if (client.prefs["stalkWholeWords"])
974 re = "(^|[\\W\\s])((" + ary.join(")|(") + "))([\\W\\s]|$)";
976 re = "(" + ary.join(")|(") + ")";
978 network.stalkExpression = new RegExp(re, "i");
981 function getDefaultFontSize()
983 const PREF_CTRID = "@mozilla.org/preferences-service;1";
984 const nsIPrefService = Components.interfaces.nsIPrefService;
985 const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
987 var prefSvc = Components.classes[PREF_CTRID].getService(nsIPrefService);
988 var prefBranch = prefSvc.getBranch(null);
990 // PX size pref: font.size.variable.x-western
994 pxSize = prefBranch.getIntPref("font.size.variable.x-western");
1001 // Get the DPI the fun way (make Mozilla do the work).
1002 var b = document.createElement("box");
1003 b.style.width = "1in";
1004 dpi = window.getComputedStyle(b, null).width.match(/^\d+/);
1010 // Get the DPI the fun way (make Mozilla do the work).
1011 b = document.createElementNS("box", XHTML_NS);
1012 b.style.width = "1in";
1013 dpi = window.getComputedStyle(b, null).width.match(/^\d+/);
1018 return Math.round((pxSize / dpi) * 72);
1021 function getDefaultContext(cx)
1025 /* Use __proto__ here and in all other get*Context so that the command can
1026 * tell the difference between getObjectDetails and actual parameters. See
1027 * cmdJoin for more details.
1029 cx.__proto__ = getObjectDetails(client.currentObject);
1033 function getMessagesContext(cx, element)
1037 cx.__proto__ = getObjectDetails(client.currentObject);
1039 element = document.popupNode;
1043 switch (element.localName)
1046 var href = element.getAttribute("href");
1051 var nickname = element.getAttribute("msg-user");
1055 // strip out a potential ME! suffix
1056 var ary = nickname.match(/([^ ]+)/);
1062 // NOTE: nickname is the unicodeName here!
1064 cx.user = cx.channel.getUser(nickname);
1066 cx.user = cx.network.getUser(nickname);
1070 cx.nickname = cx.user.unicodeName;
1071 cx.canonNick = cx.user.canonicalName;
1075 cx.nickname = nickname;
1080 element = element.parentNode;
1086 function getTabContext(cx, element)
1091 element = document.popupNode;
1095 if (element.localName == "tab")
1097 cx.__proto__ = getObjectDetails(element.view);
1100 element = element.parentNode;
1106 function getUserlistContext(cx)
1110 cx.__proto__ = getObjectDetails(client.currentObject);
1114 var user, tree = document.getElementById("user-list");
1115 cx.userList = new Array();
1116 cx.canonNickList = new Array();
1117 cx.nicknameList = getSelectedNicknames(tree);
1119 for (var i = 0; i < cx.nicknameList.length; ++i)
1121 user = cx.channel.getUser(cx.nicknameList[i])
1122 cx.userList.push(user);
1123 cx.canonNickList.push(user.canonicalName);
1127 cx.nickname = user.unicodeName;
1128 cx.canonNick = user.canonicalName;
1131 cx.userCount = cx.userList.length;
1136 function getSelectedNicknames(tree)
1139 if (!tree || !tree.view || !tree.view.selection)
1141 var rangeCount = tree.view.selection.getRangeCount();
1143 // Loop through the selection ranges.
1144 for (var i = 0; i < rangeCount; ++i)
1146 var start = {}, end = {};
1147 tree.view.selection.getRangeAt(i, start, end);
1149 // If they == -1, we've got no selection, so bail.
1150 if ((start.value == -1) && (end.value == -1))
1152 /* Workaround: Because we use select(-1) instead of clearSelection()
1153 * (see bug 197667) the tree will then give us selection ranges
1154 * starting from -1 instead of 0! (See bug 319066.)
1156 if (start.value == -1)
1159 // Loop through the contents of the current selection range.
1160 for (var k = start.value; k <= end.value; ++k)
1161 rv.push(getNicknameForUserlistRow(k));
1166 function getFontContext(cx)
1170 cx.__proto__ = getObjectDetails(client.currentObject);
1171 cx.fontSizeDefault = getDefaultFontSize();
1174 if ("prefs" in cx.sourceObject)
1176 cx.fontFamily = view.prefs["font.family"];
1177 if (cx.fontFamily.match(/^(default|(sans-)?serif|monospace)$/))
1178 delete cx.fontFamily;
1180 cx.fontSize = view.prefs["font.size"];
1181 if (cx.fontSize == 0)
1188 function msgIsImportant(msg, sourceNick, network)
1190 var plainMsg = removeColorCodes(msg);
1192 var re = network.stalkExpression;
1193 if (plainMsg.search(re) != -1 || sourceNick && sourceNick.search(re) == 0)
1199 function ensureCachedCanonicalURLs(array)
1201 if ("canonicalURLs" in array)
1204 /* Caching this on the array is safe because the PrefManager constructs
1205 * a new array if the preference changes, but otherwise keeps the same
1208 array.canonicalURLs = new Array();
1209 for (var i = 0; i < array.length; i++)
1210 array.canonicalURLs.push(makeCanonicalIRCURL(array[i]));
1213 function isStartupURL(url)
1215 // We canonicalize all URLs before we do the (string) comparison.
1216 url = makeCanonicalIRCURL(url);
1217 var list = client.prefs["initialURLs"];
1218 ensureCachedCanonicalURLs(list);
1219 return arrayContains(list.canonicalURLs, url);
1222 function cycleView(amount)
1224 var len = client.viewsArray.length;
1228 var tb = getTabForObject (client.currentObject);
1232 var vk = Number(tb.getAttribute("viewKey"));
1233 var destKey = (vk + amount) % len; /* wrap around */
1237 dispatch("set-current-view", { view: client.viewsArray[destKey].source });
1240 // Plays the sound for a particular event on a type of object.
1241 function playEventSounds(type, event)
1243 if (!client.sound || !client.prefs["sound.enabled"])
1246 // Converts .TYPE values into the event object names.
1247 // IRCChannel => channel, IRCUser => user, etc.
1248 if (type.match(/^IRC/))
1249 type = type.substr(3, type.length).toLowerCase();
1251 var ev = type + "." + event;
1253 if (ev in client.soundList)
1256 if (!(("sound." + ev) in client.prefs))
1259 var s = client.prefs["sound." + ev];
1264 if (client.prefs["sound.overlapDelay"] > 0)
1266 client.soundList[ev] = true;
1267 setTimeout("delete client.soundList['" + ev + "']",
1268 client.prefs["sound.overlapDelay"]);
1271 if (event == "start")
1273 blockEventSounds(type, "event");
1274 blockEventSounds(type, "chat");
1275 blockEventSounds(type, "stalk");
1281 // Blocks a particular type of event sound occuring.
1282 function blockEventSounds(type, event)
1284 if (!client.sound || !client.prefs["sound.enabled"])
1287 // Converts .TYPE values into the event object names.
1288 // IRCChannel => channel, IRCUser => user, etc.
1289 if (type.match(/^IRC/))
1290 type = type.substr(3, type.length).toLowerCase();
1292 var ev = type + "." + event;
1294 if (client.prefs["sound.overlapDelay"] > 0)
1296 client.soundList[ev] = true;
1297 setTimeout("delete client.soundList['" + ev + "']",
1298 client.prefs["sound.overlapDelay"]);
1302 function playSounds(list)
1304 var ary = list.split (" ");
1305 if (ary.length == 0)
1309 for (var i = 1; i < ary.length; ++i)
1310 setTimeout(playSound, 250 * i, ary[i]);
1313 function playSound(file)
1315 if (!client.sound || !client.prefs["sound.enabled"] || !file)
1320 client.sound.beep();
1326 var uri = client.iosvc.newURI(file, null, null);
1327 client.sound.play(uri);
1331 // ignore exceptions from this pile of code.
1336 /* timer-based mainloop */
1341 var count = client.eventPump.stepEvents();
1343 setTimeout("mainStep()", client.STEP_TIMEOUT);
1345 setTimeout("mainStep()", client.STEP_TIMEOUT / 5);
1349 dd("Exception in mainStep!");
1350 dd(formatException(ex));
1351 setTimeout("mainStep()", client.STEP_TIMEOUT);
1355 function openQueryTab(server, nick)
1357 var user = server.addUser(nick);
1358 if (client.globalHistory)
1359 client.globalHistory.addPage(user.getURL());
1360 if (!("messages" in user))
1364 for (var c in server.channels)
1366 var chan = server.channels[c];
1367 if (!(user.canonicalName in chan.users))
1369 /* This takes a boolean value for each channel (true - channel has
1370 * same value as first), and &&-s them all together. Thus, |same|
1371 * will tell us, at the end, if all the channels found have the
1372 * same value for charset.
1375 same = same && (value == chan.prefs["charset"]);
1377 value = chan.prefs["charset"];
1379 /* If we've got a value, and it's the same accross all channels,
1380 * we use it as the *default* for the charset pref. If not, it'll
1381 * just keep the "defer" default which pulls it off the network.
1385 user.prefManager.prefRecords["charset"].defaultValue = value;
1388 user.displayHere (getMsg(MSG_QUERY_OPENED, user.unicodeName));
1394 function arraySpeak (ary, single, plural)
1411 rv = ary[0] + " " + and + " " + ary[1];
1417 for (var i = 0; i < ary.length - 1; ++i)
1418 rv += ary[i] + ", ";
1419 rv += and + " " + ary[ary.length - 1];
1429 function getObjectDetails (obj, rv)
1434 if (!ASSERT(obj && typeof obj == "object",
1435 "INVALID OBJECT passed to getObjectDetails (" + obj + "). **"))
1440 rv.sourceObject = obj;
1442 rv.parent = ("parent" in obj) ? obj.parent : null;
1451 rv.viewType = MSG_CHANNEL;
1453 rv.channelName = obj.unicodeName;
1454 rv.server = rv.channel.parent;
1455 rv.network = rv.server.parent;
1459 rv.viewType = MSG_USER;
1461 rv.userName = obj.unicodeName;
1462 rv.server = rv.user.parent;
1463 rv.network = rv.server.parent;
1467 rv.viewType = MSG_USER;
1469 rv.userName = obj.unicodeName;
1470 rv.channel = rv.user.parent;
1471 rv.server = rv.channel.parent;
1472 rv.network = rv.server.parent;
1477 rv.viewType = MSG_NETWORK;
1478 if ("primServ" in rv.network)
1479 rv.server = rv.network.primServ;
1485 rv.viewType = MSG_TAB;
1489 //rv.viewType = MSG_USER;
1491 rv.userName = obj.unicodeName;
1495 //rv.viewType = MSG_USER;
1498 rv.userName = obj.unicodeName;
1501 case "IRCDCCFileTransfer":
1502 //rv.viewType = MSG_USER;
1505 rv.userName = obj.unicodeName;
1506 rv.fileName = obj.filename;
1510 /* no setup for unknown object */
1515 rv.networkName = rv.network.unicodeName;
1521 function findDynamicRule (selector)
1523 var rules = frames[0].document.styleSheets[1].cssRules;
1525 if (isinstance(selector, RegExp))
1530 for (var i = 0; i < rules.length; ++i)
1532 var rule = rules.item(i);
1533 if (rule.selectorText && rule.selectorText[fun](selector) == 0)
1534 return {sheet: frames[0].document.styleSheets[1], rule: rule,
1541 function addDynamicRule (rule)
1543 var rules = frames[0].document.styleSheets[1];
1545 var pos = rules.cssRules.length;
1546 rules.insertRule (rule, pos);
1549 function getCommandEnabled(command)
1552 var dispatcher = document.commandDispatcher;
1553 var controller = dispatcher.getControllerForCommand(command);
1555 return controller.isCommandEnabled(command);
1563 function doCommand(command)
1566 var dispatcher = document.commandDispatcher;
1567 var controller = dispatcher.getControllerForCommand(command);
1568 if (controller && controller.isCommandEnabled(command))
1569 controller.doCommand(command);
1576 function doCommandWithParams(command, params)
1579 var dispatcher = document.commandDispatcher;
1580 var controller = dispatcher.getControllerForCommand(command);
1581 controller.QueryInterface(Components.interfaces.nsICommandController);
1583 if (!controller || !controller.isCommandEnabled(command))
1586 var cmdparams = newObject("@mozilla.org/embedcomp/command-params;1",
1587 "nsICommandParams");
1588 for (var i in params)
1589 cmdparams.setISupportsValue(i, params[i]);
1591 controller.doCommandWithParams(command, cmdparams);
1599 ["irc:", "irc://", "irc:///", "irc:///help", "irc:///help,needkey",
1600 "irc://irc.foo.org", "irc://foo:6666",
1601 "irc://foo", "irc://irc.foo.org/", "irc://foo:6666/", "irc://foo/",
1602 "irc://irc.foo.org/,needpass", "irc://foo/,isserver",
1603 "irc://moznet/,isserver", "irc://moznet/",
1604 "irc://foo/chatzilla", "irc://foo/chatzilla/",
1605 "irc://irc.foo.org/?msg=hello%20there",
1606 "irc://irc.foo.org/?msg=hello%20there&ignorethis",
1607 "irc://irc.foo.org/%23mozilla,needkey?msg=hello%20there&ignorethis",
1609 "irc://irc.foo.org/,isnick"];
1611 function doURLTest()
1613 for (var u in testURLs)
1615 dd("testing url \"" + testURLs[u] + "\"");
1616 var o = parseIRCURL(testURLs[u]);
1618 dd("PARSE FAILED!");
1620 dd(dumpObjectTree(o));
1625 var testIRCURLObjects =
1628 [{host: "undernet"}, "irc://undernet/"],
1629 [{host: "irc.undernet.org"}, "irc://irc.undernet.org/"],
1630 [{host: "irc.undernet.org", isserver: true}, "irc://irc.undernet.org/"],
1631 [{host: "undernet", isserver: true}, "irc://undernet/,isserver"],
1632 [{host: "irc.undernet.org", port: 6667}, "irc://irc.undernet.org/"],
1633 [{host: "irc.undernet.org", port: 1}, "irc://irc.undernet.org:1/"],
1634 [{host: "irc.undernet.org", port: 1, scheme: "ircs"},
1635 "ircs://irc.undernet.org:1/"],
1636 [{host: "irc.undernet.org", port: 9999, scheme: "ircs"},
1637 "ircs://irc.undernet.org/"],
1638 [{host: "undernet", needpass: true}, "irc://undernet/,needpass"],
1639 [{host: "undernet", pass: "cz"}, "irc://undernet/?pass=cz"],
1640 [{host: "undernet", charset: "utf-8"}, "irc://undernet/?charset=utf-8"],
1641 [{host: "undernet", target: "#foo"}, "irc://undernet/%23foo"],
1642 [{host: "undernet", target: "#foo", needkey: true},
1643 "irc://undernet/%23foo,needkey"],
1644 [{host: "undernet", target: "John", isnick: true},
1645 "irc://undernet/John,isnick"],
1646 [{host: "undernet", target: "#foo", key: "cz"},
1647 "irc://undernet/%23foo?key=cz"],
1648 [{host: "undernet", charset: "utf-8"}, "irc://undernet/?charset=utf-8"],
1649 [{host: "undernet", target: "John", msg: "spam!"},
1650 "irc://undernet/John?msg=spam%21"],
1651 [{host: "undernet", target: "foo", isnick: true, msg: "spam!", pass: "cz"},
1652 "irc://undernet/foo,isnick?msg=spam%21&pass=cz"]
1655 function doObjectURLtest()
1657 var passed = 0, total = testIRCURLObjects.length;
1658 for (var i = 0; i < total; i++)
1660 var obj = testIRCURLObjects[i][0];
1661 var url = testIRCURLObjects[i][1];
1662 var parsedURL = constructIRCURL(obj)
1663 if (url != parsedURL)
1665 display("Parsed IRC Object incorrectly! Expected '" + url +
1666 "', got '" + parsedURL, MT_ERROR);
1673 display("Passed " + passed + " out of " + total + " tests (" +
1674 passed / total * 100 + "%).", MT_INFO);
1678 function gotoIRCURL(url, e)
1681 if (typeof url == "string")
1682 url = parseIRCURL(url);
1686 window.alert(getMsg(MSG_ERR_BAD_IRCURL, urlspec));
1692 /* focus the *client* view for irc:, irc:/, and irc:// (the only irc
1693 * urls that don't have a host. (irc:/// implies a connect to the
1696 client.pendingViewContext = e;
1698 delete client.pendingViewContext;
1706 var alreadyThere = false;
1707 var gettingThere = false;
1708 for (var n in client.networks)
1710 if ((client.networks[n].isConnected()) &&
1711 (client.networks[n].primServ.hostname == url.host) &&
1712 (client.networks[n].primServ.port == url.port))
1714 /* already connected to this server/port */
1715 network = client.networks[n];
1716 gettingThere = true;
1717 // Have we actually had the 001 reply yet?
1718 alreadyThere = (client.networks[n].state == NET_ONLINE);
1728 dd ("gotoIRCURL: not yet had connected to server" + url.host +
1729 " trying to connect...");
1732 // Do we have a password, if needed?
1742 pass = promptPassword(getMsg(MSG_HOST_PASSWORD,
1747 client.pendingViewContext = e;
1748 var params = {hostname: url.host, port: url.port,
1750 var cmd = (url.scheme == "ircs" ? "sslserver" : "server");
1751 network = dispatch(cmd, params);
1752 delete client.pendingViewContext;
1758 if (!("pendingURLs" in network))
1759 network.pendingURLs = new Array();
1760 network.pendingURLs.unshift({ url: url, e: e });
1766 /* parsed as a network name */
1767 if (!(url.host in client.networks))
1769 display(getMsg(MSG_ERR_UNKNOWN_NETWORK, url.host));
1773 network = client.networks[url.host];
1774 if (network.state != NET_ONLINE)
1777 dd ("gotoIRCURL: not already connected to " +
1778 "network " + url.host + " trying to connect...");
1780 var secure = (url.scheme == "ircs" ? true : false);
1781 if (!network.isConnected())
1783 client.pendingViewContext = e;
1784 client.connectToNetwork(network, secure);
1785 delete client.pendingViewContext;
1791 if (!("pendingURLs" in network))
1792 network.pendingURLs = new Array();
1793 network.pendingURLs.unshift({ url: url, e: e });
1798 /* already connected, do whatever comes next in the url */
1799 //dd ("gotoIRCURL: connected, time to finish parsing ``" + url + "''");
1806 /* url points to a person. */
1807 var nick = url.target;
1808 var ary = url.target.split("!");
1812 client.pendingViewContext = e;
1813 targetObject = network.dispatch("query", {nickname: nick});
1814 delete client.pendingViewContext;
1818 /* url points to a channel */
1825 key = window.promptPassword(getMsg(MSG_URL_KEY, url.spec));
1830 client.pendingViewContext = e;
1831 var d = { channelName: url.target, key: key,
1832 charset: url.charset };
1833 targetObject = network.dispatch("join", d);
1834 delete client.pendingViewContext;
1838 // Must do this the hard way... we have the server's format
1839 // for the channel name here, and all our commands only work
1840 // with the Unicode forms.
1841 var serv = network.primServ;
1842 var target = url.target;
1844 /* If we don't have a valid prefix, stick a "#" on it.
1845 * NOTE: This is always a "#" so that URLs may be compared
1846 * properly without involving the server (e.g. off-line).
1848 if ((arrayIndexOf(["#", "&", "+", "!"], target[0]) == -1) &&
1849 (arrayIndexOf(serv.channelTypes, target[0]) == -1))
1851 target = "#" + target;
1854 var chan = new CIRCChannel(serv, null, target);
1856 client.pendingViewContext = e;
1857 d = {channelToJoin: chan, key: key};
1858 targetObject = network.dispatch("join", d);
1859 delete client.pendingViewContext;
1868 client.pendingViewContext = e;
1870 if (url.msg.indexOf("\01ACTION") == 0)
1872 msg = filterOutput(url.msg, "ACTION", targetObject);
1873 targetObject.display(msg, "ACTION", "ME!",
1874 client.currentObject);
1878 msg = filterOutput(url.msg, "PRIVMSG", targetObject);
1879 targetObject.display(msg, "PRIVMSG", "ME!",
1880 client.currentObject);
1882 targetObject.say(msg);
1883 dispatch("set-current-view", { view: targetObject });
1884 delete client.pendingViewContext;
1889 client.pendingViewContext = e;
1890 if (!network.messages)
1891 network.displayHere(getMsg(MSG_NETWORK_OPENED, network.unicodeName));
1892 dispatch("set-current-view", { view: network });
1893 delete client.pendingViewContext;
1897 function updateProgress()
1902 if ("busy" in client.currentObject)
1903 busy = client.currentObject.busy;
1905 if ("progress" in client.currentObject)
1906 progress = client.currentObject.progress;
1911 client.progressPanel.collapsed = !busy;
1912 client.progressBar.mode = (progress < 0 ? "undetermined" : "determined");
1914 client.progressBar.value = progress;
1917 function updateSecurityIcon()
1919 var o = getObjectDetails(client.currentObject);
1920 var securityButton = window.document.getElementById("security-button");
1921 securityButton.firstChild.value = "";
1922 securityButton.removeAttribute("level");
1923 securityButton.removeAttribute("tooltiptext");
1924 if (!o.server || !o.server.isConnected) // No server or connection?
1926 securityButton.setAttribute("tooltiptext", MSG_SECURITY_INFO);
1930 var securityState = o.server.connection.getSecurityState()
1931 switch (securityState[0])
1933 case STATE_IS_SECURE:
1934 securityButton.firstChild.value = o.server.hostname;
1935 if (securityState[1] == STATE_SECURE_HIGH)
1936 securityButton.setAttribute("level", "high");
1937 else // Because low security is the worst we have when being secure
1938 securityButton.setAttribute("level", "low");
1941 var issuer = o.server.connection.getCertificate().issuerOrganization;
1942 var tooltiptext = getMsg(MSG_SECURE_CONNECTION, issuer);
1943 securityButton.setAttribute("tooltiptext", tooltiptext);
1944 securityButton.firstChild.setAttribute("tooltiptext", tooltiptext);
1945 securityButton.lastChild.setAttribute("tooltiptext", tooltiptext);
1947 case STATE_IS_BROKEN:
1948 securityButton.setAttribute("level", "broken");
1949 // No break to make sure we get the correct tooltip
1950 case STATE_IS_INSECURE:
1952 securityButton.setAttribute("tooltiptext", MSG_SECURITY_INFO);
1956 function initOfflineIcon()
1958 const IOSVC2_CID = "@mozilla.org/network/io-service;1";
1959 const PRBool_CID = "@mozilla.org/supports-PRBool;1";
1960 const OS_CID = "@mozilla.org/observer-service;1";
1961 const nsISupportsPRBool = Components.interfaces.nsISupportsPRBool;
1963 client.offlineObserver = {
1964 _element: document.getElementById("offline-status"),
1965 _getNewIOSvc: function offline_getNewIOSvc()
1969 return getService(IOSVC2_CID, "nsIIOService2");
1973 // If it failed, it's probably just not there. We don't care.
1976 state: function offline_state()
1978 return (client.iosvc.offline ? "offline" : "online");
1980 observe: function offline_observe(subject, topic, state)
1982 if ((topic == "offline-requested") &&
1983 (client.getConnectionCount() > 0))
1985 var buttonAry = [MSG_REALLY_GO_OFFLINE, MSG_DONT_GO_OFFLINE];
1986 var rv = confirmEx(MSG_GOING_OFFLINE, buttonAry);
1987 if (rv == 1) // Don't go offline, please!
1989 subject.QueryInterface(nsISupportsPRBool);
1990 subject.data = true;
1993 else if (topic == "network:offline-status-changed")
1995 this.updateOfflineUI();
1998 updateOfflineUI: function offline_uiUpdate()
2000 this._element.setAttribute("offlinestate", this.state());
2001 var tooltipMsgId = "MSG_OFFLINESTATE_" + this.state().toUpperCase();
2002 this._element.setAttribute("tooltiptext", window[tooltipMsgId]);
2004 toggleOffline: function offline_toggle()
2006 // Check whether people are OK with us going offline:
2007 if (!client.iosvc.offline && !this.canGoOffline())
2010 // Stop automatic management of the offline status, if existing.
2013 var ioSvc2 = this._getNewIOSvc();
2014 if (ioSvc2 && ("manageOfflineStatus" in ioSvc2))
2015 ioSvc2.manageOfflineStatus = false;
2019 dd("Turning off managed offline status failed!\n" + ex);
2022 // Actually change the offline state.
2023 client.iosvc.offline = !client.iosvc.offline;
2025 this.updatePrefFromOffline();
2027 canGoOffline: function offline_check()
2031 var os = getService(OS_CID, "nsIObserverService");
2032 var canGoOffline = newObject(PRBool_CID, "nsISupportsPRBool");
2033 os.notifyObservers(canGoOffline, "offline-requested", null);
2034 // Someone called for a halt
2035 if (canGoOffline.data)
2040 dd("Exception when trying to ask if we could go offline:" + ex);
2044 updateOfflineFromPref: function offline_syncFromPref()
2046 // On toolkit, we might have smart management of offline mode.
2048 var ioSvc2 = this._getNewIOSvc();
2049 if (ioSvc2 && ioSvc2.manageOfflineStatus)
2052 // This is app-managed, or should be, on startup:
2053 if (client.host == "Mozilla")
2056 var isOffline = false;
2057 var prefSvc = getService("@mozilla.org/preferences-service;1",
2059 // Let the app-specific hacks begin:
2061 if (client.host == "XULrunner")
2062 isOffline = !prefSvc.getBoolPref("network.online");
2063 else // Toolkit based, but not standalone
2064 isOffline = prefSvc.getBoolPref("browser.offline");
2066 catch (ex) { /* Whatever. */ }
2069 client.iosvc.offline = isOffline;
2071 updatePrefFromOffline: function offline_syncToPref()
2073 // This is app-managed, or should be.
2074 if (client.host == "Mozilla")
2077 var isOffline = client.iosvc.offline;
2078 var prefSvc = getService("@mozilla.org/preferences-service;1",
2080 // Let the app-specific hacks begin:
2082 if (client.host == "XULrunner")
2083 prefSvc.setBoolPref("network.online", !isOffline);
2084 else // Toolkit based, but not standalone
2085 prefSvc.setBoolPref("browser.offline", isOffline);
2089 dd("Couldn't set offline pref! Error:" + ex);
2096 var os = getService(OS_CID, "nsIObserverService");
2097 os.addObserver(client.offlineObserver, "offline-requested", false);
2098 os.addObserver(client.offlineObserver,
2099 "network:offline-status-changed", false);
2103 dd("Exception when trying to register offline observers: " + ex);
2106 var elem = client.offlineObserver._element;
2107 elem.setAttribute("onclick", "client.offlineObserver.toggleOffline()");
2108 client.offlineObserver.updateOfflineFromPref();
2109 client.offlineObserver.updateOfflineUI();
2116 function uninitOfflineIcon()
2118 const OS_CID = "@mozilla.org/observer-service;1";
2121 var os = getService(OS_CID, "nsIObserverService");
2122 os.removeObserver(client.offlineObserver, "offline-requested", false);
2123 os.removeObserver(client.offlineObserver,
2124 "network:offline-status-changed", false);
2128 dd("Exception when trying to unregister offline observers: " + ex);
2132 client.idleObserver = {
2133 QueryInterface: function io_qi(iid)
2135 if (!iid || (!iid.equals(Components.interfaces.nsIObserver) &&
2136 !iid.equals(Components.interfaces.nsISupports)))
2138 throw Components.results.NS_ERROR_NO_INTERFACE;
2142 observe: function io_observe(subject, topic, data)
2144 if ((topic == "idle") && !client.prefs["away"])
2146 if (!client.prefs["awayIdleMsg"])
2147 client.prefs["awayIdleMsg"] = MSG_AWAY_IDLE_DEFAULT;
2148 client.dispatch("idle-away", {reason: client.prefs["awayIdleMsg"]});
2149 client.isIdleAway = true;
2151 else if ((topic == "back") && client.isIdleAway)
2153 client.dispatch("idle-back");
2154 client.isIdleAway = false;
2159 function initIdleAutoAway(timeout)
2161 // Don't try to do anything if we are disabled
2165 var is = getService("@mozilla.org/widget/idleservice;1", "nsIIdleService");
2168 display(MSG_ERR_NO_IDLESERVICE, MT_WARN);
2169 client.prefs["autoIdleTime"] = 0;
2175 is.addIdleObserver(client.idleObserver, timeout * 60);
2179 display(formatException(ex), MT_ERROR);
2183 function uninitIdleAutoAway(timeout)
2185 // Don't try to do anything if we were disabled before
2189 var is = getService("@mozilla.org/widget/idleservice;1", "nsIIdleService");
2195 is.removeIdleObserver(client.idleObserver, timeout * 60);
2199 display(formatException(ex), MT_ERROR);
2203 function updateAppMotif(motifURL)
2205 var node = document.firstChild;
2206 while (node && ((node.nodeType != node.PROCESSING_INSTRUCTION_NODE) ||
2207 !(/name="dyn-motif"/).test(node.data)))
2209 node = node.nextSibling;
2212 motifURL = motifURL.replace(/"/g, "%22");
2213 var dataStr = "href=\"" + motifURL + "\" name=\"dyn-motif\"";
2216 // No dynamic style node yet.
2219 node = document.createProcessingInstruction("xml-stylesheet", dataStr);
2220 document.insertBefore(node, document.firstChild);
2224 node.data = dataStr;
2229 dd(formatException(ex));
2231 // Mozilla 1.0 doesn't like document.insertBefore(...,
2232 // document.firstChild); though it has a prototype for it -
2233 // check for the right error:
2234 if (err == "NS_ERROR_NOT_IMPLEMENTED")
2236 display(MSG_NO_DYNAMIC_STYLE, MT_INFO);
2237 updateAppMotif = function() {};
2242 function updateSpellcheck(value)
2244 value = value.toString();
2245 document.getElementById("input").setAttribute("spellcheck", value);
2246 document.getElementById("multiline-input").setAttribute("spellcheck",
2250 function updateNetwork()
2252 var o = getObjectDetails (client.currentObject);
2254 var lag = MSG_UNKNOWN;
2259 nick = o.server.me.unicodeName;
2260 lag = (o.server.lag != -1) ? o.server.lag : MSG_UNKNOWN;
2262 client.statusBar["header-url"].setAttribute("value",
2263 client.currentObject.getURL());
2264 client.statusBar["header-url"].setAttribute("href",
2265 client.currentObject.getURL());
2266 client.statusBar["header-url"].setAttribute("name",
2267 client.currentObject.unicodeName);
2270 function updateTitle (obj)
2272 if (!(("currentObject" in client) && client.currentObject) ||
2273 (obj && obj != client.currentObject))
2276 var tstring = MSG_TITLE_UNKNOWN;
2277 var o = getObjectDetails(client.currentObject);
2278 var net = o.network ? o.network.unicodeName : "";
2280 client.statusBar["server-nick"].disabled = false;
2282 switch (client.currentObject.TYPE)
2285 var serv = "", port = "";
2286 if (client.currentObject.isConnected())
2288 serv = o.server.hostname;
2289 port = o.server.port;
2291 nick = o.server.me.unicodeName;
2292 tstring = getMsg(MSG_TITLE_NET_ON, [nick, net, serv, port]);
2296 nick = client.currentObject.INITIAL_NICK;
2297 tstring = getMsg(MSG_TITLE_NET_OFF, [nick, net]);
2302 var chan = "", mode = "", topic = "";
2303 if ("me" in o.parent)
2305 nick = o.parent.me.unicodeName;
2306 if (o.parent.me.canonicalName in client.currentObject.users)
2308 var cuser = client.currentObject.users[o.parent.me.canonicalName];
2309 if (cuser.isFounder)
2311 else if (cuser.isAdmin)
2313 else if (cuser.isOp)
2315 else if (cuser.isHalfOp)
2317 else if (cuser.isVoice)
2323 nick = MSG_TITLE_NONICK;
2325 chan = o.channel.unicodeName;
2326 mode = o.channel.mode.getModeStr();
2328 mode = MSG_TITLE_NO_MODE;
2329 topic = o.channel.topic ? o.channel.topic : MSG_TITLE_NO_TOPIC;
2330 var re = /\x1f|\x02|\x0f|\x16|\x03([0-9]{1,2}(,[0-9]{1,2})?)?/g;
2331 topic = topic.replace(re, "");
2333 tstring = getMsg(MSG_TITLE_CHANNEL, [nick, chan, mode, topic]);
2337 nick = client.currentObject.unicodeName;
2339 if (client.currentObject.name)
2341 source = "<" + client.currentObject.name + "@" +
2342 client.currentObject.host +">";
2344 tstring = getMsg(MSG_TITLE_USER, [nick, source]);
2345 nick = "me" in o.parent ? o.parent.me.unicodeName : MSG_TITLE_NONICK;
2349 nick = client.prefs["nickname"];
2353 client.statusBar["server-nick"].disabled = true;
2354 nick = o.chat.me.unicodeName;
2355 tstring = getMsg(MSG_TITLE_DCCCHAT, o.userName);
2358 case "IRCDCCFileTransfer":
2359 client.statusBar["server-nick"].disabled = true;
2360 nick = o.file.me.unicodeName;
2361 var data = [o.file.progress, o.file.filename, o.userName];
2362 if (o.file.state.dir == 1)
2363 tstring = getMsg(MSG_TITLE_DCCFILE_SEND, data);
2365 tstring = getMsg(MSG_TITLE_DCCFILE_GET, data);
2369 if (0 && !client.uiState["tabstrip"])
2371 var actl = new Array();
2372 for (var i in client.activityList)
2373 actl.push ((client.activityList[i] == "!") ?
2374 (Number(i) + 1) + "!" : (Number(i) + 1));
2375 if (actl.length > 0)
2376 tstring = getMsg(MSG_TITLE_ACTIVITY,
2377 [tstring, actl.join (MSG_COMMASP)]);
2380 document.title = tstring;
2381 client.statusBar["server-nick"].setAttribute("label", nick);
2384 // Where 'right' is orientation, not wrong/right:
2385 function updateUserlistSide(shouldBeLeft)
2387 var listParent = document.getElementById("tabpanels-contents-box");
2388 var isLeft = (listParent.childNodes[0].id == "user-list-box");
2389 if (isLeft == shouldBeLeft)
2391 if (shouldBeLeft) // Move from right to left.
2393 listParent.insertBefore(listParent.childNodes[1], listParent.childNodes[0]);
2394 listParent.insertBefore(listParent.childNodes[2], listParent.childNodes[0]);
2395 listParent.childNodes[1].setAttribute("collapse", "before");
2397 else // Move from left to right.
2399 listParent.appendChild(listParent.childNodes[1]);
2400 listParent.appendChild(listParent.childNodes[0]);
2401 listParent.childNodes[1].setAttribute("collapse", "after");
2403 var userlist = document.getElementById("user-list")
2404 if (client.currentObject && (client.currentObject.TYPE == "IRCChannel"))
2405 userlist.view = client.currentObject.userList;
2408 function multilineInputMode (state)
2410 var multiInput = document.getElementById("multiline-input");
2411 var multiInputBox = document.getElementById("multiline-box");
2412 var singleInput = document.getElementById("input");
2413 var singleInputBox = document.getElementById("singleline-box");
2414 var splitter = document.getElementById("input-splitter");
2415 var iw = document.getElementById("input-widgets");
2418 client._mlMode = state;
2420 if (state) /* turn on multiline input mode */
2423 h = iw.getAttribute ("lastHeight");
2425 iw.setAttribute ("height", h); /* restore the slider position */
2427 singleInputBox.setAttribute ("collapsed", "true");
2428 splitter.setAttribute ("collapsed", "false");
2429 multiInputBox.setAttribute ("collapsed", "false");
2430 // multiInput should have the same direction as singleInput
2431 multiInput.setAttribute("dir", singleInput.getAttribute("dir"));
2432 multiInput.value = (client.input ? client.input.value : "");
2433 client.input = multiInput;
2435 else /* turn off multiline input mode */
2437 h = iw.getAttribute ("height");
2438 iw.setAttribute ("lastHeight", h); /* save the slider position */
2439 iw.removeAttribute ("height"); /* let the slider drop */
2441 splitter.setAttribute ("collapsed", "true");
2442 multiInputBox.setAttribute ("collapsed", "true");
2443 singleInputBox.setAttribute ("collapsed", "false");
2444 // singleInput should have the same direction as multiInput
2445 singleInput.setAttribute("dir", multiInput.getAttribute("dir"));
2446 singleInput.value = (client.input ? client.input.value : "");
2447 client.input = singleInput;
2450 client.input.focus();
2453 function displayCertificateInfo()
2455 var o = getObjectDetails(client.currentObject);
2459 if (!o.server.isSecure)
2461 alert(getMsg(MSG_INSECURE_SERVER, o.server.hostname));
2465 viewCert(o.server.connection.getCertificate());
2468 function newInlineText (data, className, tagName)
2470 if (typeof tagName == "undefined")
2471 tagName = "html:span";
2473 var a = document.createElementNS(XHTML_NS, tagName);
2475 a.setAttribute ("class", className);
2477 switch (typeof data)
2480 a.appendChild (document.createTextNode (data));
2486 a.setAttribute (p, data[p]);
2488 a.appendChild (document.createTextNode (data[p]));
2495 ASSERT(0, "INVALID TYPE ('" + typeof data + "') passed to " +
2505 function stringToMsg (message, obj)
2507 var ary = message.split ("\n");
2508 var span = document.createElementNS(XHTML_NS, "html:span");
2509 var data = getObjectDetails(obj);
2511 if (ary.length == 1)
2512 client.munger.munge(ary[0], span, data);
2515 for (var l = 0; l < ary.length - 1; ++l)
2517 client.munger.munge(ary[l], span, data);
2518 span.appendChild(document.createElementNS(XHTML_NS, "html:br"));
2520 client.munger.munge(ary[l], span, data);
2528 if (client.deck.childNodes.length == 0)
2530 var panel = client.deck.selectedPanel;
2531 return getContentWindow(panel);
2534 client.__defineGetter__ ("currentFrame", getFrame);
2536 function setCurrentObject (obj)
2538 if (!ASSERT(obj.messages, "INVALID OBJECT passed to setCurrentObject **"))
2541 if ("currentObject" in client && client.currentObject == obj)
2543 if (typeof client.pendingViewContext == "object")
2544 dispatch("create-tab-for-view", { view: obj });
2548 // Set window.content to make screenreader apps find the chat content.
2549 if (obj.frame && getContentWindow(obj.frame))
2550 window.content = getContentWindow(obj.frame);
2553 userList = document.getElementById("user-list");
2555 if ("currentObject" in client && client.currentObject)
2556 tb = getTabForObject(client.currentObject);
2558 tb.setAttribute("state", "normal");
2560 client.currentObject = obj;
2563 userList.view = null;
2564 if (obj.TYPE == "IRCChannel")
2566 userList.view = obj.userList;
2570 tb = dispatch("create-tab-for-view", { view: obj });
2573 tb.parentNode.selectedItem = tb;
2574 tb.setAttribute("state", "current");
2577 var vk = Number(tb.getAttribute("viewKey"));
2578 delete client.activityList[vk];
2579 client.deck.selectedPanel = obj.frame;
2581 // Style userlist and the like:
2582 updateAppMotif(obj.prefs["motif.current"]);
2586 updateSecurityIcon();
2588 scrollDown(obj.frame, false);
2590 // Input area should have the same direction as the output area
2591 if (("frame" in client.currentObject) &&
2592 client.currentObject.frame &&
2593 getContentDocument(client.currentObject.frame) &&
2594 ("body" in getContentDocument(client.currentObject.frame)) &&
2595 getContentDocument(client.currentObject.frame).body)
2597 var contentArea = getContentDocument(client.currentObject.frame).body;
2598 client.input.setAttribute("dir", contentArea.getAttribute("dir"));
2600 client.input.focus();
2603 function checkScroll(frame)
2605 var window = getContentWindow(frame);
2606 if (!window || !("document" in window))
2609 return (window.document.height - window.innerHeight -
2610 window.pageYOffset) < 160;
2613 function scrollDown(frame, force)
2615 var window = getContentWindow(frame);
2616 if (window && (force || checkScroll(frame)))
2617 window.scrollTo(0, window.document.height);
2620 function advanceKeyboardFocus(amount)
2622 var contentWin = getContentWindow(client.currentObject.frame);
2623 var contentDoc = getContentDocument(client.currentObject.frame);
2624 var userList = document.getElementById("user-list");
2626 // Focus userlist, inputbox and outputwindow in turn:
2627 var focusableElems = [userList, client.input.inputField, contentWin];
2629 var elem = document.commandDispatcher.focusedElement;
2630 // Finding focus in the content window is "hard". It's going to be null
2631 // if the window itself is focused, and "some element" inside of it if the
2632 // user starts tabbing through.
2633 if (!elem || (elem.ownerDocument == contentDoc))
2636 var newIndex = (arrayIndexOf(focusableElems, elem) * 1 + 3 + amount) % 3;
2637 focusableElems[newIndex].focus();
2639 // Make it obvious this element now has focus.
2641 if (focusableElems[newIndex] == client.input.inputField)
2642 outlinedElem = client.input.parentNode.id;
2643 else if (focusableElems[newIndex] == userList)
2644 outlinedElem = "user-list-box"
2646 outlinedElem = "browser-box";
2648 // Do magic, and make sure it gets undone at the right time:
2649 if (("focusedElemTimeout" in client) && client.focusedElemTimeout)
2650 clearTimeout(client.focusedElemTimeout);
2651 outlineFocusedElem(outlinedElem);
2652 client.focusedElemTimeout = setTimeout(outlineFocusedElem, 1000, "");
2655 function outlineFocusedElem(id)
2657 var outlinedElements = ["user-list-box", "browser-box", "multiline-hug-box",
2658 "singleline-hug-box"];
2659 for (var i = 0; i < outlinedElements.length; i++)
2661 var elem = document.getElementById(outlinedElements[i]);
2662 if (outlinedElements[i] == id)
2663 elem.setAttribute("focusobvious", "true");
2665 elem.removeAttribute("focusobvious");
2667 client.focusedElemTimeout = 0;
2670 /* valid values for |what| are "superfluous", "activity", and "attention".
2671 * final value for state is dependant on priority of the current state, and the
2672 * new state. the priority is: normal < superfluous < activity < attention.
2674 function setTabState(source, what, callback)
2676 if (typeof source != "object")
2678 if (!ASSERT(source in client.viewsArray,
2679 "INVALID SOURCE passed to setTabState"))
2681 source = client.viewsArray[source].source;
2684 callback = callback || false;
2686 var tb = source.dispatch("create-tab-for-view", { view: source });
2687 var vk = Number(tb.getAttribute("viewKey"));
2689 var current = ("currentObject" in client && client.currentObject == source);
2691 /* We want to play sounds if they're from a non-current view, or we don't
2692 * have focus at all. Also make sure stalk matches always play sounds.
2693 * Also make sure we don't play on the 2nd half of the flash (Callback).
2695 if (!callback && (!window.isFocused || !current || (what == "attention")))
2697 if (what == "attention")
2698 playEventSounds(source.TYPE, "stalk");
2699 else if (what == "activity")
2700 playEventSounds(source.TYPE, "chat");
2701 else if (what == "superfluous")
2702 playEventSounds(source.TYPE, "event");
2705 // Only change the tab's colour if it's not the active view.
2708 var state = tb.getAttribute("state");
2711 /* if the tab state has an equal priority to what we are setting
2713 if (client.prefs["activityFlashDelay"] > 0)
2715 tb.setAttribute("state", "normal");
2716 setTimeout(setTabState, client.prefs["activityFlashDelay"],
2722 if (state == "normal" || state == "superfluous" ||
2723 (state == "activity" && what == "attention"))
2725 /* if the tab state has a lower priority than what we are
2726 * setting, change it to the new state */
2727 tb.setAttribute("state", what);
2728 /* we only change the activity list if priority has increased */
2729 if (what == "attention")
2730 client.activityList[vk] = "!";
2731 else if (what == "activity")
2732 client.activityList[vk] = "+";
2735 /* this is functionally equivalent to "+" for now */
2736 client.activityList[vk] = "-";
2742 /* the current state of the tab has a higher priority than the
2744 * blink the new lower state quickly, then back to the old */
2745 if (client.prefs["activityFlashDelay"] > 0)
2747 tb.setAttribute("state", what);
2748 setTimeout(setTabState,
2749 client.prefs["activityFlashDelay"], vk,
2757 function notifyAttention (source)
2759 if (typeof source != "object")
2760 source = client.viewsArray[source].source;
2762 if (client.currentObject != source)
2764 var tb = dispatch("create-tab-for-view", { view: source });
2765 var vk = Number(tb.getAttribute("viewKey"));
2767 tb.setAttribute ("state", "attention");
2768 client.activityList[vk] = "!";
2772 if (client.prefs["notify.aggressive"])
2773 window.getAttention();
2777 function setDebugMode(mode)
2779 if (mode.indexOf("t") != -1)
2780 client.debugHook.enabled = true;
2782 client.debugHook.enabled = false;
2784 if (mode.indexOf("c") != -1)
2785 client.dbgContexts = true;
2787 delete client.dbgContexts;
2789 if (mode.indexOf("d") != -1)
2790 client.dbgDispatch = true;
2792 delete client.dbgDispatch;
2795 function setListMode(mode)
2797 var elem = document.getElementById("user-list");
2799 elem.setAttribute("mode", mode);
2801 elem.removeAttribute("mode");
2802 if (elem && elem.view && elem.treeBoxObject)
2804 elem.treeBoxObject.clearStyleAndImageCaches();
2805 elem.treeBoxObject.invalidate();
2809 function updateUserList()
2813 node = document.getElementById("user-list");
2817 if (("currentObject" in client) && client.currentObject &&
2818 client.currentObject.TYPE == "IRCChannel")
2820 reSortUserlist(client.currentObject);
2824 function reSortUserlist(channel)
2826 if (!channel || !channel.userList)
2828 channel.userList.childData.reSort();
2831 function getNicknameForUserlistRow(index)
2833 // This wouldn't be so hard if APIs didn't change so much... see bug 221619
2834 var userlist = document.getElementById("user-list");
2835 if (userlist.columns)
2836 var col = userlist.columns.getNamedColumn("usercol");
2839 return userlist.view.getCellText(index, col);
2842 function getFrameForDOMWindow(window)
2845 for (var i = 0; i < client.deck.childNodes.length; i++)
2847 frame = client.deck.childNodes[i];
2848 if (getContentWindow(frame) == window)
2854 function replaceColorCodes(msg)
2856 // Find things that look like URLs and escape the color code inside of those
2857 // to prevent munging the URLs resulting in broken links. Leave codes at
2858 // the start of the URL alone.
2859 msg = msg.replace(new RegExp(client.linkRE.source, "g"), function(url) {
2860 return url.replace(/%[BC][0-9A-Fa-f]/g, function(hex, index) {
2861 // as JS does not support lookbehind and we don't want to consume the
2862 // preceding character, we test for an existing %% manually
2863 var needPercent = ("%" == url.substr(index - 1, 1)) || (index == 0);
2864 return (needPercent ? "" : "%") + hex;
2868 // mIRC codes: underline, bold, Original (reset), colors, reverse colors.
2869 msg = msg.replace(/(^|[^%])%U/g, "$1\x1f");
2870 msg = msg.replace(/(^|[^%])%B/g, "$1\x02");
2871 msg = msg.replace(/(^|[^%])%O/g, "$1\x0f");
2872 msg = msg.replace(/(^|[^%])%C/g, "$1\x03");
2873 msg = msg.replace(/(^|[^%])%R/g, "$1\x16");
2874 // %%[UBOCR] --> %[UBOCR].
2875 msg = msg.replace(/%(%[UBOCR])/g, "$1");
2880 function decodeColorCodes(msg)
2882 // %[UBOCR] --> %%[UBOCR].
2883 msg = msg.replace(/(%[UBOCR])/g, "%$1");
2884 // Put %-codes back in place of special character codes.
2885 msg = msg.replace(/\x1f/g, "%U");
2886 msg = msg.replace(/\x02/g, "%B");
2887 msg = msg.replace(/\x0f/g, "%O");
2888 msg = msg.replace(/\x03/g, "%C");
2889 msg = msg.replace(/\x16/g, "%R");
2894 function removeColorCodes(msg)
2896 msg = msg.replace(/[\x1f\x02\x0f\x16]/g, "");
2897 // We need this to be global:
2898 msg = msg.replace(new RegExp(client.colorRE.source, "g"), "");
2902 client.progressListener = new Object();
2904 client.progressListener.QueryInterface =
2910 client.progressListener.onStateChange =
2911 function client_statechange (webProgress, request, stateFlags, status)
2913 const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
2914 const START = nsIWebProgressListener.STATE_START;
2915 const STOP = nsIWebProgressListener.STATE_STOP;
2916 const IS_NETWORK = nsIWebProgressListener.STATE_IS_NETWORK;
2917 const IS_DOCUMENT = nsIWebProgressListener.STATE_IS_DOCUMENT;
2921 // We only care about the initial start of loading, not the loading of
2922 // and page sub-components (IS_DOCUMENT, etc.).
2923 if ((stateFlags & START) && (stateFlags & IS_NETWORK) &&
2924 (stateFlags & IS_DOCUMENT))
2926 frame = getFrameForDOMWindow(webProgress.DOMWindow);
2929 dd("can't find frame for window (start)");
2932 webProgress.removeProgressListener(this);
2936 dd("Exception removing progress listener (start): " + ex);
2940 // We only want to know when the *network* stops, not the page's
2941 // individual components (STATE_IS_REQUEST/STATE_IS_DOCUMENT/somesuch).
2942 else if ((stateFlags & STOP) && (stateFlags & IS_NETWORK))
2944 frame = getFrameForDOMWindow(webProgress.DOMWindow);
2947 dd("can't find frame for window (stop)");
2950 webProgress.removeProgressListener(this);
2954 dd("Exception removing progress listener (stop): " + ex);
2959 var cwin = getContentWindow(frame);
2960 if (cwin && "initOutputWindow" in cwin)
2962 cwin.getMsg = getMsg;
2963 cwin.initOutputWindow(client, frame.source, onMessageViewClick);
2964 cwin.changeCSS(frame.source.getFontCSS("data"), "cz-fonts");
2965 scrollDown(frame, true);
2969 webProgress.removeProgressListener(this);
2973 dd("Exception removing progress listener (done): " + ex);
2976 // XXX: For about:blank it won't find initOutputWindow. Cope.
2977 else if (!cwin || !cwin.location ||
2978 (cwin.location.href != "about:blank"))
2980 // This should totally never ever happen. It will if we get in a
2981 // fight with xpcnativewrappers, though. Oops:
2982 dd("Couldn't find a content window or its initOutputWindow " +
2983 "function. This is BAD!");
2989 client.progressListener.onProgressChange =
2990 function client_progresschange (webProgress, request, currentSelf, totalSelf,
2991 currentMax, selfMax)
2995 client.progressListener.onLocationChange =
2996 function client_locationchange (webProgress, request, uri)
3000 client.progressListener.onStatusChange =
3001 function client_statuschange (webProgress, request, status, message)
3005 client.progressListener.onSecurityChange =
3006 function client_securitychange (webProgress, request, state)
3010 function syncOutputFrame(obj, nesting)
3012 const nsIWebProgress = Components.interfaces.nsIWebProgress;
3013 const WINDOW = nsIWebProgress.NOTIFY_STATE_WINDOW;
3014 const NETWORK = nsIWebProgress.NOTIFY_STATE_NETWORK;
3015 const ALL = nsIWebProgress.NOTIFY_ALL;
3017 var iframe = obj.frame;
3019 function tryAgain(nLevel)
3021 syncOutputFrame(obj, nLevel);
3024 if (typeof nesting != "number")
3029 dd("Aborting syncOutputFrame, taken too many tries.");
3035 if (getContentDocument(iframe) && ("webProgress" in iframe))
3037 var url = obj.prefs["outputWindowURL"];
3038 iframe.addProgressListener(client.progressListener, ALL);
3039 iframe.loadURI(url);
3043 setTimeout(tryAgain, 500, nesting + 1);
3048 dd("caught exception showing session view, will try again later.");
3049 dd(dumpObjectTree(ex) + "\n");
3050 setTimeout(tryAgain, 500, nesting + 1);
3054 function createMessages(source)
3056 playEventSounds(source.TYPE, "start");
3058 source.messages = document.createElementNS(XHTML_NS, "html:table");
3059 source.messages.setAttribute("class", "msg-table");
3060 source.messages.setAttribute("view-type", source.TYPE);
3061 source.messages.setAttribute("role", "log");
3062 source.messages.setAttribute("aria-live", "polite");
3064 var tbody = document.createElementNS(XHTML_NS, "html:tbody");
3065 source.messages.appendChild (tbody);
3066 source.messageCount = 0;
3069 /* Gets the <tab> element associated with a view object.
3070 * If |create| is present, and true, tab is created if not found.
3072 function getTabForObject(source, create)
3076 if (!ASSERT(source, "UNDEFINED passed to getTabForObject"))
3079 if (!ASSERT("viewName" in source, "INVALID OBJECT in getTabForObject"))
3082 name = source.viewName;
3084 var tb, id = "tb[" + name + "]";
3087 for (var i in client.viewsArray)
3089 if (client.viewsArray[i].source == source)
3091 tb = client.viewsArray[i].tb;
3095 if (client.viewsArray[i].tb.getAttribute("id") == id)
3096 id = "tb[" + name + "<" + (++matches) + ">]";
3099 /* If we found a <tab>, are allowed to create it, and have a pending view
3100 * context, then we're expected to move the existing tab according to said
3101 * context. We do that by removing the tab here, and below the creation
3102 * code (which is not run) we readd it in the correct location.
3104 if (tb && create && (typeof client.pendingViewContext == "object"))
3106 /* If we're supposed to insert before ourselves, or the next <tab>,
3107 * then bail out (since it's a no-op).
3109 var tabBefore = client.pendingViewContext.tabInsertBefore;
3112 var tbAfter = tb.nextSibling;
3113 while (tbAfter && tbAfter.collapsed && tbAfter.hidden)
3114 tbAfter = tbAfter.nextSibling;
3115 if ((tabBefore == tb) || (tabBefore == tbAfter))
3119 var viewKey = Number(tb.getAttribute("viewKey"));
3120 arrayRemoveAt(client.viewsArray, viewKey);
3121 for (i = viewKey; i < client.viewsArray.length; i++)
3122 client.viewsArray[i].tb.setAttribute("viewKey", i);
3123 client.tabs.removeChild(tb);
3125 else if (tb || (!tb && !create))
3127 /* Either: we have a tab and don't want it moved, or didn't find one
3128 * and don't wish to create one.
3133 // Didn't found a <tab>, but we're allowed to create one.
3136 if (!("messages" in source) || source.messages == null)
3137 createMessages(source);
3139 tb = document.createElement("tab");
3140 tb.setAttribute("ondraggesture",
3141 "nsDragAndDrop.startDrag(event, tabDNDObserver);");
3142 tb.setAttribute("href", source.getURL());
3143 tb.setAttribute("name", source.unicodeName);
3144 tb.setAttribute("onclick", "onTabClick(event, " + id.quote() + ");");
3145 // This wouldn't be here if there was a supported CSS property for it.
3146 tb.setAttribute("crop", "center");
3147 tb.setAttribute("context", "context:tab");
3148 tb.setAttribute("tooltip", "xul-tooltip-node");
3149 tb.setAttribute("class", "tab-bottom view-button");
3150 tb.setAttribute("id", id);
3151 tb.setAttribute("state", "normal");
3152 tb.setAttribute("label", name + (matches > 1 ? "<" + matches + ">" : ""));
3155 var browser = document.createElement("browser");
3156 browser.setAttribute("class", "output-container");
3157 browser.setAttribute("type", "content");
3158 browser.setAttribute("flex", "1");
3159 browser.setAttribute("tooltip", "html-tooltip-node");
3160 browser.setAttribute("context", "context:messages");
3161 //browser.setAttribute("onload", "scrollDown(true);");
3162 browser.setAttribute("onclick",
3163 "return onMessageViewClick(event)");
3164 browser.setAttribute("onmousedown",
3165 "return onMessageViewMouseDown(event)");
3166 browser.setAttribute("ondragover",
3167 "nsDragAndDrop.dragOver(event, " +
3168 "contentDropObserver);");
3169 browser.setAttribute("ondragdrop",
3170 "nsDragAndDrop.drop(event, contentDropObserver);");
3171 browser.setAttribute("ondraggesture",
3172 "nsDragAndDrop.startDrag(event, " +
3173 "contentAreaDNDObserver);");
3174 browser.source = source;
3175 source.frame = browser;
3176 ASSERT(client.deck, "no deck?");
3177 client.deck.appendChild(browser);
3178 syncOutputFrame(source);
3180 if (!("userList" in source) && (source.TYPE == "IRCChannel"))
3182 source.userListShare = new Object();
3183 source.userList = new XULTreeView(source.userListShare);
3184 source.userList.getCellProperties = ul_getcellprops;
3185 source.userList.childData.setSortDirection(1);
3189 var beforeTab = null;
3190 if (typeof client.pendingViewContext == "object")
3192 var c = client.pendingViewContext;
3193 /* If we have a <tab> to insert before, and it is still in the tabs,
3194 * move the newly-created <tab> into the right place.
3196 if (c.tabInsertBefore && (c.tabInsertBefore.parentNode == client.tabs))
3197 beforeTab = c.tabInsertBefore;
3202 var viewKey = beforeTab.getAttribute("viewKey");
3203 arrayInsertAt(client.viewsArray, viewKey, {source: source, tb: tb});
3204 for (i = viewKey; i < client.viewsArray.length; i++)
3205 client.viewsArray[i].tb.setAttribute("viewKey", i);
3206 client.tabs.insertBefore(tb, beforeTab);
3210 client.viewsArray.push({source: source, tb: tb});
3211 tb.setAttribute("viewKey", client.viewsArray.length - 1);
3212 client.tabs.appendChild(tb);
3215 updateTabAttributes();
3220 function updateTabAttributes()
3222 /* XXX: Workaround for Gecko bugs 272646 and 261826. Note that this breaks
3223 * the location of the spacers before and after the tabs but, due to our
3224 * own <spacer>, their flex was not being utilised anyway.
3227 for (var tab = client.tabs.firstChild; tab; tab = tab.nextSibling)
3228 tab.ordinal = tabOrdinal++;
3230 /* XXX: Workaround for tabbox.xml not coping with updating attributes when
3231 * tabs are moved. We correct the "first-tab", "last-tab", "beforeselected"
3232 * and "afterselected" attributes.
3234 * "last-tab" and "beforeselected" are updated on each valid (non-collapsed
3235 * and non-hidden) tab found, to avoid having to work backwards as well as
3236 * forwards. "first-tab" and "afterselected" are just set the once each.
3237 * |foundSelected| tracks where we are in relation to the selected tab.
3242 "beforeselected": null,
3243 "afterselected": null
3245 var foundSelected = "before";
3246 for (tab = client.tabs.firstChild; tab; tab = tab.nextSibling)
3248 if (tab.collapsed || tab.hidden)
3251 if (!tabAttrs["first-tab"])
3252 tabAttrs["first-tab"] = tab;
3253 tabAttrs["last-tab"] = tab;
3255 if ((foundSelected == "before") && tab.selected)
3256 foundSelected = "on";
3257 else if (foundSelected == "on")
3258 foundSelected = "after";
3260 if (foundSelected == "before")
3261 tabAttrs["beforeselected"] = tab;
3262 if ((foundSelected == "after") && !tabAttrs["afterselected"])
3263 tabAttrs["afterselected"] = tab;
3266 // After picking a tab for each attribute, apply them to the tabs.
3267 for (tab = client.tabs.firstChild; tab; tab = tab.nextSibling)
3269 for (var attr in tabAttrs)
3271 if (tabAttrs[attr] == tab)
3272 tab.setAttribute(attr, "true");
3274 tab.removeAttribute(attr);
3279 // Properties getter for user list tree view
3280 function ul_getcellprops(index, column, properties)
3282 if ((index < 0) || (index >= this.childData.childData.length) ||
3288 var userObj = this.childData.childData[index]._userObj;
3290 properties.AppendElement(client.atomSvc.getAtom("voice-" + userObj.isVoice));
3291 properties.AppendElement(client.atomSvc.getAtom("op-" + userObj.isOp));
3292 properties.AppendElement(client.atomSvc.getAtom("halfop-" + userObj.isHalfOp));
3293 properties.AppendElement(client.atomSvc.getAtom("admin-" + userObj.isAdmin));
3294 properties.AppendElement(client.atomSvc.getAtom("founder-" + userObj.isFounder));
3295 properties.AppendElement(client.atomSvc.getAtom("away-" + userObj.isAway));
3298 var contentDropObserver = new Object();
3300 contentDropObserver.onDragOver =
3301 function cdnd_dover(aEvent, aFlavour, aDragSession)
3303 if (aEvent.getPreventDefault())
3306 if (aDragSession.sourceDocument == aEvent.view.document)
3308 aDragSession.canDrop = false;
3313 contentDropObserver.onDrop =
3314 function cdnd_drop(aEvent, aXferData, aDragSession)
3316 var url = transferUtils.retrieveURLFromData(aXferData.data,
3317 aXferData.flavour.contentType);
3318 if (!url || url.search(client.linkRE) == -1)
3321 if (url.search(/\.css$/i) != -1 && confirm(getMsg(MSG_TABDND_DROP, url)))
3322 dispatch("motif", {"motif": url});
3323 else if (url.search(/^ircs?:\/\//i) != -1)
3324 dispatch("goto-url", {"url": url});
3327 contentDropObserver.getSupportedFlavours =
3330 var flavourSet = new FlavourSet();
3331 flavourSet.appendFlavour("text/x-moz-url");
3332 flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
3333 flavourSet.appendFlavour("text/unicode");
3337 /* Drag and Drop handler for the <tabs> element.
3339 * XXX: Some of the code below has to work around specific limitations in how
3340 * the nsDragAndDrop.js wrapper works. The wrapper greatly simplifies the DnD
3341 * code, though, so it's still worth using.
3343 * XXX: canDrop checks if there is a supported flavour of data because
3344 * nsDragAndDrop does not. This will prevent the drag service from thinking
3345 * we accept any old data when we don't.
3347 * XXX: nsDragAndDrop.checkCanDrop does this:
3348 * mDragSession.canDrop = mDragSession.sourceNode != aEvent.target;
3349 * mDragSession.canDrop &= aDragDropObserver.canDrop(...);
3350 * As a result, canDrop cannot override the false canDrop value when the source
3351 * and target are the same (i.e. the same <tab>). Thus, we override this check
3352 * inside onDragOver instead, which is called after canDrop (even if that says
3353 * it can't be dropped, luckily). As a result, after nsDragAndDrop has called
3354 * canDrop and onDragOver, the drag service's canDrop value is true iff there
3355 * is a supported flavour.
3357 * XXX: onDrop is the only place which checks we're getting an IRC URL, as
3358 * accessing the drag data at any other time is both tedious and could
3359 * significantly impact the performance of the drag (getting the data can be
3362 var tabsDropObserver = new Object();
3364 tabsDropObserver.canDrop =
3365 function tabdnd_candrop(aEvent, aDragSession)
3367 if (aEvent.getPreventDefault())
3370 // See comment above |var tabsDropObserver|.
3371 var flavourSet = this.getSupportedFlavours();
3372 for (var flavour in flavourSet.flavourTable)
3374 if (aDragSession.isDataFlavorSupported(flavour))
3380 tabsDropObserver.onDragOver =
3381 function tabdnd_dover(aEvent, aFlavour, aDragSession)
3383 if (aEvent.getPreventDefault())
3386 // See comment above |var tabsDropObserver|.
3387 if (aDragSession.sourceNode == aEvent.target)
3388 aDragSession.canDrop = true;
3390 // If we're not accepting the drag, don't show the marker either.
3391 if (!aDragSession.canDrop)
3393 client.tabDragBar.collapsed = true;
3397 /* Locate the tab we're about to drop onto. We split tabs in half, dropping
3398 * on the side closest to the mouse, or after the last tab if the mouse is
3399 * somewhere beyond all the tabs.
3401 var ltr = (window.getComputedStyle(client.tabs, null).direction == "ltr");
3402 var newPosition = client.tabs.firstChild.boxObject.x;
3403 for (var dropTab = client.tabs.firstChild; dropTab;
3404 dropTab = dropTab.nextSibling)
3406 if (dropTab.collapsed || dropTab.hidden)
3408 var bo = dropTab.boxObject;
3409 if ((ltr && (aEvent.screenX < bo.screenX + bo.width / 2)) ||
3410 (!ltr && (aEvent.screenX > bo.screenX + bo.width / 2)))
3414 newPosition = bo.x + bo.width;
3417 // Reposition the drop marker and show it. In that order.
3418 client.tabDragMarker.style.MozMarginStart = newPosition + "px";
3419 client.tabDragBar.collapsed = false;
3422 tabsDropObserver.onDragExit =
3423 function tabdnd_dexit(aEvent, aDragSession)
3425 if (aEvent.getPreventDefault())
3428 /* We've either stopped being part of a drag operation, or the dragging is
3429 * somewhere away from us.
3431 client.tabDragBar.collapsed = true;
3434 tabsDropObserver.onDrop =
3435 function tabdnd_drop(aEvent, aXferData, aDragSession)
3437 // See comment above |var tabsDropObserver|.
3438 var url = transferUtils.retrieveURLFromData(aXferData.data,
3439 aXferData.flavour.contentType);
3440 if (!url || !url.match(/^ircs?:/))
3443 // Find the tab to insertBefore() the new one.
3444 var ltr = (window.getComputedStyle(client.tabs, null).direction == "ltr");
3445 for (var dropTab = client.tabs.firstChild; dropTab;
3446 dropTab = dropTab.nextSibling)
3448 if (dropTab.collapsed || dropTab.hidden)
3450 var bo = dropTab.boxObject;
3451 if ((ltr && (aEvent.screenX < bo.screenX + bo.width / 2)) ||
3452 (!ltr && (aEvent.screenX > bo.screenX + bo.width / 2)))
3458 // Check if the URL is already in the views.
3459 for (var i = 0; i < client.viewsArray.length; i++)
3461 var view = client.viewsArray[i].source;
3462 if (view.getURL() == url)
3464 client.pendingViewContext = { tabInsertBefore: dropTab };
3465 dispatch("create-tab-for-view", { view: view });
3466 delete client.pendingViewContext;
3471 // URL not found in tabs, so force it into life - this may connect/rejoin.
3472 gotoIRCURL(url, { tabInsertBefore: dropTab });
3475 tabsDropObserver.getSupportedFlavours =
3476 function tabdnd_gsf()
3478 var flavourSet = new FlavourSet();
3479 flavourSet.appendFlavour("text/x-moz-url");
3480 flavourSet.appendFlavour("text/unicode");
3484 var tabDNDObserver = new Object();
3486 tabDNDObserver.onDragStart =
3487 function tabdnd_dstart (aEvent, aXferData, aDragAction)
3489 var tb = aEvent.currentTarget;
3490 var href = tb.getAttribute("href");
3491 var name = tb.getAttribute("name");
3493 aXferData.data = new TransferData();
3494 /* x-moz-url has the format "<url>\n<name>", goodie */
3495 aXferData.data.addDataForFlavour("text/x-moz-url", href + "\n" + name);
3496 aXferData.data.addDataForFlavour("text/unicode", href);
3497 aXferData.data.addDataForFlavour("text/html", "<a href='" + href + "'>" +
3501 var userlistDNDObserver = new Object();
3503 userlistDNDObserver.onDragStart =
3504 function userlistdnd_dstart(event, transferData, dragAction)
3506 var col = new Object(), row = new Object(), cell = new Object();
3507 var tree = document.getElementById('user-list');
3508 tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, cell);
3509 // Check whether we're actually on a normal row and cell
3510 if (!cell.value || (row.value == -1))
3513 var nickname = getNicknameForUserlistRow(row.value);
3514 transferData.data = new TransferData();
3515 transferData.data.addDataForFlavour("text/unicode", nickname);
3518 function deleteTab(tb)
3520 if (!ASSERT(tb.hasAttribute("viewKey"),
3521 "INVALID OBJECT passed to deleteTab (" + tb + ")"))
3526 var key = Number(tb.getAttribute("viewKey"));
3528 // Re-index higher tabs.
3529 for (var i = key + 1; i < client.viewsArray.length; i++)
3530 client.viewsArray[i].tb.setAttribute("viewKey", i - 1);
3531 arrayRemoveAt(client.viewsArray, key);
3532 client.tabs.removeChild(tb);
3533 updateTabAttributes();
3538 function filterOutput(msg, msgtype, dest)
3540 if ("outputFilters" in client)
3542 for (var f in client.outputFilters)
3544 if (client.outputFilters[f].enabled)
3545 msg = client.outputFilters[f].func(msg, msgtype, dest);
3552 function updateTimestamps(view)
3554 if (!("messages" in view))
3557 view._timestampLast = "";
3558 var node = view.messages.firstChild.firstChild;
3562 if(node.className == "msg-nested-tr")
3564 nested = node.firstChild.firstChild.firstChild.firstChild;
3567 updateTimestampFor(view, nested);
3568 nested = nested.nextSibling;
3573 updateTimestampFor(view, node);
3575 node = node.nextSibling;
3579 function updateTimestampFor(view, displayRow)
3581 var time = new Date(1 * displayRow.getAttribute("timestamp"));
3582 var tsCell = displayRow.firstChild;
3587 if (view.prefs["timestamps"])
3588 fmt = strftime(view.prefs["timestamps.display"], time);
3590 while (tsCell.lastChild)
3591 tsCell.removeChild(tsCell.lastChild);
3593 if (fmt && (!client.prefs["collapseMsgs"] || (fmt != view._timestampLast)))
3594 tsCell.appendChild(document.createTextNode(fmt));
3595 view._timestampLast = fmt;
3598 client.updateMenus =
3599 function c_updatemenus(menus)
3601 // Don't bother if the menus aren't even created yet.
3602 if (!client.initialized)
3605 return this.menuManager.updateMenus(document, menus);
3609 function cli_adoptnode(node, doc)
3613 doc.adoptNode(node);
3617 dd(formatException(ex));
3619 // TypeError from before adoptNode was added; NOT_IMPL after.
3620 if ((err == "TypeError") || (err == "NS_ERROR_NOT_IMPLEMENTED"))
3621 client.adoptNode = cli_adoptnode_noop;
3626 function cli_adoptnode_noop(node, doc)
3632 function cli_addnet(name, serverList, temporary)
3634 client.networks[name] =
3635 new CIRCNetwork(name, serverList, client.eventPump, temporary);
3638 client.removeNetwork =
3639 function cli_removenet(name)
3641 // Allow network a chance to clean up any mess.
3642 if (typeof client.networks[name].destroy == "function")
3643 client.networks[name].destroy();
3645 delete client.networks[name];
3648 client.connectToNetwork =
3649 function cli_connect(networkOrName, requireSecurity)
3655 if (isinstance(networkOrName, CIRCNetwork))
3657 network = networkOrName;
3661 name = networkOrName;
3663 if (!(name in client.networks))
3665 display(getMsg(MSG_ERR_UNKNOWN_NETWORK, name), MT_ERROR);
3669 network = client.networks[name];
3671 name = network.unicodeName;
3673 if (!("messages" in network))
3674 network.displayHere(getMsg(MSG_NETWORK_OPENED, name));
3676 dispatch("set-current-view", { view: network });
3678 if (network.isConnected())
3680 network.display(getMsg(MSG_ALREADY_CONNECTED, name));
3684 if (network.state != NET_OFFLINE)
3687 if (network.prefs["nickname"] == DEFAULT_NICK)
3688 network.prefs["nickname"] = prompt(MSG_ENTER_NICK, DEFAULT_NICK);
3690 if (!("connecting" in network))
3691 network.display(getMsg(MSG_NETWORK_CONNECTING, name));
3693 network.connect(requireSecurity);
3695 network.updateHeader();
3696 client.updateHeader();
3704 function cli_geturl ()
3710 function cli_load(url, scope)
3712 if (!("_loader" in client))
3714 const LOADER_CTRID = "@mozilla.org/moz/jssubscript-loader;1";
3715 const mozIJSSubScriptLoader =
3716 Components.interfaces.mozIJSSubScriptLoader;
3719 if ((cls = Components.classes[LOADER_CTRID]))
3720 client._loader = cls.getService(mozIJSSubScriptLoader);
3723 return client._loader.loadSubScript(url, scope);
3726 client.sayToCurrentTarget =
3727 function cli_say(msg, isInteractive)
3729 if ("say" in client.currentObject)
3731 client.currentObject.dispatch("say", {message: msg}, isInteractive);
3735 switch (client.currentObject.TYPE)
3738 dispatch("eval", {expression: msg}, isInteractive);
3743 display(MSG_ERR_NO_DEFAULT, MT_ERROR);
3748 CIRCNetwork.prototype.__defineGetter__("prefs", net_getprefs);
3749 function net_getprefs()
3751 if (!("_prefs" in this))
3753 this._prefManager = getNetworkPrefManager(this);
3754 this._prefs = this._prefManager.prefs;
3760 CIRCNetwork.prototype.__defineGetter__("prefManager", net_getprefmgr);
3761 function net_getprefmgr()
3763 if (!("_prefManager" in this))
3765 this._prefManager = getNetworkPrefManager(this);
3766 this._prefs = this._prefManager.prefs;
3769 return this._prefManager;
3772 CIRCServer.prototype.__defineGetter__("prefs", srv_getprefs);
3773 function srv_getprefs()
3775 return this.parent.prefs;
3778 CIRCServer.prototype.__defineGetter__("prefManager", srv_getprefmgr);
3779 function srv_getprefmgr()
3781 return this.parent.prefManager;
3784 CIRCChannel.prototype.__defineGetter__("prefs", chan_getprefs);
3785 function chan_getprefs()
3787 if (!("_prefs" in this))
3789 this._prefManager = getChannelPrefManager(this);
3790 this._prefs = this._prefManager.prefs;
3796 CIRCChannel.prototype.__defineGetter__("prefManager", chan_getprefmgr);
3797 function chan_getprefmgr()
3799 if (!("_prefManager" in this))
3801 this._prefManager = getChannelPrefManager(this);
3802 this._prefs = this._prefManager.prefs;
3805 return this._prefManager;
3808 CIRCUser.prototype.__defineGetter__("prefs", usr_getprefs);
3809 function usr_getprefs()
3811 if (!("_prefs" in this))
3813 this._prefManager = getUserPrefManager(this);
3814 this._prefs = this._prefManager.prefs;
3820 CIRCUser.prototype.__defineGetter__("prefManager", usr_getprefmgr);
3821 function usr_getprefmgr()
3823 if (!("_prefManager" in this))
3825 this._prefManager = getUserPrefManager(this);
3826 this._prefs = this._prefManager.prefs;
3829 return this._prefManager;
3832 CIRCDCCUser.prototype.__defineGetter__("prefs", dccusr_getprefs);
3833 function dccusr_getprefs()
3835 if (!("_prefs" in this))
3837 this._prefManager = getDCCUserPrefManager(this);
3838 this._prefs = this._prefManager.prefs;
3844 CIRCDCCUser.prototype.__defineGetter__("prefManager", dccusr_getprefmgr);
3845 function dccusr_getprefmgr()
3847 if (!("_prefManager" in this))
3849 this._prefManager = getDCCUserPrefManager(this);
3850 this._prefs = this._prefManager.prefs;
3853 return this._prefManager;
3856 CIRCDCCChat.prototype.__defineGetter__("prefs", dccchat_getprefs);
3857 function dccchat_getprefs()
3859 return this.user.prefs;
3862 CIRCDCCChat.prototype.__defineGetter__("prefManager", dccchat_getprefmgr);
3863 function dccchat_getprefmgr()
3865 return this.user.prefManager;
3868 CIRCDCCFileTransfer.prototype.__defineGetter__("prefs", dccfile_getprefs);
3869 function dccfile_getprefs()
3871 return this.user.prefs;
3874 CIRCDCCFileTransfer.prototype.__defineGetter__("prefManager", dccfile_getprefmgr);
3875 function dccfile_getprefmgr()
3877 return this.user.prefManager;
3880 CIRCNetwork.prototype.display =
3881 function net_display (message, msgtype, sourceObj, destObj)
3883 var o = getObjectDetails(client.currentObject);
3885 if (client.SLOPPY_NETWORKS && client.currentObject != this &&
3886 o.network == this && o.server && o.server.isConnected)
3888 client.currentObject.display (message, msgtype, sourceObj, destObj);
3892 this.displayHere (message, msgtype, sourceObj, destObj);
3896 CIRCUser.prototype.display =
3897 function usr_display(message, msgtype, sourceObj, destObj)
3899 if ("messages" in this)
3901 this.displayHere (message, msgtype, sourceObj, destObj);
3905 var o = getObjectDetails(client.currentObject);
3906 if (o.server && o.server.isConnected &&
3907 o.network == this.parent.parent &&
3908 client.currentObject.TYPE != "IRCUser")
3909 client.currentObject.display (message, msgtype, sourceObj, destObj);
3911 this.parent.parent.displayHere (message, msgtype, sourceObj,
3916 CIRCDCCChat.prototype.display =
3917 CIRCDCCFileTransfer.prototype.display =
3918 function dcc_display(message, msgtype, sourceObj, destObj)
3920 var o = getObjectDetails(client.currentObject);
3922 if ("messages" in this)
3923 this.displayHere(message, msgtype, sourceObj, destObj);
3925 client.currentObject.display(message, msgtype, sourceObj, destObj);
3928 function feedback(e, message, msgtype, sourceObj, destObj)
3930 if ("isInteractive" in e && e.isInteractive)
3931 display(message, msgtype, sourceObj, destObj);
3934 CIRCChannel.prototype.feedback =
3935 CIRCNetwork.prototype.feedback =
3936 CIRCUser.prototype.feedback =
3937 CIRCDCCChat.prototype.feedback =
3938 CIRCDCCFileTransfer.prototype.feedback =
3940 function this_feedback(e, message, msgtype, sourceObj, destObj)
3942 if ("isInteractive" in e && e.isInteractive)
3943 this.displayHere(message, msgtype, sourceObj, destObj);
3946 function display (message, msgtype, sourceObj, destObj)
3948 client.currentObject.display (message, msgtype, sourceObj, destObj);
3952 CIRCNetwork.prototype.getFontCSS =
3953 CIRCChannel.prototype.getFontCSS =
3954 CIRCUser.prototype.getFontCSS =
3955 CIRCDCCChat.prototype.getFontCSS =
3956 CIRCDCCFileTransfer.prototype.getFontCSS =
3957 function this_getFontCSS(format)
3959 /* Wow, this is cool. We just put together a CSS-rule string based on the
3960 * font preferences. *This* is what CSS is all about. :)
3961 * We also provide a "data: URL" format, to simplify other code.
3967 if (this.prefs["font.family"] != "default")
3968 fn = "font-family: " + this.prefs["font.family"] + ";";
3970 fn = "font-family: inherit;";
3971 if (this.prefs["font.size"] != 0)
3972 fs = "font-size: " + this.prefs["font.size"] + "pt;";
3974 fs = "font-size: medium;";
3976 css = "body.chatzilla-body { " + fs + fn + " }";
3978 if (format == "data")
3979 return "data:text/css," + encodeURIComponent(css);
3984 client.displayHere =
3985 CIRCNetwork.prototype.displayHere =
3986 CIRCChannel.prototype.display =
3987 CIRCChannel.prototype.displayHere =
3988 CIRCUser.prototype.displayHere =
3989 CIRCDCCChat.prototype.displayHere =
3990 CIRCDCCFileTransfer.prototype.displayHere =
3991 function __display(message, msgtype, sourceObj, destObj)
3993 // We need a message type, assume "INFO".
3998 if (msgtype.indexOf("/") != -1)
4000 var ary = msgtype.match(/^(.*)\/(.*)$/);
4005 var blockLevel = false; /* true if this row should be rendered at block
4006 * level, (like, if it has a really long nickname
4007 * that might disturb the rest of the layout) */
4008 var o = getObjectDetails(this); /* get the skinny on |this| */
4010 // Get the 'me' object, so we can be sure to get the attributes right.
4014 else if (o.server && "me" in o.server)
4017 // Let callers get away with "ME!" and we have to substitute here.
4018 if (sourceObj == "ME!")
4020 if (destObj == "ME!")
4023 // Get the TYPE of the source object.
4024 var fromType = (sourceObj && sourceObj.TYPE) ? sourceObj.TYPE : "unk";
4025 // Is the source a user?
4026 var fromUser = (fromType.search(/IRC.*User/) != -1);
4027 // Get some sort of "name" for the source.
4031 if ("unicodeName" in sourceObj)
4032 fromAttr = sourceObj.unicodeName;
4033 else if ("name" in sourceObj)
4034 fromAttr = sourceObj.name;
4036 fromAttr = sourceObj.viewName;
4038 // Attach "ME!" if appropriate, so motifs can style differently.
4039 if (sourceObj == me)
4040 fromAttr = fromAttr + " ME!";
4042 // Get the dest TYPE too...
4043 var toType = (destObj) ? destObj.TYPE : "unk";
4044 // Is the dest a user?
4045 var toUser = (toType.search(/IRC.*User/) != -1);
4046 // Get a dest name too...
4050 if ("unicodeName" in destObj)
4051 toAttr = destObj.unicodeName;
4052 else if ("name" in destObj)
4053 toAttr = destObj.name;
4055 toAttr = destObj.viewName;
4057 // Also do "ME!" work for the dest.
4058 if (destObj && destObj == me)
4059 toAttr = me.unicodeName + " ME!";
4061 /* isImportant means to style the messages as important, and flash the
4062 * window, getAttention means just flash the window. */
4063 var isImportant = false, getAttention = false, isSuperfluous = false;
4064 var viewType = this.TYPE;
4066 var time = new Date();
4068 var timeStamp = strftime(this.prefs["timestamps.log"], time);
4070 // Statusbar text, and the line that gets saved to the log.
4072 var logStringPfx = timeStamp + " ";
4073 var logStrings = new Array();
4077 statusString = getMsg(MSG_FMT_STATUS,
4079 sourceObj.unicodeName + "!" +
4080 sourceObj.name + "@" + sourceObj.host]);
4086 name = sourceObj.viewName;
4088 name = this.viewName;
4090 statusString = getMsg(MSG_FMT_STATUS,
4094 // The table row, and it's attributes.
4095 var msgRow = document.createElementNS(XHTML_NS, "html:tr");
4096 msgRow.setAttribute("class", "msg");
4097 msgRow.setAttribute("msg-type", msgtype);
4098 msgRow.setAttribute("msg-prefix", msgprefix);
4099 msgRow.setAttribute("msg-dest", toAttr);
4100 msgRow.setAttribute("dest-type", toType);
4101 msgRow.setAttribute("view-type", viewType);
4102 msgRow.setAttribute("statusText", statusString);
4103 msgRow.setAttribute("timestamp", Number(time));
4107 msgRow.setAttribute("msg-user", fromAttr);
4109 msgRow.setAttribute("msg-source", fromAttr);
4113 var msgRowTimestamp = document.createElementNS(XHTML_NS, "html:td");
4114 msgRowTimestamp.setAttribute("class", "msg-timestamp");
4117 var msgRowSource, msgRowType, msgRowData;
4118 if (fromUser && msgtype.match(/^(PRIVMSG|ACTION|NOTICE|WALLOPS)$/))
4120 var nick = sourceObj.unicodeName;
4124 // Set default decorations.
4125 if (msgtype == "ACTION")
4136 if ((sourceObj != me) && ("getURL" in sourceObj))
4137 nickURL = sourceObj.getURL();
4139 if (sourceObj != me)
4144 // ...but to us. Messages from someone else to us.
4146 getAttention = true;
4147 this.defaultCompletion = "/msg " + nick + " ";
4149 // If this is a private message, and it's not in a query view,
4150 // use *nick* instead of <nick>.
4151 if ((msgtype != "ACTION") && (this.TYPE != "IRCUser"))
4159 // ...or to us. Messages from someone else to channel or similar.
4161 if ((typeof message == "string") && me)
4163 isImportant = msgIsImportant(message, nick, o.network);
4166 this.defaultCompletion = nick +
4167 client.prefs["nickCompleteStr"] + " ";
4174 // Messages from us, on a channel or network view, to a user
4175 if (toUser && (this.TYPE != "IRCUser"))
4177 nick = destObj.unicodeName;
4183 // Log the nickname in the same format as we'll let the user copy.
4184 // If the message has a prefix, show it after a "/".
4186 logStringPfx += decorSt + nick + "/" + msgprefix + decorEn + " ";
4188 logStringPfx += decorSt + nick + decorEn + " ";
4190 if (!("lastNickDisplayed" in this) || this.lastNickDisplayed != nick)
4192 this.lastNickDisplayed = nick;
4193 this.mark = (("mark" in this) && this.mark == "even") ? "odd" : "even";
4196 msgRowSource = document.createElementNS(XHTML_NS, "html:td");
4197 msgRowSource.setAttribute("class", "msg-user");
4199 // Make excessive nicks get shunted.
4200 if (nick && (nick.length > client.MAX_NICK_DISPLAY))
4204 msgRowSource.appendChild(newInlineText(decorSt, "chatzilla-decor"));
4207 var nick_anchor = document.createElementNS(XHTML_NS, "html:a");
4208 nick_anchor.setAttribute("class", "chatzilla-link");
4209 nick_anchor.setAttribute("href", nickURL);
4210 nick_anchor.appendChild(newInlineText(nick));
4211 msgRowSource.appendChild(nick_anchor);
4215 msgRowSource.appendChild(newInlineText(nick));
4219 /* We don't style the "/" with chatzilla-decor because the one
4220 * thing we don't want is it disappearing!
4222 msgRowSource.appendChild(newInlineText("/", ""));
4223 msgRowSource.appendChild(newInlineText(msgprefix,
4224 "chatzilla-prefix"));
4227 msgRowSource.appendChild(newInlineText(decorEn, "chatzilla-decor"));
4228 canMergeData = this.prefs["collapseMsgs"];
4235 logStringPfx += decorSt + "/" + msgprefix + decorEn + " ";
4237 msgRowSource = document.createElementNS(XHTML_NS, "html:td");
4238 msgRowSource.setAttribute("class", "msg-user");
4240 msgRowSource.appendChild(newInlineText(decorSt, "chatzilla-decor"));
4241 msgRowSource.appendChild(newInlineText("/", ""));
4242 msgRowSource.appendChild(newInlineText(msgprefix, "chatzilla-prefix"));
4243 msgRowSource.appendChild(newInlineText(decorEn, "chatzilla-decor"));
4244 canMergeData = this.prefs["collapseMsgs"];
4248 isSuperfluous = true;
4249 if (!client.debugHook.enabled && msgtype in client.responseCodeMap)
4251 code = client.responseCodeMap[msgtype];
4255 if (!client.debugHook.enabled && client.HIDE_CODES)
4256 code = client.DEFAULT_RESPONSE_CODE;
4258 code = "[" + msgtype + "]";
4261 /* Display the message code */
4262 msgRowType = document.createElementNS(XHTML_NS, "html:td");
4263 msgRowType.setAttribute("class", "msg-type");
4265 msgRowType.appendChild(newInlineText(code));
4266 logStringPfx += code + " ";
4271 msgRowData = document.createElementNS(XHTML_NS, "html:td");
4272 msgRowData.setAttribute("class", "msg-data");
4274 var tmpMsgs = message;
4275 if (typeof message == "string")
4277 msgRowData.appendChild(stringToMsg(message, this));
4281 msgRowData.appendChild(message);
4282 tmpMsgs = tmpMsgs.innerHTML.replace(/<[^<]*>/g, "");
4284 tmpMsgs = tmpMsgs.split(/\r?\n/);
4285 for (var l = 0; l < tmpMsgs.length; l++)
4286 logStrings[l] = logStringPfx + tmpMsgs[l];
4290 msgRow.setAttribute("mark", this.mark);
4294 msgRow.setAttribute("important", "true");
4295 msgRow.setAttribute("aria-channel", "notify");
4298 // Timestamps first...
4299 msgRow.appendChild(msgRowTimestamp);
4300 // Now do the rest of the row, after block-level stuff.
4302 msgRow.appendChild(msgRowSource);
4304 msgRow.appendChild(msgRowType);
4306 msgRow.appendChild(msgRowData);
4307 updateTimestampFor(this, msgRow);
4311 /* putting a div here crashes mozilla, so fake it with nested tables
4313 var tr = document.createElementNS(XHTML_NS, "html:tr");
4314 tr.setAttribute ("class", "msg-nested-tr");
4315 var td = document.createElementNS(XHTML_NS, "html:td");
4316 td.setAttribute ("class", "msg-nested-td");
4317 td.setAttribute ("colspan", "3");
4320 var table = document.createElementNS(XHTML_NS, "html:table");
4321 table.setAttribute ("class", "msg-nested-table");
4322 table.setAttribute("role", "presentation");
4324 td.appendChild (table);
4325 var tbody = document.createElementNS(XHTML_NS, "html:tbody");
4327 tbody.appendChild(msgRow);
4328 table.appendChild(tbody);
4332 // Actually add the item.
4333 addHistory (this, msgRow, canMergeData);
4335 // Update attention states...
4336 if (isImportant || getAttention)
4338 setTabState(this, "attention");
4339 if (client.prefs["notify.aggressive"])
4340 window.getAttention();
4346 setTabState(this, "superfluous");
4350 setTabState(this, "activity");
4354 // Copy Important Messages [to network view].
4355 if (isImportant && client.prefs["copyMessages"] && (o.network != this))
4357 o.network.displayHere("{" + this.unicodeName + "} " + message, msgtype,
4358 sourceObj, destObj);
4362 if (this.prefs["log"])
4365 client.openLogFile(this);
4369 var LE = client.lineEnd;
4370 for (var l = 0; l < logStrings.length; l++)
4371 this.logFile.write(fromUnicode(logStrings[l] + LE, "utf-8"));
4375 // Stop logging before showing any messages!
4376 this.prefs["log"] = false;
4377 dd("Log file write error: " + formatException(ex));
4378 this.displayHere(getMsg(MSG_LOGFILE_WRITE_ERROR, getLogPath(this)),
4384 function addHistory (source, obj, mergeData)
4386 if (!("messages" in source) || (source.messages == null))
4387 createMessages(source);
4389 var tbody = source.messages.firstChild;
4390 var appendTo = tbody;
4392 var needScroll = false;
4397 // This gives us the non-nested row when there is nesting.
4398 if (inobj.className == "msg-nested-tr")
4399 inobj = inobj.firstChild.firstChild.firstChild.firstChild;
4401 var thisUserCol = inobj.firstChild;
4402 while (thisUserCol && !thisUserCol.className.match(/^(msg-user|msg-type)$/))
4403 thisUserCol = thisUserCol.nextSibling;
4405 var thisMessageCol = inobj.firstChild;
4406 while (thisMessageCol && !(thisMessageCol.className == "msg-data"))
4407 thisMessageCol = thisMessageCol.nextSibling;
4409 var ci = findPreviousColumnInfo(source.messages);
4410 var nickColumns = ci.nickColumns;
4411 var rowExtents = ci.extents;
4412 var nickColumnCount = nickColumns.length;
4414 var lastRowSpan, sameNick, sameDest, haveSameType, needSameType;
4415 var isAction, collapseActions;
4416 if (nickColumnCount == 0) // No message to collapse to.
4418 sameNick = sameDest = needSameType = haveSameType = false;
4421 else // 1 or more messages, check for doubles
4423 var lastRow = nickColumns[nickColumnCount - 1].parentNode;
4424 // What was the span last time?
4425 lastRowSpan = Number(nickColumns[0].getAttribute("rowspan"));
4426 // Are we the same user as last time?
4427 sameNick = (lastRow.getAttribute("msg-user") ==
4428 inobj.getAttribute("msg-user"));
4429 // Do we have the same prefix as last time?
4430 samePrefix = (lastRow.getAttribute("msg-prefix") ==
4431 inobj.getAttribute("msg-prefix"));
4432 // Do we have the same destination as last time?
4433 sameDest = (lastRow.getAttribute("msg-dest") ==
4434 inobj.getAttribute("msg-dest"));
4435 // Is this message the same type as the last one?
4436 haveSameType = (lastRow.getAttribute("msg-type") ==
4437 inobj.getAttribute("msg-type"));
4438 // Is either of the messages an action? We may not want to collapse
4439 // depending on the collapseActions pref
4440 isAction = ((inobj.getAttribute("msg-type") == "ACTION") ||
4441 (lastRow.getAttribute("msg-type") == "ACTION"));
4442 // Do we collapse actions?
4443 collapseActions = source.prefs["collapseActions"];
4445 // Does the motif collapse everything, regardless of type?
4446 // NOTE: the collapseActions pref can override this for actions
4447 needSameType = !(("motifSettings" in source) &&
4448 source.motifSettings &&
4449 ("collapsemore" in source.motifSettings));
4452 if (sameNick && samePrefix && sameDest &&
4453 (haveSameType || !needSameType) &&
4454 (!isAction || collapseActions))
4458 appendTo = source.messages.firstChild.lastChild.firstChild.firstChild.firstChild;
4460 if (obj.getAttribute("important"))
4462 nickColumns[nickColumnCount - 1].setAttribute("important",
4466 // Remove nickname column from new row.
4467 obj.removeChild(thisUserCol);
4469 // Expand previous grouping's nickname cell(s) to fill-in the gap.
4470 for (var i = 0; i < nickColumns.length; ++i)
4471 nickColumns[i].setAttribute("rowspan", rowExtents.length + 1);
4475 if ("frame" in source)
4476 needScroll = checkScroll(source.frame);
4478 appendTo.appendChild(client.adoptNode(obj, appendTo.ownerDocument));
4480 if (source.MAX_MESSAGES)
4482 if (typeof source.messageCount != "number")
4483 source.messageCount = 1;
4485 source.messageCount++;
4487 if (source.messageCount > source.MAX_MESSAGES)
4489 // Get the top of row 2, and subtract the top of row 1.
4490 var height = tbody.firstChild.nextSibling.firstChild.offsetTop -
4491 tbody.firstChild.firstChild.offsetTop;
4492 var window = getContentWindow(source.frame);
4493 var x = window.pageXOffset;
4494 var y = window.pageYOffset;
4495 tbody.removeChild (tbody.firstChild);
4496 --source.messageCount;
4497 while (tbody.firstChild && tbody.firstChild.childNodes[1] &&
4498 tbody.firstChild.childNodes[1].getAttribute("class") ==
4501 --source.messageCount;
4502 tbody.removeChild (tbody.firstChild);
4504 if (!checkScroll(source.frame) && (y > height))
4505 window.scrollTo(x, y - height);
4511 scrollDown(source.frame, true);
4512 setTimeout(scrollDown, 500, source.frame, false);
4513 setTimeout(scrollDown, 1000, source.frame, false);
4514 setTimeout(scrollDown, 2000, source.frame, false);
4518 function findPreviousColumnInfo(table)
4520 // All the rows in the grouping (for merged rows).
4521 var extents = new Array();
4522 // Get the last row in the table.
4523 var tr = table.firstChild.lastChild;
4524 // Bail if there's no rows.
4526 return {extents: [], nickColumns: [], nested: false};
4527 // Get message type.
4528 if (tr.className == "msg-nested-tr")
4530 var rv = findPreviousColumnInfo(tr.firstChild.firstChild);
4534 // Now get the read one...
4535 var className = (tr && tr.childNodes[1]) ? tr.childNodes[1].getAttribute("class") : "";
4536 // Keep going up rows until you find the first in a group.
4537 // This will go up until it hits the top of a multiline/merged block.
4538 while (tr && tr.childNodes[1] && className.search(/msg-user|msg-type/) == -1)
4541 tr = tr.previousSibling;
4542 if (tr && tr.childNodes[1])
4543 className = tr.childNodes[1].getAttribute("class");
4546 // If we ran out of rows, or it's not a talking line, we're outta here.
4547 if (!tr || className != "msg-user")
4548 return {extents: [], nickColumns: [], nested: false};
4552 // Time to collect the nick data...
4553 var nickCol = tr.firstChild;
4554 // All the cells that contain nickname info.
4555 var nickCols = new Array();
4558 // Just collect nickname column cells.
4559 if (nickCol.getAttribute("class") == "msg-user")
4560 nickCols.push(nickCol);
4561 nickCol = nickCol.nextSibling;
4565 return {extents: extents, nickColumns: nickCols, nested: false};
4568 function getLogPath(obj)
4570 // If we're logging, return the currently-used URL.
4572 return getURLSpecFromFile(obj.logFile.path);
4573 // If not, return the ideal URL.
4574 return getURLSpecFromFile(obj.prefs["logFileName"]);
4577 client.getConnectionCount =
4578 function cli_gccount ()
4582 for (var n in client.networks)
4584 if (client.networks[n].isConnected())
4592 function cli_quit (reason)
4595 for (var n in client.networks)
4597 net = client.networks[n];
4598 if (net.isConnected())
4600 netReason = (reason ? reason : net.prefs["defaultQuitMsg"]);
4601 netReason = (netReason ? netReason : client.userAgent);
4602 net.quit(netReason);
4608 function cli_wantToQuit(reason, deliberate)
4612 if (client.prefs["warnOnClose"] && !deliberate)
4614 const buttons = [MSG_QUIT_ANYWAY, MSG_DONT_QUIT];
4615 var checkState = { value: true };
4616 var rv = confirmEx(MSG_CONFIRM_QUIT, buttons, 0, MSG_WARN_ON_EXIT,
4619 client.prefs["warnOnClose"] = checkState.value;
4624 client.userClose = true;
4625 display(MSG_CLOSING);
4626 client.quit(reason);
4630 /* gets a tab-complete match for the line of text specified by |line|.
4631 * wordStart is the position within |line| that starts the word being matched,
4632 * wordEnd marks the end position. |cursorPos| marks the position of the caret
4635 client.performTabMatch =
4636 function gettabmatch_usr (line, wordStart, wordEnd, word, cursorPos)
4638 if (wordStart != 0 || line[0] != client.COMMAND_CHAR)
4641 var matches = client.commandManager.listNames(word.substr(1), CMD_CONSOLE);
4642 if (matches.length == 1 && wordEnd == line.length)
4644 matches[0] = client.COMMAND_CHAR + matches[0] + " ";
4648 for (var i in matches)
4649 matches[i] = client.COMMAND_CHAR + matches[i];
4655 client.openLogFile =
4656 function cli_startlog (view)
4658 function getNextLogFileDate()
4661 d.setMilliseconds(0);
4664 switch (view.smallestLogInterval)
4667 return d.setHours(d.getHours() + 1);
4670 return d.setDate(d.getDate() + 1);
4674 return d.setMonth(d.getMonth() + 1);
4679 return d.setFullYear(d.getFullYear() + 1);
4681 //XXXhack: This should work...
4685 const NORMAL_FILE_TYPE = Components.interfaces.nsIFile.NORMAL_FILE_TYPE;
4689 var file = new LocalFile(view.prefs["logFileName"]);
4690 if (!file.localFile.exists())
4692 // futils.umask may be 0022. Result is 0644.
4693 file.localFile.create(NORMAL_FILE_TYPE, 0666 & ~futils.umask);
4695 view.logFile = fopen(file.localFile, ">>");
4696 // If we're here, it's safe to say when we should re-open:
4697 view.nextLogFileDate = getNextLogFileDate();
4701 view.prefs["log"] = false;
4702 dd("Log file open error: " + formatException(ex));
4703 view.displayHere(getMsg(MSG_LOGFILE_ERROR, getLogPath(view)), MT_ERROR);
4707 if (!("logFileWrapping" in view) || !view.logFileWrapping)
4708 view.displayHere(getMsg(MSG_LOGFILE_OPENED, getLogPath(view)));
4709 view.logFileWrapping = false;
4712 client.closeLogFile =
4713 function cli_stoplog(view, wrapping)
4715 if ("frame" in view && !wrapping)
4716 view.displayHere(getMsg(MSG_LOGFILE_CLOSING, getLogPath(view)));
4718 view.logFileWrapping = Boolean(wrapping);
4722 view.logFile.close();
4723 view.logFile = null;
4727 function checkLogFiles()
4729 // For every view that has a logfile, check if we need a different file
4730 // based on the current date and the logfile preference. We close the
4731 // current logfile, and display will open the new one based on the pref
4732 // when it's needed.
4735 for (var n in client.networks)
4737 var net = client.networks[n];
4738 if (net.logFile && (d > net.nextLogFileDate))
4739 client.closeLogFile(net, true);
4740 if (("primServ" in net) && net.primServ && ("channels" in net.primServ))
4742 for (var c in net.primServ.channels)
4744 var chan = net.primServ.channels[c];
4745 if (chan.logFile && (d > chan.nextLogFileDate))
4746 client.closeLogFile(chan, true);
4751 for (var u in net.users)
4753 var user = net.users[u];
4754 if (user.logFile && (d > user.nextLogFileDate))
4755 client.closeLogFile(user, true);
4760 for (var dc in client.dcc.chats)
4762 var dccChat = client.dcc.chats[dc];
4763 if (dccChat.logFile && (d > dccChat.nextLogFileDate))
4764 client.closeLogFile(dccChat, true);
4766 for (var df in client.dcc.files)
4768 var dccFile = client.dcc.files[df];
4769 if (dccFile.logFile && (d > dccFile.nextLogFileDate))
4770 client.closeLogFile(dccFile, true);
4773 // Don't forget about the client tab:
4774 if (client.logFile && (d > client.nextLogFileDate))
4775 client.closeLogFile(client, true);
4777 // We use the same line again to make sure we keep a constant offset
4778 // from the full hour, in case the timers go crazy at some point.
4779 setTimeout("checkLogFiles()", 3602000 - (Number(new Date()) % 3600000));
4782 CIRCChannel.prototype.getLCFunction =
4783 CIRCNetwork.prototype.getLCFunction =
4784 CIRCUser.prototype.getLCFunction =
4785 CIRCDCCChat.prototype.getLCFunction =
4786 CIRCDCCFileTransfer.prototype.getLCFunction =
4789 var details = getObjectDetails(this);
4794 lcFn = function(text)
4796 return details.server.toLowerCase(text);
4803 CIRCChannel.prototype.performTabMatch =
4804 CIRCNetwork.prototype.performTabMatch =
4805 CIRCUser.prototype.performTabMatch =
4806 CIRCDCCChat.prototype.performTabMatch =
4807 CIRCDCCFileTransfer.prototype.performTabMatch =
4808 function gettabmatch_other (line, wordStart, wordEnd, word, cursorpos, lcFn)
4810 if (wordStart == 0 && line[0] == client.COMMAND_CHAR)
4812 return client.performTabMatch(line, wordStart, wordEnd, word,
4816 var matchList = new Array();
4821 var details = getObjectDetails(this);
4823 if (details.channel && word == details.channel.unicodeName[0])
4825 /* When we have #<tab>, we just want the current channel,
4827 matchList.push(details.channel.unicodeName);
4831 /* Ok, not #<tab> or no current channel, so get the full list. */
4833 if (details.channel)
4834 users = details.channel.users;
4838 channels = details.server.channels;
4839 for (var c in channels)
4840 matchList.push(channels[c].unicodeName);
4842 users = details.server.users;
4847 userIndex = matchList.length;
4848 for (var n in users)
4849 matchList.push(users[n].unicodeName);
4853 var matches = matchEntry(word, matchList, lcFn);
4855 var list = new Array();
4856 for (var i = 0; i < matches.length; i++)
4857 list.push(matchList[matches[i]]);
4859 if (list.length == 1)
4861 if (users && (userIndex >= 0) && (matches[0] >= userIndex))
4864 list[0] += client.prefs["nickCompleteStr"];
4867 if (wordEnd == line.length)
4869 /* add a space if the word is at the end of the line. */