Bug 1216893 - Add in disabled namespace for SVG r?smaug r?hsivonen draft
authorJonathan Kingston <jkt@mozilla.com>
Thu, 01 Dec 2016 07:41:22 +0000
changeset 456867 afe4ad20d75c51bb3e57e24b6fe6831006a1630d
parent 456417 b548da4e16f067e5b69349376e37b2db97983cf7
child 541350 4ec96a814a9d852507bb268a7e59db9d3c893e71
push id40635
push userjkingston@mozilla.com
push dateFri, 06 Jan 2017 12:36:43 +0000
reviewerssmaug, hsivonen
bugs1216893
milestone53.0a1
Bug 1216893 - Add in disabled namespace for SVG r?smaug r?hsivonen MozReview-Commit-ID: 7Gum6wazraS
dom/base/NameSpaceConstants.h
dom/base/nsNameSpaceManager.cpp
dom/base/nsNameSpaceManager.h
dom/xml/nsXMLContentSink.cpp
layout/svg/moz.build
layout/svg/tests/chrome.ini
layout/svg/tests/mochitest.ini
layout/svg/tests/svg_example_test.html
layout/svg/tests/test_disabled.html
layout/svg/tests/test_disabled_chrome.html
modules/libpref/init/all.js
parser/html/nsHtml5DocumentBuilder.cpp
parser/html/nsHtml5TreeOperation.cpp
toolkit/components/contextualidentity/tests/unit/test_basic.html
--- a/dom/base/NameSpaceConstants.h
+++ b/dom/base/NameSpaceConstants.h
@@ -19,11 +19,12 @@ static const int32_t kNameSpaceID_None =
 #define kNameSpaceID_XLink    4
 #define kNameSpaceID_XSLT     5
 #define kNameSpaceID_XBL      6
 #define kNameSpaceID_MathML   7
 #define kNameSpaceID_RDF      8
 #define kNameSpaceID_XUL      9
 #define kNameSpaceID_SVG      10
 #define kNameSpaceID_disabled_MathML      11
-#define kNameSpaceID_LastBuiltin          11 // last 'built-in' namespace
+#define kNameSpaceID_disabled_SVG         12
+#define kNameSpaceID_LastBuiltin          12 // last 'built-in' namespace
 
 #endif // mozilla_dom_NameSpaceConstants_h__
--- a/dom/base/nsNameSpaceManager.cpp
+++ b/dom/base/nsNameSpaceManager.cpp
@@ -23,19 +23,21 @@
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/XBLChildrenElement.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+static const char* kPrefSVGDisabled = "svg.disabled";
 static const char* kPrefMathMLDisabled = "mathml.disabled";
 static const char* kObservedPrefs[] = {
   kPrefMathMLDisabled,
+  kPrefSVGDisabled,
   nullptr
 };
 StaticRefPtr<nsNameSpaceManager> nsNameSpaceManager::sInstance;
 
 /* static */ nsNameSpaceManager*
 nsNameSpaceManager::GetInstance() {
   if (!sInstance) {
     sInstance = new nsNameSpaceManager();
@@ -58,32 +60,34 @@ bool nsNameSpaceManager::Init()
   NS_ENSURE_SUCCESS(rv, false)
 
 #define REGISTER_DISABLED_NAMESPACE(uri, id) \
   rv = AddDisabledNameSpace(dont_AddRef(uri), id); \
   NS_ENSURE_SUCCESS(rv, false)
 
   mozilla::Preferences::AddStrongObservers(this, kObservedPrefs);
   mMathMLDisabled = mozilla::Preferences::GetBool(kPrefMathMLDisabled);
+  mSVGDisabled = mozilla::Preferences::GetBool(kPrefSVGDisabled);
 
 
   // Need to be ordered according to ID.
   MOZ_ASSERT(mURIArray.IsEmpty());
   REGISTER_NAMESPACE(nsGkAtoms::empty, kNameSpaceID_None);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xmlns, kNameSpaceID_XMLNS);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xml, kNameSpaceID_XML);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xhtml, kNameSpaceID_XHTML);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xlink, kNameSpaceID_XLink);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xslt, kNameSpaceID_XSLT);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xbl, kNameSpaceID_XBL);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_mathml, kNameSpaceID_MathML);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_rdf, kNameSpaceID_RDF);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_xul, kNameSpaceID_XUL);
   REGISTER_NAMESPACE(nsGkAtoms::nsuri_svg, kNameSpaceID_SVG);
   REGISTER_DISABLED_NAMESPACE(nsGkAtoms::nsuri_mathml, kNameSpaceID_disabled_MathML);
