Index: libadblockplus.gyp
===================================================================
--- a/libadblockplus.gyp
+++ b/libadblockplus.gyp
@@ -49,6 +49,8 @@
       'src/JsEngineTransition.h',
       'src/JsError.cpp',
       'src/JsValue.cpp',
+      'src/Latch.cpp',
+      'src/Latch.h',
       'src/Notification.cpp',
       'src/ReferrerMapping.cpp',
       'src/Scheduler.cpp',
@@ -182,6 +184,9 @@
       'test/GlobalJsObject.cpp',
       'test/JsEngine.cpp',
       'test/JsValue.cpp',
+      'test/JsLatch.cpp',
+      'test/JsLatch.h',
+      'test/LatchTest.cpp',
       'test/Notification.cpp',
       'test/PausePoint.cpp',
       'test/PausePoint.h',
Index: src/Latch.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/src/Latch.cpp
@@ -0,0 +1,48 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-2016 Eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "Latch.h"
+
+void Latch::Arrive()
+{
+  UniqueLockType ul(m);
+  if (count <= 0)
+  {
+    throw std::logic_error("May not decrement count below zero");
+  }
+  --count;
+  if (synchronizationCondition())
+  {
+    cv.notify_all();
+  }
+}
+
+void Latch::Wait()
+{
+  UniqueLockType ul(m);
+  if (synchronizationCondition())
+  {
+    return;
+  }
+  cv.wait(ul, [this]() -> bool { return synchronizationCondition(); });
+}
+
+bool Latch::TryWait()
+{
+  UniqueLockType ul(m);
+  return synchronizationCondition();
+}
Index: src/Latch.h
===================================================================
new file mode 100644
--- /dev/null
+++ b/src/Latch.h
@@ -0,0 +1,96 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-2016 Eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(LATCH_H)
+#define LATCH_H
+
+#include <condition_variable>
+#include <mutex>
+#include <stdexcept>
+
+/**
+ * Latch class mimicking the concept in C++ Concurrency TS.
+ * Only some of the member functions are implemented.
+ *
+ * \par Reference
+ * - N3998 C++ Latches and Barriers
+ *   http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3998.html
+ */
+class Latch
+{
+  /** 
+   * Internal count.
+   * 
+   * The synchronization condition is that \c count is zero.
+   */
+  int count;
+
+  /**
+   * Mutex protecting the integrity of the count.
+   * This mutex is only used transiently.
+   */
+  std::mutex m;
+
+  typedef std::unique_lock<std::mutex> UniqueLockType;
+
+  /**
+   * Condition variable blocking for Wait().
+   *
+   * \par Invariant
+   * - If the synchronization condition has not been met, mutexBlocking is locked.
+   */
+  std::condition_variable cv;
+
+  bool synchronizationCondition() const
+  {
+    return count == 0;
+  }
+
+public:
+  /**
+   * Initializes the latch with a count of 'initialCount'.
+   */
+  Latch(int initialCount)
+    : count(initialCount)
+  {}
+
+  /**
+   * \par Precondition
+   * - No threads are blocked at the synchronization point.
+   */
+  ~Latch() {};
+
+  /**
+   * Decrements the internal count by 1.
+   * If the count reaches 0 the synchronization condition is reached.
+   */
+  void Arrive();
+
+  /**
+   * Blocks the calling thread at the synchronization point
+   *   until the synchronization condition is reached. 
+   * If the condition has already been reached,  the thread does not block.
+   */
+  void Wait();
+
+  /**
+   * Return the current value of the synchroniztion condition.
+   */
+  bool TryWait();
+};
+
+#endif
Index: test/BaseJsTest.cpp
===================================================================
--- a/test/BaseJsTest.cpp
+++ b/test/BaseJsTest.cpp
@@ -29,11 +29,12 @@
   jsEngine->SetLogSystem(std::make_shared<ThrowingLogSystem>());
   jsEngine->SetFileSystem(std::make_shared<ThrowingFileSystem>());
   jsEngine->SetWebRequest(std::make_shared<ThrowingWebRequest>());
