Bug 511761 - Only use compatibility.ini (not .autoreg, or stat()s) to invalidate fastloads and other caches (r=bsmedberg, RELAND WITH TEST FIX)
authorbhsieh@mozilla.com
Thu, 15 Oct 2009 23:32:29 -0700
changeset 33900 1c02fe2e41e5252bc6a5976826f00a1c7a3ef85a
parent 33899 431c83030afef7e74b7da6aed3cb9102a764c99e
child 33901 a847cf5b46690b1957f9fd53132ec19f15424ca8
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg, RELAND
bugs511761
milestone1.9.3a1pre
Bug 511761 - Only use compatibility.ini (not .autoreg, or stat()s) to invalidate fastloads and other caches (r=bsmedberg, RELAND WITH TEST FIX)
browser/app/Makefile.in
toolkit/mozapps/extensions/src/nsExtensionManager.js.in
toolkit/mozapps/extensions/test/unit/head_extensionmanager.js
toolkit/xre/nsAppRunner.cpp
xpcom/build/nsXPComInit.cpp
xpcom/components/nsComponentManager.cpp
xpcom/io/nsFastLoadFile.cpp
xpcom/system/nsIXULRuntime.idl
xulrunner/app/Makefile.in
--- a/browser/app/Makefile.in
+++ b/browser/app/Makefile.in
@@ -293,19 +293,16 @@ endif
 
 ifdef WINCE
 ifdef MOZ_FASTSTART
 libs::
 	cp -f $(DIST)/bin/faststartstub.exe $(DIST)/bin/$(MOZ_APP_NAME)faststart.exe
 endif
 endif
 
-libs::
-	touch $(DIST)/bin/.autoreg
-
 libs:: $(srcdir)/profile/prefs.js
 	$(INSTALL) $(IFLAGS1) $^ $(DIST)/bin/defaults/profile
 
 libs:: $(srcdir)/blocklist.xml
 	$(INSTALL) $(IFLAGS1) $^ $(DIST)/bin
 
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 
--- a/toolkit/mozapps/extensions/src/nsExtensionManager.js.in
+++ b/toolkit/mozapps/extensions/src/nsExtensionManager.js.in
@@ -83,17 +83,16 @@ const PREF_SELECTED_LOCALE            = 
 
 const DIR_EXTENSIONS                  = "extensions";
 const DIR_CHROME                      = "chrome";
 const DIR_STAGE                       = "staged-xpis";
 const FILE_EXTENSIONS                 = "extensions.rdf";
 const FILE_EXTENSION_MANIFEST         = "extensions.ini";
 const FILE_EXTENSIONS_STARTUP_CACHE   = "extensions.cache";
 const FILE_EXTENSIONS_LOG             = "extensions.log";
-const FILE_AUTOREG                    = ".autoreg";
 const FILE_INSTALL_MANIFEST           = "install.rdf";
 const FILE_CHROME_MANIFEST            = "chrome.manifest";
 
 const UNKNOWN_XPCOM_ABI               = "unknownABI";
 
 const TOOLKIT_ID                      = "toolkit@mozilla.org"
 
 const KEY_PROFILEDIR                  = "ProfD";
