Merge with m-c fd478c02c29c
authorDoug Turner <dougt@dougt.org>
Tue, 15 Nov 2011 11:18:06 -0800
changeset 83478 252ec90b5eaf02b63b1c0dbf1c5a15373f0ea780
parent 83477 c2d1751fe2748bd805fd35e701016cf07dc419db (current diff)
parent 81918 fd478c02c29c00077128a3d772ca18dfdf9ca119 (diff)
child 83479 2c4d1a8d4079b502d92f183c2791249da37ec5f1
push id519
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 00:38:35 +0000
treeherdermozilla-beta@788ea1ef610b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone11.0a1
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
Merge with m-c fd478c02c29c
build/poster.zip
config/autoconf.mk.in
configure.in
embedding/android/GeckoApp.java
layout/generic/nsFrame.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsIFrame.h
layout/xul/base/src/tree/src/nsITreeImageListener.h
mobile/app/mobile.js
widget/src/android/AndroidJavaWrappers.cpp
widget/src/android/Makefile.in
widget/src/android/nsWindow.cpp
--- a/.gitignore
+++ b/.gitignore
@@ -8,21 +8,21 @@ TAGS
 tags
 ID
 .DS_Store*
 
 # Vim swap files.
 .*.sw[a-z]
 
 # User files that may appear at the root
-.mozconfig
-mozconfig
-configure
-config.cache
-config.log
+/.mozconfig*
+/mozconfig
+/configure
+/config.cache
+/config.log
 
 # Empty marker file that's generated when we check out NSS
 security/manager/.nss.checkout
 
 # Build directories
 obj/*
 
 # Build directories for js shell
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -47,17 +47,16 @@ import re
 import select
 import shutil
 import signal
 import subprocess
 import sys
 import threading
 import tempfile
 import sqlite3
-import zipfile
 
 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 sys.path.insert(0, SCRIPT_DIR)
 import automationutils
 
 _DEFAULT_WEB_SERVER = "127.0.0.1"
 _DEFAULT_HTTP_PORT = 8888
 _DEFAULT_SSL_PORT = 4443
@@ -93,79 +92,16 @@ else:
 # threads, which is needed to process the output of the server and application
 # processes simultaneously.
 _log = logging.getLogger()
 handler = logging.StreamHandler(sys.stdout)
 _log.setLevel(logging.INFO)
 _log.addHandler(handler)
 
 
-class ZipFileReader(object):
-  """
-  Class to read zip files in Python 2.5 and later. Limited to only what we
-  actually use.
-  """
-
-  def __init__(self, filename):
-    self._zipfile = zipfile.ZipFile(filename, "r")
-
-  def __del__(self):
-    self._zipfile.close()
-
-  def _getnormalizedpath(self, path):
-    """
-    Gets a normalized path from 'path' (or the current working directory if
-    'path' is None). Also asserts that the path exists.
-    """
-    if path is None:
-      path = os.curdir
-    path = os.path.normpath(os.path.expanduser(path))
-    assert os.path.isdir(path)
-    return path
-
-  def _extractname(self, name, path):
-    """
-    Extracts a file with the given name from the zip file to the given path.
-    Also creates any directories needed along the way.
-    """
-    filename = os.path.normpath(os.path.join(path, name))
-    if name.endswith("/"):
-      os.makedirs(filename)
-    else:
-      path = os.path.split(filename)[0]
-      if not os.path.isdir(path):
-        os.makedirs(path)
-      with open(filename, "wb") as dest:
-        dest.write(self._zipfile.read(name))
-
-  def namelist(self):
-    return self._zipfile.namelist()
-
-  def read(self, name):
-    return self._zipfile.read(name)
-
-  def extract(self, name, path = None):
-    if hasattr(self._zipfile, "extract"):
-      return self._zipfile.extract(name, path)
-
-    # This will throw if name is not part of the zip file.
-    self._zipfile.getinfo(name)
-
-    self._extractname(name, self._getnormalizedpath(path))
-
-  def extractall(self, path = None):
-    if hasattr(self._zipfile, "extractall"):
-      return self._zipfile.extractall(path)
-
-    path = self._getnormalizedpath(path)
-
-    for name in self._zipfile.namelist():
-      self._extractname(name, path)
-
-
 #################
 # PROFILE SETUP #
 #################
 
 class SyntaxError(Exception):
   "Signifies a syntax error on a particular line in server-locations.txt."
 
   def __init__(self, lineno, msg = None):
@@ -1048,17 +984,17 @@ user_pref("camino.use_system_proxy_setti
 
     installRDFFilename = "install.rdf"
 
     extensionsRootDir = os.path.join(profileDir, "extensions", "staged")
     if not os.path.isdir(extensionsRootDir):
       os.makedirs(extensionsRootDir)
 
     if os.path.isfile(extensionSource):
-      reader = ZipFileReader(extensionSource)
+      reader = automationutils.ZipFileReader(extensionSource)
 
       for filename in reader.namelist():
         # Sanity check the zip file.
         if os.path.isabs(filename):
           self.log.info("INFO | automation.py | Cannot install extension, bad files in xpi")
           return
 
         # We may need to dig the extensionID out of the zip file...
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -31,21 +31,23 @@
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK ***** */
 
