diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp index e8770cf742..10330bf945 100644 --- a/caps/BasePrincipal.cpp +++ b/caps/BasePrincipal.cpp @@ -6,6 +6,7 @@ #include "mozilla/BasePrincipal.h" +#include "nsIContentSecurityPolicy.h" #include "nsIObjectInputStream.h" #include "nsIObjectOutputStream.h" @@ -14,6 +15,7 @@ #include "nsNullPrincipal.h" #include "nsScriptSecurityManager.h" +#include "mozilla/dom/CSPDictionariesBinding.h" #include "mozilla/dom/ToJSValue.h" namespace mozilla { @@ -117,6 +119,19 @@ BasePrincipal::SetCsp(nsIContentSecurityPolicy* aCsp) return NS_OK; } +NS_IMETHODIMP +BasePrincipal::GetCspJSON(nsAString& outCSPinJSON) +{ + outCSPinJSON.Truncate(); + dom::CSPPolicies jsonPolicies; + + if (!mCSP) { + jsonPolicies.ToJSON(outCSPinJSON); + return NS_OK; + } + return mCSP->ToJSON(outCSPinJSON); +} + NS_IMETHODIMP BasePrincipal::GetIsNullPrincipal(bool* aIsNullPrincipal) { @@ -149,6 +164,15 @@ BasePrincipal::GetOriginSuffix(nsACString& aOriginAttributes) return NS_OK; } +NS_IMETHODIMP +BasePrincipal::GetCookieJar(nsACString& aCookieJar) +{ + // We just forward to .jarPrefix for now, which is a nice compact + // stringification of the (appId, inBrowser) tuple. This will eventaully be + // swapped out for an origin attribute - see the comment in nsIPrincipal.idl. + return GetJarPrefix(aCookieJar); +} + NS_IMETHODIMP BasePrincipal::GetAppStatus(uint16_t* aAppStatus) { diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h index ff9827d30c..e21c41271d 100644 --- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -69,10 +69,12 @@ public: NS_IMETHOD SubsumesConsideringDomain(nsIPrincipal* other, bool* _retval) final; NS_IMETHOD GetCsp(nsIContentSecurityPolicy** aCsp) override; NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp) override; + NS_IMETHOD GetCspJSON(nsAString& outCSPinJSON) override; NS_IMETHOD GetIsNullPrincipal(bool* aIsNullPrincipal) override; NS_IMETHOD GetJarPrefix(nsACString& aJarPrefix) final; NS_IMETHOD GetOriginAttributes(JSContext* aCx, JS::MutableHandle aVal) final; NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final; + NS_IMETHOD GetCookieJar(nsACString& aCookieJar) final; NS_IMETHOD GetAppStatus(uint16_t* aAppStatus) final; NS_IMETHOD GetAppId(uint32_t* aAppStatus) final; NS_IMETHOD GetIsInBrowserElement(bool* aIsInBrowserElement) final; diff --git a/caps/moz.build b/caps/moz.build index 978390671e..c50409655d 100644 --- a/caps/moz.build +++ b/caps/moz.build @@ -6,6 +6,8 @@ TEST_DIRS += ['tests/mochitest'] +XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini'] + XPIDL_SOURCES += [ 'nsIDomainPolicy.idl', 'nsIPrincipal.idl', diff --git a/caps/nsIPrincipal.idl b/caps/nsIPrincipal.idl index 3e76d5706f..c31ce72f0e 100644 --- a/caps/nsIPrincipal.idl +++ b/caps/nsIPrincipal.idl @@ -20,7 +20,7 @@ interface nsIContentSecurityPolicy; [ptr] native JSPrincipals(JSPrincipals); [ptr] native PrincipalArray(nsTArray >); -[scriptable, builtinclass, uuid(74fb6760-4ae7-4ec7-8ac7-06817c60a93a)] +[scriptable, builtinclass, uuid(49c2faf0-b6de-4640-8d0f-e0217baa8627)] interface nsIPrincipal : nsISerializable { /** @@ -142,6 +142,13 @@ interface nsIPrincipal : nsISerializable */ [noscript] attribute nsIContentSecurityPolicy csp; + /** + * The CSP of the principal in JSON notation. + * Note, that the CSP itself is not exposed to JS, but script + * should be able to obtain a JSON representation of the CSP. + */ + readonly attribute AString cspJSON; + /** * Returns the jar prefix of the principal. * The jar prefix is a string that can be used to isolate data or @@ -188,6 +195,30 @@ interface nsIPrincipal : nsISerializable */ readonly attribute AUTF8String originSuffix; + /** + * Opaque string token representing the "cookie jar" associated with this + * principal. Cookie jars are intended to be a tag associated with persistent + * data (like cookies, localStorage data, etc) such that all data associated + * with a given cookie jar can be quickly located and (for example) deleted. + * Code from many origins may share a given cookie jar, so callers still need + * to consult .origin (or equivalent) to compartmentalize data - the cookie + * jar should _only_ be used as a tag in the manner described above. + * + * If two principals are in different cookie jars, they must be cross-origin. + * As such, the information making up the cookie jar token must be contained + * in the originAttributes (i.e. cookieJar must be a function of / derivable + * from originAttributes). Long term, the intention is for the cookie jar + * identifier to simply be an origin attribute. But we don't have that + * attribute yet, and we also need to concatenate the appId and inBrowser + * attributes until those go away. + * + * This getter is designed to hide these details from consumers so that they + * don't need to be updated when we swap out the implementation. For that + * reason, callers should treat the string as opaque and not rely on the + * current format. + */ + readonly attribute ACString cookieJar; + /** * The base domain of the codebase URI to which this principal pertains * (generally the document URI), handling null principals and diff --git a/caps/nsPrincipal.cpp b/caps/nsPrincipal.cpp index 0e15e8dc95..ff9b35680e 100644 --- a/caps/nsPrincipal.cpp +++ b/caps/nsPrincipal.cpp @@ -604,9 +604,39 @@ NS_IMPL_CI_INTERFACE_GETTER(nsExpandedPrincipal, nsIPrincipal, nsIExpandedPrincipal) +struct OriginComparator +{ + bool LessThan(nsIPrincipal* a, nsIPrincipal* b) const + { + nsAutoCString originA; + nsresult rv = a->GetOrigin(originA); + NS_ENSURE_SUCCESS(rv, false); + nsAutoCString originB; + rv = b->GetOrigin(originB); + NS_ENSURE_SUCCESS(rv, false); + return originA < originB; + } + + bool Equals(nsIPrincipal* a, nsIPrincipal* b) const + { + nsAutoCString originA; + nsresult rv = a->GetOrigin(originA); + NS_ENSURE_SUCCESS(rv, false); + nsAutoCString originB; + rv = b->GetOrigin(originB); + NS_ENSURE_SUCCESS(rv, false); + return a == b; + } +}; + nsExpandedPrincipal::nsExpandedPrincipal(nsTArray > &aWhiteList) { - mPrincipals.AppendElements(aWhiteList); + // We force the principals to be sorted by origin so that nsExpandedPrincipal + // origins can have a canonical form. + OriginComparator c; + for (size_t i = 0; i < aWhiteList.Length(); ++i) { + mPrincipals.InsertElementSorted(aWhiteList[i], c); + } } nsExpandedPrincipal::~nsExpandedPrincipal() diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp index d2d4fe9edf..8e53183017 100644 --- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -280,29 +280,21 @@ nsScriptSecurityManager::AppStatusForPrincipal(nsIPrincipal *aPrin) NS_ENSURE_SUCCESS(app->GetAppStatus(&status), nsIPrincipal::APP_STATUS_NOT_INSTALLED); - nsAutoCString origin; - NS_ENSURE_SUCCESS(aPrin->GetOrigin(origin), - nsIPrincipal::APP_STATUS_NOT_INSTALLED); nsString appOrigin; NS_ENSURE_SUCCESS(app->GetOrigin(appOrigin), nsIPrincipal::APP_STATUS_NOT_INSTALLED); - - // We go from string -> nsIURI -> origin to be sure we - // compare two punny-encoded origins. nsCOMPtr appURI; NS_ENSURE_SUCCESS(NS_NewURI(getter_AddRefs(appURI), appOrigin), nsIPrincipal::APP_STATUS_NOT_INSTALLED); - nsAutoCString appOriginPunned; - NS_ENSURE_SUCCESS(nsPrincipal::GetOriginForURI(appURI, appOriginPunned), - nsIPrincipal::APP_STATUS_NOT_INSTALLED); - - if (!appOriginPunned.Equals(origin)) { - return nsIPrincipal::APP_STATUS_NOT_INSTALLED; - } - - return status; - + // The app could contain a cross-origin iframe - make sure that the content + // is actually same-origin with the app. + MOZ_ASSERT(inMozBrowser == false, "Checked this above"); + OriginAttributes attrs(appId, false); + nsCOMPtr appPrin = BasePrincipal::CreateCodebasePrincipal(appURI, attrs); + NS_ENSURE_TRUE(appPrin, nsIPrincipal::APP_STATUS_NOT_INSTALLED); + return aPrin->Equals(appPrin) ? status + : nsIPrincipal::APP_STATUS_NOT_INSTALLED; } NS_IMETHODIMP diff --git a/caps/tests/unit/test_origin.js b/caps/tests/unit/test_origin.js new file mode 100644 index 0000000000..33fed1395a --- /dev/null +++ b/caps/tests/unit/test_origin.js @@ -0,0 +1,101 @@ +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/BrowserUtils.jsm"); +var ssm = Services.scriptSecurityManager; +function makeURI(uri) { return Services.io.newURI(uri, null, null); } + +function checkThrows(f) { + var threw = false; + try { f(); } catch (e) { threw = true } + do_check_true(threw); +} + +function checkCrossOrigin(a, b) { + do_check_false(a.equals(b)); + do_check_false(a.equalsConsideringDomain(b)); + do_check_false(a.subsumes(b)); + do_check_false(a.subsumesConsideringDomain(b)); + do_check_false(b.subsumes(a)); + do_check_false(b.subsumesConsideringDomain(a)); + do_check_eq(a.cookieJar === b.cookieJar, + a.originAttributes.appId == b.originAttributes.appId && + a.originAttributes.inBrowser == b.originAttributes.inBrowser); +} + +function checkOriginAttributes(prin, appId, inBrowser, suffix) { + do_check_eq(prin.originAttributes.appId, appId || 0); + do_check_eq(prin.originAttributes.inBrowser, inBrowser || false); + do_check_eq(prin.originSuffix, suffix || ''); + if (!prin.isNullPrincipal && !prin.origin.startsWith('[')) { + do_check_true(BrowserUtils.principalFromOrigin(prin.origin).equals(prin)); + } else { + checkThrows(() => BrowserUtils.principalFromOrigin(prin.origin)); + } +} + +function run_test() { + // Attributeless origins. + do_check_eq(ssm.getSystemPrincipal().origin, '[System Principal]'); + checkOriginAttributes(ssm.getSystemPrincipal()); + var exampleOrg = ssm.createCodebasePrincipal(makeURI('http://example.org'), {}); + do_check_eq(exampleOrg.origin, 'http://example.org'); + checkOriginAttributes(exampleOrg); + var exampleCom = ssm.createCodebasePrincipal(makeURI('https://www.example.com:123'), {}); + do_check_eq(exampleCom.origin, 'https://www.example.com:123'); + checkOriginAttributes(exampleCom); + var nullPrin = Cu.getObjectPrincipal(new Cu.Sandbox(null)); + do_check_true(/^moz-nullprincipal:\{([0-9]|[a-z]|\-){36}\}$/.test(nullPrin.origin)); + checkOriginAttributes(nullPrin); + var ep = Cu.getObjectPrincipal(new Cu.Sandbox([exampleCom, nullPrin, exampleOrg])); + checkOriginAttributes(ep); + checkCrossOrigin(exampleCom, exampleOrg); + checkCrossOrigin(exampleOrg, nullPrin); + + // nsEP origins should be in lexical order. + do_check_eq(ep.origin, `[Expanded Principal [${exampleOrg.origin}, ${exampleCom.origin}, ${nullPrin.origin}]]`); + + // Make sure createCodebasePrincipal does what the rest of gecko does. + do_check_true(exampleOrg.equals(Cu.getObjectPrincipal(new Cu.Sandbox('http://example.org')))); + + // + // Test origin attributes. + // + + // Just app. + var exampleOrg_app = ssm.createCodebasePrincipal(makeURI('http://example.org'), {appId: 42}); + var nullPrin_app = ssm.createNullPrincipal({appId: 42}); + checkOriginAttributes(exampleOrg_app, 42, false, '!appId=42'); + checkOriginAttributes(nullPrin_app, 42, false, '!appId=42'); + do_check_eq(exampleOrg_app.origin, 'http://example.org!appId=42'); + + // Just browser. + var exampleOrg_browser = ssm.createCodebasePrincipal(makeURI('http://example.org'), {inBrowser: true}); + var nullPrin_browser = ssm.createNullPrincipal({inBrowser: true}); + checkOriginAttributes(exampleOrg_browser, 0, true, '!inBrowser=1'); + checkOriginAttributes(nullPrin_browser, 0, true, '!inBrowser=1'); + do_check_eq(exampleOrg_browser.origin, 'http://example.org!inBrowser=1'); + + // App and browser. + var exampleOrg_appBrowser = ssm.createCodebasePrincipal(makeURI('http://example.org'), {inBrowser: true, appId: 42}); + var nullPrin_appBrowser = ssm.createNullPrincipal({inBrowser: true, appId: 42}); + checkOriginAttributes(exampleOrg_appBrowser, 42, true, '!appId=42&inBrowser=1'); + checkOriginAttributes(nullPrin_appBrowser, 42, true, '!appId=42&inBrowser=1'); + do_check_eq(exampleOrg_appBrowser.origin, 'http://example.org!appId=42&inBrowser=1'); + + // App and browser, different domain. + var exampleCom_appBrowser = ssm.createCodebasePrincipal(makeURI('https://www.example.com:123'), {appId: 42, inBrowser: true}); + checkOriginAttributes(exampleCom_appBrowser, 42, true, '!appId=42&inBrowser=1'); + do_check_eq(exampleCom_appBrowser.origin, 'https://www.example.com:123!appId=42&inBrowser=1'); + + // Check that all of the above are cross-origin. + checkCrossOrigin(exampleOrg_app, exampleOrg); + checkCrossOrigin(exampleOrg_app, nullPrin_app); + checkCrossOrigin(exampleOrg_browser, exampleOrg_app); + checkCrossOrigin(exampleOrg_browser, nullPrin_browser); + checkCrossOrigin(exampleOrg_appBrowser, exampleOrg_app); + checkCrossOrigin(exampleOrg_appBrowser, nullPrin_appBrowser); + checkCrossOrigin(exampleOrg_appBrowser, exampleCom_appBrowser); +} diff --git a/caps/tests/unit/xpcshell.ini b/caps/tests/unit/xpcshell.ini new file mode 100644 index 0000000000..c06a6b4e92 --- /dev/null +++ b/caps/tests/unit/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head = +tail = + +[test_origin.js] diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index c071db50fd..7bc9f5adcf 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -316,6 +316,10 @@ DOMInterfaces = { 'nativeType': 'nsDOMCSSDeclaration' }, +'CSSLexer': { + 'wrapperCache': False +}, + 'CSSPrimitiveValue': { 'nativeType': 'nsROCSSPrimitiveValue', }, diff --git a/dom/fetch/Response.h b/dom/fetch/Response.h index 2e83522d6b..87dc9b5b4c 100644 --- a/dom/fetch/Response.h +++ b/dom/fetch/Response.h @@ -78,6 +78,12 @@ public: return mInternalResponse->Headers(); } + const nsCString& + GetSecurityInfo() const + { + return mInternalResponse->GetSecurityInfo(); + } + Headers* Headers_(); void diff --git a/dom/interfaces/security/nsIContentSecurityPolicy.idl b/dom/interfaces/security/nsIContentSecurityPolicy.idl index 17db150de6..a42290d447 100644 --- a/dom/interfaces/security/nsIContentSecurityPolicy.idl +++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl @@ -20,7 +20,7 @@ interface nsIURI; typedef unsigned short CSPDirective; -[scriptable, uuid(1bf8394b-2bdd-47ab-9b75-b75a5a37b3e1)] +[scriptable, uuid(459fe61a-203e-4460-9ced-352a9bd3aa71)] interface nsIContentSecurityPolicy : nsISerializable { /** @@ -283,4 +283,9 @@ interface nsIContentSecurityPolicy : nsISerializable #define CSP_VIOLATION_TOPIC "csp-on-violate-policy" %} + /** + * Returns the CSP in JSON notation. + */ + AString toJSON(); + }; diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp index bd2d0d6906..b1cda70b81 100644 --- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -37,6 +37,7 @@ #include "nsString.h" #include "mozilla/Logging.h" #include "mozilla/dom/CSPReportBinding.h" +#include "mozilla/dom/CSPDictionariesBinding.h" #include "mozilla/net/ReferrerPolicy.h" #include "nsINetworkInterceptController.h" @@ -1212,6 +1213,26 @@ nsCSPContext::Permits(nsIURI* aURI, return NS_OK; } +NS_IMETHODIMP +nsCSPContext::ToJSON(nsAString& outCSPinJSON) +{ + outCSPinJSON.Truncate(); + dom::CSPPolicies jsonPolicies; + jsonPolicies.mCsp_policies.Construct(); + + for (uint32_t p = 0; p < mPolicies.Length(); p++) { + dom::CSP jsonCSP; + mPolicies[p]->toDomCSPStruct(jsonCSP); + jsonPolicies.mCsp_policies.Value().AppendElement(jsonCSP); + } + + // convert the gathered information to JSON + if (!jsonPolicies.ToJSON(outCSPinJSON)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + /* ========== CSPViolationReportListener implementation ========== */ NS_IMPL_ISUPPORTS(CSPViolationReportListener, nsIStreamListener, nsIRequestObserver, nsISupports); diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp index fb838b8cc5..56c6c11c1b 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -804,6 +804,93 @@ nsCSPDirective::toString(nsAString& outStr) const } } +void +nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const +{ + mozilla::dom::Sequence srcs; + nsString src; + for (uint32_t i = 0; i < mSrcs.Length(); i++) { + src.Truncate(); + mSrcs[i]->toString(src); + srcs.AppendElement(src); + } + + switch(mDirective) { + case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE: + outCSP.mDefault_src.Construct(); + outCSP.mDefault_src.Value() = srcs; + return; + + case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE: + outCSP.mScript_src.Construct(); + outCSP.mScript_src.Value() = srcs; + return; + + case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE: + outCSP.mObject_src.Construct(); + outCSP.mObject_src.Value() = srcs; + return; + + case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE: + outCSP.mStyle_src.Construct(); + outCSP.mStyle_src.Value() = srcs; + return; + + case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE: + outCSP.mImg_src.Construct(); + outCSP.mImg_src.Value() = srcs; + return; + + case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE: + outCSP.mMedia_src.Construct(); + outCSP.mMedia_src.Value() = srcs; + return; + + case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE: + outCSP.mFrame_src.Construct(); + outCSP.mFrame_src.Value() = srcs; + return; + + case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE: + outCSP.mFont_src.Construct(); + outCSP.mFont_src.Value() = srcs; + return; + + case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE: + outCSP.mConnect_src.Construct(); + outCSP.mConnect_src.Value() = srcs; + return; + + case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE: + outCSP.mReport_uri.Construct(); + outCSP.mReport_uri.Value() = srcs; + return; + + case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE: + outCSP.mFrame_ancestors.Construct(); + outCSP.mFrame_ancestors.Value() = srcs; + return; + + // not supporting REFLECTED_XSS_DIRECTIVE + + case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE: + outCSP.mBase_uri.Construct(); + outCSP.mBase_uri.Value() = srcs; + return; + + case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE: + outCSP.mForm_action.Construct(); + outCSP.mForm_action.Value() = srcs; + return; + + // REFERRER_DIRECTIVE is handled in nsCSPPolicy::toDomCSPStruct() + + default: + NS_ASSERTION(false, "cannot find directive to convert CSP to JSON"); + } +} + + bool nsCSPDirective::restrictsContentType(nsContentPolicyType aContentType) const { @@ -973,6 +1060,23 @@ nsCSPPolicy::toString(nsAString& outStr) const } } +void +nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP& outCSP) const +{ + outCSP.mReport_only = mReportOnly; + + for (uint32_t i = 0; i < mDirectives.Length(); ++i) { + if (mDirectives[i]->equals(nsIContentSecurityPolicy::REFERRER_DIRECTIVE)) { + mozilla::dom::Sequence srcs; + srcs.AppendElement(mReferrerPolicy); + outCSP.mReferrer.Construct(); + outCSP.mReferrer.Value() = srcs; + } else { + mDirectives[i]->toDomCSPStruct(outCSP); + } + } +} + bool nsCSPPolicy::hasDirective(CSPDirective aDir) const { diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h index 9a66765e0d..125de316eb 100644 --- a/dom/security/nsCSPUtils.h +++ b/dom/security/nsCSPUtils.h @@ -16,6 +16,12 @@ #include "nsUnicharUtils.h" #include "mozilla/Logging.h" +namespace mozilla { +namespace dom { + struct CSP; +} +} + /* =============== Logging =================== */ void CSP_LogLocalizedStr(const char16_t* aName, @@ -58,7 +64,8 @@ void CSP_LogMessage(const nsAString& aMessage, // these strings map to the CSPDirectives in nsIContentSecurityPolicy // NOTE: When implementing a new directive, you will need to add it here but also -// add a corresponding entry to the constants in nsIContentSecurityPolicy.idl +// add a corresponding entry to the constants in nsIContentSecurityPolicy.idl and +// also create an entry for the new directive in nsCSPDirective::toDomCSPStruct(). static const char* CSPStrDirectives[] = { "-error-", // NO_DIRECTIVE "default-src", // DEFAULT_SRC_DIRECTIVE @@ -282,6 +289,7 @@ class nsCSPDirective { bool permits(nsIURI* aUri) const; bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; void toString(nsAString& outStr) const; + void toDomCSPStruct(mozilla::dom::CSP& outCSP) const; inline void addSrcs(const nsTArray& aSrcs) { mSrcs = aSrcs; } @@ -323,6 +331,7 @@ class nsCSPPolicy { bool allows(nsContentPolicyType aContentType, enum CSPKeyword aKeyword) const; void toString(nsAString& outStr) const; + void toDomCSPStruct(mozilla::dom::CSP& outCSP) const; inline void addDirective(nsCSPDirective* aDir) { mDirectives.AppendElement(aDir); } diff --git a/dom/webidl/CSPDictionaries.webidl b/dom/webidl/CSPDictionaries.webidl new file mode 100644 index 0000000000..85fabf4735 --- /dev/null +++ b/dom/webidl/CSPDictionaries.webidl @@ -0,0 +1,31 @@ +/* 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/. */ + +/** + * Dictionary used to display CSP info. + */ + +dictionary CSP { + boolean report-only = false; + + sequence default-src; + sequence script-src; + sequence object-src; + sequence style-src; + sequence img-src; + sequence media-src; + sequence frame-src; + sequence font-src; + sequence connect-src; + sequence report-uri; + sequence frame-ancestors; + // sequence reflected-xss; // not suppored in Firefox + sequence base-uri; + sequence form-action; + sequence referrer; +}; + +dictionary CSPPolicies { + sequence csp-policies; +}; diff --git a/dom/webidl/CSSLexer.webidl b/dom/webidl/CSSLexer.webidl new file mode 100644 index 0000000000..449fc5b373 --- /dev/null +++ b/dom/webidl/CSSLexer.webidl @@ -0,0 +1,132 @@ +/* -*- 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/. */ + +// The possible values for CSSToken.tokenType. +enum CSSTokenType { + // Whitespace. + "whitespace", + // A CSS comment. + "comment", + // An identifier. |text| holds the identifier text. + "ident", + // A function token. |text| holds the function name. Note that the + // function token includes (i.e., consumes) the "(" -- but this is + // not included in |text|. + "function", + // "@word". |text| holds "word", without the "@". + "at", + // "#word". |text| holds "word", without the "#". + "id", + // "#word". ID is used when "word" would have been a valid IDENT + // token without the "#"; otherwise, HASH is used. + "hash", + // A number. + "number", + // A dimensioned number. + "dimension", + // A percentage. + "percentage", + // A string. + "string", + // A "bad string". This can only be returned when a string is + // unterminated at EOF. (However, currently the lexer returns + // ordinary STRING tokens in this situation.) + "bad_string", + // A URL. |text| holds the URL. + "url", + // A "bad URL". This is a URL that is unterminated at EOF. |text| + // holds the URL. + "bad_url", + // A "symbol" is any one-character symbol. This corresponds to the + // DELIM token in the CSS specification. + "symbol", + // The "~=" token. + "includes", + // The "|=" token. + "dashmatch", + // The "^=" token. + "beginsmatch", + // The "$=" token. + "endsmatch", + // The "*=" token. + "containsmatch", + // A unicode-range token. This is currently not fully represented + // by CSSToken. + "urange", + // HTML comment delimiters, either "". Note that each + // is emitted as a separate token, and the intervening text is lexed + // as normal; whereas ordinary CSS comments are lexed as a unit. + "htmlcomment" +}; + +dictionary CSSToken { + // The token type. + CSSTokenType tokenType = "whitespace"; + + // Offset of the first character of the token. + unsigned long startOffset = 0; + // Offset of the character after the final character of the token. + // This is chosen so that the offsets can be passed to |substring| + // to yield the exact contents of the token. + unsigned long endOffset = 0; + + // If the token is a number, percentage, or dimension, this holds + // the value. This is not present for other token types. + double number; + // If the token is a number, percentage, or dimension, this is true + // iff the number had an explicit sign. This is not present for + // other token types. + boolean hasSign; + // If the token is a number, percentage, or dimension, this is true + // iff the number was specified as an integer. This is not present + // for other token types. + boolean isInteger; + + // Text associated with the token. This is not present for all + // token types. In particular it is: + // + // Token type Meaning + // =============================== + // ident The identifier. + // function The function name. Note that the "(" is part + // of the token but is not present in |text|. + // at The word. + // id The word. + // hash The word. + // dimension The dimension. + // string The string contents after escape processing. + // bad_string Ditto. + // url The URL after escape processing. + // bad_url Ditto. + // symbol The symbol text. + DOMString text; +}; + +/** + * CSSLexer is an interface to the CSS lexer. It tokenizes an + * input stream and returns CSS tokens. + * + * @see inIDOMUtils.getCSSLexer to create an instance of the lexer. + */ +[ChromeOnly] +interface CSSLexer +{ + /** + * The line number of the most recently returned token. Line + * numbers are 0-based. + */ + readonly attribute unsigned long lineNumber; + + /** + * The column number of the most recently returned token. Column + * numbers are 0-based. + */ + readonly attribute unsigned long columnNumber; + + /** + * Return the next token, or null at EOF. + */ + CSSToken? nextToken(); +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 6998c324c9..dfc18d2a2c 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -88,8 +88,10 @@ WEBIDL_FILES = [ 'ConvolverNode.webidl', 'Coordinates.webidl', 'Crypto.webidl', + 'CSPDictionaries.webidl', 'CSPReport.webidl', 'CSS.webidl', + 'CSSLexer.webidl', 'CSSPrimitiveValue.webidl', 'CSSRuleList.webidl', 'CSSStyleDeclaration.webidl', diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 7d4fbe51d1..7d9e37d48e 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -32,6 +32,7 @@ #include "nsIXPConnect.h" #include "nsPerformance.h" #include "nsPIDOMWindow.h" +#include "nsSerializationHelper.h" #include #include "jsfriendapi.h" @@ -4037,6 +4038,17 @@ WorkerPrivateParent::SetPrincipal(nsIPrincipal* aPrincipal, PrincipalToPrincipalInfo(aPrincipal, mLoadInfo.mPrincipalInfo))); } +template +void +WorkerPrivateParent::SetSecurityInfo(nsISerializable* aSerializable) +{ + MOZ_ASSERT(IsServiceWorker()); + AssertIsOnMainThread(); + nsAutoCString securityInfo; + NS_SerializeToString(aSerializable, securityInfo); + SetSecurityInfo(securityInfo); +} + template JSContext* WorkerPrivateParent::ParentJSContext() const diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index 2cadd73b9e..3ad6313d84 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -37,6 +37,7 @@ class nsIDocument; class nsIEventTarget; class nsIPrincipal; class nsIScriptContext; +class nsISerializable; class nsIThread; class nsIThreadInternal; class nsITimer; @@ -488,6 +489,25 @@ public: return mLoadInfo.mServiceWorkerCacheName; } + const nsCString& + GetSecurityInfo() const + { + MOZ_ASSERT(IsServiceWorker()); + return mLoadInfo.mSecurityInfo; + } + + void + SetSecurityInfo(const nsCString& aSecurityInfo) + { + MOZ_ASSERT(IsServiceWorker()); + AssertIsOnMainThread(); + MOZ_ASSERT(mLoadInfo.mSecurityInfo.IsEmpty()); + mLoadInfo.mSecurityInfo = aSecurityInfo; + } + + void + SetSecurityInfo(nsISerializable* aSerializable); + // This is used to handle importScripts(). When the worker is first loaded // and executed, it happens in a sync loop. At this point it sets // mLoadingWorkerScript to true. importScripts() calls that occur during the diff --git a/dom/workers/Workers.h b/dom/workers/Workers.h index 00a6308020..b91b440b82 100644 --- a/dom/workers/Workers.h +++ b/dom/workers/Workers.h @@ -244,6 +244,8 @@ struct WorkerLoadInfo nsString mServiceWorkerCacheName; + nsCString mSecurityInfo; + uint64_t mWindowID; bool mFromWindow; diff --git a/layout/inspector/inDOMUtils.cpp b/layout/inspector/inDOMUtils.cpp index b129293793..3ff5551152 100644 --- a/layout/inspector/inDOMUtils.cpp +++ b/layout/inspector/inDOMUtils.cpp @@ -37,6 +37,7 @@ #include "nsRuleWalker.h" #include "nsRuleProcessorData.h" #include "nsCSSRuleProcessor.h" +#include "mozilla/dom/CSSLexer.h" #include "mozilla/dom/InspectorUtilsBinding.h" #include "mozilla/dom/ToJSValue.h" #include "nsCSSParser.h" @@ -289,6 +290,19 @@ inDOMUtils::GetRuleColumn(nsIDOMCSSRule* aRule, uint32_t* _retval) return NS_OK; } +NS_IMETHODIMP +inDOMUtils::GetCSSLexer(const nsAString& aText, JSContext* aCx, + JS::MutableHandleValue aResult) +{ + MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx)); + JS::Rooted scope(aCx, JS::CurrentGlobalOrNull(aCx)); + nsAutoPtr lexer(new CSSLexer(aText)); + if (!WrapNewBindingNonWrapperCachedObject(aCx, scope, lexer, aResult)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + NS_IMETHODIMP inDOMUtils::GetSelectorCount(nsIDOMCSSStyleRule* aRule, uint32_t *aCount) { diff --git a/layout/inspector/inIDOMUtils.idl b/layout/inspector/inIDOMUtils.idl index 68df23d51d..80cd51316a 100644 --- a/layout/inspector/inIDOMUtils.idl +++ b/layout/inspector/inIDOMUtils.idl @@ -17,7 +17,7 @@ interface nsIDOMFontFaceList; interface nsIDOMRange; interface nsIDOMCSSStyleSheet; -[scriptable, uuid(1f5b7f08-fa80-49e9-b881-888f081240da)] +[scriptable, uuid(60b4cbf7-2a08-4419-8937-6ef495417824)] interface inIDOMUtils : nsISupports { // CSS utilities @@ -28,6 +28,9 @@ interface inIDOMUtils : nsISupports unsigned long getRuleLine(in nsIDOMCSSRule aRule); unsigned long getRuleColumn(in nsIDOMCSSRule aRule); + [implicit_jscontext] + jsval getCSSLexer(in DOMString aText); + // Utilities for working with selectors. We don't have a JS OM representation // of a single selector or a selector list yet, but given a rule we can index // into the selector list. diff --git a/layout/style/CSSLexer.cpp b/layout/style/CSSLexer.cpp new file mode 100644 index 0000000000..7b31f60cac --- /dev/null +++ b/layout/style/CSSLexer.cpp @@ -0,0 +1,142 @@ +/* -*- 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 "mozilla/dom/CSSLexer.h" +#include "js/Value.h" +#include "mozilla/dom/CSSLexerBinding.h" +#include "mozilla/dom/ToJSValue.h" + +namespace mozilla { +namespace dom { + +// Ensure that constants are consistent. + +#define CHECK(X, Y) \ + static_assert(static_cast(X) == static_cast(Y), \ + "nsCSSToken and CSSTokenType should have identical values") + +CHECK(eCSSToken_Whitespace, CSSTokenType::Whitespace); +CHECK(eCSSToken_Comment, CSSTokenType::Comment); +CHECK(eCSSToken_Ident, CSSTokenType::Ident); +CHECK(eCSSToken_Function, CSSTokenType::Function); +CHECK(eCSSToken_AtKeyword, CSSTokenType::At); +CHECK(eCSSToken_ID, CSSTokenType::Id); +CHECK(eCSSToken_Hash, CSSTokenType::Hash); +CHECK(eCSSToken_Number, CSSTokenType::Number); +CHECK(eCSSToken_Dimension, CSSTokenType::Dimension); +CHECK(eCSSToken_Percentage, CSSTokenType::Percentage); +CHECK(eCSSToken_String, CSSTokenType::String); +CHECK(eCSSToken_Bad_String, CSSTokenType::Bad_string); +CHECK(eCSSToken_URL, CSSTokenType::Url); +CHECK(eCSSToken_Bad_URL, CSSTokenType::Bad_url); +CHECK(eCSSToken_Symbol, CSSTokenType::Symbol); +CHECK(eCSSToken_Includes, CSSTokenType::Includes); +CHECK(eCSSToken_Dashmatch, CSSTokenType::Dashmatch); +CHECK(eCSSToken_Beginsmatch, CSSTokenType::Beginsmatch); +CHECK(eCSSToken_Endsmatch, CSSTokenType::Endsmatch); +CHECK(eCSSToken_Containsmatch, CSSTokenType::Containsmatch); +CHECK(eCSSToken_URange, CSSTokenType::Urange); +CHECK(eCSSToken_HTMLComment, CSSTokenType::Htmlcomment); + +#undef CHECK + +CSSLexer::CSSLexer(const nsAString& aText) + : mInput(aText) + , mScanner(mInput, 1) +{ +} + +CSSLexer::~CSSLexer() +{ +} + +bool +CSSLexer::WrapObject(JSContext* aCx, JS::Handle aGivenProto, + JS::MutableHandle aReflector) +{ + return CSSLexerBinding::Wrap(aCx, this, aGivenProto, aReflector); +} + +uint32_t +CSSLexer::LineNumber() +{ + // The scanner uses 1-based line numbers, but our callers expect + // 0-based. + return mScanner.GetLineNumber() - 1; +} + +uint32_t +CSSLexer::ColumnNumber() +{ + return mScanner.GetColumnNumber(); +} + +void +CSSLexer::NextToken(Nullable& aResult) +{ + nsCSSToken token; + if (!mScanner.Next(token, eCSSScannerExclude_None)) { + return; + } + + CSSToken& resultToken(aResult.SetValue()); + + resultToken.mTokenType = static_cast(token.mType); + resultToken.mStartOffset = mScanner.GetTokenOffset(); + resultToken.mEndOffset = mScanner.GetTokenEndOffset(); + + switch (token.mType) { + case eCSSToken_Whitespace: + break; + + case eCSSToken_Ident: + case eCSSToken_Function: + case eCSSToken_AtKeyword: + case eCSSToken_ID: + case eCSSToken_Hash: + resultToken.mText.Construct(token.mIdent); + break; + + case eCSSToken_Dimension: + resultToken.mText.Construct(token.mIdent); + /* FALLTHROUGH */ + case eCSSToken_Number: + case eCSSToken_Percentage: + resultToken.mNumber.Construct(token.mNumber); + resultToken.mHasSign.Construct(token.mHasSign); + resultToken.mIsInteger.Construct(token.mIntegerValid); + break; + + case eCSSToken_String: + case eCSSToken_Bad_String: + case eCSSToken_URL: + case eCSSToken_Bad_URL: + resultToken.mText.Construct(token.mIdent); + /* Don't bother emitting the delimiter, as it is readily extracted + from the source string when needed. */ + break; + + case eCSSToken_Symbol: + resultToken.mText.Construct(nsString(&token.mSymbol, 1)); + break; + + case eCSSToken_Includes: + case eCSSToken_Dashmatch: + case eCSSToken_Beginsmatch: + case eCSSToken_Endsmatch: + case eCSSToken_Containsmatch: + case eCSSToken_URange: + break; + + case eCSSToken_Comment: + case eCSSToken_HTMLComment: + /* The comment text is easily extracted from the source string, + and is rarely useful. */ + break; + } +} + +} // namespace dom +} // namespace mozilla diff --git a/layout/style/CSSLexer.h b/layout/style/CSSLexer.h new file mode 100644 index 0000000000..a4251395e0 --- /dev/null +++ b/layout/style/CSSLexer.h @@ -0,0 +1,37 @@ +/* -*- 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 CSSLexer_h___ +#define CSSLexer_h___ + +#include "mozilla/UniquePtr.h" +#include "nsCSSScanner.h" +#include "mozilla/dom/CSSLexerBinding.h" + +namespace mozilla { +namespace dom { + +class CSSLexer : public NonRefcountedDOMObject +{ +public: + explicit CSSLexer(const nsAString&); + ~CSSLexer(); + + bool WrapObject(JSContext* aCx, JS::Handle aGivenProto, + JS::MutableHandle aReflector); + + uint32_t LineNumber(); + uint32_t ColumnNumber(); + void NextToken(Nullable& aResult); + +private: + nsString mInput; + nsCSSScanner mScanner; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* CSSLexer_h___ */ diff --git a/layout/style/moz.build b/layout/style/moz.build index 0251ca879e..4cb634d3d6 100644 --- a/layout/style/moz.build +++ b/layout/style/moz.build @@ -91,6 +91,7 @@ EXPORTS.mozilla += [ EXPORTS.mozilla.dom += [ 'CSS.h', + 'CSSLexer.h', 'CSSRuleList.h', 'CSSValue.h', 'FontFace.h', @@ -115,6 +116,7 @@ UNIFIED_SOURCES += [ 'AnimationCommon.cpp', 'CounterStyleManager.cpp', 'CSS.cpp', + 'CSSLexer.cpp', 'CSSRuleList.cpp', 'CSSStyleSheet.cpp', 'CSSVariableDeclarations.cpp', diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 0a3e712000..2387f909c2 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -2761,7 +2761,9 @@ CSSParserImpl::GetToken(bool aSkipWS) return true; } } - return mScanner->Next(mToken, aSkipWS); + return mScanner->Next(mToken, aSkipWS ? + eCSSScannerExclude_WhitespaceAndComments : + eCSSScannerExclude_Comments); } void diff --git a/layout/style/nsCSSScanner.cpp b/layout/style/nsCSSScanner.cpp index 4b24a5c25e..d0add2dfbc 100644 --- a/layout/style/nsCSSScanner.cpp +++ b/layout/style/nsCSSScanner.cpp @@ -552,7 +552,8 @@ nsCSSScanner::SkipComment() for (;;) { int32_t ch = Peek(); if (ch < 0) { - mReporter->ReportUnexpectedEOF("PECommentEOF"); + if (mReporter) + mReporter->ReportUnexpectedEOF("PECommentEOF"); SetEOFCharacters(eEOFCharacters_Asterisk | eEOFCharacters_Slash); return; } @@ -560,7 +561,8 @@ nsCSSScanner::SkipComment() Advance(); ch = Peek(); if (ch < 0) { - mReporter->ReportUnexpectedEOF("PECommentEOF"); + if (mReporter) + mReporter->ReportUnexpectedEOF("PECommentEOF"); SetEOFCharacters(eEOFCharacters_Slash); return; } @@ -985,7 +987,8 @@ nsCSSScanner::ScanString(nsCSSToken& aToken) mSeenBadToken = true; aToken.mType = eCSSToken_Bad_String; - mReporter->ReportUnexpected("SEUnterminatedString", aToken); + if (mReporter) + mReporter->ReportUnexpected("SEUnterminatedString", aToken); break; } return true; @@ -1192,15 +1195,15 @@ nsCSSScanner::NextURL(nsCSSToken& aToken) /** * Primary scanner entry point. Consume one token and fill in * |aToken| accordingly. Will skip over any number of comments first, - * and will also skip over rather than return whitespace tokens if - * |aSkipWS| is true. + * and will also skip over rather than return whitespace and comment + * tokens, depending on the value of |aSkip|. * * Returns true if it successfully consumed a token, false if EOF has * been reached. Will always advance the current read position by at * least one character unless called when already at EOF. */ bool -nsCSSScanner::Next(nsCSSToken& aToken, bool aSkipWS) +nsCSSScanner::Next(nsCSSToken& aToken, nsCSSScannerExclude aSkip) { int32_t ch; @@ -1218,15 +1221,18 @@ nsCSSScanner::Next(nsCSSToken& aToken, bool aSkipWS) ch = Peek(); if (IsWhitespace(ch)) { SkipWhitespace(); - if (!aSkipWS) { + if (aSkip != eCSSScannerExclude_WhitespaceAndComments) { aToken.mType = eCSSToken_Whitespace; return true; } continue; // start again at the beginning } if (ch == '/' && !IsSVGMode() && Peek(1) == '*') { - // FIXME: Editor wants comments to be preserved (bug 60290). SkipComment(); + if (aSkip == eCSSScannerExclude_None) { + aToken.mType = eCSSToken_Comment; + return true; + } continue; // start again at the beginning } break; diff --git a/layout/style/nsCSSScanner.h b/layout/style/nsCSSScanner.h index ba894d64f0..8c09acf1fe 100644 --- a/layout/style/nsCSSScanner.h +++ b/layout/style/nsCSSScanner.h @@ -27,6 +27,8 @@ enum nsCSSTokenType { // comments do *not* count as white space; comments separate tokens // but are not themselves tokens. eCSSToken_Whitespace, // + // A comment. + eCSSToken_Comment, // /*...*/ // Identifier-like tokens. mIdent is the text of the identifier. // The difference between ID and Hash is: if the text after the # @@ -182,13 +184,24 @@ private: bool mInitialized; }; +enum nsCSSScannerExclude { + // Return all tokens, including whitespace and comments. + eCSSScannerExclude_None, + // Include whitespace but exclude comments. + eCSSScannerExclude_Comments, + // Exclude whitespace and comments. + eCSSScannerExclude_WhitespaceAndComments +}; + // nsCSSScanner tokenizes an input stream using the CSS2.1 forward // compatible tokenization rules. Used internally by nsCSSParser; // not available for use by other code. class nsCSSScanner { public: // |aLineNumber == 1| is the beginning of a file, use |aLineNumber == 0| - // when the line number is unknown. + // when the line number is unknown. The scanner does not take + // ownership of |aBuffer|, so the caller must be sure to keep it + // alive for the lifetime of the scanner. nsCSSScanner(const nsAString& aBuffer, uint32_t aLineNumber); ~nsCSSScanner(); @@ -220,14 +233,20 @@ class nsCSSScanner { uint32_t GetColumnNumber() const { return mTokenOffset - mTokenLineOffset; } + uint32_t GetTokenOffset() const + { return mTokenOffset; } + + uint32_t GetTokenEndOffset() const + { return mOffset; } + // Get the text of the line containing the first character of // the most recently processed token. nsDependentSubstring GetCurrentLine() const; // Get the next token. Return false on EOF. aTokenResult is filled - // in with the data for the token. If aSkipWS is true, skip over - // eCSSToken_Whitespace tokens rather than returning them. - bool Next(nsCSSToken& aTokenResult, bool aSkipWS); + // in with the data for the token. aSkip controls whether + // whitespace and/or comment tokens are ever returned. + bool Next(nsCSSToken& aTokenResult, nsCSSScannerExclude aSkip); // Get the body of an URL token (everything after the 'url('). // This is exposed for use by nsCSSParser::ParseMozDocumentRule, diff --git a/layout/style/test/moz.build b/layout/style/test/moz.build index cae600df1d..ef05e532ef 100644 --- a/layout/style/test/moz.build +++ b/layout/style/test/moz.build @@ -15,6 +15,7 @@ MOCHITEST_MANIFESTS += [ 'css-visited/mochitest.ini', 'mochitest.ini', ] +XPCSHELL_TESTS_MANIFESTS += ['xpcshell.ini'] BROWSER_CHROME_MANIFESTS += ['browser.ini'] MOCHITEST_CHROME_MANIFESTS += ['chrome/chrome.ini'] diff --git a/layout/style/test/test_csslexer.js b/layout/style/test/test_csslexer.js new file mode 100644 index 0000000000..e5e0f5507f --- /dev/null +++ b/layout/style/test/test_csslexer.js @@ -0,0 +1,125 @@ +/* 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/. + */ + +function test_lexer(domutils, cssText, tokenTypes) { + let lexer = domutils.getCSSLexer(cssText); + let reconstructed = ''; + let lastTokenEnd = 0; + let i = 0; + while (true) { + let token = lexer.nextToken(); + if (!token) { + break; + } + let combined = token.tokenType; + if (token.text) + combined += ":" + token.text; + equal(combined, tokenTypes[i]); + ok(token.endOffset > token.startOffset); + equal(token.startOffset, lastTokenEnd); + lastTokenEnd = token.endOffset; + reconstructed += cssText.substring(token.startOffset, token.endOffset); + ++i; + } + // Ensure that we saw the correct number of tokens. + equal(i, tokenTypes.length); + // Ensure that the reported offsets cover all the text. + equal(reconstructed, cssText); +} + +let LEX_TESTS = [ + ["simple", ["ident:simple"]], + ["simple: { hi; }", + ["ident:simple", "symbol::", + "whitespace", "symbol:{", + "whitespace", "ident:hi", + "symbol:;", "whitespace", + "symbol:}"]], + ["/* whatever */", ["comment"]], + ["'string'", ["string:string"]], + ['"string"', ["string:string"]], + ["rgb(1,2,3)", ["function:rgb", "number", + "symbol:,", "number", + "symbol:,", "number", + "symbol:)"]], + ["@media", ["at:media"]], + ["#hibob", ["id:hibob"]], + ["#123", ["hash:123"]], + ["23px", ["dimension:px"]], + ["23%", ["percentage"]], + ["url(http://example.com)", ["url:http://example.com"]], + ["url('http://example.com')", ["url:http://example.com"]], + ["url( 'http://example.com' )", + ["url:http://example.com"]], + // In CSS Level 3, this is an ordinary URL, not a BAD_URL. + ["url(http://example.com", ["url:http://example.com"]], + // See bug 1153981 to understand why this gets a SYMBOL token. + ["url(http://example.com @", ["bad_url:http://example.com", "symbol:@"]], + ["quo\\ting", ["ident:quoting"]], + ["'bad string\n", ["bad_string:bad string", "whitespace"]], + ["~=", ["includes"]], + ["|=", ["dashmatch"]], + ["^=", ["beginsmatch"]], + ["$=", ["endsmatch"]], + ["*=", ["containsmatch"]], + + // URANGE may be on the way out, and it isn't used by devutils, so + // let's skip it. + + ["", ["htmlcomment", "whitespace", "ident:html", + "whitespace", "ident:comment", "whitespace", + "htmlcomment"]], + + // earlier versions of CSS had "bad comment" tokens, but in level 3, + // unterminated comments are just comments. + ["/* bad comment", ["comment"]] +]; + +function test_lexer_linecol(domutils, cssText, locations) { + let lexer = domutils.getCSSLexer(cssText); + let i = 0; + while (true) { + let token = lexer.nextToken(); + let startLine = lexer.lineNumber; + let startColumn = lexer.columnNumber; + + // We do this in a bit of a funny way so that we can also test the + // location of the EOF. + let combined = ":" + startLine + ":" + startColumn; + if (token) + combined = token.tokenType + combined; + + equal(combined, locations[i]); + ++i; + + if (!token) { + break; + } + } + // Ensure that we saw the correct number of tokens. + equal(i, locations.length); +} + +let LINECOL_TESTS = [ + ["simple", ["ident:0:0", ":0:6"]], + ["\n stuff", ["whitespace:0:0", "ident:1:4", ":1:9"]], + ['"string with \\\nnewline" \r\n', ["string:0:0", "whitespace:1:8", + ":2:0"]] +]; + +function run_test() +{ + let domutils = Components.classes["@mozilla.org/inspector/dom-utils;1"] + .getService(Components.interfaces.inIDOMUtils); + + let text, result; + for ([text, result] of LEX_TESTS) { + test_lexer(domutils, text, result); + } + + for ([text, result] of LINECOL_TESTS) { + test_lexer_linecol(domutils, text, result); + } +} diff --git a/layout/style/test/xpcshell.ini b/layout/style/test/xpcshell.ini new file mode 100644 index 0000000000..5019e0a481 --- /dev/null +++ b/layout/style/test/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head = +tail = + +[test_csslexer.js] diff --git a/toolkit/modules/BrowserUtils.jsm b/toolkit/modules/BrowserUtils.jsm index d9a77d54fb..ffd3157b94 100644 --- a/toolkit/modules/BrowserUtils.jsm +++ b/toolkit/modules/BrowserUtils.jsm @@ -10,6 +10,7 @@ this.EXPORTED_SYMBOLS = [ "BrowserUtils" ]; const {interfaces: Ci, utils: Cu, classes: Cc} = Components; Cu.import("resource://gre/modules/Services.jsm"); +Cu.importGlobalProperties(['URL']); this.BrowserUtils = { @@ -103,6 +104,30 @@ this.BrowserUtils = { return [elt, window]; }, + // Creates a codebase principal from a canonical origin string. This is + // the inverse operation of .origin on a codebase principal. + principalFromOrigin: function(aOriginString) { + if (aOriginString.startsWith('[')) { + throw new Error("principalFromOrigin does not support System and Expanded principals"); + } + + if (aOriginString.startsWith("moz-nullprincipal:")) { + throw new Error("principalFromOrigin does not support nsNullPrincipal"); + } + + var parts = aOriginString.split('!'); + if (parts.length > 2) { + throw new Error("bad origin string: " + aOriginString); + } + + var uri = Services.io.newURI(parts[0], null, null); + var attrs = {}; + // Parse the parameters string into a dictionary. + (parts[1] || "").split("&").map((x) => x.split('=')).forEach((x) => attrs[x[0]] = x[1]); + + return Services.scriptSecurityManager.createCodebasePrincipal(uri, attrs); + }, + /** * For a given DOM element, returns its position in "screen" * coordinates. In a content process, the coordinates returned will