+  REGISTER_DISABLED_NAMESPACE(nsGkAtoms::nsuri_svg, kNameSpaceID_disabled_SVG);
 
 #undef REGISTER_NAMESPACE
 #undef REGISTER_DISABLED_NAMESPACE
 
   return true;
 }
 
 nsresult
@@ -146,19 +150,21 @@ int32_t
 nsNameSpaceManager::GetNameSpaceID(nsIAtom* aURI,
                                    bool aInChromeDoc)
 {
   if (aURI == nsGkAtoms::_empty) {
     return kNameSpaceID_None; // xmlns="", see bug 75700 for details
   }
 
   int32_t nameSpaceID;
-  if (mMathMLDisabled &&
-      mDisabledURIToIDTable.Get(aURI, &nameSpaceID) &&
-      !aInChromeDoc) {
+  if (!aInChromeDoc
+      && (mMathMLDisabled || mSVGDisabled)
+      && mDisabledURIToIDTable.Get(aURI, &nameSpaceID)
+      && ((mMathMLDisabled && kNameSpaceID_disabled_MathML == nameSpaceID) ||
+      (mSVGDisabled && kNameSpaceID_disabled_SVG == nameSpaceID))) {
     NS_POSTCONDITION(nameSpaceID >= 0, "Bogus namespace ID");
     return nameSpaceID;
   }
   if (mURIToIDTable.Get(aURI, &nameSpaceID)) {
     NS_POSTCONDITION(nameSpaceID >= 0, "Bogus namespace ID");
     return nameSpaceID;
   }
 
@@ -181,28 +187,59 @@ NS_NewElement(Element** aResult,
     return NS_NewXULElement(aResult, ni.forget());
   }
 #endif
   if (ns == kNameSpaceID_MathML) {
     // If the mathml.disabled pref. is true, convert all MathML nodes into
     // disabled MathML nodes by swapping the namespace.
     nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance();
     if ((nsmgr && !nsmgr->mMathMLDisabled) ||
-        nsContentUtils::IsChromeDoc(ni->GetDocument())) {
+        nsContentUtils::IsSystemPrincipal(ni->GetDocument()->NodePrincipal())) {
       return NS_NewMathMLElement(aResult, ni.forget());
     }
 
     RefPtr<mozilla::dom::NodeInfo> genericXMLNI =
       ni->NodeInfoManager()->
       GetNodeInfo(ni->NameAtom(), ni->GetPrefixAtom(),
         kNameSpaceID_disabled_MathML, ni->NodeType(), ni->GetExtraName());
     return NS_NewXMLElement(aResult, genericXMLNI.forget());
   }
   if (ns == kNameSpaceID_SVG) {
-    return NS_NewSVGElement(aResult, ni.forget(), aFromParser);
+    // If the svg.disabled pref. is true, convert all SVG nodes into
+    // disabled SVG nodes by swapping the namespace.
+    nsNameSpaceManager* nsmgr = nsNameSpaceManager::GetInstance();
+    nsCOMPtr<nsILoadInfo> loadInfo;
+    bool SVGEnabled = false;
+
+    if (nsmgr && !nsmgr->mSVGDisabled) {
+      SVGEnabled = true;
+    } else {
+      nsCOMPtr<nsIChannel> channel = ni->GetDocument()->GetChannel();
+      // We don't have a channel for SVGs constructed inside a SVG script
+      if (channel) {
+        loadInfo  = channel->GetLoadInfo();
+      }
+    }
+    if (SVGEnabled ||
+        nsContentUtils::IsSystemPrincipal(ni->GetDocument()->NodePrincipal()) ||
+        (loadInfo &&
+         (loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_IMAGE ||
+         loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_OTHER) &&
+         (nsContentUtils::IsSystemPrincipal(loadInfo->LoadingPrincipal()) ||
+          nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal())
+         )
+        )
+       ) {
+      return NS_NewSVGElement(aResult, ni.forget(), aFromParser);
+    }
+    RefPtr<mozilla::dom::NodeInfo> genericXMLNI =
+      ni->NodeInfoManager()->
+      GetNodeInfo(ni->NameAtom(), ni->GetPrefixAtom(),
+        kNameSpaceID_disabled_SVG, ni->NodeType(), ni->GetExtraName());
+    return NS_NewXMLElement(aResult, genericXMLNI.forget());
   }
   if (ns == kNameSpaceID_XBL && ni->Equals(nsGkAtoms::children)) {
     NS_ADDREF(*aResult = new XBLChildrenElement(ni.forget()));
     return NS_OK;
   }
 
   return NS_NewXMLElement(aResult, ni.forget());
 }
