Merge remote-tracking branch 'origin/kitkat-dev'
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..675f480
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,27 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_CPP_EXTENSION := .cc
+
+# Set up the target identity
+LOCAL_MODULE := libpac
+LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+
+LOCAL_SRC_FILES := \
+  src/proxy_resolver_v8.cc \
+  src/proxy_resolver_js_bindings.cc \
+  src/net_util.cc
+
+LOCAL_CFLAGS += \
+  -Wno-endif-labels \
+  -Wno-import \
+  -Wno-format \
+
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/src external/v8
+
+LOCAL_STATIC_LIBRARIES := libv8
+LOCAL_SHARED_LIBRARIES := libutils libstlport liblog
+
+include external/stlport/libstlport.mk
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dc3504
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MODULE_LICENSE_BSD b/MODULE_LICENSE_BSD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_BSD
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..8dc3504
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,27 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README b/README
new file mode 100644
index 0000000..59852b5
--- /dev/null
+++ b/README
@@ -0,0 +1,7 @@
+This library is based on the proxy_resolver_v8 and other classes taken from 
+chromium project.
+
+Modifications have been made to remove dependencies on chromium utility
+functions and classes.  To make this library accessible on Android, the string 
+utilities have been modified to use stl and the network functions have been 
+modified to use UNIX functions.
diff --git a/src/net_util.cc b/src/net_util.cc
new file mode 100644
index 0000000..5f8c88b
--- /dev/null
+++ b/src/net_util.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+
+#include <fcntl.h>
+#include <netdb.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <string.h>
+
+#include "net_util.h"
+
+namespace net {
+
+#ifndef INET6_ADDRSTRLEN  /* for non IPv6 machines */
+#define INET6_ADDRSTRLEN 46
+#endif
+
+bool ParseIPLiteralToNumber(const std::string& ip_literal,
+                            IPAddressNumber* ip_number) {
+  char buf[sizeof(struct in6_addr)];
+  int size = sizeof(struct in_addr);
+  int mode = AF_INET;
+  if (ip_literal.find(':') != std::string::npos) {
+    mode = AF_INET6;
+    size = sizeof(struct in6_addr);
+  }
+  if (inet_pton(mode, ip_literal.c_str(), buf) != 1) {
+    return false;
+  }
+  ip_number->resize(size);
+  for (int i = 0; i < size; i++) {
+    (*ip_number)[i] = buf[i];
+  }
+  return true;
+}
+
+IPAddressNumber ConvertIPv4NumberToIPv6Number(
+    const IPAddressNumber& ipv4_number) {
+  // IPv4-mapped addresses are formed by:
+  // <80 bits of zeros>  + <16 bits of ones> + <32-bit IPv4 address>.
+  IPAddressNumber ipv6_number;
+  ipv6_number.reserve(16);
+  ipv6_number.insert(ipv6_number.end(), 10, 0);
+  ipv6_number.push_back(0xFF);
+  ipv6_number.push_back(0xFF);
+  ipv6_number.insert(ipv6_number.end(), ipv4_number.begin(), ipv4_number.end());
+  return ipv6_number;
+}
+
+bool ParseCIDRBlock(const std::string& cidr_literal,
+                    IPAddressNumber* ip_number,
+                    size_t* prefix_length_in_bits) {
+  // We expect CIDR notation to match one of these two templates:
+  //   <IPv4-literal> "/" <number of bits>
+  //   <IPv6-literal> "/" <number of bits>
+
+  std::vector<std::string> parts;
+  unsigned int split = cidr_literal.find('/');
+  if (split == std::string::npos)
+    return false;
+  parts.push_back(cidr_literal.substr(0, split));
+  parts.push_back(cidr_literal.substr(split + 1));
+  if (parts[1].find('/') != std::string::npos)
+    return false;
+
+  // Parse the IP address.
+  if (!ParseIPLiteralToNumber(parts[0], ip_number))
+    return false;
+
+  // Parse the prefix length.
+  int number_of_bits = atoi(parts[1].c_str());
+
+  // Make sure the prefix length is in a valid range.
+  if (number_of_bits < 0 ||
+      number_of_bits > static_cast<int>(ip_number->size() * 8))
+    return false;
+
+  *prefix_length_in_bits = static_cast<size_t>(number_of_bits);
+  return true;
+}
+
+bool IPNumberMatchesPrefix(const IPAddressNumber& ip_number,
+                           const IPAddressNumber& ip_prefix,
+                           size_t prefix_length_in_bits) {
+  // Both the input IP address and the prefix IP address should be
+  // either IPv4 or IPv6.
+
+  // In case we have an IPv6 / IPv4 mismatch, convert the IPv4 addresses to
+  // IPv6 addresses in order to do the comparison.
+  if (ip_number.size() != ip_prefix.size()) {
+    if (ip_number.size() == 4) {
+      return IPNumberMatchesPrefix(ConvertIPv4NumberToIPv6Number(ip_number),
+                                   ip_prefix, prefix_length_in_bits);
+    }
+    return IPNumberMatchesPrefix(ip_number,
+                                 ConvertIPv4NumberToIPv6Number(ip_prefix),
+                                 96 + prefix_length_in_bits);
+  }
+
+  // Otherwise we are comparing two IPv4 addresses, or two IPv6 addresses.
+  // Compare all the bytes that fall entirely within the prefix.
+  int num_entire_bytes_in_prefix = prefix_length_in_bits / 8;
+  for (int i = 0; i < num_entire_bytes_in_prefix; ++i) {
+    if (ip_number[i] != ip_prefix[i])
+      return false;
+  }
+
+  // In case the prefix was not a multiple of 8, there will be 1 byte
+  // which is only partially masked.
+  int remaining_bits = prefix_length_in_bits % 8;
+  if (remaining_bits != 0) {
+    unsigned char mask = 0xFF << (8 - remaining_bits);
+    int i = num_entire_bytes_in_prefix;
+    if ((ip_number[i] & mask) != (ip_prefix[i] & mask))
+      return false;
+  }
+
+  return true;
+}
+
+}  // namespace net
diff --git a/src/net_util.h b/src/net_util.h
new file mode 100644
index 0000000..e50008f
--- /dev/null
+++ b/src/net_util.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_BASE_NET_UTIL_H_
+#define NET_BASE_NET_UTIL_H_
+#pragma once
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <ws2tcpip.h>
+#elif defined(OS_POSIX)
+#include <sys/socket.h>
+#endif
+
+#include <list>
+#include <string>
+#include <set>
+#include <vector>
+
+#include <cstdint>
+
+namespace net {
+
+// IPAddressNumber is used to represent an IP address's numeric value as an
+// array of bytes, from most significant to least significant. This is the
+// network byte ordering.
+//
+// IPv4 addresses will have length 4, whereas IPv6 address will have length 16.
+typedef std::vector<unsigned char> IPAddressNumber;
+typedef std::vector<IPAddressNumber> IPAddressList;
+
+// Parses an IP address literal (either IPv4 or IPv6) to its numeric value.
+// Returns true on success and fills |ip_number| with the numeric value.
+bool ParseIPLiteralToNumber(const std::string& ip_literal,
+                            IPAddressNumber* ip_number);
+
+// Parses an IP block specifier from CIDR notation to an
+// (IP address, prefix length) pair. Returns true on success and fills
+// |*ip_number| with the numeric value of the IP address and sets
+// |*prefix_length_in_bits| with the length of the prefix.
+//
+// CIDR notation literals can use either IPv4 or IPv6 literals. Some examples:
+//
+//    10.10.3.1/20
+//    a:b:c::/46
+//    ::1/128
+bool ParseCIDRBlock(const std::string& cidr_literal,
+                    IPAddressNumber* ip_number,
+                    size_t* prefix_length_in_bits);
+
+// Compares an IP address to see if it falls within the specified IP block.
+// Returns true if it does, false otherwise.
+//
+// The IP block is given by (|ip_prefix|, |prefix_length_in_bits|) -- any
+// IP address whose |prefix_length_in_bits| most significant bits match
+// |ip_prefix| will be matched.
+//
+// In cases when an IPv4 address is being compared to an IPv6 address prefix
+// and vice versa, the IPv4 addresses will be converted to IPv4-mapped
+// (IPv6) addresses.
+bool IPNumberMatchesPrefix(const IPAddressNumber& ip_number,
+                           const IPAddressNumber& ip_prefix,
+                           size_t prefix_length_in_bits);
+
+}  // namespace net
+
+#endif  // NET_BASE_NET_UTIL_H_
diff --git a/src/proxy_resolver_js_bindings.cc b/src/proxy_resolver_js_bindings.cc
new file mode 100644
index 0000000..897fde4
--- /dev/null
+++ b/src/proxy_resolver_js_bindings.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "proxy_resolver_js_bindings.h"
+#include "proxy_resolver_v8.h"
+
+#include <netdb.h>
+#include <unistd.h>
+#include <cstddef>
+#include <memory>
+#include <string>
+
+#include "net_util.h"
+
+namespace net {
+
+// ProxyResolverJSBindings implementation.
+class DefaultJSBindings : public ProxyResolverJSBindings {
+ public:
+  DefaultJSBindings() {
+  }
+
+  // Handler for "myIpAddress()".
+  // TODO: Perhaps enumerate the interfaces directly, using
+  // getifaddrs().
+  virtual bool MyIpAddress(std::string* first_ip_address) {
+    return MyIpAddressImpl(first_ip_address);
+  }
+
+  // Handler for "myIpAddressEx()".
+  virtual bool MyIpAddressEx(std::string* ip_address_list) {
+    return MyIpAddressExImpl(ip_address_list);
+  }
+
+  // Handler for "dnsResolve(host)".
+  virtual bool DnsResolve(const std::string& host,
+                          std::string* first_ip_address) {
+    return DnsResolveImpl(host, first_ip_address);
+  }
+
+  // Handler for "dnsResolveEx(host)".
+  virtual bool DnsResolveEx(const std::string& host,
+                            std::string* ip_address_list) {
+    return DnsResolveExImpl(host, ip_address_list);
+  }
+
+ private:
+  bool MyIpAddressImpl(std::string* first_ip_address) {
+    std::string my_hostname = GetHostName();
+    if (my_hostname.empty())
+      return false;
+    return DnsResolveImpl(my_hostname, first_ip_address);
+  }
+
+  bool MyIpAddressExImpl(std::string* ip_address_list) {
+    std::string my_hostname = GetHostName();
+    if (my_hostname.empty())
+      return false;
+    return DnsResolveExImpl(my_hostname, ip_address_list);
+  }
+
+  bool DnsResolveImpl(const std::string& host,
+                      std::string* first_ip_address) {
+    struct hostent* he = gethostbyname(host.c_str());
+
+    if (he == NULL) {
+      return false;
+    }
+    *first_ip_address = std::string(he->h_addr);
+    return true;
+  }
+
+  bool DnsResolveExImpl(const std::string& host,
+                        std::string* ip_address_list) {
+    struct hostent* he = gethostbyname(host.c_str());
+
+    if (he == NULL) {
+      return false;
+    }
+    std::string address_list_str;
+    for (char** addr = &he->h_addr; *addr != NULL; ++addr) {
+      if (!address_list_str.empty())
+        address_list_str += ";";
+      const std::string address_string = std::string(*addr);
+      if (address_string.empty())
+        return false;
+      address_list_str += address_string;
+    }
+    *ip_address_list = std::string(he->h_addr);
+    return true;
+  }
+
+  std::string GetHostName() {
+    char buffer[256];
+    if (gethostname(buffer, 256) != 0) {
+      buffer[0] = '\0';
+    }
+    return std::string(buffer);
+  }
+};
+
+// static
+ProxyResolverJSBindings* ProxyResolverJSBindings::CreateDefault() {
+  return new DefaultJSBindings();
+}
+
+}  // namespace net
diff --git a/src/proxy_resolver_js_bindings.h b/src/proxy_resolver_js_bindings.h
new file mode 100644
index 0000000..9559492
--- /dev/null
+++ b/src/proxy_resolver_js_bindings.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_PROXY_PROXY_RESOLVER_JS_BINDINGS_H_
+#define NET_PROXY_PROXY_RESOLVER_JS_BINDINGS_H_
+#pragma once
+
+#include <utils/String16.h>
+#include <string>
+
+namespace net {
+
+class ProxyErrorListener;
+
+// Interface for the javascript bindings.
+class ProxyResolverJSBindings {
+ public:
+  ProxyResolverJSBindings() {}// : current_request_context_(NULL) {}
+
+  virtual ~ProxyResolverJSBindings() {}
+
+  // Handler for "myIpAddress()". Returns true on success and fills
+  // |*first_ip_address| with the result.
+  virtual bool MyIpAddress(std::string* first_ip_address) = 0;
+
+  // Handler for "myIpAddressEx()". Returns true on success and fills
+  // |*ip_address_list| with the result.
+  //
+  // This is a Microsoft extension to PAC for IPv6, see:
+  // http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx
+
+  virtual bool MyIpAddressEx(std::string* ip_address_list) = 0;
+
+  // Handler for "dnsResolve(host)". Returns true on success and fills
+  // |*first_ip_address| with the result.
+  virtual bool DnsResolve(const std::string& host,
+                          std::string* first_ip_address) = 0;
+
+  // Handler for "dnsResolveEx(host)". Returns true on success and fills
+  // |*ip_address_list| with the result.
+  //
+  // This is a Microsoft extension to PAC for IPv6, see:
+  // http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx
+  virtual bool DnsResolveEx(const std::string& host,
+                            std::string* ip_address_list) = 0;
+
+  // Creates a default javascript bindings implementation that will:
+  //   - Use the provided host resolver to service dnsResolve().
+  //
+  // Note that |host_resolver| will be used in sync mode mode.
+  static ProxyResolverJSBindings* CreateDefault();
+
+ private:
+};
+
+}  // namespace net
+
+#endif  // NET_PROXY_PROXY_RESOLVER_JS_BINDINGS_H_
diff --git a/src/proxy_resolver_script.h b/src/proxy_resolver_script.h
new file mode 100644
index 0000000..283eff9
--- /dev/null
+++ b/src/proxy_resolver_script.h
@@ -0,0 +1,276 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Akhil Arora <akhil.arora@sun.com>
+ *   Tomi Leppikangas <Tomi.Leppikangas@oulu.fi>
+ *   Darin Fisher <darin@meer.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef NET_PROXY_PROXY_RESOLVER_SCRIPT_H_
+#define NET_PROXY_PROXY_RESOLVER_SCRIPT_H_
+
+// The following code was formatted from:
+//   'mozilla/netwerk/base/src/nsProxyAutoConfig.js' (1.55)
+//
+// Using the command:
+//   $ cat nsProxyAutoConfig.js |
+//       awk '/var pacUtils/,/EOF/' |
+//       sed -e 's/^\s*$/""/g' |
+//       sed -e 's/"\s*[+]\s*$/"/g' |
+//       sed -e 's/"$/" \\/g' |
+//       sed -e 's/\/(ipaddr);/\/.exec(ipaddr);/g' |
+//       grep -v '^var pacUtils ='
+#define PROXY_RESOLVER_SCRIPT \
+  "function dnsDomainIs(host, domain) {\n" \
+  "    return (host.length >= domain.length &&\n" \
+  "            host.substring(host.length - domain.length) == domain);\n" \
+  "}\n" \
+  "" \
+  "function dnsDomainLevels(host) {\n" \
+  "    return host.split('.').length-1;\n" \
+  "}\n" \
+  "" \
+  "function convert_addr(ipchars) {\n" \
+  "    var bytes = ipchars.split('.');\n" \
+  "    var result = ((bytes[0] & 0xff) << 24) |\n" \
+  "                 ((bytes[1] & 0xff) << 16) |\n" \
+  "                 ((bytes[2] & 0xff) <<  8) |\n" \
+  "                  (bytes[3] & 0xff);\n" \
+  "    return result;\n" \
+  "}\n" \
+  "" \
+  "function isInNet(ipaddr, pattern, maskstr) {\n" \
+  "    var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n" \
+  "    if (test == null) {\n" \
+  "        ipaddr = dnsResolve(ipaddr);\n" \
+  "        if (ipaddr == null)\n" \
+  "            return false;\n" \
+  "    } else if (test[1] > 255 || test[2] > 255 || \n" \
+  "               test[3] > 255 || test[4] > 255) {\n" \
+  "        return false;    // not an IP address\n" \
+  "    }\n" \
+  "    var host = convert_addr(ipaddr);\n" \
+  "    var pat  = convert_addr(pattern);\n" \
+  "    var mask = convert_addr(maskstr);\n" \
+  "    return ((host & mask) == (pat & mask));\n" \
+  "    \n" \
+  "}\n" \
+  "" \
+  "function isPlainHostName(host) {\n" \
+  "    return (host.search('\\\\.') == -1);\n" \
+  "}\n" \
+  "" \
+  "function isResolvable(host) {\n" \
+  "    var ip = dnsResolve(host);\n" \
+  "    return (ip != null);\n" \
+  "}\n" \
+  "" \
+  "function localHostOrDomainIs(host, hostdom) {\n" \
+  "    return (host == hostdom) ||\n" \
+  "           (hostdom.lastIndexOf(host + '.', 0) == 0);\n" \
+  "}\n" \
+  "" \
+  "function shExpMatch(url, pattern) {\n" \
+  "   pattern = pattern.replace(/\\./g, '\\\\.');\n" \
+  "   pattern = pattern.replace(/\\*/g, '.*');\n" \
+  "   pattern = pattern.replace(/\\?/g, '.');\n" \
+  "   var newRe = new RegExp('^'+pattern+'$');\n" \
+  "   return newRe.test(url);\n" \
+  "}\n" \
+  "" \
+  "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n" \
+  "" \
+  "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n" \
+  "" \
+  "function weekdayRange() {\n" \
+  "    function getDay(weekday) {\n" \
+  "        if (weekday in wdays) {\n" \
+  "            return wdays[weekday];\n" \
+  "        }\n" \
+  "        return -1;\n" \
+  "    }\n" \
+  "    var date = new Date();\n" \
+  "    var argc = arguments.length;\n" \
+  "    var wday;\n" \
+  "    if (argc < 1)\n" \
+  "        return false;\n" \
+  "    if (arguments[argc - 1] == 'GMT') {\n" \
+  "        argc--;\n" \
+  "        wday = date.getUTCDay();\n" \
+  "    } else {\n" \
+  "        wday = date.getDay();\n" \
+  "    }\n" \
+  "    var wd1 = getDay(arguments[0]);\n" \
+  "    var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" \
+  "    return (wd1 == -1 || wd2 == -1) ? false\n" \
+  "                                    : (wd1 <= wday && wday <= wd2);\n" \
+  "}\n" \
+  "" \
+  "function dateRange() {\n" \
+  "    function getMonth(name) {\n" \
+  "        if (name in months) {\n" \
+  "            return months[name];\n" \
+  "        }\n" \
+  "        return -1;\n" \
+  "    }\n" \
+  "    var date = new Date();\n" \
+  "    var argc = arguments.length;\n" \
+  "    if (argc < 1) {\n" \
+  "        return false;\n" \
+  "    }\n" \
+  "    var isGMT = (arguments[argc - 1] == 'GMT');\n" \
+  "\n" \
+  "    if (isGMT) {\n" \
+  "        argc--;\n" \
+  "    }\n" \
+  "    // function will work even without explict handling of this case\n" \
+  "    if (argc == 1) {\n" \
+  "        var tmp = parseInt(arguments[0]);\n" \
+  "        if (isNaN(tmp)) {\n" \
+  "            return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n" \
+  "getMonth(arguments[0]));\n" \
+  "        } else if (tmp < 32) {\n" \
+  "            return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n" \
+  "        } else { \n" \
+  "            return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==\n" \
+  "tmp);\n" \
+  "        }\n" \
+  "    }\n" \
+  "    var year = date.getFullYear();\n" \
+  "    var date1, date2;\n" \
+  "    date1 = new Date(year,  0,  1,  0,  0,  0);\n" \
+  "    date2 = new Date(year, 11, 31, 23, 59, 59);\n" \
+  "    var adjustMonth = false;\n" \
+  "    for (var i = 0; i < (argc >> 1); i++) {\n" \
+  "        var tmp = parseInt(arguments[i]);\n" \
+  "        if (isNaN(tmp)) {\n" \
+  "            var mon = getMonth(arguments[i]);\n" \
+  "            date1.setMonth(mon);\n" \
+  "        } else if (tmp < 32) {\n" \
+  "            adjustMonth = (argc <= 2);\n" \
+  "            date1.setDate(tmp);\n" \
+  "        } else {\n" \
+  "            date1.setFullYear(tmp);\n" \
+  "        }\n" \
+  "    }\n" \
+  "    for (var i = (argc >> 1); i < argc; i++) {\n" \
+  "        var tmp = parseInt(arguments[i]);\n" \
+  "        if (isNaN(tmp)) {\n" \
+  "            var mon = getMonth(arguments[i]);\n" \
+  "            date2.setMonth(mon);\n" \
+  "        } else if (tmp < 32) {\n" \
+  "            date2.setDate(tmp);\n" \
+  "        } else {\n" \
+  "            date2.setFullYear(tmp);\n" \
+  "        }\n" \
+  "    }\n" \
+  "    if (adjustMonth) {\n" \
+  "        date1.setMonth(date.getMonth());\n" \
+  "        date2.setMonth(date.getMonth());\n" \
+  "    }\n" \
+  "    if (isGMT) {\n" \
+  "    var tmp = date;\n" \
+  "        tmp.setFullYear(date.getUTCFullYear());\n" \
+  "        tmp.setMonth(date.getUTCMonth());\n" \
+  "        tmp.setDate(date.getUTCDate());\n" \
+  "        tmp.setHours(date.getUTCHours());\n" \
+  "        tmp.setMinutes(date.getUTCMinutes());\n" \
+  "        tmp.setSeconds(date.getUTCSeconds());\n" \
+  "        date = tmp;\n" \
+  "    }\n" \
+  "    return ((date1 <= date) && (date <= date2));\n" \
+  "}\n" \
+  "" \
+  "function timeRange() {\n" \
+  "    var argc = arguments.length;\n" \
+  "    var date = new Date();\n" \
+  "    var isGMT= false;\n" \
+  "\n" \
+  "    if (argc < 1) {\n" \
+  "        return false;\n" \
+  "    }\n" \
+  "    if (arguments[argc - 1] == 'GMT') {\n" \
+  "        isGMT = true;\n" \
+  "        argc--;\n" \
+  "    }\n" \
+  "\n" \
+  "    var hour = isGMT ? date.getUTCHours() : date.getHours();\n" \
+  "    var date1, date2;\n" \
+  "    date1 = new Date();\n" \
+  "    date2 = new Date();\n" \
+  "\n" \
+  "    if (argc == 1) {\n" \
+  "        return (hour == arguments[0]);\n" \
+  "    } else if (argc == 2) {\n" \
+  "        return ((arguments[0] <= hour) && (hour <= arguments[1]));\n" \
+  "    } else {\n" \
+  "        switch (argc) {\n" \
+  "        case 6:\n" \
+  "            date1.setSeconds(arguments[2]);\n" \
+  "            date2.setSeconds(arguments[5]);\n" \
+  "        case 4:\n" \
+  "            var middle = argc >> 1;\n" \
+  "            date1.setHours(arguments[0]);\n" \
+  "            date1.setMinutes(arguments[1]);\n" \
+  "            date2.setHours(arguments[middle]);\n" \
+  "            date2.setMinutes(arguments[middle + 1]);\n" \
+  "            if (middle == 2) {\n" \
+  "                date2.setSeconds(59);\n" \
+  "            }\n" \
+  "            break;\n" \
+  "        default:\n" \
+  "          throw 'timeRange: bad number of arguments'\n" \
+  "        }\n" \
+  "    }\n" \
+  "\n" \
+  "    if (isGMT) {\n" \
+  "        date.setFullYear(date.getUTCFullYear());\n" \
+  "        date.setMonth(date.getUTCMonth());\n" \
+  "        date.setDate(date.getUTCDate());\n" \
+  "        date.setHours(date.getUTCHours());\n" \
+  "        date.setMinutes(date.getUTCMinutes());\n" \
+  "        date.setSeconds(date.getUTCSeconds());\n" \
+  "    }\n" \
+  "    return ((date1 <= date) && (date <= date2));\n" \
+  "}\n"
+
+// This is a Microsoft extension to PAC for IPv6, see:
+// http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx
+#define PROXY_RESOLVER_SCRIPT_EX \
+  "function isResolvableEx(host) {\n" \
+  "    var ipList = dnsResolveEx(host);\n" \
+  "    return (ipList != '');\n" \
+  "}\n"
+
+#endif  // NET_PROXY_PROXY_RESOLVER_SCRIPT_H_
diff --git a/src/proxy_resolver_v8.cc b/src/proxy_resolver_v8.cc
new file mode 100644
index 0000000..285091d
--- /dev/null
+++ b/src/proxy_resolver_v8.cc
@@ -0,0 +1,740 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <cstdio>
+#include <string>
+
+#include <utils/String16.h>
+
+#include "proxy_resolver_v8.h"
+
+#include "proxy_resolver_script.h"
+#include "net_util.h"
+#include <include/v8.h>
+#include <algorithm>
+#include <vector>
+
+#include <iostream>
+
+#include <string.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+
+// Notes on the javascript environment:
+//
+// For the majority of the PAC utility functions, we use the same code
+// as Firefox. See the javascript library that proxy_resolver_scipt.h
+// pulls in.
+//
+// In addition, we implement a subset of Microsoft's extensions to PAC.
+// - myIpAddressEx()
+// - dnsResolveEx()
+// - isResolvableEx()
+// - isInNetEx()
+// - sortIpAddressList()
+//
+// It is worth noting that the original PAC specification does not describe
+// the return values on failure. Consequently, there are compatibility
+// differences between browsers on what to return on failure, which are
+// illustrated below:
+//
+// --------------------+-------------+-------------------+--------------
+//                     | Firefox3    | InternetExplorer8 |  --> Us <---
+// --------------------+-------------+-------------------+--------------
+// myIpAddress()       | "127.0.0.1" |  ???              |  "127.0.0.1"
+// dnsResolve()        | null        |  false            |  null
+// myIpAddressEx()     | N/A         |  ""               |  ""
+// sortIpAddressList() | N/A         |  false            |  false
+// dnsResolveEx()      | N/A         |  ""               |  ""
+// isInNetEx()         | N/A         |  false            |  false
+// --------------------+-------------+-------------------+--------------
+//
+// TODO: The cell above reading ??? means I didn't test it.
+//
+// Another difference is in how dnsResolve() and myIpAddress() are
+// implemented -- whether they should restrict to IPv4 results, or
+// include both IPv4 and IPv6. The following table illustrates the
+// differences:
+//
+// --------------------+-------------+-------------------+--------------
+//                     | Firefox3    | InternetExplorer8 |  --> Us <---
+// --------------------+-------------+-------------------+--------------
+// myIpAddress()       | IPv4/IPv6   |  IPv4             |  IPv4
+// dnsResolve()        | IPv4/IPv6   |  IPv4             |  IPv4
+// isResolvable()      | IPv4/IPv6   |  IPv4             |  IPv4
+// myIpAddressEx()     | N/A         |  IPv4/IPv6        |  IPv4/IPv6
+// dnsResolveEx()      | N/A         |  IPv4/IPv6        |  IPv4/IPv6
+// sortIpAddressList() | N/A         |  IPv4/IPv6        |  IPv4/IPv6
+// isResolvableEx()    | N/A         |  IPv4/IPv6        |  IPv4/IPv6
+// isInNetEx()         | N/A         |  IPv4/IPv6        |  IPv4/IPv6
+// -----------------+-------------+-------------------+--------------
+
+static bool DoIsStringASCII(const android::String16& str) {
+  for (size_t i = 0; i < str.size(); i++) {
+    unsigned short c = str.string()[i];
+    if (c > 0x7F)
+      return false;
+  }
+  return true;
+}
+
+bool IsStringASCII(const android::String16& str) {
+  return DoIsStringASCII(str);
+}
+
+namespace net {
+
+namespace {
+
+// Pseudo-name for the PAC script.
+const char kPacResourceName[] = "proxy-pac-script.js";
+// Pseudo-name for the PAC utility script.
+const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
+
+// External string wrapper so V8 can access the UTF16 string wrapped by
+// ProxyResolverScriptData.
+class V8ExternalStringFromScriptData
+    : public v8::String::ExternalStringResource {
+ public:
+  explicit V8ExternalStringFromScriptData(
+      const android::String16& script_data)
+      : script_data_(script_data) {}
+
+  virtual const uint16_t* data() const {
+    return script_data_.string();
+  }
+
+  virtual size_t length() const {
+    return script_data_.size();
+  }
+
+ private:
+  const android::String16& script_data_;
+//  DISALLOW_COPY_AND_ASSIGN(V8ExternalStringFromScriptData);
+};
+
+// External string wrapper so V8 can access a string literal.
+class V8ExternalASCIILiteral : public v8::String::ExternalAsciiStringResource {
+ public:
+  // |ascii| must be a NULL-terminated C string, and must remain valid
+  // throughout this object's lifetime.
+  V8ExternalASCIILiteral(const char* ascii, size_t length)
+      : ascii_(ascii), length_(length) {
+  }
+
+  virtual const char* data() const {
+    return ascii_;
+  }
+
+  virtual size_t length() const {
+    return length_;
+  }
+
+ private:
+  const char* ascii_;
+  size_t length_;
+};
+
+// When creating a v8::String from a C++ string we have two choices: create
+// a copy, or create a wrapper that shares the same underlying storage.
+// For small strings it is better to just make a copy, whereas for large
+// strings there are savings by sharing the storage. This number identifies
+// the cutoff length for when to start wrapping rather than creating copies.
+const size_t kMaxStringBytesForCopy = 256;
+
+template <class string_type>
+inline typename string_type::value_type* WriteInto(string_type* str,
+                                                   size_t length_with_null) {
+  str->reserve(length_with_null);
+  str->resize(length_with_null - 1);
+  return &((*str)[0]);
+}
+
+// Converts a V8 String to a UTF8 std::string.
+std::string V8StringToUTF8(v8::Handle<v8::String> s) {
+  std::string result;
+  s->WriteUtf8(WriteInto(&result, s->Length() + 1));
+  return result;
+}
+
+// Converts a V8 String to a UTF16 string.
+android::String16 V8StringToUTF16(v8::Handle<v8::String> s) {
+  int len = s->Length();
+  char16_t* buf = new char16_t[len + 1];
+  s->Write(buf, 0, len);
+  android::String16 ret(buf, len);
+  delete buf;
+  return ret;
+}
+
+std::string UTF16ToASCII(const android::String16& str) {
+  android::String8 rstr(str);
+  return std::string(rstr.string());
+}
+
+// Converts an ASCII std::string to a V8 string.
+v8::Local<v8::String> ASCIIStringToV8String(const std::string& s) {
+  return v8::String::New(s.data(), s.size());
+}
+
+v8::Local<v8::String> UTF16StringToV8String(const android::String16& s) {
+  return v8::String::New(s.string(), s.size());
+}
+
+// Converts an ASCII string literal to a V8 string.
+v8::Local<v8::String> ASCIILiteralToV8String(const char* ascii) {
+//  DCHECK(IsStringASCII(ascii));
+  size_t length = strlen(ascii);
+  if (length <= kMaxStringBytesForCopy)
+    return v8::String::New(ascii, length);
+  return v8::String::NewExternal(new V8ExternalASCIILiteral(ascii, length));
+}
+
+// Stringizes a V8 object by calling its toString() method. Returns true
+// on success. This may fail if the toString() throws an exception.
+bool V8ObjectToUTF16String(v8::Handle<v8::Value> object,
+                           android::String16* utf16_result) {
+  if (object.IsEmpty())
+    return false;
+
+  v8::HandleScope scope;
+  v8::Local<v8::String> str_object = object->ToString();
+  if (str_object.IsEmpty())
+    return false;
+  *utf16_result = V8StringToUTF16(str_object);
+  return true;
+}
+
+// Extracts an hostname argument from |args|. On success returns true
+// and fills |*hostname| with the result.
+bool GetHostnameArgument(const v8::Arguments& args, std::string* hostname) {
+  // The first argument should be a string.
+  if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString())
+    return false;
+
+  const android::String16 hostname_utf16 = V8StringToUTF16(args[0]->ToString());
+
+  // If the hostname is already in ASCII, simply return it as is.
+  if (IsStringASCII(hostname_utf16)) {
+    *hostname = UTF16ToASCII(hostname_utf16);
+    return true;
+  }
+  return false;
+}
+
+// Wrapper for passing around IP address strings and IPAddressNumber objects.
+struct IPAddress {
+  IPAddress(const std::string& ip_string, const IPAddressNumber& ip_number)
+      : string_value(ip_string),
+        ip_address_number(ip_number) {
+  }
+
+  // Used for sorting IP addresses in ascending order in SortIpAddressList().
+  // IP6 addresses are placed ahead of IPv4 addresses.
+  bool operator<(const IPAddress& rhs) const {
+    const IPAddressNumber& ip1 = this->ip_address_number;
+    const IPAddressNumber& ip2 = rhs.ip_address_number;
+    if (ip1.size() != ip2.size())
+      return ip1.size() > ip2.size();  // IPv6 before IPv4.
+    return memcmp(&ip1[0], &ip2[0], ip1.size()) < 0;  // Ascending order.
+  }
+
+  std::string string_value;
+  IPAddressNumber ip_address_number;
+};
+
+template<typename STR>
+bool RemoveCharsT(const STR& input,
+                  const typename STR::value_type remove_chars[],
+                  STR* output) {
+  bool removed = false;
+  size_t found;
+
+  *output = input;
+
+  found = output->find_first_of(remove_chars);
+  while (found != STR::npos) {
+    removed = true;
+    output->replace(found, 1, STR());
+    found = output->find_first_of(remove_chars, found);
+  }
+
+  return removed;
+}
+
+bool RemoveChars(const std::string& input,
+                 const char remove_chars[],
+                 std::string* output) {
+  return RemoveCharsT(input, remove_chars, output);
+}
+
+// Handler for "sortIpAddressList(IpAddressList)". |ip_address_list| is a
+// semi-colon delimited string containing IP addresses.
+// |sorted_ip_address_list| is the resulting list of sorted semi-colon delimited
+// IP addresses or an empty string if unable to sort the IP address list.
+// Returns 'true' if the sorting was successful, and 'false' if the input was an
+// empty string, a string of separators (";" in this case), or if any of the IP
+// addresses in the input list failed to parse.
+bool SortIpAddressList(const std::string& ip_address_list,
+                       std::string* sorted_ip_address_list) {
+  sorted_ip_address_list->clear();
+
+  // Strip all whitespace (mimics IE behavior).
+  std::string cleaned_ip_address_list;
+  RemoveChars(ip_address_list, " \t", &cleaned_ip_address_list);
+  if (cleaned_ip_address_list.empty())
+    return false;
+
+  // Split-up IP addresses and store them in a vector.
+  std::vector<IPAddress> ip_vector;
+  IPAddressNumber ip_num;
+  char *tok_list = strtok((char *)cleaned_ip_address_list.c_str(), ";");
+  while (tok_list != NULL) {
+    if (!ParseIPLiteralToNumber(tok_list, &ip_num))
+      return false;
+    ip_vector.push_back(IPAddress(tok_list, ip_num));
+    tok_list = strtok(NULL, ";");
+  }
+
+  if (ip_vector.empty())  // Can happen if we have something like
+    return false;         // sortIpAddressList(";") or sortIpAddressList("; ;")
+
+  // Sort lists according to ascending numeric value.
+  if (ip_vector.size() > 1)
+    std::stable_sort(ip_vector.begin(), ip_vector.end());
+
+  // Return a semi-colon delimited list of sorted addresses (IPv6 followed by
+  // IPv4).
+  for (size_t i = 0; i < ip_vector.size(); ++i) {
+    if (i > 0)
+      *sorted_ip_address_list += ";";
+    *sorted_ip_address_list += ip_vector[i].string_value;
+  }
+  return true;
+}
+
+
+// Handler for "isInNetEx(ip_address, ip_prefix)". |ip_address| is a string
+// containing an IPv4/IPv6 address, and |ip_prefix| is a string containg a
+// slash-delimited IP prefix with the top 'n' bits specified in the bit
+// field. This returns 'true' if the address is in the same subnet, and
+// 'false' otherwise. Also returns 'false' if the prefix is in an incorrect
+// format, or if an address and prefix of different types are used (e.g. IPv6
+// address and IPv4 prefix).
+bool IsInNetEx(const std::string& ip_address, const std::string& ip_prefix) {
+  IPAddressNumber address;
+  std::string cleaned_ip_address;
+  if (RemoveChars(ip_address, " \t", &cleaned_ip_address))
+    return false;
+  if (!ParseIPLiteralToNumber(ip_address, &address))
+    return false;
+
+  IPAddressNumber prefix;
+  size_t prefix_length_in_bits;
+  if (!ParseCIDRBlock(ip_prefix, &prefix, &prefix_length_in_bits))
+    return false;
+
+  // Both |address| and |prefix| must be of the same type (IPv4 or IPv6).
+  if (address.size() != prefix.size())
+    return false;
+
+  return IPNumberMatchesPrefix(address, prefix, prefix_length_in_bits);
+}
+
+}  // namespace
+
+// ProxyResolverV8::Context ---------------------------------------------------
+
+class ProxyResolverV8::Context {
+ public:
+  explicit Context(ProxyResolverJSBindings* js_bindings,
+          ProxyErrorListener* error_listener)
+      : js_bindings_(js_bindings), error_listener_(error_listener) {
+  }
+
+  ~Context() {
+    v8::Locker locked;
+
+    v8_this_.Dispose();
+    v8_context_.Dispose();
+
+    // Run the V8 garbage collector. We do this to be sure the
+    // ExternalStringResource objects we allocated get properly disposed.
+    // Otherwise when running the unit-tests they may get leaked.
+    // See crbug.com/48145.
+    PurgeMemory();
+  }
+
+  int ResolveProxy(const android::String16 url, const android::String16 host,
+        android::String16* results) {
+    v8::Locker locked;
+    v8::HandleScope scope;
+
+    v8::Context::Scope function_scope(v8_context_);
+
+    v8::Local<v8::Value> function;
+    if (!GetFindProxyForURL(&function)) {
+      error_listener_->ErrorMessage(
+          android::String16("FindProxyForURL() is undefined"));
+      return ERR_PAC_SCRIPT_FAILED;
+    }
+
+    v8::Handle<v8::Value> argv[] = {
+        UTF16StringToV8String(url),
+        UTF16StringToV8String(host) };
+
+    v8::TryCatch try_catch;
+    v8::Local<v8::Value> ret = v8::Function::Cast(*function)->Call(
+        v8_context_->Global(), 2, argv);
+
+    if (try_catch.HasCaught()) {
+      error_listener_->ErrorMessage(
+          V8StringToUTF16(try_catch.Message()->Get()));
+      return ERR_PAC_SCRIPT_FAILED;
+    }
+
+    if (!ret->IsString()) {
+      error_listener_->ErrorMessage(
+          android::String16("FindProxyForURL() did not return a string."));
+      return ERR_PAC_SCRIPT_FAILED;
+    }
+
+    *results = V8StringToUTF16(ret->ToString());
+
+    if (!IsStringASCII(*results)) {
+      // TODO:         Rather than failing when a wide string is returned, we
+      //               could extend the parsing to handle IDNA hostnames by
+      //               converting them to ASCII punycode.
+      //               crbug.com/47234
+      error_listener_->ErrorMessage(
+          android::String16("FindProxyForURL() returned a non-ASCII string"));
+      return ERR_PAC_SCRIPT_FAILED;
+    }
+
+    return OK;
+  }
+
+  int InitV8(const android::String16& pac_script) {
+    v8::Locker locked;
+    v8::HandleScope scope;
+
+    v8_this_ = v8::Persistent<v8::External>::New(v8::External::New(this));
+    v8::Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
+
+    // Attach the javascript bindings.
+    v8::Local<v8::FunctionTemplate> alert_template =
+        v8::FunctionTemplate::New(&AlertCallback, v8_this_);
+    global_template->Set(ASCIILiteralToV8String("alert"), alert_template);
+
+    v8::Local<v8::FunctionTemplate> my_ip_address_template =
+        v8::FunctionTemplate::New(&MyIpAddressCallback, v8_this_);
+    global_template->Set(ASCIILiteralToV8String("myIpAddress"),
+        my_ip_address_template);
+
+    v8::Local<v8::FunctionTemplate> dns_resolve_template =
+        v8::FunctionTemplate::New(&DnsResolveCallback, v8_this_);
+    global_template->Set(ASCIILiteralToV8String("dnsResolve"),
+        dns_resolve_template);
+
+    // Microsoft's PAC extensions:
+
+    v8::Local<v8::FunctionTemplate> dns_resolve_ex_template =
+        v8::FunctionTemplate::New(&DnsResolveExCallback, v8_this_);
+    global_template->Set(ASCIILiteralToV8String("dnsResolveEx"),
+                         dns_resolve_ex_template);
+
+    v8::Local<v8::FunctionTemplate> my_ip_address_ex_template =
+        v8::FunctionTemplate::New(&MyIpAddressExCallback, v8_this_);
+    global_template->Set(ASCIILiteralToV8String("myIpAddressEx"),
+                         my_ip_address_ex_template);
+
+    v8::Local<v8::FunctionTemplate> sort_ip_address_list_template =
+        v8::FunctionTemplate::New(&SortIpAddressListCallback, v8_this_);
+    global_template->Set(ASCIILiteralToV8String("sortIpAddressList"),
+                         sort_ip_address_list_template);
+
+    v8::Local<v8::FunctionTemplate> is_in_net_ex_template =
+        v8::FunctionTemplate::New(&IsInNetExCallback, v8_this_);
+    global_template->Set(ASCIILiteralToV8String("isInNetEx"),
+                         is_in_net_ex_template);
+
+    v8_context_ = v8::Context::New(NULL, global_template);
+
+    v8::Context::Scope ctx(v8_context_);
+
+    // Add the PAC utility functions to the environment.
+    // (This script should never fail, as it is a string literal!)
+    // Note that the two string literals are concatenated.
+    int rv = RunScript(
+        ASCIILiteralToV8String(
+            PROXY_RESOLVER_SCRIPT
+            PROXY_RESOLVER_SCRIPT_EX),
+        kPacUtilityResourceName);
+    if (rv != OK) {
+      return rv;
+    }
+
+    // Add the user's PAC code to the environment.
+    rv = RunScript(UTF16StringToV8String(pac_script), kPacResourceName);
+    if (rv != OK) {
+      return rv;
+    }
+
+    // At a minimum, the FindProxyForURL() function must be defined for this
+    // to be a legitimiate PAC script.
+    v8::Local<v8::Value> function;
+    if (!GetFindProxyForURL(&function))
+      return ERR_PAC_SCRIPT_FAILED;
+
+    return OK;
+  }
+
+  void PurgeMemory() {
+    v8::Locker locked;
+    // Repeatedly call the V8 idle notification until it returns true ("nothing
+    // more to free").  Note that it makes more sense to do this than to
+    // implement a new "delete everything" pass because object references make
+    // it difficult to free everything possible in just one pass.
+    while (!v8::V8::IdleNotification())
+      ;
+  }
+
+ private:
+  bool GetFindProxyForURL(v8::Local<v8::Value>* function) {
+    *function = v8_context_->Global()->Get(
+        ASCIILiteralToV8String("FindProxyForURL"));
+    return (*function)->IsFunction();
+  }
+
+  // Handle an exception thrown by V8.
+  void HandleError(v8::Handle<v8::Message> message) {
+    if (message.IsEmpty())
+      return;
+    error_listener_->ErrorMessage(V8StringToUTF16(message->Get()));
+  }
+
+  // Compiles and runs |script| in the current V8 context.
+  // Returns OK on success, otherwise an error code.
+  int RunScript(v8::Handle<v8::String> script, const char* script_name) {
+    v8::TryCatch try_catch;
+
+    // Compile the script.
+    v8::ScriptOrigin origin =
+        v8::ScriptOrigin(ASCIILiteralToV8String(script_name));
+    v8::Local<v8::Script> code = v8::Script::Compile(script, &origin);
+
+    // Execute.
+    if (!code.IsEmpty())
+      code->Run();
+
+    // Check for errors.
+    if (try_catch.HasCaught()) {
+      HandleError(try_catch.Message());
+      return ERR_PAC_SCRIPT_FAILED;
+    }
+
+    return OK;
+  }
+
+  // V8 callback for when "alert()" is invoked by the PAC script.
+  static v8::Handle<v8::Value> AlertCallback(const v8::Arguments& args) {
+    Context* context =
+        static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
+
+    // Like firefox we assume "undefined" if no argument was specified, and
+    // disregard any arguments beyond the first.
+    android::String16 message;
+    if (args.Length() == 0) {
+      std::string undef = "undefined";
+      android::String8 undef8(undef.c_str());
+      android::String16 wundef(undef8);
+      message = wundef;
+    } else {
+      if (!V8ObjectToUTF16String(args[0], &message))
+        return v8::Undefined();  // toString() threw an exception.
+    }
+
+    context->error_listener_->AlertMessage(message);
+    return v8::Undefined();
+  }
+
+  // V8 callback for when "myIpAddress()" is invoked by the PAC script.
+  static v8::Handle<v8::Value> MyIpAddressCallback(const v8::Arguments& args) {
+    Context* context =
+        static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
+
+    std::string result;
+    bool success;
+
+    {
+      v8::Unlocker unlocker;
+
+      // We shouldn't be called with any arguments, but will not complain if
+      // we are.
+      success = context->js_bindings_->MyIpAddress(&result);
+    }
+
+    if (!success)
+      return ASCIILiteralToV8String("127.0.0.1");
+    return ASCIIStringToV8String(result);
+  }
+
+  // V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
+  static v8::Handle<v8::Value> MyIpAddressExCallback(
+      const v8::Arguments& args) {
+    Context* context =
+        static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
+
+    std::string ip_address_list;
+    bool success;
+
+    {
+      v8::Unlocker unlocker;
+
+      // We shouldn't be called with any arguments, but will not complain if
+      // we are.
+      success = context->js_bindings_->MyIpAddressEx(&ip_address_list);
+    }
+
+    if (!success)
+      ip_address_list = std::string();
+    return ASCIIStringToV8String(ip_address_list);
+  }
+
+  // V8 callback for when "dnsResolve()" is invoked by the PAC script.
+  static v8::Handle<v8::Value> DnsResolveCallback(const v8::Arguments& args) {
+    Context* context =
+        static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
+
+    // We need at least one string argument.
+    std::string hostname;
+    if (!GetHostnameArgument(args, &hostname))
+      return v8::Null();
+
+    std::string ip_address;
+    bool success;
+
+    {
+      v8::Unlocker unlocker;
+      success = context->js_bindings_->DnsResolve(hostname, &ip_address);
+    }
+
+    return success ? ASCIIStringToV8String(ip_address) : v8::Null();
+  }
+
+  // V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
+  static v8::Handle<v8::Value> DnsResolveExCallback(const v8::Arguments& args) {
+    Context* context =
+        static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
+
+    // We need at least one string argument.
+    std::string hostname;
+    if (!GetHostnameArgument(args, &hostname))
+      return v8::Undefined();
+
+    std::string ip_address_list;
+    bool success;
+
+    {
+      v8::Unlocker unlocker;
+      success = context->js_bindings_->DnsResolveEx(hostname, &ip_address_list);
+    }
+
+    if (!success)
+      ip_address_list = std::string();
+
+    return ASCIIStringToV8String(ip_address_list);
+  }
+
+  // V8 callback for when "sortIpAddressList()" is invoked by the PAC script.
+  static v8::Handle<v8::Value> SortIpAddressListCallback(
+      const v8::Arguments& args) {
+    // We need at least one string argument.
+    if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString())
+      return v8::Null();
+
+    std::string ip_address_list = V8StringToUTF8(args[0]->ToString());
+    std::string sorted_ip_address_list;
+    bool success = SortIpAddressList(ip_address_list, &sorted_ip_address_list);
+    if (!success)
+      return v8::False();
+    return ASCIIStringToV8String(sorted_ip_address_list);
+  }
+
+  // V8 callback for when "isInNetEx()" is invoked by the PAC script.
+  static v8::Handle<v8::Value> IsInNetExCallback(const v8::Arguments& args) {
+    // We need at least 2 string arguments.
+    if (args.Length() < 2 || args[0].IsEmpty() || !args[0]->IsString() ||
+        args[1].IsEmpty() || !args[1]->IsString())
+      return v8::Null();
+
+    std::string ip_address = V8StringToUTF8(args[0]->ToString());
+    std::string ip_prefix = V8StringToUTF8(args[1]->ToString());
+    return IsInNetEx(ip_address, ip_prefix) ? v8::True() : v8::False();
+  }
+
+  ProxyResolverJSBindings* js_bindings_;
+  ProxyErrorListener* error_listener_;
+  v8::Persistent<v8::External> v8_this_;
+  v8::Persistent<v8::Context> v8_context_;
+};
+
+// ProxyResolverV8 ------------------------------------------------------------
+
+ProxyResolverV8::ProxyResolverV8(
+    ProxyResolverJSBindings* custom_js_bindings,
+    ProxyErrorListener* error_listener)
+    : context_(NULL), js_bindings_(custom_js_bindings),
+      error_listener_(error_listener) {
+
+}
+
+ProxyResolverV8::~ProxyResolverV8() {
+  if (context_ != NULL) {
+    delete context_;
+    context_ = NULL;
+  }
+  if (js_bindings_ != NULL) {
+    delete js_bindings_;
+  }
+}
+
+int ProxyResolverV8::GetProxyForURL(const android::String16 spec, const android::String16 host,
+                                    android::String16* results) {
+  // If the V8 instance has not been initialized (either because
+  // SetPacScript() wasn't called yet, or because it failed.
+  if (context_ == NULL)
+    return ERR_FAILED;
+
+  // Otherwise call into V8.
+  int rv = context_->ResolveProxy(spec, host, results);
+
+  return rv;
+}
+
+void ProxyResolverV8::PurgeMemory() {
+  context_->PurgeMemory();
+}
+
+int ProxyResolverV8::SetPacScript(const android::String16& script_data) {
+  if (context_ != NULL) {
+    delete context_;
+    context_ = NULL;
+  }
+  if (script_data.size() == 0)
+    return ERR_PAC_SCRIPT_FAILED;
+
+  // Try parsing the PAC script.
+  context_ = new Context(js_bindings_, error_listener_);
+  int rv;
+  if ((rv = context_->InitV8(script_data)) != OK) {
+    context_ = NULL;
+  }
+  if (rv != OK)
+    context_ = NULL;
+  return rv;
+}
+
+}  // namespace net
diff --git a/src/proxy_resolver_v8.h b/src/proxy_resolver_v8.h
new file mode 100644
index 0000000..818cb58
--- /dev/null
+++ b/src/proxy_resolver_v8.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_PROXY_PROXY_RESOLVER_V8_H_
+#define NET_PROXY_PROXY_RESOLVER_V8_H_
+#pragma once
+#include "proxy_resolver_js_bindings.h"
+
+#include <utils/String16.h>
+
+namespace net {
+
+typedef void* RequestHandle;
+typedef void* CompletionCallback;
+
+#define OK 0
+#define ERR_PAC_SCRIPT_FAILED -1
+#define ERR_FAILED -2
+
+class ProxyErrorListener {
+protected:
+  virtual ~ProxyErrorListener() {}
+public:
+  virtual void AlertMessage(android::String16 message) = 0;
+  virtual void ErrorMessage(android::String16 error) = 0;
+};
+
+// Implementation of ProxyResolver that uses V8 to evaluate PAC scripts.
+//
+// ----------------------------------------------------------------------------
+// !!! Important note on threading model:
+// ----------------------------------------------------------------------------
+// There can be only one instance of V8 running at a time. To enforce this
+// constraint, ProxyResolverV8 holds a v8::Locker during execution. Therefore
+// it is OK to run multiple instances of ProxyResolverV8 on different threads,
+// since only one will be running inside V8 at a time.
+//
+// It is important that *ALL* instances of V8 in the process be using
+// v8::Locker. If not there can be race conditions beween the non-locked V8
+// instances and the locked V8 instances used by ProxyResolverV8 (assuming they
+// run on different threads).
+//
+// This is the case with the V8 instance used by chromium's renderer -- it runs
+// on a different thread from ProxyResolver (renderer thread vs PAC thread),
+// and does not use locking since it expects to be alone.
+class ProxyResolverV8 {
+ public:
+  // Constructs a ProxyResolverV8 with custom bindings. ProxyResolverV8 takes
+  // ownership of |custom_js_bindings| and deletes it when ProxyResolverV8
+  // is destroyed.
+  explicit ProxyResolverV8(ProxyResolverJSBindings* custom_js_bindings,
+          ProxyErrorListener* error_listener);
+
+  virtual ~ProxyResolverV8();
+
+  ProxyResolverJSBindings* js_bindings() { return js_bindings_; }
+
+  virtual int GetProxyForURL(const android::String16 spec, const android::String16 host,
+                             android::String16* results);
+  virtual void PurgeMemory();
+  virtual int SetPacScript(const android::String16& script_data);
+
+ private:
+  // Context holds the Javascript state for the most recently loaded PAC
+  // script. It corresponds with the data from the last call to
+  // SetPacScript().
+  class Context;
+  Context* context_;
+
+  ProxyResolverJSBindings* js_bindings_;
+  ProxyErrorListener* error_listener_;
+};
+
+}  // namespace net
+
+#endif  // NET_PROXY_PROXY_RESOLVER_V8_H_
diff --git a/test/Android.mk b/test/Android.mk
new file mode 100644
index 0000000..1f04f1f
--- /dev/null
+++ b/test/Android.mk
@@ -0,0 +1,27 @@
+ifneq ($(TARGET_SIMULATOR),true)
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_CPP_EXTENSION := .cc
+
+# Set up the target identity
+LOCAL_MODULE := proxy_resolver_v8_unittest
+
+LOCAL_SRC_FILES := \
+  proxy_resolver_v8_unittest.cc
+
+LOCAL_CFLAGS += \
+  -Wno-endif-labels \
+  -Wno-import \
+  -Wno-format \
+
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/../src $(LOCAL_PATH)/ external/v8 external/gtest
+
+LOCAL_SHARED_LIBRARIES := libpac libutils libstlport liblog
+
+include external/stlport/libstlport.mk
+
+include $(BUILD_NATIVE_TEST)
+
+endif
diff --git a/test/README b/test/README
new file mode 100644
index 0000000..19d2ec0
--- /dev/null
+++ b/test/README
@@ -0,0 +1,6 @@
+This directory contains a script that converts the javascript directory
+to a header file containing strings for use with testing.
+
+Do not modify proxy_script_test.h.  Instead modify the files contained
+within js-unittest/ and then run the following command.
+    ./jstocstring.pl js-unittest proxy_test_script.h
diff --git a/test/js-unittest/binding_from_global.js b/test/js-unittest/binding_from_global.js
new file mode 100644
index 0000000..91bbcf2
--- /dev/null
+++ b/test/js-unittest/binding_from_global.js
@@ -0,0 +1,8 @@
+// Calls a bindings outside of FindProxyForURL(). This causes the code to
+// get exercised during initialization.
+
+var x = myIpAddress();
+
+function FindProxyForURL(url, host) {
+  return "PROXY " + x + ":80";
+}
diff --git a/test/js-unittest/bindings.js b/test/js-unittest/bindings.js
new file mode 100644
index 0000000..7cf9f26
--- /dev/null
+++ b/test/js-unittest/bindings.js
@@ -0,0 +1,62 @@
+// Try calling the browser-side bound functions with varying (invalid)
+// inputs. There is no notion of "success" for this test, other than
+// verifying the correct C++ bindings were reached with expected values.
+
+function MyObject() {
+  this.x = "3";
+}
+
+MyObject.prototype.toString = function() {
+  throw "exception from calling toString()";
+}
+
+function expectEquals(expectation, actual) {
+  if (!(expectation === actual)) {
+    throw "FAIL: expected: " + expectation + ", actual: " + actual;
+  }
+}
+
+function FindProxyForURL(url, host) {
+  // Call dnsResolve with some wonky arguments.
+  // Those expected to fail (because we have passed a non-string parameter)
+  // will return |null|, whereas those that have called through to the C++
+  // bindings will return '127.0.0.1'.
+  expectEquals(null, dnsResolve());
+  expectEquals(null, dnsResolve(null));
+  expectEquals(null, dnsResolve(undefined));
+  expectEquals('127.0.0.1', dnsResolve(""));
+  expectEquals(null, dnsResolve({foo: 'bar'}));
+  expectEquals(null, dnsResolve(fn));
+  expectEquals(null, dnsResolve(['3']));
+  expectEquals('127.0.0.1', dnsResolve("arg1", "arg2", "arg3", "arg4"));
+
+  // Call alert with some wonky arguments.
+  alert();
+  alert(null);
+  alert(undefined);
+  alert({foo:'bar'});
+
+  // This should throw an exception when we toString() the argument
+  // to alert in the bindings.
+  try {
+    alert(new MyObject());
+  } catch (e) {
+    alert(e);
+  }
+
+  // Call myIpAddress() with wonky arguments
+  myIpAddress(null);
+  myIpAddress(null, null);
+
+  // Call myIpAddressEx() correctly (no arguments).
+  myIpAddressEx();
+
+  // Call dnsResolveEx() (note that isResolvableEx() implicity calls it.)
+  isResolvableEx("is_resolvable");
+  dnsResolveEx("foobar");
+
+  return "DIRECT";
+}
+
+function fn() {}
+
diff --git a/test/js-unittest/direct.js b/test/js-unittest/direct.js
new file mode 100644
index 0000000..43a04da
--- /dev/null
+++ b/test/js-unittest/direct.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+  return "DIRECT";
+}
+
diff --git a/test/js-unittest/dns_fail.js b/test/js-unittest/dns_fail.js
new file mode 100644
index 0000000..c71bcc3
--- /dev/null
+++ b/test/js-unittest/dns_fail.js
@@ -0,0 +1,27 @@
+// This script should be run in an environment where all DNS resolution are
+// failing. It tests that functions return the expected values.
+//
+// Returns "PROXY success:80" on success.
+function FindProxyForURL(url, host) {
+  try {
+    expectEq("127.0.0.1", myIpAddress());
+    expectEq("", myIpAddressEx());
+
+    expectEq(null, dnsResolve("not-found"));
+    expectEq("", dnsResolveEx("not-found"));
+
+    expectEq(false, isResolvable("not-found"));
+    expectEq(false, isResolvableEx("not-found"));
+
+    return "PROXY success:80";
+  } catch(e) {
+    alert(e);
+    return "PROXY failed:80";
+  }
+}
+
+function expectEq(expected, actual) {
+  if (expected != actual)
+    throw "Expected " + expected + " but was " + actual;
+}
+
diff --git a/test/js-unittest/ends_with_comment.js b/test/js-unittest/ends_with_comment.js
new file mode 100644
index 0000000..bbfef85
--- /dev/null
+++ b/test/js-unittest/ends_with_comment.js
@@ -0,0 +1,8 @@
+function FindProxyForURL(url, host) {
+  return "PROXY success:80";
+}
+
+// We end the script with a comment (and no trailing newline).
+// This used to cause problems, because internally ProxyResolverV8
+// would append some functions to the script; the first line of
+// those extra functions was being considered part of the comment.
\ No newline at end of file
diff --git a/test/js-unittest/ends_with_statement_no_semicolon.js b/test/js-unittest/ends_with_statement_no_semicolon.js
new file mode 100644
index 0000000..ea03714
--- /dev/null
+++ b/test/js-unittest/ends_with_statement_no_semicolon.js
@@ -0,0 +1,3 @@
+// Ends with a statement, and no terminal newline.
+function FindProxyForURL(url, host) { return "PROXY success:" + x; }
+x = 3
\ No newline at end of file
diff --git a/test/js-unittest/international_domain_names.js b/test/js-unittest/international_domain_names.js
new file mode 100644
index 0000000..546af13
--- /dev/null
+++ b/test/js-unittest/international_domain_names.js
@@ -0,0 +1,16 @@
+// Try resolving hostnames containing non-ASCII characters.
+
+function FindProxyForURL(url, host) {
+  // This international hostname has a non-ASCII character. It is represented
+  // in punycode as 'xn--bcher-kva.ch'
+  var idn = 'B\u00fccher.ch';
+
+  // We disregard the actual return value -- all we care about is that on
+  // the C++ end the bindings were passed the punycode equivalent of this
+  // unicode hostname.
+  dnsResolve(idn);
+  dnsResolveEx(idn);
+
+  return "DIRECT";
+}
+
diff --git a/test/js-unittest/missing_close_brace.js b/test/js-unittest/missing_close_brace.js
new file mode 100644
index 0000000..8018f8f
--- /dev/null
+++ b/test/js-unittest/missing_close_brace.js
@@ -0,0 +1,6 @@
+// This PAC script is invalid, because there is a missing close brace
+// on the function FindProxyForURL().
+
+function FindProxyForURL(url, host) {
+  return "DIRECT";
+
diff --git a/test/js-unittest/no_entrypoint.js b/test/js-unittest/no_entrypoint.js
new file mode 100644
index 0000000..8993059
--- /dev/null
+++ b/test/js-unittest/no_entrypoint.js
@@ -0,0 +1,2 @@
+var x = "This is an invalid PAC script because it lacks a " +
+        "FindProxyForURL() function";
diff --git a/test/js-unittest/pac_library_unittest.js b/test/js-unittest/pac_library_unittest.js
new file mode 100644
index 0000000..0c0a4a9
--- /dev/null
+++ b/test/js-unittest/pac_library_unittest.js
@@ -0,0 +1,366 @@
+// This should output "PROXY success:80" if all the tests pass.
+// Otherwise it will output "PROXY failure:<num-failures>".
+//
+// This aims to unit-test the PAC library functions, which are
+// exposed in the PAC's execution environment. (Namely, dnsDomainLevels,
+// timeRange, etc.)
+
+function FindProxyForURL(url, host) {
+  var numTestsFailed = 0;
+
+  // Run all the tests
+  for (var test in Tests) {
+    var t = new TestContext(test);
+
+    // Run the test.
+    Tests[test](t);
+
+    if (t.failed()) {
+      numTestsFailed++;
+    }
+  }
+
+  if (numTestsFailed == 0) {
+    return "PROXY success:80";
+  }
+  return "PROXY failure:" + numTestsFailed;
+}
+
+// --------------------------
+// Tests
+// --------------------------
+
+var Tests = {};
+
+Tests.testDnsDomainIs = function(t) {
+  t.expectTrue(dnsDomainIs("google.com", ".com"));
+  t.expectTrue(dnsDomainIs("google.co.uk", ".co.uk"));
+  t.expectFalse(dnsDomainIs("google.com", ".co.uk"));
+  t.expectFalse(dnsDomainIs("www.adobe.com", ".ad"));
+};
+
+Tests.testDnsDomainLevels = function(t) {
+  t.expectEquals(0, dnsDomainLevels("www"));
+  t.expectEquals(2, dnsDomainLevels("www.google.com"));
+  t.expectEquals(3, dnsDomainLevels("192.168.1.1"));
+};
+
+Tests.testIsInNet = function(t) {
+  t.expectTrue(
+      isInNet("192.89.132.25", "192.89.132.25", "255.255.255.255"));
+  t.expectFalse(
+      isInNet("193.89.132.25", "192.89.132.25", "255.255.255.255"));
+
+  t.expectTrue(isInNet("192.89.132.25", "192.89.0.0", "255.255.0.0"));
+  t.expectFalse(isInNet("193.89.132.25", "192.89.0.0", "255.255.0.0"));
+
+  t.expectFalse(
+      isInNet("192.89.132.a", "192.89.0.0", "255.255.0.0"));
+};
+
+Tests.testIsPlainHostName = function(t) {
+  t.expectTrue(isPlainHostName("google"));
+  t.expectFalse(isPlainHostName("google.com"));
+};
+
+Tests.testLocalHostOrDomainIs = function(t) {
+  t.expectTrue(localHostOrDomainIs("www.google.com", "www.google.com"));
+  t.expectTrue(localHostOrDomainIs("www", "www.google.com"));
+  t.expectFalse(localHostOrDomainIs("maps.google.com", "www.google.com"));
+};
+
+Tests.testShExpMatch = function(t) {
+  t.expectTrue(shExpMatch("foo.jpg", "*.jpg"));
+  t.expectTrue(shExpMatch("foo5.jpg", "*o?.jpg"));
+  t.expectFalse(shExpMatch("foo.jpg", ".jpg"));
+  t.expectFalse(shExpMatch("foo.jpg", "foo"));
+};
+
+Tests.testSortIpAddressList = function(t) {
+  t.expectEquals("::1;::2;::3", sortIpAddressList("::2;::3;::1"));
+  t.expectEquals(
+      "2001:4898:28:3:201:2ff:feea:fc14;fe80::5efe:157:9d3b:8b16;157.59.139.22",
+      sortIpAddressList("157.59.139.22;" +
+                        "2001:4898:28:3:201:2ff:feea:fc14;" +
+                        "fe80::5efe:157:9d3b:8b16"));
+
+  // Single IP address (v4 and v6).
+  t.expectEquals("127.0.0.1", sortIpAddressList("127.0.0.1"));
+  t.expectEquals("::1", sortIpAddressList("::1"))
+
+  // Verify that IPv6 address is not re-written (not reduced).
+  t.expectEquals("0:0::1;192.168.1.1", sortIpAddressList("192.168.1.1;0:0::1"));
+
+  // Input is already sorted.
+  t.expectEquals("::1;192.168.1.3", sortIpAddressList("::1;192.168.1.3"));
+
+  // Same-valued IP addresses (also tests stability).
+  t.expectEquals("0::1;::1;0:0::1", sortIpAddressList("0::1;::1;0:0::1"));
+
+  // Contains extra semi-colons.
+  t.expectEquals("127.0.0.1", sortIpAddressList(";127.0.0.1;"));
+
+  // Contains whitespace (spaces and tabs).
+  t.expectEquals("192.168.0.1;192.168.0.2",
+      sortIpAddressList("192.168.0.1; 192.168.0.2"));
+  t.expectEquals("127.0.0.0;127.0.0.1;127.0.0.2",
+      sortIpAddressList("127.0.0.1;	127.0.0.2;	 127.0.0.0"));
+
+  // Empty lists.
+  t.expectFalse(sortIpAddressList(""));
+  t.expectFalse(sortIpAddressList(" "));
+  t.expectFalse(sortIpAddressList(";"));
+  t.expectFalse(sortIpAddressList(";;"));
+  t.expectFalse(sortIpAddressList(" ;  ; "));
+
+  // Invalid IP addresses.
+  t.expectFalse(sortIpAddressList("256.0.0.1"));
+  t.expectFalse(sortIpAddressList("192.168.1.1;0:0:0:1;127.0.0.1"));
+
+  // Call sortIpAddressList() with wonky arguments.
+  t.expectEquals(null, sortIpAddressList());
+  t.expectEquals(null, sortIpAddressList(null));
+  t.expectEquals(null, sortIpAddressList(null, null));
+};
+
+Tests.testIsInNetEx = function(t) {
+  t.expectTrue(isInNetEx("198.95.249.79", "198.95.249.79/32"));
+  t.expectTrue(isInNetEx("198.95.115.10", "198.95.0.0/16"));
+  t.expectTrue(isInNetEx("198.95.1.1", "198.95.0.0/16"));
+  t.expectTrue(isInNetEx("198.95.1.1", "198.95.3.3/16"));
+  t.expectTrue(isInNetEx("0:0:0:0:0:0:7f00:1", "0:0:0:0:0:0:7f00:1/32"));
+  t.expectTrue(isInNetEx("3ffe:8311:ffff:abcd:1234:dead:beef:101",
+                         "3ffe:8311:ffff::/48"));
+
+  // IPv4 and IPv6 mix.
+  t.expectFalse(isInNetEx("127.0.0.1", "0:0:0:0:0:0:7f00:1/16"));
+  t.expectFalse(isInNetEx("192.168.24.3", "fe80:0:0:0:0:0:c0a8:1803/32"));
+
+  t.expectFalse(isInNetEx("198.95.249.78", "198.95.249.79/32"));
+  t.expectFalse(isInNetEx("198.96.115.10", "198.95.0.0/16"));
+  t.expectFalse(isInNetEx("3fff:8311:ffff:abcd:1234:dead:beef:101",
+                          "3ffe:8311:ffff::/48"));
+
+  // Call isInNetEx with wonky arguments.
+  t.expectEquals(null, isInNetEx());
+  t.expectEquals(null, isInNetEx(null));
+  t.expectEquals(null, isInNetEx(null, null));
+  t.expectEquals(null, isInNetEx(null, null, null));
+  t.expectEquals(null, isInNetEx("198.95.249.79"));
+
+  // Invalid IP address.
+  t.expectFalse(isInNetEx("256.0.0.1", "198.95.249.79"));
+  t.expectFalse(isInNetEx("127.0.0.1 ", "127.0.0.1/32"));  // Extra space.
+
+  // Invalid prefix.
+  t.expectFalse(isInNetEx("198.95.115.10", "198.95.0.0/34"));
+  t.expectFalse(isInNetEx("127.0.0.1", "127.0.0.1"));  // Missing '/' in prefix.
+};
+
+Tests.testWeekdayRange = function(t) {
+  // Test with local time.
+  MockDate.setCurrent("Tue Mar 03 2009");
+  t.expectEquals(true, weekdayRange("MON", "FRI"));
+  t.expectEquals(true, weekdayRange("TUE", "FRI"));
+  t.expectEquals(true, weekdayRange("TUE", "TUE"));
+  t.expectEquals(true, weekdayRange("TUE"));
+  t.expectEquals(false, weekdayRange("WED", "FRI"));
+  t.expectEquals(false, weekdayRange("SUN", "MON"));
+  t.expectEquals(false, weekdayRange("SAT"));
+  t.expectEquals(false, weekdayRange("FRI", "MON"));
+
+  // Test with GMT time.
+  MockDate.setCurrent("Tue Mar 03 2009 GMT");
+  t.expectEquals(true, weekdayRange("MON", "FRI", "GMT"));
+  t.expectEquals(true, weekdayRange("TUE", "FRI", "GMT"));
+  t.expectEquals(true, weekdayRange("TUE", "TUE", "GMT"));
+  t.expectEquals(true, weekdayRange("TUE", "GMT"));
+  t.expectEquals(false, weekdayRange("WED", "FRI", "GMT"));
+  t.expectEquals(false, weekdayRange("SUN", "MON", "GMT"));
+  t.expectEquals(false, weekdayRange("SAT", "GMT"));
+};
+
+Tests.testDateRange = function(t) {
+  // dateRange(day)
+  MockDate.setCurrent("Mar 03 2009");
+  t.expectEquals(true, dateRange(3));
+  t.expectEquals(false, dateRange(1));
+
+  // dateRange(day, "GMT")
+  MockDate.setCurrent("Mar 03 2009 GMT");
+  t.expectEquals(true, dateRange(3, "GMT"));
+  t.expectEquals(false, dateRange(1, "GMT"));
+
+  // dateRange(day1, day2)
+  MockDate.setCurrent("Mar 03 2009");
+  t.expectEquals(true, dateRange(1, 4));
+  t.expectEquals(false, dateRange(4, 20));
+
+  // dateRange(day, month)
+  MockDate.setCurrent("Mar 03 2009");
+  t.expectEquals(true, dateRange(3, "MAR"));
+  MockDate.setCurrent("Mar 03 2014");
+  t.expectEquals(true, dateRange(3, "MAR"));
+  // TODO(eroman):
+  //t.expectEquals(false, dateRange(2, "MAR"));
+  //t.expectEquals(false, dateRange(3, "JAN"));
+
+  // dateRange(day, month, year)
+  MockDate.setCurrent("Mar 03 2009");
+  t.expectEquals(true, dateRange(3, "MAR", 2009));
+  t.expectEquals(false, dateRange(4, "MAR", 2009));
+  t.expectEquals(false, dateRange(3, "FEB", 2009));
+  MockDate.setCurrent("Mar 03 2014");
+  t.expectEquals(false, dateRange(3, "MAR", 2009));
+
+  // dateRange(month1, month2)
+  MockDate.setCurrent("Mar 03 2009");
+  t.expectEquals(true, dateRange("JAN", "MAR"));
+  t.expectEquals(true, dateRange("MAR", "APR"));
+  t.expectEquals(false, dateRange("MAY", "SEP"));
+
+  // dateRange(day1, month1, day2, month2)
+  MockDate.setCurrent("Mar 03 2009");
+  t.expectEquals(true, dateRange(1, "JAN", 3, "MAR"));
+  t.expectEquals(true, dateRange(3, "MAR", 4, "SEP"));
+  t.expectEquals(false, dateRange(4, "MAR", 4, "SEP"));
+
+  // dateRange(month1, year1, month2, year2)
+  MockDate.setCurrent("Mar 03 2009");
+  t.expectEquals(true, dateRange("FEB", 2009, "MAR", 2009));
+  MockDate.setCurrent("Apr 03 2009");
+  t.expectEquals(true, dateRange("FEB", 2009, "MAR", 2010));
+  t.expectEquals(false, dateRange("FEB", 2009, "MAR", 2009));
+
+  // dateRange(day1, month1, year1, day2, month2, year2)
+  MockDate.setCurrent("Mar 03 2009");
+  t.expectEquals(true, dateRange(1, "JAN", 2009, 3, "MAR", 2009));
+  t.expectEquals(true, dateRange(3, "MAR", 2009, 4, "SEP", 2009));
+  t.expectEquals(true, dateRange(3, "JAN", 2009, 4, "FEB", 2010));
+  t.expectEquals(false, dateRange(4, "MAR", 2009, 4, "SEP", 2009));
+};
+
+Tests.testTimeRange = function(t) {
+  // timeRange(hour)
+  MockDate.setCurrent("Mar 03, 2009 03:34:01");
+  t.expectEquals(true, timeRange(3));
+  t.expectEquals(false, timeRange(2));
+
+  // timeRange(hour1, hour2)
+  MockDate.setCurrent("Mar 03, 2009 03:34:01");
+  t.expectEquals(true, timeRange(2, 3));
+  t.expectEquals(true, timeRange(2, 4));
+  t.expectEquals(true, timeRange(3, 5));
+  t.expectEquals(false, timeRange(1, 2));
+  t.expectEquals(false, timeRange(11, 12));
+
+  // timeRange(hour1, min1, hour2, min2)
+  MockDate.setCurrent("Mar 03, 2009 03:34:01");
+  t.expectEquals(true, timeRange(1, 0, 3, 34));
+  t.expectEquals(true, timeRange(1, 0, 3, 35));
+  t.expectEquals(true, timeRange(3, 34, 5, 0));
+  t.expectEquals(false, timeRange(1, 0, 3, 0));
+  t.expectEquals(false, timeRange(11, 0, 16, 0));
+
+  // timeRange(hour1, min1, sec1, hour2, min2, sec2)
+  MockDate.setCurrent("Mar 03, 2009 03:34:14");
+  t.expectEquals(true, timeRange(1, 0, 0, 3, 34, 14));
+  t.expectEquals(false, timeRange(1, 0, 0, 3, 34, 0));
+  t.expectEquals(true, timeRange(1, 0, 0, 3, 35, 0));
+  t.expectEquals(true, timeRange(3, 34, 0, 5, 0, 0));
+  t.expectEquals(false, timeRange(1, 0, 0, 3, 0, 0));
+  t.expectEquals(false, timeRange(11, 0, 0, 16, 0, 0));
+};
+
+// --------------------------
+// TestContext
+// --------------------------
+
+// |name| is the name of the test being executed, it will be used when logging
+// errors.
+function TestContext(name) {
+  this.numFailures_ = 0;
+  this.name_ = name;
+};
+
+TestContext.prototype.failed = function() {
+  return this.numFailures_ != 0;
+};
+
+TestContext.prototype.expectEquals = function(expectation, actual) {
+  if (!(expectation === actual)) {
+    this.numFailures_++;
+    this.log("FAIL: expected: " + expectation + ", actual: " + actual);
+  }
+};
+
+TestContext.prototype.expectTrue = function(x) {
+  this.expectEquals(true, x);
+};
+
+TestContext.prototype.expectFalse = function(x) {
+  this.expectEquals(false, x);
+};
+
+TestContext.prototype.log = function(x) {
+  // Prefix with the test name that generated the log.
+  try {
+    alert(this.name_ + ": " + x);
+  } catch(e) {
+    // In case alert() is not defined.
+  }
+};
+
+// --------------------------
+// MockDate
+// --------------------------
+
+function MockDate() {
+  this.wrappedDate_ = new MockDate.super_(MockDate.currentDateString_);
+};
+
+// Setup the MockDate so it forwards methods to "this.wrappedDate_" (which is a
+// real Date object).  We can't simply chain the prototypes since Date() doesn't
+// allow it.
+MockDate.init = function() {
+  MockDate.super_ = Date;
+
+  function createProxyMethod(methodName) {
+    return function() {
+      return this.wrappedDate_[methodName]
+          .apply(this.wrappedDate_, arguments);
+    }
+  };
+
+  for (i in MockDate.methodNames_) {
+    var methodName = MockDate.methodNames_[i];
+    // Don't define the closure directly in the loop body, since Javascript's
+    // crazy scoping rules mean |methodName| actually bleeds out of the loop!
+    MockDate.prototype[methodName] = createProxyMethod(methodName);
+  }
+
+  // Replace the native Date() with our mock.
+  Date = MockDate;
+};
+
+// Unfortunately Date()'s methods are non-enumerable, therefore list manually.
+MockDate.methodNames_ = [
+  "toString", "toDateString", "toTimeString", "toLocaleString",
+  "toLocaleDateString", "toLocaleTimeString", "valueOf", "getTime",
+  "getFullYear", "getUTCFullYear", "getMonth", "getUTCMonth",
+  "getDate", "getUTCDate", "getDay", "getUTCDay", "getHours", "getUTCHours",
+  "getMinutes", "getUTCMinutes", "getSeconds", "getUTCSeconds",
+  "getMilliseconds", "getUTCMilliseconds", "getTimezoneOffset", "setTime",
+  "setMilliseconds", "setUTCMilliseconds", "setSeconds", "setUTCSeconds",
+  "setMinutes", "setUTCMinutes", "setHours", "setUTCHours", "setDate",
+  "setUTCDate", "setMonth", "setUTCMonth", "setFullYear", "setUTCFullYear",
+  "toGMTString", "toUTCString", "getYear", "setYear"
+];
+
+MockDate.setCurrent = function(currentDateString) {
+  MockDate.currentDateString_ = currentDateString;
+}
+
+// Bind the methods to proxy requests to the wrapped Date().
+MockDate.init();
+
diff --git a/test/js-unittest/passthrough.js b/test/js-unittest/passthrough.js
new file mode 100644
index 0000000..832ac66
--- /dev/null
+++ b/test/js-unittest/passthrough.js
@@ -0,0 +1,45 @@
+// Return a single-proxy result, which encodes ALL the arguments that were
+// passed to FindProxyForURL().
+
+function FindProxyForURL(url, host) {
+  if (arguments.length != 2) {
+    throw "Wrong number of arguments passed to FindProxyForURL!";
+    return "FAIL";
+  }
+
+  return "PROXY " + makePseudoHost(url + "." + host);
+}
+
+// Form a string that kind-of resembles a host. We will replace any
+// non-alphanumeric character with a dot, then fix up the oddly placed dots.
+function makePseudoHost(str) {
+  var result = "";
+
+  for (var i = 0; i < str.length; ++i) {
+    var c = str.charAt(i);
+    if (!isValidPseudoHostChar(c)) {
+      c = '.';  // Replace unsupported characters with a dot.
+    }
+
+    // Take care not to place multiple adjacent dots,
+    // a dot at the beginning, or a dot at the end.
+    if (c == '.' &&
+        (result.length == 0 || 
+         i == str.length - 1 ||
+         result.charAt(result.length - 1) == '.')) {
+      continue;
+    }
+    result += c;
+  }
+  return result;
+}
+
+function isValidPseudoHostChar(c) {
+  if (c >= '0' && c <= '9')
+    return true;
+  if (c >= 'a' && c <= 'z')
+    return true;
+  if (c >= 'A' && c <= 'Z')
+    return true;
+  return false;
+}
diff --git a/test/js-unittest/return_empty_string.js b/test/js-unittest/return_empty_string.js
new file mode 100644
index 0000000..3342196
--- /dev/null
+++ b/test/js-unittest/return_empty_string.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+  return "";
+}
+
diff --git a/test/js-unittest/return_function.js b/test/js-unittest/return_function.js
new file mode 100644
index 0000000..9005553
--- /dev/null
+++ b/test/js-unittest/return_function.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+  return FindProxyForURL;
+}
+
diff --git a/test/js-unittest/return_integer.js b/test/js-unittest/return_integer.js
new file mode 100644
index 0000000..d86b299
--- /dev/null
+++ b/test/js-unittest/return_integer.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+  return 0;
+}
+
diff --git a/test/js-unittest/return_null.js b/test/js-unittest/return_null.js
new file mode 100644
index 0000000..6cf90c5
--- /dev/null
+++ b/test/js-unittest/return_null.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+  return null;
+}
+
diff --git a/test/js-unittest/return_object.js b/test/js-unittest/return_object.js
new file mode 100644
index 0000000..3824f8a
--- /dev/null
+++ b/test/js-unittest/return_object.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+  return {result: "PROXY foo"};
+}
+
diff --git a/test/js-unittest/return_undefined.js b/test/js-unittest/return_undefined.js
new file mode 100644
index 0000000..0f0aa98
--- /dev/null
+++ b/test/js-unittest/return_undefined.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+  return undefined;
+}
+
diff --git a/test/js-unittest/return_unicode.js b/test/js-unittest/return_unicode.js
new file mode 100644
index 0000000..5ecdd1c
--- /dev/null
+++ b/test/js-unittest/return_unicode.js
@@ -0,0 +1,4 @@
+// U+200B is the codepoint for zero-width-space.
+function FindProxyForURL(url, host) {
+  return "PROXY foo.com\u200B";
+}
diff --git a/test/js-unittest/side_effects.js b/test/js-unittest/side_effects.js
new file mode 100644
index 0000000..39b3b2d
--- /dev/null
+++ b/test/js-unittest/side_effects.js
@@ -0,0 +1,10 @@
+if (!gCounter) {
+  // We write it this way so if the script gets loaded twice,
+  // gCounter remains dirty.
+  var gCounter = 0;
+}
+
+function FindProxyForURL(url, host) {
+  return "PROXY sideffect_" + gCounter++;
+}
+
diff --git a/test/js-unittest/simple.js b/test/js-unittest/simple.js
new file mode 100644
index 0000000..c5dfa6d
--- /dev/null
+++ b/test/js-unittest/simple.js
@@ -0,0 +1,21 @@
+// PAC script which uses isInNet on both IP addresses and hosts, and calls
+// isResolvable().
+
+function FindProxyForURL(url, host) {
+  var my_ip = myIpAddress();
+
+  if (isInNet(my_ip, "172.16.0.0", "255.248.0.0")) {
+    return "PROXY a:80";
+  }
+
+  if (url.substring(0, 6) != "https:" &&
+      isInNet(host, "10.0.0.0", "255.0.0.0")) {
+    return "PROXY b:80";
+  }
+
+  if (dnsDomainIs(host, "foo.bar.baz.com") || !isResolvable(host)) {
+    return "PROXY c:100";
+  }
+
+  return "DIRECT";
+}
diff --git a/test/js-unittest/unhandled_exception.js b/test/js-unittest/unhandled_exception.js
new file mode 100644
index 0000000..9cc2856
--- /dev/null
+++ b/test/js-unittest/unhandled_exception.js
@@ -0,0 +1,5 @@
+function FindProxyForURL(url, host) {
+  // This will throw a runtime exception.
+  return "PROXY x" + undefined_variable;
+}
+
diff --git a/test/jstocstring.pl b/test/jstocstring.pl
new file mode 100755
index 0000000..7b50772
--- /dev/null
+++ b/test/jstocstring.pl
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+print "Reading from $ARGV[0]\nWriting to $ARGV[1]\n";
+open(LS, "ls $ARGV[0]|");
+open(FILE, "> $ARGV[1]");
+print FILE "// This file is auto generated using the following command.\n";
+print FILE "// Do not modify.\n";
+print FILE "// \t./jstocstring.pl $ARGV[0] $ARGV[1]\n";
+print FILE "#ifndef PROXY_TEST_SCRIPT_H_\n";
+print FILE "#define PROXY_TEST_SCRIPT_H_\n\n";
+
+while (<LS>) {
+    chomp();
+    open(FH, "cat $ARGV[0]/$_|");
+    if (s/\.js/_JS/) {
+        $upper = uc();
+        print FILE "#define $upper \\\n";
+        while (<FH>) {
+            s/\"/\\\"/g;
+            chomp();
+            print FILE "  \"",$_,"\\n\" \\\n";
+        }
+    }
+    print FILE "\n"
+}
+print FILE "#endif //PROXY_TEST_SCRIPT_H_\n";
+close(FILE);
diff --git a/test/proxy_resolver_v8_unittest.cc b/test/proxy_resolver_v8_unittest.cc
new file mode 100644
index 0000000..cfd38bc
--- /dev/null
+++ b/test/proxy_resolver_v8_unittest.cc
@@ -0,0 +1,547 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#define LOG_TAG "ProxyResolverTest"
+
+#include <utils/Log.h>
+#include "android_runtime/AndroidRuntime.h"
+#include <string.h>
+
+#include "proxy_test_script.h"
+#include "proxy_resolver_v8.h"
+#include "include/gtest/gtest.h"
+
+using namespace android;
+namespace net {
+namespace {
+
+// Javascript bindings for ProxyResolverV8, which returns mock values.
+// Each time one of the bindings is called into, we push the input into a
+// list, for later verification.
+class MockJSBindings : public ProxyResolverJSBindings, public ProxyErrorListener {
+ public:
+  MockJSBindings() : my_ip_address_count(0), my_ip_address_ex_count(0) {}
+
+  virtual bool MyIpAddress(std::string* ip_address) {
+    my_ip_address_count++;
+    *ip_address = my_ip_address_result;
+    return !my_ip_address_result.empty();
+  }
+
+  virtual bool MyIpAddressEx(std::string* ip_address_list) {
+    my_ip_address_ex_count++;
+    *ip_address_list = my_ip_address_ex_result;
+    return !my_ip_address_ex_result.empty();
+  }
+
+  virtual bool DnsResolve(const std::string& host, std::string* ip_address) {
+    dns_resolves.push_back(host);
+    *ip_address = dns_resolve_result;
+    return !dns_resolve_result.empty();
+  }
+
+  virtual bool DnsResolveEx(const std::string& host,
+                            std::string* ip_address_list) {
+    dns_resolves_ex.push_back(host);
+    *ip_address_list = dns_resolve_ex_result;
+    return !dns_resolve_ex_result.empty();
+  }
+
+  virtual void AlertMessage(String16 message) {
+    String8 m8(message);
+    std::string mstd(m8.string());
+
+    ALOGD("PAC-alert: %s\n", mstd.c_str());  // Helpful when debugging.
+    alerts.push_back(mstd);
+  }
+
+  virtual void ErrorMessage(const String16 message) {
+    String8 m8(message);
+    std::string mstd(m8.string());
+
+    ALOGD("PAC-error: %s\n", mstd.c_str());  // Helpful when debugging.
+    errors.push_back(mstd);
+  }
+
+  virtual void Shutdown() {}
+
+  // Mock values to return.
+  std::string my_ip_address_result;
+  std::string my_ip_address_ex_result;
+  std::string dns_resolve_result;
+  std::string dns_resolve_ex_result;
+
+  // Inputs we got called with.
+  std::vector<std::string> alerts;
+  std::vector<std::string> errors;
+  std::vector<std::string> dns_resolves;
+  std::vector<std::string> dns_resolves_ex;
+  int my_ip_address_count;
+  int my_ip_address_ex_count;
+};
+
+// This is the same as ProxyResolverV8, but it uses mock bindings in place of
+// the default bindings, and has a helper function to load PAC scripts from
+// disk.
+class ProxyResolverV8WithMockBindings : public ProxyResolverV8 {
+ public:
+  ProxyResolverV8WithMockBindings(MockJSBindings* mock_js_bindings) :
+      ProxyResolverV8(mock_js_bindings, mock_js_bindings), mock_js_bindings_(mock_js_bindings) {
+  }
+
+  MockJSBindings* mock_js_bindings() const {
+    return mock_js_bindings_;
+  }
+
+ private:
+  MockJSBindings* mock_js_bindings_;
+};
+
+// Doesn't really matter what these values are for many of the tests.
+const String16 kQueryUrl("http://www.google.com");
+const String16 kQueryHost("www.google.com");
+String16 kResults;
+
+String16 currentPac;
+#define SCRIPT(x) (currentPac = String16(x))
+
+void addString(std::vector<std::string>* list, std::string str) {
+  if (str.compare(0, 6, "DIRECT") == 0) {
+    list->push_back("DIRECT");
+  } else if (str.compare(0, 6, "PROXY ") == 0) {
+    list->push_back(str.substr(6));
+  } else {
+    ALOGE("Unrecognized proxy string");
+  }
+}
+
+std::vector<std::string> string16ToProxyList(String16 response) {
+    std::vector<std::string> ret;
+    String8 response8(response);
+    std::string rstr(response8.string());
+    if (rstr.find(';') == std::string::npos) {
+        addString(&ret, rstr);
+        return ret;
+    }
+    char str[128];
+    rstr.copy(str, 0, rstr.length());
+    const char* pch = strtok(str, ";");
+
+    while (pch != NULL) {
+        // Skip leading whitespace
+        while ((*pch) == ' ') ++pch;
+        std::string pstring(pch);
+        addString(&ret, pstring);
+
+        pch = strtok(NULL, "; \t");
+    }
+
+    return ret;
+}
+
+std::string StringPrintf(std::string str, int d) {
+    char buf[30];
+    sprintf(buf, str.c_str(), d);
+    return std::string(buf);
+}
+
+TEST(ProxyResolverV8Test, Direct) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(DIRECT_JS));
+  EXPECT_EQ(OK, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(OK, result);
+  std::vector<std::string> proxies = string16ToProxyList(kResults);
+  EXPECT_EQ(proxies.size(), 1U);
+  EXPECT_EQ("DIRECT",proxies[0]);
+
+  EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+  EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+}
+
+TEST(ProxyResolverV8Test, ReturnEmptyString) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(RETURN_EMPTY_STRING_JS));
+  EXPECT_EQ(OK, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(OK, result);
+  std::vector<std::string> proxies = string16ToProxyList(kResults);
+  EXPECT_EQ(proxies.size(), 0U);
+
+  EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+  EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+}
+
+TEST(ProxyResolverV8Test, Basic) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(PASSTHROUGH_JS));
+  EXPECT_EQ(OK, result);
+
+  // The "FindProxyForURL" of this PAC script simply concatenates all of the
+  // arguments into a pseudo-host. The purpose of this test is to verify that
+  // the correct arguments are being passed to FindProxyForURL().
+  {
+    String16 queryUrl("http://query.com/path");
+    String16 queryHost("query.com");
+    result = resolver.GetProxyForURL(queryUrl, queryHost, &kResults);
+    EXPECT_EQ(OK, result);
+    std::vector<std::string> proxies = string16ToProxyList(kResults);
+    EXPECT_EQ(1U, proxies.size());
+    EXPECT_EQ("http.query.com.path.query.com", proxies[0]);
+  }
+  {
+    String16 queryUrl("ftp://query.com:90/path");
+    String16 queryHost("query.com");
+    int result = resolver.GetProxyForURL(queryUrl, queryHost, &kResults);
+
+    EXPECT_EQ(OK, result);
+    // Note that FindProxyForURL(url, host) does not expect |host| to contain
+    // the port number.
+    std::vector<std::string> proxies = string16ToProxyList(kResults);
+    EXPECT_EQ(1U, proxies.size());
+    EXPECT_EQ("ftp.query.com.90.path.query.com", proxies[0]);
+
+    EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+    EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+  }
+
+  // We call this so we'll have code coverage of the function and valgrind will
+  // make sure nothing bad happens.
+  //
+  // NOTE: This is here instead of in its own test so that we'll be calling it
+  // after having done something, in hopes it won't be a no-op.
+  resolver.PurgeMemory();
+}
+
+TEST(ProxyResolverV8Test, BadReturnType) {
+  // These are the files of PAC scripts which each return a non-string
+  // types for FindProxyForURL(). They should all fail with
+  // ERR_PAC_SCRIPT_FAILED.
+  static const String16 files[] = {
+      String16(RETURN_UNDEFINED_JS),
+      String16(RETURN_INTEGER_JS),
+      String16(RETURN_FUNCTION_JS),
+      String16(RETURN_OBJECT_JS),
+      String16(RETURN_NULL_JS)
+  };
+
+  for (size_t i = 0; i < 5; ++i) {
+    ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+    int result = resolver.SetPacScript(files[i]);
+    EXPECT_EQ(OK, result);
+
+    result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+    EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+
+    MockJSBindings* bindings = resolver.mock_js_bindings();
+    EXPECT_EQ(0U, bindings->alerts.size());
+    ASSERT_EQ(1U, bindings->errors.size());
+    EXPECT_EQ("FindProxyForURL() did not return a string.",
+              bindings->errors[0]);
+  }
+}
+
+// Try using a PAC script which defines no "FindProxyForURL" function.
+TEST(ProxyResolverV8Test, NoEntryPoint) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(NO_ENTRYPOINT_JS));
+  EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(ERR_FAILED, result);
+}
+
+// Try loading a malformed PAC script.
+TEST(ProxyResolverV8Test, ParseError) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(MISSING_CLOSE_BRACE_JS));
+  EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(ERR_FAILED, result);
+
+  MockJSBindings* bindings = resolver.mock_js_bindings();
+  EXPECT_EQ(0U, bindings->alerts.size());
+
+  // We get one error during compilation.
+  ASSERT_EQ(1U, bindings->errors.size());
+
+  EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input",
+            bindings->errors[0]);
+}
+
+// Run a PAC script several times, which has side-effects.
+TEST(ProxyResolverV8Test, SideEffects) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(SIDE_EFFECTS_JS));
+
+  // The PAC script increments a counter each time we invoke it.
+  for (int i = 0; i < 3; ++i) {
+    result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+    EXPECT_EQ(OK, result);
+    std::vector<std::string> proxies = string16ToProxyList(kResults);
+    EXPECT_EQ(1U, proxies.size());
+    EXPECT_EQ(StringPrintf("sideffect_%d", i),
+              proxies[0]);
+  }
+
+  // Reload the script -- the javascript environment should be reset, hence
+  // the counter starts over.
+  result = resolver.SetPacScript(SCRIPT(SIDE_EFFECTS_JS));
+  EXPECT_EQ(OK, result);
+
+  for (int i = 0; i < 3; ++i) {
+    result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+    EXPECT_EQ(OK, result);
+    std::vector<std::string> proxies = string16ToProxyList(kResults);
+    EXPECT_EQ(1U, proxies.size());
+    EXPECT_EQ(StringPrintf("sideffect_%d", i),
+              proxies[0]);
+  }
+}
+
+// Execute a PAC script which throws an exception in FindProxyForURL.
+TEST(ProxyResolverV8Test, UnhandledException) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(UNHANDLED_EXCEPTION_JS));
+  EXPECT_EQ(OK, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+
+  MockJSBindings* bindings = resolver.mock_js_bindings();
+  EXPECT_EQ(0U, bindings->alerts.size());
+  ASSERT_EQ(1U, bindings->errors.size());
+  EXPECT_EQ("Uncaught ReferenceError: undefined_variable is not defined",
+            bindings->errors[0]);
+}
+
+TEST(ProxyResolverV8Test, ReturnUnicode) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(RETURN_UNICODE_JS));
+  EXPECT_EQ(OK, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  // The result from this resolve was unparseable, because it
+  // wasn't ASCII.
+  EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+}
+
+// Test the PAC library functions that we expose in the JS environmnet.
+TEST(ProxyResolverV8Test, JavascriptLibrary) {
+  ALOGE("Javascript start");
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(PAC_LIBRARY_UNITTEST_JS));
+  EXPECT_EQ(OK, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  // If the javascript side of this unit-test fails, it will throw a javascript
+  // exception. Otherwise it will return "PROXY success:80".
+  EXPECT_EQ(OK, result);
+  std::vector<std::string> proxies = string16ToProxyList(kResults);
+  EXPECT_EQ(1U, proxies.size());
+  EXPECT_EQ("success:80", proxies[0]);
+
+  EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+  EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+}
+
+// Try resolving when SetPacScriptByData() has not been called.
+TEST(ProxyResolverV8Test, NoSetPacScript) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+
+
+  // Resolve should fail, as we are not yet initialized with a script.
+  int result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+  EXPECT_EQ(ERR_FAILED, result);
+
+  // Initialize it.
+  result = resolver.SetPacScript(SCRIPT(DIRECT_JS));
+  EXPECT_EQ(OK, result);
+
+  // Resolve should now succeed.
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+  EXPECT_EQ(OK, result);
+
+  // Clear it, by initializing with an empty string.
+  resolver.SetPacScript(SCRIPT());
+
+  // Resolve should fail again now.
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+  EXPECT_EQ(ERR_FAILED, result);
+
+  // Load a good script once more.
+  result = resolver.SetPacScript(SCRIPT(DIRECT_JS));
+  EXPECT_EQ(OK, result);
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+  EXPECT_EQ(OK, result);
+
+  EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+  EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+}
+
+// Test marshalling/un-marshalling of values between C++/V8.
+TEST(ProxyResolverV8Test, V8Bindings) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  MockJSBindings* bindings = resolver.mock_js_bindings();
+  bindings->dns_resolve_result = "127.0.0.1";
+  int result = resolver.SetPacScript(SCRIPT(BINDINGS_JS));
+  EXPECT_EQ(OK, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(OK, result);
+  std::vector<std::string> proxies = string16ToProxyList(kResults);
+  EXPECT_EQ(1U, proxies.size());
+  EXPECT_EQ("DIRECT", proxies[0]);
+
+  EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+
+  // Alert was called 5 times.
+  ASSERT_EQ(5U, bindings->alerts.size());
+  EXPECT_EQ("undefined", bindings->alerts[0]);
+  EXPECT_EQ("null", bindings->alerts[1]);
+  EXPECT_EQ("undefined", bindings->alerts[2]);
+  EXPECT_EQ("[object Object]", bindings->alerts[3]);
+  EXPECT_EQ("exception from calling toString()", bindings->alerts[4]);
+
+  // DnsResolve was called 8 times, however only 2 of those were string
+  // parameters. (so 6 of them failed immediately).
+  ASSERT_EQ(2U, bindings->dns_resolves.size());
+  EXPECT_EQ("", bindings->dns_resolves[0]);
+  EXPECT_EQ("arg1", bindings->dns_resolves[1]);
+
+  // MyIpAddress was called two times.
+  EXPECT_EQ(2, bindings->my_ip_address_count);
+
+  // MyIpAddressEx was called once.
+  EXPECT_EQ(1, bindings->my_ip_address_ex_count);
+
+  // DnsResolveEx was called 2 times.
+  ASSERT_EQ(2U, bindings->dns_resolves_ex.size());
+  EXPECT_EQ("is_resolvable", bindings->dns_resolves_ex[0]);
+  EXPECT_EQ("foobar", bindings->dns_resolves_ex[1]);
+}
+
+// Test calling a binding (myIpAddress()) from the script's global scope.
+// http://crbug.com/40026
+TEST(ProxyResolverV8Test, BindingCalledDuringInitialization) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+
+  int result = resolver.SetPacScript(SCRIPT(BINDING_FROM_GLOBAL_JS));
+  EXPECT_EQ(OK, result);
+
+  MockJSBindings* bindings = resolver.mock_js_bindings();
+
+  // myIpAddress() got called during initialization of the script.
+  EXPECT_EQ(1, bindings->my_ip_address_count);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(OK, result);
+  std::vector<std::string> proxies = string16ToProxyList(kResults);
+  EXPECT_EQ(1U, proxies.size());
+  EXPECT_NE("DIRECT", proxies[0]);
+  EXPECT_EQ("127.0.0.1:80", proxies[0]);
+
+  // Check that no other bindings were called.
+  EXPECT_EQ(0U, bindings->errors.size());
+  ASSERT_EQ(0U, bindings->alerts.size());
+  ASSERT_EQ(0U, bindings->dns_resolves.size());
+  EXPECT_EQ(0, bindings->my_ip_address_ex_count);
+  ASSERT_EQ(0U, bindings->dns_resolves_ex.size());
+}
+
+// Try loading a PAC script that ends with a comment and has no terminal
+// newline. This should not cause problems with the PAC utility functions
+// that we add to the script's environment.
+// http://crbug.com/22864
+TEST(ProxyResolverV8Test, EndsWithCommentNoNewline) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(ENDS_WITH_COMMENT_JS));
+  EXPECT_EQ(OK, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(OK, result);
+  std::vector<std::string> proxies = string16ToProxyList(kResults);
+  EXPECT_EQ(1U, proxies.size());
+  EXPECT_NE("DIRECT", proxies[0]);
+  EXPECT_EQ("success:80", proxies[0]);
+}
+
+// Try loading a PAC script that ends with a statement and has no terminal
+// newline. This should not cause problems with the PAC utility functions
+// that we add to the script's environment.
+// http://crbug.com/22864
+TEST(ProxyResolverV8Test, EndsWithStatementNoNewline) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(
+      SCRIPT(ENDS_WITH_STATEMENT_NO_SEMICOLON_JS));
+  EXPECT_EQ(OK, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(OK, result);
+  std::vector<std::string> proxies = string16ToProxyList(kResults);
+  EXPECT_EQ(1U, proxies.size());
+  EXPECT_NE("DIRECT", proxies[0]);
+  EXPECT_EQ("success:3", proxies[0]);
+}
+
+// Test the return values from myIpAddress(), myIpAddressEx(), dnsResolve(),
+// dnsResolveEx(), isResolvable(), isResolvableEx(), when the the binding
+// returns empty string (failure). This simulates the return values from
+// those functions when the underlying DNS resolution fails.
+TEST(ProxyResolverV8Test, DNSResolutionFailure) {
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(SCRIPT(DNS_FAIL_JS));
+  EXPECT_EQ(OK, result);
+
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(OK, result);
+  std::vector<std::string> proxies = string16ToProxyList(kResults);
+  EXPECT_EQ(1U, proxies.size());
+  EXPECT_NE("DIRECT", proxies[0]);
+  EXPECT_EQ("success:80", proxies[0]);
+}
+
+TEST(ProxyResolverV8Test, DNSResolutionOfInternationDomainName) {
+    return;
+  ProxyResolverV8WithMockBindings resolver(new MockJSBindings());
+  int result = resolver.SetPacScript(String16(INTERNATIONAL_DOMAIN_NAMES_JS));
+  EXPECT_EQ(OK, result);
+
+  // Execute FindProxyForURL().
+  result = resolver.GetProxyForURL(kQueryUrl, kQueryHost, &kResults);
+
+  EXPECT_EQ(OK, result);
+  std::vector<std::string> proxies = string16ToProxyList(kResults);
+  EXPECT_EQ(1U, proxies.size());
+  EXPECT_EQ("DIRECT", proxies[0]);
+
+  // Check that the international domain name was converted to punycode
+  // before passing it onto the bindings layer.
+  MockJSBindings* bindings = resolver.mock_js_bindings();
+
+  ASSERT_EQ(1u, bindings->dns_resolves.size());
+  EXPECT_EQ("xn--bcher-kva.ch", bindings->dns_resolves[0]);
+
+  ASSERT_EQ(1u, bindings->dns_resolves_ex.size());
+  EXPECT_EQ("xn--bcher-kva.ch", bindings->dns_resolves_ex[0]);
+}
+
+}  // namespace
+}  // namespace net
diff --git a/test/proxy_test_script.h b/test/proxy_test_script.h
new file mode 100644
index 0000000..88af872
--- /dev/null
+++ b/test/proxy_test_script.h
@@ -0,0 +1,660 @@
+// This file is auto generated using the following command.
+// Do not modify.
+// 	./jstocstring.pl js-unittest proxy_test_script.h
+#ifndef PROXY_TEST_SCRIPT_H_
+#define PROXY_TEST_SCRIPT_H_
+
+#define BINDING_FROM_GLOBAL_JS \
+  "// Calls a bindings outside of FindProxyForURL(). This causes the code to\n" \
+  "// get exercised during initialization.\n" \
+  "\n" \
+  "var x = myIpAddress();\n" \
+  "\n" \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return \"PROXY \" + x + \":80\";\n" \
+  "}\n" \
+
+#define BINDINGS_JS \
+  "// Try calling the browser-side bound functions with varying (invalid)\n" \
+  "// inputs. There is no notion of \"success\" for this test, other than\n" \
+  "// verifying the correct C++ bindings were reached with expected values.\n" \
+  "\n" \
+  "function MyObject() {\n" \
+  "  this.x = \"3\";\n" \
+  "}\n" \
+  "\n" \
+  "MyObject.prototype.toString = function() {\n" \
+  "  throw \"exception from calling toString()\";\n" \
+  "}\n" \
+  "\n" \
+  "function expectEquals(expectation, actual) {\n" \
+  "  if (!(expectation === actual)) {\n" \
+  "    throw \"FAIL: expected: \" + expectation + \", actual: \" + actual;\n" \
+  "  }\n" \
+  "}\n" \
+  "\n" \
+  "function FindProxyForURL(url, host) {\n" \
+  "  // Call dnsResolve with some wonky arguments.\n" \
+  "  // Those expected to fail (because we have passed a non-string parameter)\n" \
+  "  // will return |null|, whereas those that have called through to the C++\n" \
+  "  // bindings will return '127.0.0.1'.\n" \
+  "  expectEquals(null, dnsResolve());\n" \
+  "  expectEquals(null, dnsResolve(null));\n" \
+  "  expectEquals(null, dnsResolve(undefined));\n" \
+  "  expectEquals('127.0.0.1', dnsResolve(\"\"));\n" \
+  "  expectEquals(null, dnsResolve({foo: 'bar'}));\n" \
+  "  expectEquals(null, dnsResolve(fn));\n" \
+  "  expectEquals(null, dnsResolve(['3']));\n" \
+  "  expectEquals('127.0.0.1', dnsResolve(\"arg1\", \"arg2\", \"arg3\", \"arg4\"));\n" \
+  "\n" \
+  "  // Call alert with some wonky arguments.\n" \
+  "  alert();\n" \
+  "  alert(null);\n" \
+  "  alert(undefined);\n" \
+  "  alert({foo:'bar'});\n" \
+  "\n" \
+  "  // This should throw an exception when we toString() the argument\n" \
+  "  // to alert in the bindings.\n" \
+  "  try {\n" \
+  "    alert(new MyObject());\n" \
+  "  } catch (e) {\n" \
+  "    alert(e);\n" \
+  "  }\n" \
+  "\n" \
+  "  // Call myIpAddress() with wonky arguments\n" \
+  "  myIpAddress(null);\n" \
+  "  myIpAddress(null, null);\n" \
+  "\n" \
+  "  // Call myIpAddressEx() correctly (no arguments).\n" \
+  "  myIpAddressEx();\n" \
+  "\n" \
+  "  // Call dnsResolveEx() (note that isResolvableEx() implicity calls it.)\n" \
+  "  isResolvableEx(\"is_resolvable\");\n" \
+  "  dnsResolveEx(\"foobar\");\n" \
+  "\n" \
+  "  return \"DIRECT\";\n" \
+  "}\n" \
+  "\n" \
+  "function fn() {}\n" \
+  "\n" \
+
+#define DIRECT_JS \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return \"DIRECT\";\n" \
+  "}\n" \
+  "\n" \
+
+#define DNS_FAIL_JS \
+  "// This script should be run in an environment where all DNS resolution are\n" \
+  "// failing. It tests that functions return the expected values.\n" \
+  "//\n" \
+  "// Returns \"PROXY success:80\" on success.\n" \
+  "function FindProxyForURL(url, host) {\n" \
+  "  try {\n" \
+  "    expectEq(\"127.0.0.1\", myIpAddress());\n" \
+  "    expectEq(\"\", myIpAddressEx());\n" \
+  "\n" \
+  "    expectEq(null, dnsResolve(\"not-found\"));\n" \
+  "    expectEq(\"\", dnsResolveEx(\"not-found\"));\n" \
+  "\n" \
+  "    expectEq(false, isResolvable(\"not-found\"));\n" \
+  "    expectEq(false, isResolvableEx(\"not-found\"));\n" \
+  "\n" \
+  "    return \"PROXY success:80\";\n" \
+  "  } catch(e) {\n" \
+  "    alert(e);\n" \
+  "    return \"PROXY failed:80\";\n" \
+  "  }\n" \
+  "}\n" \
+  "\n" \
+  "function expectEq(expected, actual) {\n" \
+  "  if (expected != actual)\n" \
+  "    throw \"Expected \" + expected + \" but was \" + actual;\n" \
+  "}\n" \
+  "\n" \
+
+#define ENDS_WITH_COMMENT_JS \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return \"PROXY success:80\";\n" \
+  "}\n" \
+  "\n" \
+  "// We end the script with a comment (and no trailing newline).\n" \
+  "// This used to cause problems, because internally ProxyResolverV8\n" \
+  "// would append some functions to the script; the first line of\n" \
+  "// those extra functions was being considered part of the comment.\n" \
+
+#define ENDS_WITH_STATEMENT_NO_SEMICOLON_JS \
+  "// Ends with a statement, and no terminal newline.\n" \
+  "function FindProxyForURL(url, host) { return \"PROXY success:\" + x; }\n" \
+  "x = 3\n" \
+
+#define INTERNATIONAL_DOMAIN_NAMES_JS \
+  "// Try resolving hostnames containing non-ASCII characters.\n" \
+  "\n" \
+  "function FindProxyForURL(url, host) {\n" \
+  "  // This international hostname has a non-ASCII character. It is represented\n" \
+  "  // in punycode as 'xn--bcher-kva.ch'\n" \
+  "  var idn = 'B\u00fccher.ch';\n" \
+  "\n" \
+  "  // We disregard the actual return value -- all we care about is that on\n" \
+  "  // the C++ end the bindings were passed the punycode equivalent of this\n" \
+  "  // unicode hostname.\n" \
+  "  dnsResolve(idn);\n" \
+  "  dnsResolveEx(idn);\n" \
+  "\n" \
+  "  return \"DIRECT\";\n" \
+  "}\n" \
+  "\n" \
+
+#define MISSING_CLOSE_BRACE_JS \
+  "// This PAC script is invalid, because there is a missing close brace\n" \
+  "// on the function FindProxyForURL().\n" \
+  "\n" \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return \"DIRECT\";\n" \
+  "\n" \
+
+#define NO_ENTRYPOINT_JS \
+  "var x = \"This is an invalid PAC script because it lacks a \" +\n" \
+  "        \"FindProxyForURL() function\";\n" \
+
+#define PAC_LIBRARY_UNITTEST_JS \
+  "// This should output \"PROXY success:80\" if all the tests pass.\n" \
+  "// Otherwise it will output \"PROXY failure:<num-failures>\".\n" \
+  "//\n" \
+  "// This aims to unit-test the PAC library functions, which are\n" \
+  "// exposed in the PAC's execution environment. (Namely, dnsDomainLevels,\n" \
+  "// timeRange, etc.)\n" \
+  "\n" \
+  "function FindProxyForURL(url, host) {\n" \
+  "  var numTestsFailed = 0;\n" \
+  "\n" \
+  "  // Run all the tests\n" \
+  "  for (var test in Tests) {\n" \
+  "    var t = new TestContext(test);\n" \
+  "\n" \
+  "    // Run the test.\n" \
+  "    Tests[test](t);\n" \
+  "\n" \
+  "    if (t.failed()) {\n" \
+  "      numTestsFailed++;\n" \
+  "    }\n" \
+  "  }\n" \
+  "\n" \
+  "  if (numTestsFailed == 0) {\n" \
+  "    return \"PROXY success:80\";\n" \
+  "  }\n" \
+  "  return \"PROXY failure:\" + numTestsFailed;\n" \
+  "}\n" \
+  "\n" \
+  "// --------------------------\n" \
+  "// Tests\n" \
+  "// --------------------------\n" \
+  "\n" \
+  "var Tests = {};\n" \
+  "\n" \
+  "Tests.testDnsDomainIs = function(t) {\n" \
+  "  t.expectTrue(dnsDomainIs(\"google.com\", \".com\"));\n" \
+  "  t.expectTrue(dnsDomainIs(\"google.co.uk\", \".co.uk\"));\n" \
+  "  t.expectFalse(dnsDomainIs(\"google.com\", \".co.uk\"));\n" \
+  "  t.expectFalse(dnsDomainIs(\"www.adobe.com\", \".ad\"));\n" \
+  "};\n" \
+  "\n" \
+  "Tests.testDnsDomainLevels = function(t) {\n" \
+  "  t.expectEquals(0, dnsDomainLevels(\"www\"));\n" \
+  "  t.expectEquals(2, dnsDomainLevels(\"www.google.com\"));\n" \
+  "  t.expectEquals(3, dnsDomainLevels(\"192.168.1.1\"));\n" \
+  "};\n" \
+  "\n" \
+  "Tests.testIsInNet = function(t) {\n" \
+  "  t.expectTrue(\n" \
+  "      isInNet(\"192.89.132.25\", \"192.89.132.25\", \"255.255.255.255\"));\n" \
+  "  t.expectFalse(\n" \
+  "      isInNet(\"193.89.132.25\", \"192.89.132.25\", \"255.255.255.255\"));\n" \
+  "\n" \
+  "  t.expectTrue(isInNet(\"192.89.132.25\", \"192.89.0.0\", \"255.255.0.0\"));\n" \
+  "  t.expectFalse(isInNet(\"193.89.132.25\", \"192.89.0.0\", \"255.255.0.0\"));\n" \
+  "\n" \
+  "  t.expectFalse(\n" \
+  "      isInNet(\"192.89.132.a\", \"192.89.0.0\", \"255.255.0.0\"));\n" \
+  "};\n" \
+  "\n" \
+  "Tests.testIsPlainHostName = function(t) {\n" \
+  "  t.expectTrue(isPlainHostName(\"google\"));\n" \
+  "  t.expectFalse(isPlainHostName(\"google.com\"));\n" \
+  "};\n" \
+  "\n" \
+  "Tests.testLocalHostOrDomainIs = function(t) {\n" \
+  "  t.expectTrue(localHostOrDomainIs(\"www.google.com\", \"www.google.com\"));\n" \
+  "  t.expectTrue(localHostOrDomainIs(\"www\", \"www.google.com\"));\n" \
+  "  t.expectFalse(localHostOrDomainIs(\"maps.google.com\", \"www.google.com\"));\n" \
+  "};\n" \
+  "\n" \
+  "Tests.testShExpMatch = function(t) {\n" \
+  "  t.expectTrue(shExpMatch(\"foo.jpg\", \"*.jpg\"));\n" \
+  "  t.expectTrue(shExpMatch(\"foo5.jpg\", \"*o?.jpg\"));\n" \
+  "  t.expectFalse(shExpMatch(\"foo.jpg\", \".jpg\"));\n" \
+  "  t.expectFalse(shExpMatch(\"foo.jpg\", \"foo\"));\n" \
+  "};\n" \
+  "\n" \
+  "Tests.testSortIpAddressList = function(t) {\n" \
+  "  t.expectEquals(\"::1;::2;::3\", sortIpAddressList(\"::2;::3;::1\"));\n" \
+  "  t.expectEquals(\n" \
+  "      \"2001:4898:28:3:201:2ff:feea:fc14;fe80::5efe:157:9d3b:8b16;157.59.139.22\",\n" \
+  "      sortIpAddressList(\"157.59.139.22;\" +\n" \
+  "                        \"2001:4898:28:3:201:2ff:feea:fc14;\" +\n" \
+  "                        \"fe80::5efe:157:9d3b:8b16\"));\n" \
+  "\n" \
+  "  // Single IP address (v4 and v6).\n" \
+  "  t.expectEquals(\"127.0.0.1\", sortIpAddressList(\"127.0.0.1\"));\n" \
+  "  t.expectEquals(\"::1\", sortIpAddressList(\"::1\"))\n" \
+  "\n" \
+  "  // Verify that IPv6 address is not re-written (not reduced).\n" \
+  "  t.expectEquals(\"0:0::1;192.168.1.1\", sortIpAddressList(\"192.168.1.1;0:0::1\"));\n" \
+  "\n" \
+  "  // Input is already sorted.\n" \
+  "  t.expectEquals(\"::1;192.168.1.3\", sortIpAddressList(\"::1;192.168.1.3\"));\n" \
+  "\n" \
+  "  // Same-valued IP addresses (also tests stability).\n" \
+  "  t.expectEquals(\"0::1;::1;0:0::1\", sortIpAddressList(\"0::1;::1;0:0::1\"));\n" \
+  "\n" \
+  "  // Contains extra semi-colons.\n" \
+  "  t.expectEquals(\"127.0.0.1\", sortIpAddressList(\";127.0.0.1;\"));\n" \
+  "\n" \
+  "  // Contains whitespace (spaces and tabs).\n" \
+  "  t.expectEquals(\"192.168.0.1;192.168.0.2\",\n" \
+  "      sortIpAddressList(\"192.168.0.1; 192.168.0.2\"));\n" \
+  "  t.expectEquals(\"127.0.0.0;127.0.0.1;127.0.0.2\",\n" \
+  "      sortIpAddressList(\"127.0.0.1;	127.0.0.2;	 127.0.0.0\"));\n" \
+  "\n" \
+  "  // Empty lists.\n" \
+  "  t.expectFalse(sortIpAddressList(\"\"));\n" \
+  "  t.expectFalse(sortIpAddressList(\" \"));\n" \
+  "  t.expectFalse(sortIpAddressList(\";\"));\n" \
+  "  t.expectFalse(sortIpAddressList(\";;\"));\n" \
+  "  t.expectFalse(sortIpAddressList(\" ;  ; \"));\n" \
+  "\n" \
+  "  // Invalid IP addresses.\n" \
+  "  t.expectFalse(sortIpAddressList(\"256.0.0.1\"));\n" \
+  "  t.expectFalse(sortIpAddressList(\"192.168.1.1;0:0:0:1;127.0.0.1\"));\n" \
+  "\n" \
+  "  // Call sortIpAddressList() with wonky arguments.\n" \
+  "  t.expectEquals(null, sortIpAddressList());\n" \
+  "  t.expectEquals(null, sortIpAddressList(null));\n" \
+  "  t.expectEquals(null, sortIpAddressList(null, null));\n" \
+  "};\n" \
+  "\n" \
+  "Tests.testIsInNetEx = function(t) {\n" \
+  "  t.expectTrue(isInNetEx(\"198.95.249.79\", \"198.95.249.79/32\"));\n" \
+  "  t.expectTrue(isInNetEx(\"198.95.115.10\", \"198.95.0.0/16\"));\n" \
+  "  t.expectTrue(isInNetEx(\"198.95.1.1\", \"198.95.0.0/16\"));\n" \
+  "  t.expectTrue(isInNetEx(\"198.95.1.1\", \"198.95.3.3/16\"));\n" \
+  "  t.expectTrue(isInNetEx(\"0:0:0:0:0:0:7f00:1\", \"0:0:0:0:0:0:7f00:1/32\"));\n" \
+  "  t.expectTrue(isInNetEx(\"3ffe:8311:ffff:abcd:1234:dead:beef:101\",\n" \
+  "                         \"3ffe:8311:ffff::/48\"));\n" \
+  "\n" \
+  "  // IPv4 and IPv6 mix.\n" \
+  "  t.expectFalse(isInNetEx(\"127.0.0.1\", \"0:0:0:0:0:0:7f00:1/16\"));\n" \
+  "  t.expectFalse(isInNetEx(\"192.168.24.3\", \"fe80:0:0:0:0:0:c0a8:1803/32\"));\n" \
+  "\n" \
+  "  t.expectFalse(isInNetEx(\"198.95.249.78\", \"198.95.249.79/32\"));\n" \
+  "  t.expectFalse(isInNetEx(\"198.96.115.10\", \"198.95.0.0/16\"));\n" \
+  "  t.expectFalse(isInNetEx(\"3fff:8311:ffff:abcd:1234:dead:beef:101\",\n" \
+  "                          \"3ffe:8311:ffff::/48\"));\n" \
+  "\n" \
+  "  // Call isInNetEx with wonky arguments.\n" \
+  "  t.expectEquals(null, isInNetEx());\n" \
+  "  t.expectEquals(null, isInNetEx(null));\n" \
+  "  t.expectEquals(null, isInNetEx(null, null));\n" \
+  "  t.expectEquals(null, isInNetEx(null, null, null));\n" \
+  "  t.expectEquals(null, isInNetEx(\"198.95.249.79\"));\n" \
+  "\n" \
+  "  // Invalid IP address.\n" \
+  "  t.expectFalse(isInNetEx(\"256.0.0.1\", \"198.95.249.79\"));\n" \
+  "  t.expectFalse(isInNetEx(\"127.0.0.1 \", \"127.0.0.1/32\"));  // Extra space.\n" \
+  "\n" \
+  "  // Invalid prefix.\n" \
+  "  t.expectFalse(isInNetEx(\"198.95.115.10\", \"198.95.0.0/34\"));\n" \
+  "  t.expectFalse(isInNetEx(\"127.0.0.1\", \"127.0.0.1\"));  // Missing '/' in prefix.\n" \
+  "};\n" \
+  "\n" \
+  "Tests.testWeekdayRange = function(t) {\n" \
+  "  // Test with local time.\n" \
+  "  MockDate.setCurrent(\"Tue Mar 03 2009\");\n" \
+  "  t.expectEquals(true, weekdayRange(\"MON\", \"FRI\"));\n" \
+  "  t.expectEquals(true, weekdayRange(\"TUE\", \"FRI\"));\n" \
+  "  t.expectEquals(true, weekdayRange(\"TUE\", \"TUE\"));\n" \
+  "  t.expectEquals(true, weekdayRange(\"TUE\"));\n" \
+  "  t.expectEquals(false, weekdayRange(\"WED\", \"FRI\"));\n" \
+  "  t.expectEquals(false, weekdayRange(\"SUN\", \"MON\"));\n" \
+  "  t.expectEquals(false, weekdayRange(\"SAT\"));\n" \
+  "  t.expectEquals(false, weekdayRange(\"FRI\", \"MON\"));\n" \
+  "\n" \
+  "  // Test with GMT time.\n" \
+  "  MockDate.setCurrent(\"Tue Mar 03 2009 GMT\");\n" \
+  "  t.expectEquals(true, weekdayRange(\"MON\", \"FRI\", \"GMT\"));\n" \
+  "  t.expectEquals(true, weekdayRange(\"TUE\", \"FRI\", \"GMT\"));\n" \
+  "  t.expectEquals(true, weekdayRange(\"TUE\", \"TUE\", \"GMT\"));\n" \
+  "  t.expectEquals(true, weekdayRange(\"TUE\", \"GMT\"));\n" \
+  "  t.expectEquals(false, weekdayRange(\"WED\", \"FRI\", \"GMT\"));\n" \
+  "  t.expectEquals(false, weekdayRange(\"SUN\", \"MON\", \"GMT\"));\n" \
+  "  t.expectEquals(false, weekdayRange(\"SAT\", \"GMT\"));\n" \
+  "};\n" \
+  "\n" \
+  "Tests.testDateRange = function(t) {\n" \
+  "  // dateRange(day)\n" \
+  "  MockDate.setCurrent(\"Mar 03 2009\");\n" \
+  "  t.expectEquals(true, dateRange(3));\n" \
+  "  t.expectEquals(false, dateRange(1));\n" \
+  "\n" \
+  "  // dateRange(day, \"GMT\")\n" \
+  "  MockDate.setCurrent(\"Mar 03 2009 GMT\");\n" \
+  "  t.expectEquals(true, dateRange(3, \"GMT\"));\n" \
+  "  t.expectEquals(false, dateRange(1, \"GMT\"));\n" \
+  "\n" \
+  "  // dateRange(day1, day2)\n" \
+  "  MockDate.setCurrent(\"Mar 03 2009\");\n" \
+  "  t.expectEquals(true, dateRange(1, 4));\n" \
+  "  t.expectEquals(false, dateRange(4, 20));\n" \
+  "\n" \
+  "  // dateRange(day, month)\n" \
+  "  MockDate.setCurrent(\"Mar 03 2009\");\n" \
+  "  t.expectEquals(true, dateRange(3, \"MAR\"));\n" \
+  "  MockDate.setCurrent(\"Mar 03 2014\");\n" \
+  "  t.expectEquals(true, dateRange(3, \"MAR\"));\n" \
+  "  // TODO(eroman):\n" \
+  "  //t.expectEquals(false, dateRange(2, \"MAR\"));\n" \
+  "  //t.expectEquals(false, dateRange(3, \"JAN\"));\n" \
+  "\n" \
+  "  // dateRange(day, month, year)\n" \
+  "  MockDate.setCurrent(\"Mar 03 2009\");\n" \
+  "  t.expectEquals(true, dateRange(3, \"MAR\", 2009));\n" \
+  "  t.expectEquals(false, dateRange(4, \"MAR\", 2009));\n" \
+  "  t.expectEquals(false, dateRange(3, \"FEB\", 2009));\n" \
+  "  MockDate.setCurrent(\"Mar 03 2014\");\n" \
+  "  t.expectEquals(false, dateRange(3, \"MAR\", 2009));\n" \
+  "\n" \
+  "  // dateRange(month1, month2)\n" \
+  "  MockDate.setCurrent(\"Mar 03 2009\");\n" \
+  "  t.expectEquals(true, dateRange(\"JAN\", \"MAR\"));\n" \
+  "  t.expectEquals(true, dateRange(\"MAR\", \"APR\"));\n" \
+  "  t.expectEquals(false, dateRange(\"MAY\", \"SEP\"));\n" \
+  "\n" \
+  "  // dateRange(day1, month1, day2, month2)\n" \
+  "  MockDate.setCurrent(\"Mar 03 2009\");\n" \
+  "  t.expectEquals(true, dateRange(1, \"JAN\", 3, \"MAR\"));\n" \
+  "  t.expectEquals(true, dateRange(3, \"MAR\", 4, \"SEP\"));\n" \
+  "  t.expectEquals(false, dateRange(4, \"MAR\", 4, \"SEP\"));\n" \
+  "\n" \
+  "  // dateRange(month1, year1, month2, year2)\n" \
+  "  MockDate.setCurrent(\"Mar 03 2009\");\n" \
+  "  t.expectEquals(true, dateRange(\"FEB\", 2009, \"MAR\", 2009));\n" \
+  "  MockDate.setCurrent(\"Apr 03 2009\");\n" \
+  "  t.expectEquals(true, dateRange(\"FEB\", 2009, \"MAR\", 2010));\n" \
+  "  t.expectEquals(false, dateRange(\"FEB\", 2009, \"MAR\", 2009));\n" \
+  "\n" \
+  "  // dateRange(day1, month1, year1, day2, month2, year2)\n" \
+  "  MockDate.setCurrent(\"Mar 03 2009\");\n" \
+  "  t.expectEquals(true, dateRange(1, \"JAN\", 2009, 3, \"MAR\", 2009));\n" \
+  "  t.expectEquals(true, dateRange(3, \"MAR\", 2009, 4, \"SEP\", 2009));\n" \
+  "  t.expectEquals(true, dateRange(3, \"JAN\", 2009, 4, \"FEB\", 2010));\n" \
+  "  t.expectEquals(false, dateRange(4, \"MAR\", 2009, 4, \"SEP\", 2009));\n" \
+  "};\n" \
+  "\n" \
+  "Tests.testTimeRange = function(t) {\n" \
+  "  // timeRange(hour)\n" \
+  "  MockDate.setCurrent(\"Mar 03, 2009 03:34:01\");\n" \
+  "  t.expectEquals(true, timeRange(3));\n" \
+  "  t.expectEquals(false, timeRange(2));\n" \
+  "\n" \
+  "  // timeRange(hour1, hour2)\n" \
+  "  MockDate.setCurrent(\"Mar 03, 2009 03:34:01\");\n" \
+  "  t.expectEquals(true, timeRange(2, 3));\n" \
+  "  t.expectEquals(true, timeRange(2, 4));\n" \
+  "  t.expectEquals(true, timeRange(3, 5));\n" \
+  "  t.expectEquals(false, timeRange(1, 2));\n" \
+  "  t.expectEquals(false, timeRange(11, 12));\n" \
+  "\n" \
+  "  // timeRange(hour1, min1, hour2, min2)\n" \
+  "  MockDate.setCurrent(\"Mar 03, 2009 03:34:01\");\n" \
+  "  t.expectEquals(true, timeRange(1, 0, 3, 34));\n" \
+  "  t.expectEquals(true, timeRange(1, 0, 3, 35));\n" \
+  "  t.expectEquals(true, timeRange(3, 34, 5, 0));\n" \
+  "  t.expectEquals(false, timeRange(1, 0, 3, 0));\n" \
+  "  t.expectEquals(false, timeRange(11, 0, 16, 0));\n" \
+  "\n" \
+  "  // timeRange(hour1, min1, sec1, hour2, min2, sec2)\n" \
+  "  MockDate.setCurrent(\"Mar 03, 2009 03:34:14\");\n" \
+  "  t.expectEquals(true, timeRange(1, 0, 0, 3, 34, 14));\n" \
+  "  t.expectEquals(false, timeRange(1, 0, 0, 3, 34, 0));\n" \
+  "  t.expectEquals(true, timeRange(1, 0, 0, 3, 35, 0));\n" \
+  "  t.expectEquals(true, timeRange(3, 34, 0, 5, 0, 0));\n" \
+  "  t.expectEquals(false, timeRange(1, 0, 0, 3, 0, 0));\n" \
+  "  t.expectEquals(false, timeRange(11, 0, 0, 16, 0, 0));\n" \
+  "};\n" \
+  "\n" \
+  "// --------------------------\n" \
+  "// TestContext\n" \
+  "// --------------------------\n" \
+  "\n" \
+  "// |name| is the name of the test being executed, it will be used when logging\n" \
+  "// errors.\n" \
+  "function TestContext(name) {\n" \
+  "  this.numFailures_ = 0;\n" \
+  "  this.name_ = name;\n" \
+  "};\n" \
+  "\n" \
+  "TestContext.prototype.failed = function() {\n" \
+  "  return this.numFailures_ != 0;\n" \
+  "};\n" \
+  "\n" \
+  "TestContext.prototype.expectEquals = function(expectation, actual) {\n" \
+  "  if (!(expectation === actual)) {\n" \
+  "    this.numFailures_++;\n" \
+  "    this.log(\"FAIL: expected: \" + expectation + \", actual: \" + actual);\n" \
+  "  }\n" \
+  "};\n" \
+  "\n" \
+  "TestContext.prototype.expectTrue = function(x) {\n" \
+  "  this.expectEquals(true, x);\n" \
+  "};\n" \
+  "\n" \
+  "TestContext.prototype.expectFalse = function(x) {\n" \
+  "  this.expectEquals(false, x);\n" \
+  "};\n" \
+  "\n" \
+  "TestContext.prototype.log = function(x) {\n" \
+  "  // Prefix with the test name that generated the log.\n" \
+  "  try {\n" \
+  "    alert(this.name_ + \": \" + x);\n" \
+  "  } catch(e) {\n" \
+  "    // In case alert() is not defined.\n" \
+  "  }\n" \
+  "};\n" \
+  "\n" \
+  "// --------------------------\n" \
+  "// MockDate\n" \
+  "// --------------------------\n" \
+  "\n" \
+  "function MockDate() {\n" \
+  "  this.wrappedDate_ = new MockDate.super_(MockDate.currentDateString_);\n" \
+  "};\n" \
+  "\n" \
+  "// Setup the MockDate so it forwards methods to \"this.wrappedDate_\" (which is a\n" \
+  "// real Date object).  We can't simply chain the prototypes since Date() doesn't\n" \
+  "// allow it.\n" \
+  "MockDate.init = function() {\n" \
+  "  MockDate.super_ = Date;\n" \
+  "\n" \
+  "  function createProxyMethod(methodName) {\n" \
+  "    return function() {\n" \
+  "      return this.wrappedDate_[methodName]\n" \
+  "          .apply(this.wrappedDate_, arguments);\n" \
+  "    }\n" \
+  "  };\n" \
+  "\n" \
+  "  for (i in MockDate.methodNames_) {\n" \
+  "    var methodName = MockDate.methodNames_[i];\n" \
+  "    // Don't define the closure directly in the loop body, since Javascript's\n" \
+  "    // crazy scoping rules mean |methodName| actually bleeds out of the loop!\n" \
+  "    MockDate.prototype[methodName] = createProxyMethod(methodName);\n" \
+  "  }\n" \
+  "\n" \
+  "  // Replace the native Date() with our mock.\n" \
+  "  Date = MockDate;\n" \
+  "};\n" \
+  "\n" \
+  "// Unfortunately Date()'s methods are non-enumerable, therefore list manually.\n" \
+  "MockDate.methodNames_ = [\n" \
+  "  \"toString\", \"toDateString\", \"toTimeString\", \"toLocaleString\",\n" \
+  "  \"toLocaleDateString\", \"toLocaleTimeString\", \"valueOf\", \"getTime\",\n" \
+  "  \"getFullYear\", \"getUTCFullYear\", \"getMonth\", \"getUTCMonth\",\n" \
+  "  \"getDate\", \"getUTCDate\", \"getDay\", \"getUTCDay\", \"getHours\", \"getUTCHours\",\n" \
+  "  \"getMinutes\", \"getUTCMinutes\", \"getSeconds\", \"getUTCSeconds\",\n" \
+  "  \"getMilliseconds\", \"getUTCMilliseconds\", \"getTimezoneOffset\", \"setTime\",\n" \
+  "  \"setMilliseconds\", \"setUTCMilliseconds\", \"setSeconds\", \"setUTCSeconds\",\n" \
+  "  \"setMinutes\", \"setUTCMinutes\", \"setHours\", \"setUTCHours\", \"setDate\",\n" \
+  "  \"setUTCDate\", \"setMonth\", \"setUTCMonth\", \"setFullYear\", \"setUTCFullYear\",\n" \
+  "  \"toGMTString\", \"toUTCString\", \"getYear\", \"setYear\"\n" \
+  "];\n" \
+  "\n" \
+  "MockDate.setCurrent = function(currentDateString) {\n" \
+  "  MockDate.currentDateString_ = currentDateString;\n" \
+  "}\n" \
+  "\n" \
+  "// Bind the methods to proxy requests to the wrapped Date().\n" \
+  "MockDate.init();\n" \
+  "\n" \
+
+#define PASSTHROUGH_JS \
+  "// Return a single-proxy result, which encodes ALL the arguments that were\n" \
+  "// passed to FindProxyForURL().\n" \
+  "\n" \
+  "function FindProxyForURL(url, host) {\n" \
+  "  if (arguments.length != 2) {\n" \
+  "    throw \"Wrong number of arguments passed to FindProxyForURL!\";\n" \
+  "    return \"FAIL\";\n" \
+  "  }\n" \
+  "\n" \
+  "  return \"PROXY \" + makePseudoHost(url + \".\" + host);\n" \
+  "}\n" \
+  "\n" \
+  "// Form a string that kind-of resembles a host. We will replace any\n" \
+  "// non-alphanumeric character with a dot, then fix up the oddly placed dots.\n" \
+  "function makePseudoHost(str) {\n" \
+  "  var result = \"\";\n" \
+  "\n" \
+  "  for (var i = 0; i < str.length; ++i) {\n" \
+  "    var c = str.charAt(i);\n" \
+  "    if (!isValidPseudoHostChar(c)) {\n" \
+  "      c = '.';  // Replace unsupported characters with a dot.\n" \
+  "    }\n" \
+  "\n" \
+  "    // Take care not to place multiple adjacent dots,\n" \
+  "    // a dot at the beginning, or a dot at the end.\n" \
+  "    if (c == '.' &&\n" \
+  "        (result.length == 0 || \n" \
+  "         i == str.length - 1 ||\n" \
+  "         result.charAt(result.length - 1) == '.')) {\n" \
+  "      continue;\n" \
+  "    }\n" \
+  "    result += c;\n" \
+  "  }\n" \
+  "  return result;\n" \
+  "}\n" \
+  "\n" \
+  "function isValidPseudoHostChar(c) {\n" \
+  "  if (c >= '0' && c <= '9')\n" \
+  "    return true;\n" \
+  "  if (c >= 'a' && c <= 'z')\n" \
+  "    return true;\n" \
+  "  if (c >= 'A' && c <= 'Z')\n" \
+  "    return true;\n" \
+  "  return false;\n" \
+  "}\n" \
+
+#define RETURN_EMPTY_STRING_JS \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return \"\";\n" \
+  "}\n" \
+  "\n" \
+
+#define RETURN_FUNCTION_JS \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return FindProxyForURL;\n" \
+  "}\n" \
+  "\n" \
+
+#define RETURN_INTEGER_JS \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return 0;\n" \
+  "}\n" \
+  "\n" \
+
+#define RETURN_NULL_JS \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return null;\n" \
+  "}\n" \
+  "\n" \
+
+#define RETURN_OBJECT_JS \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return {result: \"PROXY foo\"};\n" \
+  "}\n" \
+  "\n" \
+
+#define RETURN_UNDEFINED_JS \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return undefined;\n" \
+  "}\n" \
+  "\n" \
+
+#define RETURN_UNICODE_JS \
+  "// U+200B is the codepoint for zero-width-space.\n" \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return \"PROXY foo.com\u200B\";\n" \
+  "}\n" \
+
+#define SIDE_EFFECTS_JS \
+  "if (!gCounter) {\n" \
+  "  // We write it this way so if the script gets loaded twice,\n" \
+  "  // gCounter remains dirty.\n" \
+  "  var gCounter = 0;\n" \
+  "}\n" \
+  "\n" \
+  "function FindProxyForURL(url, host) {\n" \
+  "  return \"PROXY sideffect_\" + gCounter++;\n" \
+  "}\n" \
+  "\n" \
+
+#define SIMPLE_JS \
+  "// PAC script which uses isInNet on both IP addresses and hosts, and calls\n" \
+  "// isResolvable().\n" \
+  "\n" \
+  "function FindProxyForURL(url, host) {\n" \
+  "  var my_ip = myIpAddress();\n" \
+  "\n" \
+  "  if (isInNet(my_ip, \"172.16.0.0\", \"255.248.0.0\")) {\n" \
+  "    return \"PROXY a:80\";\n" \
+  "  }\n" \
+  "\n" \
+  "  if (url.substring(0, 6) != \"https:\" &&\n" \
+  "      isInNet(host, \"10.0.0.0\", \"255.0.0.0\")) {\n" \
+  "    return \"PROXY b:80\";\n" \
+  "  }\n" \
+  "\n" \
+  "  if (dnsDomainIs(host, \"foo.bar.baz.com\") || !isResolvable(host)) {\n" \
+  "    return \"PROXY c:100\";\n" \
+  "  }\n" \
+  "\n" \
+  "  return \"DIRECT\";\n" \
+  "}\n" \
+
+#define UNHANDLED_EXCEPTION_JS \
+  "function FindProxyForURL(url, host) {\n" \
+  "  // This will throw a runtime exception.\n" \
+  "  return \"PROXY x\" + undefined_variable;\n" \
+  "}\n" \
+  "\n" \
+
+#endif //PROXY_TEST_SCRIPT_H_