-import glob, logging, os, platform, shutil, subprocess, sys
+from __future__ import with_statement
+import glob, logging, os, platform, shutil, subprocess, sys, tempfile, urllib2, zipfile
 import re
 from urlparse import urlparse
 
 __all__ = [
+  "ZipFileReader",
   "addCommonOptions",
   "checkForCrashes",
   "dumpLeakLog",
   "isURL",
   "processLeakLog",
   "getDebuggerInfo",
   "DEBUGGER_INFO",
   "replaceBackSlashes",
@@ -65,16 +67,78 @@ DEBUGGER_INFO = {
   # valgrind doesn't explain much about leaks unless you set the
   # '--leak-check=full' flag.
   "valgrind": {
     "interactive": False,
     "args": "--leak-check=full"
   }
 }
 
+class ZipFileReader(object):
+  """
+  Class to read zip files in Python 2.5 and later. Limited to only what we
+  actually use.
+  """
+
+  def __init__(self, filename):
+    self._zipfile = zipfile.ZipFile(filename, "r")
+
+  def __del__(self):
+    self._zipfile.close()
+
+  def _getnormalizedpath(self, path):
+    """
+    Gets a normalized path from 'path' (or the current working directory if
+    'path' is None). Also asserts that the path exists.
+    """
+    if path is None:
+      path = os.curdir
+    path = os.path.normpath(os.path.expanduser(path))
+    assert os.path.isdir(path)
+    return path
+
+  def _extractname(self, name, path):
+    """
+    Extracts a file with the given name from the zip file to the given path.
+    Also creates any directories needed along the way.
+    """
+    filename = os.path.normpath(os.path.join(path, name))
+    if name.endswith("/"):
+      os.makedirs(filename)
+    else:
+      path = os.path.split(filename)[0]
+      if not os.path.isdir(path):
+        os.makedirs(path)
+      with open(filename, "wb") as dest:
+        dest.write(self._zipfile.read(name))
+
+  def namelist(self):
+    return self._zipfile.namelist()
+
+  def read(self, name):
+    return self._zipfile.read(name)
+
+  def extract(self, name, path = None):
+    if hasattr(self._zipfile, "extract"):
+      return self._zipfile.extract(name, path)
+
+    # This will throw if name is not part of the zip file.
+    self._zipfile.getinfo(name)
+
+    self._extractname(name, self._getnormalizedpath(path))
+
+  def extractall(self, path = None):
+    if hasattr(self._zipfile, "extractall"):
+      return self._zipfile.extractall(path)
+
+    path = self._getnormalizedpath(path)
+
+    for name in self._zipfile.namelist():
+      self._extractname(name, path)
+
 log = logging.getLogger()
 
 def isURL(thing):
   """Return True if |thing| looks like a URL."""
   return urlparse(thing).scheme != ''
 
 def addCommonOptions(parser, defaults={}):
   parser.add_option("--xre-path",
@@ -97,88 +161,87 @@ def addCommonOptions(parser, defaults={}
                            "the application on the command line")
   parser.add_option("--debugger-interactive",
                     action = "store_true", dest = "debuggerInteractive",
                     help = "prevents the test harness from redirecting "
                         "stdout and stderr for interactive debuggers")
 
 def checkForCrashes(dumpDir, symbolsPath, testName=None):
   stackwalkPath = os.environ.get('MINIDUMP_STACKWALK', None)
-  stackwalkCGI = os.environ.get('MINIDUMP_STACKWALK_CGI', None)
   # try to get the caller's filename if no test name is given
   if testName is None:
     try:
       testName = os.path.basename(sys._getframe(1).f_code.co_filename)
     except:
       testName = "unknown"
 
-  foundCrash = False
+  # Check preconditions
   dumps = glob.glob(os.path.join(dumpDir, '*.dmp'))
-  for d in dumps:
-    log.info("PROCESS-CRASH | %s | application crashed (minidump found)", testName)
-    print "Crash dump filename: " + d
-    if symbolsPath and stackwalkPath and os.path.exists(stackwalkPath):
-      p = subprocess.Popen([stackwalkPath, d, symbolsPath],
-                           stdout=subprocess.PIPE,
-                           stderr=subprocess.PIPE)
-      (out, err) = p.communicate()
-      if len(out) > 3:
-        # minidump_stackwalk is chatty, so ignore stderr when it succeeds.
-        print out
+  if len(dumps) == 0:
+    return False
+
+  foundCrash = False
+  removeSymbolsPath = False
+
+  # If our symbols are at a remote URL, download them now
+  if isURL(symbolsPath):
+    print "Downloading symbols from: " + symbolsPath
+    removeSymbolsPath = True
+    # Get the symbols and write them to a temporary zipfile
+    data = urllib2.urlopen(symbolsPath)
+    symbolsFile = tempfile.TemporaryFile()
+    symbolsFile.write(data.read())
+    # extract symbols to a temporary directory (which we'll delete after
+    # processing all crashes)
+    symbolsPath = tempfile.mkdtemp()
+    zfile = ZipFileReader(symbolsFile)
+    zfile.extractall(symbolsPath)
+
+  try:
+    for d in dumps:
+      log.info("PROCESS-CRASH | %s | application crashed (minidump found)", testName)
+      print "Crash dump filename: " + d
+      if symbolsPath and stackwalkPath and os.path.exists(stackwalkPath):
+        # run minidump stackwalk
+        p = subprocess.Popen([stackwalkPath, d, symbolsPath],
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE)
+        (out, err) = p.communicate()
+        if len(out) > 3:
+          # minidump_stackwalk is chatty, so ignore stderr when it succeeds.
+          print out
+        else:
+          print "stderr from minidump_stackwalk:"
+          print err
+        if p.returncode != 0:
+          print "minidump_stackwalk exited with return code %d" % p.returncode
       else:
-        print "stderr from minidump_stackwalk:"
-        print err
-      if p.returncode != 0:
-        print "minidump_stackwalk exited with return code %d" % p.returncode
-    elif stackwalkCGI and symbolsPath and isURL(symbolsPath):
-      f = None
-      try:
-        f = open(d, "rb")
-        sys.path.append(os.path.join(os.path.dirname(__file__), "poster.zip"))
-        from poster.encode import multipart_encode
-        from poster.streaminghttp import register_openers
-        import urllib2
-        register_openers()
-        datagen, headers = multipart_encode({"minidump": f,
-                                             "symbols": symbolsPath})
-        request = urllib2.Request(stackwalkCGI, datagen, headers)
-        result = urllib2.urlopen(request).read()
-        if len(result) > 3:
-          print result
-        else:
-          print "stackwalkCGI returned nothing."
-      finally:
-        if f:
-          f.close()
-    else:
-      if not symbolsPath:
-        print "No symbols path given, can't process dump."
-      if not stackwalkPath and not stackwalkCGI:
-        print "Neither MINIDUMP_STACKWALK nor MINIDUMP_STACKWALK_CGI is set, can't process dump."
+        if not symbolsPath:
+          print "No symbols path given, can't process dump."
+        if not stackwalkPath:
+          print "MINIDUMP_STACKWALK not set, can't process dump."
+        elif stackwalkPath and not os.path.exists(stackwalkPath):
+          print "MINIDUMP_STACKWALK binary not found: %s" % stackwalkPath
+      dumpSavePath = os.environ.get('MINIDUMP_SAVE_PATH', None)
+      if dumpSavePath:
+        shutil.move(d, dumpSavePath)
+        print "Saved dump as %s" % os.path.join(dumpSavePath,
+                                                os.path.basename(d))
       else:
-        if stackwalkPath and not os.path.exists(stackwalkPath):
-          print "MINIDUMP_STACKWALK binary not found: %s" % stackwalkPath
-        elif stackwalkCGI and not isURL(stackwalkCGI):
-          print "MINIDUMP_STACKWALK_CGI is not a URL: %s" % stackwalkCGI
-        elif symbolsPath and not isURL(symbolsPath):
-          print "symbolsPath is not a URL: %s" % symbolsPath
-    dumpSavePath = os.environ.get('MINIDUMP_SAVE_PATH', None)
-    if dumpSavePath:
-      shutil.move(d, dumpSavePath)
-      print "Saved dump as %s" % os.path.join(dumpSavePath,
-                                              os.path.basename(d))
-    else:
-      os.remove(d)
-    extra = os.path.splitext(d)[0] + ".extra"
-    if os.path.exists(extra):
-      os.remove(extra)
-    foundCrash = True
+        os.remove(d)
+      extra = os.path.splitext(d)[0] + ".extra"
+      if os.path.exists(extra):
+        os.remove(extra)
+      foundCrash = True
+  finally:
+    if removeSymbolsPath:
+      shutil.rmtree(symbolsPath)
 
   return foundCrash
-  
+
 def getFullPath(directory, path):
   "Get an absolute path relative to 'directory'."
   return os.path.normpath(os.path.join(directory, os.path.expanduser(path)))
 
 def searchPath(directory, path):
   "Go one step beyond getFullPath and try the various folders in PATH"
   # Try looking in the current working directory first.
   newpath = getFullPath(directory, path)
deleted file mode 100644
index 122b4acd4698a52d208293a904130c5310851ab9..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -1949,16 +1949,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   // assume that *most* cycles you actually want to break somewhere
   // else, and not unlink an awful lot here.
 
   tmp->mIdentifierMap.Clear();
 
   if (tmp->mAnimationController) {
     tmp->mAnimationController->Unlink();
   }
+
+  tmp->mPendingTitleChangeEvent.Revoke();
   
   tmp->mInUnlinkOrDeletion = false;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 
 nsresult
 nsDocument::Init()
 {
--- a/content/svg/content/src/nsSVGPathElement.cpp
+++ b/content/svg/content/src/nsSVGPathElement.cpp
@@ -422,17 +422,17 @@ nsSVGPathElement::GetMarkPoints(nsTArray
 
 void
 nsSVGPathElement::ConstructPath(gfxContext *aCtx)
 {
   mD.GetAnimValue().ConstructPath(aCtx);
 }
 
 gfxFloat
-nsSVGPathElement::GetScale()
+nsSVGPathElement::GetPathLengthScale()
 {
   if (mPathLength.IsExplicitlySet()) {
 
     nsRefPtr<gfxFlattenedPath> flat =
       GetFlattenedPath(PrependLocalTransformTo(gfxMatrix()));
     float pathLength = mPathLength.GetAnimValue();
 
     if (flat && pathLength != 0) {
--- a/content/svg/content/src/nsSVGPathElement.h
+++ b/content/svg/content/src/nsSVGPathElement.h
@@ -93,17 +93,22 @@ public:
   virtual SVGAnimatedPathSegList* GetAnimPathSegList() {
     return &mD;
   }
 
   virtual nsIAtom* GetPathDataAttrName() const {
     return nsGkAtoms::d;
   }
 
-  gfxFloat GetScale();
+  /**
+   * Gets the ratio of the actual path length to the content author's estimated
+   * length (as provided by the <path> element's 'pathLength' attribute). This
+   * is used to scale stroke dashing, and to scale offsets along a textPath.
+   */
+  gfxFloat GetPathLengthScale();
 
 protected:
 
   // nsSVGElement method
   virtual NumberAttributesInfo GetNumberInfo();
 
   SVGAnimatedPathSegList mD;
   nsSVGNumber2 mPathLength;
--- a/dom/src/storage/nsDOMStorage.cpp
+++ b/dom/src/storage/nsDOMStorage.cpp
@@ -282,26 +282,37 @@ nsDOMStorageManager::Initialize()
   // No observers needed in non-chrome
   if (XRE_GetProcessType() != GeckoProcessType_Default)
     return NS_OK;
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (!os)
     return NS_OK;
 
-  os->AddObserver(gStorageManager, "cookie-changed", false);
-  os->AddObserver(gStorageManager, "offline-app-removed", false);
-  os->AddObserver(gStorageManager, NS_PRIVATE_BROWSING_SWITCH_TOPIC, false);
-  os->AddObserver(gStorageManager, "profile-after-change", false);
-  os->AddObserver(gStorageManager, "perm-changed", false);
-  os->AddObserver(gStorageManager, "browser:purge-domain-data", false);
+  nsresult rv;
+  rv = os->AddObserver(gStorageManager, "cookie-changed", false);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = os->AddObserver(gStorageManager, "offline-app-removed", false);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = os->AddObserver(gStorageManager, NS_PRIVATE_BROWSING_SWITCH_TOPIC,
+                       false);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = os->AddObserver(gStorageManager, "profile-after-change", false);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = os->AddObserver(gStorageManager, "perm-changed", false);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = os->AddObserver(gStorageManager, "browser:purge-domain-data", false);
+  NS_ENSURE_SUCCESS(rv, rv);
   // Used for temporary table flushing
-  os->AddObserver(gStorageManager, "profile-before-change", false);
-  os->AddObserver(gStorageManager, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
-  os->AddObserver(gStorageManager, NS_DOMSTORAGE_FLUSH_TIMER_TOPIC, false);
+  rv = os->AddObserver(gStorageManager, "profile-before-change", false);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = os->AddObserver(gStorageManager, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = os->AddObserver(gStorageManager, NS_DOMSTORAGE_FLUSH_TIMER_TOPIC, false);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 //static
 nsDOMStorageManager*
 nsDOMStorageManager::GetInstance()
 {
--- a/embedding/android/GeckoApp.java
+++ b/embedding/android/GeckoApp.java
@@ -1090,17 +1090,19 @@ abstract public class GeckoApp
             sTryCatchAttached = true;
             mMainHandler.post(new Runnable() {
                 public void run() {
                     try {
                         Looper.loop();
                     } catch (Exception e) {
                         Log.e(LOG_NAME, "top level exception", e);
                         StringWriter sw = new StringWriter();
-                        e.printStackTrace(new PrintWriter(sw));
+                        PrintWriter pw = new PrintWriter(sw);
+                        e.printStackTrace(pw);
+                        pw.flush();
                         GeckoAppShell.reportJavaCrash(sw.toString());
                     }
                     // resetting this is kinda pointless, but oh well
                     sTryCatchAttached = false;
                 }
             });
         }
 
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -341,17 +341,17 @@ RasterImage::AdvanceFrame(TimeStamp aTim
   // If we're done decoding the next frame, go ahead and display it now and
   // reinit with the next frame's delay time.
   if (haveFullNextFrame) {
     if (mFrames.Length() == nextFrameIndex) {
       // End of Animation, unless we are looping forever
 
       // If animation mode is "loop once", it's time to stop animating
       if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
-        mAnimationFinished = PR_TRUE;
+        mAnimationFinished = true;
         EvaluateAnimation();
       }
 
       // We may have used compositingFrame to build a frame, and then copied
       // it back into mFrames[..].  If so, delete composite to save memory
       if (mAnim->compositingFrame && mAnim->lastCompositedFrameIndex == -1) {
         mAnim->compositingFrame = nsnull;
       }
@@ -378,17 +378,17 @@ RasterImage::AdvanceFrame(TimeStamp aTim
 
   } else {
     // Uh oh, the frame we want to show is currently being decoded (partial)
     // Wait until the next refresh driver tick and try again
     return false;
   }
 
   if (!(timeout > 0)) {
-    mAnimationFinished = PR_TRUE;
+    mAnimationFinished = true;
     EvaluateAnimation();
   }
 
   imgFrame *frameToUse = nsnull;
 
   if (nextFrameIndex == 0) {
     frameToUse = nextFrame;
     *aDirtyRect = mAnim->firstFrameRefreshArea;
@@ -399,23 +399,23 @@ RasterImage::AdvanceFrame(TimeStamp aTim
       return false;
     }
 
     // Change frame
     if (NS_FAILED(DoComposite(aDirtyRect, curFrame,
                               nextFrame, nextFrameIndex))) {
       // something went wrong, move on to next
       NS_WARNING("RasterImage::AdvanceFrame(): Compositing of frame failed");
-      nextFrame->SetCompositingFailed(PR_TRUE);
+      nextFrame->SetCompositingFailed(true);
       mAnim->currentAnimationFrameIndex = nextFrameIndex;
       mAnim->currentAnimationFrameTime = aTime;
       return false;
     }
 
-    nextFrame->SetCompositingFailed(PR_FALSE);
+    nextFrame->SetCompositingFailed(false);
   }
 
   // Set currentAnimationFrameIndex at the last possible moment
   mAnim->currentAnimationFrameIndex = nextFrameIndex;
   mAnim->currentAnimationFrameTime = aTime;
 
   return true;
 }
--- a/js/jsd/jsd_hook.c
+++ b/js/jsd/jsd_hook.c
@@ -119,34 +119,34 @@ jsd_ThrowHandler(JSContext *cx, JSScript
                  jsval *rval, void *closure)
 {
     JSDScript*      jsdscript;
     JSDContext*     jsdc = (JSDContext*) closure;
     JSD_ExecutionHookProc hook;
     void*                 hookData;
 
     if( ! jsdc || ! jsdc->inited )
-        return JSD_HOOK_RETURN_CONTINUE_THROW;
+        return JSTRAP_CONTINUE;
 
     if( JSD_IS_DANGEROUS_THREAD(jsdc) )
-        return JSD_HOOK_RETURN_CONTINUE_THROW;
+        return JSTRAP_CONTINUE;
 
     /* local in case jsdc->throwHook gets cleared on another thread */
     JSD_LOCK();
     hook     = jsdc->throwHook;
     hookData = jsdc->throwHookData;
     JSD_UNLOCK();
     if (!hook)
-        return JSD_HOOK_RETURN_CONTINUE_THROW;
+        return JSTRAP_CONTINUE;
 
     JSD_LOCK_SCRIPTS(jsdc);
     jsdscript = jsd_FindOrCreateJSDScript(jsdc, cx, script, NULL);
     JSD_UNLOCK_SCRIPTS(jsdc);
     if( ! jsdscript )
-        return JSD_HOOK_RETURN_CONTINUE_THROW;
+        return JSTRAP_CONTINUE;
 
     JS_GetPendingException(cx, rval);
 
     return jsd_CallExecutionHook(jsdc, cx, JSD_HOOK_THROW,
                                  hook, hookData, rval);
 }
 
 JSTrapStatus
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -44,16 +44,18 @@
 #include "BasicLayers.h"
 #include "nsSubDocumentFrame.h"
 #include "nsCSSRendering.h"
 #include "nsCSSFrameConstructor.h"
 #include "gfxUtils.h"
 #include "nsImageFrame.h"
 #include "nsRenderingContext.h"
 
+#include "mozilla/Preferences.h"
+
 #ifdef DEBUG
 #include <stdio.h>
 #endif
 
 using namespace mozilla::layers;
 
 namespace mozilla {
 
@@ -465,16 +467,37 @@ FrameLayerBuilder::DisplayItemDataEntry:
   for (PRUint32 i = 0; i < mData.Length(); ++i) {
     if (mData[i].mLayer->GetType() == Layer::TYPE_CONTAINER &&
         mData[i].mLayerState != LAYER_ACTIVE_EMPTY)
       return true;
   }
   return false;
 }
 
+void
+FrameLayerBuilder::FlashPaint(gfxContext *aContext)
+{
+  static bool sPaintFlashingEnabled;
+  static bool sPaintFlashingPrefCached = false;
+
+  if (!sPaintFlashingPrefCached) {
+    sPaintFlashingPrefCached = true;
+    mozilla::Preferences::AddBoolVarCache(&sPaintFlashingEnabled, 
+                                          "nglayout.debug.paint_flashing");
+  }
+
+  if (sPaintFlashingEnabled) {
+    float r = float(rand()) / RAND_MAX;
+    float g = float(rand()) / RAND_MAX;
+    float b = float(rand()) / RAND_MAX;
+    aContext->SetColor(gfxRGBA(r, g, b, 0.2));
+    aContext->Paint();
+  }
+}
+
 /* static */ nsTArray<FrameLayerBuilder::DisplayItemData>*
 FrameLayerBuilder::GetDisplayItemDataArrayForFrame(nsIFrame* aFrame)
 {
   FrameProperties props = aFrame->Properties();
   LayerManagerData *data =
     reinterpret_cast<LayerManagerData*>(props.Get(LayerManagerDataProperty()));
   if (!data)
     return nsnull;
@@ -2122,16 +2145,18 @@ FrameLayerBuilder::DrawThebesLayer(Thebe
 
     if (builder->LayerBuilder()->CheckDOMModified())
       break;
   }
 
   if (setClipRect) {
     aContext->Restore();
   }
+
+  FlashPaint(aContext);
 }
 
 bool
 FrameLayerBuilder::CheckDOMModified()
 {
   if (!mRootPresContext ||
       mInitialDOMGeneration == mRootPresContext->GetDOMGeneration())
     return false;
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -436,16 +436,19 @@ protected:
     nsAutoTArray<DisplayItemData, 1> mData;
 
     enum { ALLOW_MEMMOVE = false };
   };
 
   // LayerManagerData needs to see DisplayItemDataEntry.
   friend class LayerManagerData;
 
+  // Flash the area within the context clip if paint flashing is enabled.
+  static void FlashPaint(gfxContext *aContext);
+
   /*
    * Get the DisplayItemData array associated with this frame, or null if one
    * doesn't exist.
    *
    * Note that the pointer returned here is only valid so long as you don't
    * poke the LayerManagerData's mFramesWithLayers hashtable.
    */
   static nsTArray<DisplayItemData>* GetDisplayItemDataArrayForFrame(nsIFrame *aFrame);
--- a/layout/svg/base/src/nsSVGGeometryFrame.cpp
+++ b/layout/svg/base/src/nsSVGGeometryFrame.cpp
@@ -115,17 +115,18 @@ nsSVGGeometryFrame::GetStrokeDashArray(g
   if (count) {
     const nsStyleCoord *dasharray = GetStyleSVG()->mStrokeDasharray;
     nsPresContext *presContext = PresContext();
     gfxFloat totalLength = 0.0f;
 
     gfxFloat pathScale = 1.0;
 
     if (mContent->Tag() == nsGkAtoms::path) {
-      pathScale = static_cast<nsSVGPathElement*>(mContent)->GetScale();
+      pathScale =
+        static_cast<nsSVGPathElement*>(mContent)->GetPathLengthScale();
       if (pathScale <= 0) {
         return NS_OK;
       }
     }
 
     dashes = new gfxFloat[count];
     if (dashes) {
       for (PRUint32 i = 0; i < count; i++) {
--- a/layout/svg/base/src/nsSVGGlyphFrame.cpp
+++ b/layout/svg/base/src/nsSVGGlyphFrame.cpp
@@ -746,17 +746,17 @@ nsSVGGlyphFrame::GetCharacterPositions(n
 
     // textPath frame, but invalid target
     if (!data)
       return false;
 
     if (!aCharacterPositions->SetLength(strLength))
       return false;
 
-    gfxFloat pathScale = textPath->GetPathScale();
+    gfxFloat pathScale = textPath->GetOffsetScale();
 
     CharacterPosition *cp = aCharacterPositions->Elements();
 
     gfxFloat length = data->GetLength();
 
     for (PRUint32 i = 0; i < strLength; i++) {
       gfxFloat halfAdvance =
         mTextRun->GetAdvanceWidth(i, 1, nsnull)*aMetricsScale / 2.0;
@@ -872,17 +872,17 @@ nsSVGGlyphFrame::GetSubStringAdvance(PRU
 
   nsTArray<float> dxlist, notUsed;
   GetEffectiveDxDy(mTextRun->GetLength(), dxlist, notUsed);
   PRUint32 dxcount = dxlist.Length();
   if (dxcount) {
     gfxFloat pathScale = 1.0;
     nsSVGTextPathFrame *textPath = FindTextPathParent();
     if (textPath)
-      pathScale = textPath->GetPathScale();
+      pathScale = textPath->GetOffsetScale();
     if (dxcount > aFragmentChars) 
       dxcount = aFragmentChars;
     for (PRUint32 i = aCharnum; i < dxcount; i++) {
       advance += dxlist[i] * pathScale;
     }
   }
 
   return float(advance);
@@ -1096,17 +1096,17 @@ nsSVGGlyphFrame::SetGlyphPosition(gfxPoi
     x += mTextRun->GetAdvanceWidth(0, strLength, nsnull) * metricsScale;
   }
 
   gfxFloat y = (textPath || yCount <= 1) ? aPosition->y : yList[yCount - 1];
   aPosition->MoveTo(x, y);
 
   gfxFloat pathScale = 1.0;
   if (textPath)
-    pathScale = textPath->GetPathScale();
+    pathScale = textPath->GetOffsetScale();
 
   nsTArray<float> dxList, dyList;
   GetEffectiveDxDy(strLength, dxList, dyList);
 
   PRUint32 dxcount = NS_MIN(dxList.Length(), strLength);
   if (dxcount > 0) {
     mPosition.x += dxList[0] * pathScale;
   }
--- a/layout/svg/base/src/nsSVGTextPathFrame.cpp
+++ b/layout/svg/base/src/nsSVGTextPathFrame.cpp
@@ -161,27 +161,28 @@ nsSVGTextPathFrame::GetStartOffset()
 
   if (val == 0.0f)
     return 0.0;
 
   if (length->IsPercentage()) {
     nsRefPtr<gfxFlattenedPath> data = GetFlattenedPath();
     return data ? (val * data->GetLength() / 100.0) : 0.0;
   }
-  return val * GetPathScale();
+  return val * GetOffsetScale();
 }
 
 gfxFloat
-nsSVGTextPathFrame::GetPathScale() 
+nsSVGTextPathFrame::GetOffsetScale()
 {
   nsIFrame *pathFrame = GetPathFrame();
   if (!pathFrame)
     return 1.0;
 
-  return static_cast<nsSVGPathElement*>(pathFrame->GetContent())->GetScale();
+  return static_cast<nsSVGPathElement*>(pathFrame->GetContent())->
+    GetPathLengthScale();
 }
 
 //----------------------------------------------------------------------
 // nsIFrame methods
 
 NS_IMETHODIMP
 nsSVGTextPathFrame::AttributeChanged(PRInt32         aNameSpaceID,
                                      nsIAtom*        aAttribute,
--- a/layout/svg/base/src/nsSVGTextPathFrame.h
+++ b/layout/svg/base/src/nsSVGTextPathFrame.h
@@ -79,18 +79,30 @@ public:
     return MakeFrameName(NS_LITERAL_STRING("SVGTextPath"), aResult);
   }
 #endif
 
   // nsSVGTextPathFrame methods:
   already_AddRefed<gfxFlattenedPath> GetFlattenedPath();
   nsIFrame *GetPathFrame();
 
+  /**
+   * Gets the scale by which offsets along this textPath must be scaled. This
+   * scaling is due to the user provided 'pathLength' attribute on the <path>
+   * element, which is a user provided estimate of the path length.
+   */
+  gfxFloat GetOffsetScale();
+
+  /**
+   * Gets the offset from the start of the path at which the first character
+   * should be positioned. The value returned already takes GetOffsetScale
+   * into account.
+   */
   gfxFloat GetStartOffset();
-  gfxFloat GetPathScale();
+
 protected:
 
   virtual void GetXY(SVGUserUnitList *aX, SVGUserUnitList *aY);
   virtual void GetDxDy(SVGUserUnitList *aDx, SVGUserUnitList *aDy);
   virtual const SVGNumberList *GetRotate();
 };
 
 #endif
--- a/layout/tools/reftest/Makefile.in
+++ b/layout/tools/reftest/Makefile.in
@@ -79,17 +79,16 @@ endif
 _HARNESS_FILES = \
   $(srcdir)/runreftest.py \
   $(srcdir)/remotereftest.py \
   automation.py \
   $(topsrcdir)/build/mobile/devicemanager.py \
   $(topsrcdir)/build/mobile/devicemanagerADB.py \
   $(topsrcdir)/build/mobile/devicemanagerSUT.py \
   $(topsrcdir)/build/automationutils.py \
-  $(topsrcdir)/build/poster.zip \
   $(topsrcdir)/build/mobile/remoteautomation.py \
   $(topsrcdir)/testing/mochitest/server.js \
   $(topsrcdir)/build/pgo/server-locations.txt \
   $(NULL)
 
 $(_DEST_DIR):
 	$(NSINSTALL) -D $@
 
deleted file mode 100644
--- a/layout/xul/base/src/tree/src/nsITreeImageListener.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is mozilla.org code.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (C) 2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Dave Hyatt <hyatt@mozilla.org> (Original Author)
- *   Jan Varga <varga@ku.sk>
- *   Scott Johnson <sjohnson@mozilla.com>, Mozilla Corporation
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-#ifndef nsITreeImageListener_h__
-#define nsITreeImageListener_h__
-
-// The interface for our image listener.
-// {90586540-2D50-403e-8DCE-981CAA778444}
-#define NS_ITREEIMAGELISTENER_IID \
-{ 0x90586540, 0x2d50, 0x403e, { 0x8d, 0xce, 0x98, 0x1c, 0xaa, 0x77, 0x84, 0x44 } }
-
-class nsITreeImageListener : public nsISupports
-{
-public:
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITREEIMAGELISTENER_IID)
-
-  NS_IMETHOD AddCell(PRInt32 aIndex, nsITreeColumn* aCol) = 0;
-
-  /**
-   * Clear the internal frame pointer to prevent dereferencing an object
-   * that no longer exists.
-   */
-  NS_IMETHOD ClearFrame() = 0;
-};
-
-NS_DEFINE_STATIC_IID_ACCESSOR(nsITreeImageListener, NS_ITREEIMAGELISTENER_IID)
-
-#endif
--- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
+++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.cpp
@@ -2152,19 +2152,21 @@ nsTreeBodyFrame::GetImage(PRInt32 aRowIn
     // We can only call GetAnimated if we're decoded
     if (*aResult && (status & imgIRequest::STATUS_DECODE_COMPLETE))
       (*aResult)->GetAnimated(&animated);
 
     if ((!(status & imgIRequest::STATUS_LOAD_COMPLETE)) || animated) {
       // We either aren't done loading, or we're animating. Add our row as a listener for invalidations.
       nsCOMPtr<imgIDecoderObserver> obs;
       imgReq->GetDecoderObserver(getter_AddRefs(obs));
-      nsCOMPtr<nsITreeImageListener> listener(do_QueryInterface(obs));
-      if (listener)
-        listener->AddCell(aRowIndex, aCol);
+
+      if (obs) {
+        static_cast<nsTreeImageListener*> (obs.get())->AddCell(aRowIndex, aCol);
+      }
+
       return NS_OK;
     }
   }
 
   if (!*aResult) {
     // Create a new nsTreeImageListener object and pass it our row and column
     // information.
     nsTreeImageListener* listener = new nsTreeImageListener(this);
--- a/layout/xul/base/src/tree/src/nsTreeBodyFrame.h
+++ b/layout/xul/base/src/tree/src/nsTreeBodyFrame.h
@@ -56,17 +56,16 @@
 #include "nsTreeColumns.h"
 #include "nsAutoPtr.h"
 #include "nsDataHashtable.h"
 #include "imgIRequest.h"
 #include "imgIDecoderObserver.h"
 #include "nsScrollbarFrame.h"
 #include "nsThreadUtils.h"
 #include "mozilla/LookAndFeel.h"
-#include "nsITreeImageListener.h"
 
 class nsOverflowChecker;
 class nsTreeImageListener;
 
 // An entry in the tree's image cache
 struct nsTreeImageCacheEntry
 {
   nsTreeImageCacheEntry() {}
--- a/layout/xul/base/src/tree/src/nsTreeImageListener.cpp
+++ b/layout/xul/base/src/tree/src/nsTreeImageListener.cpp
@@ -37,17 +37,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsTreeImageListener.h"
 #include "nsITreeBoxObject.h"
 #include "imgIRequest.h"
 #include "imgIContainer.h"
 
-NS_IMPL_ISUPPORTS3(nsTreeImageListener, imgIDecoderObserver, imgIContainerObserver, nsITreeImageListener)
+NS_IMPL_ISUPPORTS2(nsTreeImageListener, imgIDecoderObserver, imgIContainerObserver)
 
 nsTreeImageListener::nsTreeImageListener(nsTreeBodyFrame* aTreeFrame)
   : mTreeFrame(aTreeFrame),
     mInvalidationSuppressed(true),
     mInvalidationArea(nsnull)
 {
 }
 
@@ -87,17 +87,17 @@ NS_IMETHODIMP nsTreeImageListener::OnDat
 NS_IMETHODIMP nsTreeImageListener::FrameChanged(imgIContainer *aContainer,
                                                 const nsIntRect *aDirtyRect)
 {
   Invalidate();
   return NS_OK;
 }
 
 
-NS_IMETHODIMP
+void
 nsTreeImageListener::AddCell(PRInt32 aIndex, nsITreeColumn* aCol)
 {
   if (!mInvalidationArea) {
     mInvalidationArea = new InvalidationArea(aCol);
     mInvalidationArea->AddRow(aIndex);
   }
   else {
     InvalidationArea* currArea;
@@ -109,18 +109,16 @@ nsTreeImageListener::AddCell(PRInt32 aIn
     }
     if (!currArea) {
       currArea = new InvalidationArea(aCol);
       currArea->SetNext(mInvalidationArea);
       mInvalidationArea = currArea;
       mInvalidationArea->AddRow(aIndex);
     }
   }
-
-  return NS_OK;
 }
 
 
 void
 nsTreeImageListener::Invalidate()
 {
   if (!mInvalidationSuppressed) {
     for (InvalidationArea* currArea = mInvalidationArea; currArea;
--- a/layout/xul/base/src/tree/src/nsTreeImageListener.h
+++ b/layout/xul/base/src/tree/src/nsTreeImageListener.h
@@ -40,43 +40,42 @@
 #ifndef nsTreeImageListener_h__
 #define nsTreeImageListener_h__
 
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsITreeColumns.h"
 #include "nsStubImageDecoderObserver.h"
 #include "nsTreeBodyFrame.h"
-#include "nsITreeImageListener.h"
 
 // This class handles image load observation.
-class nsTreeImageListener : public nsStubImageDecoderObserver, public nsITreeImageListener
+class nsTreeImageListener : public nsStubImageDecoderObserver
 {
 public:
   nsTreeImageListener(nsTreeBodyFrame *aTreeFrame);
   ~nsTreeImageListener();
 
   NS_DECL_ISUPPORTS
   // imgIDecoderObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage);
   NS_IMETHOD OnImageIsAnimated(imgIRequest* aRequest);
   NS_IMETHOD OnDataAvailable(imgIRequest *aRequest, bool aCurrentFrame,
                              const nsIntRect *aRect);
   // imgIContainerObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD FrameChanged(imgIContainer *aContainer,
                           const nsIntRect *aDirtyRect);
 
-  NS_IMETHOD AddCell(PRInt32 aIndex, nsITreeColumn* aCol);
   NS_IMETHOD ClearFrame();
 
   friend class nsTreeBodyFrame;
 
 protected:
   void UnsuppressInvalidation() { mInvalidationSuppressed = false; }
   void Invalidate();
+  void AddCell(PRInt32 aIndex, nsITreeColumn* aCol);
 
 private:
   nsTreeBodyFrame* mTreeFrame;
 
   // A guard that prevents us from recursive painting.
   bool mInvalidationSuppressed;
 
   class InvalidationArea {
--- a/netwerk/protocol/http/nsHttpPipeline.cpp
+++ b/netwerk/protocol/http/nsHttpPipeline.cpp
@@ -161,17 +161,18 @@ nsHttpPipeline::OnHeadersAvailable(nsAHt
 
     // trans has now received its response headers; forward to the real connection
     return mConnection->OnHeadersAvailable(trans, requestHead, responseHead, reset);
 }
 
 nsresult
 nsHttpPipeline::ResumeSend()
 {
-    NS_NOTREACHED("nsHttpPipeline::ResumeSend");
+    if (mConnection)
+        return mConnection->ResumeSend();
     return NS_ERROR_UNEXPECTED;
 }
 
 nsresult
 nsHttpPipeline::ResumeRecv()
 {
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     NS_ASSERTION(mConnection, "no connection");
@@ -406,17 +407,27 @@ nsHttpPipeline::OnTransportStatus(nsITra
         }
         break;
     }
 }
 
 bool
 nsHttpPipeline::IsDone()
 {
-    return (mRequestQ.Length() == 0) && (mResponseQ.Length() == 0);
+    bool done = true;
+    
+    PRUint32 i, count = mRequestQ.Length();
+    for (i = 0; done && (i < count); i++)
+        done = Request(i)->IsDone();
+
+    count = mResponseQ.Length();
+    for (i = 0; done && (i < count); i++)
+        done = Response(i)->IsDone();
+    
+    return done;
 }
 
 nsresult
 nsHttpPipeline::Status()
 {
     return mStatus;
 }
 
--- a/services/sync/tests/tps/all_tests.json
+++ b/services/sync/tests/tps/all_tests.json
@@ -16,14 +16,13 @@
     "test_bug575423.js",
     "test_bug546807.js",
     "test_history_collision.js",
     "test_privbrw_formdata.js",
     "test_privbrw_passwords.js",
     "test_privbrw_tabs.js",
     "test_bookmarks_in_same_named_folder.js",
     "test_client_wipe.js",
-    "test_special_tabs.js",
-    "test_mozmill_sanity.js"
+    "test_special_tabs.js"
   ]
 }
 
 
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/tps/test_addon_sanity.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * The list of phases mapped to their corresponding profiles.  The object
+ * here must be in strict JSON format, as it will get parsed by the Python
+ * testrunner (no single quotes, extra comma's, etc).
+ */
+
+var phases = { "phase1": "profile1",
+               "phase2": "profile1",
+               "phase3": "profile1",
+               "phase4": "profile1",
+               "phase5": "profile1" };
+
+/*
+ * Test phases
+ */
+
+Phase('phase1', [
+  [Addons.install, ['unsigned-1.0.xml']],
+  [Addons.verify, ['unsigned-xpi@tests.mozilla.org'], STATE_DISABLED],
+  [Sync, SYNC_WIPE_SERVER],
+]);
+
+Phase('phase2', [
+  [Sync],
+  [Addons.verify, ['unsigned-xpi@tests.mozilla.org'], STATE_ENABLED],
+  [Addons.setState, ['unsigned-xpi@tests.mozilla.org'], STATE_DISABLED],
+  [Sync],
+]);
+
+Phase('phase3', [
+  [Sync],
+  [Addons.verify, ['unsigned-xpi@tests.mozilla.org'], STATE_DISABLED],
+  [Addons.setState, ['unsigned-xpi@tests.mozilla.org'], STATE_ENABLED],
+  [Sync],
+]);
+
+Phase('phase4', [
+  [Sync],
+  [Addons.verify, ['unsigned-xpi@tests.mozilla.org'], STATE_ENABLED],
+  [Addons.uninstall, ['unsigned-xpi@tests.mozilla.org']],
+  [Sync],
+]);
+
+Phase('phase5', [
+  [Sync],
+  [Addons.verifyNot, ['unsigned-xpi@tests.mozilla.org']],
+]);
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/tps/unsigned-1.0.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<searchresults total_results="1">
+  <addon id="5612">
+  <name>Unsigned Test XPI</name>
+  <type id="1">Extension</type>
+  <guid>unsigned-xpi@tests.mozilla.org</guid>
+  <slug>unsigned-xpi</slug>
+  <version>1.0</version>
+
+  <compatible_applications><application>
+      <name>Firefox</name>
+      <application_id>1</application_id>
+      <min_version>3.6</min_version>
+      <max_version>*</max_version>
+      <appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
+    </application></compatible_applications>
+  <all_compatible_os><os>ALL</os></all_compatible_os>
+
+  <install os="ALL" size="452">http://127.0.0.1:4567/unsigned-1.0.xpi</install>
+    <created epoch="1252903662">
+      2009-09-14T04:47:42Z
+    </created>
+    <last_updated epoch="1315255329">
+      2011-09-05T20:42:09Z
+    </last_updated>
+    </addon>
+</searchresults>
\ No newline at end of file
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..51b00475a9641ea9d608874a3ab7679da3a4374b
GIT binary patch
literal 452
zc$^FHW@Zs#U}E54_?u^9#dA2DRf~~<A%uy6ftx{;Av3SIBrzvPuP7xgG=!6Z`R(;m
zPY@2RU}5;mD8f)0<mh+UKw!^vQMJ8_JN=layLm<}3F>4Pa^m=!CAB@`#_JRJCLMTP
zI9DyV<<;!;^s=<;=fu~BJ&BG`=N0*wGwVpVQP-#IS7-g;+M)E%WsUK%S<7acl?Nw(
z&oP^SK626~lcn2k1%78f5zUn+<o=|h$w0Vj$<=4!T1TCmO+4Styx{&-<y%?H6YYoF
z!mhnY7WkLQ7jcB0`=;kq&w>+E7tLzE!`U8JT5_uOTuP|ifs?i!UE8+CEYjcYy?ajS
z8plGZ?O!gpMb-1SG`8M($7*+F>!j#D=h``2kGE-L^!%E%W6!hu!CPCtPtFy8#3UhH
zdHeBp*^fM58gJ}(X{=0b>ML;8jErXXkXxv9p6O?ovig*H-&tb1i#~sHx%bD*`FKOi
zcd5uS!$os`<_Ejg2Y53wi8JF0X<h~p0CE|YG=f+t;m!&P_s|Lkh5&C?Hi$|_1~(v`
I0n)+%08F#C6#xJL
--- a/services/sync/tests/unit/xpcshell.ini
+++ b/services/sync/tests/unit/xpcshell.ini
@@ -25,19 +25,18 @@ tail =
 [test_engine.js]
 [test_engine_abort.js]
 [test_enginemanager.js]
 [test_errorhandler.js]
 [test_errorhandler_filelog.js]
 # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)
 skip-if = os == "android"
 [test_errorhandler_sync_checkServerError.js]
-# Bug 604565: this test intermittently hangs on OS X debug builds.
 # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)
-skip-if = (os == "mac" && debug) || os == "android"
+skip-if = os == "android"
 [test_forms_store.js]
 [test_forms_tracker.js]
 [test_history_engine.js]
 [test_history_store.js]
 [test_history_tracker.js]
 [test_hmac_error.js]
 [test_httpd_sync_server.js]
 [test_interval_triggers.js]
@@ -75,33 +74,30 @@ skip-if = os == "win" || os == "android"
 [test_service_migratePrefs.js]
 [test_service_passwordUTF8.js]
 [test_service_persistLogin.js]
 [test_service_startOver.js]
 [test_service_startup.js]
 [test_service_sync_401.js]
 [test_service_sync_locked.js]
 [test_service_sync_remoteSetup.js]
-# Bug 604565: this test intermittently hangs on OS X debug builds.
 # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)
-skip-if = (os == "mac" && debug) || os == "android"
+skip-if = os == "android"
 [test_service_sync_updateEnabledEngines.js]
-# Bug 604565: this test intermittently hangs on OS X debug builds.
 # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)
-skip-if = (os == "mac" && debug) || os == "android"
+skip-if = os == "android"
 [test_service_verifyLogin.js]
 [test_service_wipeClient.js]
 [test_service_wipeServer.js]
 [test_status.js]
 [test_status_checkSetup.js]
 [test_syncengine.js]
 [test_syncengine_sync.js]
-# Bug 604565: this test intermittently hangs on OS X debug builds.
 # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)
-skip-if = (os == "mac" && debug) || os == "android"
+skip-if = os == "android"
 [test_syncscheduler.js]
 [test_syncstoragerequest.js]
 [test_tab_engine.js]
 [test_tab_store.js]
 [test_tab_tracker.js]
 [test_tracker_addChanged.js]
 [test_upgrade_old_sync_key.js]
 [test_utils_atob.js]
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/tps/modules/addons.jsm
@@ -0,0 +1,252 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Crossweave.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Jonathan Griffin <jgriffin@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var EXPORTED_SYMBOLS = ["Addon", "STATE_ENABLED", "STATE_DISABLED"];
+
+const CC = Components.classes;
+const CI = Components.interfaces;
+const CU = Components.utils;
+
+CU.import("resource://gre/modules/AddonManager.jsm");
+CU.import("resource://gre/modules/AddonRepository.jsm");
+CU.import("resource://gre/modules/Services.jsm");
+CU.import("resource://services-sync/async.js");
+CU.import("resource://services-sync/util.js");
+CU.import("resource://tps/logger.jsm");
+var XPIProvider = CU.import("resource://gre/modules/XPIProvider.jsm")
+                  .XPIProvider;
+
+const ADDONSGETURL = 'http://127.0.0.1:4567/';
+const STATE_ENABLED = 1;
+const STATE_DISABLED = 2;
+
+function GetFileAsText(file)
+{
+  let channel = Services.io.newChannel(file, null, null);
+  let inputStream = channel.open();
+  if (channel instanceof CI.nsIHttpChannel && 
+      channel.responseStatus != 200) {
+    return "";
+  }
+
+  let streamBuf = "";
+  let sis = CC["@mozilla.org/scriptableinputstream;1"]
+            .createInstance(CI.nsIScriptableInputStream);
+  sis.init(inputStream);
+
+  let available;
+  while ((available = sis.available()) != 0) {
+    streamBuf += sis.read(available);
+  }
+
+  inputStream.close();
+  return streamBuf;
+}
+
+function Addon(TPS, id) {
+  this.TPS = TPS;
+  this.id = id;
+}
+
+Addon.prototype = {
+  _addons_requiring_restart: [],
+  _addons_pending_install: [],
+
+  Delete: function() {
+    // find our addon locally
+    let cb = Async.makeSyncCallback();
+    XPIProvider.getAddonsByTypes(null, cb);
+    let results =  Async.waitForSyncCallback(cb);
+    var addon;
+    var id = this.id;
+    results.forEach(function(result) {
+      if (result.id == id) {
+        addon = result;
+      }
+    });
+    Logger.AssertTrue(!!addon, 'could not find addon ' + this.id + ' to uninstall');
+    addon.uninstall();
+  },
+
+  Find: function(state) {
+    let cb = Async.makeSyncCallback();
+    let addon_found = false;
+    var that = this;
+
+    var log_addon = function(addon) {
+      that.addon = addon;
+      Logger.logInfo('addon ' + addon.id + ' found, isActive: ' + addon.isActive);
+      if (state == STATE_ENABLED || state == STATE_DISABLED) {
+          Logger.AssertEqual(addon.isActive,
+            state == STATE_ENABLED ? true : false,
+            "addon " + that.id + " has an incorrect enabled state");
+      }
+    };
+
+    // first look in the list of all addons
+    XPIProvider.getAddonsByTypes(null, cb);
+    let addonlist = Async.waitForSyncCallback(cb);
+    addonlist.forEach(function(addon) {
+      if (addon.id == that.id) {
+        addon_found = true;
+        log_addon.call(that, addon);
+      }
+    });
+
+    if (!addon_found) {
+      // then look in the list of recent installs
+      cb = Async.makeSyncCallback();
+      XPIProvider.getInstallsByTypes(null, cb);
+      addonlist = Async.waitForSyncCallback(cb);
+      for (var i in addonlist) {
+        if (addonlist[i].addon && addonlist[i].addon.id == that.id &&
+            addonlist[i].state == AddonManager.STATE_INSTALLED) {
+          addon_found = true;
+          log_addon.call(that, addonlist[i].addon);
+        }
+      }
+    }
+
+    return addon_found;
+  },
+
+  Install: function() {
+    // For Install, the id parameter initially passed is really the filename
+    // for the addon's install .xml; we'll read the actual id from the .xml.
+    let url = this.id;
+
+    // set the url used by getAddonsByIDs
+    var prefs = CC["@mozilla.org/preferences-service;1"]
+                .getService(CI.nsIPrefBranch);
+    prefs.setCharPref('extensions.getAddons.get.url', ADDONSGETURL + url);
+
+    // read the XML and find the addon id
+    xml = GetFileAsText(ADDONSGETURL + url);
+    Logger.AssertTrue(xml.indexOf("<guid>") > -1, 'guid not found in ' + url);
+    this.id = xml.substring(xml.indexOf("<guid>") + 6, xml.indexOf("</guid"));
+    Logger.logInfo('addon XML = ' + this.id);
+
+    // find our addon on 'AMO'
+    let cb = Async.makeSyncCallback();
+    AddonRepository.getAddonsByIDs([this.id], {
+      searchSucceeded: cb,
+      searchFailed: cb
+    }, false);
+
+    // Result will be array of addons on searchSucceeded or undefined on
+    // searchFailed.
+    let install_addons = Async.waitForSyncCallback(cb);
+
+    Logger.AssertTrue(install_addons,
+                      "no addons found for id " + this.id);
+    Logger.AssertEqual(install_addons.length,
+                       1,
+                       "multiple addons found for id " + this.id);
+
+    let addon = install_addons[0];
+    Logger.logInfo(JSON.stringify(addon), null, ' ');
+    if (XPIProvider.installRequiresRestart(addon)) {
+      this._addons_requiring_restart.push(addon.id);
+    }
+
+    // Start installing the addon asynchronously; finish up in
+    // onInstallEnded(), onInstallFailed(), or onDownloadFailed().
+    this._addons_pending_install.push(addon.id);
+    this.TPS.StartAsyncOperation();
+
+    Utils.nextTick(function() {
+      let callback = function(aInstall) {
+        addon.install = aInstall;
+        Logger.logInfo("addon install: " + addon.install);
+        Logger.AssertTrue(addon.install,
+                          "could not get install object for id " + this.id);
+        addon.install.addListener(this);
+        addon.install.install();
+      };
+
+      AddonManager.getInstallForURL(addon.sourceURI.spec,
+                                    callback.bind(this),
+                                    "application/x-xpinstall");
+    }, this);
+  },
+
+  SetState: function(state) {
+    if (!this.Find())
+      return false;
+    this.addon.userDisabled = state == STATE_ENABLED ? false : true;
+      return true;
+  },
+
+  // addon installation callbacks
+  onInstallEnded: function(addon) {
+    try {
+      Logger.logInfo('--------- event observed: addon onInstallEnded');
+      Logger.AssertTrue(addon.addon,
+        "No addon object in addon instance passed to onInstallEnded");
+      Logger.AssertTrue(this._addons_pending_install.indexOf(addon.addon.id) > -1,
+        "onInstallEnded received for unexpected addon " + addon.addon.id);
+      this._addons_pending_install.splice(
+        this._addons_pending_install.indexOf(addon.addon.id),
+        1);
+    }
+    catch(e) {
+      // We can't throw during a callback, as it will just get eaten by
+      // the callback's caller.
+      Utils.nextTick(function() {
+        this.DumpError(e);
+      }, this);
+      return;
+    }
+    this.TPS.FinishAsyncOperation();
+  },
+
+  onInstallFailed: function(addon) {
+    Logger.logInfo('--------- event observed: addon onInstallFailed');
+    Utils.nextTick(function() {
+      this.DumpError('Installation failed for addon ' + 
+        (addon.addon && addon.addon.id ? addon.addon.id : 'unknown'));
+    }, this);
+  },
+
+  onDownloadFailed: function(addon) {
+    Logger.logInfo('--------- event observed: addon onDownloadFailed');
+    Utils.nextTick(function() {
+      this.DumpError('Download failed for addon ' + 
+        (addon.addon && addon.addon.id ? addon.addon.id : 'unknown'));
+    }, this);
+  },
+
+};
--- a/services/sync/tps/extensions/tps/modules/tps.jsm
+++ b/services/sync/tps/extensions/tps/modules/tps.jsm
@@ -43,72 +43,55 @@
 var EXPORTED_SYMBOLS = ["TPS"];
 
 const CC = Components.classes;
 const CI = Components.interfaces;
 const CU = Components.utils;
 
 CU.import("resource://services-sync/service.js");
 CU.import("resource://services-sync/constants.js");
+CU.import("resource://services-sync/async.js");
 CU.import("resource://services-sync/util.js");
 CU.import("resource://gre/modules/XPCOMUtils.jsm");
 CU.import("resource://gre/modules/Services.jsm");
+CU.import("resource://tps/addons.jsm");
 CU.import("resource://tps/bookmarks.jsm");
 CU.import("resource://tps/logger.jsm");
 CU.import("resource://tps/passwords.jsm");
 CU.import("resource://tps/history.jsm");
 CU.import("resource://tps/forms.jsm");
 CU.import("resource://tps/prefs.jsm");
 CU.import("resource://tps/tabs.jsm");
 
 var hh = CC["@mozilla.org/network/protocol;1?name=http"]
          .getService(CI.nsIHttpProtocolHandler);
+var prefs = CC["@mozilla.org/preferences-service;1"]
+            .getService(CI.nsIPrefBranch);
 
 var mozmillInit = {}; 
 CU.import('resource://mozmill/modules/init.js', mozmillInit);
 
 const ACTION_ADD = "add";
 const ACTION_VERIFY = "verify";
 const ACTION_VERIFY_NOT = "verify-not";
 const ACTION_MODIFY = "modify";
 const ACTION_SYNC = "sync";
 const ACTION_DELETE = "delete";
 const ACTION_PRIVATE_BROWSING = "private-browsing";
 const ACTION_WIPE_SERVER = "wipe-server";
+const ACTION_SETSTATE = "set-state";
 const ACTIONS = [ACTION_ADD, ACTION_VERIFY, ACTION_VERIFY_NOT, 
                  ACTION_MODIFY, ACTION_SYNC, ACTION_DELETE,
-                 ACTION_PRIVATE_BROWSING, ACTION_WIPE_SERVER];
+                 ACTION_PRIVATE_BROWSING, ACTION_WIPE_SERVER,
+                 ACTION_SETSTATE];
 
 const SYNC_WIPE_SERVER = "wipe-server";
 const SYNC_RESET_CLIENT = "reset-client";
 const SYNC_WIPE_CLIENT = "wipe-client";
 
-function GetFileAsText(file)
-{
-  let channel = Services.io.newChannel(file, null, null);
-  let inputStream = channel.open();
-  if (channel instanceof CI.nsIHttpChannel && 
-      channel.responseStatus != 200) {
-    return "";
-  }
-
-  let streamBuf = "";
-  let sis = CC["@mozilla.org/scriptableinputstream;1"]
-            .createInstance(CI.nsIScriptableInputStream);
-  sis.init(inputStream);
-
-  let available;
-  while ((available = sis.available()) != 0) {
-    streamBuf += sis.read(available);
-  }
-
-  inputStream.close();
-  return streamBuf;
-}
-
 var TPS = 
 {
   _waitingForSync: false,
   _test: null,
   _currentAction: -1,
   _currentPhase: -1,
   _errors: 0,
   _syncErrors: 0,
@@ -346,16 +329,43 @@ var TPS =
                      " on passwords");
     }
     catch(e) {
       DumpPasswords();
       throw(e);
     }
   },
 
+  HandleAddons: function (addons, action, state) {
+    for (var i in addons) {
+      Logger.logInfo("executing action " + action.toUpperCase() + 
+                     " on addon " + JSON.stringify(addons[i]));
+      var addon = new Addon(this, addons[i]);
+      switch(action) {
+        case ACTION_ADD:
+          addon.Install();
+          break;
+        case ACTION_DELETE:
+          addon.Delete();
+          break;
+        case ACTION_VERIFY:
+          Logger.AssertTrue(addon.Find(state), 'addon ' + addon.id + ' not found');
+          break;
+        case ACTION_VERIFY_NOT:
+          Logger.AssertTrue(!addon.Find(state), 'addon ' + addon.id + " is present, but it shouldn't be");
+          break;
+        case ACTION_SETSTATE:
+          Logger.AssertTrue(addon.SetState(state), 'addon ' + addon.id + ' not found');
+          break;
+      }
+    }
+    Logger.logPass("executing action " + action.toUpperCase() + 
+                   " on addons");
+  },
+
   HandleBookmarks: function (bookmarks, action) {
     try {
       let items = [];
       for (folder in bookmarks) {
         let last_item_pos = -1;
         for each (bookmark in bookmarks[folder]) {
           Logger.clearPotentialError();
           let placesItem;
@@ -455,17 +465,17 @@ var TPS =
       else {
         this.DumpError("seconds-since-epoch not set");
         return;
       }
       
       let phase = this._phaselist["phase" + this._currentPhase];
       let action = phase[this._currentAction];
       Logger.logInfo("starting action: " + JSON.stringify(action));
-      action[0].call(this, action[1]);
+      action[0].apply(this, action.slice(1));
 
       // if we're in an async operation, don't continue on to the next action
       if (this._operations_pending)
         return;
 
       this._currentAction++;
     }
     catch(e) {
@@ -512,18 +522,16 @@ var TPS =
       Weave.Svc.Prefs.set("client.name", this.phases["phase" + this._currentPhase]);
 
       // wipe the server at the end of the final test phase
       if (this.phases["phase" + (parseInt(this._currentPhase) + 1)] == undefined)
         this_phase.push([this.WipeServer]);
 
       // Store account details as prefs so they're accessible to the mozmill
       // framework.
-      let prefs = CC["@mozilla.org/preferences-service;1"]
-                  .getService(CI.nsIPrefBranch);
       prefs.setCharPref('tps.account.username', this.config.account.username);
       prefs.setCharPref('tps.account.password', this.config.account.password);
       prefs.setCharPref('tps.account.passphrase', this.config.account.passphrase);
       if (this.config.account['serverURL']) {
         prefs.setCharPref('tps.account.serverURL', this.config.account.serverURL);
       }
 
       // start processing the test actions
@@ -629,16 +637,34 @@ var TPS =
     Logger.AssertEqual(Weave.Status.service, Weave.STATUS_OK, "Weave status not OK");
     this._waitingForSync = true;
     this.StartAsyncOperation();
     Weave.Service.sync();
     return;
   },
 };
 
+var Addons = {
+  install: function Addons__install(addons) {
+    TPS.HandleAddons(addons, ACTION_ADD);
+  },
+  setState: function Addons__setState(addons, state) {
+    TPS.HandleAddons(addons, ACTION_SETSTATE, state);
+  },
+  uninstall: function Addons__uninstall(addons) {
+    TPS.HandleAddons(addons, ACTION_DELETE);
+  },
+  verify: function Addons__verify(addons, state) {
+    TPS.HandleAddons(addons, ACTION_VERIFY, state);
+  },
+  verifyNot: function Addons__verifyNot(addons) {
+    TPS.HandleAddons(addons, ACTION_VERIFY_NOT);
+  },
+};
+
 var Bookmarks = {
   add: function Bookmarks__add(bookmarks) {
     TPS.HandleBookmarks(bookmarks, ACTION_ADD);
   },
   modify: function Bookmarks__modify(bookmarks) {
     TPS.HandleBookmarks(bookmarks, ACTION_MODIFY);
   },
   delete: function Bookmarks__delete(bookmarks) {
--- a/testing/mochitest/Makefile.in
+++ b/testing/mochitest/Makefile.in
@@ -77,17 +77,16 @@ include $(topsrcdir)/build/automation-bu
 		runtests.py \
 		automation.py \
 		runtestsremote.py \
 		runtestsvmware.py \
 		$(topsrcdir)/build/mobile/devicemanager.py \
 		$(topsrcdir)/build/mobile/devicemanagerADB.py \
 		$(topsrcdir)/build/mobile/devicemanagerSUT.py \
 		$(topsrcdir)/build/automationutils.py \
-		$(topsrcdir)/build/poster.zip \
 		$(topsrcdir)/build/mobile/remoteautomation.py \
 		gen_template.pl \
 		server.js \
 		harness-overlay.xul \
 		harness.xul \
 		browser-test-overlay.xul \
 		browser-test.js \
 		chrome-harness.js \
--- a/testing/tps/tps/__init__.py
+++ b/testing/tps/tps/__init__.py
@@ -33,9 +33,10 @@
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 from firefoxrunner import TPSFirefoxRunner
 from pulse import TPSPulseMonitor
 from testrunner import TPSTestRunner
+from mozhttpd import MozHttpd
 
new file mode 100644
--- /dev/null
+++ b/testing/tps/tps/mozhttpd.py
@@ -0,0 +1,111 @@
+#!/usr/bin/python
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# the Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Joel Maher <joel.maher@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+import BaseHTTPServer
+import SimpleHTTPServer
+import threading
+import sys
+import os
+import urllib
+import re
+from urlparse import urlparse
+from SocketServer import ThreadingMixIn
+
+DOCROOT = '.'
+
+class EasyServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
+    allow_reuse_address = True
+    
+class MozRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+    def translate_path(self, path):
+        # It appears that the default path is '/' and os.path.join makes the '/' 
+        o = urlparse(path)
+        return "%s%s" % ('' if sys.platform == 'win32' else '/', '/'.join([i.strip('/') for i in (DOCROOT, o.path)]))
+
+    # I found on my local network that calls to this were timing out
+    # I believe all of these calls are from log_message
+    def address_string(self):
+        return "a.b.c.d"
+
+    # This produces a LOT of noise
+    def log_message(self, format, *args):
+        pass
+
+class MozHttpd(object):
+    def __init__(self, host="127.0.0.1", port=8888, docroot='.'):
+        global DOCROOT
+        self.host = host
+        self.port = int(port)
+        DOCROOT = docroot
+
+    def start(self):
+        self.httpd = EasyServer((self.host, self.port), MozRequestHandler)
+        self.server = threading.Thread(target=self.httpd.serve_forever)
+        self.server.setDaemon(True) # don't hang on exit
+        self.server.start()
+        #self.testServer()
+
+    #TODO: figure this out
+    def testServer(self):
+        fileList = os.listdir(DOCROOT)
+        filehandle = urllib.urlopen('http://%s:%s' % (self.host, self.port))
+        data = filehandle.readlines();
+        filehandle.close()
+
+        for line in data:
+            found = False
+            # '@' denotes a symlink and we need to ignore it.
+            webline = re.sub('\<[a-zA-Z0-9\-\_\.\=\"\'\/\\\%\!\@\#\$\^\&\*\(\) ]*\>', '', line.strip('\n')).strip('/').strip().strip('@')
+            if webline != "":
+                if webline == "Directory listing for":
+                    found = True
+                else:
+                    for fileName in fileList:
+                        if fileName == webline:
+                            found = True
+                
+                if (found == False):
+                    print "NOT FOUND: " + webline.strip()                
+
+    def stop(self):
+        if self.httpd:
+            self.httpd.shutdown()
+        
+    __del__ = stop
+
--- a/testing/tps/tps/testrunner.py
+++ b/testing/tps/tps/testrunner.py
@@ -48,17 +48,17 @@ import traceback
 import urllib
 
 from threading import RLock
 
 from mozprofile import Profile
 
 from tps.firefoxrunner import TPSFirefoxRunner
 from tps.phase import TPSTestPhase
-
+from tps.mozhttpd import MozHttpd
 
 class TempFile(object):
   """Class for temporary files that delete themselves when garbage-collected.
   """
 
   def __init__(self, prefix=None):
     self.fd, self.filename = self.tmpfile = tempfile.mkstemp(prefix=prefix)
 
@@ -392,16 +392,19 @@ class TPSTestRunner(object):
       jsondata = f.read()
       f.close()
       testfiles = json.loads(jsondata)
       testlist = testfiles['tests']
     except ValueError:
       testlist = [os.path.basename(self.testfile)]
     testdir = os.path.dirname(self.testfile)
 
+    self.mozhttpd = MozHttpd(port=4567, docroot=testdir)
+    self.mozhttpd.start()
+
     # run each test, and save the results
     for test in testlist:
       result = self.run_single_test(testdir, test)
 
       if not self.productversion:
         self.productversion = result['productversion']
       if not self.addonversion:
         self.addonversion = result['addonversion']
@@ -410,16 +413,18 @@ class TPSTestRunner(object):
                            'name': result['name'], 
                            'message': result['message'],
                            'logdata': result['logdata']})
       if result['state'] == 'TEST-PASS':
         self.numpassed += 1
       else:
         self.numfailed += 1
 
+    self.mozhttpd.stop()
+
     # generate the postdata we'll use to post the results to the db
     self.postdata = { 'tests': self.results, 
                       'os':os_string,
                       'testtype': 'crossweave',
                       'productversion': self.productversion,
                       'addonversion': self.addonversion,
                       'synctype': self.synctype,
                     }
@@ -435,17 +440,17 @@ class TPSTestRunner(object):
       if body is None:
         buildUrl = None
         if self.firefoxRunner and self.firefoxRunner.url:
           buildUrl = self.firefoxRunner.url
         body = GenerateEmailBody(self.postdata,
                                  self.numpassed,
                                  self.numfailed,
                                  self.config['account']['serverURL'],
-                                 self.buildUrl)
+                                 buildUrl)
 
       subj = "TPS Report: "
       if self.numfailed == 0 and self.numpassed > 0:
         subj += "YEEEAAAHHH"
       else:
         subj += "PC LOAD LETTER"
 
       changeset = self.postdata['productversion']['changeset'] if \
--- a/testing/xpcshell/Makefile.in
+++ b/testing/xpcshell/Makefile.in
@@ -60,17 +60,16 @@ TEST_HARNESS_FILES := \
   head.js \
   $(NULL)
 
 # Extra files needed from $(topsrcdir)/build
 EXTRA_BUILD_FILES := \
   automationutils.py \
   manifestparser.py \
   mozinfo.py \
-  poster.zip \
   $(NULL)
 
 # And files for running xpcshell remotely from $(topsrcdir)/build/mobile
 MOBILE_BUILD_FILES := \
   devicemanager.py \
   $(NULL)
 
 # Components / typelibs that don't get packaged with