@@ -257,10 +294,11 @@ NS_IMPL_ISUPPORTS(nsNameSpaceManager,
                   nsIObserver)
 
 // nsIObserver
 NS_IMETHODIMP
 nsNameSpaceManager::Observe(nsISupports* aObject, const char* aTopic,
                             const char16_t* aMessage)
 {
   mMathMLDisabled = mozilla::Preferences::GetBool(kPrefMathMLDisabled);
+  mSVGDisabled = mozilla::Preferences::GetBool(kPrefSVGDisabled);
   return NS_OK;
 }
--- a/dom/base/nsNameSpaceManager.h
+++ b/dom/base/nsNameSpaceManager.h
@@ -61,16 +61,17 @@ public:
                          bool aInChromeDoc);
   int32_t GetNameSpaceID(nsIAtom* aURI,
                          bool aInChromeDoc);
 
   bool HasElementCreator(int32_t aNameSpaceID);
 
   static nsNameSpaceManager* GetInstance();
   bool mMathMLDisabled;
+  bool mSVGDisabled;
 
 private:
   bool Init();
   nsresult AddNameSpace(already_AddRefed<nsIAtom> aURI, const int32_t aNameSpaceID);
   nsresult AddDisabledNameSpace(already_AddRefed<nsIAtom> aURI, const int32_t aNameSpaceID);
   ~nsNameSpaceManager() {};
 
   nsDataHashtable<nsISupportsHashKey, int32_t> mURIToIDTable;
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -1054,16 +1054,19 @@ nsXMLContentSink::HandleEndElement(const
   // elements do not get pushed on the stack, the template
   // element content is pushed instead.
   bool isTemplateElement = debugTagAtom == nsGkAtoms::_template &&
                            debugNameSpaceID == kNameSpaceID_XHTML;
   NS_ASSERTION(content->NodeInfo()->Equals(debugTagAtom, debugNameSpaceID) ||
                (debugNameSpaceID == kNameSpaceID_MathML &&
                 content->NodeInfo()->NamespaceID() == kNameSpaceID_disabled_MathML &&
                 content->NodeInfo()->Equals(debugTagAtom)) ||
+               (debugNameSpaceID == kNameSpaceID_SVG &&
+                content->NodeInfo()->NamespaceID() == kNameSpaceID_disabled_SVG &&
+                content->NodeInfo()->Equals(debugTagAtom)) ||
                isTemplateElement, "Wrong element being closed");
 #endif
 
   result = CloseElement(content);
 
   if (mCurrentHead == content) {
     mCurrentHead = nullptr;
   }
--- a/layout/svg/moz.build
+++ b/layout/svg/moz.build
@@ -2,16 +2,24 @@
 # 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/.
 
 with Files('**'):
     BUG_COMPONENT = ('Core', 'SVG')
 
+if CONFIG['ENABLE_TESTS']:
+    MOCHITEST_MANIFESTS += [
+        'tests/mochitest.ini',
+]
+    MOCHITEST_CHROME_MANIFESTS += [
+        'tests/chrome.ini',
+]
+
 EXPORTS += [
     'nsFilterInstance.h',
     'nsSVGEffects.h',
     'nsSVGFilterInstance.h',
     'nsSVGForeignObjectFrame.h',
     'nsSVGIntegrationUtils.h',
     'nsSVGUtils.h',
     'SVGImageContext.h',
new file mode 100644
--- /dev/null
+++ b/layout/svg/tests/chrome.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+
+support-files =
+  svg_example_test.html
+
+[test_disabled_chrome.html]
new file mode 100644
--- /dev/null
+++ b/layout/svg/tests/mochitest.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_disabled.html]
new file mode 100644
--- /dev/null
+++ b/layout/svg/tests/svg_example_test.html
@@ -0,0 +1,7 @@
+<svg id="layout" viewBox="0 0 120 120" version="1.1"
+  xmlns="http://www.w3.org/2000/svg">
+  <circle cx="60" cy="60" r="50"/>
+</svg>
+
+<svg id="svgel">
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/svg/tests/test_disabled.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Copied from
+https://bugzilla.mozilla.org/show_bug.cgi?id=744830
+-->
+<head>
+  <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=166235">Mozilla Bug 166235</a>
+<div id="testnodes"><span>hi</span> there <!-- mon ami --></div>
+<pre id="test">
+<script type="application/javascript">
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [["svg.disabled", true]]}, doTest);
+  function doTest() {
+    let t = document.getElementById('testnodes');
+    t.innerHTML = null;
+    t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg:svg"));
+    t.firstChild.textContent = "<foo>";
+    is(t.innerHTML, "<svg:svg>&lt;foo&gt;</svg:svg>");
+
+    // This test crashes if the style tags are not handled correctly
+    t.innerHTML = `<svg version="1.1">
+      <style>
+          circle {
+              fill: currentColor;
+          }
+      </style>
+      <g><circle cx="25.8" cy="9.3" r="1.5"/></g>
+    </svg>
+    `;
+    is(t.firstChild.tagName.toLowerCase(), 'svg');
+
+    t.innerHTML = null;
+    t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg"));
+    is(t.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+    t.firstChild.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "script"));
+    is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+    t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+    is(t.innerHTML, '<svg><script>1&amp;2&lt;3&gt;4&nbsp;\u003C/script></svg>');
+
+    t.innerHTML = null;
+    t.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "svg"));
+    is(t.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+    t.firstChild.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "style"));
+    is(t.firstChild.firstChild.namespaceURI, "http://www.w3.org/2000/svg");
+    t.firstChild.firstChild.textContent = "1&2<3>4\xA0";
+    is(t.innerHTML, '<svg><style>1&amp;2&lt;3&gt;4&nbsp;\u003C/style></svg>');
+
+    SimpleTest.finish();
+  }
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/svg/tests/test_disabled_chrome.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=744830
+-->
+<head>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.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=166235">Mozilla Bug 166235</a>
+<div id="testnodes"><span>hi</span> there <!-- mon ami --></div>
+<pre id="test">
+<script type="application/javascript">
+  add_task(function* () {
+    const initialPrefValue = SpecialPowers.getBoolPref("svg.disabled");
+    SpecialPowers.setBoolPref("svg.disabled", true);
+    const Cu = SpecialPowers.Components.utils;
+    const { ContentTaskUtils } = Cu.import("resource://testing-common/ContentTaskUtils.jsm", {});
+    let t = document.getElementById('testnodes');
+
+    let url = 'chrome://mochitests/content/chrome/layout/svg/tests/svg_example_test.html'
+    const chromeIframeEl = document.createElement('iframe');
+    let chromeLoadPromise = ContentTaskUtils.waitForEvent(chromeIframeEl, 'load', false);
+    chromeIframeEl.src = url;
+    t.appendChild(chromeIframeEl);
+
+    yield chromeLoadPromise;
+    const chromeBR = chromeIframeEl.contentDocument.body.getBoundingClientRect();
+
+    url = "http://mochi.test:8888/chrome/layout/svg/tests/svg_example_test.html";
+    const iframeEl = document.createElement('iframe');
+    iframeEl.src = url;
+    let loadPromise = ContentTaskUtils.waitForEvent(iframeEl, 'load', false);
+    t.appendChild(iframeEl);
+    yield loadPromise;
+
+    const contentBR = iframeEl.contentDocument.body.getBoundingClientRect();
+
+yield new Promise(_ => setTimeout(_, 1000));
+    ok(chromeBR.height > contentBR.height, "Chrome content height should be bigger than content due to layout");
+
+    ok(!("hasExtension" in iframeEl.contentDocument.getElementById('svgel')), 'SVG is disabled so no hasExtension support is available in content iframe');
+    ok(chromeIframeEl.contentDocument.getElementById('svgel').hasExtension("http://www.w3.org/1998/Math/MathML"), 'SVG namespace support is enabled in chrome iframe');
+
+    SpecialPowers.setBoolPref("svg.disabled", initialPrefValue);
+  });
+</script>
+</pre>
+</body>
+</html>
+
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2876,16 +2876,19 @@ pref("dom.ipc.plugins.asyncInit.enabled"
 pref("dom.ipc.plugins.asyncdrawing.enabled", false);
 #else
 // Allow the AsyncDrawing mode to be used for plugins in dev channels.
 pref("dom.ipc.plugins.asyncdrawing.enabled", true);
 #endif
 
 pref("dom.ipc.processCount", 1);
 
+// Disable support for SVG
+pref("svg.disabled", false);
+
 // Override default dom.ipc.processCount for some remote content process types.
 pref("dom.ipc.processCount.webLargeAllocation", 2);
 
 // Pref to control whether we use separate content processes for top-level load
 // of file:// URIs.
 #if defined(NIGHTLY_BUILD)
 pref("browser.tabs.remote.separateFileUriProcess", true);
 #else
--- a/parser/html/nsHtml5DocumentBuilder.cpp
+++ b/parser/html/nsHtml5DocumentBuilder.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsHtml5DocumentBuilder.h"
 
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsStyleLinkElement.h"
 #include "nsScriptLoader.h"
 #include "nsIHTMLDocument.h"
+#include "nsNameSpaceManager.h"
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsHtml5DocumentBuilder, nsContentSink,
                                    mOwnedElements)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsHtml5DocumentBuilder)
 NS_INTERFACE_MAP_END_INHERITING(nsContentSink)
 
 NS_IMPL_ADDREF_INHERITED(nsHtml5DocumentBuilder, nsContentSink)
