From 516d358f36b36ffa518f0c73d95cd668f1172363 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Sun, 25 Apr 2021 13:38:44 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - partial of Bug 1153658 - browser_compartments.js logspam. r=yoric (8e2c21aa5) - Bug 1178653 - Refactor construction code to use an interface consistent with the spec, with the one exception using an out-of-the-way, differently-named method. r=efaust (d316259d7) - Bug 1175098 - PerformanceStats for e10s. r=felipe, r=mconley (515acb8d7) - Bug 1147664 - Detailed mode for PerformanceStats (low-level). r=jandem (dda8d84de) - Bug 1147664 - Detailed mode for PerformanceStats (high-level). r=mossop (b86076568) - Bug 1164304 - Run all fetch tests in the service worker context as well; r=nsm (e20fa8bfd) - Bug 1143981 - Reroute all fetch tests through a transparent service worker; r=nsm (5196acc57) - Bug 1122161 - Redirected channels should respect skip service worker flag. r=nsm (f4288392e) - Bug 1170937 - Set the URL on the Response object created from a fetch() properly if the underlying channel gets redirected; r=baku (45febabb3) - Bug 1173029 - Remove mFinalURL from InternalResponse; r=baku a=KWierso (6bdc1083b) - Bug 1137683 - Use a loadgroup derived from the document's when updating a ServiceWorker; r=bkelly (fabaa2602) - Bug 1164397 - Part 1: Use the original channel URI for constructing the cache entry key when we're dealing with an intercepted channel; r=mcmanus (b20ab36c7) - Bug 1164397 - Part 2: Add an API for overriding the original URI on HttpChannelBase; r=mcmanus (20021722f) - Bug 1164397 - Part 3: Add an API for overriding the original URI on nsJARChannel; r=jdm (492b6fd6f) - Bug 1164397 - Part 4: Add infromation about whether a channel was redirected to ChannelInfo; r=jdm (e2ce84660) - Bug 1164397 - Part 5: Save the redirected flag and the redirected URI in the DOM cache; r=bkelly (7d2d1fc92) - Bug 1162018 - Add an automated test to ensure that a redirected Request won't be visible to a service worker if it had triggered the original fetch(); r=jdm (0397a073f) - Bug 1164397 - Part 6: Add a test case for the service worker responding with a redirected Response; r=jdm (e83e0bee4) - Bug 1164397 - Part 7: Add a test case for the redirected Response object being stored in the DOM Cache; r=jdm (7a82916d8) - Bug 1169296 - Intercepting top-level document loads is not working with JAR channels. Tests. r=jdm (fe8f128c5) - Bug 1171285 - Part 1: Add a script for regenerating the application.zip used by test_app_protocol.html; r=jdm (ec303b3b2) - Bug 1171285 - Part 2: Fix test_app_protocol.html to finish both index.html and controlled.html tests; r=jdm (2e68e6665) - Bug 1169613 - Use content type of synthesized response for JAR channel requests if available. Tests. r=jdm (b0095fc3b) - Bug 1164397 - Part 8: Add a test case for the service worker for an app:// URI responding with a redirected Response; r=jdm (460e834c9) - Bug 1169044 - Patch 3 - Store and set principal with script URI on ServiceWorkers. r=ehsan (6e0b0102a) - Bug 1164397 - Part 9: Add a test case for the service worker for an app:// URI responding with a redirected HTTPS response; r=jdm (1be195f5a) - Bug 1164397 - Part 10: Add a test case for the service worker for an app:// URI responding with cached HTTP and HTTPS responses; r=jdm# Please enter the commit message for your changes. Lines starting (56432b7b5) - Bug 1164397 - Part 11: Add a test case for the service worker responding to HTTPS normal and cached Responses; r=jdm (6ec238455) - Bug 1164397 - Part 12: Add a test case for the service worker responding to normal and cached HTTP->HTTPS responses; r=jdm (925a1970f) - Bug 1190074 - PerformanceGroup now uses mozilla::RefPtr;r=jandem (53dc0a640) - Bug 1169086 followup: Add missing 'override' annotation to VerifyTraceProtoAndIfaceCacheCalledTracer::trace() method decl. rs=ehsan (cdedce447) - Bug 1172824: Initialize a few members in CompartmentCheckTracer (CID 1304705); r=terrence (3db40160c) - missing bit of 1166678 (9fb0cceeb) - Bug 1139473: File some metadata for the js/src/ subdirectory; r=jorendorff (9dc4a29a7) - Bug 1173889 - Strongly type the CallbackTracer dispatch function; r=jonco, r=mccr8 (19b47dc70) --- build/pgo/certs/cert8.db | Bin 65536 -> 65536 bytes build/pgo/certs/key3.db | Bin 110592 -> 114688 bytes build/pgo/server-locations.txt | 1 + caps/BasePrincipal.cpp | 17 + caps/BasePrincipal.h | 5 + dom/bindings/BindingUtils.h | 2 +- dom/cache/CacheTypes.ipdlh | 2 + dom/cache/DBSchema.cpp | 76 ++- dom/cache/TypeUtils.cpp | 9 + dom/fetch/ChannelInfo.cpp | 56 ++- dom/fetch/ChannelInfo.h | 9 + dom/fetch/ChannelInfo.ipdlh | 2 + dom/fetch/FetchDriver.cpp | 32 +- dom/fetch/FetchDriver.h | 3 +- dom/fetch/InternalResponse.cpp | 43 +- dom/fetch/InternalResponse.h | 56 +-- dom/fetch/Response.h | 10 + .../mochitest/fetch/fetch_test_framework.js | 89 +++- .../mochitest/fetch/message_receiver.html | 6 + dom/tests/mochitest/fetch/mochitest.ini | 19 + dom/tests/mochitest/fetch/reroute.html | 10 + dom/tests/mochitest/fetch/reroute.js | 3 + dom/tests/mochitest/fetch/reroute.js^headers^ | 1 + dom/tests/mochitest/fetch/sw_reroute.js | 29 ++ .../test_fetch_basic_http_sw_reroute.html | 22 + .../fetch/test_fetch_basic_sw_reroute.html | 22 + .../fetch/test_fetch_cors_sw_reroute.html | 22 + .../test_formdataparsing_sw_reroute.html | 22 + .../fetch/test_headers_sw_reroute.html | 16 + dom/tests/mochitest/fetch/test_request.html | 2 +- .../fetch/test_request_sw_reroute.html | 22 + .../fetch/test_response_sw_reroute.html | 22 + dom/tests/mochitest/fetch/worker_wrapper.js | 55 ++- dom/workers/ScriptLoader.cpp | 49 +- dom/workers/ServiceWorkerManager.cpp | 25 +- dom/workers/ServiceWorkerScriptCache.cpp | 53 ++- dom/workers/ServiceWorkerScriptCache.h | 2 +- .../serviceworkers/app-protocol/README.txt | 5 +- .../app-protocol/application.list | 7 + .../app-protocol/application.zip | Bin 2331 -> 3362 bytes .../app-protocol/controlled.html | 14 +- .../serviceworkers/app-protocol/index.html | 6 +- .../serviceworkers/app-protocol/makezip.sh | 4 + .../app-protocol/realindex.html | 2 + .../app-protocol/realindex.html^headers^ | 1 + .../app-protocol/redirect-https.sjs | 5 + .../serviceworkers/app-protocol/redirect.sjs | 5 + .../test/serviceworkers/app-protocol/sw.js | 59 ++- .../test/serviceworkers/app-protocol/test.js | 49 +- .../test_doc_load_interception.js | 1 + .../test/serviceworkers/fetch/fetch_tests.js | 19 + .../fetch/origin/https/index-https.sjs | 4 + .../fetch/origin/https/origin_test.js | 23 + .../fetch/origin/https/realindex.html | 6 + .../origin/https/realindex.html^headers^ | 1 + .../fetch/origin/https/register.html | 14 + .../fetch/origin/https/unregister.html | 12 + .../fetch/origin/index-to-https.sjs | 4 + .../serviceworkers/fetch/origin/index.sjs | 4 + .../fetch/origin/origin_test.js | 35 ++ .../fetch/origin/realindex.html | 6 + .../fetch/origin/realindex.html^headers^ | 1 + .../serviceworkers/fetch/origin/register.html | 14 + .../fetch/origin/unregister.html | 12 + .../fetch/requesturl/index.html | 7 + .../fetch/requesturl/redirect.sjs | 4 + .../fetch/requesturl/redirector.html | 2 + .../fetch/requesturl/register.html | 14 + .../fetch/requesturl/requesturl_test.js | 17 + .../fetch/requesturl/secret.html | 5 + .../fetch/requesturl/unregister.html | 12 + .../test/serviceworkers/fetch_event_worker.js | 18 +- dom/workers/test/serviceworkers/mochitest.ini | 27 ++ .../serviceworkers/test_app_protocol.html | 29 +- .../test_cross_origin_url_after_redirect.html | 50 ++ .../test_https_origin_after_redirect.html | 56 +++ ...st_https_origin_after_redirect_cached.html | 56 +++ .../test_origin_after_redirect.html | 57 +++ .../test_origin_after_redirect_cached.html | 57 +++ .../test_origin_after_redirect_to_https.html | 57 +++ ...origin_after_redirect_to_https_cached.html | 57 +++ js/public/HeapAPI.h | 5 +- js/public/TraceKind.h | 52 +++ js/public/TracingAPI.h | 110 +++-- js/public/UbiNode.h | 2 +- js/src/builtin/Reflect.cpp | 19 +- js/src/builtin/TestingFunctions.cpp | 4 +- js/src/gc/GCInternals.h | 10 +- js/src/gc/Marking.cpp | 22 +- js/src/gc/RootMarking.cpp | 12 +- js/src/gc/Tracer.cpp | 11 +- js/src/gc/Verifier.cpp | 18 +- js/src/jit-test/tests/basic/testBug593559.js | 5 +- js/src/jit/BaselineIC.cpp | 20 +- js/src/jit/VMFunctions.cpp | 35 +- js/src/jsapi-tests/testGCMarking.cpp | 8 +- js/src/jsapi.cpp | 124 +++-- js/src/jsapi.h | 84 ++-- js/src/jsarray.cpp | 13 +- js/src/jsfriendapi.cpp | 8 +- js/src/jsfun.cpp | 50 +- js/src/jsgc.cpp | 31 +- js/src/moz.build | 44 ++ js/src/proxy/DirectProxyHandler.cpp | 12 +- js/src/proxy/ScriptedDirectProxyHandler.cpp | 6 +- js/src/proxy/ScriptedIndirectProxyHandler.cpp | 17 +- .../extensions/new-cross-compartment.js | 42 ++ js/src/vm/Interpreter.cpp | 434 +++++++++++------- js/src/vm/Interpreter.h | 25 +- js/src/vm/Runtime.cpp | 119 +++-- js/src/vm/Runtime.h | 83 ++-- js/src/vm/Stack.h | 50 +- js/src/vm/UbiNode.cpp | 8 +- modules/libjar/nsJARChannel.cpp | 10 + modules/libjar/nsJARChannel.h | 1 + netwerk/protocol/http/HttpBaseChannel.cpp | 10 + netwerk/protocol/http/HttpBaseChannel.h | 1 + netwerk/protocol/http/nsHttpChannel.cpp | 15 +- .../content/aboutPerformance.js | 52 ++- .../tests/browser/browser.ini | 1 - .../PerformanceStats-content.js | 144 ++++++ .../perfmonitoring/PerformanceStats.jsm | 385 ++++++++++++++-- toolkit/components/perfmonitoring/moz.build | 1 + .../perfmonitoring/nsIPerformanceStats.idl | 23 +- .../perfmonitoring/nsPerformanceStats.cpp | 113 ++++- .../tests/browser/browser_compartments.js | 81 +++- .../tests/xpcshell/test_compartments.js | 51 +- .../tests/xpcshell/xpcshell.ini | 1 + xpcom/base/CycleCollectedJSRuntime.cpp | 49 +- 129 files changed, 3326 insertions(+), 747 deletions(-) create mode 100644 dom/tests/mochitest/fetch/message_receiver.html create mode 100644 dom/tests/mochitest/fetch/reroute.html create mode 100644 dom/tests/mochitest/fetch/reroute.js create mode 100644 dom/tests/mochitest/fetch/reroute.js^headers^ create mode 100644 dom/tests/mochitest/fetch/sw_reroute.js create mode 100644 dom/tests/mochitest/fetch/test_fetch_basic_http_sw_reroute.html create mode 100644 dom/tests/mochitest/fetch/test_fetch_basic_sw_reroute.html create mode 100644 dom/tests/mochitest/fetch/test_fetch_cors_sw_reroute.html create mode 100644 dom/tests/mochitest/fetch/test_formdataparsing_sw_reroute.html create mode 100644 dom/tests/mochitest/fetch/test_headers_sw_reroute.html create mode 100644 dom/tests/mochitest/fetch/test_request_sw_reroute.html create mode 100644 dom/tests/mochitest/fetch/test_response_sw_reroute.html create mode 100644 dom/workers/test/serviceworkers/app-protocol/application.list create mode 100644 dom/workers/test/serviceworkers/app-protocol/makezip.sh create mode 100644 dom/workers/test/serviceworkers/app-protocol/realindex.html create mode 100644 dom/workers/test/serviceworkers/app-protocol/realindex.html^headers^ create mode 100644 dom/workers/test/serviceworkers/app-protocol/redirect-https.sjs create mode 100644 dom/workers/test/serviceworkers/app-protocol/redirect.sjs create mode 100644 dom/workers/test/serviceworkers/app-protocol/test_doc_load_interception.js create mode 100644 dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs create mode 100644 dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js create mode 100644 dom/workers/test/serviceworkers/fetch/origin/https/realindex.html create mode 100644 dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ create mode 100644 dom/workers/test/serviceworkers/fetch/origin/https/register.html create mode 100644 dom/workers/test/serviceworkers/fetch/origin/https/unregister.html create mode 100644 dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs create mode 100644 dom/workers/test/serviceworkers/fetch/origin/index.sjs create mode 100644 dom/workers/test/serviceworkers/fetch/origin/origin_test.js create mode 100644 dom/workers/test/serviceworkers/fetch/origin/realindex.html create mode 100644 dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ create mode 100644 dom/workers/test/serviceworkers/fetch/origin/register.html create mode 100644 dom/workers/test/serviceworkers/fetch/origin/unregister.html create mode 100644 dom/workers/test/serviceworkers/fetch/requesturl/index.html create mode 100644 dom/workers/test/serviceworkers/fetch/requesturl/redirect.sjs create mode 100644 dom/workers/test/serviceworkers/fetch/requesturl/redirector.html create mode 100644 dom/workers/test/serviceworkers/fetch/requesturl/register.html create mode 100644 dom/workers/test/serviceworkers/fetch/requesturl/requesturl_test.js create mode 100644 dom/workers/test/serviceworkers/fetch/requesturl/secret.html create mode 100644 dom/workers/test/serviceworkers/fetch/requesturl/unregister.html create mode 100644 dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html create mode 100644 dom/workers/test/serviceworkers/test_https_origin_after_redirect.html create mode 100644 dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.html create mode 100644 dom/workers/test/serviceworkers/test_origin_after_redirect.html create mode 100644 dom/workers/test/serviceworkers/test_origin_after_redirect_cached.html create mode 100644 dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.html create mode 100644 dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.html create mode 100644 js/public/TraceKind.h create mode 100644 js/src/tests/ecma_6/extensions/new-cross-compartment.js create mode 100644 toolkit/components/perfmonitoring/PerformanceStats-content.js diff --git a/build/pgo/certs/cert8.db b/build/pgo/certs/cert8.db index bfc718af6536e811bbfd6ed242da91f48df47f1a..2835ad347018d1ab8caa04eda40603bd781d781c 100644 GIT binary patch delta 569 zcmZo@U}aYA($$xWV!#gEW4n_tBXBGz*2i6z{af2q-2!ke;gOdf9bV!>T zm>HNFnwXfInnsE98XA-v^L(_Uqwx{jeJ$vT-1$hy%79S7pv~K;v%aCh$x8iND?bZ7I7hB9G zDeRu%E>J(|MbWQYvhFDb2l^*|IsCe9(a8e0)z#9?UOny+1#dPpelk(cl4#9SX#5<` zalD?j@#$j6Z?Cs*o#AtYxwuavAna1UWy0A;lY5TNe~cb@+`KhYQ@exB{clRb&DUSH zrrkfZYnrTK$c2I;PDdk?02bk@jW>)RKebu$x4Y-hor)v<*L(twmso2z8afdT8GcqtPZeqG)(8P2Dh_A3Q zhsp}FG%=k9a*j7~r&c897UZPrCFkcNIQd2Co2M*|Vv?3hOU%sC%}C5kDb7gDPSrzF zJ^Af&@A{nDJ-aVf{hM(8!xO!T_5_VGC11wXeDytF_&9w;o!h@R?~r@!wZwG7@(4ZQ z%7WIEt42}=scDL_?7G(({(PqgnWAU#5 delta 495 zcmZo@U}4vO-$>7cr`FUWCdB8 zm=*y!^P0Gkf@^cd(kQ0M@0NSjNA74`{vhXOeMi80JK6iET8_`>+O_=V((>fupOFua z@%+la(*Gwu{KexPT?`xeT1*W1Pw+8HPGE9dE3@F}rX^O}4u6`dch-A8v)2^e#%VL! z|C?#K&9P`?D?7)(LNn(Ur=yBzxZN9*{qOUf51zPnzm;LuEuINS4%h3ue>^=q`M_bu L)Xh6qc!UE0XS&4I diff --git a/build/pgo/certs/key3.db b/build/pgo/certs/key3.db index bc41b88d88c852ef5ce9490e45df28918de115c7..ae47698b30a2afe073d9b7d4bdf3bc094794a346 100644 GIT binary patch delta 1689 zcmV;K24?wy;0Azz2aqcf5dZ)H9{>OV4*&oF1ONa4MzK7^9~lM!fDg(HN(__<6bW7i zOa@*C0U!VX4g)B+5g-8%1G6Y2Q3JQ-Yylesm0$rIvqK{`1GD8U8v_B~vjH&we;Eh> z01x{K`w0C5eFMh;10VnZu>i*a4g)BY5pb@RU;!Jm;;Wr(wT`?d%{KbP4vnd(# ziG(%uGn>@bh|8nYt0GjfF&!TJgKh!)eUb&xIuRD~ixc__Zabx7!KS3T%Q{u^@J3#1)~os;hR@IJm5# zhaN9Etn~t)k$1OW7F=PvRV!pDGVdXU4ah|44L`k)w>w2&R*IM#SIoF8QTp3$KwTkT zBUeY_S%r{9*36DX`>Axga&_l1nR>#&+RXZY3_jM`i|iO9f5DXioCrp)bz2+Hi*gK0)Gp2R2g2fp?D-tQPnT_Fr~~$$HRya-kL&t-Hych$mBB8mI;RVO?wclqe% zn5T0A*HUU3e|vn|FSX9lY$8z;?`eb*znI+7-$L~!9+g2Qf8aD_v1JN;mFm4NKTCRJ z4A5J@Rxz>Tj`(^p`}&8AnPu)9?-Enc#2>aJR9F^JR>M*zc!b~^=oZn=dME=VRwn=0 zNYFA%uU*7nBV!GiRds*HLs4)ec5&x*^A34s1_YV$f5?!ejmUt3VwsDO=N^OOKonA( z0)_TN4okVNE?Y-Fc6C!}{R_XSkLgSt%d4G0R4*{&p548px(e%>g~daszl&Sz*e*uWx;Me;MdbPM-1GFh6EbFbM8>Sz_u0oDt^jX(_DY|W<;<3r3HD-qP72qSt z2IpA-5=BX<9)TDAA2a*vHkZqJn9bkQi} zw@VQ|T~j84+J50ug@*t@Rbz5;8BxfCJC~)~1xR->s(j0_Ir-FuflS!xMMz27Ir3J$_Al#pDd{2|5wX*nac%mXz?Ut=M1Kl*<781|n| zc$A=X*fbeG6F_7Dbu~6>Cnh60KV5SG(#1ro@ihsH^m$9nD7rpbFMNH$SyuS8&H9Rt z7n5scx&_^3LOsVv*5-JGwM&#%rPaa0-(T6&Pnk9jwyEh3zq;V^0qj#s9 zH>+I{=#;==0Q@XTRh^auI2EzGg_M3sB$x|WtTyM0pM7k;uxYT(4`CS++(==TQiYYx z(N>zuB@XZbqA6Q!CLN~o`VXf^bJK={}*IyRt@1ovI6VjmKm4ZY#>{F*g)IIh^rO`6FL)9ktMuxkcs89)YHSrUYR& z^)r6x{C%$#T?{tvOfMS0@p;ddAIm6w9;if%8`g#|44qqe1rTwY8W`!~=7V7*JNyZf`kY&4D!D zK|}m9;7HlpnJFuX21os5VA<>RwPxSKx|SU=RM2p82|+S6Py`ctvDh=?jlnI6jlnJu|Fop@0U;!Jmc_vW?4G;_= delta 140 zcmZo@U~hQ9HbGlZfPsNQ9*FsYm<5PkH`*MLm*iq-;6KIV#WRIdgfouq9;*g3qXGj1 zAG5}GK?O#B=FJ+)fy~?2?_>;Qn;uujD82cqjx;l4{bojk|Mik=K*N4>{^t10T+4i% kVJpLN2B_Z2f(7fQCID4G+8}p+<3&UMi4V>%o4}|50NC^ForEach(iterator); } +bool +OriginAttributes::PopulateFromOrigin(const nsACString& aOrigin, + nsACString& aOriginNoSuffix) +{ + // RFindChar is only available on nsCString. + nsCString origin(aOrigin); + int32_t pos = origin.RFindChar('!'); + + if (pos == kNotFound) { + aOriginNoSuffix = origin; + return true; + } + + aOriginNoSuffix = Substring(origin, 0, pos); + return PopulateFromSuffix(Substring(origin, pos)); +} + void OriginAttributes::CookieJar(nsACString& aStr) { diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h index e68b511041..fa890de425 100644 --- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -48,6 +48,11 @@ public: bool PopulateFromSuffix(const nsACString& aStr); void CookieJar(nsACString& aStr); + + // Populates the attributes from a string like + // |uri!key1=value1&key2=value2| and returns the uri without the suffix. + bool PopulateFromOrigin(const nsACString& aOrigin, + nsACString& aOriginNoSuffix); }; /* diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 6471b77224..296f6a91a4 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -523,7 +523,7 @@ struct VerifyTraceProtoAndIfaceCacheCalledTracer : public JS::CallbackTracer : JS::CallbackTracer(rt), ok(false) {} - void trace(void** thingp, JS::TraceKind kind) { + void onChild(const JS::GCCellPtr&) override { // We don't do anything here, we only want to verify that // TraceProtoAndIfaceCache was called. } diff --git a/dom/cache/CacheTypes.ipdlh b/dom/cache/CacheTypes.ipdlh index a98d94ca8c..ddb53ddf29 100644 --- a/dom/cache/CacheTypes.ipdlh +++ b/dom/cache/CacheTypes.ipdlh @@ -7,6 +7,7 @@ include protocol PCachePushStream; include protocol PCacheStreamControl; include InputStreamParams; include ChannelInfo; +include PBackgroundSharedTypes; using HeadersGuardEnum from "mozilla/dom/cache/IPCUtils.h"; using RequestCredentials from "mozilla/dom/cache/IPCUtils.h"; @@ -83,6 +84,7 @@ struct CacheResponse HeadersGuardEnum headersGuard; CacheReadStreamOrVoid body; IPCChannelInfo channelInfo; + OptionalPrincipalInfo principalInfo; }; union CacheResponseOrVoid diff --git a/dom/cache/DBSchema.cpp b/dom/cache/DBSchema.cpp index 2d80fff6b3..d46820ac64 100644 --- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -19,6 +19,7 @@ #include "nsCRT.h" #include "nsHttp.h" #include "nsICryptoHash.h" +#include "mozilla/BasePrincipal.h" #include "mozilla/dom/HeadersBinding.h" #include "mozilla/dom/RequestBinding.h" #include "mozilla/dom/ResponseBinding.h" @@ -29,11 +30,11 @@ namespace dom { namespace cache { namespace db { -const int32_t kMaxWipeSchemaVersion = 10; +const int32_t kMaxWipeSchemaVersion = 11; namespace { -const int32_t kLatestSchemaVersion = 10; +const int32_t kLatestSchemaVersion = 11; const int32_t kMaxEntriesPerStatement = 255; const uint32_t kPageSize = 4 * 1024; @@ -301,6 +302,11 @@ CreateSchema(mozIStorageConnection* aConn) "response_headers_guard INTEGER NOT NULL, " "response_body_id TEXT NULL, " "response_security_info_id INTEGER NULL REFERENCES security_info(id), " + "response_principal_info TEXT NOT NULL, " + "response_redirected INTEGER NOT NULL, " + // Note that response_redirected_url is either going to be empty, or + // it's going to be a URL different than response_url. + "response_redirected_url TEXT NOT NULL, " "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE" ");" )); @@ -1524,6 +1530,9 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, "response_headers_guard, " "response_body_id, " "response_security_info_id, " + "response_principal_info, " + "response_redirected, " + "response_redirected_url, " "cache_id " ") VALUES (" ":request_method, " @@ -1544,6 +1553,9 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, ":response_headers_guard, " ":response_body_id, " ":response_security_info_id, " + ":response_principal_info, " + ":response_redirected, " + ":response_redirected_url, " ":cache_id " ");" ), getter_AddRefs(state)); @@ -1623,6 +1635,36 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId, } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + nsAutoCString serializedInfo; + // We only allow content serviceworkers right now. + if (aResponse.principalInfo().type() == mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) { + const mozilla::ipc::PrincipalInfo& principalInfo = + aResponse.principalInfo().get_PrincipalInfo(); + MOZ_ASSERT(principalInfo.type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); + const mozilla::ipc::ContentPrincipalInfo& cInfo = + principalInfo.get_ContentPrincipalInfo(); + + serializedInfo.Append(cInfo.spec()); + + MOZ_ASSERT(cInfo.appId() != nsIScriptSecurityManager::UNKNOWN_APP_ID); + OriginAttributes attrs(cInfo.appId(), cInfo.isInBrowserElement()); + nsAutoCString suffix; + attrs.CreateSuffix(suffix); + serializedInfo.Append(suffix); + } + + rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("response_principal_info"), + serializedInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->BindInt32ByName(NS_LITERAL_CSTRING("response_redirected"), + aResponse.channelInfo().redirected() ? 1 : 0); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("response_redirected_url"), + aResponse.channelInfo().redirectedURI()); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + rv = state->BindInt64ByName(NS_LITERAL_CSTRING("cache_id"), aCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1714,6 +1756,9 @@ ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId, "entries.response_status_text, " "entries.response_headers_guard, " "entries.response_body_id, " + "entries.response_principal_info, " + "entries.response_redirected, " + "entries.response_redirected_url, " "security_info.data " "FROM entries " "LEFT OUTER JOIN security_info " @@ -1761,7 +1806,32 @@ ReadResponse(mozIStorageConnection* aConn, EntryId aEntryId, if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } - rv = state->GetBlobAsUTF8String(6, aSavedResponseOut->mValue.channelInfo().securityInfo()); + nsAutoCString serializedInfo; + rv = state->GetUTF8String(6, serializedInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + aSavedResponseOut->mValue.principalInfo() = void_t(); + if (!serializedInfo.IsEmpty()) { + nsAutoCString originNoSuffix; + OriginAttributes attrs; + fprintf(stderr, "\n%s\n", serializedInfo.get()); + if (!attrs.PopulateFromOrigin(serializedInfo, originNoSuffix)) { + NS_WARNING("Something went wrong parsing a serialized principal!"); + return NS_ERROR_FAILURE; + } + + aSavedResponseOut->mValue.principalInfo() = + mozilla::ipc::ContentPrincipalInfo(attrs.mAppId, attrs.mInBrowser, originNoSuffix); + } + + int32_t redirected; + rv = state->GetInt32(7, &redirected); + aSavedResponseOut->mValue.channelInfo().redirected() = !!redirected; + + rv = state->GetUTF8String(8, aSavedResponseOut->mValue.channelInfo().redirectedURI()); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = state->GetBlobAsUTF8String(9, aSavedResponseOut->mValue.channelInfo().securityInfo()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConn->CreateStatement(NS_LITERAL_CSTRING( diff --git a/dom/cache/TypeUtils.cpp b/dom/cache/TypeUtils.cpp index 4af9c25ed8..187b192854 100644 --- a/dom/cache/TypeUtils.cpp +++ b/dom/cache/TypeUtils.cpp @@ -225,6 +225,11 @@ TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut, ToHeadersEntryList(aOut.headers(), headers); aOut.headersGuard() = headers->Guard(); aOut.channelInfo() = aIn.GetChannelInfo().AsIPCChannelInfo(); + if (aIn.GetPrincipalInfo()) { + aOut.principalInfo() = *aIn.GetPrincipalInfo(); + } else { + aOut.principalInfo() = void_t(); + } } void @@ -291,6 +296,10 @@ TypeUtils::ToResponse(const CacheResponse& aIn) MOZ_ASSERT(!result.Failed()); ir->InitChannelInfo(aIn.channelInfo()); + if (aIn.principalInfo().type() == mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) { + UniquePtr info(new mozilla::ipc::PrincipalInfo(aIn.principalInfo().get_PrincipalInfo())); + ir->SetPrincipalInfo(Move(info)); + } nsCOMPtr stream = ReadStream::Create(aIn.body()); ir->SetBody(stream); diff --git a/dom/fetch/ChannelInfo.cpp b/dom/fetch/ChannelInfo.cpp index 91f64402d7..5ee01864a0 100644 --- a/dom/fetch/ChannelInfo.cpp +++ b/dom/fetch/ChannelInfo.cpp @@ -13,6 +13,7 @@ #include "mozilla/ipc/ChannelInfo.h" #include "nsIJARChannel.h" #include "nsJARChannel.h" +#include "nsNetUtil.h" using namespace mozilla; using namespace mozilla::dom; @@ -20,6 +21,7 @@ using namespace mozilla::dom; void ChannelInfo::InitFromChannel(nsIChannel* aChannel) { + MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); nsCOMPtr securityInfo; @@ -28,6 +30,20 @@ ChannelInfo::InitFromChannel(nsIChannel* aChannel) SetSecurityInfo(securityInfo); } + nsLoadFlags loadFlags = 0; + aChannel->GetLoadFlags(&loadFlags); + mRedirected = (loadFlags & nsIChannel::LOAD_REPLACE); + if (mRedirected) { + // Save the spec and not the nsIURI object itself, since those objects are + // not thread-safe, and releasing them somewhere other than the main thread + // is not possible. + nsCOMPtr redirectedURI; + aChannel->GetURI(getter_AddRefs(redirectedURI)); + if (redirectedURI) { + redirectedURI->GetSpec(mRedirectedURISpec); + } + } + mInited = true; } @@ -37,6 +53,8 @@ ChannelInfo::InitFromIPCChannelInfo(const ipc::IPCChannelInfo& aChannelInfo) MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); mSecurityInfo = aChannelInfo.securityInfo(); + mRedirectedURISpec = aChannelInfo.redirectedURI(); + mRedirected = aChannelInfo.redirected(); mInited = true; } @@ -56,16 +74,22 @@ ChannelInfo::SetSecurityInfo(nsISupports* aSecurityInfo) nsresult ChannelInfo::ResurrectInfoOnChannel(nsIChannel* aChannel) { + MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mInited); + // These pointers may be null at this point. They must be checked before + // being dereferenced. + nsCOMPtr httpChannel = + do_QueryInterface(aChannel); + nsCOMPtr jarChannel = + do_QueryInterface(aChannel); + if (!mSecurityInfo.IsEmpty()) { nsCOMPtr infoObj; nsresult rv = NS_DeserializeObject(mSecurityInfo, getter_AddRefs(infoObj)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - nsCOMPtr httpChannel = - do_QueryInterface(aChannel); if (httpChannel) { net::HttpBaseChannel* httpBaseChannel = static_cast(httpChannel.get()); @@ -74,8 +98,6 @@ ChannelInfo::ResurrectInfoOnChannel(nsIChannel* aChannel) return rv; } } else { - nsCOMPtr jarChannel = - do_QueryInterface(aChannel); if (NS_WARN_IF(!jarChannel)) { return NS_ERROR_FAILURE; } @@ -84,6 +106,30 @@ ChannelInfo::ResurrectInfoOnChannel(nsIChannel* aChannel) } } + if (mRedirected) { + nsLoadFlags flags = 0; + aChannel->GetLoadFlags(&flags); + flags |= nsIChannel::LOAD_REPLACE; + aChannel->SetLoadFlags(flags); + + nsCOMPtr redirectedURI; + nsresult rv = NS_NewURI(getter_AddRefs(redirectedURI), + mRedirectedURISpec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (httpChannel) { + net::HttpBaseChannel* httpBaseChannel = + static_cast(httpChannel.get()); + httpBaseChannel->OverrideURI(redirectedURI); + } else { + if (NS_WARN_IF(!jarChannel)) { + return NS_ERROR_FAILURE; + } + static_cast(jarChannel.get())->OverrideURI(redirectedURI); + } + } + return NS_OK; } @@ -98,6 +144,8 @@ ChannelInfo::AsIPCChannelInfo() const IPCChannelInfo ipcInfo; ipcInfo.securityInfo() = mSecurityInfo; + ipcInfo.redirectedURI() = mRedirectedURISpec; + ipcInfo.redirected() = mRedirected; return ipcInfo; } diff --git a/dom/fetch/ChannelInfo.h b/dom/fetch/ChannelInfo.h index f365fdf0fc..ebedbcf0ae 100644 --- a/dom/fetch/ChannelInfo.h +++ b/dom/fetch/ChannelInfo.h @@ -8,8 +8,10 @@ #define mozilla_dom_ChannelInfo_h #include "nsString.h" +#include "nsCOMPtr.h" class nsIChannel; +class nsIURI; namespace mozilla { namespace ipc { @@ -42,12 +44,15 @@ public: ChannelInfo() : mInited(false) + , mRedirected(false) { } ChannelInfo(const ChannelInfo& aRHS) : mSecurityInfo(aRHS.mSecurityInfo) + , mRedirectedURISpec(aRHS.mRedirectedURISpec) , mInited(aRHS.mInited) + , mRedirected(aRHS.mRedirected) { } @@ -55,7 +60,9 @@ public: operator=(const ChannelInfo& aRHS) { mSecurityInfo = aRHS.mSecurityInfo; + mRedirectedURISpec = aRHS.mRedirectedURISpec; mInited = aRHS.mInited; + mRedirected = aRHS.mRedirected; return *this; } @@ -78,7 +85,9 @@ private: private: nsCString mSecurityInfo; + nsCString mRedirectedURISpec; bool mInited; + bool mRedirected; }; } // namespace dom diff --git a/dom/fetch/ChannelInfo.ipdlh b/dom/fetch/ChannelInfo.ipdlh index 605009e120..06bd713977 100644 --- a/dom/fetch/ChannelInfo.ipdlh +++ b/dom/fetch/ChannelInfo.ipdlh @@ -8,6 +8,8 @@ namespace ipc { struct IPCChannelInfo { nsCString securityInfo; + nsCString redirectedURI; + bool redirected; }; } // namespace ipc diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp index 87681f550f..8df69fbee5 100644 --- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -433,18 +433,18 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica // "If request's referrer is a URL, let referrerSource be request's // referrer." // - // This allows ServiceWorkers to function transparently when the referrer - // of the intercepted request is already set. + // XXXnsm - We never actually hit this from a fetch() call since both + // fetch and Request() create a new internal request whose referrer is + // always set to about:client. Should we just crash here instead until + // someone tries to use FetchDriver for non-fetch() APIs? nsCOMPtr referrerURI; rv = NS_NewURI(getter_AddRefs(referrerURI), referrer, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } - // FIXME(nsm): Can we assert that this case can only happen in - // ServiceWorkers and assume null mDocument? rv = - httpChan->SetReferrerWithPolicy(nullptr, + httpChan->SetReferrerWithPolicy(referrerURI, mDocument ? mDocument->GetReferrerPolicy() : net::RP_Default); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -564,14 +564,16 @@ FetchDriver::ContinueHttpFetchAfterNetworkFetch() } already_AddRefed -FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse) +FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aFinalURI) { MOZ_ASSERT(aResponse); - if (!aResponse->FinalURL()) { - nsAutoCString reqURL; + nsAutoCString reqURL; + if (aFinalURI) { + aFinalURI->GetSpec(reqURL); + } else { mRequest->GetURL(reqURL); - aResponse->SetUrl(reqURL); } + aResponse->SetUrl(reqURL); // FIXME(nsm): Handle mixed content check, step 7 of fetch. @@ -600,7 +602,7 @@ FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse) void FetchDriver::BeginResponse(InternalResponse* aResponse) { - nsRefPtr r = BeginAndGetFilteredResponse(aResponse); + nsRefPtr r = BeginAndGetFilteredResponse(aResponse, nullptr); // Release the ref. } @@ -723,9 +725,17 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest, nsCOMPtr channel = do_QueryInterface(aRequest); response->InitChannelInfo(channel); + nsCOMPtr channelURI; + rv = channel->GetURI(getter_AddRefs(channelURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + FailWithNetworkError(); + // Cancel request. + return rv; + } + // Resolves fetch() promise which may trigger code running in a worker. Make // sure the Response is fully initialized before calling this. - mResponse = BeginAndGetFilteredResponse(response); + mResponse = BeginAndGetFilteredResponse(response, channelURI); nsCOMPtr sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h index 5a5d3e637d..27494c1555 100644 --- a/dom/fetch/FetchDriver.h +++ b/dom/fetch/FetchDriver.h @@ -88,8 +88,9 @@ private: nsresult HttpFetch(bool aCORSFlag = false, bool aCORSPreflightFlag = false, bool aAuthenticationFlag = false); nsresult ContinueHttpFetchAfterNetworkFetch(); // Returns the filtered response sent to the observer. + // Callers who don't have access to a channel can pass null for aFinalURI. already_AddRefed - BeginAndGetFilteredResponse(InternalResponse* aResponse); + BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aFinalURI); // Utility since not all cases need to do any post processing of the filtered // response. void BeginResponse(InternalResponse* aResponse); diff --git a/dom/fetch/InternalResponse.cpp b/dom/fetch/InternalResponse.cpp index 2791a013e2..4ff6d8d5c9 100644 --- a/dom/fetch/InternalResponse.cpp +++ b/dom/fetch/InternalResponse.cpp @@ -7,6 +7,8 @@ #include "InternalResponse.h" #include "mozilla/dom/InternalHeaders.h" +#include "mozilla/dom/cache/CacheTypes.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" #include "nsStreamUtils.h" namespace mozilla { @@ -14,13 +16,16 @@ namespace dom { InternalResponse::InternalResponse(uint16_t aStatus, const nsACString& aStatusText) : mType(ResponseType::Default) - , mFinalURL(false) , mStatus(aStatus) , mStatusText(aStatusText) , mHeaders(new InternalHeaders(HeadersGuardEnum::Response)) { } +InternalResponse::~InternalResponse() +{ +} + already_AddRefed InternalResponse::Clone() { @@ -74,5 +79,41 @@ InternalResponse::CORSResponse() return cors.forget(); } +void +InternalResponse::SetPrincipalInfo(UniquePtr aPrincipalInfo) +{ + mPrincipalInfo = Move(aPrincipalInfo); +} + +already_AddRefed +InternalResponse::OpaqueResponse() +{ + MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueResponse a already wrapped response"); + nsRefPtr response = new InternalResponse(0, EmptyCString()); + response->mType = ResponseType::Opaque; + response->mTerminationReason = mTerminationReason; + response->mURL = mURL; + response->mChannelInfo = mChannelInfo; + if (mPrincipalInfo) { + response->mPrincipalInfo = MakeUnique(*mPrincipalInfo); + } + response->mWrappedResponse = this; + return response.forget(); +} + +already_AddRefed +InternalResponse::CreateIncompleteCopy() +{ + nsRefPtr copy = new InternalResponse(mStatus, mStatusText); + copy->mType = mType; + copy->mTerminationReason = mTerminationReason; + copy->mURL = mURL; + copy->mChannelInfo = mChannelInfo; + if (mPrincipalInfo) { + copy->mPrincipalInfo = MakeUnique(*mPrincipalInfo); + } + return copy.forget(); +} + } // namespace dom } // namespace mozilla diff --git a/dom/fetch/InternalResponse.h b/dom/fetch/InternalResponse.h index 9b88de778c..a1e314f8c9 100644 --- a/dom/fetch/InternalResponse.h +++ b/dom/fetch/InternalResponse.h @@ -12,8 +12,13 @@ #include "mozilla/dom/ResponseBinding.h" #include "mozilla/dom/ChannelInfo.h" +#include "mozilla/UniquePtr.h" namespace mozilla { +namespace ipc { +class PrincipalInfo; +} + namespace dom { class InternalHeaders; @@ -41,18 +46,7 @@ public: } already_AddRefed - OpaqueResponse() - { - MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueResponse a already wrapped response"); - nsRefPtr response = new InternalResponse(0, EmptyCString()); - response->mType = ResponseType::Opaque; - response->mTerminationReason = mTerminationReason; - response->mURL = mURL; - response->mFinalURL = mFinalURL; - response->mChannelInfo = mChannelInfo; - response->mWrappedResponse = this; - return response.forget(); - } + OpaqueResponse(); already_AddRefed BasicResponse(); @@ -90,18 +84,6 @@ public: mURL.Assign(aURL); } - bool - FinalURL() const - { - return mFinalURL; - } - - void - SetFinalURL(bool aFinalURL) - { - mFinalURL = aFinalURL; - } - uint16_t GetStatus() const { @@ -181,9 +163,18 @@ public: return mChannelInfo; } + const UniquePtr& + GetPrincipalInfo() const + { + return mPrincipalInfo; + } + + // Takes ownership of the principal info. + void + SetPrincipalInfo(UniquePtr aPrincipalInfo); + private: - ~InternalResponse() - { } + ~InternalResponse(); explicit InternalResponse(const InternalResponse& aOther) = delete; InternalResponse& operator=(const InternalResponse&) = delete; @@ -191,26 +182,17 @@ private: // Returns an instance of InternalResponse which is a copy of this // InternalResponse, except headers, body and wrapped response (if any) which // are left uninitialized. Used for cloning and filtering. - already_AddRefed CreateIncompleteCopy() - { - nsRefPtr copy = new InternalResponse(mStatus, mStatusText); - copy->mType = mType; - copy->mTerminationReason = mTerminationReason; - copy->mURL = mURL; - copy->mFinalURL = mFinalURL; - copy->mChannelInfo = mChannelInfo; - return copy.forget(); - } + already_AddRefed CreateIncompleteCopy(); ResponseType mType; nsCString mTerminationReason; nsCString mURL; - bool mFinalURL; const uint16_t mStatus; const nsCString mStatusText; nsRefPtr mHeaders; nsCOMPtr mBody; ChannelInfo mChannelInfo; + UniquePtr mPrincipalInfo; // For filtered responses. // Cache, and SW interception should always serialize/access the underlying diff --git a/dom/fetch/Response.h b/dom/fetch/Response.h index 5692d70eae..81fb6fcf44 100644 --- a/dom/fetch/Response.h +++ b/dom/fetch/Response.h @@ -17,6 +17,10 @@ #include "InternalResponse.h" namespace mozilla { +namespace ipc { +class PrincipalInfo; +} + namespace dom { class Headers; @@ -90,6 +94,12 @@ public: return mInternalResponse->GetChannelInfo(); } + const UniquePtr& + GetPrincipalInfo() const + { + return mInternalResponse->GetPrincipalInfo(); + } + Headers* Headers_(); void diff --git a/dom/tests/mochitest/fetch/fetch_test_framework.js b/dom/tests/mochitest/fetch/fetch_test_framework.js index a69be67191..b6dd514705 100644 --- a/dom/tests/mochitest/fetch/fetch_test_framework.js +++ b/dom/tests/mochitest/fetch/fetch_test_framework.js @@ -1,12 +1,25 @@ function testScript(script) { + function setupPrefs() { + return new Promise(function(resolve, reject) { + SpecialPowers.pushPrefEnv({ + "set": [["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ["dom.serviceWorkers.exemptFromPerDomainMax", true]] + }, resolve); + }); + } + function workerTest() { return new Promise(function(resolve, reject) { var worker = new Worker("worker_wrapper.js"); worker.onmessage = function(event) { + if (event.data.context != "Worker") { + return; + } if (event.data.type == 'finish') { resolve(); } else if (event.data.type == 'status') { - ok(event.data.status, "Worker fetch test: " + event.data.msg); + ok(event.data.status, event.data.context + ": " + event.data.msg); } } worker.onerror = function(event) { @@ -17,6 +30,60 @@ function testScript(script) { }); } + function serviceWorkerTest() { + var isB2G = !navigator.userAgent.includes("Android") && + /Mobile|Tablet/.test(navigator.userAgent); + if (isB2G) { + // TODO B2G doesn't support running service workers for now due to bug 1137683. + dump("Skipping running the test in SW until bug 1137683 gets fixed.\n"); + return Promise.resolve(); + } + return new Promise(function(resolve, reject) { + function setupSW(registration) { + var worker = registration.waiting || + registration.active; + + window.addEventListener("message",function onMessage(event) { + if (event.data.context != "ServiceWorker") { + return; + } + if (event.data.type == 'finish') { + window.removeEventListener("message", onMessage); + registration.unregister() + .then(resolve) + .catch(reject); + } else if (event.data.type == 'status') { + ok(event.data.status, event.data.context + ": " + event.data.msg); + } + }, false); + + worker.onerror = reject; + + var iframe = document.createElement("iframe"); + iframe.src = "message_receiver.html"; + iframe.onload = function() { + worker.postMessage({ script: script }); + }; + document.body.appendChild(iframe); + } + + navigator.serviceWorker.register("worker_wrapper.js", {scope: "."}) + .then(function(registration) { + if (registration.installing) { + var done = false; + registration.installing.onstatechange = function() { + if (!done) { + done = true; + setupSW(registration); + } + }; + } else { + setupSW(registration); + } + }); + }); + } + function windowTest() { return new Promise(function(resolve, reject) { var scriptEl = document.createElement("script"); @@ -29,13 +96,19 @@ function testScript(script) { } SimpleTest.waitForExplicitFinish(); - // We have to run the window and worker tests sequentially since some tests - // set and compare cookies and running in parallel can lead to conflicting - // values. - windowTest() + // We have to run the window, worker and service worker tests sequentially + // since some tests set and compare cookies and running in parallel can lead + // to conflicting values. + setupPrefs() + .then(function() { + return windowTest(); + }) .then(function() { return workerTest(); }) + .then(function() { + return serviceWorkerTest(); + }) .catch(function(e) { ok(false, "Some test failed in " + script); info(e); @@ -43,7 +116,11 @@ function testScript(script) { return Promise.resolve(); }) .then(function() { - SimpleTest.finish(); + if (parent && parent.finishTest) { + parent.finishTest(); + } else { + SimpleTest.finish(); + } }); } diff --git a/dom/tests/mochitest/fetch/message_receiver.html b/dom/tests/mochitest/fetch/message_receiver.html new file mode 100644 index 0000000000..82cb587c72 --- /dev/null +++ b/dom/tests/mochitest/fetch/message_receiver.html @@ -0,0 +1,6 @@ + + diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini index 0831a800bf..a5cacd29f4 100644 --- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -11,13 +11,32 @@ support-files = test_response.js utils.js worker_wrapper.js + message_receiver.html + reroute.html + reroute.js + reroute.js^headers^ + sw_reroute.js [test_headers.html] +[test_headers_sw_reroute.html] +skip-if = buildapp == 'b2g' # Bug 1137683 [test_headers_mainthread.html] [test_fetch_app_protocol.html] [test_fetch_basic.html] +[test_fetch_basic_sw_reroute.html] +skip-if = buildapp == 'b2g' # Bug 1137683 [test_fetch_basic_http.html] +[test_fetch_basic_http_sw_reroute.html] +skip-if = e10s || buildapp == 'b2g' # Bug 1173163 for e10s, bug 1137683 for b2g [test_fetch_cors.html] +[test_fetch_cors_sw_reroute.html] +skip-if = e10s || buildapp == 'b2g' # Bug 1173163 for e10s, bug 1137683 for b2g [test_formdataparsing.html] +[test_formdataparsing_sw_reroute.html] +skip-if = buildapp == 'b2g' # Bug 1137683 [test_request.html] +[test_request_sw_reroute.html] +skip-if = buildapp == 'b2g' # Bug 1137683 [test_response.html] +[test_response_sw_reroute.html] +skip-if = buildapp == 'b2g' # Bug 1137683 diff --git a/dom/tests/mochitest/fetch/reroute.html b/dom/tests/mochitest/fetch/reroute.html new file mode 100644 index 0000000000..06a99c3273 --- /dev/null +++ b/dom/tests/mochitest/fetch/reroute.html @@ -0,0 +1,10 @@ + + + + + diff --git a/dom/tests/mochitest/fetch/reroute.js b/dom/tests/mochitest/fetch/reroute.js new file mode 100644 index 0000000000..a37d5d81c8 --- /dev/null +++ b/dom/tests/mochitest/fetch/reroute.js @@ -0,0 +1,3 @@ +onfetch = function(e) { + e.respondWith(fetch(e.request)); +}; diff --git a/dom/tests/mochitest/fetch/reroute.js^headers^ b/dom/tests/mochitest/fetch/reroute.js^headers^ new file mode 100644 index 0000000000..d0b9633bb0 --- /dev/null +++ b/dom/tests/mochitest/fetch/reroute.js^headers^ @@ -0,0 +1 @@ +Service-Worker-Allowed: / diff --git a/dom/tests/mochitest/fetch/sw_reroute.js b/dom/tests/mochitest/fetch/sw_reroute.js new file mode 100644 index 0000000000..36507cefba --- /dev/null +++ b/dom/tests/mochitest/fetch/sw_reroute.js @@ -0,0 +1,29 @@ +var gRegistration; + +function testScript(script) { + function setupSW(registration) { + gRegistration = registration; + + var iframe = document.createElement("iframe"); + iframe.src = "reroute.html?" + script.replace(".js", ""); + document.body.appendChild(iframe); + } + + SpecialPowers.pushPrefEnv({ + "set": [["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ["dom.serviceWorkers.exemptFromPerDomainMax", true]] + }, function() { + navigator.serviceWorker.ready.then(setupSW); + navigator.serviceWorker.register("reroute.js", {scope: "/"}); + }); +} + +function finishTest() { + gRegistration.unregister().then(SimpleTest.finish, function(e) { + dump("unregistration failed: " + e + "\n"); + SimpleTest.finish(); + }); +} + +SimpleTest.waitForExplicitFinish(); diff --git a/dom/tests/mochitest/fetch/test_fetch_basic_http_sw_reroute.html b/dom/tests/mochitest/fetch/test_fetch_basic_http_sw_reroute.html new file mode 100644 index 0000000000..0f5052edac --- /dev/null +++ b/dom/tests/mochitest/fetch/test_fetch_basic_http_sw_reroute.html @@ -0,0 +1,22 @@ + + + + + Bug 1039846 - Test fetch() http fetching in worker + + + + +

