Merge cedar into mozilla-central
authorEhsan Akhgari <ehsan@mozilla.com>
Sun, 10 Apr 2011 15:11:22 -0400
changeset 67802 6eaee284fdb9666ca69c7c24e9dd11e920b0b50b
parent 67801 49835a54196a17fa22aec0543274aa69793c0d5f (current diff)
parent 67786 21ce62e6aebe67bfcdf4b9c2f32ce30a4bff3ea2 (diff)
child 67803 09b605eb3e0d0131630a5956d9367405b505957b
child 67820 5384e63ae39e29d2fc17b655e4ec87427418d9cf
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone2.2a1pre
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 cedar into mozilla-central
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
services/sync/tests/unit/test_utils_file.js
--- a/accessible/src/msaa/nsTextAccessibleWrap.cpp
+++ b/accessible/src/msaa/nsTextAccessibleWrap.cpp
@@ -38,23 +38,25 @@
 
 // NOTE: alphabetically ordered
 #include "nsTextAccessibleWrap.h"
 #include "ISimpleDOMText_i.c"
 
 #include "nsCoreUtils.h"
 #include "nsDocAccessible.h"
 
-#include "nsIFontMetrics.h"
+#include "nsIThebesFontMetrics.h"
 #include "nsIFrame.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsIRenderingContext.h"
 #include "nsIComponentManager.h"
 
+#include "gfxFont.h"
+
 ////////////////////////////////////////////////////////////////////////////////
 // nsTextAccessibleWrap Accessible
 ////////////////////////////////////////////////////////////////////////////////
 
 nsTextAccessibleWrap::
   nsTextAccessibleWrap(nsIContent *aContent, nsIWeakReference *aShell) :
   nsTextAccessible(aContent, aShell)
 {
@@ -248,53 +250,35 @@ nsresult nsTextAccessibleWrap::GetCharac
 }
 
 STDMETHODIMP nsTextAccessibleWrap::get_fontFamily(
     /* [retval][out] */ BSTR __RPC_FAR *aFontFamily)
 {
 __try {
   *aFontFamily = NULL;
 
-  nsIFrame *frame = GetFrame();
-  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
-  if (!frame || !presShell || !presShell->GetPresContext()) {
-    return E_FAIL;
-  }
-
-  nsCOMPtr<nsIRenderingContext> rc = presShell->GetReferenceRenderingContext();
-  if (!rc) {
-    return E_FAIL;
-  }
-
-  const nsStyleFont *font = frame->GetStyleFont();
-
-  const nsStyleVisibility *visibility = frame->GetStyleVisibility();
-
-  if (NS_FAILED(rc->SetFont(font->mFont, visibility->mLanguage,
-                            presShell->GetPresContext()->GetUserFontSet()))) {
-    return E_FAIL;
-  }
-
-  nsCOMPtr<nsIDeviceContext> deviceContext;
-  rc->GetDeviceContext(*getter_AddRefs(deviceContext));
-  if (!deviceContext) {
+  nsIFrame* frame = GetFrame();
+  if (!frame) {
     return E_FAIL;
   }
 
   nsCOMPtr<nsIFontMetrics> fm;
-  rc->GetFontMetrics(*getter_AddRefs(fm));
-  if (!fm) {
-    return E_FAIL;
-  }
+  frame->PresContext()->DeviceContext()->
+    GetMetricsFor(frame->GetStyleFont()->mFont,
+                  frame->GetStyleVisibility()->mLanguage,
+                  frame->PresContext()->GetUserFontSet(),
+                  *getter_AddRefs(fm));
 
-  nsAutoString fontFamily;
-  deviceContext->FirstExistingFont(fm->Font(), fontFamily);
-  if (fontFamily.IsEmpty())
+  nsCOMPtr<nsIThebesFontMetrics> tfm = do_QueryInterface(fm);
+  const nsString& name = tfm->GetThebesFontGroup()->GetFontAt(0)->GetName();
+
+  if (name.IsEmpty())
     return S_FALSE;
 
-  *aFontFamily = ::SysAllocStringLen(fontFamily.get(), fontFamily.Length());
+  *aFontFamily = ::SysAllocStringLen(name.get(), name.Length());
   if (!*aFontFamily)
     return E_OUTOFMEMORY;
 
-} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
+} __except(FilterA11yExceptions(::GetExceptionCode(),
+                                GetExceptionInformation())) { }
 
   return S_OK;
 }
--- a/content/base/src/CSPUtils.jsm
+++ b/content/base/src/CSPUtils.jsm
@@ -158,16 +158,17 @@ CSPPolicyURIListener.prototype = {
     if (Components.isSuccessCode(status)) {
       // send the policy we received back to the parent document's CSP
       // for parsing
       this._csp.refinePolicy(this._policy, this._docURI, this._docRequest);
     }
     else {
       // problem fetching policy so fail closed
       this._csp.refinePolicy("allow 'none'", this._docURI, this._docRequest);
+      this._csp.refinePolicy("default-src 'none'", this._docURI, this._docRequest);
     }
     // resume the parent document request
     this._docRequest.resume();
   }
 };
 
 //:::::::::::::::::::::::: CLASSES ::::::::::::::::::::::::::// 
 
