Bug 992096 - Implement Sub Resource Integrity [1/2]. r=baku,r=ckerschb
authorFrancois Marier <francois@mozilla.com>
Wed, 12 Aug 2015 20:19:11 -0700
changeset 290131 bab5913ea6cbb558457670e1785b0296cc13acc0
parent 290130 bd4a7f61176e9b5407ba2c8f6efa37f55decb23f
child 290132 948b2e9d1fa282b3acd35a67e6b020b3e68510c7
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, ckerschb
bugs992096
milestone43.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 992096 - Implement Sub Resource Integrity [1/2]. r=baku,r=ckerschb Code changes
AUTHORS
browser/devtools/webconsole/webconsole.js
dom/base/moz.build
dom/base/nsContentSink.cpp
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsGkAtomList.h
dom/base/nsIDocument.h
dom/base/nsScriptLoader.cpp
dom/base/nsScriptLoader.h
dom/base/nsStyleLinkElement.cpp
dom/html/HTMLLinkElement.cpp
dom/html/HTMLLinkElement.h
dom/html/HTMLScriptElement.cpp
dom/html/HTMLScriptElement.h
dom/locales/en-US/chrome/security/security.properties
dom/security/SRICheck.cpp
dom/security/SRICheck.h
dom/security/SRIMetadata.cpp
dom/security/SRIMetadata.h
dom/security/moz.build
dom/webidl/HTMLLinkElement.webidl
dom/webidl/HTMLScriptElement.webidl
layout/style/CSSStyleSheet.cpp
layout/style/CSSStyleSheet.h
layout/style/Loader.cpp
layout/style/Loader.h
modules/libpref/init/all.js
parser/html/nsHtml5SpeculativeLoad.cpp
parser/html/nsHtml5SpeculativeLoad.h
parser/html/nsHtml5TreeBuilderCppSupplement.h
parser/html/nsHtml5TreeOpExecutor.cpp
parser/html/nsHtml5TreeOpExecutor.h
xpcom/base/ErrorList.h
--- a/AUTHORS
+++ b/AUTHORS
@@ -354,16 +354,17 @@ Fernando Jimenez <ferjmoreno@gmail.com>
 Flock Inc.
 Florian Boesch <pyalot@gmail.com>
 Florian Hänel <heeen@gmx.de>
 Florian Queze <florian@queze.net>
 Florian Scholz <elchi3@elchi3.de>
 <flying@dom.natm.ru>
 France Telecom Research and Development
 Franck