+ +

+
+
+
+
+
diff --git a/dom/tests/mochitest/fetch/test_fetch_basic_sw_reroute.html b/dom/tests/mochitest/fetch/test_fetch_basic_sw_reroute.html
new file mode 100644
index 0000000000..bcf959addb
--- /dev/null
+++ b/dom/tests/mochitest/fetch/test_fetch_basic_sw_reroute.html
@@ -0,0 +1,22 @@
+
+
+
+
+  Bug 1039846 - Test fetch() function in worker
+  
+  
+
+
+

+ +

+
+
+
+
+
diff --git a/dom/tests/mochitest/fetch/test_fetch_cors_sw_reroute.html b/dom/tests/mochitest/fetch/test_fetch_cors_sw_reroute.html
new file mode 100644
index 0000000000..7ad368cfd6
--- /dev/null
+++ b/dom/tests/mochitest/fetch/test_fetch_cors_sw_reroute.html
@@ -0,0 +1,22 @@
+
+
+
+
+  Bug 1039846 - Test fetch() CORS mode
+  
+  
+
+
+

+ +

+
+
+
+
+
diff --git a/dom/tests/mochitest/fetch/test_formdataparsing_sw_reroute.html b/dom/tests/mochitest/fetch/test_formdataparsing_sw_reroute.html
new file mode 100644
index 0000000000..b3fe0db44d
--- /dev/null
+++ b/dom/tests/mochitest/fetch/test_formdataparsing_sw_reroute.html
@@ -0,0 +1,22 @@
+
+
+
+
+  Bug 1109751 - Test FormData parsing
+  
+  
+
+
+

+ +

+
+
+
+
+
diff --git a/dom/tests/mochitest/fetch/test_headers_sw_reroute.html b/dom/tests/mochitest/fetch/test_headers_sw_reroute.html
new file mode 100644
index 0000000000..dd6ec5fb31
--- /dev/null
+++ b/dom/tests/mochitest/fetch/test_headers_sw_reroute.html
@@ -0,0 +1,16 @@
+
+
+
+
+  Test Fetch Headers - Basic
+  
+  
+
+
+
+
+
+
diff --git a/dom/tests/mochitest/fetch/test_request.html b/dom/tests/mochitest/fetch/test_request.html
index 66c46469c8..d33b8b1760 100644
--- a/dom/tests/mochitest/fetch/test_request.html
+++ b/dom/tests/mochitest/fetch/test_request.html
@@ -5,7 +5,7 @@
 
 
 
-  Bug XXXXXX - Test Request object in worker
+  Test Request object in worker
   
   
 
diff --git a/dom/tests/mochitest/fetch/test_request_sw_reroute.html b/dom/tests/mochitest/fetch/test_request_sw_reroute.html
new file mode 100644
index 0000000000..9fbfe767c8
--- /dev/null
+++ b/dom/tests/mochitest/fetch/test_request_sw_reroute.html
@@ -0,0 +1,22 @@
+
+
+
+
+  Test Request object in worker
+  
+  
+
+
+

+ +

+
+
+
+
+
diff --git a/dom/tests/mochitest/fetch/test_response_sw_reroute.html b/dom/tests/mochitest/fetch/test_response_sw_reroute.html
new file mode 100644
index 0000000000..9f9751b8ae
--- /dev/null
+++ b/dom/tests/mochitest/fetch/test_response_sw_reroute.html
@@ -0,0 +1,22 @@
+
+
+
+
+  Bug 1039846 - Test Response object in worker
+  
+  
+
+
+

+ +

+
+
+
+
+
diff --git a/dom/tests/mochitest/fetch/worker_wrapper.js b/dom/tests/mochitest/fetch/worker_wrapper.js
index e733565095..c2f0ec00e8 100644
--- a/dom/tests/mochitest/fetch/worker_wrapper.js
+++ b/dom/tests/mochitest/fetch/worker_wrapper.js
@@ -1,31 +1,58 @@
 importScripts("utils.js");
+var client;
+var context;
 
 function ok(a, msg) {
-  postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+  client.postMessage({type: 'status', status: !!a,
+                      msg: a + ": " + msg, context: context});
 }
 
 function is(a, b, msg) {
-  postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+  client.postMessage({type: 'status', status: a === b,
+                      msg: a + " === " + b + ": " + msg, context: context});
 }
 
 addEventListener('message', function workerWrapperOnMessage(e) {
   removeEventListener('message', workerWrapperOnMessage);
   var data = e.data;
 
-  var done = function() {
-    postMessage({ type: 'finish' });
+  function loadTest() {
+    var done = function() {
+      client.postMessage({ type: 'finish', context: context });
+    }
+
+    try {
+      importScripts(data.script);
+      // runTest() is provided by the test.
+      runTest().then(done, done);
+    } catch(e) {
+      client.postMessage({
+        type: 'status',
+        status: false,
+        msg: 'worker failed to import ' + data.script + "; error: " + e.message,
+        context: context
+      });
+      done();
+    }
   }
 
-  try {
-    importScripts(data.script);
-    // runTest() is provided by the test.
-    runTest().then(done, done);
-  } catch(e) {
-    postMessage({
-      type: 'status',
-      status: false,
-      msg: 'worker failed to import ' + data.script + "; error: " + e.message
+  if ("ServiceWorker" in self) {
+    self.clients.matchAll().then(function(clients) {
+      for (var i = 0; i < clients.length; ++i) {
+        if (clients[i].url.indexOf("message_receiver.html") > -1) {
+          client = clients[i];
+          break;
+        }
+      }
+      if (!client) {
+        dump("We couldn't find the message_receiver window, the test will fail\n");
+      }
+      context = "ServiceWorker";
+      loadTest();
     });
-    done();
+  } else {
+    client = self;
+    context = "Worker";
+    loadTest();
   }
 });
diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp
index 435910c567..63619c7da4 100644
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -38,6 +38,7 @@
 #include "mozilla/LoadContext.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/dom/CacheBinding.h"
+#include "mozilla/dom/cache/CacheTypes.h"
 #include "mozilla/dom/cache/Cache.h"
 #include "mozilla/dom/cache/CacheStorage.h"
 #include "mozilla/dom/Exceptions.h"
@@ -422,6 +423,7 @@ private:
   nsCOMPtr mPump;
   nsCOMPtr mBaseURI;
   ChannelInfo mChannelInfo;
+  UniquePtr mPrincipalInfo;
 };
 
 NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver)
@@ -592,6 +594,27 @@ private:
     // saved in the cache.
     ir->InitChannelInfo(channel);
 
+    // Save the principal of the channel since its URI encodes the script URI
+    // rather than the ServiceWorkerRegistrationInfo URI.
+    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+    NS_ASSERTION(ssm, "Should never be null!");
+
+    nsCOMPtr channelPrincipal;
+    nsresult rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      channel->Cancel(rv);
+      return rv;
+    }
+
+    UniquePtr principalInfo(new PrincipalInfo());
+    rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      channel->Cancel(rv);
+      return rv;
+    }
+
+    ir->SetPrincipalInfo(Move(principalInfo));
+
     nsRefPtr response = new Response(mCacheCreator->Global(), ir);
 
     RequestOrUSVString request;
@@ -1046,7 +1069,8 @@ private:
   void
   DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString,
                         uint32_t aStringLen,
-                        const ChannelInfo& aChannelInfo)
+                        const ChannelInfo& aChannelInfo,
+                        UniquePtr aPrincipalInfo)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aIndex < mLoadInfos.Length());
@@ -1074,10 +1098,15 @@ private:
       MOZ_ASSERT(principal);
       nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup();
       MOZ_ASSERT(loadGroup);
+
+      nsCOMPtr responsePrincipal =
+        PrincipalInfoToPrincipal(*aPrincipalInfo);
+      DebugOnly equal = false;
+      MOZ_ASSERT(responsePrincipal && NS_SUCCEEDED(responsePrincipal->Equals(principal, &equal)));
+      MOZ_ASSERT(equal);
+
       mWorkerPrivate->InitChannelInfo(aChannelInfo);
-      // Needed to initialize the principal info. This is fine because
-      // the cache principal cannot change, unlike the channel principal.
-      mWorkerPrivate->SetPrincipal(principal, loadGroup);
+      mWorkerPrivate->SetPrincipal(responsePrincipal, loadGroup);
     }
 
     if (NS_SUCCEEDED(rv)) {
@@ -1429,10 +1458,16 @@ CacheScriptLoader::ResolvedCallback(JSContext* aCx,
   nsCOMPtr inputStream;
   response->GetBody(getter_AddRefs(inputStream));
   mChannelInfo = response->GetChannelInfo();
+  const UniquePtr& pInfo =
+    response->GetPrincipalInfo();
+  if (pInfo) {
+    mPrincipalInfo = MakeUnique(*pInfo);
+  }
 
   if (!inputStream) {
     mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
-    mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0, mChannelInfo);
+    mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0, mChannelInfo,
+                                     Move(mPrincipalInfo));
     return;
   }
 
@@ -1488,7 +1523,9 @@ CacheScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aCont
 
   mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
 
