Merge mozilla-central into mozilla-inbound to fix B2G builds
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 09 Apr 2012 09:53:35 -0400
changeset 94558 ced90280c7c16b0249924e03223bc5a5d9261d4f
parent 94557 1721afdbf0c3796bc1870e1526f0f2eac204a1fa (current diff)
parent 94496 9ca66ce2672f906ac678d633a6ac6fd9d13c918a (diff)
child 94559 dc8f2d891d8fbb9789ff5311dcb4fb1e75b75ef2
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central into mozilla-inbound to fix B2G builds
new file mode 100644
--- /dev/null
+++ b/dom/wifi/WifiWorker.js
@@ -0,0 +1,1697 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const DEBUG = true; // set to false to suppress debug messages
+
+const WIFIWORKER_CONTRACTID = "@mozilla.org/wifi/worker;1";
+const WIFIWORKER_CID        = Components.ID("{a14e8977-d259-433a-a88d-58dd44657e5b}");
+
+const WIFIWORKER_WORKER     = "resource://gre/modules/wifi_worker.js";
+
+// A note about errors and error handling in this file:
+// The libraries that we use in this file are intended for C code. For
+// C code, it is natural to return -1 for errors and 0 for success.
+// Therefore, the code that interacts directly with the worker uses this
+// convention (note: command functions do get boolean results since the
+// command always succeeds and we do a string/boolean check for the
+// expected results).
+var WifiManager = (function() {
+  function getSdkVersion() {
+    Cu.import("resource://gre/modules/ctypes.jsm");
+    try {
+      let cutils = ctypes.open("libcutils.so");
+      let cbuf = ctypes.char.array(4096)();
+      let c_property_get = cutils.declare("property_get", ctypes.default_abi,
+                                          ctypes.int,       // return value: length
+                                          ctypes.char.ptr,  // key
+                                          ctypes.char.ptr,  // value
+                                          ctypes.char.ptr); // default
+      let property_get = function (key, defaultValue) {
+        if (defaultValue === undefined) {
+          defaultValue = null;
+        }
+        c_property_get(key, cbuf, defaultValue);
+        return cbuf.readString();
+      }
+      return parseInt(property_get("ro.build.version.sdk"));
+    } catch(e) {
+      // Eat it.  Hopefully we're on a non-Gonk system ...
+      // 
+      // XXX we should check that
+      return 0;
+    }
+  }
+
+  let sdkVersion = getSdkVersion();
+
+  var controlWorker = new ChromeWorker(WIFIWORKER_WORKER);
+  var eventWorker = new ChromeWorker(WIFIWORKER_WORKER);
+
+  // Callbacks to invoke when a reply arrives from the controlWorker.
+  var controlCallbacks = Object.create(null);
+  var idgen = 0;
+
+  function controlMessage(obj, callback) {
+    var id = idgen++;
+    obj.id = id;
+    if (callback)
+      controlCallbacks[id] = callback;
+    controlWorker.postMessage(obj);
+  }
+
+  function onerror(e) {
+    // It is very important to call preventDefault on the event here.
+    // If an exception is thrown on the worker, it bubbles out to the
+    // component that created it. If that component doesn't have an
+    // onerror handler, the worker will try to call the error reporter
+    // on the context it was created on. However, That doesn't work
+    // for component contexts and can result in crashes. This onerror
+    // handler has to make sure that it calls preventDefault on the
+    // incoming event.
+    e.preventDefault();
+
+    var worker = (this === controlWorker) ? "control" : "event";
+
+    debug("Got an error from the " + worker + " worker: " + e.filename +
+          ":" + e.lineno + ": " + e.message + "\n");
+  }
+
+  controlWorker.onerror = onerror;
+  eventWorker.onerror = onerror;
+
+  controlWorker.onmessage = function(e) {
+    var data = e.data;
+    var id = data.id;
+    var callback = controlCallbacks[id];
+    if (callback) {
+      callback(data);
+      delete controlCallbacks[id];
+    }
+  };
+
+  // Polling the status worker
+  var recvErrors = 0;
+  eventWorker.onmessage = function(e) {
+    // process the event and tell the event worker to listen for more events
+    if (handleEvent(e.data.event))
+      waitForEvent();
+  };
+
+  function waitForEvent() {
+    eventWorker.postMessage({ cmd: "wait_for_event" });
+  }
+
+  // Commands to the control worker
+
+  function voidControlMessage(cmd, callback) {
+    controlMessage({ cmd: cmd }, function (data) {
+      callback(data.status);
+    });
+  }
+
+  function loadDriver(callback) {
+    voidControlMessage("load_driver", callback);
+  }
+
+  function unloadDriver(callback) {
+    voidControlMessage("unload_driver", callback);
+  }
+
+  function startSupplicant(callback) {
+    voidControlMessage("start_supplicant", callback);
+  }
+
+  function terminateSupplicant(callback) {
+    doBooleanCommand("TERMINATE", "OK", callback);
+  }
+
+  function stopSupplicant(callback) {
+    voidControlMessage("stop_supplicant", callback);
+  }
+
+  function connectToSupplicant(callback) {
+    voidControlMessage("connect_to_supplicant", callback);
+  }
+
+  function closeSupplicantConnection(callback) {
+    voidControlMessage("close_supplicant_connection", callback);
+  }
+
+  function doCommand(request, callback) {
+    controlMessage({ cmd: "command", request: request }, callback);
+  }
+
+  function doIntCommand(request, callback) {
+    doCommand(request, function(data) {
+      callback(data.status ? -1 : (data.reply|0));
+    });
+  }
+
+  function doBooleanCommand(request, expected, callback) {
+    doCommand(request, function(data) {
+      callback(data.status ? false : (data.reply == expected));
+    });
+  }
+
+  function doStringCommand(request, callback) {
+    doCommand(request, function(data) {
+      callback(data.status ? null : data.reply);
+    });
+  }
+
+  function listNetworksCommand(callback) {
+    doStringCommand("LIST_NETWORKS", callback);
+  }
+
+  function addNetworkCommand(callback) {
+    doIntCommand("ADD_NETWORK", callback);
+  }
+
+  function setNetworkVariableCommand(netId, name, value, callback) {
+    doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value, "OK", callback);
+  }
+
+  function getNetworkVariableCommand(netId, name, callback) {
+    doStringCommand("GET_NETWORK " + netId + " " + name, callback);
+  }
+
+  function removeNetworkCommand(netId, callback) {
+    doBooleanCommand("REMOVE_NETWORK " + netId, "OK", callback);
+  }
+
+  function enableNetworkCommand(netId, disableOthers, callback) {
+    doBooleanCommand((disableOthers ? "SELECT_NETWORK " : "ENABLE_NETWORK ") + netId, "OK", callback);
+  }
+
+  function disableNetworkCommand(netId, callback) {
+    doBooleanCommand("DISABLE_NETWORK " + netId, "OK", callback);
+  }
+
+  function statusCommand(callback) {
+    doStringCommand("STATUS", callback);
+  }
+
+  function pingCommand(callback) {
+    doBooleanCommand("PING", "PONG", callback);
+  }
+
+  function scanResultsCommand(callback) {
+    doStringCommand("SCAN_RESULTS", callback);
+  }
+
+  function disconnectCommand(callback) {
+    doBooleanCommand("DISCONNECT", "OK", callback);
+  }
+
+  function reconnectCommand(callback) {
+    doBooleanCommand("RECONNECT", "OK", callback);
+  }
+
+  function reassociateCommand(callback) {
+    doBooleanCommand("REASSOCIATE", "OK", callback);
+  }
+
+  var scanModeActive = false;
+
+  function doSetScanModeCommand(setActive, callback) {
+    doBooleanCommand(setActive ? "DRIVER SCAN-ACTIVE" : "DRIVER SCAN-PASSIVE", "OK", callback);
+  }
+
+  function scanCommand(forceActive, callback) {
+    if (forceActive && !scanModeActive) {
+      doSetScanModeCommand(true, function(ok) {
+        ok && doBooleanCommand("SCAN", "OK", function(ok) {
+          ok && doSetScanModeCommand(false, callback);
+        });
+      });
+      return;
+    }
+    doBooleanCommand("SCAN", "OK", callback);
+  }
+
+  function setScanModeCommand(setActive, callback) {
+    scanModeActive = setActive;
+    doSetScanModeCommand(setActive, callback);
+  }
+
+  function startDriverCommand(callback) {
+    doBooleanCommand("DRIVER START", "OK");
+  }
+
+  function stopDriverCommand(callback) {
+    doBooleanCommand("DRIVER STOP", "OK");
+  }
+
+  function startPacketFiltering(callback) {
+    doBooleanCommand("DRIVER RXFILTER-ADD 0", "OK", function(ok) {
+      ok && doBooleanCommand("DRIVER RXFILTER-ADD 1", "OK", function(ok) {
+        ok && doBooleanCommand("DRIVER RXFILTER-ADD 3", "OK", function(ok) {
+          ok && doBooleanCommand("DRIVER RXFILTER-START", "OK", callback)
+        });
+      });
+    });
+  }
+
+  function stopPacketFiltering(callback) {
+    doBooleanCommand("DRIVER RXFILTER-STOP", "OK", function(ok) {
+      ok && doBooleanCommand("DRIVER RXFILTER-REMOVE 3", "OK", function(ok) {
+        ok && doBooleanCommand("DRIVER RXFILTER-REMOVE 1", "OK", function(ok) {
+          ok && doBooleanCommand("DRIVER RXFILTER-REMOVE 0", "OK", callback)
+        });
+      });
+    });
+  }
+
+  function doGetRssiCommand(cmd, callback) {
+    doCommand(cmd, function(data) {
+      var rssi = -200;
+
+      if (!data.status) {
+        // If we are associating, the reply is "OK".
+        var reply = data.reply;
+        if (reply != "OK") {
+          // Format is: <SSID> rssi XX". SSID can contain spaces.
+          var offset = reply.lastIndexOf("rssi ");
+          if (offset !== -1)
+            rssi = reply.substr(offset + 5) | 0;
+        }
+      }
+      callback(rssi);
+    });
+  }
+
+  function getRssiCommand(callback) {
+    doGetRssiCommand("DRIVER RSSI", callback);
+  }
+
+  function getRssiApproxCommand(callback) {
+    doGetRssiCommand("DRIVER RSSI-APPROX", callback);
+  }
+
+  function getLinkSpeedCommand(callback) {
+    doStringCommand("DRIVER LINKSPEED", function(reply) {
+      if (reply)
+        reply = reply.split(" ")[1] | 0; // Format: LinkSpeed XX
+      callback(reply);
+    });
+  }
+
+  function getMacAddressCommand(callback) {
+    doStringCommand("DRIVER MACADDR", function(reply) {
+      if (reply)
+        reply = reply.split(" ")[2]; // Format: Macaddr = XX.XX.XX.XX.XX.XX
+      callback(reply);
+    });
+  }
+
+  function setPowerModeCommand(mode, callback) {
+    doBooleanCommand("DRIVER POWERMODE " + mode, "OK", callback);
+  }
+
+  function getPowerModeCommand(callback) {
+    doStringCommand("DRIVER GETPOWER", function(reply) {
+      if (reply)
+        reply = (reply.split()[2]|0); // Format: powermode = XX
+      callback(reply);
+    });
+  }
+
+  function setNumAllowedChannelsCommand(numChannels, callback) {
+    doBooleanCommand("DRIVER SCAN-CHANNELS " + numChannels, "OK", callback);
+  }
+
+  function getNumAllowedChannelsCommand(callback) {
+    doStringCommand("DRIVER SCAN-CHANNELS", function(reply) {
+      if (reply)
+        reply = (reply.split()[2]|0); // Format: Scan-Channels = X
+      callback(reply);
+    });
+  }
+
+  function setBluetoothCoexistenceModeCommand(mode, callback) {
+    doBooleanCommand("DRIVER BTCOEXMODE " + mode, "OK", callback);
+  }
+
+  function setBluetoothCoexistenceScanModeCommand(mode, callback) {
+    doBooleanCommand("DRIVER BTCOEXSCAN-" + (mode ? "START" : "STOP"), "OK", callback);
+  }
+
+  function saveConfigCommand(callback) {
+    // Make sure we never write out a value for AP_SCAN other than 1
+    doBooleanCommand("AP_SCAN 1", "OK", function(ok) {
+      doBooleanCommand("SAVE_CONFIG", "OK", callback);
+    });
+  }
+
+  function reloadConfigCommand(callback) {
+    doBooleanCommand("RECONFIGURE", "OK", callback);
+  }
+
+  function setScanResultHandlingCommand(mode, callback) {
+    doBooleanCommand("AP_SCAN " + mode, "OK", callback);
+  }
+
+  function addToBlacklistCommand(bssid, callback) {
+    doBooleanCommand("BLACKLIST " + bssid, "OK", callback);
+  }
+
+  function clearBlacklistCommand(callback) {
+    doBooleanCommand("BLACKLIST clear", "OK", callback);
+  }
+
+  function setSuspendOptimizationsCommand(enabled, callback) {
+    doBooleanCommand("DRIVER SETSUSPENDOPT " + (enabled ? 0 : 1), "OK", callback);
+  }
+
+  function getProperty(key, defaultValue, callback) {
+    controlMessage({ cmd: "property_get", key: key, defaultValue: defaultValue }, function(data) {
+      callback(data.status < 0 ? null : data.value);
+    });
+  }
+
+  function setProperty(key, value, callback) {
+    controlMessage({ cmd: "property_set", key: key, value: value }, function(data) {
+      callback(!data.status);
+    });
+  }
+
+  function enableInterface(ifname, callback) {
+    controlMessage({ cmd: "ifc_enable", ifname: ifname }, function(data) {
+      callback(!data.status);
+    });
+  }
+
+  function disableInterface(ifname, callback) {
+    controlMessage({ cmd: "ifc_disable", ifname: ifname }, function(data) {
+      callback(!data.status);
+    });
+  }
+
+  function addHostRoute(ifname, route, callback) {
+    controlMessage({ cmd: "ifc_add_host_route", ifname: ifname, route: route }, function(data) {
+      callback(!data.status);
+    });
+  }
+
+  function removeHostRoutes(ifname, callback) {
+    controlMessage({ cmd: "ifc_remove_host_routes", ifname: ifname }, function(data) {
+      callback(!data.status);
+    });
+  }
+
+  function setDefaultRoute(ifname, route, callback) {
+    controlMessage({ cmd: "ifc_set_default_route", ifname: ifname, route: route }, function(data) {
+      callback(!data.status);
+    });
+  }
+
+  function getDefaultRoute(ifname, callback) {
+    controlMessage({ cmd: "ifc_get_default_route", ifname: ifname }, function(data) {
+      callback(!data.route);
+    });
+  }
+
+  function removeDefaultRoute(ifname, callback) {
+    controlMessage({ cmd: "ifc_remove_default_route", ifname: ifname }, function(data) {
+      callback(!data.status);
+    });
+  }
+
+  function resetConnections(ifname, callback) {
+    controlMessage({ cmd: "ifc_reset_connections", ifname: ifname }, function(data) {
+      callback(!data.status);
+    });
+  }
+
+  var dhcpInfo = null;
+  function runDhcp(ifname, callback) {
+    controlMessage({ cmd: "dhcp_do_request", ifname: ifname }, function(data) {
+      dhcpInfo = data.status ? null : data;
+      notify("dhcpconnected", { info: dhcpInfo });
+      callback(data.status ? null : data);
+    });
+  }
+
+  function stopDhcp(ifname, callback) {
+    controlMessage({ cmd: "dhcp_stop", ifname: ifname }, function(data) {
+      if (!data.status)
+        dhcpInfo = null;
+      notify("dhcplost");
+      callback(!data.status);
+    });
+  }
+
+  function releaseDhcpLease(ifname, callback) {
+    controlMessage({ cmd: "dhcp_release_lease", ifname: ifname }, function(data) {
+      if (!data.status)
+        dhcpInfo = null;
+      notify("dhcplost");
+      callback(!data.status);
+    });
+  }
+
+  function getDhcpError(callback) {
+    controlMessage({ cmd: "dhcp_get_errmsg" }, function(data) {
+      callback(data.error);
+    });
+  }
+
+  function configureInterface(ifname, ipaddr, mask, gateway, dns1, dns2, callback) {
+    controlMessage({ cmd: "ifc_configure", ifname: ifname,
+                     ipaddr: ipaddr, mask: mask, gateway: gateway,
+                     dns1: dns1, dns2: dns2}, function(data) {
+      callback(!data.status);
+    });
+  }
+
+  function runDhcpRenew(ifname, callback) {
+    controlMessage({ cmd: "dhcp_do_request", ifname: ifname }, function(data) {
+      if (!data.status)
+        dhcpInfo = data;
+      callback(data.status ? null : data);
+    });
+  }
+
+  var manager = {};
+
+  var suppressEvents = false;
+  function notify(eventName, eventObject) {
+    if (suppressEvents)
+      return;
+    var handler = manager["on" + eventName];
+    if (handler) {
+      if (!eventObject)
+        eventObject = ({});
+      handler.call(eventObject);
+    }
+  }
+
+  function notifyStateChange(fields) {
+    fields.prevState = manager.state;
+    manager.state = fields.state;
+
+    // If we got disconnected, kill the DHCP client in preparation for
+    // reconnection.
+    if (fields.state === "DISCONNECTED" && dhcpInfo)
+      stopDhcp(manager.ifname, function() {});
+
+    notify("statechange", fields);
+  }
+
+  function parseStatus(status, reconnected) {
+    if (status === null) {
+      debug("Unable to get wpa supplicant's status");
+      return;
+    }
+
+    var ssid;
+    var bssid;
+    var state;
+    var ip_address;
+    var id;
+
+    var lines = status.split("\n");
+    for (let i = 0; i < lines.length; ++i) {
+      let [key, value] = lines[i].split("=");
+      switch (key) {
+        case "wpa_state":
+          state = value;
+          break;
+        case "ssid":
+          ssid = value;
+          break;
+        case "bssid":
+          bssid = value;
+          break;
+        case "ip_address":
+          ip_address = value;
+          break;
+        case "id":
+          id = value;
+          break;
+      }
+    }
+
+    if (bssid && ssid) {
+      manager.connectionInfo.bssid = bssid;
+      manager.connectionInfo.ssid = ssid;
+      manager.connectionInfo.id = id;
+    }
+
+    if (ip_address)
+      dhcpInfo = { ip_address: ip_address };
+
+    notifyStateChange({ state: state, fromStatus: true });
+    if (state === "COMPLETED")
+      onconnected(reconnected);
+  }
+
+  // try to connect to the supplicant
+  var connectTries = 0;
+  var retryTimer = null;
+  function connectCallback(ok) {
+    if (ok === 0) {
+      // Tell the event worker to start waiting for events.
+      retryTimer = null;
+      didConnectSupplicant(false, function(){});
+      return;
+    }
+    if (connectTries++ < 3) {
+      // try again in 5 seconds
+      if (!retryTimer)
+        retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+      retryTimer.initWithCallback(function(timer) {
+          connectToSupplicant(connectCallback);
+        }, 5000, Ci.nsITimer.TYPE_ONE_SHOT);
+      return;
+    }
+
+    retryTimer = null;
+    notify("supplicantlost");
+  }
+
+  manager.start = function() {
+    debug("detected SDK version " + sdkVersion);
+
+    // If we reconnected to an already-running supplicant, then manager.state
+    // will have already been updated to the supplicant's state. Otherwise, we
+    // started the supplicant ourselves and need to connect.
+    if (manager.state === "UNINITIALIZED")
+      connectToSupplicant(connectCallback);
+  }
+
+  function dhcpAfterConnect() {
+    runDhcp(manager.ifname, function (data) {
+      if (!data) {
+        debug("DHCP failed to run");
+        return;
+      }
+      setProperty("net.dns1", ipToString(data.dns1), function(ok) {
+        if (!ok) {
+          debug("Unable to set net.dns1");
+          return;
+        }
+        setProperty("net.dns2", ipToString(data.dns2), function(ok) {
+          if (!ok) {
+            debug("Unable to set net.dns2");
+            return;
+          }
+          getProperty("net.dnschange", "0", function(value) {
+            if (value === null) {
+              debug("Unable to get net.dnschange");
+              return;
+            }
+            setProperty("net.dnschange", String(Number(value) + 1), function(ok) {
+              if (!ok)
+                debug("Unable to set net.dnschange");
+            });
+          });
+        });
+      });
+    });
+  }
+
+  function onconnected(reconnected) {
+    if (!reconnected) {
+      dhcpAfterConnect();
+      return;
+    }
+
+    // We're in the process of reconnecting to a pre-existing wpa_supplicant.
+    // Check to see if there was already a DHCP process:
+    getProperty("init.svc.dhcpcd_" + manager.ifname, "stopped", function(value) {
+      if (value === "running") {
+        notify("dhcpconnected");
+        return;
+      }
+
+      // Some phones use a different property name for the dhcpcd daemon.
+      getProperty("init.svc.dhcpcd", "stopped", function(value) {
+        if (value === "running") {
+          notify("dhcpconnected");
+          return;
+        }
+
+        dhcpAfterConnect();
+      });
+    });
+  }
+
+  var supplicantStatesMap = (sdkVersion >= 15) ?
+    ["DISCONNECTED", "INTERFACE_DISABLED", "INACTIVE", "SCANNING",
+     "AUTHENTICATING", "ASSOCIATING", "ASSOCIATED", "FOUR_WAY_HANDSHAKE",
+     "GROUP_HANDSHAKE", "COMPLETED"]
+    :
+    ["DISCONNECTED", "INACTIVE", "SCANNING", "ASSOCIATING",
+     "ASSOCIATED", "FOUR_WAY_HANDSHAKE", "GROUP_HANDSHAKE",
+     "COMPLETED", "DORMANT", "UNINITIALIZED"];
+
+  var driverEventMap = { STOPPED: "driverstopped", STARTED: "driverstarted", HANGED: "driverhung" };
+
+  // handle events sent to us by the event worker
+  function handleEvent(event) {
+    debug("Event coming in: " + event);
+    if (event.indexOf("CTRL-EVENT-") !== 0) {
+      if (event.indexOf("WPA:") == 0 &&
+          event.indexOf("pre-shared key may be incorrect") != -1) {
+        notify("passwordmaybeincorrect");
+      }
+
+      // This is ugly, but we need to grab the SSID here. While we're at it,
+      // we grab the BSSID as well.
+      var match = /Trying to associate with ([^ ]+) \(SSID='([^']+)' freq=\d+ MHz\)/.exec(event);
+      if (match) {
+        debug("Matched: " + match[1] + " and " + match[2]);
+        manager.connectionInfo.bssid = match[1];
+        manager.connectionInfo.ssid = match[2];
+      }
+      return true;
+    }
+
+    var space = event.indexOf(" ");
+    var eventData = event.substr(0, space + 1);
+    if (eventData.indexOf("CTRL-EVENT-STATE-CHANGE") === 0) {
+      // Parse the event data
+      var fields = {};
+      var tokens = event.substr(space + 1).split(" ");
+      for (var n = 0; n < tokens.length; ++n) {
+        var kv = tokens[n].split("=");
+        if (kv.length === 2)
+          fields[kv[0]] = kv[1];
+      }
+      if (!("state" in fields))
+        return true;
+      fields.state = supplicantStatesMap[fields.state];
+
+      // The BSSID field is only valid in the ASSOCIATING and ASSOCIATED
+      // states.
+      if (fields.state === "ASSOCIATING" || fields.state == "ASSOCIATED")
+        manager.connectionInfo.bssid = fields.BSSID;
+      notifyStateChange(fields);
+      return true;
+    }
+    if (eventData.indexOf("CTRL-EVENT-DRIVER-STATE") === 0) {
+      var handlerName = driverEventMap[eventData];
+      if (handlerName)
+        notify(handlerName);
+      return true;
+    }
+    if (eventData.indexOf("CTRL-EVENT-TERMINATING") === 0) {
+      // If the monitor socket is closed, we have already stopped the
+      // supplicant and we can stop waiting for more events and
+      // simply exit here (we don't have to notify).
+      if (eventData.indexOf("connection closed") !== -1)
+        return false;
+
+      // As long we haven't seen too many recv errors yet, we
+      // will keep going for a bit longer
+      if (eventData.indexOf("recv error") !== -1 && ++recvErrors < 10)
+        return true;
+
+      notify("supplicantlost");
+      return false;
+    }
+    if (eventData.indexOf("CTRL-EVENT-DISCONNECTED") === 0) {
+      notifyStateChange({ state: "DISCONNECTED" });
+      manager.connectionInfo.bssid = null;
+      manager.connectionInfo.ssid = null;
+      return true;
+    }
+    if (eventData.indexOf("CTRL-EVENT-CONNECTED") === 0) {
+      // Format: CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]
+      var bssid = eventData.split(" ")[4];
+      var id = eventData.substr(eventData.indexOf("id=")).split(" ")[0];
+      notifyStateChange({ state: "CONNECTED", BSSID: bssid, id: id });
+      onconnected(false);
+      return true;
+    }
+    if (eventData.indexOf("CTRL-EVENT-SCAN-RESULTS") === 0) {
+      debug("Notifying of scan results available");
+      notify("scanresultsavailable");
+      return true;
+    }
+    // unknown event
+    return true;
+  }
+
+  const SUPP_PROP = "init.svc.wpa_supplicant";
+  function killSupplicant(callback) {
+    // It is interesting to note that this function does exactly what
+    // wifi_stop_supplicant does. Unforunately, on the Galaxy S2, Samsung
+    // changed that function in a way that means that it doesn't recognize
+    // wpa_supplicant as already running. Therefore, we have to roll our own
+    // version here.
+    var count = 0;
+    var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    function tick() {
+      getProperty(SUPP_PROP, "stopped", function (result) {
+        if (result === null) {
+          callback();
+          return;
+        }
+        if (result === "stopped" || ++count >= 5) {
+          // Either we succeeded or ran out of time.
+          timer = null;
+          callback();
+          return;
+        }
+
+        // Else it's still running, continue waiting.
+        timer.initWithCallback(tick, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
+      });
+    }
+
+    setProperty("ctl.stop", "wpa_supplicant", tick);
+  }
+
+  function didConnectSupplicant(reconnected, callback) {
+    waitForEvent();
+    notify("supplicantconnection");
+
+    // Load up the supplicant state.
+    statusCommand(function(status) {
+      parseStatus(status, reconnected);
+      callback();
+    });
+  }
+
+  function prepareForStartup(callback) {
+    // First, check to see if there's a wpa_supplicant running that we can
+    // connect to.
+    getProperty(SUPP_PROP, "stopped", function (value) {
+      if (value !== "running") {
+        stopDhcp(manager.ifname, function() { callback(false) });
+        return;
+      }
+
+      // It's running, try to reconnect to it.
+      connectToSupplicant(function (retval) {
+        if (retval === 0) {
+          // Successfully reconnected! Don't do anything else.
+          debug("Successfully connected!");
+
+          // It is important that we call parseStatus (in
+          // didConnectSupplicant) before calling the callback here.
+          // Otherwise, WifiManager.start will reconnect to it.
+          didConnectSupplicant(true, function() { callback(true) });
+          return;
+        }
+
+        debug("Didn't connect, trying other method.");
+        suppressEvents = true;
+        stopDhcp(manager.ifname, function() {
+          // Ignore any errors.
+          killSupplicant(function() {
+            suppressEvents = false;
+            callback(false);
+          });
+        });
+      });
+    });
+  }
+
+  // Initial state
+  manager.state = "UNINITIALIZED";
+  manager.connectionInfo = { ssid: null, bssid: null, id: -1 };
+  manager.enabled = true;
+
+  // Public interface of the wifi service
+  manager.setWifiEnabled = function(enable, callback) {
+    if ((enable && manager.state !== "UNINITIALIZED") ||
+        (!enable && manager.state === "UNINITIALIZED")) {
+      return;
+    }
+
+    if (enable) {
+      // Kill any existing connections if necessary.
+      getProperty("wifi.interface", "tiwlan0", function (ifname) {
+        if (!ifname) {
+          callback(-1);
+          return;
+        }
+        manager.ifname = ifname;
+
+        prepareForStartup(function(already_connected) {
+          if (already_connected) {
+            callback(0);
+            return;
+          }
+
+          loadDriver(function (status) {
+            if (status < 0) {
+              callback(status);
+              return;
+            }
+            startSupplicant(function (status) {
+              if (status < 0) {
+                callback(status);
+                return;
+              }
+              enableInterface(ifname, function (ok) {
+                callback(ok ? 0 : -1);
+              });
+            });
+          });
+        });
+      });
+    } else {
+      // Note these following calls ignore errors. If we fail to kill the
+      // supplicant gracefully, then we need to continue telling it to die
+      // until it does.
+      terminateSupplicant(function (ok) {
+        stopSupplicant(function (status) {
+          manager.state = "UNINITIALIZED";
+          closeSupplicantConnection(function () {
+            disableInterface(manager.ifname, function (ok) {
+              unloadDriver(callback);
+            });
+          });
+        });
+      });
+    }
+  }
+
+  manager.disconnect = disconnectCommand;
+  manager.reconnect = reconnectCommand;
+  manager.reassociate = reassociateCommand;
+
+  var networkConfigurationFields = [
+    "ssid", "bssid", "psk", "wep_key0", "wep_key1", "wep_key2", "wep_key3",
+    "wep_tx_keyidx", "priority", "key_mgmt", "scan_ssid", "disabled",
+    "identity", "password", "auth_alg"
+  ];
+
+  manager.getNetworkConfiguration = function(config, callback) {
+    var netId = config.netId;
+    var done = 0;
+    for (var n = 0; n < networkConfigurationFields.length; ++n) {
+      let fieldName = networkConfigurationFields[n];
+      getNetworkVariableCommand(netId, fieldName, function(value) {
+        if (value !== null)
+          config[fieldName] = value;
+        if (++done == networkConfigurationFields.length)
+          callback(config);
+      });
+    }
+  }
+  manager.setNetworkConfiguration = function(config, callback) {
+    var netId = config.netId;
+    var done = 0;
+    var errors = 0;
+    for (var n = 0; n < networkConfigurationFields.length; ++n) {
+      let fieldName = networkConfigurationFields[n];
+      if (!(fieldName in config) ||
+          // These fields are special: We can't retrieve them from the
+          // supplicant, and often we have a star in our config. In that case,
+          // we need to avoid overwriting the correct password with a *.
+          (fieldName === "password" ||
+           fieldName === "wep_key0" ||
+           fieldName === "psk") &&
+          config[fieldName] === '*') {
+        ++done;
+      } else {
+        setNetworkVariableCommand(netId, fieldName, config[fieldName], function(ok) {
+          if (!ok)
+            ++errors;
+          if (++done == networkConfigurationFields.length)
+            callback(errors == 0);
+        });
+      }
+    }
+    // If config didn't contain any of the fields we want, don't lose the error callback
+    if (done == networkConfigurationFields.length)
+      callback(false);
+  }
+  manager.getConfiguredNetworks = function(callback) {
+    listNetworksCommand(function (reply) {
+      var networks = Object.create(null);
+      var lines = reply.split("\n");
+      if (lines.length === 1) {
+        // We need to make sure we call the callback even if there are no
+        // configured networks.
+        callback(networks);
+        return;
+      }
+
+      var done = 0;
+      var errors = 0;
+      for (var n = 1; n < lines.length; ++n) {
+        var result = lines[n].split("\t");
+        var netId = result[0];
+        var config = networks[netId] = { netId: netId };
+        switch (result[3]) {
+        case "[CURRENT]":
+          config.status = "CURRENT";
+          break;
+        case "[DISABLED]":
+          config.status = "DISABLED";
+          break;
+        default:
+          config.status = "ENABLED";
+          break;
+        }
+        manager.getNetworkConfiguration(config, function (ok) {
+            if (!ok)
+              ++errors;
+            if (++done == lines.length - 1) {
+              if (errors) {
+                // If an error occured, delete the new netId
+                removeNetworkCommand(netId, function() {
+                  callback(null);
+                });
+              } else {
+                callback(networks);
+              }
+            }
+        });
+      }
+    });
+  }
+  manager.addNetwork = function(config, callback) {
+    addNetworkCommand(function (netId) {
+      config.netId = netId;
+      manager.setNetworkConfiguration(config, function (ok) {
+        if (!ok) {
+          removeNetworkCommand(netId, function() { callback(false); });
+          return;
+        }
+
+        callback(ok);
+      });
+    });
+  }
+  manager.updateNetwork = function(config, callback) {
+    manager.setNetworkConfiguration(config, callback);
+  }
+  manager.removeNetwork = function(netId, callback) {
+    removeNetworkCommand(netId, callback);
+  }
+
+  function ipToString(n) {
+    return String((n >>  0) & 0xFF) + "." +
+                 ((n >>  8) & 0xFF) + "." +
+                 ((n >> 16) & 0xFF) + "." +
+                 ((n >> 24) & 0xFF);
+  }
+
+  manager.saveConfig = function(callback) {
+    saveConfigCommand(callback);
+  }
+  manager.enableNetwork = function(netId, disableOthers, callback) {
+    enableNetworkCommand(netId, disableOthers, callback);
+  }
+  manager.disableNetwork = function(netId, callback) {
+    disableNetworkCommand(netId, callback);
+  }
+  manager.getMacAddress = getMacAddressCommand;
+  manager.getScanResults = scanResultsCommand;
+  manager.setScanMode = function(mode, callback) {
+    setScanModeCommand(mode === "active", callback);
+  }
+  manager.scan = scanCommand;
+  manager.getRssiApprox = getRssiApproxCommand;
+  manager.getLinkSpeed = getLinkSpeedCommand;
+  return manager;
+})();
+
+function getKeyManagement(flags) {
+  var types = [];
+  if (!flags)
+    return types;
+
+  if (/\[WPA2?-PSK/.test(flags))
+    types.push("WPA-PSK");
+  if (/\[WPA2?-EAP/.test(flags))
+    types.push("WPA-EAP");
+  if (/\[WEP/.test(flags))
+    types.push("WEP");
+  return types;
+}
+
+// These constants shamelessly ripped from WifiManager.java
+// strength is the value returned by scan_results. It is nominally in dB. We
+// transform it into a percentage for clients looking to simply show a
+// relative indication of the strength of a network.
+const MIN_RSSI = -100;
+const MAX_RSSI = -55;
+
+function calculateSignal(strength) {
+  // Some wifi drivers represent their signal strengths as 8-bit integers, so
+  // in order to avoid negative numbers, they add 256 to the actual values.
+  // While we don't *know* that this is the case here, we make an educated
+  // guess.
+  if (strength > 0)
+    strength -= 256;
+
+  if (strength <= MIN_RSSI)
+    return 0;
+  if (strength >= MAX_RSSI)
+    return 100;
+  return Math.floor(((strength - MIN_RSSI) / (MAX_RSSI - MIN_RSSI)) * 100);
+}
+
+function ScanResult(ssid, bssid, flags, signal) {
+  this.ssid = ssid;
+  this.bssid = bssid;
+  this.capabilities = getKeyManagement(flags);
+  this.signal = calculateSignal(Number(signal));
+}
+
+function quote(s) {
+  return '"' + s + '"';
+}
+
+function dequote(s) {
+  if (s[0] != '"' || s[s.length - 1] != '"')
+    throw "Invalid argument, not a quoted string: " + s;
+  return s.substr(1, s.length - 2);
+}
+
+function isWepHexKey(s) {
+  if (s.length != 10 && s.length != 26 && s.length != 58)
+    return false;
+  return !/[^a-fA-F0-9]/.test(s);
+}
+
+// TODO Make the difference between a DOM-based network object and our
+// networks objects much clearer.
+let netToDOM;
+let netFromDOM;
+
+function WifiWorker() {
+  var self = this;
+
+  this._mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
+  const messages = ["WifiManager:setEnabled", "WifiManager:getNetworks",
+                    "WifiManager:associate", "WifiManager:getState"];
+
+  messages.forEach((function(msgName) {
+    this._mm.addMessageListener(msgName, this);
+  }).bind(this));
+
+  this.wantScanResults = [];
+
+  this._needToEnableNetworks = false;
+  this._highestPriority = -1;
+
+  // networks is a map from SSID -> a scan result.
+  this.networks = Object.create(null);
+
+  // configuredNetworks is a map from SSID -> our view of a network. It only
+  // lists networks known to the wpa_supplicant. The SSID field (and other
+  // fields) are quoted for ease of use with WifiManager commands.
+  // Note that we don't have to worry about escaping embedded quotes since in
+  // all cases, the supplicant will take the last quotation that we pass it as
+  // the end of the string.
+  this.configuredNetworks = Object.create(null);
+
+  this.currentNetwork = null;
+
+  this._lastConnectionInfo = null;
+  this._connectionInfoTimer = null;
+  this._reconnectOnDisconnect = false;
+
+  // Given a connection status network, takes a network from
+  // self.configuredNetworks and prepares it for the DOM.
+  netToDOM = function(net) {
+    var pub = { ssid: dequote(net.ssid) };
+    if (net.netId)
+      pub.known = true;
+    return pub;
+  };
+
+  netFromDOM = function(net, configured) {
+    // Takes a network from the DOM and makes it suitable for insertion into
+    // self.configuredNetworks (that is calling addNetwork will do the right
+    // thing).
+    // NB: Modifies net in place: safe since we don't share objects between
+    // the dom and the chrome code.
+
+    // Things that are useful for the UI but not to us.
+    delete net.bssid;
+    delete net.signal;
+    delete net.capabilities;
+
+    if (!configured)
+      configured = {};
+
+    net.ssid = quote(net.ssid);
+
+    let wep = false;
+    if ("keyManagement" in net) {
+      if (net.keyManagement === "WEP") {
+        wep = true;
+        net.keyManagement = "NONE";
+      }
+
+      configured.key_mgmt = net.key_mgmt = net.keyManagement; // WPA2-PSK, WPA-PSK, etc.
+      delete net.keyManagement;
+    } else {
+      configured.key_mgmt = net.key_mgmt = "NONE";
+    }
+
+    function checkAssign(name, checkStar) {
+      if (name in net) {
+        let value = net[name];
+        if (!value || (checkStar && value === '*')) {
+          if (name in configured)
+            net[name] = configured[name];
+          else
+            delete net[name];
+        } else {
+          configured[name] = net[name] = quote(value);
+        }
+      }
+    }
+
+    checkAssign("psk", true);
+    checkAssign("identity", false);
+    checkAssign("password", true);
+    if (wep && net.wep && net.wep != '*') {
+      configured.wep_key0 = net.wep_key0 = isWepHexKey(net.wep) ? net.wep : quote(net.wep);
+      configured.auth_alg = net.auth_alg = "OPEN SHARED";
+    }
+
+    return net;
+  };
+
+  WifiManager.onsupplicantconnection = function() {
+    debug("Connected to supplicant");
+    WifiManager.getMacAddress(function (mac) {
+      debug("Got mac: " + mac);
+    });
+
+    self._reloadConfiguredNetworks(function(ok) {
+      // Prime this.networks.
+      if (!ok)
+        return;
+      self.waitForScan(function firstScan() {});
+    });
+  }
+  WifiManager.onsupplicantlost = function() {
+    debug("Supplicant died!");
+  }
+
+  WifiManager.onstatechange = function() {
+    debug("State change: " + this.prevState + " -> " + this.state);
+
+    if (self._connectionInfoTimer &&
+        this.state !== "CONNECTED" &&
+        this.state !== "COMPLETED") {
+      self._stopConnectionInfoTimer();
+    }
+
+    switch (this.state) {
+      case "DORMANT":
+        // The dormant state is a bad state to be in since we won't
+        // automatically connect. Try to knock us out of it. We only
+        // hit this state when we've failed to run DHCP, so trying
+        // again isn't the worst thing we can do. Eventually, we'll
+        // need to detect if we're looping in this state and bail out.
+        WifiManager.reconnect(function(){});
+        break;
+      case "ASSOCIATING":
+        // id has not yet been filled in, so we can only report the ssid and
+        // bssid.
+        self.currentNetwork =
+          { bssid: WifiManager.connectionInfo.bssid,
+            ssid: quote(WifiManager.connectionInfo.ssid) };
+        self._fireEvent("onconnecting", { network: netToDOM(self.currentNetwork) });
+        break;
+      case "ASSOCIATED":
+        self.currentNetwork.netId = this.id;
+        WifiManager.getNetworkConfiguration(self.currentNetwork, function (){});
+        break;
+      case "COMPLETED":
+        // Now that we've successfully completed the connection, re-enable the
+        // rest of our networks.
+        // XXX Need to do this eventually if the user entered an incorrect
+        // password. For now, we require user interaction to break the loop and
+        // select a better network!
+        if (self._needToEnableNetworks) {
+          self._enableAllNetworks();
+          self._needToEnableNetworks = false;
+        }
+
+        // We get the ASSOCIATED event when we've associated but not connected, so
+        // wait until the handshake is complete.
+        if (this.fromStatus) {
+          // In this case, we connected to an already-connected wpa_supplicant,
+          // because of that we need to gather information about the current
+          // network here.
+          self.currentNetwork = { ssid: quote(WifiManager.connectionInfo.ssid),
+                                  known: true }
+          WifiManager.getNetworkConfiguration(self.currentNetwork, function(){});
+        }
+
+        self._startConnectionInfoTimer();
+        self._fireEvent("onassociate", { network: netToDOM(self.currentNetwork) });
+        break;
+      case "CONNECTED":
+        break;
+      case "DISCONNECTED":
+        self._fireEvent("ondisconnect", {});
+        self.currentNetwork = null;
+
+        // We've disconnected from a network because of a call to forgetNetwork.
+        // Reconnect to the next available network (if any).
+        if (self._reconnectOnDisconnect) {
+          self._reconnectOnDisconnect = false;
+          WifiManager.reconnect(function(){});
+        }
+
+        break;
+    }
+  };
+
+  WifiManager.ondhcpconnected = function() {
+    if (this.info)
+      self._fireEvent("onconnect", { network: netToDOM(self.currentNetwork) });
+    else
+      WifiManager.disconnect(function(){});
+  };
+
+  WifiManager.onscanresultsavailable = function() {
+    if (self.wantScanResults.length === 0) {
+      debug("Scan results available, but we don't need them");
+      return;
+    }
+
+    debug("Scan results are available! Asking for them.");
+    WifiManager.getScanResults(function(r) {
+      // Now that we have scan results, there's no more need to continue
+      // scanning. Ignore any errors from this command.
+      WifiManager.setScanMode("inactive", function() {});
+      let lines = r.split("\n");
+      // NB: Skip the header line.
+      self.networks = Object.create(null);
+      for (let i = 1; i < lines.length; ++i) {
+        // bssid / frequency / signal level / flags / ssid
+        var match = /([\S]+)\s+([\S]+)\s+([\S]+)\s+(\[[\S]+\])?\s+(.*)/.exec(lines[i]);
+
+        if (match && match[5]) {
+          let ssid = match[5];
+
+          // If this is the first time that we've seen this SSID in the scan
+          // results, add it to the list along with any other information.
+          // Also, we use the highest signal strength that we see.
+          let network = self.networks[ssid];
+          if (!network) {
+            network = self.networks[ssid] =
+              new ScanResult(ssid, match[1], match[4], match[3]);
+
+            if (ssid in self.configuredNetworks) {
+              let known = self.configuredNetworks[ssid];
+              network.known = true;
+
+              if ("identity" in known && known.identity)
+                network.identity = dequote(known.identity);
+
+              // Note: we don't hand out passwords here! The * marks that there
+              // is a password that we're hiding.
+              if (("psk" in known && known.psk) ||
+                  ("password" in known && known.password) ||
+                  ("wep_key0" in known && known.wep_key0)) {
+                network.password = "*";
+              }
+            }
+          }
+
+          if (network.bssid === WifiManager.connectionInfo.bssid)
+            network.connected = true;
+
+          let signal = calculateSignal(Number(match[3]));
+          if (signal > network.signal)
+            network.signal = signal;
+        } else if (!match) {
+          debug("Match didn't find anything for: " + lines[i]);
+        }
+      }
+
+      self.wantScanResults.forEach(function(callback) { callback(self.networks) });
+      self.wantScanResults = [];
+    });
+  }
+
+  WifiManager.setWifiEnabled(true, function (ok) {
+      if (ok === 0)
+        WifiManager.start();
+      else
+        debug("Couldn't start Wifi");
+    });
+
+  debug("Wifi starting");
+}
+
+WifiWorker.prototype = {
+  classID:   WIFIWORKER_CID,
+  classInfo: XPCOMUtils.generateCI({classID: WIFIWORKER_CID,
+                                    contractID: WIFIWORKER_CONTRACTID,
+                                    classDescription: "WifiWorker",
+                                    interfaces: [Ci.nsIWorkerHolder,
+                                                 Ci.nsIWifi]}),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
+                                         Ci.nsIWifi]),
+
+  // Internal methods.
+  waitForScan: function(callback) {
+    this.wantScanResults.push(callback);
+  },
+
+  // In order to select a specific network, we disable the rest of the
+  // networks known to us. However, in general, we want the supplicant to
+  // connect to which ever network it thinks is best, so when we select the
+  // proper network (or fail to), we need to re-enable the rest.
+  _enableAllNetworks: function() {
+    for each (let net in this.configuredNetworks) {
+      WifiManager.enableNetwork(net.netId, false, function(ok) {
+        net.disabled = ok ? 1 : 0;
+      });
+    }
+  },
+
+  _startConnectionInfoTimer: function() {
+    if (this._connectionInfoTimer)
+      return;
+
+    var self = this;
+    function getConnectionInformation() {
+      WifiManager.getRssiApprox(function(rssi) {
+        // See comments in calculateSignal for information about this.
+        if (rssi > 0)
+          rssi -= 256;
+        if (rssi <= MIN_RSSI)
+          rssi = MIN_RSSI;
+        else if (rssi >= MAX_RSSI)
+          rssi = MAX_RSSI;
+
+        WifiManager.getLinkSpeed(function(linkspeed) {
+          let info = { signalStrength: rssi,
+                       relSignalStrength: calculateSignal(rssi),
+                       linkSpeed: linkspeed };
+          let last = self._lastConnectionInfo;
+
+          // Only fire the event if the link speed changed or the signal
+          // strength changed by more than 10%.
+          function tensPlace(percent) ((percent / 10) | 0)
+
+          if (last && last.linkSpeed === info.linkSpeed &&
+              tensPlace(last.relSignalStrength) === tensPlace(info.relSignalStrength)) {
+            return;
+          }
+
+          self._lastConnectionInfo = info;
+          self._fireEvent("connectionInfoUpdate", info);
+        });
+      });
+    }
+
+    // Prime our _lastConnectionInfo immediately and fire the event at the
+    // same time.
+    getConnectionInformation();
+
+    // Now, set up the timer for regular updates.
+    this._connectionInfoTimer =
+      Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    this._connectionInfoTimer.init(getConnectionInformation, 5000,
+                                   Ci.nsITimer.TYPE_REPEATING_SLACK);
+  },
+
+  _stopConnectionInfoTimer: function() {
+    if (!this._connectionInfoTimer)
+      return;
+
+    this._connectionInfoTimer.cancel();
+    this._connectionInfoTimer = null;
+    this._lastConnectionInfo = null;
+  },
+
+  _reloadConfiguredNetworks: function(callback) {
+    WifiManager.getConfiguredNetworks((function(networks) {
+      if (!networks) {
+        debug("Unable to get configured networks");
+        callback(false);
+        return;
+      }
+
+      this._highestPriority = -1;
+
+      // Convert between netId-based and ssid-based indexing.
+      for (let net in networks) {
+        let network = networks[net];
+        if (!network.ssid) {
+          delete networks[net]; // TODO support these?
+          continue;
+        }
+
+        if (network.priority && network.priority > this._highestPriority)
+          this._highestPriority = network.priority;
+        networks[dequote(network.ssid)] = network;
+        delete networks[net];
+      }
+
+      this.configuredNetworks = networks;
+      callback(true);
+    }).bind(this));
+  },
+
+  // Important side effect: calls WifiManager.saveConfig.
+  _reprioritizeNetworks: function(callback) {
+    // First, sort the networks in orer of their priority.
+    var ordered = Object.getOwnPropertyNames(this.configuredNetworks);
+    let self = this;
+    ordered.sort(function(a, b) {
+      var neta = self.configuredNetworks[a],
+          netb = self.configuredNetworks[b];
+
+      // Sort unsorted networks to the end of the list.
+      if (isNaN(neta.priority))
+        return isNaN(netb.priority) ? 0 : 1;
+      if (isNaN(netb.priority))
+        return -1;
+      return netb.priority - neta.priority;
+    });
+
+    // Skip unsorted networks.
+    let newPriority = 0, i;
+    for (i = ordered.length - 1; i >= 0; --i) {
+      if (!isNaN(this.configuredNetworks[ordered[i]].priority))
+        break;
+    }
+
+    // No networks we care about?
+    if (i < 0) {
+      WifiManager.saveConfig(callback);
+      return;
+    }
+
+    // Now assign priorities from 0 to length, starting with the smallest
+    // priority and heading towards the highest (note the dependency between
+    // total and i here).
+    let done = 0, errors = 0, total = i + 1;
+    for (; i >= 0; --i) {
+      let network = this.configuredNetworks[ordered[i]];
+      network.priority = newPriority++;
+
+      // Note: networkUpdated declared below since it happens logically after
+      // this loop.
+      WifiManager.updateNetwork(network, networkUpdated);
+    }
+
+    function networkUpdated(ok) {
+      if (!ok)
+        ++errors;
+      if (++done === total) {
+        if (errors > 0) {
+          callback(false);
+          return;
+        }
+
+        WifiManager.saveConfig(function(ok) {
+          if (!ok) {
+            callback(false);
+            return;
+          }
+
+          self._reloadConfiguredNetworks(function(ok) {
+            callback(ok);
+          });
+        });
+      }
+    }
+  },
+
+  // nsIWifi
+
+  _fireEvent: function(message, data) {
+    this._mm.sendAsyncMessage("WifiManager:" + message, data);
+  },
+
+  _sendMessage: function(message, success, data, rid, mid) {
+    this._mm.sendAsyncMessage(message + (success ? ":OK" : ":NO"),
+                              { data: data, rid: rid, mid: mid });
+  },
+
+  receiveMessage: function MessageManager_receiveMessage(aMessage) {
+    let msg = aMessage.json;
+    switch (aMessage.name) {
+      case "WifiManager:setEnabled":
+        this.setWifiEnabled(msg.data, msg.rid, msg.mid);
+        break;
+      case "WifiManager:getNetworks":
+        this.getNetworks(msg.rid, msg.mid);
+        break;
+      case "WifiManager:associate":
+        this.associate(msg.data, msg.rid, msg.mid);
+        break;
+      case "WifiManager:forget":
+        this.forget(msg.data, msg.rid, msg.mid);
+        break;
+      case "WifiManager:getState": {
+        let net = this.currentNetwork ? netToDOM(this.currentNetwork) : null;
+        return { network: net,
+                 connectionInfo: this._lastConnectionInfo,
+                 enabled: WifiManager.state !== "UNINITIALIZED", };
+      }
+    }
+  },
+
+  getNetworks: function(rid, mid) {
+    this.waitForScan((function (networks) {
+      this._sendMessage("WifiManager:getNetworks:Return",
+                        networks !== null, networks, rid, mid);
+    }).bind(this));
+    WifiManager.scan(true, function() {});
+  },
+
+  setWifiEnabled: function(enable, rid, mid) {
+    WifiManager.setWifiEnabled(enable, (function (status) {
+      if (enable && status === 0)
+        WifiManager.start();
+      this._sendMessage("WifiManager:setEnabled:Return",
+                        (status === 0), enable, rid, mid);
+    }).bind(this));
+  },
+
+  associate: function(network, rid, mid) {
+    const MAX_PRIORITY = 9999;
+    const message = "WifiManager:associate:Return";
+    let privnet = network;
+    let self = this;
+    function networkReady() {
+      // saveConfig now before we disable most of the other networks.
+      function selectAndConnect() {
+        WifiManager.enableNetwork(privnet.netId, true, function (ok) {
+          if (ok)
+            self._needToEnableNetworks = true;
+          if (WifiManager.state === "DISCONNECTED" ||
+              WifiManager.state === "SCANNING") {
+            WifiManager.reconnect(function (ok) {
+              self._sendMessage(message, ok, ok, rid, mid);
+            });
+          } else {
+            self._sendMessage(message, ok, ok, rid, mid);
+          }
+        });
+      }
+
+      if (self._highestPriority >= MAX_PRIORITY)
+        self._reprioritizeNetworks(selectAndConnect);
+      else
+        WifiManager.saveConfig(selectAndConnect);
+    }
+
+    let ssid = privnet.ssid;
+    let configured;
+
+    if (ssid in this.configuredNetworks)
+      configured = this.configuredNetworks[ssid];
+
+    netFromDOM(privnet, configured);
+
+    privnet.priority = ++this._highestPriority;
+    if (configured) {
+      privnet.netId = configured.netId;
+      WifiManager.updateNetwork(privnet, (function(ok) {
+        if (!ok) {
+          this._sendMessage(message, false, "Network is misconfigured", rid, mid);
+          return;
+        }
+
+        networkReady();
+      }).bind(this));
+    } else {
+      // networkReady, above, calls saveConfig. We want to remember the new
+      // network as being enabled, which isn't the default, so we explicitly
+      // set it to being "enabled" before we add it and save the
+      // configuration.
+      privnet.disabled = 0;
+      WifiManager.addNetwork(privnet, (function(ok) {
+        if (!ok) {
+          this._sendMessage(message, false, "Network is misconfigured", rid, mid);
+          return;
+        }
+
+        this.configuredNetworks[ssid] = privnet;
+        networkReady();
+      }).bind(this));
+    }
+  },
+
+  forget: function(network, rid, mid) {
+    const message = "WifiManager:forget:Return";
+    let ssid = network.ssid;
+    if (!(ssid in this.configuredNetworks)) {
+      this._sendMessage(message, false, "Trying to forget an unknown network", rid, mid);
+      return;
+    }
+
+    let self = this;
+    let configured = this.configuredNetworks[ssid];
+    this._reconnectOnDisconnect = (this._currentNetwork.ssid === ssid);
+    WifiManager.removeNetwork(configured.netId, function(ok) {
+      if (!ok) {
+        self._sendMessage(message, false, "Unable to remove the network", rid, mid);
+        self._reconnectOnDisconnect = false;
+        return;
+      }
+
+      WifiManager.saveConfig(function() {
+        self._reloadConfiguredNetworks(function() {
+          self._sendMessage(message, true, true, rid, mid);
+        });
+      });
+    });
+  },
+
+  // This is a bit ugly, but works. In particular, this depends on the fact
+  // that RadioManager never actually tries to get the worker from us.
+  get worker() { throw "Not implemented"; },
+
+  shutdown: function() {
+    debug("shutting down ...");
+    this.setWifiEnabled(false);
+  }
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiWorker]);
+
+let debug;
+if (DEBUG) {
+  debug = function (s) {
+    dump("-*- WifiWorker component: " + s + "\n");
+  };
+} else {
+  debug = function (s) {};
+}