@@ -182,17 +183,17 @@ function CSPRep() {
   this._allowEval = false;
   this._allowInlineScripts = false;
 
   // don't auto-populate _directives, so it is easier to find bugs
   this._directives = {};
 }
 
 CSPRep.SRC_DIRECTIVES = {
-  ALLOW:            "allow",
+  DEFAULT_SRC:      "default-src",
   SCRIPT_SRC:       "script-src",
   STYLE_SRC:        "style-src",
   MEDIA_SRC:        "media-src",
   IMG_SRC:          "img-src",
   OBJECT_SRC:       "object-src",
   FRAME_SRC:        "frame-src",
   FRAME_ANCESTORS:  "frame-ancestors",
   FONT_SRC:         "font-src",
@@ -200,16 +201,17 @@ CSPRep.SRC_DIRECTIVES = {
 };
 
 CSPRep.URI_DIRECTIVES = {
   REPORT_URI:       "report-uri", /* list of URIs */
   POLICY_URI:       "policy-uri"  /* single URI */
 };
 
 CSPRep.OPTIONS_DIRECTIVE = "options";
+CSPRep.ALLOW_DIRECTIVE   = "allow";
 
 /**
   * Factory to create a new CSPRep, parsed from a string.
   *
   * @param aStr
   *        string rep of a CSP
   * @param self (optional)
   *        string or CSPSource representing the "self" source
@@ -253,16 +255,27 @@ CSPRep.fromString = function(aStr, self,
         else if (opt === "eval-script")
           aCSPR._allowEval = true;
         else
           CSPWarning("don't understand option '" + opt + "'.  Ignoring it.");
       }
       continue directive;
     }
 
+    // ALLOW DIRECTIVE //////////////////////////////////////////////////
+    // parse "allow" as equivalent to "default-src", at least until the spec
+    // stabilizes, at which time we can stop parsing "allow"
+    if (dirname === CSPRep.ALLOW_DIRECTIVE) {
+      var dv = CSPSourceList.fromString(dirvalue, self, true);
+      if (dv) {
+        aCSPR._directives[SD.DEFAULT_SRC] = dv;
+        continue directive;
+      }
+    }
+
     // SOURCE DIRECTIVES ////////////////////////////////////////////////
     for each(var sdi in SD) {
       if (dirname === sdi) {
         // process dirs, and enforce that 'self' is defined.
         var dv = CSPSourceList.fromString(dirvalue, self, true);
         if (dv) {
           aCSPR._directives[sdi] = dv;
           continue directive;
@@ -333,80 +346,80 @@ CSPRep.fromString = function(aStr, self,
       continue directive;
     }
 
     // POLICY URI //////////////////////////////////////////////////////////
     if (dirname === UD.POLICY_URI) {
       // POLICY_URI can only be alone
       if (aCSPR._directives.length > 0 || dirs.length > 1) {
         CSPError("policy-uri directive can only appear alone");
-        return CSPRep.fromString("allow 'none'");
+        return CSPRep.fromString("default-src 'none'");
       }
       // if we were called without a reference to the parent document request
       // we won't be able to suspend it while we fetch the policy -> fail closed
       if (!docRequest || !csp) {
         CSPError("The policy-uri cannot be fetched without a parent request and a CSP.");
-        return CSPRep.fromString("allow 'none'");
+        return CSPRep.fromString("default-src 'none'");
       }
 
       var uri = '';
       try {
         uri = gIoService.newURI(dirvalue, null, selfUri);
       } catch(e) {
         CSPError("could not parse URI in policy URI: " + dirvalue);
-        return CSPRep.fromString("allow 'none'");
+        return CSPRep.fromString("default-src 'none'");
       }
 
       // Verify that policy URI comes from the same origin
       if (selfUri) {
         if (selfUri.host !== uri.host){
           CSPError("can't fetch policy uri from non-matching hostname: " + uri.host);
-          return CSPRep.fromString("allow 'none'");
+          return CSPRep.fromString("default-src 'none'");
         }
         if (selfUri.port !== uri.port){
           CSPError("can't fetch policy uri from non-matching port: " + uri.port);
-          return CSPRep.fromString("allow 'none'");
+          return CSPRep.fromString("default-src 'none'");
         }
         if (selfUri.scheme !== uri.scheme){
           CSPError("can't fetch policy uri from non-matching scheme: " + uri.scheme);
-          return CSPRep.fromString("allow 'none'");
+          return CSPRep.fromString("default-src 'none'");
         }
       }
 
       // suspend the parent document request while we fetch the policy-uri
       try {
         docRequest.suspend();
         var chan = gIoService.newChannel(uri.asciiSpec, null, null);
         // make request anonymous (no cookies, etc.) so the request for the
         // policy-uri can't be abused for CSRF
         chan.loadFlags |= Components.interfaces.nsIChannel.LOAD_ANONYMOUS;
         chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp), null);
       }
       catch (e) {
         // resume the document request and apply most restrictive policy
         docRequest.resume();
         CSPError("Error fetching policy-uri: " + e);
-        return CSPRep.fromString("allow 'none'");
+        return CSPRep.fromString("default-src 'none'");
       }
 
       // return a fully-open policy to be intersected with the contents of the
       // policy-uri when it returns
-      return CSPRep.fromString("allow *");
+      return CSPRep.fromString("default-src *");
     }
 
     // UNIDENTIFIED DIRECTIVE /////////////////////////////////////////////
     CSPWarning("Couldn't process unknown directive '" + dirname + "'");
 
   } // end directive: loop
 
-  // if makeExplicit fails for any reason, default to allow 'none'.  This
-  // includes the case where "allow" is not present.
+  // if makeExplicit fails for any reason, default to default-src 'none'.  This
+  // includes the case where "default-src" is not present.
   if (aCSPR.makeExplicit())
     return aCSPR;
-  return CSPRep.fromString("allow 'none'", self);
+  return CSPRep.fromString("default-src 'none'", self);
 };
 
 CSPRep.prototype = {
   /**
    * Returns a space-separated list of all report uris defined, or 'none' if there are none.
    */
   getReportURIs:
   function() {
@@ -529,32 +542,32 @@ CSPRep.prototype = {
    * Copies default source list to each unspecified directive.
    * @returns
    *      true  if the makeExplicit succeeds
    *      false if it fails (for some weird reason)
    */
   makeExplicit:
   function cspsd_makeExplicit() {
     var SD = CSPRep.SRC_DIRECTIVES;
-    var allowDir = this._directives[SD.ALLOW];
-    if (!allowDir) {
-      CSPWarning("'allow' directive required but not present.  Reverting to \"allow 'none'\"");
+    var defaultSrcDir = this._directives[SD.DEFAULT_SRC];
+    if (!defaultSrcDir) {
+      CSPWarning("'allow' or 'default-src' directive required but not present.  Reverting to \"default-src 'none'\"");
       return false;
     }
 
     for (var dir in SD) {
       var dirv = SD[dir];
-      if (dirv === SD.ALLOW) continue;
+      if (dirv === SD.DEFAULT_SRC) continue;
       if (!this._directives[dirv]) {
         // implicit directive, make explicit.
         // All but frame-ancestors directive inherit from 'allow' (bug 555068)
         if (dirv === SD.FRAME_ANCESTORS)
           this._directives[dirv] = CSPSourceList.fromString("*");
         else
-          this._directives[dirv] = allowDir.clone();
+          this._directives[dirv] = defaultSrcDir.clone();
         this._directives[dirv]._isImplicit = true;
       }
     }
     this._isInitialized = true;
     return true;
   },
 
   /**
--- a/content/base/src/contentSecurityPolicy.js
+++ b/content/base/src/contentSecurityPolicy.js
@@ -57,40 +57,40 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/CSPUtils.jsm");
 
 /* ::::: Policy Parsing & Data structures :::::: */
 
 function ContentSecurityPolicy() {
   CSPdebug("CSP CREATED");
   this._isInitialized = false;
   this._reportOnlyMode = false;
-  this._policy = CSPRep.fromString("allow *");
+  this._policy = CSPRep.fromString("default-src *");
 
   // default options "wide open" since this policy will be intersected soon
   this._policy._allowInlineScripts = true;
   this._policy._allowEval = true;
 
   this._requestHeaders = []; 
   this._request = "";
   this._docRequest = null;
-  CSPdebug("CSP POLICY INITED TO 'allow *'");
+  CSPdebug("CSP POLICY INITED TO 'default-src *'");
 }
 
 /*
  * Set up mappings from nsIContentPolicy content types to CSP directives.
  */
 {
   let cp = Ci.nsIContentPolicy;
   let csp = ContentSecurityPolicy;
   let cspr_sd = CSPRep.SRC_DIRECTIVES;
 
   csp._MAPPINGS=[];
 
   /* default, catch-all case */
-  csp._MAPPINGS[cp.TYPE_OTHER]             =  cspr_sd.ALLOW;
+  csp._MAPPINGS[cp.TYPE_OTHER]             =  cspr_sd.DEFAULT_SRC;
 
   /* self */
   csp._MAPPINGS[cp.TYPE_DOCUMENT]          =  null;
 
   /* shouldn't see this one */
   csp._MAPPINGS[cp.TYPE_REFRESH]           =  null;
 
   /* categorized content types */
@@ -101,19 +101,19 @@ function ContentSecurityPolicy() {
   csp._MAPPINGS[cp.TYPE_OBJECT_SUBREQUEST] = cspr_sd.OBJECT_SRC;
   csp._MAPPINGS[cp.TYPE_SUBDOCUMENT]       = cspr_sd.FRAME_SRC;
   csp._MAPPINGS[cp.TYPE_MEDIA]             = cspr_sd.MEDIA_SRC;
   csp._MAPPINGS[cp.TYPE_FONT]              = cspr_sd.FONT_SRC;
   csp._MAPPINGS[cp.TYPE_XMLHTTPREQUEST]    = cspr_sd.XHR_SRC;
 
 
   /* These must go through the catch-all */
-  csp._MAPPINGS[cp.TYPE_XBL]               = cspr_sd.ALLOW;
-  csp._MAPPINGS[cp.TYPE_PING]              = cspr_sd.ALLOW;
-  csp._MAPPINGS[cp.TYPE_DTD]               = cspr_sd.ALLOW;
+  csp._MAPPINGS[cp.TYPE_XBL]               = cspr_sd.DEFAULT_SRC;
+  csp._MAPPINGS[cp.TYPE_PING]              = cspr_sd.DEFAULT_SRC;
+  csp._MAPPINGS[cp.TYPE_DTD]               = cspr_sd.DEFAULT_SRC;
 }
 
 ContentSecurityPolicy.prototype = {
   classID:          Components.ID("{AB36A2BF-CB32-4AA6-AB41-6B4E4444A221}"),
   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentSecurityPolicy]),
 
   get isInitialized() {
     return this._isInitialized;
@@ -381,17 +381,17 @@ ContentSecurityPolicy.prototype = {
     // scan the discovered ancestors
     let cspContext = CSPRep.SRC_DIRECTIVES.FRAME_ANCESTORS;
     for (let i in ancestors) {
       let ancestor = ancestors[i].prePath;
       if (!this._policy.permits(ancestor, cspContext)) {
         // report the frame-ancestor violation
         let directive = this._policy._directives[cspContext];
         let violatedPolicy = (directive._isImplicit
-                                ? 'allow' : 'frame-ancestors ')
+                                ? 'default-src' : 'frame-ancestors ')
                                 + directive.toString();
 
         this._asyncReportViolation(ancestors[i], violatedPolicy);
 
         // need to lie if we are testing in report-only mode
         return this._reportOnlyMode;
       }
     }
@@ -436,17 +436,17 @@ ContentSecurityPolicy.prototype = {
     // frame-ancestors is taken care of early on (as this document is loaded)
 
     // If the result is *NOT* ACCEPT, then send report
     if (res != Ci.nsIContentPolicy.ACCEPT) { 
       CSPdebug("blocking request for " + aContentLocation.asciiSpec);
       try {
         let directive = this._policy._directives[cspContext];
         let violatedPolicy = (directive._isImplicit
-                                ? 'allow' : cspContext)
+                                ? 'default-src' : cspContext)
                                 + ' ' + directive.toString();
         this._asyncReportViolation(aContentLocation, violatedPolicy);
       } catch(e) {
         CSPdebug('---------------- ERROR: ' + e);
       }
     }
 
     return (this._reportOnlyMode ? Ci.nsIContentPolicy.ACCEPT : res);
--- a/content/base/test/file_CSP_main.html^headers^
+++ b/content/base/test/file_CSP_main.html^headers^
@@ -1,1 +1,1 @@
-X-Content-Security-Policy: allow 'self'
+X-Content-Security-Policy: default-src 'self'
--- a/content/base/test/test_bug548193.html
+++ b/content/base/test/test_bug548193.html
@@ -86,17 +86,17 @@ window.checkResults = function(reportObj
   is(cspReport["request"],
      "GET http://mochi.test:8888/tests/content/base/test/" + testFile + " HTTP/1.1",
      "Incorrect violating request");
   // correct blocked-uri
   is(cspReport["blocked-uri"],
      "http://example.org/tests/content/base/test/file_CSP.sjs?testid=img_bad&type=img/png",
      "Incorrect blocked uri");
   // correct violated-directive
-  is(cspReport["violated-directive"], "allow http://mochi.test:8888",
+  is(cspReport["violated-directive"], "default-src http://mochi.test:8888",
      "Incorrect violated directive");
   // not practical to test request-headers as header names and values will
   // change with the trunk
 }
 
 window.examiner = new examiner();
 
 SimpleTest.waitForExplicitFinish();
--- a/content/base/test/unit/test_csputils.js
+++ b/content/base/test/unit/test_csputils.js
@@ -68,21 +68,21 @@ function do_check_in_array(arr, val, sta
 }
 
 // helper to assert that an object or array must have a given key
 function do_check_has_key(foo, key, stack) {
   if (!stack) 
     stack = Components.stack.caller;
 
   var keys = [];
-  for(let k in keys) { keys.push(k); }
+  for (let k in foo) { keys.push(k); }
   var text = key + " in [" + keys.join(",") + "]";
 
-  for(var x in foo) {
-    if(x == key) {
+  for (var x in foo) {
+    if (x == key) {
       //succeed
       ++_passedChecks;
       dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
            stack.lineNumber + "] " + text + "\n");
       return;
     }
   }
   do_throw(text, stack);
@@ -354,36 +354,67 @@ test(
 test(
     function test_CSPRep_fromString() {
 
       // check default init
       //ASSERT(!(new CSPRep())._isInitialized, "Uninitialized rep thinks it is.")
 
       var cspr;
       var cspr_allowval;
+      var SD = CSPRep.SRC_DIRECTIVES;
 
       // check default policy "allow *"
       cspr = CSPRep.fromString("allow *", "http://self.com:80");
-      //"ALLOW directive is missing when specified in fromString"
-      do_check_has_key(cspr._directives, CSPRep.SRC_DIRECTIVES.ALLOW);
+      // "DEFAULT_SRC directive is missing when specified in fromString"
+      do_check_has_key(cspr._directives, SD.DEFAULT_SRC);
 
       // ... and check that the other directives were auto-filled with the
-      // ALLOW one.
-      var SD = CSPRep.SRC_DIRECTIVES;
-      cspr_allowval = cspr._directives[SD.ALLOW];
-      for(var d in CSPRep.SRC_DIRECTIVES) {
+      // DEFAULT_SRC one.
+      cspr_allowval = cspr._directives[SD.DEFAULT_SRC];
+      for(var d in SD) {
         //"Missing key " + d
         do_check_has_key(cspr._directives, SD[d]);
         //"Implicit directive " + d + " has non-allow value."
         do_check_eq(cspr._directives[SD[d]].toString(), cspr_allowval.toString());
       }
     });
 
 
 test(
+    function test_CSPRep_defaultSrc() {
+      var cspr, cspr_default_val, cspr_allow;
+      var SD = CSPRep.SRC_DIRECTIVES;
+
+      // apply policy of "default-src *" (e.g. "allow *")
+      cspr = CSPRep.fromString("default-src *", "http://self.com:80");
+      // "DEFAULT_SRC directive is missing when specified in fromString"
+      do_check_has_key(cspr._directives, SD.DEFAULT_SRC);
+
+      // check that the other directives were auto-filled with the
+      // DEFAULT_SRC one.
+      cspr_default_val = cspr._directives[SD.DEFAULT_SRC];
+      for (var d in SD) {
+        do_check_has_key(cspr._directives, SD[d]);
+        // "Implicit directive " + d + " has non-default-src value."
+        do_check_eq(cspr._directives[SD[d]].toString(), cspr_default_val.toString());
+      }
+
+      // check that |allow *| and |default-src *| are parsed equivalently and
+      // result in the same set of explicit policy directives
+      cspr = CSPRep.fromString("default-src *", "http://self.com:80");
+      cspr_allow = CSPRep.fromString("allow *", "http://self.com:80");
+
+      for (var d in SD) {
+        do_check_equivalent(cspr._directives[SD[d]],
+                            cspr_allow._directives[SD[d]]);
+      }
+    });
+
+
+test(
     function test_CSPRep_fromString_oneDir() {
 
       var cspr;
       var SD = CSPRep.SRC_DIRECTIVES;
       var DEFAULTS = [SD.STYLE_SRC, SD.MEDIA_SRC, SD.IMG_SRC, SD.FRAME_SRC];
 
       // check one-directive policies
       cspr = CSPRep.fromString("allow bar.com; script-src https://foo.com", 
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -1050,16 +1050,46 @@ HttpChannelChild::SetRequestHeader(const
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelChild::SetupFallbackChannel(const char *aFallbackKey)
 {
   DROP_DEAD();
 }
 
+// The next four _should_ be implemented, but we need to figure out how
+// to transfer the data from the chrome process first.
+
+NS_IMETHODIMP
+HttpChannelChild::GetRemoteAddress(nsACString & _result)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetRemotePort(PRInt32 * _result)
+{
+  NS_ENSURE_ARG_POINTER(_result);
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetLocalAddress(nsACString & _result)
+{
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetLocalPort(PRInt32 * _result)
+{
+  NS_ENSURE_ARG_POINTER(_result);
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+
 //-----------------------------------------------------------------------------
 // HttpChannelChild::nsICacheInfoChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelChild::GetCacheTokenExpirationTime(PRUint32 *_retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -104,16 +104,20 @@ public:
   NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo);
   NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *aContext);
   // HttpBaseChannel::nsIHttpChannel
   NS_IMETHOD SetRequestHeader(const nsACString& aHeader, 
                               const nsACString& aValue, 
                               PRBool aMerge);
   // nsIHttpChannelInternal
   NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey);
+  NS_IMETHOD GetLocalAddress(nsACString& addr);
+  NS_IMETHOD GetLocalPort(PRInt32* port);
+  NS_IMETHOD GetRemoteAddress(nsACString& addr);
+  NS_IMETHOD GetRemotePort(PRInt32* port);
   // nsISupportsPriority
   NS_IMETHOD SetPriority(PRInt32 value);
   // nsIResumableChannel
   NS_IMETHOD ResumeAt(PRUint64 startPos, const nsACString& entityID);
 
   // IPDL holds a reference while the PHttpChannel protocol is live (starting at
   // AsyncOpen, and ending at either OnStopRequest or any IPDL error, either of
   // which call NeckoChild::DeallocPHttpChannel()).
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -40,16 +40,17 @@
 
 #include "nsISupports.h"
 
 class nsAHttpConnection;
 class nsAHttpSegmentReader;
 class nsAHttpSegmentWriter;
 class nsIInterfaceRequestor;
 class nsIEventTarget;
+class nsITransport;
 class nsHttpRequestHead;
 
 //----------------------------------------------------------------------------
 // Abstract base class for a HTTP transaction:
 //
 // A transaction is a "sink" for the response data.  The connection pushes
 // data to the transaction by writing to it.  The transaction supports
 // WriteSegments and may refuse to accept data if its buffers are full (its
@@ -57,23 +58,24 @@ class nsHttpRequestHead;
 //----------------------------------------------------------------------------
 
 class nsAHttpTransaction : public nsISupports
 {
 public:
     // called by the connection when it takes ownership of the transaction.
     virtual void SetConnection(nsAHttpConnection *) = 0;
 
-    // called by the connection to get security callbacks to set on the 
+    // called by the connection to get security callbacks to set on the
     // socket transport.
     virtual void GetSecurityCallbacks(nsIInterfaceRequestor **,
                                       nsIEventTarget **) = 0;
 
     // called to report socket status (see nsITransportEventSink)
-    virtual void OnTransportStatus(nsresult status, PRUint64 progress) = 0;
+    virtual void OnTransportStatus(nsITransport* transport,
+                                   nsresult status, PRUint64 progress) = 0;
 
     // called to check the transaction status.
     virtual PRBool   IsDone() = 0;
     virtual nsresult Status() = 0;
 
     // called to find out how much request data is available for writing.
     virtual PRUint32 Available() = 0;
 
@@ -94,17 +96,18 @@ public:
     // called to retrieve the request headers of the transaction
     virtual nsHttpRequestHead *RequestHead() = 0;
 };
 
 #define NS_DECL_NSAHTTPTRANSACTION \
     void SetConnection(nsAHttpConnection *); \
     void GetSecurityCallbacks(nsIInterfaceRequestor **, \
                               nsIEventTarget **);       \
-    void OnTransportStatus(nsresult status, PRUint64 progress); \
+    void OnTransportStatus(nsITransport* transport, \
+                           nsresult status, PRUint64 progress); \
     PRBool   IsDone(); \
     nsresult Status(); \
     PRUint32 Available(); \
     nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *); \
     nsresult WriteSegments(nsAHttpSegmentWriter *, PRUint32, PRUint32 *); \
     void     Close(nsresult reason);                                    \
     void     SetSSLConnectFailed();                                     \
     nsHttpRequestHead *RequestHead();
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -129,41 +129,44 @@ nsHttpChannel::nsHttpChannel()
     , mFallbackChannel(PR_FALSE)
     , mTracingEnabled(PR_TRUE)
     , mCustomConditionalRequest(PR_FALSE)
     , mFallingBack(PR_FALSE)
     , mWaitingForRedirectCallback(PR_FALSE)
     , mRequestTimeInitialized(PR_FALSE)
 {
     LOG(("Creating nsHttpChannel [this=%p]\n", this));
+    // Subfields of unions cannot be targeted in an initializer list
+    mSelfAddr.raw.family = PR_AF_UNSPEC;
+    mPeerAddr.raw.family = PR_AF_UNSPEC;
 }
 
 nsHttpChannel::~nsHttpChannel()
 {
     LOG(("Destroying nsHttpChannel [this=%p]\n", this));
 
-    if (mAuthProvider) 
+    if (mAuthProvider)
         mAuthProvider->Disconnect(NS_ERROR_ABORT);
 }
 
 nsresult
 nsHttpChannel::Init(nsIURI *uri,
                     PRUint8 caps,
                     nsProxyInfo *proxyInfo)
 {
     nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo);
-    if (NS_FAILED(rv)) 
+    if (NS_FAILED(rv))
         return rv;
 
     LOG(("nsHttpChannel::Init [this=%p]\n", this));
 
     mAuthProvider =
         do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1",
                           &rv);
-    if (NS_FAILED(rv)) 
+    if (NS_FAILED(rv))
         return rv;
     rv = mAuthProvider->Init(this);
 
     return rv;
 }
 //-----------------------------------------------------------------------------
 // nsHttpChannel <private>
 //-----------------------------------------------------------------------------
@@ -3706,16 +3709,76 @@ nsHttpChannel::SetupFallbackChannel(cons
     LOG(("nsHttpChannel::SetupFallbackChannel [this=%x, key=%s]",
          this, aFallbackKey));
     mFallbackChannel = PR_TRUE;
     mFallbackKey = aFallbackKey;
 
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsHttpChannel::GetRemoteAddress(nsACString & _result)
+{
+    if (mPeerAddr.raw.family == PR_AF_UNSPEC)
+        return NS_ERROR_NOT_AVAILABLE;
+
+    _result.SetCapacity(64);
+    PR_NetAddrToString(&mPeerAddr, _result.BeginWriting(), 64);
+    _result.SetLength(strlen(_result.BeginReading()));
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetRemotePort(PRInt32 * _result)
+{
+    NS_ENSURE_ARG_POINTER(_result);
+
+    if (mPeerAddr.raw.family == PR_AF_INET) {
+        *_result = (PRInt32)PR_ntohs(mPeerAddr.inet.port);
+    }
+    else if (mPeerAddr.raw.family == PR_AF_INET6) {
+        *_result = (PRInt32)PR_ntohs(mPeerAddr.ipv6.port);
+    }
+    else
+        return NS_ERROR_NOT_AVAILABLE;
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetLocalAddress(nsACString & _result)
+{
+    if (mSelfAddr.raw.family == PR_AF_UNSPEC)
+        return NS_ERROR_NOT_AVAILABLE;
+
+    _result.SetCapacity(64);
+    PR_NetAddrToString(&mSelfAddr, _result.BeginWriting(), 64);
+    _result.SetLength(strlen(_result.BeginReading()));
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetLocalPort(PRInt32 * _result)
+{
+    NS_ENSURE_ARG_POINTER(_result);
+
+    if (mSelfAddr.raw.family == PR_AF_INET) {
+        *_result = (PRInt32)PR_ntohs(mSelfAddr.inet.port);
+    }
+    else if (mSelfAddr.raw.family == PR_AF_INET6) {
+        *_result = (PRInt32)PR_ntohs(mSelfAddr.ipv6.port);
+    }
+    else
+        return NS_ERROR_NOT_AVAILABLE;
+
+    return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsISupportsPriority
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsHttpChannel::SetPriority(PRInt32 value)
 {
     PRInt16 newValue = NS_CLAMP(value, PR_INT16_MIN, PR_INT16_MAX);
@@ -4148,16 +4211,26 @@ nsHttpChannel::OnDataAvailable(nsIReques
 NS_IMETHODIMP
 nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status,
                                  PRUint64 progress, PRUint64 progressMax)
 {
     // cache the progress sink so we don't have to query for it each time.
     if (!mProgressSink)
         GetCallback(mProgressSink);
 
+    if (status == nsISocketTransport::STATUS_CONNECTED_TO ||
+        status == nsISocketTransport::STATUS_WAITING_FOR) {
+        nsCOMPtr<nsISocketTransport> socketTransport =
+            do_QueryInterface(trans);
+        if (socketTransport) {
+            socketTransport->GetSelfAddr(&mSelfAddr);
+            socketTransport->GetPeerAddr(&mPeerAddr);
+        }
+    }
+
     // block socket status event after Cancel or OnStopRequest has been called.
     if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && !(mLoadFlags & LOAD_BACKGROUND)) {
         LOG(("sending status notification [this=%p status=%x progress=%llu/%llu]\n",
             this, status, progress, progressMax));
 
         nsCAutoString host;
         mURI->GetHost(host);
         mProgressSink->OnStatus(this, nsnull, status,
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -132,16 +132,20 @@ public:
     NS_IMETHOD Cancel(nsresult status);
     NS_IMETHOD Suspend();
     NS_IMETHOD Resume();
     // nsIChannel
     NS_IMETHOD GetSecurityInfo(nsISupports **aSecurityInfo);
     NS_IMETHOD AsyncOpen(nsIStreamListener *listener, nsISupports *aContext);
     // nsIHttpChannelInternal
     NS_IMETHOD SetupFallbackChannel(const char *aFallbackKey);
+    NS_IMETHOD GetLocalAddress(nsACString& addr);
+    NS_IMETHOD GetLocalPort(PRInt32* port);
+    NS_IMETHOD GetRemoteAddress(nsACString& addr);
+    NS_IMETHOD GetRemotePort(PRInt32* port);
     // nsISupportsPriority
     NS_IMETHOD SetPriority(PRInt32 value);
     // nsIResumableChannel
     NS_IMETHOD ResumeAt(PRUint64 startPos, const nsACString& entityID);
 
 public: /* internal necko use only */ 
     typedef void (nsHttpChannel:: *nsAsyncCallback)(void);
 
@@ -342,16 +346,19 @@ private:
     // and also we want to pass possible 304 code response through.
     PRUint32                          mCustomConditionalRequest : 1;
     PRUint32                          mFallingBack              : 1;
     PRUint32                          mWaitingForRedirectCallback : 1;
     // True if mRequestTime has been set. In such a case it is safe to update
     // the cache entry's expiration time. Otherwise, it is not(see bug 567360).
     PRUint32                          mRequestTimeInitialized : 1;
 
+    PRNetAddr                         mSelfAddr;
+    PRNetAddr                         mPeerAddr;
+
     nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
 
     nsCOMPtr<nsICryptoHash>        mHasher;
 
     nsresult WaitForRedirectCallback();
     void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
     void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
 };
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -610,17 +610,18 @@ nsHttpConnection::OnSocketWritable()
         }
         else if (n == 0) {
             // 
             // at this point we've written out the entire transaction, and now we
             // must wait for the server's response.  we manufacture a status message
             // here to reflect the fact that we are waiting.  this message will be
             // trumped (overwritten) if the server responds quickly.
             //
-            mTransaction->OnTransportStatus(nsISocketTransport::STATUS_WAITING_FOR,
+            mTransaction->OnTransportStatus(mSocketTransport,
+                                            nsISocketTransport::STATUS_WAITING_FOR,
                                             LL_ZERO);
 
             rv = mSocketIn->AsyncWait(this, 0, 0, nsnull); // start reading
             again = PR_FALSE;
         }
         // write more to the socket until error or end-of-request...
     } while (again);
 
@@ -806,17 +807,17 @@ nsHttpConnection::OnOutputStreamReady(ns
 
 NS_IMETHODIMP
 nsHttpConnection::OnTransportStatus(nsITransport *trans,
                                     nsresult status,
                                     PRUint64 progress,
                                     PRUint64 progressMax)
 {
     if (mTransaction)
-        mTransaction->OnTransportStatus(status, progress);
+        mTransaction->OnTransportStatus(trans, status, progress);
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpConnection::nsIInterfaceRequestor
 //-----------------------------------------------------------------------------
 
 // not called on the socket transport thread
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -1486,17 +1486,17 @@ nsHalfOpenSocket::OnOutputStreamReady(ns
 // method for nsITransportEventSink
 NS_IMETHODIMP
 nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
                                                          nsresult status,
                                                          PRUint64 progress,
                                                          PRUint64 progressMax)
 {
     if (mTransaction)
-        mTransaction->OnTransportStatus(status, progress);
+      mTransaction->OnTransportStatus(trans, status, progress);
     return NS_OK;
 }
 
 // method for nsIInterfaceRequestor
 NS_IMETHODIMP
 nsHttpConnectionMgr::nsHalfOpenSocket::GetInterface(const nsIID &iid,
                                                     void **result)
 {
--- a/netwerk/protocol/http/nsHttpPipeline.cpp
+++ b/netwerk/protocol/http/nsHttpPipeline.cpp
@@ -362,38 +362,39 @@ nsHttpPipeline::GetSecurityCallbacks(nsI
     else {
         *result = nsnull;
         if (target)
             *target = nsnull;
     }
 }
 
 void
-nsHttpPipeline::OnTransportStatus(nsresult status, PRUint64 progress)
+nsHttpPipeline::OnTransportStatus(nsITransport* transport,
+                                  nsresult status, PRUint64 progress)
 {
     LOG(("nsHttpPipeline::OnStatus [this=%x status=%x progress=%llu]\n",
         this, status, progress));
 
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
     nsAHttpTransaction *trans;
     switch (status) {
     case NS_NET_STATUS_RECEIVING_FROM:
-        // forward this only to the transaction currently recieving data 
+        // forward this only to the transaction currently recieving data
         trans = Response(0);
         if (trans)
-            trans->OnTransportStatus(status, progress);
+            trans->OnTransportStatus(transport, status, progress);
         break;
     default:
         // forward other notifications to all transactions
         PRInt32 i, count = mRequestQ.Length();
         for (i=0; i<count; ++i) {
             trans = Request(i);
             if (trans)
-                trans->OnTransportStatus(status, progress);
+                trans->OnTransportStatus(transport, status, progress);
         }
         break;
     }
 }
 
 PRBool
 nsHttpPipeline::IsDone()
 {
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -346,24 +346,25 @@ nsHttpTransaction::GetSecurityCallbacks(
                                         nsIEventTarget        **target)
 {
     NS_IF_ADDREF(*cb = mCallbacks);
     if (target)
         NS_IF_ADDREF(*target = mConsumerTarget);
 }
 
 void
-nsHttpTransaction::OnTransportStatus(nsresult status, PRUint64 progress)
+nsHttpTransaction::OnTransportStatus(nsITransport* transport,
+                                     nsresult status, PRUint64 progress)
 {
     LOG(("nsHttpTransaction::OnSocketStatus [this=%x status=%x progress=%llu]\n",
         this, status, progress));
 
     if (!mTransportSink)
         return;
-    
+
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
     // Need to do this before the STATUS_RECEIVING_FROM check below, to make
     // sure that the activity distributor gets told about all status events.
     if (mActivityDistributor) {
         // upon STATUS_WAITING_FOR; report request body sent
         if ((mHasRequestBody) &&
             (status == nsISocketTransport::STATUS_WAITING_FOR))
@@ -405,17 +406,17 @@ nsHttpTransaction::OnTransportStatus(nsr
         // notifications.
         progressMax = mRequestSize; // XXX mRequestSize is 32-bit!
     }
     else {
         progress = LL_ZERO;
         progressMax = 0;
     }
 
-    mTransportSink->OnTransportStatus(nsnull, status, progress, progressMax);
+    mTransportSink->OnTransportStatus(transport, status, progress, progressMax);
 }
 
 PRBool
 nsHttpTransaction::IsDone()
 {
     return mTransactionDone;
 }
 
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -41,22 +41,22 @@
 #include "nsTArray.h"
 class nsCString;
 %}
 [ptr] native StringArray(nsTArray<nsCString>);
 
 interface nsIURI;
 interface nsIProxyInfo;
 
-/** 
- * Dumping ground for http.  This interface will never be frozen.  If you are 
- * using any feature exposed by this interface, be aware that this interface 
+/**
+ * Dumping ground for http.  This interface will never be frozen.  If you are
+ * using any feature exposed by this interface, be aware that this interface
  * will change and you will be broken.  You have been warned.
  */
-[scriptable, uuid(44e35ead-6656-4f9e-b51d-ebaf1bee6e2e)]
+[scriptable, uuid(12eb906a-71fe-4b79-b33a-6fe9ab57ea38)]
 interface nsIHttpChannelInternal : nsISupports
 {
     /**
      * An http channel can own a reference to the document URI
      */
     attribute nsIURI documentURI;
 
     /**
@@ -86,22 +86,63 @@ interface nsIHttpChannelInternal : nsISu
 
     /**
      * Force relevant cookies to be sent with this load even if normally they
      * wouldn't be.
      */
     attribute boolean forceAllowThirdPartyCookie;
 
     /**
-     * Returns true iff the channel has been canceled.
+     * True iff the channel has been canceled.
      */
     readonly attribute boolean canceled;
 
     /**
-     * Lets externalhandler tell the channel it is open on behalf of a download
+     * External handlers may set this to true to notify the channel
+     * that it is open on behalf of a download.
      */
     attribute boolean channelIsForDownload;
 
     /**
-     * Transfer chain of redirected cache-keys 
+     * The local IP address to which this channel is bound, in the
+     * format produced by PR_NetAddrToString. May be IPv4 or IPv6.
+     * Note: in the presence of NAT, this may not be the same as the
+     * address that the remote host thinks it's talking to.
+     *
+     * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+     * endpoints are not yet determined, or in any case when
+     * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+     */
+    readonly attribute AUTF8String localAddress;
+
+    /**
+     * The local port number to which this channel is bound.
+     *
+     * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+     * endpoints are not yet determined, or in any case when
+     * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+     */
+    readonly attribute PRInt32 localPort;
+
+    /**
+     * The IP address of the remote host that this channel is
+     * connected to, in the format produced by PR_NetAddrToString.
+     *
+     * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+     * endpoints are not yet determined, or in any case when
+     * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+     */
+    readonly attribute AUTF8String remoteAddress;
+
+    /**
+     * The remote port number that this channel is connected to.
+     *
+     * May throw NS_ERROR_NOT_AVAILABLE if accessed when the channel's
+     * endpoints are not yet determined, or in any case when
+     * nsIHttpActivityObserver.isActive is false. See bugs 534698 and 526207.
+     */
+    readonly attribute PRInt32 remotePort;
+
+    /**
+     * Transfer chain of redirected cache-keys.
      */
     [noscript] void setCacheKeysRedirectChain(in StringArray cacheKeys);
 };
--- a/netwerk/test/unit/test_traceable_channel.js
+++ b/netwerk/test/unit/test_traceable_channel.js
@@ -15,16 +15,23 @@ var gotOnStartRequest = false;
 function TracingListener() {}
 
 TracingListener.prototype = {
   onStartRequest: function(request, context) {
     dump("*** tracing listener onStartRequest\n");
 
     gotOnStartRequest = true;
 
+    request.QueryInterface(Components.interfaces.nsIHttpChannelInternal);
+    do_check_eq(request.localAddress, "127.0.0.1");
+    do_check_eq(request.localPort > 0, true);
+    do_check_neq(request.localPort, 4444);
+    do_check_eq(request.remoteAddress, "127.0.0.1");
+    do_check_eq(request.remotePort, 4444);
+
     // Make sure listener can't be replaced after OnStartRequest was called.
     request.QueryInterface(Components.interfaces.nsITraceableChannel);
     try {
       var newListener = new TracingListener();
       newListener.listener = request.setNewListener(newListener);
     } catch(e) {
       dump("TracingListener.onStartRequest swallowing exception: " + e + "\n");
       return; // OK
--- a/services/crypto/tests/unit/head_helpers.js
+++ b/services/crypto/tests/unit/head_helpers.js
@@ -45,19 +45,26 @@ let XULAppInfoFactory = {
 
 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
 registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
                           "XULAppInfo", "@mozilla.org/xre/app-info;1",
                           XULAppInfoFactory);
 
 }
 
-// Provide resource://services-crypto if it isn't already available
-let weaveService = Cc["@mozilla.org/weave/service;1"].getService();
-weaveService.wrappedJSObject.addResourceAlias();
+// Register resource alias. Normally done in SyncComponents.manifest.
+function addResourceAlias() {
+  Cu.import("resource://gre/modules/Services.jsm");
+  const resProt = Services.io.getProtocolHandler("resource")
+                          .QueryInterface(Ci.nsIResProtocolHandler);
+  let uri = Services.io.newURI("resource:///modules/services-crypto/",
+                               null, null);
+  resProt.setSubstitution("services-crypto", uri);
+}
+addResourceAlias();
 
 /**
  * Print some debug message to the console. All arguments will be printed,
  * separated by spaces.
  *
  * @param [arg0, arg1, arg2, ...]
  *        Any number of arguments to print out
  * @usage _("Hello World") -> prints "Hello World"
--- a/services/sync/SyncComponents.manifest
+++ b/services/sync/SyncComponents.manifest
@@ -1,8 +1,11 @@
 # Weave.js
 component {74b89fb0-f200-4ae8-a3ec-dd164117f6de} Weave.js
 contract @mozilla.org/weave/service;1 {74b89fb0-f200-4ae8-a3ec-dd164117f6de}
 category app-startup WeaveService service,@mozilla.org/weave/service;1
 component {d28f8a0b-95da-48f4-b712-caf37097be41} Weave.js
 contract @mozilla.org/network/protocol/about;1?what=sync-log {d28f8a0b-95da-48f4-b712-caf37097be41}
 component {a08ee179-df50-48e0-9c87-79e4dd5caeb1} Weave.js
 contract @mozilla.org/network/protocol/about;1?what=sync-log.1 {a08ee179-df50-48e0-9c87-79e4dd5caeb1}
+# Register resource aliases
+resource services-sync resource:///modules/services-sync/
+resource services-crypto resource:///modules/services-crypto/
--- a/services/sync/Weave.js
+++ b/services/sync/Weave.js
@@ -53,48 +53,27 @@ WeaveService.prototype = {
     switch (topic) {
     case "app-startup":
       let os = Cc["@mozilla.org/observer-service;1"].
                getService(Ci.nsIObserverService);
       os.addObserver(this, "final-ui-startup", true);
       break;
 
     case "final-ui-startup":
-      this.addResourceAlias();
-
       // Force Weave service to load if it hasn't triggered from overlays
       this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
       this.timer.initWithCallback({
         notify: function() {
           Cu.import("resource://services-sync/main.js");
           if (Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED)
             Weave.Service;
         }
       }, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
       break;
     }
-  },
-
-  addResourceAlias: function() {
-    let ioService = Cc["@mozilla.org/network/io-service;1"]
-                    .getService(Ci.nsIIOService);
-    let resProt = ioService.getProtocolHandler("resource")
-                  .QueryInterface(Ci.nsIResProtocolHandler);
-
-    // Only create alias if resource://services-sync doesn't already exist.
-    if (!resProt.hasSubstitution("services-sync")) {
-      let uri = ioService.newURI("resource:///modules/services-sync/",
-                                 null, null);
-      resProt.setSubstitution("services-sync", uri);
-    }
-    if (!resProt.hasSubstitution("services-crypto")) {
-      let uri = ioService.newURI("resource:///modules/services-crypto/",
-                                 null, null);
-      resProt.setSubstitution("services-crypto", uri);
-    }
   }
 };
 
 function AboutWeaveLog() {}
 AboutWeaveLog.prototype = {
   classID: Components.ID("{d28f8a0b-95da-48f4-b712-caf37097be41}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule,
--- a/services/sync/locales/en-US/errors.properties
+++ b/services/sync/locales/en-US/errors.properties
@@ -1,12 +1,10 @@
 error.login.reason.network    = Failed to connect to the server
 error.login.reason.synckey    = Wrong Sync Key
-# error.login.reason.password is deprecated.
-error.login.reason.password   = Incorrect username or password
 error.login.reason.account    = Incorrect account name or password
 error.login.reason.no_password= No saved password to use
 error.login.reason.no_synckey = No saved Sync Key to use
 error.login.reason.server     = Server incorrectly configured
 
 error.sync.failed_partial     = One or more data types could not be synced
 
 invalid-captcha = Incorrect words, try again
--- a/services/sync/modules/constants.js
+++ b/services/sync/modules/constants.js
@@ -38,16 +38,21 @@
 
 // Process each item in the "constants hash" to add to "global" and give a name
 let EXPORTED_SYMBOLS = [((this[key] = val), key) for ([key, val] in Iterator({
 
 WEAVE_CHANNEL:                         "@weave_channel@",
 WEAVE_VERSION:                         "@weave_version@",
 WEAVE_ID:                              "@weave_id@",
 
+// Sync Server API version that the client supports.
+SYNC_API_VERSION:                      "1.1",
+USER_API_VERSION:                      "1.0",
+MISC_API_VERSION:                      "1.0",
+
 // Version of the data format this client supports. The data format describes
 // how records are packaged; this is separate from the Server API version and
 // the per-engine cleartext formats.
 STORAGE_VERSION:                       5,
 
 UPDATED_DEV_URL:                       "https://services.mozilla.com/sync/updated/?version=@weave_version@&channel=@weave_channel@",
 UPDATED_REL_URL:                       "http://www.mozilla.com/firefox/sync/updated.html",
 
@@ -83,19 +88,25 @@ MAX_IGNORE_ERROR_COUNT:                5
 // HMAC event handling timeout.
 // 10 minutes: a compromise between the multi-desktop sync interval
 // and the mobile sync interval.
 HMAC_EVENT_INTERVAL:                   600000,
 
 // How long to wait between sync attempts if the Master Password is locked.
 MASTER_PASSWORD_LOCKED_RETRY_INTERVAL: 15 * 60 * 1000,   // 15 minutes
 
+// Separate from the ID fetch batch size to allow tuning for mobile.
+MOBILE_BATCH_SIZE:                     50,
+
 // 50 is hardcoded here because of URL length restrictions.
-// (GUIDs can be up to 64 chars long)
-MOBILE_BATCH_SIZE:                     50,
+// (GUIDs can be up to 64 chars long.)
+// Individual engines can set different values for their limit if their
+// identifiers are shorter.
+DEFAULT_GUID_FETCH_BATCH_SIZE:         50,
+DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE:  50,
 
 // Default batch size for applying incoming records.
 DEFAULT_STORE_BATCH_SIZE:              1,
 HISTORY_STORE_BATCH_SIZE:              50, // same as MOBILE_BATCH_SIZE
 FORMS_STORE_BATCH_SIZE:                50, // same as MOBILE_BATCH_SIZE
 PASSWORDS_STORE_BATCH_SIZE:            50, // same as MOBILE_BATCH_SIZE
 
 // score thresholds for early syncs
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -411,24 +411,43 @@ Engine.prototype = {
     this._notify("wipe-client", this.name, this._wipeClient)();
   }
 };
 
 function SyncEngine(name) {
   Engine.call(this, name || "SyncEngine");
   this.loadToFetch();
 }
+
+// Enumeration to define approaches to handling bad records.
+// Attached to the constructor to allow use as a kind of static enumeration.
+SyncEngine.kRecoveryStrategy = {
+  ignore: "ignore",
+  retry:  "retry",
+  error:  "error"
+};
+
 SyncEngine.prototype = {
   __proto__: Engine.prototype,
   _recordObj: CryptoWrapper,
   version: 1,
+  
+  // How many records to pull in a single sync. This is primarily to avoid very
+  // long first syncs against profiles with many history records.
   downloadLimit: null,
+  
+  // How many records to pull at one time when specifying IDs. This is to avoid
+  // URI length limitations.
+  guidFetchBatchSize: DEFAULT_GUID_FETCH_BATCH_SIZE,
+  mobileGUIDFetchBatchSize: DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE,
+  
+  // How many records to process in a single batch.
   applyIncomingBatchSize: DEFAULT_STORE_BATCH_SIZE,
 
-  get storageURL() Svc.Prefs.get("clusterURL") + Svc.Prefs.get("storageAPI") +
+  get storageURL() Svc.Prefs.get("clusterURL") + SYNC_API_VERSION +
     "/" + ID.get("WeaveID").username + "/storage/",
 
   get engineURL() this.storageURL + this.name,
 
   get cryptoKeysURL() this.storageURL + "crypto/keys",
 
   get metaURL() this.storageURL + "meta/global",
 
@@ -584,17 +603,19 @@ SyncEngine.prototype = {
 
   // Process incoming records
   _processIncoming: function SyncEngine__processIncoming() {
     this._log.trace("Downloading & applying server changes");
 
     // Figure out how many total items to fetch this sync; do less on mobile.
     let batchSize = Infinity;
     let newitems = new Collection(this.engineURL, this._recordObj);
-    if (Svc.Prefs.get("client.type") == "mobile") {
+    let isMobile = (Svc.Prefs.get("client.type") == "mobile");
+
+    if (isMobile) {
       batchSize = MOBILE_BATCH_SIZE;
     }
     newitems.newer = this.lastSync;
     newitems.full = true;
     newitems.limit = batchSize;
 
     let count = {applied: 0, failed: 0, reconciled: 0};
     let handled = [];
@@ -635,24 +656,47 @@ SyncEngine.prototype = {
       item.collection = self.name;
       
       // Remember which records were processed
       handled.push(item.id);
 
       try {
         try {
           item.decrypt();
-        } catch (ex if (Utils.isHMACMismatch(ex) &&
-                        self.handleHMACMismatch(item))) {
-          // Let's try handling it.
-          // If the callback returns true, try decrypting again, because
-          // we've got new keys.
-          self._log.info("Trying decrypt again...");
-          item.decrypt();
-        }       
+        } catch (ex if Utils.isHMACMismatch(ex)) {
+          let strategy = self.handleHMACMismatch(item, true);
+          if (strategy == SyncEngine.kRecoveryStrategy.retry) {
+            // You only get one retry.
+            try {
+              // Try decrypting again, typically because we've got new keys.
+              self._log.info("Trying decrypt again...");
+              item.decrypt();
+              strategy = null;
+            } catch (ex if Utils.isHMACMismatch(ex)) {
+              strategy = self.handleHMACMismatch(item, false);
+            }
+          }
+          
+          switch (strategy) {
+            case null:
+              // Retry succeeded! No further handling.
+              break;
+            case SyncEngine.kRecoveryStrategy.retry:
+              self._log.debug("Ignoring second retry suggestion.");
+              // Fall through to error case.
+            case SyncEngine.kRecoveryStrategy.error:
+              self._log.warn("Error decrypting record: " + Utils.exceptionStr(ex));
+              failed.push(item.id);
+              return;
+            case SyncEngine.kRecoveryStrategy.ignore:
+              self._log.debug("Ignoring record " + item.id +
+                              " with bad HMAC: already handled.");
+              return;
+          }
+        }
       } catch (ex) {
         self._log.warn("Error decrypting record: " + Utils.exceptionStr(ex));
         failed.push(item.id);
         return;
       }
 
       let shouldApply;
       try {
@@ -713,17 +757,22 @@ SyncEngine.prototype = {
     }
 
     // Fast-foward the lastSync timestamp since we have stored the
     // remaining items in toFetch.
     if (this.lastSync < this.lastModified) {
       this.lastSync = this.lastModified;
     }
 
-    // Mobile: process any backlog of GUIDs
+    // Process any backlog of GUIDs.
+    // At this point we impose an upper limit on the number of items to fetch
+    // in a single request, even for desktop, to avoid hitting URI limits.
+    batchSize = isMobile ? this.mobileGUIDFetchBatchSize :
+                           this.guidFetchBatchSize;
+
     while (fetchBatch.length) {
       // Reuse the original query, but get rid of the restricting params
       // and batch remaining records.
       newitems.limit = 0;
       newitems.newer = 0;
       newitems.ids = fetchBatch.slice(0, batchSize);
 
       // Reuse the existing record handler set earlier
@@ -1002,13 +1051,37 @@ SyncEngine.prototype = {
     this.resetLastSync();
     this.toFetch = [];
   },
 
   wipeServer: function wipeServer() {
     new Resource(this.engineURL).delete();
     this._resetClient();
   },
-  
-  handleHMACMismatch: function handleHMACMismatch(item) {
-    return Weave.Service.handleHMACEvent();
+
+  removeClientData: function removeClientData() {
+    // Implement this method in engines that store client specific data
+    // on the server.
+  },
+
+  /*
+   * Decide on (and partially effect) an error-handling strategy.
+   *
+   * Asks the Service to respond to an HMAC error, which might result in keys
+   * being downloaded. That call returns true if an action which might allow a
+   * retry to occur.
+   *
+   * If `mayRetry` is truthy, and the Service suggests a retry,
+   * handleHMACMismatch returns kRecoveryStrategy.retry. Otherwise, it returns
+   * kRecoveryStrategy.error.
+   *
+   * Subclasses of SyncEngine can override this method to allow for different
+   * behavior -- e.g., to delete and ignore erroneous entries.
+   *
+   * All return values will be part of the kRecoveryStrategy enumeration.
+   */
+  handleHMACMismatch: function handleHMACMismatch(item, mayRetry) {
+    // By default we either try again, or bail out noisily.
+    return (Weave.Service.handleHMACEvent() && mayRetry) ?
+           SyncEngine.kRecoveryStrategy.retry :
+           SyncEngine.kRecoveryStrategy.error;
   }
 };
--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -53,30 +53,24 @@ const SIDEBAR_ANNO         = "bookmarkPr
 const STATICTITLE_ANNO     = "bookmarks/staticTitle";
 const FEEDURI_ANNO         = "livemark/feedURI";
 const SITEURI_ANNO         = "livemark/siteURI";
 const GENERATORURI_ANNO    = "microsummary/generatorURI";
 const MOBILEROOT_ANNO      = "mobile/bookmarksRoot";
 const MOBILE_ANNO          = "MobileBookmarks";
 const EXCLUDEBACKUP_ANNO   = "places/excludeFromBackup";
 const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
-const GUID_ANNO            = "sync/guid";
 const PARENT_ANNO          = "sync/parent";
 const ANNOS_TO_TRACK = [DESCRIPTION_ANNO, SIDEBAR_ANNO, STATICTITLE_ANNO,
-                       FEEDURI_ANNO, SITEURI_ANNO, GENERATORURI_ANNO];
+                        FEEDURI_ANNO, SITEURI_ANNO, GENERATORURI_ANNO];
 
 const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
 const FOLDER_SORTINDEX = 1000000;
 
-try {
-  Cu.import("resource://gre/modules/PlacesUtils.jsm");
-}
-catch(ex) {
-  Cu.import("resource://gre/modules/utils.js");
-}
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/util.js");
 
 Cu.import("resource://services-sync/main.js");      // For access to Service.
 
 function PlacesItem(collection, id, type) {
@@ -988,41 +982,22 @@ BookmarksStore.prototype = {
   _getStaticTitle: function BStore__getStaticTitle(id) {
     try {
       return Svc.Annos.getItemAnnotation(id, STATICTITLE_ANNO);
     } catch (e) {
       return "";
     }
   },
 
-  __childGUIDsStm: null,
   get _childGUIDsStm() {
-    if (this.__childGUIDsStm) {
-      return this.__childGUIDsStm;
-    }
-
-    let stmt;
-    if (this._haveGUIDColumn) {
-      stmt = this._getStmt(
-        "SELECT id AS item_id, guid " +
-        "FROM moz_bookmarks " +
-        "WHERE parent = :parent " +
-        "ORDER BY position");
-    } else {
-      stmt = this._getStmt(
-        "SELECT b.id AS item_id, " +
-          "(SELECT id FROM moz_anno_attributes WHERE name = '" + GUID_ANNO + "') AS name_id," +
-          "a.content AS guid " +
-        "FROM moz_bookmarks b " +
-        "LEFT JOIN moz_items_annos a ON a.item_id = b.id " +
-                                   "AND a.anno_attribute_id = name_id " +
-        "WHERE b.parent = :parent " +
-        "ORDER BY b.position");
-    }
-    return this.__childGUIDsStm = stmt;
+    return this._getStmt(
+      "SELECT id AS item_id, guid " +
+      "FROM moz_bookmarks " +
+      "WHERE parent = :parent " +
+      "ORDER BY position");
   },
   _childGUIDsCols: ["item_id", "guid"],
 
   _getChildGUIDsForId: function _getChildGUIDsForId(itemid) {
     let stmt = this._childGUIDsStm;
     stmt.params.parent = itemid;
     let rows = Utils.queryAsync(stmt, this._childGUIDsCols);
     return rows.map(function (row) {
@@ -1140,167 +1115,53 @@ BookmarksStore.prototype = {
   },
 
   _stmts: {},
   _getStmt: function(query) {
     if (query in this._stmts)
       return this._stmts[query];
 
     this._log.trace("Creating SQL statement: " + query);
-    return this._stmts[query] = Utils.createStatement(this._hsvc.DBConnection,
-                                                      query);
-  },
-
-  __haveGUIDColumn: null,
-  get _haveGUIDColumn() {
-    if (this.__haveGUIDColumn !== null) {
-      return this.__haveGUIDColumn;
-    }
-    let stmt;
-    try {
-      stmt = this._hsvc.DBConnection.createStatement(
-        "SELECT guid FROM moz_places");
-      stmt.finalize();
-      return this.__haveGUIDColumn = true;
-    } catch(ex) {
-      return this.__haveGUIDColumn = false;
-    }
+    return this._stmts[query] = this._hsvc.DBConnection
+                                    .createAsyncStatement(query);
   },
 
   get _frecencyStm() {
     return this._getStmt(
         "SELECT frecency " +
         "FROM moz_places " +
         "WHERE url = :url " +
         "LIMIT 1");
   },
   _frecencyCols: ["frecency"],
 
-  get _addGUIDAnnotationNameStm() {
-    let stmt = this._getStmt(
-      "INSERT OR IGNORE INTO moz_anno_attributes (name) VALUES (:anno_name)");
-    stmt.params.anno_name = GUID_ANNO;
-    return stmt;
-  },
-
-  get _checkGUIDItemAnnotationStm() {
-    // Gecko <2.0 only
-    let stmt = this._getStmt(
-      "SELECT b.id AS item_id, " +
-        "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS name_id, " +
-        "a.id AS anno_id, a.dateAdded AS anno_date " +
-      "FROM moz_bookmarks b " +
-      "LEFT JOIN moz_items_annos a ON a.item_id = b.id " +
-                                 "AND a.anno_attribute_id = name_id " +
-      "WHERE b.id = :item_id");
-    stmt.params.anno_name = GUID_ANNO;
-    return stmt;
-  },
-  _checkGUIDItemAnnotationCols: ["item_id", "name_id", "anno_id", "anno_date"],
-
-  get _addItemAnnotationStm() {
+  get _setGUIDStm() {
     return this._getStmt(
-    "INSERT OR REPLACE INTO moz_items_annos " +
-      "(id, item_id, anno_attribute_id, mime_type, content, flags, " +
-       "expiration, type, dateAdded, lastModified) " +
-    "VALUES (:id, :item_id, :name_id, :mime_type, :content, :flags, " +
-            ":expiration, :type, :date_added, :last_modified)");
-  },
-
-  __setGUIDStm: null,
-  get _setGUIDStm() {
-    if (this.__setGUIDStm !== null) {
-      return this.__setGUIDStm;
-    }
-
-    // Obtains a statement to set the guid iff the guid column exists.
-    let stmt;
-    if (this._haveGUIDColumn) {
-      stmt = this._getStmt(
-        "UPDATE moz_bookmarks " +
-        "SET guid = :guid " +
-        "WHERE id = :item_id");
-    } else {
-      stmt = false;
-    }
-    return this.__setGUIDStm = stmt;
+      "UPDATE moz_bookmarks " +
+      "SET guid = :guid " +
+      "WHERE id = :item_id");
   },
 
   // Some helper functions to handle GUIDs
   _setGUID: function _setGUID(id, guid) {
     if (!guid)
       guid = Utils.makeGUID();
 
-    // If we can, set the GUID on moz_bookmarks and do not do any other work.
-    let (stmt = this._setGUIDStm) {
-      if (stmt) {
-        stmt.params.guid = guid;
-        stmt.params.item_id = id;
-        Utils.queryAsync(stmt);
-        return guid;
-      }
-    }
-
-    // Ensure annotation name exists
-    Utils.queryAsync(this._addGUIDAnnotationNameStm);
-
-    let stmt = this._checkGUIDItemAnnotationStm;
+    let stmt = this._setGUIDStm;
+    stmt.params.guid = guid;
     stmt.params.item_id = id;
-    let result = Utils.queryAsync(stmt, this._checkGUIDItemAnnotationCols)[0];
-    if (!result) {
-      this._log.warn("Couldn't annotate bookmark id " + id);
-      return guid;
-    }
-
-    stmt = this._addItemAnnotationStm;
-    if (result.anno_id) {
-      stmt.params.id = result.anno_id;
-      stmt.params.date_added = result.anno_date;
-    } else {
-      stmt.params.id = null;
-      stmt.params.date_added = Date.now() * 1000;
-    }
-    stmt.params.item_id = result.item_id;
-    stmt.params.name_id = result.name_id;
-    stmt.params.content = guid;
-    stmt.params.flags = 0;
-    stmt.params.expiration = Ci.nsIAnnotationService.EXPIRE_NEVER;
-    stmt.params.type = Ci.nsIAnnotationService.TYPE_STRING;
-    stmt.params.last_modified = Date.now() * 1000;
     Utils.queryAsync(stmt);
-
     return guid;
   },
 
-  __guidForIdStm: null,
   get _guidForIdStm() {
-    if (this.__guidForIdStm) {
-      return this.__guidForIdStm;
-    }
-
-    // Try to first read from moz_bookmarks.  Creating the statement will
-    // fail, however, if the guid column does not exist.  We fallback to just
-    // reading the annotation table in this case.
-    let stmt;
-    if (this._haveGUIDColumn) {
-      stmt = this._getStmt(
-        "SELECT guid " +
-        "FROM moz_bookmarks " +
-        "WHERE id = :item_id");
-    } else {
-      stmt = this._getStmt(
-        "SELECT a.content AS guid " +
-        "FROM moz_items_annos a " +
-        "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " +
-        "JOIN moz_bookmarks b ON b.id = a.item_id " +
-        "WHERE n.name = '" + GUID_ANNO + "' " +
-        "AND b.id = :item_id");
-    }
-
-    return this.__guidForIdStm = stmt;
+    return this._getStmt(
+      "SELECT guid " +
+      "FROM moz_bookmarks " +
+      "WHERE id = :item_id");
   },
   _guidForIdCols: ["guid"],
 
   GUIDForId: function GUIDForId(id) {
     let special = kSpecialIds.specialGUIDForId(id);
     if (special)
       return special;
 
@@ -1311,47 +1172,21 @@ BookmarksStore.prototype = {
     let result = Utils.queryAsync(stmt, this._guidForIdCols)[0];
     if (result && result.guid)
       return result.guid;
 
     // Give the uri a GUID if it doesn't have one
     return this._setGUID(id);
   },
 
-  __idForGUIDStm: null,
   get _idForGUIDStm() {
-    if (this.__idForGUIDStm) {
-      return this.__idForGUIDStm;
-    }
-
-
-    // Try to first read from moz_bookmarks.  Creating the statement will
-    // fail, however, if the guid column does not exist.  We fallback to just
-    // reading the annotation table in this case.
-    let stmt;
-    if (this._haveGUIDColumn) {
-      stmt = this._getStmt(
-        "SELECT id AS item_id " +
-        "FROM moz_bookmarks " +
-        "WHERE guid = :guid");
-    } else {
-      // Order results by lastModified so we can preserve the ID of the oldest bookmark.
-      // Copying a record preserves its dateAdded, and only modifying the
-      // bookmark alters its lastModified, so we also order by its item_id --
-      // lowest wins ties. Of course, Places can still screw us by reassigning IDs...
-      stmt = this._getStmt(
-        "SELECT a.item_id AS item_id " +
-        "FROM moz_items_annos a " +
-        "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " +
-        "WHERE n.name = '" + GUID_ANNO + "' " +
-        "AND a.content = :guid " +
-        "ORDER BY a.lastModified, a.item_id");
-    }
-
-    return this.__idForGUIDStm = stmt;
+    return this._getStmt(
+      "SELECT id AS item_id " +
+      "FROM moz_bookmarks " +
+      "WHERE guid = :guid");
   },
   _idForGUIDCols: ["item_id"],
 
   // noCreate is provided as an optional argument to prevent the creation of
   // non-existent special records, such as "mobile".
   idForGUID: function idForGUID(guid, noCreate) {
     if (kSpecialIds.isSpecialGUID(guid))
       return kSpecialIds.specialIdForGUID(guid, !noCreate);
@@ -1365,32 +1200,16 @@ BookmarksStore.prototype = {
                     + results.length);
     
     // Here's the one we care about: the first.
     let result = results[0];
     
     if (!result)
       return -1;
     
-    if (!this._haveGUIDColumn) {
-      try {
-        // Assign new GUIDs to any that came later.
-        for (let i = 1; i < results.length; ++i) {
-          let surplus = results[i];
-          this._log.debug("Assigning new GUID to copied row " + surplus.item_id);
-          this._setGUID(surplus.item_id);
-        }
-      } catch (ex) {
-        // Just skip it and carry on. This shouldn't happen, but if it does we
-        // don't want to fail hard.
-        this._log.debug("Got exception assigning new GUIDs: " +
-                        Utils.exceptionStr(ex));
-      }
-    }
-    
     return result.item_id;
   },
 
   _calculateIndex: function _calculateIndex(record) {
     // Ensure folders have a very high sort index so they're not synced last.
     if (record.type == "folder")
       return FOLDER_SORTINDEX;
 
@@ -1584,44 +1403,54 @@ BookmarksTracker.prototype = {
    * folder is for livemarks
    *
    * @param itemId
    *        Item under consideration to ignore
    * @param folder (optional)
    *        Folder of the item being changed
    */
   _ignore: function BMT__ignore(itemId, folder) {
-    // Ignore unconditionally if the engine tells us to
+    // Ignore unconditionally if the engine tells us to.
     if (this.ignoreAll)
       return true;
 
-    // Ensure that the mobile bookmarks query is correct in the UI
-    this._ensureMobileQuery();
+    // Get the folder id if we weren't given one.
+    if (folder == null) {
+      try {
+        folder = this._bms.getFolderIdForItem(itemId);
+      } catch (ex) {
+        this._log.debug("getFolderIdForItem(" + itemId +
+                        ") threw; calling _ensureMobileQuery.");
+        // I'm guessing that gFIFI can throw, and perhaps that's why
+        // _ensureMobileQuery is here at all. Try not to call it.
+        this._ensureMobileQuery();
+        folder = this._bms.getFolderIdForItem(itemId);
+      }
+    }
 
-    // Make sure to remove items that have the exclude annotation
+    // Ignore livemark children.
+    if (this._ls.isLivemark(folder))
+      return true;
+
+    // Ignore changes to tags (folders under the tags folder).
+    let tags = kSpecialIds.tags;
+    if (folder == tags)
+      return true;
+
+    // Ignore tag items (the actual instance of a tag for a bookmark).
+    if (this._bms.getFolderIdForItem(folder) == tags)
+      return true;
+
+    // Make sure to remove items that have the exclude annotation.
     if (Svc.Annos.itemHasAnnotation(itemId, EXCLUDEBACKUP_ANNO)) {
       this.removeChangedID(this._GUIDForId(itemId));
       return true;
     }
 
-    // Get the folder id if we weren't given one
-    if (folder == null)
-      folder = this._bms.getFolderIdForItem(itemId);
-
-    let tags = kSpecialIds.tags;
-    // Ignore changes to tags (folders under the tags folder)
-    if (folder == tags)
-      return true;
-
-    // Ignore tag items (the actual instance of a tag for a bookmark)
-    if (this._bms.getFolderIdForItem(folder) == tags)
-      return true;
-
-    // Ignore livemark children
-    return this._ls.isLivemark(folder);
+    return false;
   },
 
   onItemAdded: function BMT_onEndUpdateBatch(itemId, folder, index) {
     if (this._ignore(itemId, folder))
       return;
 
     this._log.trace("onItemAdded: " + itemId);
     this._addId(itemId);
@@ -1650,17 +1479,17 @@ BookmarksTracker.prototype = {
 
     // Disable handling of notifications while changing the mobile query
     this.ignoreAll = true;
 
     let mobile = find(MOBILE_ANNO);
     let queryURI = Utils.makeURI("place:folder=" + kSpecialIds.mobile);
     let title = Str.sync.get("mobile.label");
 
-    // Don't add OR do remove the mobile bookmarks if there's nothing
+    // Don't add OR remove the mobile bookmarks if there's nothing.
     if (Svc.Bookmark.getIdForItemAt(kSpecialIds.mobile, 0) == -1) {
       if (mobile.length != 0)
         Svc.Bookmark.removeItem(mobile[0]);
     }
     // Add the mobile bookmarks query if it doesn't exist
     else if (mobile.length == 0) {
       let query = Svc.Bookmark.insertBookmark(all[0], queryURI, -1, title);
       Svc.Annos.setItemAnnotation(query, anno, MOBILE_ANNO, 0,
@@ -1670,38 +1499,36 @@ BookmarksTracker.prototype = {
     }
     // Make sure the existing title is correct
     else if (Svc.Bookmark.getItemTitle(mobile[0]) != title)
       Svc.Bookmark.setItemTitle(mobile[0], title);
 
     this.ignoreAll = false;
   },
 
+  // This method is oddly structured, but the idea is to return as quickly as
+  // possible -- this handler gets called *every time* a bookmark changes, for
+  // *each change*. That's particularly bad when a bunch of livemarks are
+  // updated.
   onItemChanged: function BMT_onItemChanged(itemId, property, isAnno, value) {
-    if (this._ignore(itemId))
+    // Quicker checks first.
+    if (this.ignoreAll)
       return;
 
-    // Allocate a new GUID if necessary.
-    // We only want to do it if there's a dupe, so use idForGUID to achieve that.
-    if (isAnno && (property == GUID_ANNO)) {
-      this._log.trace("onItemChanged for " + GUID_ANNO +
-                      ": probably needs a new one.");
-      this._idForGUID(this._GUIDForId(itemId));
-      this._addId(itemId);
-      return;
-    }
-
-    // ignore annotations except for the ones that we sync
-    if (isAnno && ANNOS_TO_TRACK.indexOf(property) == -1)
+    if (isAnno && (ANNOS_TO_TRACK.indexOf(property) == -1))
+      // Ignore annotations except for the ones that we sync.
       return;
 
-    // Ignore favicon changes to avoid unnecessary churn
+    // Ignore favicon changes to avoid unnecessary churn.
     if (property == "favicon")
       return;
 
+    if (this._ignore(itemId))
+      return;
+
     this._log.trace("onItemChanged: " + itemId +
                     (", " + property + (isAnno? " (anno)" : "")) +
                     (value ? (" = \"" + value + "\"") : ""));
     this._addId(itemId);
   },
 
   onItemMoved: function BMT_onItemMoved(itemId, oldParent, oldIndex, newParent, newIndex) {
     if (this._ignore(itemId))
--- a/services/sync/modules/engines/clients.js
+++ b/services/sync/modules/engines/clients.js
@@ -40,16 +40,17 @@ const EXPORTED_SYMBOLS = ["Clients", "Cl
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/ext/StringBundle.js");
 Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/util.js");
 
 const CLIENTS_TTL = 1814400; // 21 days
 const CLIENTS_TTL_REFRESH = 604800; // 7 days
 
 function ClientsRec(collection, id) {
   CryptoWrapper.call(this, collection, id);
 }
@@ -188,29 +189,36 @@ ClientEngine.prototype = {
 
   // Treat reset the same as wiping for locally cached clients
   _resetClient: function _resetClient() this._wipeClient(),
 
   _wipeClient: function _wipeClient() {
     SyncEngine.prototype._resetClient.call(this);
     this._store.wipe();
   },
-  
+
+  removeClientData: function removeClientData() {
+    let res = new Resource(this.engineURL + "/" + this.localID);
+    res.delete();
+  },
+
   // Override the default behavior to delete bad records from the server.
-  handleHMACMismatch: function handleHMACMismatch(item) {
+  handleHMACMismatch: function handleHMACMismatch(item, mayRetry) {
     this._log.debug("Handling HMAC mismatch for " + item.id);
-    if (SyncEngine.prototype.handleHMACMismatch.call(this, item))
-      return true;
+    
+    let base = SyncEngine.prototype.handleHMACMismatch.call(this, item, mayRetry);
+    if (base != SyncEngine.kRecoveryStrategy.error)
+      return base;
 
     // It's a bad client record. Save it to be deleted at the end of the sync.
     this._log.debug("Bad client record detected. Scheduling for deletion.");
     this._deleteId(item.id);
 
-    // Don't try again.
-    return false;
+    // Neither try again nor error; we're going to delete it.
+    return SyncEngine.kRecoveryStrategy.ignore;
   }
 };
 
 function ClientStore(name) {
   Store.call(this, name);
 }
 ClientStore.prototype = {
   __proto__: Store.prototype,
--- a/services/sync/modules/engines/forms.js
+++ b/services/sync/modules/engines/forms.js
@@ -61,35 +61,35 @@ FormRec.prototype = {
 Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]);
 
 
 let FormWrapper = {
   _log: Log4Moz.repository.getLogger('Engine.Forms'),
     
   getAllEntries: function getAllEntries() {
     // Sort by (lastUsed - minLast) / (maxLast - minLast) * timesUsed / maxTimes
-    let query = this.createStatement(
+    let query = Svc.Form.DBConnection.createAsyncStatement(
       "SELECT fieldname name, value FROM moz_formhistory " +
       "ORDER BY 1.0 * (lastUsed - (SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed ASC LIMIT 1)) / " +
         "((SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed DESC LIMIT 1) - (SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed ASC LIMIT 1)) * " +
         "timesUsed / (SELECT timesUsed FROM moz_formhistory ORDER BY timesUsed DESC LIMIT 1) DESC " +
       "LIMIT 500");
     return Utils.queryAsync(query, ["name", "value"]);
   },
 
   getEntry: function getEntry(guid) {
-    let query = this.createStatement(
+    let query = Svc.Form.DBConnection.createAsyncStatement(
       "SELECT fieldname name, value FROM moz_formhistory WHERE guid = :guid");
     query.params.guid = guid;
     return Utils.queryAsync(query, ["name", "value"])[0];
   },
 
   getGUID: function getGUID(name, value) {
     // Query for the provided entry
-    let getQuery = this.createStatement(
+    let getQuery = Svc.Form.DBConnection.createAsyncStatement(
       "SELECT guid FROM moz_formhistory " +
       "WHERE fieldname = :name AND value = :value");
     getQuery.params.name = name;
     getQuery.params.value = value;
 
     // Give the guid if we found one
     let item = Utils.queryAsync(getQuery, ["guid"])[0];
     
@@ -101,60 +101,43 @@ let FormWrapper = {
                       JSON.stringify(value) + ") => " + item);
       return null;
     }
     
     if (item.guid != null)
       return item.guid;
 
     // We need to create a guid for this entry
-    let setQuery = this.createStatement(
+    let setQuery = Svc.Form.DBConnection.createAsyncStatement(
       "UPDATE moz_formhistory SET guid = :guid " +
       "WHERE fieldname = :name AND value = :value");
     let guid = Utils.makeGUID();
     setQuery.params.guid = guid;
     setQuery.params.name = name;
     setQuery.params.value = value;
     Utils.queryAsync(setQuery);
 
     return guid;
   },
 
   hasGUID: function hasGUID(guid) {
-    let query = this.createStatement(
+    let query = Svc.Form.DBConnection.createAsyncStatement(
       "SELECT guid FROM moz_formhistory WHERE guid = :guid LIMIT 1");
     query.params.guid = guid;
     return Utils.queryAsync(query, ["guid"]).length == 1;
   },
 
   replaceGUID: function replaceGUID(oldGUID, newGUID) {
-    let query = this.createStatement(
+    let query = Svc.Form.DBConnection.createAsyncStatement(
       "UPDATE moz_formhistory SET guid = :newGUID WHERE guid = :oldGUID");
     query.params.oldGUID = oldGUID;
     query.params.newGUID = newGUID;
     Utils.queryAsync(query);
-  },
+  }
 
-  createStatement: function createStatement(query) {
-    try {
-      // Just return the statement right away if it's okay
-      return Utils.createStatement(Svc.Form.DBConnection, query);
-    }
-    catch(ex) {
-      // Assume guid column must not exist yet, so add it with an index
-      Svc.Form.DBConnection.executeSimpleSQL(
-        "ALTER TABLE moz_formhistory ADD COLUMN guid TEXT");
-      Svc.Form.DBConnection.executeSimpleSQL(
-        "CREATE INDEX IF NOT EXISTS moz_formhistory_guid_index " +
-        "ON moz_formhistory (guid)");
-
-      // Try creating the query now that the column exists
-      return Utils.createStatement(Svc.Form.DBConnection, query);
-    }
-  }
 };
 
 function FormEngine() {
   SyncEngine.call(this, "Forms");
 }
 FormEngine.prototype = {
   __proto__: SyncEngine.prototype,
   _storeObj: FormStore,
@@ -226,17 +209,17 @@ FormStore.prototype = {
     let entry = FormWrapper.getEntry(record.id);
     if (entry == null)
       return;
 
     Svc.Form.removeEntry(entry.name, entry.value);
   },
 
   update: function FormStore_update(record) {
-    this._log.warn("Ignoring form record update request!");
+    this._log.trace("Ignoring form record update request!");
   },
 
   wipe: function FormStore_wipe() {
     Svc.Form.removeAllEntries();
   }
 };
 
 function FormTracker(name) {
@@ -278,53 +261,29 @@ FormTracker.prototype = {
           Svc.Obs.remove("form-notifier", this);
           Svc.Obs.remove("satchel-storage-changed", this);
           Cc["@mozilla.org/observer-service;1"]
             .getService(Ci.nsIObserverService)
             .removeObserver(this, "earlyformsubmit");
           this._enabled = false;
         }
         break;
-      // Firefox 4.0
       case "satchel-storage-changed":
         if (data == "addEntry" || data == "before-removeEntry") {
           subject = subject.QueryInterface(Ci.nsIArray);
           let name = subject.queryElementAt(0, Ci.nsISupportsString)
                             .toString();
           let value = subject.queryElementAt(1, Ci.nsISupportsString)
                              .toString();
           this.trackEntry(name, value);
         }
         break;
-      // Firefox 3.5/3.6
-      case "form-notifier":
-        this.onFormNotifier(data);
-        break;
     }
   },
 
-  // Firefox 3.5/3.6
-  onFormNotifier: function onFormNotifier(data) {
-    let name, value;
-
-    // Figure out if it's a function that we care about tracking
-    let formCall = JSON.parse(data);
-    let func = formCall.func;
-    if ((func == "addEntry" && formCall.type == "after") ||
-        (func == "removeEntry" && formCall.type == "before"))
-      [name, value] = formCall.args;
-
-    // Skip if there's nothing of interest
-    if (name == null || value == null)
-      return;
-
-    this._log.trace("Logging form action: " + [func, name, value]);
-    this.trackEntry(name, value);
-  },
-
   notify: function FormTracker_notify(formElement, aWindow, actionURI) {
     if (this.ignoreAll)
       return;
 
     this._log.trace("Form submission notification for " + actionURI.spec);
 
     // XXX Bug 487541 Copy the logic from nsFormHistory::Notify to avoid
     // divergent logic, which can lead to security issues, until there's a
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -39,17 +39,16 @@
 
 const EXPORTED_SYMBOLS = ['HistoryEngine', 'HistoryRec'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
-const GUID_ANNO = "sync/guid";
 const HISTORY_TTL = 5184000; // 60 days
 const TOPIC_UPDATEPLACES_COMPLETE = "places-updatePlaces-complete";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/util.js");
@@ -73,19 +72,16 @@ function HistoryEngine() {
 HistoryEngine.prototype = {
   __proto__: SyncEngine.prototype,
   _recordObj: HistoryRec,
   _storeObj: HistoryStore,
   _trackerObj: HistoryTracker,
   downloadLimit: MAX_HISTORY_DOWNLOAD,
   applyIncomingBatchSize: HISTORY_STORE_BATCH_SIZE,
 
-  // For Gecko <2.0
-  _sync: Utils.batchSync("History", SyncEngine),
-
   _findDupe: function _findDupe(item) {
     return this._store.GUIDForUri(item.histUri);
   }
 };
 
 function HistoryStore(name) {
   Store.call(this, name);
 
@@ -108,200 +104,59 @@ HistoryStore.prototype = {
                     QueryInterface(Ci.nsIGlobalHistory2).
                     QueryInterface(Ci.nsIBrowserHistory).
                     QueryInterface(Ci.nsPIPlacesDatabase);
     return this.__hsvc;
   },
 
   __asyncHistory: null,
   get _asyncHistory() {
-    if (!this.__asyncHistory && "mozIAsyncHistory" in Components.interfaces) {
+    if (!this.__asyncHistory) {
       this.__asyncHistory = Cc["@mozilla.org/browser/history;1"]
                               .getService(Ci.mozIAsyncHistory);
     }
     return this.__asyncHistory;
   },
 
-  get _db() {
-    return this._hsvc.DBConnection;
-  },
-
   _stmts: {},
   _getStmt: function(query) {
     if (query in this._stmts)
       return this._stmts[query];
 
     this._log.trace("Creating SQL statement: " + query);
-    return this._stmts[query] = Utils.createStatement(this._db, query);
-  },
-
-  get _haveTempTablesStm() {
-    return this._getStmt(
-      "SELECT name FROM sqlite_temp_master " +
-      "WHERE name IN ('moz_places_temp', 'moz_historyvisits_temp')");
-  },
-  _haveTempTablesCols: ["name"],
-
-  __haveTempTables: null,
-  get _haveTempTables() {
-    if (this.__haveTempTables === null) {
-      this.__haveTempTables = !!Utils.queryAsync(
-        this._haveTempTablesStm, this._haveTempTablesCols).length;
-    }
-    return this.__haveTempTables;
-  },
-
-  __haveGUIDColumn: null,
-  get _haveGUIDColumn() {
-    if (this.__haveGUIDColumn !== null) {
-      return this.__haveGUIDColumn;
-    }
-    let stmt;
-    try {
-      stmt = this._db.createStatement("SELECT guid FROM moz_places");
-      stmt.finalize();
-      return this.__haveGUIDColumn = true;
-    } catch(ex) {
-      return this.__haveGUIDColumn = false;
-    }
-  },
-
-  get _addGUIDAnnotationNameStm() {
-    // Gecko <2.0 only
-    let stmt = this._getStmt(
-      "INSERT OR IGNORE INTO moz_anno_attributes (name) VALUES (:anno_name)");
-    stmt.params.anno_name = GUID_ANNO;
-    return stmt;
+    return this._stmts[query] = this._hsvc.DBConnection
+                                    .createAsyncStatement(query);
   },
 
-  get _checkGUIDPageAnnotationStm() {
-    // Gecko <2.0 only
-    let stmt = this._getStmt(
-      "SELECT h.id AS place_id, " +
-        "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS name_id, " +
-        "a.id AS anno_id, a.dateAdded AS anno_date " +
-      "FROM (SELECT id FROM moz_places_temp WHERE url = :page_url " +
-            "UNION " +
-            "SELECT id FROM moz_places WHERE url = :page_url) AS h " +
-      "LEFT JOIN moz_annos a ON a.place_id = h.id " +
-                           "AND a.anno_attribute_id = name_id");
-    stmt.params.anno_name = GUID_ANNO;
-    return stmt;
-  },
-  _checkGUIDPageAnnotationCols: ["place_id", "name_id", "anno_id",
-                                 "anno_date"],
-
-  get _addPageAnnotationStm() {
-    // Gecko <2.0 only
+  get _setGUIDStm() {
     return this._getStmt(
-    "INSERT OR REPLACE INTO moz_annos " +
-      "(id, place_id, anno_attribute_id, mime_type, content, flags, " +
-       "expiration, type, dateAdded, lastModified) " +
-    "VALUES (:id, :place_id, :name_id, :mime_type, :content, :flags, " +
-            ":expiration, :type, :date_added, :last_modified)");
-  },
-
-  __setGUIDStm: null,
-  get _setGUIDStm() {
-    if (this.__setGUIDStm !== null) {
-      return this.__setGUIDStm;
-    }
-
-    // Obtains a statement to set the guid iff the guid column exists.
-    let stmt;
-    if (this._haveGUIDColumn) {
-      stmt = this._getStmt(
-        "UPDATE moz_places " +
-        "SET guid = :guid " +
-        "WHERE url = :page_url");
-    } else {
-      stmt = false;
-    }
-
-    return this.__setGUIDStm = stmt;
+      "UPDATE moz_places " +
+      "SET guid = :guid " +
+      "WHERE url = :page_url");
   },
 
   // Some helper functions to handle GUIDs
   setGUID: function setGUID(uri, guid) {
     uri = uri.spec ? uri.spec : uri;
 
     if (!guid)
       guid = Utils.makeGUID();
 
-    // If we can, set the GUID on moz_places and do not do any other work.
-    let (stmt = this._setGUIDStm) {
-      if (stmt) {
-        stmt.params.guid = guid;
-        stmt.params.page_url = uri;
-        Utils.queryAsync(stmt);
-        return guid;
-      }
-    }
-
-    // Ensure annotation name exists
-    Utils.queryAsync(this._addGUIDAnnotationNameStm);
-
-    let stmt = this._checkGUIDPageAnnotationStm;
+    let stmt = this._setGUIDStm;
+    stmt.params.guid = guid;
     stmt.params.page_url = uri;
-    let result = Utils.queryAsync(stmt, this._checkGUIDPageAnnotationCols)[0];
-    if (!result) {
-      let log = Log4Moz.repository.getLogger("Engine.History");
-      log.warn("Couldn't annotate URI " + uri);
-      return guid;
-    }
-
-    stmt = this._addPageAnnotationStm;
-    if (result.anno_id) {
-      stmt.params.id = result.anno_id;
-      stmt.params.date_added = result.anno_date;
-    } else {
-      stmt.params.id = null;
-      stmt.params.date_added = Date.now() * 1000;
-    }
-    stmt.params.place_id = result.place_id;
-    stmt.params.name_id = result.name_id;
-    stmt.params.content = guid;
-    stmt.params.flags = 0;
-    stmt.params.expiration = Ci.nsIAnnotationService.EXPIRE_WITH_HISTORY;
-    stmt.params.type = Ci.nsIAnnotationService.TYPE_STRING;
-    stmt.params.last_modified = Date.now() * 1000;
     Utils.queryAsync(stmt);
-
     return guid;
   },
 
-  __guidStm: null,
   get _guidStm() {
-    if (this.__guidStm) {
-      return this.__guidStm;
-    }
-
-    // Try to first read from moz_places.  Creating the statement will throw
-    // if the column doesn't exist, though so fallback to just reading from
-    // the annotation table.
-    let stmt;
-    if (this._haveGUIDColumn) {
-      stmt = this._getStmt(
-        "SELECT guid " +
-        "FROM moz_places " +
-        "WHERE url = :page_url");
-    } else {
-      stmt = this._getStmt(
-        "SELECT a.content AS guid " +
-        "FROM moz_annos a " +
-        "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " +
-        "JOIN ( " +
-          "SELECT id FROM moz_places_temp WHERE url = :page_url " +
-          "UNION " +
-          "SELECT id FROM moz_places WHERE url = :page_url " +
-        ") AS h ON h.id = a.place_id " +
-        "WHERE n.name = '" + GUID_ANNO + "'");
-    }
-
-    return this.__guidStmt = stmt;
+    return this._getStmt(
+      "SELECT guid " +
+      "FROM moz_places " +
+      "WHERE url = :page_url");
   },
   _guidCols: ["guid"],
 
   GUIDForUri: function GUIDForUri(uri, create) {
     let stm = this._guidStm;
     stm.params.page_url = uri.spec ? uri.spec : uri;
 
     // Use the existing GUID if it exists
@@ -310,86 +165,33 @@ HistoryStore.prototype = {
       return result.guid;
 
     // Give the uri a GUID if it doesn't have one
     if (create)
       return this.setGUID(uri);
   },
 
   get _visitStm() {
-    // Gecko <2.0
-    if (this._haveTempTables) {
-      let where = 
-        "WHERE place_id = IFNULL( " +
-          "(SELECT id FROM moz_places_temp WHERE url = :url), " +
-          "(SELECT id FROM moz_places WHERE url = :url) " +
-        ") ";
-      return this._getStmt(
-        "SELECT visit_type type, visit_date date " +
-        "FROM moz_historyvisits_temp " + where + "UNION " +
-        "SELECT visit_type type, visit_date date " +
-        "FROM moz_historyvisits " + where +
-        "ORDER BY date DESC LIMIT 10 ");
-    }
-    // Gecko 2.0
     return this._getStmt(
       "SELECT visit_type type, visit_date date " +
       "FROM moz_historyvisits " +
       "WHERE place_id = (SELECT id FROM moz_places WHERE url = :url) " +
       "ORDER BY date DESC LIMIT 10");
   },
   _visitCols: ["date", "type"],
 
-  __urlStmt: null,
   get _urlStm() {
-    if (this.__urlStmt) {
-      return this.__urlStmt;
-    }
-
-    // Try to first read from moz_places.  Creating the statement will throw
-    // if the column doesn't exist, though so fallback to just reading from
-    // the annotation table.
-    let stmt;
-    if (this._haveGUIDColumn) {
-      stmt = this._getStmt(
-        "SELECT url, title, frecency " +
-        "FROM moz_places " +
-        "WHERE guid = :guid");
-    } else {
-      let where =
-        "WHERE id = (" +
-          "SELECT place_id " +
-          "FROM moz_annos " +
-          "WHERE content = :guid AND anno_attribute_id = (" +
-            "SELECT id " +
-            "FROM moz_anno_attributes " +
-            "WHERE name = '" + GUID_ANNO + "')) ";
-      stmt = this._getStmt(
-        "SELECT url, title, frecency FROM moz_places_temp " + where +
-        "UNION ALL " +
-        "SELECT url, title, frecency FROM moz_places " + where + "LIMIT 1");
-    }
-
-    return this.__urlStmt = stmt;
+    return this._getStmt(
+      "SELECT url, title, frecency " +
+      "FROM moz_places " +
+      "WHERE guid = :guid");
   },
   _urlCols: ["url", "title", "frecency"],
 
   get _allUrlStm() {
-    // Gecko <2.0
-    if (this._haveTempTables)
-      return this._getStmt(
-        "SELECT url, frecency FROM moz_places_temp " +
-        "WHERE last_visit_date > :cutoff_date " +
-        "UNION " +
-        "SELECT url, frecency FROM moz_places " +
-        "WHERE last_visit_date > :cutoff_date " +
-        "ORDER BY 2 DESC " +
-        "LIMIT :max_results");
-
-    // Gecko 2.0
     return this._getStmt(
       "SELECT url " +
       "FROM moz_places " +
       "WHERE last_visit_date > :cutoff_date " +
       "ORDER BY frecency DESC " +
       "LIMIT :max_results");
   },
   _allUrlCols: ["url"],
@@ -420,22 +222,16 @@ HistoryStore.prototype = {
     let self = this;
     return urls.reduce(function(ids, item) {
       ids[self.GUIDForUri(item.url, true)] = item.url;
       return ids;
     }, {});
   },
 
   applyIncomingBatch: function applyIncomingBatch(records) {
-    // Gecko <2.0
-    if (!this._asyncHistory) {
-      return Store.prototype.applyIncomingBatch.call(this, records);
-    }
-
-    // Gecko 2.0
     let failed = [];
 
     // Convert incoming records to mozIPlaceInfo objects. Some records can be
     // ignored or handled directly, so we're rewriting the array in-place.
     let i, k;
     for (i = 0, k = 0; i < records.length; i++) {
       let record = records[k] = records[i];
       let shouldApply;
@@ -524,19 +320,18 @@ HistoryStore.prototype = {
     for (i = 0, k = 0; i < record.visits.length; i++) {
       let visit = record.visits[k] = record.visits[i];
 
       if (!visit.date || typeof visit.date != "number") {
         this._log.warn("Encountered record with invalid visit date: "
                        + visit.date);
         throw "Visit has no date!";
       }
-      // TRANSITION_FRAMED_LINK = TRANSITION_DOWNLOAD + 1 is new in Gecko 2.0
       if (!visit.type || !(visit.type >= Svc.History.TRANSITION_LINK &&
-                           visit.type <= Svc.History.TRANSITION_DOWNLOAD + 1)) {
+                           visit.type <= Svc.History.TRANSITION_FRAMED_LINK)) {
         this._log.warn("Encountered record with invalid visit type: "
                        + visit.type);
         throw "Invalid visit type!";
       }
       // Dates need to be integers
       visit.date = Math.round(visit.date);
 
       if (curVisits.indexOf(visit.date + "," + visit.type) != -1) {
@@ -558,57 +353,28 @@ HistoryStore.prototype = {
       this._log.trace("Ignoring record " + record.id + " with URI "
                       + record.uri.spec + ": no visits to add.");
       return false;
     }
 
     return true;
   },
 
-  create: function HistStore_create(record) {
-    // Add the url and set the GUID
-    this.update(record);
-    this.setGUID(record.histUri, record.id);
-  },
-
   remove: function HistStore_remove(record) {
     let page = this._findURLByGUID(record.id);
     if (page == null) {
       this._log.debug("Page already removed: " + record.id);
       return;
     }
 
     let uri = Utils.makeURI(page.url);
     Svc.History.removePage(uri);
     this._log.trace("Removed page: " + [record.id, page.url, page.title]);
   },
 
-  update: function HistStore_update(record) {
-    this._log.trace("  -> processing history entry: " + record.histUri);
-
-    if (!this._recordToPlaceInfo(record)) {
-      return;
-    }
-
-    for each (let {visitDate, transitionType} in record.visits) {
-      Svc.History.addVisit(record.uri, visitDate, null, transitionType,
-                           transitionType == 5 || transitionType == 6, 0);
-    }
-
-    if (record.title) {
-      try {
-        this._hsvc.setPageTitle(record.uri, record.title);
-      } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) {
-        // There's no entry for the given URI, either because it's a
-        // URI that Places ignores (e.g. javascript:) or there were no
-        // visits.  We can just ignore those cases.
-      }
-    }
-  },
-
   itemExists: function HistStore_itemExists(id) {
     if (this._findURLByGUID(id))
       return true;
     return false;
   },
 
   urlExists: function HistStore_urlExists(url) {
     if (typeof(url) == "string")
--- a/services/sync/modules/engines/passwords.js
+++ b/services/sync/modules/engines/passwords.js
@@ -109,23 +109,18 @@ PasswordEngine.prototype = {
 };
 
 function PasswordStore(name) {
   Store.call(this, name);
   this._nsLoginInfo = new Components.Constructor(
     "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
 
   Utils.lazy2(this, "DBConnection", function() {
-    try {
-      return Svc.Login.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.mozIStorageConnection);
-    } catch (ex if (ex.result == Cr.NS_ERROR_NO_INTERFACE)) {
-      // Gecko <2.0 *sadface*
-      return null;
-    }
+    return Svc.Login.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.mozIStorageConnection);
   });
 }
 PasswordStore.prototype = {
   __proto__: Store.prototype,
 
   _nsLoginInfoFromRecord: function PasswordStore__nsLoginInfoRec(record) {
     if (record.formSubmitURL &&
         record.httpRealm) {
--- a/services/sync/modules/engines/prefs.js
+++ b/services/sync/modules/engines/prefs.js
@@ -42,16 +42,17 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const WEAVE_SYNC_PREFS = "services.sync.prefs.sync.";
 
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/ext/Preferences.js");
+Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
 
 const PREFS_GUID = Utils.encodeBase64url(Svc.AppInfo.ID);
 
 function PrefRec(collection, id) {
   CryptoWrapper.call(this, collection, id);
 }
 PrefRec.prototype = {
   __proto__: CryptoWrapper.prototype,
@@ -133,30 +134,19 @@ PrefStore.prototype = {
         // Missing prefs get the null value.
         values[pref] = this._prefs.get(pref, null);
       }
     }
     return values;
   },
 
   _setAllPrefs: function PrefStore__setAllPrefs(values) {
-    // cache 
-    let ltmExists = true;
-    let ltm = {};
-    let enabledBefore = false;
     let enabledPref = "lightweightThemes.isThemeSelected";
-    let prevTheme = "";
-    try {
-      Cu.import("resource://gre/modules/LightweightThemeManager.jsm", ltm);
-      ltm = ltm.LightweightThemeManager;
-      enabledBefore = this._prefs.get(enabledPref, false);
-      prevTheme = ltm.currentTheme;
-    } catch(ex) {
-      ltmExists = false;
-    } // LightweightThemeManager only exists in Firefox 3.6+
+    let enabledBefore = this._prefs.get(enabledPref, false);
+    let prevTheme = LightweightThemeManager.currentTheme;
 
     for (let [pref, value] in Iterator(values)) {
       if (!this._isSynced(pref))
         continue;
 
       // Pref has gone missing, best we can do is reset it.
       if (value == null) {
         this._prefs.reset(pref);
@@ -166,24 +156,22 @@ PrefStore.prototype = {
       try {
         this._prefs.set(pref, value);
       } catch(ex) {
         this._log.trace("Failed to set pref: " + pref + ": " + ex);
       } 
     }
 
     // Notify the lightweight theme manager of all the new values
-    if (ltmExists) {
-      let enabledNow = this._prefs.get(enabledPref, false);
-      if (enabledBefore && !enabledNow)
-        ltm.currentTheme = null;
-      else if (enabledNow && ltm.usedThemes[0] != prevTheme) {
-        ltm.currentTheme = null;
-        ltm.currentTheme = ltm.usedThemes[0];
-      }
+    let enabledNow = this._prefs.get(enabledPref, false);
+    if (enabledBefore && !enabledNow) {
+      LightweightThemeManager.currentTheme = null;
+    } else if (enabledNow && LightweightThemeManager.usedThemes[0] != prevTheme) {
+      LightweightThemeManager.currentTheme = null;
+      LightweightThemeManager.currentTheme = ltm.usedThemes[0];
     }
   },
 
   getAllIDs: function PrefStore_getAllIDs() {
     /* We store all prefs in just one WBO, with just one GUID */
     let allprefs = {};
     allprefs[PREFS_GUID] = true;
     return allprefs;
--- a/services/sync/modules/engines/tabs.js
+++ b/services/sync/modules/engines/tabs.js
@@ -43,16 +43,17 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const TABS_TTL = 604800; // 7 days
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/clients.js");
 Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/ext/Preferences.js");
 
 // It is safer to inspect the private browsing preferences rather than
 // the flags of nsIPrivateBrowsingService.  The user may have turned on
 // "Never remember history" in the same session, or Firefox was started
 // with the -private command line argument.  In both cases, the
 // "autoStarted" flag of nsIPrivateBrowsingService will be wrong.
@@ -101,16 +102,20 @@ TabEngine.prototype = {
   },
 
   _resetClient: function TabEngine__resetClient() {
     SyncEngine.prototype._resetClient.call(this);
     this._store.wipe();
     this._tracker.modified = true;
   },
 
+  removeClientData: function removeClientData() {
+    new Resource(this.engineURL + "/" + Clients.localID).delete();
+  },
+
   /* The intent is not to show tabs in the menu if they're already
    * open locally.  There are a couple ways to interpret this: for
    * instance, we could do it by removing a tab from the list when
    * you open it -- but then if you close it, you can't get back to
    * it.  So the way I'm doing it here is to not show a tab in the menu
    * if you have a tab open to the same URL, even though this means
    * that as soon as you navigate anywhere, the original tab will
    * reappear in the menu.
--- a/services/sync/modules/resource.js
+++ b/services/sync/modules/resource.js
@@ -16,16 +16,17 @@
  * The Initial Developer of the Original Code is Mozilla.
  * Portions created by the Initial Developer are Copyright (C) 2007
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *  Dan Mills <thunder@mozilla.com>
  *  Anant Narayanan <anant@kix.in>
  *  Philipp von Weitershausen <philipp@weitershausen.de>
+ *  Richard Newman <rnewman@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
@@ -65,28 +66,28 @@ NoOpAuthenticator.prototype = {
 
 // Warning: This will drop the high unicode bytes from passwords.
 // Use BasicAuthenticator to send non-ASCII passwords UTF8-encoded.
 function BrokenBasicAuthenticator(identity) {
   this._id = identity;
 }
 BrokenBasicAuthenticator.prototype = {
   onRequest: function BasicAuth_onRequest(headers) {
-    headers['Authorization'] = 'Basic ' +
+    headers['authorization'] = 'Basic ' +
       btoa(this._id.username + ':' + this._id.password);
     return headers;
   }
 };
 
 function BasicAuthenticator(identity) {
   this._id = identity;
 }
 BasicAuthenticator.prototype = {
   onRequest: function onRequest(headers) {
-    headers['Authorization'] = 'Basic ' +
+    headers['authorization'] = 'Basic ' +
       btoa(this._id.username + ':' + this._id.passwordUTF8);
     return headers;
   }
 };
 
 function AuthMgr() {
   this._authenticators = {};
   this.defaultAuthenticator = new NoOpAuthenticator();
@@ -122,31 +123,45 @@ AuthMgr.prototype = {
  *   post(data, callback)
  *   delete(callback)
  * 
  * 'callback' is a function with the following signature:
  * 
  *   function callback(error, result) {...}
  * 
  * 'error' will be null on successful requests. Likewise, result will not be
- * passes (=undefined) when an error occurs. Note that this is independent of
+ * passed (=undefined) when an error occurs. Note that this is independent of
  * the status of the HTTP response.
  */
 function AsyncResource(uri) {
   this._log = Log4Moz.repository.getLogger(this._logName);
   this._log.level =
     Log4Moz.Level[Utils.prefs.getCharPref("log.logger.network.resources")];
   this.uri = uri;
   this._headers = {};
   this._onComplete = Utils.bind2(this, this._onComplete);
 }
 AsyncResource.prototype = {
   _logName: "Net.Resource",
 
-  // Wait 5 minutes before killing a request
+  // The string to use as the base User-Agent in Sync requests.
+  // These strings will look something like
+  // 
+  //   Firefox/4.0 FxSync/1.8.0.20100101.mobile
+  //   
+  // or
+  // 
+  //   Firefox Aurora/5.0a1 FxSync/1.9.0.20110409.desktop
+  //
+  _userAgent:
+    Svc.AppInfo.name + "/" + Svc.AppInfo.version +     // Product.
+    " FxSync/" + WEAVE_VERSION + "." +                 // Sync.
+    Svc.AppInfo.appBuildID + ".",                      // Build.
+
+  // Wait 5 minutes before killing a request.
   ABORT_TIMEOUT: 300000,
 
   // ** {{{ Resource.authenticator }}} **
   //
   // Getter and setter for the authenticator module
   // responsible for this particular resource. The authenticator
   // module may modify the headers to perform authentication
   // while performing a request for the resource, for example.
@@ -166,22 +181,18 @@ AsyncResource.prototype = {
   // Note: Header names should be all lower case, there's no explicit
   // check for duplicates due to case!
   get headers() {
     return this.authenticator.onRequest(this._headers);
   },
   set headers(value) {
     this._headers = value;
   },
-  setHeader: function Res_setHeader() {
-    if (arguments.length % 2)
-      throw "setHeader only accepts arguments in multiples of 2";
-    for (let i = 0; i < arguments.length; i += 2) {
-      this._headers[arguments[i].toLowerCase()] = arguments[i + 1];
-    }
+  setHeader: function Res_setHeader(header, value) {
+    this._headers[header.toLowerCase()] = value;
   },
 
   // ** {{{ Resource.uri }}} **
   //
   // URI representing this resource.
   get uri() {
     return this._uri;
   },
@@ -222,39 +233,45 @@ AsyncResource.prototype = {
 
     // Always validate the cache:
     channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
     channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
 
     // Setup a callback to handle bad HTTPS certificates.
     channel.notificationCallbacks = new BadCertListener();
 
-    // Avoid calling the authorizer more than once
+    // Compose a UA string fragment from the various available identifiers.
+    if (Svc.Prefs.get("sendVersionInfo", true)) {
+      let ua = this._userAgent + Svc.Prefs.get("client.type", "desktop");
+      channel.setRequestHeader("user-agent", ua, false);
+    }
+    
+    // Avoid calling the authorizer more than once.
     let headers = this.headers;
     for (let key in headers) {
-      if (key == 'Authorization')
+      if (key == 'authorization')
         this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
       else
         this._log.trace("HTTP Header " + key + ": " + headers[key]);
       channel.setRequestHeader(key, headers[key], false);
     }
     return channel;
   },
 
   _onProgress: function Res__onProgress(channel) {},
 
   _doRequest: function _doRequest(action, data, callback) {
+    this._log.trace("In _doRequest.");
     this._callback = callback;
     let channel = this._channel = this._createRequest();
 
     if ("undefined" != typeof(data))
       this._data = data;
 
-    // PUT and POST are trreated differently because
-    // they have payload data.
+    // PUT and POST are treated differently because they have payload data.
     if ("PUT" == action || "POST" == action) {
       // Convert non-string bodies into JSON
       if (this._data.constructor.toString() != String)
         this._data = JSON.stringify(this._data);
 
       this._log.debug(action + " Length: " + this._data.length);
       this._log.trace(action + " Body: " + this._data);
 
@@ -273,83 +290,113 @@ AsyncResource.prototype = {
     // is performed asynchronously.
     let listener = new ChannelListener(this._onComplete, this._onProgress,
                                        this._log, this.ABORT_TIMEOUT);
     channel.requestMethod = action;
     channel.asyncOpen(listener, null);
   },
 
   _onComplete: function _onComplete(error, data) {
+    this._log.trace("In _onComplete. Error is " + error + ".");
+
     if (error) {
       this._callback(error);
       return;
     }
 
     this._data = data;
     let channel = this._channel;
     let action = channel.requestMethod;
 
-    // Set some default values in-case there's no response header
-    let headers = {};
+    this._log.trace("Channel: " + channel);
+    this._log.trace("Action: "  + action);
+
+    // Process status and success first. This way a problem with headers
+    // doesn't fail to include accurate status information.
     let status = 0;
     let success = false;
+
     try {
-      // Read out the response headers if available
+      status  = channel.responseStatus;
+      success = channel.requestSucceeded;    // HTTP status.
+
+      this._log.trace("Status: " + status);
+      this._log.trace("Success: " + success);
+
+      // Log the status of the request.
+      let mesg = [action, success ? "success" : "fail", status,
+                  channel.URI.spec].join(" ");
+      this._log.debug("mesg: " + mesg);
+
+      if (mesg.length > 200)
+        mesg = mesg.substr(0, 200) + "…";
+      this._log.debug(mesg);
+
+      // Additionally give the full response body when Trace logging.
+      if (this._log.level <= Log4Moz.Level.Trace)
+        this._log.trace(action + " body: " + data);
+
+    } catch(ex) {
+      // Got a response, but an exception occurred during processing.
+      // This shouldn't occur.
+      this._log.warn("Caught unexpected exception " + Utils.exceptionStr(ex) +
+                     " in _onComplete.");
+      this._log.debug(Utils.stackTrace(ex));
+    }
+
+    // Process headers. They can be empty, or the call can otherwise fail, so
+    // put this in its own try block.
+    let headers = {};
+    try {
+      this._log.trace("Processing response headers.");
+
+      // Read out the response headers if available.
       channel.visitResponseHeaders({
         visitHeader: function visitHeader(header, value) {
           headers[header.toLowerCase()] = value;
         }
       });
-      status = channel.responseStatus;
-      success = channel.requestSucceeded;
-
-      // Log the status of the request
-      let mesg = [action, success ? "success" : "fail", status,
-                  channel.URI.spec].join(" ");
-      if (mesg.length > 200)
-        mesg = mesg.substr(0, 200) + "…";
-      this._log.debug(mesg);
-      // Additionally give the full response body when Trace logging
-      if (this._log.level <= Log4Moz.Level.Trace)
-        this._log.trace(action + " body: " + data);
 
       // This is a server-side safety valve to allow slowing down
       // clients without hurting performance.
       if (headers["x-weave-backoff"])
         Observers.notify("weave:service:backoff:interval",
                          parseInt(headers["x-weave-backoff"], 10));
 
       if (success && headers["x-weave-quota-remaining"])
         Observers.notify("weave:service:quota:remaining",
                          parseInt(headers["x-weave-quota-remaining"], 10));
-    }
-    // Got a response but no header; must be cached (use default values)
-    catch(ex) {
-      this._log.debug(action + " cached: " + status);
+    } catch (ex) {
+      this._log.debug("Caught exception " + Utils.exceptionStr(ex) +
+                      " visiting headers in _onComplete.");
+      this._log.debug(Utils.stackTrace(ex));
     }
 
-    let ret = new String(data);
+    let ret     = new String(data);
+    ret.status  = status;
+    ret.success = success;
     ret.headers = headers;
-    ret.status = status;
-    ret.success = success;
 
-    // Make a lazy getter to convert the json response into an object
+    // Make a lazy getter to convert the json response into an object.
+    // Note that this can cause a parse error to be thrown far away from the
+    // actual fetch, so be warned!
     Utils.lazy2(ret, "obj", function() JSON.parse(ret));
 
-    // Notify if we get a 401 to maybe try again with a new uri
+    // Notify if we get a 401 to maybe try again with a new URI.
+    // TODO: more retry logic.
     if (status == 401) {
-      // Create an object to allow observers to decide if we should try again
+      // Create an object to allow observers to decide if we should try again.
       let subject = {
         newUri: "",
         resource: this,
         response: ret
       }
       Observers.notify("weave:resource:status:401", subject);
 
-      // Do the same type of request but with the new uri
+      // Do the same type of request but with the new URI.
       if (subject.newUri != "") {
         this.uri = subject.newUri;
         this._doRequest(action, this._data, this._callback);
         return;
       }
     }
 
     this._callback(null, ret);
@@ -476,46 +523,56 @@ function ChannelListener(onComplete, onP
   this._onProgress = onProgress;
   this._log = logger;
   this._timeout = timeout;
   this.delayAbort();
 }
 ChannelListener.prototype = {
 
   onStartRequest: function Channel_onStartRequest(channel) {
+    this._log.trace("onStartRequest called for channel " + channel + ".");
     channel.QueryInterface(Ci.nsIHttpChannel);
 
-    // Save the latest server timestamp when possible
+    // Save the latest server timestamp when possible.
     try {
       Resource.serverTime = channel.getResponseHeader("X-Weave-Timestamp") - 0;
     }
     catch(ex) {}
 
-    this._log.trace(channel.requestMethod + " " + channel.URI.spec);
+    this._log.trace("onStartRequest: " + channel.requestMethod + " " +
+                    channel.URI.spec);
     this._data = '';
     this.delayAbort();
   },
 
   onStopRequest: function Channel_onStopRequest(channel, context, status) {
-    // Clear the abort timer now that the channel is done
+    // Clear the abort timer now that the channel is done.
     this.abortTimer.clear();
 
+    let success = Components.isSuccessCode(status);
+    this._log.trace("Channel for " + channel.requestMethod + " " +
+                    channel.URI.spec + ": isSuccessCode(" + status + ")? " +
+                    success);
+
     if (this._data == '')
       this._data = null;
 
     // Throw the failure code and stop execution.  Use Components.Exception()
     // instead of Error() so the exception is QI-able and can be passed across
     // XPCOM borders while preserving the status code.
-    if (!Components.isSuccessCode(status)) {
+    if (!success) {
       let message = Components.Exception("", status).name;
       let error = Components.Exception(message, status);
       this._onComplete(error);
       return;
     }
 
+    this._log.trace("Channel: flags = " + channel.loadFlags +
+                    ", URI = " + channel.URI.spec +
+                    ", HTTP success? " + channel.requestSucceeded);
     this._onComplete(null, this._data);
   },
 
   onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) {
     let siStream = Cc["@mozilla.org/scriptableinputstream;1"].
       createInstance(Ci.nsIScriptableInputStream);
     siStream.init(stream);
     try {
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -149,25 +149,25 @@ WeaveSvc.prototype = {
     this._updateCachedURLs();
   },
 
   get miscAPI() {
     // Append to the serverURL if it's a relative fragment
     let misc = Svc.Prefs.get("miscURL");
     if (misc.indexOf(":") == -1)
       misc = this.serverURL + misc;
-    return misc + "1.0/";
+    return misc + MISC_API_VERSION + "/";
   },
 
   get userAPI() {
     // Append to the serverURL if it's a relative fragment
     let user = Svc.Prefs.get("userURL");
     if (user.indexOf(":") == -1)
       user = this.serverURL + user;
-    return user + "1.0/";
+    return user + USER_API_VERSION + "/";
   },
 
   get pwResetURL() {
     return this.serverURL + "weave-password-reset";
   },
 
   get updatedURL() {
     return WEAVE_CHANNEL == "dev" ? UPDATED_DEV_URL : UPDATED_REL_URL;
@@ -232,17 +232,17 @@ WeaveSvc.prototype = {
     return Utils.catch.call(this, func, lockExceptions);
   },
 
   _updateCachedURLs: function _updateCachedURLs() {
     // Nothing to cache yet if we don't have the building blocks
     if (this.clusterURL == "" || this.username == "")
       return;
 
-    let storageAPI = this.clusterURL + Svc.Prefs.get("storageAPI") + "/";
+    let storageAPI = this.clusterURL + SYNC_API_VERSION + "/";
     this.userBaseURL = storageAPI + this.username + "/";
     this._log.debug("Caching URLs under storage user base: " + this.userBaseURL);
 
     // Generate and cache various URLs under the storage API for this user
     this.infoURL = this.userBaseURL + "info/collections";
     this.storageURL = this.userBaseURL + "storage/";
     this.metaURL = this.storageURL + "meta/global";
     this.cryptoKeysURL = this.storageURL + "crypto/keys";
@@ -1009,16 +1009,26 @@ WeaveSvc.prototype = {
       CollectionKeys.clear();
 
       /* Login and sync. This also generates new keys. */
       this.sync(true);
       return true;
     }))(),
 
   startOver: function() {
+    // Clear client-specific data from the server, including disabled engines.
+    for each (let engine in [Clients].concat(Engines.getAll())) {
+      try {
+        engine.removeClientData();
+      } catch(ex) {
+        this._log.warn("Deleting client data for " + engine.name + " failed:"
+                       + Utils.exceptionStr(ex));
+      }
+    }
+
     // Set a username error so the status message shows "set up..."
     Status.login = LOGIN_FAILED_NO_USERNAME;
     this.logout();
     
     // Reset all engines and clear keys.
     this.resetClient();
     CollectionKeys.clear();
     
@@ -1179,22 +1189,16 @@ WeaveSvc.prototype = {
       return "weak-password";
     default:
       return "generic-server-error";
     }
   },
 
   checkAccount: function checkAccount(account) {
     let username = this._usernameFromAccount(account);
-    return this.checkUsername(username);
-  },
-
-  // Backwards compat with the Firefox UI. Fold into checkAccount() once
-  // bug 595066 has landed.
-  checkUsername: function checkUsername(username) {
     let url = this.userAPI + username;
     let res = new Resource(url);
     res.authenticator = new NoOpAuthenticator();
 
     let data = "";
     try {
       data = res.get();
       if (data.status == 200) {
@@ -1206,28 +1210,19 @@ WeaveSvc.prototype = {
 
     }
     catch(ex) {}
 
     // Convert to the error string, or default to generic on exception.
     return this._errorStr(data);
   },
 
-  createAccount: function createAccount() {
-    // Backwards compat with the Firefox UI. Change to signature to
-    // (email, password, captchaChallenge, captchaResponse) once
-    // bug 595066 has landed.
-    let username, email, password, captchaChallenge, captchaResponse;
-    if (arguments.length == 4) {
-      [email, password, captchaChallenge, captchaResponse] = arguments;
-      username = this._usernameFromAccount(email);
-    } else {
-      [username, password, email, captchaChallenge, captchaResponse] = arguments;
-    }
-
+  createAccount: function createAccount(email, password,
+                                        captchaChallenge, captchaResponse) {
+    let username = this._usernameFromAccount(email);
     let payload = JSON.stringify({
       "password": Utils.encodeUTF8(password),
       "email": email,
       "captcha-challenge": captchaChallenge,
       "captcha-response": captchaResponse
     });
 
     let url = this.userAPI + username;
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -42,25 +42,17 @@ const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/ext/Observers.js");
 Cu.import("resource://services-sync/ext/Preferences.js");
 Cu.import("resource://services-sync/ext/StringBundle.js");
 Cu.import("resource://services-sync/log4moz.js");
-
-let NetUtil;
-try {
-  let ns = {};
-  Cu.import("resource://gre/modules/NetUtil.jsm", ns);
-  NetUtil = ns.NetUtil;
-} catch (ex) {
-  // Firefox 3.5 :(
-}
+Cu.import("resource://gre/modules/NetUtil.jsm");
 
 // Constants for makeSyncCallback, waitForSyncCallback
 const CB_READY = {};
 const CB_COMPLETE = {};
 const CB_FAIL = {};
 
 
 /*
@@ -215,25 +207,16 @@ let Utils = {
       return callback.call(thisObj);
     } finally {
       if (hasTransaction) {
         db.commitTransaction();
       }
     }
   },
 
-  createStatement: function createStatement(db, query) {
-    // Gecko 2.0
-    if (db.createAsyncStatement)
-      return db.createAsyncStatement(query);
-
-    // Gecko <2.0
-    return db.createStatement(query);
-  },
-
   // Prototype for mozIStorageCallback, used in queryAsync below.
   // This allows us to define the handle* functions just once rather
   // than on every queryAsync invocation.
   _storageCallbackPrototype: {
     results: null,
     // These are set by queryAsync
     names: null,
     syncCb: null,
@@ -1064,31 +1047,16 @@ let Utils = {
       that._log.trace("Loading json from disk: " + filePath);
 
     let file = Utils.getProfileFile(filePath);
     if (!file.exists()) {
       callback.call(that);
       return;
     }
 
-    // Gecko < 2.0
-    if (!NetUtil || !NetUtil.newChannel) {
-      let json;
-      try {
-        let [is] = Utils.open(file, "<");
-        json = JSON.parse(Utils.readStream(is));
-        is.close();
-      } catch (ex) {
-        if (that._log)
-          that._log.debug("Failed to load json: " + Utils.exceptionStr(ex));
-      }
-      callback.call(that, json);
-      return;
-    }
-
     let channel = NetUtil.newChannel(file);
     channel.contentType = "application/json";
 
     NetUtil.asyncFetch(channel, function (is, result) {
       if (!Components.isSuccessCode(result)) {
         callback.call(that);
         return;
       }
@@ -1122,31 +1090,20 @@ let Utils = {
     filePath = "weave/" + filePath + ".json";
     if (that._log)
       that._log.trace("Saving json to disk: " + filePath);
 
     let file = Utils.getProfileFile({ autoCreate: true, path: filePath });
     let json = typeof obj == "function" ? obj.call(that) : obj;
     let out = JSON.stringify(json);
 
-    // Firefox 3.5
-    if (!NetUtil) {
-      let [fos] = Utils.open(file, ">");
-      fos.writeString(out);
-      fos.close();
-      if (typeof callback == "function") {
-        callback.call(that);
-      }
-      return;
-    }
-
     let fos = Cc["@mozilla.org/network/safe-file-output-stream;1"]
                 .createInstance(Ci.nsIFileOutputStream);
     fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE,
-             fos.DEFER_OPEN || 0);
+             fos.DEFER_OPEN);
     let is = this._utf8Converter.convertToInputStream(out);
     NetUtil.asyncCopy(is, fos, function (result) {
       if (typeof callback == "function") {
         callback.call(that);        
       }
     });
   },
 
@@ -1185,69 +1142,16 @@ let Utils = {
         timer.clear();
         callback.call(thisObj, timer);
       }
     }, wait, timer.TYPE_ONE_SHOT);
 
     return thisObj[name] = timer;
   },
 
-  // Gecko <2.0
-  open: function open(pathOrFile, mode, perms) {
-    let stream, file;
-
-    if (pathOrFile instanceof Ci.nsIFile) {
-      file = pathOrFile;
-    } else {
-      file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
-      dump("PATH IS" + pathOrFile + "\n");
-      file.initWithPath(pathOrFile);
-    }
-
-    if (!perms)
-      perms = PERMS_FILE;
-
-    switch(mode) {
-    case "<": {
-      if (!file.exists())
-        throw "Cannot open file for reading, file does not exist";
-      let fis = Cc["@mozilla.org/network/file-input-stream;1"].
-        createInstance(Ci.nsIFileInputStream);
-      fis.init(file, MODE_RDONLY, perms, 0);
-      stream = Cc["@mozilla.org/intl/converter-input-stream;1"].
-        createInstance(Ci.nsIConverterInputStream);
-      stream.init(fis, "UTF-8", 4096,
-                  Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
-    } break;
-
-    case ">": {
-      let fos = Cc["@mozilla.org/network/file-output-stream;1"].
-        createInstance(Ci.nsIFileOutputStream);
-      fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, perms, 0);
-      stream = Cc["@mozilla.org/intl/converter-output-stream;1"]
-        .createInstance(Ci.nsIConverterOutputStream);
-      stream.init(fos, "UTF-8", 4096, 0x0000);
-    } break;
-
-    case ">>": {
-      let fos = Cc["@mozilla.org/network/file-output-stream;1"].
-        createInstance(Ci.nsIFileOutputStream);
-      fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_APPEND, perms, 0);
-      stream = Cc["@mozilla.org/intl/converter-output-stream;1"]
-        .createInstance(Ci.nsIConverterOutputStream);
-      stream.init(fos, "UTF-8", 4096, 0x0000);
-    } break;
-
-    default:
-      throw "Illegal mode to open(): " + mode;
-    }
-
-    return [stream, file];
-  },
-
   getIcon: function(iconUri, defaultIcon) {
     try {
       let iconURI = Utils.makeURI(iconUri);
       return Svc.Favicon.getFaviconLinkForIcon(iconURI).spec;
     }
     catch(ex) {}
 
     // Just give the provided default icon or the system's default
@@ -1258,26 +1162,16 @@ let Utils = {
     try {
       return Str.errors.get(error, args || null);
     } catch (e) {}
 
     // basically returns "Unknown Error"
     return Str.errors.get("error.reason.unknown");
   },
 
-  // Gecko <2.0
-  // assumes an nsIConverterInputStream
-  readStream: function Weave_readStream(is) {
-    let ret = "", str = {};
-    while (is.readString(4096, str) != 0) {
-      ret += str.value;
-    }
-    return ret;
-  },
-
   encodeUTF8: function(str) {
     try {
       str = this._utf8Converter.ConvertFromUnicode(str);
       return str + this._utf8Converter.Finish();
     } catch(ex) {
       return null;
     }
   },
@@ -1667,25 +1561,19 @@ this.__defineGetter__("_sessionCID", fun
  ["Version", "@mozilla.org/xpcom/version-comparator;1", "nsIVersionComparator"],
  ["WinMediator", "@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator"],
  ["WinWatcher", "@mozilla.org/embedcomp/window-watcher;1", "nsIWindowWatcher"],
  ["Session", this._sessionCID, "nsISessionStore"],
 ].forEach(function(lazy) Utils.lazySvc(Svc, lazy[0], lazy[1], lazy[2]));
 
 Svc.__defineGetter__("Crypto", function() {
   let cryptoSvc;
-  try {
-    let ns = {};
-    Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
-    cryptoSvc = new ns.WeaveCrypto();
-  } catch (ex) {
-    // Fallback to binary WeaveCrypto
-    cryptoSvc = Cc["@labs.mozilla.com/Weave/Crypto;1"].
-                getService(Ci.IWeaveCrypto);
-  }
+  let ns = {};
+  Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
+  cryptoSvc = new ns.WeaveCrypto();
   delete Svc.Crypto;
   return Svc.Crypto = cryptoSvc;
 });
 
 let Str = {};
 ["errors", "sync"]
   .forEach(function(lazy) Utils.lazy2(Str, lazy, Utils.lazyStrings(lazy)));
 
--- a/services/sync/services-sync.js
+++ b/services/sync/services-sync.js
@@ -1,19 +1,19 @@
 pref("services.sync.serverURL", "https://auth.services.mozilla.com/");
-pref("services.sync.storageAPI", "1.0");
 pref("services.sync.userURL", "user/");
 pref("services.sync.miscURL", "misc/");
 pref("services.sync.termsURL", "https://services.mozilla.com/tos/");
 pref("services.sync.privacyURL", "https://services.mozilla.com/privacy-policy/");
 pref("services.sync.statusURL", "https://services.mozilla.com/status/");
 pref("services.sync.syncKeyHelpURL", "https://services.mozilla.com/help/synckey");
 
 pref("services.sync.lastversion", "firstrun");
 pref("services.sync.autoconnect", true);
+pref("services.sync.sendVersionInfo", true);
 
 pref("services.sync.engine.bookmarks", true);
 pref("services.sync.engine.history", true);
 pref("services.sync.engine.passwords", true);
 pref("services.sync.engine.prefs", true);
 pref("services.sync.engine.tabs", true);
 pref("services.sync.engine.tabs.filteredUrls", "^(about:.*|chrome://weave/.*|wyciwyg:.*|file:.*)$");
 
--- a/services/sync/tests/unit/head_appinfo.js.in
+++ b/services/sync/tests/unit/head_appinfo.js.in
@@ -61,19 +61,28 @@ let XULAppInfoFactory = {
 
 let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
 registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
                           "XULAppInfo", "@mozilla.org/xre/app-info;1",
                           XULAppInfoFactory);
 
 }
 
-// Provide resource://services-sync if it isn't already available
-let weaveService = Cc["@mozilla.org/weave/service;1"].getService();
-weaveService.wrappedJSObject.addResourceAlias();
+// Register resource aliases. Normally done in SyncComponents.manifest.
+function addResourceAlias() {
+  Cu.import("resource://gre/modules/Services.jsm");
+  const resProt = Services.io.getProtocolHandler("resource")
+                          .QueryInterface(Ci.nsIResProtocolHandler);
+  let uri;
+  uri = Services.io.newURI("resource:///modules/services-sync/", null, null);
+  resProt.setSubstitution("services-sync", uri);
+  uri = Services.io.newURI("resource:///modules/services-crypto/", null, null);
+  resProt.setSubstitution("services-crypto", uri);
+}
+addResourceAlias();
 
 
 // Some tests hang on OSX debug builds. See bug 604565.
 let DISABLE_TESTS_BUG_604565 = false;
 #ifdef XP_MACOSX
 #ifdef MOZ_DEBUG_SYMBOLS
 DISABLE_TESTS_BUG_604565 = true;
 #endif
--- a/services/sync/tests/unit/head_http_server.js
+++ b/services/sync/tests/unit/head_http_server.js
@@ -262,17 +262,17 @@ ServerCollection.prototype = {
   }
 
 };
 
 /*
  * Test setup helpers.
  */
 function sync_httpd_setup(handlers) {
-  handlers["/1.0/foo/storage/meta/global"]
+  handlers["/1.1/foo/storage/meta/global"]
       = (new ServerWBO('global', {})).handler();
   return httpd_setup(handlers);
 }
 
 /*
  * Track collection modified times. Return closures.
  */
 function track_collections_helper() {
--- a/services/sync/tests/unit/test_bookmark_engine.js
+++ b/services/sync/tests/unit/test_bookmark_engine.js
@@ -1,21 +1,16 @@
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/log4moz.js");
 Cu.import("resource://services-sync/util.js");
 
 Cu.import("resource://services-sync/service.js");
-try {
-  Cu.import("resource://gre/modules/PlacesUtils.jsm");
-}
-catch(ex) {
-  Cu.import("resource://gre/modules/utils.js");
-}
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
 Engines.register(BookmarksEngine);
 
 function makeEngine() {
   return new BookmarksEngine();
 }
 var syncTesting = new SyncTestingInfrastructure(makeEngine);
 
@@ -66,18 +61,18 @@ function test_processIncoming_error_orde
 
   let collection = new ServerCollection();
   let engine = new BookmarksEngine();
   let store = engine._store;
   let global = new ServerWBO('global',
                              {engines: {bookmarks: {version: engine.version,
                                                     syncID: engine.syncID}}});
   let server = httpd_setup({
-    "/1.0/foo/storage/meta/global": global.handler(),
-    "/1.0/foo/storage/bookmarks": collection.handler()
+    "/1.1/foo/storage/meta/global": global.handler(),
+    "/1.1/foo/storage/bookmarks": collection.handler()
   });
 
   try {
 
     let folder1_id = Svc.Bookmark.createFolder(
       Svc.Bookmark.toolbarFolder, "Folder 1", 0);
     let folder1_guid = store.GUIDForId(folder1_id);
 
@@ -148,18 +143,18 @@ function test_restorePromptsReupload() {
   let collection = new ServerCollection({}, true);
   
   let engine = new BookmarksEngine();
   let store = engine._store;
   let global = new ServerWBO('global',
                              {engines: {bookmarks: {version: engine.version,
                                                     syncID: engine.syncID}}});
   let server = httpd_setup({
-    "/1.0/foo/storage/meta/global": global.handler(),
-    "/1.0/foo/storage/bookmarks": collection.handler()
+    "/1.1/foo/storage/meta/global": global.handler(),
+    "/1.1/foo/storage/bookmarks": collection.handler()
   });
 
   Svc.Obs.notify("weave:engine:start-tracking");   // We skip usual startup...
 
   try {
 
     let folder1_id = Svc.Bookmark.createFolder(
       Svc.Bookmark.toolbarFolder, "Folder 1", 0);
@@ -323,18 +318,18 @@ function test_mismatched_types() {
 
   let engine = new BookmarksEngine();
   let store = engine._store;
   let global = new ServerWBO('global',
                              {engines: {bookmarks: {version: engine.version,
                                                     syncID: engine.syncID}}});
   _("GUID: " + store.GUIDForId(6, true));
   let server = httpd_setup({
-    "/1.0/foo/storage/meta/global": global.handler(),
-    "/1.0/foo/storage/bookmarks": collection.handler()
+    "/1.1/foo/storage/meta/global": global.handler(),
+    "/1.1/foo/storage/bookmarks": collection.handler()
   });
 
   try {
     let bms = store._bms;
     let oldR = new FakeRecord(BookmarkFolder, oldRecord);
     let newR = new FakeRecord(Livemark, newRecord);
     oldR._parent = Svc.Bookmark.toolbarFolder;
     newR._parent = Svc.Bookmark.toolbarFolder;
--- a/services/sync/tests/unit/test_bookmark_livemarks.js
+++ b/services/sync/tests/unit/test_bookmark_livemarks.js
@@ -1,21 +1,16 @@
 Cu.import("resource://services-sync/log4moz.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/util.js");
 
 Cu.import("resource://services-sync/service.js");
-try {
-  Cu.import("resource://gre/modules/PlacesUtils.jsm");
-}
-catch(ex) {
-  Cu.import("resource://gre/modules/utils.js");
-}
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
 const DESCRIPTION_ANNO = "bookmarkProperties/description";
 
 Engines.register(BookmarksEngine);
 let engine = Engines.get("bookmarks");
 let store = engine._store;
 
 // Record borrowed from Bug 631361.
--- a/services/sync/tests/unit/test_bookmark_smart_bookmarks.js
+++ b/services/sync/tests/unit/test_bookmark_smart_bookmarks.js
@@ -1,21 +1,16 @@
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/log4moz.js");
 Cu.import("resource://services-sync/util.js");
 
 Cu.import("resource://services-sync/service.js");
-try {
-  Cu.import("resource://gre/modules/PlacesUtils.jsm");
-}
-catch(ex) {
-  Cu.import("resource://gre/modules/utils.js");
-}
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
 const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
 var IOService = Cc["@mozilla.org/network/io-service;1"]
                 .getService(Ci.nsIIOService);
 ("http://www.mozilla.com", null, null);
 
 
 Engines.register(BookmarksEngine);
@@ -110,18 +105,18 @@ function test_annotation_uploaded() {
   Service.serverURL = "http://localhost:8080/";
   Service.clusterURL = "http://localhost:8080/";
 
   let collection = new ServerCollection({}, true);
   let global = new ServerWBO('global',
                              {engines: {bookmarks: {version: engine.version,
                                                     syncID: engine.syncID}}});
   let server = httpd_setup({
-    "/1.0/foo/storage/meta/global": global.handler(),
-    "/1.0/foo/storage/bookmarks": collection.handler()
+    "/1.1/foo/storage/meta/global": global.handler(),
+    "/1.1/foo/storage/bookmarks": collection.handler()
   });
 
   try {
     engine.sync();
     let wbos = [id for ([id, wbo] in Iterator(collection.wbos))
                    if (["menu", "toolbar", "mobile"].indexOf(id) == -1)];
     do_check_eq(wbos.length, 1);
 
@@ -199,18 +194,18 @@ function test_smart_bookmarks_duped() {
   Service.serverURL = "http://localhost:8080/";
   Service.clusterURL = "http://localhost:8080/";
 
   let collection = new ServerCollection({}, true);
   let global = new ServerWBO('global',
                              {engines: {bookmarks: {version: engine.version,
                                                     syncID: engine.syncID}}});
   let server = httpd_setup({
-    "/1.0/foo/storage/meta/global": global.handler(),
-    "/1.0/foo/storage/bookmarks": collection.handler()
+    "/1.1/foo/storage/meta/global": global.handler(),
+    "/1.1/foo/storage/bookmarks": collection.handler()
   });
   
   try {
     engine._syncStartup();
     
     _("Verify that lazyMap uses the anno, discovering a dupe regardless of URI.");
     do_check_eq(mostVisitedGUID, engine._lazyMap(record));
     
--- a/services/sync/tests/unit/test_bookmark_store.js
+++ b/services/sync/tests/unit/test_bookmark_store.js
@@ -347,80 +347,21 @@ function test_reparentOrphans() {
                 folder1_guid);
 
   } finally {
     _("Clean up.");
     store.wipe();
   }
 }
 
-// Copying a bookmark in the UI also copies its annotations, including the GUID
-// annotation in 3.x.
-function test_copying_avoid_duplicate_guids() {
-  if (store._haveGUIDColumn) {
-    _("No GUID annotation handling functionality; returning without testing.");
-    return;
-  }
-    
-  try {
-    _("Ensure the record isn't present yet.");
-    let ids = Svc.Bookmark.getBookmarkIdsForURI(fxuri, {});
-    do_check_eq(ids.length, 0);
-
-    _("Let's create a new record.");
-    let fxrecord = new Bookmark("bookmarks", "get-firefox1");
-    fxrecord.bmkUri        = fxuri.spec;
-    fxrecord.description   = "Firefox is awesome.";
-    fxrecord.title         = "Get Firefox!";
-    fxrecord.tags          = ["firefox", "awesome", "browser"];
-    fxrecord.keyword       = "awesome";
-    fxrecord.loadInSidebar = false;
-    fxrecord.parentName    = "Bookmarks Toolbar";
-    fxrecord.parentid      = "toolbar";
-    store.applyIncoming(fxrecord);
-
-    _("Verify it has been created correctly.");
-    let id = store.idForGUID(fxrecord.id);
-    do_check_eq(store.GUIDForId(id), fxrecord.id);
-    do_check_true(Svc.Bookmark.getBookmarkURI(id).equals(fxuri));
-    do_check_eq(Svc.Annos.getItemAnnotation(id, "bookmarkProperties/description"),
-                fxrecord.description);
-    
-    _("Copy the record as happens in the UI: with the same GUID.");
-    let parentID = Svc.Bookmark.getFolderIdForItem(id);
-    let copy = Svc.Bookmark.insertBookmark(parentID, fxuri, -1, fxrecord.title);
-    Svc.Bookmark.setItemLastModified(copy, Svc.Bookmark.getItemLastModified(id));
-    Svc.Bookmark.setItemDateAdded(copy, Svc.Bookmark.getItemDateAdded(id));
-    store._setGUID(copy, fxrecord.id);
-    
-    do_check_eq(store.GUIDForId(copy), store.GUIDForId(id));
-    
-    _("Calling idForGUID fixes things.");
-    _("GUID before: " + store.GUIDForId(copy));
-    do_check_eq(store.idForGUID(fxrecord.id), id);           // Oldest wins.
-    _("GUID now: " + store.GUIDForId(copy));
-    do_check_neq(store.GUIDForId(copy), store.GUIDForId(id));
-    
-    _("Verify that the anno itself has changed.");
-    do_check_neq(Svc.Annos.getItemAnnotation(copy, "sync/guid"), fxrecord.id);
-    do_check_eq(Svc.Annos.getItemAnnotation(copy, "sync/guid"),
-                store.GUIDForId(copy));
-    
-  } finally {
-    _("Clean up.");
-    store.wipe();
-  }
-}
-
 function run_test() {
   initTestLogging('Trace');
   test_bookmark_create();
   test_bookmark_createRecord();
   test_bookmark_update();
   test_folder_create();
   test_folder_createRecord();
   test_deleted();
   test_move_folder();
   test_move_order();
   test_orphan();
   test_reparentOrphans();
-  test_copying_avoid_duplicate_guids();
 }
--- a/services/sync/tests/unit/test_bookmark_tracker.js
+++ b/services/sync/tests/unit/test_bookmark_tracker.js
@@ -1,20 +1,12 @@
+Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/util.js");
-try {
-  Cu.import("resource://gre/modules/PlacesUtils.jsm");
-}
-catch(ex) {
-  Cu.import("resource://gre/modules/utils.js");
-}
-
-// Grab a backstage pass so we can fetch the anno constant. Evil but useful.
-let bsp = Cu.import("resource://services-sync/engines/bookmarks.js");
-const SYNC_GUID_ANNO = bsp.GUID_ANNO;      // Not the same as Places' GUID_ANNO!
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
 Engines.register(BookmarksEngine);
 let engine = Engines.get("bookmarks");
 let store  = engine._store;
 store.wipe();
 
 function test_copying_places() {
 
@@ -243,77 +235,55 @@ function test_tracking() {
   } finally {
     _("Clean up.");
     store.wipe();
     tracker.clearChangedIDs();
     Svc.Obs.notify("weave:engine:stop-tracking");
   }
 }
 
-function test_guid_stripping() {
-  // Gecko <2.0
-  if (store._haveGUIDColumn) {
-    _("We have a GUID column; not testing anno GUID fixing.");
-    return;
-  }
+function test_onItemChanged() {
+  // Anno that's in ANNOS_TO_TRACK.
+  const GENERATOR_ANNO = "microsummary/generatorURI";
 
   _("Verify we've got an empty tracker to work with.");
   let tracker = engine._tracker;
-  let folder = Svc.Bookmark.createFolder(Svc.Bookmark.bookmarksMenuFolder,
-                                         "Test Folder",
-                                         Svc.Bookmark.DEFAULT_INDEX);
-  function createBmk() {
-    return Svc.Bookmark.insertBookmark(folder,
-                                       Utils.makeURI("http://getfirefox.com"),
-                                       Svc.Bookmark.DEFAULT_INDEX,
-                                       "Get Firefox!");
-  }
-
   do_check_eq([id for (id in tracker.changedIDs)].length, 0);
 
   try {
-    _("Directly testing GUID stripping.");
+    Svc.Obs.notify("weave:engine:stop-tracking");
+    let folder = Svc.Bookmark.createFolder(Svc.Bookmark.bookmarksMenuFolder,
+                                           "Parent",
+                                           Svc.Bookmark.DEFAULT_INDEX);
+    _("Track changes to annos.");
+    let b = Svc.Bookmark.insertBookmark(folder,
+                                        Utils.makeURI("http://getfirefox.com"),
+                                        Svc.Bookmark.DEFAULT_INDEX,
+                                        "Get Firefox!");
+    let bGUID = engine._store.GUIDForId(b);
+    _("New item is " + b);
+    _("GUID: " + bGUID);
 
     Svc.Obs.notify("weave:engine:start-tracking");
-    tracker.ignoreAll = false;
-    let suspect = createBmk();
-    let victim = createBmk();
-
-    _("Suspect: " + suspect + ", victim: " + victim);
+    Svc.Annos.setItemAnnotation(b, GENERATOR_ANNO, "http://foo.bar/", 0,
+                                Svc.Annos.EXPIRE_NEVER);
+    do_check_true(tracker.changedIDs[bGUID] > 0);
 
-    let suspectGUID = store.GUIDForId(suspect);
-    let victimGUID  = store.GUIDForId(victim);
-    _("Set the GUID on one entry to be the same as another.");
-    do_check_neq(suspectGUID, victimGUID);
-    Svc.Annos.setItemAnnotation(suspect, SYNC_GUID_ANNO, store.GUIDForId(victim),
-                                0, Svc.Annos.EXPIRE_NEVER);
-
-    _("Tracker changed it to something else.");
-    let newGUID = store.GUIDForId(suspect);
-    do_check_neq(newGUID, victimGUID);
-    do_check_neq(newGUID, suspectGUID);
-
-    _("Victim GUID remains unchanged.");
-    do_check_eq(victimGUID, store.GUIDForId(victim));
-
-    _("New GUID is in the tracker.");
-    _(JSON.stringify(tracker.changedIDs));
-    do_check_neq(tracker.changedIDs[newGUID], null);
   } finally {
     _("Clean up.");
     store.wipe();
     tracker.clearChangedIDs();
     Svc.Obs.notify("weave:engine:stop-tracking");
   }
 }
 
 function run_test() {
   initTestLogging("Trace");
 
   Log4Moz.repository.getLogger("Engine.Bookmarks").level = Log4Moz.Level.Trace;
   Log4Moz.repository.getLogger("Store.Bookmarks").level = Log4Moz.Level.Trace;
   Log4Moz.repository.getLogger("Tracker.Bookmarks").level = Log4Moz.Level.Trace;
 
+  test_onItemChanged();
   test_copying_places();
   test_tracking();
-  test_guid_stripping();
 }
 
--- a/services/sync/tests/unit/test_clients_engine.js
+++ b/services/sync/tests/unit/test_clients_engine.js
@@ -1,12 +1,13 @@
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/util.js");
+Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/clients.js");
 Cu.import("resource://services-sync/service.js");
 
 const MORE_THAN_CLIENTS_TTL_REFRESH = 691200; // 8 days
 const LESS_THAN_CLIENTS_TTL_REFRESH = 86400; // 1 day
 
 function test_bad_hmac() {
   _("Ensure that Clients engine deletes corrupt records.");
@@ -28,20 +29,20 @@ function test_bad_hmac() {
       if (request.method == "DELETE")
         deleted = true;
 
       return u(request, response);
     };
   }
 
   let handlers = {
-    "/1.0/foo/info/collections": collectionsHelper.handler,
-    "/1.0/foo/storage/meta/global": upd("meta", global.handler()),
-    "/1.0/foo/storage/crypto/keys": upd("crypto", keysWBO.handler()),
-    "/1.0/foo/storage/clients": trackDeletedHandler("crypto", clientsColl.handler())
+    "/1.1/foo/info/collections": collectionsHelper.handler,
+    "/1.1/foo/storage/meta/global": upd("meta", global.handler()),
+    "/1.1/foo/storage/crypto/keys": upd("crypto", keysWBO.handler()),
+    "/1.1/foo/storage/clients": trackDeletedHandler("crypto", clientsColl.handler())
   };
 
   let server = httpd_setup(handlers);
   do_test_pending();
 
   try {
     let passphrase = "abcdeabcdeabcdeabcdeabcdea";
     Service.serverURL = "http://localhost:8080/";
@@ -71,16 +72,71 @@ function test_bad_hmac() {
     do_check_true(!deleted);
     Clients.sync();
 
     _("Old record was deleted, new one uploaded.");
     do_check_true(deleted);
     do_check_eq(1, clientsColl.count());
     _("Records now: " + clientsColl.get({}));
 
+    _("Now change our keys but don't upload them. " +
+      "That means we get an HMAC error but redownload keys.");
+    Service.lastHMACEvent = 0;
+    Clients.localID = Utils.makeGUID();
+    Clients.resetClient();
+    CollectionKeys.generateNewKeys();
+    deleted = false;
+    do_check_eq(1, clientsColl.count());
+    Clients.sync();
+
+    _("Old record was not deleted, new one uploaded.");
+    do_check_false(deleted);
+    do_check_eq(2, clientsColl.count());
+    _("Records now: " + clientsColl.get({}));
+
+    _("Now try the scenario where our keys are wrong *and* there's a bad record.");
+    // Clean up and start fresh.
+    clientsColl.wbos = {};
+    Service.lastHMACEvent = 0;
+    Clients.localID = Utils.makeGUID();
+    Clients.resetClient();
+    deleted = false;
+    do_check_eq(0, clientsColl.count());
+
+    // Create and upload keys.
+    CollectionKeys.generateNewKeys();
+    serverKeys = CollectionKeys.asWBO("crypto", "keys");
+    serverKeys.encrypt(Weave.Service.syncKeyBundle);
+    do_check_true(serverKeys.upload(Weave.Service.cryptoKeysURL).success);
+
+    // Sync once to upload a record.
+    Clients.sync();
+    do_check_eq(1, clientsColl.count());
+
+    // Generate and upload new keys, so the old client record is wrong.
+    CollectionKeys.generateNewKeys();
+    serverKeys = CollectionKeys.asWBO("crypto", "keys");
+    serverKeys.encrypt(Weave.Service.syncKeyBundle);
+    do_check_true(serverKeys.upload(Weave.Service.cryptoKeysURL).success);
+
+    // Create a new client record and new keys. Now our keys are wrong, as well
+    // as the object on the server. We'll download the new keys and also delete
+    // the bad client record.
+    Clients.localID = Utils.makeGUID();
+    Clients.resetClient();
+    CollectionKeys.generateNewKeys();
+    let oldKey = CollectionKeys.keyForCollection();
+
+    do_check_false(deleted);
+    Clients.sync();
+    do_check_true(deleted);
+    do_check_eq(1, clientsColl.count());
+    let newKey = CollectionKeys.keyForCollection();
+    do_check_false(oldKey.equals(newKey));
+
   } finally {
     server.stop(do_test_finished);
     Svc.Prefs.resetBranch("");
     Records.clearCache();
   }
 }
 
 function test_properties() {
@@ -96,29 +152,31 @@ function test_properties() {
     Svc.Prefs.resetBranch("");
   }
 }
 
 function test_sync() {
   _("Ensure that Clients engine uploads a new client record once a week.");
   Svc.Prefs.set("clusterURL", "http://localhost:8080/");
   Svc.Prefs.set("username", "foo");
-  new SyncTestingInfrastructure();
 
   CollectionKeys.generateNewKeys();
 
   let global = new ServerWBO('global',
                              {engines: {clients: {version: Clients.version,
                                                   syncID: Clients.syncID}}});
   let coll = new ServerCollection();
   let clientwbo = coll.wbos[Clients.localID] = new ServerWBO(Clients.localID);
   let server = httpd_setup({
-      "/1.0/foo/storage/meta/global": global.handler(),
-      "/1.0/foo/storage/clients": coll.handler()
+      "/1.1/foo/storage/meta/global": global.handler(),
+      "/1.1/foo/storage/clients": coll.handler()
   });
+  server.registerPathHandler(
+    "/1.1/foo/storage/clients/" + Clients.localID, clientwbo.handler());
+
   do_test_pending();
 
   try {
 
     _("First sync, client record is uploaded");
     do_check_eq(clientwbo.payload, undefined);
     do_check_eq(Clients.lastRecordUpload, 0);
     Clients.sync();
@@ -128,31 +186,34 @@ function test_sync() {
     _("Let's time travel more than a week back, new record should've been uploaded.");
     Clients.lastRecordUpload -= MORE_THAN_CLIENTS_TTL_REFRESH;
     let lastweek = Clients.lastRecordUpload;
     clientwbo.payload = undefined;
     Clients.sync();
     do_check_true(!!clientwbo.payload);
     do_check_true(Clients.lastRecordUpload > lastweek);
 
+    _("Remove client record.");
+    Clients.removeClientData();
+    do_check_eq(clientwbo.payload, undefined);
+
     _("Time travel one day back, no record uploaded.");
     Clients.lastRecordUpload -= LESS_THAN_CLIENTS_TTL_REFRESH;
     let yesterday = Clients.lastRecordUpload;
-    clientwbo.payload = undefined;
     Clients.sync();
     do_check_eq(clientwbo.payload, undefined);
     do_check_eq(Clients.lastRecordUpload, yesterday);
 
   } finally {
     server.stop(do_test_finished);
     Svc.Prefs.resetBranch("");
     Records.clearCache();
   }
 }
 
 
 function run_test() {
   initTestLogging("Trace");
   Log4Moz.repository.getLogger("Engine.Clients").level = Log4Moz.Level.Trace;
-  test_bad_hmac();      // Needs to run first: doesn't use fake service!
+  test_bad_hmac();
   test_properties();
   test_sync();
 }
--- a/services/sync/tests/unit/test_clients_escape.js
+++ b/services/sync/tests/unit/test_clients_escape.js
@@ -3,17 +3,17 @@ Cu.import("resource://services-sync/engi
 Cu.import("resource://services-sync/identity.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/identity.js");
 
 function run_test() {
   _("Set up test fixtures.");
   ID.set('WeaveID', new Identity('Some Identity', 'foo'));
   Svc.Prefs.set("clusterURL", "http://fakebase/");
-  let baseUri = "http://fakebase/1.0/foo/storage/";
+  let baseUri = "http://fakebase/1.1/foo/storage/";
   let pubUri = baseUri + "keys/pubkey";
   let privUri = baseUri + "keys/privkey";
 
   let keyBundle = ID.set("WeaveCryptoID",
                          new SyncKeyBundle(null, "john@example.com", "abcdeabcdeabcdeabcdeabcdea"));
 
   try {
     _("Test that serializing client records results in uploadable ascii");
--- a/services/sync/tests/unit/test_corrupt_keys.js
+++ b/services/sync/tests/unit/test_corrupt_keys.js
@@ -31,32 +31,32 @@ function test_locally_changed_keys() {
     };
   }
   
   Weave.Service.handleHMACEvent = counting(Weave.Service.handleHMACEvent);
   
   do_test_pending();
   let server = httpd_setup({
     // Special.
-    "/1.0/johndoe/storage/meta/global": upd("meta", meta_global.handler()),
-    "/1.0/johndoe/info/collections": collectionsHelper.handler,
-    "/1.0/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
+    "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()),
+    "/1.1/johndoe/info/collections": collectionsHelper.handler,
+    "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
       
     // Track modified times.
-    "/1.0/johndoe/storage/clients": upd("clients", clients.handler()),
-    "/1.0/johndoe/storage/clients/foobar": upd("clients", new ServerWBO("clients").handler()),
-    "/1.0/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()),
+    "/1.1/johndoe/storage/clients": upd("clients", clients.handler()),
+    "/1.1/johndoe/storage/clients/foobar": upd("clients", new ServerWBO("clients").handler()),
+    "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()),
     
     // Just so we don't get 404s in the logs.
-    "/1.0/johndoe/storage/bookmarks": new ServerCollection().handler(),
-    "/1.0/johndoe/storage/forms": new ServerCollection().handler(),
-    "/1.0/johndoe/storage/passwords": new ServerCollection().handler(),
-    "/1.0/johndoe/storage/prefs": new ServerCollection().handler(),
+    "/1.1/johndoe/storage/bookmarks": new ServerCollection().handler(),
+    "/1.1/johndoe/storage/forms": new ServerCollection().handler(),
+    "/1.1/johndoe/storage/passwords": new ServerCollection().handler(),
+    "/1.1/johndoe/storage/prefs": new ServerCollection().handler(),
     
-    "/1.0/johndoe/storage/history": upd("history", history.handler()),
+    "/1.1/johndoe/storage/history": upd("history", history.handler()),
   });
 
   try {
     
     Svc.Prefs.set("registerEngines", "Tab");
     _("Set up some tabs.");
     let myTabs = 
       {windows: [{tabs: [{index: 1,
@@ -139,17 +139,17 @@ function test_locally_changed_keys() {
       w.encrypt();
       
       let wbo = new ServerWBO(id, {ciphertext: w.ciphertext,
                                    IV: w.IV,
                                    hmac: w.hmac});
       wbo.modified = modified;
       history.wbos[id] = wbo;
       server.registerPathHandler(
-        "/1.0/johndoe/storage/history/record-no--" + i,
+        "/1.1/johndoe/storage/history/record-no--" + i,
         upd("history", wbo.handler()));
     }
     
     collections.history = Date.now()/1000;
     let old_key_time = collections.crypto;
     _("Old key time: " + old_key_time);
     
     // Check that we can decrypt one.
@@ -199,17 +199,17 @@ function test_locally_changed_keys() {
       w.hmac = w.hmac.toUpperCase();
       
       let wbo = new ServerWBO(id, {ciphertext: w.ciphertext,
                                    IV: w.IV,
                                    hmac: w.hmac});
       wbo.modified = modified;
       history.wbos[id] = wbo;
       server.registerPathHandler(
-        "/1.0/johndoe/storage/history/record-no--" + i,
+        "/1.1/johndoe/storage/history/record-no--" + i,
         upd("history", wbo.handler()));
     }
     collections.history = Date.now()/1000;
     
     _("Server key time hasn't changed.");
     do_check_eq(collections.crypto, old_key_time);
     
     _("Resetting HMAC error timer.");
--- a/services/sync/tests/unit/test_history_engine.js
+++ b/services/sync/tests/unit/test_history_engine.js
@@ -46,17 +46,17 @@ function test_processIncoming_mobile_his
         deleted: false});
     
     let wbo = new ServerWBO(id, payload);
     wbo.modified = modified;
     collection.wbos[id] = wbo;
   }
   
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/history": collection.handler()
+      "/1.1/foo/storage/history": collection.handler()
   });
   do_test_pending();
 
   let engine = new HistoryEngine("history");
   let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
   meta_global.payload.engines = {history: {version: engine.version,
                                            syncID: engine.syncID}};
 
--- a/services/sync/tests/unit/test_history_store.js
+++ b/services/sync/tests/unit/test_history_store.js
@@ -155,21 +155,20 @@ function run_test() {
     let queryres = queryHistoryVisits(resuri);
     do_check_eq(queryres.length, 1);
     do_check_eq(queryres[0].time, TIMESTAMP3);
     next();
 
   }, function (next) {
 
     _("Make sure we handle invalid URLs in places databases gracefully.");
-    let table = store._haveTempTables ? "moz_places_temp" : "moz_places";
-    let query = "INSERT INTO " + table + " "
+    let query = "INSERT INTO moz_places "
       + "(url, title, rev_host, visit_count, last_visit_date) "
       + "VALUES ('invalid-uri', 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")";
-    let stmt = Utils.createStatement(Svc.History.DBConnection, query);
+    let stmt = Svc.History.DBConnection.createAsyncStatement(query);
     let result = Utils.queryAsync(stmt);    
     do_check_eq([id for (id in store.getAllIDs())].length, 4);
 
     _("Make sure we report records with invalid URIs.");
     let invalid_uri_guid = Utils.makeGUID();
     let failed = store.applyIncomingBatch([{
       id: invalid_uri_guid,
       histUri: ":::::::::::::::",
--- a/services/sync/tests/unit/test_places_guid_downgrade.js
+++ b/services/sync/tests/unit/test_places_guid_downgrade.js
@@ -90,31 +90,29 @@ function test_history_guids() {
     Svc.Bookmark.toolbarFolder, uri, Svc.Bookmark.DEFAULT_INDEX, "Mozilla");
 
   let fxguid = store.GUIDForUri(fxuri, true);
   let tbguid = store.GUIDForUri(tburi, true);
   dump("fxguid: " + fxguid + "\n");
   dump("tbguid: " + tbguid + "\n");
 
   _("History: Verify GUIDs are added to the guid column.");
-  let stmt = Utils.createStatement(
-    Svc.History.DBConnection,
+  let stmt = Svc.History.DBConnection.createAsyncStatement(
     "SELECT id FROM moz_places WHERE guid = :guid");
 
   stmt.params.guid = fxguid;
   let result = Utils.queryAsync(stmt, ["id"]);
   do_check_eq(result.length, 1);
 
   stmt.params.guid = tbguid;
   result = Utils.queryAsync(stmt, ["id"]);
   do_check_eq(result.length, 1);
 
   _("History: Verify GUIDs weren't added to annotations.");
-  stmt = Utils.createStatement(
-    Svc.History.DBConnection,
+  stmt = Svc.History.DBConnection.createAsyncStatement(
     "SELECT a.content AS guid FROM moz_annos a WHERE guid = :guid");
 
   stmt.params.guid = fxguid;
   result = Utils.queryAsync(stmt, ["guid"]);
   do_check_eq(result.length, 0);
 
   stmt.params.guid = tbguid;
   result = Utils.queryAsync(stmt, ["guid"]);
@@ -131,33 +129,31 @@ function test_bookmark_guids() {
   let tbid = Svc.Bookmark.insertBookmark(
     Svc.Bookmark.toolbarFolder, tburi, Svc.Bookmark.DEFAULT_INDEX,
     "Get Thunderbird!");
 
   let fxguid = store.GUIDForId(fxid);
   let tbguid = store.GUIDForId(tbid);
 
   _("Bookmarks: Verify GUIDs are added to the guid column.");
-  let stmt = Utils.createStatement(
-    Svc.History.DBConnection,
+  let stmt = Svc.History.DBConnection.createAsyncStatement(
     "SELECT id FROM moz_bookmarks WHERE guid = :guid");
 
   stmt.params.guid = fxguid;
   let result = Utils.queryAsync(stmt, ["id"]);
   do_check_eq(result.length, 1);
   do_check_eq(result[0].id, fxid);
 
   stmt.params.guid = tbguid;
   result = Utils.queryAsync(stmt, ["id"]);
   do_check_eq(result.length, 1);
   do_check_eq(result[0].id, tbid);
 
   _("Bookmarks: Verify GUIDs weren't added to annotations.");
-  stmt = Utils.createStatement(
-    Svc.History.DBConnection,
+  stmt = Svc.History.DBConnection.createAsyncStatement(
     "SELECT a.content AS guid FROM moz_items_annos a WHERE guid = :guid");
 
   stmt.params.guid = fxguid;
   result = Utils.queryAsync(stmt, ["guid"]);
   do_check_eq(result.length, 0);
 
   stmt.params.guid = tbguid;
   result = Utils.queryAsync(stmt, ["guid"]);
--- a/services/sync/tests/unit/test_resource.js
+++ b/services/sync/tests/unit/test_resource.js
@@ -216,16 +216,33 @@ function run_test() {
   } catch (ex) {
     didThrow = true;
   }
   do_check_true(didThrow);
 
   let did401 = false;
   Observers.add("weave:resource:status:401", function() did401 = true);
 
+  _("Test that the BasicAuthenticator doesn't screw up header case.");
+  let res1 = new Resource("http://localhost:8080/foo");
+  res1.setHeader("Authorization", "Basic foobar");
+  res1.authenticator = new NoOpAuthenticator();
+  do_check_eq(res1._headers["authorization"], "Basic foobar");
+  do_check_eq(res1.headers["authorization"], "Basic foobar");
+  let id = new Identity("secret", "guest", "guest");
+  res1.authenticator = new BasicAuthenticator(id);
+
+  // In other words... it correctly overwrites our downcased version
+  // when accessed through .headers.
+  do_check_eq(res1._headers["authorization"], "Basic foobar");
+  do_check_eq(res1.headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q=");
+  do_check_eq(res1._headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q=");
+  do_check_true(!res1._headers["Authorization"]);
+  do_check_true(!res1.headers["Authorization"]);
+
   _("GET a password protected resource (test that it'll fail w/o pass, no throw)");
   let res2 = new Resource("http://localhost:8080/protected");
   content = res2.get();
   do_check_true(did401);
   do_check_eq(content, "This path exists and is protected - failed");
   do_check_eq(content.status, 401);
   do_check_false(content.success);
 
@@ -341,18 +358,18 @@ function run_test() {
 
   _("setHeader(): setting simple header");
   res9.setHeader('X-What-Is-Weave', 'awesome');
   do_check_eq(res9.headers['x-what-is-weave'], 'awesome');
   content = res9.get();
   do_check_eq(content, JSON.stringify({"x-what-is-weave": "awesome"}));
 
   _("setHeader(): setting multiple headers, overwriting existing header");
-  res9.setHeader('X-WHAT-is-Weave', 'more awesomer',
-                 'X-Another-Header', 'hello world');
+  res9.setHeader('X-WHAT-is-Weave', 'more awesomer');
+  res9.setHeader('X-Another-Header', 'hello world');
   do_check_eq(res9.headers['x-what-is-weave'], 'more awesomer');
   do_check_eq(res9.headers['x-another-header'], 'hello world');
   content = res9.get();
   do_check_eq(content, JSON.stringify({"x-another-header": "hello world",
                                        "x-what-is-weave": "more awesomer"}));
 
   _("Setting headers object");
   res9.headers = {};
--- a/services/sync/tests/unit/test_resource_async.js
+++ b/services/sync/tests/unit/test_resource_async.js
@@ -203,16 +203,36 @@ function run_test() {
       do_check_true(didThrow);
 
       do_test_finished();
       next();
     }));
 
   }, function (next) {
 
+    _("Test that the BasicAuthenticator doesn't screw up header case.");
+    let res1 = new AsyncResource("http://localhost:8080/foo");
+    res1.setHeader("Authorization", "Basic foobar");
+    res1.authenticator = new NoOpAuthenticator();
+    do_check_eq(res1._headers["authorization"], "Basic foobar");
+    do_check_eq(res1.headers["authorization"], "Basic foobar");
+    let id = new Identity("secret", "guest", "guest");
+    res1.authenticator = new BasicAuthenticator(id);
+
+    // In other words... it correctly overwrites our downcased version
+    // when accessed through .headers.
+    do_check_eq(res1._headers["authorization"], "Basic foobar");
+    do_check_eq(res1.headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q=");
+    do_check_eq(res1._headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q=");
+    do_check_true(!res1._headers["Authorization"]);
+    do_check_true(!res1.headers["Authorization"]);
+    next();
+
+  }, function (next) {
+
     _("GET a password protected resource (test that it'll fail w/o pass, no throw)");
     let res2 = new AsyncResource("http://localhost:8080/protected");
     do_test_pending();
     res2.get(ensureThrows(function (error, content) {
       do_check_eq(error, null);
       do_check_true(did401);
       do_check_eq(content, "This path exists and is protected - failed");
       do_check_eq(content.status, 401);
@@ -453,18 +473,18 @@ function run_test() {
       do_test_finished();
       next();
     }));
 
   }, function (next) {
 
     _("setHeader(): setting multiple headers, overwriting existing header");
     do_test_pending();
-    res_headers.setHeader('X-WHAT-is-Weave', 'more awesomer',
-                   'X-Another-Header', 'hello world');
+    res_headers.setHeader('X-WHAT-is-Weave', 'more awesomer');
+    res_headers.setHeader('X-Another-Header', 'hello world');
     do_check_eq(res_headers.headers['x-what-is-weave'], 'more awesomer');
     do_check_eq(res_headers.headers['x-another-header'], 'hello world');
     res_headers.get(ensureThrows(function (error, content) {
       do_check_eq(error, null);
       do_check_eq(content, JSON.stringify({"x-another-header": "hello world",
                                            "x-what-is-weave": "more awesomer"}));
       do_test_finished();
       next();
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_resource_ua.js
@@ -0,0 +1,91 @@
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/resource.js");
+Cu.import("resource://services-sync/service.js");
+
+function test_resource_user_agent() {
+  let meta_global = new ServerWBO('global');
+
+  // Tracking info/collections.
+  let collectionsHelper = track_collections_helper();
+  let collections = collectionsHelper.collections;
+
+  let ua;
+  function uaHandler(f) {
+    return function(request, response) {
+      ua = request.getHeader("User-Agent");
+      return f(request, response);
+    };
+  }
+
+  do_test_pending();
+  let server = httpd_setup({
+    "/1.1/johndoe/info/collections": uaHandler(collectionsHelper.handler),
+    "/1.1/johndoe/storage/meta/global": uaHandler(meta_global.handler()),
+  });
+
+  Weave.Service.serverURL  = "http://localhost:8080/";
+  Weave.Service.clusterURL = "http://localhost:8080/";
+  Weave.Service.username   = "johndoe";
+  Weave.Service.password   = "ilovejane";
+
+  let expectedUA = Svc.AppInfo.name + "/" + Svc.AppInfo.version +
+                   " FxSync/" + WEAVE_VERSION + "." + Svc.AppInfo.appBuildID;
+
+  function test_fetchInfo(next) {
+    _("Testing _fetchInfo.");
+    Weave.Service._fetchInfo();
+    _("User-Agent: " + ua);
+    do_check_eq(ua, expectedUA + ".desktop");
+    ua = "";
+    next();
+  }
+
+  function test_desktop_post(next) {
+    _("Testing direct Resource POST.");
+    let r = new AsyncResource("http://localhost:8080/1.1/johndoe/storage/meta/global");
+    r.post("foo=bar", function (error, content) {
+      _("User-Agent: " + ua);
+      do_check_eq(ua, expectedUA + ".desktop");
+      ua = "";
+      next();
+    });
+  }
+
+  function test_desktop_get(next) {
+    _("Testing async.");
+    Svc.Prefs.set("client.type", "desktop");
+    let r = new AsyncResource("http://localhost:8080/1.1/johndoe/storage/meta/global");
+    r.get(function(error, content) {
+      _("User-Agent: " + ua);
+      do_check_eq(ua, expectedUA + ".desktop");
+      ua = "";
+      next();
+    });
+  }
+
+  function test_mobile_get(next) {
+    _("Testing mobile.");
+    Svc.Prefs.set("client.type", "mobile");
+    let r = new AsyncResource("http://localhost:8080/1.1/johndoe/storage/meta/global");
+    r.get(function (error, content) {
+      _("User-Agent: " + ua);
+      do_check_eq(ua, expectedUA + ".mobile");
+      ua = "";
+      next();
+    });
+  }
+
+  Utils.asyncChain(
+    test_fetchInfo,
+    test_desktop_post,
+    test_desktop_get,
+    test_mobile_get,
+    function (next) {
+      server.stop(next);
+    },
+    do_test_finished)();
+}
+
+function run_test() {
+  test_resource_user_agent();
+}
--- a/services/sync/tests/unit/test_service_attributes.js
+++ b/services/sync/tests/unit/test_service_attributes.js
@@ -76,23 +76,23 @@ function test_urls() {
     do_check_eq(Service.userBaseURL, undefined);
     do_check_eq(Service.storageURL, undefined);
     do_check_eq(Service.metaURL, undefined);
 
     Service.serverURL = "http://weave.server/";
     Service.clusterURL = "http://weave.cluster/";
     do_check_eq(Svc.Prefs.get("clusterURL"), "http://weave.cluster/");
 
-    do_check_eq(Service.userBaseURL, "http://weave.cluster/1.0/johndoe/");
+    do_check_eq(Service.userBaseURL, "http://weave.cluster/1.1/johndoe/");
     do_check_eq(Service.infoURL,
-                "http://weave.cluster/1.0/johndoe/info/collections");
+                "http://weave.cluster/1.1/johndoe/info/collections");
     do_check_eq(Service.storageURL,
-                "http://weave.cluster/1.0/johndoe/storage/");
+                "http://weave.cluster/1.1/johndoe/storage/");
     do_check_eq(Service.metaURL,
-                "http://weave.cluster/1.0/johndoe/storage/meta/global");
+                "http://weave.cluster/1.1/johndoe/storage/meta/global");
 
     _("The 'miscURL' and 'userURL' attributes can be relative to 'serverURL' or absolute.");
     Svc.Prefs.set("miscURL", "relative/misc/");
     Svc.Prefs.set("userURL", "relative/user/");
     do_check_eq(Service.miscAPI,
                 "http://weave.server/relative/misc/1.0/");
     do_check_eq(Service.userAPI,
                 "http://weave.server/relative/user/1.0/");
--- a/services/sync/tests/unit/test_service_checkAccount.js
+++ b/services/sync/tests/unit/test_service_checkAccount.js
@@ -10,29 +10,27 @@ function run_test() {
     "/user/1.0/7wohs32cngzuqt466q3ge7indszva4of": httpd_handler(200, "OK", "0"),
     // jane@doe.com
     "/user/1.0/vuuf3eqgloxpxmzph27f5a6ve7gzlrms": httpd_handler(200, "OK", "1")
   });
   try {
     Service.serverURL = "http://localhost:8080/";
 
     _("A 404 will be recorded as 'generic-server-error'");
-    do_check_eq(Service.checkUsername("jimdoe"), "generic-server-error");
+    do_check_eq(Service.checkAccount("jimdoe"), "generic-server-error");
 
     _("Account that's available.");
     do_check_eq(Service.checkAccount("john@doe.com"), "available");
 
     _("Account that's not available.");
     do_check_eq(Service.checkAccount("jane@doe.com"), "notAvailable");
 
-    // Backwards compat with the Firefox UI. Remove once bug 595066 has landed.
+    _("Username fallback: Account that's not available.");
+    do_check_eq(Service.checkAccount("johndoe"), "notAvailable");
 
-    _("Account that's not available.");
-    do_check_eq(Service.checkUsername("johndoe"), "notAvailable");
-
-    _("Account that's available.");
-    do_check_eq(Service.checkUsername("janedoe"), "available");
+    _("Username fallback: Account that's available.");
+    do_check_eq(Service.checkAccount("janedoe"), "available");
 
   } finally {
     Svc.Prefs.resetBranch("");
     server.stop(do_test_finished);
   }
 }
--- a/services/sync/tests/unit/test_service_createAccount.js
+++ b/services/sync/tests/unit/test_service_createAccount.js
@@ -18,20 +18,17 @@ function run_test() {
 
   do_test_pending();
   let server = httpd_setup({
     // john@doe.com
     "/user/1.0/7wohs32cngzuqt466q3ge7indszva4of": send(200, "OK", "0"),
     // jane@doe.com
     "/user/1.0/vuuf3eqgloxpxmzph27f5a6ve7gzlrms": send(400, "Bad Request", "2"),
     // jim@doe.com
-    "/user/1.0/vz6fhecgw5t3sgx3a4cektoiokyczkqd": send(500, "Server Error", "Server Error"),
-    "/user/1.0/johndoe": send(200, "OK", "0"),
-    "/user/1.0/janedoe": send(400, "Bad Request", "2"),
-    "/user/1.0/jimdoe": send(500, "Server Error", "Server Error")
+    "/user/1.0/vz6fhecgw5t3sgx3a4cektoiokyczkqd": send(500, "Server Error", "Server Error")
   });
   try {
     Service.serverURL = "http://localhost:8080/";
 
     _("Create an account.");
     let res = Service.createAccount("john@doe.com", "mysecretpw",
                                     "challenge", "response");
     do_check_eq(res, null);
@@ -60,31 +57,13 @@ function run_test() {
     do_check_eq(res, "generic-server-error");
 
     _("Admin secret preference is passed as HTTP header token.");
     Svc.Prefs.set("admin-secret", "my-server-secret");
     res = Service.createAccount("john@doe.com", "mysecretpw",
                                 "challenge", "response");
     do_check_eq(secretHeader, "my-server-secret");
 
-
-    // Backwards compat with the Firefox UI. Remove once bug 595066 has landed.
-
-    _("Create an old-style account.");
-    res = Service.createAccount("johndoe", "mysecretpw", "john@doe.com",
-                                "challenge", "response");
-    do_check_eq(res, null);
-
-    _("Invalid captcha or other user-friendly error.");
-    res = Service.createAccount("janedoe", "anothersecretpw", "jane@doe.com",
-                                "challenge", "response");
-    do_check_eq(res, "invalid-captcha");
-
-    _("Generic server error.");
-    res = Service.createAccount("jimdoe", "preciousss", "jim@doe.com",
-                                "challenge", "response");
-    do_check_eq(res, "generic-server-error");
-
   } finally {
     Svc.Prefs.resetBranch("");
     server.stop(do_test_finished);
   }
 }
--- a/services/sync/tests/unit/test_service_detect_upgrade.js
+++ b/services/sync/tests/unit/test_service_detect_upgrade.js
@@ -19,30 +19,30 @@ function v4_upgrade(next) {
   // Tracking info/collections.
   let collectionsHelper = track_collections_helper();
   let upd = collectionsHelper.with_updated_collection;
   let collections = collectionsHelper.collections;
 
   let keysWBO = new ServerWBO("keys");
   let server = httpd_setup({
     // Special.
-    "/1.0/johndoe/info/collections": collectionsHelper.handler,
-    "/1.0/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
-    "/1.0/johndoe/storage/meta/global": upd("meta", meta_global.handler()),
+    "/1.1/johndoe/info/collections": collectionsHelper.handler,
+    "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
+    "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()),
       
     // Track modified times.
-    "/1.0/johndoe/storage/clients": upd("clients", clients.handler()),
-    "/1.0/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()),
+    "/1.1/johndoe/storage/clients": upd("clients", clients.handler()),
+    "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()),
     
     // Just so we don't get 404s in the logs.
-    "/1.0/johndoe/storage/bookmarks": new ServerCollection().handler(),
-    "/1.0/johndoe/storage/forms": new ServerCollection().handler(),
-    "/1.0/johndoe/storage/history": new ServerCollection().handler(),
-    "/1.0/johndoe/storage/passwords": new ServerCollection().handler(),
-    "/1.0/johndoe/storage/prefs": new ServerCollection().handler()
+    "/1.1/johndoe/storage/bookmarks": new ServerCollection().handler(),
+    "/1.1/johndoe/storage/forms": new ServerCollection().handler(),
+    "/1.1/johndoe/storage/history": new ServerCollection().handler(),
+    "/1.1/johndoe/storage/passwords": new ServerCollection().handler(),
+    "/1.1/johndoe/storage/prefs": new ServerCollection().handler()
   });
 
   try {
     
     _("Set up some tabs.");
     let myTabs = 
       {windows: [{tabs: [{index: 1,
                           entries: [{
@@ -198,24 +198,24 @@ function v5_upgrade(next) {
 
   let keysWBO = new ServerWBO("keys");
   let bulkWBO = new ServerWBO("bulk");
   let clients = new ServerCollection();
   let meta_global = new ServerWBO('global');
   
   let server = httpd_setup({
     // Special.
-    "/1.0/johndoe/storage/meta/global": upd("meta", meta_global.handler()),
-    "/1.0/johndoe/info/collections": collectionsHelper.handler,
-    "/1.0/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
-    "/1.0/johndoe/storage/crypto/bulk": upd("crypto", bulkWBO.handler()),
+    "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()),
+    "/1.1/johndoe/info/collections": collectionsHelper.handler,
+    "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
+    "/1.1/johndoe/storage/crypto/bulk": upd("crypto", bulkWBO.handler()),
       
     // Track modified times.
-    "/1.0/johndoe/storage/clients": upd("clients", clients.handler()),
-    "/1.0/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()),
+    "/1.1/johndoe/storage/clients": upd("clients", clients.handler()),
+    "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()),
   });
 
   try {
     
     _("Set up some tabs.");
     let myTabs = 
       {windows: [{tabs: [{index: 1,
                           entries: [{
--- a/services/sync/tests/unit/test_service_login.js
+++ b/services/sync/tests/unit/test_service_login.js
@@ -33,26 +33,26 @@ function run_test() {
     do_check_eq(Service._ignorableErrorCount, 0);
     Svc.IO.offline = false;
   } finally {
     Svc.Prefs.resetBranch("");
   }
  
   do_test_pending();
   let server = httpd_setup({
-    "/1.0/johndoe/info/collections": login_handler,
-    "/1.0/janedoe/info/collections": login_handler,
+    "/1.1/johndoe/info/collections": login_handler,
+    "/1.1/janedoe/info/collections": login_handler,
       
     // We need these handlers because we test login, and login
     // is where keys are generated or fetched.
     // TODO: have Jane fetch her keys, not generate them...
-    "/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(),
-    "/1.0/johndoe/storage/meta/global": new ServerWBO().handler(),
-    "/1.0/janedoe/storage/crypto/keys": new ServerWBO().handler(),
-    "/1.0/janedoe/storage/meta/global": new ServerWBO().handler()
+    "/1.1/johndoe/storage/crypto/keys": new ServerWBO().handler(),
+    "/1.1/johndoe/storage/meta/global": new ServerWBO().handler(),
+    "/1.1/janedoe/storage/crypto/keys": new ServerWBO().handler(),
+    "/1.1/janedoe/storage/meta/global": new ServerWBO().handler()
   });
 
   try {
     Service.serverURL = "http://localhost:8080/";
     Service.clusterURL = "http://localhost:8080/";
     Svc.Prefs.set("autoconnect", false);
 
     _("Force the initial state.");
--- a/services/sync/tests/unit/test_service_passwordUTF8.js
+++ b/services/sync/tests/unit/test_service_passwordUTF8.js
@@ -50,19 +50,19 @@ function change_password(request, respon
   response.bodyOutputStream.write(body, body.length);
 }
 
 function run_test() {
   initTestLogging("Trace");
   
   do_test_pending();
   let server = httpd_setup({
-    "/1.0/johndoe/info/collections": info_collections,
-    "/1.0/johndoe/storage/meta/global": new ServerWBO().handler(),
-    "/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(),
+    "/1.1/johndoe/info/collections": info_collections,
+    "/1.1/johndoe/storage/meta/global": new ServerWBO().handler(),
+    "/1.1/johndoe/storage/crypto/keys": new ServerWBO().handler(),
     "/user/1.0/johndoe/password": change_password
   });
 
   Service.username = "johndoe";
   Service.password = JAPANESE;
   Service.passphrase = "cantentsveryrelevantabbbb";
   Service.serverURL = "http://localhost:8080/";
 
--- a/services/sync/tests/unit/test_service_quota.js
+++ b/services/sync/tests/unit/test_service_quota.js
@@ -4,20 +4,20 @@ Cu.import("resource://services-sync/util
 function run_test() {
   let collection_usage = {steam:  65.11328,
                           petrol: 82.488281,
                           diesel: 2.25488281};
   let quota = [2169.65136, 8192];
 
   do_test_pending();
   let server = httpd_setup({
-    "/1.0/johndoe/info/collection_usage": httpd_handler(200, "OK", JSON.stringify(collection_usage)),
-    "/1.0/johndoe/info/quota":            httpd_handler(200, "OK", JSON.stringify(quota)),
-    "/1.0/janedoe/info/collection_usage": httpd_handler(200, "OK", "gargabe"),
-    "/1.0/janedoe/info/quota":            httpd_handler(200, "OK", "more garbage")
+    "/1.1/johndoe/info/collection_usage": httpd_handler(200, "OK", JSON.stringify(collection_usage)),
+    "/1.1/johndoe/info/quota":            httpd_handler(200, "OK", JSON.stringify(quota)),
+    "/1.1/janedoe/info/collection_usage": httpd_handler(200, "OK", "gargabe"),
+    "/1.1/janedoe/info/quota":            httpd_handler(200, "OK", "more garbage")
   });
 
   try {
     Weave.Service.clusterURL = "http://localhost:8080/";
     Weave.Service.username = "johndoe";
 
     _("Test getCollectionUsage().");
     let res = Weave.Service.getCollectionUsage();
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_service_startOver.js
@@ -0,0 +1,30 @@
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/service.js");
+Cu.import("resource://services-sync/util.js");
+
+function BlaEngine() {
+  SyncEngine.call(this, "Bla");
+}
+BlaEngine.prototype = {
+  __proto__: SyncEngine.prototype,
+
+  removed: false,
+  removeClientData: function() {
+    this.removed = true;
+  }
+
+};
+Engines.register(BlaEngine);
+
+
+function test_removeClientData() {
+  let engine = Engines.get("bla");
+  do_check_false(engine.removed);
+  Service.startOver();
+  do_check_true(engine.removed);
+}
+
+
+function run_test() {
+  test_removeClientData();
+}
--- a/services/sync/tests/unit/test_service_sync_401.js
+++ b/services/sync/tests/unit/test_service_sync_401.js
@@ -15,19 +15,19 @@ function login_handler(request, response
 }
 
 function run_test() {
   let logger = Log4Moz.repository.rootLogger;
   Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
 
   do_test_pending();
   let server = httpd_setup({
-    "/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(),
-    "/1.0/johndoe/storage/meta/global": new ServerWBO().handler(),
-    "/1.0/johndoe/info/collections": login_handler
+    "/1.1/johndoe/storage/crypto/keys": new ServerWBO().handler(),
+    "/1.1/johndoe/storage/meta/global": new ServerWBO().handler(),
+    "/1.1/johndoe/info/collections": login_handler
   });
 
   const GLOBAL_SCORE = 42;
 
   try {
     _("Set up test fixtures.");
     Weave.Service.serverURL = "http://localhost:8080/";
     Weave.Service.clusterURL = "http://localhost:8080/";
--- a/services/sync/tests/unit/test_service_sync_checkServerError.js
+++ b/services/sync/tests/unit/test_service_sync_checkServerError.js
@@ -33,37 +33,37 @@ function sync_httpd_setup() {
   // up-to-date.
   let clientsColl = new ServerCollection({}, true);
   let keysWBO     = new ServerWBO("keys");
   let globalWBO   = new ServerWBO("global", {storageVersion: STORAGE_VERSION,
                                              syncID: Utils.makeGUID(),
                                              engines: engines});
 
   let handlers = {
-    "/1.0/johndoe/info/collections":    collectionsHelper.handler,
-    "/1.0/johndoe/storage/meta/global": upd("meta",    globalWBO.handler()),
-    "/1.0/johndoe/storage/clients":     upd("clients", clientsColl.handler()),
-    "/1.0/johndoe/storage/crypto/keys": upd("crypto",  keysWBO.handler())
+    "/1.1/johndoe/info/collections":    collectionsHelper.handler,
+    "/1.1/johndoe/storage/meta/global": upd("meta",    globalWBO.handler()),
+    "/1.1/johndoe/storage/clients":     upd("clients", clientsColl.handler()),
+    "/1.1/johndoe/storage/crypto/keys": upd("crypto",  keysWBO.handler())
   }
   return httpd_setup(handlers);
 }
 
 function setUp() {
   Service.username = "johndoe";
   Service.password = "ilovejane";
   Service.passphrase = "aabcdeabcdeabcdeabcdeabcde";
   Service.clusterURL = "http://localhost:8080/";
   new FakeCryptoService();
 }
 
 function generateAndUploadKeys() {
   CollectionKeys.generateNewKeys();
   let serverKeys = CollectionKeys.asWBO("crypto", "keys");
   serverKeys.encrypt(Weave.Service.syncKeyBundle);
-  return serverKeys.upload("http://localhost:8080/1.0/johndoe/storage/crypto/keys").success;
+  return serverKeys.upload("http://localhost:8080/1.1/johndoe/storage/crypto/keys").success;
 }
 
 function test_backoff500(next) {
   _("Test: HTTP 500 sets backoff status.");
   let server = sync_httpd_setup();
   setUp();
 
   let engine = Engines.get("catapult");
--- a/services/sync/tests/unit/test_service_sync_remoteSetup.js
+++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js
@@ -28,22 +28,22 @@ function run_test() {
     };
   }
 
   let keysWBO = new ServerWBO("keys");
   let cryptoColl = new ServerCollection({keys: keysWBO});
   let metaColl = new ServerCollection({global: meta_global});
   do_test_pending();
   let server = httpd_setup({
-    "/1.0/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
-    "/1.0/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()),
-    "/1.0/johndoe/storage/clients": upd("clients", clients.handler()),
-    "/1.0/johndoe/storage/meta/global": upd("meta", wasCalledHandler(meta_global)),
-    "/1.0/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)),
-    "/1.0/johndoe/info/collections": collectionsHelper.handler
+    "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()),
+    "/1.1/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()),
+    "/1.1/johndoe/storage/clients": upd("clients", clients.handler()),
+    "/1.1/johndoe/storage/meta/global": upd("meta", wasCalledHandler(meta_global)),
+    "/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)),
+    "/1.1/johndoe/info/collections": collectionsHelper.handler
   });
 
   try {
     _("Log in.");
     Weave.Service.serverURL = "http://localhost:8080/";
     Weave.Service.clusterURL = "http://localhost:8080/";
     
     _("Checking Status.sync with no credentials.");
--- a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
+++ b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js
@@ -43,24 +43,24 @@ StirlingEngine.prototype = {
 Engines.register(StirlingEngine);
 
 // Tracking info/collections.
 let collectionsHelper = track_collections_helper();
 let upd = collectionsHelper.with_updated_collection;
 
 function sync_httpd_setup(handlers) {
     
-  handlers["/1.0/johndoe/info/collections"] = collectionsHelper.handler;
+  handlers["/1.1/johndoe/info/collections"] = collectionsHelper.handler;
   
   let cr = new ServerWBO("keys");
-  handlers["/1.0/johndoe/storage/crypto/keys"] =
+  handlers["/1.1/johndoe/storage/crypto/keys"] =
     upd("crypto", cr.handler());
   
   let cl = new ServerCollection();
-  handlers["/1.0/johndoe/storage/clients"] =
+  handlers["/1.1/johndoe/storage/clients"] =
     upd("clients", cl.handler());
   
   return httpd_setup(handlers);
 }
 
 function setUp() {
   Service.username = "johndoe";
   Service.password = "ilovejane";
@@ -70,18 +70,18 @@ function setUp() {
 }
 
 const PAYLOAD = 42;
 
 function test_newAccount() {
   _("Test: New account does not disable locally enabled engines.");
   let engine = Engines.get("steam");
   let server = sync_httpd_setup({
-    "/1.0/johndoe/storage/meta/global": new ServerWBO("global", {}).handler(),
-    "/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
+    "/1.1/johndoe/storage/meta/global": new ServerWBO("global", {}).handler(),
+    "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
   });
   do_test_pending();
   setUp();
 
   try {
     _("Engine is enabled from the beginning.");
     Service._ignorePrefObserver = true;
     engine.enabled = true;
@@ -102,18 +102,18 @@ function test_newAccount() {
 function test_enabledLocally() {
   _("Test: Engine is disabled on remote clients and enabled locally");
   Service.syncID = "abcdefghij";
   let engine = Engines.get("steam");
   let metaWBO = new ServerWBO("global", {syncID: Service.syncID,
                                          storageVersion: STORAGE_VERSION,
                                          engines: {}});
   let server = sync_httpd_setup({
-    "/1.0/johndoe/storage/meta/global": metaWBO.handler(),
-    "/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
+    "/1.1/johndoe/storage/meta/global": metaWBO.handler(),
+    "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
   });
   do_test_pending();
   setUp();
 
   try {
     _("Enable engine locally.");
     engine.enabled = true;
 
@@ -139,18 +139,18 @@ function test_disabledLocally() {
   let metaWBO = new ServerWBO("global", {
     syncID: Service.syncID,
     storageVersion: STORAGE_VERSION,
     engines: {steam: {syncID: engine.syncID,
                       version: engine.version}}
   });
   let steamCollection = new ServerWBO("steam", PAYLOAD);
   let server = sync_httpd_setup({
-    "/1.0/johndoe/storage/meta/global": metaWBO.handler(),
-    "/1.0/johndoe/storage/steam": steamCollection.handler()
+    "/1.1/johndoe/storage/meta/global": metaWBO.handler(),
+    "/1.1/johndoe/storage/steam": steamCollection.handler()
   });
   do_test_pending();
   setUp();
 
   try {
     _("Disable engine locally.");
     Service._ignorePrefObserver = true;
     engine.enabled = true;
@@ -181,18 +181,18 @@ function test_enabledRemotely() {
   let engine = Engines.get("steam");
   let metaWBO = new ServerWBO("global", {
     syncID: Service.syncID,
     storageVersion: STORAGE_VERSION,
     engines: {steam: {syncID: engine.syncID,
                       version: engine.version}}
   });
   let server = sync_httpd_setup({
-    "/1.0/johndoe/storage/meta/global": metaWBO.handler(),
-    "/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
+    "/1.1/johndoe/storage/meta/global": metaWBO.handler(),
+    "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
   });
   do_test_pending();
   setUp();
 
   try {
     _("Engine is disabled.");
     do_check_false(engine.enabled);
 
@@ -214,20 +214,20 @@ function test_enabledRemotely() {
 function test_disabledRemotelyTwoClients() {
   _("Test: Engine is enabled locally and disabled on a remote client... with two clients.");
   Service.syncID = "abcdefghij";
   let engine = Engines.get("steam");
   let metaWBO = new ServerWBO("global", {syncID: Service.syncID,
                                          storageVersion: STORAGE_VERSION,
                                          engines: {}});
   let server = sync_httpd_setup({
-    "/1.0/johndoe/storage/meta/global":
+    "/1.1/johndoe/storage/meta/global":
     upd("meta", metaWBO.handler()),
       
-    "/1.0/johndoe/storage/steam":
+    "/1.1/johndoe/storage/steam":
     upd("steam", new ServerWBO("steam", {}).handler())
   });
   do_test_pending();
   setUp();
 
   try {
     _("Enable engine locally.");
     Service._ignorePrefObserver = true;
@@ -260,18 +260,18 @@ function test_disabledRemotelyTwoClients
 function test_disabledRemotely() {
   _("Test: Engine is enabled locally and disabled on a remote client");
   Service.syncID = "abcdefghij";
   let engine = Engines.get("steam");
   let metaWBO = new ServerWBO("global", {syncID: Service.syncID,
                                          storageVersion: STORAGE_VERSION,
                                          engines: {}});
   let server = sync_httpd_setup({
-    "/1.0/johndoe/storage/meta/global": metaWBO.handler(),
-    "/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
+    "/1.1/johndoe/storage/meta/global": metaWBO.handler(),
+    "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler()
   });
   do_test_pending();
   setUp();
 
   try {
     _("Enable engine locally.");
     Service._ignorePrefObserver = true;
     engine.enabled = true;
@@ -294,19 +294,19 @@ function test_dependentEnginesEnabledLoc
   _("Test: Engine is disabled on remote clients and enabled locally");
   Service.syncID = "abcdefghij";
   let steamEngine = Engines.get("steam");
   let stirlingEngine = Engines.get("stirling");
   let metaWBO = new ServerWBO("global", {syncID: Service.syncID,
                                          storageVersion: STORAGE_VERSION,
                                          engines: {}});
   let server = sync_httpd_setup({
-    "/1.0/johndoe/storage/meta/global": metaWBO.handler(),
-    "/1.0/johndoe/storage/steam": new ServerWBO("steam", {}).handler(),
-    "/1.0/johndoe/storage/stirling": new ServerWBO("stirling", {}).handler()
+    "/1.1/johndoe/storage/meta/global": metaWBO.handler(),
+    "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler(),
+    "/1.1/johndoe/storage/stirling": new ServerWBO("stirling", {}).handler()
   });
   do_test_pending();
   setUp();
 
   try {
     _("Enable engine locally. Doing it on one is enough.");
     steamEngine.enabled = true;
 
@@ -339,19 +339,19 @@ function test_dependentEnginesDisabledLo
                       version: steamEngine.version},
               stirling: {syncID: stirlingEngine.syncID,
                          version: stirlingEngine.version}}
   });
 
   let steamCollection = new ServerWBO("steam", PAYLOAD);
   let stirlingCollection = new ServerWBO("stirling", PAYLOAD);
   let server = sync_httpd_setup({
-    "/1.0/johndoe/storage/meta/global":     metaWBO.handler(),
-    "/1.0/johndoe/storage/steam":           steamCollection.handler(),
-    "/1.0/johndoe/storage/stirling":        stirlingCollection.handler()
+    "/1.1/johndoe/storage/meta/global":     metaWBO.handler(),
+    "/1.1/johndoe/storage/steam":           steamCollection.handler(),
+    "/1.1/johndoe/storage/stirling":        stirlingCollection.handler()
   });
   do_test_pending();
   setUp();
 
   try {
     _("Disable engines locally. Doing it on one is enough.");
     Service._ignorePrefObserver = true;
     steamEngine.enabled = true;
--- a/services/sync/tests/unit/test_service_verifyLogin.js
+++ b/services/sync/tests/unit/test_service_verifyLogin.js
@@ -29,20 +29,20 @@ function run_test() {
   let logger = Log4Moz.repository.rootLogger;
   Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
 
   // This test expects a clean slate -- no saved passphrase.
   Weave.Svc.Login.removeAllLogins();
   
   do_test_pending();
   let server = httpd_setup({
-    "/api/1.0/johndoe/info/collections": login_handler,
-    "/api/1.0/janedoe/info/collections": service_unavailable,
-    "/api/1.0/johndoe/storage/meta/global": new ServerWBO().handler(),
-    "/api/1.0/johndoe/storage/crypto/keys": new ServerWBO().handler(),
+    "/api/1.1/johndoe/info/collections": login_handler,
+    "/api/1.1/janedoe/info/collections": service_unavailable,
+    "/api/1.1/johndoe/storage/meta/global": new ServerWBO().handler(),
+    "/api/1.1/johndoe/storage/crypto/keys": new ServerWBO().handler(),
     "/user/1.0/johndoe/node/weave": httpd_handler(200, "OK", "http://localhost:8080/api/")
   });
 
   try {
     Service.serverURL = "http://localhost:8080/";
 
     _("Force the initial state.");
     Status.service = STATUS_OK;
--- a/services/sync/tests/unit/test_service_wipeServer.js
+++ b/services/sync/tests/unit/test_service_wipeServer.js
@@ -39,19 +39,19 @@ function setUpTestFixtures() {
 
 function test_withCollectionList_fail() {
   _("Service.wipeServer() deletes collections given as argument.");
 
   let steam_coll = new FakeCollection();
   let diesel_coll = new FakeCollection();
 
   let server = httpd_setup({
-    "/1.0/johndoe/storage/steam": steam_coll.handler(),
-    "/1.0/johndoe/storage/petrol": serviceUnavailable,
-    "/1.0/johndoe/storage/diesel": diesel_coll.handler()
+    "/1.1/johndoe/storage/steam": steam_coll.handler(),
+    "/1.1/johndoe/storage/petrol": serviceUnavailable,
+    "/1.1/johndoe/storage/diesel": diesel_coll.handler()
   });
   do_test_pending();
 
   try {
     setUpTestFixtures();
 
     _("Confirm initial environment.");
     do_check_false(steam_coll.deleted);
@@ -94,20 +94,20 @@ function test_wipeServer_leaves_collecti
     if (!keys_coll.deleted)
       collections.keys = timestamp;
     let body = JSON.stringify(collections);
     response.setStatusLine(request.httpVersion, 200, "OK");
     response.bodyOutputStream.write(body, body.length);
   }
 
   let server = httpd_setup({
-    "/1.0/johndoe/storage/steam": steam_coll.handler(),
-    "/1.0/johndoe/storage/diesel": diesel_coll.handler(),
-    "/1.0/johndoe/storage/keys": keys_coll.handler(),
-    "/1.0/johndoe/info/collections": info_collections
+    "/1.1/johndoe/storage/steam": steam_coll.handler(),
+    "/1.1/johndoe/storage/diesel": diesel_coll.handler(),
+    "/1.1/johndoe/storage/keys": keys_coll.handler(),
+    "/1.1/johndoe/info/collections": info_collections
   });
   do_test_pending();
 
   try {
     setUpTestFixtures();
     _("Info URL: " + Service.infoURL);
 
     _("Confirm initial environment.");
--- a/services/sync/tests/unit/test_syncengine.js
+++ b/services/sync/tests/unit/test_syncengine.js
@@ -7,19 +7,19 @@ function makeSteamEngine() {
 
 var syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
 
 function test_url_attributes() {
   _("SyncEngine url attributes");
   Svc.Prefs.set("clusterURL", "https://cluster/");
   let engine = makeSteamEngine();
   try {
-    do_check_eq(engine.storageURL, "https://cluster/1.0/foo/storage/");
-    do_check_eq(engine.engineURL, "https://cluster/1.0/foo/storage/steam");
-    do_check_eq(engine.metaURL, "https://cluster/1.0/foo/storage/meta/global");
+    do_check_eq(engine.storageURL, "https://cluster/1.1/foo/storage/");
+    do_check_eq(engine.engineURL, "https://cluster/1.1/foo/storage/steam");
+    do_check_eq(engine.metaURL, "https://cluster/1.1/foo/storage/meta/global");
   } finally {
     Svc.Prefs.resetBranch("");
   }
 }
 
 function test_syncID() {
   _("SyncEngine.syncID corresponds to preference");
   let engine = makeSteamEngine();
@@ -124,17 +124,17 @@ function test_resetClient() {
 function test_wipeServer() {
   _("SyncEngine.wipeServer deletes server data and resets the client.");
   Svc.Prefs.set("clusterURL", "http://localhost:8080/");
   let engine = makeSteamEngine();
 
   const PAYLOAD = 42;
   let steamCollection = new ServerWBO("steam", PAYLOAD);
   let server = httpd_setup({
-    "/1.0/foo/storage/steam": steamCollection.handler()
+    "/1.1/foo/storage/steam": steamCollection.handler()
   });
   do_test_pending();
 
   try {
     // Some data to reset.
     engine.lastSync = 123.45;
     engine.toFetch = [Utils.makeGUID(), Utils.makeGUID(), Utils.makeGUID()];
 
--- a/services/sync/tests/unit/test_syncengine_sync.js
+++ b/services/sync/tests/unit/test_syncengine_sync.js
@@ -124,17 +124,17 @@ function test_syncStartup_emptyOrOutdate
   collection.wbos.flying = new ServerWBO(
       'flying', encryptPayload({id: 'flying',
                                 denomination: "LNER Class A3 4472"}));
   collection.wbos.scotsman = new ServerWBO(
       'scotsman', encryptPayload({id: 'scotsman',
                                   denomination: "Flying Scotsman"}));
 
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   engine._store.items = {rekolok: "Rekonstruktionslokomotive"};
   try {
 
     // Confirm initial environment
@@ -171,17 +171,17 @@ function test_syncStartup_emptyOrOutdate
 
 function test_syncStartup_serverHasNewerVersion() {
   _("SyncEngine._syncStartup ");
 
   Svc.Prefs.set("clusterURL", "http://localhost:8080/");
   Svc.Prefs.set("username", "foo");
   let global = new ServerWBO('global', {engines: {steam: {version: 23456}}});
   let server = httpd_setup({
-      "/1.0/foo/storage/meta/global": global.handler()
+      "/1.1/foo/storage/meta/global": global.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   try {
 
     // The server has a newer version of the data and our engine can
     // handle.  That should give us an exception.
@@ -210,17 +210,17 @@ function test_syncStartup_syncIDMismatch
   let server = sync_httpd_setup({});
   do_test_pending();
 
   // global record with a different syncID than our engine has
   let engine = makeSteamEngine();
   let global = new ServerWBO('global',
                              {engines: {steam: {version: engine.version,
                                                 syncID: 'foobar'}}});
-  server.registerPathHandler("/1.0/foo/storage/meta/global", global.handler());
+  server.registerPathHandler("/1.1/foo/storage/meta/global", global.handler());
 
   try {
 
     // Confirm initial environment
     do_check_eq(engine.syncID, 'fake-guid-0');
     do_check_eq(engine._tracker.changedIDs["rekolok"], undefined);
 
     engine.lastSync = Date.now() / 1000;
@@ -245,17 +245,17 @@ function test_syncStartup_syncIDMismatch
 function test_processIncoming_emptyServer() {
   _("SyncEngine._processIncoming working with an empty server backend");
 
   Svc.Prefs.set("clusterURL", "http://localhost:8080/");
   Svc.Prefs.set("username", "foo");
   let collection = new ServerCollection();
 
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   try {
 
     // Merely ensure that this code path is run without any errors
     engine._processIncoming();
@@ -288,19 +288,19 @@ function test_processIncoming_createFrom
                                   denomination: "Flying Scotsman"}));
 
   // Two pathological cases involving relative URIs gone wrong.
   collection.wbos['../pathological'] = new ServerWBO(
       '../pathological', encryptPayload({id: '../pathological',
                                          denomination: "Pathological Case"}));
 
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler(),
-      "/1.0/foo/storage/steam/flying": collection.wbos.flying.handler(),
-      "/1.0/foo/storage/steam/scotsman": collection.wbos.scotsman.handler()
+      "/1.1/foo/storage/steam": collection.handler(),
+      "/1.1/foo/storage/steam/flying": collection.wbos.flying.handler(),
+      "/1.1/foo/storage/steam/scotsman": collection.wbos.scotsman.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
   meta_global.payload.engines = {steam: {version: engine.version,
                                          syncID: engine.syncID}};
 
@@ -382,17 +382,17 @@ function test_processIncoming_reconcile(
   // This record is marked as deleted, so we're expecting the client
   // record to be removed.
   collection.wbos.nukeme = new ServerWBO(
       'nukeme', encryptPayload({id: 'nukeme',
                                 denomination: "Nuke me!",
                                 deleted: true}));
 
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   engine._store.items = {newerserver: "New data, but not as new as server!",
                          olderidentical: "Older but identical",
                          updateclient: "Got data?",
                          original: "Original Entry",
@@ -478,17 +478,17 @@ function test_processIncoming_mobile_bat
     let id = 'record-no-' + i;
     let payload = encryptPayload({id: id, denomination: "Record No. " + i});
     let wbo = new ServerWBO(id, payload);
     wbo.modified = Date.now()/1000 - 60*(i+10);
     collection.wbos[id] = wbo;
   }
 
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
   meta_global.payload.engines = {steam: {version: engine.version,
                                          syncID: engine.syncID}};
 
@@ -558,17 +558,17 @@ function test_processIncoming_store_toFe
 
   let engine = makeSteamEngine();
   engine.enabled = true;
 
   let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
   meta_global.payload.engines = {steam: {version: engine.version,
                                          syncID: engine.syncID}};
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   try {
 
     // Confirm initial environment
     do_check_eq(engine.lastSync, 0);
     do_check_eq([id for (id in engine._store.items)].length, 0);
@@ -626,17 +626,17 @@ function test_processIncoming_resume_toF
   let engine = makeSteamEngine();
   engine.lastSync = LASTSYNC;
   engine.toFetch = ["flying", "scotsman"];
 
   let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
   meta_global.payload.engines = {steam: {version: engine.version,
                                          syncID: engine.syncID}};
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   try {
 
     // Confirm initial environment
     do_check_eq(engine._store.items.flying, undefined);
     do_check_eq(engine._store.items.scotsman, undefined);
@@ -683,17 +683,17 @@ function test_processIncoming_applyIncom
     let payload = encryptPayload({id: id, denomination: "Record No. " + id});
     collection.wbos[id] = new ServerWBO(id, payload);
   }
 
   let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
   meta_global.payload.engines = {steam: {version: engine.version,
                                          syncID: engine.syncID}};
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   try {
 
     // Confirm initial environment
     do_check_eq([id for (id in engine._store.items)].length, 0);
 
@@ -741,17 +741,17 @@ function test_processIncoming_applyIncom
     let payload = encryptPayload({id: id, denomination: "Record No. " + id});
     collection.wbos[id] = new ServerWBO(id, payload);
   }
 
   let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
   meta_global.payload.engines = {steam: {version: engine.version,
                                          syncID: engine.syncID}};
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   try {
 
     // Confirm initial environment
     do_check_eq([id for (id in engine._store.items)].length, 0);
 
@@ -772,20 +772,16 @@ function test_processIncoming_applyIncom
 }
 
 
 function test_processIncoming_failed_records() {
   _("Ensure that failed records from _reconcile and applyIncomingBatch are refetched.");
   Svc.Prefs.set("clusterURL", "http://localhost:8080/");
   Svc.Prefs.set("username", "foo");
 
-  // Pretend to be a mobile client so we can test failed record handling
-  // while batching GETs.
-  Svc.Prefs.set("client.type", "mobile");
-
   // Let's create three and a bit batches worth of server side records.
   let collection = new ServerCollection();
   const NUMBER_OF_RECORDS = MOBILE_BATCH_SIZE * 3 + 5;
   for (var i = 0; i < NUMBER_OF_RECORDS; i++) {
     let id = 'record-no-' + i;
     let payload = encryptPayload({id: id, denomination: "Record No. " + id});
     let wbo = new ServerWBO(id, payload);
     wbo.modified = Date.now()/1000 + 60 * (i - MOBILE_BATCH_SIZE * 3);
@@ -819,18 +815,30 @@ function test_processIncoming_failed_rec
       throw "I don't like this record! Baaaaaah!";
     }
     return this._applyIncoming.apply(this, arguments);
   };
 
   let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
   meta_global.payload.engines = {steam: {version: engine.version,
                                          syncID: engine.syncID}};
+
+  // Keep track of requests made of a collection.
+  let count = 0;
+  let uris  = [];
+  function recording_handler(collection) {
+    let h = collection.handler();
+    return function(req, res) {
+      ++count;
+      uris.push(req.path + "?" + req.queryString);
+      return h(req, res);
+    };
+  }
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": recording_handler(collection)
   });
   do_test_pending();
 
   try {
 
     // Confirm initial environment
     do_check_eq(engine.lastSync, 0);
     do_check_eq(engine.toFetch.length, 0);
@@ -858,16 +866,43 @@ function test_processIncoming_failed_rec
     BOGUS_RECORDS.sort();
     for (let i = 0; i < engine.toFetch.length; i++) {
       do_check_eq(engine.toFetch[i], BOGUS_RECORDS[i]);
     }
 
     // Ensure the observer was notified
     do_check_eq(observerData, engine.name);
     do_check_eq(observerSubject.failed, BOGUS_RECORDS.length);
+
+    // Testing batching of failed item fetches.
+    // Try to sync again. Ensure that we split the request into chunks to avoid
+    // URI length limitations.
+    function batchDownload(batchSize) {
+      count = 0;
+      uris  = [];
+      engine.guidFetchBatchSize = batchSize;
+      engine._processIncoming();
+      _("Tried again. Requests: " + count + "; URIs: " + JSON.stringify(uris));
+      return count;
+    }
+    
+    // There are 8 bad records, so this needs 3 fetches.
+    _("Test batching with ID batch size 3, normal mobile batch size.");
+    do_check_eq(batchDownload(3), 3);
+
+    // Now see with a more realistic limit.
+    _("Test batching with sufficient ID batch size.");
+    do_check_eq(batchDownload(BOGUS_RECORDS.length), 1);
+
+    // If we're on mobile, that limit is used by default.
+    _("Test batching with tiny mobile batch size.");
+    Svc.Prefs.set("client.type", "mobile");
+    engine.mobileGUIDFetchBatchSize = 2;
+    do_check_eq(batchDownload(BOGUS_RECORDS.length), 4);
+
   } finally {
     server.stop(do_test_finished);
     Svc.Prefs.resetBranch("");
     Records.clearCache();
     syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
   }
 }
 
@@ -905,17 +940,17 @@ function test_processIncoming_decrypt_fa
   engine.enabled = true;
   engine._store.items = {nojson: "Valid JSON",
                          nodecrypt: "Valid ciphertext"};
 
   let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
   meta_global.payload.engines = {steam: {version: engine.version,
                                          syncID: engine.syncID}};
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   try {
 
     // Confirm initial state
     do_check_eq(engine.toFetch.length, 0);
 
@@ -956,19 +991,19 @@ function test_uploadOutgoing_toEmptyServ
 
   Svc.Prefs.set("clusterURL", "http://localhost:8080/");
   Svc.Prefs.set("username", "foo");
   let collection = new ServerCollection();
   collection.wbos.flying = new ServerWBO('flying');
   collection.wbos.scotsman = new ServerWBO('scotsman');
 
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler(),
-      "/1.0/foo/storage/steam/flying": collection.wbos.flying.handler(),
-      "/1.0/foo/storage/steam/scotsman": collection.wbos.scotsman.handler()
+      "/1.1/foo/storage/steam": collection.handler(),
+      "/1.1/foo/storage/steam/flying": collection.wbos.flying.handler(),
+      "/1.1/foo/storage/steam/scotsman": collection.wbos.scotsman.handler()
   });
   do_test_pending();
   CollectionKeys.generateNewKeys();
 
   let engine = makeSteamEngine();
   engine.lastSync = 123; // needs to be non-zero so that tracker is queried
   engine._store.items = {flying: "LNER Class A3 4472",
                          scotsman: "Flying Scotsman"};
@@ -1018,17 +1053,17 @@ function test_uploadOutgoing_failed() {
   Svc.Prefs.set("clusterURL", "http://localhost:8080/");
   Svc.Prefs.set("username", "foo");
   let collection = new ServerCollection();
   // We only define the "flying" WBO on the server, not the "scotsman"
   // and "peppercorn" ones.
   collection.wbos.flying = new ServerWBO('flying');
 
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   engine.lastSync = 123; // needs to be non-zero so that tracker is queried
   engine._store.items = {flying: "LNER Class A3 4472",
                          scotsman: "Flying Scotsman",
                          peppercorn: "Peppercorn Class"};
@@ -1102,17 +1137,17 @@ function test_uploadOutgoing_MAX_UPLOAD_
     collection.wbos[id] = new ServerWBO(id);
   }
 
   let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL));
   meta_global.payload.engines = {steam: {version: engine.version,
                                          syncID: engine.syncID}};
 
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   try {
 
     // Confirm initial environment
     do_check_eq(noOfUploads, 0);
 
@@ -1160,17 +1195,17 @@ function test_syncFinish_deleteByIds() {
   collection.wbos.scotsman = new ServerWBO(
       'scotsman', encryptPayload({id: 'scotsman',
                                   denomination: "Flying Scotsman"}));
   collection.wbos.rekolok = new ServerWBO(
       'rekolok', encryptPayload({id: 'rekolok',
                                 denomination: "Rekonstruktionslokomotive"}));
 
   let server = httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   try {
     engine._delete = {ids: ['flying', 'rekolok']};
     engine._syncFinish();
 
@@ -1214,17 +1249,17 @@ function test_syncFinish_deleteLotsInBat
     let id = 'record-no-' + i;
     let payload = encryptPayload({id: id, denomination: "Record No. " + i});
     let wbo = new ServerWBO(id, payload);
     wbo.modified = now / 1000 - 60 * (i + 110);
     collection.wbos[id] = wbo;
   }
 
   let server = httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   try {
 
     // Confirm initial environment
     do_check_eq(noOfUploads, 0);
@@ -1269,17 +1304,17 @@ function test_syncFinish_deleteLotsInBat
 function test_sync_partialUpload() {
   _("SyncEngine.sync() keeps changedIDs that couldn't be uploaded.");
 
   Svc.Prefs.set("clusterURL", "http://localhost:8080/");
   Svc.Prefs.set("username", "foo");
 
   let collection = new ServerCollection();
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
   CollectionKeys.generateNewKeys();
 
   let engine = makeSteamEngine();
   engine.lastSync = 123; // needs to be non-zero so that tracker is queried
   engine.lastSyncLocal = 456;
 
@@ -1351,17 +1386,17 @@ function test_canDecrypt_noCryptoKeys() 
   CollectionKeys.clear();
 
   let collection = new ServerCollection();
   collection.wbos.flying = new ServerWBO(
       'flying', encryptPayload({id: 'flying',
                                 denomination: "LNER Class A3 4472"}));
 
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   try {
 
     do_check_false(engine.canDecrypt());
 
@@ -1382,17 +1417,17 @@ function test_canDecrypt_true() {
   CollectionKeys.generateNewKeys();
   
   let collection = new ServerCollection();
   collection.wbos.flying = new ServerWBO(
       'flying', encryptPayload({id: 'flying',
                                 denomination: "LNER Class A3 4472"}));
 
   let server = sync_httpd_setup({
-      "/1.0/foo/storage/steam": collection.handler()
+      "/1.1/foo/storage/steam": collection.handler()
   });
   do_test_pending();
 
   let engine = makeSteamEngine();
   try {
 
     do_check_true(engine.canDecrypt());
 
deleted file mode 100644
--- a/services/sync/tests/unit/test_utils_file.js
+++ /dev/null
@@ -1,135 +0,0 @@
-_("Test file-related utility functions");
-
-Cu.import("resource://services-sync/util.js");
-Cu.import("resource://services-sync/constants.js");
-
-function run_test() {
-  // Disabled due to Windows failures (bug 599193)
-  //_test_getTmp();
-  //_test_open();
-}
-
-function _test_getTmp() {
-  // as the setup phase remove the tmp directory from the
-  // filesystem
-  let svc = Cc["@mozilla.org/file/directory_service;1"]
-              .getService(Ci.nsIProperties);
-  let tmp = svc.get("ProfD", Ci.nsIFile);
-  tmp.QueryInterface(Ci.nsILocalFile);
-  tmp.append("weave");
-  tmp.append("tmp");
-  if (tmp.exists())
-    tmp.remove(true);
-
-  // call getTmp with no argument. A ref to the tmp
-  // dir is returned
-  _("getTmp with no argument");
-  let tmpDir = Utils.getTmp();
-  do_check_true(tmpDir instanceof Ci.nsILocalFile);
-  do_check_true(tmpDir.isDirectory());
-  do_check_true(tmpDir.exists());
-  do_check_eq(tmpDir.leafName, "tmp");
-
-  // call getTmp with a string. A ref to the file
-  // named with this string and included in the
-  // tmp dir is returned
-  _("getTmp with a string");
-  let tmpFile = Utils.getTmp("name");
-  do_check_true(tmpFile instanceof Ci.nsILocalFile);
-  do_check_true(!tmpFile.exists()); // getTmp doesn't create the file!
-  do_check_eq(tmpFile.leafName, "name");
-  do_check_true(tmpDir.contains(tmpFile, false));
-}
-
-function _test_open() {
-
-  // we rely on Utils.getTmp to get a temporary file and
-  // test Utils.open on that file, that's ok Util.getTmp
-  // is also tested (test_getTmp)
-  function createFile() {
-    let f = Utils.getTmp("_test_");
-    f.create(f.NORMAL_FILE_TYPE, PERMS_FILE);
-    return f;
-  }
-
-  // we should probably test more things here, for example
-  // we should test that we cannot write to a stream that
-  // is created as a result of opening a file for reading
-
-  let s, f;
-
-  _("Open for reading, providing a file");
-  let f1 = createFile();
-  [s, f] = Utils.open(f1, "<");
-  do_check_eq(f.path, f1.path);
-  do_check_true(s instanceof Ci.nsIConverterInputStream);
-  f1.remove(false);
-
-  _("Open for reading, providing a file name");
-  let f2 = createFile();
-  let path2 = f2.path;
-  [s, f] = Utils.open(path2, "<");
-  do_check_eq(f.path, path2);
-  do_check_true(s instanceof Ci.nsIConverterInputStream);
-  f2.remove(false);
-
-  _("Open for writing with truncate mode, providing a file");
-  let f3 = createFile();
-  [s, f] = Utils.open(f3, ">");
-  do_check_eq(f.path, f3.path);
-  do_check_true(s instanceof Ci.nsIConverterOutputStream);
-  f3.remove(false);
-
-  _("Open for writing with truncate mode, providing a file name");
-  let f4 = createFile();
-  let path4 = f4.path;
-  [s, f] = Utils.open(path4, ">");
-  do_check_eq(f.path, path4);
-  do_check_true(s instanceof Ci.nsIConverterOutputStream);
-  f4.remove(false);
-
-  _("Open for writing with append mode, providing a file");
-  let f5 = createFile();
-  [s, f] = Utils.open(f5, ">>");
-  do_check_eq(f.path, f5.path);
-  do_check_true(s instanceof Ci.nsIConverterOutputStream);
-  f5.remove(false);
-
-  _("Open for writing with append mode, providing a file name");
-  let f6 = createFile();
-  let path6 = f6.path;
-  [s, f] = Utils.open(path6, ">>");
-  do_check_eq(f.path, path6);
-  do_check_true(s instanceof Ci.nsIConverterOutputStream);
-  f6.remove(false);
-
-  _("Open with illegal mode");
-  let f7 = createFile();
-  let except7;
-  try {
-    Utils.open(f7, "?!");
-  } catch(e) {
-    except7 = e;
-  }
-  do_check_true(!!except7);
-  f7.remove(false);
-
-  _("Open non-existing file for reading");
-  let f8 = createFile();
-  let path8 = f8.path;
-  f8.remove(false);
-  let except8;
-  try {
-    Utils.open(path8, "<");
-  } catch(e) {
-    except8 = e;
-  }
-  do_check_true(!!except8);
-
-  _("Open for reading, provide permissions");
-  let f9 = createFile();
-  [s, f] = Utils.open(f9, "<", 0644);
-  do_check_eq(f.path, f9.path);
-  do_check_true(s instanceof Ci.nsIConverterInputStream);
-  f9.remove(false);
-}
--- a/services/sync/tests/unit/test_utils_getErrorString.js
+++ b/services/sync/tests/unit/test_utils_getErrorString.js
@@ -1,14 +1,14 @@
 Cu.import("resource://services-sync/util.js");
 
 function run_test() {
   let str;
 
   // we just test whether the returned string includes the
   // string "unknown", should be good enough
  
-  str = Utils.getErrorString("error.login.reason.password");
+  str = Utils.getErrorString("error.login.reason.account");
   do_check_true(str.match(/unknown/i) == null);
 
   str = Utils.getErrorString("foobar");
   do_check_true(str.match(/unknown/i) != null);
 }
--- a/services/sync/tests/unit/test_utils_json.js
+++ b/services/sync/tests/unit/test_utils_json.js
@@ -1,10 +1,11 @@
 _("Make sure json saves and loads from disk");
 Cu.import("resource://services-sync/util.js");
+Cu.import("resource://services-sync/constants.js");
 
 function run_test() {
   do_test_pending();
   Utils.asyncChain(function (next) {
 
     _("Do a simple write of an array to json and read");
     Utils.jsonSave("foo", {}, ["v1", "v2"], ensureThrows(function() {
       Utils.jsonLoad("foo", {}, ensureThrows(function(val) {
@@ -63,19 +64,25 @@ function run_test() {
 
   }, function (next) {
 
     _("Verify that reads and read errors are logged.");
 
     // Write a file with some invalid JSON
     let file = Utils.getProfileFile({ autoCreate: true,
                                       path: "weave/log.json" });
-    let [fos] = Utils.open(file, ">");
-    fos.writeString("invalid json!");
-    fos.close();
+    let fos = Cc["@mozilla.org/network/file-output-stream;1"]
+                .createInstance(Ci.nsIFileOutputStream);
+    fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE,
+             fos.DEFER_OPEN);
+    let stream = Cc["@mozilla.org/intl/converter-output-stream;1"]
+                   .createInstance(Ci.nsIConverterOutputStream);
+    stream.init(fos, "UTF-8", 4096, 0x0000);
+    stream.writeString("invalid json!");
+    stream.close();
 
     let trace, debug;
     Utils.jsonLoad("log",
                    {_log: {trace: function(msg) { trace = msg; },
                            debug: function(msg) { debug = msg; }}},
                    ensureThrows(function(val) {
       do_check_true(!!trace);
       do_check_true(!!debug);
--- a/services/sync/version.txt
+++ b/services/sync/version.txt
@@ -1,1 +1,1 @@
-1.7
+1.8.0