+Francois Marier <francois@fmarier.org>
 Frank Tang <ftang@netscape.com>
 Frank Yan <fyan@mozilla.com>
 Franky Braem
 <franky@pacificconnections.com>
 Franz Sirl <Franz.Sirl-kernel@lauterbach.com>
 Frederic Plourde <frederic.plourde@polymtl.ca>
 Frederic Wang <fred.wang@free.fr>
 Fredrik Holmqvist <thesuckiestemail@yahoo.se>
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -4802,16 +4802,17 @@ var Utils = {
       case "Invalid HSTS Headers":
       case "Invalid HPKP Headers":
       case "SHA-1 Signature":
       case "Insecure Password Field":
       case "SSL":
       case "CORS":
       case "Iframe Sandbox":
       case "Tracking Protection":
+      case "Sub-resource Integrity":
         return CATEGORY_SECURITY;
 
       default:
         return CATEGORY_JS;
     }
   },
 
   /**
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -427,16 +427,17 @@ LOCAL_INCLUDES += [
     '/js/xpconnect/src',
     '/js/xpconnect/wrappers',
     '/layout/base',
     '/layout/generic',
     '/layout/style',
     '/layout/svg',
     '/layout/xul',
     '/netwerk/base',
+    '/security/manager/ssl',
     '/widget',
     '/xpcom/ds',
 ]
 
 if CONFIG['MOZ_B2G_BT_API_V1']:
     LOCAL_INCLUDES += [
         '../bluetooth/bluetooth1',
     ]
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -46,16 +46,26 @@
 #include "nsIWebNavigation.h"
 #include "nsGenericHTMLElement.h"
 #include "nsHTMLDNSPrefetch.h"
 #include "nsIObserverService.h"
 #include "mozilla/Preferences.h"
 #include "nsParserConstants.h"
 #include "nsSandboxFlags.h"
 
+static PRLogModuleInfo*
+GetSriLog()
+{
+  static PRLogModuleInfo *gSriPRLog;
+  if (!gSriPRLog) {
+    gSriPRLog = PR_NewLogModule("SRI");
+  }
+  return gSriPRLog;
+}
+
 using namespace mozilla;
 
 PRLogModuleInfo* gContentSinkLogModuleInfo;
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentSink)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsContentSink)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSink)
@@ -745,22 +755,33 @@ nsContentSink::ProcessStyleLink(nsIConte
     // The URI is bad, move along, don't propagate the error (for now)
     return NS_OK;
   }
 
   NS_ASSERTION(!aElement ||
                aElement->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE,
                "We only expect processing instructions here");
 
+  nsAutoString integrity;
+  if (aElement) {
+    aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
+  }
+  if (!integrity.IsEmpty()) {
+    MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+            ("nsContentSink::ProcessStyleLink, integrity=%s",
+             NS_ConvertUTF16toUTF8(integrity).get()));
+  }
+
   // If this is a fragment parser, we don't want to observe.
   // We don't support CORS for processing instructions
   bool isAlternate;
   rv = mCSSLoader->LoadStyleLink(aElement, url, aTitle, aMedia, aAlternate,
                                  CORS_NONE, mDocument->GetReferrerPolicy(),
-                                 mRunsToCompletion ? nullptr : this, &isAlternate);
+                                 integrity, mRunsToCompletion ? nullptr : this,
+                                 &isAlternate);
   NS_ENSURE_SUCCESS(rv, rv);
   
   if (!isAlternate && !mRunsToCompletion) {
     ++mPendingSheetCount;
     mScriptLoader->AddExecuteBlocker();
   }
 
   return NS_OK;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -9871,27 +9871,28 @@ public:
 };
 NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
 
 } // namespace
 
 void
 nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset,
                          const nsAString& aCrossOriginAttr,
-                         const ReferrerPolicy aReferrerPolicy)
+                         const ReferrerPolicy aReferrerPolicy,
+                         const nsAString& aIntegrity)
 {
   // The CSSLoader will retain this object after we return.
   nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
 
   // Charset names are always ASCII.
   CSSLoader()->LoadSheet(uri, NodePrincipal(),
                          NS_LossyConvertUTF16toASCII(charset),
                          obs,
                          Element::StringToCORSMode(aCrossOriginAttr),
-                         aReferrerPolicy);
+                         aReferrerPolicy, aIntegrity);
 }
 
 nsresult
 nsDocument::LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
                                 CSSStyleSheet** sheet)
 {
   return CSSLoader()->LoadSheetSync(uri, isAgentSheet, isAgentSheet, sheet);
 }
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1142,17 +1142,18 @@ public:
                                  ReferrerPolicy aReferrerPolicy) override;
   virtual void ForgetImagePreload(nsIURI* aURI) override;
 
   virtual void MaybePreconnect(nsIURI* uri,
                                mozilla::CORSMode aCORSMode) override;
 
   virtual void PreloadStyle(nsIURI* uri, const nsAString& charset,
                             const nsAString& aCrossOriginAttr,
-                            ReferrerPolicy aReferrerPolicy) override;
+                            ReferrerPolicy aReferrerPolicy,
+                            const nsAString& aIntegrity) override;
 
   virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
                                        mozilla::CSSStyleSheet** sheet) override;
 
   virtual nsISupports* GetCurrentContentSink() override;
 
   virtual mozilla::EventStates GetDocumentState() override;
 
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -484,16 +484,17 @@ GK_ATOM(input, "input")
 GK_ATOM(inputmode, "inputmode")
 GK_ATOM(ins, "ins")
 GK_ATOM(insertafter, "insertafter")
 GK_ATOM(insertbefore, "insertbefore")
 GK_ATOM(instanceOf, "instanceOf")
 GK_ATOM(int32, "int32")
 GK_ATOM(int64, "int64")
 GK_ATOM(integer, "integer")
+GK_ATOM(integrity, "integrity")
 GK_ATOM(intersection, "intersection")
 GK_ATOM(is, "is")
 GK_ATOM(iscontainer, "iscontainer")
 GK_ATOM(isempty, "isempty")
 GK_ATOM(ismap, "ismap")
 GK_ATOM(itemid, "itemid")
 GK_ATOM(itemprop, "itemprop")
 GK_ATOM(itemref, "itemref")
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -147,18 +147,18 @@ template<typename> class Sequence;
 
 template<typename, typename> class CallbackObjectHolder;
 typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
 
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID \
-{ 0xbbce44c8, 0x22fe, 0x404f, \
-  { 0x9e, 0x71, 0x23, 0x1d, 0xf4, 0xcc, 0x8e, 0x34 } }
+{ 0x6d18ec0b, 0x1f68, 0x4ae6, \
+  { 0x8b, 0x3d, 0x8d, 0x7d, 0x8b, 0x8e, 0x28, 0xd4 } }
 
 // Enum for requesting a particular type of document when creating a doc
 enum DocumentFlavor {
   DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant
   DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true
   DocumentFlavorSVG, // SVGDocument
   DocumentFlavorPlain, // Just a Document
 };
@@ -2018,17 +2018,18 @@ public:
 
   /**
    * Called by nsParser to preload style sheets.  Can also be merged into the
    * parser if and when the parser is merged with libgklayout.  aCrossOriginAttr
    * should be a void string if the attr is not present.
    */
   virtual void PreloadStyle(nsIURI* aURI, const nsAString& aCharset,
                             const nsAString& aCrossOriginAttr,
-                            ReferrerPolicyEnum aReferrerPolicy) = 0;
+                            ReferrerPolicyEnum aReferrerPolicy,
+                            const nsAString& aIntegrity) = 0;
 
   /**
    * Called by the chrome registry to load style sheets.  Can be put
    * back there if and when when that module is merged with libgklayout.
    *
    * This always does a synchronous load.  If aIsAgentSheet is true,
    * it also uses the system principal and enables unsafe rules.
    * DO NOT USE FOR UNTRUSTED CONTENT.
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -48,19 +48,31 @@
 #include "nsSandboxFlags.h"
 #include "nsContentTypeParser.h"
 #include "nsINetworkPredictor.h"
 #include "ImportManager.h"
 #include "mozilla/dom/EncodingUtils.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/unused.h"
+#include "mozilla/dom/SRICheck.h"
+#include "nsIScriptError.h"
 
 static PRLogModuleInfo* gCspPRLog;
 
+static PRLogModuleInfo*
+GetSriLog()
+{
+  static PRLogModuleInfo *gSriPRLog;
+  if (!gSriPRLog) {
+    gSriPRLog = PR_NewLogModule("SRI");
+  }
+  return gSriPRLog;
+}
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 // The nsScriptLoadRequest is passed as the context to necko, and thus
 // it needs to be threadsafe. Necko won't do anything with this
 // context, but it will AddRef and Release it on other threads.
 NS_IMPL_ISUPPORTS0(nsScriptLoadRequest)
 
@@ -601,17 +613,32 @@ nsScriptLoader::ProcessScriptElement(nsI
       } else {
         // Drop the preload
         request = nullptr;
       }
     }
 
     if (!request) {
       // no usable preload
-      request = new nsScriptLoadRequest(aElement, version, ourCORSMode);
+
+      SRIMetadata sriMetadata;
+      {
+        nsAutoString integrity;
+        scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
+                               integrity);
+        if (!integrity.IsEmpty()) {
+          MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+                 ("nsScriptLoader::ProcessScriptElement, integrity=%s",
+                  NS_ConvertUTF16toUTF8(integrity).get()));
+          SRICheck::IntegrityMetadata(integrity, mDocument, &sriMetadata);
+        }
+      }
+
+      request = new nsScriptLoadRequest(aElement, version, ourCORSMode,
+                                        sriMetadata);
       request->mURI = scriptURI;
       request->mIsInline = false;
       request->mLoading = true;
       request->mReferrerPolicy = ourRefPolicy;
 
       // set aScriptFromHead to false so we don't treat non preloaded scripts as
       // blockers for full page load. See bug 792438.
       rv = StartLoad(request, type, false);
@@ -715,17 +742,18 @@ nsScriptLoader::ProcessScriptElement(nsI
   }
 
   // Does CSP allow this inline script to run?
   if (!CSPAllowsInlineScript(aElement, mDocument)) {
     return false;
   }
 
   // Inline scripts ignore ther CORS mode and are always CORS_NONE
-  request = new nsScriptLoadRequest(aElement, version, CORS_NONE);
+  request = new nsScriptLoadRequest(aElement, version, CORS_NONE,
+                                    SRIMetadata()); // SRI doesn't apply
   request->mJSVersion = version;
   request->mLoading = false;
   request->mIsInline = true;
   request->mURI = mDocument->GetDocumentURI();
   request->mLineNo = aElement->GetScriptLineNumber();
 
   if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
       (!ReadyToExecuteScripts() || !mXSLTRequests.isEmpty())) {
@@ -1403,18 +1431,25 @@ nsScriptLoader::OnStreamComplete(nsIStre
                                  nsresult aStatus,
                                  uint32_t aStringLen,
                                  const uint8_t* aString)
 {
   nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
   NS_ASSERTION(request, "null request in stream complete handler");
   NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
 
-  nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
-                                     aString);
+  nsresult rv = NS_ERROR_SRI_CORRUPT;
+  if (request->mIntegrity.IsEmpty() ||
+      NS_SUCCEEDED(SRICheck::VerifyIntegrity(request->mIntegrity,
+                                             request->mURI,
+                                             request->mCORSMode, aStringLen,
+                                             aString, mDocument))) {
+    rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen, aString);
+  }
+
   if (NS_FAILED(rv)) {
     /*
      * Handle script not loading error because source was a tracking URL.
      * We make a note of this script node by including it in a dedicated
      * array of blocked tracking nodes under its parent document.
      */
     if (rv == NS_ERROR_TRACKING_URI) {
       nsCOMPtr<nsIContent> cont = do_QueryInterface(request->mElement);
@@ -1598,27 +1633,37 @@ nsScriptLoader::ParsingComplete(bool aTe
   // onload and all.
   ProcessPendingRequests();
 }
 
 void
 nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
                            const nsAString &aType,
                            const nsAString &aCrossOrigin,
+                           const nsAString& aIntegrity,
                            bool aScriptFromHead,
                            const mozilla::net::ReferrerPolicy aReferrerPolicy)
 {
   // Check to see if scripts has been turned off.
   if (!mEnabled || !mDocument->IsScriptEnabled()) {
     return;
   }
 
+  SRIMetadata sriMetadata;
+  if (!aIntegrity.IsEmpty()) {
+    MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+           ("nsScriptLoader::PreloadURI, integrity=%s",
+            NS_ConvertUTF16toUTF8(aIntegrity).get()));
+    SRICheck::IntegrityMetadata(aIntegrity, mDocument, &sriMetadata);
+  }
+
   nsRefPtr<nsScriptLoadRequest> request =
     new nsScriptLoadRequest(nullptr, 0,
-                            Element::StringToCORSMode(aCrossOrigin));
+                            Element::StringToCORSMode(aCrossOrigin),
+                            sriMetadata);
   request->mURI = aURI;
   request->mIsInline = false;
   request->mLoading = true;
   request->mReferrerPolicy = aReferrerPolicy;
 
   nsresult rv = StartLoad(request, aType, aScriptFromHead);
   if (NS_FAILED(rv)) {
     return;
--- a/dom/base/nsScriptLoader.h
+++ b/dom/base/nsScriptLoader.h
@@ -14,16 +14,17 @@
 #include "nsCOMPtr.h"
 #include "nsIScriptElement.h"
 #include "nsCOMArray.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "nsIDocument.h"
 #include "nsIStreamLoader.h"
 #include "mozilla/CORSMode.h"
+#include "mozilla/dom/SRIMetadata.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/net/ReferrerPolicy.h"
 
 class nsScriptLoadRequestList;
 class nsIURI;
 
 namespace JS {
   class SourceBufferHolder;
@@ -51,31 +52,33 @@ class nsScriptLoadRequest final : public
 
   // Allow LinkedListElement<nsScriptLoadRequest> to cast us to itself as needed.
   friend class mozilla::LinkedListElement<nsScriptLoadRequest>;
   friend class nsScriptLoadRequestList;
 
 public:
   nsScriptLoadRequest(nsIScriptElement* aElement,
                       uint32_t aVersion,
-                      mozilla::CORSMode aCORSMode)
+                      mozilla::CORSMode aCORSMode,
+                      const mozilla::dom::SRIMetadata &aIntegrity)
     : mElement(aElement),
       mLoading(true),
       mIsInline(true),
       mHasSourceMapURL(false),
       mIsDefer(false),
       mIsAsync(false),
       mIsNonAsyncScriptInserted(false),
       mIsXSLT(false),
       mIsCanceled(false),
       mScriptTextBuf(nullptr),
       mScriptTextLength(0),
       mJSVersion(aVersion),
       mLineNo(1),
       mCORSMode(aCORSMode),
+      mIntegrity(aIntegrity),
       mReferrerPolicy(mozilla::net::RP_Default)
   {
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   void FireScriptAvailable(nsresult aResult)
   {
@@ -117,16 +120,17 @@ public:
   char16_t* mScriptTextBuf; // Holds script text for non-inline scripts. Don't
   size_t mScriptTextLength; // use nsString so we can give ownership to jsapi.
   uint32_t mJSVersion;
   nsCOMPtr<nsIURI> mURI;
   nsCOMPtr<nsIPrincipal> mOriginPrincipal;
   nsAutoCString mURL;   // Keep the URI's filename alive during off thread parsing.
   int32_t mLineNo;
   const mozilla::CORSMode mCORSMode;
+  const mozilla::dom::SRIMetadata mIntegrity;
   mozilla::net::ReferrerPolicy mReferrerPolicy;
 };
 
 class nsScriptLoadRequestList : private mozilla::LinkedList<nsScriptLoadRequest>
 {
   typedef mozilla::LinkedList<nsScriptLoadRequest> super;
 
 public:
@@ -362,21 +366,23 @@ public:
   /**
    * Adds aURI to the preload list and starts loading it.
    *
    * @param aURI The URI of the external script.
    * @param aCharset The charset parameter for the script.
    * @param aType The type parameter for the script.
    * @param aCrossOrigin The crossorigin attribute for the script.
    *                     Void if not present.
+   * @param aIntegrity The expect hash url, if avail, of the request
    * @param aScriptFromHead Whether or not the script was a child of head
    */
   virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
                           const nsAString &aType,
                           const nsAString &aCrossOrigin,
+                          const nsAString& aIntegrity,
                           bool aScriptFromHead,
                           const mozilla::net::ReferrerPolicy aReferrerPolicy);
 
   /**
    * Process a request that was deferred so that the script could be compiled
    * off thread.
    */
   nsresult ProcessOffThreadRequest(nsScriptLoadRequest *aRequest,
--- a/dom/base/nsStyleLinkElement.cpp
+++ b/dom/base/nsStyleLinkElement.cpp
@@ -416,23 +416,31 @@ nsStyleLinkElement::DoUpdateStyleSheet(n
       return rv;
 
     // Parse the style sheet.
     rv = doc->CSSLoader()->
       LoadInlineStyle(thisContent, text, mLineNumber, title, media,
                       scopeElement, aObserver, &doneLoading, &isAlternate);
   }
   else {
+    nsAutoString integrity;
+    thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
+    if (!integrity.IsEmpty()) {
+      MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+              ("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s",
+               NS_ConvertUTF16toUTF8(integrity).get()));
+    }
+
     // XXXbz clone the URI here to work around content policies modifying URIs.
     nsCOMPtr<nsIURI> clonedURI;
     uri->Clone(getter_AddRefs(clonedURI));
     NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY);
     rv = doc->CSSLoader()->
       LoadStyleLink(thisContent, clonedURI, title, media, isAlternate,
-                    GetCORSMode(), doc->GetReferrerPolicy(),
+                    GetCORSMode(), doc->GetReferrerPolicy(), integrity,
                     aObserver, &isAlternate);
     if (NS_FAILED(rv)) {
       // Don't propagate LoadStyleLink() errors further than this, since some
       // consumers (e.g. nsXMLContentSink) will completely abort on innocuous
       // things like a stylesheet load being blocked by the security system.
       doneLoading = true;
       isAlternate = false;
       rv = NS_OK;
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -209,16 +209,21 @@ HTMLLinkElement::ParseAttribute(int32_t 
       ParseCORSValue(aValue, aResult);
       return true;
     }
 
     if (aAttribute == nsGkAtoms::sizes) {
       aResult.ParseAtomArray(aValue);
       return true;
     }
+
+    if (aAttribute == nsGkAtoms::integrity) {
+      aResult.ParseStringOrAtom(aValue);
+      return true;
+    }
   }
 
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 void
 HTMLLinkElement::CreateAndDispatchEvent(nsIDocument* aDoc,
--- a/dom/html/HTMLLinkElement.h
+++ b/dom/html/HTMLLinkElement.h
@@ -138,16 +138,24 @@ public:
   {
     SetHTMLAttr(nsGkAtoms::rev, aRev, aRv);
   }
   // XPCOM GetTarget is fine.
   void SetTarget(const nsAString& aTarget, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::target, aTarget, aRv);
   }
+  void GetIntegrity(nsAString& aIntegrity) const
+  {
+    GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
+  }
+  void SetIntegrity(const nsAString& aIntegrity, ErrorResult& aRv)
+  {
+    SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, aRv);
+  }
 
   already_AddRefed<nsIDocument> GetImport();
   already_AddRefed<ImportLoader> GetImportLoader()
   {
     return nsRefPtr<ImportLoader>(mImportLoader).forget();
   }
 
 protected:
--- a/dom/html/HTMLScriptElement.cpp
+++ b/dom/html/HTMLScriptElement.cpp
@@ -70,20 +70,26 @@ HTMLScriptElement::BindToTree(nsIDocumen
 }
 
 bool
 HTMLScriptElement::ParseAttribute(int32_t aNamespaceID,
                                   nsIAtom* aAttribute,
                                   const nsAString& aValue,
                                   nsAttrValue& aResult)
 {
-  if (aNamespaceID == kNameSpaceID_None &&
-      aAttribute == nsGkAtoms::crossorigin) {
-    ParseCORSValue(aValue, aResult);
-    return true;
+  if (aNamespaceID == kNameSpaceID_None) {
+    if (aAttribute == nsGkAtoms::crossorigin) {
+      ParseCORSValue(aValue, aResult);
+      return true;
+    }
+
+    if (aAttribute == nsGkAtoms::integrity) {
+      aResult.ParseStringOrAtom(aValue);
+      return true;
+    }
   }
 
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 nsresult
 HTMLScriptElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const
--- a/dom/html/HTMLScriptElement.h
+++ b/dom/html/HTMLScriptElement.h
@@ -74,16 +74,24 @@ public:
     // always parse to an enum value, so we don't need an invalid
     // default, and we _want_ the missing default to be null.
     GetEnumAttr(nsGkAtoms::crossorigin, nullptr, aResult);
   }
   void SetCrossOrigin(const nsAString& aCrossOrigin, ErrorResult& aError)
   {
     SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
   }
+  void GetIntegrity(nsAString& aIntegrity)
+  {
+    GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
+  }
+  void SetIntegrity(const nsAString& aIntegrity, ErrorResult& rv)
+  {
+    SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, rv);
+  }
   bool Async();
   void SetAsync(bool aValue, ErrorResult& rv);
 
 protected:
   virtual ~HTMLScriptElement();
 
   virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
   // nsScriptElement
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -48,12 +48,27 @@ InsecurePasswordsPresentOnPage=Password 
 InsecureFormActionPasswordsPresent=Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen.
 InsecurePasswordsPresentOnIframe=Password fields present on an insecure (http://) iframe. This is a security risk that allows user login credentials to be stolen.
 # LOCALIZATION NOTE: "%1$S" is the URI of the insecure mixed content resource
 LoadingMixedActiveContent2=Loading mixed (insecure) active content "%1$S" on a secure page
 LoadingMixedDisplayContent2=Loading mixed (insecure) display content "%1$S" on a secure page
 # LOCALIZATION NOTE: Do not translate "allow-scripts", "allow-same-origin", "sandbox" or "iframe"
 BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing.
 
+# Sub-Resource Integrity
+# LOCALIZATION NOTE: Do not translate "script" or "integrity"
+MalformedIntegrityURI=The script element has a malformed URI in its integrity attribute: "%1$S". The correct format is "<hash algorithm>-<hash value>".
+# LOCALIZATION NOTE: Do not translate "integrity"
+InvalidIntegrityLength=The hash contained in the integrity attribute has the wrong length.
+# LOCALIZATION NOTE: Do not translate "integrity"
+InvalidIntegrityBase64=The hash contained in the integrity attribute could not be decoded.
+# LOCALIZATION NOTE: Do not translate "integrity"
+IntegrityMismatch=None of the "%1$S" hashes in the integrity attribute match the content of the subresource.
+IneligibleResource="%1$S" is not eligible for integrity checks since it's neither CORS-enabled nor same-origin.
+# LOCALIZATION NOTE: Do not translate "integrity"
+UnsupportedHashAlg=Unsupported hash algorithm in the integrity attribute: "%1$S"
+# LOCALIZATION NOTE: Do not translate "integrity"
+NoValidMetadata=The integrity attribute does not contain any valid metadata.
+
 # LOCALIZATION NOTE: Do not translate "SSL 3.0".
 WeakProtocolVersionWarning=This site uses the protocol SSL 3.0 for encryption, which is deprecated and insecure.
 # LOCALIZATION NOTE: Do not translate "RC4".
 WeakCipherSuiteWarning=This site uses the cipher RC4 for encryption, which is deprecated and insecure.
new file mode 100644
--- /dev/null
+++ b/dom/security/SRICheck.cpp
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "SRICheck.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+#include "nsICryptoHash.h"
+#include "nsIDocument.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsWhitespaceTokenizer.h"
+
+static PRLogModuleInfo*
+GetSriLog()
+{
+  static PRLogModuleInfo *gSriPRLog;
+  if (!gSriPRLog) {
+    gSriPRLog = PR_NewLogModule("SRI");
+  }
+  return gSriPRLog;
+}
+
+#define SRILOG(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug, args)
+#define SRIERROR(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Error, args)
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Returns whether or not the sub-resource about to be loaded is eligible
+ * for integrity checks. If it's not, the checks will be skipped and the
+ * sub-resource will be loaded.
+ */
+static nsresult
+IsEligible(nsIURI* aRequestURI, const CORSMode aCORSMode,
+           const nsIDocument* aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aRequestURI);
+  NS_ENSURE_ARG_POINTER(aDocument);
+
+  nsAutoCString requestSpec;
+  nsresult rv = aRequestURI->GetSpec(requestSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ConvertUTF8toUTF16 requestSpecUTF16(requestSpec);
+
+  // Was the sub-resource loaded via CORS?
+  if (aCORSMode != CORS_NONE) {
+    SRILOG(("SRICheck::IsEligible, CORS mode"));
+    return NS_OK;
+  }
+
+  // Is the sub-resource same-origin?
+  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+  if (NS_SUCCEEDED(ssm->CheckSameOriginURI(aDocument->GetDocumentURI(),
+                                           aRequestURI, false))) {
+    SRILOG(("SRICheck::IsEligible, same-origin"));
+    return NS_OK;
+  }
+  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+    nsAutoCString documentURI;
+    aDocument->GetDocumentURI()->GetAsciiSpec(documentURI);
+    // documentURI will be empty if GetAsciiSpec failed
+    SRILOG(("SRICheck::IsEligible, NOT same origin: documentURI=%s; requestURI=%s",
+            documentURI.get(), requestSpec.get()));
+  }
+
+  const char16_t* params[] = { requestSpecUTF16.get() };
+  nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                  NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                  aDocument,
+                                  nsContentUtils::eSECURITY_PROPERTIES,
+                                  "IneligibleResource",
+                                  params, ArrayLength(params));
+  return NS_ERROR_SRI_NOT_ELIGIBLE;
+}
+
+/**
+ * Compute the hash of a sub-resource and compare it with the expected
+ * value.
+ */
+static nsresult
+VerifyHash(const SRIMetadata& aMetadata, uint32_t aHashIndex,
+           uint32_t aStringLen, const uint8_t* aString,
+           const nsIDocument* aDocument)
+{
+  NS_ENSURE_ARG_POINTER(aString);
+  NS_ENSURE_ARG_POINTER(aDocument);
+
+  nsAutoCString base64Hash;
+  aMetadata.GetHash(aHashIndex, &base64Hash);
+  SRILOG(("SRICheck::VerifyHash, hash[%u]=%s", aHashIndex, base64Hash.get()));
+
+  nsAutoCString binaryHash;
+  if (NS_WARN_IF(NS_FAILED(Base64Decode(base64Hash, binaryHash)))) {
+    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                    aDocument,
+                                    nsContentUtils::eSECURITY_PROPERTIES,
+                                    "InvalidIntegrityBase64");
+    return NS_ERROR_SRI_CORRUPT;
+  }
+
+  uint32_t hashLength;
+  int8_t hashType;
+  aMetadata.GetHashType(&hashType, &hashLength);
+  if (binaryHash.Length() != hashLength) {
+    nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                    aDocument,
+                                    nsContentUtils::eSECURITY_PROPERTIES,
+                                    "InvalidIntegrityLength");
+    return NS_ERROR_SRI_CORRUPT;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsICryptoHash> cryptoHash =
+    do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = cryptoHash->Init(hashType);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = cryptoHash->Update(aString, aStringLen);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString computedHash;
+  rv = cryptoHash->Finish(false, computedHash);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (!binaryHash.Equals(computedHash)) {
+    SRILOG(("SRICheck::VerifyHash, hash[%u] did not match", aHashIndex));
+    return NS_ERROR_SRI_CORRUPT;
+  }
+
+  SRILOG(("SRICheck::VerifyHash, hash[%u] verified successfully", aHashIndex));
+  return NS_OK;
+}
+
+/* static */ nsresult
+SRICheck::IntegrityMetadata(const nsAString& aMetadataList,
+                            const nsIDocument* aDocument,
+                            SRIMetadata* outMetadata)
+{
+  NS_ENSURE_ARG_POINTER(outMetadata);
+  NS_ENSURE_ARG_POINTER(aDocument);
+  MOZ_ASSERT(outMetadata->IsEmpty()); // caller must pass empty metadata
+
+  if (!Preferences::GetBool("security.sri.enable", false)) {
+    SRILOG(("SRICheck::IntegrityMetadata, sri is disabled (pref)"));
+    return NS_ERROR_SRI_DISABLED;
+  }
+
+  // put a reasonable bound on the length of the metadata
+  NS_ConvertUTF16toUTF8 metadataList(aMetadataList);
+  if (metadataList.Length() > SRICheck::MAX_METADATA_LENGTH) {
+    metadataList.Truncate(SRICheck::MAX_METADATA_LENGTH);
+  }
+  MOZ_ASSERT(metadataList.Length() <= aMetadataList.Length());
+
+  // the integrity attribute is a list of whitespace-separated hashes
+  // and options so we need to look at them one by one and pick the
+  // strongest (valid) one
+  nsCWhitespaceTokenizer tokenizer(metadataList);
+  nsAutoCString token;
+  for (uint32_t i=0; tokenizer.hasMoreTokens() &&
+         i < SRICheck::MAX_METADATA_TOKENS; ++i) {
+    token = tokenizer.nextToken();
+
+    SRIMetadata metadata(token);
+    if (metadata.IsMalformed()) {
+      NS_ConvertUTF8toUTF16 tokenUTF16(token);
+      const char16_t* params[] = { tokenUTF16.get() };
+      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                      NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                      aDocument,
+                                      nsContentUtils::eSECURITY_PROPERTIES,
+                                      "MalformedIntegrityURI",
+                                      params, ArrayLength(params));
+    } else if (!metadata.IsAlgorithmSupported()) {
+      nsAutoCString alg;
+      metadata.GetAlgorithm(&alg);
+      NS_ConvertUTF8toUTF16 algUTF16(alg);
+      const char16_t* params[] = { algUTF16.get() };
+      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                      NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                      aDocument,
+                                      nsContentUtils::eSECURITY_PROPERTIES,
+                                      "UnsupportedHashAlg",
+                                      params, ArrayLength(params));
+    }
+
+    nsAutoCString alg1, alg2;
+    if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+      outMetadata->GetAlgorithm(&alg1);
+      metadata.GetAlgorithm(&alg2);
+    }
+    if (*outMetadata == metadata) {
+      SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is the same as '%s'",
+              alg1.get(), alg2.get()));
+      *outMetadata += metadata; // add new hash to strongest metadata
+    } else if (*outMetadata < metadata) {
+      SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is weaker than '%s'",
+              alg1.get(), alg2.get()));
+      *outMetadata = metadata; // replace strongest metadata with current
+    }
+  }
+
+  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+    if (outMetadata->IsValid()) {
+      nsAutoCString alg;
+      outMetadata->GetAlgorithm(&alg);
+      SRILOG(("SRICheck::IntegrityMetadata, using a '%s' hash", alg.get()));
+    } else if (outMetadata->IsEmpty()) {
+      SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
+    } else {
+      SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
+    }
+  }
+  return NS_OK;
+}
+
+/* static */ nsresult
+SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
+                          nsIURI* aRequestURI,
+                          const CORSMode aCORSMode,
+                          const nsAString& aString,
+                          const nsIDocument* aDocument)
+{
+  NS_ConvertUTF16toUTF8 utf8Hash(aString);
+  return VerifyIntegrity(aMetadata, aRequestURI, aCORSMode, utf8Hash.Length(),
+                         (uint8_t*)utf8Hash.get(), aDocument);
+}
+
+/* static */ nsresult
+SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
+                          nsIURI* aRequestURI,
+                          const CORSMode aCORSMode,
+                          uint32_t aStringLen,
+                          const uint8_t* aString,
+                          const nsIDocument* aDocument)
+{
+  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+    nsAutoCString requestURL;
+    aRequestURI->GetAsciiSpec(requestURL);
+    // requestURL will be empty if GetAsciiSpec fails
+    SRILOG(("SRICheck::VerifyIntegrity, url=%s (length=%u)",
+            requestURL.get(), aStringLen));
+  }
+
+  MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
+
+  // IntegrityMetadata() checks this and returns "no metadata" if
+  // it's disabled so we should never make it this far
+  MOZ_ASSERT(Preferences::GetBool("security.sri.enable", false));
+
+  if (NS_FAILED(IsEligible(aRequestURI, aCORSMode, aDocument))) {
+    return NS_OK; // ignore non-CORS resources for forward-compatibility
+  }
+  if (!aMetadata.IsValid()) {
+    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                    NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                    aDocument,
+                                    nsContentUtils::eSECURITY_PROPERTIES,
+                                    "NoValidMetadata");
+    return NS_OK; // ignore invalid metadata for forward-compatibility
+  }
+
+  for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
+    if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aStringLen,
+                                aString, aDocument))) {
+      return NS_OK; // stop at the first valid hash
+    }
+  }
+
+  nsAutoCString alg;
+  aMetadata.GetAlgorithm(&alg);
+  NS_ConvertUTF8toUTF16 algUTF16(alg);
+  const char16_t* params[] = { algUTF16.get() };
+  nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+                                  NS_LITERAL_CSTRING("Sub-resource Integrity"),
+                                  aDocument,
+                                  nsContentUtils::eSECURITY_PROPERTIES,
+                                  "IntegrityMismatch",
+                                  params, ArrayLength(params));
+  return NS_ERROR_SRI_CORRUPT;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/security/SRICheck.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_SRICheck_h
+#define mozilla_dom_SRICheck_h
+
+#include "mozilla/CORSMode.h"
+#include "nsCOMPtr.h"
+#include "SRIMetadata.h"
+
+class nsIDocument;
+class nsIHttpChannel;
+class nsIScriptSecurityManager;
+class nsIStreamLoader;
+class nsIURI;
+
+namespace mozilla {
+namespace dom {
+
+class SRICheck final
+{
+public:
+  static const uint32_t MAX_METADATA_LENGTH = 24*1024;
+  static const uint32_t MAX_METADATA_TOKENS = 512;
+
+  /**
+   * Parse the multiple hashes specified in the integrity attribute and
+   * return the strongest supported hash.
+   */
+  static nsresult IntegrityMetadata(const nsAString& aMetadataList,
+                                    const nsIDocument* aDocument,
+                                    SRIMetadata* outMetadata);
+
+  /**
+   * Process the integrity attribute of the element.  A result of false
+   * must prevent the resource from loading.
+   */
+  static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
+                                  nsIURI* aRequestURI,
+                                  const CORSMode aCORSMode,
+                                  const nsAString& aString,
+                                  const nsIDocument* aDocument);
+
+  /**
+   * Process the integrity attribute of the element.  A result of false
+   * must prevent the resource from loading.
+   */
+  static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
+                                  nsIURI* aRequestURI,
+                                  const CORSMode aCORSMode,
+                                  uint32_t aStringLen,
+                                  const uint8_t* aString,
+                                  const nsIDocument* aDocument);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SRICheck_h
new file mode 100644
--- /dev/null
+++ b/dom/security/SRIMetadata.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "SRIMetadata.h"
+
+#include "hasht.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/Logging.h"
+#include "nsICryptoHash.h"
+
+static PRLogModuleInfo*
+GetSriMetadataLog()
+{
+  static PRLogModuleInfo *gSriMetadataPRLog;
+  if (!gSriMetadataPRLog) {
+    gSriMetadataPRLog = PR_NewLogModule("SRIMetadata");
+  }
+  return gSriMetadataPRLog;
+}
+
+#define SRIMETADATALOG(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Debug, args)
+#define SRIMETADATAERROR(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Error, args)
+
+namespace mozilla {
+namespace dom {
+
+SRIMetadata::SRIMetadata(const nsACString& aToken)
+  : mAlgorithmType(SRIMetadata::UNKNOWN_ALGORITHM), mEmpty(false)
+{
+  MOZ_ASSERT(!aToken.IsEmpty()); // callers should check this first
+
+  SRIMETADATALOG(("SRIMetadata::SRIMetadata, aToken='%s'",
+                  PromiseFlatCString(aToken).get()));
+
+  int32_t hyphen = aToken.FindChar('-');
+  if (hyphen == -1) {
+    SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (no hyphen)"));
+    return; // invalid metadata
+  }
+
+  // split the token into its components
+  mAlgorithm = Substring(aToken, 0, hyphen);
+  uint32_t hashStart = hyphen + 1;
+  if (hashStart >= aToken.Length()) {
+    SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (missing digest)"));
+    return; // invalid metadata
+  }
+  int32_t question = aToken.FindChar('?');
+  if (question == -1) {
+    mHashes.AppendElement(Substring(aToken, hashStart,
+                                    aToken.Length() - hashStart));
+  } else {
+    MOZ_ASSERT(question > 0);
+    if (static_cast<uint32_t>(question) <= hashStart) {
+      SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (options w/o digest)"));
+      return; // invalid metadata
+    }
+    mHashes.AppendElement(Substring(aToken, hashStart,
+                                    question - hashStart));
+  }
+
+  if (mAlgorithm.EqualsLiteral("sha256")) {
+    mAlgorithmType = nsICryptoHash::SHA256;
+  } else if (mAlgorithm.EqualsLiteral("sha384")) {
+    mAlgorithmType = nsICryptoHash::SHA384;
+  } else if (mAlgorithm.EqualsLiteral("sha512")) {
+    mAlgorithmType = nsICryptoHash::SHA512;
+  }
+
+  SRIMETADATALOG(("SRIMetadata::SRIMetadata, hash='%s'; alg='%s'",
+                  mHashes[0].get(), mAlgorithm.get()));
+}
+
+bool
+SRIMetadata::operator<(const SRIMetadata& aOther) const
+{
+  static_assert(nsICryptoHash::SHA256 < nsICryptoHash::SHA384,
+                "We rely on the order indicating relative alg strength");
+  static_assert(nsICryptoHash::SHA384 < nsICryptoHash::SHA512,
+                "We rely on the order indicating relative alg strength");
+  MOZ_ASSERT(mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
+             mAlgorithmType == nsICryptoHash::SHA256 ||
+             mAlgorithmType == nsICryptoHash::SHA384 ||
+             mAlgorithmType == nsICryptoHash::SHA512);
+  MOZ_ASSERT(aOther.mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
+             aOther.mAlgorithmType == nsICryptoHash::SHA256 ||
+             aOther.mAlgorithmType == nsICryptoHash::SHA384 ||
+             aOther.mAlgorithmType == nsICryptoHash::SHA512);
+
+  if (mEmpty) {
+    SRIMETADATALOG(("SRIMetadata::operator<, first metadata is empty"));
+    return true; // anything beats the empty metadata (incl. invalid ones)
+  }
+
+  SRIMETADATALOG(("SRIMetadata::operator<, alg1='%d'; alg2='%d'",
+                  mAlgorithmType, aOther.mAlgorithmType));
+  return (mAlgorithmType < aOther.mAlgorithmType);
+}
+
+bool
+SRIMetadata::operator>(const SRIMetadata& aOther) const
+{
+  MOZ_ASSERT(false);
+  return false;
+}
+
+SRIMetadata&
+SRIMetadata::operator+=(const SRIMetadata& aOther)
+{
+  MOZ_ASSERT(!aOther.IsEmpty() && !IsEmpty());
+  MOZ_ASSERT(aOther.IsValid() && IsValid());
+  MOZ_ASSERT(mAlgorithmType == aOther.mAlgorithmType);
+
+  // We only pull in the first element of the other metadata
+  MOZ_ASSERT(aOther.mHashes.Length() == 1);
+  if (mHashes.Length() < SRIMetadata::MAX_ALTERNATE_HASHES) {
+    SRIMETADATALOG(("SRIMetadata::operator+=, appending another '%s' hash (new length=%d)",
+                    mAlgorithm.get(), mHashes.Length()));
+    mHashes.AppendElement(aOther.mHashes[0]);
+  }
+
+  MOZ_ASSERT(mHashes.Length() > 1);
+  MOZ_ASSERT(mHashes.Length() <= SRIMetadata::MAX_ALTERNATE_HASHES);
+  return *this;
+}
+
+bool
+SRIMetadata::operator==(const SRIMetadata& aOther) const
+{
+  if (IsEmpty() || !IsValid()) {
+    return false;
+  }
+  return mAlgorithmType == aOther.mAlgorithmType;
+}
+
+void
+SRIMetadata::GetHash(uint32_t aIndex, nsCString* outHash) const
+{
+  MOZ_ASSERT(aIndex < SRIMetadata::MAX_ALTERNATE_HASHES);
+  if (NS_WARN_IF(aIndex >= mHashes.Length())) {
+    *outHash = nullptr;
+    return;
+  }
+  *outHash = mHashes[aIndex];
+}
+
+void
+SRIMetadata::GetHashType(int8_t* outType, uint32_t* outLength) const
+{
+  // these constants are defined in security/nss/lib/util/hasht.h and
+  // netwerk/base/public/nsICryptoHash.idl
+  switch (mAlgorithmType) {
+    case nsICryptoHash::SHA256:
+      *outLength = SHA256_LENGTH;
+      break;
+    case nsICryptoHash::SHA384:
+      *outLength = SHA384_LENGTH;
+      break;
+    case nsICryptoHash::SHA512:
+      *outLength = SHA512_LENGTH;
+      break;
+    default:
+      *outLength = 0;
+  }
+  *outType = mAlgorithmType;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/security/SRIMetadata.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_SRIMetadata_h
+#define mozilla_dom_SRIMetadata_h
+
+#include "nsTArray.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+class SRIMetadata final
+{
+public:
+  static const uint32_t MAX_ALTERNATE_HASHES = 256;
+  static const int8_t UNKNOWN_ALGORITHM = -1;
+
+  /**
+   * Create an empty metadata object.
+   */
+  SRIMetadata() : mAlgorithmType(UNKNOWN_ALGORITHM), mEmpty(true) {}
+
+  /**
+   * Split a string token into the components of an SRI metadata
+   * attribute.
+   */
+  explicit SRIMetadata(const nsACString& aToken);
+
+  /**
+   * Returns true when this object's hash algorithm is weaker than the
+   * other object's hash algorithm.
+   */
+  bool operator<(const SRIMetadata& aOther) const;
+
+  /**
+   * Not implemented. Should not be used.
+   */
+  bool operator>(const SRIMetadata& aOther) const;
+
+  /**
+   * Add another metadata's hash to this one.
+   */
+  SRIMetadata& operator+=(const SRIMetadata& aOther);
+
+  /**
+   * Returns true when the two metadata use the same hash algorithm.
+   */
+  bool operator==(const SRIMetadata& aOther) const;
+
+  bool IsEmpty() const { return mEmpty; }
+  bool IsMalformed() const { return mHashes.IsEmpty() || mAlgorithm.IsEmpty(); }
+  bool IsAlgorithmSupported() const { return mAlgorithmType != UNKNOWN_ALGORITHM; }
+  bool IsValid() const { return !IsMalformed() && IsAlgorithmSupported(); }
+
+  uint32_t HashCount() const { return mHashes.Length(); }
+  void GetHash(uint32_t aIndex, nsCString* outHash) const;
+  void GetAlgorithm(nsCString* outAlg) const { *outAlg = mAlgorithm; }
+  void GetHashType(int8_t* outType, uint32_t* outLength) const;
+
+private:
+  nsTArray<nsCString> mHashes;
+  nsCString mAlgorithm;
+  int8_t mAlgorithmType;
+  bool mEmpty;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SRIMetadata_h
--- a/dom/security/moz.build
+++ b/dom/security/moz.build
@@ -7,31 +7,35 @@
 TEST_DIRS += ['test']
 
 EXPORTS.mozilla.dom += [
     'nsContentSecurityManager.h',
     'nsCSPContext.h',
     'nsCSPService.h',
     'nsCSPUtils.h',
     'nsMixedContentBlocker.h',
+    'SRICheck.h',
+    'SRIMetadata.h',
 ]
 
 EXPORTS += [
     'nsContentSecurityManager.h',
     'nsCORSListenerProxy.h'
 ]
 
 UNIFIED_SOURCES += [
     'nsContentSecurityManager.cpp',
     'nsCORSListenerProxy.cpp',
     'nsCSPContext.cpp',
     'nsCSPParser.cpp',
     'nsCSPService.cpp',
     'nsCSPUtils.cpp',
     'nsMixedContentBlocker.cpp',
+    'SRICheck.cpp',
+    'SRIMetadata.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/caps',
     '/netwerk/base',
--- a/dom/webidl/HTMLLinkElement.webidl
+++ b/dom/webidl/HTMLLinkElement.webidl
@@ -43,8 +43,13 @@ partial interface HTMLLinkElement {
 };
 
 // http://w3c.github.io/webcomponents/spec/imports/#interface-import
 partial interface HTMLLinkElement {
     [Func="nsDocument::IsWebComponentsEnabled"]
     readonly attribute Document? import;
 };
 
+// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmllinkelement-1
+partial interface HTMLLinkElement {
+  [SetterThrows]
+  attribute DOMString integrity;
+};
--- a/dom/webidl/HTMLScriptElement.webidl
+++ b/dom/webidl/HTMLScriptElement.webidl
@@ -28,8 +28,13 @@ interface HTMLScriptElement : HTMLElemen
 // http://www.whatwg.org/specs/web-apps/current-work/#other-elements,-attributes-and-apis
 partial interface HTMLScriptElement {
   [SetterThrows]
   attribute DOMString event;
   [SetterThrows]
   attribute DOMString htmlFor;
 };
 
+// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmlscriptelement-1
+partial interface HTMLScriptElement {
+  [SetterThrows]
+  attribute DOMString integrity;
+};
--- a/layout/style/CSSStyleSheet.cpp
+++ b/layout/style/CSSStyleSheet.cpp
@@ -810,20 +810,22 @@ namespace mozilla {
 
 // -------------------------------
 // CSS Style Sheet Inner Data Container
 //
 
 
 CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
                                        CORSMode aCORSMode,
-                                       ReferrerPolicy aReferrerPolicy)
+                                       ReferrerPolicy aReferrerPolicy,
+                                       const SRIMetadata& aIntegrity)
   : mSheets()
   , mCORSMode(aCORSMode)
   , mReferrerPolicy (aReferrerPolicy)
+  , mIntegrity(aIntegrity)
   , mComplete(false)
 #ifdef DEBUG
   , mPrincipalSet(false)
 #endif
 {
   MOZ_COUNT_CTOR(CSSStyleSheetInner);
   mSheets.AppendElement(aPrimarySheet);
 
@@ -935,16 +937,17 @@ CSSStyleSheetInner::CSSStyleSheetInner(C
                                        CSSStyleSheet* aPrimarySheet)
   : mSheets(),
     mSheetURI(aCopy.mSheetURI),
     mOriginalSheetURI(aCopy.mOriginalSheetURI),
     mBaseURI(aCopy.mBaseURI),
     mPrincipal(aCopy.mPrincipal),
     mCORSMode(aCopy.mCORSMode),
     mReferrerPolicy(aCopy.mReferrerPolicy),
+    mIntegrity(aCopy.mIntegrity),
     mComplete(aCopy.mComplete)
 #ifdef DEBUG
     , mPrincipalSet(aCopy.mPrincipalSet)
 #endif
 {
   MOZ_COUNT_CTOR(CSSStyleSheetInner);
   AddSheet(aPrimarySheet);
   aCopy.mOrderedRules.EnumerateForwards(css::GroupRule::CloneRuleInto, &mOrderedRules);
@@ -1075,17 +1078,36 @@ CSSStyleSheet::CSSStyleSheet(CORSMode aC
     mDocument(nullptr),
     mOwningNode(nullptr),
     mDisabled(false),
     mDirty(false),
     mInRuleProcessorCache(false),
     mScopeElement(nullptr),
     mRuleProcessors(nullptr)
 {
-  mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy);
+  mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy,
+                                  SRIMetadata());
+}
+
+CSSStyleSheet::CSSStyleSheet(CORSMode aCORSMode,
+                             ReferrerPolicy aReferrerPolicy,
+                             const SRIMetadata& aIntegrity)
+  : mTitle(),
+    mParent(nullptr),
+    mOwnerRule(nullptr),
+    mDocument(nullptr),
+    mOwningNode(nullptr),
+    mDisabled(false),
+    mDirty(false),
+    mInRuleProcessorCache(false),
+    mScopeElement(nullptr),
+    mRuleProcessors(nullptr)
+{
+  mInner = new CSSStyleSheetInner(this, aCORSMode, aReferrerPolicy,
+                                  aIntegrity);
 }
 
 CSSStyleSheet::CSSStyleSheet(const CSSStyleSheet& aCopy,
                              CSSStyleSheet* aParentToUse,
                              css::ImportRule* aOwnerRuleToUse,
                              nsIDocument* aDocumentToUse,
                              nsINode* aOwningNodeToUse)
   : mTitle(aCopy.mTitle),
--- a/layout/style/CSSStyleSheet.h
+++ b/layout/style/CSSStyleSheet.h
@@ -21,16 +21,17 @@
 #include "nsIDOMCSSStyleSheet.h"
 #include "nsICSSLoaderObserver.h"
 #include "nsTArrayForwardDeclare.h"
 #include "nsString.h"
 #include "mozilla/CORSMode.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 #include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/dom/SRIMetadata.h"
 
 class CSSRuleListImpl;
 class nsCSSRuleProcessor;
 class nsIPrincipal;
 class nsIURI;
 class nsMediaList;
 class nsMediaQueryResultCacheKey;
 class nsPresContext;
@@ -58,17 +59,18 @@ class CSSStyleSheetInner
 public:
   friend class mozilla::CSSStyleSheet;
   friend class ::nsCSSRuleProcessor;
   typedef net::ReferrerPolicy ReferrerPolicy;
 
 private:
   CSSStyleSheetInner(CSSStyleSheet* aPrimarySheet,
                      CORSMode aCORSMode,
-                     ReferrerPolicy aReferrerPolicy);
+                     ReferrerPolicy aReferrerPolicy,
+                     const dom::SRIMetadata& aIntegrity);
   CSSStyleSheetInner(CSSStyleSheetInner& aCopy,
                      CSSStyleSheet* aPrimarySheet);
   ~CSSStyleSheetInner();
 
   CSSStyleSheetInner* CloneFor(CSSStyleSheet* aPrimarySheet);
   void AddSheet(CSSStyleSheet* aSheet);
   void RemoveSheet(CSSStyleSheet* aSheet);
 
@@ -91,16 +93,17 @@ private:
   // currently this is the case) that any time page JS can get ts hands on a
   // child sheet that means we've already ensured unique inners throughout its
   // parent chain and things are good.
   nsRefPtr<CSSStyleSheet> mFirstChild;
   CORSMode               mCORSMode;
   // The Referrer Policy of a stylesheet is used for its child sheets, so it is
   // stored here.
   ReferrerPolicy         mReferrerPolicy;
+  dom::SRIMetadata       mIntegrity;
   bool                   mComplete;
 
 #ifdef DEBUG
   bool                   mPrincipalSet;
 #endif
 };
 
 
@@ -118,16 +121,18 @@ private:
 class CSSStyleSheet final : public nsIStyleSheet,
                             public nsIDOMCSSStyleSheet,
                             public nsICSSLoaderObserver,
                             public nsWrapperCache
 {
 public:
   typedef net::ReferrerPolicy ReferrerPolicy;
   CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy);
+  CSSStyleSheet(CORSMode aCORSMode, ReferrerPolicy aReferrerPolicy,
+                const dom::SRIMetadata& aIntegrity);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(CSSStyleSheet,
                                                          nsIStyleSheet)
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_CSS_STYLE_SHEET_IMPL_CID)
 
   // nsIStyleSheet interface
@@ -254,16 +259,19 @@ public:
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
   // Get this style sheet's CORS mode
   CORSMode GetCORSMode() const { return mInner->mCORSMode; }
 
   // Get this style sheet's Referrer Policy
   ReferrerPolicy GetReferrerPolicy() const { return mInner->mReferrerPolicy; }
 
+  // Get this style sheet's integrity metadata
+  dom::SRIMetadata GetIntegrity() const { return mInner->mIntegrity; }
+
   dom::Element* GetScopeElement() const { return mScopeElement; }
   void SetScopeElement(dom::Element* aScopeElement)
   {
     mScopeElement = aScopeElement;
   }
 
   // WebIDL StyleSheet API
   // Our nsIStyleSheet::GetType is a const method, so it ends up
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -57,16 +57,17 @@
 #include "nsXULPrototypeCache.h"
 #endif
 
 #include "nsIMediaList.h"
 #include "nsIDOMStyleSheet.h"
 #include "nsError.h"
 
 #include "nsIContentSecurityPolicy.h"
+#include "mozilla/dom/SRICheck.h"
 
 #include "mozilla/dom/EncodingUtils.h"
 using mozilla::dom::EncodingUtils;
 
 using namespace mozilla::dom;
 
 /**
  * OVERALL ARCHITECTURE
@@ -260,16 +261,26 @@ static PRLogModuleInfo *
 GetLoaderLog()
 {
   static PRLogModuleInfo *sLog;
   if (!sLog)
     sLog = PR_NewLogModule("nsCSSLoader");
   return sLog;
 }
 
+static PRLogModuleInfo*
+GetSriLog()
+{
+  static PRLogModuleInfo *gSriPRLog;
+  if (!gSriPRLog) {
+    gSriPRLog = PR_NewLogModule("SRI");
+  }
+  return gSriPRLog;
+}
+
 #define LOG_ERROR(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Error, args)
 #define LOG_WARN(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Warning, args)
 #define LOG_DEBUG(args) MOZ_LOG(GetLoaderLog(), mozilla::LogLevel::Debug, args)
 #define LOG(args) LOG_DEBUG(args)
 
 #define LOG_ERROR_ENABLED() MOZ_LOG_TEST(GetLoaderLog(), mozilla::LogLevel::Error)
 #define LOG_WARN_ENABLED() MOZ_LOG_TEST(GetLoaderLog(), mozilla::LogLevel::Warning)
 #define LOG_DEBUG_ENABLED() MOZ_LOG_TEST(GetLoaderLog(), mozilla::LogLevel::Debug)
@@ -923,16 +934,28 @@ SheetLoadData::OnStreamComplete(nsIUnich
     if (errorFlag == nsIScriptError::errorFlag) {
       LOG_WARN(("  Ignoring sheet with improper MIME type %s",
                 contentType.get()));
       mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
       return NS_OK;
     }
   }
 
+  SRIMetadata sriMetadata = mSheet->GetIntegrity();
+  if (!sriMetadata.IsEmpty() &&
+      NS_FAILED(SRICheck::VerifyIntegrity(sriMetadata, channelURI,
+                                          mSheet->GetCORSMode(), aBuffer,
+                                          mLoader->mDocument))) {
+    LOG(("  Load was blocked by SRI"));
+    MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+            ("css::Loader::OnStreamComplete, bad metadata"));
+    mLoader->SheetComplete(this, NS_ERROR_SRI_CORRUPT);
+    return NS_OK;
+  }
+
   // Enough to set the URIs on mSheet, since any sibling datas we have share
   // the same mInner as mSheet and will thus get the same URI.
   mSheet->SetURIs(channelURI, originalURI, channelURI);
 
   bool completed;
   result = mLoader->ParseSheet(aBuffer, this, completed);
   NS_ASSERTION(completed || !mSyncLoad, "sync load did not complete");
   return result;
@@ -1052,16 +1075,17 @@ Loader::CheckLoadAllowed(nsIPrincipal* a
  * CreateSheet().
  */
 nsresult
 Loader::CreateSheet(nsIURI* aURI,
                     nsIContent* aLinkingContent,
                     nsIPrincipal* aLoaderPrincipal,
                     CORSMode aCORSMode,
                     ReferrerPolicy aReferrerPolicy,
+                    const nsAString& aIntegrity,
                     bool aSyncLoad,
                     bool aHasAlternateRel,
                     const nsAString& aTitle,
                     StyleSheetState& aSheetState,
                     bool *aIsAlternate,
                     CSSStyleSheet** aSheet)
 {
   LOG(("css::Loader::CreateSheet"));
@@ -1199,17 +1223,27 @@ Loader::CreateSheet(nsIURI* aURI,
       sheetURI = aLinkingContent->OwnerDoc()->GetDocumentURI();
       originalURI = nullptr;
     } else {
       baseURI = aURI;
       sheetURI = aURI;
       originalURI = aURI;
     }
 
-    nsRefPtr<CSSStyleSheet> sheet = new CSSStyleSheet(aCORSMode, aReferrerPolicy);
+    SRIMetadata sriMetadata;
+    if (!aIntegrity.IsEmpty()) {
+      MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+              ("css::Loader::CreateSheet, integrity=%s",
+               NS_ConvertUTF16toUTF8(aIntegrity).get()));
+      SRICheck::IntegrityMetadata(aIntegrity, mDocument, &sriMetadata);
+    }
+
+    nsRefPtr<CSSStyleSheet> sheet = new CSSStyleSheet(aCORSMode,
+                                                      aReferrerPolicy,
+                                                      sriMetadata);
     sheet->SetURIs(sheetURI, originalURI, baseURI);
     sheet.forget(aSheet);
   }
 
   NS_ASSERTION(*aSheet, "We should have a sheet by now!");
   NS_ASSERTION(aSheetState != eSheetStateUnknown, "Have to set a state!");
   LOG(("  State: %s", gStateStrings[aSheetState]));
 
@@ -1410,16 +1444,18 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
        (nsContentUtils::URIIsLocalFile(aLoadData->mURI) &&
         NS_SUCCEEDED(aLoadData->mLoaderPrincipal->
                      CheckMayLoad(aLoadData->mURI, false, false))));
   }
   else {
     triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
   }
 
