Index: mobile/android/thirdparty/org/adblockplus/browser/AddOnBridge.java
diff --git a/mobile/android/thirdparty/org/adblockplus/browser/AddOnBridge.java b/mobile/android/thirdparty/org/adblockplus/browser/AddOnBridge.java
new file mode 100644
index 0000000000000000000000000000000000000000..b73ca15eec28c28cbc43455839b7dac0af76bedd
--- /dev/null
+++ b/mobile/android/thirdparty/org/adblockplus/browser/AddOnBridge.java
@@ -0,0 +1,259 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-2015 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/>.
+ */
+
+package org.adblockplus.browser;
+
+import android.annotation.SuppressLint;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.util.GeckoRequest;
+import org.mozilla.gecko.util.NativeJSObject;
+
+@SuppressLint("DefaultLocale")
+public class AddOnBridge
+{
+  private static final String TAG = "AdblockBrowser.AddOnBridge";
+  private static final String REQUEST_NAME = "AdblockPlus:Api";
+  // Timeout for checking filter loading (in seconds)
+  private static final int QUERY_GET_FILTERS_LOADED_TIMEOUT = 30;
+  // How long to wait between retries (in milliseconds)
+  private static final int QUERY_GET_FILTERS_LOADED_DELAY = 500;
+  // Handler+HandlerThread for posting delayed re-tries without interfering with
+  // other threads (e.g. the UI or Gecko thread)
+  private static final HandlerThread HANDLER_THREAD;
+  private static final Handler HANDLER;
+
+  static
+  {
+    HANDLER_THREAD = new HandlerThread("abp-bridge");
+    HANDLER_THREAD.setDaemon(true);
+    HANDLER_THREAD.start();
+    HANDLER = new Handler(HANDLER_THREAD.getLooper());
+  }
+
+  public static boolean getBooleanFromJsObject(final NativeJSObject obj, final String name,
+      final boolean defaultValue)
+  {
+    try
+    {
+      return obj.getBoolean(name);
+    }
+    catch (final Exception e)
+    {
+      return defaultValue;
+    }
+  }
+
+  public static String getStringFromJsObject(final NativeJSObject obj, final String name,
+      final String defaultValue)
+  {
+    try
+    {
+      return obj.getString(name);
+    }
+    catch (final Exception e)
+    {
+      return defaultValue;
+    }
+  }
+
+  private static JSONObject createRequestData(final String action)
+  {
+    final JSONObject obj = new JSONObject();
+    try
+    {
+      obj.put("action", action);
+    }
+    catch (JSONException e)
+    {
+      // we're only adding sane objects
+      Log.e(TAG, "Creating request data failed with: " + e.getMessage(), e);
+    }
+    return obj;
+  }
+
+  private static JSONObject createRequestData(final String action, final boolean enable)
+  {
+    final JSONObject obj = createRequestData(action);
+    try
+    {
+      obj.put("enable", enable);
+    }
+    catch (JSONException e)
+    {
+      // we're only adding sane objects
+      Log.e(TAG, "Creating request data failed with: " + e.getMessage(), e);
+    }
+    return obj;
+  }
+
+  public static String makeFirstCharacterUppercase(String str)
+  {
+    if (Character.isUpperCase(str.charAt(0)))
+    {
+      return str;
+    }
+    return Character.toString(Character.toUpperCase(str.charAt(0))) + str.substring(1);
+  }
+
+  public static void queryBoolean(final AdblockPlusApiCallback callback, final String name)
+  {
+    Log.d(TAG, "queryBoolean for " + name);
+    GeckoAppShell.sendRequestToGecko(
+        new ChainedRequest(
+            createRequestData("get" + makeFirstCharacterUppercase(name)),
+            callback));
+  }
+
+  public static void setBoolean(final AdblockPlusApiCallback callback, final String name,
+      final boolean enable)
+  {
+    Log.d(TAG, "setBoolean " + enable + " for " + name);
+    GeckoAppShell.sendRequestToGecko(
+        new ChainedRequest(
+            createRequestData("set" + makeFirstCharacterUppercase(name), enable),
+            callback));
+  }
+
+  private static class ChainedRequest extends GeckoRequest
+  {
+    private final JSONObject value;
+    private final AdblockPlusApiCallback apiCallback;
+    private final boolean checkForFiltersLoaded;
+    private final long creationTime;
+
+    public ChainedRequest(final JSONObject value, final AdblockPlusApiCallback callback,
+        final boolean checkForFiltersLoaded, final long creationTime)
+    {
+      super(AddOnBridge.REQUEST_NAME,
+          checkForFiltersLoaded ? createRequestData("getFiltersLoaded") : value);
+      this.value = value;
+      this.apiCallback = callback;
+      this.checkForFiltersLoaded = checkForFiltersLoaded;
+      this.creationTime = creationTime;
+    }
+
+    public ChainedRequest(final JSONObject value, final AdblockPlusApiCallback callback)
+    {
+      this(value, callback, true, System.currentTimeMillis());
+    }
+
+    public ChainedRequest cloneForRetry()
+    {
+      return new ChainedRequest(this.value, this.apiCallback, true, this.creationTime);
+    }
+
+    public ChainedRequest cloneForRequest()
+    {
+      return new ChainedRequest(this.value, this.apiCallback, false, this.creationTime);
+    }
+
+    private void invokeSuccessCallback(final NativeJSObject jsObject)
+    {
+      try
+      {
+        if (this.apiCallback != null)
+        {
+          this.apiCallback.onApiRequestSucceeded(jsObject);
+        }
+      }
+      catch (final Exception e)
+      {
+        Log.e(TAG, "onApiRequestSucceeded threw exception: " + e.getMessage(), e);
+      }
+    }
+
+    private void invokeFailureCallback(final String msg)
+    {
+      if (this.apiCallback != null)
+      {
+        this.apiCallback.onApiRequestFailed(msg);
+      }
+    }
+
+    private void invokeFailureCallback(final NativeJSObject jsObject)
+    {
+      invokeFailureCallback(getStringFromJsObject(jsObject, "error", "unknown error"));
+    }
+
+    private void attemptRetry()
+    {
+      if (System.currentTimeMillis() - this.creationTime > (QUERY_GET_FILTERS_LOADED_TIMEOUT * 1000))
+      {
+        this.invokeFailureCallback("getFiltersLoaded timeout");
+      }
+      else
+      {
+        final ChainedRequest next = this.cloneForRetry();
+        HANDLER.postDelayed(new Runnable()
+        {
+          @Override
+          public void run()
+          {
+            GeckoAppShell.sendRequestToGecko(next);
+          }
+        }, QUERY_GET_FILTERS_LOADED_DELAY);
+      }
+    }
+
+    @Override
+    public void onError()
+    {
+      if (this.checkForFiltersLoaded)
+      {
+        this.attemptRetry();
+      }
+      else
+      {
+        this.invokeFailureCallback("GeckoRequest error");
+      }
+    }
+
+    @Override
+    public void onResponse(final NativeJSObject jsObject)
+    {
+      if (this.checkForFiltersLoaded)
+      {
+        if (getBooleanFromJsObject(jsObject, "success", false)
+            && getBooleanFromJsObject(jsObject, "value", false))
+        {
+          GeckoAppShell.sendRequestToGecko(this.cloneForRequest());
+        }
+        else
+        {
+          this.attemptRetry();
+        }
+      }
+      else
+      {
+        if (getBooleanFromJsObject(jsObject, "success", false))
+        {
+          this.invokeSuccessCallback(jsObject);
+        }
+        else
+        {
+          this.invokeFailureCallback(jsObject);
+        }
+      }
+    }
+  }
+}
