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 id17440
push userdb48x@yahoo.com
push dateWed, 08 Dec 2010 04:15:54 +0000
treeherdermozilla-central@a89f24bf1798 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone2.0b6pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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.
  */