@@ -3594,26 +3593,23 @@ ExtensionManager.prototype = {
    * Say whether or not the Extension List has changed (and thus whether or not
    * the system will have to restart the next time it is started).
    * @param   val
    *          true if the Extension List has changed, false otherwise.
    * @returns |val|
    */
   set _extensionListChanged(val) {
     // When an extension has an operation perform on it (e.g. install, upgrade,
-    // disable, etc.) we are responsible for creating the .autoreg file and
-    // nsAppRunner is responsible for removing it on restart. At some point it
-    // may make sense to be able to cancel a registration but for now we only
-    // create the file.
-    try {
-      var autoregFile = getFile(KEY_PROFILEDIR, [FILE_AUTOREG]);
-      if (val && !autoregFile.exists())
-        autoregFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
-    }
-    catch (e) {
+    // disable, etc.) we are responsible for writing this information to 
+    // compatibility.ini, and nsAppRunner is responsible for checking this on 
+    // restart. At some point it may make sense to be able to cancel a 
+    // registration but for now we only create the file.
+    if (val) {
+      let XRE = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+      XRE.invalidateCachesOnRestart();
     }
     return val;
   },
 
   /**
    * Gathers data about an item specified by the supplied Install Manifest
    * and determines whether or not it can be installed as-is. It makes this
    * determination by validating the item's GUID, Version, and determining
--- a/toolkit/mozapps/extensions/test/unit/head_extensionmanager.js
+++ b/toolkit/mozapps/extensions/test/unit/head_extensionmanager.js
@@ -163,16 +163,17 @@ function createAppInfo(id, name, version
     version: version,
     appBuildID: "2007010101",
     platformVersion: platformVersion,
     platformBuildID: "2007010101",
     inSafeMode: false,
     logConsoleErrors: true,
     OS: "XPCShell",
     XPCOMABI: "noarch-spidermonkey",
+    invalidateCachesOnRestart: function invalidateCachesOnRestart() {},
 
     QueryInterface: function QueryInterface(iid) {
       if (iid.equals(Components.interfaces.nsIXULAppInfo)
        || iid.equals(Components.interfaces.nsIXULRuntime)
        || iid.equals(Components.interfaces.nsISupports))
         return this;
 
       throw Components.results.NS_ERROR_NO_INTERFACE;
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -233,16 +233,18 @@ protected:
   HANDLE mHandle;
 };
 #endif
 
 #if defined(XP_UNIX) || defined(XP_BEOS)
   extern void InstallUnixSignalHandlers(const char *ProgramName);
 #endif
 
+#define FILE_COMPATIBILITY_INFO NS_LITERAL_CSTRING("compatibility.ini")
+
 int    gArgc;
 char **gArgv;
 
 static char gToolkitVersion[20];
 static char gToolkitBuildID[40];
 
 static int    gRestartArgc;
 static char **gRestartArgv;
@@ -707,16 +709,58 @@ nsXULAppInfo::GetXPCOMABI(nsACString& aR
 
 NS_IMETHODIMP
 nsXULAppInfo::GetWidgetToolkit(nsACString& aResult)
 {
   aResult.AssignLiteral(MOZ_WIDGET_TOOLKIT);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsXULAppInfo::InvalidateCachesOnRestart()
+{
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = NS_GetSpecialDirectory(NS_APP_PROFILE_DIR_STARTUP, 
+                                       getter_AddRefs(file));
+  if (NS_FAILED(rv))
+    return rv;
+  if (!file)
+    return NS_ERROR_NOT_AVAILABLE;
+  
+  file->AppendNative(FILE_COMPATIBILITY_INFO);
+
+  nsCOMPtr<nsILocalFile> localFile(do_QueryInterface(file));
+  nsINIParser parser;
+  rv = parser.Init(localFile);
+  if (NS_FAILED(rv)) {
+    // This fails if compatibility.ini is not there, so we'll
+    // flush the caches on the next restart anyways.
+    return NS_OK;
+  }
+  
+  nsCAutoString buf;
+  rv = parser.GetString("Compatibility", "InvalidateCaches", buf);
+  
+  if (NS_FAILED(rv)) {
+    PRFileDesc *fd = nsnull;
+    localFile->OpenNSPRFileDesc(PR_RDWR | PR_APPEND, 0600, &fd);
+    if (!fd) {
+      NS_ERROR("could not create output stream");
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+    static const char kInvalidationHeader[] = NS_LINEBREAK "InvalidateCaches=1" NS_LINEBREAK;
+    rv = PR_Write(fd, kInvalidationHeader, sizeof(kInvalidationHeader) - 1);
+    PR_Close(fd);
+    
+    if (NS_FAILED(rv))
+      return rv;
+  }
+  return NS_OK;
+}
+
 #ifdef XP_WIN
 // Matches the enum in WinNT.h for the Vista SDK but renamed so that we can
 // safely build with the Vista SDK and without it.
 typedef enum 
 {
   VistaTokenElevationTypeDefault = 1,
   VistaTokenElevationTypeFull,
   VistaTokenElevationTypeLimited
@@ -2142,23 +2186,29 @@ SelectProfile(nsIProfileLock* *aResult, 
       return ProfileLockedDialog(profileDir, profileLocalDir, unlocker,
                                  aNative, aResult);
     }
   }
 
   return ShowProfileManager(profileSvc, aNative);
 }
 
-#define FILE_COMPATIBILITY_INFO NS_LITERAL_CSTRING("compatibility.ini")
-
+/** 
+ * Checks the compatibility.ini file to see if we have updated our application
+ * or otherwise invalidated our caches. If the application has been updated, 
+ * we return PR_FALSE; otherwise, we return PR_TRUE. We also write the status 
+ * of the caches (valid/invalid) into the return param aCachesOK. The aCachesOK
+ * is always invalid if the application has been updated. 
+ */
 static PRBool
 CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion,
                    const nsCString& aOSABI, nsIFile* aXULRunnerDir,
-                   nsIFile* aAppDir)
+                   nsIFile* aAppDir, PRBool* aCachesOK)
 {
+  *aCachesOK = false;
   nsCOMPtr<nsIFile> file;
   aProfileDir->Clone(getter_AddRefs(file));
   if (!file)
     return PR_FALSE;
   file->AppendNative(FILE_COMPATIBILITY_INFO);
 
   nsINIParser parser;
   nsCOMPtr<nsILocalFile> localFile(do_QueryInterface(file));
@@ -2200,16 +2250,20 @@ CheckCompatibility(nsIFile* aProfileDir,
     if (NS_FAILED(rv))
       return PR_FALSE;
 
     rv = lf->Equals(aAppDir, &eq);
     if (NS_FAILED(rv) || !eq)
       return PR_FALSE;
   }
 
+  rv = parser.GetString("Compatibility", "InvalidateCaches", buf);
+  
+  // If we see this flag, caches are invalid.
+  *aCachesOK = (NS_FAILED(rv) || !buf.EqualsLiteral("1"));
   return PR_TRUE;
 }
 
 static void BuildVersion(nsCString &aBuf)
 {
   aBuf.Assign(gAppData->version);
   aBuf.Append('_');
   aBuf.Append(gAppData->buildID);
@@ -2266,29 +2320,16 @@ WriteVersion(nsIFile* aProfileDir, const
   }
 
   static const char kNL[] = NS_LINEBREAK;
   PR_Write(fd, kNL, sizeof(kNL) - 1);
 
   PR_Close(fd);
 }
 
