Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sun, 13 Mar 2016 19:08:03 -0400
changeset 288515 f0c0480732d36153e8839c7f17394d45f679f87d
parent 288478 985daf7c2e1ee1e08a005bf72fd8731c5f26a6bc (current diff)
parent 288514 080f495f9e41044e8de7cbd66909199e83b977cc (diff)
child 288516 2bef59a4476a99e0796bde9502c1bf0744f97182
child 288538 0ce67740545443ec87c0afd2d3d8b146dc517466
push id30082
push userryanvm@gmail.com
push dateSun, 13 Mar 2016 23:08:35 +0000
treeherdermozilla-central@f0c0480732d3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
first release with
nightly linux32
f0c0480732d3 / 48.0a1 / 20160314030215 / files
nightly linux64
f0c0480732d3 / 48.0a1 / 20160314030215 / files
nightly mac
f0c0480732d3 / 48.0a1 / 20160314030215 / files
nightly win32
f0c0480732d3 / 48.0a1 / 20160314030215 / files
nightly win64
f0c0480732d3 / 48.0a1 / 20160314030215 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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);
     else if (sPrevSEGVHandler.sa_handler == SIG_DFL || sPrevSEGVHandler.sa_handler == SIG_IGN)
         sigaction(signum, &sPrevSEGVHandler, nullptr);
     else
         sPrevSEGVHandler.sa_handler(signum);
 }
 #endif
 
-#endif // defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
+#endif // defined(ASMJS_MAY_USE_SIGNAL_HANDLERS)
 
 static void
 RedirectIonBackedgesToInterruptCheck(JSRuntime* rt)
 {
     if (jit::JitRuntime* jitRuntime = rt->jitRuntime()) {
         // If the backedge list is being mutated, the pc must be in C++ code and
         // thus not in a JIT iloop. We assume that the interrupt flag will be
         // checked at least once before entering JIT code (if not, no big deal;
@@ -1218,17 +1236,17 @@ JitInterruptHandler(int signum, siginfo_
         rt->finishHandlingJitInterrupt();
     }
 }
 #endif
 
 bool
 wasm::EnsureSignalHandlersInstalled(JSRuntime* rt)
 {
-#if defined(XP_DARWIN) && defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
+#if defined(XP_DARWIN) && defined(ASMJS_MAY_USE_SIGNAL_HANDLERS)
     // On OSX, each JSRuntime gets its own handler thread.
     if (!rt->wasmMachExceptionHandler.installed() && !rt->wasmMachExceptionHandler.install(rt))
         return false;
 #endif
 
     // All the rest of the handlers are process-wide and thus must only be
     // installed once. We assume that there are no races creating the first
     // JSRuntime of the process.
@@ -1275,19 +1293,19 @@ wasm::EnsureSignalHandlersInstalled(JSRu
     // doing to avoid problematic interference.
     if ((prev.sa_flags & SA_SIGINFO && prev.sa_sigaction) ||
         (prev.sa_handler != SIG_DFL && prev.sa_handler != SIG_IGN))
     {
         MOZ_CRASH("contention for interrupt signal");
     }
 #endif // defined(XP_WIN)
 
-#if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
+#if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS)
     // Install a SIGSEGV handler to handle safely-out-of-bounds asm.js heap
-    // access.
+    // access and/or unaligned accesses.
 # if defined(XP_WIN)
     if (!AddVectoredExceptionHandler(/* FirstHandler = */ true, AsmJSFaultHandler))
         return false;
 # elif defined(XP_DARWIN)
     // OSX handles seg faults via the Mach exception handler above, so don't
     // install AsmJSFaultHandler.
 # else
     // SA_NODEFER allows us to reenter the signal handler if we crash while
@@ -1295,17 +1313,17 @@ wasm::EnsureSignalHandlersInstalled(JSRu
     // handlingSegFault.
     struct sigaction faultHandler;
     faultHandler.sa_flags = SA_SIGINFO | SA_NODEFER;
     faultHandler.sa_sigaction = &AsmJSFaultHandler;
     sigemptyset(&faultHandler.sa_mask);
     if (sigaction(SIGSEGV, &faultHandler, &sPrevSEGVHandler))
         MOZ_CRASH("unable to install segv handler");
 # endif
-#endif // defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
+#endif // defined(ASMJS_MAY_USE_SIGNAL_HANDLERS)
 
     sResult = true;
     return true;
 }
 
 // JSRuntime::requestInterrupt sets interrupt_ (which is checked frequently by
 // C++ code at every Baseline JIT loop backedge) and jitStackLimit_ (which is
 // checked at every Baseline and Ion JIT function prologue). The remaining
@@ -1358,8 +1376,25 @@ js::InterruptRunningJitCode(JSRuntime* r
 #else
     // On Unix, we instead deliver an async signal to the main thread which
     // halts the thread and callers our JitInterruptHandler (which has already
     // been installed by EnsureSignalHandlersInstalled).
     pthread_t thread = (pthread_t)rt->ownerThreadNative();
     pthread_kill(thread, sInterruptSignal);
 #endif
 }
+
+MOZ_COLD bool
+js::wasm::IsPCInWasmCode(void *pc)
+{
+    JSRuntime* rt = RuntimeForCurrentThread();
+    if (!rt)
+        return false;
+
+    MOZ_RELEASE_ASSERT(!rt->handlingSegFault);
+
+    WasmActivation* activation = rt->