about:startup - store extension install/uninstall/enable/disable events in the startup database
authorDaniel Brooks <db48x@db48x.net>
Sat, 18 Sep 2010 00:30:06 -0400
changeset 58847 07b80de79428b8830a17eb753012a3721ba9a493
parent 58846 2d812dfd632188a97883ef52ad78b4062662fde0
child 58848 3d74b68d15c9cd86737ad50f0276f5dedc60cd62
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
milestone2.0b6pre
about:startup - store extension install/uninstall/enable/disable events in the startup database
toolkit/components/startup/src/nsAppStartup.cpp
toolkit/content/aboutStartup.js
toolkit/content/aboutStartup.xhtml
toolkit/locales/en-US/chrome/global/aboutStartup.dtd
toolkit/mozapps/extensions/AddonManager.jsm
--- a/toolkit/components/startup/src/nsAppStartup.cpp
+++ b/toolkit/components/startup/src/nsAppStartup.cpp
@@ -70,16 +70,17 @@
 #include "nsAppShellCID.h"
 #include "mozilla/Services.h"
 #include "mozilla/storage.h"
 #include "mozilla/FunctionTimer.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIXULRuntime.h"
 #include "nsIXULAppInfo.h"
 #include "nsXPCOMCIDInternal.h"
+#include "nsIPropertyBag2.h"
 
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 class nsAppExitEvent : public nsRunnable {
 private:
   nsRefPtr<nsAppStartup> mService;
 
 public:
@@ -94,16 +95,17 @@ public:
     mService->mRunning = PR_FALSE;
     return NS_OK;
   }
 };
 
 nsresult RecordStartupDuration();
 nsresult OpenStartupDatabase(mozIStorageConnection **db);
 nsresult EnsureTable(mozIStorageConnection *db, const nsACString &table, const nsACString &schema);
+nsresult RecordAddonEvent(const PRUnichar *event, nsISupports *details);
 
 //
 // nsAppStartup
 //
 
 nsAppStartup::nsAppStartup() :
   mConsiderQuitStopper(0),
   mRunning(PR_FALSE),
@@ -130,16 +132,17 @@ nsAppStartup::Init()
   if (!os)
     return NS_ERROR_FAILURE;
 
   NS_TIME_FUNCTION_MARK("Got Observer service");
 
   os->AddObserver(this, "quit-application-forced", PR_TRUE);
   os->AddObserver(this, "sessionstore-browser-state-restored", PR_TRUE);
   os->AddObserver(this, "sessionstore-windows-restored", PR_TRUE);
+  os->AddObserver(this, "AddonManager-event", PR_TRUE);
   os->AddObserver(this, "profile-change-teardown", PR_TRUE);
   os->AddObserver(this, "xul-window-registered", PR_TRUE);
   os->AddObserver(this, "xul-window-destroyed", PR_TRUE);
 
   return NS_OK;
 }
 
 
@@ -516,16 +519,18 @@ nsAppStartup::Observe(nsISupports *aSubj
     }
   } else if (!strcmp(aTopic, "xul-window-registered")) {
     EnterLastWindowClosingSurvivalArea();
   } else if (!strcmp(aTopic, "xul-window-destroyed")) {
     ExitLastWindowClosingSurvivalArea();
   } else if ((!strcmp(aTopic, "sessionstore-browser-state-restored")) ||
              (!strcmp(aTopic, "sessionstore-windows-restored"))) {
     RecordStartupDuration();
+  } else if (!strcmp(aTopic, "AddonManager-event")) {
+    RecordAddonEvent(aData, aSubject);
   } else {
     NS_ERROR("Unexpected observer topic.");
   }
 
   return NS_OK;
 }
 
 nsresult RecordStartupDuration()
