xssfilter
author Riccardo Pelizzi <r.pelizzi@gmail.com>
Sat, 11 Aug 2012 14:33:31 -0400
changeset 50 f1e86f56ee2795aa0596a442eef3d8a8dd7ff4a5
parent 49 244c359a659a0af8305c13bb711e0f27065f12a0
permissions -rw-r--r--
Fixed docshell test bug

# HG changeset patch
# User Riccardo Pelizzi <r.pelizzi@gmail.com>
# Parent 0a17cde2a4b69265c48577b9848b33d0a1ab0d5b
Bug 528661 - Reflected XSS Filter for Firefox

diff --git a/b2g/chrome/content/netError.xhtml b/b2g/chrome/content/netError.xhtml
--- a/b2g/chrome/content/netError.xhtml
+++ b/b2g/chrome/content/netError.xhtml
@@ -161,16 +161,23 @@
         }
 
         if (err == "cspFrameAncestorBlocked") {
           // Remove the "Try again" button for CSP frame ancestors violation, since it's
           // almost certainly useless. (Bug 553180)
           document.getElementById("errorTryAgain").style.display = "none";
         }
 
+        if (err == "xssBlockMode") {
+          // Remove the "Try again" button for XSS violations. We might instead consider
+          // putting a "Load Anyway" button.
+          document.getElementById("errorTryAgain").style.display = "none";
+        }
+
+
         if (err == "nssBadCert") {
           // Remove the "Try again" button for security exceptions, since it's
           // almost certainly useless.
           document.getElementById("errorTryAgain").style.display = "none";
           document.getElementById("errorPage").setAttribute("class", "certerror");
           addDomainErrorLink();
         }
         else {
@@ -290,16 +297,17 @@
         <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.title;</h1>
+        <h1 id="et_xssBlockMode">&xssBlockMode.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc2;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
         <div id="ed_malformedURI">&malformedURI.longDesc;</div>
@@ -315,16 +323,17 @@
         <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc2;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.longDesc;</div>
+        <div id="ed_xssBlockMode">&xssBlockMode.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
       </div>
     </div>
 
     <!-- Error Title -->
     <div id="errorTitle">
       <h1 class="errorTitleText" />
diff --git a/b2g/locales/en-US/chrome/overrides/appstrings.properties b/b2g/locales/en-US/chrome/overrides/appstrings.properties
--- a/b2g/locales/en-US/chrome/overrides/appstrings.properties
+++ b/b2g/locales/en-US/chrome/overrides/appstrings.properties
@@ -27,10 +27,11 @@ externalProtocolTitle=External Protocol 
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
 cspFrameAncestorBlocked=This page has a content security policy that prevents it from being embedded in this way.
+xssBlockMode=This page contains an XSS attacks that has been blocked for your security.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
diff --git a/b2g/locales/en-US/chrome/overrides/netError.dtd b/b2g/locales/en-US/chrome/overrides/netError.dtd
--- a/b2g/locales/en-US/chrome/overrides/netError.dtd
+++ b/b2g/locales/en-US/chrome/overrides/netError.dtd
@@ -147,16 +147,19 @@ be temporary, and you can try again late
   <li>The site could be temporarily unavailable or too busy. Try again in a few moments.</li>
   <li>If you are unable to load any pages, check your mobile device's data or Wi-Fi connection.</li>
 </ul>
 ">
 
 <!ENTITY cspFrameAncestorBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspFrameAncestorBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
 
+<!ENTITY xssBlockMode.title "Blocked by XSS Filter">
+<!ENTITY xssBlockMode.longDesc "<p>&brandShortName; blocked further actions on this page because the page contains injected JavaScript code.</p>">
+
 <!ENTITY corruptedContentError.title "Corrupted Content Error">
 <!ENTITY corruptedContentError.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
 
 <!ENTITY securityOverride.linkText "Or you can add an exception…">
 <!ENTITY securityOverride.getMeOutOfHereButton "Get me out of here!">
 <!ENTITY securityOverride.exceptionButtonLabel "Add Exception…">
 
 <!-- LOCALIZATION NOTE (securityOverride.warningContent) - Do not translate the
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -633,16 +633,53 @@ var gPopupBlockerObserver = {
   dontShowMessage: function ()
   {
     var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
     gPrefService.setBoolPref("privacy.popups.showBrowserMessage", !showMessage);
     gBrowser.getNotificationBox().removeCurrentNotification();
   }
 };
 
+const gXSSObserver = {
+
+  observe: function (aSubject, aTopic, aData)
+  {
+
+    if (!gPrefService.getBoolPref("security.xssfilter.displayWarning"))
+      return;
+
+    // parse incoming xss array
+    aSubject.QueryInterface(Ci.nsIArray);
+    var policy = aSubject.queryElementAt(0, Ci.nsISupportsCString).data;
+    var content = aSubject.queryElementAt(1, Ci.nsISupportsCString).data;
+    var url = aSubject.queryElementAt(2, Ci.nsISupportsCString).data;
+    var blockMode = aSubject.queryElementAt(3, Ci.nsISupportsPRBool).data;
+
+    // if it is a block mode event, do not display the warning
+    if (blockMode)
+      return;
+
+    var nb = gBrowser.getNotificationBox();
+    const priority = nb.PRIORITY_WARNING_MEDIUM;
+
+    var buttons = [{
+      label: 'View Unsafe Content',
+      accessKey: 'V',
+      popup: null,
+      callback: function () {
+        alert(content);
+      }
+    }];
+
+    nb.appendNotification("The XSS Filter has detected a potential XSS attack. Type: " +
+                          policy, 'popup-blocked', 'chrome://browser/skin/Info.png',
+                          priority, buttons);
+  }
+};
+
 const gFormSubmitObserver = {
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
 
   panel: null,
 
   init: function()
   {
     this.panel = document.getElementById('invalid-form-popup');
@@ -1250,16 +1287,17 @@ var gBrowserInit = {
 #endif
 
     Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
+    Services.obs.addObserver(gXSSObserver, "xss-on-violate-policy", false);
     Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false);
 
     BrowserOffline.init();
     OfflineApps.init();
     IndexedDBPromptHelper.init();
     gFormSubmitObserver.init();
     SocialUI.init();
     AddonManager.addAddonListener(AddonsMgrListener);
@@ -1594,16 +1632,17 @@ var gBrowserInit = {
 
       Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
       Services.obs.removeObserver(gFormSubmitObserver, "invalidformsubmit");
+      Services.obs.removeObserver(gXSSObserver, "xss-on-violate-policy");
 
       try {
         gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
       } catch (ex) {
         Cu.reportError(ex);
       }
 
       BrowserOffline.uninit();
diff --git a/browser/locales/en-US/chrome/overrides/appstrings.properties b/browser/locales/en-US/chrome/overrides/appstrings.properties
--- a/browser/locales/en-US/chrome/overrides/appstrings.properties
+++ b/browser/locales/en-US/chrome/overrides/appstrings.properties
@@ -27,10 +27,11 @@ externalProtocolTitle=External Protocol 
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
 cspFrameAncestorBlocked=This page has a content security policy that prevents it from being embedded in this way.
+xssBlockMode=This page contains an XSS attacks that has been blocked for your security.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
diff --git a/browser/locales/en-US/chrome/overrides/netError.dtd b/browser/locales/en-US/chrome/overrides/netError.dtd
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -168,16 +168,19 @@ be temporary, and you can try again late
 <!ENTITY phishingBlocked.longDesc "
 <p>Entering any personal information on this page may result in identity theft or other fraud.</p>
 <p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
 ">
 
 <!ENTITY cspFrameAncestorBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspFrameAncestorBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
 
+<!ENTITY xssBlockMode.title "Blocked by XSS Filter">
+<!ENTITY xssBlockMode.longDesc "<p>&brandShortName; blocked further actions on this page because the page contains injected JavaScript code.</p>">
+
 <!ENTITY corruptedContentError.title "Corrupted Content Error">
 <!ENTITY corruptedContentError.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
 
 
 <!ENTITY securityOverride.linkText "Or you can add an exception…">
 <!ENTITY securityOverride.getMeOutOfHereButton "Get me out of here!">
 <!ENTITY securityOverride.exceptionButtonLabel "Add Exception…">
 
diff --git a/caps/idl/nsIPrincipal.idl b/caps/idl/nsIPrincipal.idl
--- a/caps/idl/nsIPrincipal.idl
+++ b/caps/idl/nsIPrincipal.idl
@@ -5,25 +5,27 @@
 
 /* Defines the abstract interface for a principal. */
 
 #include "nsISerializable.idl"
 
 %{C++
 struct JSContext;
 struct JSPrincipals;
+class nsXSSFilter;
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 %}
 
 interface nsIURI;
 interface nsIContentSecurityPolicy;
 
 [ptr] native JSContext(JSContext);
 [ptr] native JSPrincipals(JSPrincipals);
+[ptr] native nsXSSFilter(nsXSSFilter);
 [ptr] native PrincipalArray(nsTArray<nsCOMPtr<nsIPrincipal> >);
 
 [scriptable, uuid(8a74b011-667d-4cfa-b2a2-b27582ba5f38)]
 interface nsIPrincipal : nsISerializable
 {
     /**
      * Values of capabilities for each principal. Order is
      * significant: if an operation is performed on a set
@@ -209,16 +211,21 @@ interface nsIPrincipal : nsISerializable
     readonly attribute nsISupports certificate;
 
     /**
      * A Content Security Policy associated with this principal.
      */
     [noscript] attribute nsIContentSecurityPolicy csp;
 
     /**
+     * Refcounted XSS Filter associated with the principal.
+     */
+    [noscript,notxpcom] attribute nsXSSFilter XSSFilter;
+
+    /**
      * Returns the extended origin of the principal.
      * The extended origin is a string that has more information than the origin
      * and can be used to isolate data or permissions between different
      * principals while taking into account parameters like the app id or the
      * fact that the principal is embedded in a mozbrowser.
      * Some principals will return the origin for extendedOrigin.
      * Some principals will assert if you try to access the extendedOrigin.
      *
diff --git a/caps/include/nsPrincipal.h b/caps/include/nsPrincipal.h
--- a/caps/include/nsPrincipal.h
+++ b/caps/include/nsPrincipal.h
@@ -37,16 +37,18 @@ public:
   NS_IMETHOD EnableCapability(const char* capability, void** annotation);
   NS_IMETHOD GetHasCertificate(bool* aHasCertificate);
   NS_IMETHOD GetFingerprint(nsACString& aFingerprint);
   NS_IMETHOD GetPrettyName(nsACString& aPrettyName);
   NS_IMETHOD GetSubjectName(nsACString& aSubjectName);
   NS_IMETHOD GetCertificate(nsISupports** aCertificate);
   NS_IMETHOD GetCsp(nsIContentSecurityPolicy** aCsp);
   NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp);
+  NS_IMETHOD GetXSSFilter(nsXSSFilter** aXss);
+  NS_IMETHOD SetXSSFilter(nsXSSFilter* aXss);
 public:
 
   // Call this to ensure that this principal has a subject name, a pretty name,
   // and a cert pointer.  This method will throw if there is already a
   // different subject name or if this principal has no certificate.
   nsresult EnsureCertData(const nsACString& aSubjectName,
                           const nsACString& aPrettyName,
                           nsISupports* aCert);
@@ -101,16 +103,18 @@ protected:
   // Keep this is a pointer, even though it may slightly increase the
   // cost of keeping a certificate, this is a good tradeoff though since
   // it is very rare that we actually have a certificate.
   nsAutoPtr<Certificate> mCert;
 
   DomainPolicy* mSecurityPolicy;
 
   nsCOMPtr<nsIContentSecurityPolicy> mCSP;
+  nsRefPtr<nsXSSFilter> mXSS;
+
   bool mTrusted;
 };
 
 class nsPrincipal : public nsBasePrincipal
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSISERIALIZABLE
diff --git a/caps/include/nsScriptSecurityManager.h b/caps/include/nsScriptSecurityManager.h
--- a/caps/include/nsScriptSecurityManager.h
+++ b/caps/include/nsScriptSecurityManager.h
@@ -15,16 +15,17 @@
 #include "nsHashtable.h"
 #include "nsCOMPtr.h"
 #include "nsIChannelEventSink.h"
 #include "nsIJSContextStack.h"
 #include "nsIObserver.h"
 #include "pldhash.h"
 #include "plstr.h"
 #include "nsIScriptExternalNameSet.h"
+#include "nsXSSFilter.h"
 
 #include "mozilla/StandardInteger.h"
 
 class nsIDocShell;
 class nsString;
 class nsIClassInfo;
 class nsIIOService;
 class nsIXPConnect;
@@ -396,16 +397,19 @@ private:
 
     static JSPrincipals *
     ObjectPrincipalFinder(JSObject *obj);
     
     // Decides, based on CSP, whether or not eval() and stuff can be executed.
     static JSBool
     ContentSecurityPolicyPermitsJSAction(JSContext *cx);
 
+    static JSBool
+    XSSFilterPermitsJSAction(JSContext *cx, JSString *str);
+
     // Returns null if a principal cannot be found; generally callers
     // should error out at that point.
     static nsIPrincipal* doGetObjectPrincipal(JSObject *obj);
 #ifdef DEBUG
     static nsIPrincipal*
     old_doGetObjectPrincipal(JSObject *obj, bool aAllowShortCircuit = true);
 #endif
 
diff --git a/caps/src/nsNullPrincipal.cpp b/caps/src/nsNullPrincipal.cpp
--- a/caps/src/nsNullPrincipal.cpp
+++ b/caps/src/nsNullPrincipal.cpp
@@ -223,16 +223,32 @@ nsNullPrincipal::GetCsp(nsIContentSecuri
 NS_IMETHODIMP
 nsNullPrincipal::SetCsp(nsIContentSecurityPolicy* aCsp)
 {
   // CSP on a null principal makes no sense
   return NS_ERROR_NOT_AVAILABLE;
 }
 
 NS_IMETHODIMP
+nsNullPrincipal::GetXSSFilter(nsXSSFilter** aXSS)
+{
+  // Null principals are never loaded from anywhere, and thus can
+  // never have an XSS filter
+  *aXSS = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNullPrincipal::SetXSSFilter(nsXSSFilter* aXSS)
+{
+  // filter on a null principal makes no sense
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
 nsNullPrincipal::GetDomain(nsIURI** aDomain)
 {
   return NS_EnsureSafeToReturn(mURI, aDomain);
 }
 
 NS_IMETHODIMP
 nsNullPrincipal::SetDomain(nsIURI* aDomain)
 {
diff --git a/caps/src/nsPrincipal.cpp b/caps/src/nsPrincipal.cpp
--- a/caps/src/nsPrincipal.cpp
+++ b/caps/src/nsPrincipal.cpp
@@ -19,16 +19,17 @@
 #include "nsHashtable.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIClassInfoImpl.h"
 #include "nsDOMError.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsContentUtils.h"
 #include "jswrapper.h"
+#include "nsXSSFilter.h"
 
 #include "nsPrincipal.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/HashFunctions.h"
 
 using namespace mozilla;
 
@@ -407,16 +408,34 @@ nsBasePrincipal::SetCsp(nsIContentSecuri
   // get set anew when a new principal is created.
   if (mCSP)
     return NS_ERROR_ALREADY_INITIALIZED;
 
   mCSP = aCsp;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsBasePrincipal::GetXSSFilter(nsXSSFilter** aXSS)
+{
+  NS_IF_ADDREF(*aXSS = mXSS);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBasePrincipal::SetXSSFilter(nsXSSFilter* aXSS)
+{
+  // once assigned, the filter cannot be changed.
+  if (mXSS)
+    return NS_ERROR_ALREADY_INITIALIZED;
+
+  mXSS = aXSS;
+  return NS_OK;
+}
+
 nsresult
 nsBasePrincipal::EnsureCertData(const nsACString& aSubjectName,
                                 const nsACString& aPrettyName,
                                 nsISupports* aCert)
 {
   NS_ENSURE_STATE(mCert);
 
   if (!mCert->subjectName.IsEmpty() &&
diff --git a/caps/src/nsScriptSecurityManager.cpp b/caps/src/nsScriptSecurityManager.cpp
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -56,20 +56,28 @@
 #include "nsIChromeRegistry.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/StandardInteger.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
+#include "nsXSSFilter.h"
+#include "jspubtd.h"
+#include "prlog.h"
+#include "nsJSUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+#ifdef PR_LOGGING
+static PRLogModuleInfo *gXssPRLog = PR_NewLogModule("XSS");
+#endif
+
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 
 nsIIOService    *nsScriptSecurityManager::sIOService = nullptr;
 nsIXPConnect    *nsScriptSecurityManager::sXPConnect = nullptr;
 nsIThreadJSContextStack *nsScriptSecurityManager::sJSContextStack = nullptr;
 nsIStringBundle *nsScriptSecurityManager::sStrBundle = nullptr;
 JSRuntime       *nsScriptSecurityManager::sRuntime   = 0;
 bool nsScriptSecurityManager::sStrictFileOriginPolicy = true;
@@ -534,16 +542,65 @@ nsScriptSecurityManager::ContentSecurity
                                  fileName,
                                  scriptSample,
                                  lineNum);
     }
 
     return evalOK;
 }
 
+JSBool
+nsScriptSecurityManager::XSSFilterPermitsJSAction(JSContext *cx, JSString *str)
+{
+
+    PR_LOG(gXssPRLog, PR_LOG_DEBUG, (">>>> XSSFilterPermitsJSAction"));
+
+    if (!str) {
+        NS_ERROR("XSS: str is null.");
+        return true;
+    }
+
+
+    // Get the security manager
+    nsScriptSecurityManager *ssm =
+        nsScriptSecurityManager::GetScriptSecurityManager();
+
+    NS_ASSERTION(ssm, "Failed to get security manager service");
+
+    nsresult rv;
+    nsIPrincipal* subjectPrincipal = ssm->GetSubjectPrincipal(cx, &rv);
+
+    NS_ASSERTION(NS_SUCCEEDED(rv),
+                 "XSS: Failed to get nsIPrincipal from js context");
+
+    if (!subjectPrincipal) {
+        // See bug 553448 for discussion of this case.
+        NS_ASSERTION(!JS_GetSecurityCallbacks(js::GetRuntime(cx))->findObjectPrincipals,
+                     "XSS: Should have been able to find subject principal."
+                     "Reluctantly allowing script.");
+        return true;
+    }
+
+    nsRefPtr<nsXSSFilter> xss;
+    rv = subjectPrincipal->GetXSSFilter(getter_AddRefs(xss));
+    // don't do anything unless there's a filter
+    if (!xss) {
+        return true;
+    }
+
+    nsDependentJSString ns_str;
+    if (!ns_str.init(cx, str)) {
+        NS_WARNING("Failed to initialize nsDependentJSString");
+        return true;
+    }
+
+    return xss->PermitsJSAction(ns_str);
+}
+
+
 
 JSBool
 nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JSHandleObject obj,
                                            JSHandleId id, JSAccessMode mode,
                                            jsval *vp)
 {
     // Get the security manager
     nsScriptSecurityManager *ssm =
@@ -3041,17 +3098,18 @@ nsresult nsScriptSecurityManager::Init()
 
     rv = runtimeService->GetRuntime(&sRuntime);
     NS_ENSURE_SUCCESS(rv, rv);
 
     static const JSSecurityCallbacks securityCallbacks = {
         CheckObjectAccess,
         nsJSPrincipals::Subsume,
         ObjectPrincipalFinder,
-        ContentSecurityPolicyPermitsJSAction
+        ContentSecurityPolicyPermitsJSAction,
+        XSSFilterPermitsJSAction
     };
 
     MOZ_ASSERT(!JS_GetSecurityCallbacks(sRuntime));
     JS_SetSecurityCallbacks(sRuntime, &securityCallbacks);
     JS_InitDestroyPrincipalsCallback(sRuntime, nsJSPrincipals::Destroy);
 
     JS_SetTrustedPrincipals(sRuntime, system);
 
diff --git a/caps/src/nsSystemPrincipal.cpp b/caps/src/nsSystemPrincipal.cpp
--- a/caps/src/nsSystemPrincipal.cpp
+++ b/caps/src/nsSystemPrincipal.cpp
@@ -208,16 +208,30 @@ nsSystemPrincipal::GetCsp(nsIContentSecu
 NS_IMETHODIMP
 nsSystemPrincipal::SetCsp(nsIContentSecurityPolicy* aCsp)
 {
   // CSP on a null principal makes no sense
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsSystemPrincipal::GetXSSFilter(nsXSSFilter** aXSS)
+{
+  *aXSS = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSystemPrincipal::SetXSSFilter(nsXSSFilter* aXSS)
+{
+  // no filter for system principal
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsSystemPrincipal::GetDomain(nsIURI** aDomain)
 {
     *aDomain = nullptr;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSystemPrincipal::SetDomain(nsIURI* aDomain)
diff --git a/content/base/public/Makefile.in b/content/base/public/Makefile.in
--- a/content/base/public/Makefile.in
+++ b/content/base/public/Makefile.in
@@ -79,12 +79,13 @@ XPIDLSRCS	= \
 		nsIImageLoadingContent.idl \
 		nsIObjectLoadingContent.idl \
 		nsIFrameLoader.idl \
 		nsIXMLHttpRequest.idl \
 		nsIContentSecurityPolicy.idl \
 		nsIFrameMessageManager.idl \
 		nsIWebSocket.idl \
 		nsIEventSource.idl \
+		nsIXSSUtils.idl \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
diff --git a/content/base/public/nsContentErrors.h b/content/base/public/nsContentErrors.h
--- a/content/base/public/nsContentErrors.h
+++ b/content/base/public/nsContentErrors.h
@@ -49,13 +49,17 @@
 
 #define NS_FINDBROADCASTER_AWAIT_OVERLAYS \
   NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_CONTENT, 14)
 
 /* Error codes for CSP */
 #define NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION \
   NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, 99)
 
+/* Error codes for XSS Filter */
+#define NS_ERROR_XSS_BLOCK \
+  NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, 100)
+
 /* Error codes for XBL */
 #define NS_ERROR_XBL_BLOCKED \
   NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_CONTENT, 15)
 
 #endif // nsContentErrors_h___
diff --git a/content/base/public/nsIXSSUtils.idl b/content/base/public/nsIXSSUtils.idl
new file mode 100644
--- /dev/null
+++ b/content/base/public/nsIXSSUtils.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+
+[uuid(1db76755-1e01-45fe-92b5-25679cbeb085)]
+interface nsIXSSUtils : nsISupports
+{
+
+  /**
+   * This interface is provided for testing purposes only, because it
+   * is not currently possible to directly reference libxul internal
+   * classes from testing code. normally, XPCOM interfaces are used,
+   * but nsXSSUtils is not an XPCOM component. So this is a thin
+   * wrapper that calls testing code which lives inside libxul.
+   */
+  [noscript] void test();
+
+};
diff --git a/content/base/src/Makefile.in b/content/base/src/Makefile.in
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -36,16 +36,18 @@ EXPORTS		= \
 		nsFrameMessageManager.h \
 		nsAttrAndChildArray.h \
 		nsAttrValue.h \
 		nsCrossSiteListenerProxy.h \
 		nsDOMAttributeMap.h \
 		nsGenericElement.h \
 		nsMappedAttributeElement.h \
 		nsStyledElement.h \
+		nsXSSFilter.h \
+		nsXSSUtils.h \
 		$(NULL)
 
 EXPORTS_NAMESPACES = mozilla/dom
 
 EXPORTS_mozilla/dom = \
   Link.h \
   $(NULL)
 
@@ -130,16 +132,19 @@ CPPSRCS		= \
 		nsBlobProtocolHandler.cpp \
 		nsBlobURI.cpp \
 		nsFrameMessageManager.cpp \
 		nsInProcessTabChildGlobal.cpp \
 		ThirdPartyUtil.cpp \
 		nsEventSource.cpp \
 		FileIOObject.cpp \
 		nsDOMMutationObserver.cpp \
+		nsXSSFilter.cpp \
+		nsXSSUtils.cpp \
+		nsXSSUtilsModule.cpp \
 		$(NULL)
 
 # Are we targeting x86-32 or x86-64?  If so, we want to include SSE2 code for
 # nsTextFragment.cpp
 ifneq (,$(INTEL_ARCHITECTURE))
 CPPSRCS += nsTextFragmentSSE2.cpp
 endif
 
diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -163,31 +163,34 @@
 #include "nsXULAppAPI.h"
 #include "nsDOMTouchEvent.h"
 
 #include "mozilla/Preferences.h"
 
 #include "imgILoader.h"
 #include "nsWrapperCacheInlines.h"
 
+#include "nsXSSFilter.h"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 typedef nsTArray<Link*> LinkArray;
 
 // Reference to the document which requested DOM full-screen mode.
 nsWeakPtr nsDocument::sFullScreenDoc = nullptr;
 
 // Reference to the root document of the branch containing the document
 // which requested DOM full-screen mode.
 nsWeakPtr nsDocument::sFullScreenRootDoc = nullptr;
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gDocumentLeakPRLog;
 static PRLogModuleInfo* gCspPRLog;
+static PRLogModuleInfo* gXssPRLog;
 #endif
 
 #define NAME_NOT_VALID ((nsSimpleContentList*)1)
 
 nsIdentifierMapEntry::~nsIdentifierMapEntry()
 {
 }
 
@@ -1530,16 +1533,19 @@ nsDocument::nsDocument(const char* aCont
     gDocumentLeakPRLog = PR_NewLogModule("DocumentLeak");
 
   if (gDocumentLeakPRLog)
     PR_LOG(gDocumentLeakPRLog, PR_LOG_DEBUG,
            ("DOCUMENT %p created", this));
 
   if (!gCspPRLog)
     gCspPRLog = PR_NewLogModule("CSP");
+
+  if (!gXssPRLog)
+    gXssPRLog = PR_NewLogModule("XSS");
 #endif
 
   // Start out mLastStyleSheetSet as null, per spec
   SetDOMStringToNull(mLastStyleSheetSet);
 
   mLinksToUpdate.Init();
 }
 
@@ -2404,16 +2410,18 @@ nsDocument::StartDocumentLoad(const char
 
   RetrieveRelevantHeaders(aChannel);
 
   mChannel = aChannel;
 
   nsresult rv = InitCSP();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  InitXSSFilter();
+
   return NS_OK;
 }
 
 nsresult
 nsDocument::InitCSP()
 {
   if (CSPService::sCSPEnabled) {
     nsAutoString cspHeaderValue;
@@ -2543,16 +2551,61 @@ nsDocument::InitCSP()
   else { //CSP was not enabled!
     PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
            ("CSP is disabled, skipping CSP init for document %p", this));
   }
 #endif
   return NS_OK;
 }
 
+void nsDocument::InitXSSFilter()
+{
+  if (!nsXSSFilter::sXSSEnabled) {
+    return;
+  }
+
+  bool system = false;
+  nsIScriptSecurityManager *ssm = nsContentUtils::GetSecurityManager();
+
+  if (NS_SUCCEEDED(ssm->IsSystemPrincipal(NodePrincipal(), &system)) && system) {
+    // TODO: protection for privileged documents? how would attackers
+    // provide untrusted input to it?
+    return;
+  }
+
+  nsIPrincipal* principal = GetPrincipal();
+  if (!principal) {
+#ifdef PR_LOGGING
+    PR_LOG(gXssPRLog, PR_LOG_DEBUG,
+           ("XSS: no principal to put filter in"));
+    return;
+#endif
+  }
+
+  // since there is a principal and the pref is enabled, create the object.
+  nsRefPtr<nsXSSFilter> xss = new nsXSSFilter(this);
+
+  nsresult rv = xss->ScanRequestData();
+  if (NS_FAILED(rv)) {
+    // not necessarily a failure if we bail out on X-XSS protection header
+    return;
+  }
+  rv = principal->SetXSSFilter(xss);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+#ifdef PR_LOGGING
+  PR_LOG(gXssPRLog, PR_LOG_DEBUG,
+         ("XSS: Inserted XSSFilter at %p into principal %p", xss.get(), principal));
+#endif
+
+  return;
+}
+
 void
 nsDocument::StopDocumentLoad()
 {
   if (mParser) {
     mParserAborted = true;
     mParser->Terminate();
   }
 }
@@ -2954,16 +3007,27 @@ nsDocument::SetBaseURI(nsIURI* aURI)
   if (aURI && mDocumentBaseURI) {
     bool equalBases = false;
     mDocumentBaseURI->Equals(aURI, &equalBases);
     if (equalBases) {
       return NS_OK;
     }
   }
 
+  // xss filter check
+  nsRefPtr<nsXSSFilter> xss;
+  nsresult rv = NodePrincipal()->GetXSSFilter(getter_AddRefs(xss));
+  NS_ENSURE_SUCCESS(rv, rv);
+  // only check if origin changed
+  if (xss && !xss->PermitsBaseElement(mDocumentBaseURI, aURI)) {
+    PR_LOG(gXssPRLog, PR_LOG_DEBUG, ("XSS Filter blocked <base> attack"));
+    return NS_OK;
+  }
+
+
   if (aURI) {
     mDocumentBaseURI = NS_TryToMakeImmutable(aURI);
   } else {
     mDocumentBaseURI = nullptr;
   }
   RefreshLinkHrefs();
 
   return NS_OK;
diff --git a/content/base/src/nsDocument.h b/content/base/src/nsDocument.h
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -1251,16 +1251,17 @@ private:
   // Recomputes the visibility state but doesn't set the new value.
   VisibilityState GetVisibilityState() const;
 
   void PostUnblockOnloadEvent();
   void DoUnblockOnload();
 
   nsresult CheckFrameOptions();
   nsresult InitCSP();
+  void InitXSSFilter();
 
   // Sets aElement to be the pending pointer lock element. Once this document's
   // node principal's URI is granted the "fullscreen" permission, the pointer
   // lock request will be processed. At any one time there can be only one
   // pending pointer lock request; calling this clears the previous pending
   // request.
   static nsresult SetPendingPointerLockRequest(Element* aElement);
 
diff --git a/content/base/src/nsGkAtomList.h b/content/base/src/nsGkAtomList.h
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1057,16 +1057,17 @@ GK_ATOM(width, "width")
 GK_ATOM(window, "window")
 GK_ATOM(headerWindowTarget, "window-target")
 GK_ATOM(withParam, "with-param")
 GK_ATOM(wizard, "wizard")
 GK_ATOM(wrap, "wrap")
 GK_ATOM(headerDNSPrefetchControl,"x-dns-prefetch-control")
 GK_ATOM(headerCSP, "x-content-security-policy")
 GK_ATOM(headerCSPReportOnly, "x-content-security-policy-report-only")
+GK_ATOM(headerXSS, "x-xss-protection")
 GK_ATOM(headerXFO, "x-frame-options")
 GK_ATOM(x_western, "x-western")
 GK_ATOM(xml, "xml")
 GK_ATOM(xml_stylesheet, "xml-stylesheet")
 GK_ATOM(xmlns, "xmlns")
 GK_ATOM(xmp, "xmp")
 GK_ATOM(xulcontentsgenerated, "xulcontentsgenerated")
 GK_ATOM(yes, "yes")
diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -64,23 +64,25 @@
 #include "mozAutoDocUpdate.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIChannelPolicy.h"
 #include "nsChannelPolicy.h"
 #include "mozilla/dom/Element.h"
 #include "sampler.h"
 #include "nsObjectFrame.h"
 #include "nsDOMClassInfo.h"
+#include "nsXSSFilter.h"
 
 #include "nsWidgetsCID.h"
 #include "nsContentCID.h"
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gObjectLog = PR_NewLogModule("objlc");
+static PRLogModuleInfo* gXssPRLog = PR_NewLogModule("XSS");
 #endif
 
 #define LOG(args) PR_LOG(gObjectLog, PR_LOG_DEBUG, args)
 #define LOG_ENABLED() PR_LOG_TEST(gObjectLog, PR_LOG_DEBUG)
 
 class nsAsyncInstantiateEvent : public nsRunnable {
 public:
   nsObjectLoadingContent *mContent;
@@ -1384,16 +1386,31 @@ nsObjectLoadingContent::LoadObject(nsIUR
                                 nullptr, //extra
                                 &shouldLoad,
                                 nsContentUtils::GetContentPolicy(),
                                 secMan);
     if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
       HandleBeingBlockedByContentPolicy(rv, shouldLoad);
       return NS_OK;
     }
+
+    // xss filter
+    nsRefPtr<nsXSSFilter> xss;
+    rv = doc->NodePrincipal()->GetXSSFilter(getter_AddRefs(xss));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (xss) {
+      PR_LOG(gXssPRLog, PR_LOG_DEBUG,
+             ("nsObjectLoadingContent:XSSFilter:LoadObject"));
+      if (!xss->PermitsExternalObject(aURI)) {
+        PR_LOG(gXssPRLog, PR_LOG_DEBUG, ("XSSFilter blocked object loading."));
+        Fallback(false);
+        return NS_OK;
+      }
+    }
   }
 
   nsresult rv = NS_ERROR_UNEXPECTED;
   // This fallback variable MUST be declared after the notifier variable. Do NOT
   // change the order of the declarations!
   AutoFallback fallback(this, &rv);
 
   PRUint32 caps = GetCapabilities();
diff --git a/content/base/src/nsScriptLoader.cpp b/content/base/src/nsScriptLoader.cpp
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -37,23 +37,25 @@
 #include "nsIContentSecurityPolicy.h"
 #include "prlog.h"
 #include "nsIChannelPolicy.h"
 #include "nsChannelPolicy.h"
 #include "nsCRT.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsGenericElement.h"
 #include "nsCrossSiteListenerProxy.h"
+#include "nsXSSFilter.h"
 
 #include "mozilla/FunctionTimer.h"
 #include "mozilla/CORSMode.h"
 #include "mozilla/Attributes.h"
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gCspPRLog;
+static PRLogModuleInfo* gXssPRLog;
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 //////////////////////////////////////////////////////////////
 // Per-request data structure
 //////////////////////////////////////////////////////////////
@@ -114,16 +116,18 @@ nsScriptLoader::nsScriptLoader(nsIDocume
     mEnabled(true),
     mDeferEnabled(false),
     mDocumentParsingDone(false)
 {
   // enable logging for CSP
 #ifdef PR_LOGGING
   if (!gCspPRLog)
     gCspPRLog = PR_NewLogModule("CSP");
+  if (!gXssPRLog)
+    gXssPRLog = PR_NewLogModule("XSS");
 #endif
 }
 
 nsScriptLoader::~nsScriptLoader()
 {
   mObservers.Clear();
 
   if (mParserBlockingRequest) {
@@ -248,16 +252,29 @@ nsScriptLoader::ShouldLoadScript(nsIDocu
   NS_ENSURE_SUCCESS(rv, rv);
 
   // After the security manager, the content-policy stuff gets a veto
   rv = CheckContentPolicy(aDocument, aContext, aURI, aType);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  // xss filter for external script
+  nsRefPtr<nsXSSFilter> xss;
+  rv = aDocument->NodePrincipal()->GetXSSFilter(getter_AddRefs(xss));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (xss) {
+    PR_LOG(gXssPRLog, PR_LOG_DEBUG, ("Scriptloader:XSSFilter:external"));
+    if (!xss->PermitsExternalScript(aURI)) {
+      PR_LOG(gXssPRLog, PR_LOG_DEBUG, ("XSSFilter blocked external script."));
+      return NS_ERROR_XSS_BLOCK;
+    }
+  }
+
   return NS_OK;
 }
 
 nsresult
 nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType)
 {
   nsISupports *context = aRequest->mElement.get()
                          ? static_cast<nsISupports *>(aRequest->mElement.get())
@@ -663,16 +680,31 @@ nsScriptLoader::ProcessScriptElement(nsI
       csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
                                NS_ConvertUTF8toUTF16(asciiSpec),
                                scriptText,
                                aElement->GetScriptLineNumber());
       return false;
     }
   }
 
+  // xss filter for inline script
+  nsRefPtr<nsXSSFilter> xss;
+  rv = mDocument->NodePrincipal()->GetXSSFilter(getter_AddRefs(xss));
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (xss) {
+    PR_LOG(gXssPRLog, PR_LOG_DEBUG, ("Scriptloader:XSSFilter:inline"));
+    nsAutoString scriptText;
+    aElement->GetScriptText(scriptText);
+    if (!xss->PermitsInlineScript(scriptText)) {
+      PR_LOG(gXssPRLog, PR_LOG_DEBUG, ("XSSFilter blocked inline script."));
+      return NS_ERROR_XSS_BLOCK;
+    }
+  }
+
+
   // Inline scripts ignore ther CORS mode and are always CORS_NONE
   request = new nsScriptLoadRequest(aElement, version, CORS_NONE);
   request->mJSVersion = version;
   request->mLoading = false;
   request->mIsInline = true;
   request->mURI = mDocument->GetDocumentURI();
   request->mLineNo = aElement->GetScriptLineNumber();
 
diff --git a/content/base/src/nsXSSFilter.cpp b/content/base/src/nsXSSFilter.cpp
new file mode 100644
--- /dev/null
+++ b/content/base/src/nsXSSFilter.cpp
@@ -0,0 +1,568 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prlog.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+#include "nsIObserver.h"
+#include "mozilla/dom/Element.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIChannelPolicy.h"
+#include "nsIChannelEventSink.h"
+#include "nsIPropertyBag2.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsNetError.h"
+#include "nsChannelProperties.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "mozilla/Preferences.h"
+#include "nsIHttpChannel.h"
+#include "nsDocument.h"
+#include "nsXSSUtils.h"
+#include "nsScriptSecurityManager.h"
+#include "nsIServiceManager.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "nsISupportsPrimitives.h"
+#include "nsEscape.h"
+#include "nsIDocShell.h"
+#include "nsIWebNavigation.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsContentErrors.h"
+#include "nsXSSFilter.h"
+#include "nsIInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIUploadChannel.h"
+#include "nsIConsoleService.h"
+#include "nsIMutableArray.h"
+
+using namespace mozilla;
+
+bool nsXSSFilter::sXSSEnabled = true;
+bool nsXSSFilter::sReportOnly = false;
+bool nsXSSFilter::sBlockMode = false;
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo *gXssPRLog;
+#endif
+
+
+
+nsXSSFilter::nsXSSFilter(nsIDocument *parent)
+  : mParams(),
+    mParamsInitialized(false),
+    mParentDoc(parent),
+    mDomainCache(),
+    mIsEnabled(true),
+    mBlockMode(false)
+{ }
+
+nsXSSFilter::~nsXSSFilter()
+{ }
+
+
+void
+nsXSSFilter::InitializeStatics()
+{
+  nsXSSUtils::InitializeStatics();
+#ifdef PR_LOGGING
+  if (!gXssPRLog)
+    gXssPRLog = PR_NewLogModule("XSS");
+#endif
+  Preferences::AddBoolVarCache(&sXSSEnabled, "security.xssfilter.enable");
+  Preferences::AddBoolVarCache(&sReportOnly, "security.xssfilter.reportOnly");
+  Preferences::AddBoolVarCache(&sBlockMode, "security.xssfilter.blockMode");
+  LOG_XSS("Initialized Statics for XSS Filter");
+}
+
+
+/**
+ * Two Utility function to parse the X-XSS-Protection header.
+ * note: the code for parsing the header purposedly copies Webkit, I
+ * hope that's fine. Returns true if there is more to parse.
+ */
+bool
+skipWhiteSpace(const nsACString& str, PRUint32& pos,
+               bool fromHttpEquivMeta)
+{
+  PRUint32 len = str.Length();
+
+  if (fromHttpEquivMeta) {
+    while (pos != len && str[pos] <= ' ')
+      ++pos;
+  } else {
+    while (pos != len && (str[pos] == '\t' || str[pos] == ' '))
+      ++pos;
+  }
+  return pos != len;
+}
+
+/**
+ * Returns true if the function can match the whole token (case insensitive).
+ * Note: Might return pos == str.Length()
+ */
+bool
+skipToken(const nsACString& str, PRUint32& pos,
+          const nsACString& token)
+{
+  PRUint32 len = str.Length();
+  PRUint32 tokenPos = 0;
+	PRUint32 tokenLen = token.Length();
+
+  while (pos != len && tokenPos < tokenLen) {
+    if (tolower(str[pos]) != token[tokenPos++]) {
+      return false;
+    }
+    ++pos;
+  }
+
+  return true;
+}
+
+nsresult
+nsXSSFilter::ScanRequestData()
+{
+  if (!mParentDoc->GetChannel()) {
+    return NS_ERROR_FAILURE;
+  }
+  nsCOMPtr<nsIHttpChannel> httpChannel =
+    do_QueryInterface(mParentDoc->GetChannel());
+  if (!httpChannel) {
+    return NS_ERROR_FAILURE;
+  }
+  LOG_XSS_CALL("ScanRequestData");
+
+  // Webkit logic (the de facto standard we want to comply with):
+  // 1. strip leading whitespaces.
+  // 2. if the first char is 0, disable the filter
+  // 3. if the first char is 1 enabled the filter
+  // 4. if it is "1[ ]*mode[ ]*=[ ]*block$", then enabled in block mode
+  // https://bugs.webkit.org/show_bug.cgi?id=27312
+  nsCAutoString xssHeaderValue, strippedHeaderValue;
+  httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Xss-Protection"),
+                                 xssHeaderValue);
+  LOG_XSS_1("Header: '%s'", xssHeaderValue.get());
+
+  // no need to skip spaces before the beginning of the string, firefox
+  // does this for us
+
+  if (xssHeaderValue.IsEmpty()) {
+    mIsEnabled = true;
+    return NS_OK;
+  }
+  if (xssHeaderValue[0] == '0') {
+    mIsEnabled = false;
+    return NS_OK;
+  }
+
+  PRUint32 len = xssHeaderValue.Length();
+  PRUint32 pos = 0;
+
+  if (xssHeaderValue[pos++] == '1' &&
+      skipWhiteSpace(xssHeaderValue, pos, false) &&
+      xssHeaderValue[pos++] == ';' &&
+      skipWhiteSpace(xssHeaderValue, pos, false) &&
+      skipToken(xssHeaderValue, pos, NS_LITERAL_CSTRING("mode")) &&
+      skipWhiteSpace(xssHeaderValue, pos, false) &&
+      xssHeaderValue[pos++] == '=' &&
+      skipWhiteSpace(xssHeaderValue, pos, false) &&
+      skipToken(xssHeaderValue, pos, NS_LITERAL_CSTRING("block")) &&
+      pos == len) {
+    mIsEnabled = true;
+    mBlockMode = true;
+    LOG_XSS("Block mode activated");
+    return NS_OK;
+  }
+
+  // else, do as the header was not there
+  mIsEnabled = true;
+  return NS_OK;
+}
+
+bool
+nsXSSFilter::PermitsInlineScript(const nsAString& aScript)
+{
+  LOG_XSS_CALL("Inline");
+  LOG_XSS_1("script: %s",
+            NS_ConvertUTF16toUTF8(Substring(aScript, 0, 70)).get());
+
+  if (!IsEnabled()) {
+    return true;
+  }
+
+  nsXSSUtils::CheckInline(aScript, GetParams());
+
+  if (nsXSSUtils::HasAttack(GetParams())) {
+    NotifyViolation(NS_LITERAL_STRING("Inline Script"), aScript, GetURI());
+    return IsReportOnly();
+  }
+  return true;
+}
+
+bool
+nsXSSFilter::PermitsExternalScript(nsIURI *aURI)
+{
+  if (!aURI) {
+    return true;
+  }
+
+#ifdef PR_LOGGING
+  LOG_XSS_CALL("External");
+  nsCAutoString spec;
+  aURI->GetSpec(spec);
+  LOG_XSS_1("script URI: %s", spec.get());
+#endif
+
+  if (!IsEnabled()) {
+    return true;
+  }
+
+  // fetch value from cache
+  bool c;
+  nsAutoString domain;
+  DomainMap& cache = GetDomainCache();
+  nsXSSUtils::GetDomain(aURI, domain);
+  if (cache.Get(domain, &c)) {
+    return c;
+  }
+
+  nsXSSUtils::CheckExternal(aURI, GetURI(), GetParams());
+  if (nsXSSUtils::HasAttack(GetParams())) {
+    nsCAutoString spec;
+    aURI->GetSpec(spec);
+    NotifyViolation(NS_LITERAL_STRING("External Script"), 
+                    NS_ConvertUTF8toUTF16(spec), GetURI());
+    cache.Put(domain, false);
+    return IsReportOnly();
+  }
+  cache.Put(domain, true);
+  return true;
+}
+
+bool
+nsXSSFilter::PermitsJSUrl(const nsAString& aURI)
+{
+  LOG_XSS_CALL("JSUrl");
+  LOG_XSS_1("javascript url: %s",
+            NS_ConvertUTF16toUTF8(Substring(aURI, 0, 70)).get());
+
+  if (!IsEnabled()) {
+    return true;
+  }
+
+  nsAutoString escUri;
+  nsXSSUtils::UnescapeLoop(aURI, escUri, mParentDoc);
+  LOG_XSS_1("escaped javascript url: %s",
+            NS_ConvertUTF16toUTF8(escUri).get());
+
+  nsXSSUtils::CheckInline(escUri, GetParams());
+
+  if (nsXSSUtils::HasAttack(GetParams())) {
+    NotifyViolation(NS_LITERAL_STRING("JS URL"), aURI, GetURI());
+    return IsReportOnly();
+  }
+  return true;
+}
+
+bool
+nsXSSFilter::PermitsEventListener(const nsAString& aScript)
+{
+  LOG_XSS_CALL("Event");
+  LOG_XSS_1("Event: %s",
+            NS_ConvertUTF16toUTF8(Substring(aScript, 0, 70)).get());
+
+  if (!IsEnabled()) {
+    return true;
+  }
+
+  nsXSSUtils::CheckInline(aScript, GetParams());
+
+  if (nsXSSUtils::HasAttack(GetParams())) {
+    NotifyViolation(NS_LITERAL_STRING("Event Listener"), aScript, GetURI());
+    return IsReportOnly();
+  }
+  return true;
+}
+
+bool
+nsXSSFilter::PermitsBaseElement(nsIURI *aOldURI, nsIURI* aNewURI)
+{
+  if (!aOldURI || !aNewURI) {
+    return true;
+  }
+
+#ifdef PR_LOGGING
+  LOG_XSS_CALL("Base");
+  nsCAutoString spec;
+  aNewURI->GetSpec(spec);
+  LOG_XSS_1("new URI: %s", spec.get());
+#endif
+
+  if (!IsEnabled()) {
+    return true;
+  }
+
+  // allow the base element to change the base url on the same
+  // registered domain.
+  nsAutoString oldD, newD;
+  nsXSSUtils::GetDomain(aOldURI, oldD);
+  nsXSSUtils::GetDomain(aNewURI, newD);
+  if (oldD.Equals(newD)) {
+    return true;
+  }
+
+  nsXSSUtils::CheckExternal(aNewURI, GetURI(), GetParams());
+  if (nsXSSUtils::HasAttack(GetParams())) {
+    nsCAutoString spec;
+    aNewURI->GetSpec(spec);
+    NotifyViolation(NS_LITERAL_STRING("Base Element"), 
+                    NS_ConvertUTF8toUTF16(spec), GetURI());
+    return IsReportOnly();
+  }
+  return true;
+}
+
+bool
+nsXSSFilter::PermitsExternalObject(nsIURI *aURI)
+{
+  if (!aURI) {
+    return true;
+  }
+
+#ifdef PR_LOGGING
+  LOG_XSS_CALL("Object");
+  nsCAutoString spec;
+  aURI->GetSpec(spec);
+  LOG_XSS_1("object URI: %s", spec.get());
+#endif
+
+  if (!IsEnabled()) {
+    return true;
+  }
+
+  // fetch value from cache
+  bool c;
+  nsAutoString domain;
+  DomainMap& cache = GetDomainCache();
+  nsXSSUtils::GetDomain(aURI, domain);
+  if (cache.Get(domain, &c)) {
+    return c;
+  }
+
+  nsXSSUtils::CheckExternal(aURI, GetURI(), GetParams());
+  if (nsXSSUtils::HasAttack(GetParams())) {
+    nsCAutoString spec;
+    aURI->GetSpec(spec);
+    NotifyViolation(NS_LITERAL_STRING("Object"),
+                    NS_ConvertUTF8toUTF16(spec), GetURI());
+    return IsReportOnly();
+  }
+  return true;
+}
+
+bool
+nsXSSFilter::PermitsDataURL(nsIURI *aURI)
+{
+  if (!aURI) {
+    return true;
+  }
+
+  nsCAutoString spec;
+  aURI->GetSpec(spec);
+  LOG_XSS_CALL("DataURL");
+  LOG_XSS_1("data URL: %s", spec.get());
+
+  if (!IsEnabled()) {
+    return true;
+  }
+
+  nsXSSUtils::CheckInline(NS_ConvertUTF8toUTF16(spec), GetParams());
+
+  if (nsXSSUtils::HasAttack(GetParams())) {
+    NotifyViolation(NS_LITERAL_STRING("Data URL"),
+                    NS_ConvertUTF8toUTF16(spec), GetURI());
+    return IsReportOnly();
+  }
+  return true;
+}
+
+bool
+nsXSSFilter::PermitsJSAction(const nsAString& aCode)
+{
+  LOG_XSS_CALL("JSAction");
+  LOG_XSS_1("JS: %s", NS_ConvertUTF16toUTF8(Substring(aCode, 0, 100)).get());
+
+  if (!IsEnabled()) {
+    return true;
+  }
+
+  nsXSSUtils::CheckInline(aCode, GetParams());
+
+  if (nsXSSUtils::HasAttack(GetParams())) {
+    NotifyViolation(NS_LITERAL_STRING("JS Action"), aCode, GetURI());
+    return IsReportOnly();
+  }
+  return true;
+}
+
+ParameterArray&
+nsXSSFilter::GetParams()
+{
+  if (!mParamsInitialized) {
+
+    //get params
+    nsXSSUtils::ParseURI(GetURI(), mParams, mParentDoc);
+
+    //post params
+    nsCOMPtr<nsIHttpChannel> httpChannel =
+      do_QueryInterface(mParentDoc->GetChannel());
+    nsCAutoString method;
+    httpChannel->GetRequestMethod(method);
+    if (method.EqualsLiteral("POST")) {
+      nsCOMPtr<nsIUploadChannel> uploadChannel =
+        do_QueryInterface(httpChannel);
+      nsCOMPtr<nsIInputStream> uploadStream;
+      uploadChannel->GetUploadStream(getter_AddRefs(uploadStream));
+      // rewind the stream;
+      nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(uploadStream);
+      seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+
+      PRUint32 len;
+      uploadStream->Available(&len);
+      char* buf = static_cast<char*>(moz_xmalloc(len+1));
+      PRUint32 bytesRead;
+      uploadStream->Read(buf, len, &bytesRead);
+      if (bytesRead != len) {
+        free(buf);
+      }
+      buf[len] = '\0';
+      nsXSSUtils::ParsePOST(buf, mParams, mParentDoc);
+      free(buf);
+    }
+
+    mParamsInitialized = true;
+
+  }
+
+  return mParams;
+}
+
+nsIURI*
+nsXSSFilter::GetURI()
+{
+  return mParentDoc->GetDocumentURI();
+}
+
+DomainMap&
+nsXSSFilter::GetDomainCache()
+{
+  if (!mDomainCache.IsInitialized()) {
+    mDomainCache.Init();
+  }
+  return mDomainCache;
+}
+
+bool nsXSSFilter::IsEnabled() { return sXSSEnabled && mIsEnabled; }
+bool nsXSSFilter::IsBlockMode() { return sBlockMode || mBlockMode; }
+bool nsXSSFilter::IsReportOnly() { return sReportOnly; }
+
+
+class nsXSSNotifier: public nsRunnable
+{
+public:
+  nsXSSNotifier(const nsAString& policy, const nsAString& content, nsIURI* url, bool blockMode) :
+    mPolicy(policy), mContent(content),  mURI(url), mBlockMode(blockMode)
+  { };
+
+  NS_IMETHOD Run() {
+    LOG_XSS("Sending Observer Notification");
+    nsCOMPtr<nsIObserverService> observerService =
+      services::GetObserverService();
+    if (!observerService) {
+      return NS_OK;
+    }
+
+    // the nsIArray will contain four parameters: violated policy,
+    // content, url and blockMode
+    nsresult rv = NS_OK;
+    nsCOMPtr<nsIMutableArray> params = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsISupportsCString>
+      wrappedPolicy(do_CreateInstance("@mozilla.org/supports-cstring;1"));
+    wrappedPolicy->SetData(NS_ConvertUTF16toUTF8(mPolicy));
+
+    nsCOMPtr<nsISupportsCString>
+      wrappedContent(do_CreateInstance("@mozilla.org/supports-cstring;1"));
+    wrappedContent->SetData(NS_ConvertUTF16toUTF8(mContent));
+
+    nsCOMPtr<nsISupportsCString>
+      wrappedUrl(do_CreateInstance("@mozilla.org/supports-cstring;1"));
+    nsCAutoString spec;
+    mURI->GetSpec(spec);
+    wrappedUrl->SetData(spec);
+
+    nsCOMPtr<nsISupportsPRBool>
+      wrappedBlock(do_CreateInstance("@mozilla.org/supports-PRBool;1"));
+    wrappedBlock->SetData(mBlockMode);
+    
+    params->AppendElement(wrappedPolicy, false);
+    params->AppendElement(wrappedContent, false);
+    params->AppendElement(wrappedUrl, false);
+    params->AppendElement(wrappedBlock, false);
+    observerService->NotifyObservers(params, "xss-on-violate-policy", NULL);
+    return NS_OK;
+  }
+
+
+
+private:
+  ~nsXSSNotifier() { };
+  nsString mPolicy, mContent;
+  nsIURI* mURI;
+  bool mBlockMode;
+};
+
+nsresult
+nsXSSFilter::NotifyViolation(const nsAString& policy, const nsAString& content, nsIURI* url)
+{
+  LOG_XSS("Violation");
+
+  nsCAutoString spec;
+  url->GetSpec(spec);
+
+  // send to console
+  nsCOMPtr<nsIConsoleService> aConsoleService =
+    do_GetService( "@mozilla.org/consoleservice;1" );
+  nsAutoString msg;
+  msg.Assign(NS_LITERAL_STRING("XSS Violation on URL "));
+  msg.Append(NS_ConvertUTF8toUTF16(spec));
+  msg.Append(NS_LITERAL_STRING(": "));
+  msg.Append(policy);
+  aConsoleService->
+    LogStringMessage(msg.get());
+
+  // send to observers as xss-on-violate-policy
+  nsCOMPtr<nsIThread> thread = do_GetMainThread();
+  if (!thread) {
+    LOG_XSS("Main thread unavailable");
+    return NS_OK;
+  }
+  nsCOMPtr<nsIRunnable> runnable = new nsXSSNotifier(policy, content, url, IsBlockMode());
+  thread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
+
+  // block the page is block mode is enabled
+  if (IsBlockMode()) {
+    nsCOMPtr<nsIHttpChannel> httpChannel =
+      do_QueryInterface(mParentDoc->GetChannel());
+    httpChannel->Cancel(NS_ERROR_XSS_BLOCK);
+  }
+
+  return NS_OK;
+}
diff --git a/content/base/src/nsXSSFilter.h b/content/base/src/nsXSSFilter.h
new file mode 100644
--- /dev/null
+++ b/content/base/src/nsXSSFilter.h
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// TODO: do we have a MIME parser for POST requests? brandon?  jonas
+// wrote one for the sjs server of html/content, but it's a javascript
+// simplification and can only be copied, not reused.
+// thunderbird can definitely parse mime, but it might
+// be a little bit overkill. look at the AsyncConvertData
+// method. right now we are just appending the whole mime data as a
+// parameter, but that might hurt performance for large POSTs. I think
+// chrome had the same problem, how did they solve it?
+
+// TODO: what about report-only and report-url? we could copy the idea
+// from CSPs. No other filter has them though, so we would need to
+// come up with new headers.
+
+// TODO: should i comment out the static test stuff in optimized
+// builds? it might appear as static data in libxul, taking up memory.
+
+// TODO: caching eval result? I thought it was useless, but it seems
+// that the JS engine is caching eval compilation already... should we
+// cache XSS checks along with the compiled code?
+
+// TODO: the filter can be configured to try to stop HPP-based XSS as well.
+// e.g. http://server/?q=<script>&q=alert(1)&q=</script>
+// they get concatenated!
+// we could reassemble the params w/o the param names and equal signs.
+// perhaps we can trigger this iff a param has been seen twice.
+
+// TODO: it would be great to cache jsurl and data url results as
+// well, as we do with event listeners. Unfortunately, the channels
+// where we interpose are recreated for each single execution, thus no
+// state can be kept. Moving the interposition up in the docshell does
+// not seem like a good idea either.
+
+
+#ifndef nsXSSFilter_h
+#define nsXSSFilter_h
+
+#include "nsTArray.h"
+#include "jsapi.h"
+#include "nsXSSUtils.h"
+
+class nsIURI;
+class nsIHttpChannel;
+class nsAString;
+class nsIDocument;
+
+/**
+ * A refcounted C++ class that every document owns through its
+ * principal. Before executing potentially injected JavaScript, hooks
+ * scattered through the codebase retrieve the principal and call back
+ * into these functions to check whether the code about to be executed
+ * is an XSS attack. Most of the hooks are placed along with CSP
+ * checks, but we don't currently use the nsIContentPolicy for all
+ * external resources.
+ */
+class nsXSSFilter {
+
+ public:
+  nsXSSFilter(nsIDocument *parent);
+  virtual ~nsXSSFilter();
+
+  NS_INLINE_DECL_REFCOUNTING(nsXSSFilter)
+
+  /**
+   * Called after the XSS object is created to look at the
+   * X-XSS-Protection header and configure the filter.
+   */
+  nsresult ScanRequestData();
+  /**
+   * Checks whether an inlined <script>...</script> element should be
+   * executed.
+   */
+  bool PermitsInlineScript(const nsAString& aScript);
+  /**
+   * Checks whether an external <script src=...></script> element
+   * should be fetched and executed.
+   */
+  bool PermitsExternalScript(nsIURI *aURI);
+  /**
+   * Checks whether a URL with the javascript: protocol should be
+   * allowed. Lazy.
+   */
+  bool PermitsJSUrl(const nsAString& aURI);
+  /**
+   * Checks whether an event listener should be fired due to an event.
+   * Lazy, and caches results for further executions of the handler.
+   */
+  bool PermitsEventListener(const nsAString& aScript);
+  /**
+   * Checks whether the base URL for a document should be changed by a
+   * <base href=... /> element.
+   */
+  bool PermitsBaseElement(nsIURI *aOldURI, nsIURI* aNewURI);
+  /**
+   * Checks whether an object element specifying an external resource
+   * should be fetched and loaded.
+   */
+  bool PermitsExternalObject(nsIURI *aURI);
+  /**
+   * Checks whether a data URL that contains a script should be
+   * executed. Lazy.
+   */
+  bool PermitsDataURL(nsIURI *aURI);
+  /**
+   * Checks whether the string argument from certain calls in
+   * JavaScript (eval, setTimeout, setInterval) should be executed.
+   */
+  bool PermitsJSAction(const nsAString& code);
+
+  /**
+   * Sets up Observers for the static preferences described below.
+   */
+  static void InitializeStatics();
+  /**
+   * Synced to preference security.xssfilter.enabled
+   * The filter allows all if this is set to false.
+   */
+  static bool sXSSEnabled;
+  /**
+   * Synced to security.xssfilter.reportOnly. When set to true,
+   * policy calls will report violations to observers, but will allow
+   * all script content regardless.
+   */
+  static bool sReportOnly;
+  /**
+   * Synced to security.xssfilter.blockMode. When set to true, all
+   * violations will terminate execution of the page and will
+   * display an error page.
+   */
+  static bool sBlockMode;
+
+ private:
+  /**
+   * An array of parameters extracted from the GET/POST
+   * request. Lazily evaluated through GetParams().
+   */
+  ParameterArray mParams;
+  /**
+   * Whether we still need to fill in params. (b/c empty != uninitialized)
+   */
+  bool mParamsInitialized;
+  /**
+   * The document that owns the filter (through its principal).
+   */
+  nsIDocument *mParentDoc;
+  /**
+   * A cache of safe domains to avoid checking URLs for the same
+   * domain twice. Used by external URLs and objects.
+   */
+  DomainMap mDomainCache;
+  /**
+   * Whether the XSS filter is enabled or disabled by the
+   * X-XSS-Protection header.
+   */
+  bool mIsEnabled;
+  /**
+   * Whether the XSS header explicitly requests block mode.
+   */
+  bool mBlockMode;
+  /**
+   * Returns true if XSS is enabled (pref && header)
+   */
+  bool IsEnabled();
+  /**
+   * Returns true if report only mode is enabled (pref).
+   */
+  bool IsReportOnly();
+  /**
+   * Returns true if block mode is enabled (pref || header).
+   */
+  bool IsBlockMode();
+  /**
+   * Gets the URL of the document for origin checks
+   */
+  nsIURI* GetURI();
+  /**
+   * Gets the array of parameters provided through the request for the
+   * taint-inference algorithm. These are lazily evaluated.
+   */
+  ParameterArray& GetParams();
+  /**
+   * Lazily Initializes the domain cache
+   */
+  DomainMap& GetDomainCache();
+  /**
+   * Notifies the user of an XSS Violation. Three events are generated:
+   * 1) message to error console
+   * 2) notify observers of xss-on-violate-policy (Currently, there is a
+   * pref that is used by browser.js to popup a notification. We plan
+   * something less consipcuous for the final version)
+   * 3) If block mode is enabled, it also navigates away the tab to an
+   * error page.
+   */
+  nsresult NotifyViolation(const nsAString& policy, const nsAString& content, nsIURI* url);
+};
+
+
+
+
+#endif /* nsXSSFilter_h */
diff --git a/content/base/src/nsXSSUtils.cpp b/content/base/src/nsXSSUtils.cpp
new file mode 100644
--- /dev/null
+++ b/content/base/src/nsXSSUtils.cpp
@@ -0,0 +1,1704 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsTArray.h"
+#include "nsIURI.h"
+#include "nsAString.h"
+#include "nsXSSUtils.h"
+#include "nsIIOService.h"
+#include "nsIURL.h"
+#include "nsCOMPtr.h"
+#include "nsIIOService.h"
+#include "nsNetUtil.h"
+#include "nsEscape.h"
+#include "nsIEffectiveTLDService.h"
+#include <string.h>
+#include <wchar.h>
+#include "math.h"
+#include "nsGenericHTMLElement.h"
+#include "mozAutoDocUpdate.h"
+#include "nsIDocument.h"
+#include "mozilla/dom/Element.h"
+
+#include "nsScriptLoader.h"
+#include "nsHtml5Module.h"
+
+using namespace mozilla::dom;
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo *gXssPRLog;
+#endif
+
+
+#define THRESHOLD 0.2
+#define MIN_INL_LEN 10
+#define MIN_EXT_LEN 5
+#define MIN_PARAM_LEN 5
+#define MIN_MATCH_LEN 5
+#define SCRIPT_LEN 40
+
+#define PATH_CHARS " -/.;"
+#define QUERY_CHARS " &=-"
+#define REF_CHARS "-"
+
+Parameter::Parameter(const nsAString& aName, const nsAString& aValue,
+                     bool aDangerous, bool aSpecial, bool aAttack)
+  : name(aName),
+    value(aValue),
+    attack(aAttack),
+    dangerous(aDangerous),
+    special(aSpecial)
+{ }
+
+Parameter::Parameter(const char* aName, const char* aValue,
+                     bool aDangerous, bool aSpecial, bool aAttack)
+  : name(NS_ConvertASCIItoUTF16(aName)),
+    value(NS_ConvertASCIItoUTF16(aValue)),
+    attack(aAttack),
+    dangerous(aDangerous),
+    special(aSpecial)
+{ }
+
+bool
+Parameter::operator==(const Parameter& other) const
+{
+  return name.Equals(other.name) && value.Equals(other.value) &&
+    attack == other.attack && dangerous == other.dangerous &&
+    special == other.special;
+}
+
+/* for debugging */
+void
+Parameter::print() const
+{
+  printf("name = '%s', ", NS_ConvertUTF16toUTF8(this->name).get());
+  printf("value = '%s', ", NS_ConvertUTF16toUTF8(this->value).get());
+  if (attack) {
+    printf(" attack, ");
+  }
+  if (dangerous) {
+    printf(" dangerous, ");
+  }
+  if (special) {
+    printf(" special");
+  }
+  printf("\n");
+}
+
+/* [a-zA-Z0-9_] */
+bool
+IsAlphanumeric(PRUnichar c)
+{
+  return (PRUnichar('a') <= c && c <= PRUnichar('z')) ||
+    (PRUnichar('A') <= c && c <= PRUnichar('Z')) ||
+    (PRUnichar('0') <= c && c <= PRUnichar('9')) ||
+    c == PRUnichar('_');
+}
+
+bool
+IsCharListed(PRUnichar c, const char *chars)
+{
+  for (PRUint8 i = 0; i < strlen(chars); i++)
+    if (c == PRUnichar(chars[i])) {
+      return true;
+    }
+  return false;
+}
+
+nsresult
+nsXSSUtils::TrimSafeChars(const nsAString& str, nsAString& result,
+                          const char *chars, PRUint8 flags)
+{
+  if (str.IsEmpty()) {
+    result = str;
+    return NS_OK;
+  }
+  PRUint32 start;
+  for (start = 0; start < str.Length(); start++) {
+    PRUnichar cur = str[start];
+    if (!IsAlphanumeric(cur) && !IsCharListed(cur, chars)) {
+      break;
+    }
+  }
+  PRUint32 end;
+  for (end = str.Length() - 1; end > start; end--) {
+    PRUnichar cur = str[end];
+    if (!IsAlphanumeric(cur) && !IsCharListed(cur, chars)) {
+      break;
+    }
+  }
+  if (start > end) {
+    result.Truncate(0);
+    return NS_OK;
+  }
+  bool leave_beg = (flags >> 0) % 2;
+  bool leave_end = (flags >> 1) % 2;
+  if (leave_beg) {
+    start = 0;
+  }
+  if (leave_end) {
+    end = str.Length();
+  }
+
+  result = Substring(str, start, end - start + 1);
+  return NS_OK;
+}
+
+nsresult
+nsXSSUtils::ParseURI(nsIURI *aURI, ParameterArray& aParams, nsIDocument* doc)
+{
+  nsresult rv;
+  nsAutoString result;
+  nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv);
+  if (NS_FAILED(rv)) {
+    LOG_XSS("Not an nsIURL");
+    return rv;
+  }
+
+  nsCAutoString path8, query8, ref8;
+  url->GetFilePath(path8);
+  url->GetQuery(query8);
+  url->GetRef(ref8);
+
+  nsAutoString path, query, ref;
+  nsAutoString tPath, tQuery, tRef;
+  CopyUTF8toUTF16(path8, path);
+  CopyUTF8toUTF16(query8, query);
+  CopyUTF8toUTF16(ref8, ref);
+
+  //trimming was originally used to cut away parts of the request that
+  //did not contain suspicious characters, but it has now been
+  //replaced by a more conservative policy, and the function is just
+  //used as if it was something like "hasSuspiciousChars".
+  nsXSSUtils::UnescapeLoop(path, path, doc);
+  nsXSSUtils::TrimSafeChars(path, tPath, PATH_CHARS);
+  nsXSSUtils::UnescapeLoop(query, query, doc);
+  nsXSSUtils::TrimSafeChars(query, tQuery, QUERY_CHARS);
+  nsXSSUtils::UnescapeLoop(ref, ref, doc);
+  nsXSSUtils::TrimSafeChars(ref, tRef, REF_CHARS);
+
+  if (!tPath.IsEmpty() || !tQuery.IsEmpty() || !tRef.IsEmpty()) {
+    result += Substring(path, 1, path.Length() - 1); // trim '/'
+    if (!result.IsEmpty() && !query.IsEmpty()) {
+      result += NS_LITERAL_STRING("?");
+    }
+    result += query;
+    if (!result.IsEmpty() && !ref.IsEmpty()) {
+      result += NS_LITERAL_STRING("#");
+    }
+    result += ref;
+    aParams.AppendElement(Parameter(NS_LITERAL_STRING("URL"),
+                                    result, true, true));
+  }
+  return NS_OK;
+}
+
+nsresult
+nsXSSUtils::ParsePOST(char *request, ParameterArray& aParams, nsIDocument* doc)
+{
+  char* separator = strstr(request, "\r\n\r\n");
+  char* urlencoded = strstr(request, "application/x-www-form-urlencoded");
+  char* multipart = strstr(request, "multipart/form-data");
+
+  if (urlencoded && urlencoded < separator) {
+    nsAutoString params;
+    params.Assign(NS_ConvertASCIItoUTF16(separator+4));
+
+    LOG_XSS_1("POST payload: %s", NS_ConvertUTF16toUTF8(params).get());
+
+    // replace + with ' '
+    PRUnichar* cur = params.BeginWriting();
+    PRUnichar* end = params.EndWriting();
+    for (; cur < end; ++cur) {
+      if (PRUnichar('+') == *cur) {
+        *cur = PRUnichar(' ');
+      }
+    }
+    // escape it like a normal url path
+    nsXSSUtils::UnescapeLoop(params, params, doc);
+    nsXSSUtils::TrimSafeChars(params, params, QUERY_CHARS);
+    LOG_XSS_1("POST escaped payload: %s",
+              NS_ConvertUTF16toUTF8(params).get());
+    if (!params.IsEmpty()) {
+      aParams.AppendElement(Parameter(NS_LITERAL_STRING("POST"),
+                                      params, true, true));
+    }
+  } else if(multipart && multipart < separator) {
+    // TODO: this works, but it's not optimal: it would be best to
+    // isolate the single parameters (and concatenate them in the
+    // paramarray). this wastes at lot of cpu power during the string
+    // matching to attempt matching data that is not under the control
+    // of the attacker (MIME boundaries)
+    nsAutoString params;
+    params.Assign(NS_ConvertASCIItoUTF16(separator+4));
+
+    LOG_XSS_1("MIME POST payload: %s",
+              NS_ConvertUTF16toUTF8(params).get());
+    if (!params.IsEmpty()) {
+      aParams.AppendElement(Parameter(NS_LITERAL_STRING("MIMEPOST"),
+                                          params, true, true));
+    }
+  } else {
+    LOG_XSS("Unsupported or invalid POST request");
+  }
+  return NS_OK;
+
+}
+
+nsresult
+nsXSSUtils::UnescapeLoop(const nsAString& aString, nsAString& result,
+                         nsIDocument *doc)
+{
+  nsAutoString tmp;
+  nsCAutoString tmp8;
+
+  tmp = aString;
+  do {
+    result = tmp;
+    if (doc) {
+      nsXSSUtils::DecodeHTMLEntities(tmp, tmp, doc);
+    }
+    // expensive?
+    CopyUTF16toUTF8(tmp, tmp8);
+    NS_UnescapeURL(tmp8);
+    CopyUTF8toUTF16(tmp8, tmp);
+  } while(!tmp.Equals(result));
+
+  return NS_OK;
+}
+
+nsresult
+nsXSSUtils::DecodeHTMLEntities(const nsAString& aString, nsAString& result,
+                               nsIDocument* doc)
+{
+  // 2 bugs so far, both in svg reftest. one seems to be a random
+  // failure, while the second seems to be triggered by the call to
+  // setinnerhtml. specifically, an assert condition is violated.
+
+  LOG_XSS("DecodeHTMLEntities");
+  result = aString;
+
+  Element *rootElement = doc->GetRootElement();
+  if (rootElement) {
+    LOG_XSS("Has root element.");
+    if (rootElement->GetNameSpaceID() == kNameSpaceID_SVG) {
+      LOG_XSS("SVG Doc, bailing out");
+      return NS_OK; // SVG doesn't support setting a title
+    }
+  } else {
+    LOG_XSS("No root element");
+  }
+
+  mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, true);
+
+  nsCOMPtr<nsINodeInfo> titleInfo;
+  titleInfo =
+    doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::title, nsnull,
+                                        kNameSpaceID_XHTML,
+                                        nsIDOMNode::ELEMENT_NODE);
+  if (!titleInfo) {
+    return NS_OK;
+  }
+  nsIContent* title = NS_NewHTMLTitleElement(titleInfo.forget());
+  if (!title) {
+    return NS_OK;
+  }
+  nsCOMPtr<nsGenericHTMLElement> elTitle(do_QueryInterface(title));
+  elTitle->SetInnerHTML(aString);
+  title->GetTextContent(result);
+  return NS_OK;
+}
+
+PRUint32
+nsXSSUtils::GetHostLimit(nsIURI* aURI)
+{
+  nsCAutoString prePath, path;
+  aURI->GetPrePath(prePath);
+  aURI->GetPath(path);
+  if (!path.IsEmpty()) {
+    return prePath.Length() + 1;
+  }
+  NS_ERROR("Path Empty. Does this ever happen? It shouldn't");
+  return prePath.Length();
+}
+
+
+nsresult
+nsXSSUtils::GetDomain(nsIURI* aURI, nsAString& result)
+{
+  nsresult rv;
+  nsCOMPtr<nsIEffectiveTLDService>
+    eTLDService(do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCAutoString result8;
+  rv = eTLDService->GetBaseDomain(aURI, 0, result8);
+  // we might have a problem with unicode domains, as this
+  // returns an ASCII string. If the conversion is lossy there is a
+  // chance for a clash between different names.
+  if (rv == NS_ERROR_HOST_IS_IP_ADDRESS) {
+    aURI->GetHost(result8);
+    result = NS_ConvertUTF8toUTF16(result8);
+    return NS_OK;
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+  result = NS_ConvertUTF8toUTF16(result8);
+  return NS_OK;
+}
+
+void
+nsXSSUtils::InitializeStatics()
+{
+#ifdef PR_LOGGING
+  gXssPRLog = PR_NewLogModule("XSS");
+#endif
+  LOG_XSS("Initialized Statics for XSS Utils");
+}
+
+bool
+nsXSSUtils::FindInlineXSS(const nsAString& aParam, const nsAString& aScript)
+{
+  MatchRes mres;
+    // the script could be cut for efficiency, but since there is a
+    // bound on the parameter length (which is usually a small number), it
+    // probably does not improve performance much.
+  if (aParam.Length() >= aScript.Length()) {
+    // base case where the attacker injects the whole script. Since
+    // the tags/quotes are stripped by the parser, the script ends up
+    // being shorter.
+    nsXSSUtils::P1FastMatchReverse(aParam, aScript, THRESHOLD, mres);
+  } else if (aParam.Length() >= aScript.Length() * (1-THRESHOLD)) {
+    // if we do not care about partial injections the only possible
+    // case of |script| > |param| is due to sanitization increasing
+    // the size of the script (e.g. add slashes). In this case, it
+    // makes sense to match only if the length is close enough.
+    nsXSSUtils::P1FastMatch(aParam, aScript, THRESHOLD, mres);
+  }
+  mres.ClearInvalidMatches(THRESHOLD);
+
+  for (PRUint32 i = 0; i < mres.elem_.Length(); i++) {
+    LOG_XSS_2("Examining Match in FindInlineXSS: %d %d\n", mres[i].matchBeg_,
+              mres[i].matchEnd_);
+    // check: tainted string must not be at the beginning of the script
+    if (mres[i].matchBeg_ == 0) {
+      LOG_XSS("Match is at the beginning of script!");
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
+nsXSSUtils::FindExternalXSS(const nsAString& aParam, nsIURI *aURI)
+{
+  MatchRes mres;
+  nsAutoString url;
+  // the string has already been converted up in the call stack. if
+  // this is an expensive operation, i guess it could be passed
+  // directly.
+  nsCAutoString url8;
+  aURI->GetSpec(url8);
+  CopyUTF8toUTF16(url8, url);
+  // same logic wrt to length as findinlinexss
+  if (aParam.Length() >= url.Length()) {
+    nsXSSUtils::P1FastMatchReverse(aParam, url, THRESHOLD, mres);
+  } else if (aParam.Length() >= url.Length() * (1-THRESHOLD)) {
+    nsXSSUtils::P1FastMatch(aParam, url, THRESHOLD, mres);
+  }
+  mres.ClearInvalidMatches(THRESHOLD);
+  PRUint32 safeIndex = GetHostLimit(aURI);
+  for (PRUint32 i = 0; i < mres.Length(); i++) {
+    LOG_XSS_2("Match in FindExternalXSS: %d %d\n", mres[i].matchBeg_,
+              mres[i].matchEnd_);
+    if (mres[i].matchBeg_  < safeIndex) {
+      LOG_XSS("Match controls host portion");
+      return true;
+    }
+  }
+  return false;
+}
+
+nsresult
+nsXSSUtils::CheckInline(const nsAString& aScript, ParameterArray& aParams)
+{
+  if (aParams.IsEmpty()) {
+    return NS_OK;
+  }
+  for (PRUint32 i = 0; i < aParams.Length(); i++) {
+    if (!aParams[i].dangerous) {
+      continue;
+    }
+    if (aParams[i].value.Length() < MIN_INL_LEN) {
+      continue;
+    }
+    aParams[i].attack = nsXSSUtils::FindInlineXSS(aParams[i].value, aScript);
+  }
+  return NS_OK;
+}
+
+nsresult
+nsXSSUtils::CheckExternal(nsIURI *aURI, nsIURI *aPageURI,
+                          ParameterArray& aParams)
+{
+  if (aParams.IsEmpty()) {
+    return NS_OK;
+  }
+  nsAutoString domain, pageDomain;
+  nsCAutoString spec;
+  nsXSSUtils::GetDomain(aURI, domain);
+  nsXSSUtils::GetDomain(aPageURI, pageDomain);
+  if (domain.Equals(pageDomain)) {
+    return NS_OK;
+  }
+  for (PRUint32 i = 0; i < aParams.Length(); i++) {
+    if (!aParams[i].dangerous) {
+      continue;
+    }
+    if (aParams[i].value.Length() < MIN_EXT_LEN) {
+      continue;
+    }
+    // TODO: utf8.Lenth() returns bytes, not code points. the length
+    // might be greater than the same str in utf16
+    aURI->GetSpec(spec);
+    aParams[i].attack = nsXSSUtils::FindExternalXSS(aParams[i].value, aURI);
+  }
+  return NS_OK;
+}
+
+bool
+nsXSSUtils::HasAttack(ParameterArray& aParams)
+{
+  for (PRUint32 i = 0; i < aParams.Length(); i++)
+    if (aParams[i].attack) {
+      return true;
+    }
+  return false;
+}
+
+/*****************
+ * p1Match stuff
+ ****************/
+
+void
+MatchRes::ClearInvalidMatches(double threshold)
+{
+  for (PRUint32 i = 0; i < elem_.Length(); i++) {
+    if (elem_[i].dist_ > costThresh_ ||
+        (elem_[i].matchEnd_ - elem_[i].matchBeg_ < MIN_MATCH_LEN)) {
+      elem_.RemoveElementAt(i);
+      i--;
+    }
+  }
+}
+
+
+#define DEFAULT_COST 20
+
+#define IS_ASCII(u)       ((u) < 0x80)
+
+
+// We map x -> x, except for upper-case letters,
+ // which we map to their lower-case equivalents.
+const PRUint8 gASCIIToLower [128] = {
+  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+  0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+  0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+  0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+  0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+};
+
+// this version does not put non-ascii chars to lowercase
+NS_ALWAYS_INLINE PRUnichar
+ASCIIToLowerCase(PRUnichar aChar)
+{
+  if (IS_ASCII(aChar)) {
+    return gASCIIToLower[aChar];
+  }
+  return aChar;
+}
+
+PRUint8
+DistMetric::GetICost(PRUnichar sc)
+{
+  return DEFAULT_COST;
+}
+
+PRUint8
+DistMetric::GetDCost(PRUnichar pc)
+{
+  return DEFAULT_COST;
+}
+
+PRUint8
+DistMetric::GetSCost(PRUnichar pc, PRUnichar sc)
+{
+  // in this case, normalization might not be very useful...
+  if (ASCIIToLowerCase(pc) == ASCIIToLowerCase(sc)) {
+    return 0;
+  }
+  return ( (GetICost(sc) + GetDCost(pc)) * 3) / 4;
+}
+
+PRUnichar
+DistMetric::GetNorm(PRUnichar c)
+{
+  return ASCIIToLowerCase(c);
+}
+
+const PRUnichar DistVector::NOT_ASCII = PRUnichar(~0x007F);
+
+bool
+DistVector::IsASCII(PRUnichar c)
+{
+  return !(c & DistVector::NOT_ASCII);
+}
+
+PRUint8
+DistVector::GetASCII(PRUnichar c)
+{
+  return c;
+}
+
+DistVector::DistVector()
+{
+  mMap.Init();
+  memset(mArray, 0, sizeof(PRInt32)*128);
+}
+
+PRInt32
+DistVector::GetDiff(PRUnichar c)
+{
+  if (IsASCII(c)) {
+    return mArray[GetASCII(c)];
+  }
+  return mMap.Get(c);
+}
+
+void
+DistVector::SetDiff(PRUnichar c, PRInt32 val)
+{
+  if (IsASCII(c)) {
+    mArray[GetASCII(c)] = val;
+  }
+  mMap.Put(c, val);
+}
+
+static PLDHashOperator
+PrintVector(const PRUnichar& aKey, PRInt32 aData, void *aUserArg)
+{
+  printf("%lc: %d\n", aKey, aData);
+  return PL_DHASH_NEXT;
+}
+
+
+void
+DistVector::Print()
+{
+  printf("DIFFVEC\n");
+  for (int i = 0; i < 128; i++)
+    if (mArray[i] > 0) {
+      printf("arr -- %c: %d\n", i, mArray[i]);
+    }
+  mMap.EnumerateRead(PrintVector, this);
+}
+
+#define NS_MIN3(a, b, c) NS_MIN(NS_MIN((a), (b)), (c))
+#define MAX_DIST 100000000
+#define dist(a, b) dist[(a)*(slen+1)+(b)]
+#define NONE 4
+#define SUBST 1
+#define DELETE 2
+#define INSERT 3
+
+bool printMatch = false;
+bool printDistMatrix = false;
+bool printApproxMatches = false;
+bool printAllMatchSummary = false;
+bool printAllMatches = false;
+
+inline void
+printDist(PRInt32 *dist, PRInt32 slen, PRInt32 plen)
+{
+  if (printDistMatrix) {
+    PRInt32 i, j;
+    printf("  *");
+    for (j = 0; j <= slen; j++)
+      printf("%5d", j);
+    printf("\n");
+    for (i = 0; i <= plen; i++) {
+      printf("%3d", i);
+      for (j = 0; j <= slen; j++) {
+        if (dist(i, j) > 300 || dist(i, j) < -300) {
+          printf("%5d", -1);
+        } else {
+          printf("%5d", dist(i, j));
+        }
+      }
+      printf("\n");
+    }
+  }
+}
+
+inline int
+getMatch(PRInt32 *dist, const nsAString& p, const nsAString& s,
+         int sEnd, double thresh, PRUint8*& opmap)
+{
+  // If we need to print the match, then we need to create the
+  // string s1 and p1 that are obtained by aligning the matching
+  // portion of s with p, and inserting '-' characters where
+  // insertions/deletions take place.
+  //
+  // If we dont need to print, then we need only compute the
+  // beginning of the match, which is in "j" at the end of the loop below
+
+  PRInt32 plen = p.Length();
+  PRInt32 slen = s.Length();
+
+  int i = plen, j = sEnd;
+  int k = i+sEnd+1;
+  int rv;
+
+  PRUint8 *op = (PRUint8 *) nsMemory::Alloc(k+1);
+  op[k--] = 0;
+
+  while ((i > 0) && (j > 0)) {
+    PRUnichar sc = s[j-1], pc = p[i-1];
+    PRInt32 curDist = dist(i, j);
+    if (curDist == dist(i-1, j) + DistMetric::GetDCost(pc)) {
+      op[k] = DELETE;
+      i--;
+    } else if (curDist == dist(i, j-1) + DistMetric::GetICost(sc)) {
+      op[k] = INSERT;
+      j--;
+    } else {
+        if (DistMetric::GetSCost(pc, sc) == 0) {
+              op[k] = NONE;
+        } else {
+          op[k] = SUBST;
+        }
+        i--; j--;
+    }
+    k--;
+  }
+
+  k++;
+  rv = j;
+  opmap = (PRUint8 *) strdup((const char *) &op[k]);
+  nsMemory::Free(op);
+  return rv;
+}
+
+inline void
+prtMatch(PRInt32 b, PRInt32 e, double d, const nsAString& p, const nsAString& s,
+         const PRUint8* op, bool printMatch)
+{
+  int i, j; bool done;
+  if (printMatch) {
+	printf("distance(s[%d-%d]): %g\n\tp: ", b, e, d);
+	for (i=0, j=0, done=false; (!done); j++) {
+	  switch (op[j]) {
+	  case NONE:
+	  case SUBST:
+		putwchar(p[i]); i++; continue;
+	  case DELETE:
+		putwchar(p[i]); i++; continue;
+	  case INSERT:
+		putchar('-'); continue;
+	  default:
+		done=true; break;
+	  }
+	}
+	printf("\n\ts: ");
+	for (i=b, j=0, done=false; (!done); j++) {
+	  switch (op[j]) {
+	  case NONE:
+	  case SUBST:
+		putwchar(s[i]); i++; continue;
+	  case INSERT:
+		putwchar(s[i]); i++; continue;
+	  case DELETE:
+		putchar('-'); continue;
+	  default:
+		done=true; break;
+	  }
+	}
+	printf("\n\to: ");
+	for (i=b, j=0, done=false; (!done); j++) {
+	  switch (op[j]) {
+	  case NONE:
+		putchar('N'); continue;
+	  case SUBST:
+		putchar('S'); continue;
+	  case INSERT:
+		putchar('I'); continue;
+	  case DELETE:
+		putchar('D'); continue;
+	  default:
+		done=true; break;
+	  }
+	}
+	printf("\n");
+  }
+}
+
+//#define FIND_OVERLAPS
+
+nsresult
+P1Match(const nsAString& p, const nsAString& s, PRInt32 sOffset,
+        double distThreshold, MatchRes& mres)
+{
+  PRInt32 i, j;
+  PRInt32 sBeg, sEnd;
+  PRInt32 bestDist=MAX_DIST;
+  PRInt32 prevBest;
+  PRInt32 distDn=0, distRt=0, distDiag=0;
+  PRInt32* dist;
+  PRUint8 *opmap;
+
+  PRInt32 plen = p.Length();
+  PRInt32 slen = s.Length();
+
+  dist = (PRInt32 *) nsMemory::Alloc((plen+1)*(slen+1)*sizeof(PRInt32 *));
+  if (dist == NULL) {
+    NS_WARNING("dist is NULL!\n");
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  PRInt32 costThresh;
+
+#ifdef DEBUG
+  if (printAllMatchSummary) {
+    printf("p1Match(p, s[%d-%d])\n", sOffset, sOffset+slen);
+  }
+#endif
+  // Lazy Initialization of costThresh_
+  if (mres.costThresh_ < 0) {
+    PRInt32 maxCost = 0;
+    prevBest = MAX_DIST;
+    for (i = 0; i < plen; i++)
+      maxCost += DistMetric::GetDCost(p[i]);
+    costThresh = (PRInt32)floorf((float) distThreshold * maxCost);
+    mres.costThresh_ = costThresh;
+  } else {
+    prevBest = mres.bestDist_;
+    costThresh = mres.costThresh_;
+  }
+
+  // Initialize distance matrix
+
+  dist(0, 0) = 0;
+  for (j=1; j <= slen; j++)
+    dist(0, j) = 0; // this is in the for loop...
+  for (i=1; i <= plen; i++)
+    dist(i, 0) = dist(i-1, 0) + DistMetric::GetDCost(p[i-1]);
+
+#ifdef DEBUG
+  printDist(dist, slen, plen);
+#endif
+
+  // Main loop: compute the minimum distance matrix
+
+  for (i = 1; i <= plen; i++) {
+    for (j = 1; j <= slen; j++) {
+      PRUnichar sc = s[j-1], pc = p[i-1];
+      distDn = dist(i-1, j) + DistMetric::GetDCost(pc);
+      distRt = dist(i, j-1) + DistMetric::GetICost(sc);
+      distDiag = dist(i-1, j-1) + DistMetric::GetSCost(pc, sc);
+      dist(i, j) = NS_MIN3(distDn, distRt, distDiag);
+    }
+  }
+
+#ifdef DEBUG
+  printDist(dist, slen, plen);
+#endif
+  /* Now, look for j such that dist(plen, j) is minimum. This gives
+     the lowest cost substring match between p and s */
+
+  sEnd = 1;
+  for (j=1; j <= slen; j++) {
+    if (dist(plen, j) < bestDist) {
+      bestDist = dist(plen, j);
+      sEnd = j;
+    }
+  }
+
+  // Compare the best with previous best matches to see if there is any
+  // sense in continuing further. We retain results that are below costThresh
+  // that are within 50% of costThresh from the best result so far.
+
+  // TODO: what do we need "bad" matches for in the first place?
+  // we already know they won't be used by the caller, do we need them
+  // for overlaps? Perhaps we can simply keep valid matches only, and
+  // eventually merge them at the end if necessary.
+
+  if (bestDist <= prevBest + (costThresh/2)) {
+    PRInt32 bestSoFar = NS_MIN(bestDist, prevBest);
+    mres.bestDist_ = bestSoFar;
+
+    if (bestDist < prevBest) {
+      for (PRUint32 i = 0; i < mres.elem_.Length(); i++) {
+        if ((mres[i].dist_ > costThresh) &&
+            (mres[i].dist_ > bestDist+(costThresh/2))) {
+#ifdef DEBUG
+          printf("Removing worse match: [%d, %d]\n", mres[i].matchBeg_,
+                 mres[i].matchEnd_);
+#endif
+          mres.elem_.RemoveElementAt(i);
+          i--; // next loop goes back to i;
+        }
+      }
+    }
+
+    sBeg = getMatch(dist, p, s, sEnd, distThreshold, opmap);
+#ifdef DEBUG
+    prtMatch(sBeg, sEnd, bestDist, p, s, opmap, printMatch);
+#endif
+    // previously sEnd was decreased by 1. why?
+    MatchResElem mre(bestDist, sBeg + sOffset, sEnd + sOffset, opmap);
+    mres.elem_.AppendElement(mre);
+
+    // Now, compare the best match with other possible matches
+    // identified in this invocation of this function
+
+#ifdef FIND_OVERLAPS
+    PRInt32 l;
+    for (l=1; l <= slen; l++) {
+      PRInt32 currDist  = dist(plen, l);
+      if (((currDist <= costThresh) ||
+           (currDist <= bestSoFar + (costThresh/2))) &&
+          // The first two tests below eliminate consideration
+          // of distances that do not correspond to local minima
+          (currDist < dist(plen, l-1)) &&
+          ((l == slen) || currDist < dist(plen, l+1)) &&
+          (l != sEnd)) /* Dont consider the global minima, either */ {
+
+        j = getMatch(dist, p, s, l, distThreshold, opmap);
+
+        /* s[j]...s[l-1] are included in the match with p */
+
+        // Eliminate matches that have large overlap with the
+        // main match.  This is necessary since the "non-local
+        // minima" test earlier misses situations where the
+        // distance increases briefly but then decreases after
+        // a few more characters. In that case, you seem to
+        // have a local minima, but the corresponding match is
+        // subsumed in the ultimate match that is discovered.
+
+        PRInt32 uniqLen=0;
+        if (j < sBeg) {
+          uniqLen += sBeg-j;
+        }
+        if (l > sEnd) {
+          uniqLen += l-sEnd;
+        }
+        if (uniqLen > (plen*distThreshold)) {
+          // TODO: why are we only adding a match and not deleting the
+          // previous matches?
+          MatchResElem mre(bestDist, j + sOffset, l-1 + sOffset, opmap);
+          mres.elem_.InsertElementAt(0, mre);
+          printf("prtMatch, find overlap\n");
+          prtMatch(j, l, dist(plen, l), p, s, opmap, printAllMatches);
+        }
+      } // if ((currDist <= costThresh) ...
+    } // for (l=1; ...
+#endif
+  } // if (bestDist <= ...
+  nsMemory::Free(dist);
+  return NS_OK;
+
+}
+
+inline void
+adjustDiffIns(PRUnichar c, DistVector& vec, int& idiff, int& ddiff)
+{
+  if (vec.GetDiff(c) > 0) {
+    // an excess of c in p, now reduced when c is added to s
+    ddiff -= DistMetric::GetDCost(c);
+  } else {
+    // an excess of c in s, now excess further increased
+    idiff += DistMetric::GetICost(c);
+  }
+  vec.SetDiff(c, vec.GetDiff(c) - 1);
+}
+
+inline void
+adjustDiffDel(PRUnichar c, DistVector& vec, int& idiff, int& ddiff)
+{
+  if (vec.GetDiff(c) >= 0) {
+    // an excess of c in p, further increased now
+    ddiff += DistMetric::GetDCost(c);
+  } else {
+    // an excess of c in s, excess being reduced now, so decrease
+    idiff -= DistMetric::GetICost(c); // insertion cost correspondingly.
+  }
+  vec.SetDiff(c, vec.GetDiff(c) + 1);
+}
+
+nsresult
+nsXSSUtils::P1FastMatch(const nsAString& p, const nsAString& s,
+                        double distThreshold, MatchRes& mres)
+{
+  PRInt32 plen = p.Length();
+  PRInt32 slen = s.Length();
+
+  if (plen > slen) {
+    NS_ERROR("p should be smaller or as long as s");
+    printf("p (%d): %s\n", plen, NS_LossyConvertUTF16toASCII(p).get());
+    printf("s (%d): %s\n", slen, NS_LossyConvertUTF16toASCII(s).get());
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  if (p.IsEmpty()) {
+    return NS_OK;
+  }
+
+
+  PRInt32 idiff=0, ddiff=0, diff;
+  PRInt32 diffThresh;
+  PRInt32 start=-1, end=-1;
+  DistVector diffVec;
+  const PRUnichar* cur = p.BeginReading();
+  const PRUnichar* finish = p.EndReading();
+
+  mres.costThresh_ = -1;
+  mres.bestDist_ = MAX_DIST;
+
+  for (; cur < finish; ++cur) {
+    PRUnichar c = DistMetric::GetNorm(*cur);
+    adjustDiffDel(c, diffVec, idiff, ddiff);
+  }
+
+  diffThresh = (PRInt32)floor(ddiff*distThreshold);
+
+
+  for (PRInt32 i = 0; i < plen; ++i) {
+    PRUnichar d = DistMetric::GetNorm(s[i]);
+    adjustDiffIns(d, diffVec, idiff, ddiff);
+  }
+
+  //diffVec.print();
+
+  diff = NS_MIN(idiff, ddiff);
+
+  if (diff <= diffThresh) {
+    start = 0;
+  }
+
+  // move the sliding window, and when the difference is low enough,
+  // call the actual P1Match on the candidate.
+  for (PRInt32 j=plen; j < slen; j++) {
+    PRUnichar c = DistMetric::GetNorm(s[j-plen]);
+    PRUnichar d = DistMetric::GetNorm(s[j]);
+
+    adjustDiffDel(c, diffVec, idiff, ddiff);
+    adjustDiffIns(d, diffVec, idiff, ddiff);
+    diff = NS_MIN(idiff, ddiff);
+
+    if (start == -1) {
+      if (diff <= diffThresh) {
+        start = j-plen+1;
+      }
+    } else if (diff > diffThresh) {
+      end = j+1;//-1+diffThresh;
+      if (end >= slen) {
+        end = slen;
+      }
+      start -= 1;//diffThresh;
+      if (start < 0) {
+        start = 0;
+      }
+
+      const nsAString& newS = Substring(s, start, end-start+1);
+      nsresult rv = P1Match(p, newS, start, distThreshold, mres);
+      NS_ENSURE_SUCCESS(rv, rv);
+      start = -1;
+      //j = end-1;
+    }
+  }
+
+  // this is for the remaining of the string if you started seeing a
+  // match and you get to the end of the string before the end of
+  // the match.
+  if (start != -1) {
+    end = slen-1;
+
+    // increasing 1 seems to be ok here... it is a bit confusing
+    // because we print the values and they don't always have the
+    // same semantic meaning. sometimes they are "last character
+    // in the match" and sometimes "first char out of the match"
+    const nsAString &newS = Substring(s, start, end-start+1);
+    nsresult rv = P1Match(p, newS, start, distThreshold, mres);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+#ifdef DEBUG
+  if (printApproxMatches || printAllMatchSummary) {
+    if (!mres.elem_.IsEmpty() && (mres.bestDist_ <= mres.costThresh_) &&
+        (printAllMatchSummary
+         || ((mres.bestDist_ > 0) || (mres.elem_.Length() != 1)))) {
+      for (PRUint32 i = 0; i < mres.elem_.Length(); i++) {
+        printf("prtMatch, printMatchsummary\n");
+        prtMatch(mres[i].matchBeg_, mres[i].matchEnd_,
+                 mres[i].dist_, p, s, mres[i].opmap_, true);
+      }
+
+      for (PRUint32 i = 0; i < mres.elem_.Length(); i++) {
+        PRInt32 smin = mres[i].matchBeg_ - 6;
+        PRInt32 smax = mres[i].matchEnd_ + 6;
+        printf("MATCH:\n");
+        if (smin > 0) {
+          printf("...");
+        } else {
+          printf("   ");
+        }
+
+        for (PRInt32 k=smin; k <= NS_MIN(smax, slen-1); k++) {
+          if (k >= 0) {
+            if ((s[k] == PRUnichar('\n')) ||
+                (s[k] == PRUnichar('\t'))) {
+              printf(" ");
+            } else {
+              putwchar(s[k]);
+            }
+          } else {
+            printf(" ");
+          }
+        }
+        if (smax < slen-1) {
+          printf("...");
+        }
+        printf("[%d,%d]", mres[i].matchBeg_, mres[i].matchEnd_);
+        if (mres[i].dist_ != 0) {
+          printf(": %f", (mres[i].dist_*distThreshold)/mres.costThresh_);
+        }
+        printf("\n");
+        if (mres.costThresh_ != 0) {
+          printf("Relative Distance: %f%%\n",
+                 (mres[i].dist_*distThreshold*100)/mres.costThresh_);
+          printf("dist: %d\n", mres[i].dist_);
+          printf("distThreshold: %f\n", distThreshold);
+          printf("costThresh: %d\n", mres.costThresh_);
+        }
+      }
+    }
+    if (mres.elem_.IsEmpty() || (mres.bestDist_ > mres.costThresh_)) {
+      printf("p1FastMatch found no good matches.\n");
+    }
+  }
+#endif // DEBUG
+
+  return NS_OK;
+
+}
+
+nsresult
+nsXSSUtils::P1FastMatchReverse(const nsAString& s, const nsAString& p,
+                               double distThreshold, MatchRes& mres)
+{
+  nsresult rv;
+  rv = nsXSSUtils::P1FastMatch(p, s, distThreshold, mres);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // we need to take care of both deletion and substitution (though
+  // the current costs always make deletion cheaper)
+  for (PRUint32 i = 0; i < mres.elem_.Length(); i++) {
+    PRUint32 len = strlen((char*) mres[i].opmap_);
+    PRUint32 beg;
+    for (beg = 0; beg < len; beg++)
+      if (mres[i].opmap_[beg] != SUBST &&
+          mres[i].opmap_[beg] != DELETE) {
+        break;
+      }
+    PRUint32 end;
+    for (end = len; end > 0; end--) {
+      if (mres[i].opmap_[end-1] != SUBST &&
+          mres[i].opmap_[end-1] != DELETE) {
+        break;
+      }
+    }
+    mres[i].matchBeg_ = beg;
+    mres[i].matchEnd_ = end;
+  }
+  return NS_OK;
+
+}
+
+
+/************************
+ * XPCOM Module stuff
+ ************************/
+
+nsXSSUtilsWrapper* nsXSSUtilsWrapper::gXSSUtilsWrapper = nsnull;
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsXSSUtilsWrapper, nsIXSSUtils)
+
+nsXSSUtilsWrapper*
+nsXSSUtilsWrapper::GetSingleton()
+{
+  if (!gXSSUtilsWrapper) {
+    gXSSUtilsWrapper = new nsXSSUtilsWrapper();
+  }
+
+  NS_ADDREF(gXSSUtilsWrapper);
+  return gXSSUtilsWrapper;
+}
+
+/********************
+ * TESTS
+ ********************/
+
+
+/*** Macros and utils ***/
+
+static PRUint32 gFailCount = 0;
+
+#define PASS(_msg) printf("TEST-PASS | %s\n", _msg);
+#define FAIL(_msg) printf("TEST-UNEXPECTED-FAIL | %s\n", _msg); ++gFailCount;
+
+#define TEST_ENSURE_COND(_test, _msg)			\
+  if (_test) {                            \
+    PASS(_msg);                           \
+  } else {                                \
+    FAIL(_msg);                           \
+  }
+
+/*** Testing structures ***/
+
+struct stringBool
+{
+  const char* val;
+  bool rv;
+};
+
+struct twoStrings
+{
+  const char *str1;
+  const char *str2;
+};
+
+struct twoStringsBool
+{
+  const char *p;
+  const char *s;
+  bool rv;
+};
+
+struct twoChars
+{
+  const PRUnichar a,b;
+};
+
+struct stringInt
+{
+  const char *str;
+  const PRInt32 i;
+};
+
+struct staticParam
+{
+  const char *name, *value;
+  bool dangerous, special;
+  Parameter ToParam() const
+  {
+    return Parameter(name, value, dangerous, special);
+  };
+};
+/**** Testing data ****/
+
+const twoStrings suspiciousPathData[] = {
+  { "hello", ""},
+  { "watch/video", ""},
+  { "subdir/index_en.php", ""},
+  { "search/<script>/puppa.html", "<script>"},
+  { "page/'\"><script>alert(1)</script>/44/", "'\"><script>alert(1)</script>"},
+  {"/pages/static/<script>alert(2)/fake/</script>/index.html",
+   "<script>alert(2)/fake/</script>"},
+  {"a=b", "="},
+  {"/src/file_depot/mozilla-central/b.php", ""}
+};
+
+const twoStrings unescapeData[] = {
+  { "hello", "hello" },
+  { "hello%20world", "hello world" },
+  { "hello%2520world", "hello world" },
+  { "a=3&b=a%26b", "a=3&b=a&b" },
+  { "", "" }
+};
+
+const twoStrings domainData[] = {
+  { "http://www.google.com", "google.com" },
+  { "https://google.co.uk/index.html?a=5", "google.co.uk" },
+  { "http://lab.cs.cam.ac.uk/dir/a.php#aaa", "cam.ac.uk" },
+  { "http://a:b@nothing.asfd-fdfd.co.jp", "asfd-fdfd.co.jp" },
+  { "https://seclab.cs.sunysb.edu:8080/faculty/index.html", "sunysb.edu" },
+};
+
+const char *uri1 = "http://www.a.com";
+const staticParam* uriData1 = nsnull;
+const char *uri2 = "http://www.a.com/index.php?a=3";
+const staticParam* uriData2 = nsnull;
+const char *uri3 =
+  "http://www.a.com/index.php?a=3&b=aaaaa&df=<script>xss()</script>";
+const staticParam uriData3[] = {
+  {"URL", "index.php?a=3&b=aaaaa&df=<script>xss()</script>", true, true},
+};
+const char *uri4 =
+  "http://www.a.com/index.php?a=3&b=<script>alert(1)</script>#hello";
+const staticParam uriData4[] = {
+  {"URL", "index.php?a=3&b=<script>alert(1)</script>#hello", true, true}
+};
+const char *uri5 = "http://www.a.com/dir/adf_en.html?a=4#hi";
+const staticParam* uriData5 = nsnull;
+const char *uri6 =
+  "http://www.a.com/dir/<script>/index.php?param_1=<script>#<hello>aa";
+const staticParam uriData6[] = {
+  {"URL", "dir/<script>/index.php?param_1=<script>#<hello>aa",
+   true, true}
+};
+const char *uri7 =
+  "http://www.a.com/index.php;param<script>param";
+const staticParam uriData7[] = {
+  {"URL", "index.php;param<script>param", true, true}
+};
+
+
+
+const char *p1pStr1 = "hello";
+const char *p1sStr1 = "hello";
+const MatchResElem p1Data1[] = {
+  MatchResElem(0, 0, 5, nsnull),
+};
+const char *p1pStr2 = "hello";
+const char *p1sStr2 = "helloworldhello";
+const MatchResElem p1Data2[] = {
+  MatchResElem(0, 0, 5, nsnull),
+  MatchResElem(0, 10, 15, nsnull),
+};
+const char *p1pStr3 = "hello";
+const char *p1sStr3 = "aaaaahellobbbbbbbbbbbbbb";
+const MatchResElem p1Data3[] = {
+  MatchResElem(0, 5, 10, nsnull),
+};
+const char *p1pStr4 = "ahello";
+const char *p1sStr4 = "zzzhallozzz";
+const MatchResElem* p1Data4 = nsnull;
+
+const char *p1pStr5 = "lowerCaseStuff";
+const char *p1sStr5 = "this string has LOWERCASESTUFF";
+const MatchResElem p1Data5[] = {
+  MatchResElem(0, 16, 30, nsnull),
+};
+const char *p1pStr6 = "abc'; alert('xss'); //";
+const char *p1sStr6 = "var q = 'abc'; alert('xss'); //';";
+const MatchResElem p1Data6[] = {
+  MatchResElem(0, 9, 31, nsnull),
+};
+
+// reverse stuff
+const char *p1pStr7 = "<script>alert('xss')</script>";
+const char *p1sStr7 = "alert('xss');";
+const MatchResElem p1Data7[] = {
+  MatchResElem(20, 0, 12, nsnull),
+};
+const char *p1pStr8 = "<script>alert('xss')</script>";
+const char *p1sStr8 = "bbalert('xss')a";
+const MatchResElem p1Data8[] = {
+  MatchResElem(60, 2, 14, nsnull),
+};
+const char *p1pStr9 = "<script>alert('xss')</script>";
+const char *p1sStr9 = "blert('xss']";
+const MatchResElem p1Data9[] = {
+  MatchResElem(40, 1, 11, nsnull),
+};
+const char *p1pStr10 = "<script>alert(\"xss attack\");</script>";
+const char *p1sStr10 = "alert('xss attack');";
+const MatchResElem p1Data10[] = {
+  MatchResElem(60, 0, 20, nsnull),
+};
+
+const twoStringsBool findInlineData[] = {
+  { "<script>alert('xss')</script>", "alert('xss')", true },
+  { "index<dff>.php?<script>alert(2);</script>", "alert(2);", true },
+  { "index<dff>.php?<script>alert(2);</script>#<script>pippo()</script>",
+    "xss()", false },
+  { "abav' onmouseover='alert(\"xss\")'", "alert(\"xss\")", true}
+};
+
+const twoStringsBool findExternalData[] = {
+  { "<script src='http://evil.com/helloworld.js'></script>",
+    "http://evil.com/helloworld.js", true },
+  { "<script src='http://othersite.co.uk/helloworld.js'></script>",
+    "http://evil.com/helloworld.js", false }
+};
+
+//TODO: create a test that works with findoverlaps
+
+/*** Tests ***/
+nsresult
+TestLowerCaseASCII()
+{
+  TEST_ENSURE_COND( ASCIIToLowerCase(PRUnichar(L'a')) == PRUnichar(L'a'),
+                    "ASCIIToLowerCase" );
+  TEST_ENSURE_COND( ASCIIToLowerCase(PRUnichar(L'A')) == PRUnichar(L'a'),
+                    "ASCIIToLowerCase");
+  TEST_ENSURE_COND( ASCIIToLowerCase(PRUnichar(L' ')) == PRUnichar(L' '),
+                    "ASCIIToLowerCase" );
+  TEST_ENSURE_COND( ASCIIToLowerCase(PRUnichar(L'Ł')) == PRUnichar(L'Ł'),
+                    "ASCIIToLowerCase");
+  return NS_OK;
+}
+
+
+// requires s, uri, ios and params to be defined
+#define TestOneURL(__str, __data)                                       \
+  s.Assign(__str);                                                      \
+  ios->NewURI(s, nsnull, nsnull, getter_AddRefs(uri));                  \
+  nsXSSUtils::ParseURI(uri, params, nsnull);                            \
+  if (__data) {                                                         \
+    for (unsigned int i = 0; i < sizeof(__data) / sizeof(staticParam); i++) { \
+      TEST_ENSURE_COND(__data[i].ToParam() == params[i],                \
+                       "ParseURI: " #__data);                           \
+    }                                                                   \
+    TEST_ENSURE_COND(params.Length() == sizeof(__data) / sizeof(staticParam), \
+                     "ParseURI Len: " #__data);                         \
+  } else {                                                              \
+    TEST_ENSURE_COND(params.IsEmpty(), "ParseURI: " #__data);           \
+  }                                                                     \
+  params.Clear();
+
+nsresult
+TestParseURI()
+{
+  ParameterArray params;
+  nsCOMPtr<nsIIOService> ios = do_GetIOService();
+  nsCOMPtr<nsIURI> uri;
+  nsCAutoString s;
+  TestOneURL(uri1, uriData1);
+  TestOneURL(uri2, uriData2);
+  TestOneURL(uri3, uriData3);
+  TestOneURL(uri4, uriData4);
+  TestOneURL(uri5, uriData5);
+  TestOneURL(uri6, uriData6);
+  TestOneURL(uri7, uriData7);
+  return NS_OK;
+}
+
+nsresult
+TestUnescapeLoop()
+{
+  for (unsigned int i = 0; i < sizeof(unescapeData) / sizeof(twoStrings); i++) {
+    nsAutoString s;
+    s = NS_ConvertASCIItoUTF16(unescapeData[i].str1);
+    nsXSSUtils::UnescapeLoop(s, s, nsnull);
+    TEST_ENSURE_COND(s.Equals(NS_ConvertASCIItoUTF16(unescapeData[i].str2)),
+                     "UnescapeLoop");
+  }
+  return NS_OK;
+}
+
+#define TestTrimMacro(word, chars)                                      \
+nsresult TestTrim##word() {                                             \
+  for (unsigned int i = 0;                                              \
+       i < sizeof(suspicious##word##Data) / sizeof(twoStrings); i++) {  \
+    nsAutoString s;                                                     \
+    s = NS_ConvertASCIItoUTF16(suspicious##word##Data[i].str1);         \
+    nsXSSUtils::TrimSafeChars(s, s, chars);                             \
+    TEST_ENSURE_COND(s.Equals(                                          \
+        NS_ConvertASCIItoUTF16(suspicious##word##Data[i].str2)),        \
+                     "Trim word");                                      \
+  }                                                                     \
+  return NS_OK;                                                         \
+}
+
+TestTrimMacro(Path, PATH_CHARS);
+//TestTrimMacro(Ref, REF_CHARS); similar enough, don't bother
+//TestTrimMacro(Query, QUERY_CHARS);
+
+
+
+nsresult
+TestGetDomain()
+{
+  nsCOMPtr<nsIIOService> ios = do_GetIOService();
+  nsCOMPtr<nsIURI> uri;
+  for (unsigned int i = 0; i < sizeof(domainData) / sizeof(twoStrings); i++) {
+    nsCAutoString s(domainData[i].str1);
+    ios->NewURI(s, nsnull, nsnull, getter_AddRefs(uri));
+    nsAutoString result;
+    nsXSSUtils::GetDomain(uri, result);
+    TEST_ENSURE_COND(result.Equals(NS_ConvertASCIItoUTF16(domainData[i].str2)), "GetDomain");
+  }
+  return NS_OK;
+}
+
+
+nsresult
+TestDistVector()
+{
+  DistVector v;
+  v.SetDiff(PRUnichar('a'), 2);
+  v.SetDiff(PRUnichar('a'), 0);
+  v.SetDiff(PRUnichar(L'Ł'), 3);
+  v.SetDiff(PRUnichar(L'Ɖ'), -1);
+  v.SetDiff(PRUnichar('f'), 8);
+  TEST_ENSURE_COND(v.GetDiff(PRUnichar('a')) == 0, "DistVector");
+  TEST_ENSURE_COND(v.GetDiff(PRUnichar(L'Ł')) == 3, "DistVector");
+  TEST_ENSURE_COND(v.GetDiff(PRUnichar(L'Ɖ')) == -1, "DistVector");
+  TEST_ENSURE_COND(v.GetDiff(PRUnichar('f')) == 8, "DistVector");
+  TEST_ENSURE_COND(DistVector::IsASCII(PRUnichar('a')) == true, "DistVector");
+  TEST_ENSURE_COND(DistVector::IsASCII(PRUnichar(L'Ɖ')) == false, "DistVector");
+  TEST_ENSURE_COND(DistVector::IsASCII(PRUnichar('F')) == true, "DistVector");
+  TEST_ENSURE_COND(DistVector::IsASCII(PRUnichar(L'Ł')) == false, "DistVector");
+  return NS_OK;
+}
+
+
+
+// requires p, s, mres to be defined
+#define TestOneP1(__p, __s, __data)                                     \
+  mres.costThresh_ = -1;                                                \
+  mres.bestDist_ = MAX_DIST;                                            \
+  mres.elem_.Clear();                                                   \
+  p.Assign(NS_ConvertASCIItoUTF16(__p));                                \
+  s.Assign(NS_ConvertASCIItoUTF16(__s));                                \
+  P1Match(p, s, 0, THRESHOLD, mres);                                    \
+  mres.ClearInvalidMatches(THRESHOLD);                                  \
+  if (__data) {                                                         \
+    for (unsigned int i = 0; i < sizeof(__data) / sizeof(MatchResElem); i++) \
+      TEST_ENSURE_COND(__data[i] == mres[i], "P1Match");                \
+    TEST_ENSURE_COND(mres.elem_.Length() == sizeof(__data) / sizeof(MatchResElem), \
+                     "P1Match");                                        \
+  } else {                                                              \
+    TEST_ENSURE_COND(mres.elem_.IsEmpty(), "P1Match");                  \
+  }
+
+nsresult
+TestP1Match()
+{
+  MatchRes mres;
+  nsAutoString p, s;
+  TestOneP1(p1pStr1, p1sStr1, p1Data1);
+  //TestOneP1(p1pStr2, p1sStr2, p1Data2);
+  TestOneP1(p1pStr3, p1sStr3, p1Data3);
+  TestOneP1(p1pStr4, p1sStr4, p1Data4);
+  TestOneP1(p1pStr5, p1sStr5, p1Data5);
+  TestOneP1(p1pStr6, p1sStr6, p1Data6);
+  return NS_OK;
+}
+
+// requires p, s, mres to be defined
+#define TestOneP1Fast(__p, __s, __data)                                 \
+  mres.elem_.Clear();                                                   \
+  p.Assign(NS_ConvertASCIItoUTF16(__p));                                \
+  s.Assign(NS_ConvertASCIItoUTF16(__s));                                \
+  nsXSSUtils::P1FastMatch(p, s, THRESHOLD, mres);                       \
+  mres.ClearInvalidMatches(THRESHOLD);                                  \
+  if (__data) {                                                         \
+    for (unsigned int i = 0; i < sizeof(__data) / sizeof(MatchResElem); i++) \
+      TEST_ENSURE_COND(__data[i] == mres[i], "P1FastMatch");            \
+    TEST_ENSURE_COND(mres.elem_.Length() == sizeof(__data) / sizeof(MatchResElem), \
+                     "P1FastMatch");                                    \
+  } else {                                                              \
+    TEST_ENSURE_COND(mres.elem_.IsEmpty(), "P1FastMatch");              \
+  }
+
+nsresult
+TestP1FastMatch()
+{
+  MatchRes mres;
+  nsAutoString p, s;
+  TestOneP1Fast(p1pStr1, p1sStr1, p1Data1);
+  TestOneP1Fast(p1pStr2, p1sStr2, p1Data2);
+  TestOneP1Fast(p1pStr3, p1sStr3, p1Data3);
+  TestOneP1Fast(p1pStr4, p1sStr4, p1Data4);
+  TestOneP1Fast(p1pStr5, p1sStr5, p1Data5);
+  TestOneP1Fast(p1pStr6, p1sStr6, p1Data6);
+  // a couple of tests using unicode chars
+  mres.elem_.Clear();
+  p.Assign(NS_LITERAL_STRING("您好世界您好世界您好世界"));
+  s.Assign(NS_LITERAL_STRING("您好世界您好世界您好世界"));
+  nsXSSUtils::P1FastMatch(p, s, THRESHOLD, mres);
+  mres.ClearInvalidMatches(THRESHOLD);
+  TEST_ENSURE_COND(mres[0].dist_ == 0, "p1FastMatch.Unicode1");
+  mres.elem_.Clear();
+  // it seems that msvc++ does not understand unicode characters
+  // directly in source files.
+#ifndef _WINDOWS
+  p.Assign(NS_LITERAL_STRING("您好世界您好世界您好世界"));
+  s.Assign(NS_LITERAL_STRING("您好a界您好世界您好世界"));
+  nsXSSUtils::P1FastMatch(p, s, THRESHOLD, mres);
+  mres.ClearInvalidMatches(THRESHOLD);
+  TEST_ENSURE_COND(mres[0].dist_ == 30, "p1FastMatch.Unicode2");
+  mres.elem_.Clear();
+  p.Assign(NS_LITERAL_STRING("您好世界您好世界世界"));
+  s.Assign(NS_LITERAL_STRING("您好世界您好世界您好世界"));
+  nsXSSUtils::P1FastMatch(p, s, THRESHOLD, mres);
+  mres.ClearInvalidMatches(THRESHOLD);
+  TEST_ENSURE_COND(mres[0].dist_ == 40, "p1FastMatch.Unicode3");
+#endif
+  return NS_OK;
+}
+
+// requires p, s, mres to be defined
+#define TestOneP1FastReverse(__p, __s, __data)                          \
+  mres.elem_.Clear();                                                   \
+  p.Assign(NS_ConvertASCIItoUTF16(__p));                                \
+  s.Assign(NS_ConvertASCIItoUTF16(__s));                                \
+  nsXSSUtils::P1FastMatchReverse(p, s, THRESHOLD, mres);                \
+  mres.ClearInvalidMatches(THRESHOLD);                                  \
+  if (__data) {                                                         \
+    for (unsigned int i = 0; i < sizeof(__data) / sizeof(MatchResElem); i++) \
+      TEST_ENSURE_COND(__data[i] == mres[i], "P1FastMatchReverse");      \
+    TEST_ENSURE_COND(mres.elem_.Length() == sizeof(__data) / sizeof(MatchResElem), \
+                     "P1FastMatchReverse");                             \
+  } else {                                                              \
+    TEST_ENSURE_COND(mres.elem_.IsEmpty(), "P1FastMatchReverse");       \
+  }
+
+nsresult
+TestP1FastMatchReverse()
+{
+  MatchRes mres;
+  nsAutoString p, s;
+  TestOneP1FastReverse(p1pStr7, p1sStr7, p1Data7);
+  TestOneP1FastReverse(p1pStr8, p1sStr8, p1Data8);
+  TestOneP1FastReverse(p1pStr9, p1sStr9, p1Data9);
+  TestOneP1FastReverse(p1pStr10, p1sStr10, p1Data10);
+  return NS_OK;
+}
+
+nsresult
+TestFindInlineXSS()
+{
+  for (unsigned int i = 0; i < sizeof(findInlineData) / sizeof(twoStringsBool); i++) {
+    nsAutoString p, s;
+    bool rv;
+    p = NS_ConvertASCIItoUTF16(findInlineData[i].p);
+    s = NS_ConvertASCIItoUTF16(findInlineData[i].s);
+    if (p.Length() > s.Length()) {
+      rv = nsXSSUtils::FindInlineXSS(p, s);
+    } else {
+      rv = nsXSSUtils::FindInlineXSS(p, s);
+    }
+    TEST_ENSURE_COND(rv == findInlineData[i].rv, "FindInlineXSS");
+  }
+  return NS_OK;
+}
+
+nsresult
+TestFindExternalXSS()
+{
+  nsCOMPtr<nsIURI> uri;
+  nsCOMPtr<nsIIOService> ios = do_GetIOService();
+  for (unsigned int i = 0;
+       i < sizeof(findExternalData) / sizeof(twoStringsBool); i++) {
+    nsAutoString p;
+    nsCAutoString u;
+    bool rv;
+    p.Assign(NS_ConvertASCIItoUTF16(findExternalData[i].p));
+    u.Assign(findExternalData[i].s);
+    ios->NewURI(u, nsnull, nsnull, getter_AddRefs(uri));
+    if (p.Length() > u.Length()) {
+      rv = nsXSSUtils::FindExternalXSS(p, uri);
+    } else {
+      rv = nsXSSUtils::FindExternalXSS(p, uri);
+    }
+    TEST_ENSURE_COND(rv == findExternalData[i].rv, "FindExternalXSS");
+  }
+  return NS_OK;
+}
+
+// these two don't need much testing, this path is taken enough by mochitests
+nsresult
+TestCheckInline()
+{
+  nsAutoString script;
+  ParameterArray params;
+
+  // reversed
+  script.Assign(NS_LITERAL_STRING("alert('xss attack')"));
+  params.AppendElement(Parameter("URL", "<script>hello world</script>",
+                                 true, true));
+  nsXSSUtils::CheckInline(script, params);
+  TEST_ENSURE_COND(params[0].attack == false, "CheckInline");
+  params.Clear();
+
+  script.Assign(NS_LITERAL_STRING("alert('xss attack')"));
+  params.AppendElement(Parameter("URL", "<script>alert(\"xss attack\")</script>",
+                                 true, true));
+  nsXSSUtils::CheckInline(script, params);
+  TEST_ENSURE_COND(params[0].attack == true, "CheckInline");
+  params.Clear();
+
+  return NS_OK;
+
+}
+
+nsresult
+TestCheckExternal()
+{
+  nsCAutoString u, pU;
+  ParameterArray params;
+  nsCOMPtr<nsIURI> uri, pageUri;
+  nsCOMPtr<nsIIOService> ios = do_GetIOService();
+
+  pU.Assign(NS_LITERAL_CSTRING("http://www.localhost.net/pages/stuff.index.html"));
+  ios->NewURI(pU, nsnull, nsnull, getter_AddRefs(pageUri));
+
+  u.Assign(NS_LITERAL_CSTRING("http://www.google.com/scripts/script.js"));
+  ios->NewURI(u, nsnull, nsnull, getter_AddRefs(uri));
+  params.AppendElement(
+    Parameter("URL", "<script src='http://www.google.com/helloworld/gino.js'></script>",
+              true, true));
+  nsXSSUtils::CheckExternal(uri, pageUri, params);
+  TEST_ENSURE_COND(params[0].attack == false, "CheckExternal");
+  params.Clear();
+
+  u.Assign(NS_LITERAL_CSTRING("http://hello.localhost.net/scripts/script.js"));
+  ios->NewURI(u, nsnull, nsnull, getter_AddRefs(uri));
+  params.AppendElement(Parameter("a", "<script src='hello.localhost.net/scripts/script.js'></script>",
+                                 true, false));
+  nsXSSUtils::CheckExternal(uri, pageUri, params);
+  TEST_ENSURE_COND(params[0].attack == false, "CheckExternal");
+  params.Clear();
+
+  u.Assign(NS_LITERAL_CSTRING("http://www.pippo.com/script.js"));
+  ios->NewURI(u, nsnull, nsnull, getter_AddRefs(uri));
+  params.AppendElement(Parameter("URL1", "<script src='http://www.pippo.com/script.js'></script>", true, true));
+  params.AppendElement(Parameter("URL2", "<script src='http://www.localhost.com/script.js'></script>", true, true));
+  nsXSSUtils::CheckExternal(uri, pageUri, params);
+  TEST_ENSURE_COND(params[0].attack == true, "CheckExternal");
+  TEST_ENSURE_COND(params[1].attack == false, "CheckExternal");
+
+  return NS_OK;
+}
+
+nsresult
+nsXSSUtilsWrapper::Test()
+{
+#ifdef PR_LOGGING
+  gXssPRLog = PR_NewLogModule("XSS");
+#endif
+  TestLowerCaseASCII();
+  TestTrimPath();
+  TestParseURI();
+  TestUnescapeLoop();
+  TestGetDomain();
+  TestDistVector();
+  TestP1Match();
+  TestP1FastMatch();
+  TestP1FastMatchReverse();
+  TestFindInlineXSS();
+  TestFindExternalXSS();
+  TestCheckInline();
+  TestCheckExternal();
+  if (gFailCount > 0) {
+    printf("TESTS FAILED\n");
+    printf("%d failures\n", gFailCount);
+    return NS_ERROR_BASE;
+  }
+  return NS_OK;
+}
diff --git a/content/base/src/nsXSSUtils.h b/content/base/src/nsXSSUtils.h
new file mode 100644
--- /dev/null
+++ b/content/base/src/nsXSSUtils.h
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsXSSUtils_h
+#define nsXSSUtils_h
+
+#include "prlog.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsIXSSUtils.h"
+#include "nsDataHashtable.h"
+
+#ifdef PR_LOGGING
+#define LOG_XSS_CALL(f) PR_LOG(gXssPRLog, PR_LOG_DEBUG, (">>>>> %s", f));
+#define LOG_XSS(msg) PR_LOG(gXssPRLog, PR_LOG_DEBUG, ("XSS: " msg));
+#define LOG_XSS_1(msg, arg) PR_LOG(gXssPRLog, PR_LOG_DEBUG, \
+                                   ("XSS: " msg, arg));
+#define LOG_XSS_2(msg, arg1, arg2) PR_LOG(gXssPRLog, PR_LOG_DEBUG, \
+                                          ("XSS: " msg, arg1, arg2));
+#else
+#define LOG_XSS_CALL(f) ;
+#define LOG_XSS(msg) ;
+#define LOG_XSS_1(msg, arg) ;
+#define LOG_XSS_2(msg, arg1, arg2) ;
+#endif
+
+class nsIDocument;
+
+/**
+ * This class is used to store the various sources of XSS attacks for
+ * each HTTP request and the result of the XSS checks again a particular
+ * script. Currently, at most two parameters are being inserted into
+ * the filter: the url of the webpage and the body of POST
+ * requests. Look at ParseURI and ParsePOST for the actual logic.  An
+ * earlier iteration of the filter would break down the URL into many
+ * different parameters, and this class still supports that if needed.
+ * I think the dangerous and special fields are not actively used anymore.
+ */
+struct Parameter {
+  nsString name, value;
+  bool attack, dangerous, special;
+  Parameter(const nsAString& aName, const nsAString& aValue,
+            bool aDangerous, bool aSpecial, bool aAttack = false);
+  Parameter(const char* aName, const char* aValue,
+            bool aDangerous, bool aSpecial, bool aAttack = false);
+  // for debugging
+  bool operator==(const Parameter& other) const;
+  void print() const;
+};
+
+class nsIURI;
+
+typedef nsTArray<Parameter> ParameterArray;
+
+/**
+ * MatchRes, DistMetric and DistVector are used by the approximate
+ * substring matching algorithm, which is implemented in
+ * p1FastMatch/p1Match.
+ */
+class MatchResElem {
+ public:
+  PRInt32 dist_;		// edit distance
+  PRUint32 matchBeg_;	// start and
+  PRUint32 matchEnd_;	//   end of match
+  PRUint8 *opmap_;		// table of edit operator costs
+
+ public:
+  MatchResElem(PRUint32 d, PRUint32 b, PRUint32 e, PRUint8 *opmap)
+	{dist_ = d; matchBeg_ = b; matchEnd_ = e; opmap_ = opmap; };
+  bool operator==(const MatchResElem& other) const
+  { return dist_ == other.dist_ && matchBeg_ == other.matchBeg_ &&
+      matchEnd_ == other.matchEnd_; }; // don't compare opmap for testing
+  // are we leaking mem with opmap?
+};
+
+
+typedef nsTArray<MatchResElem> Matches;
+/**
+ * Represents a Match Result as returned by p1FastMach.
+ */
+class MatchRes {
+ public:
+  PRInt32 costThresh_;	// minimum edit distance for matches
+  PRInt32 bestDist_;	// best obseved edit distance in the matches found
+  Matches elem_;	// list of matches found
+  bool HasMatches() { return !elem_.IsEmpty(); };
+  MatchResElem& operator[] (PRUint32 i) { return elem_.ElementAt(i); };
+  PRUint32 Length() const { return elem_.Length(); };
+  void ClearInvalidMatches(double threshold);
+};
+
+class DistMetric {
+ public:
+  static PRUint8 GetICost(PRUnichar sc);
+  static PRUint8 GetDCost(PRUnichar pc);
+  static PRUint8 GetSCost(PRUnichar pc, PRUnichar sc);
+  static PRUnichar GetNorm(PRUnichar c);
+};
+
+class nsUnicharHashKey : public PLDHashEntryHdr
+{
+public:
+  typedef const PRUnichar& KeyType;
+  typedef const PRUnichar* KeyTypePointer;
+
+  nsUnicharHashKey(KeyTypePointer aKey) : mValue(*aKey) { }
+  nsUnicharHashKey(const nsUnicharHashKey& toCopy) : mValue(toCopy.mValue) { }
+  ~nsUnicharHashKey() { }
+
+  KeyType GetKey() const { return mValue; }
+  bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
+
+  static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+  static PLDHashNumber HashKey(KeyTypePointer aKey) { return *aKey; }
+  enum { ALLOW_MEMMOVE = true };
+
+private:
+  const PRUnichar mValue;
+};
+// why can't we simply template the entryHeader as PLDHashEntryHdr<PRUnichar>?
+typedef nsDataHashtable<nsUnicharHashKey, PRInt32> UnicharMap;
+typedef nsDataHashtableMT<nsStringHashKey, bool> DomainMap;
+
+
+class DistVector {
+public:
+  DistVector();
+  PRInt32 GetDiff(PRUnichar c);
+  void SetDiff(PRUnichar c, PRInt32 val);
+  //PRInt32& operator[] (PRUnichar c);  can't get a reference to nsDataHashtable!
+  void Print();
+  static bool IsASCII(PRUnichar c);
+  static PRUint8 GetASCII(PRUnichar c);
+private:
+  UnicharMap mMap;
+  PRInt32 mArray[128]; // fast path for ASCII chars
+  static const PRUnichar NOT_ASCII;
+};
+
+
+/**
+ * A namespace-class which contains utilities that are used by
+ * nsXSSFilter. I guess this could be refactored into a namespace, but
+ * right now it needs to be a class because it is an
+ * implementation of an interface used for tests.
+ */
+class nsXSSUtils {
+
+public:
+  /**
+   * Find p in a substring of s (trimming s is free). Returns the
+   * matched interval on s.
+   */
+  static nsresult P1FastMatch(const nsAString& p, const nsAString& s,
+                              double distThreshold, MatchRes& mres);
+  /**
+   * Find p in a substring of s (trimming s is free). Returns the
+   * matched interval on p instead of s.
+   */
+  static nsresult P1FastMatchReverse(const nsAString& s, const nsAString& p,
+                                     double distThreshold, MatchRes &mres);
+  /**
+   * Returns whether the current parameter represents an XSS attack
+   * for the script
+   */
+  static bool FindInlineXSS(const nsAString& aParam,
+                              const nsAString& aScript);
+  /**
+   * Returns whether the current parameter represents an XSS attack
+   * for the url
+   */
+  static bool FindExternalXSS(const nsAString& aParam, nsIURI *aUri);
+  /**
+   * Returns the registered domain name. Handles 2nd-level tlds such
+   * as google.co.uk
+   */
+  static nsresult GetDomain(nsIURI* aUri, nsAString& result);
+  /**
+   *  Main interface for XSSFilter. These two function detect which
+   *  params contribute to an injected script or url.
+   */
+  static nsresult CheckInline(const nsAString& script, ParameterArray& aParams);
+  static nsresult CheckExternal(nsIURI *aUri, nsIURI *pageURI,
+                                ParameterArray& aParams);
+  /**
+   * Unescapes the string repeatedly until it does not change. Does
+   * URL and HTML entities escaping (the latter only if a doc is
+   * provided, because it needs access to DOM functionalities).
+   */
+  static nsresult UnescapeLoop(const nsAString& aString, nsAString& result,
+                               nsIDocument *doc);
+  /**
+   * Escapes the HTML entities in aString and write the output to result.
+   */
+  static nsresult DecodeHTMLEntities(const nsAString& aString,
+                                     nsAString& result,
+                                     nsIDocument* doc);
+  /**
+   * Returns the last position where the attacker can control the domain.
+   */
+  static PRUint32 GetHostLimit(nsIURI* aUri);
+  /**
+   * Trims the input string to the biggest substring that has
+   * suspicious chars. If no suspicious chars are found, it is empty.
+   * The flags LEAVE_BEG and LEAVE_END can be used to disable
+   * trimming.
+   */
+  static nsresult TrimSafeChars(const nsAString& path, nsAString& result,
+                                const char *chars, PRUint8 flags = 0);
+#define LEAVE_BEG 1
+#define LEAVE_END 2
+  /**
+   * Extracts suspicious content from the URL for XSS checks,
+   * appending it to aParams.
+   */
+  static nsresult ParseURI(nsIURI *aURI, ParameterArray& aParams,
+                           nsIDocument* doc);
+  /**
+   * Extracts suspicious content from the post body for XSS checks,
+   * appending it to aParams.
+   */
+  static nsresult ParsePOST(char *request, ParameterArray& aParams,
+                            nsIDocument* doc);
+  /**
+   * Returns true if the string contains anything other than
+   * [a-zA-Z0-9 _-=&]
+   */
+  static bool IsQueryDangerous(const nsAString& aString);
+  /**
+   * Checks whether at least one of the parameters in the array
+   * represents an XSS attack.
+   */
+  static bool HasAttack(ParameterArray& aParams);
+  /**
+   * Initialize the logger and other static variables.
+   */
+  static void InitializeStatics();
+};
+
+
+#define NS_XSSUTILS_CID \
+  { 0x1db76755, 0x1e01, 0x45fe, { 0x92, 0xb5, 0x25, 0x67, 0x9c, 0xbe, 0xb0, 0x85}}
+#define NS_XSSUTILS_CONTRACTID "@mozilla.com/xssutils;1"
+
+/**
+ * This wrapper is used only for testing.
+ */
+class nsXSSUtilsWrapper : public nsIXSSUtils {
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIXSSUTILS
+
+  static nsXSSUtilsWrapper* GetSingleton();
+  static nsXSSUtilsWrapper* gXSSUtilsWrapper;
+};
+
+#endif // nsXSSUtils_h
diff --git a/content/base/src/nsXSSUtilsModule.cpp b/content/base/src/nsXSSUtilsModule.cpp
new file mode 100644
--- /dev/null
+++ b/content/base/src/nsXSSUtilsModule.cpp
@@ -0,0 +1,48 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+
+#include "nscore.h"
+#include "pratom.h"
+#include "prmem.h"
+#include "prio.h"
+#include "plstr.h"
+#include "prlog.h"
+
+#include "nsID.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "nsIModule.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsXSSUtils.h"
+
+// XXX Need help with guard for ENABLE_TEST
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsXSSUtilsWrapper,
+                                         nsXSSUtilsWrapper::GetSingleton)
+NS_DEFINE_NAMED_CID(NS_XSSUTILS_CID);
+
+static const mozilla::Module::CIDEntry kXSSUtilsCIDs[] = {
+    { &kNS_XSSUTILS_CID, false, NULL, nsXSSUtilsWrapperConstructor },
+    { NULL }
+};
+
+static const mozilla::Module::ContractIDEntry kXSSUtilsContracts[] = {
+    { NS_XSSUTILS_CONTRACTID, &kNS_XSSUTILS_CID },
+    { NULL }
+};
+
+static const mozilla::Module kXSSUtilsModule = {
+    mozilla::Module::kVersion,
+    kXSSUtilsCIDs,
+    kXSSUtilsContracts,
+    NULL,
+    NULL,
+    NULL,
+    NULL
+};
+
+NSMODULE_DEFN(XSSUtilsModule) = &kXSSUtilsModule;
diff --git a/content/base/test/Makefile.in b/content/base/test/Makefile.in
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -17,19 +17,19 @@ DIRS += \
   $(NULL)
 
 MODULE = content
 
 CPP_UNIT_TESTS = \
                  TestNativeXMLHttpRequest.cpp \
                  TestGetURL.cpp \
                  TestPlainTextSerializer.cpp \
+		 TestXSSUtils.cpp \
                  $(NULL)
 
-
 XPCSHELL_TESTS = \
                unit \
                $(NULL)
 # FIXME/bug 575918: out-of-process xpcshell is broken on OS X
 ifneq ($(OS_ARCH),Darwin)
 XPCSHELL_TESTS += unit_ipc
 endif
 
@@ -548,16 +548,22 @@ MOCHITEST_FILES_B = \
 		test_bug753278.html \
 		test_bug761120.html \
 		test_XHR_onuploadprogress.html \
 		test_XHR_anon.html \
 		file_XHR_anon.sjs \
 		test_XHR_system.html \
 		test_XHR_parameters.html \
 		test_ipc_messagemanager_blob.html \
+		test_XSS.html \
+		file_XSS.js \
+		file_XSS.sjs \
+		file_XSS_title.js \
+		file_XSS_title.jpg \
+		file_XSS_binding.xml \
 		$(NULL)
 
 MOCHITEST_CHROME_FILES =	\
 		test_bug357450.js \
 		$(NULL)
 
 MOCHITEST_FILES_PARTS = $(foreach s,A B,MOCHITEST_FILES_$(s))
 
diff --git a/content/base/test/TestXSSUtils.cpp b/content/base/test/TestXSSUtils.cpp
new file mode 100644
--- /dev/null
+++ b/content/base/test/TestXSSUtils.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestHarness.h"
+#include "nsIXSSUtils.h"
+
+
+
+int main(int argc, char** argv)
+{
+
+  ScopedXPCOM xpcom("XSSUtils");
+  if (xpcom.failed()) {
+    return 1;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIXSSUtils> xss =
+    do_GetService("@mozilla.com/xssutils;1", &rv);
+  if (!xss) {
+    printf("XSS: null ptr\n");
+    return NS_OK;
+  }
+  return xss->Test();
+}
diff --git a/content/base/test/file_XSS.js b/content/base/test/file_XSS.js
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_XSS.js
@@ -0,0 +1,318 @@
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const env = Cc["@mozilla.org/process/environment;1"].
+  getService(Ci.nsIEnvironment);
+// we use this for clarity
+var GET = "GET", POST = "POST";
+var ATTACK = "ATTACK", ATTACK_BLOCK = "ATTACK_BLOCK", SAFE = "SAFE";
+var TODO = "TODO", DONE = "DONE";
+var NONE = "none";
+// format:
+// method, description, server context, content, header, encoding
+// (form only), attack, todo
+
+var TESTS = [
+  [GET, "partial injection inline", "inside",
+   'abc\'; parent.postMessage("xss", "*");//', NONE, NONE, ATTACK, TODO],
+
+  [GET, "whole script injection", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', NONE, NONE, ATTACK, DONE],
+  [GET, "whole script with HTML quirk", "outside",
+   '<IMG """><SCRIPT>parent.postMessage("xss", "*");</SCRIPT>">',
+   NONE, NONE, ATTACK, DONE],
+
+  [GET, "external script w/ quirk", "outside",
+   '<SCRIPT/XSS SRC="http://example.org/tests/content/base/test/file_XSS_title.js"></SCRIPT>', NONE, NONE, ATTACK, DONE],
+  [GET, "script tag w/ quirk", "outside",
+   '<<SCRIPT>parent.postMessage("xss", "*");//<</SCRIPT>', NONE, NONE, ATTACK, DONE],
+  //  '<' does not work on windows
+  // ["external_outside5_bad", "outside_script",
+  //  '<SCRIPT SRC=http://example.org/tests/content/base/test/file_XSS_title.js?<B>'],
+  [GET, "external w/ quirk", "outside_script",
+   '<SCRIPT SRC=//example.org/tests/content/base/test/file_XSS_title.js>',
+   NONE, NONE, ATTACK, DONE],
+  [GET, "whole script w/ eval w/o quotes", "outside",
+   '<SCRIPT>eval(/parent.postMessage("xss", /\\*/.source[1])/.source)</SCRIPT>',
+   NONE, NONE, ATTACK, DONE],
+  [GET, "iframe src w/ jsurl", "outside",
+   '<IFRAME ID="frm" SRC="javascript:void(parent.parent.postMessage(\'xss\', \'*\'));"></IFRAME>', NONE, NONE, ATTACK, DONE],
+  [GET, "svg object", "outside",
+   '<EMBED SRC="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxOTQiIGhlaWdodD0iMjAwIiBpZD0ieHNzIj48c2NyaXB0IHR5cGU9InRleHQvZWNtYXNjcmlwdCI+cGFyZW50LnBhcmVudC5wb3N0TWVzc2FnZSgieHNzIiwgIioiKTs8L3NjcmlwdD48L3N2Zz4=" type="image/svg+xml" AllowScriptAccess="always"></EMBED>', NONE, NONE, ATTACK, DONE],
+  [GET, "external w/ fake jpg", "outside",
+   '<SCRIPT SRC="http://example.org/tests/content/base/test/file_XSS_title.jpg"></SCRIPT>', NONE, NONE, ATTACK, DONE],
+  [GET, "external script w/ quirk", "outside",
+   '<SCRIPT a=">" SRC="http://example.org/tests/content/base/test/file_XSS_title.js"></SCRIPT>', NONE, NONE, ATTACK, DONE],
+  [GET, "whole script w/ doc.write", "outside",
+   '<SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="http://example.org/tests/content/base/test/file_XSS_title.js"></SCRIPT>', NONE, NONE, ATTACK, DONE],
+  [GET, "inline_outside_esc_bad", "outside_esc",
+   '<script>parent.postMessage("xss", "*");</script>', NONE, NONE, ATTACK, DONE],
+  [GET, "handler_plain_bad", "handler_plain",
+   'parent.postMessage("xss", "*");', NONE, NONE, ATTACK, DONE],
+  [GET, "handler_inside_bad", "handler_inside",
+   '= 3; parent.postMessage("xss", "*"); //', NONE, NONE, ATTACK, TODO],
+  [GET, "external_outside_bad", "outside",
+'<script src="http://example.org/tests/content/base/test/file_XSS_title.js">' +
+   '</script>', NONE, NONE, ATTACK, DONE],
+  [GET, "benign external script", "external_path",
+   "file_XSS_title.js", NONE, NONE, SAFE, DONE],
+  // p larger than s?
+  [GET, "external w/o http", "external_domain",
+   "example.org/tests/content/base/test/file_XSS_title.js",
+   NONE, NONE, ATTACK, DONE],
+  [GET, "jsurl in frame", "html_nobody",
+   '<FRAMESET><FRAME SRC="javascript:void(parent.parent.postMessage(\'xss\', \'*\'));"></FRAMESET>', NONE, NONE, ATTACK, DONE],
+  [GET, "jsurl with html entities/utf8", "outside",
+   '<IFRAME SRC=&#106&#97&#118&#97&#115&#99&#114&#105&#112&#116&#58&#112&#97&#114&#101&#110&#116&#46&#112&#97&#114&#101&#110&#116&#46&#112&#111&#115&#116&#77&#101&#115&#115&#97&#103&#101&#40&#34&#120&#115&#115&#34&#44&#32&#34&#42&#34&#41></iframe>',
+   NONE, NONE, ATTACK, DONE],
+  [GET, "jsurl with html entities 2", "outside",
+   '<IFRAME SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000112&#0000097&#0000114&#0000101&#0000110&#0000116&#0000046&#0000112&#0000097&#0000114&#0000101&#0000110&#0000116&#0000046&#0000112&#0000111&#0000115&#0000116&#0000077&#0000101&#0000115&#0000115&#0000097&#0000103&#0000101&#0000040&#0000034&#0000120&#0000115&#0000115&#0000034&#0000044&#0000032&#0000034&#0000042&#0000034&#0000041></iframe>', NONE, NONE, ATTACK, DONE],
+  // TODO: create a flash file?
+  //["inline_outside10_bad", "outside",
+  // '<EMBED SRC="http://ha.ckers.org/xss.swf" AllowScriptAccess="always"></EMBED>'],
+  [GET, "inside js url", "js_url",
+   "javascript:void(parent.parent.postMessage(\'xss\', \'*\'));",
+   NONE, NONE, ATTACK, DONE],
+  [GET, "body onload event", "html_nobody",
+   '<body onload="parent.postMessage(\'xss\', \'*\');">Hello</body>',
+   NONE, NONE, ATTACK, DONE],
+  // does not seem to be a valid vector for firefox
+  // ["meta1_bad", "meta",
+  // '<META HTTP-EQUIV="refresh" CONTENT="0;url=javascript:void(parent.parent.postMessage(\'xss\', \'*\'));">'],
+  // document domain has not been set
+  // ["meta2_bad", "meta",
+  // '<META HTTP-EQUIV="refresh" CONTENT="1;url=data:text/html;base64,c2V0VGltZW91dCgiZG9jdW1lbnQudGl0bGUgPSAnWWVzJyIsIDUwMCk7">'],
+  // does not work. maybe server sends content type?
+  // ["utf7_bad", "meta",
+  // '<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=UTF-7"> </HEAD><body>+ADw-SCRIPT+AD4-alert(\'XSS\');+ADw-/SCRIPT+AD4-</body><!--'],
+  [GET, "eval", "eval",
+   'parent.postMessage("xss", "*");', NONE, NONE, ATTACK, DONE],
+  [GET, "eval after ready", "eval_ready",
+   'parent.postMessage("xss", "*");', NONE, NONE, ATTACK, DONE],
+  [GET, 'js function', 'function',
+   'parent.postMessage("xss", "*");', NONE, NONE, ATTACK, DONE],
+  [GET, 'setTimeout', 'timeout',
+   'parent.postMessage("xss", "*");', NONE, NONE, ATTACK, DONE],
+
+  [POST, "whole script injection", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', NONE,
+   'application/x-www-form-urlencoded', ATTACK, DONE],
+  [POST, "whole scr w/ mime post", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', NONE,
+   'multipart/form-data', ATTACK, DONE],
+  [POST, "partial inj w/POST", "inside",
+   'abc\'; parent.postMessage("xss", "*");//', NONE,
+   "application/x-www-form-urlencoded", ATTACK, TODO],
+
+  [GET, "blank hdr", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "", NONE, ATTACK, DONE],
+  [GET, "hdr enabled", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "1", NONE, ATTACK, DONE],
+  [GET, "hdr enabled w/space", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "  1", NONE, ATTACK, DONE],
+  [GET, "wrong hdr", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "pippo", NONE, ATTACK, DONE],
+  [GET, "hdr disable", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "0", NONE, SAFE, DONE],
+  [GET, "hdr disable w/spaces", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "  0", NONE, SAFE, DONE],
+  [GET, "hdr disable w/junk", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "0dfdf", NONE, SAFE, DONE],
+  [GET, "enable w/block mode", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "1; mode=block",
+   NONE, ATTACK_BLOCK, DONE],
+  [GET, "enable w/block mode 2", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "1;mode =   block  ",
+   NONE, ATTACK_BLOCK, DONE],
+  [GET, "enable w/block mode 3", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "1 ; mODe= bLock",
+   NONE, ATTACK_BLOCK, DONE],
+  [GET, "wrong block mode", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "1 ; mODe= bLock;",
+   NONE, ATTACK, DONE],
+  [GET, "hdr wrong", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "0; disabled;",
+   NONE, SAFE, DONE],
+  [GET, "junk", "outside",
+   '<script>parent.postMessage("xss", "*");</script>', "a0",
+   NONE, ATTACK, DONE],
+
+];
+
+function setXss (state){
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+    .getService(Components.interfaces.nsIPrefService);
+  var cspBranch = prefService.getBranch("security.xssfilter.");
+  cspBranch.setBoolPref("enabled", state);
+}
+
+// caution, text is unescaped, do not output html chars
+function say(text) {
+  jQuery('body').append(text + '<br>\n');
+}
+
+function sayLink(url, link, msg) {
+  say("<a target='_blank' href='" + url.replace(/'/g, "\'") + "'>" + link +
+      "</a> " + msg);
+}
+
+function getObserverService() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  return Cc["@mozilla.org/observer-service;1"].
+    getService(Ci.nsIObserverService);
+}
+
+function parseXSSArray(xpcomarray) {
+  if (xpcomarray.length == 0)
+    return null;
+  return {policy: policy, content: content, url: url, blockMode: blockMode};
+}
+
+const Observer = {
+
+  observe: function (aSubject, aTopic, aData)
+  {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var t = Tester.getCase();
+    aSubject.QueryInterface(Ci.nsIArray);
+    var policy = aSubject.queryElementAt(0, Ci.nsISupportsCString).data;
+    var content = aSubject.queryElementAt(1, Ci.nsISupportsCString).data;
+    var url = aSubject.queryElementAt(2, Ci.nsISupportsCString).data;
+    var blockMode = aSubject.queryElementAt(3, Ci.nsISupportsPRBool).data;
+    var shouldBlock = t.attack == ATTACK_BLOCK;
+    var shouldAttack = t.attack == ATTACK || t.attack == ATTACK_BLOCK;
+    if (t.todo == DONE) {
+      ok(shouldAttack && (blockMode == shouldBlock), t.method + ": " + t.description +
+	 " (" + t.attack + ")");
+    } else {
+      todo(shouldAttack && (blockMode == shouldBlock), t.method + ": " + t.description +
+	   " (" + t.attack + ")");
+    }
+    setTimeout("Tester.next();", 10);
+  }
+};
+
+var Tester = {
+
+  current: 0,
+  tests: TESTS,
+  tID: 0,
+
+  test: function() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var t = this.getCase();
+    // setup listeners for postmessage and notification topic
+    window.addEventListener("message", this.receiveMessage, false);
+    getObserverService().addObserver(Observer, "xss-on-violate-policy", false);
+    this.tID = window.setTimeout("Tester.timeout()", 5000);
+    if (t.method == GET) {
+      sayLink('file_XSS.sjs?hdr=' + escape(t.header) + '&type=' + t.context +
+	      '&content=' + escape(t.content), t.description, t.attack);
+      jQuery('body').append(jQuery("<iframe id='iframe_" + t.id +
+				   "' src='file_XSS.sjs?" +
+				   "hdr=" + escape(t.header) +
+				   "&type=" + escape(t.context) +
+				   "&content=" + escape(t.content) +
+				   "'></iframe>"));
+    } else {
+      say("Test: " + t.description);
+      jQuery('body').append(
+	jQuery("<iframe id='iframe_" + t.id + "' name='iframe_" + t.id +
+	       "' src='about:blank'></iframe>"));
+      jQuery('body').append(jQuery(
+	"<form method='POST' action='file_XSS.sjs' target='iframe_" + t.id +
+	  "' id='form_" + t.id + "' enctype='" + t.encoding + "'>" +
+	  "<input type='text' name='type' value='" + t.context + "' />" +
+	  "<input type='text' name='hdr' value='" + t.header + "' />" +
+	  "<input type='text' name='content' id='content_" + t.id + "' />" +
+
+	  "<input type='submit' name='boh' value='submit test' />" +
+	  "</form>"));
+      jQuery('#content_' + t.id).val(t.content);
+      jQuery("#form_" + t.id).submit();
+    }
+  },
+
+  next: function () {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var t = this.getCase();
+    // remove listeners (and forms!)
+    if (t.method == POST) {
+      jQuery("#form_" + t.id).remove();
+    }
+    window.clearTimeout(this.tID);
+    window.removeEventListener("message", this.receiveMessage);
+    getObserverService().removeObserver(Observer, "xss-on-violate-policy");
+    jQuery("#iframe_" + t.id).remove();
+    this.current += 1;
+    if (this.current >= this.tests.length) {
+      say("Tests done.");
+      SimpleTest.finish();
+      return;
+    }
+    setTimeout("Tester.test()", 10);
+  },
+
+  receiveMessage: function(event)
+  {
+    var t = Tester.getCase();
+    if (t.todo == DONE) {
+      ok(t.attack == SAFE, t.method + ": " + t.description +
+	 " (" + t.attack + ")");
+    } else {
+      todo(t.attack == SAFE, t.method + ": "+ t.description +
+	   " (" + t.attack + ")");
+    }
+    setTimeout("Tester.next();", 10);
+  },
+
+  timeout: function() {
+    var t = this.getCase();
+    say("Timeout for " + t.description);
+    if (t.todo == DONE) {
+      ok(false, "Timeout for " + t.description + " (" + t.attack + ")");
+    } else {
+      todo(false, "Timeout for " + t.description + " (" + t.attack + ")");
+    }
+    setTimeout("Tester.next();", 10);
+  },
+
+  getCase: function() {
+    var t = this.tests[this.current];
+    return {method: t[0], description: t[1], context: t[2], content: t[3],
+	    header: t[4], encoding: t[5], attack: t[6], todo: t[7],
+	    id: this.current};
+  },
+
+  printLinks: function() {
+    say('Only GET tests are printed.');
+    for (var i = 0; i < this.tests.length; i++) {
+      this.current = i;
+      var t = this.getCase();
+      if (t.method == GET) {
+	sayLink('file_XSS.sjs?hdr=' + escape(t.header) + '&type=' + t.context +
+		'&content=' + escape(t.content), t.description, t.attack);
+      }
+    }
+  },
+
+};
+
+jQuery(document).ready(
+    function () {
+      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+      if (env.exists("MANUALTEST") == false) {
+	setTimeout("Tester.test()", 10);
+	SimpleTest.waitForExplicitFinish();
+      } else {
+	// just print the links to open manually (only GET)
+	Tester.printLinks();
+	SimpleTest.finish();
+      }
+    }
+);
+
+
+// TODO: SimpleTest can't figure out the URL for the page.
\ No newline at end of file
diff --git a/content/base/test/file_XSS.sjs b/content/base/test/file_XSS.sjs
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_XSS.sjs
@@ -0,0 +1,243 @@
+// SJS file for XSS mochitests
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function handleRequest(request, response)
+{
+  var query = {};
+  var requestBody;
+  if (request.queryString) {
+    request.queryString.split('&').forEach(function (val) {
+      var [name, value] = val.split('=');
+      query[name] = unescape(value);
+    });
+  }
+
+  if (request.method == "POST") {
+    requestBody = NetUtil.readInputStreamToString(
+      request.bodyInputStream,
+      request.bodyInputStream.available()
+    );
+  }
+
+  if (request.method == "POST" &&
+      request.hasHeader("Content-Type") &&
+      request.getHeader("Content-Type") ==
+      "application/x-www-form-urlencoded") {
+    requestBody.split('&').forEach(function (val) {
+      var [name, value] = val.split('=');
+      query[name] = unescape(value.replace(/\+/g, ' '));
+    });
+  }
+
+  if (request.method == "POST" &&
+      request.hasHeader("Content-Type") &&
+      request.getHeader("Content-Type")
+      .indexOf("multipart/form-data") != -1) {
+
+    var regex = /boundary=(-+.*)/;
+    var boundary = regex.exec(request.getHeader("Content-Type"))[1];
+    if (boundary == "") {
+      response.write("Cannot parse MIME post params");
+      return;
+    }
+    //dump("Boundary: " + boundary + "\n\n\n");
+    //dump("Requestbody:\n" + requestBody + "\n\n\n\n");
+    var fields = requestBody.split(boundary + "\r\n");
+    for (var i = 1; i < fields.length; i++) {
+      fields[i] = fields[i].replace(boundary + "--", "");
+      //dump("Field: " + fields[i] + "\n\n\n\n");
+      regex = /Content-Disposition:.+; name="(.*?)"(?:; filename="(.*?)")?/;
+      var result = regex.exec(fields[i]);
+      if (!result) {
+	response.write("mime parsing error");
+	return;
+      }
+      var name = result[1];
+      var filename = result[2];
+      if (filename == undefined) {
+       	var content = fields[i].split("\r\n\r\n")[1].replace("\r\n--", "");
+       	query[name] = content;
+      } else {
+       	query[name] = filename;
+      }
+    }
+  }
+
+  dump("Query: " + JSON.stringify(query) + "\n");
+  if (!query.content) {
+    response.write("No content");
+    return;
+  }
+
+  if (query['hdr'] != 'none') {
+    response.setHeader("x-xss-protection", query['hdr'], false);
+  }
+
+  //avoid confusing cache behaviors
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/html", false);
+
+  //response.write('type=' + query['type']);
+
+  HTML = "<html><head><title>No</title>" +
+    '<script type="text/javascript" ' +
+    'src="/tests/dom/tests/mochitest/ajax/jquery/dist/jquery.js"></script>' +
+    "</head><body>HERE</body></html>";
+
+  HTML_NOBODY = "<html><head><title>No</title>" +
+    '<script type="text/javascript" ' +
+    'src="/tests/dom/tests/mochitest/ajax/jquery/dist/jquery.js"></script></head>' +
+    "HERE</html>";
+
+  HTML_HEAD = "<html><head><title>No</title>" +
+    '<script type="text/javascript" ' +
+    'src="/tests/dom/tests/mochitest/ajax/jquery/dist/jquery.js"></script>HERE' +
+    "</head><body>Hello</body></html>";
+
+  FRAMESET = "<html><head><title>No</title></head>HERE</html>";
+
+
+  INLINE_SCRIPT = "<script>var pippo = 3456;\n unescape('llla<>');"
+		+ "var query = 'term_HERE';"
+		+ "var done = /aaa/.test('bbb');</script>";
+
+  STATIC_SCRIPT = "<script>var abcd = 234+2;</script>";
+
+  ATTR = "<a href=\'#\' HERE>Click</a>";
+  HANDLER_ATTR = "onclick='HERE'";
+  HANDLER_PIECE = "do_HERE()";
+
+  // add these to execute inline event handlers
+  FAKE_CLICK = "<script>"
+	       + "function fakeClick(obj) {"
+	       + "  var evt = document.createEvent('MouseEvents');"
+	       + "  evt.initMouseEvent('click', true, true, window,"
+	       + "  0, 0, 0, 0, 0, false, false, false, false, 0, null);"
+	       + "  var allowDefault = obj.dispatchEvent(evt);"
+	       + "}"
+	       + "</script>";
+  CLICK_TRIGGER = "<script>$(function () { fakeClick($('a')[0]) })</script>";
+
+  EXTERNAL_DOMAIN = '<script src="http://HERE"></script>';
+
+  EXTERNAL_PATH = 'example.org/tests/content/base/test/HERE';
+
+  // add this to run JS urls
+  AUTO_FORM = '<form action="HERE">' +
+    '<input type="submit" value="Submit"></form>' +
+    "<script>$(function () { $('form').submit() })</script>";
+
+  // some quirks require another script tag after the attack
+  ADD_SCRIPT = "<script>nop();</script>";
+
+  // TODO: document.getElementById("frm").document is undefined
+  ADD_SCRIPT_IFRAME = "<script>$(document).ready( function() { document.title = document.getElementById('frm').document.title; } )</script>";
+
+  EVAL = "<script>eval('HERE');</script>";
+  EVAL_READY = "<script>$(document).ready(function() { eval('HERE'); });</script>";
+  FUNCTION = "<script>f = Function('HERE'); f();</script>";
+  TIMEOUT = "<script>setTimeout('HERE', 1);</script>";
+
+  var fix_param;
+
+  switch(query['type']) {
+  case "outside":
+    response.write(HTML.replace("HERE", query['content']));
+    break;
+  case "outside_esc":
+    response.write(HTML.replace("HERE", query['content'].replace(/\"/g, "'")));
+    break;
+  case "outside_script":
+    response.write(HTML.replace("HERE", query['content'] + ADD_SCRIPT));
+    break;
+  case "outside_iframe":
+    response.write(HTML.replace("HERE", query['content'] + ADD_SCRIPT_IFRAME));
+    break;
+  case "inside":
+    var outside = INLINE_SCRIPT.replace("HERE", query['content']);
+    response.write(HTML.replace("HERE", outside));
+    break;
+  case "attribute":
+    var outside = ATTR.replace("HERE", query['content']);
+    response.write(HTML.replace("HERE", outside));
+    break;
+  case "handler_plain":
+    var handler = HANDLER_ATTR.replace("HERE", query['content']);
+    var outside = ATTR.replace("HERE", handler);
+    response.write(HTML.replace("HERE", FAKE_CLICK + outside + CLICK_TRIGGER));
+    break;
+  case "handler_inside":
+    var piece = HANDLER_PIECE.replace("HERE", query['content']);
+    var handler = HANDLER_ATTR.replace("HERE", piece);
+    var outside = ATTR.replace("HERE", handler);
+    response.write(HTML.replace("HERE", FAKE_CLICK + outside + CLICK_TRIGGER));
+    break;
+  case "external_domain":
+    var outside = EXTERNAL_DOMAIN.replace("HERE", query['content']);
+    response.write(HTML.replace("HERE", outside));
+    break;
+  case "external_path":
+    var path = EXTERNAL_PATH.replace("HERE", query['content']);
+    var outside = EXTERNAL_DOMAIN.replace("HERE", path);
+    response.write(HTML.replace("HERE", outside));
+    break;
+  case "js_url":
+    var outside = AUTO_FORM.replace("HERE", query['content']);
+    response.write(HTML.replace("HERE", outside));
+    break;
+  case "html_nobody":
+    response.write(HTML_NOBODY.replace("HERE", query['content']));
+    break;
+  case "frameset":
+    response.write(FRAMESET.replace("HERE", query['content']));
+    break;
+  case "meta":
+    response.write(HTML_HEAD.replace("HERE", query['content']));
+    break;
+  case "static":
+    response.write(HTML.replace("HERE", STATIC_SCRIPT));
+    break;
+  case "data_url":
+    break;
+  case "full_inline_good":
+    fix_param = '<script>document.title = "Yes";</script>';
+    response.write(HTML.replace("HERE",	fix_param));
+    break;
+  case "part_inline_good":
+    fix_param = 'abc\'; document.title = "Yes";//';
+    var outside = INLINE_SCRIPT.replace("HERE", fix_param);
+    response.write(HTML.replace("HERE", outside));
+    break;
+  case "handler_plain_good":
+    fix_param = 'document.title = "Yes";';
+    var handler = HANDLER_ATTR.replace("HERE", fix_param);
+    var outside = ATTR.replace("HERE", handler);
+    response.write(HTML.replace("HERE", FAKE_CLICK + outside + CLICK_TRIGGER));
+    break;
+  case "full_external_good":
+    fix_param =
+'<script src="http://example.org/tests/content/base/test/file_XSS_title.js">' +
+'</script>';
+    response.write(HTML.replace("HERE",	fix_param));
+  case "eval":
+    var outside = EVAL.replace("HERE", query['content']);
+    response.write(HTML.replace("HERE", outside));
+    break;
+  case "eval_ready":
+    var outside = EVAL_READY.replace("HERE", query['content']);
+    response.write(HTML.replace("HERE", outside));
+    break;
+  case "function":
+    var outside = FUNCTION.replace("HERE", query['content']);
+    response.write(HTML.replace("HERE", outside));
+    break;
+  case "timeout":
+    var outside = TIMEOUT.replace("HERE", query['content']);
+    response.write(HTML.replace("HERE", outside));
+    break;
+  default:
+    response.write("ERROR");
+  }
+
+}
diff --git a/content/base/test/file_XSS_binding.xml b/content/base/test/file_XSS_binding.xml
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_XSS_binding.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<bindings xmlns="http://www.mozilla.org/xbl">
+  <binding id="xss">
+    <implementation>
+      <constructor><![CDATA[document.title = 'Yes']]></constructor>
+    </implementation>
+  </binding>
+</bindings>
diff --git a/content/base/test/file_XSS_title.jpg b/content/base/test/file_XSS_title.jpg
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_XSS_title.jpg
@@ -0,0 +1,1 @@
+parent.postMessage("xss", "*");
\ No newline at end of file
diff --git a/content/base/test/file_XSS_title.js b/content/base/test/file_XSS_title.js
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_XSS_title.js
@@ -0,0 +1,1 @@
+parent.postMessage("xss", "*");
\ No newline at end of file
diff --git a/content/base/test/test_XSS.html b/content/base/test/test_XSS.html
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_XSS.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test for XSS Filter</title>
+<script type="text/javascript" src="/MochiKit/packed.js"></script>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<!--<script type="text/javascript" src="file_jquery.js"></script>-->
+<script type="text/javascript"
+  src="/tests/dom/tests/mochitest/ajax/jquery/dist/jquery.js">
+</script>
+<script>jQuery.noConflict();</script>
+<script type="text/javascript" src="file_XSS.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+
+  <pre id="debug">
+  </pre>
+
+<a href="javascript:setXss(true)">Filter on</a>
+<a href="javascript:setXss(false)">Filter off</a>
+<br>
+
+</body>
+</html>
diff --git a/content/events/src/nsEventListenerManager.cpp b/content/events/src/nsEventListenerManager.cpp
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -49,20 +49,25 @@
 #include "nsDOMJSUtils.h"
 #include "nsDOMScriptObjectHolder.h"
 #include "nsDataHashtable.h"
 #include "nsCOMArray.h"
 #include "nsEventListenerService.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsJSEnvironment.h"
 #include "xpcpublic.h"
+#include "nsXSSFilter.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::hal;
 
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gXssPRLog;
+#endif
+
 #define EVENT_TYPE_EQUALS( ls, type, userType ) \
   (ls->mEventType == type && \
   (ls->mEventType != NS_USER_DEFINED_EVENT || ls->mTypeAtom == userType))
 
 static NS_DEFINE_CID(kDOMScriptObjectFactoryCID,
                      NS_DOM_SCRIPT_OBJECT_FACTORY_CID);
 
 static const PRUint32 kAllMutationBits =
@@ -109,16 +114,23 @@ nsEventListenerManager::nsEventListenerM
   mMayHaveTouchEventListener(false),
   mMayHaveMouseEnterLeaveEventListener(false),
   mNoListenerForEvent(0),
   mTarget(aTarget)
 {
   NS_ASSERTION(aTarget, "unexpected null pointer");
 
   ++sCreatedCount;
+
+  // enable logging for XSS
+#ifdef PR_LOGGING
+  if (!gXssPRLog)
+    gXssPRLog = PR_NewLogModule("XSS");
+#endif
+
 }
 
 nsEventListenerManager::~nsEventListenerManager() 
 {
   // If your code fails this assertion, a possible reason is that
   // a class did not call our Disconnect() manually. Note that
   // this class can have Disconnect called in one of two ways:
   // if it is part of a cycle, then in Unlink() (such a cycle
@@ -499,16 +511,17 @@ nsEventListenerManager::SetJSEventListen
     }
   } else {
     ls->GetJSListener()->SetHandler(aHandler);
   }
 
   if (NS_SUCCEEDED(rv) && ls) {
     // Set flag to indicate possible need for compilation later
     ls->mHandlerIsString = !aHandler;
+
     if (aPermitUntrustedEvents) {
       ls->mFlags |= NS_PRIV_EVENT_UNTRUSTED_PERMITTED;
     }
 
     *aListenerStruct = ls;
   } else {
     *aListenerStruct = nullptr;
   }
@@ -601,16 +614,27 @@ nsEventListenerManager::AddScriptEventLi
         scriptSample.AppendLiteral(" element");
         csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
                                  NS_ConvertUTF8toUTF16(asciiSpec),
                                  scriptSample,
                                  0);
         return NS_OK;
       }
     }
+
+    nsRefPtr<nsXSSFilter> xss;
+    rv = doc->NodePrincipal()->GetXSSFilter(getter_AddRefs(xss));
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (xss) {
+      if (!xss->PermitsEventListener(aBody)) {
+        PR_LOG(gXssPRLog, PR_LOG_DEBUG,
+               ("XSS:Onload:blocked onload event"));
+        return NS_OK;
+      }
+    }
   }
 
   // This might be the first reference to this language in the global
   // We must init the language before we attempt to fetch its context.
   if (NS_FAILED(global->EnsureScriptEnvironment())) {
     NS_WARNING("Failed to setup script environment for this language");
     // but fall through and let the inevitable failure below handle it.
   }
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3999,16 +3999,21 @@ nsDocShell::DisplayLoadError(nsresult aE
         formatStrCount = 1;
         error.AssignLiteral("netTimeout");
     }
     else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError) {
         // CSP error
         cssClass.AssignLiteral("neterror");
         error.AssignLiteral("cspFrameAncestorBlocked");
     }
+    else if (NS_ERROR_XSS_BLOCK == aError) {
+        // XSS violation in block mode
+        cssClass.AssignLiteral("neterror");
+        error.AssignLiteral("xssBlockMode");
+    }
     else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) {
         nsCOMPtr<nsINSSErrorsService> nsserr =
             do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID);
 
         PRUint32 errorClass;
         if (!nsserr ||
             NS_FAILED(nsserr->GetErrorClass(aError, &errorClass))) {
           errorClass = nsINSSErrorsService::ERROR_CLASS_SSL_PROTOCOL;
diff --git a/docshell/resources/content/netError.xhtml b/docshell/resources/content/netError.xhtml
--- a/docshell/resources/content/netError.xhtml
+++ b/docshell/resources/content/netError.xhtml
@@ -160,16 +160,22 @@
         }
 
         if (err == "cspFrameAncestorBlocked") {
           // Remove the "Try again" button for CSP frame ancestors violation, since it's
           // almost certainly useless. (Bug 553180)
           document.getElementById("errorTryAgain").style.display = "none";
         }
 
+        if (err == "xssBlockMode") {
+          // Remove the "Try again" button for XSS violations. We might instead consider
+          // putting a "Load Anyway" button.
+          document.getElementById("errorTryAgain").style.display = "none";
+        }
+
         if (err == "nssBadCert") {
           // Remove the "Try again" button for security exceptions, since it's
           // almost certainly useless.
           document.getElementById("errorTryAgain").style.display = "none";
           document.getElementById("errorPageContainer").setAttribute("class", "certerror");
           addDomainErrorLink();
         }
         else {
@@ -291,16 +297,17 @@
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
         <h1 id="et_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.title;</h1>
+        <h1 id="et_xssBlockMode">&xssBlockMode.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
         <div id="ed_malformedURI">&malformedURI.longDesc;</div>
@@ -317,16 +324,17 @@
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
         <div id="ed_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.longDesc;</div>
+        <div id="ed_xssBlockMode">&xssBlockMode.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
       </div>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer">
     
diff --git a/docshell/test/navigation/navigate.html b/docshell/test/navigation/navigate.html
--- a/docshell/test/navigation/navigate.html
+++ b/docshell/test/navigation/navigate.html
@@ -1,14 +1,27 @@
 <html>
 <head>
     <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
     <script src="NavigationUtils.js"></script>
     <script>
+
+    function setXss(value) {        
+        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+        var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+	  .getService(Components.interfaces.nsIPrefService);
+        var xssBranch = prefService.getBranch("security.xssfilter.");
+        originalValue = xssBranch.getBoolPref("enable");
+        xssBranch.setBoolPref("enable", value);
+    }
+
     function navigate() {
+        // some tests using this page will trigger an xss violation
+        setXss(false);
+
         var arguments = window.location.hash.substring(1).split(",");
         var target = arguments[0];
         var mechanism = arguments[1];
 
         switch(mechanism) {
         case "location":
           navigateByLocation(eval(target));
           break;
@@ -17,16 +30,19 @@
           break;
         case "form":
           navigateByForm(target);
           break;
         case "hyperlink":
           navigateByHyperlink(target);
           break;
         }
+
+        setXss(true);
+
     }
     </script>
 </head>
 <body onload="navigate();">
 <script>
 var arguments = window.location.hash.substring(1).split(",");
 var target = arguments[0];
 var mechanism = arguments[1];
diff --git a/dom/base/nsJSTimeoutHandler.cpp b/dom/base/nsJSTimeoutHandler.cpp
--- a/dom/base/nsJSTimeoutHandler.cpp
+++ b/dom/base/nsJSTimeoutHandler.cpp
@@ -15,16 +15,17 @@
 #include "nsContentUtils.h"
 #include "nsJSEnvironment.h"
 #include "nsServiceManagerUtils.h"
 #include "nsDOMError.h"
 #include "nsGlobalWindow.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsAlgorithm.h"
 #include "mozilla/Attributes.h"
+#include "nsXSSFilter.h"
 
 static const char kSetIntervalStr[] = "setInterval";
 static const char kSetTimeoutStr[] = "setTimeout";
 
 // Our JS nsIScriptTimeoutHandler implementation.
 class nsJSScriptTimeoutHandler MOZ_FINAL : public nsIScriptTimeoutHandler
 {
 public:
@@ -180,16 +181,17 @@ nsJSScriptTimeoutHandler::Init(nsGlobalW
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRUint32 argc;
   jsval *argv = nullptr;
 
   ncc->GetArgc(&argc);
   ncc->GetArgvPtr(&argv);
 
+  JSString *str = nsnull;
   JSFlatString *expr = nullptr;
   JSObject *funobj = nullptr;
 
   JSAutoRequest ar(cx);
 
   if (argc < 1) {
     ::JS_ReportError(cx, "Function %s requires at least 2 parameter",
                      *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
@@ -213,17 +215,17 @@ nsJSScriptTimeoutHandler::Init(nsGlobalW
   switch (::JS_TypeOfValue(cx, argv[0])) {
   case JSTYPE_FUNCTION:
     funobj = JSVAL_TO_OBJECT(argv[0]);
     break;
 
   case JSTYPE_STRING:
   case JSTYPE_OBJECT:
     {
-      JSString *str = ::JS_ValueToString(cx, argv[0]);
+      str = ::JS_ValueToString(cx, argv[0]);
       if (!str)
         return NS_ERROR_OUT_OF_MEMORY;
 
       expr = ::JS_FlattenString(cx, str);
       if (!expr)
           return NS_ERROR_OUT_OF_MEMORY;
 
       argv[0] = STRING_TO_JSVAL(str);
@@ -258,16 +260,31 @@ nsJSScriptTimeoutHandler::Init(nsGlobalW
         if (!allowsEval) {
           ::JS_ReportError(cx, "call to %s blocked by CSP",
                             *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
 
           // Note: Our only caller knows to turn NS_ERROR_DOM_TYPE_ERR into NS_OK.
           return NS_ERROR_DOM_TYPE_ERR;
         }
       }
+      nsRefPtr<nsXSSFilter> xss;
+      rv = doc->NodePrincipal()->GetXSSFilter(getter_AddRefs(xss));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (xss) {
+        //xss settimeout
+        nsDependentJSString ns_str;
+        ns_str.init(cx, str);
+        if (!xss->PermitsJSAction(ns_str)) {
+          ::JS_ReportError(cx, "call to %s blocked by XSS Filter",
+                           *aIsInterval ? kSetIntervalStr : kSetTimeoutStr);
+          return NS_ERROR_DOM_TYPE_ERR;
+        }
+      }
+
     } // if there's no document, we don't have to do anything.
 
     rv = NS_HOLD_JS_OBJECTS(this, nsJSScriptTimeoutHandler);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mExpr = expr;
 
     // Get the calling location.
diff --git a/dom/locales/en-US/chrome/appstrings.properties b/dom/locales/en-US/chrome/appstrings.properties
--- a/dom/locales/en-US/chrome/appstrings.properties
+++ b/dom/locales/en-US/chrome/appstrings.properties
@@ -26,10 +26,11 @@ externalProtocolTitle=External Protocol 
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
 cspFrameAncestorBlocked=This page has a content security policy that prevents it from being embedded in this way.
+xssBlockMode=This page contains an XSS attacks that has been blocked for your security.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default.
diff --git a/dom/locales/en-US/chrome/netError.dtd b/dom/locales/en-US/chrome/netError.dtd
--- a/dom/locales/en-US/chrome/netError.dtd
+++ b/dom/locales/en-US/chrome/netError.dtd
@@ -85,16 +85,19 @@
 <!ENTITY phishingBlocked.longDesc "
 <p>Entering any personal information on this page may result in identity theft or other fraud.</p>
 <p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
 ">
 
 <!ENTITY cspFrameAncestorBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspFrameAncestorBlocked.longDesc "<p>The browser prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
 
+<!ENTITY xssBlockMode.title "Blocked by XSS Filter">
+<!ENTITY xssBlockMode.longDesc "<p>The browser blocked further actions on this page because the page contains injected JavaScript code.</p>">
+
 <!ENTITY corruptedContentError.title "Corrupted Content Error">
 <!ENTITY corruptedContentError.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
 
 <!ENTITY remoteXUL.title "Remote XUL">
 <!ENTITY remoteXUL.longDesc "<p><ul><li>Please contact the website owners to inform them of this problem.</li></ul></p>">
 
 <!-- Include app-specific error messages - do not change this in localization!
      Some applications might override netErrorApp.dtd with their specific version,
diff --git a/dom/src/jsurl/nsJSProtocolHandler.cpp b/dom/src/jsurl/nsJSProtocolHandler.cpp
--- a/dom/src/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/src/jsurl/nsJSProtocolHandler.cpp
@@ -39,16 +39,22 @@
 #include "nsThreadUtils.h"
 #include "nsIJSContextStack.h"
 #include "nsIScriptChannel.h"
 #include "nsIDocument.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsIContentSecurityPolicy.h"
+#include "nsXSSFilter.h"
+#include "prlog.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gXssPRLog;
+#endif
 
 static NS_DEFINE_CID(kJSURICID, NS_JSURI_CID);
 
 class nsJSThunk : public nsIInputStream
 {
 public:
     nsJSThunk();
 
@@ -90,16 +96,21 @@ nsresult nsJSThunk::Init(nsIURI* uri)
     // Get the script string to evaluate...
     nsresult rv = uri->GetPath(mScript);
     if (NS_FAILED(rv)) return rv;
 
     // Get the url.
     rv = uri->GetSpec(mURL);
     if (NS_FAILED(rv)) return rv;
 
+#ifdef PR_LOGGING
+    if (!gXssPRLog)
+        gXssPRLog = PR_NewLogModule("XSS");
+#endif
+
     return NS_OK;
 }
 
 static bool
 IsISO88591(const nsString& aString)
 {
     for (nsString::const_char_iterator c = aString.BeginReading(),
                                    c_end = aString.EndReading();
@@ -177,16 +188,27 @@ nsresult nsJSThunk::EvaluateScript(nsICh
 		  csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
 								   NS_ConvertUTF8toUTF16(asciiSpec),
 								   NS_ConvertUTF8toUTF16(mURL),
                                    0);
           return NS_ERROR_DOM_RETVAL_UNDEFINED;
       }
     }
 
+    nsRefPtr<nsXSSFilter> xss;
+    rv = principal->GetXSSFilter(getter_AddRefs(xss));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (xss) {
+        NS_ConvertUTF8toUTF16 arg(mScript);
+        if (!xss->PermitsJSUrl(arg)) {
+            return NS_ERROR_DOM_RETVAL_UNDEFINED;
+        }
+    }
+
     // Get the global object we should be running on.
     nsIScriptGlobalObject* global = GetGlobalObject(aChannel);
     if (!global) {
         return NS_ERROR_FAILURE;
     }
 
     nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(global));
 
diff --git a/dom/tests/mochitest/localstorage/frameOrder.html b/dom/tests/mochitest/localstorage/frameOrder.html
--- a/dom/tests/mochitest/localstorage/frameOrder.html
+++ b/dom/tests/mochitest/localstorage/frameOrder.html
@@ -1,12 +1,21 @@
 <html>
 <head>
 </head>
 <script type="text/javascript">
+// some tests using this page will trigger an xss violation
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+  .getService(Components.interfaces.nsIPrefService);
+var cspBranch = prefService.getBranch("security.xssfilter.");
+cspBranch.setBoolPref("enabled", false);
+</script>
+
+<script type="text/javascript">
 function doTest()
 {
   var query = location.search.substring(1);
   query = unescape(query);
   var keyNames = eval(query);
 
   parent.is(localStorage.a, "10", "a = 10");
   parent.is(localStorage.b, "20", "b = 20");
@@ -15,13 +24,18 @@ function doTest()
   parent.is(localStorage.e, "50", "e = 50");
   parent.is(localStorage.length, 5, "length = 5");
 
   for (var i = 0; i < localStorage.length; ++i)
     parent.is(keyNames[i], localStorage.key(i), "key "+keyNames[i]+" on same index");
 
   parent.SimpleTest.finish();
   localStorage.clear();
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+    .getService(Components.interfaces.nsIPrefService);
+  var cspBranch = prefService.getBranch("security.xssfilter.");
+  cspBranch.setBoolPref("enabled", true);
 }
 </script>
 <body onload="doTest();">
 </body>
 </html>
diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -167,16 +167,26 @@ EvalKernel(JSContext *cx, const CallArgs
         return true;
     }
     if (!args[0].isString()) {
         args.rval().set(args[0]);
         return true;
     }
     JSString *str = args[0].toString();
 
+    // xss filter
+    const JSSecurityCallbacks *callbacks = 
+        JS_GetSecurityCallbacks(js::GetRuntime(cx));
+    if (callbacks && callbacks->xssFilterAllows) {
+        if (!callbacks->xssFilterAllows(cx, str)) {
+            JS_ReportError(cx, "call to eval() is an xss attack");
+            return false;
+        }
+    }
+
     // ES5 15.1.2.1 steps 2-8.
 
     // Per ES5, indirect eval runs in the global scope. (eval is specified this
     // way so that the compiler can make assumptions about what bindings may or
     // may not exist in the current frame if it doesn't see 'eval'.)
     unsigned staticLevel;
     RootedValue thisv(cx);
     if (evalType == DIRECT_EVAL) {
diff --git a/js/src/js.msg b/js/src/js.msg
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -348,8 +348,9 @@ MSG_DEF(JSMSG_DEBUG_VARIABLE_NOT_FOUND, 
 MSG_DEF(JSMSG_PARAMETER_AFTER_REST,   295, 0, JSEXN_SYNTAXERR, "parameter after rest parameter")
 MSG_DEF(JSMSG_NO_REST_NAME,           296, 0, JSEXN_SYNTAXERR, "no parameter name after ...")
 MSG_DEF(JSMSG_ARGUMENTS_AND_REST,     297, 0, JSEXN_SYNTAXERR, "'arguments' object may not be used in conjunction with a rest parameter")
 MSG_DEF(JSMSG_FUNCTION_ARGUMENTS_AND_REST, 298, 0, JSEXN_ERR, "the 'arguments' property of a function with a rest parameter may not be used")
 MSG_DEF(JSMSG_REST_WITH_DEFAULT,      299, 0, JSEXN_SYNTAXERR, "rest parameter may not have a default")
 MSG_DEF(JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT, 300, 0, JSEXN_SYNTAXERR, "parameter(s) with default followed by parameter without default")
 MSG_DEF(JSMSG_YIELD_IN_DEFAULT,       301, 0, JSEXN_SYNTAXERR, "yield in default expression")
 MSG_DEF(JSMSG_INTRINSIC_NOT_DEFINED,  302, 1, JSEXN_REFERENCEERR, "no intrinsic function {0}")
+MSG_DEF(JSMSG_XSS_BLOCKED_FUNCTION,   303, 0, JSEXN_ERR, "call to Function() blocked by XSS Filter")
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1980,16 +1980,23 @@ typedef JSPrincipals *
 /*
  * Used to check if a CSP instance wants to disable eval() and friends.
  * See js_CheckCSPPermitsJSAction() in jsobj.
  */
 typedef JSBool
 (* JSCSPEvalChecker)(JSContext *cx);
 
 /*
+ * Used to check if an xss filter wants to disable eval() and friends.
+ * See js_CheckXSSPermitsJSAction() in jsobj.
+ */
+typedef JSBool
+(* JSXSSFilterChecker)(JSContext *cx, JSString *str);
+
+/*
  * Callback used to ask the embedding for the cross compartment wrapper handler
  * that implements the desired prolicy for this kind of object in the
  * destination compartment.
  */
 typedef JSObject *
 (* JSWrapObjectCallback)(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent,
                          unsigned flags);
 
@@ -4828,16 +4835,17 @@ JS_HoldPrincipals(JSPrincipals *principa
 extern JS_PUBLIC_API(void)
 JS_DropPrincipals(JSRuntime *rt, JSPrincipals *principals);
 
 struct JSSecurityCallbacks {
     JSCheckAccessOp            checkObjectAccess;
     JSSubsumePrincipalsOp      subsumePrincipals;
     JSObjectPrincipalsFinder   findObjectPrincipals;
     JSCSPEvalChecker           contentSecurityPolicyAllows;
+    JSXSSFilterChecker         xssFilterAllows;
 };
 
 extern JS_PUBLIC_API(void)
 JS_SetSecurityCallbacks(JSRuntime *rt, const JSSecurityCallbacks *callbacks);
 
 extern JS_PUBLIC_API(const JSSecurityCallbacks *)
 JS_GetSecurityCallbacks(JSRuntime *rt);
 
diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1320,16 +1320,25 @@ Function(JSContext *cx, unsigned argc, V
 
     if (args.length()) {
         JSString *str = ToString(cx, args[args.length() - 1]);
         if (!str)
             return false;
         strAnchor.set(str);
         chars = str->getChars(cx);
         length = str->length();
+        const JSSecurityCallbacks *callbacks = JS_GetSecurityCallbacks(js::GetRuntime(cx));
+        // this callback is not always defined
+        if (callbacks && callbacks->xssFilterAllows) {
+            if (!callbacks->xssFilterAllows(cx, str)) {
+                JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                                     JSMSG_XSS_BLOCKED_FUNCTION);
+                return JS_FALSE;
+            }
+        }
     } else {
         chars = cx->runtime->emptyString->chars();
         length = 0;
     }
 
     /*
      * NB: (new Function) is not lexically closed by its caller, it's just an
      * anonymous function in the top-level scope that its constructor inhabits.
diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -48,16 +48,17 @@
 #include "nsIFrameTraversal.h"
 #include "nsLayoutCID.h"
 #include "nsStyleSheetService.h"
 #include "nsFocusManager.h"
 #include "ThirdPartyUtil.h"
 #include "mozilla/Services.h"
 #include "nsStructuredCloneContainer.h"
 #include "mozilla/Attributes.h"
+#include "nsXSSUtils.h"
 
 #include "nsIEventListenerService.h"
 #include "nsIFrameMessageManager.h"
 
 // Transformiix stuff
 #include "nsXPathEvaluator.h"
 #include "txMozillaXSLTProcessor.h"
 #include "txNodeSetAdaptor.h"
@@ -627,16 +628,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMScri
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGeolocation, Init)
 
 #define NS_GEOLOCATION_SERVICE_CID \
   { 0x404d02a, 0x1CA, 0xAAAB, { 0x47, 0x62, 0x94, 0x4b, 0x1b, 0xf2, 0xf7, 0xb5 } }
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationService::GetGeolocationService)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(CSPService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsXSSUtilsWrapper)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsPrincipal)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSecurityNameSet)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsSystemPrincipal,
     nsScriptSecurityManager::SystemPrincipalSingletonConstructor)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsNullPrincipal, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsStructuredCloneContainer)
 
@@ -770,16 +772,17 @@ NS_DEFINE_NAMED_CID(NS_EDITORCONTROLLER_
 NS_DEFINE_NAMED_CID(NS_EDITINGCONTROLLER_CID);
 NS_DEFINE_NAMED_CID(NS_EDITORCOMMANDTABLE_CID);
 NS_DEFINE_NAMED_CID(NS_EDITINGCOMMANDTABLE_CID);
 NS_DEFINE_NAMED_CID(NS_TEXTSERVICESDOCUMENT_CID);
 NS_DEFINE_NAMED_CID(NS_GEOLOCATION_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_GEOLOCATION_CID);
 NS_DEFINE_NAMED_CID(NS_FOCUSMANAGER_CID);
 NS_DEFINE_NAMED_CID(CSPSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_XSSUTILS_CID);
 NS_DEFINE_NAMED_CID(NS_EVENTLISTENERSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_GLOBALMESSAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_PARENTPROCESSMESSAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_CHILDPROCESSMESSAGEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NSCHANNELPOLICY_CID);
 NS_DEFINE_NAMED_CID(NS_SCRIPTSECURITYMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_PRINCIPAL_CID);
 NS_DEFINE_NAMED_CID(NS_SYSTEMPRINCIPAL_CID);
@@ -1044,16 +1047,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_EDITINGCONTROLLER_CID, false, NULL, nsEditingControllerConstructor },
   { &kNS_EDITORCOMMANDTABLE_CID, false, NULL, nsEditorCommandTableConstructor },
   { &kNS_EDITINGCOMMANDTABLE_CID, false, NULL, nsEditingCommandTableConstructor },
   { &kNS_TEXTSERVICESDOCUMENT_CID, false, NULL, nsTextServicesDocumentConstructor },
   { &kNS_GEOLOCATION_SERVICE_CID, false, NULL, nsGeolocationServiceConstructor },
   { &kNS_GEOLOCATION_CID, false, NULL, nsGeolocationConstructor },
   { &kNS_FOCUSMANAGER_CID, false, NULL, CreateFocusManager },
   { &kCSPSERVICE_CID, false, NULL, CSPServiceConstructor },
+  { &kNS_XSSUTILS_CID, false, NULL, nsXSSUtilsWrapperConstructor },
   { &kNS_EVENTLISTENERSERVICE_CID, false, NULL, CreateEventListenerService },
   { &kNS_GLOBALMESSAGEMANAGER_CID, false, NULL, CreateGlobalMessageManager },
   { &kNS_PARENTPROCESSMESSAGEMANAGER_CID, false, NULL, CreateParentMessageManager },
   { &kNS_CHILDPROCESSMESSAGEMANAGER_CID, false, NULL, CreateChildMessageManager },
   { &kNSCHANNELPOLICY_CID, false, NULL, nsChannelPolicyConstructor },
   { &kNS_SCRIPTSECURITYMANAGER_CID, false, NULL, Construct_nsIScriptSecurityManager },
   { &kNS_PRINCIPAL_CID, false, NULL, nsPrincipalConstructor },
   { &kNS_SYSTEMPRINCIPAL_CID, false, NULL, nsSystemPrincipalConstructor },
@@ -1180,16 +1184,17 @@ static const mozilla::Module::ContractID
 #endif
   { "@mozilla.org/editor/editorcontroller;1", &kNS_EDITORCONTROLLER_CID },
   { "@mozilla.org/editor/editingcontroller;1", &kNS_EDITINGCONTROLLER_CID },
   { "@mozilla.org/textservices/textservicesdocument;1", &kNS_TEXTSERVICESDOCUMENT_CID },
   { "@mozilla.org/geolocation/service;1", &kNS_GEOLOCATION_SERVICE_CID },
   { "@mozilla.org/geolocation;1", &kNS_GEOLOCATION_CID },
   { "@mozilla.org/focus-manager;1", &kNS_FOCUSMANAGER_CID },
   { CSPSERVICE_CONTRACTID, &kCSPSERVICE_CID },
+  { NS_XSSUTILS_CONTRACTID, &kNS_XSSUTILS_CID },
   { NS_EVENTLISTENERSERVICE_CONTRACTID, &kNS_EVENTLISTENERSERVICE_CID },
   { NS_GLOBALMESSAGEMANAGER_CONTRACTID, &kNS_GLOBALMESSAGEMANAGER_CID },
   { NS_PARENTPROCESSMESSAGEMANAGER_CONTRACTID, &kNS_PARENTPROCESSMESSAGEMANAGER_CID },
   { NS_CHILDPROCESSMESSAGEMANAGER_CONTRACTID, &kNS_CHILDPROCESSMESSAGEMANAGER_CID },
   { NSCHANNELPOLICY_CONTRACTID, &kNSCHANNELPOLICY_CID },
   { NS_SCRIPTSECURITYMANAGER_CONTRACTID, &kNS_SCRIPTSECURITYMANAGER_CID },
   { NS_GLOBAL_CHANNELEVENTSINK_CONTRACTID, &kNS_SCRIPTSECURITYMANAGER_CID },
   { NS_PRINCIPAL_CONTRACTID, &kNS_PRINCIPAL_CID },
diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -89,16 +89,17 @@
 #include "nsJSEnvironment.h"
 #include "nsContentSink.h"
 #include "nsFrameMessageManager.h"
 #include "nsRefreshDriver.h"
 #include "nsDOMMutationObserver.h"
 #include "nsHyphenationManager.h"
 #include "nsEditorSpellCheck.h"
 #include "nsWindowMemoryReporter.h"
+#include "nsXSSFilter.h"
 
 extern void NS_ShutdownChainItemPool();
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsrefcnt nsLayoutStatics::sLayoutStaticRefcnt = 0;
 
@@ -228,16 +229,18 @@ nsLayoutStatics::Initialize()
 #endif
 
   nsContentSink::InitializeStatics();
   nsHtml5Module::InitializeStatics();
   nsLayoutUtils::Initialize();
   nsIPresShell::InitializeStatics();
   nsRefreshDriver::InitializeStatics();
 
+  nsXSSFilter::InitializeStatics();
+
   nsCORSListenerProxy::Startup();
 
   nsFrameList::Init();
 
   NS_SealStaticAtomTable();
 
   nsWindowMemoryReporter::Init();
 
diff --git a/mobile/android/chrome/content/netError.xhtml b/mobile/android/chrome/content/netError.xhtml
--- a/mobile/android/chrome/content/netError.xhtml
+++ b/mobile/android/chrome/content/netError.xhtml
@@ -161,16 +161,22 @@
         }
 
         if (err == "cspFrameAncestorBlocked") {
           // Remove the "Try again" button for CSP frame ancestors violation, since it's
           // almost certainly useless. (Bug 553180)
           document.getElementById("errorTryAgain").style.display = "none";
         }
 
+        if (err == "xssBlockMode") {
+          // Remove the "Try again" button for XSS violations. We might instead consider
+          // putting a "Load Anyway" button.
+          document.getElementById("errorTryAgain").style.display = "none";
+        }
+
         if (err == "nssBadCert") {
           // Remove the "Try again" button for security exceptions, since it's
           // almost certainly useless.
           document.getElementById("errorTryAgain").style.display = "none";
           document.getElementById("errorPage").setAttribute("class", "certerror");
           addDomainErrorLink();
         }
         else {
@@ -291,16 +297,17 @@
         <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.title;</h1>
+        <h1 id="et_xssBlockMode">&xssBlockMode.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc2;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
         <div id="ed_malformedURI">&malformedURI.longDesc;</div>
@@ -316,16 +323,17 @@
         <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc2;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.longDesc;</div>
+        <div id="ed_xssBlockMode">&xssBlockMode.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
       </div>
     </div>
 
     <!-- Error Title -->
     <div id="errorTitle">
       <h1 class="errorTitleText" />
diff --git a/mobile/locales/en-US/overrides/netError.dtd b/mobile/locales/en-US/overrides/netError.dtd
--- a/mobile/locales/en-US/overrides/netError.dtd
+++ b/mobile/locales/en-US/overrides/netError.dtd
@@ -147,16 +147,19 @@ be temporary, and you can try again late
   <li>The site could be temporarily unavailable or too busy. Try again in a few moments.</li>
   <li>If you are unable to load any pages, check your mobile device's data or Wi-Fi connection.</li>
 </ul>
 ">
 
 <!ENTITY cspFrameAncestorBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspFrameAncestorBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
 
+<!ENTITY xssBlockMode.title "Blocked by XSS Filter">
+<!ENTITY xssBlockMode.longDesc "<p>&brandShortName; blocked further actions on this page because the page contains injected JavaScript code.</p>">
+
 <!ENTITY corruptedContentError.title "Corrupted Content Error">
 <!ENTITY corruptedContentError.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
 
 <!ENTITY securityOverride.linkText "Or you can add an exception…">
 <!ENTITY securityOverride.getMeOutOfHereButton "Get me out of here!">
 <!ENTITY securityOverride.exceptionButtonLabel "Add Exception…">
 
 <!-- LOCALIZATION NOTE (securityOverride.warningContent) - Do not translate the
diff --git a/mobile/xul/chrome/content/netError.xhtml b/mobile/xul/chrome/content/netError.xhtml
--- a/mobile/xul/chrome/content/netError.xhtml
+++ b/mobile/xul/chrome/content/netError.xhtml
@@ -161,16 +161,22 @@
         }
 
         if (err == "cspFrameAncestorBlocked") {
           // Remove the "Try again" button for CSP frame ancestors violation, since it's
           // almost certainly useless. (Bug 553180)
           document.getElementById("errorTryAgain").style.display = "none";
         }
 
+        if (err == "xssBlockMode") {
+          // Remove the "Try again" button for XSS violations. We might instead consider
+          // putting a "Load Anyway" button.
+          document.getElementById("errorTryAgain").style.display = "none";
+        }
+
         if (err == "nssBadCert") {
           // Remove the "Try again" button for security exceptions, since it's
           // almost certainly useless.
           document.getElementById("errorTryAgain").style.display = "none";
           document.getElementById("errorPage").setAttribute("class", "certerror");
           addDomainErrorLink();
         }
         else {
@@ -291,16 +297,17 @@
         <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.title;</h1>
+        <h1 id="et_xssBlockMode">&xssBlockMode.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc2;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
         <div id="ed_malformedURI">&malformedURI.longDesc;</div>
@@ -316,16 +323,17 @@
         <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc2;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_cspFrameAncestorBlocked">&cspFrameAncestorBlocked.longDesc;</div>
+        <div id="ed_xssBlockMode">&xssBlockMode.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
       </div>
     </div>
 
     <!-- Error Title -->
     <div id="errorTitle">
       <h1 class="errorTitleText" />
diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -1316,16 +1316,21 @@ pref("signed.applets.codebase_principal_
 pref("security.checkloaduri", true);
 pref("security.xpconnect.plugin.unrestricted", true);
 // security-sensitive dialogs should delay button enabling. In milliseconds.
 pref("security.dialog_enable_delay", 2000);
 
 pref("security.csp.enable", true);
 pref("security.csp.debug", false);
 
+pref("security.xssfilter.enable", true);
+pref("security.xssfilter.reportOnly", false);
+pref("security.xssfilter.blockMode", false);
+pref("security.xssfilter.displayWarning", true);
+
 // Modifier key prefs: default to Windows settings,
 // menu access key = alt, accelerator key = control.
 // Use 17 for Ctrl, 18 for Alt, 224 for Meta, 91 for Win, 0 for none. Mac settings in macprefs.js
 pref("ui.key.accelKey", 17);
 pref("ui.key.menuAccessKey", 18);
 pref("ui.key.generalAccessKey", -1);
 
 // If generalAccessKey is -1, use the following two prefs instead.
diff --git a/netwerk/protocol/data/nsDataChannel.cpp b/netwerk/protocol/data/nsDataChannel.cpp
--- a/netwerk/protocol/data/nsDataChannel.cpp
+++ b/netwerk/protocol/data/nsDataChannel.cpp
@@ -12,25 +12,54 @@
 #include "nsIPipe.h"
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
 #include "nsReadableUtils.h"
 #include "nsEscape.h"
 #include "plbase64.h"
 #include "plstr.h"
 #include "prmem.h"
+#include "nsXSSFilter.h"
+#include "nsIPrincipal.h"
+#include "prlog.h"
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gXssPRLog;
+#endif
 
 nsresult
 nsDataChannel::OpenContentStream(bool async, nsIInputStream **result,
                                  nsIChannel** channel)
 {
     NS_ENSURE_TRUE(URI(), NS_ERROR_NOT_INITIALIZED);
 
+#ifdef PR_LOGGING
+    if (!gXssPRLog)
+        gXssPRLog = PR_NewLogModule("XSS");
+#endif
+
     nsresult rv;
 
+    nsCOMPtr<nsIPrincipal> principal;
+    GetOwner(getter_AddRefs(principal));
+    if (principal) {
+        //PR_LOG(gXssPRLog, PR_LOG_DEBUG, ("data url: Got principal"));
+        nsRefPtr<nsXSSFilter> xss;
+        rv = principal->GetXSSFilter(getter_AddRefs(xss));
+        NS_ENSURE_SUCCESS(rv, rv);
+        if (xss) {
+            // TODO: some of these might not be scripts! do we want to
+            // whitelist static content such as images?
+            if (!xss->PermitsDataURL(URI())) {
+                PR_LOG(gXssPRLog, PR_LOG_DEBUG, ("XSS in data URL"));
+                return NS_ERROR_FAILURE;
+            }
+        }
+    }
+
     nsCAutoString spec;
     rv = URI()->GetAsciiSpec(spec);
     if (NS_FAILED(rv)) return rv;
 
     nsCString contentType, contentCharset, dataBuffer, hashRef;
     bool lBase64;
     rv = nsDataHandler::ParseURI(spec, contentType, contentCharset,
                                  lBase64, dataBuffer, hashRef);