Browse Source

Some new ChatZilla patches

Ian Neal 2 weeks ago
parent
commit
2c38dc28fb

+ 31 - 0
comm-release/patches/1923211-irc-fix-decodeTagData-25320.patch

@@ -0,0 +1,31 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1728336808 -3600
+# Parent  a94f0552129d136b7cb08b766bef2debcd161871
+Bug 1923211 - Sometimes tag data from an IRC server doesn't contain a pair. r=frg a=frg
+
+diff --git a/suite/extensions/irc/js/lib/irc.js b/suite/extensions/irc/js/lib/irc.js
+--- a/suite/extensions/irc/js/lib/irc.js
++++ b/suite/extensions/irc/js/lib/irc.js
+@@ -776,17 +776,20 @@ function serv_decodetagdata(str)
+     }
+ 
+     var obj = Object();
+ 
+     var tags = str.split(";");
+     for (var i = 0; i < tags.length; i++)
+     {
+         var [key, val] = tags[i].split("=");
+-        val = unescapeTagValue(val);
++        if (val)
++            val = unescapeTagValue(val);
++        else
++            val = "";
+         obj[key] = val;
+     }
+ 
+     return obj;
+ }
+ 
+ // Returns the IRC URL representation of this server.
+ CIRCServer.prototype.getURL =

+ 32 - 0
comm-release/patches/1923213-irc-network-away-fix-25320.patch