@@ -533,25 +538,17 @@ nsresult RecordStartupDuration()
   nsresult rv;
   PRTime launched, started, finished;
   finished = PR_Now();
 
   nsCOMPtr<mozIStorageConnection> db;
   rv = OpenStartupDatabase(getter_AddRefs(db));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = EnsureTable(db,
-                   NS_LITERAL_CSTRING("duration"),
-                   NS_LITERAL_CSTRING("timestamp INTEGER, launch INTEGER, startup INTEGER, appVersion TEXT, appBuild TEXT, platformVersion TEXT, platformBuild TEXT"));
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = EnsureTable(db,
-                   NS_LITERAL_CSTRING("events"),
-                   NS_LITERAL_CSTRING("timestamp INTEGER, extensionID TEXT, extensionVersion TEXT, action TEXT"));
-  NS_ENSURE_SUCCESS(rv, rv);
-
+  
   nsCOMPtr<mozIStorageStatement> statement;
   rv = db->CreateStatement(NS_LITERAL_CSTRING("INSERT INTO duration VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"),
                            getter_AddRefs(statement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIXULRuntime> runtime = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
   nsCOMPtr<nsIXULAppInfo> appinfo = do_QueryInterface(runtime);
 
@@ -574,17 +571,56 @@ nsresult RecordStartupDuration()
   NS_ENSURE_SUCCESS(rv, rv); 
   rv = statement->BindStringParameter(4, NS_ConvertUTF8toUTF16(appBuild));
   NS_ENSURE_SUCCESS(rv, rv); 
   rv = statement->BindStringParameter(5, NS_ConvertUTF8toUTF16(platformVersion));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = statement->BindStringParameter(6, NS_ConvertUTF8toUTF16(platformBuild));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  statement->Execute();
+  rv = statement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult RecordAddonEvent(const PRUnichar *event, nsISupports *details)
+{
+  PRTime now = PR_Now();
+  nsresult rv;
+
+  nsCOMPtr<mozIStorageConnection> db;
+  rv = OpenStartupDatabase(getter_AddRefs(db));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStorageStatement> statement;
+  rv = db->CreateStatement(NS_LITERAL_CSTRING("INSERT INTO events VALUES (?1, ?2, ?3, ?4, ?5)"),
+                           getter_AddRefs(statement));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(details);
+  nsAutoString id, name, version;
+  bag->GetPropertyAsAString(NS_LITERAL_STRING("id"), id);
+  bag->GetPropertyAsAString(NS_LITERAL_STRING("name"), name);
+  bag->GetPropertyAsAString(NS_LITERAL_STRING("version"), version);
+
+  rv = statement->BindInt64Parameter(0, now);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = statement->BindStringParameter(1, id);
+  NS_ENSURE_SUCCESS(rv, rv); 
+  rv = statement->BindStringParameter(2, name);
+  NS_ENSURE_SUCCESS(rv, rv); 
+  rv = statement->BindStringParameter(3, version);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = statement->BindStringParameter(4, nsDependentString(event));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = statement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 nsresult OpenStartupDatabase(mozIStorageConnection **db)
 {
   nsresult rv;
   nsCOMPtr<nsIFile> file;
   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
@@ -597,16 +633,26 @@ nsresult OpenStartupDatabase(mozIStorage
   rv = svc->OpenDatabase(file, db);
   if (NS_ERROR_FILE_CORRUPTED == rv)
   {
     svc->BackupDatabaseFile(file, NS_LITERAL_STRING("startup.sqlite.backup"), nsnull, nsnull);
     rv = svc->OpenDatabase(file, db);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = EnsureTable(*db,
+                   NS_LITERAL_CSTRING("duration"),
+                   NS_LITERAL_CSTRING("timestamp INTEGER, launch INTEGER, startup INTEGER, appVersion TEXT, appBuild TEXT, platformVersion TEXT, platformBuild TEXT"));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = EnsureTable(*db,
+                   NS_LITERAL_CSTRING("events"),
+                   NS_LITERAL_CSTRING("timestamp INTEGER, id TEXT, name TEXT, version TEXT, action TEXT"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 nsresult EnsureTable(mozIStorageConnection *db, const nsACString &table, const nsACString &schema)
 {
   nsresult rv;
   PRBool exists = false;
   rv = db->TableExists(table, &exists);
--- a/toolkit/content/aboutStartup.js
+++ b/toolkit/content/aboutStartup.js
@@ -9,32 +9,34 @@ let branding = stringsvc.createBundle("c
 function displayTimestamp(id, µs) document.getElementById(id).textContent = formatstamp(µs);
 function displayDuration(id, µs) document.getElementById(id).nextSibling.textContent = formatms(msFromµs(µs));
 
 function formatStr(str, args) strings.formatStringFromName("about.startup."+ str, args, args.length);
 function appVersion(version, build) formatStr("appVersion",
 //                                              [branding.getStringFromName("brandShortName"),
                                               ["Firefox",
                                                version, build]);
-function formatExtension(str, id, version) formatStr(str, [id, version]);
+function formatExtension(str, id, version) formatStr("extension"+str, [id, version]);
 
 function msFromµs(µs) µs / 1000;
 function formatstamp(µs) new Date(msFromµs(µs));
 function formatµs(µs) µs + " µs";
 function formatms(ms) formatStr("milliseconds", [ms]);
 
 function point(stamp, µs, v, b) [msFromµs(stamp), msFromµs(µs), { appVersion: v, appBuild: b }];
 function range(a, b) ({ from: msFromµs(a), to: msFromµs(b || a) });
 function mark(x, y) { var r = {}; x && (r.xaxis = x); y && (r.yaxis = y); return r };
-function major(r) { r.color = "#444"; return r; }
-function minor(r) { r.color = "#AAA"; return r; }
-function label(r, l) { r.label = l; return r; }
+function label(r, l) $.extend(r, { label: l });
+function color(r, c) $.extend(r, { color: c });
+function major(r) color(r, "#444");
+function minor(r) color(r, "#AAA");
+function green(r) color(r, "#00F");
 function majorMark(x, l) label(major(mark(range(x))), l);
 function minorMark(x, l) label(minor(mark(range(x))), l);
-function extensionMark(x, l) label(mark(range(x)), l);
+function extensionMark(x, l) label(green(mark(range(x))), l);
 
 ///// First, display the timings from the current startup
 let launched, startup, restored;
 let runtime = Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime);
 
 try {
   displayTimestamp("launched", launched = runtime.launchTimestamp);
 } catch(x) { }
@@ -194,40 +196,40 @@ function populateMeasurements()
       overview = $.plot($("#overview"), series, overviewOpts);
       go();
     },
   });
 }
 
 function populateEvents()
 {
-  var query = db.createStatement("SELECT timestamp, extensionID, extensionVersion, action FROM events");
+  var query = db.createStatement("SELECT timestamp, id, name, version, action FROM events");
   let lastver, lastbuild;
   let hasresults;
 
   query.executeAsync({
     handleResult: function(results)
     {
       let table = document.getElementById("events-table");
       for (let row = results.getNextRow(); row; row = results.getNextRow())
       {
         hasresults = true;
         let stamp = row.getResultByName("timestamp"),
-            id = row.getResultByName("extensionID"),
-            name = id,
-            version = row.getResultByName("extensionVersion"),
+            id = row.getResultByName("id"),
+            name = row.getResultByName("name"),
+            version = row.getResultByName("version"),
             action = row.getResultByName("action");
 
         options.grid.markings.push(extensionMark(stamp, formatExtension(action, name, version)));
 
         table.appendChild(tr(td(formatstamp(stamp)),
-                             td(id),
+                             td(action),
                              td(name),
-                             td(version),
-                             td(action)));
+                             td(id),
+                             td(version)));
       }
       if (hasresults)
         $("#events-table > .empty").hide();
     },
     handleError: function(error)
     {
       $("#events-table").appendChild(tr(td("Error: "+ error.message +" ("+ error.result +")")));
     },
--- a/toolkit/content/aboutStartup.xhtml
+++ b/toolkit/content/aboutStartup.xhtml
@@ -45,16 +45,22 @@
           <th>&about.startup.timestamp;</th>
           <th>&about.startup.duration.launch;</th>
           <th>&about.startup.duration.startup;</th>
           <th>&about.startup.duration.ready;</th>
           <th colspan="2">&about.startup.version;</th>
         </tr>
         <tr class="empty"><td colspan="6"><i>&about.startup.noevents;</i></td></tr>
       </table>
-      <table id="event-table">
-        <tr><th>&about.startup.timestamp;</th><th>&about.startup.eventdesc;</th></tr>
+      <table id="events-table">
+        <tr>
+          <th>&about.startup.timestamp;</th>
+          <th>&about.startup.action;</th>
+          <th>&about.startup.extension;</th>
+          <th>&about.startup.extensionID;</th>
+          <th>&about.startup.version;</th>
+        </tr>
         <tr class="empty"><td colspan="2"><i>&about.startup.noevents;</i></td></tr>
       </table>
     </div>
     <script type="application/javascript;version=1.8" src="chrome://global/content/aboutStartup.js"/>
   </body>
 </html>
--- a/toolkit/locales/en-US/chrome/global/aboutStartup.dtd
+++ b/toolkit/locales/en-US/chrome/global/aboutStartup.dtd
@@ -33,10 +33,12 @@ startup process. -->
 <!-- LOCALIZATION NOTE (about.startup.duration.ready): The total time
 that the user had to wait between when they lauched firefox and it was
 ready to use. -->
 <!ENTITY about.startup.duration.ready "Elapsed Time">
 
 <!ENTITY about.startup.version "Version">
 <!ENTITY about.startup.table "Show Table">
 <!ENTITY about.startup.graph "Show Graph">
-<!ENTITY about.startup.eventdesc "Event Description">
+<!ENTITY about.startup.extension "Extension">
+<!ENTITY about.startup.extensionID "ID">
+<!ENTITY about.startup.action "Action">
 <!ENTITY about.startup.noevents "No Events Recorded">
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -219,20 +219,21 @@ var AddonManagerInternal = {
 
   /**
    * Initializes the AddonManager, loading any known providers and initializing
    * them.
    */
   startup: function AMI_startup() {
     if (this.started)
       return;
-
     this.installListeners = [];
     this.addonListeners = [];
 
+    this._addNotificationListeners();
+
     let appChanged = undefined;
 
     try {
       appChanged = Services.appinfo.version !=
                    Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION);
     }
     catch (e) { }
 
@@ -822,17 +823,38 @@ var AddonManagerInternal = {
    *
    * @param  aListener
    *         The listener to remove
    */
   removeAddonListener: function AMI_removeAddonListener(aListener) {
     this.addonListeners = this.addonListeners.filter(function(i) {
       return i != aListener;
     });
-  }
+  },
+
+  _addNotificationListeners: function()
+  {
+    const svc = Cc["@mozilla.org/observer-service;1"]
+                  .getService(Ci.nsIObserverService);
+    function notify(msg, extension)
+    {
+      WARN("notifying observers of extension"+ msg);
+      let bag = Cc["@mozilla.org/hash-property-bag;1"]
+                  .createInstance(Ci.nsIWritablePropertyBag2);
+      bag.setPropertyAsAString("id", extension.id);
+      bag.setPropertyAsAString("name", extension.name);
+      bag.setPropertyAsAString("version", extension.version);
+      svc.notifyObservers(bag, "AddonManager-event", msg);
+    }
+    this.addAddonListener({ onEnabling: function(extension) { notify("Enabled", extension) },
+                            onDisabling: function(extension) { notify("Disabled", extension) },
+                            onInstalling: function(extension) { notify("Installed", extension) },
+                            onUninstalling: function(extension) { notify("Uninstalled", extension) },
+                          });
+  },
 };
 
 /**
  * Should not be used outside of core Mozilla code. This is a private API for
  * the startup and platform integration code to use. Refer to the methods on
  * AddonManagerInternal for documentation however note that these methods are
  * subject to change at any time.
  */