-  mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen, mChannelInfo);
+  MOZ_ASSERT(mPrincipalInfo);
+  mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen, mChannelInfo,
+                                   Move(mPrincipalInfo));
   return NS_OK;
 }
 
diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
index 17018fd3d2..d00d685a28 100644
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -873,6 +873,7 @@ class ServiceWorkerRegisterJob final : public ServiceWorkerJob,
   nsRefPtr mCallback;
   nsCOMPtr mPrincipal;
   nsRefPtr mUpdateAndInstallInfo;
+  nsCOMPtr mLoadGroup;
 
   ~ServiceWorkerRegisterJob()
   { }
@@ -893,15 +894,19 @@ public:
                            const nsCString& aScope,
                            const nsCString& aScriptSpec,
                            ServiceWorkerUpdateFinishCallback* aCallback,
-                           nsIPrincipal* aPrincipal)
+                           nsIPrincipal* aPrincipal,
+                           nsILoadGroup* aLoadGroup)
     : ServiceWorkerJob(aQueue)
     , mScope(aScope)
     , mScriptSpec(aScriptSpec)
     , mCallback(aCallback)
     , mPrincipal(aPrincipal)
+    , mLoadGroup(aLoadGroup)
     , mJobType(REGISTER_JOB)
     , mCanceled(false)
-  { }
+  {
+    MOZ_ASSERT(mLoadGroup);
+  }
 
   // [[Update]]
   ServiceWorkerRegisterJob(ServiceWorkerJobQueue* aQueue,
@@ -1223,7 +1228,7 @@ private:
     nsresult rv =
       serviceWorkerScriptCache::Compare(mRegistration->mPrincipal, cacheName,
                                         NS_ConvertUTF8toUTF16(mRegistration->mScriptSpec),
-                                        this);
+                                        this, mLoadGroup);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return Fail(rv);
     }
@@ -1495,8 +1500,20 @@ ServiceWorkerManager::Register(nsIDOMWindow* aWindow,
   nsRefPtr cb =
     new ServiceWorkerResolveWindowPromiseOnUpdateCallback(window, promise);
 
+  nsCOMPtr docLoadGroup = doc->GetDocumentLoadGroup();
+  nsRefPtr ir =
+    new WorkerLoadInfo::InterfaceRequestor(documentPrincipal, docLoadGroup);
+  ir->MaybeAddTabChild(docLoadGroup);
+
+  // Create a load group that is separate from, yet related to, the document's load group.
+  // This allows checks for interfaces like nsILoadContext to yield the values used by the
+  // the document, yet will not cancel the update job if the document's load group is cancelled.
+  nsCOMPtr loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+  rv = loadGroup->SetNotificationCallbacks(ir);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
+
   nsRefPtr job =
-    new ServiceWorkerRegisterJob(queue, cleanedScope, spec, cb, documentPrincipal);
+    new ServiceWorkerRegisterJob(queue, cleanedScope, spec, cb, documentPrincipal, loadGroup);
   queue->Append(job);
 
   AssertIsOnMainThread();
diff --git a/dom/workers/ServiceWorkerScriptCache.cpp b/dom/workers/ServiceWorkerScriptCache.cpp
index 9fd11cd521..897752cc06 100644
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -9,6 +9,8 @@
 #include "mozilla/dom/CacheBinding.h"
 #include "mozilla/dom/cache/CacheStorage.h"
 #include "mozilla/dom/cache/Cache.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "nsIThreadRetargetableRequest.h"
 
 #include "nsIPrincipal.h"
@@ -72,7 +74,7 @@ public:
   }
 
   nsresult
-  Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL)
+  Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup)
   {
     MOZ_ASSERT(aPrincipal);
     AssertIsOnMainThread();
@@ -83,10 +85,17 @@ public:
       return rv;
     }
 
+    nsCOMPtr loadGroup;
+    rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
     rv = NS_NewChannel(getter_AddRefs(mChannel),
                        uri, aPrincipal,
                        nsILoadInfo::SEC_NORMAL,
-                       nsIContentPolicy::TYPE_SCRIPT); // FIXME(nsm): TYPE_SERVICEWORKER
+                       nsIContentPolicy::TYPE_SCRIPT, // FIXME(nsm): TYPE_SERVICEWORKER
+                       loadGroup);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
@@ -275,7 +284,7 @@ public:
 
   nsresult
   Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
-             const nsAString& aCacheName)
+             const nsAString& aCacheName, nsILoadGroup* aLoadGroup)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aPrincipal);
@@ -292,7 +301,7 @@ public:
     }
 
     mCN = new CompareNetwork(this);
-    nsresult rv = mCN->Initialize(aPrincipal, aURL);
+    nsresult rv = mCN->Initialize(aPrincipal, aURL, aLoadGroup);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
@@ -445,6 +454,28 @@ public:
     mChannelInfo.InitFromChannel(aChannel);
   }
 
+  nsresult
+  SetPrincipalInfo(nsIChannel* aChannel)
+  {
+    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+    NS_ASSERTION(ssm, "Should never be null!");
+
+    nsCOMPtr channelPrincipal;
+    nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    UniquePtr principalInfo(new mozilla::ipc::PrincipalInfo());
+    rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    mPrincipalInfo = Move(principalInfo);
+    return NS_OK;
+  }
+
 private:
   ~CompareManager()
   {
@@ -540,6 +571,9 @@ private:
     ir->SetBody(body);
 
     ir->InitChannelInfo(mChannelInfo);
+    if (mPrincipalInfo) {
+      ir->SetPrincipalInfo(Move(mPrincipalInfo));
+    }
 
     nsRefPtr response = new Response(aCache->GetGlobalObject(), ir);
 
@@ -572,6 +606,8 @@ private:
 
   ChannelInfo mChannelInfo;
 
+  UniquePtr mPrincipalInfo;
+
   nsCString mMaxScope;
 
   enum {
@@ -600,6 +636,10 @@ CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 #endif
 
   mManager->InitChannelInfo(mChannel);
+  nsresult rv = mManager->SetPrincipalInfo(mChannel);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   return NS_OK;
 }
@@ -930,7 +970,8 @@ GenerateCacheName(nsAString& aName)
 
 nsresult
 Compare(nsIPrincipal* aPrincipal, const nsAString& aCacheName,
-        const nsAString& aURL, CompareCallback* aCallback)
+        const nsAString& aURL, CompareCallback* aCallback,
+        nsILoadGroup* aLoadGroup)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aPrincipal);
@@ -939,7 +980,7 @@ Compare(nsIPrincipal* aPrincipal, const nsAString& aCacheName,
 
   nsRefPtr cm = new CompareManager(aCallback);
 
-  nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName);
+  nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName, aLoadGroup);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
diff --git a/dom/workers/ServiceWorkerScriptCache.h b/dom/workers/ServiceWorkerScriptCache.h
index 23c53c3ee9..bfbc5c2d9e 100644
--- a/dom/workers/ServiceWorkerScriptCache.h
+++ b/dom/workers/ServiceWorkerScriptCache.h
@@ -44,7 +44,7 @@ public:
 
 nsresult
 Compare(nsIPrincipal* aPrincipal, const nsAString& aCacheName,
-        const nsAString& aURL, CompareCallback* aCallback);
+        const nsAString& aURL, CompareCallback* aCallback, nsILoadGroup* aLoadGroup);
 
 } // namespace serviceWorkerScriptCache
 