+  SRIMetadata sriMetadata = aLoadData->mSheet->GetIntegrity();
+
   if (aLoadData->mSyncLoad) {
     LOG(("  Synchronous load"));
     NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
     NS_ASSERTION(aSheetState == eSheetNeedsParser,
                  "Sync loads can't reuse existing async loads");
 
     // Create a nsIUnicharStreamLoader instance to which we will feed
     // the data from the sync load.  Do this before creating the
@@ -1594,17 +1630,17 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
     nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
     if (cos) {
       cos->AddClassFlags(nsIClassOfService::Leader);
     }
   }
 
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
   if (httpChannel) {
-    // send a minimal Accept header for text/css
+    // Send a minimal Accept header for text/css
     httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
                                   NS_LITERAL_CSTRING("text/css,*/*;q=0.1"),
                                   false);
     nsCOMPtr<nsIURI> referrerURI = aLoadData->GetReferrerURI();
     if (referrerURI)
       httpChannel->SetReferrerWithPolicy(referrerURI,
                                          aLoadData->mSheet->GetReferrerPolicy());
 
@@ -1927,18 +1963,20 @@ Loader::LoadInlineStyle(nsIContent* aEle
   NS_ASSERTION(owningElement, "Element is not a style linking element!");
 
   // Since we're not planning to load a URI, no need to hand a principal to the
   // load data or to CreateSheet().  Also, OK to use CORS_NONE for the CORS
   // mode and mDocument's ReferrerPolicy.
   StyleSheetState state;
   nsRefPtr<CSSStyleSheet> sheet;
   nsresult rv = CreateSheet(nullptr, aElement, nullptr, CORS_NONE,
-                            mDocument->GetReferrerPolicy(), false, false,
-                            aTitle, state, aIsAlternate, getter_AddRefs(sheet));
+                            mDocument->GetReferrerPolicy(),
+                            EmptyString(), // no inline integrity checks
+                            false, false, aTitle, state, aIsAlternate,
+                            getter_AddRefs(sheet));
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ASSERTION(state == eSheetNeedsParser,
                "Inline sheets should not be cached");
 
   LOG(("  Sheet is alternate: %d", *aIsAlternate));
 
   PrepareSheet(sheet, aTitle, aMedia, nullptr, aScopeElement, *aIsAlternate);
 