-static PRBool ComponentsListChanged(nsIFile* aProfileDir)
-{
-  nsCOMPtr<nsIFile> file;
-  aProfileDir->Clone(getter_AddRefs(file));
-  if (!file)
-    return PR_TRUE;
-  file->AppendNative(NS_LITERAL_CSTRING(".autoreg"));
-
-  PRBool exists = PR_FALSE;
-  file->Exists(&exists);
-  return exists;
-}
-
 static void RemoveComponentRegistries(nsIFile* aProfileDir, nsIFile* aLocalProfileDir,
                                       PRBool aRemoveEMFiles)
 {
   nsCOMPtr<nsIFile> file;
   aProfileDir->Clone(getter_AddRefs(file));
   if (!file)
     return;
 
@@ -2307,16 +2348,19 @@ static void RemoveComponentRegistries(ns
   }
 
   aLocalProfileDir->Clone(getter_AddRefs(file));
   if (!file)
     return;
 
   file->AppendNative(NS_LITERAL_CSTRING("XUL" PLATFORM_FASL_SUFFIX));
   file->Remove(PR_FALSE);
+  
+  file->SetNativeLeafName(NS_LITERAL_CSTRING("XPC" PLATFORM_FASL_SUFFIX));
+  file->Remove(PR_FALSE);
 }
 
 // To support application initiated restart via nsIAppStartup.quit, we
 // need to save various environment variables, and then restore them
 // before re-launching the application.
 
 static struct {
   const char *name;
@@ -3167,38 +3211,45 @@ XRE_main(int argc, char* argv[], const n
 #else
     // No TARGET_XPCOM_ABI, but at least the OS is known
     NS_NAMED_LITERAL_CSTRING(osABI, OS_TARGET "_UNKNOWN");
 #endif
 
     // Check for version compatibility with the last version of the app this 
     // profile was started with.  The format of the version stamp is defined
     // by the BuildVersion function.
-    PRBool versionOK = CheckCompatibility(profD, version, osABI,
+    // Also check to see if something has happened to invalidate our
+    // fastload caches, like an extension upgrade or installation.
+    PRBool cachesOK;
+    PRBool versionOK = CheckCompatibility(profD, version, osABI, 
                                           dirProvider.GetGREDir(),
-                                          gAppData->directory);
+                                          gAppData->directory, &cachesOK);
 
     // Every time a profile is loaded by a build with a different version,
     // it updates the compatibility.ini file saying what version last wrote
     // the compreg.dat.  On subsequent launches if the version matches, 
     // there is no need for re-registration.  If the user loads the same
     // profile in different builds the component registry must be
     // re-generated to prevent mysterious component loading failures.
     //
     if (gSafeMode) {
       RemoveComponentRegistries(profD, profLD, PR_FALSE);
       WriteVersion(profD, NS_LITERAL_CSTRING("Safe Mode"), osABI,
                    dirProvider.GetGREDir(), gAppData->directory);
     }
     else if (versionOK) {
-      if (ComponentsListChanged(profD)) {
+      if (!cachesOK) {
         // Remove compreg.dat and xpti.dat, forcing component re-registration.
         // The new list of additional components directories is derived from
         // information in "extensions.ini".
         RemoveComponentRegistries(profD, profLD, PR_FALSE);
+        
+        // Rewrite compatibility.ini to remove the flag
+        WriteVersion(profD, version, osABI,
+                     dirProvider.GetGREDir(), gAppData->directory);
       }
       // Nothing need be done for the normal startup case.
     }
     else {
       // Remove compreg.dat and xpti.dat, forcing component re-registration
       // with the default set of components (this disables any potentially
       // troublesome incompatible XPCOM components). 
       RemoveComponentRegistries(profD, profLD, PR_TRUE);
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -280,95 +280,16 @@ RegisterGenericFactory(nsIComponentRegis
     rv = registrar->RegisterFactory(info->mCID, 
                                     info->mDescription,
                                     info->mContractID, 
                                     fact);
     NS_RELEASE(fact);
     return rv;
 }
 
-// In order to support the installer, we need
-// to be told out of band if we should cause
-// an autoregister.  If the file ".autoreg" exists in the binary
-// directory, we check its timestamp against the timestamp of the
-// compreg.dat file.  If the .autoreg file is newer, we autoregister.
-static PRBool CheckUpdateFile()
-{
-    nsresult rv;
-    nsCOMPtr<nsIFile> compregFile;
-    rv = nsDirectoryService::gService->Get(NS_XPCOM_COMPONENT_REGISTRY_FILE,
-                                           NS_GET_IID(nsIFile),
-                                           getter_AddRefs(compregFile));
-
-    if (NS_FAILED(rv)) {
-        NS_WARNING("Getting NS_XPCOM_COMPONENT_REGISTRY_FILE failed");
-        return PR_FALSE;
-    }
-
-    PRInt64 compregModTime;
-    rv = compregFile->GetLastModifiedTime(&compregModTime);
-    if (NS_FAILED(rv))
-        return PR_TRUE;
-    
-    nsCOMPtr<nsIFile> file;
-    rv = nsDirectoryService::gService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, 
-                                           NS_GET_IID(nsIFile), 
-                                           getter_AddRefs(file));
-
-    if (NS_FAILED(rv)) {
-        NS_WARNING("Getting NS_XPCOM_CURRENT_PROCESS_DIR failed");
-        return PR_FALSE;
-    }
-
-    file->AppendNative(nsDependentCString(".autoreg"));
-
-    // superfluous cast
-    PRInt64 nowTime = PR_Now() / PR_USEC_PER_MSEC;
-    PRInt64 autoregModTime;
-    rv = file->GetLastModifiedTime(&autoregModTime);
-    if (NS_FAILED(rv))
-        goto next;
-
-    if (autoregModTime > compregModTime) {
-        if (autoregModTime < nowTime) {
-            return PR_TRUE;
-        } else {
-            NS_WARNING("Screwy timestamps, ignoring .autoreg");
-        }
-    }
-
-next:
-    nsCOMPtr<nsIFile> greFile;
-    rv = nsDirectoryService::gService->Get(NS_GRE_DIR,
-                                           NS_GET_IID(nsIFile),
-                                           getter_AddRefs(greFile));
-
-    if (NS_FAILED(rv)) {
-        NS_WARNING("Getting NS_GRE_DIR failed");
-        return PR_FALSE;
-    }
-
-    greFile->AppendNative(nsDependentCString(".autoreg"));
-
-    PRBool equals;
-    rv = greFile->Equals(file, &equals);
-    if (NS_SUCCEEDED(rv) && equals)
-        return PR_FALSE;
-
-    rv = greFile->GetLastModifiedTime(&autoregModTime);
-    if (NS_FAILED(rv))
-        return PR_FALSE;
-
-    if (autoregModTime > nowTime) {
-        NS_WARNING("Screwy timestamps, ignoring .autoreg");
-        return PR_FALSE;
-    }
-    return autoregModTime > compregModTime; 
-}
-
 
 nsComponentManagerImpl* nsComponentManagerImpl::gComponentManager = NULL;
 PRBool gXPCOMShuttingDown = PR_FALSE;
 
 // For each class that wishes to support nsIClassInfo, add a line like this
 // NS_DECL_CLASSINFO(nsMyClass)
 
 #define COMPONENT(NAME, Ctor)                                                  \
@@ -679,18 +600,19 @@ NS_InitXPCOM3(nsIServiceManager* *result
                           NS_SIMPLE_UNICHAR_STREAM_FACTORY_CONTRACTID,
                           nsSimpleUnicharStreamFactory::GetInstance());
     }
 
     // Pay the cost at startup time of starting this singleton.
     nsIInterfaceInfoManager* iim =
         xptiInterfaceInfoManager::GetInterfaceInfoManagerNoAddRef();
 
-    if (CheckUpdateFile() || NS_FAILED(
-        nsComponentManagerImpl::gComponentManager->ReadPersistentRegistry())) {
+    // "Re-register the world" if compreg.dat doesn't exist
+    rv = nsComponentManagerImpl::gComponentManager->ReadPersistentRegistry();
+    if (NS_FAILED(rv)) {
         // If the component registry is out of date, malformed, or incomplete,
         // autoregister the default component directories.
         (void) iim->AutoRegisterInterfaces();
         nsComponentManagerImpl::gComponentManager->AutoRegister(nsnull);
     }
 
     // After autoreg, but before we actually instantiate any components,
     // add any services listed in the "xpcom-directory-providers" category
--- a/xpcom/components/nsComponentManager.cpp
+++ b/xpcom/components/nsComponentManager.cpp
@@ -3001,21 +3001,29 @@ nsComponentManagerImpl::AutoRegisterComp
 
     nsCOMPtr<nsIHashable> lfhash(do_QueryInterface(aComponentFile));
     if (!lfhash) {
         NS_ERROR("localfile not implementing nsIHashable!");
         return NS_NOINTERFACE;
     }
 
     PRInt64 modTime = 0;
-    if (NS_SUCCEEDED(aComponentFile->GetLastModifiedTime(&modTime))) {
-        PRInt64 cachedModTime;
-        if (mAutoRegEntries.Get(lfhash, &cachedModTime) &&
-            cachedModTime == modTime)
+    PRInt64 cachedModTime;
+
+    // If it's in the cache, it should be valid.
+    // The cache file is removed if files are modified.
+    if (mAutoRegEntries.Get(lfhash, &cachedModTime)) {
+#ifdef DEBUG
+        if (NS_SUCCEEDED(aComponentFile->GetLastModifiedTime(&modTime))
+            && cachedModTime == modTime) {
             return NS_OK;
+        }
+#else
+        return NS_OK;
+#endif
     }
 
     const char *registryType = nsnull;
 
     nsCOMPtr<nsIModule> module;
 
     if (minLoader == NS_LOADER_TYPE_NATIVE) {
         rv = mNativeModuleLoader.LoadModule(aComponentFile,
--- a/xpcom/io/nsFastLoadFile.cpp
+++ b/xpcom/io/nsFastLoadFile.cpp
@@ -723,31 +723,30 @@ nsFastLoadFileReader::ReadFooter(nsFastL
         rv = Read64(reinterpret_cast<PRUint64*>(&fastLoadMtime));
         if (NS_FAILED(rv))
             return rv;
 
         nsCOMPtr<nsILocalFile> file;
         rv = NS_NewNativeLocalFile(filename, PR_TRUE, getter_AddRefs(file));
         if (NS_FAILED(rv))
             return rv;
-
+#ifdef DEBUG
         PRInt64 currentMtime;
         rv = file->GetLastModifiedTime(&currentMtime);
         if (NS_FAILED(rv))
             return rv;
 
         if (LL_NE(fastLoadMtime, currentMtime)) {
-#ifdef DEBUG
             nsCAutoString path;
             file->GetNativePath(path);
             printf("%s mtime changed, invalidating FastLoad file\n",
                    path.get());
-#endif
             return NS_ERROR_FAILURE;
         }
+#endif
 
         rv = readDeps->AppendElement(file);
         if (NS_FAILED(rv))
             return rv;
     }
 
     aFooter->mDependencies = readDeps;
     return NS_OK;
--- a/xpcom/system/nsIXULRuntime.idl
+++ b/xpcom/system/nsIXULRuntime.idl
@@ -39,17 +39,17 @@
 
 /**
  * Provides information about the XUL runtime.
  * @status UNSTABLE - This interface is not frozen and will probably change in
  *                    future releases. If you need this functionality to be
  *                    stable/frozen, please contact Benjamin Smedberg.
  */
 
-[scriptable, uuid(17311145-97da-49eb-b984-965bdee8879c)]
+[scriptable, uuid(C61D0C09-05AD-4E72-BAC5-203F3F380352)]
 interface nsIXULRuntime : nsISupports
 {
   /**
    * Whether the application was launched in safe mode.
    */
   readonly attribute boolean inSafeMode;
 
   /**
@@ -81,9 +81,16 @@ interface nsIXULRuntime : nsISupports
    */
   readonly attribute AUTF8String XPCOMABI;
 
   /**
    * A string tag identifying the target widget toolkit in use.
    * This is taken from the MOZ_WIDGET_TOOLKIT configure variable.
    */
   readonly attribute AUTF8String widgetToolkit;
+
+  /**
+   * Signal the apprunner to invalidate caches on the next restart.
+   * This will cause components to be autoregistered and all
+   * fastload data to be re-created.
+   */
+  void invalidateCachesOnRestart();
 };
--- a/xulrunner/app/Makefile.in
+++ b/xulrunner/app/Makefile.in
@@ -256,19 +256,16 @@ ifeq ($(MOZ_WIDGET_TOOLKIT),gtk2)
 	cp $(srcdir)/default16.png   $(DIST)/branding/default16.png
 	cp $(srcdir)/default32.png   $(DIST)/branding/default32.png
 	cp $(srcdir)/default48.png   $(DIST)/branding/default48.png
 endif
 ifeq ($(OS_ARCH),OS2)
 	cp $(srcdir)/xulrunner-os2.ico   $(DIST)/branding/xulrunner.ico
 endif
 
-libs::
-	touch $(DIST)/bin/.autoreg
-
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 
 FRAMEWORK_NAME = XUL
 FRAMEWORK_VERSION = $(MOZILLA_VERSION)
 
 libs:: $(PROGRAM)
 	mkdir -p $(DIST)/$(FRAMEWORK_NAME).framework/Versions/$(FRAMEWORK_VERSION)/Resources
 	$(NSINSTALL) $(srcdir)/macbuild/InfoPlist.strings $(DIST)/$(FRAMEWORK_NAME).framework/Versions/$(FRAMEWORK_VERSION)/Resources