@@ -0,0 +1,32 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1728337379 -3600
+# Parent  87cb73ddda4ba017b95a4c82c0ca96e36b06c3f5
+Bug 1923213 - Fix call to updateUsers in network onAway. r=frg a=frg
+
+diff --git a/suite/extensions/irc/xul/content/handlers.js b/suite/extensions/irc/xul/content/handlers.js
+--- a/suite/extensions/irc/xul/content/handlers.js
++++ b/suite/extensions/irc/xul/content/handlers.js
+@@ -2731,19 +2731,20 @@ CIRCNetwork.prototype.onAway =
+ function my_away(e)
+ {
+     var userlist = document.getElementById("user-list");
+     for (var c in e.server.channels)
+     {
+         var chan = e.server.channels[c];
+         if (chan.active && (e.user.collectionKey in chan.users))
+         {
+-            let index = chan.users[e.user.collectionKey].chanListEntry.childIndex;
++            let user = chan.users[e.user.collectionKey];
++            let index = user.chanListEntry.childIndex;
+             userlist.treeBoxObject.invalidateRow(index);
+-            e.server.channels[c].updateUsers([e.user.collectionKey]);
++            e.server.channels[c].updateUsers([user]);
+         }
+     }
+ }
+ 
+ /* user host changed */
+ CIRCNetwork.prototype.onChghost =
+ function my_chghost(e)
+ {

+ 62 - 0
comm-release/patches/1923215-irc-remove-XTLabelRecord-25320.patch

@@ -0,0 +1,62 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1728337743 -3600
+# Parent  5b3985746b1e9f7a9df692a5bb6769592dfed238
+Bug 1923215 - Remove unused XTLabelRecord from tree-utils.js. r=frg a=frg
+
+diff --git a/suite/extensions/irc/xul/lib/tree-utils.js b/suite/extensions/irc/xul/lib/tree-utils.js
+--- a/suite/extensions/irc/xul/lib/tree-utils.js
++++ b/suite/extensions/irc/xul/lib/tree-utils.js
+@@ -1024,52 +1024,16 @@ function xtvr_find (targetRow, myRow)
+ 
+         /* otherwise, get ready to ask the next kid */
+         childStart += child.visualFootprint;
+     }
+ 
+     return null;
+ }   
+ 
+-/**
+- * Used to drop a label into an arbitrary place in an arbitrary tree.
+- *
+- * Normally, specializations of |XULTreeViewRecord| are tied to a specific
+- * tree because of implementation details.  |XTLabelRecords| are specially
+- * designed (err, hacked) to work around these details - this makes them
+- * slower, but more generic.
+- *
+- * We set up a getter for |_share| that defers to the parent object. This lets
+- * |XTLabelRecords| work in any tree.
+- */
+-function XTLabelRecord (columnName, label, blankCols)
+-{
+-    this.setColumnPropertyName (columnName, "label");
+-    this.label = label;
+-    this.property = null;
+-    
+-    if (typeof blankCols == "object")
+-    {
+-        for (var i in blankCols)
+-            this._colValues[blankCols[i]] = "";
+-    }
+-}
+-
+-XTLabelRecord.prototype = new XULTreeViewRecord (null);
+-
+-XTLabelRecord.prototype.__defineGetter__("_share", tolr_getshare);
+-function tolr_getshare()
+-{
+-    if ("parentRecord" in this)
+-        return this.parentRecord._share;
+-
+-    ASSERT (0, "XTLabelRecord cannot be the root of a visible tree.");
+-    return null;
+-}
+-
+ // @internal
+ function XTRootRecord (tree, share)
+ {
+     this._share = share;
+     this._treeView = tree;
+     this.visualFootprint = 0;
+     this.isHidden = false;
+     this.reserveChildren();

+ 367 - 0
comm-release/patches/1923219-irc-connection-xpcom-tidy-25320.patch

@@ -0,0 +1,367 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1728338342 -3600
+# Parent  509230dd9d30cbb4fe89db44f2e9e596a6171ea9
+Bug 1923219 - Remove unused code from connection-xpcom.js. r=frg a=frg
+
+diff --git a/suite/extensions/irc/js/lib/connection-xpcom.js b/suite/extensions/irc/js/lib/connection-xpcom.js
+--- a/suite/extensions/irc/js/lib/connection-xpcom.js
++++ b/suite/extensions/irc/js/lib/connection-xpcom.js
+@@ -29,55 +29,30 @@ const NS_NET_STATUS_CONNECTING_TO = NS_E
+ const ERROR_CLASS_SSL_PROTOCOL = 1;
+ const ERROR_CLASS_BAD_CERT = 2;
+ 
+ // Security Constants.
+ const STATE_IS_BROKEN = 1;
+ const STATE_IS_SECURE = 2;
+ const STATE_IS_INSECURE = 3;
+ 
+-const nsIScriptableInputStream = Components.interfaces.nsIScriptableInputStream;
+-
+-const nsIBinaryInputStream = Components.interfaces.nsIBinaryInputStream;
+-const nsIBinaryOutputStream = Components.interfaces.nsIBinaryOutputStream;
+-
+-function toSInputStream(stream, binary)
++function toSInputStream(stream)
+ {
+-    var sstream;
+-
+-    if (binary)
+-    {
+-        sstream = Components.classes["@mozilla.org/binaryinputstream;1"];
+-        sstream = sstream.createInstance(nsIBinaryInputStream);
+-        sstream.setInputStream(stream);
+-    }
+-    else
+-    {
+-        sstream = Components.classes["@mozilla.org/scriptableinputstream;1"];
+-        sstream = sstream.createInstance(nsIScriptableInputStream);
+-        sstream.init(stream);
+-    }
++    var sstream = Components.classes["@mozilla.org/binaryinputstream;1"];
++    sstream = sstream.createInstance(Components.interfaces.nsIBinaryInputStream);
++    sstream.setInputStream(stream);
+ 
+     return sstream;
+ }
+ 
+-function toSOutputStream(stream, binary)
++function toSOutputStream(stream)
+ {
+-    var sstream;
+-
+-    if (binary)
+-    {
+-        sstream = Components.classes["@mozilla.org/binaryoutputstream;1"];
+-        sstream = sstream.createInstance(Components.interfaces.nsIBinaryOutputStream);
+-        sstream.setOutputStream(stream);
+-    }
+-    else
+-    {
+-        sstream = stream;
+-    }
++    var sstream = Components.classes["@mozilla.org/binaryoutputstream;1"];
++    sstream = sstream.createInstance(Components.interfaces.nsIBinaryOutputStream);
++    sstream.setOutputStream(stream);
+ 
+     return sstream;
+ }
+ 
+ /* This object implements nsIBadCertListener2
+  * The idea is to suppress the default UI's alert box
+  * and allow the exception to propagate normally
+  */
+@@ -110,102 +85,28 @@ function badcert_queryinterface(aIID)
+ BadCertHandler.prototype.notifyCertProblem =
+ function badcert_notifyCertProblem(socketInfo, sslStatus, targetHost)
+ {
+     return true;
+ }
+ 
+ /**
+  * Wraps up various mechanics of sockets for easy consumption by other code.
+- *
+- * @param binary Provide |true| or |false| here to override the automatic
+- *               selection of binary or text streams. This should only ever be
+- *               specified as |true| or omitted, otherwise you will be shooting
+- *               yourself in the foot on some versions - let the code handle
+- *               the choice unless you know you need binary.
+  */
+-function CBSConnection (binary)
++function CBSConnection ()
+ {
+-    /* Since 2003-01-17 18:14, Mozilla has had this contract ID for the STS.
+-     * Prior to that it didn't have one, so we also include the CID for the
+-     * STS back then - DO NOT UPDATE THE ID if it changes in Mozilla.
+-     */
+-    const sockClassByName =
+-        Components.classes["@mozilla.org/network/socket-transport-service;1"];
+-    const sockClassByID =
+-        Components.classesByID["{c07e81e0-ef12-11d2-92b6-00105a1b0d64}"];
+-
+-    var sockServiceClass = (sockClassByName || sockClassByID);
+-
+-    if (!sockServiceClass)
+-        throw ("Couldn't get socket service class.");
+-
+-    var sockService = sockServiceClass.getService();
+-    if (!sockService)
++    this._sockService =
++        Components.classes["@mozilla.org/network/socket-transport-service;1"]
++                  .getService(Components.interfaces.nsISocketTransportService);
++    if (!this._sockService)
+         throw ("Couldn't get socket service.");
+ 
+-    this._sockService = sockService.QueryInterface
+-        (Components.interfaces.nsISocketTransportService);
+-
+-    /* Note: as part of the mess from bug 315288 and bug 316178, ChatZilla now
+-     *       uses the *binary* stream interfaces for all network
+-     *       communications.
+-     *
+-     *       However, these interfaces do not exist prior to 1999-11-05. To
+-     *       make matters worse, an incompatible change to the "readBytes"
+-     *       method of this interface was made on 2003-03-13; luckly, this
+-     *       change also added a "readByteArray" method, which we will check
+-     *       for below, to determine if we can use the binary streams.
+-     */
+-
+-    // We want to check for working binary streams only the first time.
+-    if (CBSConnection.prototype.workingBinaryStreams == -1)
+-    {
+-        CBSConnection.prototype.workingBinaryStreams = false;
+-
+-        if (typeof nsIBinaryInputStream != "undefined")
+-        {
+-            var isCls = Components.classes["@mozilla.org/binaryinputstream;1"];
+-            var inputStream = isCls.createInstance(nsIBinaryInputStream);
+-            if ("readByteArray" in inputStream)
+-                CBSConnection.prototype.workingBinaryStreams = true;
+-        }
+-    }
+-
+-    /*
+-     * As part of the changes in Gecko 1.9, invalid SSL certificates now
+-     * produce a horrible error message. We must look up the toolkit version
+-     * to see if we need to catch these errors cleanly - see bug 454966.
+-     */
+-    if (!("strictSSL" in CBSConnection.prototype))
+-    {
+-        CBSConnection.prototype.strictSSL = false;
+-        var app = getService("@mozilla.org/xre/app-info;1", "nsIXULAppInfo");
+-        if (app && ("platformVersion" in app) &&
+-            compareVersions("1.9", app.platformVersion) >= 0)
+-        {
+-            CBSConnection.prototype.strictSSL = true;
+-        }
+-    }
+-
+     this.wrappedJSObject = this;
+-    if (typeof binary != "undefined")
+-        this.binaryMode = binary;
+-    else
+-        this.binaryMode = this.workingBinaryStreams;
+-
+-    if (!ASSERT(!this.binaryMode || this.workingBinaryStreams,
+-                "Unable to use binary streams in this build."))
+-    {
+-        throw ("Unable to use binary streams in this build.");
+-    }
+ }
+ 
+-CBSConnection.prototype.workingBinaryStreams = -1;
+-
+ CBSConnection.prototype.connect =
+ function bc_connect(host, port, config, observer)
+ {
+     this.host = host.toLowerCase();
+     this.port = port;
+ 
+     /* The APIs below want host:port. Later on, we also reformat the host to
+      * strip IPv6 literal brackets.
+@@ -297,43 +198,39 @@ function bc_connect(host, port, config, 
+         throw JSIRC_ERR_PAC_LOADING;
+ 
+         /* use new necko interfaces */
+         if (("isSecure" in config) && config.isSecure)
+         {
+             this._transport = this._sockService.
+                               createTransport(["ssl"], 1, host, port,
+                                               proxyInfo);
+-
+-            if (this.strictSSL)
+-                this._transport.securityCallbacks = new BadCertHandler();
++            this._transport.securityCallbacks = new BadCertHandler();
+         }
+         else
+         {
+             this._transport = this._sockService.
+                               createTransport(["starttls"], 1, host, port, proxyInfo);
+         }
+         if (!this._transport)
+             throw ("Error creating transport.");
+ 
+         var openFlags = 0;
+ 
+         /* no limit on the output stream buffer */
+         this._outputStream =
+             this._transport.openOutputStream(openFlags, 4096, -1);
+         if (!this._outputStream)
+             throw "Error getting output stream.";
+-        this._sOutputStream = toSOutputStream(this._outputStream,
+-                                              this.binaryMode);
++        this._sOutputStream = toSOutputStream(this._outputStream);
+ 
+         this._inputStream = this._transport.openInputStream(openFlags, 0, 0);
+         if (!this._inputStream)
+             throw "Error getting input stream.";
+-        this._sInputStream = toSInputStream(this._inputStream,
+-                                            this.binaryMode);
++        this._sInputStream = toSInputStream(this._inputStream);
+ 
+     this.connectDate = new Date();
+     this.isConnected = true;
+ 
+     // Bootstrap the connection if we're proxying via an HTTP proxy.
+     if (usingHTTPCONNECT)
+         this.sendData("CONNECT " + hostPort + " HTTP/1.1\r\n\r\n");
+ 
+@@ -388,24 +285,22 @@ function bc_accept(transport, observer)
+ 
+         var openFlags = 0;
+ 
+         /* no limit on the output stream buffer */
+         this._outputStream =
+             this._transport.openOutputStream(openFlags, 4096, -1);
+         if (!this._outputStream)
+             throw "Error getting output stream.";
+-        this._sOutputStream = toSOutputStream(this._outputStream,
+-                                              this.binaryMode);
++        this._sOutputStream = toSOutputStream(this._outputStream);
+ 
+         this._inputStream = this._transport.openInputStream(openFlags, 0, 0);
+         if (!this._inputStream)
+             throw "Error getting input stream.";
+-        this._sInputStream = toSInputStream(this._inputStream,
+-                                            this.binaryMode);
++        this._sInputStream = toSInputStream(this._inputStream);
+ 
+     this.connectDate = new Date();
+     this.isConnected = true;
+ 
+     // Clean up listening socket.
+     this.close();
+ 
+     return this.isConnected;
+@@ -455,45 +350,34 @@ function bc_readdata(timeout, count)
+         dump("OMG, setting up _sInputStream!\n");
+     }
+ 
+     try
+     {
+         // XPCshell h4x
+         if (typeof count == "undefined")
+             count = this._sInputStream.available();
+-        if (this.binaryMode)
+-            rv = this._sInputStream.readBytes(count);
+-        else
+-            rv = this._sInputStream.read(count);
++        rv = this._sInputStream.readBytes(count);
+     }
+     catch (ex)
+     {
+         dd ("*** Caught " + ex + " while reading.");
+         this.disconnect();
+         throw (ex);
+     }
+ 
+     return rv;
+ }
+ 
+ CBSConnection.prototype.startAsyncRead =
+ function bc_saread (observer)
+ {
+         var cls = Components.classes["@mozilla.org/network/input-stream-pump;1"];
+         var pump = cls.createInstance(Components.interfaces.nsIInputStreamPump);
+-        // Account for Bug 1402888 which removed the startOffset and readLimit
+-        // parameters from init.
+-        if (pump.init.length > 5)
+-        {
+-            pump.init(this._inputStream, -1, -1, 0, 0, false);
+-        } else
+-        {
+-            pump.init(this._inputStream, 0, 0, false);
+-        }
++        pump.init(this._inputStream, 0, 0, false);
+         pump.asyncRead(new StreamListener(observer), this);
+ }
+ 
+ CBSConnection.prototype.asyncWrite =
+ function bc_awrite (str)
+ {
+     this._streamProvider.pendingData += str;
+     if (this._streamProvider.isBlocked)
+@@ -511,20 +395,17 @@ function bc_haspwrite ()
+ 
+ CBSConnection.prototype.sendDataNow =
+ function bc_senddatanow(str)
+ {
+     var rv = false;
+ 
+     try
+     {
+-        if (this.binaryMode)
+-            this._sOutputStream.writeBytes(str, str.length);
+-        else
+-            this._sOutputStream.write(str, str.length);
++        this._sOutputStream.writeBytes(str, str.length);
+         rv = true;
+     }
+     catch (ex)
+     {
+         dd ("*** Caught " + ex + " while sending.");
+         this.disconnect();
+         throw (ex);
+     }
+@@ -611,22 +492,16 @@ function sp_datawrite (request, ctxt, os
+     //dd ("StreamProvider.prototype.onDataWritable");
+ 
+     if ("isClosed" in this && this.isClosed)
+         throw Components.results.NS_BASE_STREAM_CLOSED;
+ 
+     if (!this.pendingData)
+     {
+         this.isBlocked = true;
+-
+-        /* this is here to support pre-XPCDOM builds (0.9.0 era), which
+-         * don't have this result code mapped. */
+-        if (!Components.results.NS_BASE_STREAM_WOULD_BLOCK)
+-            throw 2152136711;
+-
+         throw Components.results.NS_BASE_STREAM_WOULD_BLOCK;
+     }
+ 
+     var len = ostream.write (this.pendingData, this.pendingData.length);
+     this.pendingData = this.pendingData.substr (len);
+ }
+ 
+ StreamProvider.prototype.onStartRequest =
+@@ -672,17 +547,17 @@ function sl_dataavail (request, ctxt, in
+     if (!ctxt)
+     {
+         dd ("*** Can't get wrappedJSObject from ctxt in " +
+             "StreamListener.onDataAvailable ***");
+         return;
+     }
+ 
+     if (!("_sInputStream" in ctxt))
+-        ctxt._sInputStream = toSInputStream(inStr, false);
++        ctxt._sInputStream = toSInputStream(inStr);
+ 
+     if (this._observer)
+         this._observer.onStreamDataAvailable(request, inStr, sourceOffset,
+                                              count);
+ }
+ 
+ function SocketListener(connection, observer)
+ {

+ 318 - 0
comm-release/patches/1923221-irc-utils-unused-25320.patch

@@ -0,0 +1,318 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1728339523 -3600
+# Parent  2575f24e1bd8648d20a7d77dfab97f5aac2b214a
+Bug 1923221 - Remove unusued code from utils.js. r=frg a=frg
+
+diff --git a/suite/extensions/irc/js/lib/utils.js b/suite/extensions/irc/js/lib/utils.js
+--- a/suite/extensions/irc/js/lib/utils.js
++++ b/suite/extensions/irc/js/lib/utils.js
+@@ -322,30 +322,16 @@ function Clone (obj)
+         for (var p in obj)
+             robj[p] = obj[p];
+     }
+ 
+     return robj;
+ 
+ }
+ 
+-function Copy(source, dest, overwrite)
+-{
+-    if (!dest)
+-        dest = new Object();
+-
+-    for (var p in source)
+-    {
+-        if (overwrite || !(p in dest))
+-            dest[p] = source[p];
+-    }
+-
+-    return dest;
+-}
+-
+ /*
+  * matches a real object against one or more pattern objects.
+  * if you pass an array of pattern objects, |negate| controls whether to check
+  * if the object matches ANY of the patterns, or NONE of the patterns.
+  */
+ function matchObject (o, pattern, negate)
+ {
+     negate = Boolean(negate);
+@@ -481,21 +467,16 @@ function getCommonPfx (list, lcFn)
+             }
+         }
+     }
+ 
+     return pfx;
+ 
+ }
+ 
+-function openTopWin (url)
+-{
+-    return openDialog (getBrowserURL(), "_blank", "chrome,all,dialog=no", url);
+-}
+-
+ function getWindowByType (windowType)
+ {
+     const MEDIATOR_CONTRACTID =
+         "@mozilla.org/appshell/window-mediator;1";
+     const nsIWindowMediator  = Components.interfaces.nsIWindowMediator;
+ 
+     var windowManager =
+         Components.classes[MEDIATOR_CONTRACTID].getService(nsIWindowMediator);
+@@ -637,44 +618,16 @@ function getContentDocument(frame)
+     }
+     catch (ex)
+     {
+         // throws exception is contentDocument is gone
+         return null;
+     }
+ }
+ 
+-function getPriv (priv)
+-{
+-    var rv = true;
+-
+-    try
+-    {
+-        netscape.security.PrivilegeManager.enablePrivilege(priv);
+-    }
+-    catch (e)
+-    {
+-        dd ("getPriv: unable to get privlege '" + priv + "': " + e);
+-        rv = false;
+-    }
+-
+-    return rv;
+-
+-}
+-
+-function len(o)
+-{
+-    var l = 0;
+-
+-    for (var p in o)
+-        ++l;
+-
+-    return l;
+-}
+-
+ function keys (o)
+ {
+     var rv = new Array();
+ 
+     for (var p in o)
+         rv.push(p);
+ 
+     return rv;
+@@ -688,17 +641,18 @@ function stringTrim (s)
+     s = s.replace (/^\s+/, "");
+     return s.replace (/\s+$/, "");
+ 
+ }
+ 
+ /* the offset should be in seconds, it will be rounded to 2 decimal places */
+ function formatDateOffset (offset, format)
+ {
+-    var seconds = roundTo(offset % 60, 2);
++    var seconds = offset % 60;
++    seconds = Math.round((seconds + Number.EPSILON) * 100) / 100;
+     var minutes = Math.floor(offset / 60);
+     var hours = Math.floor(minutes / 60);
+     minutes = minutes % 60;
+     var days = Math.floor(hours / 24);
+     hours = hours % 24;
+ 
+     if (!format)
+     {
+@@ -779,43 +733,19 @@ function abbreviateWord (str, length)
+ 
+     var left = str.substr (0, (length / 2) - 1);
+     var right = str.substr (str.length - (length / 2) + 1);
+ 
+     return left + "..." + right;
+ }
+ 
+ /*
+- * Inserts the string |hyphen| into string |str| every |pos| characters.
+  * If there are any wordbreaking characters in |str| within -/+5 characters of
+- * of a |pos| then the hyphen is inserted there instead, in order to produce a
+- * "cleaner" break.
+- */
+-function hyphenateWord (str, pos, hyphen)
+-{
+-    if (str.length <= pos)
+-        return str;
+-    if (typeof hyphen == "undefined")
+-        hyphen = " ";
+-
+-    /* search for a nice place to break the word, fuzzfactor of +/-5, centered
+-     * around |pos| */
+-    var splitPos =
+-        str.substring(pos - 5, pos + 5).search(/[^A-Za-z0-9]/);
+-
+-    splitPos = (splitPos != -1) ? pos - 4 + splitPos : pos;
+-    var left = str.substr (0, splitPos);
+-    var right = hyphenateWord(str.substr (splitPos), pos, hyphen);
+-
+-    return left + hyphen + right;
+-}
+-
+-/*
+- * Like hyphenateWord, except individual chunks of the word are returned as
+- * elements of an array.
++ * of a |pos| then the word is broken up there. Individual chunks of the word
++ * are returned as elements of an array.
+  */
+ function splitLongWord (str, pos)
+ {
+     if (str.length <= pos)
+         return [str];
+ 
+     var ary = new Array();
+     var right = str;
+@@ -832,43 +762,16 @@ function splitLongWord (str, pos)
+         right = right.substr (splitPos);
+     }
+ 
+     ary.push (right);
+ 
+     return ary;
+ }
+ 
+-function getRandomElement (ary)
+-{
+-
+-    return ary[Math.floor(Math.random() * ary.length)];
+-
+-}
+-
+-function roundTo (num, prec)
+-{
+-
+-    return Math.round(num * Math.pow (10, prec)) / Math.pow (10, prec);
+-
+-}
+-
+-function randomRange (min, max)
+-{
+-
+-    if (typeof min == "undefined")
+-        min = 0;
+-
+-    if (typeof max == "undefined")
+-        max = 1;
+-
+-    return Math.floor(Math.random() * (max - min + 1)) + min;
+-
+-}
+-
+ // Creates a random string of |len| characters from a-z, A-Z, 0-9.
+ function randomString(len) {
+     var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+     var rv = "";
+ 
+     for (var i = 0; i < len; i++)
+         rv += chars.substr(Math.floor(Math.random() * chars.length), 1);
+ 
+@@ -886,76 +789,16 @@ function getStackTrace ()
+         str += "\n" + name + "@" + frame.lineNumber;
+         frame = frame.caller;
+     }
+ 
+     return str;
+ 
+ }
+ 
+-function getInterfaces (cls)
+-{
+-    var rv = new Object();
+-    var e;
+-
+-    for (var i in Components.interfaces)
+-    {
+-        try
+-        {
+-            var ifc = Components.interfaces[i];
+-            cls.QueryInterface(ifc);
+-            rv[i] = ifc;
+-        }
+-        catch (e)
+-        {
+-            /* nada */
+-        }
+-    }
+-
+-    return rv;
+-
+-}
+-
+-/**
+- * Calls a named function for each element in an array, sending
+- * the same parameter each call.
+- *
+- * @param ary           an array of objects
+- * @param func_name     string name of function to call.
+- * @param data          data object to pass to each object.
+- */
+-function mapObjFunc(ary, func_name, data)
+-{
+-    /*
+-     * WARNING: Caller assumes resonsibility to verify ary
+-     * and func_name
+-     */
+-
+-    for (var i in ary)
+-        ary[i][func_name](data);
+-}
+-
+-/**
+- * Passes each element of an array to a given function object.
+- *
+- * @param func  a function object.
+- * @param ary   an array of values.
+- */
+-function map(func, ary) {
+-
+-    /*
+-     * WARNING: Caller assumnes responsibility to verify
+-     * func and ary.
+-     */
+-
+-    for (var i in ary)
+-        func(ary[i]);
+-
+-}
+-
+ function getSpecialDirectory(name)
+ {
+     if (!("directoryService" in utils))
+     {
+         const DS_CTR = "@mozilla.org/file/directory_service;1";
+         const nsIProperties = Components.interfaces.nsIProperties;
+ 
+         utils.directoryService =
+@@ -1236,23 +1079,16 @@ function isinstance(inst, base)
+      * 254067 which makes instanceof fail if the two sides are 'from'
+      * different windows (something we don't care about).
+      */
+     return (inst && base &&
+             ((inst instanceof base) ||
+              (inst.constructor && (inst.constructor.name == base.name))));
+ }
+ 
+-function isDefaultPrevented(ev)
+-{
+-    if ("defaultPrevented" in ev)
+-        return ev.defaultPrevented;
+-    return ev.getPreventDefault();
+-}
+-
+ function scaleNumberBy1024(number)
+ {
+     var scale = 0;
+     while ((number >= 1000) && (scale < 6))
+     {
+         scale++;
+         number /= 1024;
+     }

+ 402 - 0
comm-release/patches/1923224-irc-remove-arrayContains-25320.patch

@@ -0,0 +1,402 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1728340974 -3600
+# Parent  7fa51d46dbf54541781764815d0e18a2a77f74e4
+Bug 1923224 - Switch from using arrayContains helper to using JS Array includes method. r=frg a=frg
+
+diff --git a/suite/extensions/irc/js/lib/irc.js b/suite/extensions/irc/js/lib/irc.js
+--- a/suite/extensions/irc/js/lib/irc.js
++++ b/suite/extensions/irc/js/lib/irc.js
+@@ -2470,17 +2470,17 @@ function my_cap (e)
+         // A capability is no longer available.
+         var caps = e.params[3].split(/\s+/);
+         var caps_nodel = ["sts"];
+         for (var i = 0; i < caps.length; i++)
+         {
+             var cap = caps[i].split(/=(.+)/)[0];
+             cap = cap.trim();
+ 
+-            if (arrayContains(caps_nodel, cap))
++            if (caps_nodel.includes(cap))
+                 continue;
+ 
+             this.caps[cap] = null;
+         }
+     }
+     else
+     {
+         dd("Unknown CAP reply " + e.params[2]);
+@@ -2786,17 +2786,17 @@ function serv_chanmode (e)
+         {
+             // Get the data in case we need it, but don't increment the counter.
+             var datacounter = BASE_PARAM + params_eaten;
+             var data = (datacounter in e.params) ? e.params[datacounter] : null;
+             canonicalModeValue = modeMap[mode_str[i]].getValue(modifier, data);
+             e.channel.mode[modeMap[mode_str[i]].name] = canonicalModeValue;
+         }
+ 
+-        if (arrayContains(cmList.a, mode_str[i]))
++        if (cmList.a.includes(mode_str[i]))
+         {
+             var data = e.params[BASE_PARAM + params_eaten++];
+             if (modifier == "+")
+             {
+                 e.channel.mode.modeA[data] = true;
+             }
+             else
+             {
+@@ -2806,42 +2806,42 @@ function serv_chanmode (e)
+                 }
+                 else
+                 {
+                     dd("** Trying to remove channel mode '" + mode_str[i] +
+                        "'/'" + data + "' which does not exist in list.");
+                 }
+             }
+         }
+-        else if (arrayContains(cmList.b, mode_str[i]))
++        else if (cmList.b.includes(mode_str[i]))
+         {
+             var data = e.params[BASE_PARAM + params_eaten++];
+             if (modifier == "+")
+             {
+                 e.channel.mode.modeB[mode_str[i]] = data;
+             }
+             else
+             {
+                 // Save 'null' even though we have some data.
+                 e.channel.mode.modeB[mode_str[i]] = null;
+             }
+         }
+-        else if (arrayContains(cmList.c, mode_str[i]))
++        else if (cmList.c.includes(mode_str[i]))
+         {
+             if (modifier == "+")
+             {
+                 var data = e.params[BASE_PARAM + params_eaten++];
+                 e.channel.mode.modeC[mode_str[i]] = data;
+             }
+             else
+             {
+                 e.channel.mode.modeC[mode_str[i]] = null;
+             }
+         }
+-        else if (arrayContains(cmList.d, mode_str[i]))
++        else if (cmList.d.includes(mode_str[i]))
+         {
+             e.channel.mode.modeD[mode_str[i]] = (modifier == "+");
+         }
+         else
+         {
+             dd("** UNKNOWN mode symbol '" + mode_str[i] + "' in ChanMode event **");
+         }
+     }
+@@ -3002,22 +3002,22 @@ function serv_join(e)
+         // Between 1s - 3s.
+         setTimeout(delayFn1, 1000 + 2000 * Math.random(), this);
+ 
+         var delayFn2 = function(t) {
+             if (!e.channel.active)
+                 return;
+ 
+             // Get a full list of bans and exceptions, if supported.
+-            if (arrayContains(t.channelModes.a, "b"))
++            if (t.channelModes.a.includes("b"))
+             {
+                 e.server.sendData("MODE " + e.channel.encodedName + " +b\n");
+                 e.channel.pendingBanList = true;
+             }
+-            if (arrayContains(t.channelModes.a, "e"))
++            if (t.channelModes.a.includes("e"))
+             {
+                 e.server.sendData("MODE " + e.channel.encodedName + " +e\n");
+                 e.channel.pendingExceptList = true;
+             }
+ 
+             //If away-notify is active, query the list of users for away status.
+             if (e.server.caps["away-notify"])
+             {
+@@ -3632,17 +3632,17 @@ function chan_userslen (mode)
+             if (this.users[p].isVoice)
+                 this.voiceCount++;
+             i++;
+         }
+     }
+     else
+     {
+         for (p in this.users)
+-            if (arrayContains(this.users[p].modes, mode))
++            if (this.users[p].modes.includes(mode))
+                 i++;
+     }
+ 
+     return i;
+ }
+ 
+ CIRCChannel.prototype.iAmOp =
+ function chan_amop()
+@@ -4085,40 +4085,35 @@ function CIRCChanUser(parent, unicodeNam
+                 // We have a +/- mode list, so carefully update the mode list.
+                 for (var m in modes)
+                 {
+                     // This will remove '-' modes, and all other modes will be
+                     // added.
+                     var mode = modes[m][1];
+                     if (modes[m][0] == "-")
+                     {
+-                        if (arrayContains(existingUser.modes, mode))
++                        if (existingUser.modes.includes(mode))
+                         {
+                             var i = arrayIndexOf(existingUser.modes, mode);
+                             arrayRemoveAt(existingUser.modes, i);
+                         }
+                     }
+                     else
+                     {
+-                        if (!arrayContains(existingUser.modes, mode))
++                        if (!existingUser.modes.includes(mode))
+                             existingUser.modes.push(mode);
+                     }
+                 }
+             }
+         }
+-        existingUser.isFounder = (arrayContains(existingUser.modes, "q")) ?
+-            true : false;
+-        existingUser.isAdmin = (arrayContains(existingUser.modes, "a")) ?
+-            true : false;
+-        existingUser.isOp = (arrayContains(existingUser.modes, "o")) ?
+-            true : false;
+-        existingUser.isHalfOp = (arrayContains(existingUser.modes, "h")) ?
+-            true : false;
+-        existingUser.isVoice = (arrayContains(existingUser.modes, "v")) ?
+-            true : false;
++        existingUser.isFounder = existingUser.modes.includes("q");
++        existingUser.isAdmin = existingUser.modes.includes("a");
++        existingUser.isOp = existingUser.modes.includes("o");
++        existingUser.isHalfOp = existingUser.modes.includes("h");
++        existingUser.isVoice = existingUser.modes.includes("v");
+         existingUser.updateSortName();
+         return existingUser;
+     }
+ 
+     var protoUser = new CIRCUser(parent.parent, unicodeName, encodedName, name, host);
+ 
+     this.__proto__ = protoUser;
+     this.getURL = cusr_geturl;
+@@ -4134,21 +4129,21 @@ function CIRCChanUser(parent, unicodeNam
+     this.whois = cusr_whois;
+     this.updateSortName = cusr_updatesortname;
+     this.parent = parent;
+     this.TYPE = "IRCChanUser";
+ 
+     this.modes = new Array();
+     if (typeof modes != "undefined")
+         this.modes = modes;
+-    this.isFounder = (arrayContains(this.modes, "q")) ? true : false;
+-    this.isAdmin = (arrayContains(this.modes, "a")) ? true : false;
+-    this.isOp = (arrayContains(this.modes, "o")) ? true : false;
+-    this.isHalfOp = (arrayContains(this.modes, "h")) ? true : false;
+-    this.isVoice = (arrayContains(this.modes, "v")) ? true : false;
++    this.isFounder = this.modes.includes("q");
++    this.isAdmin = this.modes.includes("a");
++    this.isOp = this.modes.includes("o");
++    this.isHalfOp = this.modes.includes("h");
++    this.isVoice = this.modes.includes("v");
+     this.updateSortName();
+ 
+     if (userInChannel)
+         parent.users[this.collectionKey] = this;
+ 
+     return this;
+ }
+ 
+diff --git a/suite/extensions/irc/js/lib/menu-manager.js b/suite/extensions/irc/js/lib/menu-manager.js
+--- a/suite/extensions/irc/js/lib/menu-manager.js
++++ b/suite/extensions/irc/js/lib/menu-manager.js
+@@ -58,17 +58,17 @@ function mmgr_initcx (document, id)
+ 
+         var dp = document.getElementById("dynamic-popups");
+         var popup = this.appendPopupMenu (dp, null, id, id);
+         var items = this.menuSpecs[id].items;
+         this.createMenuItems (popup, null, items);
+ 
+         if (!("uiElements" in this.menuSpecs[id]))
+             this.menuSpecs[id].uiElements = [popup];
+-        else if (!arrayContains(this.menuSpecs[id].uiElements, popup))
++        else if (!this.menuSpecs[id].uiElements.includes(popup))
+             this.menuSpecs[id].uiElements.push(popup);
+     }
+ }
+ 
+ 
+ MenuManager.prototype.createMenus =
+ function mmgr_createtb(document, menuid)
+ {
+@@ -793,17 +793,17 @@ function mmgr_newmenu (parentNode, befor
+ 
+     var subMenu = this.appendSubMenu(parentNode, beforeNode, menuName, domId,
+                                      menuSpec.label, menuSpec.accesskey,
+                                      attribs);
+ 
+     // Keep track where we're adding popup nodes derived from some menuSpec
+     if (!("uiElements" in this.menuSpecs[menuName]))
+         this.menuSpecs[menuName].uiElements = [subMenu];
+-    else if (!arrayContains(this.menuSpecs[menuName].uiElements, subMenu))
++    else if (!this.menuSpecs[menuName].uiElements.includes(subMenu))
+         this.menuSpecs[menuName].uiElements.push(subMenu);
+ 
+     this.createMenuItems (subMenu, null, menuSpec.items);
+     return subMenu;
+ }
+ 
+ MenuManager.prototype.createMenuItems =
+ function mmgr_newitems (parentNode, beforeNode, menuItems)
+diff --git a/suite/extensions/irc/js/lib/utils.js b/suite/extensions/irc/js/lib/utils.js
+--- a/suite/extensions/irc/js/lib/utils.js
++++ b/suite/extensions/irc/js/lib/utils.js
+@@ -691,21 +691,16 @@ function formatDateOffset (offset, forma
+     return format;
+ }
+ 
+ function arrayHasElementAt(ary, i)
+ {
+     return typeof ary[i] != "undefined";
+ }
+ 
+-function arrayContains (ary, elem)
+-{
+-    return (arrayIndexOf (ary, elem) != -1);
+-}
+-
+ function arrayIndexOf (ary, elem)
+ {
+     for (var i in ary)
+         if (ary[i] == elem)
+             return i;
+ 
+     return -1;
+ }
+diff --git a/suite/extensions/irc/xul/content/commands.js b/suite/extensions/irc/xul/content/commands.js
+--- a/suite/extensions/irc/xul/content/commands.js
++++ b/suite/extensions/irc/xul/content/commands.js
+@@ -1118,17 +1118,17 @@ function cmdChanUserMode(e)
+         var adding = modestr[0] == "+";
+         for (userKey in e.channel.users)
+         {
+             var user = e.channel.users[userKey];
+             /* Never change our own mode and avoid trying to change someone
+              * else in a no-op manner (e.g. voicing an already voiced user).
+              */
+             if ((user.encodedName != me.encodedName) &&
+-                (arrayContains(user.modes, mode) ^ adding))
++                (user.modes.includes(mode) ^ adding))
+             {
+                 nickList.push(user.encodedName);
+             }
+         }
+         nicks = combineNicks(nickList);
+     }
+     else if (e.nicknameList)
+     {
+@@ -1977,17 +1977,17 @@ function cmdMode(e)
+     {
+         chan = fromUnicode(e.target, e.server);
+     }
+ 
+     // Check whether our mode string makes sense
+     if (!e.modestr)
+     {
+         e.modestr = "";
+-        if (!e.channel && arrayContains(e.server.channelTypes, chan[0]))
++        if (!e.channel && e.server.channelTypes.includes(chan[0]))
+             e.channel = new CIRCChannel(e.server, null, chan);
+         if (e.channel)
+             e.channel.pendingModeReply = true;
+     }
+     else if (!(/^([+-][a-z]+)+$/i).test(e.modestr))
+     {
+         display(getMsg(MSG_ERR_INVALID_MODE, e.modestr), MT_ERROR);
+         return;
+diff --git a/suite/extensions/irc/xul/content/handlers.js b/suite/extensions/irc/xul/content/handlers.js
+--- a/suite/extensions/irc/xul/content/handlers.js
++++ b/suite/extensions/irc/xul/content/handlers.js
+@@ -1314,35 +1314,35 @@ function my_303 (e)
+     var displayTab;
+     var i;
+ 
+     if ("network" in o && o.network == this && client.currentObject != this)
+         displayTab = client.currentObject;
+ 
+     for (i = 0; i < this.prefs["notifyList"].length; i++)
+     {
+-        if (!arrayContains(onList, lower(this.prefs["notifyList"][i])))
++        if (!onList.includes(lower(this.prefs["notifyList"][i])))
+             /* user is not on */
+             offList.push(lower(this.prefs["notifyList"][i]));
+     }
+ 
+     if ("onList" in this)
+     {
+         for (i in onList)
+-            if (!arrayContains(this.onList, onList[i]))
++            if (!this.onList.includes(onList[i]))
+                 /* we didn't know this person was on */
+                 newArrivals.push(onList[i]);
+     }
+     else
+         this.onList = newArrivals = onList;
+ 
+     if ("offList" in this)
+     {
+         for (i in offList)
+-            if (!arrayContains(this.offList, offList[i]))
++            if (!this.offList.includes(offList[i]))
+                 /* we didn't know this person was off */
+                 newDepartures.push(offList[i]);
+     }
+     else
+         this.offList = newDepartures = offList;
+ 
+     if (newArrivals.length > 0)
+     {
+@@ -2577,17 +2577,17 @@ function my_cap(e)
+     }
+     else if (e.params[2] == "NAK")
+     {
+         this.display(getMsg(MSG_CAPS_ERROR, e.caps.join(", ")));
+     }
+     else if (e.params[2] == "NEW")
+     {
+         // Handle a new STS policy
+-        if (client.sts.ENABLED && (arrayContains(e.newcaps, "sts")))
++        if (client.sts.ENABLED && e.newcaps.includes("sts"))
+         {
+             var policy = client.sts.parseParameters(e.server.capvals["sts"]);
+             if (!e.server.isSecure && policy.port)
+             {
+                 // Inform the user of the new upgrade policy and
+                 // offer an option to reconnect.
+                 client.munger.getRule(".inline-buttons").enabled = true;
+                 this.display(getMsg(MSG_STS_UPGRADE_NEW, [this.unicodeName, "reconnect"]));
+diff --git a/suite/extensions/irc/xul/content/static.js b/suite/extensions/irc/xul/content/static.js
+--- a/suite/extensions/irc/xul/content/static.js
++++ b/suite/extensions/irc/xul/content/static.js
+@@ -1226,17 +1226,17 @@ function ensureCachedCanonicalURLs(array
+ }
+ 
+ function isStartupURL(url)
+ {
+     // We canonicalize all URLs before we do the (string) comparison.
+     url = makeCanonicalIRCURL(url);
+     var list = client.prefs["initialURLs"];
+     ensureCachedCanonicalURLs(list);
+-    return arrayContains(list.canonicalURLs, url);
++    return list.canonicalURLs.includes(url);
+ }
+ 
+ function cycleView(amount)
+ {
+     var len = client.viewsArray.length;
+     if (len <= 1)
+         return;
+ 

+ 468 - 0
comm-release/patches/1923225-irc-remove-arrayIndexOf-25320.patch

@@ -0,0 +1,468 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1728341054 -3600
+# Parent  9c3a35181b22810c3701964daa83079fc6dd39f2
+Bug 1923225 - Switch from using arrayIndexOf helper to using JS Array includes and indexOf methods. r=frg a=frg
+
+diff --git a/suite/extensions/irc/js/lib/dcc.js b/suite/extensions/irc/js/lib/dcc.js
+--- a/suite/extensions/irc/js/lib/dcc.js
++++ b/suite/extensions/irc/js/lib/dcc.js
+@@ -127,36 +127,36 @@ function dcc_getmatches(nickname, filena
+     var k;
+     var list = new Array();
+     if (!types)
+         types = ["chat", "file"];
+ 
+     var n = nickname;
+     var f = filename;
+ 
+-    if (arrayIndexOf(types, "chat") >= 0)
++    if (types.includes("chat"))
+     {
+         for (k = 0; k < this.chats.length; k++)
+         {
+             if ((!nickname || matchNames(this.chats[k].user.unicodeName, n)) &&
+-                (!dirs || arrayIndexOf(dirs, this.chats[k].state.dir) >= 0) &&
+-                (!states || arrayIndexOf(states, this.chats[k].state.state) >= 0))
++                (!dirs || dirs.includes(this.chats[k].state.dir)) &&
++                (!states || states.includes(this.chats[k].state.state)))
+             {
+                 list.push(this.chats[k]);
+             }
+         }
+     }
+-    if (arrayIndexOf(types, "file") >= 0)
++    if (types.includes("file"))
+     {
+         for (k = 0; k < this.files.length; k++)
+         {
+             if ((!nickname || matchNames(this.files[k].user.unicodeName, n)) &&
+                 (!filename || matchNames(this.files[k].filename, f)) &&
+-                (!dirs || arrayIndexOf(dirs, this.files[k].state.dir) >= 0) &&
+-                (!states || arrayIndexOf(states, this.files[k].state.state) >= 0))
++                (!dirs || dirs.includes(this.files[k].state.dir)) &&
++                (!states || states.includes(this.files[k].state.state)))
+             {
+                 list.push(this.files[k]);
+             }
+         }
+     }
+ 
+     return list;
+ }
+diff --git a/suite/extensions/irc/js/lib/irc.js b/suite/extensions/irc/js/lib/irc.js
+--- a/suite/extensions/irc/js/lib/irc.js
++++ b/suite/extensions/irc/js/lib/irc.js
+@@ -405,17 +405,17 @@ function net_doconnect(e)
+             else
+             {
+                 var hostname = this.serverList[host].hostname;
+                 var matches = this.serverList.filter(function(s) {
+                     return  s.hostname == hostname && s.port == newPort;
+                 });
+                 if (matches.length > 0)
+                 {
+-                    host = arrayIndexOf(this.serverList, matches[0]);
++                    host = this.serverList.indexOf(matches[0]);
+                 }
+                 else
+                 {
+                     this.addServer(hostname, newPort, true,
+                                     this.serverList[host].password);
+                     host = this.serverList.length - 1;
+                 }
+             }
+@@ -1040,17 +1040,17 @@ function serv_monitorlist(nicks, isAdd)
+         this.sendData(prefix + nicks_part + "\n");
+     }
+     this.sendData(prefix + nicks_string + "\n");
+ }
+ 
+ CIRCServer.prototype.addTarget =
+ function serv_addtarget(name)
+ {
+-    if (arrayIndexOf(this.channelTypes, name[0]) != -1) {
++    if (this.channelTypes.includes(name[0])) {
+         return this.addChannel(name);
+     } else {
+         return this.addUser(name);
+     }
+ }
+ 
+ CIRCServer.prototype.addChannel =
+ function serv_addchan(unicodeName, charset)
+@@ -2687,17 +2687,17 @@ function serv_chghost(e)
+ }
+ 
+ /* user changed the mode */
+ CIRCServer.prototype.onMode =
+ function serv_mode (e)
+ {
+     e.destObject = this;
+     /* modes are not allowed in +channels -> no need to test that here.. */
+-    if (arrayIndexOf(this.channelTypes, e.params[1][0]) != -1)
++    if (this.channelTypes.includes(e.params[1][0]))
+     {
+         e.channel = new CIRCChannel(this, null, e.params[1]);
+         if ("user" in e && e.user)
+             e.user = new CIRCChanUser(e.channel, e.user.unicodeName);
+         e.type = "chanmode";
+         e.destMethod = "onChanMode";
+     }
+     else
+@@ -3116,17 +3116,17 @@ function serv_notice_privmsg (e)
+                 targetName = targetName.substr(1);
+                 break;
+             }
+         }
+     }
+ 
+     /* setting replyTo provides a standard place to find the target for     */
+     /* replies associated with this event.                                  */
+-    if (arrayIndexOf(this.channelTypes, targetName[0]) != -1)
++    if (this.channelTypes && this.channelTypes.includes(targetName[0]))
+     {
+         e.channel = new CIRCChannel(this, null, targetName);
+         if ("user" in e)
+             e.user = new CIRCChanUser(e.channel, e.user.unicodeName);
+         e.replyTo = e.channel;
+         e.set = "channel";
+     }
+     else if (!("user" in e))
+@@ -3553,17 +3553,17 @@ CIRCChannel.prototype.topic = "";
+ // Returns the IRC URL representation of this channel.
+ CIRCChannel.prototype.getURL =
+ function chan_geturl()
+ {
+     var target = this.encodedName;
+     var flags = this.mode.key ? ["needkey"] : [];
+ 
+     if ((target[0] == "#") && (target.length > 1) &&
+-        arrayIndexOf(this.parent.channelTypes, target[1]) == -1)
++        !this.parent.channelTypes.includes(target[1]))
+     {
+         /* First character is "#" (which we're allowed to omit), and the
+          * following character is NOT a valid prefix, so it's safe to remove.
+          */
+         target = target.substr(1);
+     }
+     return this.parent.parent.getURL(target, flags);
+ }
+@@ -4087,17 +4087,17 @@ function CIRCChanUser(parent, unicodeNam
+                 {
+                     // This will remove '-' modes, and all other modes will be
+                     // added.
+                     var mode = modes[m][1];
+                     if (modes[m][0] == "-")
+                     {
+                         if (existingUser.modes.includes(mode))
+                         {
+-                            var i = arrayIndexOf(existingUser.modes, mode);
++                            var i = existingUser.modes.indexOf(mode);
+                             arrayRemoveAt(existingUser.modes, i);
+                         }
+                     }
+                     else
+                     {
+                         if (!existingUser.modes.includes(mode))
+                             existingUser.modes.push(mode);
+                     }
+diff --git a/suite/extensions/irc/js/lib/message-manager.js b/suite/extensions/irc/js/lib/message-manager.js
+--- a/suite/extensions/irc/js/lib/message-manager.js
++++ b/suite/extensions/irc/js/lib/message-manager.js
+@@ -101,17 +101,17 @@ function mm_importbundle(bundle, targetW
+         return matched;
+     };
+     const nsIPropertyElement = Components.interfaces.nsIPropertyElement;
+ 
+     if (!targetWindow)
+         targetWindow = window;
+ 
+     if (typeof index == "undefined")
+-        index = arrayIndexOf(this.bundleList, bundle);
++        index = this.bundleList.indexOf(bundle);
+     
+     var pfx;
+     if (index == 0)
+         pfx = "";
+     else
+         pfx = index + ":";
+ 
+     var enumer = bundle.getSimpleEnumeration();
+diff --git a/suite/extensions/irc/js/lib/utils.js b/suite/extensions/irc/js/lib/utils.js
+--- a/suite/extensions/irc/js/lib/utils.js
++++ b/suite/extensions/irc/js/lib/utils.js
+@@ -691,25 +691,16 @@ function formatDateOffset (offset, forma
+     return format;
+ }
+ 
+ function arrayHasElementAt(ary, i)
+ {
+     return typeof ary[i] != "undefined";
+ }
+ 
+-function arrayIndexOf (ary, elem)
+-{
+-    for (var i in ary)
+-        if (ary[i] == elem)
+-            return i;
+-
+-    return -1;
+-}
+-
+ function arrayInsertAt (ary, i, o)
+ {
+     ary.splice (i, 0, o);
+ }
+ 
+ function arrayRemoveAt (ary, i)
+ {
+     ary.splice (i, 1);
+diff --git a/suite/extensions/irc/xul/content/channels.js b/suite/extensions/irc/xul/content/channels.js
+--- a/suite/extensions/irc/xul/content/channels.js
++++ b/suite/extensions/irc/xul/content/channels.js
+@@ -663,17 +663,17 @@ function processOpFilterStart(opData)
+     if (opData.searchTopics)
+         filters.push("topics");
+     if (opData.minUsers)
+         filters.push("min-users");
+     if (opData.maxUsers)
+         filters.push("max-users");
+ 
+     if (opData.channelText &&
+-        (arrayIndexOf(["#", "&", "+", "!"], opData.channelText[0]) == -1))
++        !["#", "&", "+", "!"].includes(opData.channelText[0]))
+     {
+         opData.channelText = "#" + opData.channelText;
+     }
+     else
+     {
+         // Log that user has specified an explicit prefix.
+         filters.push("prefix");
+     }
+diff --git a/suite/extensions/irc/xul/content/commands.js b/suite/extensions/irc/xul/content/commands.js
+--- a/suite/extensions/irc/xul/content/commands.js
++++ b/suite/extensions/irc/xul/content/commands.js
+@@ -2431,18 +2431,18 @@ function cmdJoin(e)
+                                           server: e.server,
+                                           charset: e.charset,
+                                           channelName: chans[c],
+                                           key: keys.shift() });
+             }
+             return chan;
+         }
+ 
+-        if ((arrayIndexOf(["#", "&", "+", "!"], e.channelName[0]) == -1) &&
+-            (arrayIndexOf(e.server.channelTypes, e.channelName[0]) == -1))
++        if (!["#", "&", "+", "!"].includes(e.channelName[0]) &&
++            !e.server.channelTypes.includes(e.channelName[0]))
+         {
+             e.channelName = e.server.channelTypes[0] + e.channelName;
+         }
+ 
+         var charset = e.charset ? e.charset : e.network.prefs["charset"];
+         chan = e.server.addChannel(e.channelName, charset);
+         if (e.charset)
+             chan.prefs["charset"] = e.charset;
+@@ -2476,17 +2476,17 @@ function cmdLeave(e)
+         // channel names. If we discover that we were passed an invalid channel
+         // name, but have a channel on the event, we'll just leave that channel
+         // with the full message (including what we thought was a channel name)
+         // and return false in order to not process the rest of what we thought
+         // was a channel name. If there's a genuine error, e.g. because the user
+         // specified a non-existing channel and isn't in a channel either, we
+         // will also return a falsy value
+         var shouldContinue = true;
+-        if (arrayIndexOf(e.server.channelTypes, channelName[0]) == -1)
++        if (!e.server.channelTypes.includes(channelName[0]))
+         {
+             // No valid prefix character. Check they really meant a channel...
+             var valid = false;
+             for (var i = 0; i < e.server.channelTypes.length; i++)
+             {
+                 // Hmm, not ideal...
+                 var chan = e.server.getChannel(e.server.channelTypes[i] +
+                                                channelName);
+@@ -3017,17 +3017,17 @@ function cmdAway(e)
+ }
+ 
+ function cmdOpenAtStartup(e)
+ {
+     var origURL = e.sourceObject.getURL();
+     var url = makeCanonicalIRCURL(origURL);
+     var list = client.prefs["initialURLs"];
+     ensureCachedCanonicalURLs(list);
+-    var index = arrayIndexOf(list.canonicalURLs, url);
++    var index = list.canonicalURLs.indexOf(url);
+ 
+     if (e.toggle == null)
+     {
+         if (index == -1)
+             display(getMsg(MSG_STARTUP_NOTFOUND, url));
+         else
+             display(getMsg(MSG_STARTUP_EXISTS, url));
+         return;
+@@ -3360,17 +3360,17 @@ function cmdNotify(e)
+         var adds = new Array();
+         var subs = new Array();
+ 
+         for (var i in e.nicknameList)
+         {
+             var nickname = e.server.toLowerCase(e.nicknameList[i]);
+             var list = net.prefs["notifyList"];
+             list = e.server.toLowerCase(list.join(";")).split(";");
+-            var idx = arrayIndexOf (list, nickname);
++            var idx = list.indexOf(nickname);
+             if (idx == -1)
+             {
+                 net.prefs["notifyList"].push (nickname);
+                 adds.push(nickname);
+             }
+             else
+             {
+                 arrayRemoveAt (net.prefs["notifyList"], idx);
+@@ -4356,18 +4356,17 @@ function cmdDCCAutoAcceptAdd(e)
+         return display(MSG_DCC_NOT_ENABLED);
+ 
+     var list = e.network.prefs["dcc.autoAccept.list"];
+ 
+     if (!e.user && e.server)
+         e.user = e.server.getUser(e.nickname);
+ 
+     var mask = e.user ? "*!" + e.user.name + "@" + e.user.host : e.nickname;
+-    var index = arrayIndexOf(list, mask);
+-    if (index == -1)
++    if (!list.includes(mask))
+     {
+         list.push(mask);
+         list.update();
+         display(getMsg(MSG_DCCACCEPT_ADD, mask));
+     }
+     else
+     {
+         display(getMsg(MSG_DCCACCEPT_ADDERR,
+diff --git a/suite/extensions/irc/xul/content/handlers.js b/suite/extensions/irc/xul/content/handlers.js
+--- a/suite/extensions/irc/xul/content/handlers.js
++++ b/suite/extensions/irc/xul/content/handlers.js
+@@ -1007,17 +1007,17 @@ function my_unknown (e)
+ 
+     e.params.shift(); /* remove the code */
+     e.params.shift(); /* and the dest. nick (always me) */
+ 
+     // Handle random IRC numerics automatically.
+     var msg = getMsg("msg.irc." + e.code, null, "");
+     if (msg)
+     {
+-        if (arrayIndexOf(e.server.channelTypes, e.params[0][0]) != -1)
++        if (e.server.channelTypes.includes(e.params[0][0]))
+         {
+             // Message about a channel (e.g. join failed).
+             e.channel = new CIRCChannel(e.server, null, e.params[0]);
+         }
+ 
+         var targetDisplayObj = this;
+         if (e.channel && ("messages" in e.channel))
+             targetDisplayObj = e.channel;
+@@ -1722,17 +1722,17 @@ function my_401(e)
+ {
+     var server, channel, user;
+ 
+     /* Note that servers generally only send 401 and 402, sharing the former
+      * between nicknames and channels, but we're ready for anything.
+      */
+     if (e.code == 402)
+         server = e.decodeParam(2);
+-    else if (arrayIndexOf(e.server.channelTypes, e.params[2][0]) != -1)
++    else if (e.server.channelTypes.includes(e.params[2][0]))
+         channel = new CIRCChannel(e.server, null, e.params[2]);
+     else
+         user = new CIRCUser(e.server, null, e.params[2]);
+ 
+     if (user && this.whoisList && (user.collectionKey in this.whoisList))
+     {
+         // If this is from a /whois, send a /whowas and don't display anything.
+         this.primServ.whowas(user.unicodeName, 1);
+@@ -2029,18 +2029,17 @@ function my_433 (e)
+     if ("pendingReclaimCheck" in this)
+     {
+         delete this.pendingReclaimCheck;
+         return;
+     }
+ 
+     if (this.state == NET_CONNECTING)
+     {
+-        // Force a number, thanks.
+-        var nickIndex = 1 * arrayIndexOf(this.prefs["nicknameList"], nick);
++        var nickIndex = this.prefs["nicknameList"].indexOf(nick);
+         var newnick = null;
+ 
+         dd("433: failed with " + nick + " (" + nickIndex + ")");
+ 
+         var tryList = true;
+ 
+         if ((("_firstNick" in this) && (this._firstNick == -1)) ||
+             (this.prefs["nicknameList"].length == 0) ||
+diff --git a/suite/extensions/irc/xul/content/static.js b/suite/extensions/irc/xul/content/static.js
+--- a/suite/extensions/irc/xul/content/static.js
++++ b/suite/extensions/irc/xul/content/static.js
+@@ -1079,18 +1079,18 @@ function getViewsContext(cx)
+ 
+         var label = view.viewName;
+         if (!getTabForObject(view))
+             label = getMsg(MSG_VIEW_HIDDEN, [label]);
+ 
+         var types = ["IRCClient", "IRCNetwork", "IRCDCCChat",
+                      "IRCDCCFileTransfer"];
+         var typesNetwork = ["IRCNetwork", "IRCChannel", "IRCUser"];
+-        var group = String(arrayIndexOf(types, view.TYPE));
+-        if (arrayIndexOf(typesNetwork, view.TYPE) != -1)
++        var group = String(types.indexOf(view.TYPE));
++        if (typesNetwork.includes(view.TYPE))
+             group = "1-" + getObjectDetails(view).network.viewName;
+ 
+         var sort = group + "-" + view.viewName;
+         if (view.TYPE == "IRCNetwork")
+             sort = group;
+ 
+         cx.views.push({url: url, label: label, group: group, sort: sort});
+         urls[url] = true
+@@ -1903,18 +1903,18 @@ function gotoIRCURL(url, e)
+                 // Must do this the hard way... we have the server's format
+                 // for the channel name here, and all our commands only work
+                 // with the Unicode forms.
+ 
+                 /* If we don't have a valid prefix, stick a "#" on it.
+                  * NOTE: This is always a "#" so that URLs may be compared
+                  * properly without involving the server (e.g. off-line).
+                  */
+-                if ((arrayIndexOf(["#", "&", "+", "!"], target[0]) == -1) &&
+-                    (arrayIndexOf(serv.channelTypes, target[0]) == -1))
++                if (!["#", "&", "+", "!"].includes(target[0]) &&
++                    !serv.channelTypes.includes(target[0]))
+                 {
+                     target = "#" + target;
+                 }
+ 
+                 var chan = new CIRCChannel(serv, null, target);
+             }
+ 
+             if (url.needkey && !chan.joined)
+@@ -2628,17 +2628,17 @@ function advanceKeyboardFocus(amount)
+ 
+     var elem = document.commandDispatcher.focusedElement;
+     // Finding focus in the content window is "hard". It's going to be null
+     // if the window itself is focused, and "some element" inside of it if the
+     // user starts tabbing through.
+     if (!elem || (elem.ownerDocument == contentDoc))
+         elem = contentWin;
+ 
+-    var newIndex = (arrayIndexOf(focusableElems, elem) * 1 + 3 + amount) % 3;
++    var newIndex = (focusableElems.indexOf(elem) + 3 + amount) % 3;
+     focusableElems[newIndex].focus();
+ 
+     // Make it obvious this element now has focus.
+     var outlinedElem;
+     if (focusableElems[newIndex] == client.input.inputField)
+         outlinedElem = client.input.parentNode.id;
+     else if (focusableElems[newIndex] == userList)
+         outlinedElem = "user-list-box"

+ 313 - 0
comm-release/patches/1923227-irc-remove-arrayRemoveInsert-25320.patch

@@ -0,0 +1,313 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1728341623 -3600
+# Parent  a70fb847efda106985fa040558821b7ca8aaa52d
+Bug 1923227 - Switch from using arrayRemoveAt and arrayInsertAt helpers to using JS Array splice and unshift methods. r=frg a=frg
+
+diff --git a/suite/extensions/irc/js/lib/events.js b/suite/extensions/irc/js/lib/events.js
+--- a/suite/extensions/irc/js/lib/events.js
++++ b/suite/extensions/irc/js/lib/events.js
+@@ -180,31 +180,31 @@ CEventPump.prototype.removeHookByName =
+ function ep_remhookname(name, hooks)
+ {
+     if (typeof hooks == "undefined")
+         hooks = this.hooks;
+ 
+     for (var h in hooks)
+         if (hooks[h].name.toLowerCase() == name.toLowerCase())
+         {
+-            arrayRemoveAt (hooks, h);
++            hooks.splice(h, 1);
+             return true;
+         }
+ 
+     return false;
+ 
+ }
+ 
+ CEventPump.prototype.removeHookByIndex =
+ function ep_remhooki(idx, hooks)
+ {
+     if (typeof hooks == "undefined")
+         hooks = this.hooks;
+ 
+-    return arrayRemoveAt (hooks, idx);
++    return hooks.splice(idx, 1);
+ 
+ }
+ 
+ CEventPump.prototype.addEvent =
+ function ep_addevent (e)
+ {
+     e.queuedAt = new Date();
+     this.queue.push(e);
+diff --git a/suite/extensions/irc/js/lib/irc.js b/suite/extensions/irc/js/lib/irc.js
+--- a/suite/extensions/irc/js/lib/irc.js
++++ b/suite/extensions/irc/js/lib/irc.js
+@@ -1093,17 +1093,17 @@ function serv_senddata (msg)
+ }
+ 
+ CIRCServer.prototype.queuedSendData =
+ function serv_senddata (msg)
+ {
+     if (this.sendQueue.length == 0)
+         this.parent.eventPump.addEvent (new CEvent ("server", "senddata",
+                                                     this, "onSendData"));
+-    arrayInsertAt (this.sendQueue, 0, new String(msg));
++    this.sendQueue.unshift(new String(msg));
+ }
+ 
+ // Utility method for splitting large lines prior to sending.
+ CIRCServer.prototype.splitLinesForSending =
+ function serv_splitlines(line, prettyWrap)
+ {
+     let lines = String(line).split("\n");
+     let realLines = [];
+@@ -4085,20 +4085,20 @@ function CIRCChanUser(parent, unicodeNam
+                 // We have a +/- mode list, so carefully update the mode list.
+                 for (var m in modes)
+                 {
+                     // This will remove '-' modes, and all other modes will be
+                     // added.
+                     var mode = modes[m][1];
+                     if (modes[m][0] == "-")
+                     {
+-                        if (existingUser.modes.includes(mode))
++                        let idx = existingUser.modes.indexOf(mode);
++                        if (idx >= 0)
+                         {
+-                            var i = existingUser.modes.indexOf(mode);
+-                            arrayRemoveAt(existingUser.modes, i);
++                            existingUser.modes.splice(idx, 1);
+                         }
+                     }
+                     else
+                     {
+                         if (!existingUser.modes.includes(mode))
+                             existingUser.modes.push(mode);
+                     }
+                 }
+diff --git a/suite/extensions/irc/js/lib/pref-manager.js b/suite/extensions/irc/js/lib/pref-manager.js
+--- a/suite/extensions/irc/js/lib/pref-manager.js
++++ b/suite/extensions/irc/js/lib/pref-manager.js
+@@ -93,23 +93,20 @@ function pm_addobserver(observer)
+         throw "Bad observer!";
+ 
+     this.observers.push(observer);
+ }
+ 
+ PrefManager.prototype.removeObserver =
+ function pm_removeobserver(observer)
+ {
+-    for (var i = 0; i < this.observers.length; i++)
++    let idx = this.observers.indexOf(observer);
++    if (idx >= 0)
+     {
+-        if (this.observers[i] == observer)
+-        {
+-            arrayRemoveAt(this.observers, i);
+-            break;
+-        }
++        this.observers.splice(idx, 1);
+     }
+ }
+ 
+ PrefManager.prototype.delayedSave =
+ function pm_delayedsave()
+ {
+     // this.prefSaveTimer
+     var now = Number(new Date());
+diff --git a/suite/extensions/irc/js/lib/utils.js b/suite/extensions/irc/js/lib/utils.js
+--- a/suite/extensions/irc/js/lib/utils.js
++++ b/suite/extensions/irc/js/lib/utils.js
+@@ -691,26 +691,16 @@ function formatDateOffset (offset, forma
+     return format;
+ }
+ 
+ function arrayHasElementAt(ary, i)
+ {
+     return typeof ary[i] != "undefined";
+ }
+ 
+-function arrayInsertAt (ary, i, o)
+-{
+-    ary.splice (i, 0, o);
+-}
+-
+-function arrayRemoveAt (ary, i)
+-{
+-    ary.splice (i, 1);
+-}
+-
+ function objectContains(o, p)
+ {
+     return Object.hasOwnProperty.call(o, p);
+ }
+ 
+ /* length should be an even number >= 6 */
+ function abbreviateWord (str, length)
+ {
+diff --git a/suite/extensions/irc/xul/content/commands.js b/suite/extensions/irc/xul/content/commands.js
+--- a/suite/extensions/irc/xul/content/commands.js
++++ b/suite/extensions/irc/xul/content/commands.js
+@@ -2822,17 +2822,17 @@ function cmdAlias(e)
+         ary = getAlias(e.aliasName);
+         if (!ary)
+         {
+             display(getMsg(MSG_NOT_AN_ALIAS, e.aliasName), MT_ERROR);
+             return;
+         }
+ 
+         // Command Manager is updated when the preference changes.
+-        arrayRemoveAt(aliasDefs, ary[0]);
++        aliasDefs.splice(ary[0], 1);
+         aliasDefs.update();
+ 
+         feedback(e, getMsg(MSG_ALIAS_REMOVED, e.aliasName));
+     }
+     else if (e.aliasName && e.commandList)
+     {
+         /* add/change alias */
+         ary = getAlias(e.aliasName);
+@@ -3049,17 +3049,17 @@ function cmdOpenAtStartup(e)
+             display(getMsg(MSG_STARTUP_EXISTS, url));
+         }
+     }
+     else
+     {
+         // no, please don't open at startup
+         if (index != -1)
+         {
+-            arrayRemoveAt(list, index);
++            list.splice(index, 1);
+             list.update();
+             display(getMsg(MSG_STARTUP_REMOVED, url));
+         }
+         else
+         {
+             display(getMsg(MSG_STARTUP_NOTFOUND, url));
+         }
+     }
+@@ -3368,17 +3368,17 @@ function cmdNotify(e)
+             var idx = list.indexOf(nickname);
+             if (idx == -1)
+             {
+                 net.prefs["notifyList"].push (nickname);
+                 adds.push(nickname);
+             }
+             else
+             {
+-                arrayRemoveAt (net.prefs["notifyList"], idx);
++                net.prefs["notifyList"].splice(idx, 1);
+                 subs.push(nickname);
+             }
+         }
+         net.prefs["notifyList"].update();
+ 
+         var msgname;
+ 
+         if (adds.length > 0)
+diff --git a/suite/extensions/irc/xul/content/handlers.js b/suite/extensions/irc/xul/content/handlers.js
+--- a/suite/extensions/irc/xul/content/handlers.js
++++ b/suite/extensions/irc/xul/content/handlers.js
+@@ -1442,17 +1442,17 @@ function my_734(e)
+     var nickList = e.server.toLowerCase(e.params[3]).split(",")
+     var i;
+     var msgname;
+ 
+     for (i = 0; i < nickList.length; i++)
+     {
+         var j = this.prefs["notifyList"].indexOf(nickList[i]);
+         if (j >= 0)
+-            arrayRemoveAt(this.prefs["notifyList"], j);
++            this.prefs["notifyList"].splice(j, 1);
+     }
+     this.prefs["notifyList"].update();
+ 
+     if (e.params[4])
+         this.display(e.params[4], e.code, undefined, undefined, e.tags)
+     else
+         this.display(MSG_NOTIFY_FULL);
+ 
+diff --git a/suite/extensions/irc/xul/content/static.js b/suite/extensions/irc/xul/content/static.js
+--- a/suite/extensions/irc/xul/content/static.js
++++ b/suite/extensions/irc/xul/content/static.js
+@@ -3418,17 +3418,17 @@ function getTabForObject(source, create)
+             var tbAfter = tb.nextSibling;
+             while (tbAfter && tbAfter.collapsed && tbAfter.hidden)
+                 tbAfter = tbAfter.nextSibling;
+             if ((tabBefore == tb) || (tabBefore == tbAfter))
+                 return tb;
+         }
+ 
+         var viewKey = Number(tb.getAttribute("viewKey"));
+-        arrayRemoveAt(client.viewsArray, viewKey);
++        client.viewsArray.splice(viewKey, 1);
+         for (i = viewKey; i < client.viewsArray.length; i++)
+             client.viewsArray[i].tb.setAttribute("viewKey", i);
+         client.tabs.removeChild(tb);
+     }
+     else if (tb || (!tb && !create))
+     {
+         /* Either: we have a tab and don't want it moved, or didn't find one
+          * and don't wish to create one.
+@@ -3497,17 +3497,17 @@ function getTabForObject(source, create)
+          */
+         if (c.tabInsertBefore && (c.tabInsertBefore.parentNode == client.tabs))
+             beforeTab = c.tabInsertBefore;
+     }
+ 
+     if (beforeTab)
+     {
+         var viewKey = beforeTab.getAttribute("viewKey");
+-        arrayInsertAt(client.viewsArray, viewKey, {source: source, tb: tb});
++        client.viewsArray.splice(viewKey, 0, {source: source, tb: tb});
+         for (i = viewKey; i < client.viewsArray.length; i++)
+             client.viewsArray[i].tb.setAttribute("viewKey", i);
+         client.tabs.insertBefore(tb, beforeTab);
+     }
+     else
+     {
+         client.viewsArray.push({source: source, tb: tb});
+         tb.setAttribute("viewKey", client.viewsArray.length - 1);
+@@ -3771,17 +3771,17 @@ function deleteTab(tb)
+         return null;
+     }
+ 
+     var key = Number(tb.getAttribute("viewKey"));
+ 
+     // Re-index higher tabs.
+     for (var i = key + 1; i < client.viewsArray.length; i++)
+         client.viewsArray[i].tb.setAttribute("viewKey", i - 1);
+-    arrayRemoveAt(client.viewsArray, key);
++    client.viewsArray.splice(key, 1);
+     client.tabs.removeChild(tb);
+     setTimeout(updateTabAttributes, 0);
+ 
+     return key;
+ }
+ 
+ function deleteFrame(view)
+ {
+diff --git a/suite/extensions/irc/xul/lib/tree-utils.js b/suite/extensions/irc/xul/lib/tree-utils.js
+--- a/suite/extensions/irc/xul/lib/tree-utils.js
++++ b/suite/extensions/irc/xul/lib/tree-utils.js
+@@ -755,17 +755,17 @@ function xtvr_remchild(index)
+     var len = this.childData.length;
+     if (!ASSERT(index >= 0 && index < len, "index out of bounds"))
+         return;
+ 
+     var orphan = this.childData[index];
+     var delta = -orphan.visualFootprint;
+     var changeStart = orphan.calculateVisualRow();
+     delete orphan.parentRecord;
+-    arrayRemoveAt(this.childData, index);
++    this.childData.splice(index, 1);
+ 
+     if (!orphan.isHidden && "isContainerOpen" in this && this.isContainerOpen)
+         this.onVisualFootprintChanged(changeStart, delta);
+ }
+ 
+ /*
+  * Removes a range of children from this record by index. Faster than multiple
+  * removeChildAtIndex() calls.

+ 177 - 0
comm-release/patches/1923229-irc-remove-stringTrim-25320.patch

@@ -0,0 +1,177 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1728341886 -3600
+# Parent  42487ecaa6fae40900426c322c0d839380cd3efb
+Bug 1923229 - Switch from using stringTrim helper to using JS string trim method. r=frg a=frg
+
+diff --git a/suite/extensions/irc/js/lib/http.js b/suite/extensions/irc/js/lib/http.js
+--- a/suite/extensions/irc/js/lib/http.js
++++ b/suite/extensions/irc/js/lib/http.js
+@@ -84,17 +84,17 @@ function http_poll (e)
+             {
+                 this.data = line.substr (c, line.length);
+                 this.state = "receive-data";
+                 line = line.substr (0, c);
+ 
+                 c = this.data.search(/\<\/html\>/i);
+                 if (c != -1)
+                 {
+-                    this.docType = stringTrim(this.docType);    
++                    this.docType = this.docType.trim();
+                     this.state = "complete";
+                     need_more = false;
+                 }
+                 
+             }
+ 
+             this.headers += line;
+             c = this.headers.search (/\<\!doctype/i);
+@@ -109,17 +109,17 @@ function http_poll (e)
+             if (this.state == "opened")
+                 this.state = "receive-header";
+             break;
+ 
+         case "receive-doctype":
+             var c = line.search (/\<html\>/i);
+             if (c != -1)
+             {
+-                this.docType = stringTrim(this.docType);    
++                this.docType = this.docType.trim();
+                 this.data = line.substr (c, line.length);
+                 this.state = "receive-data";
+                 line = line.substr (0, c);
+             }
+ 
+             this.docType += line;
+             break;
+ 
+diff --git a/suite/extensions/irc/js/lib/utils.js b/suite/extensions/irc/js/lib/utils.js
+--- a/suite/extensions/irc/js/lib/utils.js
++++ b/suite/extensions/irc/js/lib/utils.js
+@@ -629,25 +629,16 @@ function keys (o)
+ 
+     for (var p in o)
+         rv.push(p);
+ 
+     return rv;
+ 
+ }
+ 
+-function stringTrim (s)
+-{
+-    if (!s)
+-        return "";
+-    s = s.replace (/^\s+/, "");
+-    return s.replace (/\s+$/, "");
+-
+-}
+-
+ /* the offset should be in seconds, it will be rounded to 2 decimal places */
+ function formatDateOffset (offset, format)
+ {
+     var seconds = offset % 60;
+     seconds = Math.round((seconds + Number.EPSILON) * 100) / 100;
+     var minutes = Math.floor(offset / 60);
+     var hours = Math.floor(minutes / 60);
+     minutes = minutes % 60;
+diff --git a/suite/extensions/irc/xul/content/commands.js b/suite/extensions/irc/xul/content/commands.js
+--- a/suite/extensions/irc/xul/content/commands.js
++++ b/suite/extensions/irc/xul/content/commands.js
+@@ -475,17 +475,17 @@ function dispatch(text, e, isInteractive
+     if (!ary)
+     {
+         display(getMsg(MSG_ERR_UNKNOWN_COMMAND, ""));
+         return null;
+     }
+ 
+     e.commandText = ary[1];
+     if (ary[2])
+-        e.inputData = stringTrim(ary[2]);
++        e.inputData = ary[2].trim();
+ 
+     /* list matching commands */
+     ary = client.commandManager.list(e.commandText, flags, true);
+     var rv = null;
+     var i;
+ 
+     switch (ary.length)
+     {
+@@ -872,17 +872,17 @@ function dispatchCommand (command, e, fl
+ 
+         callBeforeHooks();
+         try
+         {
+             for (i = 0; i < commandList.length; ++i)
+             {
+                 var newEvent = Clone(e);
+                 delete newEvent.command;
+-                commandList[i] = stringTrim(commandList[i]);
++                commandList[i] = commandList[i].trim();
+                 dispatch(commandList[i], newEvent, flags);
+             }
+         }
+         finally
+         {
+             callAfterHooks();
+         }
+     }
+diff --git a/suite/extensions/irc/xul/content/handlers.js b/suite/extensions/irc/xul/content/handlers.js
+--- a/suite/extensions/irc/xul/content/handlers.js
++++ b/suite/extensions/irc/xul/content/handlers.js
+@@ -1301,17 +1301,17 @@ function my_303 (e)
+     {
+         return e.server.toLowerCase(text);
+     };
+ 
+     var onList = new Array();
+     // split() gives an array of one item ("") when splitting "", which we
+     // don't want, so only do the split if there's something to split.
+     if (e.params[2])
+-        onList = stringTrim(e.server.toLowerCase(e.params[2])).split(/\s+/);
++        onList = e.server.toLowerCase(e.params[2]).trim().split(/\s+/);
+     var offList = new Array();
+     var newArrivals = new Array();
+     var newDepartures = new Array();
+     var o = getObjectDetails(client.currentObject);
+     var displayTab;
+     var i;
+ 
+     if ("network" in o && o.network == this && client.currentObject != this)
+@@ -1935,17 +1935,17 @@ function my_whoisreply (e)
+                 e.user.lastShownAwayMessage = "";
+ 
+             text = getMsg(MSG_WHOIS_NAME,
+                           [nick, e.params[3], e.params[4],
+                            e.decodeParam(6)]);
+             break;
+ 
+         case 319:
+-            var ary = stringTrim(e.decodeParam(3)).split(" ");
++            var ary = e.decodeParam(3).trim().split(" ");
+             text = getMsg(MSG_WHOIS_CHANNELS, [nick, arraySpeak(ary)]);
+             break;
+ 
+         case 312:
+             text = getMsg(MSG_WHOIS_SERVER,
+                           [nick, e.params[3], e.params[4]]);
+             break;
+ 
+@@ -2417,17 +2417,17 @@ function my_netdisconnect (e)
+         delete this.reconnect;
+     }
+ }
+ 
+ CIRCNetwork.prototype.onCTCPReplyPing =
+ function my_replyping (e)
+ {
+     // see bug 326523
+-    if (stringTrim(e.CTCPData).length != 13)
++    if (e.CTCPData.trim().length != 13)
+     {
+         this.display(getMsg(MSG_PING_REPLY_INVALID, e.user.unicodeName),
+                      "INFO", e.user, "ME!", e.tags);
+         return;
+     }
+ 
+     var delay = formatDateOffset((new Date() - new Date(Number(e.CTCPData))) /
+                                  1000);

+ 153 - 0
comm-release/patches/1923232-irc-remove-keys-25320.patch

@@ -0,0 +1,153 @@
+# HG changeset patch
+# User Ian Neal <iann_cvs@blueyonder.co.uk>
+# Date 1728342167 -3600
+# Parent  e99ed24e71ea5e6e003b98392680f0df697f6de1
+Bug 1923232 - Switch from using keys helper to using JS Object keys method. r=frg a=frg
+
+diff --git a/suite/extensions/irc/js/lib/command-manager.js b/suite/extensions/irc/js/lib/command-manager.js
+--- a/suite/extensions/irc/js/lib/command-manager.js
++++ b/suite/extensions/irc/js/lib/command-manager.js
+@@ -538,17 +538,17 @@ function cmgr_list(partialName, flags, e
+ 
+         if (a > b)
+             return 1;
+ 
+         return -1;
+     }
+ 
+     var ary = new Array();
+-    var commandNames = keys(this.commands);
++    var commandNames = Object.keys(this.commands);
+ 
+     for (var name of commandNames)
+     {
+         let command = this.commands[name];
+         if ((!flags || (command.flags & flags)) &&
+             (!partialName || command.name.startsWith(partialName)))
+         {
+             if (exact && partialName &&
+diff --git a/suite/extensions/irc/js/lib/utils.js b/suite/extensions/irc/js/lib/utils.js
+--- a/suite/extensions/irc/js/lib/utils.js
++++ b/suite/extensions/irc/js/lib/utils.js
+@@ -618,27 +618,16 @@ function getContentDocument(frame)
+     }
+     catch (ex)
+     {
+         // throws exception is contentDocument is gone
+         return null;
+     }
+ }
+ 
+-function keys (o)
+-{
+-    var rv = new Array();
+-
+-    for (var p in o)
+-        rv.push(p);
+-
+-    return rv;
+-
+-}
+-
+ /* the offset should be in seconds, it will be rounded to 2 decimal places */
+ function formatDateOffset (offset, format)
+ {
+     var seconds = offset % 60;
+     seconds = Math.round((seconds + Number.EPSILON) * 100) / 100;
+     var minutes = Math.floor(offset / 60);
+     var hours = Math.floor(minutes / 60);
+     minutes = minutes % 60;
+diff --git a/suite/extensions/irc/xul/content/commands.js b/suite/extensions/irc/xul/content/commands.js
+--- a/suite/extensions/irc/xul/content/commands.js
++++ b/suite/extensions/irc/xul/content/commands.js
+@@ -1432,17 +1432,17 @@ function cmdNetwork(e)
+     dispatch("create-tab-for-view", { view: network });
+     dispatch("set-current-view", { view: network });
+ }
+ 
+ function cmdNetworks(e)
+ {
+     var wrapper = newInlineText(MSG_NETWORKS_HEADA);
+ 
+-    var netnames = keys(client.networks).sort();
++    var netnames = Object.keys(client.networks).sort();
+ 
+     for (let i = 0; i < netnames.length; i++)
+     {
+         let net = client.networks[netnames[i]];
+         let hasSecure = networkHasSecure(net.serverList);
+ 
+         var linkData = {
+             "data": net.unicodeName,
+@@ -3986,17 +3986,17 @@ function cmdIgnore(e)
+         else
+         {
+             if (e.network.unignore(e.mask))
+                 display(getMsg(MSG_IGNORE_DEL, e.mask));
+             else
+                 display(getMsg(MSG_IGNORE_DELERR, e.mask));
+         }
+         // Update pref:
+-        var ignoreList = keys(e.network.ignoreList);
++        var ignoreList = Object.keys(e.network.ignoreList);
+         e.network.prefs["ignoreList"] = ignoreList;
+         e.network.prefs["ignoreList"].update();
+     }
+     else
+     {
+         var list = new Array();
+         for (var m in e.network.ignoreList)
+             list.push(m);
+diff --git a/suite/extensions/irc/xul/content/config.js b/suite/extensions/irc/xul/content/config.js
+--- a/suite/extensions/irc/xul/content/config.js
++++ b/suite/extensions/irc/xul/content/config.js
+@@ -364,18 +364,17 @@ function opdata_loadXUL(tabOrder)
+     this.tabbox.appendChild(this.tabs);
+     this.tabbox.appendChild(this.tabPanels);
+     this.deck.appendChild(this.tabbox);
+     
+     this.deckIndex = this.deck.childNodes.length - 1;
+     
+     this.loadData();
+     
+-    var prefList = keys(this.prefs);
+-    prefList.sort(sortByLabel);
++    var prefList = Object.keys(this.prefs).sort(sortByLabel);
+     
+     for (var i = 0; i < tabOrder.length; i++)
+     {
+         var pto = tabOrder[i];
+         var needTab = pto.fixed;
+         if (!needTab)
+         {
+             // Not a "always visible" tab, check we need it.
+@@ -1087,26 +1086,26 @@ function pwin_onLoad()
+ 
+     // Add the client object...
+     this.prefObjects.addObject(client);
+     // ...and everyone else.
+     var i, j;
+     /* We sort the keys (property names, i.e. network names). This means the UI
+      * will show them in lexographical order, which is good.
+      */
+-    var sortedNets = keys(client.networks).sort();
++    var sortedNets = Object.keys(client.networks).sort();
+     for (i = 0; i < sortedNets.length; i++) {
+         net = client.networks[sortedNets[i]];
+         this.prefObjects.addObject(net);
+         
+-        var sortedChans = keys(net.channels).sort();
++        var sortedChans = Object.keys(net.channels).sort();
+         for (j = 0; j < sortedChans.length; j++)
+             this.prefObjects.addObject(net.channels[sortedChans[j]]);
+         
+-        var sortedUsers = keys(net.users).sort();
++        var sortedUsers = Object.keys(net.users).sort();
+         for (j = 0; j < sortedUsers.length; j++)
+             this.prefObjects.addObject(net.users[sortedUsers[j]]);
+     }
+     
+     // Select the first item in the list.
+     var prefTree = document.getElementById("pref-tree-object");
+     if ("selection" in prefTree.treeBoxObject)
+         prefTree.treeBoxObject.selection.select(0);

+ 10 - 0
comm-release/patches/series

@@ -2196,3 +2196,13 @@ TOP-1906540-mozdevice-removal-comm-25320.patch
 1658682-2-81a1.patch
 1661940-82a1.patch
 9999999-handler-25320.patch
+1923211-irc-fix-decodeTagData-25320.patch
+1923213-irc-network-away-fix-25320.patch
+1923215-irc-remove-XTLabelRecord-25320.patch
+1923219-irc-connection-xpcom-tidy-25320.patch
+1923221-irc-utils-unused-25320.patch
+1923224-irc-remove-arrayContains-25320.patch
+1923225-irc-remove-arrayIndexOf-25320.patch
+1923227-irc-remove-arrayRemoveInsert-25320.patch
+1923229-irc-remove-stringTrim-25320.patch
+1923232-irc-remove-keys-25320.patch