@@ -1974,16 +2012,17 @@ Loader::LoadInlineStyle(nsIContent* aEle
 nsresult
 Loader::LoadStyleLink(nsIContent* aElement,
                       nsIURI* aURL,
                       const nsAString& aTitle,
                       const nsAString& aMedia,
                       bool aHasAlternateRel,
                       CORSMode aCORSMode,
                       ReferrerPolicy aReferrerPolicy,
+                      const nsAString& aIntegrity,
                       nsICSSLoaderObserver* aObserver,
                       bool* aIsAlternate)
 {
   LOG(("css::Loader::LoadStyleLink"));
   NS_PRECONDITION(aURL, "Must have URL to load");
   NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
 
   LOG_URI("  Link uri: '%s'", aURL);
@@ -2008,17 +2047,17 @@ Loader::LoadStyleLink(nsIContent* aEleme
   nsresult rv = CheckLoadAllowed(principal, aURL, context);
   if (NS_FAILED(rv)) return rv;
 
   LOG(("  Passed load check"));
 
   StyleSheetState state;
   nsRefPtr<CSSStyleSheet> sheet;
   rv = CreateSheet(aURL, aElement, principal, aCORSMode,
-                   aReferrerPolicy, false,
+                   aReferrerPolicy, aIntegrity, false,
                    aHasAlternateRel, aTitle, state, aIsAlternate,
                    getter_AddRefs(sheet));
   NS_ENSURE_SUCCESS(rv, rv);
 
   LOG(("  Sheet is alternate: %d", *aIsAlternate));
 
   PrepareSheet(sheet, aTitle, aMedia, nullptr, nullptr, *aIsAlternate);
 
@@ -2172,16 +2211,17 @@ Loader::LoadChildSheet(CSSStyleSheet* aP
   // loop) do so.
   nsRefPtr<CSSStyleSheet> sheet;
   bool isAlternate;
   StyleSheetState state;
   const nsSubstring& empty = EmptyString();
   // For now, use CORS_NONE for child sheets
   rv = CreateSheet(aURL, nullptr, principal, CORS_NONE,
                    aParentSheet->GetReferrerPolicy(),
+                   EmptyString(), // integrity is only checked on main sheet
                    parentData ? parentData->mSyncLoad : false,
                    false, empty, state, &isAlternate, getter_AddRefs(sheet));
   NS_ENSURE_SUCCESS(rv, rv);
 
   PrepareSheet(sheet, empty, empty, aMedia, nullptr, isAlternate);
 
   rv = InsertChildSheet(sheet, aParentSheet, aParentRule);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -2238,35 +2278,37 @@ Loader::LoadSheet(nsIURI* aURL,
 }
 
 nsresult
 Loader::LoadSheet(nsIURI* aURL,
                   nsIPrincipal* aOriginPrincipal,
                   const nsCString& aCharset,
                   nsICSSLoaderObserver* aObserver,
                   CORSMode aCORSMode,
-                  ReferrerPolicy aReferrerPolicy)
+                  ReferrerPolicy aReferrerPolicy,
+                  const nsAString& aIntegrity)
 {
   LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
   return InternalLoadNonDocumentSheet(aURL, false, false,
                                       aOriginPrincipal, aCharset,
                                       nullptr, aObserver, aCORSMode,
-                                      aReferrerPolicy);
+                                      aReferrerPolicy, aIntegrity);
 }
 
 nsresult
 Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
                                      bool aAllowUnsafeRules,
                                      bool aUseSystemPrincipal,
                                      nsIPrincipal* aOriginPrincipal,
                                      const nsCString& aCharset,
                                      CSSStyleSheet** aSheet,
                                      nsICSSLoaderObserver* aObserver,
                                      CORSMode aCORSMode,
-                                     ReferrerPolicy aReferrerPolicy)
+                                     ReferrerPolicy aReferrerPolicy,
+                                     const nsAString& aIntegrity)
 {
   NS_PRECONDITION(aURL, "Must have a URI to load");
   NS_PRECONDITION(aSheet || aObserver, "Sheet and observer can't both be null");
   NS_PRECONDITION(!aUseSystemPrincipal || !aObserver,
                   "Shouldn't load system-principal sheets async");
   NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");
 
   LOG_URI("  Non-document sheet uri: '%s'", aURL);
@@ -2287,17 +2329,17 @@ Loader::InternalLoadNonDocumentSheet(nsI
 
   StyleSheetState state;
   bool isAlternate;
   nsRefPtr<CSSStyleSheet> sheet;
   bool syncLoad = (aObserver == nullptr);
   const nsSubstring& empty = EmptyString();
 
   rv = CreateSheet(aURL, nullptr, aOriginPrincipal, aCORSMode,
-                   aReferrerPolicy, syncLoad, false,
+                   aReferrerPolicy, aIntegrity, syncLoad, false,
                    empty, state, &isAlternate, getter_AddRefs(sheet));
   NS_ENSURE_SUCCESS(rv, rv);
 
   PrepareSheet(sheet, empty, empty, nullptr, nullptr, isAlternate);
 
   if (state == eSheetComplete) {
     LOG(("  Sheet already complete"));
     if (aObserver || !mObservers.IsEmpty()) {
--- a/layout/style/Loader.h
+++ b/layout/style/Loader.h
@@ -218,16 +218,17 @@ public:
    */
   nsresult LoadStyleLink(nsIContent* aElement,
                          nsIURI* aURL,
                          const nsAString& aTitle,
                          const nsAString& aMedia,
                          bool aHasAlternateRel,
                          CORSMode aCORSMode,
                          ReferrerPolicy aReferrerPolicy,
+                         const nsAString& aIntegrity,
                          nsICSSLoaderObserver* aObserver,
                          bool* aIsAlternate);
 
   /**
    * Load a child (@import-ed) style sheet.  In addition to loading the sheet,
    * this method will insert it into the child sheet list of aParentSheet.  If
    * there is no sheet currently being parsed and the child sheet is not
    * complete when this method returns, then when the child sheet becomes
@@ -315,17 +316,18 @@ public:
    * Same as above, to be used when the caller doesn't care about the
    * not-yet-loaded sheet.
    */
   nsresult LoadSheet(nsIURI* aURL,
                      nsIPrincipal* aOriginPrincipal,
                      const nsCString& aCharset,
                      nsICSSLoaderObserver* aObserver,
                      CORSMode aCORSMode = CORS_NONE,
-                     ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default);
+                     ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default,
+                     const nsAString& aIntegrity = EmptyString());
 
   /**
    * Stop loading all sheets.  All nsICSSLoaderObservers involved will be
    * notified with NS_BINDING_ABORTED as the status, possibly synchronously.
    */
   nsresult Stop(void);
 
   /**
@@ -412,16 +414,17 @@ private:
   // must be non-null then.  The loader principal must never be null
   // if aURI is not null.
   // *aIsAlternate is set based on aTitle and aHasAlternateRel.
   nsresult CreateSheet(nsIURI* aURI,
                        nsIContent* aLinkingContent,
                        nsIPrincipal* aLoaderPrincipal,
                        CORSMode aCORSMode,
                        ReferrerPolicy aReferrerPolicy,
+                       const nsAString& aIntegrity,
                        bool aSyncLoad,
                        bool aHasAlternateRel,
                        const nsAString& aTitle,
                        StyleSheetState& aSheetState,
                        bool *aIsAlternate,
                        CSSStyleSheet** aSheet);
 
   // Pass in either a media string or the nsMediaList from the
@@ -445,17 +448,18 @@ private:
   nsresult InternalLoadNonDocumentSheet(nsIURI* aURL,
                                         bool aAllowUnsafeRules,
                                         bool aUseSystemPrincipal,
                                         nsIPrincipal* aOriginPrincipal,
                                         const nsCString& aCharset,
                                         CSSStyleSheet** aSheet,
                                         nsICSSLoaderObserver* aObserver,
                                         CORSMode aCORSMode = CORS_NONE,
-                                        ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default);
+                                        ReferrerPolicy aReferrerPolicy = mozilla::net::RP_Default,
+                                        const nsAString& aIntegrity = EmptyString());
 
   // Post a load event for aObserver to be notified about aSheet.  The
   // notification will be sent with status NS_OK unless the load event is
   // canceled at some point (in which case it will be sent with
   // NS_BINDING_ABORTED).  aWasAlternate indicates the state when the load was
   // initiated, not the state at some later time.  aURI should be the URI the
   // sheet was loaded from (may be null for inline sheets).  aElement is the
   // owning element for this sheet.
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1953,16 +1953,19 @@ pref("security.csp.experimentalEnabled",
 
 // Default Content Security Policy to apply to privileged apps.
 pref("security.apps.privileged.CSP.default", "default-src * data: blob:; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'");
 
 // Mixed content blocking
 pref("security.mixed_content.block_active_content", false);
 pref("security.mixed_content.block_display_content", false);
 
+// Sub-resource integrity
+pref("security.sri.enable", false);
+
 // Disable pinning checks by default.
 pref("security.cert_pinning.enforcement_level", 0);
 // Do not process hpkp headers rooted by not built in roots by default.
 // This is to prevent accidental pinning from MITM devices and is used
 // for tests.
 pref("security.cert_pinning.process_headers_from_non_builtin_roots", false);
 
 // Modifier key prefs: default to Windows settings,
--- a/parser/html/nsHtml5SpeculativeLoad.cpp
+++ b/parser/html/nsHtml5SpeculativeLoad.cpp
@@ -40,24 +40,24 @@ nsHtml5SpeculativeLoad::Perform(nsHtml5T
       aExecutor->PreloadEndPicture();
       break;
     case eSpeculativeLoadPictureSource:
       aExecutor->PreloadPictureSource(mSrcset, mSizes, mTypeOrCharsetSource,
                                       mMedia);
       break;
     case eSpeculativeLoadScript:
       aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSource,
-                               mCrossOrigin, false);
+                               mCrossOrigin, mIntegrity, false);
       break;
     case eSpeculativeLoadScriptFromHead:
       aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSource,
-                               mCrossOrigin, true);
+                               mCrossOrigin, mIntegrity, true);
       break;
     case eSpeculativeLoadStyle:
-      aExecutor->PreloadStyle(mUrl, mCharset, mCrossOrigin);
+      aExecutor->PreloadStyle(mUrl, mCharset, mCrossOrigin, mIntegrity);
       break;
     case eSpeculativeLoadManifest:  
       aExecutor->ProcessOfflineManifest(mUrl);
       break;
     case eSpeculativeLoadSetDocumentCharset: {
         nsAutoCString narrowName;
         CopyUTF16toUTF8(mCharset, narrowName);
         NS_ASSERTION(mTypeOrCharsetSource.Length() == 1,
--- a/parser/html/nsHtml5SpeculativeLoad.h
+++ b/parser/html/nsHtml5SpeculativeLoad.h
@@ -100,37 +100,41 @@ class nsHtml5SpeculativeLoad {
       mTypeOrCharsetSource.Assign(aType);
       mMedia.Assign(aMedia);
     }
 
     inline void InitScript(const nsAString& aUrl,
                            const nsAString& aCharset,
                            const nsAString& aType,
                            const nsAString& aCrossOrigin,
+                           const nsAString& aIntegrity,
                            bool aParserInHead)
     {
       NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
                       "Trying to reinitialize a speculative load!");
       mOpCode = aParserInHead ?
           eSpeculativeLoadScriptFromHead : eSpeculativeLoadScript;
       mUrl.Assign(aUrl);
       mCharset.Assign(aCharset);
       mTypeOrCharsetSource.Assign(aType);
       mCrossOrigin.Assign(aCrossOrigin);
+      mIntegrity.Assign(aIntegrity);
     }
 
     inline void InitStyle(const nsAString& aUrl, const nsAString& aCharset,
-                          const nsAString& aCrossOrigin)
+                          const nsAString& aCrossOrigin,
+                          const nsAString& aIntegrity)
     {
       NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
                       "Trying to reinitialize a speculative load!");
       mOpCode = eSpeculativeLoadStyle;
       mUrl.Assign(aUrl);
       mCharset.Assign(aCharset);
       mCrossOrigin.Assign(aCrossOrigin);
+      mIntegrity.Assign(aIntegrity);
     }
 
     /**
      * "Speculative" manifest loads aren't truly speculative--if a manifest
      * gets loaded, we are committed to it. There can never be a <script>
      * before the manifest, so the situation of having to undo a manifest due
      * to document.write() never arises. The reason why a parser
      * thread-discovered manifest gets loaded via the speculative load queue
@@ -214,11 +218,17 @@ class nsHtml5SpeculativeLoad {
      * attribute.  If the attribute is not set, this will be a void string.
      */
     nsString mSizes;
     /**
      * If mOpCode is eSpeculativeLoadPictureSource, this is the value of "media"
      * attribute.  If the attribute is not set, this will be a void string.
      */
     nsString mMedia;
+    /**
+     * If mOpCode is eSpeculativeLoadScript[FromHead], this is the value of the
+     * "integrity" attribute.  If the attribute is not set, this will be a void
+     * string.
+     */
+    nsString mIntegrity;
 };
 
 #endif // nsHtml5SpeculativeLoad_h
--- a/parser/html/nsHtml5TreeBuilderCppSupplement.h
+++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h
@@ -160,41 +160,47 @@ nsHtml5TreeBuilder::createElement(int32_
           treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber());
 
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_SRC);
           if (url) {
             nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
             nsString* type = aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
             nsString* crossOrigin =
               aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+            nsString* integrity =
+              aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
             mSpeculativeLoadQueue.AppendElement()->
               InitScript(*url,
                          (charset) ? *charset : EmptyString(),
                          (type) ? *type : EmptyString(),
                          (crossOrigin) ? *crossOrigin : NullString(),
+                         (integrity) ? *integrity : NullString(),
                          mode == NS_HTML5TREE_BUILDER_IN_HEAD);
             mCurrentHtmlScriptIsAsyncOrDefer =
               aAttributes->contains(nsHtml5AttributeName::ATTR_ASYNC) ||
               aAttributes->contains(nsHtml5AttributeName::ATTR_DEFER);
           }
         } else if (nsHtml5Atoms::link == aName) {
           nsString* rel = aAttributes->getValue(nsHtml5AttributeName::ATTR_REL);
           // Not splitting on space here is bogus but the old parser didn't even
           // do a case-insensitive check.
           if (rel) {
             if (rel->LowerCaseEqualsASCII("stylesheet")) {
               nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
               if (url) {
                 nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
                 nsString* crossOrigin =
                   aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+                nsString* integrity =
+                  aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
                 mSpeculativeLoadQueue.AppendElement()->
                   InitStyle(*url,
                             (charset) ? *charset : EmptyString(),
-                            (crossOrigin) ? *crossOrigin : NullString());
+                            (crossOrigin) ? *crossOrigin : NullString(),
+                            (integrity) ? *integrity : NullString());
               }
             } else if (rel->LowerCaseEqualsASCII("preconnect")) {
               nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
               if (url) {
                 nsString* crossOrigin =
                   aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
                 mSpeculativeLoadQueue.AppendElement()->
                   InitPreconnect(*url, (crossOrigin) ? *crossOrigin : NullString());
@@ -251,35 +257,41 @@ nsHtml5TreeBuilder::createElement(int32_
           NS_ASSERTION(treeOp, "Tree op allocation failed.");
           treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber());
 
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
           if (url) {
             nsString* type = aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
             nsString* crossOrigin =
               aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+            nsString* integrity =
+              aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
             mSpeculativeLoadQueue.AppendElement()->
               InitScript(*url,
                          EmptyString(),
                          (type) ? *type : EmptyString(),
                          (crossOrigin) ? *crossOrigin : NullString(),
+                         (integrity) ? *integrity : NullString(),
                          mode == NS_HTML5TREE_BUILDER_IN_HEAD);
           }
         } else if (nsHtml5Atoms::style == aName) {
           nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
           NS_ASSERTION(treeOp, "Tree op allocation failed.");
           treeOp->Init(eTreeOpSetStyleLineNumber, content, tokenizer->getLineNumber());
 
           nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
           if (url) {
             nsString* crossOrigin =
               aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+            nsString* integrity =
+              aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
             mSpeculativeLoadQueue.AppendElement()->
               InitStyle(*url, EmptyString(),
-                        (crossOrigin) ? *crossOrigin : NullString());
+                        (crossOrigin) ? *crossOrigin : NullString(),
+                        (integrity) ? *integrity : NullString());
           }
         }
         break;
     }
   } else if (aNamespace != kNameSpaceID_MathML) {
     // No speculative loader--just line numbers and defer/async check
     if (nsHtml5Atoms::style == aName) {
       nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -906,38 +906,40 @@ nsHtml5TreeOpExecutor::ShouldPreloadURI(
   return true;
 }
 
 void
 nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
                                      const nsAString& aCharset,
                                      const nsAString& aType,
                                      const nsAString& aCrossOrigin,
+                                     const nsAString& aIntegrity,
                                      bool aScriptFromHead)
 {
   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
   if (!uri) {
     return;
   }
   mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin,
-                                        aScriptFromHead,
+                                        aIntegrity, aScriptFromHead,
                                         mSpeculationReferrerPolicy);
 }
 
 void
 nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
                                     const nsAString& aCharset,
