home *** CD-ROM | disk | FTP | other *** search
/ PC World 2003 July & August / PCWorld_2003-07-08_cd.bin / Komunik / firebird / MozillaFirebird-0.6-win32.exe / MozillaFirebird / components / nsUpdateNotifier.js < prev    next >
Text File  |  2003-03-21  |  20KB  |  613 lines

  1. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  * ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Update Notifier.
  16.  *
  17.  * The Initial Developer of the Original Code is 
  18.  * Netscape Communications Corporation.
  19.  * Portions created by the Initial Developer are Copyright (C) 2002
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *  Samir Gehani <sgehani@netscape.com> (Original Author) 
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. const kDebug               = false;
  40. const kUpdateCheckDelay    = 5 * 60 * 1000; // 5 minutes
  41. const kUNEnabledPref       = "update_notifications.enabled";
  42. const kUNDatasourceURIPref = "update_notifications.provider.0.datasource";
  43. const kUNFrequencyPref     = "update_notifications.provider.0.frequency";
  44. const kUNLastCheckedPref   = "update_notifications.provider.0.last_checked";
  45. const kUNBundleURI         = 
  46.   "chrome://communicator/locale/update-notifications.properties";
  47.  
  48. ////////////////////////////////////////////////////////////////////////
  49. // 
  50. //   nsUpdateNotifier : nsIProfileStartupListener, nsIObserver
  51. //
  52. //   Checks for updates of the client by polling a distributor's website
  53. //   for the latest available version of the software and comparing it
  54. //   with the version of the running client.
  55. //
  56. ////////////////////////////////////////////////////////////////////////
  57.  
  58. var nsUpdateNotifier = 
  59. {
  60.   onProfileStartup: function(aProfileName)
  61.   {
  62.     debug("onProfileStartup");
  63.  
  64.     // now wait for the first app window to open
  65.     var observerService = Components.
  66.       classes["@mozilla.org/observer-service;1"].
  67.       getService(Components.interfaces.nsIObserverService);
  68.     observerService.addObserver(this, "domwindowopened", false);
  69.   },
  70.  
  71.   mTimer: null, // need to hold on to timer ref
  72.  
  73.   observe: function(aSubject, aTopic, aData)
  74.   {
  75.     debug("observe: " + aTopic);
  76.  
  77.     if (aTopic == "domwindowopened")
  78.     {
  79.       try 
  80.       {
  81.         const kITimer = Components.interfaces.nsITimer;
  82.         this.mTimer = Components.classes["@mozilla.org/timer;1"].
  83.           createInstance(kITimer);
  84.         this.mTimer.init(this, kUpdateCheckDelay, kITimer.TYPE_ONE_SHOT);
  85.  
  86.         // we are no longer interested in the ``domwindowopened'' topic
  87.         var observerService = Components.
  88.           classes["@mozilla.org/observer-service;1"].
  89.           getService(Components.interfaces.nsIObserverService);
  90.         observerService.removeObserver(this, "domwindowopened");
  91.  
  92.         // but we are interested in removing our timer reference on XPCOM
  93.         // shutdown so as not to leak.
  94.         observerService.addObserver(this, "xpcom-shutdown", false);
  95.       }
  96.       catch (ex)
  97.       {
  98.         debug("Exception init'ing timer: " + ex);
  99.       }
  100.     }
  101.     else if (aTopic == "timer-callback")
  102.     {
  103.       this.mTimer = null; // free up timer so it can be gc'ed
  104.       this.checkForUpdate();
  105.     }
  106.     else if (aTopic == "xpcom-shutdown")
  107.     {
  108.       /*
  109.        * We need to drop our timer reference here to avoid a leak
  110.        * since the timer keeps a reference to the observer.
  111.        */
  112.       this.mTimer = null;
  113.     }
  114.   },
  115.  
  116.   checkForUpdate: function()
  117.   {
  118.     debug("checkForUpdate");
  119.     
  120.     if (this.shouldCheckForUpdate())
  121.     {
  122.       try
  123.       {
  124.         // get update ds URI from prefs
  125.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  126.           getService(Components.interfaces.nsIPrefBranch);
  127.         var updateDatasourceURI = prefs.
  128.           getComplexValue(kUNDatasourceURIPref,
  129.           Components.interfaces.nsIPrefLocalizedString).data;
  130.  
  131.         var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"].
  132.           getService(Components.interfaces.nsIRDFService);
  133.         var ds = rdf.GetDataSource(updateDatasourceURI);
  134.         
  135.         ds = ds.QueryInterface(Components.interfaces.nsIRDFXMLSink);
  136.         ds.addXMLSinkObserver(nsUpdateDatasourceObserver);
  137.       }
  138.       catch (ex)
  139.       {
  140.         debug("Exception getting updates.rdf: " + ex);
  141.       }
  142.     }
  143.   },
  144.  
  145.   shouldCheckForUpdate: function()
  146.   {
  147.     debug("shouldCheckForUpdate");
  148.  
  149.     var shouldCheck = false;
  150.  
  151.     try
  152.     {
  153.       var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  154.         getService(Components.interfaces.nsIPrefBranch);
  155.   
  156.       if (prefs.getBoolPref(kUNEnabledPref))
  157.       {
  158.         var freq = prefs.getIntPref(kUNFrequencyPref) * (24 * 60 * 60); // secs
  159.         var now = (new Date().valueOf())/1000; // secs
  160.  
  161.         if (!prefs.prefHasUserValue(kUNLastCheckedPref))
  162.         {
  163.           // setting last_checked pref first time so must randomize in 
  164.           // order that servers don't get flooded with updates.rdf checks
  165.           // (and eventually downloads of new clients) all at the same time
  166.  
  167.           var randomizedLastChecked = now + freq * (1 + Math.random());
  168.           prefs.setIntPref(kUNLastCheckedPref, randomizedLastChecked);
  169.  
  170.           return false;
  171.         }
  172.  
  173.         var lastChecked = prefs.getIntPref(kUNLastCheckedPref);
  174.         if ((lastChecked + freq) > now)
  175.           return false;
  176.         
  177.         prefs.setIntPref(kUNLastCheckedPref, now);
  178.         prefs = prefs.QueryInterface(Components.interfaces.nsIPrefService);
  179.         prefs.savePrefFile(null); // flush prefs now
  180.  
  181.         shouldCheck = true;
  182.       }
  183.     }
  184.     catch (ex)
  185.     {
  186.       shouldCheck = false;
  187.       debug("Exception in shouldCheckForUpdate: " + ex);
  188.     }
  189.  
  190.     return shouldCheck;
  191.   },
  192.  
  193.   QueryInterface: function(aIID)
  194.   {
  195.     if (!aIID.equals(Components.interfaces.nsIObserver) &&
  196.         !aIID.equals(Components.interfaces.nsIProfileStartupListener) &&
  197.         !aIID.equals(Components.interfaces.nsISupports))
  198.       throw Components.results.NS_ERROR_NO_INTERFACE;
  199.  
  200.     return this;
  201.   }
  202. }
  203.  
  204. ////////////////////////////////////////////////////////////////////////
  205. //
  206. //   nsUpdateDatasourceObserver : nsIRDFXMLSinkObserver
  207. //
  208. //   Gets relevant info on latest available update after the updates.rdf
  209. //   datasource has completed loading asynchronously.
  210. //
  211. ////////////////////////////////////////////////////////////////////////
  212.  
  213. var nsUpdateDatasourceObserver = 
  214. {
  215.   onBeginLoad: function(aSink)
  216.   {
  217.   },
  218.  
  219.   onInterrupt: function(aSink)
  220.   {
  221.   },
  222.  
  223.   onResume: function(aSink)
  224.   {
  225.   },
  226.  
  227.   onEndLoad: function(aSink)
  228.   {
  229.     debug("onEndLoad");
  230.     
  231.     aSink.removeXMLSinkObserver(this);
  232.  
  233.     var ds = aSink.QueryInterface(Components.interfaces.nsIRDFDataSource);
  234.     var updateInfo = this.getUpdateInfo(ds);
  235.     if (updateInfo && this.newerVersionAvailable(updateInfo))
  236.     {
  237.       var promptService = Components.
  238.         classes["@mozilla.org/embedcomp/prompt-service;1"].
  239.         getService(Components.interfaces.nsIPromptService);
  240.       var winWatcher = Components.
  241.         classes["@mozilla.org/embedcomp/window-watcher;1"].
  242.         getService(Components.interfaces.nsIWindowWatcher);
  243.       
  244.       var unBundle = this.getBundle(kUNBundleURI);
  245.       if (!unBundle)
  246.         return;
  247.  
  248.       var title = unBundle.formatStringFromName("title", 
  249.         [updateInfo.productName], 1);
  250.       var desc = unBundle.formatStringFromName("desc", 
  251.         [updateInfo.productName], 1);
  252.       var button0Text = unBundle.GetStringFromName("getItNow");
  253.       var button1Text = unBundle.GetStringFromName("noThanks");
  254.       var checkMsg = unBundle.GetStringFromName("dontAskAgain");
  255.       var checkVal = {value:0};
  256.  
  257.       var result = promptService.confirmEx(winWatcher.activeWindow, title, desc, 
  258.         (promptService.BUTTON_POS_0 * promptService.BUTTON_TITLE_IS_STRING) +
  259.         (promptService.BUTTON_POS_1 * promptService.BUTTON_TITLE_IS_STRING),
  260.         button0Text, button1Text, null, checkMsg, checkVal);
  261.  
  262.       // user wants update now so open new window 
  263.       // (result => 0 is button0)
  264.       if (result == 0)
  265.         winWatcher.openWindow(winWatcher.activeWindow, updateInfo.URL, 
  266.           "_blank", "", null);
  267.  
  268.       // if "Don't ask again" was checked disable update notifications
  269.       if (checkVal.value)
  270.       {
  271.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  272.           getService(Components.interfaces.nsIPrefBranch);
  273.         prefs.setBoolPref(kUNEnabledPref, false);
  274.       }
  275.     }
  276.   },
  277.  
  278.   onError: function(aSink, aStatus, aErrorMsg)
  279.   {
  280.     debug("Error " + aStatus + ": " + aErrorMsg);
  281.     aSink.removeXMLSinkObserver(this);
  282.   },
  283.  
  284.   getUpdateInfo: function(aDS)
  285.   {
  286.     var info = null;
  287.  
  288.     try
  289.     {
  290.       var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"].
  291.         getService(Components.interfaces.nsIRDFService);
  292.       var src = "urn:updates:latest";
  293.  
  294.       info = new Object;
  295.       info.registryName = this.getTarget(rdf, aDS, src, "registryName");
  296.       info.version = this.getTarget(rdf, aDS, src, "version");
  297.       info.URL = this.getTarget(rdf, aDS, src, "URL");
  298.       info.productName = this.getTarget(rdf, aDS, src, "productName");
  299.     }
  300.     catch (ex)
  301.     {
  302.       info = null;
  303.       debug("Exception getting update info: " + ex);
  304.  
  305.       // NOTE: If the (possibly remote) datasource doesn't exist 
  306.       //       or fails to load the first |GetTarget()| call will fail
  307.       //       bringing us to this exception handler.  In turn, we 
  308.       //       will fail silently.  Testing has revealed that for a 
  309.       //       non-existent datasource (invalid URI) the 
  310.       //       |nsIRDFXMLSinkObserver.onEndLoad()| is called instead of
  311.       //       |nsIRDFXMLSinkObserver.onError()| as one may expect.  In 
  312.       //       addition, if we QI the aSink parameter of |onEndLoad()| 
  313.       //       to an |nsIRDFRemoteDataSource| and check the |loaded| 
  314.       //       boolean, it reflects true so we can't use that.  The 
  315.       //       safe way to know we have failed to load the datasource 
  316.       //       is by handling the first exception as we are doing now.
  317.     }
  318.  
  319.     return info;
  320.   },
  321.  
  322.   getTarget: function(aRDF, aDS, aSrc, aProp)
  323.   {
  324.     var src = aRDF.GetResource(aSrc);
  325.     var arc = aRDF.GetResource("http://home.netscape.com/NC-rdf#" + aProp);
  326.     var target = aDS.GetTarget(src, arc, true);
  327.     return target.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
  328.   },
  329.  
  330.   newerVersionAvailable: function(aUpdateInfo)
  331.   {
  332.     // sanity check 
  333.     if (!aUpdateInfo.registryName || !aUpdateInfo.version)
  334.     {
  335.       debug("Sanity check failed: aUpdateInfo is invalid!");
  336.       return false;
  337.     }
  338.  
  339.     // when we know we are updating the ``Browser'' component
  340.     // we can rely on Necko to give us the app version
  341.  
  342.     if (aUpdateInfo.registryName == "Browser")
  343.       return this.neckoHaveNewer(aUpdateInfo);
  344.  
  345.     return this.xpinstallHaveNewer(aUpdateInfo);
  346.   },
  347.  
  348.   neckoHaveNewer: function(aUpdateInfo)
  349.   {
  350.     try
  351.     {
  352.       var httpHandler = Components.
  353.         classes["@mozilla.org/network/protocol;1?name=http"].
  354.         getService(Components.interfaces.nsIHttpProtocolHandler);
  355.       var synthesized = this.synthesizeVersion(httpHandler.misc, 
  356.         httpHandler.productSub);
  357.       var local = new nsVersion(synthesized);
  358.       var server = new nsVersion(aUpdateInfo.version); 
  359.  
  360.       return (server.isNewerThan(local));
  361.     }
  362.     catch (ex)
  363.     {
  364.       // fail silently
  365.       debug("Exception getting httpHandler: " + ex);
  366.       return false;
  367.     }
  368.  
  369.     return false; // return value expected from this function
  370.   }, 
  371.  
  372.   xpinstallHaveNewer: function(aUpdateInfo)
  373.   {
  374.     // XXX Once InstallTrigger is a component we will be able to
  375.     //     get at it without needing to reference it from hiddenDOMWindow.
  376.     //     This will enable us to |compareVersion()|s even when 
  377.     //     XPInstall is disabled but update notifications are enabled.
  378.     //     See <http://bugzilla.mozilla.org/show_bug.cgi?id=121506>.
  379.     var ass = Components.classes["@mozilla.org/appshell/appShellService;1"].
  380.       getService(Components.interfaces.nsIAppShellService);
  381.     var trigger = ass.hiddenDOMWindow.InstallTrigger;
  382.     var diffLevel = trigger.compareVersion(aUpdateInfo.registryName, 
  383.       aUpdateInfo.version);
  384.     if (diffLevel < trigger.EQUAL && diffLevel != trigger.NOT_FOUND)
  385.       return true;
  386.     return false; // already have newer version or 
  387.                   // fail silently if old version not found on disk
  388.   },
  389.  
  390.   synthesizeVersion: function(aMisc, aProductSub)
  391.   {
  392.     // Strip out portion of nsIHttpProtocolHandler.misc that
  393.     // contains version info and stuff all ``missing'' portions
  394.     // with a default 0 value.  We are interested in the first 3
  395.     // numbers delimited by periods.  The 4th comes from aProductSub.
  396.     // e.g., x => x.0.0, x.1 => x.1.0, x.1.2 => x.1.2, x.1.2.3 => x.1.2
  397.     
  398.     var synthesized = "0.0.0.";
  399.  
  400.     // match only digits and periods after "rv:" in the misc
  401.     var onlyVer = /rv:([0-9.]+)/.exec(aMisc);
  402.  
  403.     // original string in onlyVer[0], matched substring in onlyVer[1]
  404.     if (onlyVer && onlyVer.length >= 2) 
  405.     {
  406.       var parts = onlyVer[1].split('.');
  407.       var len = parts.length;
  408.       if (len > 0)
  409.       {
  410.         synthesized = "";
  411.  
  412.         // extract first 3 dot delimited numbers in misc (after "rv:")
  413.         for (var i = 0; i < 3; ++i)
  414.         {
  415.           synthesized += ((len >= i+1) ? parts[i] : "0") + ".";
  416.         }
  417.       }
  418.     }
  419.  
  420.     // tack on productSub for nsVersion.mBuild field if available
  421.     synthesized += aProductSub ? aProductSub : "0";
  422.    
  423.     return synthesized;
  424.   },
  425.  
  426.   getBundle: function(aURI)
  427.   {
  428.     if (!aURI)
  429.       return null;
  430.  
  431.     var bundle = null;
  432.     try
  433.     {
  434.       var strBundleService = Components.
  435.         classes["@mozilla.org/intl/stringbundle;1"].
  436.         getService(Components.interfaces.nsIStringBundleService);
  437.       bundle = strBundleService.createBundle(aURI);
  438.     }
  439.     catch (ex)
  440.     {
  441.       bundle = null;
  442.       debug("Exception getting bundle " + aURI + ": " + ex);
  443.     }
  444.  
  445.     return bundle;
  446.   }
  447. }
  448.  
  449. ////////////////////////////////////////////////////////////////////////
  450. //
  451. //   nsVersion
  452. //
  453. //   Constructs a version object given a string representation.  This
  454. //   constructor populates the mMajor, mMinor, mRelease, and mBuild
  455. //   fields regardless of whether string contains all the fields.  
  456. //   The default for all unspecified fields is 0.
  457. //
  458. ////////////////////////////////////////////////////////////////////////
  459.  
  460. function nsVersion(aStringVersion)
  461. {
  462.   var parts = aStringVersion.split('.');
  463.   var len = parts.length;
  464.  
  465.   this.mMajor   = (len >= 1) ? this.getValidInt(parts[0]) : 0;
  466.   this.mMinor   = (len >= 2) ? this.getValidInt(parts[1]) : 0;
  467.   this.mRelease = (len >= 3) ? this.getValidInt(parts[2]) : 0;
  468.   this.mBuild   = (len >= 4) ? this.getValidInt(parts[3]) : 0;
  469. }
  470.  
  471. nsVersion.prototype = 
  472. {
  473.   isNewerThan: function(aOther)
  474.   {
  475.     if (this.mMajor == aOther.mMajor)
  476.     {
  477.       if (this.mMinor == aOther.mMinor)
  478.       {
  479.         if (this.mRelease == aOther.mRelease)
  480.         {
  481.           if (this.mBuild <= aOther.mBuild)
  482.             return false;
  483.           else
  484.             return true; // build is newer
  485.         }
  486.         else if (this.mRelease < aOther.mRelease)
  487.           return false;
  488.         else
  489.           return true; // release is newer
  490.       }
  491.       else if (this.mMinor < aOther.mMinor)
  492.         return false;
  493.       else
  494.         return true; // minor is newer
  495.     }
  496.     else if (this.mMajor < aOther.mMajor)
  497.       return false;
  498.     else
  499.       return true; // major is newer
  500.  
  501.     return false;
  502.   },
  503.  
  504.   getValidInt: function(aString)
  505.   {
  506.     var integer = parseInt(aString);
  507.     if (isNaN(integer))
  508.       return 0;
  509.     return integer;
  510.   }
  511. }
  512.  
  513. ////////////////////////////////////////////////////////////////////////
  514. //
  515. //   nsUpdateNotifierModule : nsIModule
  516. //
  517. ////////////////////////////////////////////////////////////////////////
  518.  
  519. var nsUpdateNotifierModule = 
  520. {
  521.   mClassName:     "Update Notifier",
  522.   mContractID:    "@mozilla.org/update-notifier;1",
  523.   mClassID:       Components.ID("8b6dcf5e-3b5a-4fff-bff5-65a8fa9d71b2"),
  524.  
  525.   getClassObject: function(aCompMgr, aCID, aIID)
  526.   {
  527.     if (!aCID.equals(this.mClassID))
  528.       throw Components.results.NS_ERROR_NO_INTERFACE;
  529.     if (!aIID.equals(Components.interfaces.nsIFactory))
  530.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  531.  
  532.     return this.mFactory;
  533.   },
  534.  
  535.   registerSelf: function(aCompMgr, aFileSpec, aLocation, aType)
  536.   {
  537.     if (kDebug)
  538.       dump("*** Registering nsUpdateNotifier (a JavaScript Module)\n");
  539.  
  540.     aCompMgr = aCompMgr.QueryInterface(
  541.                  Components.interfaces.nsIComponentRegistrar);
  542.     aCompMgr.registerFactoryLocation(this.mClassID, this.mClassName, 
  543.       this.mContractID, aFileSpec, aLocation, aType);
  544.  
  545.     // receive startup notification from the profile manager
  546.     // (we get |createInstance()|d at startup-notification time)
  547.     this.getCategoryManager().addCategoryEntry("profile-startup-category", 
  548.       this.mContractID, "", true, true);
  549.   },
  550.  
  551.   unregisterSelf: function(aCompMgr, aFileSpec, aLocation)
  552.   {
  553.     aCompMgr = aCompMgr.QueryInterface(
  554.                  Components.interfaces.nsIComponentRegistrar);
  555.     aCompMgr.unregisterFactoryLocation(this.mClassID, aFileSpec);
  556.  
  557.     this.getCategoryManager().deleteCategoryEntry("profile-startup-category", 
  558.       this.mContractID, true);
  559.   },
  560.  
  561.   canUnload: function(aCompMgr)
  562.   {
  563.     return true;
  564.   },
  565.  
  566.   getCategoryManager: function()
  567.   {
  568.     return Components.classes["@mozilla.org/categorymanager;1"].
  569.       getService(Components.interfaces.nsICategoryManager);
  570.   },
  571.  
  572.   //////////////////////////////////////////////////////////////////////
  573.   //
  574.   //   mFactory : nsIFactory
  575.   //
  576.   //////////////////////////////////////////////////////////////////////
  577.   mFactory:
  578.   {
  579.     createInstance: function(aOuter, aIID)
  580.     {
  581.       if (aOuter != null)
  582.         throw Components.results.NS_ERROR_NO_AGGREGATION;
  583.       if (!aIID.equals(Components.interfaces.nsIObserver) &&
  584.           !aIID.equals(Components.interfaces.nsIProfileStartupListener) &&
  585.           !aIID.equals(Components.interfaces.nsISupports))
  586.         throw Components.results.NS_ERROR_INVALID_ARG;
  587.  
  588.       // return the singleton 
  589.       return nsUpdateNotifier.QueryInterface(aIID);
  590.     },
  591.  
  592.     lockFactory: function(aLock)
  593.     {
  594.       // quiten warnings
  595.     }
  596.   }
  597. };
  598.  
  599. function NSGetModule(aCompMgr, aFileSpec)
  600. {
  601.   return nsUpdateNotifierModule;
  602. }
  603.  
  604. ////////////////////////////////////////////////////////////////////////
  605. //
  606. //   Debug helper
  607. //
  608. ////////////////////////////////////////////////////////////////////////
  609. if (!kDebug)
  610.   debug = function(m) {};
  611. else
  612.   debug = function(m) {dump("\t *** nsUpdateNotifier: " + m + "\n");};
  613.