Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sun, 13 Mar 2016 19:08:03 -0400
changeset 339874 f0c0480732d36153e8839c7f17394d45f679f87d
parent 339859 985daf7c2e1ee1e08a005bf72fd8731c5f26a6bc (current diff)
parent 339873 080f495f9e41044e8de7cbd66909199e83b977cc (diff)
child 339875 36f81aa9a4875d56fc95e2587803935513fb2f9c
child 339876 ff898cfeb5a769b5825a74a959402b58f63b6eea
child 339885 be5fb4bee0c86bd45ee7f8c290eb8c8727395239
child 339888 55bc0d2ad0af0e871a19923766ee03d1351f3385
child 339901 0ce67740545443ec87c0afd2d3d8b146dc517466
child 339917 1ca1c8b0a08dfff543c51d66633ec34bc86b3610
child 339922 025dd09899783ea5ede8873a8205ca653b7fcbc7
child 339923 c6ddf162e8b9b6e08003931672fa73907ab1627c
child 339928 3b08a5bc1f4cd25451e685d2ed0e8fa4ced2b7b2
child 339929 3834f2099b3be35c22e7c81a1353001a6cc231f9
child 339930 343aab5cb460641d4fb0dd21760ec40cc68e9c28
child 339952 d1ac6b44e341e4a06367ed7ec71e68284d2ca6d8
child 339953 729c1c8a963fe1aac424706238048165e244f7ed
child 339954 ca090394562aa261545a3e86dfa6a64befb38fb0
child 339955 3c56b418f36dccdc8ba3e3773ce0833bb737d67f
child 339956 35247e73df1e167c740db564c44ec0bfa8220eb0
child 339958 07b2350165636e9eddeb9904e7571fa6218a744e
child 339973 e0f410edb76c4f2f735f26a5cf3f2c6dbd886c68
child 339975 a00f5ac29927072264054dea50c2ce3646e2bf6c
child 339976 dcf0bfd31740ee1796df5bd26c7f9fb6e9433623
child 339978 e2ba55b58b3f04b5c1eecc8a9abe137fe3f0523a
child 339979 c2c18fbf014c81d939e3fbb525c3b15f3979c240
child 339980 ed31162ae9a0930ebeab1675dd45d68776c8153a
child 339981 0848206733858d4e0748e29e89f861a90b54a3a6
child 339982 d503defb23d545c4b1f870b9bbb1e1039d7b9679
child 339983 2bef59a4476a99e0796bde9502c1bf0744f97182
child 339995 06b29403664e2c785488f5543aae77f76e3337ed
child 339997 9bce48571e58ecd43272623e852db3b83c4f1ed4
child 339998 07dc0bdd7e6ee8068268d132825536d80c0d08f0
child 340008 4d2a0725992f5c8ca11f8b981bf92b0a7ced0e38
child 340009 f5693c673a929267dc10394c9f3a42b3d074639b
child 340017 6e46d916bbb34d645f6b0c465edc3992d5531782
child 340023 352f6649d900be010a501e61ef44e94ae6799856
child 340026 ed9a510679288e6e4bd0f200180df12369a03c99
child 340028 948a412a26628133758e978202c0b9f30daece8c
child 340035 038c2fe02074d03a6c34d44f68cb5f078a2472cc
child 340037 f3126bf6c31e39f7e7f65e18f76e396b9b4a8399
child 340062 f2859ae0e59946755dd240a3d8b97f344b0c0ef4
child 340234 fcff690629f9f908cb1a867903276e8deefdb362
child 340238 6907e24a15e3286fa7342ec3ab4502b8c59546ff
child 340337 e580c9a4538fcb6eb4d45a23647c38cfb773e488
child 340441 d328e155cb1272fa5ce1e6234be654e768522cb1
child 340448 d314d1706311c8e9d336171118640d1ea63e7e5e
child 340451 c6fe15cd03e38145c5a5b8d6f0c27b1d2c61b565
child 340453 b5c20af06d2445e769100d8138457f4d34bc9113
child 340468 1274caf563bdf362ac06ec43a3df21f7b1473ab6
child 340481 638926970280f0f948503735dc2e72b60676a6ee
child 340483 b5a5ce3f454fec7bfb30c2e807553cca0eb6594f
child 340500 1ee1f2f6679335cd7c0c5a731c4ff5e51e4ae4dc
child 340519 aa3971a552ebd5cacc7af60b61bb651dafe102e6
child 340533 165e8f7be9c957a10357684dbf1fda5c92d77867
child 340665 31ee8742de597ddf2319daf07fd0c82dc82e0f1a
child 340666 a70f592debfc708e5d2e7df2de4ac460c4d85286
child 340764 2b815d83a921d369ede715f9f42ac1df3a0d2ec5
child 340989 ea1edf93612a28f631696ff5eb2ef05e8a55e7f9
child 341118 66c12a2665dbbbc7086a378faffb5e9d7d054f71
child 341522 d1117dd71c25f55dbad4c5e2366579eb63b0cb52
child 341526 4fa0d31b6b994da9a9ae311df9aacfd95671639e
child 341530 967fc89f275bc3241af39d4ab6d9ca58b4435265
child 341729 5aa1e9cb8b9ab976a6af17dee750c8ff59daaf26
child 341796 d3c263ab8ad8714571b1422aa124ca6f4faf0776
child 341979 348e4dd68b604fece32c4e8484499921f197c2c5
child 342355 81f37655e4010e24892dec8d5f134d1e6fccb9db
child 342764 d0a23adb4ac0fe364922bfff5423b5d1ec9c197d
child 342797 e3bc6bf96a79a3c74bf3781d78cd4b61eb1d85a4
child 342974 03aecdafb18faa4b6239bcfda10f8b568baff07d
child 342999 ecf6346ac00bce58aac41c4035694f99b3c92780
child 343499 60318f8edf36357be72229594728ac09babd16f0
child 343502 3a08d5cd2b6bebedc85a886a9fb02b4cada97029
child 343526 ebee5089c00689b16c75b4e36b5f786dd634ad73
child 343529 59c09813060098296ed83a4adeac1f95f98084f7
child 344216 a60e1b0bac6d95bb65c1c7aaf94c5a072557ce6c
child 344288 3fcbc83885407a33d5b1758fa8101b2dbfb24196
child 344668 e1f8b08c54e038e2e05f5e21d77f21c886995a2d
child 345368 4787db1e0e53e68f39a490585e93a3555aacd290
child 349606 41c54222455088ad5f51e2ab15e7818d85c8706d
child 352837 9ce8f861ba56b5de10939d568fa0427d79d20a3e
child 354575 19c280c6ebd7871982eff25b958f7d43b08e0e15
child 355532 40223cbab66f0178c157800723347473c74fe84b
child 355610 5621e0bab2d8d0b04b3bc3024f1be4c8c051bb5e
child 388281 73681cc9b66ba57c280e84462384f0c04fc9b873
push id12811
push userdbaron@mozilla.com
push dateMon, 14 Mar 2016 02:47:45 +0000
reviewersmerge
milestone48.0a1
Merge inbound to m-c. a=merge
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -437,19 +437,16 @@ pref("dom.meta-viewport.enabled", true);
 #endif
 
 // SMS/MMS
 pref("dom.sms.enabled", true);
 
 //The waiting time in network manager.
 pref("network.gonk.ms-release-mms-connection", 30000);
 
-// WebContacts
-pref("dom.mozContacts.enabled", true);
-
 // Shortnumber matching needed for e.g. Brazil:
 // 03187654321 can be found with 87654321
 pref("dom.phonenumber.substringmatching.BR", 8);
 pref("dom.phonenumber.substringmatching.CO", 10);
 pref("dom.phonenumber.substringmatching.VE", 7);
 pref("dom.phonenumber.substringmatching.CL", 8);
 pref("dom.phonenumber.substringmatching.PE", 7);
 
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -104,17 +104,18 @@ PocketAboutPage.prototype = {
   getURIFlags: function(aURI) {
     return Ci.nsIAboutModule.ALLOW_SCRIPT |
            Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
            Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT |
            Ci.nsIAboutModule.MAKE_UNLINKABLE;
   },
 
   newChannel: function(aURI, aLoadInfo) {
-    let channel = Services.io.newChannelFromURIWithLoadInfo(this.chromeURL,
+    let newURI = Services.io.newURI(this.chromeURL, null, null);
+    let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
                                                             aLoadInfo);
     channel.originalURI = aURI;
     return channel;
   },
 
   createInstance: function(outer, iid) {
     if (outer != null) {
       throw Cr.NS_ERROR_NO_AGGREGATION;
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -50,18 +50,22 @@
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
   color: var(--identity-box-verified-color);
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.chromeUI {
   color: var(--identity-box-chrome-color);
 }
 
-#identity-icon-labels {
-  padding-inline-start: 2px;
+#identity-icon-labels:-moz-locale-dir(ltr) {
+  padding-left: 2px;
+}
+
+#identity-icon-labels:-moz-locale-dir(rtl) {
+  padding-right: 2px;
 }
 
 #notification-popup-box:not([hidden]) + #identity-box {
   padding-inline-start: 10px;
   border-radius: 0;
 }
 
 @conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
--- a/config/check_spidermonkey_style.py
+++ b/config/check_spidermonkey_style.py
@@ -61,16 +61,17 @@ included_inclnames_to_ignore = set([
     'ffi.h',                    # generated in ctypes/libffi/
     'devtools/sharkctl.h',      # we ignore devtools/ in general
     'devtools/Instruments.h',   # we ignore devtools/ in general
     'double-conversion.h',      # strange MFBT case
     'javascript-trace.h',       # generated in $OBJDIR if HAVE_DTRACE is defined
     'jsautokw.h',               # generated in $OBJDIR
     'jscustomallocator.h',      # provided by embedders;  allowed to be missing
     'js-config.h',              # generated in $OBJDIR
+    'fdlibm.h',                 # fdlibm
     'pratom.h',                 # NSPR
     'prcvar.h',                 # NSPR
     'prerror.h',                # NSPR
     'prinit.h',                 # NSPR
     'prlink.h',                 # NSPR
     'prlock.h',                 # NSPR
     'prprf.h',                  # NSPR
     'prthread.h',               # NSPR
new file mode 100644
--- /dev/null
+++ b/config/external/fdlibm/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library('fdlibm')
+
+with Files('**'):
+    BUG_COMPONENT = ('Core', 'JavaScript Engine')
+
+DIRS += [
+    '../../../modules/fdlibm',
+]
--- a/dom/animation/AnimationEffectTiming.cpp
+++ b/dom/animation/AnimationEffectTiming.cpp
@@ -14,34 +14,52 @@ namespace mozilla {
 namespace dom {
 
 JSObject*
 AnimationEffectTiming::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return AnimationEffectTimingBinding::Wrap(aCx, this, aGivenProto);
 }
 
-void
-AnimationEffectTiming::NotifyTimingUpdate()
+static inline void
+PostSpecifiedTimingUpdated(KeyframeEffect* aEffect)
 {
-  if (mEffect) {
-    mEffect->NotifySpecifiedTimingUpdated();
+  if (aEffect) {
+    aEffect->NotifySpecifiedTimingUpdated();
   }
 }
 
 void
 AnimationEffectTiming::SetEndDelay(double aEndDelay)
 {
   TimeDuration endDelay = TimeDuration::FromMilliseconds(aEndDelay);
   if (mTiming.mEndDelay == endDelay) {
     return;
   }
   mTiming.mEndDelay = endDelay;
 
-  NotifyTimingUpdate();
+  PostSpecifiedTimingUpdated(mEffect);
+}
+
+void
+AnimationEffectTiming::SetIterationStart(double aIterationStart,
+                                         ErrorResult& aRv)
+{
+  if (mTiming.mIterationStart == aIterationStart) {
+    return;
+  }
+
+  TimingParams::ValidateIterationStart(aIterationStart, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  mTiming.mIterationStart = aIterationStart;
+
+  PostSpecifiedTimingUpdated(mEffect);
 }
 
 void
 AnimationEffectTiming::SetDuration(const UnrestrictedDoubleOrString& aDuration,
                                    ErrorResult& aRv)
 {
   Maybe<StickyTimeDuration> newDuration =
     TimingParams::ParseDuration(aDuration, aRv);
@@ -50,13 +68,13 @@ AnimationEffectTiming::SetDuration(const
   }
 
   if (mTiming.mDuration == newDuration) {
     return;
   }
 
   mTiming.mDuration = newDuration;
 
-  NotifyTimingUpdate();
+  PostSpecifiedTimingUpdated(mEffect);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/AnimationEffectTiming.h
+++ b/dom/animation/AnimationEffectTiming.h
@@ -20,20 +20,20 @@ public:
     : AnimationEffectTimingReadOnly(aTiming)
     , mEffect(aEffect) { }
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void Unlink() override { mEffect = nullptr; }
 
   void SetEndDelay(double aEndDelay);
+  void SetIterationStart(double aIterationStart, ErrorResult& aRv);
   void SetDuration(const UnrestrictedDoubleOrString& aDuration,
                    ErrorResult& aRv);
 
 private:
-  void NotifyTimingUpdate();
   KeyframeEffect* MOZ_NON_OWNING_REF mEffect;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_AnimationEffectTiming_h
--- a/dom/animation/AnimationUtils.cpp
+++ b/dom/animation/AnimationUtils.cpp
@@ -5,20 +5,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AnimationUtils.h"
 
 #include "nsCSSParser.h" // For nsCSSParser
 #include "nsDebug.h"
 #include "nsIAtom.h"
 #include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsGlobalWindow.h"
 #include "nsString.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ComputedTimingFunction.h" // ComputedTimingFunction
 #include "mozilla/dom/Element.h" // For dom::Element
+#include "xpcpublic.h" // For xpc::CurrentWindowOrNull
 
 namespace mozilla {
 
 /* static */ void
 AnimationUtils::LogAsyncAnimationFailure(nsCString& aMessage,
                                          const nsIContent* aContent)
 {
   if (aContent) {
@@ -94,9 +97,19 @@ AnimationUtils::ParseEasing(const dom::E
       break;
     default:
       MOZ_ASSERT_UNREACHABLE("unexpected animation-timing-function unit");
       break;
   }
   return Nothing();
 }
 
+/* static */ nsIDocument*
+AnimationUtils::GetCurrentRealmDocument(JSContext* aCx)
+{
+  nsGlobalWindow* win = xpc::CurrentWindowOrNull(aCx);
+  if (!win) {
+    return nullptr;
+  }
+  return win->GetDoc();
+}
+
 } // namespace mozilla
--- a/dom/animation/AnimationUtils.h
+++ b/dom/animation/AnimationUtils.h
@@ -7,42 +7,44 @@
 #ifndef mozilla_dom_AnimationUtils_h
 #define mozilla_dom_AnimationUtils_h
 
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/Nullable.h"
 #include "nsStringFwd.h"
 
 class nsIContent;
+class nsIDocument;
+struct JSContext;
 
 namespace mozilla {
 
 class ComputedTimingFunction;
 
 namespace dom {
 class Element;
 }
 
 class AnimationUtils
 {
 public:
   static dom::Nullable<double>
-    TimeDurationToDouble(const dom::Nullable<TimeDuration>& aTime)
+  TimeDurationToDouble(const dom::Nullable<TimeDuration>& aTime)
   {
     dom::Nullable<double> result;
 
     if (!aTime.IsNull()) {
       result.SetValue(aTime.Value().ToMilliseconds());
     }
 
     return result;
   }
 
   static dom::Nullable<TimeDuration>
-    DoubleToTimeDuration(const dom::Nullable<double>& aTime)
+  DoubleToTimeDuration(const dom::Nullable<double>& aTime)
   {
     dom::Nullable<TimeDuration> result;
 
     if (!aTime.IsNull()) {
       result.SetValue(TimeDuration::FromMilliseconds(aTime.Value()));
     }
 
     return result;
@@ -52,14 +54,20 @@ public:
                                        const nsIContent* aContent = nullptr);
 
   /**
    * Parses a CSS <single-transition-timing-function> value from
    * aEasing into a ComputedTimingFunction.  If parsing fails, Nothing() will
    * be returned.
    */
   static Maybe<ComputedTimingFunction>
-    ParseEasing(const dom::Element* aTarget, const nsAString& aEasing);
+  ParseEasing(const dom::Element* aTarget, const nsAString& aEasing);
+
+  /**
+   * Get the document from the JS context to use when parsing CSS properties.
+   */
+  static nsIDocument*
+  GetCurrentRealmDocument(JSContext* aCx);
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/animation/TimingParams.cpp
+++ b/dom/animation/TimingParams.cpp
@@ -59,21 +59,27 @@ TimingParamsFromOptionsUnion(
       if (target.IsElement()) {
         targetElement = &target.GetAsElement();
       } else {
         targetElement = target.GetAsCSSPseudoElement().ParentElement();
       }
     }
     const dom::AnimationEffectTimingProperties& timing =
       GetTimingProperties(aOptions);
+
     Maybe<StickyTimeDuration> duration =
       TimingParams::ParseDuration(timing.mDuration, aRv);
     if (aRv.Failed()) {
       return result;
     }
+    TimingParams::ValidateIterationStart(timing.mIterationStart, aRv);
+    if (aRv.Failed()) {
+      return result;
+    }
+
     result.mDuration = duration;
     result.mDelay = TimeDuration::FromMilliseconds(timing.mDelay);
     result.mEndDelay = TimeDuration::FromMilliseconds(timing.mEndDelay);
     result.mIterations = timing.mIterations;
     result.mIterationStart = timing.mIterationStart;
     result.mDirection = timing.mDirection;
     result.mFill = timing.mFill;
     result.mFunction =
--- a/dom/animation/TimingParams.h
+++ b/dom/animation/TimingParams.h
@@ -44,31 +44,40 @@ struct TimingParams
     ErrorResult& aRv);
 
   // Range-checks and validates an UnrestrictedDoubleOrString or
   // OwningUnrestrictedDoubleOrString object and converts to a
   // StickyTimeDuration value or Nothing() if aDuration is "auto".
   // Caller must check aRv.Failed().
   template <class DoubleOrString>
   static Maybe<StickyTimeDuration> ParseDuration(DoubleOrString& aDuration,
-                                                 ErrorResult& aRv) {
+                                                 ErrorResult& aRv)
+  {
     Maybe<StickyTimeDuration> result;
     if (aDuration.IsUnrestrictedDouble()) {
       double durationInMs = aDuration.GetAsUnrestrictedDouble();
       if (durationInMs >= 0) {
         result.emplace(StickyTimeDuration::FromMilliseconds(durationInMs));
         return result;
       }
     } else if (aDuration.GetAsString().EqualsLiteral("auto")) {
       return result;
     }
     aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
     return result;
   }
 
+  static void ValidateIterationStart(double aIterationStart,
+                                     ErrorResult& aRv)
+  {
+    if (aIterationStart < 0) {
+      aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+    }
+  }
+
   // mDuration.isNothing() represents the "auto" value
   Maybe<StickyTimeDuration> mDuration;
   TimeDuration mDelay;      // Initializes to zero
   TimeDuration mEndDelay;
   double mIterations = 1.0; // Can be NaN, negative, +/-Infinity
   double mIterationStart = 0.0;
   dom::PlaybackDirection mDirection = dom::PlaybackDirection::Normal;
   dom::FillMode mFill = dom::FillMode::Auto;
--- a/dom/base/test/chrome.ini
+++ b/dom/base/test/chrome.ini
@@ -9,16 +9,17 @@ support-files =
 
 [test_anonymousContent_xul_window.xul]
 [test_bug715041.xul]
 [test_bug715041_removal.xul]
 [test_domrequesthelper.xul]
 [test_url.xul]
 [test_console.xul]
 [test_navigator_resolve_identity_xrays.xul]
+support-files = file_navigator_resolve_identity_xrays.xul
 [test_sendQueryContentAndSelectionSetEvent.html]
 [test_bug1016960.html]
 [test_bug357450.js]
 [test_copypaste.xul]
 [test_messagemanager_principal.html]
 [test_messagemanager_send_principal.html]
 skip-if = buildapp == 'mulet'
 [test_bug945152.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_navigator_resolve_identity_xrays.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=985827
+-->
+<window title="Mozilla Bug 985827"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <iframe id="t"></iframe>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+  /** Test for Bug 985827 **/
+
+  addLoadEvent(function() {
+    var ok = parent.ok;
+    var is = parent.is;
+
+    var nav = document.getElementById("t").contentWindow.navigator;
+
+    ok(Components.utils.isXrayWrapper(nav), "Should have an Xray here");
+
+    // Test WebIDL NavigatorProperty objects
+    is(typeof nav.mozContacts, "object", "Should have a mozContacts object");
+    is(nav.mozContacts, nav.mozContacts,
+       "Should have gotten the same mozContacts object again");
+  });
+
+  ]]>
+  </script>
+</window>
--- a/dom/base/test/test_navigator_resolve_identity.html
+++ b/dom/base/test/test_navigator_resolve_identity.html
@@ -7,22 +7,39 @@ https://bugzilla.mozilla.org/show_bug.cg
   <meta charset="utf-8">
   <title>Test for Bug 985827</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   /** Test for Bug 985827 **/
 
-  // Test WebIDL NavigatorProperty objects
-  var x = navigator.mozContacts;
-  is(typeof x, "object", "Should have a mozContacts object");
-  delete navigator.mozContacts;
-  var y = navigator.mozContacts;
-  is(x, y, "Should have gotten the same mozContacts object again");
+  function test() {
+    var is = parent.is;
+
+    // Test WebIDL NavigatorProperty objects
+    var x = navigator.mozContacts;
+    is(typeof x, "object", "Should have a mozContacts object");
+    delete navigator.mozContacts;
+    var y = navigator.mozContacts;
+    is(x, y, "Should have gotten the same mozContacts object again");
+  }
+
+  SimpleTest.waitForExplicitFinish();
+
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], function() {
+    var iframe = document.createElement("iframe");
+    iframe.src = "data:text/html,<script>(" + escape(test.toString()) + ")();</scr" + "ipt>";
+    document.body.appendChild(iframe);
+    iframe.onload = function() { SimpleTest.finish(); };
+  });
 
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=985827">Mozilla Bug 985827</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
--- a/dom/base/test/test_navigator_resolve_identity_xrays.xul
+++ b/dom/base/test/test_navigator_resolve_identity_xrays.xul
@@ -4,36 +4,55 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=985827
 -->
 <window title="Mozilla Bug 985827"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
 
-  <iframe id="t"></iframe>
-
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml">
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=985827"
      target="_blank">Mozilla Bug 985827</a>
+  <iframe id="t"></iframe>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
   /** Test for Bug 985827 **/
 
   SimpleTest.waitForExplicitFinish();
+
+  Components.utils.import("resource://gre/modules/Services.jsm");
+
   addLoadEvent(function() {
-    var nav = $("t").contentWindow.navigator;
-    ok(Components.utils.isXrayWrapper(nav), "Should have an Xray here");
+    var iframe = document.getElementById("t");
+
+    Services.perms.addFromPrincipal(iframe.contentDocument.nodePrincipal,
+                                    "contacts-read",
+                                    Services.perms.ALLOW_ACTION);
+    Services.perms.addFromPrincipal(iframe.contentDocument.nodePrincipal,
+                                    "contacts-write",
+                                    Services.perms.ALLOW_ACTION);
+    Services.perms.addFromPrincipal(iframe.contentDocument.nodePrincipal,
+                                    "contacts-create",
+                                    Services.perms.ALLOW_ACTION);
 
-    // Test WebIDL NavigatorProperty objects
-    is(typeof nav.mozContacts, "object", "Should have a mozContacts object");
-    is(nav.mozContacts, nav.mozContacts,
-       "Should have gotten the same mozContacts object again");
+    var dir = "chrome://mochitests/content/chrome/dom/base/test/";
+    iframe.src = dir + "file_navigator_resolve_identity_xrays.xul";
+    iframe.onload = function() { finish(); };
 
-    SimpleTest.finish();
+    function finish() {
+      Services.perms.removeFromPrincipal(document.nodePrincipal,
+                                         "contacts-read");
+      Services.perms.removeFromPrincipal(document.nodePrincipal,
+                                         "contacts-write");
+      Services.perms.removeFromPrincipal(document.nodePrincipal,
+                                         "contacts-create");
+      SimpleTest.finish();
+    }
   });
+
   ]]>
   </script>
 </window>
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/file_bug707564-2.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=707564
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 707564</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 707564 **/
+  var ok = parent.ok;
+  var isnot = parent.isnot;
+
+  addLoadEvent(function() {
+    var props = Object.getOwnPropertyNames(frames[0].navigator);
+    isnot(props.indexOf("mozContacts"), -1,
+          "Should enumerate a mozContacts property on navigator");
+
+    // Now enumerate a different navigator object
+    var found = false;
+    for (var name in frames[1].navigator) {
+      if (name == "mozContacts") {
+        found = true;
+      }
+    }
+    ok(found, "Should enumerate a mozContacts property on navigator via for...in");
+    parent.SimpleTest.finish();
+  });
+
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=707564">Mozilla Bug 707564</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe></iframe>
+<iframe></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/bindings/test/mochitest.ini
+++ b/dom/bindings/test/mochitest.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 support-files =
   file_InstanceOf.html
   file_bug707564.html
+  file_bug707564-2.html
   file_bug775543.html
   file_document_location_set_via_xray.html
   file_dom_xrays.html
   file_proxies_via_xray.html
   forOf_iframe.html
 
 [test_async_stacks.html]
 [test_ByteString.html]
--- a/dom/bindings/test/test_bug707564-chrome.html
+++ b/dom/bindings/test/test_bug707564-chrome.html
@@ -9,17 +9,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=707564">Mozilla Bug 707564</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 <iframe id="t1" src="http://example.org/tests/dom/bindings/test/file_bug707564.html"></iframe>
-<iframe id="t2" src="http://example.org/tests/dom/bindings/test/file_bug707564.html"></iframe>
+<iframe id="t2"></iframe>
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 775543 **/
 function test()
 {
   var nav = document.getElementById("t1").contentWindow.navigator;
@@ -38,15 +38,29 @@ function test()
       found = true;
     }
   }
   ok(found, "Should enumerate a mozContacts property on navigator xray via for...in");
 
   SimpleTest.finish();
 }
 
-addLoadEvent(test);
+onload = test;
+onload = function() {
+  var iframe1 = document.getElementById("t1");
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: iframe1.contentDocument},
+    {type: "contacts-write", allow: true, context: iframe1.contentDocument},
+    {type: "contacts-create", allow: true, context: iframe1.contentDocument},
+  ], function() {
+    iframe1.src = "http://example.org/tests/dom/bindings/test/file_bug707564.html";
+    iframe1.onload = function() {
+      var iframe2 = document.getElementById("t2");
+      iframe2.src = "http://example.org/tests/dom/bindings/test/file_bug707564.html";
+      iframe2.onload = test;
+    };
+  });
+};
 
-SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/bindings/test/test_bug707564.html
+++ b/dom/bindings/test/test_bug707564.html
@@ -1,43 +1,29 @@
 <!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=707564
--->
 <head>
-  <meta charset="utf-8">
-  <title>Test for Bug 707564</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <script type="application/javascript">
-
-  /** Test for Bug 707564 **/
-  SimpleTest.waitForExplicitFinish();
-
-  addLoadEvent(function() {
-    var props = Object.getOwnPropertyNames(frames[0].navigator);
-    isnot(props.indexOf("mozContacts"), -1,
-          "Should enumerate a mozContacts property on navigator");
-
-    // Now enumerate a different navigator object
-    var found = false;
-    for (var name in frames[1].navigator) {
-      if (name == "mozContacts") {
-        found = true;
-      }
-    }
-    ok(found, "Should enumerate a mozContacts property on navigator via for...in");
-    SimpleTest.finish();
-  });
-  </script>
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=707564">Mozilla Bug 707564</a>
-<p id="display"></p>
-<div id="content" style="display: none">
 <iframe></iframe>
-<iframe></iframe>
-</div>
 <pre id="test">
+<script type="application/javascript">
+
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_bug707564-2.html";
+}
+
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
+};
+
+</script>
 </pre>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_contacts_basics.html