-                                    const nsAString& aCrossOrigin)
+                                    const nsAString& aCrossOrigin,
+                                    const nsAString& aIntegrity)
 {
   nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
   if (!uri) {
     return;
   }
   mDocument->PreloadStyle(uri, aCharset, aCrossOrigin,
-                          mSpeculationReferrerPolicy);
+                          mSpeculationReferrerPolicy, aIntegrity);
 }
 
 void
 nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
                                     const nsAString& aCrossOrigin,
                                     const nsAString& aSrcset,
                                     const nsAString& aSizes,
                                     const nsAString& aImageReferrerPolicy)
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -243,20 +243,22 @@ class nsHtml5TreeOpExecutor final : publ
 #endif
 
     nsIURI* GetViewSourceBaseURI();
 
     void PreloadScript(const nsAString& aURL,
                        const nsAString& aCharset,
                        const nsAString& aType,
                        const nsAString& aCrossOrigin,
+                       const nsAString& aIntegrity,
                        bool aScriptFromHead);
 
     void PreloadStyle(const nsAString& aURL, const nsAString& aCharset,
-                      const nsAString& aCrossOrigin);
+                      const nsAString& aCrossOrigin,
+                      const nsAString& aIntegrity);
 
     void PreloadImage(const nsAString& aURL,
                       const nsAString& aCrossOrigin,
                       const nsAString& aSrcset,
                       const nsAString& aSizes,
                       const nsAString& aImageReferrerPolicy);
 
     void PreloadOpenPicture();
