import changes from `dev' branch of rmottola/Arctic-Fox:

- caps part only: Bug 1178533 - Add nsIInstallPackagedWebapp for registering permissions when navigating to signed packages r=bholley,fabrice,valentin (8174625aab)
- Bug 1299615 - Part 2: Skip ICU source directory in Clang build plugin when searching for implicit conversion constructors. r=Waldo, r=mystor (30a51b6602)
- Bug 1264827 - Part 1: Rename the existing code to make it clear it's checking for template args; r=mystor (2710d7e318)
- Bug 1264827 - Part 2: Add a static analysis to help check Rust wrapped C++ classes for members which are unsafe to memmove; r=mystor (8f053f59cf)
- Bug 1264827 - Part 3: Add the MOZ_NEEDS_MEMMOVABLE_MEMBERS annotation to MFBT; r=froydnj (937622926a)
- Bug 1229769 - We should be able to use DOM promises in the worker debugger;r=khuey (b3b32e9b94)
- Bug 1265035 - Make ~WorkerJSRuntime() handle Initialize() failure better. r=khuey. (67e67fab06)
- Bug 1256428 P1 Add ServiceWorkerJob2 base class. r=jdm (0bedbfa070)
- Bug 1256428 P2 Add ServiceWorkerJobQueue2 class. r=jdm (806e9b242e)
- Bug 1260933 - Part 1: For invalid easing values, print the invalid value. r=birtles (482541cfcb)
- Bug 1260933 - Part 2: For invalid duration values, print the invalid value. r=birtles (5b20918f77)
- Bug 1256428 P3 Add ServiceWorkerUpdateJob2. r=jdm (135e9bf05d)
- Bug 1256428 P4 Add ServiceWorkerRegisterJob2. r=jdm (e790f95b1d)
- Bug 1256428 P5 Add ServiceWorkerUnregisterJob2 class. r=jdm (cf63826b57)
- Bug 1220757 - Add report to console when service worker register fails due to mismatching scope path.r=bkelly (dbbf1a8515)
- Bug 1255621 - Ignore service workers previously registered in non-private windows. r=bkelly (593ebfc612)
- Bug 1241531 - Part 1: Only pop jobs from the queue when the correct job completes. r=ehsan (f8a1ea2fda)
- Bug 1241531 - Part 2: Move Cancel() to ServiceWorkerJob base class. r=ehsan (33ffccb8a4)
- Bug 1241531 - Part 3: Call Cancel() on all service worker jobs. r=ehsan (4cce06ab41)
- Bug 1241531 - Part 4: Make service worker unregister job respect cancelation. r=ehsan (1877cb3919)
- Bug 1261776 Use SafeElementAt() in service worker job queue. r=ehsan (c7a883a087)
- Bug 1238990 P1 ServiceWorkerManager should trigger automatic updates in current process. r=ehsan (c65bded060)
- Bug 1238990 P2 Try to ensure service worker jobs do not run during shutdown. r=ehsan (f816a012f2)
- Bug 1256428 P6 Use ServiceWorkerJobQueue2 and new job classes in ServiceWorkerManager. r=jdm (641af03802)
- Bug 1256428 P0 Fix unified build failures in dom/workers. r=jdm (33aaafd188)
- Bug 1261814: Use the presence of the content global, and not any random global, to determine whether to run the close handler. r=bz (8f182bf345)
- Bug 1256428 P7 Fix wpt update.https.html to expect TypeError per current spec. r=jdm (4c6cad6e0f)
- Bug 1256428 P8 Fix wpt unregister-then-register-new-script.https.html to new spec expectations matching blink's tests. r=jdm (d9191f7002)
- Bug 1226443 P5 Always use first scheduled update timer instead of rescheduling on new events. r=ehsan (7b1b31dcc0)
- Bug 1230341 Hold a strong ref in service worker NS_NewRunnableMethodWithArg uses. r=ehsan a=abillings (2b1d942ae4)
- Bug 1256428 P9 Remove now unused code from ServiceWorkerManager.cpp. r=jdm (7f97035007)
- Bug 1256428 P10 Remove ServiceWorkerRegistrationInfo::mUpdating flag. r=jdm (31fc686d5d)
- Bug 1256428 P11 Don't coalesce SW jobs after the existing job has already resolved its promise. r=jdm (1ce373f98b)
- Bug 1256428 P12 ServiceWorkerUnregisterJob2 should not use ServiceWorkerManager internals. r=jdm (1abe304c3c)
- Bug 1256428 P13 Remove unnecessary ServiceWorkerUnregsterJob2 stop. r=jdm (05d0717b7c)
- Bug 1256428 P14 Remove dead code in SeviceWorkerUpdateJob.cpp. r=jdm (7d1ac1112a)
- Bug 1256428 P15 Perform byte-for-byte comparison check after validating script URL. r=jdm (dc30ec75a9)
- Bug 1256428 P16 Fix some issues calling purgeCache() in ServiceWorkerUpdateJob.cpp. r=jdm (cffe93613a)
- Bug 1256428 P17 Rename service worker job classes to remove "2" suffix. r=jdm (f1d7a6aadf)
- Bug 1256428 P18 Add spec annotations and tweak asserts in ServiceWorkerUpdateJob. r=jdm (1a9c95a5bb)
- Bug 1256428 P19 Address ServiceWorkerUnregisterJob review feedback. r=jdm (2b8775a9ad)
- Bug 1260591 Move ServiceWorkerInfo and ServiceWorkerRegistrationInfo into separate files. r=jdm (2e31a3c002)
- Bug 1266857 Make Clients.claim() use observer document list instead of secondary hashtable. r=bz (2b318072f5)
- Bug 1261428: Migrate the useless setTimeout error message to the bindings infrastructure. r=bz (80d2503978)
- Bug 1263307 P1 Make ServiceWorkerRegistrationInfo::mScope const. r=jdm (b8b03bc594)
- Bug 1263307 P2 Make ServiceWorkerRegistrationInfo worker members private. r=jdm (16773a9134)
- Bug 1263307 P3 Move ServiceWorker update logic into central place in ServiceWorkerRegistrationInfo methods. r=jdm (68b288cbfb)
- Bug 1265761 Clients.matchAll() should treat http windows as secure if devtools are open and http testing is enabled. r=ehsan (100e16ca08)
- Bug 1265795 P1 Uncontrolled service workers when global is removed from document. r=bz (db069b0756)
- Bug 1265795 P2 Add a web-platform-test for the window navigation case. r=bz (6571257e5b)
- Bug 1265795 P3 Assert that controlled documents have an outer window. r=bz (fdc14dbf66)
- Bug 1265795 P4 Always call nsDocument::SetScriptGlobalObject(nullptr) from nsDocument::Destroy(). r=bz (8825c3dbd5)
- Bug 1254194: Add a validator for custom add-on content security policies. r=billm f=aswan (c557dd47ef)
- Bug 1254194: Allow iterating over and inspecting sources of parsed CSP directives. r=ckerschb (2d93cdda56)
- Bug 1142332 - Prevent calling CSP_EnumToKeyword with CSP_HASH. r=ckerschb (15a80ed62f)
- Bug 1236416 - Remove some misc toolkit content UI from Fennec r=margaret (01f7f81c93)
- Bug 1234403 - Part 1: Implement CSSPseudoElement.getAnimations. r=birtles (91ce2e1cae)
- Bug 1234403 - Part 2: Implement document.getAnimations. r=birtles (49afbacadb)
- Bug 1234403 - Part 3: Test for the CSSPseudoElement objects returned by effect.target. r=birtles (bf34dda38f)
- Bug 1234403 - Part 4: Test for the animation order returned by document.getAnimations(). r=birtles (fa8ec8e01f)
- Bug 1234403 - Part 5: Test for CSSPseudoElement.getAnimations. r=birtles (3ef598f2ba)
- Bug 1254418 - Part 1: Support generated-content element for Element.getAnimations. r=birtles (7ae806859a)
- Bug 1254418 - Part 2: Test getAnimations for generated-content elements. r=birtles (b562ec7478)
- Bug 1254761 - Part 1: Implement getAnimations({ subtree: true }). r=smaug (c5419ffec0)
- Bug 1254761 - Part 2: Removes extra whitespaces. r=birtles (2a98381928)
- Bug 1254761 - Part 3: Add tests for AnimationFilter. r=birtles (bdd9b39849)
- Bug 1254194: Apply a content security policy to all WebExtension documents. r=gabor (c3a9f32be8)
- Bug 1257246: Update the version of eslint that mach installs. r=gps (da0481d7e4)
- Bug 1229588: Add a taskcluster test for eslint. r=dustin (e6eff5caf2)
- Bug 1257246: Update lint test image to newer packages of eslint. r=ahal (bcfaf3b5d8)
- Bug 1263637 - Fix eslint 2 warnings for WebExtensions code. r=kmag (16537b22dc)
- Bug 1238177 - fix extension content needs to use the correct user context id origin attribute. r=sicking (834faa0f62)
This commit is contained in:
2024-05-02 16:55:13 +08:00
parent b5ee72fe09
commit 5f6d4971d0
95 changed files with 4994 additions and 2194 deletions
+49 -8
View File
@@ -91,7 +91,12 @@ private:
virtual void run(const MatchFinder::MatchResult &Result);
};
class NonMemMovableChecker : public MatchFinder::MatchCallback {
class NonMemMovableTemplateArgChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
class NonMemMovableMemberChecker : public MatchFinder::MatchCallback {
public:
virtual void run(const MatchFinder::MatchResult &Result);
};
@@ -125,7 +130,8 @@ private:
ExplicitOperatorBoolChecker explicitOperatorBoolChecker;
NoDuplicateRefCntMemberChecker noDuplicateRefCntMemberChecker;
NeedsNoVTableTypeChecker needsNoVTableTypeChecker;
NonMemMovableChecker nonMemMovableChecker;
NonMemMovableTemplateArgChecker nonMemMovableTemplateArgChecker;
NonMemMovableMemberChecker nonMemMovableMemberChecker;
ExplicitImplicitChecker explicitImplicitChecker;
NoAutoTypeChecker noAutoTypeChecker;
NoExplicitMoveConstructorChecker noExplicitMoveConstructorChecker;
@@ -201,7 +207,8 @@ bool isIgnoredPathForImplicitCtor(const Decl *decl) {
begin->compare_lower(StringRef("harfbuzz")) == 0 ||
begin->compare_lower(StringRef("hunspell")) == 0 ||
begin->compare_lower(StringRef("scoped_ptr.h")) == 0 ||
begin->compare_lower(StringRef("graphite2")) == 0) {
begin->compare_lower(StringRef("graphite2")) == 0 ||
begin->compare_lower(StringRef("icu")) == 0) {
return true;
}
if (begin->compare_lower(StringRef("chromium")) == 0) {
@@ -710,10 +717,15 @@ AST_MATCHER(QualType, isNonMemMovable) {
}
/// This matcher will select classes which require a memmovable template arg
AST_MATCHER(CXXRecordDecl, needsMemMovable) {
AST_MATCHER(CXXRecordDecl, needsMemMovableTemplateArg) {
return MozChecker::hasCustomAnnotation(&Node, "moz_needs_memmovable_type");
}
/// This matcher will select classes which require all members to be memmovable
AST_MATCHER(CXXRecordDecl, needsMemMovableMembers) {
return MozChecker::hasCustomAnnotation(&Node, "moz_needs_memmovable_members");
}
AST_MATCHER(CXXConstructorDecl, isInterestingImplicitCtor) {
const CXXConstructorDecl *decl = Node.getCanonicalDecl();
return
@@ -1015,10 +1027,16 @@ DiagnosticsMatcher::DiagnosticsMatcher() {
// Handle non-mem-movable template specializations
astMatcher.addMatcher(
classTemplateSpecializationDecl(
allOf(needsMemMovable(),
allOf(needsMemMovableTemplateArg(),
hasAnyTemplateArgument(refersToType(isNonMemMovable()))))
.bind("specialization"),
&nonMemMovableChecker);
&nonMemMovableTemplateArgChecker);
// Handle non-mem-movable members
astMatcher.addMatcher(
cxxRecordDecl(needsMemMovableMembers())
.bind("decl"),
&nonMemMovableMemberChecker);
astMatcher.addMatcher(constructorDecl(isInterestingImplicitCtor(),
ofClass(allOf(isConcreteClass(),
@@ -1385,7 +1403,7 @@ void DiagnosticsMatcher::NeedsNoVTableTypeChecker::run(
<< specialization;
}
void DiagnosticsMatcher::NonMemMovableChecker::run(
void DiagnosticsMatcher::NonMemMovableTemplateArgChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
@@ -1406,7 +1424,7 @@ void DiagnosticsMatcher::NonMemMovableChecker::run(
specialization->getTemplateInstantiationArgs();
for (unsigned i = 0; i < args.size(); ++i) {
QualType argType = args[i].getAsType();
if (NonMemMovable.hasEffectiveAnnotation(args[i].getAsType())) {
if (NonMemMovable.hasEffectiveAnnotation(argType)) {
Diag.Report(specialization->getLocation(), errorID) << specialization
<< argType;
// XXX It would be really nice if we could get the instantiation stack
@@ -1424,6 +1442,29 @@ void DiagnosticsMatcher::NonMemMovableChecker::run(
}
}
void DiagnosticsMatcher::NonMemMovableMemberChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
DiagnosticIDs::Error,
"class %0 cannot have non-memmovable member %1 of type %2");
// Get the specialization
const CXXRecordDecl* Decl =
Result.Nodes.getNodeAs<CXXRecordDecl>("decl");
// Report an error for every member which is non-memmovable
for (const FieldDecl *Field : Decl->fields()) {
QualType Type = Field->getType();
if (NonMemMovable.hasEffectiveAnnotation(Type)) {
Diag.Report(Field->getLocation(), errorID) << Decl
<< Field
<< Type;
NonMemMovable.dumpAnnotationReason(Diag, Type, Decl->getLocation());
}
}
}
void DiagnosticsMatcher::ExplicitImplicitChecker::run(
const MatchFinder::MatchResult &Result) {
DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
+23 -5
View File
@@ -1,5 +1,6 @@
#define MOZ_NON_MEMMOVABLE __attribute__((annotate("moz_non_memmovable")))
#define MOZ_NEEDS_MEMMOVABLE_TYPE __attribute__((annotate("moz_needs_memmovable_type")))
#define MOZ_NEEDS_MEMMOVABLE_MEMBERS __attribute__((annotate("moz_needs_memmovable_members")))
/*
These are a bunch of structs with variable levels of memmovability.
@@ -9,12 +10,12 @@ struct MOZ_NON_MEMMOVABLE NonMovable {};
struct Movable {};
// Subclasses
struct S_NonMovable : NonMovable {}; // expected-note 48 {{'S_NonMovable' is a non-memmove()able type because it inherits from a non-memmove()able type 'NonMovable'}}
struct S_NonMovable : NonMovable {}; // expected-note 51 {{'S_NonMovable' is a non-memmove()able type because it inherits from a non-memmove()able type 'NonMovable'}}
struct S_Movable : Movable {};
// Members
struct W_NonMovable {
NonMovable m; // expected-note 32 {{'W_NonMovable' is a non-memmove()able type because member 'm' is a non-memmove()able type 'NonMovable'}}
NonMovable m; // expected-note 34 {{'W_NonMovable' is a non-memmove()able type because member 'm' is a non-memmove()able type 'NonMovable'}}
};
struct W_Movable {
Movable m;
@@ -22,17 +23,17 @@ struct W_Movable {
// Wrapped Subclasses
struct WS_NonMovable {
S_NonMovable m; // expected-note 32 {{'WS_NonMovable' is a non-memmove()able type because member 'm' is a non-memmove()able type 'S_NonMovable'}}
S_NonMovable m; // expected-note 34 {{'WS_NonMovable' is a non-memmove()able type because member 'm' is a non-memmove()able type 'S_NonMovable'}}
};
struct WS_Movable {
S_Movable m;
};
// Combinations of the above
struct SW_NonMovable : W_NonMovable {}; // expected-note 16 {{'SW_NonMovable' is a non-memmove()able type because it inherits from a non-memmove()able type 'W_NonMovable'}}
struct SW_NonMovable : W_NonMovable {}; // expected-note 17 {{'SW_NonMovable' is a non-memmove()able type because it inherits from a non-memmove()able type 'W_NonMovable'}}
struct SW_Movable : W_Movable {};
struct SWS_NonMovable : WS_NonMovable {}; // expected-note 16 {{'SWS_NonMovable' is a non-memmove()able type because it inherits from a non-memmove()able type 'WS_NonMovable'}}
struct SWS_NonMovable : WS_NonMovable {}; // expected-note 17 {{'SWS_NonMovable' is a non-memmove()able type because it inherits from a non-memmove()able type 'WS_NonMovable'}}
struct SWS_Movable : WS_Movable {};
// Basic templated wrapper
@@ -810,3 +811,20 @@ void specialization() {
Defaulted_Templated_NeedyTemplate7<S_SpecializedNonMovable> c7;
W_Defaulted_Templated_NeedyTemplate8<S_SpecializedNonMovable> c8;
}
class MOZ_NEEDS_MEMMOVABLE_MEMBERS NeedsMemMovableMembers {
Movable m1;
NonMovable m2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'm2' of type 'NonMovable'}}
S_Movable sm1;
S_NonMovable sm2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'sm2' of type 'S_NonMovable'}}
W_Movable wm1;
W_NonMovable wm2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'wm2' of type 'W_NonMovable'}}
SW_Movable swm1;
SW_NonMovable swm2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'swm2' of type 'SW_NonMovable'}}
WS_Movable wsm1;
WS_NonMovable wsm2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'wsm2' of type 'WS_NonMovable'}}
SWS_Movable swsm1;
SWS_NonMovable swsm2; // expected-error {{class 'NeedsMemMovableMembers' cannot have non-memmovable member 'swsm2' of type 'SWS_NonMovable'}}
};
class NeedsMemMovableMembersDerived : public NeedsMemMovableMembers {};
+29
View File
@@ -454,6 +454,13 @@ BasePrincipal::GetAppId(uint32_t* aAppId)
return NS_OK;
}
NS_IMETHODIMP
BasePrincipal::GetAddonId(nsAString& aAddonId)
{
aAddonId.Assign(mOriginAttributes.mAddonId);
return NS_OK;
}
NS_IMETHODIMP
BasePrincipal::GetUserContextId(uint32_t* aUserContextId)
{
@@ -507,6 +514,28 @@ BasePrincipal::CreateCodebasePrincipal(nsIURI* aURI, const OriginAttributes& aAt
return codebase.forget();
}
already_AddRefed<BasePrincipal>
BasePrincipal::CreateCodebasePrincipal(const nsACString& aOrigin)
{
MOZ_ASSERT(!StringBeginsWith(aOrigin, NS_LITERAL_CSTRING("[")),
"CreateCodebasePrincipal does not support System and Expanded principals");
MOZ_ASSERT(!StringBeginsWith(aOrigin, NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME ":")),
"CreateCodebasePrincipal does not support nsNullPrincipal");
nsAutoCString originNoSuffix;
mozilla::OriginAttributes attrs;
if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
return nullptr;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
NS_ENSURE_SUCCESS(rv, nullptr);
return BasePrincipal::CreateCodebasePrincipal(uri, attrs);
}
bool
BasePrincipal::AddonAllowsLoad(nsIURI* aURI)
{
+2
View File
@@ -157,6 +157,7 @@ public:
NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final;
NS_IMETHOD GetAppStatus(uint16_t* aAppStatus) final;
NS_IMETHOD GetAppId(uint32_t* aAppStatus) final;
NS_IMETHOD GetAddonId(nsAString& aAddonId) final;
NS_IMETHOD GetIsInBrowserElement(bool* aIsInBrowserElement) final;
NS_IMETHOD GetUnknownAppId(bool* aUnknownAppId) final;
NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
@@ -168,6 +169,7 @@ public:
static BasePrincipal* Cast(nsIPrincipal* aPrin) { return static_cast<BasePrincipal*>(aPrin); }
static already_AddRefed<BasePrincipal>
CreateCodebasePrincipal(nsIURI* aURI, const OriginAttributes& aAttrs);
static already_AddRefed<BasePrincipal> CreateCodebasePrincipal(const nsACString& aOrigin);
const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; }
uint32_t AppId() const { return mOriginAttributes.mAppId; }
+34
View File
@@ -14,6 +14,25 @@
[scriptable,uuid(8a034ef9-9d14-4c5d-8319-06c1ab574baa)]
interface nsIAddonPolicyService : nsISupports
{
/**
* Returns the base content security policy, which is applied to all
* extension documents, in addition to any custom policies.
*/
readonly attribute AString baseCSP;
/**
* Returns the default content security policy which applies to extension
* documents which do not specify any custom policies.
*/
readonly attribute AString defaultCSP;
/**
* Returns the content security policy which applies to documents belonging
* to the extension with the given ID. This may be either a custom policy,
* if one was supplied, or the default policy if one was not.
*/
AString getAddonCSP(in AString aAddonId);
/**
* Returns true if unprivileged code associated with the given addon may load
* data from |aURI|.
@@ -30,3 +49,18 @@ interface nsIAddonPolicyService : nsISupports
*/
AString extensionURIToAddonId(in nsIURI aURI);
};
/**
* This interface exposes functionality related to add-on content policy
* enforcement.
*/
[scriptable,uuid(7a4fe60b-9131-45f5-83f3-dc63b5d71a5d)]
interface nsIAddonContentPolicy : nsISupports
{
/**
* Checks a custom content security policy string, to ensure that it meets
* minimum security requirements. Returns null for valid policies, or a
* string describing the error for invalid policies.
*/
AString validateAddonCSP(in AString aPolicyString);
};
+5
View File
@@ -286,6 +286,11 @@ interface nsIPrincipal : nsISerializable
*/
[infallible] readonly attribute unsigned long appId;
/**
* Gets the ID of the add-on this principal belongs to.
*/
readonly attribute AString addonId;
/**
* Gets the id of the user context this principal is inside. If this
* principal is inside the default userContext, this returns
+8 -1
View File
@@ -26,7 +26,7 @@ class DomainPolicyClone;
[ptr] native JSObjectPtr(JSObject);
[ptr] native DomainPolicyClonePtr(mozilla::dom::DomainPolicyClone);
[scriptable, uuid(73f92674-f59d-4c9b-a9b5-f7a3ae8ffa98)]
[scriptable, uuid(b7ae2310-576e-11e5-a837-0800200c9a66)]
interface nsIScriptSecurityManager : nsISupports
{
/**
@@ -195,6 +195,13 @@ interface nsIScriptSecurityManager : nsISupports
[implicit_jscontext]
nsIPrincipal createCodebasePrincipal(in nsIURI uri, in jsval originAttributes);
/**
* Returns a principal whose origin is the one we pass in.
* See nsIPrincipal.idl for a description of origin attributes, and
* ChromeUtils.webidl for a list of origin attributes and their defaults.
*/
nsIPrincipal createCodebasePrincipalFromOrigin(in ACString origin);
/**
* Returns a unique nonce principal with |originAttributes|.
* See nsIPrincipal.h for a description of origin attributes, and
+18
View File
@@ -68,6 +68,7 @@
#include "nsContentUtils.h"
#include "nsJSUtils.h"
#include "nsILoadInfo.h"
#include "nsXPCOMStrings.h"
// This should be probably defined on some other place... but I couldn't find it
#define WEBAPPS_PERM_NAME "webapps-manage"
@@ -1096,6 +1097,23 @@ nsScriptSecurityManager::CreateCodebasePrincipal(nsIURI* aURI, JS::Handle<JS::Va
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsScriptSecurityManager::CreateCodebasePrincipalFromOrigin(const nsACString& aOrigin,
nsIPrincipal** aPrincipal)
{
if (StringBeginsWith(aOrigin, NS_LITERAL_CSTRING("["))) {
return NS_ERROR_INVALID_ARG;
}
if (StringBeginsWith(aOrigin, NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME ":"))) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aOrigin);
prin.forget(aPrincipal);
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsScriptSecurityManager::CreateNullPrincipal(JS::Handle<JS::Value> aOriginAttributes,
JSContext* aCx, nsIPrincipal** aPrincipal)
+2 -3
View File
@@ -3,7 +3,6 @@ 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); }
@@ -28,9 +27,9 @@ function checkOriginAttributes(prin, attrs, suffix) {
do_check_eq(prin.originAttributes.inBrowser, attrs.inBrowser || false);
do_check_eq(prin.originSuffix, suffix || '');
if (!prin.isNullPrincipal && !prin.origin.startsWith('[')) {
do_check_true(BrowserUtils.principalFromOrigin(prin.origin).equals(prin));
do_check_true(ssm.createCodebasePrincipalFromOrigin(prin.origin).equals(prin));
} else {
checkThrows(() => BrowserUtils.principalFromOrigin(prin.origin));
checkThrows(() => ssm.createCodebasePrincipalFromOrigin(prin.origin));
}
}
+3 -1
View File
@@ -1230,12 +1230,14 @@ Animation::GetPresContext() const
void
Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
{
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
DoFinishNotificationImmediately();
} else if (!mFinishNotificationTask.IsPending()) {
RefPtr<nsRunnableMethod<Animation>> runnable =
NS_NewRunnableMethod(this, &Animation::DoFinishNotificationImmediately);
Promise::DispatchToMicroTask(runnable);
runtime->DispatchToMicroTask(runnable);
mFinishNotificationTask = runnable;
}
}
+10 -3
View File
@@ -7,6 +7,7 @@
#include "mozilla/dom/CSSPseudoElement.h"
#include "mozilla/dom/CSSPseudoElementBinding.h"
#include "mozilla/dom/Element.h"
#include "mozilla/AnimationComparator.h"
namespace mozilla {
namespace dom {
@@ -49,10 +50,16 @@ CSSPseudoElement::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
}
void
CSSPseudoElement::GetAnimations(nsTArray<RefPtr<Animation>>& aRetVal)
CSSPseudoElement::GetAnimations(const AnimationFilter& filter,
nsTArray<RefPtr<Animation>>& aRetVal)
{
// Bug 1234403: Implement this API.
NS_NOTREACHED("CSSPseudoElement::GetAnimations() is not implemented yet.");
nsIDocument* doc = mParentElement->GetComposedDoc();
if (doc) {
doc->FlushPendingNotifications(Flush_Style);
}
Element::GetAnimationsUnsorted(mParentElement, mPseudoType, aRetVal);
aRetVal.Sort(AnimationPtrComparator<RefPtr<Animation>>());
}
already_AddRefed<Animation>
+2 -1
View File
@@ -57,7 +57,8 @@ public:
return retVal.forget();
}
void GetAnimations(nsTArray<RefPtr<Animation>>& aRetVal);
void GetAnimations(const AnimationFilter& filter,
nsTArray<RefPtr<Animation>>& aRetVal);
already_AddRefed<Animation>
Animate(JSContext* aContext,
JS::Handle<JSObject*> aFrames,
+1 -1
View File
@@ -161,7 +161,7 @@ TimingParams::ParseEasing(const nsAString& aEasing,
MOZ_ASSERT_UNREACHABLE("unexpected animation-timing-function unit");
break;
}
aRv.ThrowTypeError<dom::MSG_INVALID_EASING_ERROR>();
aRv.ThrowTypeError<dom::MSG_INVALID_EASING_ERROR>(aEasing);
return Nothing();
}
+2 -1
View File
@@ -60,7 +60,8 @@ struct TimingParams
NS_LITERAL_STRING("duration"));
}
} else if (!aDuration.GetAsString().EqualsLiteral("auto")) {
aRv.ThrowTypeError<dom::MSG_INVALID_DURATION_ERROR>();
aRv.ThrowTypeError<dom::MSG_INVALID_DURATION_ERROR>(
aDuration.GetAsString());
}
return result;
}
+2 -1
View File
@@ -9,8 +9,9 @@ support-files =
# file_animate_xrays.html needs to go in mochitest.ini since it is served
# over HTTP
[chrome/test_animation_observers.html]
[chrome/test_animation_properties.html]
[chrome/test_animation_property_state.html]
[chrome/test_animation_properties.html]
[chrome/test_generated_content_getAnimations.html]
[chrome/test_restyles.html]
[chrome/test_running_on_compositor.html]
skip-if = buildapp == 'b2g'
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<head>
<meta charset=utf-8>
<title>Test getAnimations() for generated-content elements</title>
<script type="application/javascript" src="../testharness.js"></script>
<script type="application/javascript" src="../testharnessreport.js"></script>
<script type="application/javascript" src="../testcommon.js"></script>
<style>
@keyframes anim { }
@keyframes anim2 { }
.before::before {
content: '';
animation: anim 100s;
}
.after::after {
content: '';
animation: anim 100s, anim2 100s;
}
</style>
</head>
<body>
<div id='root' class='before after'>
<div class='before'></div>
<div></div>
</div>
<script>
'use strict';
const {Cc, Ci, Cu} = SpecialPowers;
function getWalker(node) {
var walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"].
createInstance(Ci.inIDeepTreeWalker);
walker.showAnonymousContent = true;
walker.init(node.ownerDocument, Ci.nsIDOMNodeFilter.SHOW_ALL);
walker.currentNode = node;
return walker;
}
test(function(t) {
var root = document.getElementById('root');
// Flush first to make sure the generated-content elements are ready
// in the tree.
flushComputedStyle(root);
var before = getWalker(root).firstChild();
var after = getWalker(root).lastChild();
// Sanity Checks
assert_equals(document.getAnimations().length, 4,
'All animations in this document');
assert_equals(before.tagName, '_moz_generated_content_before',
'First child is ::before element');
assert_equals(after.tagName, '_moz_generated_content_after',
'Last child is ::after element');
// Test Element.getAnimations() for generated-content elements
assert_equals(before.getAnimations().length, 1,
'Animations of ::before generated-content element');
assert_equals(after.getAnimations().length, 2,
'Animations of ::after generated-content element');
}, 'Element.getAnimations() used on generated-content elements');
test(function(t) {
var root = document.getElementById('root');
flushComputedStyle(root);
var walker = getWalker(root);
var animations = [];
var element = walker.currentNode;
while (element) {
if (element.getAnimations) {
animations = [...animations, ...element.getAnimations()];
}
element = walker.nextNode();
}
assert_equals(animations.length, document.getAnimations().length,
'The number of animations got by DeepTreeWalker and ' +
'document.getAnimations() should be the same');
}, 'Element.getAnimations() used by traversing DeepTreeWalker');
</script>
</body>
@@ -14,10 +14,6 @@
@keyframes animRight {
to { right: 100px }
}
.with-before-animation::before {
content: " ";
animation: animLeft 100s;
}
</style>
<body>
<script>
@@ -245,14 +241,35 @@ test(function(t) {
}, 'CSS Animations cancelled and restarted via the API are returned');
test(function(t) {
var div = addDiv(t, { class: 'with-before-animation' });
// FIXME: This should actually return the animation on the pseudo element
// but we haven't implemented a PseudoElement interface to use as
// animation's target (bug 1174575) so we simply don't return these animations
// until we can support them properly.
assert_equals(document.getAnimations().length, 0,
'CSS animations on pseudo elements are not returned');
}, 'CSS Animations targetting pseudo-elements are not returned');
addStyle(t, { '#parent::after': 'animation: animLeft 10s;',
'#parent::before': 'animation: animRight 10s;' });
// create two divs with these arrangement:
// parent
// ::before,
// ::after
// |
// child
var parent = addDiv(t, { 'id': 'parent' });
var child = addDiv(t);
parent.appendChild(child);
[parent, child].forEach((div) => {
div.setAttribute('style', 'animation: animBottom 10s');
});
var anims = document.getAnimations();
assert_equals(anims.length, 4,
'CSS animations on both pseudo-elements and elements ' +
'are returned');
assert_equals(anims[0].effect.target, parent,
'The animation targeting the parent element comes first');
assert_equals(anims[1].effect.target.type, '::before',
'The animation targeting the ::before element comes second');
assert_equals(anims[2].effect.target.type, '::after',
'The animation targeting the ::after element comes third');
assert_equals(anims[3].effect.target, child,
'The animation targeting the child element comes last');
}, 'CSS Animations targetting (pseudo-)elements should have correct order ' +
'after sorting');
done();
</script>
@@ -14,7 +14,40 @@ test(function(t) {
var animation = div.getAnimations()[0];
assert_equals(animation.effect.target, div,
'Animation.target is the animatable div');
}, 'Returned CSS animations have the correct Animation.target');
}, 'Returned CSS animations have the correct effect target');
test(function(t) {
addStyle(t, { '.after::after': 'animation: anim 10s, anim 100s;' });
var div = addDiv(t, { class: 'after' });
var anims = document.getAnimations();
assert_equals(anims.length, 2,
'Got animations running on ::after pseudo element');
assert_equals(anims[0].effect.target, anims[1].effect.target,
'Both animations return the same target object');
}, 'effect.target should return the same CSSPseudoElement object each time');
test(function(t) {
addStyle(t, { '.after::after': 'animation: anim 10s;' });
var div = addDiv(t, { class: 'after' });
var pseudoTarget = document.getAnimations()[0].effect.target;
var effect = new KeyframeEffectReadOnly(pseudoTarget,
{ background: ["blue", "red"] },
3000);
var newAnim = new Animation(effect, document.timeline);
newAnim.play();
var anims = document.getAnimations();
assert_equals(anims.length, 2,
'Got animations running on ::after pseudo element');
assert_not_equals(anims[0], newAnim,
'The scriped-generated animation appears last');
assert_equals(newAnim.effect.target, pseudoTarget,
'The effect.target of the scripted-generated animation is ' +
'the same as the one from the argument of ' +
'KeyframeEffectReadOnly constructor');
assert_equals(anims[0].effect.target, newAnim.effect.target,
'Both animations return the same target object');
}, 'effect.target from the script-generated animation should return the same ' +
'CSSPseudoElement object as that from the CSS generated animation');
done();
</script>
@@ -304,6 +304,147 @@ async_test(function(t) {
}));
}, 'getAnimations for CSS Animations follows animation-name order');
test(function(t) {
addStyle(t, { '#target::after': 'animation: anim1 10s;',
'#target::before': 'animation: anim1 10s;' });
var target = addDiv(t, { 'id': 'target' });
target.style.animation = 'anim1 100s';
var animations = target.getAnimations({ subtree: false });
assert_equals(animations.length, 1,
'Should find only the element');
assert_equals(animations[0].effect.target, target,
'Effect target should be the element');
}, 'Test AnimationFilter{ subtree: false } with single element');
test(function(t) {
addStyle(t, { '#target::after': 'animation: anim1 10s;',
'#target::before': 'animation: anim1 10s;' });
var target = addDiv(t, { 'id': 'target' });
target.style.animation = 'anim1 100s';
var animations = target.getAnimations({ subtree: true });
assert_equals(animations.length, 3,
'getAnimations({ subtree: true }) ' +
'should return animations on pseudo-elements');
assert_equals(animations[0].effect.target, target,
'The animation targeting the parent element ' +
'should be returned first');
assert_equals(animations[1].effect.target.type, '::before',
'The animation targeting the ::before pseudo-element ' +
'should be returned second');
assert_equals(animations[2].effect.target.type, '::after',
'The animation targeting the ::after pesudo-element ' +
'should be returned last');
}, 'Test AnimationFilter{ subtree: true } with single element');
test(function(t) {
addStyle(t, { '#parent::after': 'animation: anim1 10s;',
'#parent::before': 'animation: anim1 10s;',
'#child::after': 'animation: anim1 10s;',
'#child::before': 'animation: anim1 10s;' });
var parent = addDiv(t, { 'id': 'parent' });
parent.style.animation = 'anim1 100s';
var child = addDiv(t, { 'id': 'child' });
child.style.animation = 'anim1 100s';
parent.appendChild(child);
var animations = parent.getAnimations({ subtree: false });
assert_equals(animations.length, 1,
'Should find only the element even if it has a child');
assert_equals(animations[0].effect.target, parent,
'Effect target shuld be the element');
}, 'Test AnimationFilter{ subtree: false } with element that has a child');
test(function(t) {
addStyle(t, { '#parent::after': 'animation: anim1 10s;',
'#parent::before': 'animation: anim1 10s;',
'#child::after': 'animation: anim1 10s;',
'#child::before': 'animation: anim1 10s;' });
var parent = addDiv(t, { 'id': 'parent' });
var child = addDiv(t, { 'id': 'child' });
parent.style.animation = 'anim1 100s';
child.style.animation = 'anim1 100s';
parent.appendChild(child);
var animations = parent.getAnimations({ subtree: true });
assert_equals(animations.length, 6,
'Should find all elements, pesudo-elements that parent has');
assert_equals(animations[0].effect.target, parent,
'The animation targeting the parent element ' +
'should be returned first');
assert_equals(animations[1].effect.target.type, '::before',
'The animation targeting the ::before pseudo-element ' +
'should be returned second');
assert_equals(animations[1].effect.target.parentElement, parent,
'This ::before element should be child of parent element');
assert_equals(animations[2].effect.target.type, '::after',
'The animation targeting the ::after pesudo-element ' +
'should be returned third');
assert_equals(animations[2].effect.target.parentElement, parent,
'This ::after element should be child of parent element');
assert_equals(animations[3].effect.target, child,
'The animation targeting the child element ' +
'should be returned fourth');
assert_equals(animations[4].effect.target.type, '::before',
'The animation targeting the ::before pseudo-element ' +
'should be returned fifth');
assert_equals(animations[4].effect.target.parentElement, child,
'This ::before element should be child of child element');
assert_equals(animations[5].effect.target.type, '::after',
'The animation targeting the ::after pesudo-element ' +
'should be returned last');
assert_equals(animations[5].effect.target.parentElement, child,
'This ::after element should be child of child element');
}, 'Test AnimationFilter{ subtree: true } with element that has a child');
test(function(t) {
var parent = addDiv(t, { 'id': 'parent' });
var child1 = addDiv(t, { 'id': 'child1' });
var grandchild1 = addDiv(t, { 'id': 'grandchild1' });
var grandchild2 = addDiv(t, { 'id': 'grandchild2' });
var child2 = addDiv(t, { 'id': 'child2' });
parent.style.animation = 'anim1 100s';
child1.style.animation = 'anim1 100s';
grandchild1.style.animation = 'anim1 100s';
grandchild2.style.animation = 'anim1 100s';
child2.style.animation = 'anim1 100s';
parent.appendChild(child1);
child1.appendChild(grandchild1);
child1.appendChild(grandchild2);
parent.appendChild(child2);
var animations = parent.getAnimations({ subtree: true });
assert_equals(
parent.getAnimations({ subtree: true }).length, 5,
'Should find all descendants of the element');
assert_equals(animations[0].effect.target, parent,
'The animation targeting the parent element ' +
'should be returned first');
assert_equals(animations[1].effect.target, child1,
'The animation targeting the child1 element ' +
'should be returned second');
assert_equals(animations[2].effect.target, grandchild1,
'The animation targeting the grandchild1 element ' +
'should be returned third');
assert_equals(animations[3].effect.target, grandchild2,
'The animation targeting the grandchild2 element ' +
'should be returned fourth');
assert_equals(animations[4].effect.target, child2,
'The animation targeting the child2 element ' +
'should be returned last');
}, 'Test AnimationFilter{ subtree: true } with element that has many descendant');
done();
</script>
</body>
@@ -0,0 +1,70 @@
<!doctype html>
<meta charset=utf-8>
<script src="../testcommon.js"></script>
<style>
@keyframes anim1 { }
@keyframes anim2 { }
.before::before {
animation: anim1 10s;
}
.after-with-mix-anims-trans::after {
content: '';
animation: anim1 10s, anim2 10s;
width: 0px;
height: 0px;
transition: all 100s;
}
.after-change::after {
width: 100px;
height: 100px;
}
</style>
<body>
<script>
'use strict';
test(function(t) {
var div = addDiv(t, { class: 'before' });
var pseudoTarget = document.getAnimations()[0].effect.target;
assert_equals(pseudoTarget.getAnimations().length, 1,
'Expected number of animations are returned');
assert_equals(pseudoTarget.getAnimations()[0].animationName, 'anim1',
'CSS animation name matches');
}, 'getAnimations returns CSSAnimation objects');
test(function(t) {
var div = addDiv(t, { class: 'after-with-mix-anims-trans' });
// Trigger transitions
flushComputedStyle(div);
div.classList.add('after-change');
// Create additional animation on the pseudo-element from script
var pseudoTarget = document.getAnimations()[0].effect.target;
var effect = new KeyframeEffectReadOnly(pseudoTarget,
{ background: ["blue", "red"] },
3000);
var newAnimation = new Animation(effect, document.timeline);
newAnimation.id = 'scripted-anim';
newAnimation.play();
// Check order - the script-generated animation should appear later
var anims = pseudoTarget.getAnimations();
assert_equals(anims.length, 5,
'Got expected number of animations/trnasitions running on ' +
'::after pseudo element');
assert_equals(anims[0].transitionProperty, 'height',
'1st animation is the 1st transition sorted by name');
assert_equals(anims[1].transitionProperty, 'width',
'2nd animation is the 2nd transition sorted by name ');
assert_equals(anims[2].animationName, 'anim1',
'3rd animation is the 1st animation in animation-name list');
assert_equals(anims[3].animationName, 'anim2',
'4rd animation is the 2nd animation in animation-name list');
assert_equals(anims[4].id, 'scripted-anim',
'Animation added by script appears last');
}, 'getAnimations returns css transitions/animations, and script-generated ' +
'animations in the expected order');
done();
</script>
</body>
@@ -0,0 +1,14 @@
<!doctype html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
'use strict';
setup({explicit_done: true});
SpecialPowers.pushPrefEnv(
{ "set": [["dom.animations-api.core.enabled", true]]},
function() {
window.open("file_pseudoElement-get-animations.html");
});
</script>
@@ -31,6 +31,49 @@ test(function(t) {
'getAnimations returns no running CSS Transitions');
}, 'getAnimations for CSS Transitions');
test(function(t) {
addStyle(t, { '.init::after': 'content: ""; width: 0px; ' +
'transition: all 100s;',
'.init::before': 'content: ""; width: 0px; ' +
'transition: all 10s;',
'.change::after': 'width: 100px;',
'.change::before': 'width: 100px;' });
// create two divs with these arrangement:
// parent
// ::before,
// ::after
// |
// child
var parent = addDiv(t);
var child = addDiv(t);
parent.appendChild(child);
parent.style.left = '0px';
parent.style.transition = 'left 10s';
parent.classList.add('init');
child.style.left = '0px';
child.style.transition = 'left 10s';
flushComputedStyle(parent);
parent.style.left = '100px';
parent.classList.add('change');
child.style.left = '100px';
var anims = document.getAnimations();
assert_equals(anims.length, 4,
'CSS transition on both pseudo-elements and elements ' +
'are returned');
assert_equals(anims[0].effect.target, parent,
'The animation targeting the parent element comes first');
assert_equals(anims[1].effect.target.type, '::before',
'The animation targeting the ::before element comes second');
assert_equals(anims[2].effect.target.type, '::after',
'The animation targeting the ::after element comes third');
assert_equals(anims[3].effect.target, child,
'The animation targeting the child element comes last');
}, 'CSS Transitions targetting (pseudo-)elements should have correct order ' +
'after sorting');
async_test(function(t) {
var div = addDiv(t, { style: 'left: 0px; transition: all 50ms' });
flushComputedStyle(div);
@@ -18,6 +18,49 @@ test(function(t) {
'Animation.target is the animatable div');
}, 'Returned CSS transitions have the correct Animation.target');
test(function(t) {
addStyle(t, { '.init::after': 'content: ""; width: 0px; height: 0px; ' +
'transition: all 10s;',
'.change::after': 'width: 100px; height: 100px;' });
var div = addDiv(t, { class: 'init' });
flushComputedStyle(div);
div.classList.add('change');
var anims = document.getAnimations();
assert_equals(anims.length, 2,
'Got transitions running on ::after pseudo element');
assert_equals(anims[0].effect.target, anims[1].effect.target,
'Both transitions return the same target object');
}, 'effect.target should return the same CSSPseudoElement object each time');
test(function(t) {
addStyle(t, { '.init::after': 'content: ""; width: 0px; transition: all 10s;',
'.change::after': 'width: 100px;' });
var div = addDiv(t, { class: 'init' });
flushComputedStyle(div);
div.classList.add('change');
var pseudoTarget = document.getAnimations()[0].effect.target;
var effect = new KeyframeEffectReadOnly(pseudoTarget,
{ background: ["blue", "red"] },
3000);
var newAnim = new Animation(effect, document.timeline);
newAnim.play();
var anims = document.getAnimations();
assert_equals(anims.length, 2,
'Got animations running on ::after pseudo element');
assert_not_equals(anims[0], newAnim,
'The scriped-generated animation appears last');
assert_equals(newAnim.effect.target, pseudoTarget,
'The effect.target of the scripted-generated animation is ' +
'the same as the one from the argument of ' +
'KeyframeEffectReadOnly constructor');
assert_equals(anims[0].effect.target, newAnim.effect.target,
'Both the transition and the scripted-generated animation ' +
'return the same target object');
}, 'effect.target from the script-generated animation should return the same ' +
'CSSPseudoElement object as that from the CSS generated transition');
done();
</script>
</body>
@@ -0,0 +1,45 @@
<!doctype html>
<meta charset=utf-8>
<script src="../testcommon.js"></script>
<style>
.init::before {
content: '';
height: 0px;
width: 0px;
opacity: 0;
transition: all 100s;
}
.change::before {
height: 100px;
width: 100px;
opacity: 1;
}
</style>
<body>
<script>
'use strict';
test(function(t) {
var div = addDiv(t, { class: 'init' });
flushComputedStyle(div);
div.classList.add('change');
// Sanity checks
assert_equals(document.getAnimations().length, 3,
'Got expected number of animations on document');
var pseudoTarget = document.getAnimations()[0].effect.target;
assert_class_string(pseudoTarget, 'CSSPseudoElement',
'Got pseudo-element target');
// Check animations returned from the pseudo element are in correct order
var anims = pseudoTarget.getAnimations();
assert_equals(anims.length, 3,
'Got expected number of animations on pseudo-element');
assert_equals(anims[0].transitionProperty, 'height');
assert_equals(anims[1].transitionProperty, 'opacity');
assert_equals(anims[2].transitionProperty, 'width');
}, 'getAnimations sorts simultaneous transitions by name');
done();
</script>
</body>
@@ -0,0 +1,14 @@
<!doctype html>
<meta charset=utf-8>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
'use strict';
setup({explicit_done: true});
SpecialPowers.pushPrefEnv(
{ "set": [["dom.animations-api.core.enabled", true]]},
function() {
window.open("file_pseudoElement-get-animations.html");
});
</script>
+28
View File
@@ -17,6 +17,34 @@ function addDiv(t) {
return div;
}
/**
* Appends a style div to the document head.
*
* @param t The testharness.js Test object. If provided, this will be used
* to register a cleanup callback to remove the style element
* when the test finishes.
*
* @param rules A dictionary object with selector names and rules to set on
* the style sheet.
*/
function addStyle(t, rules) {
var extraStyle = document.createElement('style');
document.head.appendChild(extraStyle);
if (rules) {
var sheet = extraStyle.sheet;
for (var selector in rules) {
sheet.insertRule(selector + '{' + rules[selector] + '}',
sheet.cssRules.length);
}
}
if (t && typeof t.add_cleanup === 'function') {
t.add_cleanup(function() {
extraStyle.remove();
});
}
}
/**
* Promise wrapper for requestAnimationFrame.
*/
+1 -8
View File
@@ -126,13 +126,6 @@ ChromeUtils::OriginAttributesMatchPattern(dom::GlobalObject& aGlobal,
return pattern.Matches(attrs);
}
/* static */ void
ChromeUtils::CreateDefaultOriginAttributes(dom::GlobalObject& aGlobal,
dom::OriginAttributesDictionary& aAttrs)
{
aAttrs = OriginAttributes();
}
/* static */ void
ChromeUtils::CreateOriginAttributesFromOrigin(dom::GlobalObject& aGlobal,
const nsAString& aOrigin,
@@ -149,7 +142,7 @@ ChromeUtils::CreateOriginAttributesFromOrigin(dom::GlobalObject& aGlobal,
}
/* static */ void
ChromeUtils::CreateOriginAttributesFromDict(dom::GlobalObject& aGlobal,
ChromeUtils::FillNonDefaultOriginAttributes(dom::GlobalObject& aGlobal,
const dom::OriginAttributesDictionary& aAttrs,
dom::OriginAttributesDictionary& aNewAttrs)
{
+1 -5
View File
@@ -72,10 +72,6 @@ public:
const dom::OriginAttributesDictionary& aAttrs,
const dom::OriginAttributesPatternDictionary& aPattern);
static void
CreateDefaultOriginAttributes(dom::GlobalObject& aGlobal,
dom::OriginAttributesDictionary& aAttrs);
static void
CreateOriginAttributesFromOrigin(dom::GlobalObject& aGlobal,
const nsAString& aOrigin,
@@ -83,7 +79,7 @@ public:
ErrorResult& aRv);
static void
CreateOriginAttributesFromDict(dom::GlobalObject& aGlobal,
FillNonDefaultOriginAttributes(dom::GlobalObject& aGlobal,
const dom::OriginAttributesDictionary& aAttrs,
dom::OriginAttributesDictionary& aNewAttrs);
};
+51 -6
View File
@@ -68,6 +68,7 @@
#include "nsDocument.h"
#include "nsAttrValueOrString.h"
#include "nsAttrValueInlines.h"
#include "nsCSSPseudoElements.h"
#ifdef MOZ_XUL
#include "nsXULElement.h"
#endif /* MOZ_XUL */
@@ -3451,22 +3452,66 @@ Element::Animate(const Nullable<ElementOrCSSPseudoElement>& aTarget,
}
void
Element::GetAnimations(nsTArray<RefPtr<Animation>>& aAnimations)
Element::GetAnimations(const AnimationFilter& filter,
nsTArray<RefPtr<Animation>>& aAnimations)
{
nsIDocument* doc = GetComposedDoc();
if (doc) {
doc->FlushPendingNotifications(Flush_Style);
}
GetAnimationsUnsorted(aAnimations);
Element* elem = this;
CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
// For animations on generated-content elements, the animations are stored
// on the parent element.
nsIAtom* name = NodeInfo()->NameAtom();
if (name == nsGkAtoms::mozgeneratedcontentbefore) {
elem = GetParentElement();
pseudoType = CSSPseudoElementType::before;
} else if (name == nsGkAtoms::mozgeneratedcontentafter) {
elem = GetParentElement();
pseudoType = CSSPseudoElementType::after;
}
if (!elem) {
return;
}
if (!filter.mSubtree ||
pseudoType == CSSPseudoElementType::before ||
pseudoType == CSSPseudoElementType::after) {
GetAnimationsUnsorted(elem, pseudoType, aAnimations);
} else {
for (nsIContent* node = this;
node;
node = node->GetNextNode(this)) {
if (!node->IsElement()) {
continue;
}
Element* element = node->AsElement();
Element::GetAnimationsUnsorted(element, CSSPseudoElementType::NotPseudo,
aAnimations);
Element::GetAnimationsUnsorted(element, CSSPseudoElementType::before,
aAnimations);
Element::GetAnimationsUnsorted(element, CSSPseudoElementType::after,
aAnimations);
}
}
aAnimations.Sort(AnimationPtrComparator<RefPtr<Animation>>());
}
void
Element::GetAnimationsUnsorted(nsTArray<RefPtr<Animation>>& aAnimations)
/* static */ void
Element::GetAnimationsUnsorted(Element* aElement,
CSSPseudoElementType aPseudoType,
nsTArray<RefPtr<Animation>>& aAnimations)
{
EffectSet* effects = EffectSet::GetEffectSet(this,
CSSPseudoElementType::NotPseudo);
MOZ_ASSERT(aPseudoType == CSSPseudoElementType::NotPseudo ||
aPseudoType == CSSPseudoElementType::after ||
aPseudoType == CSSPseudoElementType::before,
"Unsupported pseudo type");
MOZ_ASSERT(aElement, "Null element");
EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType);
if (!effects) {
return;
}
+10 -5
View File
@@ -55,6 +55,7 @@ class nsDocument;
namespace mozilla {
namespace dom {
struct AnimationFilter;
struct ScrollIntoViewOptions;
struct ScrollToOptions;
class ElementOrCSSPseudoElement;
@@ -120,6 +121,7 @@ enum {
ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET);
namespace mozilla {
enum class CSSPseudoElementType : uint8_t;
class EventChainPostVisitor;
class EventChainPreVisitor;
class EventChainVisitor;
@@ -180,7 +182,7 @@ public:
* removing it from the document).
*/
void UpdateState(bool aNotify);
/**
* Method to update mState with link state information. This does not notify.
*/
@@ -340,7 +342,7 @@ public:
break;
}
/*
/*
* Only call UpdateState if we need to notify, because we call
* SetDirectionality for every element, and UpdateState is very very slow
* for some elements.
@@ -874,8 +876,11 @@ public:
ErrorResult& aError);
// Note: GetAnimations will flush style while GetAnimationsUnsorted won't.
void GetAnimations(nsTArray<RefPtr<Animation>>& aAnimations);
void GetAnimationsUnsorted(nsTArray<RefPtr<Animation>>& aAnimations);
void GetAnimations(const AnimationFilter& filter,
nsTArray<RefPtr<Animation>>& aAnimations);
static void GetAnimationsUnsorted(Element* aElement,
CSSPseudoElementType aPseudoType,
nsTArray<RefPtr<Animation>>& aAnimations);
NS_IMETHOD GetInnerHTML(nsAString& aInnerHTML);
virtual void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError);
@@ -1006,7 +1011,7 @@ public:
* Return the CORS mode for a given string
*/
static CORSMode StringToCORSMode(const nsAString& aValue);
/**
* Return the CORS mode for a given nsAttrValue (which may be null,
* but if not should have been parsed via ParseCORSValue).
+66 -40
View File
@@ -31,6 +31,7 @@
#include "nsILoadContext.h"
#include "nsUnicharUtils.h"
#include "nsContentList.h"
#include "nsCSSPseudoElements.h"
#include "nsIObserver.h"
#include "nsIBaseWindow.h"
#include "mozilla/css/Loader.h"
@@ -198,7 +199,9 @@
#include "imgRequestProxy.h"
#include "nsWrapperCacheInlines.h"
#include "nsSandboxFlags.h"
#include "nsIAddonPolicyService.h"
#include "nsIAppsService.h"
#include "mozilla/dom/AnimatableBinding.h"
#include "mozilla/dom/AnonymousContent.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DocumentFragment.h"
@@ -1455,7 +1458,7 @@ nsIDocument::nsIDocument()
{
SetInDocument();
PR_INIT_CLIST(&mDOMMediaQueryLists);
PR_INIT_CLIST(&mDOMMediaQueryLists);
}
// NOTE! nsDocument::operator new() zeroes out all members, so don't
@@ -2812,12 +2815,18 @@ nsDocument::InitCSP(nsIChannel* aChannel)
}
}
// Check if this is part of the Loop/Hello service
bool applyLoopCSP = IsLoopDocument(aChannel);
// Check if this is a document from a WebExtension.
nsString addonId;
principal->GetAddonId(addonId);
bool applyAddonCSP = !addonId.IsEmpty();
// Check if this is part of the Loop/Hello service
bool applyLoopCSP = IsLoopDocument(aChannel);
// If there's no CSP to apply, go ahead and return early
if (!applyAppDefaultCSP &&
!applyAppManifestCSP &&
!applyAddonCSP &&
!applyLoopCSP &&
cspHeaderValue.IsEmpty() &&
cspROHeaderValue.IsEmpty()) {
@@ -2875,6 +2884,22 @@ nsDocument::InitCSP(nsIChannel* aChannel)
csp->AppendPolicy(appManifestCSP, false, false);
}
// ----- if the doc is an addon, apply its CSP.
if (applyAddonCSP) {
nsCOMPtr<nsIAddonPolicyService> aps = do_GetService("@mozilla.org/addons/policy-service;1");
nsAutoString addonCSP;
rv = aps->GetBaseCSP(addonCSP);
if (NS_SUCCEEDED(rv)) {
csp->AppendPolicy(addonCSP, false, false);
}
rv = aps->GetAddonCSP(addonId, addonCSP);
if (NS_SUCCEEDED(rv)) {
csp->AppendPolicy(addonCSP, false, false);
}
}
// ----- if the doc is part of Loop, apply the loop CSP
if (applyLoopCSP) {
nsAdoptingString loopCSP;
@@ -3197,27 +3222,13 @@ nsDocument::Timeline()
void
nsDocument::GetAnimations(nsTArray<RefPtr<Animation>>& aAnimations)
{
FlushPendingNotifications(Flush_Style);
// Bug 1174575: Until we implement a suitable PseudoElement interface we
// don't have anything to return for the |target| attribute of
// KeyframeEffect(ReadOnly) objects that refer to pseudo-elements.
// Rather than return some half-baked version of these objects (e.g.
// we a null effect attribute) we simply don't provide access to animations
// whose effect refers to a pseudo-element until we can support them
// properly.
for (nsIContent* node = nsINode::GetFirstChild();
node;
node = node->GetNextNode(this)) {
if (!node->IsElement()) {
continue;
}
node->AsElement()->GetAnimationsUnsorted(aAnimations);
Element* root = GetRootElement();
if (!root) {
return;
}
// Sort animations by priority
aAnimations.Sort(AnimationPtrComparator<RefPtr<Animation>>());
AnimationFilter filter;
filter.mSubtree = true;
root->GetAnimations(filter, aAnimations);
}
/* Return true if the document is in the focused top-level window, and is an
@@ -4645,6 +4656,26 @@ nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject)
loadGroup->RemoveRequest(mOnloadBlocker, nullptr, NS_OK);
}
}
using mozilla::dom::workers::ServiceWorkerManager;
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (swm) {
ErrorResult error;
if (swm->IsControlled(this, error)) {
imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this);
if (loader) {
loader->ClearCacheForControlledDocument(this);
}
// We may become controlled again if this document comes back out
// of bfcache. Clear our state to allow that to happen. Only
// clear this flag if we are actually controlled, though, so pages
// that were force reloaded don't become controlled when they
// come out of bfcache.
mMaybeServiceWorkerControlled = false;
}
swm->MaybeStopControlling(this);
}
}
// BlockOnload() might be called before mScriptGlobalObject is set.
@@ -4758,8 +4789,13 @@ nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject)
nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
if (swm) {
nsAutoString documentId;
static_cast<nsDocShell*>(docShell.get())->GetInterceptedDocumentId(documentId);
// If this document is being resurrected from the bfcache, then we may
// already have a document ID. In that case reuse the same ID. Otherwise
// get our document ID from the docshell.
nsString documentId(GetId());
if (documentId.IsEmpty()) {
static_cast<nsDocShell*>(docShell.get())->GetInterceptedDocumentId(documentId);
}
swm->MaybeStartControlling(this, documentId);
mMaybeServiceWorkerControlled = true;
@@ -8891,6 +8927,7 @@ nsDocument::Destroy()
mIsGoingAway = true;
SetScriptGlobalObject(nullptr);
RemovedFromDocShell();
bool oldVal = mInUnlinkOrDeletion;
@@ -8921,19 +8958,6 @@ nsDocument::RemovedFromDocShell()
if (mRemovedFromDocShell)
return;
using mozilla::dom::workers::ServiceWorkerManager;
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (swm) {
ErrorResult error;
if (swm->IsControlled(this, error)) {
imgLoader* loader = nsContentUtils::GetImgLoaderForDocument(this);
if (loader) {
loader->ClearCacheForControlledDocument(this);
}
}
swm->MaybeStopControlling(this);
}
mRemovedFromDocShell = true;
EnumerateActivityObservers(NotifyActivityChanged, nullptr);
@@ -13127,7 +13151,7 @@ nsDocument::ReportUseCounters()
for (int32_t c = 0;
c < eUseCounter_Count; ++c) {
UseCounter uc = static_cast<UseCounter>(c);
Telemetry::ID id =
static_cast<Telemetry::ID>(Telemetry::HistogramFirstUseCounter + uc * 2);
bool value = GetUseCounter(uc);
@@ -13270,7 +13294,9 @@ nsIDocument::GetOrCreateId(nsAString& aId)
void
nsIDocument::SetId(const nsAString& aId)
{
MOZ_ASSERT(mId.IsEmpty(), "Cannot set the document ID after we have one");
// The ID should only be set one time, but we may get the same value
// more than once if the document is controlled coming out of bfcache.
MOZ_ASSERT_IF(mId != aId, mId.IsEmpty());
mId = aId;
}
+5
View File
@@ -2844,6 +2844,11 @@ protected:
FlushUserFontSet();
}
const nsString& GetId() const
{
return mId;
}
nsCString mReferrer;
nsString mLastModified;
+4 -2
View File
@@ -92,5 +92,7 @@ MSG_DEF(MSG_SW_INSTALL_ERROR, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for
MSG_DEF(MSG_SW_SCRIPT_THREW, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} threw an exception during script evaluation.")
MSG_DEF(MSG_TYPEDARRAY_IS_SHARED, 1, JSEXN_TYPEERR, "{0} can't be a typed array on SharedArrayBuffer")
MSG_DEF(MSG_CACHE_ADD_FAILED_RESPONSE, 3, JSEXN_TYPEERR, "Cache got {0} response with bad status {1} while trying to add request {2}")
MSG_DEF(MSG_INVALID_DURATION_ERROR, 0, JSEXN_TYPEERR, "Invalid duration.")
MSG_DEF(MSG_INVALID_EASING_ERROR, 0, JSEXN_TYPEERR, "Invalid easing.")
MSG_DEF(MSG_SW_UPDATE_BAD_REGISTRATION, 2, JSEXN_TYPEERR, "Failed to update the ServiceWorker for scope {0] because the registration has been {1} since the update was scheduled.")
MSG_DEF(MSG_INVALID_DURATION_ERROR, 1, JSEXN_TYPEERR, "Invalid duration '{0}'.")
MSG_DEF(MSG_INVALID_EASING_ERROR, 1, JSEXN_TYPEERR, "Invalid easing '{0}'.")
MSG_DEF(MSG_USELESS_SETTIMEOUT, 1, JSEXN_TYPEERR, "Useless {0} call (missing quotes around argument?)")
@@ -21,6 +21,12 @@ interface nsIURI;
typedef unsigned short CSPDirective;
%{C++
class nsCSPPolicy;
%}
[ptr] native CSPPolicyPtr(const nsCSPPolicy);
[scriptable, builtinclass, uuid(b3c4c0ae-bd5e-4cad-87e0-8d210dbb3f9f)]
interface nsIContentSecurityPolicy : nsISerializable
{
@@ -60,6 +66,13 @@ interface nsIContentSecurityPolicy : nsISerializable
*/
AString getPolicy(in unsigned long index);
/**
* Accessor method for a read-only pointer the policy object at a given
* index. Returns a null pointer if the index is larger than the current
* policy count.
*/
[noscript,notxpcom,nostdcall] CSPPolicyPtr GetPolicy(in unsigned long index);
/**
* Returns the number of policies attached to this CSP instance. Useful with
* getPolicy().
@@ -190,6 +190,8 @@ InterceptionCanceledWithURL=Failed to load '%S'. A ServiceWorker canceled the lo
InterceptionRejectedResponseWithURL=Failed to load '%1$S'. A ServiceWorker passed a promise to FetchEvent.respondWith() that rejected with '%2$S'.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "promise", "FetchEvent.respondWith()", or "Response". %1$S is a URL. %2$S is an error string.
InterceptedNonResponseWithURL=Failed to load '%1$S'. A ServiceWorker passed a promise to FetchEvent.respondWith() that resolved with non-Response value '%2$S'.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Service-Worker-Allowed" or "HTTP". %1$S and %2$S are URLs.
ServiceWorkerScopePathMismatch=Failed to register a ServiceWorker: The path of the provided scope '%1$S' is not under the max scope allowed '%2$S'. Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.
ExecCommandCutCopyDeniedNotInputDriven=document.execCommand('cut'/'copy') was denied because it was not called from inside a short running user-generated event handler.
PatternAttributeCompileFailure=Unable to check <input pattern='%S'> because the pattern is not a valid regexp: %S
# LOCALIZATION NOTE: Do not translate "postMessage" or DOMWindow. %S values are origins, like https://domain.com:port
+2 -2
View File
@@ -2575,8 +2575,8 @@ public:
// This is coming from a ServiceWorkerRegistrationWorkerThread.
MOZ_ASSERT(registration);
if (!registration->mActiveWorker ||
registration->mActiveWorker->ID() != mWorkerPrivate->ServiceWorkerID()) {
if (!registration->GetActive() ||
registration->GetActive()->ID() != mWorkerPrivate->ServiceWorkerID()) {
mRv = NS_ERROR_NOT_AVAILABLE;
}
+76 -21
View File
@@ -909,7 +909,11 @@ Promise::MaybeRejectWithNull()
bool
Promise::PerformMicroTaskCheckpoint()
{
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
// On the main thread, we always use the main promise micro task queue.
std::queue<nsCOMPtr<nsIRunnable>>& microtaskQueue =
runtime->GetPromiseMicroTaskQueue();
@@ -917,10 +921,7 @@ Promise::PerformMicroTaskCheckpoint()
return false;
}
Maybe<AutoSafeJSContext> cx;
if (NS_IsMainThread()) {
cx.emplace();
}
AutoSafeJSContext cx;
do {
nsCOMPtr<nsIRunnable> runnable = microtaskQueue.front();
@@ -932,15 +933,77 @@ Promise::PerformMicroTaskCheckpoint()
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
if (cx.isSome()) {
JS_CheckForInterrupt(cx.ref());
}
JS_CheckForInterrupt(cx);
runtime->AfterProcessMicrotask();
} while (!microtaskQueue.empty());
return true;
}
void
Promise::PerformWorkerMicroTaskCheckpoint()
{
MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
for (;;) {
// For a normal microtask checkpoint, we try to use the debugger microtask
// queue first. If the debugger queue is empty, we use the normal microtask
// queue instead.
std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
&runtime->GetDebuggerPromiseMicroTaskQueue();
if (microtaskQueue->empty()) {
microtaskQueue = &runtime->GetPromiseMicroTaskQueue();
if (microtaskQueue->empty()) {
break;
}
}
nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front();
MOZ_ASSERT(runnable);
// This function can re-enter, so we remove the element before calling.
microtaskQueue->pop();
nsresult rv = runnable->Run();
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
runtime->AfterProcessMicrotask();
}
}
void
Promise::PerformWorkerDebuggerMicroTaskCheckpoint()
{
MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
for (;;) {
// For a debugger microtask checkpoint, we always use the debugger microtask
// queue.
std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
&runtime->GetDebuggerPromiseMicroTaskQueue();
if (microtaskQueue->empty()) {
break;
}
nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front();
MOZ_ASSERT(runnable);
// This function can re-enter, so we remove the element before calling.
microtaskQueue->pop();
nsresult rv = runnable->Run();
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
runtime->AfterProcessMicrotask();
}
}
#ifndef SPIDERMONKEY_PROMISE
/* static */ bool
@@ -2402,18 +2465,6 @@ Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
}
#endif // SPIDERMONKEY_PROMISE
/* static */ void
Promise::DispatchToMicroTask(nsIRunnable* aRunnable)
{
MOZ_ASSERT(aRunnable);
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
std::queue<nsCOMPtr<nsIRunnable>>& microtaskQueue =
runtime->GetPromiseMicroTaskQueue();
microtaskQueue.push(aRunnable);
}
#ifndef SPIDERMONKEY_PROMISE
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
void
@@ -2504,6 +2555,8 @@ void
Promise::ResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
mResolvePending = true;
if (aValue.isObject()) {
@@ -2545,7 +2598,7 @@ Promise::ResolveInternal(JSContext* aCx,
new PromiseInit(nullptr, thenObj, mozilla::dom::GetIncumbentGlobal());
RefPtr<PromiseResolveThenableJob> task =
new PromiseResolveThenableJob(this, valueObj, thenCallback);
DispatchToMicroTask(task);
runtime->DispatchToMicroTask(task);
return;
}
}
@@ -2635,6 +2688,8 @@ Promise::MaybeSettle(JS::Handle<JS::Value> aValue,
void
Promise::TriggerPromiseReactions()
{
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
nsTArray<RefPtr<PromiseCallback>> callbacks;
callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
: mRejectCallbacks);
@@ -2644,7 +2699,7 @@ Promise::TriggerPromiseReactions()
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
RefPtr<PromiseReactionJob> task =
new PromiseReactionJob(this, callbacks[i], mResult);
DispatchToMicroTask(task);
runtime->DispatchToMicroTask(task);
}
}
+4 -4
View File
@@ -166,6 +166,10 @@ public:
// Returns true if at least one microtask was processed.
static bool PerformMicroTaskCheckpoint();
static void PerformWorkerMicroTaskCheckpoint();
static void PerformWorkerDebuggerMicroTaskCheckpoint();
// WebIDL
nsIGlobalObject* GetParentObject() const
@@ -288,10 +292,6 @@ public:
uint64_t GetID();
#endif // SPIDERMONKEY_PROMISE
// Queue an async microtask to current main or worker thread.
static void
DispatchToMicroTask(nsIRunnable* aRunnable);
#ifndef SPIDERMONKEY_PROMISE
enum JSCallbackSlots {
SLOT_PROMISE = 0,
+9
View File
@@ -299,6 +299,15 @@ nsCSPContext::GetPolicy(uint32_t aIndex, nsAString& outStr)
return NS_OK;
}
const nsCSPPolicy*
nsCSPContext::GetPolicy(uint32_t aIndex)
{
if (aIndex >= mPolicies.Length()) {
return nullptr;
}
return mPolicies[aIndex];
}
NS_IMETHODIMP
nsCSPContext::GetPolicyCount(uint32_t *outPolicyCount)
{
+58
View File
@@ -389,6 +389,12 @@ nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirect
return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure);
}
bool
nsCSPSchemeSrc::visit(nsCSPSrcVisitor* aVisitor) const
{
return aVisitor->visitSchemeSrc(*this);
}
void
nsCSPSchemeSrc::toString(nsAString& outStr) const
{
@@ -597,6 +603,12 @@ nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected
return true;
}
bool
nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const
{
return aVisitor->visitHostSrc(*this);
}
void
nsCSPHostSrc::toString(nsAString& outStr) const
{
@@ -674,6 +686,12 @@ nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce)
return mKeyword == aKeyword;
}
bool
nsCSPKeywordSrc::visit(nsCSPSrcVisitor* aVisitor) const
{
return aVisitor->visitKeywordSrc(*this);
}
void
nsCSPKeywordSrc::toString(nsAString& outStr) const
{
@@ -730,6 +748,12 @@ nsCSPNonceSrc::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) c
return mNonce.Equals(aHashOrNonce);
}
bool
nsCSPNonceSrc::visit(nsCSPSrcVisitor* aVisitor) const
{
return aVisitor->visitNonceSrc(*this);
}
void
nsCSPNonceSrc::toString(nsAString& outStr) const
{
@@ -787,6 +811,12 @@ nsCSPHashSrc::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) co
return NS_ConvertUTF16toUTF8(mHash).Equals(hash);
}
bool
nsCSPHashSrc::visit(nsCSPSrcVisitor* aVisitor) const
{
return aVisitor->visitHashSrc(*this);
}
void
nsCSPHashSrc::toString(nsAString& outStr) const
{
@@ -808,6 +838,12 @@ nsCSPReportURI::~nsCSPReportURI()
{
}
bool
nsCSPReportURI::visit(nsCSPSrcVisitor* aVisitor) const
{
return false;
}
void
nsCSPReportURI::toString(nsAString& outStr) const
{
@@ -1012,6 +1048,17 @@ nsCSPDirective::getReportURIs(nsTArray<nsString> &outReportURIs) const
}
}
bool
nsCSPDirective::visitSrcs(nsCSPSrcVisitor* aVisitor) const
{
for (uint32_t i = 0; i < mSrcs.Length(); i++) {
if (!mSrcs[i]->visit(aVisitor)) {
return false;
}
}
return true;
}
bool nsCSPDirective::equals(CSPDirective aDirective) const
{
return (mDirective == aDirective);
@@ -1320,3 +1367,14 @@ nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const
}
}
}
bool
nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir, nsCSPSrcVisitor* aVisitor) const
{
for (uint32_t i = 0; i < mDirectives.Length(); i++) {
if (mDirectives[i]->equals(aDir)) {
return mDirectives[i]->visitSrcs(aVisitor);
}
}
return false;
}
+63 -1
View File
@@ -145,7 +145,11 @@ inline const char* CSP_EnumToKeyword(enum CSPKeyword aKey)
static_assert((sizeof(CSPStrKeywords) / sizeof(CSPStrKeywords[0]) ==
static_cast<uint32_t>(CSP_LAST_KEYWORD_VALUE)),
"CSP_LAST_KEYWORD_VALUE does not match length of CSPStrKeywords");
return CSPStrKeywords[static_cast<uint32_t>(aKey)];
if (static_cast<uint32_t>(aKey) < static_cast<uint32_t>(CSP_LAST_KEYWORD_VALUE)) {
return CSPStrKeywords[static_cast<uint32_t>(aKey)];
}
return "error: invalid keyword in CSP_EnumToKeyword";
}
inline CSPKeyword CSP_KeywordToEnum(const nsAString& aKey)
@@ -177,6 +181,7 @@ bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey);
bool CSP_IsQuotelessKeyword(const nsAString& aKey);
CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType);
class nsCSPSrcVisitor;
/* =============== nsCSPSrc ================== */
@@ -188,6 +193,7 @@ class nsCSPBaseSrc {
virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
bool aReportOnly, bool aUpgradeInsecure) const;
virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
virtual bool visit(nsCSPSrcVisitor* aVisitor) const = 0;
virtual void toString(nsAString& outStr) const = 0;
};
@@ -200,8 +206,12 @@ class nsCSPSchemeSrc : public nsCSPBaseSrc {
bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
bool aReportOnly, bool aUpgradeInsecure) const;
bool visit(nsCSPSrcVisitor* aVisitor) const;
void toString(nsAString& outStr) const;
inline void getScheme(nsAString& outStr) const
{ outStr.Assign(mScheme); };
private:
nsString mScheme;
};
@@ -215,12 +225,25 @@ class nsCSPHostSrc : public nsCSPBaseSrc {
bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
bool aReportOnly, bool aUpgradeInsecure) const;
bool visit(nsCSPSrcVisitor* aVisitor) const;
void toString(nsAString& outStr) const;
void setScheme(const nsAString& aScheme);
void setPort(const nsAString& aPort);
void appendPath(const nsAString &aPath);
inline void getScheme(nsAString& outStr) const
{ outStr.Assign(mScheme); };
inline void getHost(nsAString& outStr) const
{ outStr.Assign(mHost); };
inline void getPort(nsAString& outStr) const
{ outStr.Assign(mPort); };
inline void getPath(nsAString& outStr) const
{ outStr.Assign(mPath); };
private:
nsString mScheme;
nsString mHost;
@@ -236,9 +259,13 @@ class nsCSPKeywordSrc : public nsCSPBaseSrc {
virtual ~nsCSPKeywordSrc();
bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
bool visit(nsCSPSrcVisitor* aVisitor) const;
void toString(nsAString& outStr) const;
void invalidate();
inline CSPKeyword getKeyword() const
{ return mKeyword; };
private:
CSPKeyword mKeyword;
// invalidate 'unsafe-inline' if nonce- or hash-source specified
@@ -255,8 +282,12 @@ class nsCSPNonceSrc : public nsCSPBaseSrc {
bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
bool aReportOnly, bool aUpgradeInsecure) const;
bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
bool visit(nsCSPSrcVisitor* aVisitor) const;
void toString(nsAString& outStr) const;
inline void getNonce(nsAString& outStr) const
{ outStr.Assign(mNonce); };
private:
nsString mNonce;
};
@@ -270,6 +301,13 @@ class nsCSPHashSrc : public nsCSPBaseSrc {
bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
void toString(nsAString& outStr) const;
bool visit(nsCSPSrcVisitor* aVisitor) const;
inline void getAlgorithm(nsAString& outStr) const
{ outStr.Assign(mAlgorithm); };
inline void getHash(nsAString& outStr) const
{ outStr.Assign(mHash); };
private:
nsString mAlgorithm;
@@ -283,12 +321,32 @@ class nsCSPReportURI : public nsCSPBaseSrc {
explicit nsCSPReportURI(nsIURI* aURI);
virtual ~nsCSPReportURI();
bool visit(nsCSPSrcVisitor* aVisitor) const;
void toString(nsAString& outStr) const;
private:
nsCOMPtr<nsIURI> mReportURI;
};
/* =============== nsCSPSrcVisitor ================== */
class nsCSPSrcVisitor {
public:
virtual bool visitSchemeSrc(const nsCSPSchemeSrc& src) = 0;
virtual bool visitHostSrc(const nsCSPHostSrc& src) = 0;
virtual bool visitKeywordSrc(const nsCSPKeywordSrc& src) = 0;
virtual bool visitNonceSrc(const nsCSPNonceSrc& src) = 0;
virtual bool visitHashSrc(const nsCSPHashSrc& src) = 0;
protected:
explicit nsCSPSrcVisitor() {};
virtual ~nsCSPSrcVisitor() {};
};
/* =============== nsCSPDirective ============= */
class nsCSPDirective {
@@ -314,6 +372,8 @@ class nsCSPDirective {
void getReportURIs(nsTArray<nsString> &outReportURIs) const;
bool visitSrcs(nsCSPSrcVisitor* aVisitor) const;
private:
CSPDirective mDirective;
nsTArray<nsCSPBaseSrc*> mSrcs;
@@ -476,6 +536,8 @@ class nsCSPPolicy {
inline uint32_t getNumDirectives() const
{ return mDirectives.Length(); }
bool visitDirectiveSrcs(CSPDirective aDir, nsCSPSrcVisitor* aVisitor) const;
private:
nsUpgradeInsecureDirective* mUpgradeInsecDir;
nsTArray<nsCSPDirective*> mDirectives;
+5 -1
View File
@@ -14,6 +14,10 @@ dictionary KeyframeAnimationOptions : KeyframeEffectOptions {
DOMString id = "";
};
dictionary AnimationFilter {
boolean subtree = false;
};
[NoInterfaceObject]
interface Animatable {
[Func="nsDocument::IsWebAnimationsEnabled", Throws]
@@ -21,5 +25,5 @@ interface Animatable {
optional (unrestricted double or KeyframeAnimationOptions)
options);
[Func="nsDocument::IsWebAnimationsEnabled"]
sequence<Animation> getAnimations();
sequence<Animation> getAnimations(optional AnimationFilter filter);
};
+1 -11
View File
@@ -29,16 +29,6 @@ interface ChromeUtils : ThreadSafeChromeUtils {
originAttributesMatchPattern(optional OriginAttributesDictionary originAttrs,
optional OriginAttributesPatternDictionary pattern);
/**
* Returns an OriginAttributesDictionary with all default attributes added
* and assigned default values.
*
* @returns An OriginAttributesDictionary populated with the
* default attributes added and assigned default values.
*/
static OriginAttributesDictionary
createDefaultOriginAttributes();
/**
* Returns an OriginAttributesDictionary with values from the |origin| suffix
* and unspecified attributes added and assigned default values.
@@ -62,7 +52,7 @@ interface ChromeUtils : ThreadSafeChromeUtils {
* default values.
*/
static OriginAttributesDictionary
createOriginAttributesFromDict(optional OriginAttributesDictionary originAttrs);
fillNonDefaultOriginAttributes(optional OriginAttributesDictionary originAttrs);
};
/**
+1 -1
View File
@@ -22,7 +22,7 @@ callback AnyCallback = any (any value);
// values work.
#ifndef SPIDERMONKEY_PROMISE
[Constructor(PromiseInit init),
Exposed=(Window,Worker,System)]
Exposed=(Window,Worker,WorkerDebugger,System)]
// Need to escape "Promise" so it's treated as an identifier.
interface _Promise {
// Have to use "any" (or "object", but "any" is simpler) as the type to
+32 -2
View File
@@ -880,8 +880,10 @@ public:
~WorkerJSRuntime()
{
JSRuntime* rt = Runtime();
MOZ_ASSERT(rt);
JSRuntime* rt = MaybeRuntime();
if (!rt) {
return; // Initialize() must have failed
}
delete static_cast<WorkerThreadRuntimePrivate*>(JS_GetRuntimePrivate(rt));
JS_SetRuntimePrivate(rt, nullptr);
@@ -969,6 +971,34 @@ public:
}
}
virtual void DispatchToMicroTask(nsIRunnable* aRunnable) override
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aRunnable);
std::queue<nsCOMPtr<nsIRunnable>>* microTaskQueue = nullptr;
JSContext* cx = GetCurrentThreadJSContext();
NS_ASSERTION(cx, "This should never be null!");
JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
NS_ASSERTION(global, "This should never be null!");
// On worker threads, if the current global is the worker global, we use the
// main promise micro task queue. Otherwise, the current global must be
// either the debugger global or a debugger sandbox, and we use the debugger
// promise micro task queue instead.
if (IsWorkerGlobal(global)) {
microTaskQueue = &mPromiseMicroTaskQueue;
} else {
MOZ_ASSERT(IsDebuggerGlobal(global) || IsDebuggerSandbox(global));
microTaskQueue = &mDebuggerPromiseMicroTaskQueue;
}
microTaskQueue->push(aRunnable);
}
private:
WorkerPrivate* mWorkerPrivate;
};
+205
View File
@@ -0,0 +1,205 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ServiceWorkerInfo.h"
#include "ServiceWorkerScriptCache.h"
BEGIN_WORKERS_NAMESPACE
NS_IMPL_ISUPPORTS(ServiceWorkerInfo, nsIServiceWorkerInfo)
NS_IMETHODIMP
ServiceWorkerInfo::GetScriptSpec(nsAString& aScriptSpec)
{
AssertIsOnMainThread();
CopyUTF8toUTF16(mScriptSpec, aScriptSpec);
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerInfo::GetCacheName(nsAString& aCacheName)
{
AssertIsOnMainThread();
aCacheName = mCacheName;
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerInfo::GetDebugger(nsIWorkerDebugger** aResult)
{
if (NS_WARN_IF(!aResult)) {
return NS_ERROR_FAILURE;
}
return mServiceWorkerPrivate->GetDebugger(aResult);
}
NS_IMETHODIMP
ServiceWorkerInfo::AttachDebugger()
{
return mServiceWorkerPrivate->AttachDebugger();
}
NS_IMETHODIMP
ServiceWorkerInfo::DetachDebugger()
{
return mServiceWorkerPrivate->DetachDebugger();
}
void
ServiceWorkerInfo::AppendWorker(ServiceWorker* aWorker)
{
MOZ_ASSERT(aWorker);
#ifdef DEBUG
nsAutoString workerURL;
aWorker->GetScriptURL(workerURL);
MOZ_ASSERT(workerURL.Equals(NS_ConvertUTF8toUTF16(mScriptSpec)));
#endif
MOZ_ASSERT(!mInstances.Contains(aWorker));
mInstances.AppendElement(aWorker);
aWorker->SetState(State());
}
void
ServiceWorkerInfo::RemoveWorker(ServiceWorker* aWorker)
{
MOZ_ASSERT(aWorker);
#ifdef DEBUG
nsAutoString workerURL;
aWorker->GetScriptURL(workerURL);
MOZ_ASSERT(workerURL.Equals(NS_ConvertUTF8toUTF16(mScriptSpec)));
#endif
MOZ_ASSERT(mInstances.Contains(aWorker));
mInstances.RemoveElement(aWorker);
}
namespace {
class ChangeStateUpdater final : public nsRunnable
{
public:
ChangeStateUpdater(const nsTArray<ServiceWorker*>& aInstances,
ServiceWorkerState aState)
: mState(aState)
{
for (size_t i = 0; i < aInstances.Length(); ++i) {
mInstances.AppendElement(aInstances[i]);
}
}
NS_IMETHODIMP Run()
{
// We need to update the state of all instances atomically before notifying
// them to make sure that the observed state for all instances inside
// statechange event handlers is correct.
for (size_t i = 0; i < mInstances.Length(); ++i) {
mInstances[i]->SetState(mState);
}
for (size_t i = 0; i < mInstances.Length(); ++i) {
mInstances[i]->DispatchStateChange(mState);
}
return NS_OK;
}
private:
AutoTArray<RefPtr<ServiceWorker>, 1> mInstances;
ServiceWorkerState mState;
};
}
void
ServiceWorkerInfo::UpdateState(ServiceWorkerState aState)
{
AssertIsOnMainThread();
#ifdef DEBUG
// Any state can directly transition to redundant, but everything else is
// ordered.
if (aState != ServiceWorkerState::Redundant) {
MOZ_ASSERT_IF(mState == ServiceWorkerState::EndGuard_, aState == ServiceWorkerState::Installing);
MOZ_ASSERT_IF(mState == ServiceWorkerState::Installing, aState == ServiceWorkerState::Installed);
MOZ_ASSERT_IF(mState == ServiceWorkerState::Installed, aState == ServiceWorkerState::Activating);
MOZ_ASSERT_IF(mState == ServiceWorkerState::Activating, aState == ServiceWorkerState::Activated);
}
// Activated can only go to redundant.
MOZ_ASSERT_IF(mState == ServiceWorkerState::Activated, aState == ServiceWorkerState::Redundant);
#endif
// Flush any pending functional events to the worker when it transitions to the
// activated state.
// TODO: Do we care that these events will race with the propagation of the
// state change?
if (aState == ServiceWorkerState::Activated && mState != aState) {
mServiceWorkerPrivate->Activated();
}
mState = aState;
nsCOMPtr<nsIRunnable> r = new ChangeStateUpdater(mInstances, mState);
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r.forget()));
if (mState == ServiceWorkerState::Redundant) {
serviceWorkerScriptCache::PurgeCache(mPrincipal, mCacheName);
}
}
ServiceWorkerInfo::ServiceWorkerInfo(nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec,
const nsAString& aCacheName)
: mPrincipal(aPrincipal)
, mScope(aScope)
, mScriptSpec(aScriptSpec)
, mCacheName(aCacheName)
, mState(ServiceWorkerState::EndGuard_)
, mServiceWorkerID(GetNextID())
, mServiceWorkerPrivate(new ServiceWorkerPrivate(this))
, mSkipWaitingFlag(false)
{
MOZ_ASSERT(mPrincipal);
MOZ_ASSERT(!mScope.IsEmpty());
MOZ_ASSERT(!mScriptSpec.IsEmpty());
MOZ_ASSERT(!mCacheName.IsEmpty());
}
ServiceWorkerInfo::~ServiceWorkerInfo()
{
MOZ_ASSERT(mServiceWorkerPrivate);
mServiceWorkerPrivate->NoteDeadServiceWorkerInfo();
}
static uint64_t gServiceWorkerInfoCurrentID = 0;
uint64_t
ServiceWorkerInfo::GetNextID() const
{
return ++gServiceWorkerInfoCurrentID;
}
already_AddRefed<ServiceWorker>
ServiceWorkerInfo::GetOrCreateInstance(nsPIDOMWindow* aWindow)
{
AssertIsOnMainThread();
MOZ_ASSERT(aWindow);
RefPtr<ServiceWorker> ref;
for (uint32_t i = 0; i < mInstances.Length(); ++i) {
MOZ_ASSERT(mInstances[i]);
if (mInstances[i]->GetOwner() == aWindow) {
ref = mInstances[i];
break;
}
}
if (!ref) {
ref = new ServiceWorker(aWindow, this);
}
return ref.forget();
}
END_WORKERS_NAMESPACE
+144
View File
@@ -0,0 +1,144 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_workers_serviceworkerinfo_h
#define mozilla_dom_workers_serviceworkerinfo_h
#include "mozilla/dom/ServiceWorkerBinding.h" // For ServiceWorkerState
#include "nsIServiceWorkerManager.h"
namespace mozilla {
namespace dom {
namespace workers {
class ServiceWorker;
class ServiceWorkerPrivate;
/*
* Wherever the spec treats a worker instance and a description of said worker
* as the same thing; i.e. "Resolve foo with
* _GetNewestWorker(serviceWorkerRegistration)", we represent the description
* by this class and spawn a ServiceWorker in the right global when required.
*/
class ServiceWorkerInfo final : public nsIServiceWorkerInfo
{
private:
nsCOMPtr<nsIPrincipal> mPrincipal;
const nsCString mScope;
const nsCString mScriptSpec;
const nsString mCacheName;
ServiceWorkerState mState;
// This id is shared with WorkerPrivate to match requests issued by service
// workers to their corresponding serviceWorkerInfo.
uint64_t mServiceWorkerID;
// We hold rawptrs since the ServiceWorker constructor and destructor ensure
// addition and removal.
// There is a high chance of there being at least one ServiceWorker
// associated with this all the time.
AutoTArray<ServiceWorker*, 1> mInstances;
RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
bool mSkipWaitingFlag;
~ServiceWorkerInfo();
// Generates a unique id for the service worker, with zero being treated as
// invalid.
uint64_t
GetNextID() const;
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISERVICEWORKERINFO
class ServiceWorkerPrivate*
WorkerPrivate() const
{
MOZ_ASSERT(mServiceWorkerPrivate);
return mServiceWorkerPrivate;
}
nsIPrincipal*
GetPrincipal() const
{
return mPrincipal;
}
const nsCString&
ScriptSpec() const
{
return mScriptSpec;
}
const nsCString&
Scope() const
{
return mScope;
}
bool SkipWaitingFlag() const
{
AssertIsOnMainThread();
return mSkipWaitingFlag;
}
void SetSkipWaitingFlag()
{
AssertIsOnMainThread();
mSkipWaitingFlag = true;
}
ServiceWorkerInfo(nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec,
const nsAString& aCacheName);
ServiceWorkerState
State() const
{
return mState;
}
const nsString&
CacheName() const
{
return mCacheName;
}
uint64_t
ID() const
{
return mServiceWorkerID;
}
void
UpdateState(ServiceWorkerState aState);
// Only used to set initial state when loading from disk!
void
SetActivateStateUncheckedWithoutEvent(ServiceWorkerState aState)
{
AssertIsOnMainThread();
mState = aState;
}
void
AppendWorker(ServiceWorker* aWorker);
void
RemoveWorker(ServiceWorker* aWorker);
already_AddRefed<ServiceWorker>
GetOrCreateInstance(nsPIDOMWindow* aWindow);
};
} // namespace workers
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_workers_serviceworkerinfo_h
+231
View File
@@ -0,0 +1,231 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ServiceWorkerJob.h"
#include "nsProxyRelease.h"
#include "nsThreadUtils.h"
#include "Workers.h"
namespace mozilla {
namespace dom {
namespace workers {
ServiceWorkerJob::Type
ServiceWorkerJob::GetType() const
{
return mType;
}
ServiceWorkerJob::State
ServiceWorkerJob::GetState() const
{
return mState;
}
bool
ServiceWorkerJob::Canceled() const
{
return mCanceled;
}
bool
ServiceWorkerJob::ResultCallbacksInvoked() const
{
return mResultCallbacksInvoked;
}
bool
ServiceWorkerJob::IsEquivalentTo(ServiceWorkerJob* aJob) const
{
AssertIsOnMainThread();
MOZ_ASSERT(aJob);
return mType == aJob->mType &&
mScope.Equals(aJob->mScope) &&
mScriptSpec.Equals(aJob->mScriptSpec) &&
mPrincipal->Equals(aJob->mPrincipal);
}
void
ServiceWorkerJob::AppendResultCallback(Callback* aCallback)
{
AssertIsOnMainThread();
MOZ_ASSERT(mState != State::Finished);
MOZ_ASSERT(aCallback);
MOZ_ASSERT(mFinalCallback != aCallback);
MOZ_ASSERT(!mResultCallbackList.Contains(aCallback));
MOZ_ASSERT(!mResultCallbacksInvoked);
mResultCallbackList.AppendElement(aCallback);
}
void
ServiceWorkerJob::StealResultCallbacksFrom(ServiceWorkerJob* aJob)
{
AssertIsOnMainThread();
MOZ_ASSERT(aJob);
MOZ_ASSERT(aJob->mState == State::Initial);
// Take the callbacks from the other job immediately to avoid the
// any possibility of them existing on both jobs at once.
nsTArray<RefPtr<Callback>> callbackList;
callbackList.SwapElements(aJob->mResultCallbackList);
for (RefPtr<Callback>& callback : callbackList) {
// Use AppendResultCallback() so that assertion checking is performed on
// each callback.
AppendResultCallback(callback);
}
}
void
ServiceWorkerJob::Start(Callback* aFinalCallback)
{
AssertIsOnMainThread();
MOZ_ASSERT(!mCanceled);
MOZ_ASSERT(aFinalCallback);
MOZ_ASSERT(!mFinalCallback);
MOZ_ASSERT(!mResultCallbackList.Contains(aFinalCallback));
mFinalCallback = aFinalCallback;
MOZ_ASSERT(mState == State::Initial);
mState = State::Started;
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &ServiceWorkerJob::AsyncExecute);
// We may have to wait for the PBackground actor to be initialized
// before proceeding. We should always be able to get a ServiceWorkerManager,
// however, since Start() should not be called during shutdown.
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (!swm->HasBackgroundActor()) {
swm->AppendPendingOperation(runnable);
return;
}
// Otherwise start asynchronously. We should never run a job synchronously.
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
NS_DispatchToMainThread(runnable.forget())));
}
void
ServiceWorkerJob::Cancel()
{
AssertIsOnMainThread();
MOZ_ASSERT(!mCanceled);
mCanceled = true;
}
ServiceWorkerJob::ServiceWorkerJob(Type aType,
nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec)
: mType(aType)
, mPrincipal(aPrincipal)
, mScope(aScope)
, mScriptSpec(aScriptSpec)
, mState(State::Initial)
, mCanceled(false)
, mResultCallbacksInvoked(false)
{
AssertIsOnMainThread();
MOZ_ASSERT(mPrincipal);
MOZ_ASSERT(!mScope.IsEmpty());
// Some job types may have an empty script spec
}
ServiceWorkerJob::~ServiceWorkerJob()
{
AssertIsOnMainThread();
// Jobs must finish or never be started. Destroying an actively running
// job is an error.
MOZ_ASSERT(mState != State::Started);
MOZ_ASSERT_IF(mState == State::Finished, mResultCallbacksInvoked);
}
void
ServiceWorkerJob::InvokeResultCallbacks(ErrorResult& aRv)
{
AssertIsOnMainThread();
MOZ_ASSERT(mState == State::Started);
MOZ_ASSERT(!mResultCallbacksInvoked);
mResultCallbacksInvoked = true;
nsTArray<RefPtr<Callback>> callbackList;
callbackList.SwapElements(mResultCallbackList);
for (RefPtr<Callback>& callback : callbackList) {
// The callback might consume an exception on the ErrorResult, so we need
// to clone in order to maintain the error for the next callback.
ErrorResult rv;
aRv.CloneTo(rv);
callback->JobFinished(this, rv);
// The callback might not consume the error.
rv.SuppressException();
}
}
void
ServiceWorkerJob::InvokeResultCallbacks(nsresult aRv)
{
ErrorResult converted(aRv);
InvokeResultCallbacks(converted);
}
void
ServiceWorkerJob::Finish(ErrorResult& aRv)
{
AssertIsOnMainThread();
MOZ_ASSERT(mState == State::Started);
// Ensure that we only surface SecurityErr, TypeErr or InvalidStateErr to script.
if (aRv.Failed() && !aRv.ErrorCodeIs(NS_ERROR_DOM_SECURITY_ERR) &&
!aRv.ErrorCodeIs(NS_ERROR_DOM_TYPE_ERR) &&
!aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR)) {
// Remove the old error code so we can replace it with a TypeError.
aRv.SuppressException();
NS_ConvertUTF8toUTF16 scriptSpec(mScriptSpec);
NS_ConvertUTF8toUTF16 scope(mScope);
// Throw the type error with a generic error message.
aRv.ThrowTypeError<MSG_SW_INSTALL_ERROR>(scriptSpec, scope);
}
// The final callback may drop the last ref to this object.
RefPtr<ServiceWorkerJob> kungFuDeathGrip = this;
if (!mResultCallbacksInvoked) {
InvokeResultCallbacks(aRv);
}
mState = State::Finished;
mFinalCallback->JobFinished(this, aRv);
mFinalCallback = nullptr;
// The callback might not consume the error.
aRv.SuppressException();
// Async release this object to ensure that our caller methods complete
// as well.
NS_ReleaseOnMainThread(kungFuDeathGrip.forget(), true /* always proxy */);
}
void
ServiceWorkerJob::Finish(nsresult aRv)
{
ErrorResult converted(aRv);
Finish(converted);
}
} // namespace workers
} // namespace dom
} // namespace mozilla
+155
View File
@@ -0,0 +1,155 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_workers_serviceworkerjob_h
#define mozilla_dom_workers_serviceworkerjob_h
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsTArray.h"
class nsIPrincipal;
namespace mozilla {
class ErrorResult;
namespace dom {
namespace workers {
class ServiceWorkerJob
{
public:
// Implement this interface to receive notification when a job completes.
class Callback
{
public:
// Called once when the job completes. If the job is started, then this
// will be called. If a job is never executed due to browser shutdown,
// then this method will never be called. This method is always called
// on the main thread asynchronously after Start() completes.
virtual void JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) = 0;
NS_IMETHOD_(MozExternalRefCountType)
AddRef(void) = 0;
NS_IMETHOD_(MozExternalRefCountType)
Release(void) = 0;
};
enum class Type
{
Register,
Update,
Unregister
};
enum class State
{
Initial,
Started,
Finished
};
Type
GetType() const;
State
GetState() const;
// Determine if the job has been canceled. This does not change the
// current State, but indicates that the job should progress to Finished
// as soon as possible.
bool
Canceled() const;
// Determine if the result callbacks have already been called. This is
// equivalent to the spec checked to see if the job promise has settled.
bool
ResultCallbacksInvoked() const;
bool
IsEquivalentTo(ServiceWorkerJob* aJob) const;
// Add a callback that will be invoked when the job's result is available.
// Some job types will invoke this before the job is actually finished.
// If an early callback does not occur, then it will be called automatically
// when Finish() is called. These callbacks will be invoked while the job
// state is Started.
void
AppendResultCallback(Callback* aCallback);
// This takes ownership of any result callbacks associated with the given job
// and then appends them to this job's callback list.
void
StealResultCallbacksFrom(ServiceWorkerJob* aJob);
// Start the job. All work will be performed asynchronously on
// the main thread. The Finish() method must be called exactly
// once after this point. A final callback must be provided. It
// will be invoked after all other callbacks have been processed.
void
Start(Callback* aFinalCallback);
// Set an internal flag indicating that a started job should finish as
// soon as possible.
void
Cancel();
protected:
ServiceWorkerJob(Type aType,
nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec);
virtual ~ServiceWorkerJob();
// Invoke the result callbacks immediately. The job must be in the
// Started state. The callbacks are cleared after being invoked,
// so subsequent method calls have no effect.
void
InvokeResultCallbacks(ErrorResult& aRv);
// Convenience method that converts to ErrorResult and calls real method.
void
InvokeResultCallbacks(nsresult aRv);
// Indicate that the job has completed. The must be called exactly
// once after Start() has initiated job execution. It may not be
// called until Start() has returned.
void
Finish(ErrorResult& aRv);
// Convenience method that converts to ErrorResult and calls real method.
void
Finish(nsresult aRv);
// Specific job types should define AsyncExecute to begin their work.
// All errors and successes must result in Finish() being called.
virtual void
AsyncExecute() = 0;
const Type mType;
nsCOMPtr<nsIPrincipal> mPrincipal;
const nsCString mScope;
const nsCString mScriptSpec;
private:
RefPtr<Callback> mFinalCallback;
nsTArray<RefPtr<Callback>> mResultCallbackList;
State mState;
bool mCanceled;
bool mResultCallbacksInvoked;
public:
NS_INLINE_DECL_REFCOUNTING(ServiceWorkerJob)
};
} // namespace workers
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_workers_serviceworkerjob_h
+134
View File
@@ -0,0 +1,134 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ServiceWorkerJobQueue.h"
#include "ServiceWorkerJob.h"
#include "Workers.h"
namespace mozilla {
namespace dom {
namespace workers {
class ServiceWorkerJobQueue::Callback final : public ServiceWorkerJob::Callback
{
RefPtr<ServiceWorkerJobQueue> mQueue;
~Callback()
{
}
public:
explicit Callback(ServiceWorkerJobQueue* aQueue)
: mQueue(aQueue)
{
AssertIsOnMainThread();
MOZ_ASSERT(mQueue);
}
virtual void
JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) override
{
AssertIsOnMainThread();
mQueue->JobFinished(aJob);
}
NS_INLINE_DECL_REFCOUNTING(ServiceWorkerJobQueue::Callback, override)
};
ServiceWorkerJobQueue::~ServiceWorkerJobQueue()
{
AssertIsOnMainThread();
MOZ_ASSERT(mJobList.IsEmpty());
}
void
ServiceWorkerJobQueue::JobFinished(ServiceWorkerJob* aJob)
{
AssertIsOnMainThread();
MOZ_ASSERT(aJob);
// XXX There are some corner cases where jobs can double-complete. Until
// we track all these down we do a non-fatal assert in debug builds and
// a runtime check to verify the queue is in the correct state.
NS_ASSERTION(!mJobList.IsEmpty(),
"Job queue should contain the job that just completed.");
NS_ASSERTION(mJobList.SafeElementAt(0, nullptr) == aJob,
"Job queue should contain the job that just completed.");
if (NS_WARN_IF(mJobList.SafeElementAt(0, nullptr) != aJob)) {
return;
}
mJobList.RemoveElementAt(0);
if (mJobList.IsEmpty()) {
return;
}
RunJob();
}
void
ServiceWorkerJobQueue::RunJob()
{
AssertIsOnMainThread();
MOZ_ASSERT(!mJobList.IsEmpty());
MOZ_ASSERT(mJobList[0]->GetState() == ServiceWorkerJob::State::Initial);
RefPtr<Callback> callback = new Callback(this);
mJobList[0]->Start(callback);
}
ServiceWorkerJobQueue::ServiceWorkerJobQueue()
{
AssertIsOnMainThread();
}
void
ServiceWorkerJobQueue::ScheduleJob(ServiceWorkerJob* aJob)
{
AssertIsOnMainThread();
MOZ_ASSERT(aJob);
MOZ_ASSERT(!mJobList.Contains(aJob));
if (mJobList.IsEmpty()) {
mJobList.AppendElement(aJob);
RunJob();
return;
}
MOZ_ASSERT(mJobList[0]->GetState() == ServiceWorkerJob::State::Started);
RefPtr<ServiceWorkerJob>& tailJob = mJobList[mJobList.Length() - 1];
if (!tailJob->ResultCallbacksInvoked() && aJob->IsEquivalentTo(tailJob)) {
tailJob->StealResultCallbacksFrom(aJob);
return;
}
mJobList.AppendElement(aJob);
}
void
ServiceWorkerJobQueue::CancelAll()
{
AssertIsOnMainThread();
for (RefPtr<ServiceWorkerJob>& job : mJobList) {
job->Cancel();
}
// Remove jobs that are queued but not started since they should never
// run after being canceled. This means throwing away all jobs except
// for the job at the front of the list.
if (!mJobList.IsEmpty()) {
MOZ_ASSERT(mJobList[0]->GetState() == ServiceWorkerJob::State::Started);
mJobList.TruncateLength(1);
}
}
} // namespace workers
} // namespace dom
} // namespace mozilla
+49
View File
@@ -0,0 +1,49 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_workers_serviceworkerjobqueue_h
#define mozilla_dom_workers_serviceworkerjobqueue_h
#include "mozilla/RefPtr.h"
#include "nsTArray.h"
namespace mozilla {
namespace dom {
namespace workers {
class ServiceWorkerJob;
class ServiceWorkerJobQueue final
{
class Callback;
nsTArray<RefPtr<ServiceWorkerJob>> mJobList;
~ServiceWorkerJobQueue();
void
JobFinished(ServiceWorkerJob* aJob);
void
RunJob();
public:
ServiceWorkerJobQueue();
void
ScheduleJob(ServiceWorkerJob* aJob);
void
CancelAll();
NS_INLINE_DECL_REFCOUNTING(ServiceWorkerJobQueue)
};
} // namespace workers
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_workers_serviceworkerjobqueue_h
File diff suppressed because it is too large Load Diff
+8 -254
View File
@@ -20,10 +20,10 @@
#include "mozilla/WeakPtr.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ServiceWorkerBinding.h" // For ServiceWorkerState
#include "mozilla/dom/ServiceWorkerCommon.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
#include "mozilla/dom/workers/ServiceWorkerRegistrationInfo.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
@@ -44,132 +44,12 @@ class ServiceWorkerRegistrationListener;
namespace workers {
class ServiceWorker;
class ServiceWorkerClientInfo;
class ServiceWorkerInfo;
class ServiceWorkerJob;
class ServiceWorkerJobQueue;
class ServiceWorkerManagerChild;
class ServiceWorkerPrivate;
class ServiceWorkerRegistrationInfo final
: public nsIServiceWorkerRegistrationInfo
{
uint32_t mControlledDocumentsCounter;
enum
{
NoUpdate,
NeedTimeCheckAndUpdate,
NeedUpdate
} mUpdateState;
uint64_t mLastUpdateCheckTime;
virtual ~ServiceWorkerRegistrationInfo();
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISERVICEWORKERREGISTRATIONINFO
nsCString mScope;
nsCOMPtr<nsIPrincipal> mPrincipal;
RefPtr<ServiceWorkerInfo> mActiveWorker;
RefPtr<ServiceWorkerInfo> mWaitingWorker;
RefPtr<ServiceWorkerInfo> mInstallingWorker;
nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> mListeners;
// According to the spec, Soft Update shouldn't queue an update job
// if the registration queue is not empty. Because our job queue
// works slightly different, we use a flag to determine if the registration
// is already updating.
bool mUpdating;
// When unregister() is called on a registration, it is not immediately
// removed since documents may be controlled. It is marked as
// pendingUninstall and when all controlling documents go away, removed.
bool mPendingUninstall;
ServiceWorkerRegistrationInfo(const nsACString& aScope,
nsIPrincipal* aPrincipal);
already_AddRefed<ServiceWorkerInfo>
Newest() const
{
RefPtr<ServiceWorkerInfo> newest;
if (mInstallingWorker) {
newest = mInstallingWorker;
} else if (mWaitingWorker) {
newest = mWaitingWorker;
} else {
newest = mActiveWorker;
}
return newest.forget();
}
already_AddRefed<ServiceWorkerInfo>
GetServiceWorkerInfoById(uint64_t aId);
void
StartControllingADocument()
{
++mControlledDocumentsCounter;
}
void
StopControllingADocument()
{
MOZ_ASSERT(mControlledDocumentsCounter);
--mControlledDocumentsCounter;
}
bool
IsControllingDocuments() const
{
return mActiveWorker && mControlledDocumentsCounter;
}
void
Clear();
void
PurgeActiveWorker();
void
TryToActivateAsync();
void
TryToActivate();
void
Activate();
void
FinishActivate(bool aSuccess);
void
RefreshLastUpdateCheckTime();
bool
IsLastUpdateCheckTimeOverOneDay() const;
void
NotifyListenersOnChange();
void
MaybeScheduleTimeCheckAndUpdate();
void
MaybeScheduleUpdate();
bool
CheckAndClearIfUpdateNeeded();
};
class ServiceWorkerUpdateFinishCallback
{
protected:
@@ -186,126 +66,6 @@ public:
void UpdateFailed(ErrorResult& aStatus) = 0;
};
/*
* Wherever the spec treats a worker instance and a description of said worker
* as the same thing; i.e. "Resolve foo with
* _GetNewestWorker(serviceWorkerRegistration)", we represent the description
* by this class and spawn a ServiceWorker in the right global when required.
*/
class ServiceWorkerInfo final : public nsIServiceWorkerInfo
{
private:
nsCOMPtr<nsIPrincipal> mPrincipal;
const nsCString mScope;
const nsCString mScriptSpec;
const nsString mCacheName;
ServiceWorkerState mState;
// This id is shared with WorkerPrivate to match requests issued by service
// workers to their corresponding serviceWorkerInfo.
uint64_t mServiceWorkerID;
// We hold rawptrs since the ServiceWorker constructor and destructor ensure
// addition and removal.
// There is a high chance of there being at least one ServiceWorker
// associated with this all the time.
AutoTArray<ServiceWorker*, 1> mInstances;
RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
bool mSkipWaitingFlag;
~ServiceWorkerInfo();
// Generates a unique id for the service worker, with zero being treated as
// invalid.
uint64_t
GetNextID() const;
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISERVICEWORKERINFO
class ServiceWorkerPrivate*
WorkerPrivate() const
{
MOZ_ASSERT(mServiceWorkerPrivate);
return mServiceWorkerPrivate;
}
nsIPrincipal*
GetPrincipal() const
{
return mPrincipal;
}
const nsCString&
ScriptSpec() const
{
return mScriptSpec;
}
const nsCString&
Scope() const
{
return mScope;
}
bool SkipWaitingFlag() const
{
AssertIsOnMainThread();
return mSkipWaitingFlag;
}
void SetSkipWaitingFlag()
{
AssertIsOnMainThread();
mSkipWaitingFlag = true;
}
ServiceWorkerInfo(nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec,
const nsAString& aCacheName);
ServiceWorkerState
State() const
{
return mState;
}
const nsString&
CacheName() const
{
return mCacheName;
}
uint64_t
ID() const
{
return mServiceWorkerID;
}
void
UpdateState(ServiceWorkerState aState);
// Only used to set initial state when loading from disk!
void
SetActivateStateUncheckedWithoutEvent(ServiceWorkerState aState)
{
AssertIsOnMainThread();
mState = aState;
}
void
AppendWorker(ServiceWorker* aWorker);
void
RemoveWorker(ServiceWorker* aWorker);
already_AddRefed<ServiceWorker>
GetOrCreateInstance(nsPIDOMWindow* aWindow);
};
#define NS_SERVICEWORKERMANAGER_IMPL_IID \
{ /* f4f8755a-69ca-46e8-a65d-775745535990 */ \
0xf4f8755a, \
@@ -327,13 +87,10 @@ class ServiceWorkerManager final
friend class GetReadyPromiseRunnable;
friend class GetRegistrationsRunnable;
friend class GetRegistrationRunnable;
friend class ServiceWorkerJobQueue;
friend class ServiceWorkerInstallJob;
friend class ServiceWorkerRegisterJob;
friend class ServiceWorkerJobBase;
friend class ServiceWorkerScriptJobBase;
friend class ServiceWorkerJob;
friend class ServiceWorkerRegistrationInfo;
friend class ServiceWorkerUnregisterJob;
friend class ServiceWorkerUpdateJob;
friend class UpdateTimerCallback;
public:
@@ -349,9 +106,6 @@ public:
nsRefPtrHashtable<nsISupportsHashKey, ServiceWorkerRegistrationInfo> mControlledDocuments;
// Set of all documents that may be controlled by a service worker.
nsTHashtable<nsISupportsHashKey> mAllDocuments;
// Track all documents that have attempted to register a service worker for a
// given scope.
typedef nsTArray<nsCOMPtr<nsIWeakReference>> WeakDocumentList;
@@ -512,7 +266,7 @@ private:
void
Init();
ServiceWorkerJobQueue*
already_AddRefed<ServiceWorkerJobQueue>
GetOrCreateJobQueue(const nsACString& aOriginSuffix,
const nsACString& aScope);
@@ -626,8 +380,6 @@ private:
};
void AppendPendingOperation(nsIRunnable* aRunnable);
void AppendPendingOperation(ServiceWorkerJobQueue* aQueue,
ServiceWorkerJob* aJob);
bool HasBackgroundActor() const
{
@@ -645,8 +397,7 @@ private:
RefPtr<ServiceWorkerManagerChild> mActor;
struct PendingOperation;
nsTArray<PendingOperation> mPendingOperations;
nsTArray<nsCOMPtr<nsIRunnable>> mPendingOperations;
bool mShuttingDown;
@@ -676,6 +427,9 @@ private:
void
UpdateTimerFired(nsIPrincipal* aPrincipal, const nsACString& aScope);
void
MaybeSendUnregister(nsIPrincipal* aPrincipal, const nsACString& aScope);
};
} // namespace workers
+67
View File
@@ -0,0 +1,67 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ServiceWorkerRegisterJob.h"
#include "Workers.h"
namespace mozilla {
namespace dom {
namespace workers {
ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec,
nsILoadGroup* aLoadGroup)
: ServiceWorkerUpdateJob(Type::Register, aPrincipal, aScope, aScriptSpec,
aLoadGroup)
{
}
void
ServiceWorkerRegisterJob::AsyncExecute()
{
AssertIsOnMainThread();
if (Canceled()) {
FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
return;
}
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
RefPtr<ServiceWorkerRegistrationInfo> registration =
swm->GetRegistration(mPrincipal, mScope);
if (registration) {
// If we are resurrecting an uninstalling registration, then persist
// it to disk again. We preemptively removed it earlier during
// unregister so that closing the window by shutting down the browser
// results in the registration being gone on restart.
if (registration->mPendingUninstall) {
swm->StoreRegistration(mPrincipal, registration);
}
registration->mPendingUninstall = false;
RefPtr<ServiceWorkerInfo> newest = registration->Newest();
if (newest && mScriptSpec.Equals(newest->ScriptSpec())) {
SetRegistration(registration);
Finish(NS_OK);
return;
}
} else {
registration = swm->CreateNewRegistration(mScope, mPrincipal);
}
SetRegistration(registration);
Update();
}
ServiceWorkerRegisterJob::~ServiceWorkerRegisterJob()
{
}
} // namespace workers
} // namespace dom
} // namespace mozilla
+40
View File
@@ -0,0 +1,40 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_workers_serviceworkerregisterjob_h
#define mozilla_dom_workers_serviceworkerregisterjob_h
#include "ServiceWorkerUpdateJob.h"
namespace mozilla {
namespace dom {
namespace workers {
// The register job. This implements the steps in the spec Register algorithm,
// but then uses ServiceWorkerUpdateJob to implement the Update and Install
// spec algorithms.
class ServiceWorkerRegisterJob final : public ServiceWorkerUpdateJob
{
public:
ServiceWorkerRegisterJob(nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec,
nsILoadGroup* aLoadGroup);
private:
// Implement the Register algorithm steps and then call the parent class
// Update() to complete the job execution.
virtual void
AsyncExecute() override;
virtual ~ServiceWorkerRegisterJob();
};
} // namespace workers
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_workers_serviceworkerregisterjob_h
@@ -0,0 +1,450 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ServiceWorkerRegistrationInfo.h"
BEGIN_WORKERS_NAMESPACE
void
ServiceWorkerRegistrationInfo::Clear()
{
if (mInstallingWorker) {
mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
mInstallingWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
mInstallingWorker = nullptr;
// FIXME(nsm): Abort any inflight requests from installing worker.
}
if (mWaitingWorker) {
mWaitingWorker->UpdateState(ServiceWorkerState::Redundant);
mWaitingWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
mWaitingWorker = nullptr;
}
if (mActiveWorker) {
mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
mActiveWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
mActiveWorker = nullptr;
}
NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER |
WhichServiceWorker::WAITING_WORKER |
WhichServiceWorker::ACTIVE_WORKER);
}
ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& aScope,
nsIPrincipal* aPrincipal)
: mControlledDocumentsCounter(0)
, mUpdateState(NoUpdate)
, mLastUpdateCheckTime(0)
, mScope(aScope)
, mPrincipal(aPrincipal)
, mPendingUninstall(false)
{}
ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo()
{
if (IsControllingDocuments()) {
NS_WARNING("ServiceWorkerRegistrationInfo is still controlling documents. This can be a bug or a leak in ServiceWorker API or in any other API that takes the document alive.");
}
}
NS_IMPL_ISUPPORTS(ServiceWorkerRegistrationInfo, nsIServiceWorkerRegistrationInfo)
NS_IMETHODIMP
ServiceWorkerRegistrationInfo::GetPrincipal(nsIPrincipal** aPrincipal)
{
AssertIsOnMainThread();
NS_ADDREF(*aPrincipal = mPrincipal);
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerRegistrationInfo::GetScope(nsAString& aScope)
{
AssertIsOnMainThread();
CopyUTF8toUTF16(mScope, aScope);
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerRegistrationInfo::GetScriptSpec(nsAString& aScriptSpec)
{
AssertIsOnMainThread();
RefPtr<ServiceWorkerInfo> newest = Newest();
if (newest) {
CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec);
}
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerRegistrationInfo::GetInstallingWorker(nsIServiceWorkerInfo **aResult)
{
AssertIsOnMainThread();
nsCOMPtr<nsIServiceWorkerInfo> info = do_QueryInterface(mInstallingWorker);
info.forget(aResult);
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerRegistrationInfo::GetWaitingWorker(nsIServiceWorkerInfo **aResult)
{
AssertIsOnMainThread();
nsCOMPtr<nsIServiceWorkerInfo> info = do_QueryInterface(mWaitingWorker);
info.forget(aResult);
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerRegistrationInfo::GetActiveWorker(nsIServiceWorkerInfo **aResult)
{
AssertIsOnMainThread();
nsCOMPtr<nsIServiceWorkerInfo> info = do_QueryInterface(mActiveWorker);
info.forget(aResult);
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerRegistrationInfo::GetWorkerByID(uint64_t aID, nsIServiceWorkerInfo **aResult)
{
AssertIsOnMainThread();
MOZ_ASSERT(aResult);
RefPtr<ServiceWorkerInfo> info = GetServiceWorkerInfoById(aID);
// It is ok to return null for a missing service worker info.
info.forget(aResult);
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerRegistrationInfo::AddListener(
nsIServiceWorkerRegistrationInfoListener *aListener)
{
AssertIsOnMainThread();
if (!aListener || mListeners.Contains(aListener)) {
return NS_ERROR_INVALID_ARG;
}
mListeners.AppendElement(aListener);
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerRegistrationInfo::RemoveListener(
nsIServiceWorkerRegistrationInfoListener *aListener)
{
AssertIsOnMainThread();
if (!aListener || !mListeners.Contains(aListener)) {
return NS_ERROR_INVALID_ARG;
}
mListeners.RemoveElement(aListener);
return NS_OK;
}
already_AddRefed<ServiceWorkerInfo>
ServiceWorkerRegistrationInfo::GetServiceWorkerInfoById(uint64_t aId)
{
RefPtr<ServiceWorkerInfo> serviceWorker;
if (mInstallingWorker && mInstallingWorker->ID() == aId) {
serviceWorker = mInstallingWorker;
} else if (mWaitingWorker && mWaitingWorker->ID() == aId) {
serviceWorker = mWaitingWorker;
} else if (mActiveWorker && mActiveWorker->ID() == aId) {
serviceWorker = mActiveWorker;
}
return serviceWorker.forget();
}
void
ServiceWorkerRegistrationInfo::TryToActivateAsync()
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(this,
&ServiceWorkerRegistrationInfo::TryToActivate);
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
}
/*
* TryToActivate should not be called directly, use TryToACtivateAsync instead.
*/
void
ServiceWorkerRegistrationInfo::TryToActivate()
{
if (!IsControllingDocuments() ||
// Waiting worker will be removed if the registration is removed
(mWaitingWorker && mWaitingWorker->SkipWaitingFlag())) {
Activate();
}
}
void
ServiceWorkerRegistrationInfo::Activate()
{
if (!mWaitingWorker) {
return;
}
TransitionWaitingToActive();
// FIXME(nsm): Unlink appcache if there is one.
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->CheckPendingReadyPromises();
// "Queue a task to fire a simple event named controllerchange..."
nsCOMPtr<nsIRunnable> controllerChangeRunnable =
NS_NewRunnableMethodWithArg<RefPtr<ServiceWorkerRegistrationInfo>>(
swm, &ServiceWorkerManager::FireControllerChange, this);
NS_DispatchToMainThread(controllerChangeRunnable);
nsCOMPtr<nsIRunnable> failRunnable =
NS_NewRunnableMethodWithArg<bool>(this,
&ServiceWorkerRegistrationInfo::FinishActivate,
false /* success */);
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> handle(
new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(this));
RefPtr<LifeCycleEventCallback> callback = new ContinueActivateRunnable(handle);
ServiceWorkerPrivate* workerPrivate = mActiveWorker->WorkerPrivate();
MOZ_ASSERT(workerPrivate);
nsresult rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("activate"),
callback, failRunnable);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(failRunnable));
return;
}
}
void
ServiceWorkerRegistrationInfo::FinishActivate(bool aSuccess)
{
if (mPendingUninstall || !mActiveWorker ||
mActiveWorker->State() != ServiceWorkerState::Activating) {
return;
}
// Activation never fails, so aSuccess is ignored.
mActiveWorker->UpdateState(ServiceWorkerState::Activated);
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->StoreRegistration(mPrincipal, this);
}
void
ServiceWorkerRegistrationInfo::RefreshLastUpdateCheckTime()
{
AssertIsOnMainThread();
mLastUpdateCheckTime = PR_IntervalNow() / PR_MSEC_PER_SEC;
}
bool
ServiceWorkerRegistrationInfo::IsLastUpdateCheckTimeOverOneDay() const
{
AssertIsOnMainThread();
// For testing.
if (Preferences::GetBool("dom.serviceWorkers.testUpdateOverOneDay")) {
return true;
}
const uint64_t kSecondsPerDay = 86400;
const uint64_t now = PR_IntervalNow() / PR_MSEC_PER_SEC;
if ((mLastUpdateCheckTime != 0) &&
(now - mLastUpdateCheckTime > kSecondsPerDay)) {
return true;
}
return false;
}
void
ServiceWorkerRegistrationInfo::NotifyListenersOnChange(WhichServiceWorker aChangedWorkers)
{
AssertIsOnMainThread();
MOZ_ASSERT(aChangedWorkers & (WhichServiceWorker::INSTALLING_WORKER |
WhichServiceWorker::WAITING_WORKER |
WhichServiceWorker::ACTIVE_WORKER));
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->InvalidateServiceWorkerRegistrationWorker(this, aChangedWorkers);
nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> listeners(mListeners);
for (size_t index = 0; index < listeners.Length(); ++index) {
listeners[index]->OnChange();
}
}
void
ServiceWorkerRegistrationInfo::MaybeScheduleTimeCheckAndUpdate()
{
AssertIsOnMainThread();
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (!swm) {
// shutting down, do nothing
return;
}
if (mUpdateState == NoUpdate) {
mUpdateState = NeedTimeCheckAndUpdate;
}
swm->ScheduleUpdateTimer(mPrincipal, mScope);
}
void
ServiceWorkerRegistrationInfo::MaybeScheduleUpdate()
{
AssertIsOnMainThread();
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (!swm) {
// shutting down, do nothing
return;
}
mUpdateState = NeedUpdate;
swm->ScheduleUpdateTimer(mPrincipal, mScope);
}
bool
ServiceWorkerRegistrationInfo::CheckAndClearIfUpdateNeeded()
{
AssertIsOnMainThread();
bool result = mUpdateState == NeedUpdate ||
(mUpdateState == NeedTimeCheckAndUpdate &&
IsLastUpdateCheckTimeOverOneDay());
mUpdateState = NoUpdate;
return result;
}
ServiceWorkerInfo*
ServiceWorkerRegistrationInfo::GetInstalling() const
{
AssertIsOnMainThread();
return mInstallingWorker;
}
ServiceWorkerInfo*
ServiceWorkerRegistrationInfo::GetWaiting() const
{
AssertIsOnMainThread();
return mWaitingWorker;
}
ServiceWorkerInfo*
ServiceWorkerRegistrationInfo::GetActive() const
{
AssertIsOnMainThread();
return mActiveWorker;
}
void
ServiceWorkerRegistrationInfo::ClearInstalling()
{
AssertIsOnMainThread();
if (!mInstallingWorker) {
return;
}
mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
mInstallingWorker = nullptr;
NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER);
}
void
ServiceWorkerRegistrationInfo::SetInstalling(ServiceWorkerInfo* aServiceWorker)
{
AssertIsOnMainThread();
MOZ_ASSERT(aServiceWorker);
MOZ_ASSERT(!mInstallingWorker);
MOZ_ASSERT(mWaitingWorker != aServiceWorker);
MOZ_ASSERT(mActiveWorker != aServiceWorker);
mInstallingWorker = aServiceWorker;
mInstallingWorker->UpdateState(ServiceWorkerState::Installing);
NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER);
}
void
ServiceWorkerRegistrationInfo::TransitionInstallingToWaiting()
{
AssertIsOnMainThread();
MOZ_ASSERT(mInstallingWorker);
if (mWaitingWorker) {
MOZ_ASSERT(mInstallingWorker->CacheName() != mWaitingWorker->CacheName());
mWaitingWorker->UpdateState(ServiceWorkerState::Redundant);
}
mWaitingWorker = mInstallingWorker.forget();
mWaitingWorker->UpdateState(ServiceWorkerState::Installed);
NotifyListenersOnChange(WhichServiceWorker::INSTALLING_WORKER |
WhichServiceWorker::WAITING_WORKER);
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->StoreRegistration(mPrincipal, this);
}
void
ServiceWorkerRegistrationInfo::SetActive(ServiceWorkerInfo* aServiceWorker)
{
AssertIsOnMainThread();
MOZ_ASSERT(aServiceWorker);
// TODO: Assert installing, waiting, and active are nullptr once the SWM
// moves to the parent process. After that happens this code will
// only run for browser initialization and not for cross-process
// overrides.
MOZ_ASSERT(mInstallingWorker != aServiceWorker);
MOZ_ASSERT(mWaitingWorker != aServiceWorker);
MOZ_ASSERT(mActiveWorker != aServiceWorker);
if (mActiveWorker) {
MOZ_ASSERT(aServiceWorker->CacheName() != mActiveWorker->CacheName());
mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
}
// The active worker is being overriden due to initial load or
// another process activating a worker. Move straight to the
// Activated state.
mActiveWorker = aServiceWorker;
mActiveWorker->SetActivateStateUncheckedWithoutEvent(ServiceWorkerState::Activated);
NotifyListenersOnChange(WhichServiceWorker::ACTIVE_WORKER);
}
void
ServiceWorkerRegistrationInfo::TransitionWaitingToActive()
{
AssertIsOnMainThread();
MOZ_ASSERT(mWaitingWorker);
if (mActiveWorker) {
MOZ_ASSERT(mWaitingWorker->CacheName() != mActiveWorker->CacheName());
mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
}
// We are transitioning from waiting to active normally, so go to
// the activating state.
mActiveWorker = mWaitingWorker.forget();
mActiveWorker->UpdateState(ServiceWorkerState::Activating);
NotifyListenersOnChange(WhichServiceWorker::WAITING_WORKER |
WhichServiceWorker::ACTIVE_WORKER);
}
END_WORKERS_NAMESPACE
+167
View File
@@ -0,0 +1,167 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_workers_serviceworkerregistrationinfo_h
#define mozilla_dom_workers_serviceworkerregistrationinfo_h
#include "mozilla/dom/workers/ServiceWorkerInfo.h"
namespace mozilla {
namespace dom {
namespace workers {
class ServiceWorkerRegistrationInfo final
: public nsIServiceWorkerRegistrationInfo
{
uint32_t mControlledDocumentsCounter;
enum
{
NoUpdate,
NeedTimeCheckAndUpdate,
NeedUpdate
} mUpdateState;
uint64_t mLastUpdateCheckTime;
RefPtr<ServiceWorkerInfo> mActiveWorker;
RefPtr<ServiceWorkerInfo> mWaitingWorker;
RefPtr<ServiceWorkerInfo> mInstallingWorker;
virtual ~ServiceWorkerRegistrationInfo();
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISERVICEWORKERREGISTRATIONINFO
const nsCString mScope;
nsCOMPtr<nsIPrincipal> mPrincipal;
nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> mListeners;
// When unregister() is called on a registration, it is not immediately
// removed since documents may be controlled. It is marked as
// pendingUninstall and when all controlling documents go away, removed.
bool mPendingUninstall;
ServiceWorkerRegistrationInfo(const nsACString& aScope,
nsIPrincipal* aPrincipal);
already_AddRefed<ServiceWorkerInfo>
Newest() const
{
RefPtr<ServiceWorkerInfo> newest;
if (mInstallingWorker) {
newest = mInstallingWorker;
} else if (mWaitingWorker) {
newest = mWaitingWorker;
} else {
newest = mActiveWorker;
}
return newest.forget();
}
already_AddRefed<ServiceWorkerInfo>
GetServiceWorkerInfoById(uint64_t aId);
void
StartControllingADocument()
{
++mControlledDocumentsCounter;
}
void
StopControllingADocument()
{
MOZ_ASSERT(mControlledDocumentsCounter);
--mControlledDocumentsCounter;
}
bool
IsControllingDocuments() const
{
return mActiveWorker && mControlledDocumentsCounter;
}
void
Clear();
void
TryToActivateAsync();
void
TryToActivate();
void
Activate();
void
FinishActivate(bool aSuccess);
void
RefreshLastUpdateCheckTime();
bool
IsLastUpdateCheckTimeOverOneDay() const;
void
NotifyListenersOnChange(WhichServiceWorker aChangedWorkers);
void
MaybeScheduleTimeCheckAndUpdate();
void
MaybeScheduleUpdate();
bool
CheckAndClearIfUpdateNeeded();
ServiceWorkerInfo*
GetInstalling() const;
ServiceWorkerInfo*
GetWaiting() const;
ServiceWorkerInfo*
GetActive() const;
// Remove an existing installing worker, if present. The worker will
// be transitioned to the Redundant state.
void
ClearInstalling();
// Set a new installing worker. This may only be called if there is no
// existing installing worker. The worker is transitioned to the Installing
// state.
void
SetInstalling(ServiceWorkerInfo* aServiceWorker);
// Transition the current installing worker to be the waiting worker. The
// workers state is updated to Installed.
void
TransitionInstallingToWaiting();
// Override the current active worker. This is used during browser
// initialization to load persisted workers. Its also used to propagate
// active workers across child processes in e10s. This second use will
// go away once the ServiceWorkerManager moves to the parent process.
// The worker is transitioned to the Activated state.
void
SetActive(ServiceWorkerInfo* aServiceWorker);
// Transition the current waiting worker to be the new active worker. The
// worker is updated to the Activating state.
void
TransitionWaitingToActive();
};
} // namespace workers
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_workers_serviceworkerregistrationinfo_h
@@ -0,0 +1,89 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ServiceWorkerUnregisterJob.h"
namespace mozilla {
namespace dom {
namespace workers {
ServiceWorkerUnregisterJob::ServiceWorkerUnregisterJob(nsIPrincipal* aPrincipal,
const nsACString& aScope,
bool aSendToParent)
: ServiceWorkerJob(Type::Unregister, aPrincipal, aScope, EmptyCString())
, mResult(false)
, mSendToParent(aSendToParent)
{
}
bool
ServiceWorkerUnregisterJob::GetResult() const
{
AssertIsOnMainThread();
return mResult;
}
ServiceWorkerUnregisterJob::~ServiceWorkerUnregisterJob()
{
}
void
ServiceWorkerUnregisterJob::AsyncExecute()
{
AssertIsOnMainThread();
if (Canceled()) {
Finish(NS_ERROR_DOM_ABORT_ERR);
return;
}
// Step 1 of the Unregister algorithm requires checking that the
// client origin matches the scope's origin. We perform this in
// registration->update() method directly since we don't have that
// client information available here.
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
// "Let registration be the result of running [[Get Registration]]
// algorithm passing scope as the argument."
RefPtr<ServiceWorkerRegistrationInfo> registration =
swm->GetRegistration(mPrincipal, mScope);
if (!registration) {
// "If registration is null, then, resolve promise with false."
Finish(NS_OK);
return;
}
// Note, we send the message to remove the registration from disk now even
// though we may only set the mPendingUninstall flag below. This is
// necessary to ensure the registration is removed if the controlled
// clients are closed by shutting down the browser. If the registration
// is resurrected by clearing mPendingUninstall then it should be saved
// to disk again.
if (mSendToParent && !registration->mPendingUninstall) {
swm->MaybeSendUnregister(mPrincipal, mScope);
}
// "Set registration's uninstalling flag."
registration->mPendingUninstall = true;
// "Resolve promise with true"
mResult = true;
InvokeResultCallbacks(NS_OK);
// "If no service worker client is using registration..."
if (!registration->IsControllingDocuments()) {
// "Invoke [[Clear Registration]]..."
swm->RemoveRegistration(registration);
}
Finish(NS_OK);
}
} // namespace workers
} // namespace dom
} // namespace mozilla
+40
View File
@@ -0,0 +1,40 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_workers_serviceworkerunregisterjob_h
#define mozilla_dom_workers_serviceworkerunregisterjob_h
#include "ServiceWorkerJob.h"
namespace mozilla {
namespace dom {
namespace workers {
class ServiceWorkerUnregisterJob final : public ServiceWorkerJob
{
public:
ServiceWorkerUnregisterJob(nsIPrincipal* aPrincipal,
const nsACString& aScope,
bool aSendToParent);
bool
GetResult() const;
private:
virtual ~ServiceWorkerUnregisterJob();
virtual void
AsyncExecute() override;
bool mResult;
bool mSendToParent;
};
} // namespace workers
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_workers_serviceworkerunregisterjob_h
+485
View File
@@ -0,0 +1,485 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ServiceWorkerUpdateJob.h"
#include "ServiceWorkerScriptCache.h"
#include "Workers.h"
namespace mozilla {
namespace dom {
namespace workers {
class ServiceWorkerUpdateJob::CompareCallback final : public serviceWorkerScriptCache::CompareCallback
{
RefPtr<ServiceWorkerUpdateJob> mJob;
~CompareCallback()
{
}
public:
explicit CompareCallback(ServiceWorkerUpdateJob* aJob)
: mJob(aJob)
{
MOZ_ASSERT(mJob);
}
virtual void
ComparisonResult(nsresult aStatus,
bool aInCacheAndEqual,
const nsAString& aNewCacheName,
const nsACString& aMaxScope) override
{
mJob->ComparisonResult(aStatus, aInCacheAndEqual, aNewCacheName, aMaxScope);
}
NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateJob::CompareCallback, override)
};
class ServiceWorkerUpdateJob::ContinueUpdateRunnable final : public LifeCycleEventCallback
{
nsMainThreadPtrHandle<ServiceWorkerUpdateJob> mJob;
bool mSuccess;
public:
explicit ContinueUpdateRunnable(const nsMainThreadPtrHandle<ServiceWorkerUpdateJob>& aJob)
: mJob(aJob)
, mSuccess(false)
{
AssertIsOnMainThread();
}
void
SetResult(bool aResult) override
{
mSuccess = aResult;
}
NS_IMETHOD
Run() override
{
AssertIsOnMainThread();
mJob->ContinueUpdateAfterScriptEval(mSuccess);
mJob = nullptr;
return NS_OK;
}
};
class ServiceWorkerUpdateJob::ContinueInstallRunnable final : public LifeCycleEventCallback
{
nsMainThreadPtrHandle<ServiceWorkerUpdateJob> mJob;
bool mSuccess;
public:
explicit ContinueInstallRunnable(const nsMainThreadPtrHandle<ServiceWorkerUpdateJob>& aJob)
: mJob(aJob)
, mSuccess(false)
{
AssertIsOnMainThread();
}
void
SetResult(bool aResult) override
{
mSuccess = aResult;
}
NS_IMETHOD
Run() override
{
AssertIsOnMainThread();
mJob->ContinueAfterInstallEvent(mSuccess);
mJob = nullptr;
return NS_OK;
}
};
ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec,
nsILoadGroup* aLoadGroup)
: ServiceWorkerJob(Type::Update, aPrincipal, aScope, aScriptSpec)
, mLoadGroup(aLoadGroup)
{
}
already_AddRefed<ServiceWorkerRegistrationInfo>
ServiceWorkerUpdateJob::GetRegistration() const
{
AssertIsOnMainThread();
RefPtr<ServiceWorkerRegistrationInfo> ref = mRegistration;
return ref.forget();
}
ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(Type aType,
nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec,
nsILoadGroup* aLoadGroup)
: ServiceWorkerJob(aType, aPrincipal, aScope, aScriptSpec)
, mLoadGroup(aLoadGroup)
{
}
ServiceWorkerUpdateJob::~ServiceWorkerUpdateJob()
{
}
void
ServiceWorkerUpdateJob::FailUpdateJob(ErrorResult& aRv)
{
AssertIsOnMainThread();
MOZ_ASSERT(aRv.Failed());
// Cleanup after a failed installation. This essentially implements
// step 12 of the Install algorithm.
//
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#installation-algorithm
//
// The spec currently only runs this after an install event fails,
// but we must handle many more internal errors. So we check for
// cleanup on every non-successful exit.
if (mRegistration) {
if (mServiceWorker) {
mServiceWorker->UpdateState(ServiceWorkerState::Redundant);
serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal,
mServiceWorker->CacheName());
}
mRegistration->ClearInstalling();
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (swm) {
swm->MaybeRemoveRegistration(mRegistration);
}
}
mServiceWorker = nullptr;
mRegistration = nullptr;
Finish(aRv);
}
void
ServiceWorkerUpdateJob::FailUpdateJob(nsresult aRv)
{
ErrorResult rv(aRv);
FailUpdateJob(rv);
}
void
ServiceWorkerUpdateJob::AsyncExecute()
{
AssertIsOnMainThread();
MOZ_ASSERT(GetType() == Type::Update);
if (Canceled()) {
FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
return;
}
// Begin step 1 of the Update algorithm.
//
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#update-algorithm
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
RefPtr<ServiceWorkerRegistrationInfo> registration =
swm->GetRegistration(mPrincipal, mScope);
if (!registration || registration->mPendingUninstall) {
ErrorResult rv;
rv.ThrowTypeError<MSG_SW_UPDATE_BAD_REGISTRATION>(NS_ConvertUTF8toUTF16(mScope),
NS_LITERAL_STRING("uninstalled"));
FailUpdateJob(rv);
return;
}
// If a Register job with a new script executed ahead of us in the job queue,
// then our update for the old script no longer makes sense. Simply abort
// in this case.
RefPtr<ServiceWorkerInfo> newest = registration->Newest();
if (newest && !mScriptSpec.Equals(newest->ScriptSpec())) {
ErrorResult rv;
rv.ThrowTypeError<MSG_SW_UPDATE_BAD_REGISTRATION>(NS_ConvertUTF8toUTF16(mScope),
NS_LITERAL_STRING("changed"));
FailUpdateJob(rv);
return;
}
SetRegistration(registration);
Update();
}
void
ServiceWorkerUpdateJob::SetRegistration(ServiceWorkerRegistrationInfo* aRegistration)
{
AssertIsOnMainThread();
MOZ_ASSERT(!mRegistration);
MOZ_ASSERT(aRegistration);
mRegistration = aRegistration;
}
void
ServiceWorkerUpdateJob::Update()
{
AssertIsOnMainThread();
MOZ_ASSERT(!Canceled());
// SetRegistration() must be called before Update().
MOZ_ASSERT(mRegistration);
MOZ_ASSERT(!mRegistration->GetInstalling());
// Begin the script download and comparison steps starting at step 5
// of the Update algorithm.
RefPtr<ServiceWorkerInfo> workerInfo = mRegistration->Newest();
nsAutoString cacheName;
// If the script has not changed, we need to perform a byte-for-byte
// comparison.
if (workerInfo && workerInfo->ScriptSpec().Equals(mScriptSpec)) {
cacheName = workerInfo->CacheName();
}
RefPtr<CompareCallback> callback = new CompareCallback(this);
nsresult rv =
serviceWorkerScriptCache::Compare(mRegistration, mPrincipal, cacheName,
NS_ConvertUTF8toUTF16(mScriptSpec),
callback, mLoadGroup);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailUpdateJob(rv);
return;
}
}
void
ServiceWorkerUpdateJob::ComparisonResult(nsresult aStatus,
bool aInCacheAndEqual,
const nsAString& aNewCacheName,
const nsACString& aMaxScope)
{
AssertIsOnMainThread();
if (NS_WARN_IF(Canceled())) {
FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
return;
}
// Handle failure of the download or comparison. This is part of Update
// step 5 as "If the algorithm asynchronously completes with null, then:".
if (NS_WARN_IF(NS_FAILED(aStatus))) {
FailUpdateJob(aStatus);
return;
}
// The spec validates the response before performing the byte-for-byte check.
// Here we perform the comparison in another module and then validate the
// script URL and scope. Make sure to do this validation before accepting
// an byte-for-byte match since the service-worker-allowed header might have
// changed since the last time it was installed.
// This is step 2 the "validate response" section of Update algorithm step 5.
// Step 1 is performed in the serviceWorkerScriptCache code.
nsCOMPtr<nsIURI> scriptURI;
nsresult rv = NS_NewURI(getter_AddRefs(scriptURI), mScriptSpec);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsCOMPtr<nsIURI> maxScopeURI;
if (!aMaxScope.IsEmpty()) {
rv = NS_NewURI(getter_AddRefs(maxScopeURI), aMaxScope,
nullptr, scriptURI);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
return;
}
}
nsAutoCString defaultAllowedPrefix;
rv = GetRequiredScopeStringPrefix(scriptURI, defaultAllowedPrefix,
eUseDirectory);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsAutoCString maxPrefix(defaultAllowedPrefix);
if (maxScopeURI) {
rv = GetRequiredScopeStringPrefix(maxScopeURI, maxPrefix, eUsePath);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
return;
}
}
if (!StringBeginsWith(mRegistration->mScope, maxPrefix)) {
nsXPIDLString message;
NS_ConvertUTF8toUTF16 reportScope(mRegistration->mScope);
NS_ConvertUTF8toUTF16 reportMaxPrefix(maxPrefix);
const char16_t* params[] = { reportScope.get(), reportMaxPrefix.get() };
rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
"ServiceWorkerScopePathMismatch",
params, message);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to format localized string");
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->ReportToAllClients(mScope,
message,
EmptyString(),
EmptyString(), 0, 0,
nsIScriptError::errorFlag);
FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
return;
}
// The response has been validated, so now we can consider if its a
// byte-for-byte match. This is step 6 of the Update algorithm.
if (aInCacheAndEqual) {
Finish(NS_OK);
return;
}
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_UPDATED, 1);
// Begin step 7 of the Update algorithm to evaluate the new script.
MOZ_ASSERT(!mServiceWorker);
mServiceWorker = new ServiceWorkerInfo(mRegistration->mPrincipal,
mRegistration->mScope,
mScriptSpec, aNewCacheName);
nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(this));
RefPtr<LifeCycleEventCallback> callback = new ContinueUpdateRunnable(handle);
ServiceWorkerPrivate* workerPrivate = mServiceWorker->WorkerPrivate();
MOZ_ASSERT(workerPrivate);
rv = workerPrivate->CheckScriptEvaluation(callback);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
return;
}
}
void
ServiceWorkerUpdateJob::ContinueUpdateAfterScriptEval(bool aScriptEvaluationResult)
{
AssertIsOnMainThread();
if (Canceled()) {
FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
return;
}
// Step 7.5 of the Update algorithm verifying that the script evaluated
// successfully.
if (NS_WARN_IF(!aScriptEvaluationResult)) {
ErrorResult error;
NS_ConvertUTF8toUTF16 scriptSpec(mScriptSpec);
NS_ConvertUTF8toUTF16 scope(mRegistration->mScope);
error.ThrowTypeError<MSG_SW_SCRIPT_THREW>(scriptSpec, scope);
FailUpdateJob(error);
return;
}
Install();
}
void
ServiceWorkerUpdateJob::Install()
{
AssertIsOnMainThread();
MOZ_ASSERT(!Canceled());
MOZ_ASSERT(!mRegistration->GetInstalling());
// Begin step 2 of the Install algorithm.
//
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#installation-algorithm
MOZ_ASSERT(mServiceWorker);
mRegistration->SetInstalling(mServiceWorker);
mServiceWorker = nullptr;
// Step 6 of the Install algorithm resolving the job promise.
InvokeResultCallbacks(NS_OK);
// The job promise cannot be rejected after this point, but the job can
// still fail; e.g. if the install event handler throws, etc.
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
// fire the updatefound event
nsCOMPtr<nsIRunnable> upr =
NS_NewRunnableMethodWithArg<RefPtr<ServiceWorkerRegistrationInfo>>(
swm,
&ServiceWorkerManager::FireUpdateFoundOnServiceWorkerRegistrations,
mRegistration);
NS_DispatchToMainThread(upr);
// Call ContinueAfterInstallEvent(false) on main thread if the SW
// script fails to load.
nsCOMPtr<nsIRunnable> failRunnable = NS_NewRunnableMethodWithArgs<bool>
(this, &ServiceWorkerUpdateJob::ContinueAfterInstallEvent, false);
nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(this));
RefPtr<LifeCycleEventCallback> callback = new ContinueInstallRunnable(handle);
// Send the install event to the worker thread
ServiceWorkerPrivate* workerPrivate =
mRegistration->GetInstalling()->WorkerPrivate();
nsresult rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("install"),
callback, failRunnable);
if (NS_WARN_IF(NS_FAILED(rv))) {
ContinueAfterInstallEvent(false /* aSuccess */);
}
}
void
ServiceWorkerUpdateJob::ContinueAfterInstallEvent(bool aInstallEventSuccess)
{
if (Canceled()) {
return FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
}
MOZ_ASSERT(mRegistration->GetInstalling());
// Continue executing the Install algorithm at step 12.
// "If installFailed is true"
if (NS_WARN_IF(!aInstallEventSuccess)) {
// The installing worker is cleaned up by FailUpdateJob().
FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
return;
}
mRegistration->TransitionInstallingToWaiting();
Finish(NS_OK);
// Step 20 calls for explicitly waiting for queued event tasks to fire. Instead,
// we simply queue a runnable to execute Activate. This ensures the events are
// flushed from the queue before proceeding.
// Step 22 of the Install algorithm. Activate is executed after the completion
// of this job. The controlling client and skipWaiting checks are performed
// in TryToActivate().
mRegistration->TryToActivateAsync();
}
} // namespace workers
} // namespace dom
} // namespace mozilla
+103
View File
@@ -0,0 +1,103 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_workers_serviceworkerupdatejob_h
#define mozilla_dom_workers_serviceworkerupdatejob_h
#include "ServiceWorkerJob.h"
namespace mozilla {
namespace dom {
namespace workers {
// A job class that performs the Update and Install algorithms from the
// service worker spec. This class is designed to be inherited and customized
// as a different job type. This is necessary because the register job
// performs largely the same operations as the update job, but has a few
// different starting steps.
class ServiceWorkerUpdateJob : public ServiceWorkerJob
{
public:
// Construct an update job to be used only for updates.
ServiceWorkerUpdateJob(nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec,
nsILoadGroup* aLoadGroup);
already_AddRefed<ServiceWorkerRegistrationInfo>
GetRegistration() const;
protected:
// Construct an update job that is overriden as another job type.
ServiceWorkerUpdateJob(Type aType,
nsIPrincipal* aPrincipal,
const nsACString& aScope,
const nsACString& aScriptSpec,
nsILoadGroup* aLoadGroup);
virtual ~ServiceWorkerUpdateJob();
// FailUpdateJob() must be called if an update job needs Finish() with
// an error.
void
FailUpdateJob(ErrorResult& aRv);
void
FailUpdateJob(nsresult aRv);
// The entry point when the update job is being used directly. Job
// types overriding this class should override this method to
// customize behavior.
virtual void
AsyncExecute() override;
// Set the registration to be operated on by Update() or to be immediately
// returned as a result of the job. This must be called before Update().
void
SetRegistration(ServiceWorkerRegistrationInfo* aRegistration);
// Execute the bulk of the update job logic using the registration defined
// by a previous SetRegistration() call. This can be called by the overriden
// AsyncExecute() to complete the job. The Update() method will always call
// Finish(). This method corresponds to the spec Update algorithm.
void
Update();
private:
class CompareCallback;
class ContinueUpdateRunnable;
class ContinueInstallRunnable;
// Utility method called after a script is loaded and compared to
// our current cached script.
void
ComparisonResult(nsresult aStatus,
bool aInCacheAndEqual,
const nsAString& aNewCacheName,
const nsACString& aMaxScope);
// Utility method called after evaluating the worker script.
void
ContinueUpdateAfterScriptEval(bool aScriptEvaluationResult);
// Utility method corresponding to the spec Install algorithm.
void
Install();
// Utility method called after the install event is handled.
void
ContinueAfterInstallEvent(bool aInstallEventSuccess);
nsCOMPtr<nsILoadGroup> mLoadGroup;
RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
RefPtr<ServiceWorkerInfo> mServiceWorker;
};
} // namespace workers
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_workers_serviceworkerupdatejob_h
+2
View File
@@ -26,6 +26,8 @@
namespace mozilla {
namespace dom {
using namespace mozilla::dom::workers;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerNavigator)
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WorkerNavigator, AddRef)
+13 -8
View File
@@ -103,14 +103,11 @@
#include "SharedWorker.h"
#include "WorkerDebuggerManager.h"
#include "WorkerFeature.h"
#include "WorkerNavigator.h"
#include "WorkerRunnable.h"
#include "WorkerScope.h"
#include "WorkerThread.h"
#ifdef XP_WIN
#undef PostMessage
#endif
// JS_MaybeGC will run once every second during normal execution.
#define PERIODIC_GC_TIMER_DELAY_SEC 1
@@ -4539,6 +4536,9 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
static_cast<nsIRunnable*>(runnable)->Run();
runnable->Release();
// Flush the promise queue.
Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
if (debuggerRunnablesPending) {
WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
MOZ_ASSERT(globalScope);
@@ -5589,6 +5589,9 @@ WorkerPrivate::EnterDebuggerEventLoop()
static_cast<nsIRunnable*>(runnable)->Run();
runnable->Release();
// Flush the promise queue.
Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
// Now *might* be a good time to GC. Let the JS engine make the decision.
if (JS::CurrentGlobalOrNull(cx)) {
JS_MaybeGC(cx);
@@ -5707,7 +5710,7 @@ WorkerPrivate::NotifyInternal(JSContext* aCx, Status aStatus)
// If the worker script never ran, or failed to compile, we don't need to do
// anything else, except pretend that we ran the close handler.
if (!JS::CurrentGlobalOrNull(aCx)) {
if (!GlobalScope()) {
mCloseHandlerStarted = true;
mCloseHandlerFinished = true;
return true;
@@ -5903,8 +5906,10 @@ WorkerPrivate::SetTimeout(JSContext* aCx,
newInfo->mTimeoutString = aStringHandler;
}
else {
JS_ReportError(aCx, "Useless %s call (missing quotes around argument?)",
aIsInterval ? "setInterval" : "setTimeout");
NS_NAMED_LITERAL_STRING(kSetInterval, "setInterval");
NS_NAMED_LITERAL_STRING(kSetTimeout, "setTimeout");
aRv.ThrowTypeError<MSG_USELESS_SETTIMEOUT>(aIsInterval ? kSetInterval
: kSetTimeout);
return 0;
}
@@ -6083,7 +6088,7 @@ WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
// Since we might be processing more timeouts, go ahead and flush
// the promise queue now before we do that.
Promise::PerformMicroTaskCheckpoint();
Promise::PerformWorkerMicroTaskCheckpoint();
NS_ASSERTION(mRunningExpiredTimeouts, "Someone changed this!");
}
+4
View File
@@ -32,6 +32,10 @@
#include "Queue.h"
#include "WorkerFeature.h"
#ifdef XP_WIN
#undef PostMessage
#endif
class nsIChannel;
class nsIDocument;
class nsIEventTarget;
+3 -3
View File
@@ -237,7 +237,7 @@ WorkerGlobalScope::SetTimeout(JSContext* aCx,
}
int32_t
WorkerGlobalScope::SetTimeout(JSContext* /* unused */,
WorkerGlobalScope::SetTimeout(JSContext* aCx,
const nsAString& aHandler,
const int32_t aTimeout,
const Sequence<JS::Value>& /* unused */,
@@ -245,8 +245,8 @@ WorkerGlobalScope::SetTimeout(JSContext* /* unused */,
{
mWorkerPrivate->AssertIsOnWorkerThread();
Sequence<JS::Value> dummy;
return mWorkerPrivate->SetTimeout(GetCurrentThreadJSContext(), nullptr,
aHandler, aTimeout, dummy, false, aRv);
return mWorkerPrivate->SetTimeout(aCx, nullptr, aHandler, aTimeout, dummy,
false, aRv);
}
void
+2 -3
View File
@@ -118,9 +118,8 @@ public:
SetTimeout(JSContext* aCx, Function& aHandler, const int32_t aTimeout,
const Sequence<JS::Value>& aArguments, ErrorResult& aRv);
int32_t
SetTimeout(JSContext* /* unused */, const nsAString& aHandler,
const int32_t aTimeout, const Sequence<JS::Value>& /* unused */,
ErrorResult& aRv);
SetTimeout(JSContext* aCx, const nsAString& aHandler, const int32_t aTimeout,
const Sequence<JS::Value>& /* unused */, ErrorResult& aRv);
void
ClearTimeout(int32_t aHandle);
int32_t
+9
View File
@@ -23,7 +23,9 @@ EXPORTS.mozilla.dom += [
EXPORTS.mozilla.dom.workers += [
'RuntimeService.h',
'ServiceWorkerInfo.h',
'ServiceWorkerManager.h',
'ServiceWorkerRegistrationInfo.h',
'WorkerDebuggerManager.h',
'Workers.h',
]
@@ -66,15 +68,22 @@ UNIFIED_SOURCES += [
'ServiceWorkerClients.cpp',
'ServiceWorkerContainer.cpp',
'ServiceWorkerEvents.cpp',
'ServiceWorkerInfo.cpp',
'ServiceWorkerJob.cpp',
'ServiceWorkerJobQueue.cpp',
'ServiceWorkerManager.cpp',
'ServiceWorkerManagerChild.cpp',
'ServiceWorkerManagerParent.cpp',
'ServiceWorkerManagerService.cpp',
'ServiceWorkerMessageEvent.cpp',
'ServiceWorkerPrivate.cpp',
'ServiceWorkerRegisterJob.cpp',
'ServiceWorkerRegistrar.cpp',
'ServiceWorkerRegistration.cpp',
'ServiceWorkerRegistrationInfo.cpp',
'ServiceWorkerScriptCache.cpp',
'ServiceWorkerUnregisterJob.cpp',
'ServiceWorkerUpdateJob.cpp',
'ServiceWorkerWindowClient.cpp',
'SharedWorker.cpp',
'URL.cpp',
@@ -0,0 +1,30 @@
"use strict";
var self = this;
self.onmessage = function (event) {
if (event.data !== "resolve") {
return;
}
// This then-handler should be executed inside the top-level event loop,
// within the context of the debugger's global.
Promise.resolve().then(function () {
var dbg = new Debugger(global);
dbg.onDebuggerStatement = function () {
self.onmessage = function (event) {
if (event.data !== "resume") {
return;
}
// This then-handler should be executed inside the nested event loop,
// within the context of the debugger's global.
Promise.resolve().then(function () {
postMessage("resumed");
leaveEventLoop();
});
};
postMessage("paused");
enterEventLoop();
};
postMessage("resolved");
});
};
@@ -0,0 +1,25 @@
"use strict";
self.onmessage = function (event) {
if (event.data !== "resolve") {
return;
}
// This then-handler should be executed inside the top-level event loop,
// within the context of the worker's global.
Promise.resolve().then(function () {
self.onmessage = function (event) {
if (event.data !== "pause") {
return;
}
// This then-handler should be executed inside the top-level event loop,
// within the context of the worker's global. Because the debugger
// statement here below enters a nested event loop, the then-handler
// should not be executed until the debugger statement returns.
Promise.resolve().then(function () {
postMessage("resumed");
});
debugger;
}
postMessage("resolved");
});
};
+3
View File
@@ -28,6 +28,8 @@ support-files =
WorkerDebugger_frozen_iframe2.html
WorkerDebugger_frozen_worker1.js
WorkerDebugger_frozen_worker2.js
WorkerDebugger_promise_debugger.js
WorkerDebugger_promise_worker.js
WorkerDebugger_sharedWorker.js
WorkerDebugger_suspended_debugger.js
WorkerDebugger_suspended_worker.js
@@ -65,6 +67,7 @@ support-files =
[test_WorkerDebuggerManager.xul]
[test_WorkerDebugger_console.xul]
[test_WorkerDebugger_frozen.xul]
[test_WorkerDebugger_promise.xul]
[test_WorkerDebugger_suspended.xul]
[test_bug883784.xul]
[test_chromeWorker.xul]
@@ -12,6 +12,8 @@ const Ci = Components.interfaces;
var mainWindow;
var contentPage = "http://mochi.test:8888/chrome/dom/workers/test/empty.html";
var workerScope = "http://mochi.test:8888/chrome/dom/workers/test/serviceworkers/";
var workerURL = workerScope + "worker.js";
function testOnWindow(aIsPrivate, aCallback) {
var win = mainWindow.OpenBrowserWindow({private: aIsPrivate});
@@ -44,18 +46,34 @@ function setupWindow() {
}
var wN;
var registration;
var wP;
function testPrivateWindow() {
testOnWindow(true, function(aWin) {
wP = aWin;
ok(!("serviceWorker" in wP.content.navigator), "ServiceWorkers are not available for private windows");
runTest();
});
}
function doTests() {
testOnWindow(false, function(aWin) {
wN = aWin;
ok("serviceWorker" in wN.content.navigator, "ServiceWorkers are available for normal windows");
testOnWindow(true, function(aWin) {
wP = aWin;
ok(!("serviceWorker" in wP.content.navigator), "ServiceWorkers are not available for private windows");
runTest();
});
wN.content.navigator.serviceWorker.register(workerURL,
{ scope: workerScope })
.then(function(aRegistration) {
registration = aRegistration;
ok(registration, "Registering a service worker in a normal window should succeed");
// Bug 1255621: We should be able to load a controlled document in a private window.
testPrivateWindow();
}, function(aError) {
ok(false, "Error registering worker in normal window: " + aError);
testPrivateWindow();
});
});
}
@@ -64,12 +82,17 @@ var steps = [
doTests
];
function cleanup() {
wN.close();
wP.close();
SimpleTest.finish();
}
function runTest() {
if (!steps.length) {
wN.close();
wP.close();
registration.unregister().then(cleanup, cleanup);
SimpleTest.finish();
return;
}
@@ -80,6 +103,7 @@ function runTest() {
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true],
["browser.startup.page", 0],
["browser.startup.homepage_override.mstone", "ignore"],
]}, runTest);
@@ -0,0 +1,70 @@
<?xml version="1.0"?>
<!--
Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
-->
<window title="Test for WorkerDebugger with DOM Promises"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="test();">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript" src="dom_worker_helper.js"/>
<script type="application/javascript">
<![CDATA[
const WORKER_URL = "WorkerDebugger_promise_worker.js";
const DEBUGGER_URL = BASE_URL + "WorkerDebugger_promise_debugger.js";
function test() {
Task.spawn(function* () {
SimpleTest.waitForExplicitFinish();
let promise = waitForRegister(WORKER_URL, DEBUGGER_URL);
let worker = new Worker(WORKER_URL);
let dbg = yield promise;
info("Send a request to the worker. This should cause the worker " +
"to send a response.");
promise = waitForWorkerMessage(worker, "resolved");
worker.postMessage("resolve");
yield promise;
info("Send a request to the debugger. This should cause the debugger " +
"to send a response.");
promise = waitForDebuggerMessage(dbg, "resolved");
dbg.postMessage("resolve");
yield promise;
info("Send a request to the worker. This should cause the debugger " +
"to enter a nested event loop.");
promise = waitForDebuggerMessage(dbg, "paused");
worker.postMessage("pause");
yield promise;
info("Send a request to the debugger. This should cause the debugger " +
"to leave the nested event loop.");
promise = waitForMultiple([
waitForDebuggerMessage(dbg, "resumed"),
waitForWorkerMessage(worker, "resumed")
]);
dbg.postMessage("resume");
yield promise;
SimpleTest.finish();
});
}
]]>
</script>
<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display"></p>
<div id="content" style="display:none;"></div>
<pre id="test"></pre>
</body>
<label id="test-result"/>
</window>
+4
View File
@@ -1594,6 +1594,10 @@ ReloadPrefsCallback(const char* pref, void* data)
XPCJSRuntime::~XPCJSRuntime()
{
// Elsewhere we abort immediately if XPCJSRuntime initialization fails.
// Therefore the runtime must be non-null.
MOZ_ASSERT(MaybeRuntime());
// This destructor runs before ~CycleCollectedJSRuntime, which does the
// actual JS_DestroyRuntime() call. But destroying the runtime triggers
// one final GC, which can call back into the runtime with various
-2
View File
@@ -1850,7 +1850,6 @@ nsDocumentViewer::SetDocumentInternal(nsIDocument* aDocument,
}
if (mDocument->IsStaticDocument()) {
mDocument->SetScriptGlobalObject(nullptr);
mDocument->Destroy();
}
@@ -4419,7 +4418,6 @@ nsDocumentViewer::OnDonePrinting()
}
} else if (mClosingWhilePrinting) {
if (mDocument) {
mDocument->SetScriptGlobalObject(nullptr);
mDocument->Destroy();
mDocument = nullptr;
}
+5
View File
@@ -522,6 +522,9 @@
* template arguments are required to be safe to move in memory using
* memmove(). Passing MOZ_NON_MEMMOVABLE types to these templates is a
* compile time error.
* MOZ_NEEDS_MEMMOVABLE_MEMBERS: Applies to class declarations where each member
* must be safe to move in memory using memmove(). MOZ_NON_MEMMOVABLE types
* used in members of these classes are compile time errors.
* MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS: Applies to template class
* declarations where an instance of the template should be considered, for
* static analysis purposes, to inherit any type annotations (such as
@@ -555,6 +558,7 @@
# define MOZ_NEEDS_NO_VTABLE_TYPE __attribute__((annotate("moz_needs_no_vtable_type")))
# define MOZ_NON_MEMMOVABLE __attribute__((annotate("moz_non_memmovable")))
# define MOZ_NEEDS_MEMMOVABLE_TYPE __attribute__((annotate("moz_needs_memmovable_type")))
# define MOZ_NEEDS_MEMMOVABLE_MEMBERS __attribute__((annotate("moz_needs_memmovable_members")))
# define MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS \
__attribute__((annotate("moz_inherit_type_annotations_from_template_args")))
# define MOZ_NON_AUTOABLE __attribute__((annotate("moz_non_autoable")))
@@ -587,6 +591,7 @@
# define MOZ_NEEDS_NO_VTABLE_TYPE /* nothing */
# define MOZ_NON_MEMMOVABLE /* nothing */
# define MOZ_NEEDS_MEMMOVABLE_TYPE /* nothing */
# define MOZ_NEEDS_MEMMOVABLE_MEMBERS /* nothing */
# define MOZ_INHERIT_TYPE_ANNOTATIONS_FROM_TEMPLATE_ARGS /* nothing */
# define MOZ_NON_AUTOABLE /* nothing */
#endif /* MOZ_CLANG_PLUGIN */
+24
View File
@@ -0,0 +1,24 @@
FROM node:4.2
MAINTAINER Dave Townsend <dtownsend@oxymoronical.com>
RUN useradd -d /home/worker -s /bin/bash -m worker
WORKDIR /home/worker
# install necessary npm packages
RUN npm install -g taskcluster-vcs@2.3.12
RUN npm install -g eslint@2.8.0
RUN npm install -g eslint-plugin-html@1.4.0
RUN npm install -g eslint-plugin-react@4.2.3
# Set variable normally configured at login, by the shells parent process, these
# are taken from GNU su manual
ENV HOME /home/worker
ENV SHELL /bin/bash
ENV USER worker
ENV LOGNAME worker
ENV HOSTNAME taskcluster-worker
ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8
# Set a default command useful for debugging
CMD ["/bin/bash", "--login"]
@@ -23,6 +23,7 @@ flags:
- aries
- aries-ota
- aries-eng
- eslint-gecko
tests:
- cppunit
+40
View File
@@ -0,0 +1,40 @@
# This is the "base" task which contains the common values all linting tests must
# provide.
---
taskId: {{build_slugid}}
task:
created: '{{now}}'
deadline: '{{#from_now}}24 hours{{/from_now}}'
metadata:
source: http://todo.com/soon
owner: mozilla-taskcluster-maintenance@mozilla.com
tags:
createdForUser: {{owner}}
workerType: b2gtest
provisionerId: aws-provisioner-v1
schedulerId: task-graph-scheduler
routes:
- 'index.gecko.v1.{{project}}.revision.linux.{{head_rev}}.{{build_name}}.{{build_type}}'
- 'index.gecko.v1.{{project}}.latest.linux.{{build_name}}.{{build_type}}'
scopes:
# Nearly all of our build tasks use tc-vcs so just include the scope across
# the board.
- 'docker-worker:cache:tc-vcs'
payload:
# Thirty minutes should be enough for lint checks
maxRunTime: 1800
cache:
tc-vcs: '/home/worker/.tc-vcs'
extra:
build_product: '{{build_product}}'
build_name: '{{build_name}}'
build_type: '{{build_type}}'
index:
rank: {{pushlog_id}}
@@ -0,0 +1,35 @@
---
$inherits:
from: 'tasks/lint.yml'
task:
metadata:
name: '[TC] - ESLint'
description: 'ESLint test'
payload:
image:
type: 'task-image'
path: 'public/image.tar'
taskId: '{{#task_id_for_image}}lint{{/task_id_for_image}}'
command:
- bash
- -cx
- >
tc-vcs checkout ./gecko {{base_repository}} {{head_repository}} {{head_rev}} {{head_ref}} &&
cd gecko &&
npm link testing/eslint-plugin-mozilla &&
eslint --plugin html --ext [.js,.jsm,.jsx,.xml,.html] -f tools/lint/eslint-formatter .
extra:
locations:
build: null
tests: null
treeherder:
machine:
platform: lint
groupSymbol: tc
symbol: ES
treeherderEnv:
- production
- staging
@@ -0,0 +1,159 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<script>
var worker_url = 'resources/empty-worker.js';
async_test(function(t) {
var scope = 'resources/scope/unregister-then-register-new-script-that-exists';
var new_worker_url = worker_url + '?new';
var iframe;
var registration;
var new_registration;
service_worker_unregister_and_register(t, worker_url, scope)
.then(function(r) {
registration = r;
return wait_for_state(t, r.installing, 'activated');
})
.then(function() {
return with_iframe(scope);
})
.then(function(frame) {
iframe = frame;
return registration.unregister();
})
.then(function() {
return navigator.serviceWorker.register(new_worker_url,
{ scope: scope });
})
.then(function(r) {
new_registration = r;
assert_equals(registration.installing.scriptURL,
normalizeURL(new_worker_url),
'before activated registration.installing');
assert_equals(registration.waiting, null,
'before activated registration.waiting');
assert_equals(registration.active.scriptURL, normalizeURL(worker_url),
'before activated registration.active');
assert_equals(new_registration.installing.scriptURL,
normalizeURL(new_worker_url),
'before activated new_registration.installing');
assert_equals(new_registration.waiting, null,
'before activated new_registration.waiting');
assert_equals(new_registration.active.scriptURL,
normalizeURL(worker_url),
'before activated new_registration.active');
iframe.remove();
return wait_for_state(t, registration.installing, 'activated');
})
.then(function() {
assert_equals(new_registration.installing, null,
'after activated new_registration.installing');
assert_equals(new_registration.waiting, null,
'after activated new_registration.waiting');
assert_equals(new_registration.active.scriptURL,
normalizeURL(new_worker_url),
'after activated new_registration.active');
return with_iframe(scope);
})
.then(function(frame) {
assert_equals(
frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
normalizeURL(new_worker_url),
'the new worker should control a new document');
frame.remove();
return registration.unregister();
})
.then(function() {
t.done();
})
.catch(unreached_rejection(t));
}, 'Registering a new script URL while an unregistered registration is in use');
async_test(function(t) {
var scope = 'resources/scope/unregister-then-register-new-script-that-404s';
var iframe;
var registration;
service_worker_unregister_and_register(t, worker_url, scope)
.then(function(r) {
registration = r;
return wait_for_state(t, r.installing, 'activated');
})
.then(function() {
return with_iframe(scope);
})
.then(function(frame) {
iframe = frame;
return registration.unregister();
})
.then(function() {
var promise = navigator.serviceWorker.register('this-will-404',
{ scope: scope });
iframe.remove();
return promise;
})
.then(
function() {
assert_unreached('register should reject the promise');
},
function() {
return with_iframe(scope);
})
.then(function(frame) {
assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
null,
'document should not load with a controller');
frame.remove();
t.done();
})
.catch(unreached_rejection(t));
}, 'Registering a new script URL that 404s does not resurrect an ' +
'unregistered registration');
async_test(function(t) {
var scope = 'resources/scope/unregister-then-register-reject-install-worker';
var iframe;
var registration;
service_worker_unregister_and_register(t, worker_url, scope)
.then(function(r) {
registration = r;
return wait_for_state(t, r.installing, 'activated');
})
.then(function() {
return with_iframe(scope);
})
.then(function(frame) {
iframe = frame;
return registration.unregister();
})
.then(function() {
var promise = navigator.serviceWorker.register(
'resources/reject-install-worker.js', { scope: scope });
iframe.remove();
return promise;
})
.then(function(r) {
registration = r;
return wait_for_state(t, r.installing, 'redundant');
})
.then(function() {
return with_iframe(scope);
})
.then(function(frame) {
assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
null,
'document should not load with a controller');
frame.remove();
return registration.unregister();
})
.then(function() {
t.done();
})
.catch(unreached_rejection(t));
}, 'Registering a new script URL that fails to install does not resurrect ' +
'an unregistered registration');
</script>
@@ -0,0 +1,124 @@
<!DOCTYPE html>
<title>Service Worker: Registration update()</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/testharness-helpers.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<script>
promise_test(function(t) {
var scope = 'resources/simple.txt';
var worker_url = 'resources/update-worker.py';
var expected_url = normalizeURL(worker_url);
var registration;
var iframe;
return service_worker_unregister_and_register(t, worker_url, scope)
.then(function(r) {
registration = r;
return wait_for_state(t, registration.installing, 'activated');
})
.then(function() {
assert_equals(registration.installing, null,
'installing should be null in the initial state.');
assert_equals(registration.waiting, null,
'waiting should be null in the initial state.');
assert_equals(registration.active.scriptURL, expected_url,
'active should exist in the initial state.');
// A new worker (generated by update-worker.py) should be found.
// The returned promise should resolve when a new worker script is
// fetched and starts installing.
return Promise.all([registration.update(),
wait_for_update(t, registration)]);
})
.then(function() {
assert_equals(registration.installing.scriptURL, expected_url,
'new installing should be set after update resolves.');
assert_equals(registration.waiting, null,
'waiting should still be null after update resolves.');
assert_equals(registration.active.scriptURL, expected_url,
'active should still exist after update found.');
return wait_for_state(t, registration.installing, 'installed');
})
.then(function() {
assert_equals(registration.installing, null,
'installing should be null after installing.');
if (registration.waiting) {
assert_equals(registration.waiting.scriptURL, expected_url,
'waiting should be set after installing.');
assert_equals(registration.active.scriptURL, expected_url,
'active should still exist after installing.');
return wait_for_state(t, registration.waiting, 'activated');
}
})
.then(function() {
assert_equals(registration.installing, null,
'installing should be null after activated.');
assert_equals(registration.waiting, null,
'waiting should be null after activated.');
assert_equals(registration.active.scriptURL, expected_url,
'new worker should be promoted to active.');
})
.then(function() {
// A new worker(generated by update-worker.py) should be found.
// The returned promise should reject as update-worker.py sets the
// mimetype to a disallowed value for this attempt.
return registration.update();
})
.then(
function() { assert_unreached("update() should reject."); },
function(e) {
assert_throws('SecurityError', function() { throw e; },
'Using a disallowed mimetype should make update() ' +
'promise reject with a SecurityError.');
assert_equals(registration.active.scriptURL, expected_url,
'active should still exist after update failure.');
// A new worker(generated by update-worker.py) should be found.
// The returned promise should reject as update-worker.py returns
// a worker script with a syntax error.
return registration.update();
})
.then(
function() { assert_unreached("update() should reject."); },
function(e) {
assert_throws({name: 'TypeError'}, function () { throw e; },
'A script syntax error should make update() ' +
'promise reject with a TypeError.');
assert_equals(registration.active.scriptURL, expected_url,
'active should still exist after update failure.');
// A new worker(generated by update-worker.py) should be found.
// The returned promise should not reject, even though
// update-worker.py returns a worker script that throws in the
// install event handler.
return Promise.all([registration.update(),
wait_for_update(t, registration)]);
})
.then(function() {
assert_equals(registration.installing.scriptURL, expected_url,
'new installing should be set after update resolves.');
assert_equals(registration.waiting, null,
'waiting should be null after activated.');
assert_equals(registration.active.scriptURL, expected_url,
'active should still exist after update found.');
// We need to hold a client alive so that unregister() below doesn't
// remove the registration before update() has had a chance to look
// at the pending uninstall flag.
return with_iframe(scope);
})
.then(function(frame) {
iframe = frame;
return assert_promise_rejects(
Promise.all([registration.unregister(), registration.update()]),
new TypeError(),
'Calling update() while the uninstalling flag is set' +
'should return a promise that rejects with an ' +
'InvalidStateError.');
})
.then(function() {
iframe.remove();
return t.done();
});
}, 'Update a registration.');
</script>
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<title>Service Worker: Navigate a Window</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/get-host-info.sub.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<body>
<script>
var host_info = get_host_info();
var BASE_URL = host_info['HTTPS_ORIGIN'] + base_path();
function wait_for_message(msg) {
return new Promise(function(resolve, reject) {
window.addEventListener('message', function onMsg(evt) {
if (evt.data.type === msg) {
resolve();
}
});
});
}
function with_window(url) {
var win = window.open(url);
return wait_for_message('LOADED').then(_ => win);
}
function navigate_window(win, url) {
win.location = url;
return wait_for_message('LOADED').then(_ => win);
}
function go_back(win) {
win.history.back();
return wait_for_message('PAGESHOW').then(_ => win);
}
function go_forward(win) {
win.history.forward();
return wait_for_message('PAGESHOW').then(_ => win);
}
function get_clients(win, sw, opts) {
return new Promise((resolve, reject) => {
win.navigator.serviceWorker.addEventListener('message', function onMsg(evt) {
win.navigator.serviceWorker.removeEventListener('message', onMsg);
if (evt.data.type === 'success') {
resolve(evt.data.detail);
} else {
reject(evt.data.detail);
}
});
sw.postMessage({ type: 'GET_CLIENTS', opts: (opts || {}) });
});
}
function validate_window(win, url, opts) {
return win.navigator.serviceWorker.getRegistration(url)
.then(reg => {
// In order to compare service worker instances we need to
// make sure the DOM object is owned by the same global; the
// opened window in this case.
assert_equals(win.navigator.serviceWorker.controller, reg.active,
'window should be controlled by service worker');
return get_clients(win, reg.active);
})
.then(resultList => {
assert_equals(resultList.length, 1, 'there should only be one client');
assert_equals(resultList[0].url, url,
'client should be our opened window');
assert_equals(resultList[0].frameType, 'auxiliary',
'window.open() should create a client with an auxiliary frame type');
return win;
})
}
async_test(function(t) {
var worker = BASE_URL + 'resources/navigate-window-worker.js';
var scope = BASE_URL + 'resources/loaded.html?navigate-window';
var url1 = scope + '&q=1';
var url2 = scope + '&q=2';
service_worker_unregister_and_register(t, worker, scope)
.then(reg => wait_for_state(t, reg.installing, 'activated') )
.then(___ => with_window(url1))
.then(win => validate_window(win, url1, { includeUncontrolled: false }))
.then(win => navigate_window(win, url2))
.then(win => validate_window(win, url2, { includeUncontrolled: false }))
.then(win => go_back(win))
.then(win => validate_window(win, url1, { includeUncontrolled: false }))
.then(win => go_forward(win))
.then(win => validate_window(win, url2, { includeUncontrolled: false }))
.then(win => win.close())
.catch(unreached_rejection(t))
.then(___ => service_worker_unregister_and_done(t, scope))
}, 'Clients.matchAll() should not show an old window as controlled after ' +
'it navigates.');
</script>
</body>
@@ -0,0 +1,9 @@
<script>
addEventListener('load', function() {
opener.postMessage({ type: 'LOADED' }, '*');
});
addEventListener('pageshow', function() {
opener.postMessage({ type: 'PAGESHOW' }, '*');
});
</script>
@@ -0,0 +1,21 @@
addEventListener('message', function(evt) {
if (evt.data.type === 'GET_CLIENTS') {
clients.matchAll(evt.data.opts).then(function(clientList) {
var resultList = clientList.map(function(c) {
return { url: c.url, frameType: c.frameType, id: c.id };
});
evt.source.postMessage({ type: 'success', detail: resultList });
}).catch(function(err) {
evt.source.postMessage({
type: 'failure',
detail: 'matchAll() rejected with "' + err + '"'
});
});
return;
}
evt.source.postMessage({
type: 'failure',
detail: 'Unexpected message type "' + evt.data.type + '"'
});
});
@@ -0,0 +1,85 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
const cps = Cc["@mozilla.org/addons/content-policy;1"].getService(Ci.nsIAddonContentPolicy);
add_task(function* test_csp_validator() {
let checkPolicy = (policy, expectedResult, message = null) => {
do_print(`Checking policy: ${policy}`);
let result = cps.validateAddonCSP(policy);
equal(result, expectedResult);
};
checkPolicy("script-src 'self'; object-src 'self';",
null);
let hash = "'sha256-NjZhMDQ1YjQ1MjEwMmM1OWQ4NDBlYzA5N2Q1OWQ5NDY3ZTEzYTNmMzRmNjQ5NGU1MzlmZmQzMmMxYmIzNWYxOCAgLQo='";
checkPolicy(`script-src 'self' https://com https://*.example.com moz-extension://09abcdef blob: filesystem: ${hash} 'unsafe-eval'; ` +
`object-src 'self' https://com https://*.example.com moz-extension://09abcdef blob: filesystem: ${hash}`,
null);
checkPolicy("",
"Policy is missing a required 'script-src' directive");
checkPolicy("object-src 'none';",
"Policy is missing a required 'script-src' directive");
checkPolicy("default-src 'self'", null,
"A valid default-src should count as a valid script-src or object-src");
checkPolicy("default-src 'self'; script-src 'self'", null,
"A valid default-src should count as a valid script-src or object-src");
checkPolicy("default-src 'self'; object-src 'self'", null,
"A valid default-src should count as a valid script-src or object-src");
checkPolicy("default-src 'self'; script-src http://example.com",
"'script-src' directive contains a forbidden http: protocol source",
"A valid default-src should not allow an invalid script-src directive");
checkPolicy("default-src 'self'; object-src http://example.com",
"'object-src' directive contains a forbidden http: protocol source",
"A valid default-src should not allow an invalid object-src directive");
checkPolicy("script-src 'self';",
"Policy is missing a required 'object-src' directive");
checkPolicy("script-src 'none'; object-src 'none'",
"'script-src' must include the source 'self'");
checkPolicy("script-src 'self'; object-src 'none';",
null);
checkPolicy("script-src 'self' 'unsafe-inline'; object-src 'self';",
"'script-src' directive contains a forbidden 'unsafe-inline' keyword");
let directives = ["script-src", "object-src"];
for (let [directive, other] of [directives, directives.slice().reverse()]) {
for (let src of ["https://*", "https://*.blogspot.com", "https://*"]) {
checkPolicy(`${directive} 'self' ${src}; ${other} 'self';`,
`https: wildcard sources in '${directive}' directives must include at least one non-generic sub-domain (e.g., *.example.com rather than *.com)`);
}
checkPolicy(`${directive} 'self' https:; ${other} 'self';`,
`https: protocol requires a host in '${directive}' directives`);
checkPolicy(`${directive} 'self' http://example.com; ${other} 'self';`,
`'${directive}' directive contains a forbidden http: protocol source`);
for (let protocol of ["http", "ftp", "meh"]) {
checkPolicy(`${directive} 'self' ${protocol}:; ${other} 'self';`,
`'${directive}' directive contains a forbidden ${protocol}: protocol source`);
}
checkPolicy(`${directive} 'self' 'nonce-01234'; ${other} 'self';`,
`'${directive}' directive contains a forbidden 'nonce-*' keyword`);
}
});
+4
View File
@@ -40,15 +40,19 @@ toolkit.jar:
content/global/browser-content.js
*+ content/global/buildconfig.html
* content/global/contentAreaUtils.js
#ifndef MOZ_FENNEC
content/global/customizeToolbar.css
* content/global/customizeToolbar.js
content/global/customizeToolbar.xul
#endif
content/global/devicestorage.properties
#ifndef MOZ_FENNEC
content/global/editMenuOverlay.js
*+ content/global/editMenuOverlay.xul
content/global/finddialog.js
*+ content/global/finddialog.xul
content/global/findUtils.js
#endif
content/global/filepicker.properties
*+ content/global/globalOverlay.js
content/global/logopage.xhtml
+3
View File
@@ -18,6 +18,9 @@ if CONFIG['OS_TARGET'] == 'Android':
if CONFIG['MOZ_ANDROID_CXX_STL'] == 'mozstlport':
DEFINES['USE_STLPORT'] = True
if CONFIG['MOZ_BUILD_APP'] == 'mobile/android':
DEFINES['MOZ_FENNEC'] = True
JAR_MANIFESTS += ['jar.mn']
with Files('aboutTelemetry.*'):
-5
View File
@@ -4,11 +4,6 @@
- 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/. -->
<!DOCTYPE bindings [
<!ENTITY % findBarDTD SYSTEM "chrome://global/locale/findbar.dtd" >
%findBarDTD;
]>
<bindings id="browserBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
@@ -0,0 +1,12 @@
csp.error.missing-directive = Policy is missing a required '%S' directive
csp.error.illegal-keyword = '%1$S' directive contains a forbidden %2$S keyword
csp.error.illegal-protocol = '%1$S' directive contains a forbidden %2$S: protocol source
csp.error.missing-host = %2$S: protocol requires a host in '%1$S' directives
csp.error.missing-source = '%1$S' must include the source %2$S
csp.error.illegal-host-wildcard = %2$S: wildcard sources in '%1$S' directives must include at least one non-generic sub-domain (e.g., *.example.com rather than *.com)
+11
View File
@@ -31,8 +31,10 @@
locale/@AB_CD@/global/console.dtd (%chrome/global/console.dtd)
locale/@AB_CD@/global/console.properties (%chrome/global/console.properties)
locale/@AB_CD@/global/contentAreaCommands.properties (%chrome/global/contentAreaCommands.properties)
#ifndef MOZ_FENNEC
locale/@AB_CD@/global/customizeToolbar.dtd (%chrome/global/customizeToolbar.dtd)
locale/@AB_CD@/global/customizeToolbar.properties (%chrome/global/customizeToolbar.properties)
#endif
locale/@AB_CD@/global/datetimepicker.dtd (%chrome/global/datetimepicker.dtd)
locale/@AB_CD@/global/dateFormat.properties (%chrome/global/dateFormat.properties)
locale/@AB_CD@/global/devtools/csscoverage.properties (%chrome/global/devtools/csscoverage.properties)
@@ -94,15 +96,20 @@
locale/@AB_CD@/global/devtools/webide.properties (%chrome/global/devtools/webide.properties)
#endif
locale/@AB_CD@/global/dialogOverlay.dtd (%chrome/global/dialogOverlay.dtd)
#ifndef MOZ_FENNEC
locale/@AB_CD@/global/editMenuOverlay.dtd (%chrome/global/editMenuOverlay.dtd)
#endif
locale/@AB_CD@/global/extensions.properties (%chrome/global/extensions.properties)
locale/@AB_CD@/global/fallbackMenubar.properties (%chrome/global/fallbackMenubar.properties)
locale/@AB_CD@/global/filefield.properties (%chrome/global/filefield.properties)
locale/@AB_CD@/global/filepicker.dtd (%chrome/global/filepicker.dtd)
locale/@AB_CD@/global/filepicker.properties (%chrome/global/filepicker.properties)
#ifndef MOZ_FENNEC
locale/@AB_CD@/global/findbar.dtd (%chrome/global/findbar.dtd)
locale/@AB_CD@/global/findbar.properties (%chrome/global/findbar.properties)
locale/@AB_CD@/global/finddialog.dtd (%chrome/global/finddialog.dtd)
locale/@AB_CD@/global/finddialog.properties (%chrome/global/finddialog.properties)
#endif
locale/@AB_CD@/global/globalKeys.dtd (%chrome/global/globalKeys.dtd)
locale/@AB_CD@/global/headsUpDisplay.properties (%chrome/global/headsUpDisplay.properties)
locale/@AB_CD@/global/intl.css (%chrome/global/intl.css)
@@ -146,6 +153,7 @@
locale/@AB_CD@/mozapps/downloads/downloads.dtd (%chrome/mozapps/downloads/downloads.dtd)
locale/@AB_CD@/mozapps/downloads/downloads.properties (%chrome/mozapps/downloads/downloads.properties)
locale/@AB_CD@/mozapps/extensions/extensions.dtd (%chrome/mozapps/extensions/extensions.dtd)
#ifndef MOZ_FENNEC
locale/@AB_CD@/mozapps/extensions/extensions.properties (%chrome/mozapps/extensions/extensions.properties)
locale/@AB_CD@/mozapps/extensions/blocklist.dtd (%chrome/mozapps/extensions/blocklist.dtd)
locale/@AB_CD@/mozapps/extensions/about.dtd (%chrome/mozapps/extensions/about.dtd)
@@ -153,6 +161,7 @@
locale/@AB_CD@/mozapps/extensions/update.properties (%chrome/mozapps/extensions/update.properties)
locale/@AB_CD@/mozapps/extensions/newaddon.dtd (%chrome/mozapps/extensions/newaddon.dtd)
locale/@AB_CD@/mozapps/extensions/newaddon.properties (%chrome/mozapps/extensions/newaddon.properties)
#endif
locale/@AB_CD@/mozapps/handling/handling.dtd (%chrome/mozapps/handling/handling.dtd)
locale/@AB_CD@/mozapps/handling/handling.properties (%chrome/mozapps/handling/handling.properties)
locale/@AB_CD@/mozapps/plugins/plugins.dtd (%chrome/mozapps/plugins/plugins.dtd)
@@ -170,9 +179,11 @@
% locale alerts @AB_CD@ %locale/@AB_CD@/alerts/
locale/@AB_CD@/alerts/alert.dtd (%chrome/alerts/alert.dtd)
locale/@AB_CD@/alerts/alert.properties (%chrome/alerts/alert.properties)
#ifndef MOZ_FENNEC
% locale cookie @AB_CD@ %locale/@AB_CD@/cookie/
locale/@AB_CD@/cookie/cookieAcceptDialog.dtd (%chrome/cookie/cookieAcceptDialog.dtd)
locale/@AB_CD@/cookie/cookieAcceptDialog.properties (%chrome/cookie/cookieAcceptDialog.properties)
#endif
% locale formautofill @AB_CD@ %locale/@AB_CD@/formautofill/
locale/@AB_CD@/formautofill/requestAutocomplete.dtd (%chrome/formautofill/requestAutocomplete.dtd)
% locale passwordmgr @AB_CD@ %locale/@AB_CD@/passwordmgr/
@@ -6,19 +6,30 @@
#include "AddonContentPolicy.h"
#include "mozilla/dom/nsCSPUtils.h"
#include "nsCOMPtr.h"
#include "nsContentPolicyUtils.h"
#include "nsContentTypeParser.h"
#include "nsContentUtils.h"
#include "nsIConsoleService.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIEffectiveTLDService.h"
#include "nsIScriptError.h"
#include "nsIStringBundle.h"
#include "nsIUUIDGenerator.h"
#include "nsIURI.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
using namespace mozilla;
/* Enforces content policies for WebExtension scopes. Currently:
*
* - Prevents loading scripts with a non-default JavaScript version.
* - Checks custom content security policies for sufficiently stringent
* script-src and object-src directives.
*/
#define VERSIONED_JS_BLOCKED_MESSAGE \
@@ -34,7 +45,7 @@ AddonContentPolicy::~AddonContentPolicy()
{
}
NS_IMPL_ISUPPORTS(AddonContentPolicy, nsIContentPolicy)
NS_IMPL_ISUPPORTS(AddonContentPolicy, nsIContentPolicy, nsIAddonContentPolicy)
static nsresult
GetWindowIDFromContext(nsISupports* aContext, uint64_t *aResult)
@@ -80,6 +91,9 @@ LogMessage(const nsAString &aMessage, nsIURI* aSourceURI, const nsAString &aSour
return NS_OK;
}
// Content policy enforcement:
NS_IMETHODIMP
AddonContentPolicy::ShouldLoad(uint32_t aContentType,
nsIURI* aContentLocation,
@@ -143,3 +157,323 @@ AddonContentPolicy::ShouldProcess(uint32_t aContentType,
*aShouldProcess = nsIContentPolicy::ACCEPT;
return NS_OK;
}
// CSP Validation:
static const char* allowedSchemes[] = {
"blob",
"filesystem",
nullptr
};
static const char* allowedHostSchemes[] = {
"https",
"moz-extension",
nullptr
};
/**
* Validates a CSP directive to ensure that it is sufficiently stringent.
* In particular, ensures that:
*
* - No remote sources are allowed other than from https: schemes
*
* - No remote sources specify host wildcards for generic domains
* (*.blogspot.com, *.com, *)
*
* - All remote sources and local extension sources specify a host
*
* - No scheme sources are allowed other than blob:, filesystem:,
* moz-extension:, and https:
*
* - No keyword sources are allowed other than 'none', 'self', 'unsafe-eval',
* and hash sources.
*/
class CSPValidator final : public nsCSPSrcVisitor {
public:
CSPValidator(nsAString& aURL, CSPDirective aDirective, bool aDirectiveRequired = true) :
mURL(aURL),
mDirective(CSP_CSPDirectiveToString(aDirective)),
mFoundSelf(false)
{
// Start with the default error message for a missing directive, since no
// visitors will be called if the directive isn't present.
if (aDirectiveRequired) {
FormatError("csp.error.missing-directive");
}
}
// Visitors
bool visitSchemeSrc(const nsCSPSchemeSrc& src) override
{
nsAutoString scheme;
src.getScheme(scheme);
if (SchemeInList(scheme, allowedHostSchemes)) {
FormatError("csp.error.missing-host", scheme);
return false;
}
if (!SchemeInList(scheme, allowedSchemes)) {
FormatError("csp.error.illegal-protocol", scheme);
return false;
}
return true;
};
bool visitHostSrc(const nsCSPHostSrc& src) override
{
nsAutoString scheme, host;
src.getScheme(scheme);
src.getHost(host);
if (scheme.LowerCaseEqualsLiteral("https")) {
if (!HostIsAllowed(host)) {
FormatError("csp.error.illegal-host-wildcard", scheme);
return false;
}
} else if (scheme.LowerCaseEqualsLiteral("moz-extension")) {
// The CSP parser silently converts 'self' keywords to the origin
// URL, so we need to reconstruct the URL to see if it was present.
if (!mFoundSelf) {
nsAutoString url(MOZ_UTF16("moz-extension://"));
url.Append(host);
mFoundSelf = url.Equals(mURL);
}
if (host.IsEmpty() || host.EqualsLiteral("*")) {
FormatError("csp.error.missing-host", scheme);
return false;
}
} else if (!SchemeInList(scheme, allowedSchemes)) {
FormatError("csp.error.illegal-protocol", scheme);
return false;
}
return true;
};
bool visitKeywordSrc(const nsCSPKeywordSrc& src) override
{
switch (src.getKeyword()) {
case CSP_NONE:
case CSP_SELF:
case CSP_UNSAFE_EVAL:
return true;
default:
NS_ConvertASCIItoUTF16 keyword(CSP_EnumToKeyword(src.getKeyword()));
FormatError("csp.error.illegal-keyword", keyword);
return false;
}
};
bool visitNonceSrc(const nsCSPNonceSrc& src) override
{
FormatError("csp.error.illegal-keyword", NS_LITERAL_STRING("'nonce-*'"));
return false;
};
bool visitHashSrc(const nsCSPHashSrc& src) override
{
return true;
};
// Accessors
inline nsAString& GetError()
{
return mError;
};
inline bool FoundSelf()
{
return mFoundSelf;
};
// Formatters
template <typename... T>
inline void FormatError(const char* aName, const T ...aParams)
{
const char16_t* params[] = { mDirective.get(), aParams.get()... };
FormatErrorParams(aName, params, MOZ_ARRAY_LENGTH(params));
};
private:
// Validators
bool HostIsAllowed(nsAString& host)
{
if (host.First() == '*') {
if (host.EqualsLiteral("*") || host[1] != '.') {
return false;
}
host.Cut(0, 2);
nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
if (!tldService) {
return false;
}
NS_ConvertUTF16toUTF8 cHost(host);
nsAutoCString publicSuffix;
nsresult rv = tldService->GetPublicSuffixFromHost(cHost, publicSuffix);
return NS_SUCCEEDED(rv) && !cHost.Equals(publicSuffix);
}
return true;
};
bool SchemeInList(nsAString& scheme, const char** schemes)
{
for (; *schemes; schemes++) {
if (scheme.LowerCaseEqualsASCII(*schemes)) {
return true;
}
}
return false;
};
// Formatters
already_AddRefed<nsIStringBundle>
GetStringBundle()
{
nsCOMPtr<nsIStringBundleService> sbs =
mozilla::services::GetStringBundleService();
NS_ENSURE_TRUE(sbs, nullptr);
nsCOMPtr<nsIStringBundle> stringBundle;
sbs->CreateBundle("chrome://global/locale/extensions.properties",
getter_AddRefs(stringBundle));
return stringBundle.forget();
};
void FormatErrorParams(const char* aName, const char16_t** aParams, int32_t aLength)
{
nsresult rv = NS_ERROR_FAILURE;
nsCOMPtr<nsIStringBundle> stringBundle = GetStringBundle();
if (stringBundle) {
NS_ConvertASCIItoUTF16 name(aName);
rv = stringBundle->FormatStringFromName(name.get(), aParams, aLength,
getter_Copies(mError));
}
if (NS_WARN_IF(NS_FAILED(rv))) {
mError.AssignLiteral("An unexpected error occurred");
}
};
// Data members
nsAutoString mURL;
NS_ConvertASCIItoUTF16 mDirective;
nsXPIDLString mError;
bool mFoundSelf;
};
/**
* Validates a custom content security policy string for use by an add-on.
* In particular, ensures that:
*
* - Both object-src and script-src directives are present, and meet
* the policies required by the CSPValidator class
*
* - The script-src directive includes the source 'self'
*/
NS_IMETHODIMP
AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString,
nsAString& aResult)
{
nsresult rv;
// Validate against a randomly-generated extension origin.
// There is no add-on-specific behavior in the CSP code, beyond the ability
// for add-ons to specify a custom policy, but the parser requires a valid
// origin in order to operate correctly.
nsAutoString url(MOZ_UTF16("moz-extension://"));
{
nsCOMPtr<nsIUUIDGenerator> uuidgen = services::GetUUIDGenerator();
NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
nsID id;
rv = uuidgen->GenerateUUIDInPlace(&id);
NS_ENSURE_SUCCESS(rv, rv);
char idString[NSID_LENGTH];
id.ToProvidedString(idString);
MOZ_RELEASE_ASSERT(idString[0] == '{' && idString[NSID_LENGTH - 2] == '}',
"UUID generator did not return a valid UUID");
url.AppendASCII(idString + 1, NSID_LENGTH - 3);
}
RefPtr<BasePrincipal> principal =
BasePrincipal::CreateCodebasePrincipal(NS_ConvertUTF16toUTF8(url));
nsCOMPtr<nsIContentSecurityPolicy> csp;
rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp));
NS_ENSURE_SUCCESS(rv, rv);
csp->AppendPolicy(aPolicyString, false, false);
const nsCSPPolicy* policy = csp->GetPolicy(0);
if (!policy) {
CSPValidator validator(url, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE);
aResult.Assign(validator.GetError());
return NS_OK;
}
bool haveValidDefaultSrc = false;
{
CSPDirective directive = nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
CSPValidator validator(url, directive);
haveValidDefaultSrc = policy->visitDirectiveSrcs(directive, &validator);
}
aResult.SetIsVoid(true);
{
CSPDirective directive = nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE;
CSPValidator validator(url, directive, !haveValidDefaultSrc);
if (!policy->visitDirectiveSrcs(directive, &validator)) {
aResult.Assign(validator.GetError());
} else if (!validator.FoundSelf()) {
validator.FormatError("csp.error.missing-source", NS_LITERAL_STRING("'self'"));
aResult.Assign(validator.GetError());
}
}
if (aResult.IsVoid()) {
CSPDirective directive = nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
CSPValidator validator(url, directive, !haveValidDefaultSrc);
if (!policy->visitDirectiveSrcs(directive, &validator)) {
aResult.Assign(validator.GetError());
}
}
return NS_OK;
}
@@ -5,8 +5,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsIContentPolicy.h"
#include "nsIAddonPolicyService.h"
class AddonContentPolicy : public nsIContentPolicy
class AddonContentPolicy : public nsIContentPolicy,
public nsIAddonContentPolicy
{
protected:
virtual ~AddonContentPolicy();
@@ -16,4 +18,5 @@ public:
NS_DECL_ISUPPORTS
NS_DECL_NSICONTENTPOLICY
NS_DECL_NSIADDONCONTENTPOLICY
};
+23
View File
@@ -0,0 +1,23 @@
/* 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/. */
"use strict";
const path = require("path")
module.exports = function(results) {
for (let file of results) {
let filePath = path.relative(".", file.filePath);
for (let message of file.messages) {
let status = message.message;
if ("ruleId" in message) {
status = `${status} (${message.ruleId})`;
}
let severity = message.severity == 1 ? "TEST-UNEXPECTED-WARNING"
: "TEST-UNEXPECTED-ERROR";
console.log(`${severity} | ${filePath}:${message.line}:${message.column} | ${status}`);
}
}
};
+20 -3
View File
@@ -955,7 +955,7 @@ CycleCollectedJSRuntime::EnqueuePromiseJobCallback(JSContext* aCx,
MOZ_ASSERT(Get() == self);
nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aCx, aJob);
self->GetPromiseMicroTaskQueue().push(runnable);
self->DispatchToMicroTask(runnable);
return true;
}
@@ -1140,6 +1140,13 @@ CycleCollectedJSRuntime::GetPromiseMicroTaskQueue()
return mPromiseMicroTaskQueue;
}
std::queue<nsCOMPtr<nsIRunnable>>&
CycleCollectedJSRuntime::GetDebuggerPromiseMicroTaskQueue()
{
MOZ_ASSERT(mJSRuntime);
return mDebuggerPromiseMicroTaskQueue;
}
nsCycleCollectionParticipant*
CycleCollectedJSRuntime::GCThingParticipant()
{
@@ -1371,10 +1378,11 @@ CycleCollectedJSRuntime::AfterProcessTask(uint32_t aRecursionDepth)
// Step 4.1: Execute microtasks.
if (NS_IsMainThread()) {
nsContentUtils::PerformMainThreadMicroTaskCheckpoint();
Promise::PerformMicroTaskCheckpoint();
} else {
Promise::PerformWorkerMicroTaskCheckpoint();
}
Promise::PerformMicroTaskCheckpoint();
// Step 4.2 Execute any events that were waiting for a stable state.
ProcessStableStateQueue();
}
@@ -1652,6 +1660,15 @@ CycleCollectedJSRuntime::PrepareWaitingZonesForGC()
}
}
void
CycleCollectedJSRuntime::DispatchToMicroTask(nsIRunnable* aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aRunnable);
mPromiseMicroTaskQueue.push(aRunnable);
}
void
CycleCollectedJSRuntime::EnvironmentPreparer::invoke(JS::HandleObject scope,
js::ScriptEnvironmentPreparer::Closure& closure)
+11 -2
View File
@@ -158,6 +158,9 @@ protected:
return true; // Don't block context creation.
}
std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
std::queue<nsCOMPtr<nsIRunnable>> mDebuggerPromiseMicroTaskQueue;
private:
void
DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
@@ -285,6 +288,7 @@ public:
void SetPendingException(nsIException* aException);
std::queue<nsCOMPtr<nsIRunnable>>& GetPromiseMicroTaskQueue();
std::queue<nsCOMPtr<nsIRunnable>>& GetDebuggerPromiseMicroTaskQueue();
nsCycleCollectionParticipant* GCThingParticipant();
nsCycleCollectionParticipant* ZoneParticipant();
@@ -317,6 +321,10 @@ public:
return mJSRuntime;
}
protected:
JSRuntime* MaybeRuntime() const { return mJSRuntime; }
public:
// nsThread entrypoints
virtual void BeforeProcessTask(bool aMightBlock) { };
virtual void AfterProcessTask(uint32_t aRecursionDepth);
@@ -348,6 +356,9 @@ public:
// full GC.
void PrepareWaitingZonesForGC();
// Queue an async microtask to the current main or worker thread.
virtual void DispatchToMicroTask(nsIRunnable* aRunnable);
// Storage for watching rejected promises waiting for some client to
// consume their rejection.
// We store values as `nsISupports` to avoid adding compile-time dependencies
@@ -378,8 +389,6 @@ private:
nsCOMPtr<nsIException> mPendingException;
nsThread* mOwningThread; // Manual refcounting to avoid include hell.
std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
struct RunInMetastableStateData
{
nsCOMPtr<nsIRunnable> mRunnable;