@@ -52,28 +53,31 @@ nsHtml5DocumentBuilder::SetDocumentChars
     mDocument->SetDocumentCharacterSetSource(aCharsetSource);
     mDocument->SetDocumentCharacterSet(aCharset);
   }
 }
 
 void
 nsHtml5DocumentBuilder::UpdateStyleSheet(nsIContent* aElement)
 {
+  nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(aElement));
+  if (!ssle) {
+    MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, "Node didn't QI to style, but SVG wasn't disabled.");
+    return;
+  }
+
   // Break out of the doc update created by Flush() to zap a runnable
   // waiting to call UpdateStyleSheet without the right observer
   EndDocUpdate();
 
   if (MOZ_UNLIKELY(!mParser)) {
     // EndDocUpdate ran stuff that called nsIParser::Terminate()
     return;
   }
 
-  nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(aElement));
-  NS_ASSERTION(ssle, "Node didn't QI to style.");
-
   ssle->SetEnableUpdates(true);
 
   bool willNotify;
   bool isAlternate;
   nsresult rv = ssle->UpdateStyleSheet(mRunsToCompletion ? nullptr : this,
                                        &willNotify,
                                        &isAlternate);
   if (NS_SUCCEEDED(rv) && willNotify && !isAlternate && !mRunsToCompletion) {
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -819,18 +819,21 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
     }
     case eTreeOpStreamEnded: {
       aBuilder->DidBuildModel(false); // this causes a notifications flush anyway
       return NS_OK;
     }
     case eTreeOpSetStyleLineNumber: {
       nsIContent* node = *(mOne.node);
       nsCOMPtr<nsIStyleSheetLinkingElement> ssle = do_QueryInterface(node);
-      NS_ASSERTION(ssle, "Node didn't QI to style.");
-      ssle->SetLineNumber(mFour.integer);
+      if (ssle) {
+        ssle->SetLineNumber(mFour.integer);
+      } else {
+        MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, "Node didn't QI to style, but SVG wasn't disabled.");
+      }
       return NS_OK;
     }
     case eTreeOpSetScriptLineNumberAndFreeze: {
       nsIContent* node = *(mOne.node);
       nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(node);
       NS_ASSERTION(sele, "Node didn't QI to script.");
       sele->SetScriptLineNumber(mFour.integer);
       sele->FreezeUriAsyncDefer();
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contextualidentity/tests/unit/test_basic.html
@@ -0,0 +1,71 @@
+"use strict";
+
+do_get_profile();
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
+
+const TEST_STORE_FILE_NAME = "test-containers.json";
+
+let cis;
+
+// Basic tests
+add_task(function() {
+  ok(!!ContextualIdentityService, "ContextualIdentityService exists");
+
+  cis = ContextualIdentityService.createNewInstanceForTesting(TEST_STORE_FILE_NAME);
+  ok(!!cis, "We have our instance of ContextualIdentityService");
+
+  equal(cis.getIdentities().length, 4, "By default, 4 containers.");
+  equal(cis.getIdentityFromId(0), null, "No identity with id 0");
+
+  ok(!!cis.getIdentityFromId(1), "Identity 1 exists");
+  ok(!!cis.getIdentityFromId(2), "Identity 2 exists");
+  ok(!!cis.getIdentityFromId(3), "Identity 3 exists");
+  ok(!!cis.getIdentityFromId(4), "Identity 4 exists");
+});
+
+// Create a new identity
+add_task(function() {
+  equal(cis.getIdentities().length, 4, "By default, 4 containers.");
+
+  let identity = cis.create("New Container", "Icon", "Color");
+  ok(!!identity, "New container created");
+  equal(identity.name, "New Container", "Name matches");
+  equal(identity.icon, "Icon", "Icon matches");
+  equal(identity.color, "Color", "Color matches");
+
+  equal(cis.getIdentities().length, 5, "Expected 5 containers.");
+
+  ok(!!cis.getIdentityFromId(identity.userContextId), "Identity exists");
+  equal(cis.getIdentityFromId(identity.userContextId).name, "New Container", "Identity name is OK");
+  equal(cis.getIdentityFromId(identity.userContextId).icon, "Icon", "Identity icon is OK");
+  equal(cis.getIdentityFromId(identity.userContextId).color, "Color", "Identity color is OK");
+  equal(cis.getUserContextLabel(identity.userContextId), "New Container", "Identity label is OK");
+});
+
+// Remove an identity
+add_task(function() {
+  equal(cis.getIdentities().length, 5, "Expected 5 containers.");
+
+  equal(cis.remove(-1), false, "cis.remove() returns false if identity doesn't exist.");
+  equal(cis.remove(1), true, "cis.remove() returns true if identity exists.");
+
+  equal(cis.getIdentities().length, 4, "Expected 4 containers.");
+});
+
+// Update an identity
+add_task(function() {
+  ok(!!cis.getIdentityFromId(2), "Identity 2 exists");
+
+  equal(cis.update(-1, "Container", "Icon", "Color"), false, "Update returns true if everything is OK");
+
+  equal(cis.update(2, "Container", "Icon", "Color"), true, "Update returns true if everything is OK");
+
+  ok(!!cis.getIdentityFromId(2), "Identity exists");
+  equal(cis.getIdentityFromId(2).name, "Container", "Identity name is OK");
+  equal(cis.getIdentityFromId(2).icon, "Icon", "Identity icon is OK");
+  equal(cis.getIdentityFromId(2).color, "Color", "Identity color is OK");
+  equal(cis.getUserContextLabel(2), "Container", "Identity label is OK");
+});