--- a/xpcom/base/ErrorList.h
+++ b/xpcom/base/ErrorList.h
@@ -652,16 +652,21 @@
   /* ======================================================================= */
   /* 21: NS_ERROR_MODULE_SECURITY */
   /* ======================================================================= */
 #define MODULE NS_ERROR_MODULE_SECURITY
   /* Error code for CSP */
   ERROR(NS_ERROR_CSP_FORM_ACTION_VIOLATION,        FAILURE(98)),
   ERROR(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION,     FAILURE(99)),
 
+  /* Error code for Sub-Resource Integrity */
+  ERROR(NS_ERROR_SRI_CORRUPT,                      FAILURE(200)),
+  ERROR(NS_ERROR_SRI_DISABLED,                     FAILURE(201)),
+  ERROR(NS_ERROR_SRI_NOT_ELIGIBLE,                 FAILURE(202)),
+
   /* CMS specific nsresult error codes.  Note: the numbers used here correspond
    * to the values in nsICMSMessageErrors.idl. */
   ERROR(NS_ERROR_CMS_VERIFY_NOT_SIGNED,            FAILURE(1024)),
   ERROR(NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO,       FAILURE(1025)),
   ERROR(NS_ERROR_CMS_VERIFY_BAD_DIGEST,            FAILURE(1026)),
   ERROR(NS_ERROR_CMS_VERIFY_NOCERT,                FAILURE(1028)),
   ERROR(NS_ERROR_CMS_VERIFY_UNTRUSTED,             FAILURE(1029)),
   ERROR(NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED,      FAILURE(1031)),