Bug 1216893 - Add in disabled namespace for SVG r=hsivonen,smaug
authorJonathan Kingston <jkt@mozilla.com>
Thu, 01 Dec 2016 07:41:22 +0000 (2016-12-01)
changeset 328589 0d48c28cc547dd6c4cd8a71b9e9c878319fbed31
parent 328588 5a792df3106ce6d7ca7c107d5197d53387a8cfd4
child 328590 83fbff91e9d2aec7ebc92c3c2017af39ebc49a36
push id31181
push usercbook@mozilla.com
push dateTue, 10 Jan 2017 11:09:50 +0000 (2017-01-10)
treeherdermozilla-central@acd4177b28d7 [default view] [failures only]
reviewershsivonen, smaug
bugs1216893
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1216893 - Add in disabled namespace for SVG r=hsivonen,smaug 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
@@ -2874,16 +2874,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");
+});