@@ -0,0 +1,787 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=674720
+-->
+<head>
+  <title>Test for Bug 674720 WebContacts</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=674720">Mozilla Bug 674720</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var ok = parent.ok;
+var is = parent.is;
+
+var initialRev;
+
+function checkRevision(revision, msg, then) {
+  var revReq = mozContacts.getRevision();
+  revReq.onsuccess = function(e) {
+    is(e.target.result, initialRev+revision, msg);
+    then();
+  };
+  // The revision function isn't supported on Android so treat on failure as success
+  if (isAndroid) {
+    revReq.onerror = function(e) {
+      then();
+    };
+  } else {
+    revReq.onerror = onFailure;
+  }
+}
+
+var req;
+
+var steps = [
+  function() {
+    req = mozContacts.getRevision();
+    req.onsuccess = function(e) {
+      initialRev = e.target.result;
+      next();
+    };
+
+    // Android does not support the revision function. Treat errors as success.
+    if (isAndroid) {
+      req.onerror = function(e) {
+        initialRev = 0;
+        next();
+      };
+    } else {
+      req.onerror = onFailure;
+    }
+  },
+  function () {
+    ok(true, "Deleting database");
+    checkRevision(0, "Initial revision is 0", function() {
+      req = mozContacts.clear();
+      req.onsuccess = function () {
+        ok(true, "Deleted the database");
+        checkCount(0, "No contacts after clear", function() {
+          checkRevision(1, "Revision was incremented on clear", next);
+        });
+      };
+      req.onerror = onFailure;
+    });
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find(defaultOptions);
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Empty database.");
+      checkRevision(1, "Revision was not incremented on find", next);
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding empty contact");
+    createResult1 = new mozContact({});
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      checkCount(1, "1 contact after adding empty contact", function() {
+        checkRevision(2, "Revision was incremented on save", next);
+      });
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find(defaultOptions);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "One contact.");
+      findResult1 = req.result[0];
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting empty contact");
+    req = navigator.mozContacts.remove(findResult1);
+    req.onsuccess = function () {
+      var req2 = mozContacts.find(defaultOptions);
+      req2.onsuccess = function () {
+        is(req2.result.length, 0, "Empty Database.");
+        clearTemps();
+        checkRevision(3, "Revision was incremented on remove", next);
+      }
+      req2.onerror = onFailure;
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact1");
+    createResult1 = new mozContact(properties1);
+
+    mozContacts.oncontactchange = function(event) {
+      is(event.contactID, createResult1.id, "Same contactID");
+      is(event.reason, "create", "Same reason");
+      next();
+    }
+
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      checkContacts(createResult1, properties1);
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 1");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[1].substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      // Some manual testing. Testint the testfunctions
+      // tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+55 (31) 9876-3456"}],
+      is(findResult1.tel[0].carrier, "testCarrier", "Same Carrier");
+      is(String(findResult1.tel[0].type), "work", "Same type");
+      is(findResult1.tel[0].value, "123456", "Same Value");
+      is(findResult1.tel[1].type[1], "fax", "Same type");
+      is(findResult1.tel[1].value, "+55 (31) 9876-3456", "Same Value");
+
+      is(findResult1.adr[0].countryName, "country 1", "Same country");
+
+      // email: [{type: ["work"], value: "x@y.com"}]
+      is(String(findResult1.email[0].type), "work", "Same Type");
+      is(findResult1.email[0].value, "x@y.com", "Same Value");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for exact email");
+    var options = {filterBy: ["email"],
+                   filterOp: "equals",
+                   filterValue: properties1.email[0].value};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(findResult1, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring and update");
+    mozContacts.oncontactchange = function(event) {
+       is(event.contactID, findResult1.id, "Same contactID");
+       is(event.reason, "update", "Same reason");
+     }
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[0].substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      findResult1.jobTitle = ["new Job"];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact");
+    mozContacts.oncontactchange = function(event) {
+       is(event.contactID, createResult2.id, "Same contactID");
+       is(event.reason, "create", "Same reason");
+     }
+    createResult2 = new mozContact({name: ["newName"]});
+    req = navigator.mozContacts.save(createResult2);
+    req.onsuccess = function () {
+      ok(createResult2.id, "The contact now has an ID.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 2");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[0].substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      checkContacts(createResult1, findResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function() {
+    ok(true, "Retrieving by name equality 1");
+    var options = {filterBy: ["name"],
+                   filterOp: "equals",
+                   filterValue: properties1.name[0]};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      checkContacts(createResult1, findResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function() {
+    ok(true, "Retrieving by name equality 2");
+    var options = {filterBy: ["name"],
+                   filterOp: "equals",
+                   filterValue: properties1.name[1]};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      checkContacts(createResult1, findResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function() {
+    ok(true, "Retrieving by name substring 1");
+    var options = {filterBy: ["name"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.name[0].substring(0,3).toLowerCase()};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      checkContacts(createResult1, findResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function() {
+    ok(true, "Retrieving by name substring 2");
+    var options = {filterBy: ["name"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.name[1].substring(0,3).toLowerCase()};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      checkContacts(createResult1, findResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Remove contact1");
+    mozContacts.oncontactchange = function(event) {
+      is(event.contactID, createResult1.id, "Same contactID");
+      is(event.reason, "remove", "Same reason");
+    }
+    req = navigator.mozContacts.remove(createResult1);
+    req.onsuccess = function () {
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 3");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[1].substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Found no contact.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Remove contact2");
+    mozContacts.oncontactchange = function(event) {
+      is(event.contactID, createResult2.id, "Same contactID");
+      is(event.reason, "remove", "Same reason");
+    }
+    req = navigator.mozContacts.remove(createResult2);
+    req.onsuccess = function () {
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 4");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[1].substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Found no contact.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    mozContacts.oncontactchange = function(event) {
+      is(event.contactID, "undefined", "Same contactID");
+      is(event.reason, "remove", "Same reason");
+    }
+    req = mozContacts.clear();
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact with properties1");
+    createResult1 = new mozContact(properties1);
+    mozContacts.oncontactchange = null;
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring tel1");
+    var options = {filterBy: ["tel"],
+                   filterOp: "contains",
+                   filterValue: properties1.tel[1].value.substring(2,5)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by tel exact");
+    var options = {filterBy: ["tel"],
+                   filterOp: "equals",
+                   filterValue: "+55 319 8 7 6 3456"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by tel exact with substring");
+    var options = {filterBy: ["tel"],
+                   filterOp: "equals",
+                   filterValue: "3456"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Found no contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by tel exact with substring");
+    var options = {filterBy: ["tel"],
+                   filterOp: "equals",
+                   filterValue: "+55 (31)"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Found no contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by tel match national number");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: "3198763456"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by tel match national format");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: "0451 491934"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by tel match entered number");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: "123456"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by tel match international number");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: "+55 31 98763456"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by match with field other than tel");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "match",
+                   filterValue: "my friends call me 555-4040"};
+    req = mozContacts.find(options);
+    req.onsuccess = onUnwantedSuccess;
+    req.onerror = function() {
+      ok(true, "Failed");
+      next();
+    }
+  },
+  function () {
+    ok(true, "Retrieving by substring tel2");
+    var options = {filterBy: ["tel"],
+                   filterOp: "startsWith",
+                   filterValue: "9876"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring tel3");
+    var options = {filterBy: ["tel"],
+                   filterOp: "startsWith",
+                   filterValue: "98763456"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 5");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[0].substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 6");
+    var options = {filterBy: ["familyName", "givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[0].substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring3, Testing multi entry");
+    var options = {filterBy: ["givenName", "familyName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.familyName[1].substring(0,3).toLowerCase()};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find(defaultOptions);
+    req.onsuccess = function() {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(createResult1, findResult1);
+      if (!isAndroid) {
+        ok(findResult1.updated, "Has updated field");
+        ok(findResult1.published, "Has published field");
+      }
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Modifying contact1");
+    if (!findResult1) {
+      SpecialPowers.executeSoon(next);
+    } else {
+      findResult1.impp = properties1.impp = [{value:"phil impp"}];
+      req = navigator.mozContacts.save(findResult1);
+      req.onsuccess = function () {
+        var req2 = mozContacts.find(defaultOptions);
+        req2.onsuccess = function() {
+          is(req2.result.length, 1, "Found exactly 1 contact.");
+          findResult2 = req2.result[0];
+          ok(findResult2.id == sample_id1, "Same ID");
+          checkContacts(findResult2, properties1);
+          is(findResult2.impp.length, 1, "Found exactly 1 IMS info.");
+          next();
+        };
+        req2.onerror = onFailure;
+      };
+      req.onerror = onFailure;
+    }
+  },
+  function() {
+    // Android does not support published/updated fields. Skip this.
+    if (isAndroid) {
+      next();
+      return;
+    }
+
+    ok(true, "Saving old contact, should abort!");
+    req = mozContacts.save(createResult1);
+    req.onsuccess = onUnwantedSuccess;
+    req.onerror   = function() { ok(true, "Successfully declined updating old contact!"); next(); };
+  },
+  function () {
+    ok(true, "Retrieving a specific contact by ID");
+    var options = {filterBy: ["id"],
+                   filterOp: "equals",
+                   filterValue: sample_id1};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(findResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving a specific contact by givenName");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "equals",
+                   filterValue: properties1.givenName[0]};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(findResult1, properties1);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Modifying contact2");
+    if (!findResult1) {
+      SpecialPowers.executeSoon(next);
+    } else {
+      findResult1.impp = properties1.impp = [{value: "phil impp"}];
+      req = mozContacts.save(findResult1);
+      req.onsuccess = function () {
+        var req2 = mozContacts.find(defaultOptions);
+        req2.onsuccess = function () {
+          is(req2.result.length, 1, "Found exactly 1 contact.");
+          findResult1 = req2.result[0];
+          ok(findResult1.id == sample_id1, "Same ID");
+          checkContacts(findResult1, properties1);
+          is(findResult1.impp.length, 1, "Found exactly 1 IMS info.");
+          next();
+        }
+        req2.onerror = onFailure;
+      };
+      req.onerror = onFailure;
+    }
+  },
+  function () {
+    ok(true, "Searching contacts by query");
+    var options = {filterBy: ["givenName", "email"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[0].substring(0,4)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(findResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching contacts by query");
+    var options = {filterBy: ["givenName", "email"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[0]};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(findResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching contacts with multiple indices");
+    var options = {filterBy: ["email", "givenName"],
+                   filterOp: "equals",
+                   filterValue: properties1.givenName[1]};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(findResult1, properties1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Modifying contact3");
+    if (!findResult1) {
+      SpecialPowers.executeSoon(next);
+    } else {
+      findResult1.email = [{value: properties1.nickname}];
+      findResult1.nickname = ["TEST"];
+      var newContact = new mozContact(findResult1);
+      req = mozContacts.save(newContact);
+      req.onsuccess = function () {
+        var options = {filterBy: ["email", "givenName"],
+                       filterOp: "startsWith",
+                       filterValue: properties1.givenName[0]};
+        // One contact has it in nickname and the other in email
+        var req2 = mozContacts.find(options);
+        req2.onsuccess = function () {
+          is(req2.result.length, 2, "Found exactly 2 contacts.");
+          ok(req2.result[0].id != req2.result[1].id, "Different ID");
+          next();
+        }
+        req2.onerror = onFailure;
+      };
+      req.onerror = onFailure;
+    }
+  },
+  function () {
+    ok(true, "Deleting contact" + findResult1);
+    req = mozContacts.remove(findResult1);
+    req.onsuccess = function () {
+      var req2 = mozContacts.find(defaultOptions);
+      req2.onsuccess = function () {
+        is(req2.result.length, 1, "One contact left.");
+        findResult1 = req2.result[0];
+        next();
+      }
+      req2.onerror = onFailure;
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.remove(findResult1);
+    req.onsuccess =  function () {
+      clearTemps();
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function() {
+    ok(true, "Test JSON.stringify output for mozContact objects");
+    var json = JSON.parse(JSON.stringify(new mozContact(properties1)));
+    checkContacts(json, properties1);
+    next();
+  },
+  function() {
+    ok(true, "Test slice");
+    var c = new mozContact();
+    c.email = [{ type: ["foo"], value: "bar@baz" }]
+    var arr = c.email;
+    is(arr[0].value, "bar@baz", "Should have the right value");
+    arr = arr.slice();
+    is(arr[0].value, "bar@baz", "Should have the right value after slicing");
+    next();
+  },
+  function () {
+    ok(true, "all done!\n");
+    clearTemps();
+
+    parent.SimpleTest.finish();
+  }
+];
+
+start_tests();
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_contacts_basics2.html
@@ -0,0 +1,1153 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=674720
+-->
+<head>
+  <title>Test for Bug 674720 WebContacts</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=674720">Mozilla Bug 674720</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var ok = parent.ok;
+var is = parent.is;
+var isnot = parent.isnot;
+
+var req;
+
+var steps = [
+  function () {
+    ok(true, "Adding a new contact");
+    createResult1 = new mozContact(properties1);
+    req = mozContacts.save(createResult1)
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact2");
+    createResult2 = new mozContact(properties2);
+    req = mozContacts.save(createResult2);
+    req.onsuccess = function () {
+      ok(createResult2.id, "The contact now has an ID.");
+      sample_id2 = createResult2.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find({sortBy: "familyName"});
+    req.onsuccess = function () {
+      is(req.result.length, 2, "Found exactly 2 contact.");
+      checkContacts(req.result[1], properties1);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    console.log("Searching contacts by query1");
+    var options = {filterBy: ["givenName", "email"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[0].substring(0, 4)}
+    req = mozContacts.find(options)
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(findResult1, createResult1);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching contacts by query2");
+    var options = {filterBy: ["givenName", "email"],
+                   filterOp: "startsWith",
+                   filterValue: properties2.givenName[0].substring(0, 4)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      is(findResult1.adr.length, 2, "Adr length 2");
+      checkContacts(findResult1, createResult2);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching contacts by tel");
+    var options = {filterBy: ["tel"],
+                   filterOp: "contains",
+                   filterValue: properties2.tel[0].value.substring(3, 7)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id2, "Same ID");
+      checkContacts(findResult1, createResult2);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching contacts by email");
+    var options = {filterBy: ["email"],
+                   filterOp: "startsWith",
+                   filterValue: properties2.email[0].value.substring(0, 4)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id2, "Same ID");
+      checkContacts(findResult1, createResult2);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear();
+    req.onsuccess = function () {
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding 20 contacts");
+    for (var i=0; i<19; i++) {
+      createResult1 = new mozContact(properties1);
+      req = mozContacts.save(createResult1);
+      req.onsuccess = function () {
+        ok(createResult1.id, "The contact now has an ID.");
+      };
+      req.onerror = onFailure;
+    };
+    createResult1 = new mozContact(properties1);
+    req = mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkStrArray(createResult1.name, properties1.name, "Same Name");
+      checkCount(20, "20 contacts in DB", next);
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find(defaultOptions);
+    req.onsuccess = function () {
+      is(req.result.length, 20, "20 Entries.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts with limit 10");
+    var options = { filterLimit: 10 };
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 10, "10 Entries.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts with limit 10 and sorted");
+    var options = { filterLimit: 10,
+                    sortBy: 'FamilyName',
+                    sortOrder: 'descending' };
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 10, "10 Entries.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts2");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[0].substring(0, 4)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 20, "20 Entries.");
+      checkContacts(createResult1, req.result[19]);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts3");
+    var options = {filterBy: ["givenName", "tel", "email"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[0].substring(0, 4)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 20, "20 Entries.");
+      checkContacts(createResult1, req.result[10]);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear();
+    req.onsuccess = function () {
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Testing clone contact");
+    createResult1 = new mozContact(properties1);
+    req = mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkStrArray(createResult1.name, properties1.name, "Same Name");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function() {
+    ok(true, "Testing clone contact2");
+    var cloned = new mozContact(createResult1);
+    ok(cloned.id != createResult1.id, "Cloned contact has new ID");
+    cloned.email = [{value: "new email!"}];
+    cloned.givenName = ["Tom"];
+    req = mozContacts.save(cloned);
+    req.onsuccess = function () {
+      ok(cloned.id, "The contact now has an ID.");
+      is(cloned.email[0].value, "new email!", "Same Email");
+      isnot(createResult1.email[0].value, cloned.email[0].value, "Clone has different email");
+      is(String(cloned.givenName), "Tom", "New Name");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties2.givenName[0].substring(0, 4)};
+    req = mozContacts.find(defaultOptions);
+    req.onsuccess = function () {
+      is(req.result.length, 2, "2 Entries.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Search with redundant fields should only return 1 contact");
+    createResult1 = new mozContact({name: ["XXX"],
+                                    givenName: ["XXX"],
+                                    email: [{value: "XXX"}],
+                                    tel: [{value: "XXX"}]
+                                   });
+    req = mozContacts.save(createResult1);
+    req.onsuccess = function() {
+      var options = {filterBy: ["givenName", "familyName"],
+                     filterOp: "equals",
+                     filterValue: "XXX"};
+      var req2 = mozContacts.find(options);
+      req2.onsuccess = function() {
+        is(req2.result.length, 1, "1 Entry");
+        next();
+      }
+      req2.onerror = onFailure;
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c3);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c3, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c2);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c2, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c4);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c4, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c1);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c1, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    var options = {sortBy: "familyName",
+                   sortOrder: "ascending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 4, "4 results");
+      checkContacts(req.result[0], c1);
+      checkContacts(req.result[1], c2);
+      checkContacts(req.result[2], c3);
+      checkContacts(req.result[3], c4);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    var options = {sortBy: "familyName",
+                   sortOrder: "descending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 4, "4 results");
+      checkContacts(req.result[0], c4);
+      checkContacts(req.result[1], c3);
+      checkContacts(req.result[2], c2);
+      checkContacts(req.result[3], c1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c5);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c5, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting with empty string");
+    var options = {sortBy: "familyName",
+                   sortOrder: "ascending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 5, "5 results");
+      checkContacts(req.result[0], c5);
+      checkContacts(req.result[1], c1);
+      checkContacts(req.result[2], c2);
+      checkContacts(req.result[3], c3);
+      checkContacts(req.result[4], c4);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Don't allow to add custom fields");
+    createResult1 = new mozContact({givenName: ["customTest"], yyy: "XXX"});
+    req = mozContacts.save(createResult1);
+    req.onsuccess = function() {
+      var options = {filterBy: ["givenName"],
+                     filterOp: "equals",
+                     filterValue: "customTest"};
+      var req2 = mozContacts.find(options);
+      req2.onsuccess = function() {
+        is(req2.result.length, 1, "1 Entry");
+        checkStrArray(req2.result[0].givenName, ["customTest"], "same name");
+        ok(req2.result.yyy === undefined, "custom property undefined");
+        next();
+      }
+      req2.onerror = onFailure;
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c7);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c7, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c6);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c6, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c8);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c8, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    // Android does not support published/updated fields. Skip this.
+    if (isAndroid) {
+      next();
+      return;
+    }
+
+    ok(true, "Test sorting with published");
+    var options = {sortBy: "familyName",
+                   sortOrder: "descending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 3, "3 results");
+      ok(req.result[0].published < req.result[1].published, "Right sorting order");
+      ok(req.result[1].published < req.result[2].published, "Right sorting order");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear();
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact with properties2");
+    createResult2 = new mozContact(properties2);
+    req = mozContacts.save(createResult2);
+    req.onsuccess = function () {
+      ok(createResult2.id, "The contact now has an ID.");
+      sample_id2 = createResult2.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test category search with startsWith");
+    var options = {filterBy: ["category"],
+                   filterOp: "startsWith",
+                   filterValue: properties2.category[0]};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "1 Entry.");
+      checkContacts(req.result[0], createResult2);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test category search with equals");
+    var options = {filterBy: ["category"],
+                   filterOp: "equals",
+                   filterValue: properties2.category[0]};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "1 Entry.");
+      checkContacts(req.result[0], createResult2);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact for category search");
+    createResult1 = new mozContact({name: ["5"], givenName: ["5"]});
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test category search with equals");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: "5"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "1 Entry.");
+      checkContacts(req.result[0], createResult1);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact with invalid data");
+    var obj = {
+        honorificPrefix: [],
+        honorificSuffix: [{foo: "bar"}],
+        sex: 17,
+        genderIdentity: 18,
+        email: [{type: ["foo"], value: "bar"}]
+    };
+    obj.honorificPrefix.__defineGetter__('0',(function() {
+      var c = 0;
+      return function() {
+        if (c == 0) {
+          c++;
+          return "string";
+        } else {
+          return {foo:"bar"};
+        }
+      }
+    })());
+    createResult1 = new mozContact(obj);
+    createResult1.email.push({aeiou: "abcde"});
+    req = mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      checkContacts(createResult1, {
+        honorificPrefix: ["string"],
+        honorificSuffix: ["[object Object]"],
+        sex: "17",
+        genderIdentity: "18",
+        email: [{type: ["foo"], value: "bar"}, {}]
+      });
+      next();
+    };
+  },
+  function () {
+    ok(true, "Adding contact with no number but carrier");
+    createResult1 = new mozContact({ tel: [{type: ["home"], carrier: "myCarrier"} ] });
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact with email but no value");
+    createResult1 = new mozContact({ email: [{type: ["home"]}] });
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Testing numbersOnly search 1");
+    createResult1 = new mozContact({ name: ["aaaaaaaaa"], givenName: ["aaaaaaaaa"], tel: [{ value: "1234567890"}]});
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test numbersOnly search 2");
+    var options = {filterBy: ["givenName", "tel"],
+                   filterOp: "contains",
+                   filterValue: "a"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 1, "1 Entry.");
+      checkContacts(req.result[0], createResult1);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test numbersOnly search 3");
+    var options = {filterBy: ["givenName", "tel"],
+                   filterOp: "contains",
+                   filterValue: "b"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 0, "0 Entries.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test numbersOnly search 4");
+    var options = {filterBy: ["givenName", "tel"],
+                   filterOp: "contains",
+                   filterValue: "1a"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 0, "0 Entries.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test numbersOnly search 5");
+    var options = {filterBy: ["givenName", "tel"],
+                   filterOp: "contains",
+                   filterValue: "1(23)"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 1, "1 Entry.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test numbersOnly search 6");
+    var options = {filterBy: ["givenName", "tel"],
+                   filterOp: "contains",
+                   filterValue: "1(23)a"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 0, "0 Entries.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function() {
+    ok(true, "Test that after setting array properties to scalar values the property os not a non-array")
+    const FIELDS = ["email","url","adr","tel","impp"];
+    createResult1 = new mozContact();
+    for (var prop of FIELDS) {
+      try {
+        createResult1[prop] = {type: ["foo"]};
+      } catch (e) {}
+      ok(createResult1[prop] === null ||
+         Array.isArray(createResult1[prop]), prop + " is array");
+    }
+    next();
+  },
+  function() {
+    ok(true, "Undefined properties of fields should be treated correctly");
+    var c = new mozContact({
+      adr: [{streetAddress: undefined}],
+      email: [{value: undefined}],
+      url: [{value: undefined}],
+      impp: [{value: undefined}],
+      tel: [{value: undefined}],
+    });
+    is(c.adr[0].streetAddress, undefined, "adr.streetAddress is undefined");
+    is(c.adr[0].locality, undefined, "adr.locality is undefined");
+    is(c.adr[0].pref, undefined, "adr.pref is undefined");
+    is(c.email[0].value, undefined, "email.value is undefined");
+    is(c.url[0].value, undefined, "url.value is undefined");
+    is(c.impp[0].value, undefined, "impp.value is undefined");
+    is(c.tel[0].value, undefined, "tel.value is undefined");
+    next();
+  },
+  function() {
+    ok(true, "Setting array properties to an empty array should work");
+    var c = new mozContact();
+    function testArrayProp(prop) {
+      is(c[prop], null, "property is initially null");
+      c[prop] = [];
+      ok(Array.isArray(c[prop]), "property is an array after setting");
+      is(c[prop].length, 0, "property has length 0 after setting");
+    }
+    testArrayProp("email");
+    testArrayProp("adr");
+    testArrayProp("tel");
+    testArrayProp("impp");
+    testArrayProp("url");
+    next();
+  },
+  function() {
+    ok(true, "Passing a mozContact with invalid data to save() should throw");
+    var c = new mozContact({
+      photo: [],
+      tel: []
+    });
+    c.photo.push({});
+    SimpleTest.doesThrow(()=>navigator.mozContacts.save(c), "Invalid data in Blob array");
+    c.tel.push(123);
+    SimpleTest.doesThrow(()=>navigator.mozContacts.save(c), "Invalid data in dictionary array");
+    next();
+  },
+  function() {
+    ok(true, "Inline changes to array properties should be seen by save");
+    var c = new mozContact({
+      name: [],
+      familyName: [],
+      givenName: [],
+      phoneticFamilyName: [],
+      phoneticGivenName: [],
+      nickname: [],
+      tel: [],
+      adr: [],
+      email: []
+    });
+    for (var prop of Object.getOwnPropertyNames(properties1)) {
+      if (!Array.isArray(properties1[prop])) {
+        continue;
+      }
+      for (var i = 0; i < properties1[prop].length; ++i) {
+        c[prop].push(properties1[prop][i]);
+      }
+    }
+    req = navigator.mozContacts.save(c);
+    req.onsuccess = function() {
+      req = navigator.mozContacts.find(defaultOptions);
+      req.onsuccess = function() {
+        is(req.result.length, 1, "Got 1 contact");
+        checkContacts(req.result[0], properties1);
+        next();
+      };
+      req.onerror = onFailure;
+    };
+    req.onerror = onFailure;
+  },
+  clearDatabase,
+  function() {
+    ok(true, "mozContact.init deprecation message");
+    var c = new mozContact();
+    SimpleTest.monitorConsole(next, [
+      { errorMessage: "mozContact.init is DEPRECATED. Use the mozContact constructor instead. " +
+                      "See https://developer.mozilla.org/docs/WebAPI/Contacts for details." }
+    ], /* forbidUnexpectedMsgs */ true);
+    c.init({name: ["Bar"]});
+    c.init({name: ["Bar"]});
+    SimpleTest.endMonitorConsole();
+  },
+  function() {
+    ok(true, "mozContact.init works as expected");
+    var c = new mozContact({name: ["Foo"]});
+    c.init({name: ["Bar"]});
+    is(c.name[0], "Bar", "Same name");
+    next();
+  },
+  function() {
+    ok(true, "mozContact.init without parameters");
+    var c = new mozContact({name: ["Foo"]});
+    c.init();
+    next();
+  },
+  function() {
+    ok(true, "mozContact.init resets properties");
+    var c = new mozContact({jobTitle: ["Software Engineer"]});
+    c.init({nickname: ["Jobless Johnny"]});
+    is(c.nickname[0], "Jobless Johnny", "Same nickname");
+    ok(!c.jobTitle, "jobTitle is not set");
+    next();
+  },
+  function() {
+    ok(true, "mozContacts.remove with an ID works");
+    var c = new mozContact({name: ["Ephemeral Jimmy"]});
+    req = navigator.mozContacts.save(c);
+    req.onsuccess = function() {
+      req = navigator.mozContacts.remove(c.id);
+      req.onsuccess = function() {
+        req = navigator.mozContacts.find({
+          filterBy: ["id"],
+          filterOp: "equals",
+          filterValue: c.id
+        });
+        req.onsuccess = function() {
+          is(req.result.length, 0, "Successfully removed contact by ID");
+          next();
+        };
+        req.onerror = onFailure;
+      };
+      req.onerror = onFailure;
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact");
+    createResult1 = new mozContact(properties3);
+    req = mozContacts.save(createResult1)
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact2");
+    createResult2 = new mozContact(properties4);
+    req = mozContacts.save(createResult2);
+    req.onsuccess = function () {
+      ok(createResult2.id, "The contact now has an ID.");
+      sample_id2 = createResult2.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find({sortBy: "phoneticFamilyName"});
+    req.onsuccess = function () {
+      is(req.result.length, 2, "Found exactly 2 contact.");
+      checkContacts(req.result[1], properties3);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching contacts by query1");
+    var options = {filterBy: ["phoneticGivenName", "email"],
+                   filterOp: "startsWith",
+                   filterValue: properties3.phoneticGivenName[0].substring(0, 3)}
+    req = mozContacts.find(options)
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      checkContacts(findResult1, createResult1);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching contacts by query2");
+    var options = {filterBy: ["phoneticGivenName", "email"],
+                   filterOp: "startsWith",
+                   filterValue: properties4.phoneticGivenName[0].substring(0, 3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      is(findResult1.adr.length, 2, "Adr length 2");
+      checkContacts(findResult1, createResult2);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  clearDatabase,
+  function () {
+    ok(true, "Adding 20 contacts");
+    for (var i=0; i<19; i++) {
+      createResult1 = new mozContact(properties3);
+      req = mozContacts.save(createResult1);
+      req.onsuccess = function () {
+        ok(createResult1.id, "The contact now has an ID.");
+      };
+      req.onerror = onFailure;
+    };
+    createResult1 = new mozContact(properties3);
+    req = mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkStrArray(createResult1.name, properties3.name, "Same Name");
+      checkCount(20, "20 contacts in DB", next);
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find(defaultOptions);
+    req.onsuccess = function () {
+      is(req.result.length, 20, "20 Entries.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts2");
+    var options = {filterBy: ["phoneticGivenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties3.phoneticGivenName[0].substring(0, 3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 20, "20 Entries.");
+      checkContacts(createResult1, req.result[19]);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts3");
+    var options = {filterBy: ["phoneticGivenName", "tel", "email"],
+                   filterOp: "startsWith",
+                   filterValue: properties3.phoneticGivenName[0].substring(0, 3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 20, "20 Entries.");
+      checkContacts(createResult1, req.result[10]);
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  clearDatabase,
+  function () {
+    ok(true, "Testing clone contact");
+    createResult1 = new mozContact(properties3);
+    req = mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkStrArray(createResult1.phoneticFamilyName, properties3.phoneticFamilyName, "Same phoneticFamilyName");
+      checkStrArray(createResult1.phoneticGivenName, properties3.phoneticGivenName, "Same phoneticGivenName");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find({sortBy: "phoneticGivenName"});
+    req.onsuccess = function () {
+      is(req.result.length, 1, "1 Entries.");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  clearDatabase,
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c11);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c11, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c10);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c10, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c12);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c12, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c9);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c9, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    var options = {sortBy: "phoneticFamilyName",
+                   sortOrder: "ascending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 4, "4 results");
+      checkContacts(req.result[0], c9);
+      checkContacts(req.result[1], c10);
+      checkContacts(req.result[2], c11);
+      checkContacts(req.result[3], c12);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    var options = {sortBy: "phoneticFamilyName",
+                   sortOrder: "descending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 4, "4 results");
+      checkContacts(req.result[0], c12);
+      checkContacts(req.result[1], c11);
+      checkContacts(req.result[2], c10);
+      checkContacts(req.result[3], c9);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c13);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c13, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting with empty string");
+    var options = {sortBy: "phoneticFamilyName",
+                   sortOrder: "ascending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 5, "5 results");
+      checkContacts(req.result[0], c13);
+      checkContacts(req.result[1], c9);
+      checkContacts(req.result[2], c10);
+      checkContacts(req.result[3], c11);
+      checkContacts(req.result[4], c12);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  clearDatabase,
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c15);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c15, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c14);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c14, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Test sorting");
+    createResult1 = new mozContact(c16);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      checkContacts(c16, createResult1);
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    // Android does not support published/updated fields. Skip this.
+    if (isAndroid) {
+      next();
+      return;
+    }
+
+    ok(true, "Test sorting with published");
+    var options = {sortBy: "phoneticFamilyName",
+                   sortOrder: "descending"};
+    req = navigator.mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 3, "3 results");
+      ok(req.result[0].published < req.result[1].published, "Right sorting order");
+      ok(req.result[1].published < req.result[2].published, "Right sorting order");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  clearDatabase,
+  function () {
+    ok(true, "all done!\n");
+    parent.SimpleTest.finish();
+  }
+];
+
+function next() {
+  ok(true, "Begin!");
+  if (index >= steps.length) {
+    ok(false, "Shouldn't get here!");
+    return;
+  }
+  try {
+    var i = index++;
+    steps[i]();
+  } catch(ex) {
+    ok(false, "Caught exception", ex);
+  }
+}
+
+start_tests();
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_contacts_blobs.html
@@ -0,0 +1,226 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=674720
+-->
+<head>
+  <title>Test for Bug 674720 WebContacts</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=674720">Mozilla Bug 674720</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var ok = parent.ok;
+var is = parent.is;
+var isnot = parent.isnot;
+
+var utils = SpecialPowers.getDOMWindowUtils(window);
+
+function getView(size)
+{
+ var buffer = new ArrayBuffer(size);
+ var view = new Uint8Array(buffer);
+ is(buffer.byteLength, size, "Correct byte length");
+ return view;
+}
+
+function getRandomView(size)
+{
+ var view = getView(size);
+ for (var i = 0; i < size; i++) {
+   view[i] = parseInt(Math.random() * 255)
+ }
+ return view;
+}
+
+function getRandomBlob(size)
+{
+  return new Blob([getRandomView(size)], { type: "binary/random" });
+}
+
+function compareBuffers(buffer1, buffer2)
+{
+  if (buffer1.byteLength != buffer2.byteLength) {
+    return false;
+  }
+  var view1 = new Uint8Array(buffer1);
+  var view2 = new Uint8Array(buffer2);
+  for (var i = 0; i < buffer1.byteLength; i++) {
+    if (view1[i] != view2[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+function verifyBuffers(buffer1, buffer2, isLast)
+{
+  ok(compareBuffers(buffer1, buffer2), "Correct blob data");
+  if (isLast)
+    next();
+}
+
+var randomBlob = getRandomBlob(1024);
+var randomBlob2 = getRandomBlob(1024);
+
+var properties1 = {
+  name: ["xTestname1"],
+  givenName: ["xTestname1"],
+  photo: [randomBlob]
+};
+
+var properties2 = {
+  name: ["yTestname2"],
+  givenName: ["yTestname2"],
+  photo: [randomBlob, randomBlob2]
+};
+
+var sample_id1;
+var createResult1;
+var findResult1;
+
+function verifyBlob(blob1, blob2, isLast)
+{
+  is(blob1 instanceof Blob, true,
+     "blob1 is an instance of DOMBlob");
+  is(blob2 instanceof Blob, true,
+     "blob2 is an instance of DOMBlob");
+  isnot(blob1 instanceof File, true,
+     "blob1 is an instance of File");
+  isnot(blob2 instanceof File, true,
+     "blob2 is an instance of File");
+  is(blob1.size, blob2.size, "Same size");
+  is(blob1.type, blob2.type, "Same type");
+
+  var buffer1;
+  var buffer2;
+
+  var reader1 = new FileReader();
+  reader1.readAsArrayBuffer(blob2);
+  reader1.onload = function(event) {
+    buffer2 = event.target.result;
+    if (buffer1) {
+      verifyBuffers(buffer1, buffer2, isLast);
+    }
+  }
+
+  var reader2 = new FileReader();
+  reader2.readAsArrayBuffer(blob1);
+  reader2.onload = function(event) {
+    buffer1 = event.target.result;
+    if (buffer2) {
+      verifyBuffers(buffer1, buffer2, isLast);
+    }
+  }
+}
+
+function verifyBlobArray(blobs1, blobs2)
+{
+  is(blobs1 instanceof Array, true, "blobs1 is an array object");
+  is(blobs2 instanceof Array, true, "blobs2 is an array object");
+  is(blobs1.length, blobs2.length, "Same length");
+
+  if (!blobs1.length) {
+    next();
+    return;
+  }
+
+  for (var i = 0; i < blobs1.length; i++) {
+    verifyBlob(blobs1[i], blobs2[i], i == blobs1.length - 1);
+  }
+}
+
+var req;
+
+var steps = [
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear();
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact with photo");
+    createResult1 = new mozContact(properties1);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties1.givenName[0].substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      verifyBlobArray(findResult1.photo, properties1.photo);
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact with 2 photos");
+    createResult1 = new mozContact(properties2);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring");
+    var options = {filterBy: ["givenName"],
+                   filterOp: "startsWith",
+                   filterValue: properties2.givenName[0].substring(0,3)};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      verifyBlobArray(findResult1.photo, properties2.photo);
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "all done!\n");
+
+    parent.SimpleTest.finish();
+  }
+];
+
+start_tests();
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_contacts_events.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=764667
+-->
+<head>
+  <title>Test for Bug 678695</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=764667">Mozilla Bug 764667</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 764667 **/
+
+var ok = parent.ok;
+var is = parent.is;
+
+var e = new MozContactChangeEvent("contactchanged", {contactID: "123", reason: "create"});
+ok(e, "Should have contactsChange event!");
+is(e.contactID, "123", "ID should be 123.");
+is(e.reason, "create", "Reason should be create.");
+
+e = new MozContactChangeEvent("contactchanged", {contactID: "test", reason: "test"});
+is(e.contactID, "test", "Name should be 'test'.");
+is(e.reason, "test", "Name should be 'test'.");
+
+e = new MozContactChangeEvent("contactchanged", {contactID: "a", reason: ""});
+is(e.contactID, "a", "Name should be a.");
+is(e.reason, "", "Value should be empty");
+
+parent.SimpleTest.finish();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_contacts_getall.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=836519
+-->
+<head>
+  <title>Mozilla Bug 836519</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=836519">Mozilla Bug 836519</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
+<script class="testbody" type="text/javascript;version=1.8">
+"use strict";
+
+var ok = parent.ok;
+var is = parent.is;
+var isnot = parent.isnot;
+
+let req;
+
+let steps = [
+  function start() {
+    SpecialPowers.Cc["@mozilla.org/tools/profiler;1"].getService(SpecialPowers.Ci.nsIProfiler).AddMarker("GETALL_START");
+    next();
+  },
+  clearDatabase,
+  addContacts,
+
+  function() {
+    ok(true, "Delete the current contact while iterating");
+    req = mozContacts.getAll({});
+    let count = 0;
+    let previousId = null;
+    req.onsuccess = function() {
+      if (req.result) {
+        ok(true, "on success");
+        if (previousId) {
+          isnot(previousId, req.result.id, "different contacts returned");
+        }
+        previousId = req.result.id;
+        count++;
+        let delReq = mozContacts.remove(req.result);
+        delReq.onsuccess = function() {
+          ok(true, "deleted current contact");
+          req.continue();
+        };
+      } else {
+        is(count, 40, "returned 40 contacts");
+        next();
+      }
+    };
+  },
+
+  clearDatabase,
+  addContacts,
+
+  function() {
+    ok(true, "Iterating through the contact list inside a cursor callback");
+    let count1 = 0, count2 = 0;
+    let req1 = mozContacts.getAll({});
+    let req2;
+    req1.onsuccess = function() {
+      if (count1 == 0) {
+        count1++;
+        req2 = mozContacts.getAll({});
+        req2.onsuccess = function() {
+          if (req2.result) {
+            count2++;
+            req2.continue();
+          } else {
+            is(count2, 40, "inner cursor returned 40 contacts");
+            req1.continue();
+          }
+        };
+      } else {
+        if (req1.result) {
+          count1++;
+          req1.continue();
+        } else {
+          is(count1, 40, "outer cursor returned 40 contacts");
+          next();
+        }
+      }
+    };
+  },
+
+  clearDatabase,
+  addContacts,
+
+  function() {
+    ok(true, "20 concurrent cursors");
+    const NUM_CURSORS = 20;
+    let completed = 0;
+    for (let i = 0; i < NUM_CURSORS; ++i) {
+      mozContacts.getAll({}).onsuccess = (function(i) {
+        let count = 0;
+        return function(event) {
+          let req = event.target;
+          if (req.result) {
+            count++;
+            req.continue();
+          } else {
+            is(count, 40, "cursor " + i + " returned 40 contacts");
+            if (++completed == NUM_CURSORS) {
+              next();
+            }
+          }
+        };
+      })(i);
+    }
+  },
+
+  clearDatabase,
+  addContacts,
+
+  function() {
+    if (!SpecialPowers.isMainProcess()) {
+      // We stop calling continue() intentionally here to see if the cursor gets
+      // cleaned up properly in the parent.
+      ok(true, "Leaking a cursor");
+      req = mozContacts.getAll({
+        sortBy: "familyName",
+        sortOrder: "ascending"
+      });
+      req.onsuccess = function(event) {
+        next();
+      };
+      req.onerror = onFailure;
+    } else {
+      next();
+    }
+  },
+
+  clearDatabase,
+
+  function() {
+    ok(true, "all done!\n");
+    SpecialPowers.Cc["@mozilla.org/tools/profiler;1"].getService(SpecialPowers.Ci.nsIProfiler).AddMarker("GETALL_END");
+    parent.SimpleTest.finish();
+  }
+];
+
+start_tests();
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_contacts_getall2.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=836519
+-->
+<head>
+  <title>Mozilla Bug 836519</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=836519">Mozilla Bug 836519</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
+<script class="testbody" type="text/javascript;version=1.8">
+"use strict";
+
+var ok = parent.ok;
+var is = parent.is;
+
+let req;
+
+let steps = [
+  clearDatabase,
+  function() {
+    // add a contact
+    createResult1 = new mozContact({});
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function() {
+      next();
+    };
+    req.onerror = onFailure;
+  },
+
+  getOne(),
+  getOne("Retrieving one contact with getAll - cached"),
+
+  clearDatabase,
+  addContacts,
+
+  getAll(),
+  getAll("Retrieving 40 contacts with getAll - cached"),
+
+  function() {
+    ok(true, "Deleting one contact");
+    req = mozContacts.remove(createResult1);
+    req.onsuccess = function() {
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function() {
+    ok(true, "Test cache invalidation");
+    req = mozContacts.getAll({});
+    let count = 0;
+    req.onsuccess = function(event) {
+      ok(true, "on success");
+      if (req.result) {
+        ok(true, "result is valid");
+        count++;
+        req.continue();
+      } else {
+        is(count, 39, "last contact - 39 contacts returned");
+        next();
+      }
+    };
+    req.onerror = onFailure;
+  },
+
+  clearDatabase,
+  addContacts,
+
+  function() {
+    ok(true, "Test cache consistency when deleting contact during getAll");
+    req = mozContacts.find({});
+    req.onsuccess = function(e) {
+      let lastContact = e.target.result[e.target.result.length-1];
+      req = mozContacts.getAll({});
+      let count = 0;
+      let firstResult = true;
+      req.onsuccess = function(event) {
+        ok(true, "on success");
+        if (firstResult) {
+          if (req.result) {
+            count++;
+          }
+          let delReq = mozContacts.remove(lastContact);
+          delReq.onsuccess = function() {
+            firstResult = false;
+            req.continue();
+          };
+        } else {
+          if (req.result) {
+            ok(true, "result is valid");
+            count++;
+            req.continue();
+          } else {
+            is(count, 40, "last contact - 40 contacts returned");
+            next();
+          }
+        }
+      };
+    };
+  },
+
+  clearDatabase,
+
+  function() {
+    ok(true, "all done!\n");
+    parent.SimpleTest.finish();
+  }
+];
+
+start_tests();
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_contacts_international.html
@@ -0,0 +1,277 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815833
+-->
+<head>
+  <title>Test for Bug 815833 WebContacts</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815833">Mozilla Bug 815833</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var ok = parent.ok;
+var ise = parent.ise;
+
+var number1 = {
+  local: "7932012345",
+  international: "+557932012345"
+};
+
+var number2 = {
+  local: "7932012346",
+  international: "+557932012346"
+};
+
+var properties1 = {
+  name: ["Testname1"],
+  tel: [{type: ["work"], value: number1.local, carrier: "testCarrier"} , {type: ["home", "fax"], value: number2.local}],
+};
+
+var shortNumber = "888";
+var properties2 = {
+  name: ["Testname2"],
+  tel: [{type: ["work"], value: shortNumber, carrier: "testCarrier"}]
+};
+
+var number3 = {
+  local: "7932012345",
+  international: "+557932012345"
+};
+
+var properties3 = {
+  name: ["Testname2"],
+  tel: [{value: number3.international}]
+};
+
+var req;
+var createResult1;
+var findResult1;
+var sample_id1;
+
+var steps = [
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear();
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact1");
+    createResult1 = new mozContact(properties1);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact2");
+    var createResult2 = new mozContact(properties2);
+    req = navigator.mozContacts.save(createResult2);
+    req.onsuccess = function () {
+      ok(createResult2.id, "The contact now has an ID.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for local number");
+    var options = {filterBy: ["tel"],
+                   filterOp: "startsWith",
+                   filterValue: number1.local};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      is(findResult1.id, sample_id1, "Same ID");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for international number");
+    var options = {filterBy: ["tel"],
+                   filterOp: "startsWith",
+                   filterValue: number1.international};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Found exactly 0 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for a short number matching the prefix");
+    var shortNumber = number1.local.substring(0, 3);
+    var options = {filterBy: ["tel"],
+                   filterOp: "equals",
+                   filterValue: shortNumber};
+    req = mozContacts.find(options);
+    req.onsuccess = function() {
+      is(req.result.length, 0, "The prefix short number should not match any contact.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for a short number matching the suffix");
+    var shortNumber = number1.local.substring(number1.local.length - 3);
+    var options = {filterBy: ["tel"],
+                   filterOp: "equals",
+                   filterValue: shortNumber};
+    req = mozContacts.find(options);
+    req.onsuccess = function() {
+      is(req.result.length, 0, "The suffix short number should not match any contact.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for a short number matching a contact");
+    var options = {filterBy: ["tel"],
+                   filterOp: "equals",
+                   filterValue: shortNumber};
+    req = mozContacts.find(options);
+    req.onsuccess = function() {
+      is(req.result.length, 1, "Found the contact equally matching the shortNumber.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function() {
+    ok(true, "Modifying number");
+    if (!findResult1) {
+      SpecialPowers.executeSoon(next);
+    } else {
+      findResult1.tel[0].value = number2.local;
+      req = mozContacts.save(findResult1);
+      req.onsuccess = function () {
+        next();
+      };
+    }
+  },
+  function () {
+    ok(true, "Searching for local number");
+    var options = {filterBy: ["tel"],
+                   filterOp: "startsWith",
+                   filterValue: number1.local};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Found exactly 0 contact.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for local number");
+    var options = {filterBy: ["tel"],
+                   filterOp: "startsWith",
+                   filterValue: number1.international};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Found exactly 0 contact.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for local number");
+    var options = {filterBy: ["tel"],
+                   filterOp: "startsWith",
+                   filterValue: number2.local};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      is(findResult1.id, sample_id1, "Same ID");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for local number");
+    var options = {filterBy: ["tel"],
+                   filterOp: "startsWith",
+                   filterValue: number2.international};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Found exactly 1 contact.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear();
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a contact with a Brazilian country code");
+    createResult1 = new mozContact(properties3);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for Brazilian number using local number");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: number3.local};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      is(findResult1.id, sample_id1, "Same ID");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear();
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "all done!\n");
+    parent.SimpleTest.finish();
+  }
+];
+
+SpecialPowers.pushPrefEnv({
+  set: [
+    ["ril.lastKnownSimMcc", "000"]
+  ]
+}, start_tests);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_contacts_substringmatching.html
@@ -0,0 +1,351 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=877302
+-->
+<head>
+  <title>Test for Bug 877302 substring matching for WebContacts</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=877302">Mozilla Bug 877302</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var ok = parent.ok;
+var is = parent.is;
+
+var substringLength = 8;
+
+var prop = {
+  tel: [{value: "7932012345" }, {value: "7932012346"}]
+};
+
+var prop2 = {
+  tel: [{value: "01187654321" }]
+};
+
+var prop3 = {
+  tel: [{ value: "+43332112346" }]
+};
+
+var prop4 = {
+  tel: [{ value: "(0414) 233-9888" }]
+};
+
+var brazilianNumber = {
+  international1: "0041557932012345",
+  international2: "+557932012345"
+};
+
+var prop5 = {
+  tel: [{value: brazilianNumber.international2}]
+};
+
+var req;
+var steps = [
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact");
+    createResult1 = new mozContact(prop);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find({});
+    req.onsuccess = function () {
+      is(req.result.length, 1, "One contact.");
+      findResult1 = req.result[0];
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 1");
+    var length = prop.tel[0].value.length;
+    var num = prop.tel[0].value.substring(length - substringLength, length);
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: num};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      is(findResult1.tel[0].value, "7932012345", "Same Value");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 2");
+    var length = prop.tel[1].value.length;
+    var num = prop.tel[1].value.substring(length - substringLength, length);
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: num};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      is(findResult1.tel[0].value, "7932012345", "Same Value");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 3");
+    var length = prop.tel[0].value.length;
+    var num = prop.tel[0].value.substring(length - substringLength + 1, length);
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: num};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Found exactly 0 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 4");
+    var length = prop.tel[0].value.length;
+    var num = prop.tel[0].value.substring(length - substringLength - 1, length);
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: num};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact");
+    createResult1 = new mozContact(prop2);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 5");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: "87654321"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 6");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: "01187654321"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 7");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: "909087654321"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 8");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: "0411187654321"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 9");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: "90411187654321"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 10");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: "+551187654321"};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact");
+    createResult1 = new mozContact(prop3);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    if (!isAndroid) { // Bug 905927
+      ok(true, "Retrieving by substring 1");
+      var length = prop3.tel[0].value.length;
+      var num = prop3.tel[0].value.substring(length - substringLength, length);
+      var options = {filterBy: ["tel"],
+                     filterOp: "match",
+                     filterValue: num};
+      req = mozContacts.find(options);
+      req.onsuccess = function () {
+        is(req.result.length, 0, "Found exactly 0 contacts.");
+        next();
+      };
+      req.onerror = onFailure;
+    } else {
+      SpecialPowers.executeSoon(next);
+    }
+  },
+  function () {
+    ok(true, "Adding contact");
+    createResult1 = new mozContact(prop4);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 1");
+    var num = "(0424) 233-9888"
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: num};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding a new contact with a Brazilian country code");
+    createResult1 = new mozContact(prop5);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for international number with prefix");
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: brazilianNumber.international1};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      ok(req.result.length == 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "all done!\n");
+    parent.SimpleTest.finish();
+  }
+];
+
+SpecialPowers.pushPrefEnv({
+  set: [
+    ["dom.phonenumber.substringmatching.BR", substringLength],
+    ["ril.lastKnownSimMcc", "724"]
+  ]
+}, start_tests);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_contacts_substringmatchingCL.html
@@ -0,0 +1,207 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=877302
+-->
+<head>
+  <title>Test for Bug 949537 substring matching for WebContacts</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949537">Mozilla Bug 949537</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var ok = parent.ok;
+var ise = parent.ise;
+
+var landlineNumber = "+56 2 27654321";
+
+var number = {
+  local: "87654321",
+  international: "+56 9 87654321"
+};
+
+var properties = {
+  name: ["Testname2"],
+  tel: [{value: number.international}]
+};
+
+var req;
+var steps = [
+  function () {
+    ok(true, "Adding a contact with a Chilean number");
+    createResult1 = new mozContact(properties);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for Chilean number with prefix");
+    req = mozContacts.find({
+      filterBy: ["tel"],
+      filterOp: "match",
+      filterValue: number.international
+    });
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      is(findResult1.id, sample_id1, "Same ID");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Searching for Chilean number using local number");
+    req = mozContacts.find({
+      filterBy: ["tel"],
+      filterOp: "match",
+      filterValue: number.local
+    });
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found 0 contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+
+  clearDatabase,
+
+  function () {
+    ok(true, "Adding contact with mobile number");
+    createResult1 = new mozContact({tel: [{value: number.international}]});
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find({});
+    req.onsuccess = function () {
+      is(req.result.length, 1, "One contact.");
+      findResult1 = req.result[0];
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by last 8 digits");
+    req = mozContacts.find({
+      filterBy: ["tel"],
+      filterOp: "match",
+      filterValue: number.international.slice(-8)
+    });
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      is(findResult1.id, sample_id1, "Same ID");
+      is(findResult1.tel[0].value, number.international, "Same Value");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by last 9 digits");
+    req = mozContacts.find({
+      filterBy: ["tel"],
+      filterOp: "match",
+      filterValue: number.international.slice(-9)
+    });
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      is(findResult1.id, sample_id1, "Same ID");
+      is(findResult1.tel[0].value, number.international, "Same Value");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by last 6 digits");
+    req = mozContacts.find({
+      filterBy: ["tel"],
+      filterOp: "match",
+      filterValue: number.international.slice(-6)
+    });
+    req.onsuccess = function () {
+      is(req.result.length, 0, "Found exactly zero contacts.");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+
+  clearDatabase,
+
+  function () {
+    ok(true, "Adding contact with landline number");
+    createResult1 = new mozContact({tel: [{value: landlineNumber}]});
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find({});
+    req.onsuccess = function () {
+      is(req.result.length, 1, "One contact.");
+      findResult1 = req.result[0];
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by last 7 digits (local number) with landline calling prefix");
+    req = mozContacts.find({
+      filterBy: ["tel"],
+      filterOp: "match",
+      filterValue: "022" + landlineNumber.slice(-7)
+    });
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      is(findResult1.id, sample_id1, "Same ID");
+      is(findResult1.tel[0].value, landlineNumber, "Same Value");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+
+  clearDatabase,
+
+  function () {
+    ok(true, "all done!\n");
+    parent.SimpleTest.finish();
+  }
+];
+
+SpecialPowers.pushPrefEnv({
+  set: [
+    ["dom.phonenumber.substringmatching.CL", 8],
+    ["ril.lastKnownSimMcc", "730"]
+  ]
+}, start_tests);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_contacts_substringmatchingVE.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=877302
+-->
+<head>
+  <title>Test for Bug 877302 substring matching for WebContacts</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=877302">Mozilla Bug 877302</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var ok = parent.ok;
+var is = parent.is;
+
+var prop = {
+  tel: [{value: "7932012345" }, {value: "7704143727591"}]
+};
+
+var prop2 = {
+  tel: [{value: "7932012345" }, {value: "+58 212 5551212"}]
+};
+
+var req;
+var steps = [
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact");
+    createResult1 = new mozContact(prop);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving all contacts");
+    req = mozContacts.find({});
+    req.onsuccess = function () {
+      is(req.result.length, 1, "One contact.");
+      findResult1 = req.result[0];
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 1");
+    var length = prop.tel[0].value.length;
+    var num = "04143727591"
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: num};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      is(findResult1.tel[1].value, "7704143727591", "Same Value");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Adding contact");
+    createResult1 = new mozContact(prop2);
+    req = navigator.mozContacts.save(createResult1);
+    req.onsuccess = function () {
+      ok(createResult1.id, "The contact now has an ID.");
+      sample_id1 = createResult1.id;
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Retrieving by substring 2");
+    var num = "5551212";
+    var options = {filterBy: ["tel"],
+                   filterOp: "match",
+                   filterValue: num};
+    req = mozContacts.find(options);
+    req.onsuccess = function () {
+      is(req.result.length, 1, "Found exactly 1 contact.");
+      findResult1 = req.result[0];
+      ok(findResult1.id == sample_id1, "Same ID");
+      is(findResult1.tel[1].value, "+58 212 5551212", "Same Value");
+      next();
+    };
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "Deleting database");
+    req = mozContacts.clear()
+    req.onsuccess = function () {
+      ok(true, "Deleted the database");
+      next();
+    }
+    req.onerror = onFailure;
+  },
+  function () {
+    ok(true, "all done!\n");
+    parent.SimpleTest.finish();
+  }
+];
+
+SpecialPowers.pushPrefEnv({
+  set: [
+    ["dom.phonenumber.substringmatching.VE", 7],
+    ["ril.lastKnownSimMcc", "734"]
+  ]
+}, start_tests);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_migration.html
@@ -0,0 +1,197 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Migration tests</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<h1>migration tests</h1>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="text/javascript;version=1.8" src="shared.js"></script>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var ok = parent.ok;
+var ise = parent.ise;
+
+var backend, contactsCount, allContacts;
+function loadChromeScript() {
+  var url = SimpleTest.getTestFileURL("test_migration_chrome.js");
+  backend = SpecialPowers.loadChromeScript(url);
+}
+
+function addBackendEvents() {
+  backend.addMessageListener("createDB.success", function(count) {
+    contactsCount = count;
+    ok(true, "Created the database");
+    next();
+  });
+  backend.addMessageListener("createDB.error", function(err) {
+    ok(false, err);
+    next();
+  });
+
+  backend.addMessageListener("deleteDB.success", function() {
+    ok(true, "Deleted the database");
+    next();
+  });
+  backend.addMessageListener("deleteDB.error", function(err) {
+    ok(false, err);
+    next();
+  });
+}
+
+function createDB(version) {
+  info("Will create the DB at version " + version);
+  backend.sendAsyncMessage("createDB", version);
+}
+
+function deleteDB() {
+  info("Will delete the DB.");
+  backend.sendAsyncMessage("deleteDB");
+}
+
+var steps = [
+  function setupChromeScript() {
+    loadChromeScript();
+    addBackendEvents();
+    next();
+  },
+
+  deleteDB, // let's be sure the DB does not exist yet
+  createDB.bind(null, 12),
+
+  function testAccessMozContacts() {
+    info("Checking we have the right number of contacts: " + contactsCount);
+    var req = mozContacts.getCount();
+    req.onsuccess = function onsuccess() {
+      ok(true, "Could access the mozContacts API");
+      is(this.result, contactsCount, "Contacts count is correct");
+      next();
+    };
+
+    req.onerror = function onerror() {
+      ok(false, "Couldn't access the mozContacts API");
+      next();
+    };
+  },
+
+  function testRetrieveAllContacts() {
+    /* if the migration does not work right, either we'll have an error, or the
+       contacts won't be migrated properly and thus will fail WebIDL conversion,
+       which will manifest as a timeout */
+    info("Checking the contacts are corrected to obey WebIDL constraints.  (upgrades 14 to 17)");
+    var req = mozContacts.find();
+    req.onsuccess = function onsuccess() {
+      if (this.result) {
+        is(this.result.length, contactsCount, "Contacts array length is correct");
+        allContacts = this.result;
+        next();
+      } else {
+        ok(false, "Could access the mozContacts API but got no contacts!");
+        next();
+      }
+    };
+
+    req.onerror = function onerror() {
+      ok(false, "Couldn't access the mozContacts API");
+      next();
+    };
+  },
+
+  function checkNameIndex() {
+    info("Checking name index migration (upgrades 17 to 19).");
+    if (!allContacts) {
+      next();
+    }
+
+    var count = allContacts.length;
+
+    function finishRequest() {
+      count--;
+      if (!count) {
+        next();
+      }
+    }
+
+    allContacts.forEach(function(contact) {
+      var name = contact.name && contact.name[0];
+      if (!name) {
+        count--;
+        return;
+      }
+
+      var req = mozContacts.find({
+        filterBy: ["name"],
+        filterValue: name,
+        filterOp: "equals"
+      });
+
+      req.onsuccess = function onsuccess() {
+        if (this.result) {
+          info("Found contact '" + name + "', checking it's the correct one.");
+          checkContacts(this.result[0], contact);
+        } else {
+          ok(false, "Could not find contact with name '" + name + "'");
+        }
+
+        finishRequest();
+      };
+
+      req.onerror = function onerror() {
+        ok(false, "Error while finding contact with name '" + name + "'!");
+        finishRequest();
+      }
+    });
+
+    if (!count) {
+      ok(false, "No contact had a name, this is unexpected.");
+      next();
+    }
+  },
+
+  function checkSubstringMatching() {
+    var subject = "0004567890"; // the last 7 digits are the same that at least one contact
+    info("Looking for a contact matching " + subject);
+    var req = mozContacts.find({
+      filterValue: subject,
+      filterOp: "match",
+      filterBy: ["tel"],
+      filterLimit: 1
+    });
+
+    req.onsuccess = function onsuccess() {
+      if (this.result && this.result[0]) {
+        ok(true, "Found a contact with number " + this.result[0].tel[0].value);
+      }
+      next();
+    };
+
+    req.onerror = function onerror() {
+      ok(false, "Error while finding contact for substring matching check!");
+      next();
+    };
+  },
+
+  deleteDB,
+
+  function finish() {
+    backend.destroy();
+    info("all done!\n");
+    parent.SimpleTest.finish();
+  }
+];
+
+// this is the Mcc for Brazil, so that we trigger the previous pref
+SpecialPowers.pushPrefEnv({"set": [["dom.phonenumber.substringmatching.BR", 7],
+                                   ["ril.lastKnownSimMcc", "724"]]}, start_tests);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/contacts/tests/file_permission_denied.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1081873
+-->
+<head>
+  <title>Test for Bug 1081873</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1081873">Mozilla Bug 1081873</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+"use strict";
+
+parent.is("mozContacts" in navigator, false, "navigator.mozContacts must be inaccessible");
+parent.SimpleTest.finish();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/contacts/tests/mochitest.ini
+++ b/dom/contacts/tests/mochitest.ini
@@ -1,10 +1,24 @@
 [DEFAULT]
-support-files = shared.js
+support-files =
+  shared.js
+  file_contacts_basics.html
+  file_contacts_basics2.html
+  file_contacts_blobs.html
+  file_contacts_events.html
+  file_contacts_getall.html
+  file_contacts_getall2.html
+  file_contacts_international.html
+  file_contacts_substringmatching.html
+  file_contacts_substringmatchingVE.html
+  file_contacts_substringmatchingCL.html
+  test_migration_chrome.js
+  file_migration.html
+  file_permission_denied.html
 
 [test_contacts_basics.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_contacts_basics2.html]
 skip-if = (toolkit == 'gonk' && debug) || (os == 'win' && os_version == '5.1') #debug-only failure, bug 967258 on XP
 [test_contacts_blobs.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_contacts_events.html]
@@ -12,12 +26,11 @@ skip-if = (toolkit == 'gonk' && debug) #
 skip-if = (toolkit == 'gonk' && debug) || (toolkit == 'android' && processor == 'x86') #debug-only failure #x86 only
 [test_contacts_getall2.html]
 skip-if = (toolkit == 'gonk' && debug) #debug-only failure
 [test_contacts_international.html]
 [test_contacts_substringmatching.html]
 [test_contacts_substringmatchingVE.html]
 [test_contacts_substringmatchingCL.html]
 [test_migration.html]
-  support-files =
-    test_migration_chrome.js
+  support-files +=
   skip-if = os == "android"
 [test_permission_denied.html]
--- a/dom/contacts/tests/shared.js
+++ b/dom/contacts/tests/shared.js
@@ -489,21 +489,15 @@ function next() {
 SimpleTest.waitForExplicitFinish();
 
 function start_tests() {
   // Skip tests on Android < 4.0 due to test failures on tbpl (see bugs 897924 & 888891)
   let androidVersion = SpecialPowers.Cc['@mozilla.org/system-info;1']
                                     .getService(SpecialPowers.Ci.nsIPropertyBag2)
                                     .getProperty('version');
   if (!isAndroid || androidVersion >= 14) {
-    SpecialPowers.pushPermissions([
-      {type: "contacts-write", allow: 1, context: document},
-      {type: "contacts-read", allow: 1, context: document},
-      {type: "contacts-create", allow: 1, context: document},
-    ], function() {
-      mozContacts = navigator.mozContacts;
-      next();
-    });
+    mozContacts = navigator.mozContacts;
+    next();
   } else {
     ok(true, "Skip tests on Android < 4.0 (bugs 897924 & 888891");
-    SimpleTest.finish();
+    parent.SimpleTest.finish();
   }
 }
--- a/dom/contacts/tests/test_contacts_basics.html
+++ b/dom/contacts/tests/test_contacts_basics.html
@@ -1,784 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=674720
--->
 <head>
-  <title>Test for Bug 674720 WebContacts</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=674720">Mozilla Bug 674720</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
-<script class="testbody" type="text/javascript">
-"use strict";
-
-var initialRev;
+<script type="application/javascript">
 
-function checkRevision(revision, msg, then) {
-  var revReq = mozContacts.getRevision();
-  revReq.onsuccess = function(e) {
-    is(e.target.result, initialRev+revision, msg);
-    then();
-  };
-  // The revision function isn't supported on Android so treat on failure as success
-  if (isAndroid) {
-    revReq.onerror = function(e) {
-      then();
-    };
-  } else {
-    revReq.onerror = onFailure;
-  }
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_contacts_basics.html";
 }
 
-var req;
-
-var steps = [
-  function() {
-    req = mozContacts.getRevision();
-    req.onsuccess = function(e) {
-      initialRev = e.target.result;
-      next();
-    };
-
-    // Android does not support the revision function. Treat errors as success.
-    if (isAndroid) {
-      req.onerror = function(e) {
-        initialRev = 0;
-        next();
-      };
-    } else {
-      req.onerror = onFailure;
-    }
-  },
-  function () {
-    ok(true, "Deleting database");
-    checkRevision(0, "Initial revision is 0", function() {
-      req = mozContacts.clear();
-      req.onsuccess = function () {
-        ok(true, "Deleted the database");
-        checkCount(0, "No contacts after clear", function() {
-          checkRevision(1, "Revision was incremented on clear", next);
-        });
-      };
-      req.onerror = onFailure;
-    });
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find(defaultOptions);
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Empty database.");
-      checkRevision(1, "Revision was not incremented on find", next);
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding empty contact");
-    createResult1 = new mozContact({});
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      checkCount(1, "1 contact after adding empty contact", function() {
-        checkRevision(2, "Revision was incremented on save", next);
-      });
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find(defaultOptions);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "One contact.");
-      findResult1 = req.result[0];
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting empty contact");
-    req = navigator.mozContacts.remove(findResult1);
-    req.onsuccess = function () {
-      var req2 = mozContacts.find(defaultOptions);
-      req2.onsuccess = function () {
-        is(req2.result.length, 0, "Empty Database.");
-        clearTemps();
-        checkRevision(3, "Revision was incremented on remove", next);
-      }
-      req2.onerror = onFailure;
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a new contact1");
-    createResult1 = new mozContact(properties1);
-
-    mozContacts.oncontactchange = function(event) {
-      is(event.contactID, createResult1.id, "Same contactID");
-      is(event.reason, "create", "Same reason");
-      next();
-    }
-
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      checkContacts(createResult1, properties1);
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 1");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[1].substring(0,3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      // Some manual testing. Testint the testfunctions
-      // tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+55 (31) 9876-3456"}],
-      is(findResult1.tel[0].carrier, "testCarrier", "Same Carrier");
-      is(String(findResult1.tel[0].type), "work", "Same type");
-      is(findResult1.tel[0].value, "123456", "Same Value");
-      is(findResult1.tel[1].type[1], "fax", "Same type");
-      is(findResult1.tel[1].value, "+55 (31) 9876-3456", "Same Value");
-
-      is(findResult1.adr[0].countryName, "country 1", "Same country");
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
+};
 
-      // email: [{type: ["work"], value: "x@y.com"}]
-      is(String(findResult1.email[0].type), "work", "Same Type");
-      is(findResult1.email[0].value, "x@y.com", "Same Value");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for exact email");
-    var options = {filterBy: ["email"],
-                   filterOp: "equals",
-                   filterValue: properties1.email[0].value};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(findResult1, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring and update");
-    mozContacts.oncontactchange = function(event) {
-       is(event.contactID, findResult1.id, "Same contactID");
-       is(event.reason, "update", "Same reason");
-     }
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[0].substring(0,3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      findResult1.jobTitle = ["new Job"];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a new contact");
-    mozContacts.oncontactchange = function(event) {
-       is(event.contactID, createResult2.id, "Same contactID");
-       is(event.reason, "create", "Same reason");
-     }
-    createResult2 = new mozContact({name: ["newName"]});
-    req = navigator.mozContacts.save(createResult2);
-    req.onsuccess = function () {
-      ok(createResult2.id, "The contact now has an ID.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 2");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[0].substring(0,3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      checkContacts(createResult1, findResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function() {
-    ok(true, "Retrieving by name equality 1");
-    var options = {filterBy: ["name"],
-                   filterOp: "equals",
-                   filterValue: properties1.name[0]};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      checkContacts(createResult1, findResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function() {
-    ok(true, "Retrieving by name equality 2");
-    var options = {filterBy: ["name"],
-                   filterOp: "equals",
-                   filterValue: properties1.name[1]};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      checkContacts(createResult1, findResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function() {
-    ok(true, "Retrieving by name substring 1");
-    var options = {filterBy: ["name"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.name[0].substring(0,3).toLowerCase()};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      checkContacts(createResult1, findResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function() {
-    ok(true, "Retrieving by name substring 2");
-    var options = {filterBy: ["name"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.name[1].substring(0,3).toLowerCase()};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      checkContacts(createResult1, findResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Remove contact1");
-    mozContacts.oncontactchange = function(event) {
-      is(event.contactID, createResult1.id, "Same contactID");
-      is(event.reason, "remove", "Same reason");
-    }
-    req = navigator.mozContacts.remove(createResult1);
-    req.onsuccess = function () {
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 3");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[1].substring(0,3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Found no contact.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Remove contact2");
-    mozContacts.oncontactchange = function(event) {
-      is(event.contactID, createResult2.id, "Same contactID");
-      is(event.reason, "remove", "Same reason");
-    }
-    req = navigator.mozContacts.remove(createResult2);
-    req.onsuccess = function () {
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 4");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[1].substring(0,3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Found no contact.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    mozContacts.oncontactchange = function(event) {
-      is(event.contactID, "undefined", "Same contactID");
-      is(event.reason, "remove", "Same reason");
-    }
-    req = mozContacts.clear();
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a new contact with properties1");
-    createResult1 = new mozContact(properties1);
-    mozContacts.oncontactchange = null;
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring tel1");
-    var options = {filterBy: ["tel"],
-                   filterOp: "contains",
-                   filterValue: properties1.tel[1].value.substring(2,5)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by tel exact");
-    var options = {filterBy: ["tel"],
-                   filterOp: "equals",
-                   filterValue: "+55 319 8 7 6 3456"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by tel exact with substring");
-    var options = {filterBy: ["tel"],
-                   filterOp: "equals",
-                   filterValue: "3456"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Found no contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by tel exact with substring");
-    var options = {filterBy: ["tel"],
-                   filterOp: "equals",
-                   filterValue: "+55 (31)"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Found no contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by tel match national number");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: "3198763456"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by tel match national format");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: "0451 491934"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by tel match entered number");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: "123456"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by tel match international number");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: "+55 31 98763456"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by match with field other than tel");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "match",
-                   filterValue: "my friends call me 555-4040"};
-    req = mozContacts.find(options);
-    req.onsuccess = onUnwantedSuccess;
-    req.onerror = function() {
-      ok(true, "Failed");
-      next();
-    }
-  },
-  function () {
-    ok(true, "Retrieving by substring tel2");
-    var options = {filterBy: ["tel"],
-                   filterOp: "startsWith",
-                   filterValue: "9876"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring tel3");
-    var options = {filterBy: ["tel"],
-                   filterOp: "startsWith",
-                   filterValue: "98763456"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 5");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[0].substring(0,3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 6");
-    var options = {filterBy: ["familyName", "givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[0].substring(0,3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring3, Testing multi entry");
-    var options = {filterBy: ["givenName", "familyName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.familyName[1].substring(0,3).toLowerCase()};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find(defaultOptions);
-    req.onsuccess = function() {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(createResult1, findResult1);
-      if (!isAndroid) {
-        ok(findResult1.updated, "Has updated field");
-        ok(findResult1.published, "Has published field");
-      }
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Modifying contact1");
-    if (!findResult1) {
-      SpecialPowers.executeSoon(next);
-    } else {
-      findResult1.impp = properties1.impp = [{value:"phil impp"}];
-      req = navigator.mozContacts.save(findResult1);
-      req.onsuccess = function () {
-        var req2 = mozContacts.find(defaultOptions);
-        req2.onsuccess = function() {
-          is(req2.result.length, 1, "Found exactly 1 contact.");
-          findResult2 = req2.result[0];
-          ok(findResult2.id == sample_id1, "Same ID");
-          checkContacts(findResult2, properties1);
-          is(findResult2.impp.length, 1, "Found exactly 1 IMS info.");
-          next();
-        };
-        req2.onerror = onFailure;
-      };
-      req.onerror = onFailure;
-    }
-  },
-  function() {
-    // Android does not support published/updated fields. Skip this.
-    if (isAndroid) {
-      next();
-      return;
-    }
-
-    ok(true, "Saving old contact, should abort!");
-    req = mozContacts.save(createResult1);
-    req.onsuccess = onUnwantedSuccess;
-    req.onerror   = function() { ok(true, "Successfully declined updating old contact!"); next(); };
-  },
-  function () {
-    ok(true, "Retrieving a specific contact by ID");
-    var options = {filterBy: ["id"],
-                   filterOp: "equals",
-                   filterValue: sample_id1};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(findResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving a specific contact by givenName");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "equals",
-                   filterValue: properties1.givenName[0]};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(findResult1, properties1);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Modifying contact2");
-    if (!findResult1) {
-      SpecialPowers.executeSoon(next);
-    } else {
-      findResult1.impp = properties1.impp = [{value: "phil impp"}];
-      req = mozContacts.save(findResult1);
-      req.onsuccess = function () {
-        var req2 = mozContacts.find(defaultOptions);
-        req2.onsuccess = function () {
-          is(req2.result.length, 1, "Found exactly 1 contact.");
-          findResult1 = req2.result[0];
-          ok(findResult1.id == sample_id1, "Same ID");
-          checkContacts(findResult1, properties1);
-          is(findResult1.impp.length, 1, "Found exactly 1 IMS info.");
-          next();
-        }
-        req2.onerror = onFailure;
-      };
-      req.onerror = onFailure;
-    }
-  },
-  function () {
-    ok(true, "Searching contacts by query");
-    var options = {filterBy: ["givenName", "email"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[0].substring(0,4)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(findResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching contacts by query");
-    var options = {filterBy: ["givenName", "email"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[0]};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(findResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching contacts with multiple indices");
-    var options = {filterBy: ["email", "givenName"],
-                   filterOp: "equals",
-                   filterValue: properties1.givenName[1]};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(findResult1, properties1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Modifying contact3");
-    if (!findResult1) {
-      SpecialPowers.executeSoon(next);
-    } else {
-      findResult1.email = [{value: properties1.nickname}];
-      findResult1.nickname = ["TEST"];
-      var newContact = new mozContact(findResult1);
-      req = mozContacts.save(newContact);
-      req.onsuccess = function () {
-        var options = {filterBy: ["email", "givenName"],
-                       filterOp: "startsWith",
-                       filterValue: properties1.givenName[0]};
-        // One contact has it in nickname and the other in email
-        var req2 = mozContacts.find(options);
-        req2.onsuccess = function () {
-          is(req2.result.length, 2, "Found exactly 2 contacts.");
-          ok(req2.result[0].id != req2.result[1].id, "Different ID");
-          next();
-        }
-        req2.onerror = onFailure;
-      };
-      req.onerror = onFailure;
-    }
-  },
-  function () {
-    ok(true, "Deleting contact" + findResult1);
-    req = mozContacts.remove(findResult1);
-    req.onsuccess = function () {
-      var req2 = mozContacts.find(defaultOptions);
-      req2.onsuccess = function () {
-        is(req2.result.length, 1, "One contact left.");
-        findResult1 = req2.result[0];
-        next();
-      }
-      req2.onerror = onFailure;
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.remove(findResult1);
-    req.onsuccess =  function () {
-      clearTemps();
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function() {
-    ok(true, "Test JSON.stringify output for mozContact objects");
-    var json = JSON.parse(JSON.stringify(new mozContact(properties1)));
-    checkContacts(json, properties1);
-    next();
-  },
-  function() {
-    ok(true, "Test slice");
-    var c = new mozContact();
-    c.email = [{ type: ["foo"], value: "bar@baz" }]
-    var arr = c.email;
-    is(arr[0].value, "bar@baz", "Should have the right value");
-    arr = arr.slice();
-    is(arr[0].value, "bar@baz", "Should have the right value after slicing");
-    next();
-  },
-  function () {
-    ok(true, "all done!\n");
-    clearTemps();
-
-    SimpleTest.finish();
-  }
-];
-
-start_tests();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/contacts/tests/test_contacts_basics2.html
+++ b/dom/contacts/tests/test_contacts_basics2.html
@@ -1,1149 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=674720
--->
 <head>
-  <title>Test for Bug 674720 WebContacts</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=674720">Mozilla Bug 674720</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
-<script class="testbody" type="text/javascript">
-"use strict";
-
-var req;
-
-var steps = [
-  function () {
-    ok(true, "Adding a new contact");
-    createResult1 = new mozContact(properties1);
-    req = mozContacts.save(createResult1)
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a new contact2");
-    createResult2 = new mozContact(properties2);
-    req = mozContacts.save(createResult2);
-    req.onsuccess = function () {
-      ok(createResult2.id, "The contact now has an ID.");
-      sample_id2 = createResult2.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find({sortBy: "familyName"});
-    req.onsuccess = function () {
-      is(req.result.length, 2, "Found exactly 2 contact.");
-      checkContacts(req.result[1], properties1);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    console.log("Searching contacts by query1");
-    var options = {filterBy: ["givenName", "email"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[0].substring(0, 4)}
-    req = mozContacts.find(options)
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(findResult1, createResult1);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching contacts by query2");
-    var options = {filterBy: ["givenName", "email"],
-                   filterOp: "startsWith",
-                   filterValue: properties2.givenName[0].substring(0, 4)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      is(findResult1.adr.length, 2, "Adr length 2");
-      checkContacts(findResult1, createResult2);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching contacts by tel");
-    var options = {filterBy: ["tel"],
-                   filterOp: "contains",
-                   filterValue: properties2.tel[0].value.substring(3, 7)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id2, "Same ID");
-      checkContacts(findResult1, createResult2);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching contacts by email");
-    var options = {filterBy: ["email"],
-                   filterOp: "startsWith",
-                   filterValue: properties2.email[0].value.substring(0, 4)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id2, "Same ID");
-      checkContacts(findResult1, createResult2);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear();
-    req.onsuccess = function () {
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding 20 contacts");
-    for (var i=0; i<19; i++) {
-      createResult1 = new mozContact(properties1);
-      req = mozContacts.save(createResult1);
-      req.onsuccess = function () {
-        ok(createResult1.id, "The contact now has an ID.");
-      };
-      req.onerror = onFailure;
-    };
-    createResult1 = new mozContact(properties1);
-    req = mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkStrArray(createResult1.name, properties1.name, "Same Name");
-      checkCount(20, "20 contacts in DB", next);
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find(defaultOptions);
-    req.onsuccess = function () {
-      is(req.result.length, 20, "20 Entries.");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts with limit 10");
-    var options = { filterLimit: 10 };
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 10, "10 Entries.");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts with limit 10 and sorted");
-    var options = { filterLimit: 10,
-                    sortBy: 'FamilyName',
-                    sortOrder: 'descending' };
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 10, "10 Entries.");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts2");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[0].substring(0, 4)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 20, "20 Entries.");
-      checkContacts(createResult1, req.result[19]);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts3");
-    var options = {filterBy: ["givenName", "tel", "email"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[0].substring(0, 4)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 20, "20 Entries.");
-      checkContacts(createResult1, req.result[10]);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear();
-    req.onsuccess = function () {
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Testing clone contact");
-    createResult1 = new mozContact(properties1);
-    req = mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkStrArray(createResult1.name, properties1.name, "Same Name");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function() {
-    ok(true, "Testing clone contact2");
-    var cloned = new mozContact(createResult1);
-    ok(cloned.id != createResult1.id, "Cloned contact has new ID");
-    cloned.email = [{value: "new email!"}];
-    cloned.givenName = ["Tom"];
-    req = mozContacts.save(cloned);
-    req.onsuccess = function () {
-      ok(cloned.id, "The contact now has an ID.");
-      is(cloned.email[0].value, "new email!", "Same Email");
-      isnot(createResult1.email[0].value, cloned.email[0].value, "Clone has different email");
-      is(String(cloned.givenName), "Tom", "New Name");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties2.givenName[0].substring(0, 4)};
-    req = mozContacts.find(defaultOptions);
-    req.onsuccess = function () {
-      is(req.result.length, 2, "2 Entries.");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Search with redundant fields should only return 1 contact");
-    createResult1 = new mozContact({name: ["XXX"],
-                                    givenName: ["XXX"],
-                                    email: [{value: "XXX"}],
-                                    tel: [{value: "XXX"}]
-                                   });
-    req = mozContacts.save(createResult1);
-    req.onsuccess = function() {
-      var options = {filterBy: ["givenName", "familyName"],
-                     filterOp: "equals",
-                     filterValue: "XXX"};
-      var req2 = mozContacts.find(options);
-      req2.onsuccess = function() {
-        is(req2.result.length, 1, "1 Entry");
-        next();
-      }
-      req2.onerror = onFailure;
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c3);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c3, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c2);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c2, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c4);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c4, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c1);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c1, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    var options = {sortBy: "familyName",
-                   sortOrder: "ascending"};
-    req = navigator.mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 4, "4 results");
-      checkContacts(req.result[0], c1);
-      checkContacts(req.result[1], c2);
-      checkContacts(req.result[2], c3);
-      checkContacts(req.result[3], c4);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    var options = {sortBy: "familyName",
-                   sortOrder: "descending"};
-    req = navigator.mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 4, "4 results");
-      checkContacts(req.result[0], c4);
-      checkContacts(req.result[1], c3);
-      checkContacts(req.result[2], c2);
-      checkContacts(req.result[3], c1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c5);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c5, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting with empty string");
-    var options = {sortBy: "familyName",
-                   sortOrder: "ascending"};
-    req = navigator.mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 5, "5 results");
-      checkContacts(req.result[0], c5);
-      checkContacts(req.result[1], c1);
-      checkContacts(req.result[2], c2);
-      checkContacts(req.result[3], c3);
-      checkContacts(req.result[4], c4);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Don't allow to add custom fields");
-    createResult1 = new mozContact({givenName: ["customTest"], yyy: "XXX"});
-    req = mozContacts.save(createResult1);
-    req.onsuccess = function() {
-      var options = {filterBy: ["givenName"],
-                     filterOp: "equals",
-                     filterValue: "customTest"};
-      var req2 = mozContacts.find(options);
-      req2.onsuccess = function() {
-        is(req2.result.length, 1, "1 Entry");
-        checkStrArray(req2.result[0].givenName, ["customTest"], "same name");
-        ok(req2.result.yyy === undefined, "custom property undefined");
-        next();
-      }
-      req2.onerror = onFailure;
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c7);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c7, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c6);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c6, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c8);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c8, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    // Android does not support published/updated fields. Skip this.
-    if (isAndroid) {
-      next();
-      return;
-    }
+<script type="application/javascript">
 
-    ok(true, "Test sorting with published");
-    var options = {sortBy: "familyName",
-                   sortOrder: "descending"};
-    req = navigator.mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 3, "3 results");
-      ok(req.result[0].published < req.result[1].published, "Right sorting order");
-      ok(req.result[1].published < req.result[2].published, "Right sorting order");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear();
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a new contact with properties2");
-    createResult2 = new mozContact(properties2);
-    req = mozContacts.save(createResult2);
-    req.onsuccess = function () {
-      ok(createResult2.id, "The contact now has an ID.");
-      sample_id2 = createResult2.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test category search with startsWith");
-    var options = {filterBy: ["category"],
-                   filterOp: "startsWith",
-                   filterValue: properties2.category[0]};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "1 Entry.");
-      checkContacts(req.result[0], createResult2);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test category search with equals");
-    var options = {filterBy: ["category"],
-                   filterOp: "equals",
-                   filterValue: properties2.category[0]};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "1 Entry.");
-      checkContacts(req.result[0], createResult2);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact for category search");
-    createResult1 = new mozContact({name: ["5"], givenName: ["5"]});
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test category search with equals");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: "5"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "1 Entry.");
-      checkContacts(req.result[0], createResult1);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact with invalid data");
-    var obj = {
-        honorificPrefix: [],
-        honorificSuffix: [{foo: "bar"}],
-        sex: 17,
-        genderIdentity: 18,
-        email: [{type: ["foo"], value: "bar"}]
-    };
-    obj.honorificPrefix.__defineGetter__('0',(function() {
-      var c = 0;
-      return function() {
-        if (c == 0) {
-          c++;
-          return "string";
-        } else {
-          return {foo:"bar"};
-        }
-      }
-    })());
-    createResult1 = new mozContact(obj);
-    createResult1.email.push({aeiou: "abcde"});
-    req = mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      checkContacts(createResult1, {
-        honorificPrefix: ["string"],
-        honorificSuffix: ["[object Object]"],
-        sex: "17",
-        genderIdentity: "18",
-        email: [{type: ["foo"], value: "bar"}, {}]
-      });
-      next();
-    };
-  },
-  function () {
-    ok(true, "Adding contact with no number but carrier");
-    createResult1 = new mozContact({ tel: [{type: ["home"], carrier: "myCarrier"} ] });
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact with email but no value");
-    createResult1 = new mozContact({ email: [{type: ["home"]}] });
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Testing numbersOnly search 1");
-    createResult1 = new mozContact({ name: ["aaaaaaaaa"], givenName: ["aaaaaaaaa"], tel: [{ value: "1234567890"}]});
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test numbersOnly search 2");
-    var options = {filterBy: ["givenName", "tel"],
-                   filterOp: "contains",
-                   filterValue: "a"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      ok(req.result.length == 1, "1 Entry.");
-      checkContacts(req.result[0], createResult1);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test numbersOnly search 3");
-    var options = {filterBy: ["givenName", "tel"],
-                   filterOp: "contains",
-                   filterValue: "b"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      ok(req.result.length == 0, "0 Entries.");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test numbersOnly search 4");
-    var options = {filterBy: ["givenName", "tel"],
-                   filterOp: "contains",
-                   filterValue: "1a"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      ok(req.result.length == 0, "0 Entries.");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test numbersOnly search 5");
-    var options = {filterBy: ["givenName", "tel"],
-                   filterOp: "contains",
-                   filterValue: "1(23)"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      ok(req.result.length == 1, "1 Entry.");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test numbersOnly search 6");
-    var options = {filterBy: ["givenName", "tel"],
-                   filterOp: "contains",
-                   filterValue: "1(23)a"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      ok(req.result.length == 0, "0 Entries.");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function() {
-    ok(true, "Test that after setting array properties to scalar values the property os not a non-array")
-    const FIELDS = ["email","url","adr","tel","impp"];
-    createResult1 = new mozContact();
-    for (var prop of FIELDS) {
-      try {
-        createResult1[prop] = {type: ["foo"]};
-      } catch (e) {}
-      ok(createResult1[prop] === null ||
-         Array.isArray(createResult1[prop]), prop + " is array");
-    }
-    next();
-  },
-  function() {
-    ok(true, "Undefined properties of fields should be treated correctly");
-    var c = new mozContact({
-      adr: [{streetAddress: undefined}],
-      email: [{value: undefined}],
-      url: [{value: undefined}],
-      impp: [{value: undefined}],
-      tel: [{value: undefined}],
-    });
-    is(c.adr[0].streetAddress, undefined, "adr.streetAddress is undefined");
-    is(c.adr[0].locality, undefined, "adr.locality is undefined");
-    is(c.adr[0].pref, undefined, "adr.pref is undefined");
-    is(c.email[0].value, undefined, "email.value is undefined");
-    is(c.url[0].value, undefined, "url.value is undefined");
-    is(c.impp[0].value, undefined, "impp.value is undefined");
-    is(c.tel[0].value, undefined, "tel.value is undefined");
-    next();
-  },
-  function() {
-    ok(true, "Setting array properties to an empty array should work");
-    var c = new mozContact();
-    function testArrayProp(prop) {
-      is(c[prop], null, "property is initially null");
-      c[prop] = [];
-      ok(Array.isArray(c[prop]), "property is an array after setting");
-      is(c[prop].length, 0, "property has length 0 after setting");
-    }
-    testArrayProp("email");
-    testArrayProp("adr");
-    testArrayProp("tel");
-    testArrayProp("impp");
-    testArrayProp("url");
-    next();
-  },
-  function() {
-    ok(true, "Passing a mozContact with invalid data to save() should throw");
-    var c = new mozContact({
-      photo: [],
-      tel: []
-    });
-    c.photo.push({});
-    SimpleTest.doesThrow(()=>navigator.mozContacts.save(c), "Invalid data in Blob array");
-    c.tel.push(123);
-    SimpleTest.doesThrow(()=>navigator.mozContacts.save(c), "Invalid data in dictionary array");
-    next();
-  },
-  function() {
-    ok(true, "Inline changes to array properties should be seen by save");
-    var c = new mozContact({
-      name: [],
-      familyName: [],
-      givenName: [],
-      phoneticFamilyName: [],
-      phoneticGivenName: [],
-      nickname: [],
-      tel: [],
-      adr: [],
-      email: []
-    });
-    for (var prop of Object.getOwnPropertyNames(properties1)) {
-      if (!Array.isArray(properties1[prop])) {
-        continue;
-      }
-      for (var i = 0; i < properties1[prop].length; ++i) {
-        c[prop].push(properties1[prop][i]);
-      }
-    }
-    req = navigator.mozContacts.save(c);
-    req.onsuccess = function() {
-      req = navigator.mozContacts.find(defaultOptions);
-      req.onsuccess = function() {
-        is(req.result.length, 1, "Got 1 contact");
-        checkContacts(req.result[0], properties1);
-        next();
-      };
-      req.onerror = onFailure;
-    };
-    req.onerror = onFailure;
-  },
-  clearDatabase,
-  function() {
-    ok(true, "mozContact.init deprecation message");
-    var c = new mozContact();
-    SimpleTest.monitorConsole(next, [
-      { errorMessage: "mozContact.init is DEPRECATED. Use the mozContact constructor instead. " +
-                      "See https://developer.mozilla.org/docs/WebAPI/Contacts for details." }
-    ], /* forbidUnexpectedMsgs */ true);
-    c.init({name: ["Bar"]});
-    c.init({name: ["Bar"]});
-    SimpleTest.endMonitorConsole();
-  },
-  function() {
-    ok(true, "mozContact.init works as expected");
-    var c = new mozContact({name: ["Foo"]});
-    c.init({name: ["Bar"]});
-    is(c.name[0], "Bar", "Same name");
-    next();
-  },
-  function() {
-    ok(true, "mozContact.init without parameters");
-    var c = new mozContact({name: ["Foo"]});
-    c.init();
-    next();
-  },
-  function() {
-    ok(true, "mozContact.init resets properties");
-    var c = new mozContact({jobTitle: ["Software Engineer"]});
-    c.init({nickname: ["Jobless Johnny"]});
-    is(c.nickname[0], "Jobless Johnny", "Same nickname");
-    ok(!c.jobTitle, "jobTitle is not set");
-    next();
-  },
-  function() {
-    ok(true, "mozContacts.remove with an ID works");
-    var c = new mozContact({name: ["Ephemeral Jimmy"]});
-    req = navigator.mozContacts.save(c);
-    req.onsuccess = function() {
-      req = navigator.mozContacts.remove(c.id);
-      req.onsuccess = function() {
-        req = navigator.mozContacts.find({
-          filterBy: ["id"],
-          filterOp: "equals",
-          filterValue: c.id
-        });
-        req.onsuccess = function() {
-          is(req.result.length, 0, "Successfully removed contact by ID");
-          next();
-        };
-        req.onerror = onFailure;
-      };
-      req.onerror = onFailure;
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a new contact");
-    createResult1 = new mozContact(properties3);
-    req = mozContacts.save(createResult1)
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a new contact2");
-    createResult2 = new mozContact(properties4);
-    req = mozContacts.save(createResult2);
-    req.onsuccess = function () {
-      ok(createResult2.id, "The contact now has an ID.");
-      sample_id2 = createResult2.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find({sortBy: "phoneticFamilyName"});
-    req.onsuccess = function () {
-      is(req.result.length, 2, "Found exactly 2 contact.");
-      checkContacts(req.result[1], properties3);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching contacts by query1");
-    var options = {filterBy: ["phoneticGivenName", "email"],
-                   filterOp: "startsWith",
-                   filterValue: properties3.phoneticGivenName[0].substring(0, 3)}
-    req = mozContacts.find(options)
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      checkContacts(findResult1, createResult1);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching contacts by query2");
-    var options = {filterBy: ["phoneticGivenName", "email"],
-                   filterOp: "startsWith",
-                   filterValue: properties4.phoneticGivenName[0].substring(0, 3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      is(findResult1.adr.length, 2, "Adr length 2");
-      checkContacts(findResult1, createResult2);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  clearDatabase,
-  function () {
-    ok(true, "Adding 20 contacts");
-    for (var i=0; i<19; i++) {
-      createResult1 = new mozContact(properties3);
-      req = mozContacts.save(createResult1);
-      req.onsuccess = function () {
-        ok(createResult1.id, "The contact now has an ID.");
-      };
-      req.onerror = onFailure;
-    };
-    createResult1 = new mozContact(properties3);
-    req = mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkStrArray(createResult1.name, properties3.name, "Same Name");
-      checkCount(20, "20 contacts in DB", next);
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find(defaultOptions);
-    req.onsuccess = function () {
-      is(req.result.length, 20, "20 Entries.");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts2");
-    var options = {filterBy: ["phoneticGivenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties3.phoneticGivenName[0].substring(0, 3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 20, "20 Entries.");
-      checkContacts(createResult1, req.result[19]);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts3");
-    var options = {filterBy: ["phoneticGivenName", "tel", "email"],
-                   filterOp: "startsWith",
-                   filterValue: properties3.phoneticGivenName[0].substring(0, 3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 20, "20 Entries.");
-      checkContacts(createResult1, req.result[10]);
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  clearDatabase,
-  function () {
-    ok(true, "Testing clone contact");
-    createResult1 = new mozContact(properties3);
-    req = mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkStrArray(createResult1.phoneticFamilyName, properties3.phoneticFamilyName, "Same phoneticFamilyName");
-      checkStrArray(createResult1.phoneticGivenName, properties3.phoneticGivenName, "Same phoneticGivenName");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find({sortBy: "phoneticGivenName"});
-    req.onsuccess = function () {
-      is(req.result.length, 1, "1 Entries.");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  clearDatabase,
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c11);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c11, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c10);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c10, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c12);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c12, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c9);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c9, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    var options = {sortBy: "phoneticFamilyName",
-                   sortOrder: "ascending"};
-    req = navigator.mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 4, "4 results");
-      checkContacts(req.result[0], c9);
-      checkContacts(req.result[1], c10);
-      checkContacts(req.result[2], c11);
-      checkContacts(req.result[3], c12);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    var options = {sortBy: "phoneticFamilyName",
-                   sortOrder: "descending"};
-    req = navigator.mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 4, "4 results");
-      checkContacts(req.result[0], c12);
-      checkContacts(req.result[1], c11);
-      checkContacts(req.result[2], c10);
-      checkContacts(req.result[3], c9);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c13);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c13, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting with empty string");
-    var options = {sortBy: "phoneticFamilyName",
-                   sortOrder: "ascending"};
-    req = navigator.mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 5, "5 results");
-      checkContacts(req.result[0], c13);
-      checkContacts(req.result[1], c9);
-      checkContacts(req.result[2], c10);
-      checkContacts(req.result[3], c11);
-      checkContacts(req.result[4], c12);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  clearDatabase,
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c15);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c15, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c14);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c14, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Test sorting");
-    createResult1 = new mozContact(c16);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      checkContacts(c16, createResult1);
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    // Android does not support published/updated fields. Skip this.
-    if (isAndroid) {
-      next();
-      return;
-    }
-
-    ok(true, "Test sorting with published");
-    var options = {sortBy: "phoneticFamilyName",
-                   sortOrder: "descending"};
-    req = navigator.mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 3, "3 results");
-      ok(req.result[0].published < req.result[1].published, "Right sorting order");
-      ok(req.result[1].published < req.result[2].published, "Right sorting order");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  clearDatabase,
-  function () {
-    ok(true, "all done!\n");
-    SimpleTest.finish();
-  }
-];
-
-function next() {
-  ok(true, "Begin!");
-  if (index >= steps.length) {
-    ok(false, "Shouldn't get here!");
-    return;
-  }
-  try {
-    var i = index++;
-    steps[i]();
-  } catch(ex) {
-    ok(false, "Caught exception", ex);
-  }
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_contacts_basics2.html";
 }
 
-start_tests();
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
+};
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/contacts/tests/test_contacts_blobs.html
+++ b/dom/contacts/tests/test_contacts_blobs.html
@@ -1,222 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=674720
--->
 <head>
-  <title>Test for Bug 674720 WebContacts</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=674720">Mozilla Bug 674720</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
-<script class="testbody" type="text/javascript">
-"use strict";
-
-var utils = SpecialPowers.getDOMWindowUtils(window);
+<script type="application/javascript">
 
-function getView(size)
-{
- var buffer = new ArrayBuffer(size);
- var view = new Uint8Array(buffer);
- is(buffer.byteLength, size, "Correct byte length");
- return view;
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_contacts_blobs.html";
 }
 
-function getRandomView(size)
-{
- var view = getView(size);
- for (var i = 0; i < size; i++) {
-   view[i] = parseInt(Math.random() * 255)
- }
- return view;
-}
-
-function getRandomBlob(size)
-{
-  return new Blob([getRandomView(size)], { type: "binary/random" });
-}
-
-function compareBuffers(buffer1, buffer2)
-{
-  if (buffer1.byteLength != buffer2.byteLength) {
-    return false;
-  }
-  var view1 = new Uint8Array(buffer1);
-  var view2 = new Uint8Array(buffer2);
-  for (var i = 0; i < buffer1.byteLength; i++) {
-    if (view1[i] != view2[i]) {
-      return false;
-    }
-  }
-  return true;
-}
-
-function verifyBuffers(buffer1, buffer2, isLast)
-{
-  ok(compareBuffers(buffer1, buffer2), "Correct blob data");
-  if (isLast)
-    next();
-}
-
-var randomBlob = getRandomBlob(1024);
-var randomBlob2 = getRandomBlob(1024);
-
-var properties1 = {
-  name: ["xTestname1"],
-  givenName: ["xTestname1"],
-  photo: [randomBlob]
-};
-
-var properties2 = {
-  name: ["yTestname2"],
-  givenName: ["yTestname2"],
-  photo: [randomBlob, randomBlob2]
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
 };
 
-var sample_id1;
-var createResult1;
-var findResult1;
-
-function verifyBlob(blob1, blob2, isLast)
-{
-  is(blob1 instanceof Blob, true,
-     "blob1 is an instance of DOMBlob");
-  is(blob2 instanceof Blob, true,
-     "blob2 is an instance of DOMBlob");
-  isnot(blob1 instanceof File, true,
-     "blob1 is an instance of File");
-  isnot(blob2 instanceof File, true,
-     "blob2 is an instance of File");
-  is(blob1.size, blob2.size, "Same size");
-  is(blob1.type, blob2.type, "Same type");
-
-  var buffer1;
-  var buffer2;
-
-  var reader1 = new FileReader();
-  reader1.readAsArrayBuffer(blob2);
-  reader1.onload = function(event) {
-    buffer2 = event.target.result;
-    if (buffer1) {
-      verifyBuffers(buffer1, buffer2, isLast);
-    }
-  }
-
-  var reader2 = new FileReader();
-  reader2.readAsArrayBuffer(blob1);
-  reader2.onload = function(event) {
-    buffer1 = event.target.result;
-    if (buffer2) {
-      verifyBuffers(buffer1, buffer2, isLast);
-    }
-  }
-}
-
-function verifyBlobArray(blobs1, blobs2)
-{
-  is(blobs1 instanceof Array, true, "blobs1 is an array object");
-  is(blobs2 instanceof Array, true, "blobs2 is an array object");
-  is(blobs1.length, blobs2.length, "Same length");
-
-  if (!blobs1.length) {
-    next();
-    return;
-  }
-
-  for (var i = 0; i < blobs1.length; i++) {
-    verifyBlob(blobs1[i], blobs2[i], i == blobs1.length - 1);
-  }
-}
-
-var req;
-
-var steps = [
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear();
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact with photo");
-    createResult1 = new mozContact(properties1);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName[0].substring(0,3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      ok(req.result.length == 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      verifyBlobArray(findResult1.photo, properties1.photo);
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact with 2 photos");
-    createResult1 = new mozContact(properties2);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties2.givenName[0].substring(0,3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      ok(req.result.length == 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      verifyBlobArray(findResult1.photo, properties2.photo);
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "all done!\n");
-
-    SimpleTest.finish();
-  }
-];
-
-start_tests();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/contacts/tests/test_contacts_events.html
+++ b/dom/contacts/tests/test_contacts_events.html
@@ -1,41 +1,29 @@
 <!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=764667
--->
 <head>
-  <title>Test for Bug 678695</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=764667">Mozilla Bug 764667</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-  
-</div>
+<iframe></iframe>
 <pre id="test">
 <script type="application/javascript">
 
-/** Test for Bug 764667 **/
-
-SpecialPowers.addPermission("contacts-read", true, document);
-
-var e = new MozContactChangeEvent("contactchanged", {contactID: "123", reason: "create"});
-ok(e, "Should have contactsChange event!");
-is(e.contactID, "123", "ID should be 123.");
-is(e.reason, "create", "Reason should be create.");
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_contacts_events.html";
+}
 
-e = new MozContactChangeEvent("contactchanged", {contactID: "test", reason: "test"});
-is(e.contactID, "test", "Name should be 'test'.");
-is(e.reason, "test", "Name should be 'test'.");
-
-e = new MozContactChangeEvent("contactchanged", {contactID: "a", reason: ""});
-is(e.contactID, "a", "Name should be a.");
-is(e.reason, "", "Value should be empty");
-
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
+};
 
 </script>
 </pre>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/dom/contacts/tests/test_contacts_getall.html
+++ b/dom/contacts/tests/test_contacts_getall.html
@@ -1,152 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=836519
--->
 <head>
-  <title>Mozilla Bug 836519</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=836519">Mozilla Bug 836519</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
-<script class="testbody" type="text/javascript;version=1.8">
-"use strict";
-
-let req;
-
-let steps = [
-  function start() {
-    SpecialPowers.Cc["@mozilla.org/tools/profiler;1"].getService(SpecialPowers.Ci.nsIProfiler).AddMarker("GETALL_START");
-    next();
-  },
-  clearDatabase,
-  addContacts,
+<script type="application/javascript">
 
-  function() {
-    ok(true, "Delete the current contact while iterating");
-    req = mozContacts.getAll({});
-    let count = 0;
-    let previousId = null;
-    req.onsuccess = function() {
-      if (req.result) {
-        ok(true, "on success");
-        if (previousId) {
-          isnot(previousId, req.result.id, "different contacts returned");
-        }
-        previousId = req.result.id;
-        count++;
-        let delReq = mozContacts.remove(req.result);
-        delReq.onsuccess = function() {
-          ok(true, "deleted current contact");
-          req.continue();
-        };
-      } else {
-        is(count, 40, "returned 40 contacts");
-        next();
-      }
-    };
-  },
-
-  clearDatabase,
-  addContacts,
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_contacts_getall.html";
+}
 
-  function() {
-    ok(true, "Iterating through the contact list inside a cursor callback");
-    let count1 = 0, count2 = 0;
-    let req1 = mozContacts.getAll({});
-    let req2;
-    req1.onsuccess = function() {
-      if (count1 == 0) {
-        count1++;
-        req2 = mozContacts.getAll({});
-        req2.onsuccess = function() {
-          if (req2.result) {
-            count2++;
-            req2.continue();
-          } else {
-            is(count2, 40, "inner cursor returned 40 contacts");
-            req1.continue();
-          }
-        };
-      } else {
-        if (req1.result) {
-          count1++;
-          req1.continue();
-        } else {
-          is(count1, 40, "outer cursor returned 40 contacts");
-          next();
-        }
-      }
-    };
-  },
-
-  clearDatabase,
-  addContacts,
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
+};
 
-  function() {
-    ok(true, "20 concurrent cursors");
-    const NUM_CURSORS = 20;
-    let completed = 0;
-    for (let i = 0; i < NUM_CURSORS; ++i) {
-      mozContacts.getAll({}).onsuccess = (function(i) {
-        let count = 0;
-        return function(event) {
-          let req = event.target;
-          if (req.result) {
-            count++;
-            req.continue();
-          } else {
-            is(count, 40, "cursor " + i + " returned 40 contacts");
-            if (++completed == NUM_CURSORS) {
-              next();
-            }
-          }
-        };
-      })(i);
-    }
-  },
-
-  clearDatabase,
-  addContacts,
-
-  function() {
-    if (!SpecialPowers.isMainProcess()) {
-      // We stop calling continue() intentionally here to see if the cursor gets
-      // cleaned up properly in the parent.
-      ok(true, "Leaking a cursor");
-      req = mozContacts.getAll({
-        sortBy: "familyName",
-        sortOrder: "ascending"
-      });
-      req.onsuccess = function(event) {
-        next();
-      };
-      req.onerror = onFailure;
-    } else {
-      next();
-    }
-  },
-
-  clearDatabase,
-
-  function() {
-    ok(true, "all done!\n");
-    SpecialPowers.Cc["@mozilla.org/tools/profiler;1"].getService(SpecialPowers.Ci.nsIProfiler).AddMarker("GETALL_END");
-    SimpleTest.finish();
-  }
-];
-
-start_tests();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/contacts/tests/test_contacts_getall2.html
+++ b/dom/contacts/tests/test_contacts_getall2.html
@@ -1,120 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=836519
--->
 <head>
-  <title>Mozilla Bug 836519</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=836519">Mozilla Bug 836519</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
-<script class="testbody" type="text/javascript;version=1.8">
-"use strict";
-let req;
+<script type="application/javascript">
 
-let steps = [
-  clearDatabase,
-  function() {
-    // add a contact
-    createResult1 = new mozContact({});
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function() {
-      next();
-    };
-    req.onerror = onFailure;
-  },
-
-  getOne(),
-  getOne("Retrieving one contact with getAll - cached"),
-
-  clearDatabase,
-  addContacts,
-
-  getAll(),
-  getAll("Retrieving 40 contacts with getAll - cached"),
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_contacts_getall2.html";
+}
 
-  function() {
-    ok(true, "Deleting one contact");
-    req = mozContacts.remove(createResult1);
-    req.onsuccess = function() {
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function() {
-    ok(true, "Test cache invalidation");
-    req = mozContacts.getAll({});
-    let count = 0;
-    req.onsuccess = function(event) {
-      ok(true, "on success");
-      if (req.result) {
-        ok(true, "result is valid");
-        count++;
-        req.continue();
-      } else {
-        is(count, 39, "last contact - 39 contacts returned");
-        next();
-      }
-    };
-    req.onerror = onFailure;
-  },
-
-  clearDatabase,
-  addContacts,
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
+};
 
-  function() {
-    ok(true, "Test cache consistency when deleting contact during getAll");
-    req = mozContacts.find({});
-    req.onsuccess = function(e) {
-      let lastContact = e.target.result[e.target.result.length-1];
-      req = mozContacts.getAll({});
-      let count = 0;
-      let firstResult = true;
-      req.onsuccess = function(event) {
-        ok(true, "on success");
-        if (firstResult) {
-          if (req.result) {
-            count++;
-          }
-          let delReq = mozContacts.remove(lastContact);
-          delReq.onsuccess = function() {
-            firstResult = false;
-            req.continue();
-          };
-        } else {
-          if (req.result) {
-            ok(true, "result is valid");
-            count++;
-            req.continue();
-          } else {
-            is(count, 40, "last contact - 40 contacts returned");
-            next();
-          }
-        }
-      };
-    };
-  },
-
-  clearDatabase,
-
-  function() {
-    ok(true, "all done!\n");
-    SimpleTest.finish();
-  }
-];
-
-start_tests();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/contacts/tests/test_contacts_international.html
+++ b/dom/contacts/tests/test_contacts_international.html
@@ -1,274 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=815833
--->
 <head>
-  <title>Test for Bug 815833 WebContacts</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815833">Mozilla Bug 815833</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
-<script class="testbody" type="text/javascript">
-"use strict";
-
-var number1 = {
-  local: "7932012345",
-  international: "+557932012345"
-};
+<script type="application/javascript">
 
-var number2 = {
-  local: "7932012346",
-  international: "+557932012346"
-};
-
-var properties1 = {
-  name: ["Testname1"],
-  tel: [{type: ["work"], value: number1.local, carrier: "testCarrier"} , {type: ["home", "fax"], value: number2.local}],
-};
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_contacts_international.html";
+}
 
-var shortNumber = "888";
-var properties2 = {
-  name: ["Testname2"],
-  tel: [{type: ["work"], value: shortNumber, carrier: "testCarrier"}]
-};
-
-var number3 = {
-  local: "7932012345",
-  international: "+557932012345"
-};
-
-var properties3 = {
-  name: ["Testname2"],
-  tel: [{value: number3.international}]
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
 };
 
-var req;
-var createResult1;
-var findResult1;
-var sample_id1;
-
-var steps = [
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear();
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a new contact1");
-    createResult1 = new mozContact(properties1);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a new contact2");
-    var createResult2 = new mozContact(properties2);
-    req = navigator.mozContacts.save(createResult2);
-    req.onsuccess = function () {
-      ok(createResult2.id, "The contact now has an ID.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for local number");
-    var options = {filterBy: ["tel"],
-                   filterOp: "startsWith",
-                   filterValue: number1.local};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      is(findResult1.id, sample_id1, "Same ID");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for international number");
-    var options = {filterBy: ["tel"],
-                   filterOp: "startsWith",
-                   filterValue: number1.international};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Found exactly 0 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for a short number matching the prefix");
-    var shortNumber = number1.local.substring(0, 3);
-    var options = {filterBy: ["tel"],
-                   filterOp: "equals",
-                   filterValue: shortNumber};
-    req = mozContacts.find(options);
-    req.onsuccess = function() {
-      is(req.result.length, 0, "The prefix short number should not match any contact.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for a short number matching the suffix");
-    var shortNumber = number1.local.substring(number1.local.length - 3);
-    var options = {filterBy: ["tel"],
-                   filterOp: "equals",
-                   filterValue: shortNumber};
-    req = mozContacts.find(options);
-    req.onsuccess = function() {
-      is(req.result.length, 0, "The suffix short number should not match any contact.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for a short number matching a contact");
-    var options = {filterBy: ["tel"],
-                   filterOp: "equals",
-                   filterValue: shortNumber};
-    req = mozContacts.find(options);
-    req.onsuccess = function() {
-      is(req.result.length, 1, "Found the contact equally matching the shortNumber.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function() {
-    ok(true, "Modifying number");
-    if (!findResult1) {
-      SpecialPowers.executeSoon(next);
-    } else {
-      findResult1.tel[0].value = number2.local;
-      req = mozContacts.save(findResult1);
-      req.onsuccess = function () {
-        next();
-      };
-    }
-  },
-  function () {
-    ok(true, "Searching for local number");
-    var options = {filterBy: ["tel"],
-                   filterOp: "startsWith",
-                   filterValue: number1.local};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Found exactly 0 contact.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for local number");
-    var options = {filterBy: ["tel"],
-                   filterOp: "startsWith",
-                   filterValue: number1.international};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Found exactly 0 contact.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for local number");
-    var options = {filterBy: ["tel"],
-                   filterOp: "startsWith",
-                   filterValue: number2.local};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      is(findResult1.id, sample_id1, "Same ID");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for local number");
-    var options = {filterBy: ["tel"],
-                   filterOp: "startsWith",
-                   filterValue: number2.international};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Found exactly 1 contact.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear();
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a contact with a Brazilian country code");
-    createResult1 = new mozContact(properties3);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for Brazilian number using local number");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: number3.local};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      is(findResult1.id, sample_id1, "Same ID");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear();
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "all done!\n");
-    SimpleTest.finish();
-  }
-];
-
-SpecialPowers.pushPrefEnv({
-  set: [
-    ["ril.lastKnownSimMcc", "000"]
-  ]
-}, start_tests);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/contacts/tests/test_contacts_substringmatching.html
+++ b/dom/contacts/tests/test_contacts_substringmatching.html
@@ -1,348 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=877302
--->
 <head>
-  <title>Test for Bug 877302 substring matching for WebContacts</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=877302">Mozilla Bug 877302</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
-<script class="testbody" type="text/javascript">
-"use strict";
-
-var substringLength = 8;
-
-var prop = {
-  tel: [{value: "7932012345" }, {value: "7932012346"}]
-};
+<script type="application/javascript">
 
-var prop2 = {
-  tel: [{value: "01187654321" }]
-};
-
-var prop3 = {
-  tel: [{ value: "+43332112346" }]
-};
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_contacts_substringmatching.html";
+}
 
-var prop4 = {
-  tel: [{ value: "(0414) 233-9888" }]
-};
-
-var brazilianNumber = {
-  international1: "0041557932012345",
-  international2: "+557932012345"
-};
-
-var prop5 = {
-  tel: [{value: brazilianNumber.international2}]
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
 };
 
-var req;
-var steps = [
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact");
-    createResult1 = new mozContact(prop);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find({});
-    req.onsuccess = function () {
-      is(req.result.length, 1, "One contact.");
-      findResult1 = req.result[0];
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 1");
-    var length = prop.tel[0].value.length;
-    var num = prop.tel[0].value.substring(length - substringLength, length);
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: num};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      is(findResult1.tel[0].value, "7932012345", "Same Value");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 2");
-    var length = prop.tel[1].value.length;
-    var num = prop.tel[1].value.substring(length - substringLength, length);
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: num};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      is(findResult1.tel[0].value, "7932012345", "Same Value");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 3");
-    var length = prop.tel[0].value.length;
-    var num = prop.tel[0].value.substring(length - substringLength + 1, length);
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: num};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Found exactly 0 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 4");
-    var length = prop.tel[0].value.length;
-    var num = prop.tel[0].value.substring(length - substringLength - 1, length);
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: num};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact");
-    createResult1 = new mozContact(prop2);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 5");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: "87654321"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 6");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: "01187654321"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 7");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: "909087654321"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 8");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: "0411187654321"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 9");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: "90411187654321"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 10");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: "+551187654321"};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact");
-    createResult1 = new mozContact(prop3);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    if (!isAndroid) { // Bug 905927
-      ok(true, "Retrieving by substring 1");
-      var length = prop3.tel[0].value.length;
-      var num = prop3.tel[0].value.substring(length - substringLength, length);
-      var options = {filterBy: ["tel"],
-                     filterOp: "match",
-                     filterValue: num};
-      req = mozContacts.find(options);
-      req.onsuccess = function () {
-        is(req.result.length, 0, "Found exactly 0 contacts.");
-        next();
-      };
-      req.onerror = onFailure;
-    } else {
-      SpecialPowers.executeSoon(next);
-    }
-  },
-  function () {
-    ok(true, "Adding contact");
-    createResult1 = new mozContact(prop4);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 1");
-    var num = "(0424) 233-9888"
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: num};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding a new contact with a Brazilian country code");
-    createResult1 = new mozContact(prop5);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for international number with prefix");
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: brazilianNumber.international1};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      ok(req.result.length == 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "all done!\n");
-    SimpleTest.finish();
-  }
-];
-
-SpecialPowers.pushPrefEnv({
-  set: [
-    ["dom.phonenumber.substringmatching.BR", substringLength],
-    ["ril.lastKnownSimMcc", "724"]
-  ]
-}, start_tests);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/contacts/tests/test_contacts_substringmatchingCL.html
+++ b/dom/contacts/tests/test_contacts_substringmatchingCL.html
@@ -1,204 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=877302
--->
 <head>
-  <title>Test for Bug 949537 substring matching for WebContacts</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949537">Mozilla Bug 949537</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
-<script class="testbody" type="text/javascript">
-"use strict";
+<script type="application/javascript">
 
-var landlineNumber = "+56 2 27654321";
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_contacts_substringmatchingCL.html";
+}
 
-var number = {
-  local: "87654321",
-  international: "+56 9 87654321"
-};
-
-var properties = {
-  name: ["Testname2"],
-  tel: [{value: number.international}]
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
 };
 
-var req;
-var steps = [
-  function () {
-    ok(true, "Adding a contact with a Chilean number");
-    createResult1 = new mozContact(properties);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for Chilean number with prefix");
-    req = mozContacts.find({
-      filterBy: ["tel"],
-      filterOp: "match",
-      filterValue: number.international
-    });
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      is(findResult1.id, sample_id1, "Same ID");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Searching for Chilean number using local number");
-    req = mozContacts.find({
-      filterBy: ["tel"],
-      filterOp: "match",
-      filterValue: number.local
-    });
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found 0 contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-
-  clearDatabase,
-
-  function () {
-    ok(true, "Adding contact with mobile number");
-    createResult1 = new mozContact({tel: [{value: number.international}]});
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find({});
-    req.onsuccess = function () {
-      is(req.result.length, 1, "One contact.");
-      findResult1 = req.result[0];
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by last 8 digits");
-    req = mozContacts.find({
-      filterBy: ["tel"],
-      filterOp: "match",
-      filterValue: number.international.slice(-8)
-    });
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      is(findResult1.id, sample_id1, "Same ID");
-      is(findResult1.tel[0].value, number.international, "Same Value");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by last 9 digits");
-    req = mozContacts.find({
-      filterBy: ["tel"],
-      filterOp: "match",
-      filterValue: number.international.slice(-9)
-    });
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      is(findResult1.id, sample_id1, "Same ID");
-      is(findResult1.tel[0].value, number.international, "Same Value");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by last 6 digits");
-    req = mozContacts.find({
-      filterBy: ["tel"],
-      filterOp: "match",
-      filterValue: number.international.slice(-6)
-    });
-    req.onsuccess = function () {
-      is(req.result.length, 0, "Found exactly zero contacts.");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-
-  clearDatabase,
-
-  function () {
-    ok(true, "Adding contact with landline number");
-    createResult1 = new mozContact({tel: [{value: landlineNumber}]});
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find({});
-    req.onsuccess = function () {
-      is(req.result.length, 1, "One contact.");
-      findResult1 = req.result[0];
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by last 7 digits (local number) with landline calling prefix");
-    req = mozContacts.find({
-      filterBy: ["tel"],
-      filterOp: "match",
-      filterValue: "022" + landlineNumber.slice(-7)
-    });
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      is(findResult1.id, sample_id1, "Same ID");
-      is(findResult1.tel[0].value, landlineNumber, "Same Value");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-
-  clearDatabase,
-
-  function () {
-    ok(true, "all done!\n");
-    SimpleTest.finish();
-  }
-];
-
-SpecialPowers.pushPrefEnv({
-  set: [
-    ["dom.phonenumber.substringmatching.CL", 8],
-    ["ril.lastKnownSimMcc", "730"]
-  ]
-}, start_tests);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/contacts/tests/test_contacts_substringmatchingVE.html
+++ b/dom/contacts/tests/test_contacts_substringmatchingVE.html
@@ -1,132 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=877302
--->
 <head>
-  <title>Test for Bug 877302 substring matching for WebContacts</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=877302">Mozilla Bug 877302</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script type="text/javascript;version=1.8" src="http://mochi.test:8888/tests/dom/contacts/tests/shared.js"></script>
-<script class="testbody" type="text/javascript">
-"use strict";
+<script type="application/javascript">
+
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_contacts_substringmatchingVE.html";
+}
 
-var prop = {
-  tel: [{value: "7932012345" }, {value: "7704143727591"}]
-};
-
-var prop2 = {
-  tel: [{value: "7932012345" }, {value: "+58 212 5551212"}]
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
 };
 
-var req;
-var steps = [
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact");
-    createResult1 = new mozContact(prop);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving all contacts");
-    req = mozContacts.find({});
-    req.onsuccess = function () {
-      is(req.result.length, 1, "One contact.");
-      findResult1 = req.result[0];
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 1");
-    var length = prop.tel[0].value.length;
-    var num = "04143727591"
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: num};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      is(findResult1.tel[1].value, "7704143727591", "Same Value");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact");
-    createResult1 = new mozContact(prop2);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring 2");
-    var num = "5551212";
-    var options = {filterBy: ["tel"],
-                   filterOp: "match",
-                   filterValue: num};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      is(req.result.length, 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      is(findResult1.tel[1].value, "+58 212 5551212", "Same Value");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Deleting database");
-    req = mozContacts.clear()
-    req.onsuccess = function () {
-      ok(true, "Deleted the database");
-      next();
-    }
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "all done!\n");
-    SimpleTest.finish();
-  }
-];
-
-SpecialPowers.pushPrefEnv({
-  set: [
-    ["dom.phonenumber.substringmatching.VE", 7],
-    ["ril.lastKnownSimMcc", "734"]
-  ]
-}, start_tests);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/contacts/tests/test_migration.html
+++ b/dom/contacts/tests/test_migration.html
@@ -1,194 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
 <head>
-  <title>Migration tests</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-<h1>migration tests</h1>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script type="text/javascript;version=1.8" src="shared.js"></script>
-<script class="testbody" type="text/javascript">
-"use strict";
-
-var backend, contactsCount, allContacts;
-function loadChromeScript() {
-  var url = SimpleTest.getTestFileURL("test_migration_chrome.js");
-  backend = SpecialPowers.loadChromeScript(url);
-}
+<script type="application/javascript">
 
-function addBackendEvents() {
-  backend.addMessageListener("createDB.success", function(count) {
-    contactsCount = count;
-    ok(true, "Created the database");
-    next();
-  });
-  backend.addMessageListener("createDB.error", function(err) {
-    ok(false, err);
-    next();
-  });
-
-  backend.addMessageListener("deleteDB.success", function() {
-    ok(true, "Deleted the database");
-    next();
-  });
-  backend.addMessageListener("deleteDB.error", function(err) {
-    ok(false, err);
-    next();
-  });
-}
-
-function createDB(version) {
-  info("Will create the DB at version " + version);
-  backend.sendAsyncMessage("createDB", version);
-}
-
-function deleteDB() {
-  info("Will delete the DB.");
-  backend.sendAsyncMessage("deleteDB");
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_migration.html";
 }
 
-var steps = [
-  function setupChromeScript() {
-    loadChromeScript();
-    addBackendEvents();
-    next();
-  },
-
-  deleteDB, // let's be sure the DB does not exist yet
-  createDB.bind(null, 12),
-
-  function testAccessMozContacts() {
-    info("Checking we have the right number of contacts: " + contactsCount);
-    var req = mozContacts.getCount();
-    req.onsuccess = function onsuccess() {
-      ok(true, "Could access the mozContacts API");
-      is(this.result, contactsCount, "Contacts count is correct");
-      next();
-    };
-
-    req.onerror = function onerror() {
-      ok(false, "Couldn't access the mozContacts API");
-      next();
-    };
-  },
-
-  function testRetrieveAllContacts() {
-    /* if the migration does not work right, either we'll have an error, or the
-       contacts won't be migrated properly and thus will fail WebIDL conversion,
-       which will manifest as a timeout */
-    info("Checking the contacts are corrected to obey WebIDL constraints.  (upgrades 14 to 17)");
-    var req = mozContacts.find();
-    req.onsuccess = function onsuccess() {
-      if (this.result) {
-        is(this.result.length, contactsCount, "Contacts array length is correct");
-        allContacts = this.result;
-        next();
-      } else {
-        ok(false, "Could access the mozContacts API but got no contacts!");
-        next();
-      }
-    };
-
-    req.onerror = function onerror() {
-      ok(false, "Couldn't access the mozContacts API");
-      next();
-    };
-  },
-
-  function checkNameIndex() {
-    info("Checking name index migration (upgrades 17 to 19).");
-    if (!allContacts) {
-      next();
-    }
-
-    var count = allContacts.length;
-
-    function finishRequest() {
-      count--;
-      if (!count) {
-        next();
-      }
-    }
+SimpleTest.waitForExplicitFinish();
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: true, context: document},
+    {type: "contacts-write", allow: true, context: document},
+    {type: "contacts-create", allow: true, context: document},
+  ], run_tests);
+};
 
-    allContacts.forEach(function(contact) {
-      var name = contact.name && contact.name[0];
-      if (!name) {
-        count--;
-        return;
-      }
-
-      var req = mozContacts.find({
-        filterBy: ["name"],
-        filterValue: name,
-        filterOp: "equals"
-      });
-
-      req.onsuccess = function onsuccess() {
-        if (this.result) {
-          info("Found contact '" + name + "', checking it's the correct one.");
-          checkContacts(this.result[0], contact);
-        } else {
-          ok(false, "Could not find contact with name '" + name + "'");
-        }
-
-        finishRequest();
-      };
-
-      req.onerror = function onerror() {
-        ok(false, "Error while finding contact with name '" + name + "'!");
-        finishRequest();
-      }
-    });
-
-    if (!count) {
-      ok(false, "No contact had a name, this is unexpected.");
-      next();
-    }
-  },
-
-  function checkSubstringMatching() {
-    var subject = "0004567890"; // the last 7 digits are the same that at least one contact
-    info("Looking for a contact matching " + subject);
-    var req = mozContacts.find({
-      filterValue: subject,
-      filterOp: "match",
-      filterBy: ["tel"],
-      filterLimit: 1
-    });
-
-    req.onsuccess = function onsuccess() {
-      if (this.result && this.result[0]) {
-        ok(true, "Found a contact with number " + this.result[0].tel[0].value);
-      }
-      next();
-    };
-
-    req.onerror = function onerror() {
-      ok(false, "Error while finding contact for substring matching check!");
-      next();
-    };
-  },
-
-  deleteDB,
-
-  function finish() {
-    backend.destroy();
-    info("all done!\n");
-    SimpleTest.finish();
-  }
-];
-
-// this is the Mcc for Brazil, so that we trigger the previous pref
-SpecialPowers.pushPrefEnv({"set": [["dom.phonenumber.substringmatching.BR", 7],
-                                   ["ril.lastKnownSimMcc", "724"]]}, start_tests);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/contacts/tests/test_permission_denied.html
+++ b/dom/contacts/tests/test_permission_denied.html
@@ -1,120 +1,29 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
 <html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1081873
--->
 <head>
-  <title>Test for Bug 1081873</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1081873">Mozilla Bug 1081873</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
+<iframe></iframe>
 <pre id="test">
-<script class="testbody" type="text/javascript">
-
-"use strict";
-
-SpecialPowers.addPermission("contacts-write", false, document);
-SpecialPowers.addPermission("contacts-read", false, document);
-SpecialPowers.addPermission("contacts-create", false, document);
-
-function onUnexpectedSuccess() {
-  ok(false, "Unexpected success");
-  next();
-}
+<script type="application/javascript">
 
-function onExpectedError(event) {
-  is(event.target.error.name, PERMISSION_DENIED, "Expected PERMISSION_DENIED");
-  next();
-}
-
-const PERMISSION_DENIED = "PERMISSION_DENIED";
-
-var index = 0;
-
-function next() {
-  info("Step " + index);
-  if (index >= steps.length) {
-    ok(false, "Shouldn't get here!");
-    return;
-  }
-  try {
-    var i = index++;
-    steps[i]();
-  } catch(ex) {
-    ok(false, "Caught exception", ex);
-  }
+function run_tests() {
+  var iframe = document.querySelector("iframe");
+  iframe.src = "file_permission_denied.html";
 }
 
-var steps = [
-  function() {
-    ok(true, "Add contact without permission");
-    var req = navigator.mozContacts.save(new mozContact({}));
-    req.onsuccess = onUnexpectedSuccess;
-    req.onerror = onExpectedError;
-  },
-  function() {
-    ok(true, "Find contact without permission");
-    var req = navigator.mozContacts.find({});
-    req.onsuccess = onUnexpectedSuccess;
-    req.onerror = onExpectedError;
-  },
-  function() {
-    ok(true, "Get all contacts without permission");
-    var req = navigator.mozContacts.getAll();
-    req.onsuccess = onUnexpectedSuccess;
-    req.onerror = onExpectedError;
-  },
-  function() {
-    ok(true, "Remove contact without permission");
-    var req = navigator.mozContacts.remove("aId");
-    req.onsuccess = onUnexpectedSuccess;
-    req.onerror = onExpectedError;
-  },
-  function() {
-    ok(true, "Clear contacts without permission");
-    var req = navigator.mozContacts.clear();
-    req.onsuccess = onUnexpectedSuccess;
-    req.onerror = onExpectedError;
-  },
-  function() {
-    ok(true, "Get revision without permission");
-    var req = navigator.mozContacts.getRevision();
-    req.onsuccess = onUnexpectedSuccess;
-    req.onerror = onExpectedError;
-  },
-  function() {
-    ok(true, "Get count without permission");
-    var req = navigator.mozContacts.getCount();
-    req.onsuccess = onUnexpectedSuccess;
-    req.onerror = function() {
-      is(req.error.name, PERMISSION_DENIED, "Expected PERMISSION_DENIED");
-      SimpleTest.finish();
-    };
-  }
-];
-
 SimpleTest.waitForExplicitFinish();
-
-const DENY = SpecialPowers.Ci.nsIPermissionManager.DENY_ACTION;
-var interval = setInterval(function() {
-  if (!SpecialPowers.testPermission("contacts-read", DENY, document) ||
-      !SpecialPowers.testPermission("contacts-write", DENY, document) ||
-      !SpecialPowers.testPermission("contacts-create", DENY, document)) {
-    return;
-  }
-  clearInterval(interval);
-  next();
-}, 1000);
+onload = function() {
+  SpecialPowers.pushPermissions([
+    {type: "contacts-read", allow: false, context: document},
+    {type: "contacts-write", allow: false, context: document},
+    {type: "contacts-create", allow: false, context: document},
+  ], run_tests);
+};
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2798,16 +2798,18 @@ nsresult HTMLMediaElement::InitializeDec
     return NS_ERROR_FAILURE;
   RefPtr<MediaDecoder> decoder = aOriginal->Clone(this);
   if (!decoder)
     return NS_ERROR_FAILURE;
 
   LOG(LogLevel::Debug, ("%p Cloned decoder %p from %p", this, decoder.get(), aOriginal));
 
   decoder->SetMediaSeekable(aOriginal->IsMediaSeekable());
+  decoder->SetMediaSeekableOnlyInBufferedRanges(
+    aOriginal->IsMediaSeekableOnlyInBufferedRanges());
 
   RefPtr<MediaResource> resource =
     originalResource->CloneData(decoder->GetResourceCallback());
 
   if (!resource) {
     LOG(LogLevel::Debug, ("%p Failed to cloned stream for decoder %p", this, decoder.get()));
     return NS_ERROR_FAILURE;
   }
--- a/dom/icc/tests/marionette/test_icc_contact_read.js
+++ b/dom/icc/tests/marionette/test_icc_contact_read.js
@@ -1,13 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = "head.js";
+MARIONETTE_CONTEXT = "chrome";
 
 function testReadContacts(aIcc, aType) {
   log("testReadContacts: type=" + aType);
   let iccId = aIcc.iccInfo.iccid;
   return aIcc.readContacts(aType)
     .then((aResult) => {
 
       is(Array.isArray(aResult), true);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -142,16 +142,17 @@
 #include "nsVolume.h"
 #include "nsVolumeService.h"
 #include "SpeakerManagerService.h"
 #endif
 
 #ifdef XP_WIN
 #include <process.h>
 #define getpid _getpid
+#include "mozilla/widget/AudioSession.h"
 #endif
 
 #ifdef MOZ_X11
 #include "mozilla/X11Util.h"
 #endif
 
 #ifdef ACCESSIBILITY
 #include "nsIAccessibilityService.h"
@@ -3038,16 +3039,20 @@ ContentChild::RecvShutdown()
   }
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->NotifyObservers(static_cast<nsIContentChild*>(this),
                           "content-child-shutdown", nullptr);
   }
 
+#if defined(XP_WIN)
+    mozilla::widget::StopAudioSession();
+#endif
+
   GetIPCChannel()->SetAbortOnError(false);
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
   if (profiler_is_active()) {
     // We're shutting down while we were profiling. Send the
     // profile up to the parent so that we don't lose this
     // information.
     Unused << RecvGatherProfile();
@@ -3156,16 +3161,36 @@ ContentChild::RecvGamepadUpdate(const Ga
   RefPtr<GamepadService> svc(GamepadService::GetService());
   if (svc) {
     svc->Update(aGamepadEvent);
   }
 #endif
   return true;
 }
 
+bool
+ContentChild::RecvSetAudioSessionData(const nsID& aId,
+                                      const nsString& aDisplayName,
+                                      const nsString& aIconPath)
+{
+#if defined(XP_WIN)
+    if (NS_FAILED(mozilla::widget::RecvAudioSessionData(aId, aDisplayName,
+                                                        aIconPath))) {
+      return true;
+    }
+
+    // Ignore failures here; we can't really do anything about them
+    mozilla::widget::StartAudioSession();
+    return true;
+#else
+    NS_RUNTIMEABORT("Not Reached!");
+    return false;
+#endif
+}
+
 // This code goes here rather than nsGlobalWindow.cpp because nsGlobalWindow.cpp
 // can't include ContentChild.h since it includes windows.h.
 
 static uint64_t gNextWindowID = 0;
 
 // We use only 53 bits for the window ID so that it can be converted to and from
 // a JS value without loss of precision. The upper bits of the window ID hold the
 // process ID. The lower bits identify the window.
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -605,16 +605,22 @@ public:
   AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
                                       const IPC::Principal& aPrincipal,
                                       const TabId& aTabId) override;
   virtual bool
   DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor) override;
 
   virtual bool RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
 
+  // Windows specific - set up audio session
+  virtual bool
+  RecvSetAudioSessionData(const nsID& aId,
+                          const nsString& aDisplayName,
+                          const nsString& aIconPath) override;
+
 private:
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   virtual void ProcessingError(Result aCode, const char* aReason) override;
 
   /**
    * Exit *now*.  Do not shut down XPCOM, do not pass Go, do not run
    * static destructors, do not collect $200.
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -258,16 +258,20 @@ using namespace mozilla::system;
 #include "nsIProfiler.h"
 #include "nsIProfileSaveEvent.h"
 #endif
 
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/GamepadMonitoring.h"
 #endif
 
+#ifdef XP_WIN
+#include "mozilla/widget/AudioSession.h"
+#endif
+
 #include "VRManagerParent.h"            // for VRManagerParent
 
 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
 
 #if defined(XP_WIN)
 // e10s forced enable pref, defined in nsAppRunner.cpp
 extern const char* kForceEnableE10sPref;
 #endif
@@ -2615,16 +2619,26 @@ ContentParent::InitInternal(ProcessPrior
       MOZ_ASSERT(static_cast<const FileDescriptor&>(brokerFd).IsValid());
     }
   }
 #endif
   if (shouldSandbox && !SendSetProcessSandbox(brokerFd)) {
     KillHard("SandboxInitFailed");
   }
 #endif
+#if defined(XP_WIN)
+  // Send the info needed to join the browser process's audio session.
+  nsID id;
+  nsString sessionName;
+  nsString iconPath;
+  if (NS_SUCCEEDED(mozilla::widget::GetAudioSessionData(id, sessionName,
+                                                        iconPath))) {
+    Unused << SendSetAudioSessionData(id, sessionName, iconPath);
+  }
+#endif
 }
 
 bool
 ContentParent::IsAlive() const
 {
   return mIsAlive;
 }
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -737,16 +737,23 @@ child:
      */
     async PushWithData(nsCString scope, Principal principal, uint8_t[] data);
 
     /**
      * Send a `pushsubscriptionchange` event to a service worker in the child.
      */
     async PushSubscriptionChange(nsCString scope, Principal principal);
 
+    /**
+     * Windows specific: associate this content process with the browsers
+     * audio session.
+     */
+    async SetAudioSessionData(nsID aID,
+                              nsString aDisplayName,
+                              nsString aIconPath);
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
--- a/dom/media/MediaDataDemuxer.h
+++ b/dom/media/MediaDataDemuxer.h
@@ -65,16 +65,20 @@ public:
   // The actual Track ID is to be retrieved by calling
   // MediaTrackDemuxer::TrackInfo.
   virtual already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(TrackInfo::TrackType aType,
                                                               uint32_t aTrackNumber) = 0;
 
   // Returns true if the underlying resource allows seeking.
   virtual bool IsSeekable() const = 0;
 
+  // Returns true if the underlying resource can only seek within buffered
+  // ranges.
+  virtual bool IsSeekableOnlyInBufferedRanges() const { return false; }
+
   // Returns the media's crypto information, or nullptr if media isn't
   // encrypted.
   virtual UniquePtr<EncryptionInfo> GetCrypto()
   {
     return nullptr;
   }
 
   // Notifies the demuxer that the underlying resource has received more data
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -559,16 +559,18 @@ MediaDecoder::MediaDecoder(MediaDecoderO
   , mPlaybackBytesPerSecond(AbstractThread::MainThread(), 0.0,
                             "MediaDecoder::mPlaybackBytesPerSecond (Canonical)")
   , mPlaybackRateReliable(AbstractThread::MainThread(), true,
                           "MediaDecoder::mPlaybackRateReliable (Canonical)")
   , mDecoderPosition(AbstractThread::MainThread(), 0,
                      "MediaDecoder::mDecoderPosition (Canonical)")
   , mMediaSeekable(AbstractThread::MainThread(), true,
                    "MediaDecoder::mMediaSeekable (Canonical)")
+  , mMediaSeekableOnlyInBufferedRanges(AbstractThread::MainThread(), false,
+                   "MediaDecoder::mMediaSeekableOnlyInBufferedRanges (Canonical)")
   , mTelemetryReported(false)
 {
   MOZ_COUNT_CTOR(MediaDecoder);
   MOZ_ASSERT(NS_IsMainThread());
   MediaMemoryTracker::AddMediaDecoder(this);
 
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
   mResourceCallback->Connect(this);
@@ -870,16 +872,17 @@ MediaDecoder::MetadataLoaded(nsAutoPtr<M
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShuttingDown);
 
   DECODER_LOG("MetadataLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
               aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
               aInfo->HasAudio(), aInfo->HasVideo());
 
   SetMediaSeekable(aInfo->mMediaSeekable);
+  SetMediaSeekableOnlyInBufferedRanges(aInfo->mMediaSeekableOnlyInBufferedRanges);
   mInfo = aInfo.forget();
   ConstructMediaTracks();
 
   // Make sure the element and the frame (if any) are told about
   // our new size.
   if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     mFiredMetadataLoaded = true;
     mOwner->MetadataLoaded(mInfo, nsAutoPtr<const MetadataTags>(aTags.forget()));
@@ -1372,39 +1375,54 @@ MediaDecoder::UpdateEstimatedMediaDurati
 }
 
 void
 MediaDecoder::SetMediaSeekable(bool aMediaSeekable) {
   MOZ_ASSERT(NS_IsMainThread());
   mMediaSeekable = aMediaSeekable;
 }
 
+void
+MediaDecoder::SetMediaSeekableOnlyInBufferedRanges(bool aMediaSeekableOnlyInBufferedRanges){
+  MOZ_ASSERT(NS_IsMainThread());
+  mMediaSeekableOnlyInBufferedRanges = aMediaSeekableOnlyInBufferedRanges;
+}
+
 bool
 MediaDecoder::IsTransportSeekable()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return GetResource()->IsTransportSeekable();
 }
 
 bool
 MediaDecoder::IsMediaSeekable()
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE(GetStateMachine(), false);
   return mMediaSeekable;
 }
 
+bool
+MediaDecoder::IsMediaSeekableOnlyInBufferedRanges()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mMediaSeekableOnlyInBufferedRanges;
+}
+
 media::TimeIntervals
 MediaDecoder::GetSeekable()
 {
   MOZ_ASSERT(NS_IsMainThread());
   // We can seek in buffered range if the media is seekable. Also, we can seek
   // in unbuffered ranges if the transport level is seekable (local file or the
-  // server supports range requests, etc.)
-  if (!IsMediaSeekable()) {
+  // server supports range requests, etc.) or in cue-less WebMs
+  if (IsMediaSeekableOnlyInBufferedRanges()) {
+    return GetBuffered();
+  } else if (!IsMediaSeekable()) {
     return media::TimeIntervals();
   } else if (!IsTransportSeekable()) {
     return GetBuffered();
   } else {
     return media::TimeIntervals(
       media::TimeInterval(media::TimeUnit::FromMicroseconds(0),
                           IsInfinite() ?
                             media::TimeUnit::FromInfinity() :
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -248,22 +248,27 @@ protected:
   // changed, this causes a durationchanged event to fire to the media
   // element.
   void UpdateEstimatedMediaDuration(int64_t aDuration) override;
 
 public:
   // Called from HTMLMediaElement when owner document activity changes
   virtual void SetElementVisibility(bool aIsVisible) {}
 
-  // Set a flag indicating whether seeking is supported
+  // Set a flag indicating whether random seeking is supported
   void SetMediaSeekable(bool aMediaSeekable);
+  // Set a flag indicating whether seeking is supported only in buffered ranges
+  void SetMediaSeekableOnlyInBufferedRanges(bool aMediaSeekableOnlyInBufferedRanges);
 
-  // Returns true if this media supports seeking. False for example for WebM
-  // files without an index and chained ogg files.
+  // Returns true if this media supports random seeking. False for example with
+  // chained ogg files.
   bool IsMediaSeekable();
+  // Returns true if this media supports seeking only in buffered ranges. True
+  // for example in WebMs with no cues
+  bool IsMediaSeekableOnlyInBufferedRanges();
   // Returns true if seeking is supported on a transport level (e.g. the server
   // supports range requests, we are playing a file, etc.).
   bool IsTransportSeekable();
 
   // Return the time ranges that can be seeked into.
   virtual media::TimeIntervals GetSeekable();
 
   // Set the end time of the media resource. When playback reaches
@@ -787,16 +792,19 @@ protected:
   // is up to consuming the stream. This is not adjusted during decoder
   // seek operations, but it's updated at the end when we start playing
   // back again.
   Canonical<int64_t> mDecoderPosition;
 
   // True if the media is seekable (i.e. supports random access).
   Canonical<bool> mMediaSeekable;
 
+  // True if the media is only seekable within its buffered ranges.
+  Canonical<bool> mMediaSeekableOnlyInBufferedRanges;
+
 public:
   AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override;
   AbstractCanonical<double>* CanonicalVolume() {
     return &mVolume;
   }
   AbstractCanonical<double>* CanonicalPlaybackRate() {
     return &mPlaybackRate;
   }
@@ -828,16 +836,19 @@ public:
     return &mPlaybackRateReliable;
   }
   AbstractCanonical<int64_t>* CanonicalDecoderPosition() {
     return &mDecoderPosition;
   }
   AbstractCanonical<bool>* CanonicalMediaSeekable() {
     return &mMediaSeekable;
   }
+  AbstractCanonical<bool>* CanonicalMediaSeekableOnlyInBufferedRanges() {
+    return &mMediaSeekableOnlyInBufferedRanges;
+  }
 
 private:
   // Notify owner when the audible state changed
   void NotifyAudibleStateChanged();
 
   /* Functions called by ResourceCallback */
 
   // A media stream is assumed to be infinite if the metadata doesn't
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -263,16 +263,18 @@ MediaDecoderStateMachine::MediaDecoderSt
   mPlaybackBytesPerSecond(mTaskQueue, 0.0,
                           "MediaDecoderStateMachine::mPlaybackBytesPerSecond (Mirror)"),
   mPlaybackRateReliable(mTaskQueue, true,
                         "MediaDecoderStateMachine::mPlaybackRateReliable (Mirror)"),
   mDecoderPosition(mTaskQueue, 0,
                    "MediaDecoderStateMachine::mDecoderPosition (Mirror)"),
   mMediaSeekable(mTaskQueue, true,
                  "MediaDecoderStateMachine::mMediaSeekable (Mirror)"),
+  mMediaSeekableOnlyInBufferedRanges(mTaskQueue, false,
+                 "MediaDecoderStateMachine::mMediaSeekableOnlyInBufferedRanges (Mirror)"),
   mDuration(mTaskQueue, NullableTimeUnit(),
             "MediaDecoderStateMachine::mDuration (Canonical"),
   mIsShutdown(mTaskQueue, false,
               "MediaDecoderStateMachine::mIsShutdown (Canonical)"),
   mNextFrameStatus(mTaskQueue, MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
                    "MediaDecoderStateMachine::mNextFrameStatus (Canonical)"),
   mCurrentPosition(mTaskQueue, 0,
                    "MediaDecoderStateMachine::mCurrentPosition (Canonical)"),
@@ -345,16 +347,17 @@ MediaDecoderStateMachine::Initialization
   mVolume.Connect(aDecoder->CanonicalVolume());
   mLogicalPlaybackRate.Connect(aDecoder->CanonicalPlaybackRate());
   mPreservesPitch.Connect(aDecoder->CanonicalPreservesPitch());
   mSameOriginMedia.Connect(aDecoder->CanonicalSameOriginMedia());
   mPlaybackBytesPerSecond.Connect(aDecoder->CanonicalPlaybackBytesPerSecond());
   mPlaybackRateReliable.Connect(aDecoder->CanonicalPlaybackRateReliable());
   mDecoderPosition.Connect(aDecoder->CanonicalDecoderPosition());
   mMediaSeekable.Connect(aDecoder->CanonicalMediaSeekable());
+  mMediaSeekableOnlyInBufferedRanges.Connect(aDecoder->CanonicalMediaSeekableOnlyInBufferedRanges());
 
   // Initialize watchers.
   mWatchManager.Watch(mBuffered, &MediaDecoderStateMachine::BufferedRangeUpdated);
   mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mVideoCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
   mWatchManager.Watch(mLogicalPlaybackRate, &MediaDecoderStateMachine::LogicalPlaybackRateChanged);
@@ -1469,19 +1472,18 @@ RefPtr<MediaDecoder::SeekPromise>
 MediaDecoderStateMachine::Seek(SeekTarget aTarget)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (IsShutdown()) {
     return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
   }
 
-  // We need to be able to seek both at a transport level and at a media level
-  // to seek.
-  if (!mMediaSeekable) {
+  // We need to be able to seek in some way
+  if (!mMediaSeekable && !mMediaSeekableOnlyInBufferedRanges) {
     DECODER_WARN("Seek() function should not be called on a non-seekable state machine");
     return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
   }
 
   MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA,
                "We should have got duration already");
 
   if (mState < DECODER_STATE_DECODING ||
@@ -2187,16 +2189,17 @@ MediaDecoderStateMachine::FinishShutdown
   mVolume.DisconnectIfConnected();
   mLogicalPlaybackRate.DisconnectIfConnected();
   mPreservesPitch.DisconnectIfConnected();
   mSameOriginMedia.DisconnectIfConnected();
   mPlaybackBytesPerSecond.DisconnectIfConnected();
   mPlaybackRateReliable.DisconnectIfConnected();
   mDecoderPosition.DisconnectIfConnected();
   mMediaSeekable.DisconnectIfConnected();
+  mMediaSeekableOnlyInBufferedRanges.DisconnectIfConnected();
 
   mDuration.DisconnectAll();
   mIsShutdown.DisconnectAll();
   mNextFrameStatus.DisconnectAll();
   mCurrentPosition.DisconnectAll();
   mPlaybackOffset.DisconnectAll();
   mIsAudioDataAudible.DisconnectAll();
 
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -1234,16 +1234,19 @@ private:
   Mirror<bool> mPlaybackRateReliable;
 
   // Current decoding position in the stream.
   Mirror<int64_t> mDecoderPosition;
 
   // True if the media is seekable (i.e. supports random access).
   Mirror<bool> mMediaSeekable;
 
+  // True if the media is seekable only in buffered ranges.
+  Mirror<bool> mMediaSeekableOnlyInBufferedRanges;
+
   // Duration of the media. This is guaranteed to be non-null after we finish
   // decoding the first frame.
   Canonical<media::NullableTimeUnit> mDuration;
 
   // Whether we're currently in or transitioning to shutdown state.
   Canonical<bool> mIsShutdown;
 
   // The status of our next frame. Mirrored on the main thread and used to
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -332,16 +332,18 @@ MediaFormatReader::OnDemuxerInitDone(nsr
   int64_t audioDuration = HasAudio() ? mInfo.mAudio.mDuration : 0;
 
   int64_t duration = std::max(videoDuration, audioDuration);
   if (duration != -1) {
     mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(duration));
   }
 
   mInfo.mMediaSeekable = mDemuxer->IsSeekable();
+  mInfo.mMediaSeekableOnlyInBufferedRanges =
+    mDemuxer->IsSeekableOnlyInBufferedRanges();
 
   if (!videoActive && !audioActive) {
     mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__);
     return;
   }
 
   mInitDone = true;
   RefPtr<MetadataHolder> metadata = new MetadataHolder();
@@ -1423,17 +1425,17 @@ MediaFormatReader::Seek(SeekTarget aTarg
 
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty());
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise());
   MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasPromise());
   MOZ_DIAGNOSTIC_ASSERT(mPendingSeekTime.isNothing());
   MOZ_DIAGNOSTIC_ASSERT(mVideo.mTimeThreshold.isNothing());
   MOZ_DIAGNOSTIC_ASSERT(mAudio.mTimeThreshold.isNothing());
 
-  if (!mInfo.mMediaSeekable) {
+  if (!mInfo.mMediaSeekable && !mInfo.mMediaSeekableOnlyInBufferedRanges) {
     LOG("Seek() END (Unseekable)");
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   if (mShutdown) {
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -412,16 +412,19 @@ public:
   // The Ogg reader tries to kinda-sorta compute the duration by seeking to the
   // end and determining the timestamp of the last frame. This isn't useful as
   // a duration until we know the start time, so we need to track it separately.
   media::NullableTimeUnit mUnadjustedMetadataEndTime;
 
   // True if the media is seekable (i.e. supports random access).
   bool mMediaSeekable = true;
 
+  // True if the media is only seekable within its buffered ranges.
+  bool mMediaSeekableOnlyInBufferedRanges = false;
+
   EncryptionInfo mCrypto;
 };
 
 class SharedTrackInfo {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedTrackInfo)
 public:
   SharedTrackInfo(const TrackInfo& aOriginal, uint32_t aStreamID)
     : mInfo(aOriginal.Clone())
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -501,20 +501,26 @@ var gFastSeekTests = [
 ];
 
 function IsWindows8OrLater() {
   var re = /Windows NT (\d.\d)/;
   var winver = manifestNavigator().userAgent.match(re);
   return winver && winver.length == 2 && parseFloat(winver[1]) >= 6.2;
 }
 
+// These files are WebMs without cues. They're seekable within their buffered
+// ranges. If work renders WebMs fully seekable these files should be moved
+// into gSeekTests
+var gCuelessWebMTests = [
+  { name:"no-cues.webm", type:"video/webm", duration:3.967 },
+];
+
 // These are files that are non seekable, due to problems with the media,
 // for example broken or missing indexes.
 var gUnseekableTests = [
-  { name:"no-cues.webm", type:"video/webm" },
   { name:"bogus.duh", type:"bogus/duh"}
 ];
 
 var androidVersion = -1; // non-Android platforms
 if (manifestNavigator().userAgent.indexOf("Mobile") != -1 ||
     manifestNavigator().userAgent.indexOf("Tablet") != -1) {
   // See nsSystemInfo.cpp, the getProperty('version') returns different value
   // on each platforms, so we need to distinguish the android and B2G platform.
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -618,16 +618,19 @@ skip-if = buildapp == 'b2g' || (toolkit 
 [test_can_play_type_no_ogg.html]
 [test_can_play_type_ogg.html]
 [test_chaining.html]
 [test_clone_media_element.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439
 [test_closing_connections.html]
 [test_constants.html]
 [test_controls.html]
+[test_cueless_webm_seek-1.html]
+[test_cueless_webm_seek-2.html]
+[test_cueless_webm_seek-3.html]
 [test_currentTime.html]
 [test_decode_error.html]
 [test_decoder_disable.html]
 [test_defaultMuted.html]
 [test_delay_load.html]
 skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1082984
 [test_dormant_playback.html]
 skip-if = (os == 'win' && os_version == '5.1') || (os != 'win' && toolkit != 'gonk')
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_cueless_webm_seek-1.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=657791
+-->
+<head>
+  <title>Test for Bug 657791</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657791">Mozilla Bug 657791</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+// Subset of seek tests for cueless WebMs. When random seeking (rather than just
+// in buffered ranges) is implemented for WebM, these tests can be removed and
+// the cueless WebM(s) references can be moved to the general test_seek test
+// array.
+// Test array is defined in manifest.js
+
+var manager = new MediaTestManager;
+
+// Exercise functionality as in test_seek-1
+function testWebM1(e) {
+  var v = e.target;
+  v.removeEventListener('loadeddata', testWebM1);
+
+  var startPassed = false;
+  var endPassed = false;
+  var seekFlagStart = false;
+  var seekFlagEnd = false;
+  var readonly = true;
+  var completed = false;
+
+  ok(v.buffered.length >= 1, "Should have a buffered range");
+  var halfBuffered = v.buffered.end(0) / 2;
+
+  function startTest() {
+    is(v.seekable.start(0), v.buffered.start(0), "Seekable start should be buffered start");
+    is(v.seekable.end(0), v.buffered.end(0), "Seekable end should be buffered end");
+    ok(!completed, "Should not be completed yet");
+    ok(!v.seeking, "seeking should default to false");
+    try {
+      v.seeking = true;
+      readonly = v.seeking === false;
+    }
+    catch(e) {
+      readonly = "threw exception: " + e;
+    }
+    is(readonly, true, "seeking should be readonly");
+
+    v.currentTime = halfBuffered;
+    seekFlagStart = v.seeking;
+  }
+
+  function seekStarted() {
+    ok(!completed, "should not be completed yet");
+    startPassed = true;
+  }
+
+  function seekEnded() {
+    ok(!completed, "should not be completed yet");
+    ok(Math.abs(v.currentTime - halfBuffered) < 0.1,
+       "Video currentTime should be around " + halfBuffered + ": " + v.currentTime + " (seeked)");
+    endPassed = true;
+    seekFlagEnd = v.seeking;
+    v.play();
+  }
+
+  function playbackEnded() {
+    ok(!completed, "should not be completed yet");
+
+    completed = true;
+    ok(startPassed, "seeking event");
+    ok(endPassed, "seeked event");
+    ok(seekFlagStart, "seeking flag on start should be true");
+    ok(!seekFlagEnd, "seeking flag on end should be false");
+    removeNodeAndSource(v);
+    manager.finished(v._token);
+  }
+
+  once(v, "ended", playbackEnded);
+  once(v, "seeking", seekStarted);
+  once(v, "seeked", seekEnded);
+
+  startTest();
+}
+
+// Fetch the media resource using XHR so we can be sure the entire
+// resource is loaded before we test buffered ranges. This ensures
+// we have deterministic behaviour.
+function fetch(url, fetched_callback) {
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", url, true);
+  xhr.responseType = "blob";
+
+  var loaded = function (event) {
+    if (xhr.status == 200 || xhr.status == 206) {
+      // Request fulfilled. Note sometimes we get 206... Presumably because either
+      // httpd.js or Necko cached the result.
+      fetched_callback(window.URL.createObjectURL(xhr.response));
+    } else {
+      ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders());
+    }
+  };
+
+  xhr.addEventListener("load", loaded, false);
+  xhr.send();
+}
+
+function startTest(test, token) {
+  var onfetched = function(uri) {
+    var v = document.createElement('video');
+    v._token = token;
+    v.src = uri;
+    v.addEventListener("loadeddata", testWebM1, false);
+    document.body.appendChild(v);
+  }
+  manager.started(token);
+  fetch(test.name, onfetched);
+}
+
+SimpleTest.waitForExplicitFinish();
+manager.runTests(gCuelessWebMTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_cueless_webm_seek-2.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=657791
+-->
+<head>
+  <title>Test for Bug 657791</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657791">Mozilla Bug 657791</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+// Subset of seek tests for cueless WebMs. When random seeking (rather than just
+// in buffered ranges) is implemented for WebM, these tests can be removed and
+// the cueless WebM(s) references can be moved to the general test_seek test
+// array.
+// Test array is defined in manifest.js
+
+var manager = new MediaTestManager;
+
+// Exercise functionality as in test_seek-2
+function testWebM2(e) {
+  var v = e.target;
+  v.removeEventListener('loadeddata', testWebM2);
+
+  var startPassed = false;
+  var endPassed = false;
+  var completed = false;
+
+  ok(v.buffered.length >= 1, "Should have a buffered range");
+  var halfBuffered = v.buffered.end(0) / 2;
+
+  function startTest() {
+    if (completed)
+      return;
+
+    is(v.seekable.start(0), v.buffered.start(0), "Seekable start should be buffered start");
+    is(v.seekable.end(0), v.buffered.end(0), "Seekable end should be buffered end");
+    v.currentTime=halfBuffered;
+    v.play();
+  }
+
+  function seekStarted() {
+    if (completed)
+      return;
+
+    startPassed = true;
+  }
+
+  function seekEnded() {
+    if (completed)
+      return;
+
+    endPassed = true;
+  }
+
+  function playbackEnded() {
+    if (completed)
+      return;
+
+    completed = true;
+    ok(startPassed, "send seeking event");
+    ok(endPassed, "send seeked event");
+    ok(v.ended, "Checking playback has ended");
+    ok(Math.abs(v.currentTime - v.duration) <= 0.1, "Checking currentTime at end: " + v.currentTime);
+    removeNodeAndSource(v);
+    manager.finished(v._token);
+  }
+
+  v.addEventListener("ended", playbackEnded, false);
+  v.addEventListener("seeking", seekStarted, false);
+  v.addEventListener("seeked", seekEnded, false);
+
+  startTest();
+}
+
+// Fetch the media resource using XHR so we can be sure the entire
+// resource is loaded before we test buffered ranges. This ensures
+// we have deterministic behaviour.
+function fetch(url, fetched_callback) {
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", url, true);
+  xhr.responseType = "blob";
+
+  var loaded = function (event) {
+    if (xhr.status == 200 || xhr.status == 206) {
+      // Request fulfilled. Note sometimes we get 206... Presumably because either
+      // httpd.js or Necko cached the result.
+      fetched_callback(window.URL.createObjectURL(xhr.response));
+    } else {
+      ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders());
+    }
+  };
+
+  xhr.addEventListener("load", loaded, false);
+  xhr.send();
+}
+
+function startTest(test, token) {
+  var onfetched = function(uri) {
+    var v = document.createElement('video');
+    v._token = token;
+    v.src = uri;
+    v.addEventListener("loadeddata", testWebM2, false);
+    document.body.appendChild(v);
+  }
+  manager.started(token);
+  fetch(test.name, onfetched);
+}
+
+SimpleTest.waitForExplicitFinish();
+manager.runTests(gCuelessWebMTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_cueless_webm_seek-3.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=657791
+-->
+<head>
+  <title>Test for Bug 657791</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=657791">Mozilla Bug 657791</a>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+// Subset of seek tests for cueless WebMs. When random seeking (rather than just
+// in buffered ranges) is implemented for WebM, these tests can be removed and
+// the cueless WebM(s) references can be moved to the general test_seek test
+// array.
+// Test array is defined in manifest.js
+
+var manager = new MediaTestManager;
+
+// Exercise functionality as in test_seek-3
+function testWebM3(e) {
+  var v = e.target;
+  v.removeEventListener('loadeddata', testWebM3);
+
+  var startPassed = false;
+  var completed = false;
+  var gotTimeupdate = false;
+
+  ok(v.buffered.length >= 1, "Should have a buffered range");
+  var halfBuffered = v.buffered.end(0) / 2;
+
+  function startTest() {
+    if (completed)
+      return;
+
+    is(v.seekable.start(0), v.buffered.start(0), "Seekable start should be buffered start");
+    is(v.seekable.end(0), v.buffered.end(0), "Seekable end should be buffered end");
+    v.currentTime=halfBuffered;
+  }
+
+  function timeupdate() {
+    gotTimeupdate = true;
+    v.removeEventListener("timeupdate", timeupdate, false);
+  }
+
+  function seekStarted() {
+    if (completed)
+      return;
+
+    v.addEventListener("timeupdate", timeupdate, false);
+    startPassed = true;
+  }
+
+  function seekEnded() {
+    if (completed)
+      return;
+
+    var t = v.currentTime;
+    ok(Math.abs(t - halfBuffered) <= 0.1, "Video currentTime should be around " + halfBuffered + ": " + t);
+    ok(gotTimeupdate, "Should have got timeupdate between seeking and seekended");
+    completed = true;
+    removeNodeAndSource(v);
+    manager.finished(v._token);
+  }
+
+  v.addEventListener("seeking", seekStarted, false);
+  v.addEventListener("seeked", seekEnded, false);
+
+  startTest()
+}
+
+// Fetch the media resource using XHR so we can be sure the entire
+// resource is loaded before we test buffered ranges. This ensures
+// we have deterministic behaviour.
+function fetch(url, fetched_callback) {
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", url, true);
+  xhr.responseType = "blob";
+
+  var loaded = function (event) {
+    if (xhr.status == 200 || xhr.status == 206) {
+      // Request fulfilled. Note sometimes we get 206... Presumably because either
+      // httpd.js or Necko cached the result.
+      fetched_callback(window.URL.createObjectURL(xhr.response));
+    } else {
+      ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders());
+    }
+  };
+
+  xhr.addEventListener("load", loaded, false);
+  xhr.send();
+}
+
+function startTest(test, token) {
+  var onfetched = function(uri) {
+    var v = document.createElement('video');
+    v._token = token;
+    v.src = uri;
+    v.addEventListener("loadeddata", testWebM3, false);
+    document.body.appendChild(v);
+  }
+  manager.started(token);
+  fetch(test.name, onfetched);
+}
+
+SimpleTest.waitForExplicitFinish();
+manager.runTests(gCuelessWebMTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
\ No newline at end of file
--- a/dom/media/webm/WebMBufferedParser.cpp
+++ b/dom/media/webm/WebMBufferedParser.cpp
@@ -327,26 +327,32 @@ bool WebMBufferedState::CalculateBuffere
   *aEndTime = mTimeMapping[end].mTimecode + frameDuration;
   return true;
 }
 
 bool WebMBufferedState::GetOffsetForTime(uint64_t aTime, int64_t* aOffset)
 {
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
+  if(mTimeMapping.IsEmpty()) {
+    return false;
+  }
+
   uint64_t time = aTime;
   if (time > 0) {
     time = time - 1;
   }
   uint32_t idx = mTimeMapping.IndexOfFirstElementGt(time, TimeComparator());
   if (idx == mTimeMapping.Length()) {
-    return false;
+    // Clamp to end
+    *aOffset = mTimeMapping[mTimeMapping.Length() - 1].mSyncOffset;
+  } else {
+    // Idx is within array or has been clamped to start
+    *aOffset = mTimeMapping[idx].mSyncOffset;
   }
-
-  *aOffset = mTimeMapping[idx].mSyncOffset;
   return true;
 }
 
 void WebMBufferedState::NotifyDataArrived(const unsigned char* aBuffer, uint32_t aLength, int64_t aOffset)
 {
   uint32_t idx = mRangeParsers.IndexOfFirstElementGt(aOffset - 1);
   if (idx == 0 || !(mRangeParsers[idx-1] == aOffset)) {
     // If the incoming data overlaps an already parsed range, adjust the
--- a/dom/media/webm/WebMBufferedParser.h
+++ b/dom/media/webm/WebMBufferedParser.h
@@ -274,19 +274,21 @@ public:
   }
 
   void NotifyDataArrived(const unsigned char* aBuffer, uint32_t aLength, int64_t aOffset);
   void Reset();
   void UpdateIndex(const MediaByteRangeSet& aRanges, MediaResource* aResource);
   bool CalculateBufferedForRange(int64_t aStartOffset, int64_t aEndOffset,
                                  uint64_t* aStartTime, uint64_t* aEndTime);
 
-  // Returns true if aTime is is present in mTimeMapping and sets aOffset to
+  // Returns true if mTimeMapping is not empty and sets aOffset to
   // the latest offset for which decoding can resume without data
-  // dependencies to arrive at aTime.
+  // dependencies to arrive at aTime. aTime will be clamped to the start
+  // of mTimeMapping if it is earlier than the first element, and to the end
+  // if later than the last
   bool GetOffsetForTime(uint64_t aTime, int64_t* aOffset);
 
   // Returns end offset of init segment or -1 if none found.
   int64_t GetInitEndOffset();
   // Returns the end offset of the last complete block or -1 if none found.
   int64_t GetLastBlockOffset();
 
   // Returns start time
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -429,16 +429,22 @@ WebMDemuxer::ReadMetadata()
 }
 
 bool
 WebMDemuxer::IsSeekable() const
 {
   return mContext && nestegg_has_cues(mContext);
 }
 
+bool
+WebMDemuxer::IsSeekableOnlyInBufferedRanges() const
+{
+  return mContext && !nestegg_has_cues(mContext);
+}
+
 void
 WebMDemuxer::EnsureUpToDateIndex()
 {
   if (!mNeedReIndex || !mInitData) {
     return;
   }
   AutoPinned<MediaResource> resource(mResource.GetResource());
   MediaByteRangeSet byteRanges;
--- a/dom/media/webm/WebMDemuxer.h
+++ b/dom/media/webm/WebMDemuxer.h
@@ -93,16 +93,18 @@ public:
 
   UniquePtr<TrackInfo> GetTrackInfo(TrackInfo::TrackType aType, size_t aTrackNumber) const;
 
   already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(TrackInfo::TrackType aType,
                                                       uint32_t aTrackNumber) override;
 
   bool IsSeekable() const override;
 
+  bool IsSeekableOnlyInBufferedRanges() const override;
+
   UniquePtr<EncryptionInfo> GetCrypto() override;
 
   bool GetOffsetForTime(uint64_t aTime, int64_t* aOffset);
 
   // Demux next WebM packet and append samples to MediaRawDataQueue
   bool GetNextPacket(TrackInfo::TrackType aType, MediaRawDataQueue *aSamples);
 
   nsresult Reset();
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -2360,18 +2360,18 @@ PluginModuleChromeParent::RecvNP_Initial
         }
     } else if (aError == NPERR_NO_ERROR) {
         // Initialization steps for (e10s && !asyncInit) || !e10s
 #if defined XP_WIN
         if (mIsStartingAsync) {
             SetPluginFuncs(mNPPIface);
         }
 
-        // Send the info needed to join the chrome process's audio session to the
-        // plugin process
+        // Send the info needed to join the browser process's audio session to the
+        // plugin process.
         nsID id;
         nsString sessionName;
         nsString iconPath;
 
         if (NS_SUCCEEDED(mozilla::widget::GetAudioSessionData(id, sessionName,
                                                               iconPath))) {
             Unused << SendSetAudioSessionData(id, sessionName, iconPath);
         }
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -793,19 +793,19 @@ var interfaceNamesInGlobalScope =
     "MouseEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MouseScrollEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozActivity", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozClirModeEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "mozContact",
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    "MozContactChangeEvent",
+    {name: "mozContact", b2g: true, permission: "contacts-read"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "MozContactChangeEvent", b2g: true, permission: "contacts-read"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MozCSSKeyframeRule",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MozCSSKeyframesRule",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozEmergencyCbModeEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozInputContext", b2g: true, permission: ["input"]},
@@ -821,17 +821,17 @@ var interfaceNamesInGlobalScope =
     {name: "MozMobileConnectionInfo", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozMobileNetworkInfo", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozNDEFRecord", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozOtaStatusEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "MozPowerManager",
+    {name: "MozPowerManager", b2g: true, permission: "power"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "mozRTCIceCandidate",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "mozRTCPeerConnection",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "mozRTCSessionDescription",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "MozSettingsEvent",
--- a/dom/webidl/AnimationEffectTiming.webidl
+++ b/dom/webidl/AnimationEffectTiming.webidl
@@ -7,24 +7,24 @@
  * https://w3c.github.io/web-animations/#animationeffecttiming
  *
  * Copyright © 2015 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 [Func="nsDocument::IsWebAnimationsEnabled"]
 interface AnimationEffectTiming : AnimationEffectTimingReadOnly {
-   //Bug 1244633 - implement AnimationEffectTiming delay
-   //inherit attribute double                             delay;
-   inherit attribute double                             endDelay;
-   //Bug 1244637 - implement AnimationEffectTiming fill
-   //inherit attribute FillMode                           fill;
-   //Bug 1244638 - implement AnimationEffectTiming iterationStart
-   //inherit attribute double                             iterationStart;
-   //Bug 1244640 - implement AnimationEffectTiming iterations
-   //inherit attribute unrestricted double                iterations;
+  //Bug 1244633 - implement AnimationEffectTiming delay
+  //inherit attribute double                             delay;
+  inherit attribute double                             endDelay;
+  //Bug 1244637 - implement AnimationEffectTiming fill
+  //inherit attribute FillMode                           fill;
+  [SetterThrows]
+  inherit attribute double                             iterationStart;
+  //Bug 1244640 - implement AnimationEffectTiming iterations
+  //inherit attribute unrestricted double                iterations;
   [SetterThrows]
   inherit attribute (unrestricted double or DOMString) duration;
-   //Bug 1244642 - implement AnimationEffectTiming direction
-   //inherit attribute PlaybackDirection                  direction;
-   //Bug 1244643 - implement AnimationEffectTiming easing
-   //inherit attribute DOMString                          easing;
+  //Bug 1244642 - implement AnimationEffectTiming direction
+  //inherit attribute PlaybackDirection                  direction;
+  //Bug 1244643 - implement AnimationEffectTiming easing
+  //inherit attribute DOMString                          easing;
 };
--- a/dom/webidl/Contacts.webidl
+++ b/dom/webidl/Contacts.webidl
@@ -53,17 +53,18 @@ dictionary ContactProperties {
   sequence<DOMString>?           category;
   sequence<DOMString>?           org;
   sequence<DOMString>?           jobTitle;
   sequence<DOMString>?           note;
   sequence<DOMString>?           key;
 };
 
 [Constructor(optional ContactProperties properties),
- JSImplementation="@mozilla.org/contact;1"]
+ JSImplementation="@mozilla.org/contact;1",
+ CheckAnyPermissions="contacts-read contacts-write contacts-create"]
 interface mozContact {
                  attribute DOMString  id;
         readonly attribute Date?      published;
         readonly attribute Date?      updated;
 
                  attribute Date?      bday;
                  attribute Date?      anniversary;
 
@@ -111,17 +112,18 @@ dictionary ContactFindSortOptions {
 dictionary ContactFindOptions : ContactFindSortOptions {
   DOMString      filterValue;  // e.g. "Tom"
   DOMString      filterOp;     // e.g. "startsWith"
   any            filterBy;     // e.g. ["givenName", "nickname"]
   unsigned long  filterLimit = 0;
 };
 
 [NoInterfaceObject, NavigatorProperty="mozContacts",
- JSImplementation="@mozilla.org/contactManager;1"]
+ JSImplementation="@mozilla.org/contactManager;1",
+ CheckAnyPermissions="contacts-read contacts-write contacts-create"]
 interface ContactManager : EventTarget {
   DOMRequest find(optional ContactFindOptions options);
   DOMCursor  getAll(optional ContactFindSortOptions options);
   DOMRequest clear();
   DOMRequest save(mozContact contact);
   DOMRequest remove((mozContact or DOMString) contactOrId);
   DOMRequest getRevision();
   DOMRequest getCount();
--- a/dom/webidl/MozContactChangeEvent.webidl
+++ b/dom/webidl/MozContactChangeEvent.webidl
@@ -1,15 +1,16 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-[Constructor(DOMString type, optional MozContactChangeEventInit eventInitDict)]
+[Constructor(DOMString type, optional MozContactChangeEventInit eventInitDict),
+ CheckAnyPermissions="contacts-read contacts-write contacts-create"]
 interface MozContactChangeEvent : Event
 {
   readonly attribute DOMString? contactID;
   readonly attribute DOMString? reason;
 };
 
 dictionary MozContactChangeEventInit : EventInit
 {
--- a/dom/webidl/MozPowerManager.webidl
+++ b/dom/webidl/MozPowerManager.webidl
@@ -16,16 +16,17 @@ enum FactoryResetReason {
     "normal",
     "wipe",
     "root"
 };
 
 /**
  * This interface implements navigator.mozPower
  */
+[CheckAnyPermissions="power"]
 interface MozPowerManager
 {
     [Throws]
     void    powerOff();
     [Throws]
     void    reboot();
     void    factoryReset(optional FactoryResetReason reason = "normal");
 
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -1434,26 +1434,16 @@ EmitHeapAddress(FunctionCompiler& f, MDe
     bool accessNeedsBoundsCheck = true;
     if (endOffset > f.mirGen().foldableOffsetRange(accessNeedsBoundsCheck)) {
         MDefinition* rhs = f.constant(Int32Value(offset), MIRType_Int32);
         *base = f.binary<MAdd>(*base, rhs, MIRType_Int32);
         offset = 0;
         access->setOffset(offset);
     }
 
-    // TODO Remove this after implementing unaligned loads/stores.
-    int32_t maskVal = ~(Scalar::byteSize(access->accessType()) - 1);
-    if (maskVal == -1)
-        return true;
-
-    offset &= maskVal;
-    access->setOffset(offset);
-
-    MDefinition* mask = f.constant(Int32Value(maskVal), MIRType_Int32);
-    *base = f.bitwise<MBitAnd>(*base, mask, MIRType_Int32);
     return true;
 }
 
 static bool
 EmitLoad(FunctionCompiler& f, Scalar::Type viewType, MDefinition** def)
 {
     MDefinition* base;
     MAsmJSHeapAccess access(viewType);
--- a/js/src/asmjs/WasmSignalHandlers.cpp
+++ b/js/src/asmjs/WasmSignalHandlers.cpp
@@ -725,16 +725,43 @@ EmulateHeapAccess(EMULATOR_CONTEXT* cont
           case Disassembler::HeapAccess::Unknown:
             MOZ_CRASH("Failed to disassemble instruction");
         }
     }
 
     return end;
 }
 
+#elif defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_UNALIGNED)
+
+MOZ_COLD static uint8_t*
+EmulateHeapAccess(EMULATOR_CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress,
+                  const HeapAccess* heapAccess, const Module& module)
+{
+    // TODO: Implement unaligned accesses.
+    return module.outOfBounds();
+}
+
+#endif // defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_UNALIGNED)
+
+#if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS)
+
+MOZ_COLD static bool
+IsHeapAccessAddress(const Module &module, uint8_t* faultingAddress)
+{
+#if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
+    size_t accessLimit = MappedSize;
+#elif defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_UNALIGNED)
+    size_t accessLimit = module.heapLength();
+#endif
+    return module.usesHeap() &&
+           faultingAddress >= module.heap() &&
+           faultingAddress < module.heap() + accessLimit;
+}
+
 #if defined(XP_WIN)
 
 static bool
 HandleFault(PEXCEPTION_POINTERS exception)
 {
     EXCEPTION_RECORD* record = exception->ExceptionRecord;
     CONTEXT* context = exception->ContextRecord;
 
@@ -754,25 +781,22 @@ HandleFault(PEXCEPTION_POINTERS exceptio
     AutoSetHandlingSegFault handling(rt);
 
     WasmActivation* activation = rt->wasmActivationStack();
     if (!activation)
         return false;
 
     const Module& module = activation->module();
 
-    // These checks aren't necessary, but, since we can, check anyway to make
+    uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(record->ExceptionInformation[1]);
+
+    // This check isn't necessary, but, since we can, check anyway to make
     // sure we aren't covering up a real bug.
-    uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(record->ExceptionInformation[1]);
-    if (!module.usesHeap() ||
-        faultingAddress < module.heap() ||
-        faultingAddress >= module.heap() + MappedSize)
-    {
+    if (!IsHeapAccessAddress(module, faultingAddress))
         return false;
-    }
 
     if (!module.containsFunctionPC(pc)) {
         // On Windows, it is possible for InterruptRunningCode to execute
         // between a faulting heap access and the handling of the fault due
         // to InterruptRunningCode's use of SuspendThread. When this happens,
         // after ResumeThread, the exception handler is called with pc equal to
         // module.interrupt, which is logically wrong. The Right Thing would
         // be for the OS to make fault-handling atomic (so that CONTEXT.pc was
@@ -899,25 +923,22 @@ HandleMachException(JSRuntime* rt, const
     WasmActivation* activation = rt->wasmActivationStack();
     if (!activation)
         return false;
 
     const Module& module = activation->module();
     if (!module.containsFunctionPC(pc))
         return false;
 
-    // These checks aren't necessary, but, since we can, check anyway to make
+    uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(request.body.code[1]);
+
+    // This check isn't necessary, but, since we can, check anyway to make
     // sure we aren't covering up a real bug.
-    uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(request.body.code[1]);
-    if (!module.usesHeap() ||
-        faultingAddress < module.heap() ||
-        faultingAddress >= module.heap() + MappedSize)
-    {
+    if (!IsHeapAccessAddress(module, faultingAddress))
         return false;
-    }
 
     const HeapAccess* heapAccess = module.lookupHeapAccess(pc);
     if (!heapAccess)
         return false;
 
     *ppc = EmulateHeapAccess(&context, pc, faultingAddress, heapAccess, module);
 
     // Update the thread state with the new pc and register values.
@@ -1109,25 +1130,22 @@ HandleFault(int signum, siginfo_t* info,
     WasmActivation* activation = rt->wasmActivationStack();
     if (!activation)
         return false;
 
     const Module& module = activation->module();
     if (!module.containsFunctionPC(pc))
         return false;
 
-    // These checks aren't necessary, but, since we can, check anyway to make
+    uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(info->si_addr);
+
+    // This check isn't necessary, but, since we can, check anyway to make
     // sure we aren't covering up a real bug.
-    uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(info->si_addr);
-    if (!module.usesHeap() ||
-        faultingAddress < module.heap() ||
-        faultingAddress >= module.heap() + MappedSize)
-    {
+    if (!IsHeapAccessAddress(module, faultingAddress))
         return false;
-    }
 
     const HeapAccess* heapAccess = module.lookupHeapAccess(pc);
     if (!heapAccess)
         return false;
 
     *ppc = EmulateHeapAccess(context, pc, faultingAddress, heapAccess, module);
 
     return true;
@@ -1157,17 +1175,17 @@ AsmJSFaultHandler(int signum, siginfo_t*
         sPrevSEGVHandler.sa_sigaction(signum, info, context);
     e