diff --git a/dom/workers/test/serviceworkers/app-protocol/README.txt b/dom/workers/test/serviceworkers/app-protocol/README.txt
index fcf383b300..541e2bda23 100644
--- a/dom/workers/test/serviceworkers/app-protocol/README.txt
+++ b/dom/workers/test/serviceworkers/app-protocol/README.txt
@@ -1,2 +1,3 @@
-application.zip contains foo.txt, index.html, sw.js and manifest.webapp.
-Any change to one of these three files should be added to application.zip as well.
+application.list contains a list of files that are in application.zip.
+
+To update application.zip when changing one of those files, run makezip.sh.
diff --git a/dom/workers/test/serviceworkers/app-protocol/application.list b/dom/workers/test/serviceworkers/app-protocol/application.list
new file mode 100644
index 0000000000..8ebb411002
--- /dev/null
+++ b/dom/workers/test/serviceworkers/app-protocol/application.list
@@ -0,0 +1,7 @@
+controlled.html
+foo.txt
+index.html
+manifest.webapp
+sw.js
+test.js
+test_doc_load_interception.js
diff --git a/dom/workers/test/serviceworkers/app-protocol/application.zip b/dom/workers/test/serviceworkers/app-protocol/application.zip
index 0e914a94cf6639d4f26e47a5a26e21c0422eb82e..b179b0819cc03684676645149018a195b8b8790a 100644
GIT binary patch
literal 3362
zcmai$c{r5oAI4wCHlwj+DIqe$psY!e3YlaqV@ytsCEGA!W;8~&lEhKAk|j=Lrv=%`
zATid8j-?gZ4P_Z?Le8k`{ObJbbpCj*>-pooKJW9s_x*hD8;t^S2m=7X1GM-yBH!_S
zz*E@(z#0Sq{D1`DL?%%wWDgI#voe87^f-Y910clw*%L~vK3pja$&*RFY*=3^hr+jHoQi>MhiATjvX_rpha-g`
z9ibhFsz?IS?RL}PCl1TFaIrE+ybLUOFwFwtC%4r{Tz#^-J6NNm$^AvKfJB9T9mcF<1ru`~tZmfHYy%Q`+yi8DZ7s#q-&jNR!fuNQ2>WwnTMs!k3up4KX
zJl?Kue?mOX>G^RNzg+PbdT(Ez<8QgYBj45R(b??93Cvxd3Zt;pj}Hx}?w91mmmUeD
zm$^q26(n7!s!nq(ZS3nTyR9YKlQYofZRa?sv-+E>jJJ^bxb4v6w4E6{*H`3S(GZYC
zda@)$$Mm4|nE=QwRXe|!cO$i4@`fn*hr@nfWcz
zQr+#0CK$>=gwvbj!E!Ru@cHuS8`$W#YNo3*FjFmU4xxvz;?uX+Z>J>p>GrqV*
zVTE2#Lu_KXqIM+SSV8PIK29)wJN>?S&&7wO{y2GSslVLnNlFCdxMy*Cy55
zRe`1W_Hixst!tSxun-zkF4EpL{_ZT~C~T==iHz8Ky&Lyjh0qVTobnfw)bPMfW`g&N
zHM_8<&RgU==l0~DzqzW{@d&qi;Em-IZCXYq-OeWk%zY%5DU@lMOrqOBjQ@(#2p2&O!7=jd=`4X+hnX;w3$&aJRA5Fo_eMwBjed
za=m*SQ<%4uMrVW7dg6<&Z-2EH9|bKqn`^M<#u1AqcI0C2KA=H;V&
z#_LyO<{7q?^j1}%W>tx+%81?Zp6ia*Yy>)uQ;4qYkAPf;H;YJyV_TU*9G{ml!c+T>
zLsh2IwV|$emiu>0I7G?&`W{Z{@5{vUaX!{`*0j*Mr`S82)6)_{RMa=Gz3~`ly&Tkj
z%zX6E{J6T5YZX(4-U)eyRTOZ{N@zzDh#BENcjM`XPPqdel?=M(-(DFvV?|(O&V_K<
z#@v?3
z-5tKOMVH?n4lAW{O(zrMj)`80xUgm533*=-qg%8?8)GL~rM5C0{~&-SA@B8ef;X4w
zS1IQOvv^c+{3
zo>Xqrjs?!KTVq}nhMHG7$e(YN^c}>aj_W5rPj;tbohx=CRj;ZM@~~c^Lbba3;>L_z
zb)|Wx0^Ao#!9eQ^-Qd3R)wo%a8A0W|u#^79&rB91G$!}AyE|OFeiRv%>r$ec){|BVcg&$5A6-LfN%<-Z+lgS_{&A1e3Hqf12i>?K)wHGr;=51-mv78
zWmcl^Owx88z)pWkH_O&wh-aoPIB-ny8~m2u2E}c=aGWz^NT+K6C=LQyPar8nxIHtf
zyrJQ_M{?3-$5qAkes3bgmNg6Z#znT(E?TO~#8j*`TRqV!ihkJSua$Zz5T=ilAGKZ>
zOQ<~duqi;Sd;I3>1hGgRWumuZVD0|TG$$@a?Q?sDqs;vhtr|qCisJkd2+g-&euC{(NwY6q!1k#eLls%r)qF=ZO!r%iHs)^<5M9F>`{f
zjfhHP`mQNSo5`%_Y}o08nw|M${4_I&%&{)UksSu2OV#!tyTJ23bvStJKZ6i$jx|
zQ(z^t>E*G!I}%I452##
z4wSMW9Lj=6JS?vu`v{+z0g)~lRUneza)!H550Qv@bIA=$g~tg)9*ZKoqphqw7x;Nj
z9q8v*sz`u6*l+Z#60Kyvr+lx!4bH@
zqqBTN)R$Het->@#$YjNW@staCNWI9hyg!TGcsiHfH<;XSEt
zWYXUku-}OMv$6nfE`kQBZsDlAtxyYs$pr}_5Q)+)(T=Tj%!j&9o_+99(XPs!O6`ue
z;2!ofR|O0uPQ{7Y=zdmTYb3%>CC++!6+!MwB@>b>20>^P8@m+f-@5EJ)=>d~)n1?NUlr2qf`

literal 2331
zcmaJ?c~nzp7EcH<0TOnB0hKrn${G*`WNl$0yFeNZn;Mo7*}`IoEV2s3DmWRILRnKW
zpdge}Fl1y?1S|=J9T@~smJykPG8L?F2r@6Im{3pOcfRw^`{Uj7yZ5(ToGV0983YGT
zD3Rj;5_zOS@*upu-3d2)3@x5U@J4_JPWcg31VqB`U}ey`frrRWBZR|j;_Y=r_Hse;-_rtEJ)xlk2W#90GH)%H2v&
zj2zY&bnfeard|FD?APG6$V!U
zI|j!9Aj^P@+l7wJ6^AYdKqo~-(x_1s3ONW9LW`gX@k@C65vEW3?cgWmx`lTcA+D@!
z?4RWEv<@4!*p=Bn>A6T&{)JJ`GJB1m;<`I%Z>`)7?rq7>Tw8ps`XDhSaWW;6=>Nal
z4Sn&(pd{O2$D{X%8w#xkmU5lG6B_B$w6qrD@kn&rcm-dw)u!}*bY9X?b!kHQ$v92kRdolrLma_H!7}^?X#+m&DNLYM?V{*o$#7z;Flk+@U*Fmu=EsD*!afiBXlC
zBTki2WucKlD>hVscEj
zF(2=;y}4}OKhjW6aUJyV@W_f{K4+@^S*Qp}KQD_*%hw#uv
zL4V2gD-eQLG1n#{?TA$
z9;YHm@mT|neWxlKjykJ4iAqM9(mOottj?Kdl-M3m=
zGmXA{fSsC5_-EyKZa{L={FSgN>7}CCA^bb0|G=eV%)v=rv$pW*sh0!Tnk=EZH8tf6!lG#83MCZh)@$%>1B6f0X@8s_%lrijTR9Q8G4=^L&L<8
z0|TO?_Xb7n0v?C!b>+Je8u2))zBXb+FNSIrZzP~La>nKq>D}oPbhhBH^vV$$6pJ;%n#d?=)%#+0B=*?RMbR6OSAdwr
zO7}<%3V@7>!-U0rf_Cepx=a~%GU>MR^p*EnaT=;&OJ^6gTA+546$^r*AJ<
zshJ3!DSg*o%)MA6LmJSGr8ay!alxr3D1pGX>oxJ>QV~-AeFcjhD}}5@v$09buIZ*S
z{go!#-=!0-wEHU0oRYL||8MT)x#Q84w(OHVV#lvwbpJ*r3+HR~ShcFx;zKJQRLiQ#pgdB3KRkn5TBH#I4Ey8m!m1G$iq?Z_hAw2h;f!xq0W^w!-$VNkD81&BP-cdm}RlJ>A)zxmy$
ziT`}GGe3L#D$l}sj7_&c?2&}HdZ#O;HIc+j?9X?Y&*SJ3e}CYU$zbO6hD~u`v|D$z
zs0}M;^rfmCeOc$~LUJu@?U1yA?~m!c?e^3mR5#nho?t2ShW4B;)1Phkc@@{^KIWtMRUqCU82v8OhrO+Q|4G%z`s{4}
zhh)qsDF`$IToAwG0$md-tx{9Ph2!NKI$F}ma@GZJI;k%m?=lZ#ZaA}Dqubfzo
zR-ncoRv3JDg^3mt2DNC|!o&*61JHM4`^!j#nJnse@ovro1w<1X4*HbPdqIfWQM?

Q@ diff --git a/dom/workers/test/serviceworkers/app-protocol/controlled.html b/dom/workers/test/serviceworkers/app-protocol/controlled.html index cf11816aea..7a7c2d431e 100644 --- a/dom/workers/test/serviceworkers/app-protocol/controlled.html +++ b/dom/workers/test/serviceworkers/app-protocol/controlled.html @@ -3,12 +3,24 @@ Test app for bug 1161684 + diff --git a/dom/workers/test/serviceworkers/app-protocol/index.html b/dom/workers/test/serviceworkers/app-protocol/index.html index 38726531c5..7dcc8cac52 100644 --- a/dom/workers/test/serviceworkers/app-protocol/index.html +++ b/dom/workers/test/serviceworkers/app-protocol/index.html @@ -7,7 +7,6 @@ function registerServiceWorker() { return new Promise((resolve, reject) => { navigator.serviceWorker.ready.then(() => { - ready(); resolve(); }); navigator.serviceWorker.register('sw.js', {scope: '.'}) @@ -20,9 +19,10 @@ function registerServiceWorker() { function runTests() { return Promise.resolve() - .then(() => { return testFetchAppResource('networkresponse'); }) + .then(() => { return testFetchAppResource('foo.txt', + 'networkresponse'); }) .then(registerServiceWorker) - .then(done); + .then(ready); } diff --git a/dom/workers/test/serviceworkers/app-protocol/makezip.sh b/dom/workers/test/serviceworkers/app-protocol/makezip.sh new file mode 100644 index 0000000000..317c908919 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/makezip.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +rm application.zip +zip application.zip `cat application.list` diff --git a/dom/workers/test/serviceworkers/app-protocol/realindex.html b/dom/workers/test/serviceworkers/app-protocol/realindex.html new file mode 100644 index 0000000000..a0a381f5b3 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/realindex.html @@ -0,0 +1,2 @@ + +real index diff --git a/dom/workers/test/serviceworkers/app-protocol/realindex.html^headers^ b/dom/workers/test/serviceworkers/app-protocol/realindex.html^headers^ new file mode 100644 index 0000000000..cb762eff80 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/realindex.html^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/dom/workers/test/serviceworkers/app-protocol/redirect-https.sjs b/dom/workers/test/serviceworkers/app-protocol/redirect-https.sjs new file mode 100644 index 0000000000..7806ee0a05 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/redirect-https.sjs @@ -0,0 +1,5 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.setHeader("Location", "https://example.org/tests/dom/workers/test/serviceworkers/app-protocol/realindex.html", false); +} diff --git a/dom/workers/test/serviceworkers/app-protocol/redirect.sjs b/dom/workers/test/serviceworkers/app-protocol/redirect.sjs new file mode 100644 index 0000000000..a63b0c2f92 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/redirect.sjs @@ -0,0 +1,5 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.setHeader("Location", "http://example.org/tests/dom/workers/test/serviceworkers/app-protocol/realindex.html", false); +} diff --git a/dom/workers/test/serviceworkers/app-protocol/sw.js b/dom/workers/test/serviceworkers/app-protocol/sw.js index 38975595b0..ccf2ce69ad 100644 --- a/dom/workers/test/serviceworkers/app-protocol/sw.js +++ b/dom/workers/test/serviceworkers/app-protocol/sw.js @@ -1,8 +1,63 @@ +const kHTTPRedirect = "http://example.com/tests/dom/workers/test/serviceworkers/app-protocol/redirect.sjs"; +const kHTTPSRedirect = "https://example.com/tests/dom/workers/test/serviceworkers/app-protocol/redirect-https.sjs"; + +self.addEventListener('install', (event) => { + event.waitUntil( + self.caches.open("origin-app-cache") + .then(c => { + return Promise.all( + [ + c.add(kHTTPRedirect), + c.add(kHTTPSRedirect), + ] + ); + }) + ); +}); + self.addEventListener('fetch', (event) => { if (event.request.url.indexOf('foo.txt') >= 0) { - var body = 'swresponse'; - event.respondWith(new Response(body, { + event.respondWith(new Response('swresponse', { headers: {'Content-Type': 'text/plain'} })); } + + if (event.request.url.indexOf('test_doc_load_interception.js') >= 0 ) { + var scriptContent = 'alert("OK: Script modified by service worker")'; + event.respondWith(new Response(scriptContent, { + headers: {'Content-Type': 'application/javascript'} + })); + } + + if (event.request.url.indexOf('test_custom_content_type') >= 0) { + event.respondWith(new Response('customContentType', { + headers: {'Content-Type': 'text/html'} + })); + } + + if (event.request.url.indexOf('redirected.html') >= 0) { + event.respondWith(fetch(kHTTPRedirect)); + } + + if (event.request.url.indexOf('redirected-https.html') >= 0) { + event.respondWith(fetch(kHTTPSRedirect)); + } + + if (event.request.url.indexOf('redirected-cached.html') >= 0) { + event.respondWith( + self.caches.open("origin-app-cache") + .then(c => { + return c.match(kHTTPRedirect); + }) + ); + } + + if (event.request.url.indexOf('redirected-https-cached.html') >= 0) { + event.respondWith( + self.caches.open("origin-app-cache") + .then(c => { + return c.match(kHTTPSRedirect); + }) + ); + } }); diff --git a/dom/workers/test/serviceworkers/app-protocol/test.js b/dom/workers/test/serviceworkers/app-protocol/test.js index e08d615604..814310c732 100644 --- a/dom/workers/test/serviceworkers/app-protocol/test.js +++ b/dom/workers/test/serviceworkers/app-protocol/test.js @@ -14,16 +14,59 @@ function done() { alert('DONE'); } -function testFetchAppResource(aExpectedResponse) { - return fetch('foo.txt').then(res => { +function testFetchAppResource(aUrl, + aExpectedResponse, + aExpectedContentType) { + return fetch(aUrl).then(res => { ok(true, 'fetch should resolve'); if (res.type == 'error') { ok(false, 'fetch failed'); } ok(res.status == 200, 'status should be 200'); ok(res.statusText == 'OK', 'statusText should be OK'); + if (aExpectedContentType) { + var headers = res.headers.getAll('Content-Type'); + ok(headers.length, "Headers length"); + var contentType = res.headers.get('Content-Type'); + ok(contentType == aExpectedContentType, ('content type ' + + contentType + ' should match with ' + aExpectedContentType)); + } return res.text().then(body => { - ok(body == aExpectedResponse, 'body should match'); + ok(body == aExpectedResponse, 'body ' + body + + ' should match with ' + aExpectedResponse); }); }); } + +function testRedirectedResponse() { + return testRedirectedResponseWorker("redirected", "IFRAMELOADED"); +} + +function testRedirectedHttpsResponse() { + return testRedirectedResponseWorker("redirected-https", "HTTPSIFRAMELOADED"); +} + +function testCachedRedirectedResponse() { + return testRedirectedResponseWorker("redirected-cached", "IFRAMELOADED"); +} + +function testCachedRedirectedHttpsResponse() { + return testRedirectedResponseWorker("redirected-https-cached", "HTTPSIFRAMELOADED"); +} + +function testRedirectedResponseWorker(aFrameId, aAlert) { + // Because of the CSP policies applied to privileged apps, we cannot run + // inline script inside realindex.html, and loading a script from the app:// + // URI is also not an option, so we let the parent iframe which has access + // to the SpecialPowers API use those privileges to access the document. + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + iframe.src = aFrameId + ".html"; + iframe.id = aFrameId; + return new Promise(resolve => { + iframe.addEventListener("load", event => { + alert(aAlert); + resolve(); + }, false); + }); +} diff --git a/dom/workers/test/serviceworkers/app-protocol/test_doc_load_interception.js b/dom/workers/test/serviceworkers/app-protocol/test_doc_load_interception.js new file mode 100644 index 0000000000..df42904269 --- /dev/null +++ b/dom/workers/test/serviceworkers/app-protocol/test_doc_load_interception.js @@ -0,0 +1 @@ +alert('KO: Should not load this file, but the sw modified version instead'); diff --git a/dom/workers/test/serviceworkers/fetch/fetch_tests.js b/dom/workers/test/serviceworkers/fetch/fetch_tests.js index b256ca3b65..257e8d5e7c 100644 --- a/dom/workers/test/serviceworkers/fetch/fetch_tests.js +++ b/dom/workers/test/serviceworkers/fetch/fetch_tests.js @@ -123,6 +123,25 @@ fetchXHR('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?st finish(); }); +// Test that when the page fetches a url the controlling SW forces a redirect to +// another location. This other location fetch should also be intercepted by +// the SW. +fetchXHR('something.txt', function(xhr) { + my_ok(xhr.status == 200, "load should be successful"); + my_ok(xhr.responseText == "something else response body", "load should have something else"); + finish(); +}); + +// Test fetch will internally get it's SkipServiceWorker flag set. The request is +// made from the SW through fetch(). fetch() fetches a server-side JavaScript +// file that force a redirect. The redirect location fetch does not go through +// the SW. +fetchXHR('redirect_serviceworker.sjs', function(xhr) { + my_ok(xhr.status == 200, "load should be successful"); + my_ok(xhr.responseText == "// empty worker, always succeed!\n", "load should have redirection content"); + finish(); +}); + expectAsyncResult(); fetch('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*') .then(function(res) { diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs b/dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs new file mode 100644 index 0000000000..7266925ea7 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/index-https.sjs @@ -0,0 +1,4 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Location", "https://example.org/tests/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html", false); +} diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js b/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js new file mode 100644 index 0000000000..ddbf02d4c6 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/origin_test.js @@ -0,0 +1,23 @@ +var prefix = "/tests/dom/workers/test/serviceworkers/fetch/origin/https/"; + +self.addEventListener("install", function(event) { + event.waitUntil( + self.caches.open("origin-cache") + .then(c => { + return c.add(prefix + 'index-https.sjs'); + }) + ); +}); + +self.addEventListener("fetch", function(event) { + if (event.request.url.indexOf("index-cached-https.sjs") >= 0) { + event.respondWith( + self.caches.open("origin-cache") + .then(c => { + return c.match(prefix + 'index-https.sjs'); + }) + ); + } else { + event.respondWith(fetch(event.request)); + } +}); diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html new file mode 100644 index 0000000000..87f3489455 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html @@ -0,0 +1,6 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ new file mode 100644 index 0000000000..5ed82fd065 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/realindex.html^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: https://example.com diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/register.html b/dom/workers/test/serviceworkers/fetch/origin/https/register.html new file mode 100644 index 0000000000..2e99adba53 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/register.html @@ -0,0 +1,14 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html b/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/https/unregister.html @@ -0,0 +1,12 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs b/dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs new file mode 100644 index 0000000000..1cc916ff39 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/index-to-https.sjs @@ -0,0 +1,4 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Location", "https://example.org/tests/dom/workers/test/serviceworkers/fetch/origin/realindex.html", false); +} diff --git a/dom/workers/test/serviceworkers/fetch/origin/index.sjs b/dom/workers/test/serviceworkers/fetch/origin/index.sjs new file mode 100644 index 0000000000..a79588e76e --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/index.sjs @@ -0,0 +1,4 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Location", "http://example.org/tests/dom/workers/test/serviceworkers/fetch/origin/realindex.html", false); +} diff --git a/dom/workers/test/serviceworkers/fetch/origin/origin_test.js b/dom/workers/test/serviceworkers/fetch/origin/origin_test.js new file mode 100644 index 0000000000..7b5bac2282 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/origin_test.js @@ -0,0 +1,35 @@ +var prefix = "/tests/dom/workers/test/serviceworkers/fetch/origin/"; + +self.addEventListener("install", function(event) { + event.waitUntil( + self.caches.open("origin-cache") + .then(c => { + return Promise.all( + [ + c.add(prefix + 'index.sjs'), + c.add(prefix + 'index-to-https.sjs'), + ] + ); + }) + ); +}); + +self.addEventListener("fetch", function(event) { + if (event.request.url.indexOf("index-cached.sjs") >= 0) { + event.respondWith( + self.caches.open("origin-cache") + .then(c => { + return c.match(prefix + 'index.sjs'); + }) + ); + } else if (event.request.url.indexOf("index-to-https-cached.sjs") >= 0) { + event.respondWith( + self.caches.open("origin-cache") + .then(c => { + return c.match(prefix + 'index-to-https.sjs'); + }) + ); + } else { + event.respondWith(fetch(event.request)); + } +}); diff --git a/dom/workers/test/serviceworkers/fetch/origin/realindex.html b/dom/workers/test/serviceworkers/fetch/origin/realindex.html new file mode 100644 index 0000000000..87f3489455 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/realindex.html @@ -0,0 +1,6 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ b/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ new file mode 100644 index 0000000000..3a6a85d894 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/realindex.html^headers^ @@ -0,0 +1 @@ +Access-Control-Allow-Origin: http://mochi.test:8888 diff --git a/dom/workers/test/serviceworkers/fetch/origin/register.html b/dom/workers/test/serviceworkers/fetch/origin/register.html new file mode 100644 index 0000000000..2e99adba53 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/register.html @@ -0,0 +1,14 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/origin/unregister.html b/dom/workers/test/serviceworkers/fetch/origin/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/origin/unregister.html @@ -0,0 +1,12 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/index.html b/dom/workers/test/serviceworkers/fetch/requesturl/index.html new file mode 100644 index 0000000000..bc3e400a94 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/requesturl/index.html @@ -0,0 +1,7 @@ + + + diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/redirect.sjs b/dom/workers/test/serviceworkers/fetch/requesturl/redirect.sjs new file mode 100644 index 0000000000..7b92fec20d --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/requesturl/redirect.sjs @@ -0,0 +1,4 @@ +function handleRequest(request, response) { + response.setStatusLine(null, 308, "Permanent Redirect"); + response.setHeader("Location", "http://example.org/tests/dom/workers/test/serviceworkers/fetch/requesturl/secret.html", false); +} diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/redirector.html b/dom/workers/test/serviceworkers/fetch/requesturl/redirector.html new file mode 100644 index 0000000000..73bf4af49c --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/requesturl/redirector.html @@ -0,0 +1,2 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/register.html b/dom/workers/test/serviceworkers/fetch/requesturl/register.html new file mode 100644 index 0000000000..19a2e022c2 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/requesturl/register.html @@ -0,0 +1,14 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/requesturl_test.js b/dom/workers/test/serviceworkers/fetch/requesturl/requesturl_test.js new file mode 100644 index 0000000000..c8be3daf43 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/requesturl/requesturl_test.js @@ -0,0 +1,17 @@ +addEventListener("fetch", event => { + var url = event.request.url; + var badURL = url.indexOf("secret.html") > -1; + event.respondWith( + new Promise(resolve => { + clients.matchAll().then(clients => { + for (var client of clients) { + if (client.url.indexOf("index.html") > -1) { + client.postMessage({status: "ok", result: !badURL, message: "Should not find a bad URL (" + url + ")"}); + break; + } + } + resolve(fetch(event.request)); + }); + }) + ); +}); diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/secret.html b/dom/workers/test/serviceworkers/fetch/requesturl/secret.html new file mode 100644 index 0000000000..694c336355 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/requesturl/secret.html @@ -0,0 +1,5 @@ + +secret stuff + diff --git a/dom/workers/test/serviceworkers/fetch/requesturl/unregister.html b/dom/workers/test/serviceworkers/fetch/requesturl/unregister.html new file mode 100644 index 0000000000..1f13508fa7 --- /dev/null +++ b/dom/workers/test/serviceworkers/fetch/requesturl/unregister.html @@ -0,0 +1,12 @@ + + diff --git a/dom/workers/test/serviceworkers/fetch_event_worker.js b/dom/workers/test/serviceworkers/fetch_event_worker.js index d4dfb728bf..2805fb2b64 100644 --- a/dom/workers/test/serviceworkers/fetch_event_worker.js +++ b/dom/workers/test/serviceworkers/fetch_event_worker.js @@ -179,4 +179,20 @@ onfetch = function(ev) { return new Response(body + body); })); } -} + + else if (ev.request.url.includes('something.txt')) { + ev.respondWith(Response.redirect('fetch/somethingelse.txt')); + } + + else if (ev.request.url.includes('somethingelse.txt')) { + ev.respondWith(new Response('something else response body', {})); + } + + else if (ev.request.url.includes('redirect_serviceworker.sjs')) { + // The redirect_serviceworker.sjs server-side JavaScript file redirects to + // 'http://mochi.test:8888/tests/dom/workers/test/serviceworkers/worker.js' + // The redirected fetch should not go through the SW since the original + // fetch was initiated from a SW. + ev.respondWith(fetch('redirect_serviceworker.sjs')); + } +}; diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini index d250cbc51c..b840b96bf5 100644 --- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -46,6 +46,26 @@ support-files = fetch/https/clonedresponse/register.html fetch/https/clonedresponse/unregister.html fetch/https/clonedresponse/https_test.js + fetch/origin/index.sjs + fetch/origin/index-to-https.sjs + fetch/origin/realindex.html + fetch/origin/realindex.html^headers^ + fetch/origin/register.html + fetch/origin/unregister.html + fetch/origin/origin_test.js + fetch/origin/https/index-https.sjs + fetch/origin/https/realindex.html + fetch/origin/https/realindex.html^headers^ + fetch/origin/https/register.html + fetch/origin/https/unregister.html + fetch/origin/https/origin_test.js + fetch/requesturl/index.html + fetch/requesturl/redirect.sjs + fetch/requesturl/redirector.html + fetch/requesturl/register.html + fetch/requesturl/requesturl_test.js + fetch/requesturl/secret.html + fetch/requesturl/unregister.html fetch/sandbox/index.html fetch/sandbox/intercepted_index.html fetch/sandbox/register.html @@ -150,3 +170,10 @@ support-files = [test_force_refresh.html] [test_skip_waiting.html] [test_strict_mode_error.html] +[test_cross_origin_url_after_redirect.html] +[test_origin_after_redirect.html] +[test_origin_after_redirect_cached.html] +[test_origin_after_redirect_to_https.html] +[test_origin_after_redirect_to_https_cached.html] +[test_https_origin_after_redirect.html] +[test_https_origin_after_redirect_cached.html] diff --git a/dom/workers/test/serviceworkers/test_app_protocol.html b/dom/workers/test/serviceworkers/test_app_protocol.html index daa3af05e8..4a50a4cb7c 100644 --- a/dom/workers/test/serviceworkers/test_app_protocol.html +++ b/dom/workers/test/serviceworkers/test_app_protocol.html @@ -22,14 +22,14 @@ const appManifestURL = let gApp; function setup() { - info('Setting up'); return new Promise((resolve, reject) => { SpecialPowers.setAllAppsLaunchable(true); SpecialPowers.pushPrefEnv({'set': [ ['dom.mozBrowserFramesEnabled', true], ['dom.serviceWorkers.exemptFromPerDomainMax', true], ['dom.serviceWorkers.enabled', true], - ['dom.serviceWorkers.testing.enabled', true] + ['dom.serviceWorkers.testing.enabled', true], + ['dom.caches.enabled', true], ]}, () => { SpecialPowers.pushPermissions([ { 'type': 'webapps-manage', 'allow': 1, 'context': document }, @@ -75,9 +75,15 @@ function launchApp() { iframe.setAttribute('mozapp', gApp.manifestURL); iframe.addEventListener('mozbrowsershowmodalprompt', function listener(e) { let message = e.detail.message; - if (/READY/.exec(message)) { + if (/OK/.exec(message)) { + ok(true, "Message from app: " + message); + } else if (/KO/.exec(message)) { + ok(false, "Message from app: " + message); + } else if (/READY/.exec(message)) { ok(true, "Message from app: " + message); resolve(); + } else { + ok(false, "Unexpected message received: " + message); } }, false); let domParent = document.getElementById('container'); @@ -88,31 +94,42 @@ function launchApp() { } function loadControlled() { - info("reloading"); return new Promise((resolve, reject) => { let iframe = document.createElement('iframe'); iframe.setAttribute('mozbrowser', 'true'); iframe.setAttribute('mozapp', gApp.manifestURL); iframe.addEventListener('mozbrowsershowmodalprompt', function listener(e) { let message = e.detail.message; - info(message); if (/OK/.exec(message)) { ok(true, "Message from app: " + message); } else if (/KO/.exec(message)) { ok(false, "Message from app: " + message); + } else if (/HTTPSIFRAMELOADED/.exec(message)) { + let doc = SpecialPowers.wrap(iframe).contentDocument; + let innerDoc = SpecialPowers.wrap(doc.getElementById("redirected-https").contentDocument); + let innerLocation = innerDoc.defaultView.location; + is(innerDoc.domain, "example.org", "Correct domain expected (https)"); + is(innerLocation.origin, "https://example.org", "Correct origin expected (https)"); + } else if (/IFRAMELOADED/.exec(message)) { + let doc = SpecialPowers.wrap(iframe).contentDocument; + let innerDoc = SpecialPowers.wrap(doc.getElementById("redirected").contentDocument); + let innerLocation = innerDoc.defaultView.location; + is(innerDoc.domain, "example.org", "Correct domain expected"); + is(innerLocation.origin, "http://example.org", "Correct origin expected"); } else if (/DONE/.exec(message)) { ok(true, "Messaging from app complete"); iframe.removeEventListener('mozbrowsershowmodalprompt', listener); let domParent = document.getElementById('container'); domParent.removeChild(iframe); resolve(); + } else { + ok(false, "Unexpected message received: " + message); } }, false); let domParent = document.getElementById('container'); domParent.appendChild(iframe); SpecialPowers.wrap(iframe.contentWindow).location = gApp.origin + '/controlled.html'; - info("reloaded"); }); } diff --git a/dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html b/dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html new file mode 100644 index 0000000000..e56bb84ca0 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html @@ -0,0 +1,50 @@ + + + + + Test access to a cross origin Request.url property from a service worker for a redirected intercepted iframe + + + + +

+ +

+
+
+
+
diff --git a/dom/workers/test/serviceworkers/test_https_origin_after_redirect.html b/dom/workers/test/serviceworkers/test_https_origin_after_redirect.html
new file mode 100644
index 0000000000..8f6b26809f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_https_origin_after_redirect.html
@@ -0,0 +1,56 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
diff --git a/dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.html b/dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.html
new file mode 100644
index 0000000000..9493d9dd85
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_https_origin_after_redirect_cached.html
@@ -0,0 +1,56 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
diff --git a/dom/workers/test/serviceworkers/test_origin_after_redirect.html b/dom/workers/test/serviceworkers/test_origin_after_redirect.html
new file mode 100644
index 0000000000..42f8b35caa
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect.html
@@ -0,0 +1,57 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
diff --git a/dom/workers/test/serviceworkers/test_origin_after_redirect_cached.html b/dom/workers/test/serviceworkers/test_origin_after_redirect_cached.html
new file mode 100644
index 0000000000..2a19e20dec
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_cached.html
@@ -0,0 +1,57 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
diff --git a/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.html b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.html
new file mode 100644
index 0000000000..dcac11aea1
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https.html
@@ -0,0 +1,57 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
diff --git a/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.html b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.html
new file mode 100644
index 0000000000..b70d1704e8
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_origin_after_redirect_to_https_cached.html
@@ -0,0 +1,57 @@
+
+
+
+
+  Test the origin of a redirected response from a service worker
+  
+  
+
+
+

+ +

+
+
+
+
diff --git a/js/public/HeapAPI.h b/js/public/HeapAPI.h
index 5aa41dcfc2..1c1530845b 100644
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -9,7 +9,9 @@
 
 #include 
 
-#include "js/TracingAPI.h"
+#include "jspubtd.h"
+
+#include "js/TraceKind.h"
 #include "js/Utility.h"
 
 /* These values are private to the JS engine. */
@@ -165,6 +167,7 @@ class JS_FRIEND_API(GCCellPtr)
     explicit GCCellPtr(JSFunction* fun) : ptr(checkedCast(fun, JS::TraceKind::Object)) { }
     explicit GCCellPtr(JSString* str) : ptr(checkedCast(str, JS::TraceKind::String)) { }
     explicit GCCellPtr(JSFlatString* str) : ptr(checkedCast(str, JS::TraceKind::String)) { }
+    explicit GCCellPtr(JS::Symbol* sym) : ptr(checkedCast(sym, JS::TraceKind::Symbol)) { }
     explicit GCCellPtr(JSScript* script) : ptr(checkedCast(script, JS::TraceKind::Script)) { }
     explicit GCCellPtr(const Value& v);
 
diff --git a/js/public/TraceKind.h b/js/public/TraceKind.h
new file mode 100644
index 0000000000..272f577399
--- /dev/null
+++ b/js/public/TraceKind.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 js_TraceKind_h
+#define js_TraceKind_h
+
+namespace JS {
+
+// When tracing a thing, the GC needs to know about the layout of the object it
+// is looking at. There are a fixed number of different layouts that the GC
+// knows about. The "trace kind" is a static map which tells which layout a GC
+// thing has.
+//
+// Although this map is public, the details are completely hidden. Not all of
+// the matching C++ types are exposed, and those that are, are opaque.
+//
+// See Value::gcKind() and JSTraceCallback in Tracer.h for more details.
+enum class TraceKind
+{
+    // These trace kinds have a publicly exposed, although opaque, C++ type.
+    // Note: The order here is determined by our Value packing. Other users
+    //       should sort alphabetically, for consistency.
+    Object = 0x00,
+    String = 0x01,
+    Symbol = 0x02,
+    Script = 0x03,
+
+    // Shape details are exposed through JS_TraceShapeCycleCollectorChildren.
+    Shape = 0x04,
+
+    // ObjectGroup details are exposed through JS_TraceObjectGroupCycleCollectorChildren.
+    ObjectGroup = 0x05,
+
+    // The kind associated with a nullptr.
+    Null = 0x06,
+
+    // The following kinds do not have an exposed C++ idiom.
+    BaseShape = 0x0F,
+    JitCode = 0x1F,
+    LazyScript = 0x2F
+};
+const static uintptr_t OutOfLineTraceKindMask = 0x07;
+static_assert(uintptr_t(JS::TraceKind::BaseShape) & OutOfLineTraceKindMask, "mask bits are set");
+static_assert(uintptr_t(JS::TraceKind::JitCode) & OutOfLineTraceKindMask, "mask bits are set");
+static_assert(uintptr_t(JS::TraceKind::LazyScript) & OutOfLineTraceKindMask, "mask bits are set");
+
+} // namespace JS
+
+#endif // js_TraceKind_h
diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h
index a88c41ca38..053132c9df 100644
--- a/js/public/TracingAPI.h
+++ b/js/public/TracingAPI.h
@@ -8,9 +8,10 @@
 #define js_TracingAPI_h
 
 #include "jsalloc.h"
-#include "jspubtd.h"
 
 #include "js/HashTable.h"
+#include "js/HeapAPI.h"
+#include "js/TraceKind.h"
 
 class JS_PUBLIC_API(JSTracer);
 
@@ -19,66 +20,20 @@ class JS_PUBLIC_API(CallbackTracer);
 template  class Heap;
 template  class TenuredHeap;
 
-// When tracing a thing, the GC needs to know about the layout of the object it
-// is looking at. There are a fixed number of different layouts that the GC
-// knows about. The "trace kind" is a static map which tells which layout a GC
-// thing has.
-//
-// Although this map is public, the details are completely hidden. Not all of
-// the matching C++ types are exposed, and those that are, are opaque.
-//
-// See Value::gcKind() and JSTraceCallback in Tracer.h for more details.
-enum class TraceKind
-{
-    // These trace kinds have a publicly exposed, although opaque, C++ type.
-    // Note: The order here is determined by our Value packing. Other users
-    //       should sort alphabetically, for consistency.
-    Object = 0x00,
-    String = 0x01,
-    Symbol = 0x02,
-    Script = 0x03,
-
-    // Shape details are exposed through JS_TraceShapeCycleCollectorChildren.
-    Shape = 0x04,
-
-    // ObjectGroup details are exposed through JS_TraceObjectGroupCycleCollectorChildren.
-    ObjectGroup = 0x05,
-
-    // The kind associated with a nullptr.
-    Null = 0x06,
-
-    // The following kinds do not have an exposed C++ idiom.
-    BaseShape = 0x0F,
-    JitCode = 0x1F,
-    LazyScript = 0x2F
-};
-const static uintptr_t OutOfLineTraceKindMask = 0x07;
-static_assert(uintptr_t(JS::TraceKind::BaseShape) & OutOfLineTraceKindMask, "mask bits are set");
-static_assert(uintptr_t(JS::TraceKind::JitCode) & OutOfLineTraceKindMask, "mask bits are set");
-static_assert(uintptr_t(JS::TraceKind::LazyScript) & OutOfLineTraceKindMask, "mask bits are set");
-
 // Returns a static string equivalent of |kind|.
 JS_FRIEND_API(const char*)
 GCTraceKindToAscii(JS::TraceKind kind);
 
 } // namespace JS
 
-// Tracer callback, called for each traceable thing directly referenced by a
-// particular object or runtime structure. It is the callback responsibility
-// to ensure the traversal of the full object graph via calling eventually
-// JS_TraceChildren on the passed thing. In this case the callback must be
-// prepared to deal with cycles in the traversal graph.
-//
-// kind argument is one of JS::TraceKind::Object, JS::TraceKind::String or a
-// tag denoting internal implementation-specific traversal kind. In the latter
-// case the only operations on thing that the callback can do is to call
-// JS_TraceChildren or JS_GetTraceThingInfo.
-//
-// If eagerlyTraceWeakMaps is true, when we trace a WeakMap visit all
-// of its mappings. This should be used in cases where the tracer
-// wants to use the existing liveness of entries.
-typedef void
-(* JSTraceCallback)(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind);
+namespace js {
+class BaseShape;
+class LazyScript;
+class ObjectGroup;
+namespace jit {
+class JitCode;
+} // namespace jit
+} // namespace js
 
 enum WeakMapTraceKind {
     DoNotTraceWeakMaps = 0,
@@ -132,8 +87,34 @@ class JS_PUBLIC_API(CallbackTracer) : public JSTracer
         contextName_(nullptr), contextIndex_(InvalidIndex), contextFunctor_(nullptr)
     {}
 
-    // Override this method to receive notification when an edge is visited.
-    virtual void trace(void** thing, JS::TraceKind kind) = 0;
+    // Override these methods to receive notification when an edge is visited
+    // with the type contained in the callback. The default implementation
+    // dispatches to the fully-generic onChild implementation, so for cases that
+    // do not care about boxing overhead and do not need the actual edges,
+    // just override the generic onChild.
+    virtual void onObjectEdge(JSObject** objp) { onChild(JS::GCCellPtr(*objp)); }
+    virtual void onStringEdge(JSString** strp) { onChild(JS::GCCellPtr(*strp)); }
+    virtual void onSymbolEdge(JS::Symbol** symp) { onChild(JS::GCCellPtr(*symp)); }
+    virtual void onScriptEdge(JSScript** scriptp) { onChild(JS::GCCellPtr(*scriptp)); }
+    virtual void onShapeEdge(js::Shape** shapep) {
+        onChild(JS::GCCellPtr(*shapep, JS::TraceKind::Shape));
+    }
+    virtual void onObjectGroupEdge(js::ObjectGroup** groupp) {
+        onChild(JS::GCCellPtr(*groupp, JS::TraceKind::ObjectGroup));
+    }
+    virtual void onBaseShapeEdge(js::BaseShape** basep) {
+        onChild(JS::GCCellPtr(*basep, JS::TraceKind::BaseShape));
+    }
+    virtual void onJitCodeEdge(js::jit::JitCode** codep) {
+        onChild(JS::GCCellPtr(*codep, JS::TraceKind::JitCode));
+    }
+    virtual void onLazyScriptEdge(js::LazyScript** lazyp) {
+        onChild(JS::GCCellPtr(*lazyp, JS::TraceKind::LazyScript));
+    }
+
+    // Override this method to receive notification when a node in the GC
+    // heap graph is visited.
+    virtual void onChild(const JS::GCCellPtr& thing) = 0;
 
     // Access to the tracing context:
     // When tracing with a JS::CallbackTracer, we invoke the callback with the
@@ -185,6 +166,21 @@ class JS_PUBLIC_API(CallbackTracer) : public JSTracer
     virtual TracerKind getTracerKind() const { return TracerKind::DoNotCare; }
 #endif
 
+    // In C++, overriding a method hides all methods in the base class with
+    // that name, not just methods with that signature. Thus, the typed edge
+    // methods have to have distinct names to allow us to override them
+    // individually, which is freqently useful if, for example, we only want to
+    // process only one type of edge.
+    void dispatchToOnEdge(JSObject** objp) { onObjectEdge(objp); }
+    void dispatchToOnEdge(JSString** strp) { onStringEdge(strp); }
+    void dispatchToOnEdge(JS::Symbol** symp) { onSymbolEdge(symp); }
+    void dispatchToOnEdge(JSScript** scriptp) { onScriptEdge(scriptp); }
+    void dispatchToOnEdge(js::Shape** shapep) { onShapeEdge(shapep); }
+    void dispatchToOnEdge(js::ObjectGroup** groupp) { onObjectGroupEdge(groupp); }
+    void dispatchToOnEdge(js::BaseShape** basep) { onBaseShapeEdge(basep); }
+    void dispatchToOnEdge(js::jit::JitCode** codep) { onJitCodeEdge(codep); }
+    void dispatchToOnEdge(js::LazyScript** lazyp) { onLazyScriptEdge(lazyp); }
+
   private:
     friend class AutoTracingName;
     const char* contextName_;
diff --git a/js/public/UbiNode.h b/js/public/UbiNode.h
index 872ebd12de..63925ec06f 100644
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -331,7 +331,7 @@ class JS_FRIEND_API(Node) {
     // JS::ubi::Node are both essentially tagged references to other sorts of
     // objects, so letting conversions happen automatically is appropriate.
     MOZ_IMPLICIT Node(JS::HandleValue value);
-    Node(JS::TraceKind kind, void* ptr);
+    explicit Node(const JS::GCCellPtr& thing);
 
     // copy construction and copy assignment just use memcpy, since we know
     // instances contain nothing but a vtable pointer and a data pointer.
diff --git a/js/src/builtin/Reflect.cpp b/js/src/builtin/Reflect.cpp
index 9d3beab189..7f5f511ec9 100644
--- a/js/src/builtin/Reflect.cpp
+++ b/js/src/builtin/Reflect.cpp
@@ -24,8 +24,9 @@ using namespace js;
  * The elementTypes argument is not supported. The result list is
  * pushed to *args.
  */
+template 
 static bool
-InitArgsFromArrayLike(JSContext* cx, HandleValue v, InvokeArgs* args, bool construct)
+InitArgsFromArrayLike(JSContext* cx, HandleValue v, InvokeArgs* args)
 {
     // Step 3.
     RootedObject obj(cx, NonNullObject(cx, v));
@@ -42,7 +43,7 @@ InitArgsFromArrayLike(JSContext* cx, HandleValue v, InvokeArgs* args, bool const
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TOO_MANY_FUN_APPLY_ARGS);
         return false;
     }
-    if (!args->init(len, construct))
+    if (!args->init(len))
         return false;
 
     // Steps 6-8.
@@ -71,7 +72,7 @@ Reflect_apply(JSContext* cx, unsigned argc, Value* vp)
     // Steps 2-3.
     FastInvokeGuard fig(cx, args.get(0));
     InvokeArgs& invokeArgs = fig.args();
-    if (!InitArgsFromArrayLike(cx, args.get(2), &invokeArgs, false))
+    if (!InitArgsFromArrayLike(cx, args.get(2), &invokeArgs))
         return false;
     invokeArgs.setCallee(args.get(0));
     invokeArgs.setThis(args.get(1));
@@ -108,18 +109,12 @@ Reflect_construct(JSContext* cx, unsigned argc, Value* vp)
     }
 
     // Step 4-5.
-    InvokeArgs invokeArgs(cx);
-    if (!InitArgsFromArrayLike(cx, args.get(1), &invokeArgs, true))
+    ConstructArgs constructArgs(cx);
+    if (!InitArgsFromArrayLike(cx, args.get(1), &constructArgs))
         return false;
-    invokeArgs.setCallee(args.get(0));
-    invokeArgs.setThis(MagicValue(JS_THIS_POISON));
-    invokeArgs.newTarget().set(newTarget);
 
     // Step 6.
-    if (!InvokeConstructor(cx, invokeArgs))
-        return false;
-    args.rval().set(invokeArgs.rval());
-    return true;
+    return Construct(cx, args.get(0), constructArgs, newTarget, args.rval());
 }
 
 /* ES6 26.1.3 Reflect.defineProperty(target, propertyKey, attributes) */
diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp
index f47a4b0fba..c2c520334b 100644
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -800,8 +800,8 @@ class HasChildTracer : public JS::CallbackTracer
     RootedValue child_;
     bool found_;
 
-    void trace(void** thingp, JS::TraceKind kind) {
-        if (*thingp == child_.toGCThing())
+    void onChild(const JS::GCCellPtr& thing) override {
+        if (thing.asCell() == child_.toGCThing())
             found_ = true;
     }
 
diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h
index 7487eb1c57..3fcdbfb0a6 100644
--- a/js/src/gc/GCInternals.h
+++ b/js/src/gc/GCInternals.h
@@ -138,9 +138,15 @@ void
 CheckHashTablesAfterMovingGC(JSRuntime* rt);
 #endif
 
-struct MovingTracer : JS::CallbackTracer {
+struct MovingTracer : JS::CallbackTracer
+{
     explicit MovingTracer(JSRuntime* rt) : CallbackTracer(rt, TraceWeakMapKeysValues) {}
-    void trace(void** thingp, JS::TraceKind kind) override;
+
+    void onObjectEdge(JSObject** objp) override;
+    void onChild(const JS::GCCellPtr& thing) override {
+        MOZ_ASSERT(!RelocationOverlay::isCellForwarded(thing.asCell()));
+    }
+
 #ifdef DEBUG
     TracerKind getTracerKind() const override { return TracerKind::Moving; }
 #endif
diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp
index 87ffae37c9..4c8bfa54c3 100644
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2303,9 +2303,9 @@ TypeSet::MarkTypeUnbarriered(JSTracer* trc, TypeSet::Type* v, const char* name)
 #ifdef DEBUG
 struct AssertNonGrayTracer : public JS::CallbackTracer {
     explicit AssertNonGrayTracer(JSRuntime* rt) : JS::CallbackTracer(rt) {}
-    void trace(void** thingp, JS::TraceKind kind) override {
-        DebugOnly thing(static_cast(*thingp));
-        MOZ_ASSERT_IF(thing->isTenured(), !thing->asTenured().isMarked(js::gc::GRAY));
+    void onChild(const JS::GCCellPtr& thing) override {
+        MOZ_ASSERT_IF(thing.asCell()->isTenured(),
+                      !thing.asCell()->asTenured().isMarked(js::gc::GRAY));
     }
 };
 #endif
@@ -2330,7 +2330,7 @@ struct UnmarkGrayTracer : public JS::CallbackTracer
         unmarkedAny(false)
     {}
 
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 
     /* True iff we are tracing the immediate children of a shape. */
     bool tracingShape;
@@ -2373,7 +2373,7 @@ struct UnmarkGrayTracer : public JS::CallbackTracer
  *   containers.
  */
 void
-UnmarkGrayTracer::trace(void** thingp, JS::TraceKind kind)
+UnmarkGrayTracer::onChild(const JS::GCCellPtr& thing)
 {
     int stackDummy;
     if (!JS_CHECK_STACK_SIZE(runtime()->mainThread.nativeStackLimit[StackForSystemCode],
@@ -2387,14 +2387,14 @@ UnmarkGrayTracer::trace(void** thingp, JS::TraceKind kind)
         return;
     }
 
-    Cell* cell = static_cast(*thingp);
+    Cell* cell = thing.asCell();
 
     // Cells in the nursery cannot be gray, and therefore must necessarily point
     // to only black edges.
     if (!cell->isTenured()) {
 #ifdef DEBUG
         AssertNonGrayTracer nongray(runtime());
-        TraceChildren(&nongray, cell, kind);
+        TraceChildren(&nongray, cell, thing.kind());
 #endif
         return;
     }
@@ -2411,16 +2411,16 @@ UnmarkGrayTracer::trace(void** thingp, JS::TraceKind kind)
     // The parent will later trace |tenured|. This is done to avoid increasing
     // the stack depth during shape tracing. It is safe to do because a shape
     // can only have one child that is a shape.
-    UnmarkGrayTracer childTracer(this, kind == JS::TraceKind::Shape);
+    UnmarkGrayTracer childTracer(this, thing.kind() == JS::TraceKind::Shape);
 
-    if (kind != JS::TraceKind::Shape) {
-        TraceChildren(&childTracer, &tenured, kind);
+    if (thing.kind() != JS::TraceKind::Shape) {
+        TraceChildren(&childTracer, &tenured, thing.kind());
         MOZ_ASSERT(!childTracer.previousShape);
         unmarkedAny |= childTracer.unmarkedAny;
         return;
     }
 
-    MOZ_ASSERT(kind == JS::TraceKind::Shape);
+    MOZ_ASSERT(thing.kind() == JS::TraceKind::Shape);
     Shape* shape = static_cast(&tenured);
     if (tracingShape) {
         MOZ_ASSERT(!previousShape);
diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp
index 5ba9ea70c7..e96f72622d 100644
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -484,7 +484,7 @@ class BufferGrayRootsTracer : public JS::CallbackTracer
     // Set to false if we OOM while buffering gray roots.
     bool bufferingGrayRootsFailed;
 
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 
   public:
     explicit BufferGrayRootsTracer(JSRuntime* rt)
@@ -537,24 +537,24 @@ struct SetMaybeAliveFunctor {
 };
 
 void
-BufferGrayRootsTracer::trace(void** thingp, JS::TraceKind kind)
+BufferGrayRootsTracer::onChild(const JS::GCCellPtr& thing)
 {
     MOZ_ASSERT(runtime()->isHeapBusy());
 
     if (bufferingGrayRootsFailed)
         return;
 
-    gc::TenuredCell* thing = gc::TenuredCell::fromPointer(*thingp);
+    gc::TenuredCell* tenured = gc::TenuredCell::fromPointer(thing.asCell());
 
-    Zone* zone = thing->zone();
+    Zone* zone = tenured->zone();
     if (zone->isCollecting()) {
         // See the comment on SetMaybeAliveFlag to see why we only do this for
         // objects and scripts. We rely on gray root buffering for this to work,
         // but we only need to worry about uncollected dead compartments during
         // incremental GCs (when we do gray root buffering).
-        CallTyped(SetMaybeAliveFunctor(), thing, kind);
+        CallTyped(SetMaybeAliveFunctor(), tenured, thing.kind());
 
-        if (!zone->gcGrayRoots.append(thing))
+        if (!zone->gcGrayRoots.append(tenured))
             bufferingGrayRootsFailed = true;
     }
 }
diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp
index 25bddcc27f..d62b682c4c 100644
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -47,9 +47,8 @@ T
 DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name)
 {
     CheckTracedThing(trc, *thingp);
-    JS::TraceKind kind = MapTypeToTraceKind::Type>::kind;
     JS::AutoTracingName ctx(trc, name);
-    trc->trace(reinterpret_cast(thingp), kind);
+    trc->dispatchToOnEdge(thingp);
     return *thingp;
 }
 #define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(name, type, _) \
@@ -316,21 +315,19 @@ struct ObjectGroupCycleCollectorTracer : public JS::CallbackTracer
           innerTracer(innerTracer)
     {}
 
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 
     JS::CallbackTracer* innerTracer;
     Vector seen, worklist;
 };
 
 void
-ObjectGroupCycleCollectorTracer::trace(void** thingp, JS::TraceKind kind)
+ObjectGroupCycleCollectorTracer::onChild(const JS::GCCellPtr& thing)
 {
-    JS::GCCellPtr thing(*thingp, kind);
-
     if (thing.isObject() || thing.isScript()) {
         // Invoke the inner cycle collector callback on this child. It will not
         // recurse back into TraceChildren.
-        innerTracer->trace(thingp, kind);
+        innerTracer->onChild(thing);
         return;
     }
 
diff --git a/js/src/gc/Verifier.cpp b/js/src/gc/Verifier.cpp
index 57a3cb8a69..58ab3077ed 100644
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -81,7 +81,7 @@ class js::VerifyPreTracer : public JS::CallbackTracer
 {
     JS::AutoDisableGenerationalGC noggc;
 
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 
   public:
     /* The gcNumber when the verification began. */
@@ -111,9 +111,9 @@ class js::VerifyPreTracer : public JS::CallbackTracer
  * node.
  */
 void
-VerifyPreTracer::trace(void** thingp, JS::TraceKind kind)
+VerifyPreTracer::onChild(const JS::GCCellPtr& thing)
 {
-    MOZ_ASSERT(!IsInsideNursery(*reinterpret_cast(thingp)));
+    MOZ_ASSERT(!IsInsideNursery(thing.asCell()));
 
     edgeptr += sizeof(EdgeValue);
     if (edgeptr >= term) {
@@ -124,8 +124,8 @@ VerifyPreTracer::trace(void** thingp, JS::TraceKind kind)
     VerifyNode* node = curnode;
     uint32_t i = node->count;
 
-    node->edges[i].thing = *thingp;
-    node->edges[i].kind = kind;
+    node->edges[i].thing = thing.asCell();
+    node->edges[i].kind = thing.kind();
     node->edges[i].label = contextName();
     node->count++;
 }
@@ -251,7 +251,7 @@ IsMarkedOrAllocated(TenuredCell* cell)
 struct CheckEdgeTracer : public JS::CallbackTracer {
     VerifyNode* node;
     explicit CheckEdgeTracer(JSRuntime* rt) : JS::CallbackTracer(rt), node(nullptr) {}
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 };
 
 static const uint32_t MAX_VERIFIER_EDGES = 1000;
@@ -264,15 +264,15 @@ static const uint32_t MAX_VERIFIER_EDGES = 1000;
  * been modified) must point to marked objects.
  */
 void
-CheckEdgeTracer::trace(void** thingp, JS::TraceKind kind)
+CheckEdgeTracer::onChild(const JS::GCCellPtr& thing)
 {
     /* Avoid n^2 behavior. */
     if (node->count > MAX_VERIFIER_EDGES)
         return;
 
     for (uint32_t i = 0; i < node->count; i++) {
-        if (node->edges[i].thing == *thingp) {
-            MOZ_ASSERT(node->edges[i].kind == kind);
+        if (node->edges[i].thing == thing.asCell()) {
+            MOZ_ASSERT(node->edges[i].kind == thing.kind());
             node->edges[i].thing = nullptr;
             return;
         }
diff --git a/js/src/jit-test/tests/basic/testBug593559.js b/js/src/jit-test/tests/basic/testBug593559.js
index 15c29d7799..59994f362a 100644
--- a/js/src/jit-test/tests/basic/testBug593559.js
+++ b/js/src/jit-test/tests/basic/testBug593559.js
@@ -3,6 +3,7 @@ var t = gen.throw;
 try {
     new t;
 } catch (e) {
-    actual = "" + e;
+    actual = e;
 }
-assertEq(actual, "TypeError: t is not a constructor");
+assertEq(actual.name, "TypeError");
+assertEq(/is not a constructor/.test(actual.message), true);
diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp
index c9b3398af4..fc86757f94 100644
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -8882,7 +8882,25 @@ DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, uint
     }
 
     if (op == JSOP_NEW) {
-        if (!InvokeConstructor(cx, callee, argc, args, true, res))
+        // Callees from the stack could have any old non-constructor callee.
+        if (!IsConstructor(callee)) {
+            ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, callee, nullptr);
+            return false;
+        }
+
+        ConstructArgs cargs(cx);
+        if (!cargs.init(argc))
+            return false;
+
+        for (uint32_t i = 0; i < argc; i++)
+            cargs[i].set(args[i]);
+
+        RootedValue newTarget(cx, args[argc]);
+        MOZ_ASSERT(IsConstructor(newTarget),
+                   "either callee == newTarget, or the initial |new| checked "
+                   "that IsConstructor(newTarget)");
+
+        if (!Construct(cx, callee, cargs, newTarget, res))
             return false;
     } else if ((op == JSOP_EVAL || op == JSOP_STRICTEVAL) &&
                frame->scopeChain()->global().valueIsEval(callee))
diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp
index ba524d1519..3aeb0272a9 100644
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -63,16 +63,37 @@ InvokeFunction(JSContext* cx, HandleObject obj, bool constructing, uint32_t argc
     AutoArrayRooter argvRoot(cx, argc + 1 + constructing, argv);
 
     // Data in the argument vector is arranged for a JIT -> JIT call.
-    Value thisv = argv[0];
+    RootedValue thisv(cx, argv[0]);
     Value* argvWithoutThis = argv + 1;
 
-    // For constructing functions, |this| is constructed at caller side and we can just call Invoke.
-    // When creating this failed / is impossible at caller site, i.e. MagicValue(JS_IS_CONSTRUCTING),
-    // we use InvokeConstructor that creates it at the callee side.
-    if (thisv.isMagic(JS_IS_CONSTRUCTING))
-        return InvokeConstructor(cx, ObjectValue(*obj), argc, argvWithoutThis, true, rval);
+    RootedValue fval(cx, ObjectValue(*obj));
+    if (constructing) {
+        if (!IsConstructor(fval)) {
+            ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, fval, nullptr);
+            return false;
+        }
 
-    return Invoke(cx, thisv, ObjectValue(*obj), argc, argvWithoutThis, rval);
+        ConstructArgs cargs(cx);
+        if (!cargs.init(argc))
+            return false;
+
+        for (uint32_t i = 0; i < argc; i++)
+            cargs[i].set(argvWithoutThis[i]);
+
+        RootedValue newTarget(cx, argvWithoutThis[argc]);
+
+        // If |this| hasn't been created, we can use normal construction code.
+        if (thisv.isMagic(JS_IS_CONSTRUCTING))
+            return Construct(cx, fval, cargs, newTarget, rval);
+
+        // Otherwise the default |this| has already been created.  We could
+        // almost perform a *call* at this point, but we'd break |new.target|
+        // in the function.  So in this one weird case we call a one-off
+        // construction path that *won't* set |this| to JS_IS_CONSTRUCTING.
+        return InternalConstructWithProvidedThis(cx, fval, thisv, cargs, newTarget, rval);
+    }
+
+    return Invoke(cx, thisv, fval, argc, argvWithoutThis, rval);
 }
 
 bool
diff --git a/js/src/jsapi-tests/testGCMarking.cpp b/js/src/jsapi-tests/testGCMarking.cpp
index 980ee09391..fd3eea1449 100644
--- a/js/src/jsapi-tests/testGCMarking.cpp
+++ b/js/src/jsapi-tests/testGCMarking.cpp
@@ -8,16 +8,16 @@
 #include "jsapi-tests/tests.h"
 
 class CCWTestTracer : public JS::CallbackTracer {
-    void trace(void** thingp, JS::TraceKind kind) {
+    void onChild(const JS::GCCellPtr& thing) override {
         numberOfThingsTraced++;
 
-        printf("*thingp         = %p\n", *thingp);
+        printf("*thingp         = %p\n", thing.asCell());
         printf("*expectedThingp = %p\n", *expectedThingp);
 
-        printf("kind         = %d\n", static_cast(kind));
+        printf("kind         = %d\n", static_cast(thing.kind()));
         printf("expectedKind = %d\n", static_cast(expectedKind));
 
-        if (*thingp != *expectedThingp || kind != expectedKind)
+        if (thing.asCell() != *expectedThingp || thing.kind() != expectedKind)
             okay = false;
     }
 
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index 386bca008e..f43c35cf8b 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -318,16 +318,21 @@ IterPerformanceStats(JSContext* cx,
     }
 
     JSRuntime* rt = JS_GetRuntime(cx);
-    for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) {
+
+    // First report the shared groups
+    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         JSCompartment* compartment = c.get();
-        if (!compartment->performanceMonitoring.isLinked()) {
+        if (!c->principals()) {
+            // Compartments without principals could show up here, but
+            // reporting them doesn't really make sense.
+            continue;
+        }
+        if (!c->performanceMonitoring.hasSharedGroup()) {
             // Don't report compartments that do not even have a PerformanceGroup.
             continue;
         }
-
         js::AutoCompartment autoCompartment(cx, compartment);
-        PerformanceGroup* group = compartment->performanceMonitoring.getGroup(cx);
-
+        PerformanceGroup* group = compartment->performanceMonitoring.getSharedGroup(cx);
         if (group->data.ticks == 0) {
             // Don't report compartments that have never been used.
             continue;
@@ -339,7 +344,9 @@ IterPerformanceStats(JSContext* cx,
             continue;
         }
 
-        if (!(*walker)(cx, group->data, group->uid, closure)) {
+        if (!(*walker)(cx,
+                       group->data, group->uid, nullptr,
+                       closure)) {
             // Issue in callback
             return false;
         }
@@ -348,7 +355,36 @@ IterPerformanceStats(JSContext* cx,
             return false;
         }
     }
-    *processStats = rt->stopwatch.performance;
+
+    // Then report the own groups
+    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
+        JSCompartment* compartment = c.get();
+        if (!c->principals()) {
+            // Compartments without principals could show up here, but
+            // reporting them doesn't really make sense.
+            continue;
+        }
+        if (!c->performanceMonitoring.hasOwnGroup()) {
+            // Don't report compartments that do not even have a PerformanceGroup.
+            continue;
+        }
+        js::AutoCompartment autoCompartment(cx, compartment);
+        mozilla::RefPtr ownGroup = compartment->performanceMonitoring.getOwnGroup();
+        if (ownGroup->data.ticks == 0) {
+            // Don't report compartments that have never been used.
+            continue;
+        }
+        mozilla::RefPtr sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx);
+        if (!(*walker)(cx,
+                       ownGroup->data, ownGroup->uid, &sharedGroup->uid,
+                       closure)) {
+            // Issue in callback
+            return false;
+        }
+    }
+
+    // Finally, report the process stats
+    *processStats = rt->stopwatch.performance.getOwnGroup()->data;
     return true;
 }
 
@@ -4604,7 +4640,16 @@ JS::Construct(JSContext* cx, HandleValue fval, const JS::HandleValueArray& args,
     assertSameCompartment(cx, fval, args);
     AutoLastFrameCheck lfc(cx);
 
-    return InvokeConstructor(cx, fval, args.length(), args.begin(), false, rval);
+    if (!IsConstructor(fval)) {
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, fval, nullptr);
+        return false;
+    }
+
+    ConstructArgs cargs(cx);
+    if (!FillArgumentsFromArraylike(cx, cargs, args))
+        return false;
+
+    return js::Construct(cx, fval, cargs, fval, rval);
 }
 
 JS_PUBLIC_API(bool)
@@ -4616,26 +4661,22 @@ JS::Construct(JSContext* cx, HandleValue fval, HandleObject newTarget, const JS:
     assertSameCompartment(cx, fval, newTarget, args);
     AutoLastFrameCheck lfc(cx);
 
-    // Reflect.construct ensures that the supplied new.target value is a
-    // constructor. Frankly, this makes good sense, so we reproduce the check.
-    if (!newTarget->isConstructor()) {
-        RootedValue val(cx, ObjectValue(*newTarget));
-        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, val, nullptr);
+    if (!IsConstructor(fval)) {
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, fval, nullptr);
         return false;
     }
 
-    // This is a littlesilly, but we need to convert from what's useful for our
-    // consumers to what we can actually handle internally.
-    AutoValueVector argv(cx);
-    unsigned argc = args.length();
-    if (!argv.reserve(argc + 1))
+    RootedValue newTargetVal(cx, ObjectValue(*newTarget));
+    if (!IsConstructor(newTargetVal)) {
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, newTargetVal, nullptr);
         return false;
-    for (unsigned i = 0; i < argc; i++) {
-        argv.infallibleAppend(args[i]);
     }
-    argv.infallibleAppend(ObjectValue(*newTarget));
 
-    return InvokeConstructor(cx, fval, argc, argv.begin(), true, rval);
+    ConstructArgs cargs(cx);
+    if (!FillArgumentsFromArraylike(cx, cargs, args))
+        return false;
+
+    return js::Construct(cx, fval, cargs, newTargetVal, rval);
 }
 
 static JSObject*
@@ -4645,36 +4686,21 @@ JS_NewHelper(JSContext* cx, HandleObject ctor, const JS::HandleValueArray& input
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, ctor, inputArgs);
 
-    // This is not a simple variation of JS_CallFunctionValue because JSOP_NEW
-    // is not a simple variation of JSOP_CALL. We have to determine what class
-    // of object to create, create it, and clamp the return value to an object,
-    // among other details. InvokeConstructor does the hard work.
-    InvokeArgs args(cx);
-    if (!args.init(inputArgs.length(), true))
-        return nullptr;
-
-    args.setCallee(ObjectValue(*ctor));
-    args.setThis(NullValue());
-    PodCopy(args.array(), inputArgs.begin(), inputArgs.length());
-    args.newTarget().setObject(*ctor);
-
-    if (!InvokeConstructor(cx, args))
-        return nullptr;
-
-    if (!args.rval().isObject()) {
-        /*
-         * Although constructors may return primitives (via proxies), this
-         * API is asking for an object, so we report an error.
-         */
-        JSAutoByteString bytes;
-        if (ValueToPrintable(cx, args.rval(), &bytes)) {
-            JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_NEW_RESULT,
-                                 bytes.ptr());
-        }
+    RootedValue ctorVal(cx, ObjectValue(*ctor));
+    if (!IsConstructor(ctorVal)) {
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, ctorVal, nullptr);
         return nullptr;
     }
 
-    return &args.rval().toObject();
+    ConstructArgs args(cx);
+    if (!FillArgumentsFromArraylike(cx, args, inputArgs))
+        return nullptr;
+
+    RootedValue rval(cx);
+    if (!js::Construct(cx, ctorVal, args, ctorVal, &rval))
+        return nullptr;
+
+    return &rval.toObject();
 }
 
 JS_PUBLIC_API(JSObject*)
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index 309dcfb24b..46bd68cd84 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -13,6 +13,7 @@
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Range.h"
 #include "mozilla/RangedPtr.h"
+#include "mozilla/RefPtr.h"
 
 #include 
 #include 
@@ -5428,7 +5429,7 @@ BuildStackString(JSContext *cx, HandleObject stack, MutableHandleString stringp)
 
 namespace js {
 
-struct AutoStopwatch;
+class AutoStopwatch;
 
 // Container for performance data
 // All values are monotonic.
@@ -5522,15 +5523,22 @@ struct PerformanceGroup {
         stopwatch_ = nullptr;
     }
 
-    explicit PerformanceGroup(JSContext* cx, void* key);
-    ~PerformanceGroup()
-    {
-        MOZ_ASSERT(refCount_ == 0);
-    }
-  private:
+    // Refcounting. For use with mozilla::RefPtr.
+    void AddRef();
+    void Release();
+
+    // Construct a PerformanceGroup for a single compartment.
+    explicit PerformanceGroup(JSRuntime* rt);
+
+    // Construct a PerformanceGroup for a group of compartments.
+    explicit PerformanceGroup(JSContext* rt, void* key);
+
+private:
     PerformanceGroup& operator=(const PerformanceGroup&) = delete;
     PerformanceGroup(const PerformanceGroup&) = delete;
 
+    JSRuntime* runtime_;
+
     // The stopwatch currently monitoring the group,
     // or `nullptr` if none. Used ony for comparison.
     const AutoStopwatch* stopwatch_;
@@ -5542,38 +5550,41 @@ struct PerformanceGroup {
     // The hash key for this PerformanceGroup.
     void* const key_;
 
-    // Increment/decrement the refcounter, return the updated value.
-    uint64_t incRefCount() {
-        MOZ_ASSERT(refCount_ + 1 > 0);
-        return ++refCount_;
-    }
-    uint64_t decRefCount() {
-        MOZ_ASSERT(refCount_ > 0);
-        return --refCount_;
-    }
-    friend struct PerformanceGroupHolder;
-
-private:
-    // A reference counter. Maintained by PerformanceGroupHolder.
+    // A reference counter.
     uint64_t refCount_;
+
+
+    // `true` if this PerformanceGroup may be shared by several
+    // compartments, `false` if it is dedicated to a single
+    // compartment.
+    const bool isSharedGroup_;
 };
 
 //
-// Indirection towards a PerformanceGroup.
-// This structure handles reference counting for instances of PerformanceGroup.
+// Each PerformanceGroupHolder handles:
+// - a reference-counted indirection towards a PerformanceGroup shared
+//   by several compartments
+// - a owned PerformanceGroup representing the performance of a single
+//   compartment.
 //
 struct PerformanceGroupHolder {
-    // Get the group.
+    // Get the shared group.
     // On first call, this causes a single Hashtable lookup.
     // Successive calls do not require further lookups.
-    js::PerformanceGroup* getGroup(JSContext*);
+    js::PerformanceGroup* getSharedGroup(JSContext*);
 
-    // `true` if the this holder is currently associated to a
+    // Get the own group.
+    js::PerformanceGroup* getOwnGroup();
+
+    // `true` if the this holder is currently associated to a shared
     // PerformanceGroup, `false` otherwise. Use this method to avoid
     // instantiating a PerformanceGroup if you only need to get
     // available performance data.
-    inline bool isLinked() const {
-        return group_ != nullptr;
+    inline bool hasSharedGroup() const {
+        return sharedGroup_ != nullptr;
+    }
+    inline bool hasOwnGroup() const {
+        return ownGroup_ != nullptr;
     }
 
     // Remove the link to the PerformanceGroup. This method is designed
@@ -5583,10 +5594,10 @@ struct PerformanceGroupHolder {
 
     explicit PerformanceGroupHolder(JSRuntime* runtime)
       : runtime_(runtime)
-      , group_(nullptr)
     {   }
     ~PerformanceGroupHolder();
-private:
+
+  private:
     // Return the key representing this PerformanceGroup in
     // Runtime::Stopwatch.
     // Do not deallocate the key.
@@ -5594,10 +5605,11 @@ private:
 
     JSRuntime *runtime_;
 
-    // The PerformanceGroup held by this object.
-    // Initially set to `nullptr` until the first cal to `getGroup`.
+    // The PerformanceGroups held by this object.
+    // Initially set to `nullptr` until the first call to `getGroup`.
     // May be reset to `nullptr` by a call to `unlink`.
-    js::PerformanceGroup* group_;
+    mozilla::RefPtr sharedGroup_;
+    mozilla::RefPtr ownGroup_;
 };
 
 /**
@@ -5623,6 +5635,10 @@ extern JS_PUBLIC_API(bool)
 SetStopwatchIsMonitoringJank(JSRuntime*, bool);
 extern JS_PUBLIC_API(bool)
 GetStopwatchIsMonitoringJank(JSRuntime*);
+extern JS_PUBLIC_API(bool)
+SetStopwatchIsMonitoringPerCompartment(JSRuntime*, bool);
+extern JS_PUBLIC_API(bool)
+GetStopwatchIsMonitoringPerCompartment(JSRuntime*);
 
 extern JS_PUBLIC_API(bool)
 IsStopwatchActive(JSRuntime*);
@@ -5634,7 +5650,9 @@ extern JS_PUBLIC_API(PerformanceData*)
 GetPerformanceData(JSRuntime*);
 
 typedef bool
-(PerformanceStatsWalker)(JSContext* cx, const PerformanceData& stats, uint64_t uid, void* closure);
+(PerformanceStatsWalker)(JSContext* cx,
+                         const PerformanceData& stats, uint64_t uid,
+                         const uint64_t* parentId, void* closure);
 
 /**
  * Extract the performance statistics.
diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp
index a6d8854446..170ab85fc4 100644
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3152,13 +3152,16 @@ array_of(JSContext* cx, unsigned argc, Value* vp)
     // Step 4.
     RootedObject obj(cx);
     {
+        ConstructArgs cargs(cx);
+        if (!cargs.init(1))
+            return false;
+        cargs[0].setNumber(args.length());
+
         RootedValue v(cx);
-        Value argv[1] = {NumberValue(args.length())};
-        if (!InvokeConstructor(cx, args.thisv(), 1, argv, false, &v))
-            return false;
-        obj = ToObject(cx, v);
-        if (!obj)
+        if (!Construct(cx, args.thisv(), cargs, args.thisv(), &v))
             return false;
+
+        obj = &v.toObject();
     }
 
     // Step 8.
diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp
index 4b7ebdccab..1ec3b2db76 100644
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -853,7 +853,7 @@ struct DumpHeapTracer : public JS::CallbackTracer, public WeakMapTracer
                 map, key.asCell(), kdelegate, value.asCell());
     }
 
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 };
 
 static char
@@ -907,14 +907,14 @@ DumpHeapVisitCell(JSRuntime* rt, void* data, void* thing,
 }
 
 void
-DumpHeapTracer::trace(void** thingp, JS::TraceKind kind)
+DumpHeapTracer::onChild(const JS::GCCellPtr& thing)
 {
-    if (gc::IsInsideNursery((js::gc::Cell*)*thingp))
+    if (gc::IsInsideNursery(thing.asCell()))
         return;
 
     char buffer[1024];
     getTracingEdgeName(buffer, sizeof(buffer));
-    fprintf(output, "%s%p %c %s\n", prefix, *thingp, MarkDescriptor(*thingp), buffer);
+    fprintf(output, "%s%p %c %s\n", prefix, thing.asCell(), MarkDescriptor(thing.asCell()), buffer);
 }
 
 void
diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp
index ff4da6d7d2..234de20c69 100644
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1595,9 +1595,10 @@ js::CallOrConstructBoundFunction(JSContext* cx, unsigned argc, Value* vp)
     MOZ_ASSERT(fun->isBoundFunction());
 
     /* 15.3.4.5.1 step 1, 15.3.4.5.2 step 3. */
-    unsigned argslen = fun->getBoundFunctionArgumentCount();
+    unsigned boundArgsLen = fun->getBoundFunctionArgumentCount();
 
-    if (args.length() + argslen > ARGS_LENGTH_MAX) {
+    uint32_t argsLen = args.length();
+    if (argsLen + boundArgsLen > ARGS_LENGTH_MAX) {
         ReportAllocationOverflow(cx);
         return false;
     }
@@ -1608,31 +1609,44 @@ js::CallOrConstructBoundFunction(JSContext* cx, unsigned argc, Value* vp)
     /* 15.3.4.5.1 step 2. */
     const Value& boundThis = fun->getBoundFunctionThis();
 
+    if (args.isConstructing()) {
+        ConstructArgs cargs(cx);
+        if (!cargs.init(argsLen + boundArgsLen))
+            return false;
+
+        /* 15.3.4.5.1, 15.3.4.5.2 step 4. */
+        for (uint32_t i = 0; i < boundArgsLen; i++)
+            cargs[i].set(fun->getBoundFunctionArgument(i));
+        for (uint32_t i = 0; i < argsLen; i++)
+            cargs[boundArgsLen + i].set(args[i]);
+
+        RootedValue targetv(cx, ObjectValue(*target));
+
+        /* ES6 9.4.1.2 step 5 */
+        RootedValue newTarget(cx);
+        if (&args.newTarget().toObject() == fun)
+            newTarget.set(targetv);
+        else
+            newTarget.set(args.newTarget());
+
+        return Construct(cx, targetv, cargs, newTarget, args.rval());
+    }
+
     InvokeArgs invokeArgs(cx);
-    if (!invokeArgs.init(args.length() + argslen, args.isConstructing()))
+    if (!invokeArgs.init(argsLen + boundArgsLen))
         return false;
 
     /* 15.3.4.5.1, 15.3.4.5.2 step 4. */
-    for (unsigned i = 0; i < argslen; i++)
+    for (uint32_t i = 0; i < boundArgsLen; i++)
         invokeArgs[i].set(fun->getBoundFunctionArgument(i));
-    PodCopy(invokeArgs.array() + argslen, vp + 2, args.length());
+    for (uint32_t i = 0; i < argsLen; i++)
+        invokeArgs[boundArgsLen + i].set(args[i]);
 
     /* 15.3.4.5.1, 15.3.4.5.2 step 5. */
     invokeArgs.setCallee(ObjectValue(*target));
+    invokeArgs.setThis(boundThis);
 
-    bool constructing = args.isConstructing();
-    if (!constructing)
-        invokeArgs.setThis(boundThis);
-
-    /* ES6 9.4.1.2 step 5 */
-    if (constructing) {
-        if (&args.newTarget().toObject() == fun)
-            invokeArgs.newTarget().setObject(*target);
-        else
-            invokeArgs.newTarget().set(args.newTarget());
-    }
-
-    if (constructing ? !InvokeConstructor(cx, invokeArgs) : !Invoke(cx, invokeArgs))
+    if (!Invoke(cx, invokeArgs))
         return false;
 
     args.rval().set(invokeArgs.rval());
diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp
index 9b284974b6..838907eb1f 100644
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2207,19 +2207,10 @@ GCRuntime::relocateArenas(Zone *zone, JS::gcreason::Reason reason, SliceBudget &
 }
 
 void
-MovingTracer::trace(void** thingp, JS::TraceKind kind)
+MovingTracer::onObjectEdge(JSObject** objp)
 {
-    TenuredCell* thing = TenuredCell::fromPointer(*thingp);
-
-    // Currently we only relocate objects.
-    if (kind != JS::TraceKind::Object) {
-        MOZ_ASSERT(!RelocationOverlay::isCellForwarded(thing));
-        return;
-    }
-
-    JSObject* obj = reinterpret_cast(thing);
-    if (IsForwarded(obj))
-        *thingp = Forwarded(obj);
+    if (IsForwarded(*objp))
+        *objp = Forwarded(*objp);
 }
 
 void
@@ -3671,10 +3662,12 @@ GCRuntime::shouldPreserveJITCode(JSCompartment* comp, int64_t currentTime,
 #ifdef DEBUG
 class CompartmentCheckTracer : public JS::CallbackTracer
 {
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 
   public:
-    explicit CompartmentCheckTracer(JSRuntime* rt) : JS::CallbackTracer(rt) {}
+    explicit CompartmentCheckTracer(JSRuntime* rt)
+      : JS::CallbackTracer(rt), src(nullptr), zone(nullptr), compartment(nullptr)
+    {}
 
     Cell* src;
     JS::TraceKind srcKind;
@@ -3712,17 +3705,17 @@ struct MaybeCompartmentFunctor {
 };
 
 void
-CompartmentCheckTracer::trace(void** thingp, JS::TraceKind kind)
+CompartmentCheckTracer::onChild(const JS::GCCellPtr& thing)
 {
-    TenuredCell* thing = TenuredCell::fromPointer(*thingp);
+    TenuredCell* tenured = TenuredCell::fromPointer(thing.asCell());
 
-    JSCompartment* comp = CallTyped(MaybeCompartmentFunctor(), thing, kind);
+    JSCompartment* comp = CallTyped(MaybeCompartmentFunctor(), tenured, thing.kind());
     if (comp && compartment) {
         MOZ_ASSERT(comp == compartment || runtime()->isAtomsCompartment(comp) ||
                    (srcKind == JS::TraceKind::Object &&
-                    InCrossCompartmentMap(static_cast(src), thing, kind)));
+                    InCrossCompartmentMap(static_cast(src), tenured, thing.kind())));
     } else {
-        MOZ_ASSERT(thing->zone() == zone || thing->zone()->isAtomsZone());
+        MOZ_ASSERT(tenured->zone() == zone || tenured->zone()->isAtomsZone());
     }
 }
 
diff --git a/js/src/moz.build b/js/src/moz.build
index be04d9ab25..a618258718 100644
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -4,6 +4,49 @@
 # 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/.
 
+# Directory metadata
+component_engine = ('Core', 'JavaScript Engine')
+component_gc     = ('Core', 'JavaScript: GC')
+component_intl   = ('Core', 'JavaScript: Internationalization API')
+component_jit    = ('Core', 'JavaScript Engine: JIT')
+component_stl    = ('Core', 'JavaScript: Standard Library')
+
+with Files('../public/**'):
+    BUG_COMPONENT = component_engine
+with Files('*'):
+    BUG_COMPONENT = component_engine
+
+with Files('asmjs/**'):
+    BUG_COMPONENT = component_jit
+with Files('builtin/**'):
+    BUG_COMPONENT = component_stl
+with Files('ctypes/**'):
+    BUG_COMPONENT = ('Core', 'js-ctypes')
+with Files('gc/**'):
+    BUG_COMPONENT = component_gc
+with Files('jit/**'):
+    BUG_COMPONENT = component_jit
+
+# File-specific metadata
+for gcfile in ['jsgc*', 'devtools/rootAnalysis', 'devtools/gc-ubench', 'devtools/gctrace']:
+    with Files(gcfile):
+        BUG_COMPONENT = component_gc
+for header in ('GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TraceKind.h', 'TracingAPI.h', 'WeakMapPtr.h'):
+    with Files('../public/' + header):
+        BUG_COMPONENT = component_gc
+
+for stlfile in ['jsarray.*', 'jsbool*', 'jsdate.*', 'jsnum.*', 'json.*', 'jsreflect.*', 'jsstr.*']:
+    with Files(stlfile):
+        BUG_COMPONENT = component_stl
+
+with Files('builtin/Intl*'):
+    BUG_COMPONENT = component_intl
+with Files('builtin/make_intl_data.py'):
+    BUG_COMPONENT = component_intl
+
+with Files('../public/TrackedOptimizationInfo.h'):
+    BUG_COMPONENT = component_jit
+
 if CONFIG['JS_BUNDLED_EDITLINE']:
     DIRS += ['editline']
 
@@ -80,6 +123,7 @@ EXPORTS.js += [
     '../public/RootingAPI.h',
     '../public/SliceBudget.h',
     '../public/StructuredClone.h',
+    '../public/TraceKind.h',
     '../public/TracingAPI.h',
     '../public/TrackedOptimizationInfo.h',
     '../public/TypeDecls.h',
diff --git a/js/src/proxy/DirectProxyHandler.cpp b/js/src/proxy/DirectProxyHandler.cpp
index ca94f598da..f4b8fc7553 100644
--- a/js/src/proxy/DirectProxyHandler.cpp
+++ b/js/src/proxy/DirectProxyHandler.cpp
@@ -81,8 +81,18 @@ bool
 DirectProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
+
     RootedValue target(cx, proxy->as().private_());
-    return InvokeConstructor(cx, target, args.length(), args.array(), true, args.rval());
+    if (!IsConstructor(target)) {
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, target, nullptr);
+        return false;
+    }
+
+    ConstructArgs cargs(cx);
+    if (!FillArgumentsFromArraylike(cx, cargs, args))
+        return false;
+
+    return Construct(cx, target, cargs, args.newTarget(), args.rval());
 }
 
 bool
diff --git a/js/src/proxy/ScriptedDirectProxyHandler.cpp b/js/src/proxy/ScriptedDirectProxyHandler.cpp
index cb08c4c797..062b6df76f 100644
--- a/js/src/proxy/ScriptedDirectProxyHandler.cpp
+++ b/js/src/proxy/ScriptedDirectProxyHandler.cpp
@@ -1059,8 +1059,12 @@ ScriptedDirectProxyHandler::construct(JSContext* cx, HandleObject proxy, const C
 
     // step 6
     if (trap.isUndefined()) {
+        ConstructArgs cargs(cx);
+        if (!FillArgumentsFromArraylike(cx, cargs, args))
+            return false;
+
         RootedValue targetv(cx, ObjectValue(*target));
-        return InvokeConstructor(cx, targetv, args.length(), args.array(), true, args.rval());
+        return Construct(cx, targetv, cargs, args.newTarget(), args.rval());
     }
 
     // step 8-9
diff --git a/js/src/proxy/ScriptedIndirectProxyHandler.cpp b/js/src/proxy/ScriptedIndirectProxyHandler.cpp
index 9587cc9abc..7254091df1 100644
--- a/js/src/proxy/ScriptedIndirectProxyHandler.cpp
+++ b/js/src/proxy/ScriptedIndirectProxyHandler.cpp
@@ -463,11 +463,24 @@ bool
 CallableScriptedIndirectProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
+
     RootedObject ccHolder(cx, &proxy->as().extra(0).toObject());
     MOZ_ASSERT(ccHolder->getClass() == &CallConstructHolder);
+
     RootedValue construct(cx, ccHolder->as().getReservedSlot(1));
-    MOZ_ASSERT(construct.isObject() && construct.toObject().isCallable());
-    return InvokeConstructor(cx, construct, args.length(), args.array(), true, args.rval());
+
+    // We could enforce this at proxy creation time, but lipstick on a pig.
+    // Plus, let's delay in-the-field bustage as long as possible.
+    if (!IsConstructor(construct)) {
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, construct, nullptr);
+        return false;
+    }
+
+    ConstructArgs cargs(cx);
+    if (!FillArgumentsFromArraylike(cx, cargs, args))
+        return false;
+
+    return Construct(cx, construct, cargs, args.newTarget(), args.rval());
 }
 
 const CallableScriptedIndirectProxyHandler CallableScriptedIndirectProxyHandler::singleton;
diff --git a/js/src/tests/ecma_6/extensions/new-cross-compartment.js b/js/src/tests/ecma_6/extensions/new-cross-compartment.js
new file mode 100644
index 0000000000..5df4375203
--- /dev/null
+++ b/js/src/tests/ecma_6/extensions/new-cross-compartment.js
@@ -0,0 +1,42 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1178653;
+var summary =
+  "|new| on a cross-compartment wrapper to a non-constructor shouldn't assert";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var g = newGlobal();
+
+var otherStr = new g.String("foo");
+assertEq(otherStr instanceof g.String, true);
+assertEq(otherStr.valueOf(), "foo");
+
+// THIS IS WRONG.  |new| itself should throw if !IsConstructor(constructor),
+// meaning this global's TypeError should be used.  The problem ultimately is
+// that wrappers conflate callable/constructible, so any old function from
+// another global appears to be both.  Somebody fix bug XXXXXX!
+try
+{
+  var constructor = g.parseInt;
+  new constructor();
+  throw new Error("no error thrown");
+}
+catch (e)
+{
+  assertEq(e instanceof g.TypeError, true,
+           "THIS REALLY SHOULD BE |e instanceof TypeError|");
+}
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index 005966070c..bc14fb131e 100644
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -369,15 +369,47 @@ ExecuteState::pushInterpreterFrame(JSContext* cx)
                                                               scopeChain_, type_, evalInFrame_);
 }
 namespace js {
-
 // Implementation of per-performance group performance measurement.
 //
 //
 // All mutable state is stored in `Runtime::stopwatch` (per-process
 // performance stats and logistics) and in `PerformanceGroup` (per
 // group performance stats).
-struct AutoStopwatch final
+class AutoStopwatch final
 {
+    // The context with which this object was initialized.
+    // Non-null.
+    JSContext* const cx_;
+
+    // An indication of the number of times we have entered the event
+    // loop.  Used only for comparison.
+    uint64_t iteration_;
+
+    // `true` if we are monitoring jank, `false` otherwise.
+    bool isMonitoringJank_;
+    // `true` if we are monitoring CPOW, `false` otherwise.
+    bool isMonitoringCPOW_;
+
+    // Timestamps captured while starting the stopwatch.
+    uint64_t userTimeStart_;
+    uint64_t systemTimeStart_;
+    uint64_t CPOWTimeStart_;
+
+   // The performance group shared by this compartment and possibly
+   // others, or `nullptr` if another AutoStopwatch is already in
+   // charge of monitoring that group.
+   mozilla::RefPtr sharedGroup_;
+
+   // The toplevel group, representing the entire process, or `nullptr`
+   // if another AutoStopwatch is already in charge of monitoring that group.
+   mozilla::RefPtr topGroup_;
+
+   // The performance group specific to this compartment, or
+   // `nullptr` if another AutoStopwatch is already in charge of
+   // monitoring that group.
+   mozilla::RefPtr ownGroup_;
+
+   public:
     // If the stopwatch is active, constructing an instance of
     // AutoStopwatch causes it to become the current owner of the
     // stopwatch.
@@ -386,120 +418,162 @@ struct AutoStopwatch final
     explicit inline AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : cx_(cx)
       , iteration_(0)
-      , isActive_(false)
-      , isTop_(false)
+      , isMonitoringJank_(false)
+      , isMonitoringCPOW_(false)
       , userTimeStart_(0)
       , systemTimeStart_(0)
       , CPOWTimeStart_(0)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
-        JSRuntime* runtime = JS_GetRuntime(cx_);
-        if (!runtime->stopwatch.isMonitoringJank())
+        JSCompartment* compartment = cx_->compartment();
+        if (compartment->scheduledForDestruction)
             return;
 
+        JSRuntime* runtime = cx_->runtime();
+        iteration_ = runtime->stopwatch.iteration;
+
+        sharedGroup_ = acquireGroup(compartment->performanceMonitoring.getSharedGroup(cx));
+        if (sharedGroup_)
+            topGroup_ = acquireGroup(runtime->stopwatch.performance.getOwnGroup());
+
+        if (runtime->stopwatch.isMonitoringPerCompartment())
+            ownGroup_ = acquireGroup(compartment->performanceMonitoring.getOwnGroup());
+
+        if (!sharedGroup_ && !ownGroup_) {
+            // We are not in charge of monitoring anything.
+            return;
+        }
+
+        enter();
+    }
+    ~AutoStopwatch()
+    {
+        if (!sharedGroup_ && !ownGroup_) {
+            // We are not in charge of monitoring anything.
+            // (isMonitoringForTop_ implies isMonitoringForGroup_,
+            // so we do not need to check it)
+            return;
+        }
+
         JSCompartment* compartment = cx_->compartment();
         if (compartment->scheduledForDestruction)
             return;
 
-        iteration_ = runtime->stopwatch.iteration;
-
-        PerformanceGroup *group = compartment->performanceMonitoring.getGroup(cx);
-        MOZ_ASSERT(group);
-
-        if (group->hasStopwatch(iteration_)) {
-            // Someone is already monitoring this group during this
-            // tick, no need for further monitoring.
-            return;
-        }
-
-        // Start the stopwatch.
-        if (!this->getTimes(runtime, &userTimeStart_, &systemTimeStart_))
-            return;
-        isActive_ = true;
-        CPOWTimeStart_ = runtime->stopwatch.performance.totalCPOWTime;
-
-        // We are now in charge of monitoring this group for the tick,
-        // until destruction of `this` or until we enter a nested event
-        // loop and `iteration_` is incremented.
-        group->acquireStopwatch(iteration_, this);
-
-        if (runtime->stopwatch.isEmpty) {
-            // This is the topmost stopwatch on the stack.
-            // It will be in charge of updating the per-process
-            // performance data.
-            runtime->stopwatch.isEmpty = false;
-            runtime->stopwatch.performance.ticks++;
-            isTop_ = true;
-        }
-    }
-    inline ~AutoStopwatch() {
-        if (!isActive_) {
-            // We are not in charge of monitoring anything.
-            return;
-        }
-
-        JSRuntime* runtime = JS_GetRuntime(cx_);
-        JSCompartment* compartment = cx_->compartment();
-
-        MOZ_ASSERT(!compartment->scheduledForDestruction);
-
-        if (!runtime->stopwatch.isMonitoringJank()) {
-            // Monitoring has been stopped while we were
-            // executing the code. Drop everything.
-            return;
-        }
-
+        JSRuntime* runtime = cx_->runtime();
         if (iteration_ != runtime->stopwatch.iteration) {
             // We have entered a nested event loop at some point.
             // Any information we may have is obsolete.
             return;
         }
 
-        PerformanceGroup *group = compartment->performanceMonitoring.getGroup(cx_);
-        MOZ_ASSERT(group);
+        releaseGroup(sharedGroup_);
+        releaseGroup(topGroup_);
+        releaseGroup(ownGroup_);
 
-        // Compute time spent.
-        group->releaseStopwatch(iteration_, this);
-        uint64_t userTimeEnd, systemTimeEnd;
-        if (!this->getTimes(runtime, &userTimeEnd, &systemTimeEnd))
+        // Finish and commit measures
+        exit();
+    }
+   private:
+    void enter() {
+        JSRuntime* runtime = cx_->runtime();
+
+        if (runtime->stopwatch.isMonitoringCPOW()) {
+            CPOWTimeStart_ = runtime->stopwatch.performance.getOwnGroup()->data.totalCPOWTime;
+            isMonitoringCPOW_ = true;
+        }
+
+        if (runtime->stopwatch.isMonitoringJank()) {
+            if (this->getTimes(runtime, &userTimeStart_, &systemTimeStart_)) {
+                isMonitoringJank_ = true;
+            }
+        }
+
+    }
+
+    void exit() {
+        JSRuntime* runtime = cx_->runtime();
+
+        uint64_t userTimeDelta = 0;
+        uint64_t systemTimeDelta = 0;
+        if (isMonitoringJank_ && runtime->stopwatch.isMonitoringJank()) {
+            // We were monitoring jank when we entered and we still are.
+            uint64_t userTimeEnd, systemTimeEnd;
+            if (!this->getTimes(runtime, &userTimeEnd, &systemTimeEnd)) {
+                // We make no attempt to recover from this error. If
+                // we bail out here, we lose nothing of value, plus
+                // I'm nearly sure that this error cannot happen in
+                // practice.
+                return;
+            }
+            userTimeDelta = userTimeEnd - userTimeStart_;
+            systemTimeDelta = systemTimeEnd - systemTimeStart_;
+        }
+
+        uint64_t CPOWTimeDelta = 0;
+        if (isMonitoringCPOW_ && runtime->stopwatch.isMonitoringCPOW()) {
+            // We were monitoring CPOW when we entered and we still are.
+            CPOWTimeDelta = runtime->stopwatch.performance.getOwnGroup()->data.totalCPOWTime - CPOWTimeStart_;
+
+        }
+        commitDeltasToGroups(userTimeDelta, systemTimeDelta, CPOWTimeDelta);
+    }
+
+    // Attempt to acquire a group
+    // If the group is `null` or if the group already has a stopwatch,
+    // do nothing and return `null`.
+    // Otherwise, bind the group to `this` for the current iteration
+    // and return `group`.
+    PerformanceGroup* acquireGroup(PerformanceGroup* group) {
+        if (!group)
+            return nullptr;
+
+        if (group->hasStopwatch(iteration_))
+            return nullptr;
+
+        group->acquireStopwatch(iteration_, this);
+        return group;
+    }
+
+    // Release a group.
+    // Noop if `group` is null or if `this` is not the stopwatch
+    // of `group` for the current iteration.
+    void releaseGroup(PerformanceGroup* group) {
+        if (group)
+            group->releaseStopwatch(iteration_, this);
+    }
+
+    void commitDeltasToGroups(uint64_t userTimeDelta, uint64_t systemTimeDelta,
+                              uint64_t CPOWTimeDelta) const {
+        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, sharedGroup_);
+        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, topGroup_);
+        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, ownGroup_);
+    }
+
+    void applyDeltas(uint64_t userTimeDelta, uint64_t systemTimeDelta,
+                     uint64_t CPOWTimeDelta, PerformanceGroup* group) const {
+        if (!group)
             return;
 
-        uint64_t userTimeDelta = userTimeEnd - userTimeStart_;
-        uint64_t systemTimeDelta = systemTimeEnd - systemTimeStart_;
-        uint64_t CPOWTimeDelta = runtime->stopwatch.performance.totalCPOWTime - CPOWTimeStart_;
+        group->data.ticks++;
+
+        uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta;
         group->data.totalUserTime += userTimeDelta;
         group->data.totalSystemTime += systemTimeDelta;
         group->data.totalCPOWTime += CPOWTimeDelta;
 
-        uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta;
-        updateDurations(totalTimeDelta, group->data.durations);
-        group->data.ticks++;
+        // Update an array containing the number of times we have missed
+        // at least 2^0 successive ms, 2^1 successive ms, ...
+        // 2^i successive ms.
 
-        if (isTop_) {
-            // This is the topmost stopwatch on the stack.
-            // Record the timing information.
-            runtime->stopwatch.performance.totalUserTime = userTimeEnd;
-            runtime->stopwatch.performance.totalSystemTime = systemTimeEnd;
-            updateDurations(totalTimeDelta, runtime->stopwatch.performance.durations);
-            runtime->stopwatch.isEmpty = true;
-        }
-    }
-
- private:
-
-    // Update an array containing the number of times we have missed
-    // at least 2^0 successive ms, 2^1 successive ms, ...
-    // 2^i successive ms.
-    template
-    void updateDurations(uint64_t totalTimeDelta, uint64_t (&array)[N]) const {
         // Duration of one frame, i.e. 16ms in museconds
         size_t i = 0;
         uint64_t duration = 1000;
         for (i = 0, duration = 1000;
-             i < N && duration < totalTimeDelta;
-             ++i, duration *= 2) {
-            array[i]++;
+             i < ArrayLength(group->data.durations) && duration < totalTimeDelta;
+             ++i, duration *= 2)
+        {
+            group->data.durations[i]++;
         }
     }
 
@@ -582,31 +656,9 @@ struct AutoStopwatch final
         return true;
     }
 
-  private:
-    // The context with which this object was initialized.
-    // Non-null.
-    JSContext* const cx_;
 
-    // An indication of the number of times we have entered the event
-    // loop.  Used only for comparison.
-    uint64_t iteration_;
-
-    // `true` if this object is currently used to monitor performance,
-    // `false` otherwise, i.e. if the stopwatch mechanism is off or if
-    // another stopwatch is already in charge of monitoring for the
-    // same PerformanceGroup.
-    bool isActive_;
-
-    // `true` if this stopwatch is the topmost stopwatch on the stack
-    // for this event, `false` otherwise.
-    bool isTop_;
-
-    // Timestamps captured while starting the stopwatch.
-    uint64_t userTimeStart_;
-    uint64_t systemTimeStart_;
-    uint64_t CPOWTimeStart_;
-
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+private:
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
 };
 
 }
@@ -769,26 +821,23 @@ js::Invoke(JSContext* cx, const Value& thisv, const Value& fval, unsigned argc,
     return true;
 }
 
-bool
-js::InvokeConstructor(JSContext* cx, const CallArgs& args)
+static bool
+InternalConstruct(JSContext* cx, const CallArgs& args)
 {
+    MOZ_ASSERT(args.array() + args.length() + 1 == args.end(),
+               "must pass constructing arguments to a construction attempt");
     MOZ_ASSERT(!JSFunction::class_.construct);
 
-    args.setThis(MagicValue(JS_IS_CONSTRUCTING));
-
-    // +2 here and below to pass over |this| and |new.target|
-    if (!args.calleev().isObject())
-        return ReportIsNotFunction(cx, args.calleev(), args.length() + 2, CONSTRUCT);
-
-    MOZ_ASSERT(args.newTarget().isObject());
+    // Callers are responsible for enforcing these preconditions.
+    MOZ_ASSERT(IsConstructor(args.calleev()),
+               "trying to construct a value that isn't a constructor");
+    MOZ_ASSERT(IsConstructor(args.newTarget()),
+               "provided new.target value must be a constructor");
 
     JSObject& callee = args.callee();
     if (callee.is()) {
         RootedFunction fun(cx, &callee.as());
 
-        if (!fun->isConstructor())
-            return ReportIsNotFunction(cx, args.calleev(), args.length() + 2, CONSTRUCT);
-
         if (fun->isNative())
             return CallJSNativeConstructor(cx, fun->native(), args);
 
@@ -800,29 +849,65 @@ js::InvokeConstructor(JSContext* cx, const CallArgs& args)
     }
 
     JSNative construct = callee.constructHook();
-    if (!construct)
-        return ReportIsNotFunction(cx, args.calleev(), args.length() + 2, CONSTRUCT);
+    MOZ_ASSERT(construct != nullptr, "IsConstructor without a construct hook?");
 
     return CallJSNativeConstructor(cx, construct, args);
 }
 
-bool
-js::InvokeConstructor(JSContext* cx, Value fval, unsigned argc, const Value* argv,
-                      bool newTargetInArgv, MutableHandleValue rval)
+// Check that |callee|, the callee in a |new| expression, is a constructor.
+static bool
+StackCheckIsConstructorCalleeNewTarget(JSContext* cx, HandleValue callee, HandleValue newTarget)
 {
-    InvokeArgs args(cx);
-    if (!args.init(argc, true))
+    // Calls from the stack could have any old non-constructor callee.
+    if (!IsConstructor(callee)) {
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_SEARCH_STACK, callee, nullptr);
+        return false;
+    }
+
+    // The new.target for a stack construction attempt is just the callee: no
+    // need to check that it's a constructor.
+    MOZ_ASSERT(&callee.toObject() == &newTarget.toObject());
+
+    return true;
+}
+
+static bool
+ConstructFromStack(JSContext* cx, const CallArgs& args)
+{
+    if (!StackCheckIsConstructorCalleeNewTarget(cx, args.calleev(), args.newTarget()))
         return false;
 
-    args.setCallee(fval);
-    args.setThis(MagicValue(JS_THIS_POISON));
-    PodCopy(args.array(), argv, argc);
-    if (newTargetInArgv)
-        args.newTarget().set(argv[argc]);
-    else
-        args.newTarget().set(fval);
+    args.setThis(MagicValue(JS_IS_CONSTRUCTING));
+    return InternalConstruct(cx, args);
+}
 
-    if (!InvokeConstructor(cx, args))
+bool
+js::Construct(JSContext* cx, HandleValue fval, const ConstructArgs& args, HandleValue newTarget,
+              MutableHandleValue rval)
+{
+    args.setCallee(fval);
+    args.setThis(MagicValue(JS_IS_CONSTRUCTING));
+    args.newTarget().set(newTarget);
+    if (!InternalConstruct(cx, args))
+        return false;
+
+    rval.set(args.rval());
+    return true;
+}
+
+bool
+js::InternalConstructWithProvidedThis(JSContext* cx, HandleValue fval, HandleValue thisv,
+                                      const ConstructArgs& args, HandleValue newTarget,
+                                      MutableHandleValue rval)
+{
+    args.setCallee(fval);
+
+    MOZ_ASSERT(thisv.isObject());
+    args.setThis(thisv);
+
+    args.newTarget().set(newTarget);
+
+    if (!InternalConstruct(cx, args))
         return false;
 
     rval.set(args.rval());
@@ -3006,7 +3091,7 @@ CASE(JSOP_FUNCALL)
         (!construct && maybeFun->isClassConstructor()))
     {
         if (construct) {
-            if (!InvokeConstructor(cx, args))
+            if (!ConstructFromStack(cx, args))
                 goto error;
         } else {
             if (!Invoke(cx, args))
@@ -3919,7 +4004,7 @@ CASE(JSOP_CLASSHERITAGE)
         if (!GetBuiltinPrototype(cx, JSProto_Function, &funcProto))
             goto error;
     } else {
-        if (!val.isObject() || !val.toObject().isConstructor()) {
+        if (!IsConstructor(val)) {
             ReportIsNotFunction(cx, val, 0, CONSTRUCT);
             goto error;
         }
@@ -4643,44 +4728,53 @@ js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, Hand
         MOZ_ASSERT(!aobj->getDenseElement(i).isMagic());
 #endif
 
-    InvokeArgs args(cx);
-
-    if (!args.init(length, constructing))
-        return false;
-
-    args.setCallee(callee);
-    args.setThis(thisv);
-
-    if (!GetElements(cx, aobj, length, args.array()))
-        return false;
-
-    if (constructing)
-        args.newTarget().set(newTarget);
-
-    switch (op) {
-      case JSOP_SPREADNEW:
-        if (!InvokeConstructor(cx, args))
+    if (op == JSOP_SPREADNEW) {
+        if (!StackCheckIsConstructorCalleeNewTarget(cx, callee, newTarget))
             return false;
-        break;
-      case JSOP_SPREADCALL:
-        if (!Invoke(cx, args))
+
+        ConstructArgs cargs(cx);
+        if (!cargs.init(length))
             return false;
-        break;
-      case JSOP_SPREADEVAL:
-      case JSOP_STRICTSPREADEVAL:
-        if (cx->global()->valueIsEval(args.calleev())) {
-            if (!DirectEval(cx, args))
-                return false;
-        } else {
+
+        if (!GetElements(cx, aobj, length, cargs.array()))
+            return false;
+
+        if (!Construct(cx, callee, cargs, newTarget, res))
+            return false;
+    } else {
+        InvokeArgs args(cx);
+
+        if (!args.init(length))
+            return false;
+
+        args.setCallee(callee);
+        args.setThis(thisv);
+
+        if (!GetElements(cx, aobj, length, args.array()))
+            return false;
+
+        switch (op) {
+          case JSOP_SPREADCALL:
             if (!Invoke(cx, args))
                 return false;
+            break;
+          case JSOP_SPREADEVAL:
+          case JSOP_STRICTSPREADEVAL:
+            if (cx->global()->valueIsEval(args.calleev())) {
+                if (!DirectEval(cx, args))
+                    return false;
+            } else {
+                if (!Invoke(cx, args))
+                    return false;
+            }
+            break;
+          default:
+            MOZ_CRASH("bad spread opcode");
         }
-        break;
-      default:
-        MOZ_CRASH("bad spread opcode");
+
+        res.set(args.rval());
     }
 
-    res.set(args.rval());
     TypeScript::Monitor(cx, script, pc, res);
     return true;
 }
diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h
index 588b25b931..175eadbd6d 100644
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -85,17 +85,26 @@ InvokeGetter(JSContext* cx, JSObject* obj, Value fval, MutableHandleValue rval);
 extern bool
 InvokeSetter(JSContext* cx, const Value& thisv, Value fval, HandleValue v);
 
-/*
- * InvokeConstructor implement a function call from a constructor context
- * (e.g. 'new') handling the the creation of the new 'this' object.
- */
+// ES6 7.3.13 Construct(F, argumentsList, newTarget).  All parameters are
+// required, hopefully forcing callers to be careful not to (say) blindly pass
+// callee as |newTarget| when a different value should have been passed.
+//
+// NOTE: As with the ES6 spec operation, it's the caller's responsibility to
+//       ensure |fval| and |newTarget| are both |IsConstructor|.
 extern bool
-InvokeConstructor(JSContext* cx, const CallArgs& args);
+Construct(JSContext* cx, HandleValue fval, const ConstructArgs& args, HandleValue newTarget,
+          MutableHandleValue rval);
 
-/* See the fval overload of Invoke. */
+// Call Construct(fval, args, newTarget), but use the given |thisv| as |this|
+// during construction of |fval|.
+//
+// This method exists only for very rare cases where a |this| was created
+// caller-side for construction of |fval|: basically only for JITs using
+// |CreateThis|.  If that's not you, use Construct()!
 extern bool
-InvokeConstructor(JSContext* cx, Value fval, unsigned argc, const Value* argv,
-                  bool newTargetInArgv, MutableHandleValue rval);
+InternalConstructWithProvidedThis(JSContext* cx, HandleValue fval, HandleValue thisv,
+                                  const ConstructArgs& args, HandleValue newTarget,
+                                  MutableHandleValue rval);
 
 /*
  * Executes a script with the given scopeChain/this. The 'type' indicates
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp
index 86e76bcaf6..ec93e5a5b9 100644
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -221,7 +221,8 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
     largeAllocationFailureCallback(nullptr),
     oomCallback(nullptr),
     debuggerMallocSizeOf(ReturnZeroSize),
-    lastAnimationTime(0)
+    lastAnimationTime(0),
+    stopwatch(thisFromCtor())
 {
     setGCStoreBufferPtr(&gc.storeBuffer);
 
@@ -886,6 +887,17 @@ js::GetStopwatchIsMonitoringCPOW(JSRuntime* rt)
     return rt->stopwatch.isMonitoringCPOW();
 }
 
+bool
+js::SetStopwatchIsMonitoringPerCompartment(JSRuntime* rt, bool value)
+{
+    return rt->stopwatch.setIsMonitoringPerCompartment(value);
+}
+bool
+js::GetStopwatchIsMonitoringPerCompartment(JSRuntime* rt)
+{
+    return rt->stopwatch.isMonitoringPerCompartment();
+}
+
 js::PerformanceGroupHolder::~PerformanceGroupHolder()
 {
     unlink();
@@ -905,62 +917,91 @@ js::PerformanceGroupHolder::getHashKey(JSContext* cx)
 void
 js::PerformanceGroupHolder::unlink()
 {
-    if (!group_) {
-        // The group has never been instantiated.
-        return;
-    }
-
-    js::PerformanceGroup* group = group_;
-    group_ = nullptr;
-
-    if (group->decRefCount() > 0) {
-        // The group has at least another owner.
-        return;
-    }
-
-
-    JSRuntime::Stopwatch::Groups::Ptr ptr =
-        runtime_->stopwatch.groups_.lookup(group->key_);
-    MOZ_ASSERT(ptr);
-    runtime_->stopwatch.groups_.remove(ptr);
-    js_delete(group);
+    ownGroup_ = nullptr;
+    sharedGroup_ = nullptr;
 }
 
 PerformanceGroup*
-js::PerformanceGroupHolder::getGroup(JSContext* cx)
+js::PerformanceGroupHolder::getOwnGroup()
 {
-    if (group_)
-        return group_;
+    if (ownGroup_)
+        return ownGroup_;
+
+    return ownGroup_ = runtime_->new_(runtime_);
+}
+
+PerformanceGroup*
+js::PerformanceGroupHolder::getSharedGroup(JSContext* cx)
+{
+    if (sharedGroup_)
+        return sharedGroup_;
+
+    if (!runtime_->stopwatch.groups().initialized())
+        return nullptr;
 
     void* key = getHashKey(cx);
-    JSRuntime::Stopwatch::Groups::AddPtr ptr =
-        runtime_->stopwatch.groups_.lookupForAdd(key);
+    JSRuntime::Stopwatch::Groups::AddPtr ptr = runtime_->stopwatch.groups().lookupForAdd(key);
     if (ptr) {
-        group_ = ptr->value();
-        MOZ_ASSERT(group_);
+        sharedGroup_ = ptr->value();
+        MOZ_ASSERT(sharedGroup_);
     } else {
-        group_ = runtime_->new_(cx, key);
-        runtime_->stopwatch.groups_.add(ptr, key, group_);
+        sharedGroup_ = runtime_->new_(cx, key);
+        if (!sharedGroup_)
+            return nullptr;
+
+        runtime_->stopwatch.groups().add(ptr, key, sharedGroup_);
     }
 
-    group_->incRefCount();
-
-    return group_;
+    return sharedGroup_;
 }
 
 PerformanceData*
 js::GetPerformanceData(JSRuntime* rt)
 {
-    return &rt->stopwatch.performance;
+    return &rt->stopwatch.performance.getOwnGroup()->data;
 }
 
-js::PerformanceGroup::PerformanceGroup(JSContext* cx, void* key)
-  : uid(cx->runtime()->stopwatch.uniqueId())
-  , stopwatch_(nullptr)
-  , iteration_(0)
-  , key_(key)
-  , refCount_(0)
+js::PerformanceGroup::PerformanceGroup(JSRuntime* rt)
+  : uid(rt->stopwatch.uniqueId()),
+    runtime_(rt),
+    stopwatch_(nullptr),
+    iteration_(0),
+    key_(nullptr),
+    refCount_(0),
+    isSharedGroup_(false)
+{ }
+
+ js::PerformanceGroup::PerformanceGroup(JSContext* cx, void* key)
+   : uid(cx->runtime()->stopwatch.uniqueId()),
+     runtime_(cx->runtime()),
+     stopwatch_(nullptr),
+     iteration_(0),
+     key_(key),
+     refCount_(0),
+     isSharedGroup_(true)
+{ }
+
+void
+js::PerformanceGroup::AddRef()
 {
+    ++refCount_;
+}
+
+void
+js::PerformanceGroup::Release()
+{
+    MOZ_ASSERT(refCount_ > 0);
+    --refCount_;
+    if (refCount_ > 0)
+        return;
+
+    if (isSharedGroup_) {
+        JSRuntime::Stopwatch::Groups::Ptr ptr = runtime_->stopwatch.groups().lookup(key_);
+        MOZ_ASSERT(ptr);
+        runtime_->stopwatch.groups().remove(ptr);
+    }
+
+    js_delete(this);
 }
 
 void
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h
index 40ea933ee2..a6178cf047 100644
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1488,6 +1488,34 @@ struct JSRuntime : public JS::shadow::Runtime,
        Performance measurements
        ------------------------------------------ */
     struct Stopwatch {
+        /**
+         * A map used to collapse compartments belonging to the same
+         * add-on (respectively to the same webpage, to the platform)
+         * into a single group.
+         *
+         * Keys: for system compartments, a `JSAddonId*` (which may be
+         * `nullptr`), and for webpages, a `JSPrincipals*` (which may
+         * not). Note that compartments may start as non-system
+         * compartments and become compartments later during their
+         * lifetime, which requires an invalidation.
+         *
+         * This map is meant to be accessed only by instances of
+         * PerformanceGroupHolder, which handle both reference-counting
+         * of the values and invalidation of the key/value pairs.
+         */
+        typedef js::HashMap,
+                            js::SystemAllocPolicy> Groups;
+
+        Groups& groups() {
+            return groups_;
+        }
+
+        /**
+         * Performance data on the entire runtime.
+         */
+        js::PerformanceGroupHolder performance;
+
         /**
          * The number of times we have entered the event loop.
          * Used to reset counters whenever we enter the loop,
@@ -1499,17 +1527,6 @@ struct JSRuntime : public JS::shadow::Runtime,
          */
         uint64_t iteration;
 
-        /**
-         * `true` if no stopwatch has been registered for the
-         * current run of the event loop, `false` until then.
-         */
-        bool isEmpty;
-
-        /**
-         * Performance data on the entire runtime.
-         */
-        js::PerformanceData performance;
-
         /**
          * Callback used to ask the embedding to determine in which
          * Performance Group the current execution belongs. Typically, this is
@@ -1522,12 +1539,13 @@ struct JSRuntime : public JS::shadow::Runtime,
          */
         JSCurrentPerfGroupCallback currentPerfGroupCallback;
 
-        Stopwatch()
-          : iteration(0)
-          , isEmpty(true)
+        explicit Stopwatch(JSRuntime* runtime)
+          : performance(runtime)
+          , iteration(0)
           , currentPerfGroupCallback(nullptr)
           , isMonitoringJank_(false)
           , isMonitoringCPOW_(false)
+          , isMonitoringPerCompartment_(false)
           , idCounter_(0)
         { }
 
@@ -1540,7 +1558,6 @@ struct JSRuntime : public JS::shadow::Runtime,
          */
         void reset() {
             ++iteration;
-            isEmpty = true;
         }
         /**
          * Activate/deactivate stopwatch measurement of jank.
@@ -1569,6 +1586,21 @@ struct JSRuntime : public JS::shadow::Runtime,
             return isMonitoringJank_;
         }
 
+        bool setIsMonitoringPerCompartment(bool value) {
+            if (isMonitoringPerCompartment_ != value)
+                reset();
+
+            if (value && !groups_.initialized()) {
+                if (!groups_.init(128))
+                    return false;
+            }
+
+            isMonitoringPerCompartment_ = value;
+            return true;
+        }
+        bool isMonitoringPerCompartment() const {
+            return isMonitoringPerCompartment_;
+        }
 
         /**
          * Activate/deactivate stopwatch measurement of CPOW.
@@ -1612,24 +1644,8 @@ struct JSRuntime : public JS::shadow::Runtime,
         MonotonicTimeStamp userTimeFix;
 
     private:
-        /**
-         * A map used to collapse compartments belonging to the same
-         * add-on (respectively to the same webpage, to the platform)
-         * into a single group.
-         *
-         * Keys: for system compartments, a `JSAddonId*` (which may be
-         * `nullptr`), and for webpages, a `JSPrincipals*` (which may
-         * not). Note that compartments may start as non-system
-         * compartments and become compartments later during their
-         * lifetime, which requires an invalidation.
-         *
-         * This map is meant to be accessed only by instances of
-         * PerformanceGroupHolder, which handle both reference-counting
-         * of the values and invalidation of the key/value pairs.
-         */
-        typedef js::HashMap,
-                            js::SystemAllocPolicy> Groups;
+        Stopwatch(const Stopwatch&) = delete;
+        Stopwatch& operator=(const Stopwatch&) = delete;
 
         Groups groups_;
         friend struct js::PerformanceGroupHolder;
@@ -1639,6 +1655,7 @@ struct JSRuntime : public JS::shadow::Runtime,
          */
         bool isMonitoringJank_;
         bool isMonitoringCPOW_;
+        bool isMonitoringPerCompartment_;
 
         /**
          * A counter used to generate unique identifiers for groups.
diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h
index 229b867a2d..d37be6545c 100644
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1050,24 +1050,62 @@ void MarkInterpreterActivations(JSRuntime* rt, JSTracer* trc);
 
 /*****************************************************************************/
 
-class InvokeArgs : public JS::CallArgs
+namespace detail {
+
+class GenericInvokeArgs : public JS::CallArgs
 {
+  protected:
     AutoValueVector v_;
 
-  public:
-    explicit InvokeArgs(JSContext* cx, bool construct = false) : v_(cx) {}
+    explicit GenericInvokeArgs(JSContext* cx) : v_(cx) {}
 
-    bool init(unsigned argc, bool construct = false) {
+    bool init(unsigned argc, bool construct) {
         MOZ_ASSERT(2 + argc + construct > argc);  // no overflow
         if (!v_.resize(2 + argc + construct))
             return false;
-        ImplicitCast(*this) = CallArgsFromVp(argc, v_.begin());
-        // Set the internal flag, since we are not initializing from a made array
+
+        *static_cast(this) = CallArgsFromVp(argc, v_.begin());
         constructing_ = construct;
         return true;
     }
 };
 
+} // namespace detail
+
+class InvokeArgs : public detail::GenericInvokeArgs
+{
+  public:
+    explicit InvokeArgs(JSContext* cx) : detail::GenericInvokeArgs(cx) {}
+
+    bool init(unsigned argc) {
+        return detail::GenericInvokeArgs::init(argc, false);
+    }
+};
+
+class ConstructArgs : public detail::GenericInvokeArgs
+{
+  public:
+    explicit ConstructArgs(JSContext* cx) : detail::GenericInvokeArgs(cx) {}
+
+    bool init(unsigned argc) {
+        return detail::GenericInvokeArgs::init(argc, true);
+    }
+};
+
+template 
+inline bool
+FillArgumentsFromArraylike(JSContext* cx, Args& args, const Arraylike& arraylike)
+{
+    uint32_t len = arraylike.length();
+    if (!args.init(len))
+        return false;
+
+    for (uint32_t i = 0; i < len; i++)
+        args[i].set(arraylike[i]);
+
+    return true;
+}
+
 template <>
 struct DefaultHasher {
     typedef AbstractFramePtr Lookup;
diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp
index 3d3b54f38b..7fce99290a 100644
--- a/js/src/vm/UbiNode.cpp
+++ b/js/src/vm/UbiNode.cpp
@@ -65,9 +65,9 @@ struct Node::ConstructFunctor : public js::BoolDefaultAdaptor {
     template  bool operator()(T* t, Node* node) { node->construct(t); return true; }
 };
 
-Node::Node(JS::TraceKind kind, void* ptr)
+Node::Node(const JS::GCCellPtr &thing)
 {
-    CallTyped(ConstructFunctor(), ptr, kind, this);
+    js::gc::CallTyped(ConstructFunctor(), thing.asCell(), thing.kind(), this);
 }
 
 Node::Node(HandleValue value)
@@ -111,7 +111,7 @@ class SimpleEdgeVectorTracer : public JS::CallbackTracer {
     // True if we should populate the edge's names.
     bool wantNames;
 
-    void trace(void** thingp, JS::TraceKind kind) {
+    void onChild(const JS::GCCellPtr& thing) override {
         if (!okay)
             return;
 
@@ -139,7 +139,7 @@ class SimpleEdgeVectorTracer : public JS::CallbackTracer {
         // ownership of name; if the append succeeds, the vector element
         // then takes ownership; if the append fails, then the temporary
         // retains it, and its destructor will free it.
-        if (!vec->append(mozilla::Move(SimpleEdge(name16, Node(kind, *thingp))))) {
+        if (!vec->append(mozilla::Move(SimpleEdge(name16, Node(thing))))) {
             okay = false;
             return;
         }
diff --git a/modules/libjar/nsJARChannel.cpp b/modules/libjar/nsJARChannel.cpp
index 9c23e9426e..e41e6218b9 100644
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -695,6 +695,16 @@ nsJARChannel::OverrideSecurityInfo(nsISupports* aSecurityInfo)
   return NS_OK;
 }
 
+void
+nsJARChannel::OverrideURI(nsIURI* aRedirectedURI)
+{
+  MOZ_RELEASE_ASSERT(mLoadFlags & LOAD_REPLACE,
+                     "This can only happen if the LOAD_REPLACE flag is set");
+  MOZ_RELEASE_ASSERT(ShouldIntercept(),
+                     "This can only be called on channels that can be intercepted");
+  mAppURI = aRedirectedURI;
+}
+
 NS_IMETHODIMP
 nsJARChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
 {
diff --git a/modules/libjar/nsJARChannel.h b/modules/libjar/nsJARChannel.h
index d4866a34f8..338c2f1db0 100644
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -62,6 +62,7 @@ public:
     nsresult Init(nsIURI *uri);
 
     nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo);
+    void OverrideURI(nsIURI* aRedirectedURI);
 
 private:
     virtual ~nsJARChannel();
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
index 5254cb30c5..b003ab3bc6 100644
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -1503,6 +1503,16 @@ HttpBaseChannel::OverrideSecurityInfo(nsISupports* aSecurityInfo)
   return NS_OK;
 }
 
+void
+HttpBaseChannel::OverrideURI(nsIURI* aRedirectedURI)
+{
+  MOZ_RELEASE_ASSERT(mLoadFlags & LOAD_REPLACE,
+                     "This can only happen if the LOAD_REPLACE flag is set");
+  MOZ_RELEASE_ASSERT(ShouldIntercept(),
+                     "This can only be called on channels that can be intercepted");
+  mURI = aRedirectedURI;
+}
+
 NS_IMETHODIMP
 HttpBaseChannel::IsNoStoreResponse(bool *value)
 {
diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h
index 130f5a45aa..1ae60f99cb 100644
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -248,6 +248,7 @@ public:
     const NetAddr& GetPeerAddr() { return mPeerAddr; }
 
     nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo);
+    void OverrideURI(nsIURI* aRedirectedURI);
 
 public: /* Necko internal use only... */
     bool IsNavigation();
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
index ddc275050d..7d02d528f1 100644
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -2780,7 +2780,15 @@ nsHttpChannel::OpenCacheEntry(bool isHttps)
         NS_ENSURE_SUCCESS(rv, rv);
     }
     else {
-        openURI = mURI;
+        // In the case of intercepted channels, we need to construct the cache
+        // entry key based on the original URI, so that in case the intercepted
+        // channel is redirected, the cache entry key before and after the
+        // redirect is the same.
+        if (PossiblyIntercepted()) {
+            openURI = mOriginalURI;
+        } else {
+            openURI = mURI;
+        }
     }
 
     uint32_t appId = info->AppId();
@@ -2858,6 +2866,11 @@ nsHttpChannel::OpenCacheEntry(bool isHttps)
                 new InterceptedChannelChrome(this, controller, entry);
         intercepted->NotifyController();
     } else {
+        if (mInterceptCache == INTERCEPTED) {
+            DebugOnly exists;
+            MOZ_ASSERT(NS_SUCCEEDED(cacheStorage->Exists(openURI, extension, &exists)) && exists,
+                       "The entry must exist in the cache after we create it here");
+        }
         rv = cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags, this);
         NS_ENSURE_SUCCESS(rv, rv);
     }
diff --git a/toolkit/components/aboutperformance/content/aboutPerformance.js b/toolkit/components/aboutperformance/content/aboutPerformance.js
index d4f22c384b..0b3d2a9dfc 100644
--- a/toolkit/components/aboutperformance/content/aboutPerformance.js
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.js
@@ -86,7 +86,10 @@ let AutoUpdate = {
 };
 
 let State = {
-  _monitor: PerformanceStats.getMonitor(["jank", "cpow", "ticks"]),
+  _monitor: PerformanceStats.getMonitor([
+    "jank", "cpow", "ticks",
+    "jank-content", "cpow-content", "ticks-content",
+  ]),
 
   /**
    * @type{PerformanceData}
@@ -244,7 +247,7 @@ let updateLiveData = Task.async(function*() {
     let headerElt = document.createElement("tr");
     dataElt.appendChild(headerElt);
     headerElt.classList.add("header");
-    for (let column of [...MEASURES, {key: "name", name: ""}]) {
+    for (let column of [...MEASURES, {key: "name", name: ""}, {key: "process", name: ""}]) {
       let el = document.createElement("td");
       el.classList.add(column.key);
       el.textContent = column.label;
@@ -280,20 +283,37 @@ let updateLiveData = Task.async(function*() {
         el.textContent = value;
       }
 
-      // Name
-      let el = document.createElement("td");
-      let id = item.id;
-      el.classList.add("contents");
-      el.classList.add("name");
-      row.appendChild(el);
-      if (item.addonId) {
-        let _el = el;
-        let _item = item;
-        AddonManager.getAddonByID(item.addonId, a => {
-          _el.textContent = a ? a.name : _item.name
-        });
-      } else {
-        el.textContent = item.title || item.name;
+      {
+        // Name
+        let el = document.createElement("td");
+        let id = item.id;
+        el.classList.add("contents");
+        el.classList.add("name");
+        row.appendChild(el);
+        if (item.addonId) {
+          let _el = el;
+          let _item = item;
+          AddonManager.getAddonByID(item.addonId, a => {
+            _el.textContent = a ? a.name : _item.name
+          });
+        } else {
+          el.textContent = item.title || item.name;
+        }
+      }
+
+      {
+        // Process information.
+        let el = document.createElement("td");
+        el.classList.add("contents");
+        el.classList.add("process");
+        row.appendChild(el);
+        if (item.isChildProcess) {
+          el.textContent = "(child)";
+          row.classList.add("child");
+        } else {
+          el.textContent = "(parent)";
+          row.classList.add("parent");
+        }
       }
     }
   } catch (ex) {
diff --git a/toolkit/components/aboutperformance/tests/browser/browser.ini b/toolkit/components/aboutperformance/tests/browser/browser.ini
index 87eb74f607..92f1d98e6a 100644
--- a/toolkit/components/aboutperformance/tests/browser/browser.ini
+++ b/toolkit/components/aboutperformance/tests/browser/browser.ini
@@ -6,4 +6,3 @@ support-files =
   browser_compartments_script.js
 
 [browser_aboutperformance.js]
-skip-if = e10s # Feature not implemented yet – bug 1140310
diff --git a/toolkit/components/perfmonitoring/PerformanceStats-content.js b/toolkit/components/perfmonitoring/PerformanceStats-content.js
new file mode 100644
index 0000000000..79ca340efb
--- /dev/null
+++ b/toolkit/components/perfmonitoring/PerformanceStats-content.js
@@ -0,0 +1,144 @@
+/* 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/. */
+
+/**
+ * A proxy implementing communication between the PerformanceStats.jsm modules
+ * of the parent and children processes.
+ *
+ * This script is loaded in all processes but is essentially a NOOP in the
+ * parent process.
+ */
+
+"use strict";
+
+const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+XPCOMUtils.defineLazyModuleGetter(this, "PerformanceStats",
+  "resource://gre/modules/PerformanceStats.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
+
+/**
+ * A global performance monitor used by this process.
+ *
+ * For the sake of simplicity, rather than attempting to map each PerformanceMonitor
+ * of the parent to a PerformanceMonitor in each child process, we maintain a single
+ * PerformanceMonitor in each child process. Probes activation/deactivation for this
+ * monitor is controlled by the activation/deactivation of probes marked as "-content"
+ * in the parent.
+ *
+ * In the parent, this is always an empty monitor.
+ */
+let gMonitor = PerformanceStats.getMonitor([]);
+
+/**
+ * `true` if this is a content process, `false` otherwise.
+ */
+let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+
+/**
+ * Handle message `performance-stats-service-acquire`: ensure that the global
+ * monitor has a given probe. This message must be sent by the parent process
+ * whenever a probe is activated application-wide.
+ *
+ * Note that we may miss acquire messages if they are sent before this process is
+ * launched. For this reason, `performance-stats-service-collect` automatically
+ * re-acquires probes if it realizes that they are missing.
+ *
+ * This operation is a NOOP on the parent process.
+ *
+ * @param {{payload: Array}} msg.data The message received. `payload`
+ * must be an array of probe names.
+ */
+Services.cpmm.addMessageListener("performance-stats-service-acquire", function(msg) {
+  if (!isContent) {
+    return;
+  }
+  let name = msg.data.payload;
+  ensureAcquired(name);
+});
+
+/**
+ * Handle message `performance-stats-service-release`: release a given probe
+ * from the global monitor. This message must be sent by the parent process
+ * whenever a probe is deactivated application-wide.
+ *
+ * Note that we may miss release messages if they are sent before this process is
+ * launched. This is ok, as probes are inactive by default: if we miss the release
+ * message, we have already missed the acquire message, and the effect of both
+ * messages together is to reset to the default state.
+ *
+ * This operation is a NOOP on the parent process.
+ *
+ * @param {{payload: Array}} msg.data The message received. `payload`
+ * must be an array of probe names.
+ */
+Services.cpmm.addMessageListener("performance-stats-service-release", function(msg) {
+  if (!isContent) {
+    return;
+  }
+  // Keep only the probes that do not appear in the payload
+  let probes = gMonitor.getProbeNames
+    .filter(x => msg.data.payload.indexOf(x) == -1);
+  gMonitor = PerformanceStats.getMonitor(probes);
+});
+
+/**
+ * Ensure that this process has all the probes it needs.
+ *
+ * @param {Array} probeNames The name of all probes needed by the
+ * process.
+ */
+function ensureAcquired(probeNames) {
+  let alreadyAcquired = gMonitor.probeNames;
+
+  // Algorithm is O(n^2) because we expect that n ≤ 3.
+  let shouldAcquire = [];
+  for (let probeName of probeNames) {
+    if (alreadyAcquired.indexOf(probeName) == -1) {
+      shouldAcquire.push(probeName)
+    }
+  }
+
+  if (shouldAcquire.length == 0) {
+    return;
+  }
+  gMonitor = PerformanceStats.getMonitor([...alreadyAcquired, ...shouldAcquire]);
+}
+
+/**
+ * Handle message `performance-stats-service-collected`: collect the data
+ * obtained by the monitor. This message must be sent by the parent process
+ * whenever we grab a performance snapshot of the application.
+ *
+ * This operation provides `null` on the parent process.
+ *
+ * @param {{data: {payload: Array}}} msg The message received. `payload`
+ * must be an array of probe names.
+ */
+Services.cpmm.addMessageListener("performance-stats-service-collect", Task.async(function*(msg) {
+  let {id, payload: {probeNames}} = msg.data;
+  if (!isContent) {
+    // This message was sent by the parent process to itself.
+    // As per protocol, respond `null`.
+    Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
+      id,
+      data: null
+    });
+    return;
+  }
+
+  // We may have missed acquire messages if the process was loaded too late.
+  // Catch up now.
+  ensureAcquired(probeNames);
+
+  // Collect and return data.
+  let data = yield gMonitor.promiseSnapshot({probeNames});
+  Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
+    id,
+    data
+  });
+}));
diff --git a/toolkit/components/perfmonitoring/PerformanceStats.jsm b/toolkit/components/perfmonitoring/PerformanceStats.jsm
index 8ff56cf47e..cee430374c 100644
--- a/toolkit/components/perfmonitoring/PerformanceStats.jsm
+++ b/toolkit/components/perfmonitoring/PerformanceStats.jsm
@@ -23,6 +23,14 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+  "resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
+  "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
+  "resource://gre/modules/Timer.jsm");
 
 // The nsIPerformanceStatsService provides lower-level
 // access to SpiderMonkey and the probes.
@@ -37,14 +45,17 @@ XPCOMUtils.defineLazyServiceGetter(this, "finalizer",
   Ci.nsIFinalizationWitnessService
 );
 
-
 // The topic used to notify that a PerformanceMonitor has been garbage-collected
 // and that we can release/close the probes it holds.
 const FINALIZATION_TOPIC = "performancemonitor-finalize";
 
-const PROPERTIES_META_IMMUTABLE = ["name", "addonId", "isSystem", "groupId"];
-const PROPERTIES_META = [...PROPERTIES_META_IMMUTABLE, "windowId", "title"];
+const PROPERTIES_META_IMMUTABLE = ["addonId", "isSystem", "isChildProcess", "groupId"];
+const PROPERTIES_META = [...PROPERTIES_META_IMMUTABLE, "windowId", "title", "name"];
 
+// How long we wait for children processes to respond.
+const MAX_WAIT_FOR_CHILD_PROCESS_MS = 5000;
+
+let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
 /**
  * Access to a low-level performance probe.
  *
@@ -125,14 +136,24 @@ Probe.prototype = {
    * @return {object} An object representing `a - b`. If `b` is
    * `null`, this is `a`.
    */
-  substract: function(a, b) {
+  subtract: function(a, b) {
     if (a == null) {
       throw new TypeError();
     }
     if (b == null) {
       return a;
     }
-    return this._impl.substract(a, b);
+    return this._impl.subtract(a, b);
+  },
+
+  importChildCompartments: function(parent, children) {
+    if (!Array.isArray(children)) {
+      throw new TypeError();
+    }
+    if (!parent || !(parent instanceof PerformanceData)) {
+      throw new TypeError();
+    }
+    return this._impl.importChildCompartments(parent, children);
   },
 
   /**
@@ -204,7 +225,7 @@ let Probes = {
       }
       return true;
     },
-    substract: function(a, b) {
+    subtract: function(a, b) {
       // invariant: `a` and `b` are both non-null
       let result = {
         totalUserTime: a.totalUserTime - b.totalUserTime,
@@ -217,7 +238,8 @@ let Probes = {
       }
       result.longestDuration = lastNonZero(result.durations);
       return result;
-    }
+    },
+    importChildCompartments: function() { /* nothing to do */ },
   }),
 
   /**
@@ -243,11 +265,12 @@ let Probes = {
     isEqual: function(a, b) {
       return a.totalCPOWTime == b.totalCPOWTime;
     },
-    substract: function(a, b) {
+    subtract: function(a, b) {
       return {
         totalCPOWTime: a.totalCPOWTime - b.totalCPOWTime
       };
-    }
+    },
+    importChildCompartments: function() { /* nothing to do */ },
   }),
 
   /**
@@ -272,12 +295,109 @@ let Probes = {
     isEqual: function(a, b) {
       return a.ticks == b.ticks;
     },
-    substract: function(a, b) {
+    subtract: function(a, b) {
       return {
         ticks: a.ticks - b.ticks
       };
-    }
+    },
+    importChildCompartments: function() { /* nothing to do */ },
   }),
+
+  "jank-content": new Probe("jank-content", {
+    _isActive: false,
+    set isActive(x) {
+      this._isActive = x;
+      if (x) {
+        Process.broadcast("acquire", ["jank"]);
+      } else {
+        Process.broadcast("release", ["jank"]);
+      }
+    },
+    get isActive() {
+      return this._isActive;
+    },
+    extract: function(xpcom) {
+      return {};
+    },
+    isEqual: function(a, b) {
+      return true;
+    },
+    subtract: function(a, b) {
+      return null;
+    },
+    importChildCompartments: function() { /* nothing to do */ },
+  }),
+
+  "cpow-content": new Probe("cpow-content", {
+    _isActive: false,
+    set isActive(x) {
+      this._isActive = x;
+      if (x) {
+        Process.broadcast("acquire", ["cpow"]);
+      } else {
+        Process.broadcast("release", ["cpow"]);
+      }
+    },
+    get isActive() {
+      return this._isActive;
+    },
+    extract: function(xpcom) {
+      return {};
+    },
+    isEqual: function(a, b) {
+      return true;
+    },
+    subtract: function(a, b) {
+      return null;
+    },
+    importChildCompartments: function() { /* nothing to do */ },
+  }),
+
+  "ticks-content": new Probe("ticks-content", {
+    _isActive: false,
+    set isActive(x) {
+      this._isActive = x;
+      if (x) {
+        Process.broadcast("acquire", ["ticks"]);
+      } else {
+        Process.broadcast("release", ["ticks"]);
+      }
+    },
+    get isActive() {
+      return this._isActive;
+    },
+    extract: function(xpcom) {
+      return {};
+    },
+    isEqual: function(a, b) {
+      return true;
+    },
+    subtract: function(a, b) {
+      return null;
+    },
+    importChildCompartments: function() { /* nothing to do */ },
+  }),
+
+  compartments: new Probe("compartments", {
+    set isActive(x) {
+      performanceStatsService.isMonitoringPerCompartment = x;
+    },
+    get isActive() {
+      return performanceStatsService.isMonitoringPerCompartment;
+    },
+    extract: function(xpcom) {
+      return null;
+    },
+    isEqual: function(a, b) {
+      return true;
+    },
+    subtract: function(a, b) {
+      return true;
+    },
+    importChildCompartments: function(parent, children) {
+      parent.children = children;
+    },
+  })
 };
 
 
@@ -305,6 +425,13 @@ function PerformanceMonitor(probes) {
   PerformanceMonitor._monitors.set(this._id, probes);
 }
 PerformanceMonitor.prototype = {
+  /**
+   * The names of probes activated in this monitor.
+   */
+  get probeNames() {
+    return [for (probe of this._probes) probe.name];
+  },
+
   /**
    * Return asynchronously a snapshot with the data
    * for each probe monitored by this PerformanceMonitor.
@@ -317,7 +444,7 @@ PerformanceMonitor.prototype = {
    * will return a `Snapshot` in which all values are 0. For most uses,
    * the appropriate scenario is to perform a first call to `promiseSnapshot()`
    * to obtain a baseline, and then watch evolution of the values by calling
-   * `promiseSnapshot()` and `substract()`.
+   * `promiseSnapshot()` and `subtract()`.
    *
    * On the other hand, numeric values are also monotonic across several instances
    * of a PerformanceMonitor with the same probes. 
@@ -330,18 +457,45 @@ PerformanceMonitor.prototype = {
    *
    *  // all values of `snapshot2` are greater or equal to values of `snapshot1`.
    *
+   * @param {object} options If provided, an object that may contain the following
+   *   fields:
+   *   {Array} probeNames The subset of probes to use for this snapshot.
+   *      These probes must be a subset of the probes active in the monitor.
+   *
    * @return {Promise}
    * @resolve {Snapshot}
    */
-  promiseSnapshot: function() {
+  promiseSnapshot: function(options = null) {
     if (!this._finalizer) {
       throw new Error("dispose() has already been called, this PerformanceMonitor is not usable anymore");
     }
-    // Current implementation is actually synchronous.
-    return Promise.resolve().then(() => new Snapshot({
-      xpcom: performanceStatsService.getSnapshot(),
-      probes: this._probes
-    }));
+    let probes;
+    if (options && options.probeNames || undefined) {
+      if (!Array.isArray(options.probeNames)) {
+        throw new TypeError();
+      }
+      // Make sure that we only request probes that we have
+      for (let probeName of options.probeNames) {
+        let probe = this._probes.find(probe => probe.name == probeName);
+        if (!probe) {
+          throw new TypeError(`I need probe ${probeName} but I only have ${this.probeNames}`);
+        }
+        if (!probes) {
+          probes = [];
+        }
+        probes.push(probe);
+      }
+    } else {
+      probes = this._probes;
+    }
+    return Task.spawn(function*() {
+      let collected = yield Process.broadcastAndCollect("collect", {probeNames: [for (probe of probes) probe.name]});
+      return new Snapshot({
+        xpcom: performanceStatsService.getSnapshot(),
+        childProcesses: collected,
+        probes
+      });
+    });
   },
 
   /**
@@ -381,7 +535,7 @@ PerformanceMonitor.make = function(probeNames) {
   let probes = [];
   for (let probeName of probeNames) {
     if (!(probeName in Probes)) {
-      throw new TypeError("Probe not implemented: " + k);
+      throw new TypeError("Probe not implemented: " + probeName);
     }
     probes.push(Probes[probeName]);
   }
@@ -467,12 +621,24 @@ this.PerformanceStats = {
  * @field {object|undefined} cpow See the documentation of probe "cpow".
  *   `undefined` if this probe is not active.
  */
-function PerformanceData({xpcom, probes}) {
-  for (let k of PROPERTIES_META) {
-    this[k] = xpcom[k];
+function PerformanceData({xpcom, json, probes}) {
+  if (xpcom && json) {
+    throw new TypeError("Cannot import both xpcom and json data");
   }
-  for (let probe of probes) {
-    this[probe.name] = probe.extract(xpcom);
+  let source = xpcom || json;
+  for (let k of PROPERTIES_META) {
+    this[k] = source[k];
+  }
+  if (xpcom) {
+    for (let probe of probes) {
+      this[probe.name] = probe.extract(xpcom);
+    }
+    this.isChildProcess = false;
+  } else {
+    for (let probe of probes) {
+      this[probe.name] = json[probe.name];
+    }
+    this.isChildProcess = true;
   }
 }
 PerformanceData.prototype = {
@@ -519,20 +685,177 @@ function PerformanceDiff(current, old = null) {
   for (let probeName of Object.keys(Probes)) {
     let other = old ? old[probeName] : null;
     if (probeName in current) {
-      this[probeName] = Probes[probeName].substract(current[probeName], other);
+      this[probeName] = Probes[probeName].subtract(current[probeName], other);
     }
   }
 }
 
 /**
- * A snapshot of the performance usage of the process.
+ * A snapshot of the performance usage of the application.
+ *
+ * @param {nsIPerformanceSnapshot} xpcom The data acquired from this process.
+ * @param {Array} childProcesses The data acquired from children processes.
+ * @param {Array} probes The active probes.
  */
-function Snapshot({xpcom, probes}) {
+function Snapshot({xpcom, childProcesses, probes}) {
   this.componentsData = [];
-  let enumeration = xpcom.getComponentsData().enumerate();
-  while (enumeration.hasMoreElements()) {
-    let stat = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats);
-    this.componentsData.push(new PerformanceData({xpcom: stat, probes}));
+
+  // Current process
+  if (xpcom) {
+    let children = new Map();
+    let enumeration = xpcom.getComponentsData().enumerate();
+    while (enumeration.hasMoreElements()) {
+      let xpcom = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats);
+      let stat = new PerformanceData({xpcom, probes});
+      if (!stat.parentId) {
+        this.componentsData.push(stat);
+      } else {
+        let siblings = children.get(stat.parentId);
+        if (!siblings) {
+          children.set(stat.parentId, (siblings = []));
+        }
+        siblings.push(stat);
+      }
+    }
+
+    for (let parent of this.componentsData) {
+      for (let probe of probes) {
+        probe.importChildCompartments(parent, children.get(parent.groupId) || []);
+      }
+    }
   }
+
+  // Child processes
+  if (childProcesses) {
+    for (let {componentsData} of childProcesses) {
+      // We are only interested in `componentsData` for the time being.
+      for (let json of componentsData) {
+        this.componentsData.push(new PerformanceData({json, probes}));
+      }
+    }
+  }
+
   this.processData = new PerformanceData({xpcom: xpcom.getProcessData(), probes});
 }
+
+/**
+ * Communication with other processes
+ */
+let Process = {
+  // `true` once communications have been initialized
+  _initialized: false,
+
+  // the message manager
+  _loader: null,
+
+  // a counter used to match responses to requests
+  _idcounter: 0,
+
+  /**
+   * If we are in a child process, return `null`.
+   * Otherwise, return the global parent process message manager
+   * and load the script to connect to children processes.
+   */
+  get loader() {
+    if (this._initialized) {
+      return this._loader;
+    }
+    this._initialized = true;
+    this._loader = Services.ppmm;
+    if (!this._loader) {
+      // We are in a child process.
+      return null;
+    }
+    this._loader.loadProcessScript("resource://gre/modules/PerformanceStats-content.js",
+      true/*including future processes*/);
+    return this._loader;
+  },
+
+  /**
+   * Broadcast a message to all children processes.
+   *
+   * NOOP if we are in a child process.
+   */
+  broadcast: function(topic, payload) {
+    if (!this.loader) {
+      return;
+    }
+    this.loader.broadcastAsyncMessage("performance-stats-service-" + topic, {payload});
+  },
+
+  /**
+   * Brodcast a message to all children processes and wait for answer.
+   *
+   * NOOP if we are in a child process, or if we have no children processes,
+   * in which case we return `undefined`.
+   *
+   * @return {undefined} If we have no children processes, in particular
+   * if we are in a child process.
+   * @return {Promise>} If we have children processes, an
+   * array of objects with a structure similar to PerformanceData. Note
+   * that the array may be empty if no child process responded.
+   */
+  broadcastAndCollect: Task.async(function*(topic, payload) {
+    if (!this.loader || this.loader.childCount == 1) {
+      return undefined;
+    }
+    const TOPIC = "performance-stats-service-" + topic;
+    let id = this._idcounter++;
+
+    // The number of responses we are expecting. Note that we may
+    // not receive all responses if a process is too long to respond.
+    let expecting = this.loader.childCount;
+
+    // The responses we have collected, in arbitrary order.
+    let collected = [];
+    let deferred = PromiseUtils.defer();
+
+    // The content script may be loaded more than once (bug 1184115).
+    // To avoid double-responses, we keep track of who has already responded.
+    // Note that we could it on the other end, at the expense of implementing
+    // an additional .jsm just for that purpose.
+    let responders = new Set();
+    let observer = function({data, target}) {
+      if (data.id != id) {
+        // Collision between two collections,
+        // ignore the other one.
+        return;
+      }
+      if (responders.has(target)) {
+        return;
+      }
+      responders.add(target);
+      if (data.data) {
+        collected.push(data.data)
+      }
+      if (--expecting > 0) {
+        // We are still waiting for at least one response.
+        return;
+      }
+      deferred.resolve();
+    };
+    this.loader.addMessageListener(TOPIC, observer);
+    this.loader.broadcastAsyncMessage(
+      TOPIC,
+      {id, payload}
+    );
+
+    // Processes can die/freeze/be busy loading a page..., so don't expect
+    // that they will always respond.
+    let timeout = setTimeout(() => {
+      if (expecting == 0) {
+        return;
+      }
+      deferred.resolve();
+    }, MAX_WAIT_FOR_CHILD_PROCESS_MS);
+
+    deferred.promise.then(() => {
+      clearTimeout(timeout);
+    });
+
+    yield deferred.promise;
+    this.loader.removeMessageListener(TOPIC, observer);
+
+    return collected;
+  })
+};
diff --git a/toolkit/components/perfmonitoring/moz.build b/toolkit/components/perfmonitoring/moz.build
index a649065295..a0ed43244e 100644
--- a/toolkit/components/perfmonitoring/moz.build
+++ b/toolkit/components/perfmonitoring/moz.build
@@ -13,6 +13,7 @@ XPIDL_MODULE = 'toolkit_perfmonitoring'
 
 EXTRA_JS_MODULES += [
     'AddonWatcher.jsm',
+    'PerformanceStats-content.js',
     'PerformanceStats.jsm',
 ]
 
diff --git a/toolkit/components/perfmonitoring/nsIPerformanceStats.idl b/toolkit/components/perfmonitoring/nsIPerformanceStats.idl
index 272178e253..dc1d11ba84 100644
--- a/toolkit/components/perfmonitoring/nsIPerformanceStats.idl
+++ b/toolkit/components/perfmonitoring/nsIPerformanceStats.idl
@@ -17,12 +17,12 @@
 
 /**
  * Snapshot of the performance of a component, e.g. an add-on, a web
- * page, system built-ins, or the entire process itself.
+ * page, system built-ins, a module or the entire process itself.
  *
  * All values are monotonic and are updated only when
  * `nsIPerformanceStatsService.isStopwatchActive` is `true`.
  */
-[scriptable, uuid(47f8d36d-1d67-43cb-befd-d2f4720ac568)]
+[scriptable, uuid(1bc2d016-e9ae-4186-97c6-9478eddda245)]
 interface nsIPerformanceStats: nsISupports {
   /**
    * An identifier unique to the component.
@@ -32,6 +32,16 @@ interface nsIPerformanceStats: nsISupports {
    */
   readonly attribute AString groupId;
 
+  /**
+   * If this component is part of a larger component, the larger
+   * component. Otherwise, null.
+   *
+   * As of this writing, there can be at most two levels of components:
+   * - compartments (a single module, iframe, etc.);
+   * - groups (an entire add-on, an entire webpage, etc.).
+   */
+  readonly attribute AString parentId;
+
   /**
    * The name of the component:
    * - for the process itself, "";
@@ -112,7 +122,7 @@ interface nsIPerformanceSnapshot: nsISupports {
   nsIPerformanceStats getProcessData();
 };
 
-[scriptable, builtinclass, uuid(0469e6af-95c3-4961-a385-4bc009128939)]
+[scriptable, builtinclass, uuid(60973d54-13e2-455c-a3c6-84dea5dfc8b9)]
 interface nsIPerformanceStatsService : nsISupports {
   /**
    * `true` if we should monitor CPOW, `false` otherwise.
@@ -124,6 +134,13 @@ interface nsIPerformanceStatsService : nsISupports {
    */
   [implicit_jscontext] attribute bool isMonitoringJank;
 
+  /**
+   * `true` if all compartments need to be monitored individually,
+   * `false` if only performance groups (i.e. entire add-ons, entire
+   * webpages, etc.) need to be monitored.
+   */
+  [implicit_jscontext] attribute bool isMonitoringPerCompartment;
+
   /**
    * Capture a snapshot of the performance data.
    */
diff --git a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
index 27c6d19a43..30a39ab291 100644
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
@@ -30,6 +30,7 @@
 class nsPerformanceStats: public nsIPerformanceStats {
 public:
   nsPerformanceStats(const nsAString& aName,
+                     nsIPerformanceStats* aParent,
                      const nsAString& aGroupId,
                      const nsAString& aAddonId,
                      const nsAString& aTitle,
@@ -44,6 +45,10 @@ public:
     , mIsSystem(aIsSystem)
     , mPerformanceData(aPerformanceData)
   {
+    if (aParent) {
+      mozilla::DebugOnly rv = aParent->GetGroupId(mParentId);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
   }
   explicit nsPerformanceStats() {}
 
@@ -61,6 +66,12 @@ public:
     return NS_OK;
   };
 
+  /* readonly attribute AString parentId; */
+  NS_IMETHOD GetParentId(nsAString& aParentId) override {
+    aParentId.Assign(mParentId);
+    return NS_OK;
+  };
+
   /* readonly attribute AString addonId; */
   NS_IMETHOD GetAddonId(nsAString& aAddonId) override {
     aAddonId.Assign(mAddonId);
@@ -124,6 +135,7 @@ public:
 
 private:
   nsString mName;
+  nsString mParentId;
   nsString mGroupId;
   nsString mAddonId;
   nsString mTitle;
@@ -159,13 +171,18 @@ private:
    * entire process, rather than the statistics for a specific set of
    * compartments.
    */
-  already_AddRefed ImportStats(JSContext* cx, const js::PerformanceData& data, uint64_t uid);
+  already_AddRefed ImportStats(JSContext* cx, const js::PerformanceData& data, uint64_t uid, nsIPerformanceStats* parent);
 
   /**
    * Callbacks for iterating through the `PerformanceStats` of a runtime.
    */
-  bool IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats, uint64_t uid);
-  static bool IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, uint64_t uid, void* self);
+  bool IterPerformanceStatsCallbackInternal(JSContext* cx,
+                                            const js::PerformanceData& ownStats, const uint64_t ownId,
+                                            const uint64_t* parentId);
+  static bool IterPerformanceStatsCallback(JSContext* cx,
+                                           const js::PerformanceData& ownStats, const uint64_t ownId,
+                                           const uint64_t* parentId,
+                                           void* self);
 
   // If the context represents a window, extract the title and window ID.
   // Otherwise, extract "" and 0.
@@ -175,6 +192,11 @@ private:
   void GetGroupId(JSContext*,
                   uint64_t uid,
                   nsString& groupId);
+
+  static void GetName(JSContext*,
+                      JS::Handle global,
+                      nsString& name);
+
   // If the context presents an add-on, extract the addon ID.
   // Otherwise, extract "".
   static void GetAddonId(JSContext*,
@@ -188,6 +210,7 @@ private:
 private:
   nsCOMArray mComponentsData;
   nsCOMPtr mProcessData;
+  nsBaseHashtable, nsCOMPtr > mCachedStats;
   uint64_t mProcessId;
 };
 
@@ -260,7 +283,7 @@ nsPerformanceSnapshot::GetGroupId(JSContext* cx,
 
   groupId.AssignLiteral("process: ");
   groupId.AppendInt(mProcessId);
-  groupId.AssignLiteral(", thread: ");
+  groupId.AppendLiteral(", thread: ");
   groupId.AppendInt(runtimeId);
   groupId.AppendLiteral(", group: ");
   groupId.AppendInt(uid);
@@ -273,8 +296,44 @@ nsPerformanceSnapshot::GetIsSystem(JSContext*,
   return nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global));
 }
 
+/* static */
+void
+nsPerformanceSnapshot::GetName(JSContext* cx,
+                               JS::Handle global,
+                               nsString& name)
+{
+  nsAutoCString cname;
+  do {
+    // Attempt to use the URL as name.
+    nsCOMPtr principal = nsContentUtils::ObjectPrincipal(global);
+    if (!principal) {
+      break;
+    }
+
+    nsCOMPtr uri;
+    nsresult rv = principal->GetURI(getter_AddRefs(uri));
+    if (NS_FAILED(rv) || !uri) {
+      break;
+    }
+
+    rv = uri->GetSpec(cname);
+    if (NS_FAILED(rv)) {
+      break;
+    }
+
+    name.Assign(NS_ConvertUTF8toUTF16(cname));
+    return;
+  } while(false);
+  xpc::GetCurrentCompartmentName(cx, cname);
+  name.Assign(NS_ConvertUTF8toUTF16(cname));
+}
+
 already_AddRefed
-nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& performance, const uint64_t uid) {
+nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& performance, const uint64_t uid, nsIPerformanceStats* parent) {
+  if (performance.ticks == 0) {
+    // Ignore compartments with no activity.
+    return nullptr;
+  }
   JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
 
   if (!global) {
@@ -293,28 +352,34 @@ nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& per
   uint64_t windowId;
   GetWindowData(cx, title, &windowId);
 
-  nsAutoString name;
-  nsAutoCString cname;
-  xpc::GetCurrentCompartmentName(cx, cname);
-  name.Assign(NS_ConvertUTF8toUTF16(cname));
+  nsString name;
+  GetName(cx, global, name);
 
   bool isSystem = GetIsSystem(cx, global);
 
   nsCOMPtr result =
-    new nsPerformanceStats(name, groupId, addonId, title, windowId, isSystem, performance);
+    new nsPerformanceStats(name, parent, groupId, addonId, title, windowId, isSystem, performance);
   return result.forget();
 }
 
 /*static*/ bool
-nsPerformanceSnapshot::IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, const uint64_t uid, void* self) {
-  return reinterpret_cast(self)->IterPerformanceStatsCallbackInternal(cx, stats, uid);
+nsPerformanceSnapshot::IterPerformanceStatsCallback(JSContext* cx,
+                                                    const js::PerformanceData& stats, const uint64_t id,
+                                                    const uint64_t* parentId,
+                                                    void* self) {
+  return reinterpret_cast(self)->IterPerformanceStatsCallbackInternal(cx, stats, id, parentId);
 }
 
 bool
-nsPerformanceSnapshot::IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats, const uint64_t uid) {
-  nsCOMPtr result = ImportStats(cx, stats, uid);
+nsPerformanceSnapshot::IterPerformanceStatsCallbackInternal(JSContext* cx,
+                                                            const js::PerformanceData& stats, const uint64_t id,
+                                                            const uint64_t* parentId) {
+
+  nsCOMPtr parent = parentId ? mCachedStats.Get(*parentId) : nullptr;
+  nsCOMPtr result = ImportStats(cx, stats, id, parent);
   if (result) {
     mComponentsData.AppendElement(result);
+    mCachedStats.Put(id, result);
   }
 
   return true;
@@ -328,8 +393,12 @@ nsPerformanceSnapshot::Init(JSContext* cx, uint64_t processId) {
     return NS_ERROR_UNEXPECTED;
   }
 
+  nsAutoString processGroupId;
+  processGroupId.AssignLiteral("process: ");
+  processGroupId.AppendInt(processId);
   mProcessData = new nsPerformanceStats(NS_LITERAL_STRING(""), // name
-                                        NS_LITERAL_STRING(""), // group id
+                                        nullptr,                        // parent
+                                        processGroupId,                 // group id
                                         NS_LITERAL_STRING(""),          // add-on id
                                         NS_LITERAL_STRING(""),          // title
                                         0,                              // window id
@@ -405,6 +474,20 @@ NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool
   }
   return NS_OK;
 }
+NS_IMETHODIMP nsPerformanceStatsService::GetIsMonitoringPerCompartment(JSContext* cx, bool *aIsStopwatchActive)
+{
+  JSRuntime *runtime = JS_GetRuntime(cx);
+  *aIsStopwatchActive = js::GetStopwatchIsMonitoringPerCompartment(runtime);
+  return NS_OK;
+}
+NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext* cx, bool aIsStopwatchActive)
+{
+  JSRuntime *runtime = JS_GetRuntime(cx);
+  if (!js::SetStopwatchIsMonitoringPerCompartment(runtime, aIsStopwatchActive)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
 
 /* readonly attribute nsIPerformanceSnapshot snapshot; */
 NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot)
diff --git a/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js
index 2575c5d0b1..4492a4c55c 100644
--- a/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js
+++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js
@@ -28,7 +28,7 @@ function frameScript() {
       getService(Ci.nsIPerformanceStatsService);
 
     // Make sure that the stopwatch is now active.
-    let monitor = PerformanceStats.getMonitor(["jank", "cpow", "ticks"]);
+    let monitor = PerformanceStats.getMonitor(["jank", "cpow", "ticks", "compartments"]);
 
     addMessageListener("compartments-test:getStatistics", () => {
       try {
@@ -60,10 +60,33 @@ function frameScript() {
   }
 }
 
-function Assert_leq(a, b, msg) {
-  Assert.ok(a <= b, `${msg}: ${a} <= ${b}`);
-}
+// A variant of `Assert` that doesn't spam the logs
+// in case of success.
+let SilentAssert = {
+  equal: function(a, b, msg) {
+    if (a == b) {
+      return;
+    }
+    Assert.equal(a, b, msg);
+  },
+  notEqual: function(a, b, msg) {
+    if (a != b) {
+      return;
+    }
+    Assert.notEqual(a, b, msg);
+  },
+  ok: function(a, msg) {
+    if (a) {
+      return;
+    }
+    Assert.ok(a, msg);
+  },
+  leq: function(a, b, msg) {
+    this.ok(a <= b, `${msg}: ${a} <= ${b}`);
+  }
+};
 
+let isShuttingDown = false;
 function monotinicity_tester(source, testName) {
   // In the background, check invariants:
   // - numeric data can only ever increase;
@@ -107,6 +130,10 @@ function monotinicity_tester(source, testName) {
   };
   let iteration = 0;
   let frameCheck = Task.async(function*() {
+    if (isShuttingDown) {
+      window.clearInterval(interval);
+      return;
+    }
     let name = `${testName}: ${iteration++}`;
     let snapshot = yield source();
     if (!snapshot) {
@@ -118,9 +145,9 @@ function monotinicity_tester(source, testName) {
 
     // Sanity check on the process data.
     sanityCheck(previous.processData, snapshot.processData);
-    Assert.equal(snapshot.processData.isSystem, true);
-    Assert.equal(snapshot.processData.name, "");
-    Assert.equal(snapshot.processData.addonId, "");
+    SilentAssert.equal(snapshot.processData.isSystem, true);
+    SilentAssert.equal(snapshot.processData.name, "");
+    SilentAssert.equal(snapshot.processData.addonId, "");
     previous.procesData = snapshot.processData;
 
     // Sanity check on components data.
@@ -131,14 +158,37 @@ function monotinicity_tester(source, testName) {
         ["jank", "totalSystemTime"],
         ["cpow", "totalCPOWTime"]
       ]) {
-        SilentAssert.leq(item[probe][k], snapshot.processData[probe][k],
-          `Sanity check (${testName}): component has a lower ${k} than process`);
+        // Note that we cannot expect components data to be always smaller
+        // than process data, as `getrusage` & co are not monotonic.
+        SilentAssert.leq(item[probe][k], 2 * snapshot.processData[probe][k],
+          `Sanity check (${testName}): ${k} of component is not impossibly larger than that of process`);
       }
 
       let key = item.groupId;
-      SilentAssert.ok(!map.has(key), "The component hasn't been seen yet.");
+      if (map.has(key)) {
+        let old = map.get(key);
+        Assert.ok(false, `Component ${key} has already been seen. Latest: ${item.title||item.addonId||item.name}, previous: ${old.title||old.addonId||old.name}`);
+      }
       map.set(key, item);
     }
+    for (let item of snapshot.componentsData) {
+      if (!item.parentId) {
+        continue;
+      }
+      let parent = map.get(item.parentId);
+      SilentAssert.ok(parent, `The parent exists ${item.parentId}`);
+
+      for (let [probe, k] of [
+        ["jank", "totalUserTime"],
+        ["jank", "totalSystemTime"],
+        ["cpow", "totalCPOWTime"]
+      ]) {
+        // Note that we cannot expect components data to be always smaller
+        // than parent data, as `getrusage` & co are not monotonic.
+        SilentAssert.leq(item[probe][k], 2 * parent[probe][k],
+          `Sanity check (${testName}): ${k} of component is not impossibly larger than that of parent`);
+      }
+    }
     for (let [key, item] of map) {
       sanityCheck(previous.componentsMap.get(key), item);
       previous.componentsMap.set(key, item);
@@ -213,13 +263,20 @@ add_task(function* test() {
       info("Searching by title, we didn't find the main frame");
       continue;
     }
+    info("Found the main frame");
 
-    if (skipTotalUserTime || parent.jank.totalUserTime > 1000) {
+    if (skipTotalUserTime) {
+      info("Not looking for total user time on this platform, we're done");
+      break;
+    } else if (parent.jank.totalUserTime > 1000) {
+      info("Enough CPU time detected, we're done");
       break;
     } else {
-      info(`Not enough CPU time detected: ${parent.jank.totalUserTime}`)
+      info(`Not enough CPU time detected: ${parent.jank.totalUserTime}`);
+      info(`Details: ${JSON.stringify(parent, null, "\t")}`);
     }
   }
+  isShuttingDown = true;
 
   // Cleanup
   gBrowser.removeTab(newTab);
diff --git a/toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js b/toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js
index 44a10a08ba..f4c7d562f3 100644
--- a/toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js
+++ b/toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js
@@ -19,10 +19,12 @@ let promiseStatistics = Task.async(function*(name) {
   let componentsData = [];
   let componentsEnum = snapshot.getComponentsData().enumerate();
   while (componentsEnum.hasMoreElements()) {
-    componentsData.push(componentsEnum.getNext().QueryInterface(Ci.nsIPerformanceStats));
+    let data = componentsEnum.getNext().QueryInterface(Ci.nsIPerformanceStats);
+    let normalized = JSON.parse(JSON.stringify(data));
+    componentsData.push(data);
   }
   return {
-    processData: snapshot.getProcessData(),
+    processData: JSON.parse(JSON.stringify(snapshot.getProcessData())),
     componentsData
   };
 });
@@ -35,11 +37,19 @@ let promiseSetMonitoring = Task.async(function*(to) {
   yield Promise.resolve();
 });
 
+let promiseSetPerCompartment = Task.async(function*(to) {
+  let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].
+    getService(Ci.nsIPerformanceStatsService);
+  service.isMonitoringPerCompartment = to;
+  yield Promise.resolve();
+});
+
 function getBuiltinStatistics(name, snapshot) {
   let stats = snapshot.componentsData.find(stats =>
     stats.isSystem && !stats.addonId
   );
   do_print(`Built-in statistics for ${name} were ${stats?"":"not "}found`);
+  do_print(JSON.stringify(snapshot.componentsData, null, "\t"));
   return stats;
 }
 
@@ -57,17 +67,25 @@ function burnCPU(ms) {
 }
 
 function ensureEquals(snap1, snap2, name) {
-  Assert.equal(
-    JSON.stringify(snap1.processData),
-    JSON.stringify(snap2.processData),
-    "Same process data: " + name);
+  for (let k of Object.keys(snap1.processData)) {
+    if (k == "ticks") {
+      // Ticks monitoring cannot be deactivated
+      continue;
+    }
+    Assert.equal(snap1.processData[k], snap2.processData[k], `Same process data value ${k} (${name})`)
+  }
   let stats1 = snap1.componentsData.sort((a, b) => a.name <= b.name);
   let stats2 = snap2.componentsData.sort((a, b) => a.name <= b.name);
-  Assert.equal(
-    JSON.stringify(stats1),
-    JSON.stringify(stats2),
-    "Same components data: " + name
-  );
+  Assert.equal(stats1.length, stats2.length, `Same number of components (${name})`);
+  for (let i = 0; i < stats1.length; ++i) {
+    for (let k of Object.keys(stats1[i])) {
+      if (k == "ticks") {
+        // Ticks monitoring cannot be deactivated
+        continue;
+      }
+      Assert.equal(stats1[i][k], stats1[i][k], `Same component data value ${i} ${k} (${name})`)
+    }
+  }
 }
 
 function hasLowPrecision() {
@@ -88,6 +106,7 @@ function hasLowPrecision() {
 
 add_task(function* test_measure() {
   let skipPrecisionTests = hasLowPrecision();
+  yield promiseSetPerCompartment(false);
 
   do_print("Burn CPU without the stopwatch");
   yield promiseSetMonitoring(false);
@@ -137,4 +156,14 @@ add_task(function* test_measure() {
   Assert.equal(builtin2.totalCPOWTime, builtin1.totalCPOWTime, "No CPOW for built-in statistics");
   Assert.equal(builtin4.totalUserTime, builtin3.totalUserTime, "After deactivating the stopwatch, we didn't count any time for the built-in");
   Assert.equal(builtin4.totalCPOWTime, builtin3.totalCPOWTime, "After deactivating the stopwatch, we didn't count any CPOW time for the built-in");
+
+  // Ideally, we should be able to look for test_compartments.js, but
+  // it doesn't have its own compartment.
+  for (let stats of [stats1, stats2, stats3, stats4]) {
+    Assert.ok(!stats.componentsData.find(x => x.name.includes("Task.jsm")), "At this stage, Task.jsm doesn't show up in the components data");
+  }
+  yield promiseSetPerCompartment(true);
+  burnCPU(300);
+  let stats5 = yield promiseStatistics("With per-compartment monitoring");
+  Assert.ok(stats5.componentsData.find(x => x.name.includes("Task.jsm")), "With per-compartment monitoring, test_compartments.js shows up");
 });
diff --git a/toolkit/components/perfmonitoring/tests/xpcshell/xpcshell.ini b/toolkit/components/perfmonitoring/tests/xpcshell/xpcshell.ini
index c4a1aa8ce9..3dd0b3d277 100644
--- a/toolkit/components/perfmonitoring/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/perfmonitoring/tests/xpcshell/xpcshell.ini
@@ -3,3 +3,4 @@ head=
 tail=
 
 [test_compartments.js]
+skip-if = toolkit == 'gonk' # Fails on b2g emulator, bug 1147664
diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp
index 8aac162d94..d8515b4a22 100644
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -117,7 +117,7 @@ struct NoteWeakMapChildrenTracer : public JS::CallbackTracer
       mKey(nullptr), mKeyDelegate(nullptr)
   {
   }
-  void trace(void** aThingp, JS::TraceKind aKind) override;
+  void onChild(const JS::GCCellPtr& aThing) override;
   nsCycleCollectionNoteRootCallback& mCb;
   bool mTracedAny;
   JSObject* mMap;
@@ -126,23 +126,21 @@ struct NoteWeakMapChildrenTracer : public JS::CallbackTracer
 };
 
 void
-NoteWeakMapChildrenTracer::trace(void** aThingp, JS::TraceKind aKind)
+NoteWeakMapChildrenTracer::onChild(const JS::GCCellPtr& aThing)
 {
-  JS::GCCellPtr thing(*aThingp, aKind);
-
-  if (thing.isString()) {
+  if (aThing.isString()) {
     return;
   }
 
-  if (!JS::GCThingIsMarkedGray(thing) && !mCb.WantAllTraces()) {
+  if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) {
     return;
   }
 
-  if (AddToCCKind(thing.kind())) {
-    mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, thing);
+  if (AddToCCKind(aThing.kind())) {
+    mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing);
     mTracedAny = true;
   } else {
-    JS_TraceChildren(this, thing.asCell(), thing.kind());
+    JS_TraceChildren(this, aThing.asCell(), aThing.kind());
   }
 }
 
@@ -314,15 +312,15 @@ struct TraversalTracer : public JS::CallbackTracer
     : JS::CallbackTracer(aRt, DoNotTraceWeakMaps), mCb(aCb)
   {
   }
-  void trace(void** aThingp, JS::TraceKind aTraceKind) override;
+  void onChild(const JS::GCCellPtr& aThing) override;
   nsCycleCollectionTraversalCallback& mCb;
 };
 
-static void
-NoteJSChild(TraversalTracer* aTrc, JS::GCCellPtr aThing)
+void
+TraversalTracer::onChild(const JS::GCCellPtr& aThing)
 {
   // Don't traverse non-gray objects, unless we want all traces.
-  if (!JS::GCThingIsMarkedGray(aThing) && !aTrc->mCb.WantAllTraces()) {
+  if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) {
     return;
   }
 
@@ -334,42 +332,35 @@ NoteJSChild(TraversalTracer* aTrc, JS::GCCellPtr aThing)
    * use special APIs to handle such chains iteratively.
    */
   if (AddToCCKind(aThing.kind())) {
-    if (MOZ_UNLIKELY(aTrc->mCb.WantDebugInfo())) {
+    if (MOZ_UNLIKELY(mCb.WantDebugInfo())) {
       char buffer[200];
-      aTrc->getTracingEdgeName(buffer, sizeof(buffer));
-      aTrc->mCb.NoteNextEdgeName(buffer);
+      getTracingEdgeName(buffer, sizeof(buffer));
+      mCb.NoteNextEdgeName(buffer);
     }
     if (aThing.isObject()) {
-      aTrc->mCb.NoteJSObject(aThing.toObject());
+      mCb.NoteJSObject(aThing.toObject());
     } else {
-      aTrc->mCb.NoteJSScript(aThing.toScript());
+      mCb.NoteJSScript(aThing.toScript());
     }
   } else if (aThing.isShape()) {
     // The maximum depth of traversal when tracing a Shape is unbounded, due to
     // the parent pointers on the shape.
-    JS_TraceShapeCycleCollectorChildren(aTrc, aThing);
+    JS_TraceShapeCycleCollectorChildren(this, aThing);
   } else if (aThing.isObjectGroup()) {
     // The maximum depth of traversal when tracing an ObjectGroup is unbounded,
     // due to information attached to the groups which can lead other groups to
     // be traced.
-    JS_TraceObjectGroupCycleCollectorChildren(aTrc, aThing);
+    JS_TraceObjectGroupCycleCollectorChildren(this, aThing);
   } else if (!aThing.isString()) {
-    JS_TraceChildren(aTrc, aThing.asCell(), aThing.kind());
+    JS_TraceChildren(this, aThing.asCell(), aThing.kind());
   }
 }
 
-void
-TraversalTracer::trace(void** aThingp, JS::TraceKind aTraceKind)
-{
-  JS::GCCellPtr thing(*aThingp, aTraceKind);
-  NoteJSChild(this, thing);
-}
-
 static void
 NoteJSChildGrayWrapperShim(void* aData, JS::GCCellPtr aThing)
 {
   TraversalTracer* trc = static_cast(aData);
-  NoteJSChild(trc, aThing);
+  trc->onChild(aThing);
 }
 
 /*