+  engine = ToInternal(jsEngine);
 }
 
 void BaseJsTest::TearDown()
 {
-  ToInternal(jsEngine)->WaitForQuietScheduler();
+  engine->WaitForQuietScheduler();
   EXPECT_EQ(1, jsEngine.use_count());
   jsEngine.reset();
 }
Index: test/BaseJsTest.h
===================================================================
--- a/test/BaseJsTest.h
+++ b/test/BaseJsTest.h
@@ -23,6 +23,7 @@
 #include <thread>
 
 #include <AdblockPlus.h>
+#include "../src/JsEngineInternal.h"
 #include <gtest/gtest.h>
 
 class ThrowingLogSystem : public AdblockPlus::LogSystem
@@ -181,6 +182,7 @@
 {
 protected:
   AdblockPlus::JsEnginePtr jsEngine;
+  JsEngineInternal* engine;
   virtual void SetUp();
   virtual void TearDown();
 };
Index: test/FileSystemJsObject.cpp
===================================================================
--- a/test/FileSystemJsObject.cpp
+++ b/test/FileSystemJsObject.cpp
@@ -17,6 +17,8 @@
 
 #include <sstream>
 #include "BaseJsTest.h"
+#include "JsLatch.h"
+#include "../src/JsEngineTransition.h"
 
 namespace
 {
@@ -100,8 +102,10 @@
   void ReadFile(AdblockPlus::JsEnginePtr jsEngine, std::string& content,
                 std::string& error)
   {
-    jsEngine->Evaluate("_fileSystem.read('', function(r) {result = r})");
-    std::this_thread::sleep_for(std::chrono::milliseconds(50));
+    auto engine = ToInternal(jsEngine);
+    JsTestingLatch latch(engine, "latch");
+    jsEngine->Evaluate("_fileSystem.read('', function(r) {result = r; latch.Arrive();})");
+    latch.Wait();
     content = jsEngine->Evaluate("result.content")->AsString();
     error = jsEngine->Evaluate("result.error")->AsString();
   }
@@ -156,8 +160,9 @@
 
 TEST_F(FileSystemJsObjectTest, Write)
 {
-  jsEngine->Evaluate("_fileSystem.write('foo', 'bar', function(e) {error = e})");
-  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("_fileSystem.write('foo', 'bar', function(e) {error = e; latch.Arrive();})");
+  latch.Wait();
   ASSERT_EQ("foo", mockFileSystem->lastWrittenPath);
   ASSERT_EQ("bar", mockFileSystem->lastWrittenContent);
   ASSERT_EQ("", jsEngine->Evaluate("error")->AsString());
@@ -172,15 +177,17 @@
 TEST_F(FileSystemJsObjectTest, WriteError)
 {
   mockFileSystem->success = false;
-  jsEngine->Evaluate("_fileSystem.write('foo', 'bar', function(e) {error = e})");
-  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("_fileSystem.write('foo', 'bar', function(e) {error = e; latch.Arrive();})");
+  latch.Wait();
   ASSERT_NE("", jsEngine->Evaluate("error")->AsString());
 }
 
 TEST_F(FileSystemJsObjectTest, Move)
 {
-  jsEngine->Evaluate("_fileSystem.move('foo', 'bar', function(e) {error = e})");
-  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("_fileSystem.move('foo', 'bar', function(e) {error = e; latch.Arrive();})");
+  latch.Wait();
   ASSERT_EQ("foo", mockFileSystem->movedFrom);
   ASSERT_EQ("bar", mockFileSystem->movedTo);
   ASSERT_EQ("", jsEngine->Evaluate("error")->AsString());
@@ -195,15 +202,17 @@
 TEST_F(FileSystemJsObjectTest, MoveError)
 {
   mockFileSystem->success = false;
-  jsEngine->Evaluate("_fileSystem.move('foo', 'bar', function(e) {error = e})");
-  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("_fileSystem.move('foo', 'bar', function(e) {error = e; latch.Arrive();})");
+  latch.Wait();
   ASSERT_NE("", jsEngine->Evaluate("error")->AsString());
 }
 
 TEST_F(FileSystemJsObjectTest, Remove)
 {
-  jsEngine->Evaluate("_fileSystem.remove('foo', function(e) {error = e})");
-  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("_fileSystem.remove('foo', function(e) {error = e; latch.Arrive();})");
+  latch.Wait();
   ASSERT_EQ("foo", mockFileSystem->removedPath);
   ASSERT_EQ("", jsEngine->Evaluate("error")->AsString());
 }
@@ -217,8 +226,9 @@
 TEST_F(FileSystemJsObjectTest, RemoveError)
 {
   mockFileSystem->success = false;
-  jsEngine->Evaluate("_fileSystem.remove('foo', function(e) {error = e})");
-  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("_fileSystem.remove('foo', function(e) {error = e; latch.Arrive();})");
+  latch.Wait();
   ASSERT_NE("", jsEngine->Evaluate("error")->AsString());
 }
 
@@ -228,8 +238,9 @@
   mockFileSystem->statIsDirectory= false;
   mockFileSystem->statIsFile = true;
   mockFileSystem->statLastModified = 1337;
-  jsEngine->Evaluate("_fileSystem.stat('foo', function(r) {result = r})");
-  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("_fileSystem.stat('foo', function(r) {result = r; latch.Arrive();})");
+  latch.Wait();
   ASSERT_EQ("foo", mockFileSystem->statPath);
   ASSERT_EQ("", jsEngine->Evaluate("result.error")->AsString());
   ASSERT_TRUE(jsEngine->Evaluate("result.exists")->AsBool());
@@ -247,7 +258,8 @@
 TEST_F(FileSystemJsObjectTest, StatError)
 {
   mockFileSystem->success = false;
-  jsEngine->Evaluate("_fileSystem.stat('foo', function(r) {result = r})");
-  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("_fileSystem.stat('foo', function(r) {result = r; latch.Arrive();})");
+  latch.Wait();
   ASSERT_NE("", jsEngine->Evaluate("result.error")->AsString());
 }
Index: test/FilterEngine.cpp
===================================================================
--- a/test/FilterEngine.cpp
+++ b/test/FilterEngine.cpp
@@ -16,6 +16,7 @@
  */
 
 #include "BaseJsTest.h"
+#include "JsLatch.h"
 #include <thread>
 #include "../src/JsEngineTransition.h"
 
@@ -136,9 +137,6 @@
     int& timesCalled;
   };
 
-  // Workaround for https://issues.adblockplus.org/ticket/1397.
-  void NoOpUpdaterCallback(const std::string&) {}
-
   class FilterEngineWithFreshFolder : public ::testing::Test
   {
   protected:
@@ -556,13 +554,15 @@
   MockUpdateAvailableCallback mockUpdateAvailableCallback(timesCalled);
 
   filterEngine->SetUpdateAvailableCallback(mockUpdateAvailableCallback);
-  filterEngine->ForceUpdateCheck(&NoOpUpdaterCallback);
-  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  Latch latchA(1);
+  filterEngine->ForceUpdateCheck([&](const std::string&) {latchA.Arrive(); });
+  latchA.Wait();
   ASSERT_EQ(1, timesCalled);
 
   filterEngine->RemoveUpdateAvailableCallback();
-  filterEngine->ForceUpdateCheck(&NoOpUpdaterCallback);
-  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  Latch latchB(1);
+  filterEngine->ForceUpdateCheck([&](const std::string&) {latchB.Arrive(); });
+  latchB.Wait();
   ASSERT_EQ(1, timesCalled);
 }
 
Index: test/GlobalJsObject.cpp
===================================================================
--- a/test/GlobalJsObject.cpp
+++ b/test/GlobalJsObject.cpp
@@ -16,6 +16,7 @@
  */
 
 #include "BaseJsTest.h"
+#include "JsLatch.h"
 
 namespace
 {
@@ -26,17 +27,19 @@
 
 TEST_F(GlobalJsObjectTest, SetTimeout)
 {
-  jsEngine->Evaluate("setTimeout(function() {foo = 'bar';}, 100)");
-  ASSERT_TRUE(jsEngine->Evaluate("this.foo")->IsUndefined());
-  std::this_thread::sleep_for(std::chrono::milliseconds(200));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("setTimeout(function() {foo = 'bar'; latch.Arrive();}, 100)");
+  ASSERT_TRUE(jsEngine->Evaluate("this.foo")->IsUndefined()); // RACE
+  latch.Wait();
   ASSERT_EQ("bar", jsEngine->Evaluate("this.foo")->AsString());
 }
 
 TEST_F(GlobalJsObjectTest, SetTimeoutWithArgs)
 {
-  jsEngine->Evaluate("setTimeout(function(s) {foo = s;}, 100, 'foobar')");
-  ASSERT_TRUE(jsEngine->Evaluate("this.foo")->IsUndefined());
-  std::this_thread::sleep_for(std::chrono::milliseconds(200));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("setTimeout(function(s) {foo = s; latch.Arrive();}, 100, 'foobar')");
+  ASSERT_TRUE(jsEngine->Evaluate("this.foo")->IsUndefined()); // RACE
+  latch.Wait();
   ASSERT_EQ("foobar", jsEngine->Evaluate("this.foo")->AsString());
 }
 
@@ -48,9 +51,10 @@
 
 TEST_F(GlobalJsObjectTest, SetMultipleTimeouts)
 {
+  JsTestingLatch latch(engine, "latch", 2); // two arrivals
   jsEngine->Evaluate("foo = []");
-  jsEngine->Evaluate("setTimeout(function(s) {foo.push('1');}, 100)");
-  jsEngine->Evaluate("setTimeout(function(s) {foo.push('2');}, 150)");
-  std::this_thread::sleep_for(std::chrono::milliseconds(200));
+  jsEngine->Evaluate("setTimeout(function(s) {foo.push('1'); latch.Arrive();}, 100)");
+  jsEngine->Evaluate("setTimeout(function(s) {foo.push('2'); latch.Arrive();}, 150)");
+  latch.Wait();
   ASSERT_EQ("1,2", jsEngine->Evaluate("this.foo")->AsString());
 }
Index: test/JsLatch.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/test/JsLatch.cpp
@@ -0,0 +1,74 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-2016 Eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "JsLatch.h"
+
+namespace
+{
+  /**
+   * Implementation of JS function "Arrive()" on latch objects.
+   */
+  v8::Handle<v8::Value> ArriveCallback(const v8::Arguments& arguments)
+  {
+    auto self = static_cast<JsTestingLatch *>(
+      v8::Local<v8::External>::Cast(arguments.Holder()->GetInternalField(0))->Value());
+    if (self)
+    {
+      self->GetLatch().Arrive();
+    }
+    return v8::Undefined();
+  }
+}
+
+/**
+ * \par Implementation Notes
+ *    Internal field 0 of the JS object contains a copy of \c this.
+ */
+V8PersistentNG<v8::Object> JsTestingLatch::JsObjectInitializer(const std::string& propertyName)
+{
+  /*
+   * N.B. Member order ensures that \c engine has been initialized before this function runs.
+   */
+  V8ExecutionScope context(engine);
+  // Object template
+  auto jsObjectTemplate = v8::ObjectTemplate::New();
+  jsObjectTemplate->SetInternalFieldCount(1);
+  jsObjectTemplate->Set(engine->ToV8String("Arrive"), v8::FunctionTemplate::New(ArriveCallback));
+  // Object
+  auto localJsObject = jsObjectTemplate->NewInstance();
+  localJsObject->SetInternalField(0, v8::External::New(this));
+  // Property on global
+  engine->GetGlobalObject()->Set(engine->ToV8String(propertyName), localJsObject);
+  // Persistent
+  return V8PersistentNG<v8::Object>(engine->GetIsolate(), localJsObject);
+}
+
+JsTestingLatch::JsTestingLatch(JsEngineInternal* engine, const std::string& propertyName, int count)
+  : engine(engine), latch(count), jsObject(JsObjectInitializer(propertyName))
+{}
+
+JsTestingLatch::~JsTestingLatch()
+{
+  V8ExecutionScope context(engine);
+  auto obj = jsObject.Get(engine->GetIsolate());
+  // ensure object still exists
+  if (obj->IsObject())
+  {
+    obj->SetInternalField(0, v8::External::New(nullptr));
+  }
+  // Rather than changing the "Arrive" property, we handle a null pointer in the callback for it.
+}
Index: test/JsLatch.h
===================================================================
new file mode 100644
--- /dev/null
+++ b/test/JsLatch.h
@@ -0,0 +1,93 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-2016 Eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(JS_LATCH_H)
+#define JS_LATCH_H
+
+#include "../src/Latch.h"
+#include "../src/JsEngineInternal.h"
+
+/**
+ * A Latch partially exposed as a JS object within an engine.
+ *
+ * This latch class is designed for a common use case in these unit tests:
+ * 1) Evaluate an expression that causes execution of an asynchronous task,
+ *    either I/O or web request/
+ * 2) Wait for the asynchronous task to complete.
+ * 3) Continue with the test.
+ * It would be better if our asynchronous tasks did this themselves,
+ *   but they're not written that way.
+ *
+ * This class wraps a simple latch object and exposes it as both
+ *   a C++ object and a JS object, yet with different interfaces.
+ * The C++ side sees the latch ordinarily (as a reference).
+ * The JS side sees only an \c Arrive() method.
+ * Because JS is single-threaded we don't expose a wait method.
+ *
+ * The life span of a C++ instance and that of a corresponding JS object
+ *   are only partially linked together.
+ * Because this is for unit tests, we create the object on the C++ side
+ *   and expose it on the JS side.
+ * If the C++ instance is destroyed, the JS object remains valid,
+ *   although its \c Arrive() method becomes a no-op.
+ * If the JS instance is destroyed, the C++ object remains valid,
+ *   but the handle to the JS object becomes null.
+ */
+class JsTestingLatch
+{
+  Latch latch;
+
+  // We need this for the destructor to run.
+  JsEngineInternal* engine;
+
+  V8PersistentNG<v8::Object> jsObject;
+
+  V8PersistentNG<v8::Object> JsObjectInitializer(const std::string& propertyName);
+
+public:
+  /**
+   * Create a JS latch object as a property with a given name on the global object.
+   *
+   * @param engine
+   *   Engine in which to create instance
+   * @param propertyName
+   *   Name of property on global object for the instance
+   * @param count
+   *   Initial count for the latch
+   */
+  JsTestingLatch(JsEngineInternal* engine, const std::string& propertyName, int count = 1);
+
+  /**
+   * Destructor nullifies the JS object's pointer back to us 
+   */
+  ~JsTestingLatch();
+
+  Latch& GetLatch()
+  {
+    return latch;
+  }
+
+  /**
+   * Convenience method. Forwarded from the underlying latch.
+   */
+  void Wait()
+  {
+    latch.Wait();
+  }
+};
+
+#endif
Index: test/LatchTest.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/test/LatchTest.cpp
@@ -0,0 +1,91 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-2016 Eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtest/gtest.h>
+#include "BaseJsTest.h"
+
+#include "JsLatch.h"
+#include "../src/JsEngineTransition.h"
+
+TEST(Latch, InstanceZero)
+{
+  Latch x(0);
+  ASSERT_TRUE(x.TryWait());
+}
+
+TEST(Latch, InstanceOne)
+{
+  Latch x(1);
+  ASSERT_FALSE(x.TryWait());
+}
+
+/*
+ * Verify that Wait() does not block when the latch is initialized to zero in the same thread.
+ */
+TEST(Latch, InitialZeroSameThread)
+{
+  ASSERT_NO_THROW(
+  {
+    Latch x(0);
+    x.Wait();
+  });
+}
+
+/*
+ * Verify that Wait() does not block when the latch becomes zero in the same thread.
+ */
+TEST(Latch, BecomesZeroSameThread)
+{
+  ASSERT_NO_THROW(
+  {
+    Latch x(1);
+    x.Arrive();
+    ASSERT_TRUE(x.TryWait());
+    x.Wait();
+  });
+}
+
+namespace
+{
+  struct JsTestingLatchTest
+    : public BaseJsTest
+  {
+    JsEngineInternal* engine;
+
+    void SetUp()
+    {
+      BaseJsTest::SetUp();
+      engine = ToInternal(jsEngine);
+    }
+  };
+}
+
+TEST_F(JsTestingLatchTest, Instance)
+{
+  JsTestingLatch x(engine, "foo");
+}
+
+TEST_F(JsTestingLatchTest, InstanceInIsolate)
+{
+  JsTestingLatch x(engine, "foo");
+  auto y = engine->Evaluate("foo");
+  ASSERT_FALSE(y->IsUndefined());
+  y = engine->Evaluate("foo.Arrive()");
+  ASSERT_TRUE(x.GetLatch().TryWait());
+  x.Wait();
+}
+
Index: test/Prefs.cpp
===================================================================
--- a/test/Prefs.cpp
+++ b/test/Prefs.cpp
@@ -151,7 +151,7 @@
   filterEngine->SetPref("patternsbackupinterval", jsEngine->NewValue(48));
   filterEngine->SetPref("subscriptions_autoupdate", jsEngine->NewValue(false));
 
-  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  std::this_thread::sleep_for(std::chrono::milliseconds(100)); // RACE
 
   ASSERT_FALSE(fileSystem->prefsContents.empty());
 
@@ -222,7 +222,7 @@
   ASSERT_TRUE(filterEngine->GetPref("suppress_first_run_page")->AsBool());
   filterEngine->SetPref("suppress_first_run_page", jsEngine->NewValue(false));
 
-  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  std::this_thread::sleep_for(std::chrono::milliseconds(100)); // RACE
 
   ASSERT_FALSE(fileSystem->prefsContents.empty());
 
Index: test/UpdateCheck.cpp
===================================================================
--- a/test/UpdateCheck.cpp
+++ b/test/UpdateCheck.cpp
@@ -18,6 +18,7 @@
 #include <functional>
 
 #include "BaseJsTest.h"
+#include "JsLatch.h"
 #include "../src/JsEngineTransition.h"
 
 namespace
@@ -100,10 +101,15 @@
       Construct();
     }
 
-    void ForceUpdateCheck()
+    void ForceUpdateCheckSynchronous()
     {
-      filterEngine->ForceUpdateCheck(
-          std::bind(&UpdateCheckTest::UpdateCallback, this, std::placeholders::_1));
+      Latch latch(1);
+      filterEngine->ForceUpdateCheck([&, this](const std::string& error) -> void
+      {
+        UpdateCallback(error);
+        latch.Arrive();
+      });
+      latch.Wait();
     }
 
     void EventCallback(AdblockPlus::JsValueList& params)
@@ -131,9 +137,7 @@
   appInfo.developmentBuild = false;
 
   Reset();
-  ForceUpdateCheck();
-
-  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  ForceUpdateCheckSynchronous();
 
   ASSERT_FALSE(eventCallbackCalled);
   ASSERT_TRUE(updateCallbackCalled);
@@ -168,9 +172,7 @@
   appInfo.developmentBuild = true;
 
   Reset();
-  ForceUpdateCheck();
-
-  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  ForceUpdateCheckSynchronous();
 
   ASSERT_TRUE(eventCallbackCalled);
   ASSERT_EQ(1u, eventCallbackParams.size());
@@ -207,9 +209,7 @@
   appInfo.developmentBuild = true;
 
   Reset();
-  ForceUpdateCheck();
-
-  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  ForceUpdateCheckSynchronous();
 
   ASSERT_TRUE(eventCallbackCalled);
   ASSERT_EQ(1u, eventCallbackParams.size());
@@ -231,9 +231,7 @@
   appInfo.developmentBuild = true;
 
   Reset();
-  ForceUpdateCheck();
-
-  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  ForceUpdateCheckSynchronous();
 
   ASSERT_FALSE(eventCallbackCalled);
   ASSERT_TRUE(updateCallbackCalled);
@@ -253,9 +251,7 @@
   appInfo.developmentBuild = true;
 
   Reset();
-  ForceUpdateCheck();
-
-  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  ForceUpdateCheckSynchronous();
 
   ASSERT_FALSE(eventCallbackCalled);
   ASSERT_TRUE(updateCallbackCalled);
@@ -275,9 +271,7 @@
   appInfo.developmentBuild = true;
 
   Reset();
-  ForceUpdateCheck();
-
-  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  ForceUpdateCheckSynchronous();
 
   ASSERT_FALSE(eventCallbackCalled);
   ASSERT_TRUE(updateCallbackCalled);
Index: test/WebRequest.cpp
===================================================================
--- a/test/WebRequest.cpp
+++ b/test/WebRequest.cpp
@@ -17,6 +17,7 @@
 
 #include <sstream>
 #include "BaseJsTest.h"
+#include "JsLatch.h"
 
 namespace
 {
@@ -25,8 +26,6 @@
   public:
     AdblockPlus::ServerResponse GET(const std::string& url, const AdblockPlus::HeaderList& requestHeaders) const
     {
-      std::this_thread::sleep_for(std::chrono::milliseconds(50));
-
       AdblockPlus::ServerResponse result;
       result.status = NS_OK;
       result.responseStatus = 123;
@@ -64,9 +63,9 @@
 
 TEST_F(MockWebRequestTest, SuccessfulRequest)
 {
-  jsEngine->Evaluate("_webRequest.GET('http://example.com/', {X: 'Y'}, function(result) {foo = result;} )");
-  ASSERT_TRUE(jsEngine->Evaluate("this.foo")->IsUndefined());
-  std::this_thread::sleep_for(std::chrono::milliseconds(200));
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("_webRequest.GET('http://example.com/', {X: 'Y'}, function(result) {foo = result; latch.Arrive();} )");
+  latch.Wait();
   ASSERT_EQ(AdblockPlus::WebRequest::NS_OK, jsEngine->Evaluate("foo.status")->AsInt());
   ASSERT_EQ(123, jsEngine->Evaluate("foo.responseStatus")->AsInt());
   ASSERT_EQ("http://example.com/\nX\nY", jsEngine->Evaluate("foo.responseText")->AsString());
@@ -78,11 +77,9 @@
 {
   // This URL should redirect to easylist-downloads.adblockplus.org and we
   // should get the actual filter list back.
-  jsEngine->Evaluate("_webRequest.GET('https://easylist-downloads.adblockplus.org/easylist.txt', {}, function(result) {foo = result;} )");
-  do
-  {
-    std::this_thread::sleep_for(std::chrono::milliseconds(200));
-  } while (jsEngine->Evaluate("this.foo")->IsUndefined());
+  JsTestingLatch latch(engine, "latch");
+  jsEngine->Evaluate("_webRequest.GET('https://easylist-downloads.adblockplus.org/easylist.txt', {}, function(result) {foo = result; latch.Arrive();} )");
+  latch.Wait();
   ASSERT_EQ("text/plain", jsEngine->Evaluate("foo.responseHeaders['content-type'].substr(0, 10)")->AsString());
   ASSERT_EQ(AdblockPlus::WebRequest::NS_OK, jsEngine->Evaluate("foo.status")->AsInt());
   ASSERT_EQ(200, jsEngine->Evaluate("foo.responseStatus")->AsInt());
@@ -94,7 +91,7 @@
 TEST_F(DefaultWebRequestTest, XMLHttpRequest)
 {
   AdblockPlus::FilterEngine filterEngine(jsEngine);
-
+  JsTestingLatch latch(engine, "latch");
   jsEngine->Evaluate("\
     var result;\
     var request = new XMLHttpRequest();\
@@ -102,13 +99,10 @@
     request.setRequestHeader('X', 'Y');\
     request.setRequestHeader('X2', 'Y2');\
     request.overrideMimeType('text/plain');\
-    request.addEventListener('load', function() {result = request.responseText;}, false);\
-    request.addEventListener('error', function() {result = 'error';}, false);\
+    request.addEventListener('load', function() {result = request.responseText; latch.Arrive();}, false);\
+    request.addEventListener('error', function() {result = 'error'; latch.Arrive();}, false);\
     request.send(null);");
-  do
-  {
-    std::this_thread::sleep_for(std::chrono::milliseconds(200));
-  } while (jsEngine->Evaluate("result")->IsUndefined());
+  latch.Wait();
   ASSERT_EQ(AdblockPlus::WebRequest::NS_OK, jsEngine->Evaluate("request.channel.status")->AsInt());
   ASSERT_EQ(200, jsEngine->Evaluate("request.status")->AsInt());
   ASSERT_EQ("[Adblock Plus ", jsEngine->Evaluate("result.substr(0, 14)")->AsString());
