From 5f6d4971d01077a13d474a6db5e6662616cc75f5 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Thu, 2 May 2024 16:55:13 +0800 Subject: [PATCH] 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) --- build/clang-plugin/clang-plugin.cpp | 57 +- .../clang-plugin/tests/TestNonMemMovable.cpp | 28 +- caps/BasePrincipal.cpp | 29 + caps/BasePrincipal.h | 2 + caps/nsIAddonPolicyService.idl | 34 + caps/nsIPrincipal.idl | 5 + caps/nsIScriptSecurityManager.idl | 9 +- caps/nsScriptSecurityManager.cpp | 18 + caps/tests/unit/test_origin.js | 5 +- dom/animation/Animation.cpp | 4 +- dom/animation/CSSPseudoElement.cpp | 13 +- dom/animation/CSSPseudoElement.h | 3 +- dom/animation/TimingParams.cpp | 2 +- dom/animation/TimingParams.h | 3 +- dom/animation/test/chrome.ini | 3 +- .../test_generated_content_getAnimations.html | 83 + .../file_document-get-animations.html | 41 +- .../css-animations/file_effect-target.html | 35 +- .../file_element-get-animations.html | 141 ++ .../file_pseudoElement-get-animations.html | 70 + .../test_pseudoElement-get-animations.html | 14 + .../file_document-get-animations.html | 43 + .../css-transitions/file_effect-target.html | 43 + .../file_pseudoElement-get-animations.html | 45 + .../test_pseudoElement-get-animations.html | 14 + dom/animation/test/testcommon.js | 28 + dom/base/ChromeUtils.cpp | 9 +- dom/base/ChromeUtils.h | 6 +- dom/base/Element.cpp | 57 +- dom/base/Element.h | 15 +- dom/base/nsDocument.cpp | 106 +- dom/base/nsIDocument.h | 5 + dom/bindings/Errors.msg | 6 +- .../security/nsIContentSecurityPolicy.idl | 13 + dom/locales/en-US/chrome/dom/dom.properties | 2 + dom/notification/Notification.cpp | 4 +- dom/promise/Promise.cpp | 97 +- dom/promise/Promise.h | 8 +- dom/security/nsCSPContext.cpp | 9 + dom/security/nsCSPUtils.cpp | 58 + dom/security/nsCSPUtils.h | 64 +- dom/webidl/Animatable.webidl | 6 +- dom/webidl/ChromeUtils.webidl | 12 +- dom/webidl/Promise.webidl | 2 +- dom/workers/RuntimeService.cpp | 34 +- dom/workers/ServiceWorkerInfo.cpp | 205 ++ dom/workers/ServiceWorkerInfo.h | 144 ++ dom/workers/ServiceWorkerJob.cpp | 231 ++ dom/workers/ServiceWorkerJob.h | 155 ++ dom/workers/ServiceWorkerJobQueue.cpp | 134 ++ dom/workers/ServiceWorkerJobQueue.h | 49 + dom/workers/ServiceWorkerManager.cpp | 2026 +++-------------- dom/workers/ServiceWorkerManager.h | 262 +-- dom/workers/ServiceWorkerRegisterJob.cpp | 67 + dom/workers/ServiceWorkerRegisterJob.h | 40 + dom/workers/ServiceWorkerRegistrationInfo.cpp | 450 ++++ dom/workers/ServiceWorkerRegistrationInfo.h | 167 ++ dom/workers/ServiceWorkerUnregisterJob.cpp | 89 + dom/workers/ServiceWorkerUnregisterJob.h | 40 + dom/workers/ServiceWorkerUpdateJob.cpp | 485 ++++ dom/workers/ServiceWorkerUpdateJob.h | 103 + dom/workers/WorkerNavigator.cpp | 2 + dom/workers/WorkerPrivate.cpp | 21 +- dom/workers/WorkerPrivate.h | 4 + dom/workers/WorkerScope.cpp | 6 +- dom/workers/WorkerScope.h | 5 +- dom/workers/moz.build | 9 + .../test/WorkerDebugger_promise_debugger.js | 30 + .../test/WorkerDebugger_promise_worker.js | 25 + dom/workers/test/chrome.ini | 3 + .../serviceworkers/test_privateBrowsing.html | 40 +- .../test/test_WorkerDebugger_promise.xul | 70 + js/xpconnect/src/XPCJSRuntime.cpp | 4 + layout/base/nsDocumentViewer.cpp | 2 - mfbt/Attributes.h | 5 + testing/docker/lint/Dockerfile | 24 + .../tasks/branches/base_job_flags.yml | 1 + testing/taskcluster/tasks/lint.yml | 40 + .../taskcluster/tasks/tests/eslint-gecko.yml | 35 + ...gister-then-register-new-script.https.html | 159 ++ .../service-worker/update.https.html | 124 + .../service-worker/navigate-window.https.html | 97 + .../service-worker/resources/loaded.html | 9 + .../resources/navigate-window-worker.js | 21 + .../test/xpcshell/test_csp_validator.js | 85 + toolkit/content/jar.mn | 4 + toolkit/content/moz.build | 3 + toolkit/content/widgets/browser.xml | 5 - .../en-US/chrome/global/extensions.properties | 12 + toolkit/locales/jar.mn | 11 + .../mozapps/extensions/AddonContentPolicy.cpp | 336 ++- .../mozapps/extensions/AddonContentPolicy.h | 5 +- tools/lint/eslint-formatter.js | 23 + xpcom/base/CycleCollectedJSRuntime.cpp | 23 +- xpcom/base/CycleCollectedJSRuntime.h | 13 +- 95 files changed, 4994 insertions(+), 2194 deletions(-) create mode 100644 dom/animation/test/chrome/test_generated_content_getAnimations.html create mode 100644 dom/animation/test/css-animations/file_pseudoElement-get-animations.html create mode 100644 dom/animation/test/css-animations/test_pseudoElement-get-animations.html create mode 100644 dom/animation/test/css-transitions/file_pseudoElement-get-animations.html create mode 100644 dom/animation/test/css-transitions/test_pseudoElement-get-animations.html create mode 100644 dom/workers/ServiceWorkerInfo.cpp create mode 100644 dom/workers/ServiceWorkerInfo.h create mode 100644 dom/workers/ServiceWorkerJob.cpp create mode 100644 dom/workers/ServiceWorkerJob.h create mode 100644 dom/workers/ServiceWorkerJobQueue.cpp create mode 100644 dom/workers/ServiceWorkerJobQueue.h create mode 100644 dom/workers/ServiceWorkerRegisterJob.cpp create mode 100644 dom/workers/ServiceWorkerRegisterJob.h create mode 100644 dom/workers/ServiceWorkerRegistrationInfo.cpp create mode 100644 dom/workers/ServiceWorkerRegistrationInfo.h create mode 100644 dom/workers/ServiceWorkerUnregisterJob.cpp create mode 100644 dom/workers/ServiceWorkerUnregisterJob.h create mode 100644 dom/workers/ServiceWorkerUpdateJob.cpp create mode 100644 dom/workers/ServiceWorkerUpdateJob.h create mode 100644 dom/workers/test/WorkerDebugger_promise_debugger.js create mode 100644 dom/workers/test/WorkerDebugger_promise_worker.js create mode 100644 dom/workers/test/test_WorkerDebugger_promise.xul create mode 100644 testing/docker/lint/Dockerfile create mode 100644 testing/taskcluster/tasks/lint.yml create mode 100644 testing/taskcluster/tasks/tests/eslint-gecko.yml create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/unregister-then-register-new-script.https.html create mode 100644 testing/web-platform/mozilla/tests/service-workers/service-worker/update.https.html create mode 100644 testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html create mode 100644 testing/web-platform/tests/service-workers/service-worker/resources/loaded.html create mode 100644 testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js create mode 100644 toolkit/components/extensions/test/xpcshell/test_csp_validator.js create mode 100644 toolkit/locales/en-US/chrome/global/extensions.properties create mode 100644 tools/lint/eslint-formatter.js diff --git a/build/clang-plugin/clang-plugin.cpp b/build/clang-plugin/clang-plugin.cpp index ea88c65f7a..f2779bebb0 100644 --- a/build/clang-plugin/clang-plugin.cpp +++ b/build/clang-plugin/clang-plugin.cpp @@ -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("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(); diff --git a/build/clang-plugin/tests/TestNonMemMovable.cpp b/build/clang-plugin/tests/TestNonMemMovable.cpp index 3665fadaea..a726d0ab6b 100644 --- a/build/clang-plugin/tests/TestNonMemMovable.cpp +++ b/build/clang-plugin/tests/TestNonMemMovable.cpp @@ -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 c7; W_Defaulted_Templated_NeedyTemplate8 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 {}; diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp index 8545a13df7..57fbd8f0ba 100644 --- a/caps/BasePrincipal.cpp +++ b/caps/BasePrincipal.cpp @@ -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::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 uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); + NS_ENSURE_SUCCESS(rv, nullptr); + + return BasePrincipal::CreateCodebasePrincipal(uri, attrs); +} + bool BasePrincipal::AddonAllowsLoad(nsIURI* aURI) { diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h index d8faed7f82..f47720c228 100644 --- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -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(aPrin); } static already_AddRefed CreateCodebasePrincipal(nsIURI* aURI, const OriginAttributes& aAttrs); + static already_AddRefed CreateCodebasePrincipal(const nsACString& aOrigin); const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; } uint32_t AppId() const { return mOriginAttributes.mAppId; } diff --git a/caps/nsIAddonPolicyService.idl b/caps/nsIAddonPolicyService.idl index 7a9c1f6621..ff0db01f41 100644 --- a/caps/nsIAddonPolicyService.idl +++ b/caps/nsIAddonPolicyService.idl @@ -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); +}; diff --git a/caps/nsIPrincipal.idl b/caps/nsIPrincipal.idl index 277561a838..eb7c109de2 100644 --- a/caps/nsIPrincipal.idl +++ b/caps/nsIPrincipal.idl @@ -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 diff --git a/caps/nsIScriptSecurityManager.idl b/caps/nsIScriptSecurityManager.idl index 6147f5feb1..dced0afcd7 100644 --- a/caps/nsIScriptSecurityManager.idl +++ b/caps/nsIScriptSecurityManager.idl @@ -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 diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp index 740cd63788..4b0152f1be 100644 --- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -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 prin = BasePrincipal::CreateCodebasePrincipal(aOrigin); + prin.forget(aPrincipal); + return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; +} + NS_IMETHODIMP nsScriptSecurityManager::CreateNullPrincipal(JS::Handle aOriginAttributes, JSContext* aCx, nsIPrincipal** aPrincipal) diff --git a/caps/tests/unit/test_origin.js b/caps/tests/unit/test_origin.js index b492ef97d0..dc2b8975bd 100644 --- a/caps/tests/unit/test_origin.js +++ b/caps/tests/unit/test_origin.js @@ -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)); } } diff --git a/dom/animation/Animation.cpp b/dom/animation/Animation.cpp index b46189f770..b120f35ce0 100644 --- a/dom/animation/Animation.cpp +++ b/dom/animation/Animation.cpp @@ -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> runnable = NS_NewRunnableMethod(this, &Animation::DoFinishNotificationImmediately); - Promise::DispatchToMicroTask(runnable); + runtime->DispatchToMicroTask(runnable); mFinishNotificationTask = runnable; } } diff --git a/dom/animation/CSSPseudoElement.cpp b/dom/animation/CSSPseudoElement.cpp index 588d13aaaa..8712e07c03 100644 --- a/dom/animation/CSSPseudoElement.cpp +++ b/dom/animation/CSSPseudoElement.cpp @@ -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 aGivenProto) } void -CSSPseudoElement::GetAnimations(nsTArray>& aRetVal) +CSSPseudoElement::GetAnimations(const AnimationFilter& filter, + nsTArray>& 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>()); } already_AddRefed diff --git a/dom/animation/CSSPseudoElement.h b/dom/animation/CSSPseudoElement.h index 4082a07aa7..8631eba011 100644 --- a/dom/animation/CSSPseudoElement.h +++ b/dom/animation/CSSPseudoElement.h @@ -57,7 +57,8 @@ public: return retVal.forget(); } - void GetAnimations(nsTArray>& aRetVal); + void GetAnimations(const AnimationFilter& filter, + nsTArray>& aRetVal); already_AddRefed Animate(JSContext* aContext, JS::Handle aFrames, diff --git a/dom/animation/TimingParams.cpp b/dom/animation/TimingParams.cpp index 79603ca486..5f64241d66 100644 --- a/dom/animation/TimingParams.cpp +++ b/dom/animation/TimingParams.cpp @@ -161,7 +161,7 @@ TimingParams::ParseEasing(const nsAString& aEasing, MOZ_ASSERT_UNREACHABLE("unexpected animation-timing-function unit"); break; } - aRv.ThrowTypeError(); + aRv.ThrowTypeError(aEasing); return Nothing(); } diff --git a/dom/animation/TimingParams.h b/dom/animation/TimingParams.h index a97322569c..9ea596a072 100644 --- a/dom/animation/TimingParams.h +++ b/dom/animation/TimingParams.h @@ -60,7 +60,8 @@ struct TimingParams NS_LITERAL_STRING("duration")); } } else if (!aDuration.GetAsString().EqualsLiteral("auto")) { - aRv.ThrowTypeError(); + aRv.ThrowTypeError( + aDuration.GetAsString()); } return result; } diff --git a/dom/animation/test/chrome.ini b/dom/animation/test/chrome.ini index c5fae26349..761e266c3d 100644 --- a/dom/animation/test/chrome.ini +++ b/dom/animation/test/chrome.ini @@ -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' diff --git a/dom/animation/test/chrome/test_generated_content_getAnimations.html b/dom/animation/test/chrome/test_generated_content_getAnimations.html new file mode 100644 index 0000000000..04e0f9bd09 --- /dev/null +++ b/dom/animation/test/chrome/test_generated_content_getAnimations.html @@ -0,0 +1,83 @@ + + + +Test getAnimations() for generated-content elements + + + + + + +
+
+
+
+ + diff --git a/dom/animation/test/css-animations/file_document-get-animations.html b/dom/animation/test/css-animations/file_document-get-animations.html index f57c3cb5b2..abe02d7fc3 100644 --- a/dom/animation/test/css-animations/file_document-get-animations.html +++ b/dom/animation/test/css-animations/file_document-get-animations.html @@ -14,10 +14,6 @@ @keyframes animRight { to { right: 100px } } -.with-before-animation::before { - content: " "; - animation: animLeft 100s; -} diff --git a/dom/animation/test/css-animations/file_effect-target.html b/dom/animation/test/css-animations/file_effect-target.html index 022715634c..9ca78a887a 100644 --- a/dom/animation/test/css-animations/file_effect-target.html +++ b/dom/animation/test/css-animations/file_effect-target.html @@ -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(); diff --git a/dom/animation/test/css-animations/file_element-get-animations.html b/dom/animation/test/css-animations/file_element-get-animations.html index df0476c025..c5f132f235 100644 --- a/dom/animation/test/css-animations/file_element-get-animations.html +++ b/dom/animation/test/css-animations/file_element-get-animations.html @@ -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(); diff --git a/dom/animation/test/css-animations/file_pseudoElement-get-animations.html b/dom/animation/test/css-animations/file_pseudoElement-get-animations.html new file mode 100644 index 0000000000..de1abfbb1c --- /dev/null +++ b/dom/animation/test/css-animations/file_pseudoElement-get-animations.html @@ -0,0 +1,70 @@ + + + + + + + diff --git a/dom/animation/test/css-animations/test_pseudoElement-get-animations.html b/dom/animation/test/css-animations/test_pseudoElement-get-animations.html new file mode 100644 index 0000000000..1e0dc5c825 --- /dev/null +++ b/dom/animation/test/css-animations/test_pseudoElement-get-animations.html @@ -0,0 +1,14 @@ + + + + +
+ diff --git a/dom/animation/test/css-transitions/file_document-get-animations.html b/dom/animation/test/css-transitions/file_document-get-animations.html index d49b2dd5d5..a5d55b76cd 100644 --- a/dom/animation/test/css-transitions/file_document-get-animations.html +++ b/dom/animation/test/css-transitions/file_document-get-animations.html @@ -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); diff --git a/dom/animation/test/css-transitions/file_effect-target.html b/dom/animation/test/css-transitions/file_effect-target.html index b54bff8d39..0f67b0b9aa 100644 --- a/dom/animation/test/css-transitions/file_effect-target.html +++ b/dom/animation/test/css-transitions/file_effect-target.html @@ -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(); diff --git a/dom/animation/test/css-transitions/file_pseudoElement-get-animations.html b/dom/animation/test/css-transitions/file_pseudoElement-get-animations.html new file mode 100644 index 0000000000..5683a14a14 --- /dev/null +++ b/dom/animation/test/css-transitions/file_pseudoElement-get-animations.html @@ -0,0 +1,45 @@ + + + + + + + diff --git a/dom/animation/test/css-transitions/test_pseudoElement-get-animations.html b/dom/animation/test/css-transitions/test_pseudoElement-get-animations.html new file mode 100644 index 0000000000..1e0dc5c825 --- /dev/null +++ b/dom/animation/test/css-transitions/test_pseudoElement-get-animations.html @@ -0,0 +1,14 @@ + + + + +
+ diff --git a/dom/animation/test/testcommon.js b/dom/animation/test/testcommon.js index af537a441f..db355df8e9 100644 --- a/dom/animation/test/testcommon.js +++ b/dom/animation/test/testcommon.js @@ -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. */ diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp index f087a67c29..a0ed93b3eb 100644 --- a/dom/base/ChromeUtils.cpp +++ b/dom/base/ChromeUtils.cpp @@ -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) { diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h index 9eace5c27f..d7f2575328 100644 --- a/dom/base/ChromeUtils.h +++ b/dom/base/ChromeUtils.h @@ -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); }; diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 0eab968932..49c647ba69 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -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& aTarget, } void -Element::GetAnimations(nsTArray>& aAnimations) +Element::GetAnimations(const AnimationFilter& filter, + nsTArray>& 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>()); } -void -Element::GetAnimationsUnsorted(nsTArray>& aAnimations) +/* static */ void +Element::GetAnimationsUnsorted(Element* aElement, + CSSPseudoElementType aPseudoType, + nsTArray>& 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; } diff --git a/dom/base/Element.h b/dom/base/Element.h index a75ded0f0d..09ed24068a 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -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>& aAnimations); - void GetAnimationsUnsorted(nsTArray>& aAnimations); + void GetAnimations(const AnimationFilter& filter, + nsTArray>& aAnimations); + static void GetAnimationsUnsorted(Element* aElement, + CSSPseudoElementType aPseudoType, + nsTArray>& 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). diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 2d111cfffe..4767e9dc7d 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -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 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>& 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>()); + 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 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 swm = mozilla::services::GetServiceWorkerManager(); if (swm) { - nsAutoString documentId; - static_cast(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(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 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(c); - + Telemetry::ID id = static_cast(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; } diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index e445d24036..74cf3dc2fa 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -2844,6 +2844,11 @@ protected: FlushUserFontSet(); } + const nsString& GetId() const + { + return mId; + } + nsCString mReferrer; nsString mLastModified; diff --git a/dom/bindings/Errors.msg b/dom/bindings/Errors.msg index d82e57b8b7..c30d3cc25a 100644 --- a/dom/bindings/Errors.msg +++ b/dom/bindings/Errors.msg @@ -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.") \ No newline at end of file +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?)") diff --git a/dom/interfaces/security/nsIContentSecurityPolicy.idl b/dom/interfaces/security/nsIContentSecurityPolicy.idl index 211d88eb45..c03af3af8c 100644 --- a/dom/interfaces/security/nsIContentSecurityPolicy.idl +++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl @@ -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(). diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 3c77a8c974..d2b6274240 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -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 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 diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index deecdd4cc9..e1737955e3 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -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; } diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index ad44e8e109..1dde79b83a 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -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>& microtaskQueue = runtime->GetPromiseMicroTaskQueue(); @@ -917,10 +921,7 @@ Promise::PerformMicroTaskCheckpoint() return false; } - Maybe cx; - if (NS_IsMainThread()) { - cx.emplace(); - } + AutoSafeJSContext cx; do { nsCOMPtr 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>* microtaskQueue = + &runtime->GetDebuggerPromiseMicroTaskQueue(); + + if (microtaskQueue->empty()) { + microtaskQueue = &runtime->GetPromiseMicroTaskQueue(); + if (microtaskQueue->empty()) { + break; + } + } + + nsCOMPtr 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>* microtaskQueue = + &runtime->GetDebuggerPromiseMicroTaskQueue(); + + if (microtaskQueue->empty()) { + break; + } + + nsCOMPtr 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>& 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 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 task = new PromiseResolveThenableJob(this, valueObj, thenCallback); - DispatchToMicroTask(task); + runtime->DispatchToMicroTask(task); return; } } @@ -2635,6 +2688,8 @@ Promise::MaybeSettle(JS::Handle aValue, void Promise::TriggerPromiseReactions() { + CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get(); + nsTArray> callbacks; callbacks.SwapElements(mState == Resolved ? mResolveCallbacks : mRejectCallbacks); @@ -2644,7 +2699,7 @@ Promise::TriggerPromiseReactions() for (uint32_t i = 0; i < callbacks.Length(); ++i) { RefPtr task = new PromiseReactionJob(this, callbacks[i], mResult); - DispatchToMicroTask(task); + runtime->DispatchToMicroTask(task); } } diff --git a/dom/promise/Promise.h b/dom/promise/Promise.h index 418470ee40..e20a8a6819 100644 --- a/dom/promise/Promise.h +++ b/dom/promise/Promise.h @@ -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, diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp index 5f160b9b51..e9f8aef70c 100644 --- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -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) { diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp index 87d23678c9..aa43fb81bc 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -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 &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& 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; +} diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h index 402a988e2b..75456ff018 100644 --- a/dom/security/nsCSPUtils.h +++ b/dom/security/nsCSPUtils.h @@ -145,7 +145,11 @@ inline const char* CSP_EnumToKeyword(enum CSPKeyword aKey) static_assert((sizeof(CSPStrKeywords) / sizeof(CSPStrKeywords[0]) == static_cast(CSP_LAST_KEYWORD_VALUE)), "CSP_LAST_KEYWORD_VALUE does not match length of CSPStrKeywords"); - return CSPStrKeywords[static_cast(aKey)]; + + if (static_cast(aKey) < static_cast(CSP_LAST_KEYWORD_VALUE)) { + return CSPStrKeywords[static_cast(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 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 &outReportURIs) const; + bool visitSrcs(nsCSPSrcVisitor* aVisitor) const; + private: CSPDirective mDirective; nsTArray 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 mDirectives; diff --git a/dom/webidl/Animatable.webidl b/dom/webidl/Animatable.webidl index f1e0a5564f..128051cd05 100644 --- a/dom/webidl/Animatable.webidl +++ b/dom/webidl/Animatable.webidl @@ -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 getAnimations(); + sequence getAnimations(optional AnimationFilter filter); }; diff --git a/dom/webidl/ChromeUtils.webidl b/dom/webidl/ChromeUtils.webidl index 356931ef9c..85a9f006f2 100644 --- a/dom/webidl/ChromeUtils.webidl +++ b/dom/webidl/ChromeUtils.webidl @@ -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); }; /** diff --git a/dom/webidl/Promise.webidl b/dom/webidl/Promise.webidl index d449368af8..af658c5609 100644 --- a/dom/webidl/Promise.webidl +++ b/dom/webidl/Promise.webidl @@ -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 diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index fc461a2bf0..9e5bf4604c 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -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(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>* microTaskQueue = nullptr; + + JSContext* cx = GetCurrentThreadJSContext(); + NS_ASSERTION(cx, "This should never be null!"); + + JS::Rooted 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; }; diff --git a/dom/workers/ServiceWorkerInfo.cpp b/dom/workers/ServiceWorkerInfo.cpp new file mode 100644 index 0000000000..265d519c33 --- /dev/null +++ b/dom/workers/ServiceWorkerInfo.cpp @@ -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& 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, 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 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 +ServiceWorkerInfo::GetOrCreateInstance(nsPIDOMWindow* aWindow) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aWindow); + + RefPtr 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 diff --git a/dom/workers/ServiceWorkerInfo.h b/dom/workers/ServiceWorkerInfo.h new file mode 100644 index 0000000000..8b2d180623 --- /dev/null +++ b/dom/workers/ServiceWorkerInfo.h @@ -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 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 mInstances; + + RefPtr 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 + GetOrCreateInstance(nsPIDOMWindow* aWindow); +}; + +} // namespace workers +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_workers_serviceworkerinfo_h diff --git a/dom/workers/ServiceWorkerJob.cpp b/dom/workers/ServiceWorkerJob.cpp new file mode 100644 index 0000000000..c634fb9154 --- /dev/null +++ b/dom/workers/ServiceWorkerJob.cpp @@ -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> callbackList; + callbackList.SwapElements(aJob->mResultCallbackList); + + for (RefPtr& 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 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 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> callbackList; + callbackList.SwapElements(mResultCallbackList); + + for (RefPtr& 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(scriptSpec, scope); + } + + // The final callback may drop the last ref to this object. + RefPtr 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 diff --git a/dom/workers/ServiceWorkerJob.h b/dom/workers/ServiceWorkerJob.h new file mode 100644 index 0000000000..56802ed97d --- /dev/null +++ b/dom/workers/ServiceWorkerJob.h @@ -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 mPrincipal; + const nsCString mScope; + const nsCString mScriptSpec; + +private: + RefPtr mFinalCallback; + nsTArray> 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 diff --git a/dom/workers/ServiceWorkerJobQueue.cpp b/dom/workers/ServiceWorkerJobQueue.cpp new file mode 100644 index 0000000000..15a798a4d8 --- /dev/null +++ b/dom/workers/ServiceWorkerJobQueue.cpp @@ -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 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 = 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& tailJob = mJobList[mJobList.Length() - 1]; + if (!tailJob->ResultCallbacksInvoked() && aJob->IsEquivalentTo(tailJob)) { + tailJob->StealResultCallbacksFrom(aJob); + return; + } + + mJobList.AppendElement(aJob); +} + +void +ServiceWorkerJobQueue::CancelAll() +{ + AssertIsOnMainThread(); + + for (RefPtr& 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 diff --git a/dom/workers/ServiceWorkerJobQueue.h b/dom/workers/ServiceWorkerJobQueue.h new file mode 100644 index 0000000000..2af8682b36 --- /dev/null +++ b/dom/workers/ServiceWorkerJobQueue.h @@ -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> 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 diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 72560413bb..9a1ee542bc 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -64,12 +64,17 @@ #include "ServiceWorker.h" #include "ServiceWorkerClient.h" #include "ServiceWorkerContainer.h" +#include "ServiceWorkerInfo.h" +#include "ServiceWorkerJobQueue.h" #include "ServiceWorkerManagerChild.h" #include "ServiceWorkerPrivate.h" +#include "ServiceWorkerRegisterJob.h" #include "ServiceWorkerRegistrar.h" #include "ServiceWorkerRegistration.h" #include "ServiceWorkerScriptCache.h" #include "ServiceWorkerEvents.h" +#include "ServiceWorkerUnregisterJob.h" +#include "ServiceWorkerUpdateJob.h" #include "SharedWorker.h" #include "WorkerInlines.h" #include "WorkerPrivate.h" @@ -145,173 +150,12 @@ struct ServiceWorkerManager::RegistrationDataPerPrincipal final nsRefPtrHashtable mInfos; // Maps scopes to job queues. - nsClassHashtable mJobQueues; + nsRefPtrHashtable mJobQueues; // Map scopes to scheduled update timers. nsInterfaceHashtable mUpdateTimers; }; -struct ServiceWorkerManager::PendingOperation final -{ - nsCOMPtr mRunnable; - - ServiceWorkerJobQueue* mQueue; - RefPtr mJob; - - ServiceWorkerRegistrationData mRegistration; -}; - -class ServiceWorkerJob : public nsISupports -{ - friend class ServiceWorkerJobQueue; - -public: - NS_DECL_ISUPPORTS - - enum Type - { - RegisterJob, - UpdateJob, - InstallJob, - UnregisterJob - }; - - virtual void Start() = 0; - - bool - IsRegisterOrInstallJob() const - { - return mJobType == RegisterJob || mJobType == UpdateJob || - mJobType == InstallJob; - } - -protected: - // The queue keeps the jobs alive, so they can hold a rawptr back to the - // queue. - ServiceWorkerJobQueue* mQueue; - - Type mJobType; - - explicit ServiceWorkerJob(ServiceWorkerJobQueue* aQueue, Type aJobType) - : mQueue(aQueue) - , mJobType(aJobType) - {} - - virtual ~ServiceWorkerJob() - {} - - void - Done(nsresult aStatus); -}; - -class ServiceWorkerJobQueue final -{ - friend class ServiceWorkerJob; - - struct QueueData final - { - QueueData() - : mPopping(false) - { } - - ~QueueData() - { - if (!mJobs.IsEmpty()) { - NS_WARNING("Pending/running jobs still around on shutdown!"); - } - } - - nsTArray> mJobs; - bool mPopping; - }; - - const nsCString mScopeKey; - QueueData mRegistrationJobQueue; - QueueData mInstallationJobQueue; - -public: - explicit ServiceWorkerJobQueue(const nsACString& aScopeKey) - : mScopeKey(aScopeKey) - {} - - ~ServiceWorkerJobQueue() - { } - - void - Append(ServiceWorkerJob* aJob) - { - MOZ_ASSERT(aJob); - QueueData& queue = GetQueue(aJob->mJobType); - MOZ_ASSERT(!queue.mJobs.Contains(aJob)); - - bool wasEmpty = queue.mJobs.IsEmpty(); - queue.mJobs.AppendElement(aJob); - if (wasEmpty) { - aJob->Start(); - } - } - - void - CancelJobs(); - -private: - void - CancelJobs(QueueData& aQueue); - - // Internal helper function used to assign jobs to the correct queue. - QueueData& - GetQueue(ServiceWorkerJob::Type aType) - { - switch (aType) { - case ServiceWorkerJob::Type::RegisterJob: - case ServiceWorkerJob::Type::UpdateJob: - case ServiceWorkerJob::Type::UnregisterJob: - return mRegistrationJobQueue; - case ServiceWorkerJob::Type::InstallJob: - return mInstallationJobQueue; - default: - MOZ_CRASH("Invalid job queue type."); - return mRegistrationJobQueue; - } - } - - bool - IsEmpty() - { - return mRegistrationJobQueue.mJobs.IsEmpty() && - mInstallationJobQueue.mJobs.IsEmpty(); - } - - void - Pop(QueueData& aQueue) - { - MOZ_ASSERT(!aQueue.mPopping, - "Pop() called recursively, did you write a job which calls Done() synchronously from Start()?"); - - AutoRestore savePopping(aQueue.mPopping); - aQueue.mPopping = true; - MOZ_ASSERT(!aQueue.mJobs.IsEmpty()); - aQueue.mJobs.RemoveElementAt(0); - if (!aQueue.mJobs.IsEmpty()) { - aQueue.mJobs[0]->Start(); - } else if (IsEmpty()) { - RefPtr swm = ServiceWorkerManager::GetInstance(); - MOZ_ASSERT(swm); - swm->MaybeRemoveRegistrationInfo(mScopeKey); - } - } - - void - Done(ServiceWorkerJob* aJob) - { - MOZ_ASSERT(aJob); - QueueData& queue = GetQueue(aJob->mJobType); - MOZ_ASSERT(!queue.mJobs.IsEmpty()); - MOZ_ASSERT(queue.mJobs[0] == aJob); - Pop(queue); - } -}; - namespace { nsresult @@ -338,9 +182,9 @@ PopulateRegistrationData(nsIPrincipal* aPrincipal, return NS_ERROR_FAILURE; } - if (aRegistration->mActiveWorker) { - aData.currentWorkerURL() = aRegistration->mActiveWorker->ScriptSpec(); - aData.cacheName() = aRegistration->mActiveWorker->CacheName(); + if (aRegistration->GetActive()) { + aData.currentWorkerURL() = aRegistration->GetActive()->ScriptSpec(); + aData.cacheName() = aRegistration->GetActive()->CacheName(); } return NS_OK; @@ -370,200 +214,6 @@ private: } // namespace -NS_IMPL_ISUPPORTS0(ServiceWorkerJob) - -void -ServiceWorkerJob::Done(nsresult aStatus) -{ - if (NS_WARN_IF(NS_FAILED(aStatus))) { -#ifdef DEBUG - nsAutoCString errorName; - GetErrorName(aStatus, errorName); -#endif - NS_WARNING(nsPrintfCString("ServiceWorkerJob failed with error: %s", - errorName.get()).get()); - } - - if (mQueue) { - mQueue->Done(this); - } -} - -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); - - nsresult rv = serviceWorkerScriptCache::PurgeCache(mPrincipal, - mWaitingWorker->CacheName()); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to purge the waiting cache."); - } - - mWaitingWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo(); - mWaitingWorker = nullptr; - } - - if (mActiveWorker) { - mActiveWorker->UpdateState(ServiceWorkerState::Redundant); - - nsresult rv = serviceWorkerScriptCache::PurgeCache(mPrincipal, - mActiveWorker->CacheName()); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to purge the active cache."); - } - - mActiveWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo(); - mActiveWorker = nullptr; - } - - RefPtr swm = ServiceWorkerManager::GetInstance(); - MOZ_ASSERT(swm); - swm->InvalidateServiceWorkerRegistrationWorker(this, - 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) - , mUpdating(false) - , 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 newest = Newest(); - if (newest) { - CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec); - } - return NS_OK; -} - -NS_IMETHODIMP -ServiceWorkerRegistrationInfo::GetInstallingWorker(nsIServiceWorkerInfo **aResult) -{ - AssertIsOnMainThread(); - nsCOMPtr info = do_QueryInterface(mInstallingWorker); - info.forget(aResult); - return NS_OK; -} - -NS_IMETHODIMP -ServiceWorkerRegistrationInfo::GetWaitingWorker(nsIServiceWorkerInfo **aResult) -{ - AssertIsOnMainThread(); - nsCOMPtr info = do_QueryInterface(mWaitingWorker); - info.forget(aResult); - return NS_OK; -} - -NS_IMETHODIMP -ServiceWorkerRegistrationInfo::GetActiveWorker(nsIServiceWorkerInfo **aResult) -{ - AssertIsOnMainThread(); - nsCOMPtr info = do_QueryInterface(mActiveWorker); - info.forget(aResult); - return NS_OK; -} - -NS_IMETHODIMP -ServiceWorkerRegistrationInfo::GetWorkerByID(uint64_t aID, nsIServiceWorkerInfo **aResult) -{ - AssertIsOnMainThread(); - MOZ_ASSERT(aResult); - - RefPtr 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 -ServiceWorkerRegistrationInfo::GetServiceWorkerInfoById(uint64_t aId) -{ - RefPtr 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(); -} - ////////////////////////// // ServiceWorkerManager // ////////////////////////// @@ -623,55 +273,14 @@ ServiceWorkerManager::Init() } } -class ContinueLifecycleTask : public nsISupports +class ContinueActivateRunnable final : public LifeCycleEventCallback { - NS_DECL_ISUPPORTS - -protected: - virtual ~ContinueLifecycleTask() - {} - -public: - virtual void ContinueAfterWorkerEvent(bool aSuccess) = 0; -}; - -NS_IMPL_ISUPPORTS0(ContinueLifecycleTask); - -class ServiceWorkerInstallJob; - -class ContinueInstallTask final : public ContinueLifecycleTask -{ - RefPtr mJob; - -public: - explicit ContinueInstallTask(ServiceWorkerInstallJob* aJob) - : mJob(aJob) - {} - - void ContinueAfterWorkerEvent(bool aSuccess) override; -}; - -class ContinueActivateTask final : public ContinueLifecycleTask -{ - RefPtr mRegistration; - -public: - explicit ContinueActivateTask(ServiceWorkerRegistrationInfo* aReg) - : mRegistration(aReg) - {} - - void - ContinueAfterWorkerEvent(bool aSuccess) override; -}; - -class ContinueLifecycleRunnable final : public LifeCycleEventCallback -{ - nsMainThreadPtrHandle mTask; + nsMainThreadPtrHandle mRegistration; bool mSuccess; public: - explicit ContinueLifecycleRunnable(const nsMainThreadPtrHandle& aTask) - : mTask(aTask) + explicit ContinueActivateRunnable(const nsMainThreadPtrHandle& aRegistration) + : mRegistration(aRegistration) , mSuccess(false) { AssertIsOnMainThread(); @@ -687,61 +296,51 @@ public: Run() override { AssertIsOnMainThread(); - mTask->ContinueAfterWorkerEvent(mSuccess); + mRegistration->FinishActivate(mSuccess); + mRegistration = nullptr; return NS_OK; } }; -class ServiceWorkerResolveWindowPromiseOnUpdateCallback final : public ServiceWorkerUpdateFinishCallback +class ServiceWorkerResolveWindowPromiseOnRegisterCallback final : public ServiceWorkerJob::Callback { RefPtr mWindow; // The promise "returned" by the call to Update up to // navigator.serviceWorker.register(). RefPtr mPromise; - ~ServiceWorkerResolveWindowPromiseOnUpdateCallback() + ~ServiceWorkerResolveWindowPromiseOnRegisterCallback() {} + virtual void + JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) override + { + AssertIsOnMainThread(); + MOZ_ASSERT(aJob); + + if (aStatus.Failed()) { + mPromise->MaybeReject(aStatus); + return; + } + + MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Register); + RefPtr registerJob = + static_cast(aJob); + RefPtr reg = registerJob->GetRegistration(); + + RefPtr swr = + mWindow->GetServiceWorkerRegistration(NS_ConvertUTF8toUTF16(reg->mScope)); + mPromise->MaybeResolve(swr); + } + public: - ServiceWorkerResolveWindowPromiseOnUpdateCallback(nsPIDOMWindow* aWindow, Promise* aPromise) + ServiceWorkerResolveWindowPromiseOnRegisterCallback(nsPIDOMWindow* aWindow, + Promise* aPromise) : mWindow(aWindow) , mPromise(aPromise) {} - void - UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) override - { - RefPtr swr = - mWindow->GetServiceWorkerRegistration(NS_ConvertUTF8toUTF16(aInfo->mScope)); - mPromise->MaybeResolve(swr); - } - - void - UpdateFailed(ErrorResult& aStatus) override - { - mPromise->MaybeReject(aStatus); - } -}; - -class ContinueUpdateRunnable final : public LifeCycleEventCallback -{ - nsMainThreadPtrHandle mJob; - bool mScriptEvaluationResult; -public: - explicit ContinueUpdateRunnable(const nsMainThreadPtrHandle aJob) - : mJob(aJob) - , mScriptEvaluationResult(false) - { - AssertIsOnMainThread(); - } - - void - SetResult(bool aResult) - { - mScriptEvaluationResult = aResult; - } - - NS_IMETHOD Run(); + NS_INLINE_DECL_REFCOUNTING(ServiceWorkerResolveWindowPromiseOnRegisterCallback, override) }; namespace { @@ -943,719 +542,6 @@ private: } // namespace -class ServiceWorkerJobBase : public ServiceWorkerJob -{ -public: - ServiceWorkerJobBase(ServiceWorkerJobQueue* aQueue, - ServiceWorkerJob::Type aJobType, - nsIPrincipal* aPrincipal, - const nsACString& aScope, - const nsACString& aScriptSpec, - ServiceWorkerUpdateFinishCallback* aCallback, - ServiceWorkerInfo* aServiceWorkerInfo) - : ServiceWorkerJob(aQueue, aJobType) - , mPrincipal(aPrincipal) - , mScope(aScope) - , mScriptSpec(aScriptSpec) - , mCallback(aCallback) - , mUpdateAndInstallInfo(aServiceWorkerInfo) - , mCanceled(false) - { - AssertIsOnMainThread(); - MOZ_ASSERT(aPrincipal); - } - - void - Cancel() - { - mQueue = nullptr; - mCanceled = true; - } - -protected: - nsCOMPtr mPrincipal; - const nsCString mScope; - const nsCString mScriptSpec; - RefPtr mCallback; - RefPtr mRegistration; - RefPtr mUpdateAndInstallInfo; - bool mCanceled; - - ~ServiceWorkerJobBase() - { } - - // Ensure that mRegistration is set for the job. Also, if mRegistration was - // already set, ensure that a new registration object has not replaced it in - // the ServiceWorkerManager. This can happen when jobs race such that the - // registration is cleared and recreated while an update job is executing. - nsresult - EnsureAndVerifyRegistration() - { - AssertIsOnMainThread(); - - RefPtr swm = ServiceWorkerManager::GetInstance(); - if (NS_WARN_IF(!swm)) { - mRegistration = nullptr; - return NS_ERROR_NOT_AVAILABLE; - } - - RefPtr registration = - swm->GetRegistration(mPrincipal, mScope); - - if (NS_WARN_IF(!registration)) { - mRegistration = nullptr; - return NS_ERROR_NOT_AVAILABLE; - } - - if (NS_WARN_IF(mRegistration && registration != mRegistration)) { - mRegistration = nullptr; - return NS_ERROR_NOT_AVAILABLE; - } - - mRegistration = registration.forget(); - return NS_OK; - } - - void - Succeed() - { - AssertIsOnMainThread(); - // We don't have a callback for soft updates. - if (mCallback) { - mCallback->UpdateSucceeded(mRegistration); - mCallback = nullptr; - } - } - - // This MUST only be called when the job is still performing actions related - // to registration or update. After the spec resolves the update promise, use - // Done() with the failure code instead. - // Callers MUST hold a strong ref before calling this! - void - FailWithErrorResult(ErrorResult& aRv) - { - AssertIsOnMainThread(); - - // With cancellation support, we may only be running with one reference - // from another object like a stream loader or something. - RefPtr kungFuDeathGrip = this; - - // Save off the plain error code to pass to Done() where its logged to - // stderr as a warning. - nsresult origStatus = static_cast(aRv.ErrorCodeAsInt()); - - // 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(scriptSpec, scope); - } - - if (mCallback) { - mCallback->UpdateFailed(aRv); - mCallback = nullptr; - } - // In case the callback does not consume the exception - aRv.SuppressException(); - - mUpdateAndInstallInfo = nullptr; - - if (!mRegistration) { - Done(origStatus); - return; - } - - if (mRegistration->mInstallingWorker) { - nsresult rv = serviceWorkerScriptCache::PurgeCache(mPrincipal, - mRegistration->mInstallingWorker->CacheName()); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to purge the installing worker cache."); - } - } - - RefPtr swm = ServiceWorkerManager::GetInstance(); - swm->MaybeRemoveRegistration(mRegistration); - // Ensures that the job can't do anything useful from this point on. - mRegistration = nullptr; - Done(origStatus); - } - - void - Fail(nsresult aRv) - { - ErrorResult rv(aRv); - FailWithErrorResult(rv); - } -}; - -class ServiceWorkerInstallJob final : public ServiceWorkerJobBase -{ - friend class ContinueInstallTask; - -public: - enum InstallType { - UpdateSameScript, - OverwriteScript - }; - - ServiceWorkerInstallJob(ServiceWorkerJobQueue* aQueue, - nsIPrincipal* aPrincipal, - const nsACString& aScope, - const nsACString& aScriptSpec, - ServiceWorkerUpdateFinishCallback* aCallback, - ServiceWorkerInfo* aServiceWorkerInfo, - InstallType aType) - : ServiceWorkerJobBase(aQueue, Type::InstallJob, aPrincipal, aScope, - aScriptSpec, aCallback, aServiceWorkerInfo) - , mType(aType) - { - } - - void - Start() - { - AssertIsOnMainThread(); - nsCOMPtr r = - NS_NewRunnableMethod(this, &ServiceWorkerInstallJob::Install); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r)); - } - - void - Install() - { - RefPtr kungFuDeathGrip = this; - - if (mCanceled) { - return Fail(NS_ERROR_DOM_ABORT_ERR); - } - - nsresult rv = EnsureAndVerifyRegistration(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return Fail(NS_ERROR_DOM_ABORT_ERR); - } - - // If we are trying to install an update for an existing script, then - // make sure we don't overwrite a recent script change or resurrect a - // dead registration. - if (mType == UpdateSameScript) { - RefPtr newest = mRegistration->Newest(); - if (!newest || !mScriptSpec.Equals(newest->ScriptSpec())) { - return Fail(NS_ERROR_DOM_ABORT_ERR); - } - } - - // Begin [[Install]] atomic step 3. - if (mRegistration->mInstallingWorker) { - mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); - mRegistration->mInstallingWorker->WorkerPrivate()->TerminateWorker(); - } - - RefPtr swm = ServiceWorkerManager::GetInstance(); - swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, - WhichServiceWorker::INSTALLING_WORKER); - - mRegistration->mInstallingWorker = mUpdateAndInstallInfo.forget(); - mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Installing); - mRegistration->NotifyListenersOnChange(); - - Succeed(); - - // The job should NOT call fail from this point on. - - // Step 8 "Queue a task..." for updatefound. - nsCOMPtr upr = - NS_NewRunnableMethodWithArg( - swm, - &ServiceWorkerManager::FireUpdateFoundOnServiceWorkerRegistrations, - mRegistration); - - NS_DispatchToMainThread(upr); - - // Call ContinueAfterInstallEvent(false) on main thread if the SW - // script fails to load. - nsCOMPtr failRunnable = NS_NewRunnableMethodWithArgs - (this, &ServiceWorkerInstallJob::ContinueAfterInstallEvent, false); - - nsMainThreadPtrHandle installTask( - new nsMainThreadPtrHolder(new ContinueInstallTask(this))); - RefPtr callback = new ContinueLifecycleRunnable(installTask); - - // This triggers Step 4.7 "Queue a task to run the following substeps..." - // which sends the install event to the worker. - ServiceWorkerPrivate* workerPrivate = - mRegistration->mInstallingWorker->WorkerPrivate(); - rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("install"), - callback, failRunnable); - - if (NS_WARN_IF(NS_FAILED(rv))) { - ContinueAfterInstallEvent(false /* aSuccess */); - } - } - - void - ContinueAfterInstallEvent(bool aInstallEventSuccess) - { - if (mCanceled) { - return Done(NS_ERROR_DOM_ABORT_ERR); - } - - nsresult rv = EnsureAndVerifyRegistration(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return Fail(NS_ERROR_DOM_ABORT_ERR); - } - - if (!mRegistration->mInstallingWorker) { - NS_WARNING("mInstallingWorker was null."); - return Done(NS_ERROR_DOM_ABORT_ERR); - } - - RefPtr swm = ServiceWorkerManager::GetInstance(); - - // "If installFailed is true" - if (NS_WARN_IF(!aInstallEventSuccess)) { - mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); - mRegistration->mInstallingWorker = nullptr; - swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, - WhichServiceWorker::INSTALLING_WORKER); - swm->MaybeRemoveRegistration(mRegistration); - return Done(NS_ERROR_DOM_ABORT_ERR); - } - - // "If registration's waiting worker is not null" - if (mRegistration->mWaitingWorker) { - mRegistration->mWaitingWorker->WorkerPrivate()->TerminateWorker(); - mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Redundant); - - nsresult rv = - serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal, - mRegistration->mWaitingWorker->CacheName()); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to purge the old waiting cache."); - } - } - - mRegistration->mWaitingWorker = mRegistration->mInstallingWorker.forget(); - mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Installed); - mRegistration->NotifyListenersOnChange(); - swm->StoreRegistration(mPrincipal, mRegistration); - swm->InvalidateServiceWorkerRegistrationWorker(mRegistration, - WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER); - - Done(NS_OK); - // Activate() is invoked out of band of atomic. - mRegistration->TryToActivateAsync(); - } - -private: - const InstallType mType; -}; - -class ServiceWorkerRegisterJob final : public ServiceWorkerJobBase, - public serviceWorkerScriptCache::CompareCallback -{ - friend class ContinueUpdateRunnable; - - nsCOMPtr mLoadGroup; - - ~ServiceWorkerRegisterJob() - { } - -public: - NS_DECL_ISUPPORTS_INHERITED - - // [[Register]] - ServiceWorkerRegisterJob(ServiceWorkerJobQueue* aQueue, - nsIPrincipal* aPrincipal, - const nsACString& aScope, - const nsACString& aScriptSpec, - ServiceWorkerUpdateFinishCallback* aCallback, - nsILoadGroup* aLoadGroup) - : ServiceWorkerJobBase(aQueue, Type::RegisterJob, aPrincipal, aScope, - aScriptSpec, aCallback, nullptr) - , mLoadGroup(aLoadGroup) - { - AssertIsOnMainThread(); - MOZ_ASSERT(mLoadGroup); - MOZ_ASSERT(aCallback); - } - - // [[Update]] - ServiceWorkerRegisterJob(ServiceWorkerJobQueue* aQueue, - nsIPrincipal* aPrincipal, - const nsACString& aScope, - const nsACString& aScriptSpec, - ServiceWorkerUpdateFinishCallback* aCallback) - : ServiceWorkerJobBase(aQueue, Type::UpdateJob, aPrincipal, aScope, - aScriptSpec, aCallback, nullptr) - { - AssertIsOnMainThread(); - } - - void - Start() override - { - AssertIsOnMainThread(); - MOZ_ASSERT(!mCanceled); - - RefPtr swm = ServiceWorkerManager::GetInstance(); - if (!swm->HasBackgroundActor()) { - nsCOMPtr runnable = - NS_NewRunnableMethod(this, &ServiceWorkerRegisterJob::Start); - swm->AppendPendingOperation(runnable); - return; - } - - if (mJobType == RegisterJob) { - MOZ_ASSERT(!mRegistration); - mRegistration = swm->GetRegistration(mPrincipal, mScope); - - if (mRegistration) { - // 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 (mRegistration->mPendingUninstall) { - swm->StoreRegistration(mPrincipal, mRegistration); - } - mRegistration->mPendingUninstall = false; - RefPtr newest = mRegistration->Newest(); - if (newest && mScriptSpec.Equals(newest->ScriptSpec())) { - Succeed(); - - // Done() must always be called async from Start() - nsCOMPtr runnable = - NS_NewRunnableMethodWithArg( - this, - &ServiceWorkerRegisterJob::Done, - NS_OK); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable)); - - return; - } - } else { - mRegistration = swm->CreateNewRegistration(mScope, mPrincipal); - } - } else { - MOZ_ASSERT(mJobType == UpdateJob); - - nsresult rv = EnsureAndVerifyRegistration(); - if (NS_WARN_IF(NS_FAILED(rv))) { - // Do nothing here, but since mRegistration is nullptr we will - // trigger the async Fail() call below. - MOZ_ASSERT(!mRegistration); - } - - // "If registration's uninstalling flag is set, abort these steps." - if (mRegistration && mRegistration->mPendingUninstall) { - nsCOMPtr runnable = - NS_NewRunnableMethodWithArg( - this, - &ServiceWorkerRegisterJob::Fail, - NS_ERROR_DOM_INVALID_STATE_ERR); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable)); - - return; - } - - // If a different script spec has been registered between when this update - // was scheduled and it running now, then simply abort. - RefPtr newest = mRegistration ? mRegistration->Newest() - : nullptr; - if (!mRegistration || - (newest && !mScriptSpec.Equals(newest->ScriptSpec()))) { - - // Done() must always be called async from Start() - nsCOMPtr runnable = - NS_NewRunnableMethodWithArg( - this, - &ServiceWorkerRegisterJob::Fail, - NS_ERROR_DOM_ABORT_ERR); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable)); - - return; - } - } - - Update(); - } - - void - ComparisonResult(nsresult aStatus, bool aInCacheAndEqual, - const nsAString& aNewCacheName, - const nsACString& aMaxScope) override - { - RefPtr kungFuDeathGrip = this; - - if (NS_WARN_IF(mCanceled)) { - Fail(NS_ERROR_DOM_ABORT_ERR); - return; - } - - if (NS_WARN_IF(NS_FAILED(aStatus))) { - Fail(aStatus); - return; - } - - nsresult rv = EnsureAndVerifyRegistration(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return Fail(NS_ERROR_DOM_ABORT_ERR); - } - - if (aInCacheAndEqual) { - Succeed(); - Done(NS_OK); - return; - } - - AssertIsOnMainThread(); - Telemetry::Accumulate(Telemetry::SERVICE_WORKER_UPDATED, 1); - - RefPtr swm = ServiceWorkerManager::GetInstance(); - - nsCOMPtr scriptURI; - rv = NS_NewURI(getter_AddRefs(scriptURI), mScriptSpec); - if (NS_WARN_IF(NS_FAILED(rv))) { - Fail(NS_ERROR_DOM_SECURITY_ERR); - return; - } - nsCOMPtr maxScopeURI; - if (!aMaxScope.IsEmpty()) { - rv = NS_NewURI(getter_AddRefs(maxScopeURI), aMaxScope, - nullptr, scriptURI); - if (NS_WARN_IF(NS_FAILED(rv))) { - Fail(NS_ERROR_DOM_SECURITY_ERR); - return; - } - } - - nsAutoCString defaultAllowedPrefix; - rv = GetRequiredScopeStringPrefix(scriptURI, defaultAllowedPrefix, - eUseDirectory); - if (NS_WARN_IF(NS_FAILED(rv))) { - Fail(NS_ERROR_DOM_SECURITY_ERR); - return; - } - nsAutoCString maxPrefix(defaultAllowedPrefix); - if (maxScopeURI) { - rv = GetRequiredScopeStringPrefix(maxScopeURI, maxPrefix, eUsePath); - if (NS_WARN_IF(NS_FAILED(rv))) { - Fail(NS_ERROR_DOM_SECURITY_ERR); - return; - } - } - - if (!StringBeginsWith(mRegistration->mScope, maxPrefix)) { - NS_WARNING("By default a service worker's scope is restricted to at or below it's script's location."); - Fail(NS_ERROR_DOM_SECURITY_ERR); - return; - } - - nsAutoCString scopeKey; - rv = swm->PrincipalToScopeKey(mRegistration->mPrincipal, scopeKey); - if (NS_WARN_IF(NS_FAILED(rv))) { - return Fail(NS_ERROR_FAILURE); - } - - ServiceWorkerManager::RegistrationDataPerPrincipal* data; - if (!swm->mRegistrationInfos.Get(scopeKey, &data)) { - return Fail(NS_ERROR_FAILURE); - } - - MOZ_ASSERT(!mUpdateAndInstallInfo); - mUpdateAndInstallInfo = - new ServiceWorkerInfo(mRegistration->mPrincipal, mRegistration->mScope, - mScriptSpec, aNewCacheName); - - RefPtr upcasted = this; - nsMainThreadPtrHandle handle( - new nsMainThreadPtrHolder(upcasted)); - RefPtr callback = new ContinueUpdateRunnable(handle); - - ServiceWorkerPrivate* workerPrivate = - mUpdateAndInstallInfo->WorkerPrivate(); - rv = workerPrivate->CheckScriptEvaluation(callback); - - if (NS_WARN_IF(NS_FAILED(rv))) { - Fail(NS_ERROR_DOM_ABORT_ERR); - } - } - -private: - // This will perform steps 27 and 28 from [[Update]] - // Remove the job from the registration queue and invoke [[Install]] - void - ContinueInstall(bool aScriptEvaluationResult) - { - AssertIsOnMainThread(); - - nsresult rv = EnsureAndVerifyRegistration(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return Fail(NS_ERROR_DOM_ABORT_ERR); - } - - mRegistration->mUpdating = false; - - RefPtr kungFuDeathGrip = this; - if (mCanceled) { - return Fail(NS_ERROR_DOM_ABORT_ERR); - } - - if (NS_WARN_IF(!aScriptEvaluationResult)) { - ErrorResult error; - - NS_ConvertUTF8toUTF16 scriptSpec(mScriptSpec); - NS_ConvertUTF8toUTF16 scope(mRegistration->mScope); - error.ThrowTypeError(scriptSpec, scope); - return FailWithErrorResult(error); - } - - // For updates we want to make sure our install job does not end up - // changing the script for the registration. Since a registration - // script change can be queued in an install job, we can not - // conclusively verify that the update install should proceed here. - // Instead, we have to pass a flag into our install job indicating - // if a script change is allowed or not. This can then be used to - // check the current script after all previous install jobs have been - // flushed. - ServiceWorkerInstallJob::InstallType installType = - mJobType == UpdateJob ? ServiceWorkerInstallJob::UpdateSameScript - : ServiceWorkerInstallJob::OverwriteScript; - - RefPtr job = - new ServiceWorkerInstallJob(mQueue, mPrincipal, mScope, mScriptSpec, - mCallback, mUpdateAndInstallInfo, - installType); - mQueue->Append(job); - Done(NS_OK); - } - - void - Update() - { - AssertIsOnMainThread(); - - // Since Update() is called synchronously from Start(), we can assert this. - MOZ_ASSERT(!mCanceled); - MOZ_ASSERT(mRegistration); - nsCOMPtr r = - NS_NewRunnableMethod(this, &ServiceWorkerRegisterJob::ContinueUpdate); - NS_DispatchToMainThread(r); - - mRegistration->mUpdating = true; - } - - // Aspects of (actually the whole algorithm) of [[Update]] after - // "Run the following steps in parallel." - void - ContinueUpdate() - { - AssertIsOnMainThread(); - RefPtr kungFuDeathGrip = this; - - if (mCanceled) { - return Fail(NS_ERROR_DOM_ABORT_ERR); - } - - nsresult rv = EnsureAndVerifyRegistration(); - if (NS_WARN_IF(NS_FAILED(rv))) { - return Fail(NS_ERROR_DOM_ABORT_ERR); - } - - if (mRegistration->mInstallingWorker) { - mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant); - mRegistration->mInstallingWorker->WorkerPrivate()->TerminateWorker(); - mRegistration->mInstallingWorker = nullptr; - } - - RefPtr workerInfo = mRegistration->Newest(); - nsAutoString cacheName; - - // 9.2.20 If newestWorker is not null, and newestWorker's script url is - // equal to registration's registering script url and response is a - // byte-for-byte match with the script resource of newestWorker... - if (workerInfo && workerInfo->ScriptSpec().Equals(mScriptSpec)) { - cacheName = workerInfo->CacheName(); - } - - rv = serviceWorkerScriptCache::Compare(mRegistration, mPrincipal, cacheName, - NS_ConvertUTF8toUTF16(mScriptSpec), - this, mLoadGroup); - if (NS_WARN_IF(NS_FAILED(rv))) { - return Fail(rv); - } - } - - void - Done(nsresult aStatus) - { - AssertIsOnMainThread(); - - if (mRegistration) { - mRegistration->mUpdating = false; - } - - ServiceWorkerJob::Done(aStatus); - } -}; - -NS_IMPL_ISUPPORTS_INHERITED0(ServiceWorkerRegisterJob, ServiceWorkerJob); - -void -ServiceWorkerJobQueue::CancelJobs() -{ - // The order doesn't matter. Cancel() just sets a flag on these jobs. - CancelJobs(mRegistrationJobQueue); - CancelJobs(mInstallationJobQueue); -} - -void -ServiceWorkerJobQueue::CancelJobs(QueueData& aQueue) -{ - if (aQueue.mJobs.IsEmpty()) { - return; - } - - // We have to treat the first job specially. It is the running job and needs - // to be notified correctly. - RefPtr runningJob = aQueue.mJobs[0]; - // We can just let an Unregister job run to completion. - if (runningJob->IsRegisterOrInstallJob()) { - ServiceWorkerJobBase* job = static_cast(runningJob.get()); - job->Cancel(); - } - - // Get rid of everything. Non-main thread objects may still be holding a ref - // to the running register job. Since we called Cancel() on it, the job's - // main thread functions will just exit. - aQueue.mJobs.Clear(); -} - -NS_IMETHODIMP -ContinueUpdateRunnable::Run() -{ - AssertIsOnMainThread(); - RefPtr job = static_cast(mJob.get()); - RefPtr upjob = static_cast(job.get()); - upjob->ContinueInstall(mScriptEvaluationResult); - return NS_OK; -} - -void -ContinueInstallTask::ContinueAfterWorkerEvent(bool aSuccess) -{ - // This does not start the job immediately if there are other jobs in the - // queue, which captures the "atomic" behaviour we want. - mJob->ContinueAfterInstallEvent(aSuccess); -} - // This function implements parts of the step 3 of the following algorithm: // https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-secure static bool @@ -1809,11 +695,11 @@ ServiceWorkerManager::Register(nsIDOMWindow* aWindow, AddRegisteringDocument(cleanedScope, doc); - ServiceWorkerJobQueue* queue = GetOrCreateJobQueue(scopeKey, cleanedScope); - MOZ_ASSERT(queue); + RefPtr queue = GetOrCreateJobQueue(scopeKey, + cleanedScope); - RefPtr cb = - new ServiceWorkerResolveWindowPromiseOnUpdateCallback(window, promise); + RefPtr cb = + new ServiceWorkerResolveWindowPromiseOnRegisterCallback(window, promise); nsCOMPtr docLoadGroup = doc->GetDocumentLoadGroup(); RefPtr ir = @@ -1827,9 +713,10 @@ ServiceWorkerManager::Register(nsIDOMWindow* aWindow, MOZ_ALWAYS_SUCCEEDS(loadGroup->SetNotificationCallbacks(ir)); RefPtr job = - new ServiceWorkerRegisterJob(queue, documentPrincipal, cleanedScope, spec, - cb, loadGroup); - queue->Append(job); + new ServiceWorkerRegisterJob(documentPrincipal, cleanedScope, spec, + loadGroup); + job->AppendResultCallback(cb); + queue->ScheduleJob(job); AssertIsOnMainThread(); Telemetry::Accumulate(Telemetry::SERVICE_WORKER_REGISTRATIONS, 1); @@ -1838,21 +725,6 @@ ServiceWorkerManager::Register(nsIDOMWindow* aWindow, return NS_OK; } -void -ServiceWorkerManager::AppendPendingOperation(ServiceWorkerJobQueue* aQueue, - ServiceWorkerJob* aJob) -{ - MOZ_ASSERT(!mActor); - MOZ_ASSERT(aQueue); - MOZ_ASSERT(aJob); - - if (!mShuttingDown) { - PendingOperation* opt = mPendingOperations.AppendElement(); - opt->mQueue = aQueue; - opt->mJob = aJob; - } -} - void ServiceWorkerManager::AppendPendingOperation(nsIRunnable* aRunnable) { @@ -1860,102 +732,7 @@ ServiceWorkerManager::AppendPendingOperation(nsIRunnable* aRunnable) MOZ_ASSERT(aRunnable); if (!mShuttingDown) { - PendingOperation* opt = mPendingOperations.AppendElement(); - opt->mRunnable = aRunnable; - } -} - -void -ServiceWorkerRegistrationInfo::TryToActivateAsync() -{ - nsCOMPtr 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 -ContinueActivateTask::ContinueAfterWorkerEvent(bool aSuccess) -{ - mRegistration->FinishActivate(aSuccess); -} - -void -ServiceWorkerRegistrationInfo::PurgeActiveWorker() -{ - RefPtr exitingWorker = mActiveWorker.forget(); - if (!exitingWorker) - return; - - // FIXME(jaoo): Bug 1170543 - Wait for exitingWorker to finish and terminate it. - exitingWorker->UpdateState(ServiceWorkerState::Redundant); - nsresult rv = serviceWorkerScriptCache::PurgeCache(mPrincipal, - exitingWorker->CacheName()); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to purge the activating cache."); - } - RefPtr swm = ServiceWorkerManager::GetInstance(); - swm->InvalidateServiceWorkerRegistrationWorker(this, WhichServiceWorker::ACTIVE_WORKER); -} - -void -ServiceWorkerRegistrationInfo::Activate() -{ - RefPtr activatingWorker = mWaitingWorker; - if (!activatingWorker) { - return; - } - - PurgeActiveWorker(); - - RefPtr swm = ServiceWorkerManager::GetInstance(); - swm->InvalidateServiceWorkerRegistrationWorker(this, WhichServiceWorker::WAITING_WORKER); - - mActiveWorker = activatingWorker.forget(); - mWaitingWorker = nullptr; - mActiveWorker->UpdateState(ServiceWorkerState::Activating); - NotifyListenersOnChange(); - - // FIXME(nsm): Unlink appcache if there is one. - - swm->CheckPendingReadyPromises(); - - // "Queue a task to fire a simple event named controllerchange..." - nsCOMPtr controllerChangeRunnable = - NS_NewRunnableMethodWithArg(swm, - &ServiceWorkerManager::FireControllerChange, - this); - NS_DispatchToMainThread(controllerChangeRunnable); - - nsCOMPtr failRunnable = - NS_NewRunnableMethodWithArg(this, - &ServiceWorkerRegistrationInfo::FinishActivate, - false /* success */); - - nsMainThreadPtrHandle continueActivateTask( - new nsMainThreadPtrHolder(new ContinueActivateTask(this))); - RefPtr callback = - new ContinueLifecycleRunnable(continueActivateTask); - - ServiceWorkerPrivate* workerPrivate = mActiveWorker->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; + mPendingOperations.AppendElement(aRunnable); } } @@ -2404,7 +1181,7 @@ ServiceWorkerManager::CheckReadyPromise(nsPIDOMWindow* aWindow, RefPtr registration = GetServiceWorkerRegistrationInfo(principal, aURI); - if (registration && registration->mActiveWorker) { + if (registration && registration->GetActive()) { NS_ConvertUTF8toUTF16 scope(registration->mScope); RefPtr swr = aWindow->GetServiceWorkerRegistration(scope); @@ -2434,7 +1211,7 @@ ServiceWorkerManager::GetActiveWorkerInfoForScope(const OriginAttributes& aOrigi return nullptr; } - return registration->mActiveWorker; + return registration->GetActive(); } ServiceWorkerInfo* @@ -2449,123 +1226,49 @@ ServiceWorkerManager::GetActiveWorkerInfoForDocument(nsIDocument* aDocument) return nullptr; } - return registration->mActiveWorker; + return registration->GetActive(); } -class ServiceWorkerUnregisterJob final : public ServiceWorkerJob -{ - RefPtr mRegistration; - const nsCString mScope; - nsCOMPtr mCallback; - nsCOMPtr mPrincipal; - const bool mSendToParent; +namespace { - ~ServiceWorkerUnregisterJob() - {} +class UnregisterJobCallback final : public ServiceWorkerJob::Callback +{ + nsCOMPtr mCallback; + + ~UnregisterJobCallback() + { + } public: - ServiceWorkerUnregisterJob(ServiceWorkerJobQueue* aQueue, - const nsACString& aScope, - nsIServiceWorkerUnregisterCallback* aCallback, - nsIPrincipal* aPrincipal, - bool aSendToParent = true) - : ServiceWorkerJob(aQueue, Type::UnregisterJob) - , mScope(aScope) - , mCallback(aCallback) - , mPrincipal(aPrincipal) - , mSendToParent(aSendToParent) + explicit UnregisterJobCallback(nsIServiceWorkerUnregisterCallback* aCallback) + : mCallback(aCallback) { AssertIsOnMainThread(); + MOZ_ASSERT(mCallback); } void - Start() override + JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) { AssertIsOnMainThread(); - nsCOMPtr r = - NS_NewRunnableMethod(this, &ServiceWorkerUnregisterJob::UnregisterAndDone); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r)); + MOZ_ASSERT(aJob); + + if (aStatus.Failed()) { + mCallback->UnregisterFailed(); + return; + } + + MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Unregister); + RefPtr unregisterJob = + static_cast(aJob); + mCallback->UnregisterSucceeded(unregisterJob->GetResult()); } -private: - // You probably want UnregisterAndDone(). - nsresult - Unregister() - { - AssertIsOnMainThread(); - - PrincipalInfo principalInfo; - if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(mPrincipal, - &principalInfo)))) { - return mCallback ? mCallback->UnregisterSucceeded(false) : NS_OK; - } - - RefPtr swm = ServiceWorkerManager::GetInstance(); - - nsAutoCString scopeKey; - nsresult rv = swm->PrincipalToScopeKey(mPrincipal, scopeKey); - if (NS_WARN_IF(NS_FAILED(rv))) { - return mCallback ? mCallback->UnregisterSucceeded(false) : NS_OK; - } - - // "Let registration be the result of running [[Get Registration]] - // algorithm passing scope as the argument." - ServiceWorkerManager::RegistrationDataPerPrincipal* data; - if (!swm->mRegistrationInfos.Get(scopeKey, &data)) { - // "If registration is null, then, resolve promise with false." - return mCallback ? mCallback->UnregisterSucceeded(false) : NS_OK; - } - - RefPtr registration; - if (!data->mInfos.Get(mScope, getter_AddRefs(registration))) { - // "If registration is null, then, resolve promise with false." - return mCallback ? mCallback->UnregisterSucceeded(false) : NS_OK; - } - - MOZ_ASSERT(registration); - - // 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->mActor) { - swm->mActor->SendUnregister(principalInfo, NS_ConvertUTF8toUTF16(mScope)); - } - - // "Set registration's uninstalling flag." - registration->mPendingUninstall = true; - // "Resolve promise with true" - rv = mCallback ? mCallback->UnregisterSucceeded(true) : NS_OK; - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - // "If no service worker client is using registration..." - if (!registration->IsControllingDocuments()) { - // "If registration's uninstalling flag is set.." - if (!registration->mPendingUninstall) { - return NS_OK; - } - - // "Invoke [[Clear Registration]]..." - swm->RemoveRegistration(registration); - } - - return NS_OK; - } - - // The unregister job is done irrespective of success or failure of any sort. - void - UnregisterAndDone() - { - nsresult rv = Unregister(); - Unused << NS_WARN_IF(NS_FAILED(rv)); - Done(rv); - } + NS_INLINE_DECL_REFCOUNTING(UnregisterJobCallback) }; +} // anonymous namespace + NS_IMETHODIMP ServiceWorkerManager::Unregister(nsIPrincipal* aPrincipal, nsIServiceWorkerUnregisterCallback* aCallback, @@ -2596,18 +1299,17 @@ ServiceWorkerManager::Unregister(nsIPrincipal* aPrincipal, } NS_ConvertUTF16toUTF8 scope(aScope); - ServiceWorkerJobQueue* queue = GetOrCreateJobQueue(scopeKey, scope); - MOZ_ASSERT(queue); + RefPtr queue = GetOrCreateJobQueue(scopeKey, scope); RefPtr job = - new ServiceWorkerUnregisterJob(queue, scope, aCallback, aPrincipal); + new ServiceWorkerUnregisterJob(aPrincipal, scope, true /* send to parent */); - if (mActor) { - queue->Append(job); - return NS_OK; + if (aCallback) { + RefPtr cb = new UnregisterJobCallback(aCallback); + job->AppendResultCallback(cb); } - AppendPendingOperation(queue, job); + queue->ScheduleJob(job); return NS_OK; } @@ -2637,22 +1339,17 @@ ServiceWorkerManager::NotifyUnregister(nsIPrincipal* aPrincipal, } NS_ConvertUTF16toUTF8 scope(aScope); - ServiceWorkerJobQueue* queue = GetOrCreateJobQueue(scopeKey, scope); - MOZ_ASSERT(queue); + RefPtr queue = GetOrCreateJobQueue(scopeKey, scope); RefPtr job = - new ServiceWorkerUnregisterJob(queue, scope, nullptr, aPrincipal, false); + new ServiceWorkerUnregisterJob(aPrincipal, scope, + false /* send to parent */); - if (mActor) { - queue->Append(job); - return NS_OK; - } - - AppendPendingOperation(queue, job); + queue->ScheduleJob(job); return NS_OK; } -ServiceWorkerJobQueue* +already_AddRefed ServiceWorkerManager::GetOrCreateJobQueue(const nsACString& aKey, const nsACString& aScope) { @@ -2663,13 +1360,14 @@ ServiceWorkerManager::GetOrCreateJobQueue(const nsACString& aKey, mRegistrationInfos.Put(aKey, data); } - ServiceWorkerJobQueue* queue; - if (!data->mJobQueues.Get(aScope, &queue)) { - queue = new ServiceWorkerJobQueue(aKey); - data->mJobQueues.Put(aScope, queue); + RefPtr queue; + if (!data->mJobQueues.Get(aScope, getter_AddRefs(queue))) { + RefPtr newQueue = new ServiceWorkerJobQueue(); + queue = newQueue; + data->mJobQueues.Put(aScope, newQueue.forget()); } - return queue; + return queue.forget(); } /* static */ @@ -2707,9 +1405,13 @@ ServiceWorkerManager::ReportToAllClients(const nsCString& aScope, uint32_t aFlags) { nsCOMPtr uri; - nsresult rv = NS_NewURI(getter_AddRefs(uri), aFilename); - if (NS_WARN_IF(NS_FAILED(rv))) { - return; + nsresult rv; + + if (!aFilename.IsEmpty()) { + rv = NS_NewURI(getter_AddRefs(uri), aFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } } AutoTArray windows; @@ -2880,104 +1582,6 @@ ServiceWorkerManager::HandleError(JSContext* aCx, aColumnNumber, aFlags); } -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 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() -{ - nsTArray> listeners(mListeners); - for (size_t index = 0; index < listeners.Length(); ++index) { - listeners[index]->OnChange(); - } -} - -void -ServiceWorkerRegistrationInfo::MaybeScheduleTimeCheckAndUpdate() -{ - AssertIsOnMainThread(); - - RefPtr swm = ServiceWorkerManager::GetInstance(); - if (!swm) { - // shutting down, do nothing - return; - } - - if (mUpdateState == NoUpdate) { - mUpdateState = NeedTimeCheckAndUpdate; - } - - swm->ScheduleUpdateTimer(mPrincipal, mScope); -} - -void -ServiceWorkerRegistrationInfo::MaybeScheduleUpdate() -{ - AssertIsOnMainThread(); - - RefPtr 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; -} - void ServiceWorkerManager::LoadRegistration( const ServiceWorkerRegistrationData& aRegistration) @@ -2997,8 +1601,8 @@ ServiceWorkerManager::LoadRegistration( } else { // If active worker script matches our expectations for a "current worker", // then we are done. - if (registration->mActiveWorker && - registration->mActiveWorker->ScriptSpec() == aRegistration.currentWorkerURL()) { + if (registration->GetActive() && + registration->GetActive()->ScriptSpec() == aRegistration.currentWorkerURL()) { // No needs for updates. return; } @@ -3006,10 +1610,10 @@ ServiceWorkerManager::LoadRegistration( const nsCString& currentWorkerURL = aRegistration.currentWorkerURL(); if (!currentWorkerURL.IsEmpty()) { - registration->mActiveWorker = + registration->SetActive( new ServiceWorkerInfo(registration->mPrincipal, registration->mScope, - currentWorkerURL, aRegistration.cacheName()); - registration->mActiveWorker->SetActivateStateUncheckedWithoutEvent(ServiceWorkerState::Activated); + currentWorkerURL, aRegistration.cacheName())); + registration->GetActive()->SetActivateStateUncheckedWithoutEvent(ServiceWorkerState::Activated); } } @@ -3036,6 +1640,11 @@ ServiceWorkerManager::ActorCreated(mozilla::ipc::PBackgroundChild* aActor) MOZ_ASSERT(aActor); MOZ_ASSERT(!mActor); + if (mShuttingDown) { + mPendingOperations.Clear(); + return; + } + PServiceWorkerManagerChild* actor = aActor->SendPServiceWorkerManagerConstructor(); @@ -3043,17 +1652,10 @@ ServiceWorkerManager::ActorCreated(mozilla::ipc::PBackgroundChild* aActor) // Flush the pending requests. for (uint32_t i = 0, len = mPendingOperations.Length(); i < len; ++i) { - MOZ_ASSERT(mPendingOperations[i].mRunnable || - (mPendingOperations[i].mJob && mPendingOperations[i].mQueue)); - - if (mPendingOperations[i].mRunnable) { - nsresult rv = NS_DispatchToCurrentThread(mPendingOperations[i].mRunnable); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to dispatch a runnable."); - return; - } - } else { - mPendingOperations[i].mQueue->Append(mPendingOperations[i].mJob); + MOZ_ASSERT(mPendingOperations[i]); + nsresult rv = NS_DispatchToCurrentThread(mPendingOperations[i].forget()); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch a runnable."); } } @@ -3344,12 +1946,7 @@ ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc, const nsAString& aDocumentId) { AssertIsOnMainThread(); - - // We keep a set of documents that service workers may choose to start - // controlling using claim(). - MOZ_ASSERT(!mAllDocuments.Contains(aDoc)); - mAllDocuments.PutEntry(aDoc); - + MOZ_ASSERT(aDoc); RefPtr registration = GetServiceWorkerRegistrationInfo(aDoc); if (registration) { @@ -3361,6 +1958,7 @@ ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc, void ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc) { + AssertIsOnMainThread(); MOZ_ASSERT(aDoc); RefPtr registration; mControlledDocuments.Remove(aDoc, getter_AddRefs(registration)); @@ -3370,8 +1968,6 @@ ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc) if (registration) { StopControllingADocument(registration); } - - mAllDocuments.RemoveEntry(aDoc); } void @@ -3421,9 +2017,9 @@ ServiceWorkerManager::StopControllingADocument(ServiceWorkerRegistrationInfo* aR } else { // If the registration has an active worker that is running // this might be a good time to stop it. - if (aRegistration->mActiveWorker) { + if (aRegistration->GetActive()) { ServiceWorkerPrivate* serviceWorkerPrivate = - aRegistration->mActiveWorker->WorkerPrivate(); + aRegistration->GetActive()->WorkerPrivate(); serviceWorkerPrivate->NoteStoppedControllingDocuments(); } aRegistration->TryToActivateAsync(); @@ -3557,11 +2153,11 @@ ServiceWorkerManager::GetServiceWorkerForScope(nsIDOMWindow* aWindow, RefPtr info; if (aWhichWorker == WhichServiceWorker::INSTALLING_WORKER) { - info = registration->mInstallingWorker; + info = registration->GetInstalling(); } else if (aWhichWorker == WhichServiceWorker::WAITING_WORKER) { - info = registration->mWaitingWorker; + info = registration->GetWaiting(); } else if (aWhichWorker == WhichServiceWorker::ACTIVE_WORKER) { - info = registration->mActiveWorker; + info = registration->GetActive(); } else { MOZ_CRASH("Invalid worker type"); } @@ -3700,8 +2296,8 @@ ServiceWorkerManager::DispatchFetchEvent(const OriginAttributes& aOriginAttribut } // This should only happen if IsAvailable() returned true. - MOZ_ASSERT(registration->mActiveWorker); - serviceWorker = registration->mActiveWorker; + MOZ_ASSERT(registration->GetActive()); + serviceWorker = registration->GetActive(); AddNavigationInterception(serviceWorker->Scope(), aChannel); } @@ -3742,7 +2338,7 @@ ServiceWorkerManager::IsAvailable(nsIPrincipal* aPrincipal, RefPtr registration = GetServiceWorkerRegistrationInfo(aPrincipal, aURI); - return registration && registration->mActiveWorker; + return registration && registration->GetActive(); } bool @@ -3750,6 +2346,12 @@ ServiceWorkerManager::IsControlled(nsIDocument* aDoc, ErrorResult& aRv) { MOZ_ASSERT(aDoc); + if (nsContentUtils::IsInPrivateBrowsing(aDoc)) { + // Handle the case where a service worker was previously registered in + // a non-private window (bug 1255621). + return false; + } + RefPtr registration; nsresult rv = GetDocumentRegistration(aDoc, getter_AddRefs(registration)); if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE)) { @@ -3758,7 +2360,6 @@ ServiceWorkerManager::IsControlled(nsIDocument* aDoc, ErrorResult& aRv) return false; } - MOZ_ASSERT_IF(!!registration, !nsContentUtils::IsInPrivateBrowsing(aDoc)); return !!registration; } @@ -3772,7 +2373,7 @@ ServiceWorkerManager::GetDocumentRegistration(nsIDocument* aDoc, } // If the document is controlled, the current worker MUST be non-null. - if (!registration->mActiveWorker) { + if (!registration->GetActive()) { return NS_ERROR_NOT_AVAILABLE; } @@ -3802,9 +2403,9 @@ ServiceWorkerManager::GetDocumentController(nsIDOMWindow* aWindow, return rv; } - MOZ_ASSERT(registration->mActiveWorker); + MOZ_ASSERT(registration->GetActive()); RefPtr serviceWorker = - registration->mActiveWorker->GetOrCreateInstance(window); + registration->GetActive()->GetOrCreateInstance(window); serviceWorker.forget(aServiceWorker); return NS_OK; @@ -3885,6 +2486,10 @@ ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes, { AssertIsOnMainThread(); + if (mShuttingDown) { + return; + } + nsCOMPtr scopeURI; nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -3915,7 +2520,7 @@ ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes, } // "If registration's installing worker is not null, abort these steps." - if (registration->mInstallingWorker) { + if (registration->GetInstalling()) { return; } @@ -3931,17 +2536,55 @@ ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes, // or its equivalent, with client, registration as its argument." // TODO(catalinb): We don't implement the force bypass cache flag. // See: https://github.com/slightlyoff/ServiceWorker/issues/759 - if (!registration->mUpdating) { - ServiceWorkerJobQueue* queue = GetOrCreateJobQueue(scopeKey, aScope); - MOZ_ASSERT(queue); + RefPtr queue = GetOrCreateJobQueue(scopeKey, + aScope); - RefPtr job = - new ServiceWorkerRegisterJob(queue, principal, registration->mScope, - newest->ScriptSpec(), nullptr); - queue->Append(job); - } + RefPtr job = + new ServiceWorkerUpdateJob(principal, registration->mScope, + newest->ScriptSpec(), nullptr); + queue->ScheduleJob(job); } +namespace { + +class UpdateJobCallback final : public ServiceWorkerJob::Callback +{ + RefPtr mCallback; + + ~UpdateJobCallback() + { + } + +public: + explicit UpdateJobCallback(ServiceWorkerUpdateFinishCallback* aCallback) + : mCallback(aCallback) + { + AssertIsOnMainThread(); + MOZ_ASSERT(mCallback); + } + + void + JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aJob); + + if (aStatus.Failed()) { + mCallback->UpdateFailed(aStatus); + return; + } + + MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Update); + RefPtr updateJob = + static_cast(aJob); + RefPtr reg = updateJob->GetRegistration(); + mCallback->UpdateSucceeded(reg); + } + + NS_INLINE_DECL_REFCOUNTING(UpdateJobCallback) +}; +} // anonymous namespace + void ServiceWorkerManager::Update(nsIPrincipal* aPrincipal, const nsACString& aScope, @@ -3976,16 +2619,18 @@ ServiceWorkerManager::Update(nsIPrincipal* aPrincipal, return; } - ServiceWorkerJobQueue* queue = - GetOrCreateJobQueue(scopeKey, aScope); - MOZ_ASSERT(queue); + RefPtr queue = GetOrCreateJobQueue(scopeKey, aScope); // "Invoke Update algorithm, or its equivalent, with client, registration as // its argument." - RefPtr job = - new ServiceWorkerRegisterJob(queue, aPrincipal, registration->mScope, - newest->ScriptSpec(), aCallback); - queue->Append(job); + RefPtr job = + new ServiceWorkerUpdateJob(aPrincipal, registration->mScope, + newest->ScriptSpec(), nullptr); + + RefPtr cb = new UpdateJobCallback(aCallback); + job->AppendResultCallback(cb); + + queue->ScheduleJob(job); } namespace { @@ -4109,7 +2754,10 @@ ServiceWorkerManager::GetAllClients(nsIPrincipal* aPrincipal, return; } - if (!Preferences::GetBool("dom.serviceWorkers.testing.enabled") && + // Treat http windows with devtools opened as secure if the correct devtools + // setting is enabled. + if (!aDoc->GetWindow()->GetServiceWorkersTestingEnabled() && + !Preferences::GetBool("dom.serviceWorkers.testing.enabled") && !IsFromAuthenticatedOrigin(aDoc)) { return; } @@ -4143,6 +2791,10 @@ ServiceWorkerManager::GetAllClients(nsIPrincipal* aPrincipal, } nsCOMPtr doc = do_QueryInterface(iter.Key()); + + // All controlled documents must have an outer window. + MOZ_ASSERT(doc->GetWindow()); + ProcessDocument(aPrincipal, doc); } } @@ -4153,7 +2805,7 @@ ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDocument, ServiceWorkerRegistrationInfo* aWorkerRegistration) { MOZ_ASSERT(aWorkerRegistration); - MOZ_ASSERT(aWorkerRegistration->mActiveWorker); + MOZ_ASSERT(aWorkerRegistration->GetActive()); // Same origin check if (!aWorkerRegistration->mPrincipal->Equals(aDocument->NodePrincipal())) { @@ -4188,16 +2840,34 @@ ServiceWorkerManager::ClaimClients(nsIPrincipal* aPrincipal, RefPtr registration = GetRegistration(aPrincipal, aScope); - if (!registration || !registration->mActiveWorker || - !(registration->mActiveWorker->ID() == aId)) { + if (!registration || !registration->GetActive() || + !(registration->GetActive()->ID() == aId)) { // The worker is not active. return NS_ERROR_DOM_INVALID_STATE_ERR; } - RefPtr swm = ServiceWorkerManager::GetInstance(); - for (auto iter = mAllDocuments.Iter(); !iter.Done(); iter.Next()) { - nsCOMPtr document = do_QueryInterface(iter.Get()->GetKey()); - swm->MaybeClaimClient(document, registration); + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr enumerator; + nsresult rv = obs->EnumerateObservers("service-worker-get-client", + getter_AddRefs(enumerator)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool loop = true; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&loop)) && loop) { + nsCOMPtr ptr; + rv = enumerator->GetNext(getter_AddRefs(ptr)); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + nsCOMPtr doc = do_QueryInterface(ptr); + MaybeClaimClient(doc, registration); } return NS_OK; @@ -4214,13 +2884,13 @@ ServiceWorkerManager::SetSkipWaitingFlag(nsIPrincipal* aPrincipal, return NS_ERROR_FAILURE; } - if (registration->mInstallingWorker && - (registration->mInstallingWorker->ID() == aServiceWorkerID)) { - registration->mInstallingWorker->SetSkipWaitingFlag(); - } else if (registration->mWaitingWorker && - (registration->mWaitingWorker->ID() == aServiceWorkerID)) { - registration->mWaitingWorker->SetSkipWaitingFlag(); - if (registration->mWaitingWorker->State() == ServiceWorkerState::Installed) { + if (registration->GetInstalling() && + (registration->GetInstalling()->ID() == aServiceWorkerID)) { + registration->GetInstalling()->SetSkipWaitingFlag(); + } else if (registration->GetWaiting() && + (registration->GetWaiting()->ID() == aServiceWorkerID)) { + registration->GetWaiting()->SetSkipWaitingFlag(); + if (registration->GetWaiting()->State() == ServiceWorkerState::Installed) { registration->TryToActivateAsync(); } } else { @@ -4442,10 +3112,10 @@ ServiceWorkerManager::ForceUnregister(RegistrationDataPerPrincipal* aRegistratio MOZ_ASSERT(aRegistrationData); MOZ_ASSERT(aRegistration); - ServiceWorkerJobQueue* queue; - aRegistrationData->mJobQueues.Get(aRegistration->mScope, &queue); + RefPtr queue; + aRegistrationData->mJobQueues.Get(aRegistration->mScope, getter_AddRefs(queue)); if (queue) { - queue->CancelJobs(); + queue->CancelAll(); } nsCOMPtr timer = @@ -4755,6 +3425,12 @@ ServiceWorkerManager::Observe(nsISupports* aSubject, timer->Cancel(); } it1.UserData()->mUpdateTimers.Clear(); + + for (auto it2 = it1.UserData()->mJobQueues.Iter(); !it2.Done(); it2.Next()) { + RefPtr queue = it2.UserData(); + queue->CancelAll(); + } + it1.UserData()->mJobQueues.Clear(); } nsCOMPtr obs = mozilla::services::GetObserverService(); @@ -4775,6 +3451,8 @@ ServiceWorkerManager::Observe(nsISupports* aSubject, nsresult rv = NS_DispatchToMainThread(runnable); Unused << NS_WARN_IF(NS_FAILED(rv)); mActor = nullptr; + } else { + mPendingOperations.Clear(); } return NS_OK; } @@ -5025,13 +3703,16 @@ ServiceWorkerManager::ScheduleUpdateTimer(nsIPrincipal* aPrincipal, nsCOMPtr timer = data->mUpdateTimers.Get(aScope); if (timer) { - timer->Cancel(); - data->mUpdateTimers.Remove(aScope); - } else { - timer = do_CreateInstance("@mozilla.org/timer;1", &rv); - if (NS_WARN_IF(NS_FAILED(rv))) { - return; - } + // There is already a timer scheduled. In this case just use the original + // schedule time. We don't want to push it out to a later time since that + // could allow updates to be starved forever if events are continuously + // fired. + return; + } + + timer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; } nsCOMPtr callback = new UpdateTimerCallback(aPrincipal, @@ -5091,197 +3772,28 @@ ServiceWorkerManager::UpdateTimerFired(nsIPrincipal* aPrincipal, OriginAttributes attrs = BasePrincipal::Cast(aPrincipal)->OriginAttributesRef(); - // Then trigger an update to fire asynchronously now. - PropagateSoftUpdate(attrs, NS_ConvertUTF8toUTF16(aScope)); -} - -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(); + SoftUpdate(attrs, aScope); } 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& 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, 1> mInstances; - ServiceWorkerState mState; -}; - -} - -void -ServiceWorkerInfo::UpdateState(ServiceWorkerState aState) +ServiceWorkerManager::MaybeSendUnregister(nsIPrincipal* aPrincipal, + const nsACString& aScope) { 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 r = new ChangeStateUpdater(mInstances, mState); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r.forget())); -} + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(!aScope.IsEmpty()); -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 -ServiceWorkerInfo::GetOrCreateInstance(nsPIDOMWindow* aWindow) -{ - AssertIsOnMainThread(); - MOZ_ASSERT(aWindow); - - RefPtr ref; - - for (uint32_t i = 0; i < mInstances.Length(); ++i) { - MOZ_ASSERT(mInstances[i]); - if (mInstances[i]->GetOwner() == aWindow) { - ref = mInstances[i]; - break; - } + if (!mActor) { + return; } - if (!ref) { - ref = new ServiceWorker(aWindow, this); + PrincipalInfo principalInfo; + nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; } - return ref.forget(); + Unused << mActor->SendUnregister(principalInfo, NS_ConvertUTF8toUTF16(aScope)); } END_WORKERS_NAMESPACE diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h index ef61f90421..c0260fea54 100644 --- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -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 mPrincipal; - - RefPtr mActiveWorker; - RefPtr mWaitingWorker; - RefPtr mInstallingWorker; - - nsTArray> 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 - Newest() const - { - RefPtr newest; - if (mInstallingWorker) { - newest = mInstallingWorker; - } else if (mWaitingWorker) { - newest = mWaitingWorker; - } else { - newest = mActiveWorker; - } - - return newest.forget(); - } - - already_AddRefed - 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 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 mInstances; - - RefPtr 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 - 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 mControlledDocuments; - // Set of all documents that may be controlled by a service worker. - nsTHashtable mAllDocuments; - // Track all documents that have attempted to register a service worker for a // given scope. typedef nsTArray> WeakDocumentList; @@ -512,7 +266,7 @@ private: void Init(); - ServiceWorkerJobQueue* + already_AddRefed 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 mActor; - struct PendingOperation; - nsTArray mPendingOperations; + nsTArray> mPendingOperations; bool mShuttingDown; @@ -676,6 +427,9 @@ private: void UpdateTimerFired(nsIPrincipal* aPrincipal, const nsACString& aScope); + + void + MaybeSendUnregister(nsIPrincipal* aPrincipal, const nsACString& aScope); }; } // namespace workers diff --git a/dom/workers/ServiceWorkerRegisterJob.cpp b/dom/workers/ServiceWorkerRegisterJob.cpp new file mode 100644 index 0000000000..c585033d0f --- /dev/null +++ b/dom/workers/ServiceWorkerRegisterJob.cpp @@ -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 swm = ServiceWorkerManager::GetInstance(); + RefPtr 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 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 diff --git a/dom/workers/ServiceWorkerRegisterJob.h b/dom/workers/ServiceWorkerRegisterJob.h new file mode 100644 index 0000000000..a459e25b6c --- /dev/null +++ b/dom/workers/ServiceWorkerRegisterJob.h @@ -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 diff --git a/dom/workers/ServiceWorkerRegistrationInfo.cpp b/dom/workers/ServiceWorkerRegistrationInfo.cpp new file mode 100644 index 0000000000..2560717513 --- /dev/null +++ b/dom/workers/ServiceWorkerRegistrationInfo.cpp @@ -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 newest = Newest(); + if (newest) { + CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec); + } + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetInstallingWorker(nsIServiceWorkerInfo **aResult) +{ + AssertIsOnMainThread(); + nsCOMPtr info = do_QueryInterface(mInstallingWorker); + info.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetWaitingWorker(nsIServiceWorkerInfo **aResult) +{ + AssertIsOnMainThread(); + nsCOMPtr info = do_QueryInterface(mWaitingWorker); + info.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetActiveWorker(nsIServiceWorkerInfo **aResult) +{ + AssertIsOnMainThread(); + nsCOMPtr info = do_QueryInterface(mActiveWorker); + info.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +ServiceWorkerRegistrationInfo::GetWorkerByID(uint64_t aID, nsIServiceWorkerInfo **aResult) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aResult); + + RefPtr 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 +ServiceWorkerRegistrationInfo::GetServiceWorkerInfoById(uint64_t aId) +{ + RefPtr 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 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 swm = ServiceWorkerManager::GetInstance(); + swm->CheckPendingReadyPromises(); + + // "Queue a task to fire a simple event named controllerchange..." + nsCOMPtr controllerChangeRunnable = + NS_NewRunnableMethodWithArg>( + swm, &ServiceWorkerManager::FireControllerChange, this); + NS_DispatchToMainThread(controllerChangeRunnable); + + nsCOMPtr failRunnable = + NS_NewRunnableMethodWithArg(this, + &ServiceWorkerRegistrationInfo::FinishActivate, + false /* success */); + + nsMainThreadPtrHandle handle( + new nsMainThreadPtrHolder(this)); + RefPtr 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 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 swm = ServiceWorkerManager::GetInstance(); + swm->InvalidateServiceWorkerRegistrationWorker(this, aChangedWorkers); + + nsTArray> listeners(mListeners); + for (size_t index = 0; index < listeners.Length(); ++index) { + listeners[index]->OnChange(); + } +} + +void +ServiceWorkerRegistrationInfo::MaybeScheduleTimeCheckAndUpdate() +{ + AssertIsOnMainThread(); + + RefPtr swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + // shutting down, do nothing + return; + } + + if (mUpdateState == NoUpdate) { + mUpdateState = NeedTimeCheckAndUpdate; + } + + swm->ScheduleUpdateTimer(mPrincipal, mScope); +} + +void +ServiceWorkerRegistrationInfo::MaybeScheduleUpdate() +{ + AssertIsOnMainThread(); + + RefPtr 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 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 diff --git a/dom/workers/ServiceWorkerRegistrationInfo.h b/dom/workers/ServiceWorkerRegistrationInfo.h new file mode 100644 index 0000000000..54ab52a72a --- /dev/null +++ b/dom/workers/ServiceWorkerRegistrationInfo.h @@ -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 mActiveWorker; + RefPtr mWaitingWorker; + RefPtr mInstallingWorker; + + virtual ~ServiceWorkerRegistrationInfo(); + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISERVICEWORKERREGISTRATIONINFO + + const nsCString mScope; + + nsCOMPtr mPrincipal; + + nsTArray> 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 + Newest() const + { + RefPtr newest; + if (mInstallingWorker) { + newest = mInstallingWorker; + } else if (mWaitingWorker) { + newest = mWaitingWorker; + } else { + newest = mActiveWorker; + } + + return newest.forget(); + } + + already_AddRefed + 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 diff --git a/dom/workers/ServiceWorkerUnregisterJob.cpp b/dom/workers/ServiceWorkerUnregisterJob.cpp new file mode 100644 index 0000000000..839e4250ca --- /dev/null +++ b/dom/workers/ServiceWorkerUnregisterJob.cpp @@ -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 swm = ServiceWorkerManager::GetInstance(); + + // "Let registration be the result of running [[Get Registration]] + // algorithm passing scope as the argument." + RefPtr 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 diff --git a/dom/workers/ServiceWorkerUnregisterJob.h b/dom/workers/ServiceWorkerUnregisterJob.h new file mode 100644 index 0000000000..04a675c119 --- /dev/null +++ b/dom/workers/ServiceWorkerUnregisterJob.h @@ -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 diff --git a/dom/workers/ServiceWorkerUpdateJob.cpp b/dom/workers/ServiceWorkerUpdateJob.cpp new file mode 100644 index 0000000000..22b7d85002 --- /dev/null +++ b/dom/workers/ServiceWorkerUpdateJob.cpp @@ -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 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 mJob; + bool mSuccess; + +public: + explicit ContinueUpdateRunnable(const nsMainThreadPtrHandle& 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 mJob; + bool mSuccess; + +public: + explicit ContinueInstallRunnable(const nsMainThreadPtrHandle& 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 +ServiceWorkerUpdateJob::GetRegistration() const +{ + AssertIsOnMainThread(); + RefPtr 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 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 swm = ServiceWorkerManager::GetInstance(); + RefPtr registration = + swm->GetRegistration(mPrincipal, mScope); + + if (!registration || registration->mPendingUninstall) { + ErrorResult rv; + rv.ThrowTypeError(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 newest = registration->Newest(); + if (newest && !mScriptSpec.Equals(newest->ScriptSpec())) { + ErrorResult rv; + rv.ThrowTypeError(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 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 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 scriptURI; + nsresult rv = NS_NewURI(getter_AddRefs(scriptURI), mScriptSpec); + if (NS_WARN_IF(NS_FAILED(rv))) { + FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + nsCOMPtr 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 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 handle( + new nsMainThreadPtrHolder(this)); + RefPtr 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(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 swm = ServiceWorkerManager::GetInstance(); + + // fire the updatefound event + nsCOMPtr upr = + NS_NewRunnableMethodWithArg>( + swm, + &ServiceWorkerManager::FireUpdateFoundOnServiceWorkerRegistrations, + mRegistration); + NS_DispatchToMainThread(upr); + + // Call ContinueAfterInstallEvent(false) on main thread if the SW + // script fails to load. + nsCOMPtr failRunnable = NS_NewRunnableMethodWithArgs + (this, &ServiceWorkerUpdateJob::ContinueAfterInstallEvent, false); + + nsMainThreadPtrHandle handle( + new nsMainThreadPtrHolder(this)); + RefPtr 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 diff --git a/dom/workers/ServiceWorkerUpdateJob.h b/dom/workers/ServiceWorkerUpdateJob.h new file mode 100644 index 0000000000..c94eee5fd1 --- /dev/null +++ b/dom/workers/ServiceWorkerUpdateJob.h @@ -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 + 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 mLoadGroup; + RefPtr mRegistration; + RefPtr mServiceWorker; +}; + +} // namespace workers +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_workers_serviceworkerupdatejob_h diff --git a/dom/workers/WorkerNavigator.cpp b/dom/workers/WorkerNavigator.cpp index b88dcd2bcf..75156501f7 100644 --- a/dom/workers/WorkerNavigator.cpp +++ b/dom/workers/WorkerNavigator.cpp @@ -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) diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 3d4fb96258..275fdff553 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -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(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(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(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!"); } diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index 696ac04aca..e11daef8d2 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -32,6 +32,10 @@ #include "Queue.h" #include "WorkerFeature.h" +#ifdef XP_WIN +#undef PostMessage +#endif + class nsIChannel; class nsIDocument; class nsIEventTarget; diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 7059ecc90d..bc8af5c6c3 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -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& /* unused */, @@ -245,8 +245,8 @@ WorkerGlobalScope::SetTimeout(JSContext* /* unused */, { mWorkerPrivate->AssertIsOnWorkerThread(); Sequence dummy; - return mWorkerPrivate->SetTimeout(GetCurrentThreadJSContext(), nullptr, - aHandler, aTimeout, dummy, false, aRv); + return mWorkerPrivate->SetTimeout(aCx, nullptr, aHandler, aTimeout, dummy, + false, aRv); } void diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index 445b6d25ef..ba11eea7ab 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -118,9 +118,8 @@ public: SetTimeout(JSContext* aCx, Function& aHandler, const int32_t aTimeout, const Sequence& aArguments, ErrorResult& aRv); int32_t - SetTimeout(JSContext* /* unused */, const nsAString& aHandler, - const int32_t aTimeout, const Sequence& /* unused */, - ErrorResult& aRv); + SetTimeout(JSContext* aCx, const nsAString& aHandler, const int32_t aTimeout, + const Sequence& /* unused */, ErrorResult& aRv); void ClearTimeout(int32_t aHandle); int32_t diff --git a/dom/workers/moz.build b/dom/workers/moz.build index bd2a40d7f9..872e18ac48 100644 --- a/dom/workers/moz.build +++ b/dom/workers/moz.build @@ -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', diff --git a/dom/workers/test/WorkerDebugger_promise_debugger.js b/dom/workers/test/WorkerDebugger_promise_debugger.js new file mode 100644 index 0000000000..7d7eaf5328 --- /dev/null +++ b/dom/workers/test/WorkerDebugger_promise_debugger.js @@ -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"); + }); +}; diff --git a/dom/workers/test/WorkerDebugger_promise_worker.js b/dom/workers/test/WorkerDebugger_promise_worker.js new file mode 100644 index 0000000000..a77737af5d --- /dev/null +++ b/dom/workers/test/WorkerDebugger_promise_worker.js @@ -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"); + }); +}; diff --git a/dom/workers/test/chrome.ini b/dom/workers/test/chrome.ini index 89af58f0db..edd3ab77ca 100644 --- a/dom/workers/test/chrome.ini +++ b/dom/workers/test/chrome.ini @@ -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] diff --git a/dom/workers/test/serviceworkers/test_privateBrowsing.html b/dom/workers/test/serviceworkers/test_privateBrowsing.html index 3b17c86815..9763377118 100644 --- a/dom/workers/test/serviceworkers/test_privateBrowsing.html +++ b/dom/workers/test/serviceworkers/test_privateBrowsing.html @@ -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); diff --git a/dom/workers/test/test_WorkerDebugger_promise.xul b/dom/workers/test/test_WorkerDebugger_promise.xul new file mode 100644 index 0000000000..24ed07133c --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger_promise.xul @@ -0,0 +1,70 @@ + + + + + + + +

+ +

+  
+  
diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 8861cea400..0000915c38 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -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 diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 8526572bba..e6b051c306 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -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; } diff --git a/mfbt/Attributes.h b/mfbt/Attributes.h index 8d044b9f03..7228b53bdd 100644 --- a/mfbt/Attributes.h +++ b/mfbt/Attributes.h @@ -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 */ diff --git a/testing/docker/lint/Dockerfile b/testing/docker/lint/Dockerfile new file mode 100644 index 0000000000..159effe9e1 --- /dev/null +++ b/testing/docker/lint/Dockerfile @@ -0,0 +1,24 @@ +FROM node:4.2 +MAINTAINER Dave Townsend + +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"] diff --git a/testing/taskcluster/tasks/branches/base_job_flags.yml b/testing/taskcluster/tasks/branches/base_job_flags.yml index 0c96f6c287..986e997d74 100644 --- a/testing/taskcluster/tasks/branches/base_job_flags.yml +++ b/testing/taskcluster/tasks/branches/base_job_flags.yml @@ -23,6 +23,7 @@ flags: - aries - aries-ota - aries-eng + - eslint-gecko tests: - cppunit diff --git a/testing/taskcluster/tasks/lint.yml b/testing/taskcluster/tasks/lint.yml new file mode 100644 index 0000000000..b79070e5d1 --- /dev/null +++ b/testing/taskcluster/tasks/lint.yml @@ -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}} diff --git a/testing/taskcluster/tasks/tests/eslint-gecko.yml b/testing/taskcluster/tasks/tests/eslint-gecko.yml new file mode 100644 index 0000000000..015e20e09b --- /dev/null +++ b/testing/taskcluster/tasks/tests/eslint-gecko.yml @@ -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 diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/unregister-then-register-new-script.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/unregister-then-register-new-script.https.html new file mode 100644 index 0000000000..385430c2d8 --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/unregister-then-register-new-script.https.html @@ -0,0 +1,159 @@ + + + + + diff --git a/testing/web-platform/mozilla/tests/service-workers/service-worker/update.https.html b/testing/web-platform/mozilla/tests/service-workers/service-worker/update.https.html new file mode 100644 index 0000000000..8b41e52d8b --- /dev/null +++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/update.https.html @@ -0,0 +1,124 @@ + +Service Worker: Registration update() + + + + + diff --git a/testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html b/testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html new file mode 100644 index 0000000000..4a0e51d3d8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/navigate-window.https.html @@ -0,0 +1,97 @@ + +Service Worker: Navigate a Window + + + + + + + diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/loaded.html b/testing/web-platform/tests/service-workers/service-worker/resources/loaded.html new file mode 100644 index 0000000000..0cabce69f8 --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/loaded.html @@ -0,0 +1,9 @@ + diff --git a/testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js b/testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js new file mode 100644 index 0000000000..f9617439fc --- /dev/null +++ b/testing/web-platform/tests/service-workers/service-worker/resources/navigate-window-worker.js @@ -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 + '"' + }); +}); diff --git a/toolkit/components/extensions/test/xpcshell/test_csp_validator.js b/toolkit/components/extensions/test/xpcshell/test_csp_validator.js new file mode 100644 index 0000000000..2e6f048557 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_csp_validator.js @@ -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`); + } +}); diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index 65c5533112..900d8971be 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -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 diff --git a/toolkit/content/moz.build b/toolkit/content/moz.build index d62a2e7a34..0fb97e3e7c 100644 --- a/toolkit/content/moz.build +++ b/toolkit/content/moz.build @@ -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.*'): diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml index 157921bdb6..eff5fd19ae 100644 --- a/toolkit/content/widgets/browser.xml +++ b/toolkit/content/widgets/browser.xml @@ -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/. --> - - %findBarDTD; -]> - diff --git a/toolkit/locales/en-US/chrome/global/extensions.properties b/toolkit/locales/en-US/chrome/global/extensions.properties new file mode 100644 index 0000000000..f0fc7cd1ff --- /dev/null +++ b/toolkit/locales/en-US/chrome/global/extensions.properties @@ -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) diff --git a/toolkit/locales/jar.mn b/toolkit/locales/jar.mn index 45c5b9c591..c6fcff986a 100644 --- a/toolkit/locales/jar.mn +++ b/toolkit/locales/jar.mn @@ -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/ diff --git a/toolkit/mozapps/extensions/AddonContentPolicy.cpp b/toolkit/mozapps/extensions/AddonContentPolicy.cpp index 40175fa70f..99db3edadf 100644 --- a/toolkit/mozapps/extensions/AddonContentPolicy.cpp +++ b/toolkit/mozapps/extensions/AddonContentPolicy.cpp @@ -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 + 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 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 + GetStringBundle() + { + nsCOMPtr sbs = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sbs, nullptr); + + nsCOMPtr 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 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 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 principal = + BasePrincipal::CreateCodebasePrincipal(NS_ConvertUTF16toUTF8(url)); + + nsCOMPtr 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; +} diff --git a/toolkit/mozapps/extensions/AddonContentPolicy.h b/toolkit/mozapps/extensions/AddonContentPolicy.h index dc517b87d1..4c8af4828d 100644 --- a/toolkit/mozapps/extensions/AddonContentPolicy.h +++ b/toolkit/mozapps/extensions/AddonContentPolicy.h @@ -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 }; diff --git a/tools/lint/eslint-formatter.js b/tools/lint/eslint-formatter.js new file mode 100644 index 0000000000..6458d4adbc --- /dev/null +++ b/tools/lint/eslint-formatter.js @@ -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}`); + } + } +}; diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp index b95caf3585..13b53da10f 100644 --- a/xpcom/base/CycleCollectedJSRuntime.cpp +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -955,7 +955,7 @@ CycleCollectedJSRuntime::EnqueuePromiseJobCallback(JSContext* aCx, MOZ_ASSERT(Get() == self); nsCOMPtr runnable = new PromiseJobRunnable(aCx, aJob); - self->GetPromiseMicroTaskQueue().push(runnable); + self->DispatchToMicroTask(runnable); return true; } @@ -1140,6 +1140,13 @@ CycleCollectedJSRuntime::GetPromiseMicroTaskQueue() return mPromiseMicroTaskQueue; } +std::queue>& +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) diff --git a/xpcom/base/CycleCollectedJSRuntime.h b/xpcom/base/CycleCollectedJSRuntime.h index 4beea5cd11..15e9b9c93d 100644 --- a/xpcom/base/CycleCollectedJSRuntime.h +++ b/xpcom/base/CycleCollectedJSRuntime.h @@ -158,6 +158,9 @@ protected: return true; // Don't block context creation. } + std::queue> mPromiseMicroTaskQueue; + std::queue> mDebuggerPromiseMicroTaskQueue; + private: void DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing, @@ -285,6 +288,7 @@ public: void SetPendingException(nsIException* aException); std::queue>& GetPromiseMicroTaskQueue(); + std::queue>& 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 mPendingException; nsThread* mOwningThread; // Manual refcounting to avoid include hell. - std::queue> mPromiseMicroTaskQueue; - struct RunInMetastableStateData { nsCOMPtr mRunnable;