-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
Copy pathbootstrap.diff
6204 lines (6164 loc) · 212 KB
/
bootstrap.diff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
diff --git a/accessible/base/NotificationController.h b/accessible/base/NotificationController.h
index c6aa1cf44c8ba339704a18ebe92fe5a7751e52f5..cfe64bdda54d49ee5b11b2368a2f9856cc9ea3cf 100644
--- a/accessible/base/NotificationController.h
+++ b/accessible/base/NotificationController.h
@@ -270,6 +270,8 @@ class NotificationController final : public EventQueue,
}
#endif
+ bool IsUpdatePendingForJugglerAccessibility() { return IsUpdatePending(); }
+
protected:
virtual ~NotificationController();
diff --git a/accessible/interfaces/nsIAccessibleDocument.idl b/accessible/interfaces/nsIAccessibleDocument.idl
index a91df31c96afda66f478a5a38eaa4352039c2a0b..ee777c1746284027fb3aa2f1686f8082af9d89ee 100644
--- a/accessible/interfaces/nsIAccessibleDocument.idl
+++ b/accessible/interfaces/nsIAccessibleDocument.idl
@@ -72,4 +72,9 @@ interface nsIAccessibleDocument : nsISupports
* Return the child document accessible at the given index.
*/
nsIAccessibleDocument getChildDocumentAt(in unsigned long index);
+
+ /**
+ * Return whether it is updating.
+ */
+ readonly attribute boolean isUpdatePendingForJugglerAccessibility;
};
diff --git a/accessible/xpcom/xpcAccessibleDocument.cpp b/accessible/xpcom/xpcAccessibleDocument.cpp
index e3dbe73f22252f11080c3f266b2309f842eba9dc..87f50fe3df7cc8f9bc26dabd5ee571cae270912a 100644
--- a/accessible/xpcom/xpcAccessibleDocument.cpp
+++ b/accessible/xpcom/xpcAccessibleDocument.cpp
@@ -143,6 +143,15 @@ xpcAccessibleDocument::GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor) {
return NS_OK;
}
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetIsUpdatePendingForJugglerAccessibility(bool* updating) {
+ NS_ENSURE_ARG_POINTER(updating);
+ *updating = Intl()->Controller()->IsUpdatePendingForJugglerAccessibility();
+ return NS_OK;
+}
+
+
////////////////////////////////////////////////////////////////////////////////
// xpcAccessibleDocument
diff --git a/accessible/xpcom/xpcAccessibleDocument.h b/accessible/xpcom/xpcAccessibleDocument.h
index f042cc1081850ac60e329b70b5569f8b97d4e4dc..65bcff9b41b9471ef1427e3ea330481c194409bc 100644
--- a/accessible/xpcom/xpcAccessibleDocument.h
+++ b/accessible/xpcom/xpcAccessibleDocument.h
@@ -48,6 +48,8 @@ class xpcAccessibleDocument : public xpcAccessibleHyperText,
nsIAccessibleDocument** aDocument) final;
NS_IMETHOD GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor) final;
+ NS_IMETHOD GetIsUpdatePendingForJugglerAccessibility(bool* aUpdating) final;
+
/**
* Return XPCOM wrapper for the internal accessible.
*/
diff --git a/browser/installer/allowed-dupes.mn b/browser/installer/allowed-dupes.mn
index cf0ae812a9f9741128fac124db03fb158ca54c30..7a0657ae0784e13929daf301549151236f1e53c1 100644
--- a/browser/installer/allowed-dupes.mn
+++ b/browser/installer/allowed-dupes.mn
@@ -139,6 +139,11 @@ browser/chrome/browser/res/payments/formautofill/autofillEditForms.js
# Bug 1451050 - Remote settings empty dumps (will be populated with data eventually)
browser/defaults/settings/pinning/pins.json
browser/defaults/settings/main/example.json
+# Juggler/marionette files
+chrome/juggler/content/content/floating-scrollbars.css
+browser/chrome/devtools/skin/floating-scrollbars-responsive-design.css
+chrome/juggler/content/server/stream-utils.js
+chrome/marionette/content/stream-utils.js
#ifdef MOZ_EME_WIN32_ARTIFACT
gmp-clearkey/0.1/manifest.json
i686/gmp-clearkey/0.1/manifest.json
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
index 7054749357ec13f175be8022852b42fcfeda9134..c9064880ecf7e70290c6a84bfc209e084aa37ddf 100644
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -211,6 +211,11 @@
@RESPATH@/components/marionette.js
#endif
+@RESPATH@/chrome/juggler@JAREXT@
+@RESPATH@/chrome/juggler.manifest
+@RESPATH@/components/juggler.manifest
+@RESPATH@/components/juggler.js
+
#if defined(ENABLE_TESTS) && defined(MOZ_DEBUG)
@RESPATH@/components/TestInterfaceJS.js
@RESPATH@/components/TestInterfaceJS.manifest
diff --git a/devtools/server/socket/websocket-server.js b/devtools/server/socket/websocket-server.js
index 040c7b124dec6bb254563bbe74fe50012cb077a3..b4e6b8132786af70e8ad0dce88b67c2835307f88 100644
--- a/devtools/server/socket/websocket-server.js
+++ b/devtools/server/socket/websocket-server.js
@@ -133,13 +133,12 @@ function writeHttpResponse(output, response) {
* Process the WebSocket handshake headers and return the key to be sent in
* Sec-WebSocket-Accept response header.
*/
-function processRequest({ requestLine, headers }) {
+function processRequest({ requestLine, headers }, expectedPath) {
const [method, path] = requestLine.split(" ");
if (method !== "GET") {
throw new Error("The handshake request must use GET method");
}
-
- if (path !== "/") {
+ if (path !== expectedPath) {
throw new Error("The handshake request has unknown path");
}
@@ -189,13 +188,13 @@ function computeKey(key) {
/**
* Perform the server part of a WebSocket opening handshake on an incoming connection.
*/
-const serverHandshake = async function(input, output) {
+const serverHandshake = async function(input, output, expectedPath) {
// Read the request
const request = await readHttpRequest(input);
try {
// Check and extract info from the request
- const { acceptKey } = processRequest(request);
+ const { acceptKey } = processRequest(request, expectedPath);
// Send response headers
await writeHttpResponse(output, [
@@ -217,8 +216,8 @@ const serverHandshake = async function(input, output) {
* Performs the WebSocket handshake and waits for the WebSocket to open.
* Returns Promise with a WebSocket ready to send and receive messages.
*/
-const accept = async function(transport, input, output) {
- await serverHandshake(input, output);
+const accept = async function(transport, input, output, expectedPath) {
+ await serverHandshake(input, output, expectedPath || "/");
const transportProvider = {
setListener(upgradeListener) {
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index b30c186c88daa7dd62f69e452dedc9e968511bb5..3a9bda87d5c577fd578bf3a523854d46c2a8db6a 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -95,6 +95,7 @@
#include "nsIDocShellTreeItem.h"
#include "nsIDocShellTreeOwner.h"
#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
#include "nsIDocumentLoaderFactory.h"
#include "nsIDOMWindow.h"
#include "nsIEditingSession.h"
@@ -351,6 +352,8 @@ nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext,
mUseStrictSecurityChecks(false),
mObserveErrorPages(true),
mCSSErrorReportingEnabled(false),
+ mFileInputInterceptionEnabled(false),
+ mBypassCSPEnabled(false),
mAllowAuth(mItemType == typeContent),
mAllowKeywordFixup(false),
mIsOffScreenBrowser(false),
@@ -1213,6 +1216,7 @@ bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest,
isSubFrame = mLSHE->GetIsSubFrame();
}
+ FireOnFrameLocationChange(this, aRequest, aURI, aLocationFlags);
if (!isSubFrame && !isRoot) {
/*
* We don't want to send OnLocationChange notifications when
@@ -3347,6 +3351,54 @@ nsDocShell::GetContentBlockingLog(Promise** aPromise) {
return NS_OK;
}
+nsDocShell* nsDocShell::GetRootDocShell() {
+ nsCOMPtr<nsIDocShellTreeItem> rootAsItem;
+ GetInProcessSameTypeRootTreeItem(getter_AddRefs(rootAsItem));
+ nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(rootAsItem);
+ return nsDocShell::Cast(rootShell);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetBypassCSPEnabled(bool* aEnabled) {
+ MOZ_ASSERT(aEnabled);
+ *aEnabled = mBypassCSPEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetBypassCSPEnabled(bool aEnabled) {
+ mBypassCSPEnabled = aEnabled;
+ return NS_OK;
+}
+
+bool nsDocShell::IsBypassCSPEnabled() {
+ return GetRootDocShell()->mBypassCSPEnabled;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetFileInputInterceptionEnabled(bool* aEnabled) {
+ MOZ_ASSERT(aEnabled);
+ *aEnabled = mFileInputInterceptionEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetFileInputInterceptionEnabled(bool aEnabled) {
+ mFileInputInterceptionEnabled = aEnabled;
+ return NS_OK;
+}
+
+bool nsDocShell::IsFileInputInterceptionEnabled() {
+ return GetRootDocShell()->mFileInputInterceptionEnabled;
+}
+
+void nsDocShell::FilePickerShown(mozilla::dom::Element* element) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->NotifyObservers(
+ ToSupports(element), "juggler-file-picker-shown", nullptr);
+}
+
NS_IMETHODIMP
nsDocShell::GetIsNavigating(bool* aOut) {
*aOut = mIsNavigating;
diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
index e88da0c1e9adcc6f50ca4b3cb4a55d12430736e8..1544de7726143464e204532dae12dd2ad7373a0f 100644
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -18,6 +18,7 @@
#include "mozilla/WeakPtr.h"
#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Element.h"
#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/dom/ChildSHistory.h"
@@ -485,6 +486,11 @@ class nsDocShell final : public nsDocLoader,
mSkipBrowsingContextDetachOnDestroy = true;
}
+ bool IsFileInputInterceptionEnabled();
+ void FilePickerShown(mozilla::dom::Element* element);
+
+ bool IsBypassCSPEnabled();
+
// Create a content viewer within this nsDocShell for the given
// `WindowGlobalChild` actor.
nsresult CreateContentViewerForActor(
@@ -1036,6 +1042,8 @@ class nsDocShell final : public nsDocLoader,
bool CSSErrorReportingEnabled() const { return mCSSErrorReportingEnabled; }
+ nsDocShell* GetRootDocShell();
+
// Handles retrieval of subframe session history for nsDocShell::LoadURI. If a
// load is requested in a subframe of the current DocShell, the subframe
// loadType may need to reflect the loadType of the parent document, or in
@@ -1292,6 +1300,8 @@ class nsDocShell final : public nsDocLoader,
bool mUseStrictSecurityChecks : 1;
bool mObserveErrorPages : 1;
bool mCSSErrorReportingEnabled : 1;
+ bool mFileInputInterceptionEnabled: 1;
+ bool mBypassCSPEnabled : 1;
bool mAllowAuth : 1;
bool mAllowKeywordFixup : 1;
bool mIsOffScreenBrowser : 1;
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
index 267856626bdf2e7411b4ac975d4f64b824c826a1..03b7a44b39b5b3303c2519614dd39863b595fac6 100644
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -1142,4 +1142,8 @@ interface nsIDocShell : nsIDocShellTreeItem
* @see nsISHEntry synchronizeLayoutHistoryState().
*/
void synchronizeLayoutHistoryState();
+
+ attribute boolean fileInputInterceptionEnabled;
+
+ attribute boolean bypassCSPEnabled;
};
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
index 9eac6924968638025556b8e97546ed3d91bfe28d..8839c880b75f00cb475bee7f68c48112e5263a1c 100644
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -3267,6 +3267,9 @@ void Document::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
}
void Document::ApplySettingsFromCSP(bool aSpeculative) {
+ if (mDocumentContainer && mDocumentContainer->IsBypassCSPEnabled())
+ return;
+
nsresult rv = NS_OK;
if (!aSpeculative) {
// 1) apply settings from regular CSP
@@ -3316,6 +3319,11 @@ nsresult Document::InitCSP(nsIChannel* aChannel) {
return NS_OK;
}
+ nsCOMPtr<nsIDocShell> shell(mDocumentContainer);
+ if (shell && nsDocShell::Cast(shell)->IsBypassCSPEnabled()) {
+ return NS_OK;
+ }
+
// If this is a data document - no need to set CSP.
if (mLoadedAsData) {
return NS_OK;
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
index 5fda32ce01630bb9151e47cb4a8cdb1180d120bd..5e4cfe38dc69f257f3057dbf1197f1f3e12fe654 100644
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -45,6 +45,7 @@
#include "nsMappedAttributes.h"
#include "nsIFormControl.h"
#include "mozilla/dom/Document.h"
+#include "nsDocShell.h"
#include "nsIFormControlFrame.h"
#include "nsITextControlFrame.h"
#include "nsIFrame.h"
@@ -730,6 +731,12 @@ nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
return NS_ERROR_FAILURE;
}
+ nsDocShell* docShell = static_cast<nsDocShell*>(win->GetDocShell());
+ if (docShell && docShell->IsFileInputInterceptionEnabled()) {
+ docShell->FilePickerShown(this);
+ return NS_OK;
+ }
+
if (IsPopupBlocked()) {
return NS_OK;
}
diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp
index edda707be08292a767f66d20f2abca98af113796..f7031a8e1fd813a9371b8f6d3a987a32e47b1dac 100644
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -3632,6 +3632,13 @@ NS_IMETHODIMP BrowserChild::OnStateChange(nsIWebProgress* aWebProgress,
return NS_OK;
}
+NS_IMETHODIMP BrowserChild::OnFrameLocationChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ nsIURI *aLocation,
+ uint32_t aFlags) {
+ return NS_OK;
+}
+
NS_IMETHODIMP BrowserChild::OnProgressChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
int32_t aCurSelfProgress,
diff --git a/dom/script/ScriptSettings.cpp b/dom/script/ScriptSettings.cpp
index 9e3c1a56d10394d98de9e84fb8cd6ee8e3be5870..8c661de349d6cb64fd8d81d5db9c28f2a2af9138 100644
--- a/dom/script/ScriptSettings.cpp
+++ b/dom/script/ScriptSettings.cpp
@@ -140,6 +140,30 @@ ScriptSettingsStackEntry::~ScriptSettingsStackEntry() {
MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->HasJSGlobal());
}
+static nsIGlobalObject* UnwrapSandboxGlobal(nsIGlobalObject* global) {
+ if (!global)
+ return global;
+ JSObject* globalObject = global->GetGlobalJSObject();
+ if (!globalObject)
+ return global;
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (!cx)
+ return global;
+ JS::Rooted<JSObject*> proto(cx);
+ JS::RootedObject rootedGlobal(cx, globalObject);
+ if (!JS_GetPrototype(cx, rootedGlobal, &proto))
+ return global;
+ if (!proto || !xpc::IsSandboxPrototypeProxy(proto))
+ return global;
+ // If this is a sandbox associated with a DOMWindow via a
+ // sandboxPrototype, use that DOMWindow. This supports GreaseMonkey
+ // and JetPack content scripts.
+ proto = js::CheckedUnwrapDynamic(proto, cx, /* stopAtWindowProxy = */ false);
+ if (!proto)
+ return global;
+ return xpc::WindowGlobalOrNull(proto);
+}
+
// If the entry or incumbent global ends up being something that the subject
// principal doesn't subsume, we don't want to use it. This never happens on
// the web, but can happen with asymmetric privilege relationships (i.e.
@@ -167,7 +191,7 @@ static nsIGlobalObject* ClampToSubject(nsIGlobalObject* aGlobalOrNull) {
NS_ENSURE_TRUE(globalPrin, GetCurrentGlobal());
if (!nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()
->SubsumesConsideringDomain(globalPrin)) {
- return GetCurrentGlobal();
+ return UnwrapSandboxGlobal(GetCurrentGlobal());
}
return aGlobalOrNull;
diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp
index f0c28cfdae1c9ac33013e9688e0142d161763543..a38ab106e37dbab58e91ef5a873f8954c35881e7 100644
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -121,6 +121,11 @@ void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc,
return;
}
+ if (aDoc.GetDocShell() &&
+ nsDocShell::Cast(aDoc.GetDocShell())->IsBypassCSPEnabled()) {
+ return;
+ }
+
nsAutoString policyStr(
nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
aPolicyStr));
diff --git a/extensions/permissions/nsPermissionManager.cpp b/extensions/permissions/nsPermissionManager.cpp
index 9b667d3a4c29e71297dc0bd33bfe30ab670a9f36..0971b5ca7930cfd6d7ac6e21f7187718bfc2499b 100644
--- a/extensions/permissions/nsPermissionManager.cpp
+++ b/extensions/permissions/nsPermissionManager.cpp
@@ -167,7 +167,7 @@ void MaybeStripOAs(OriginAttributes& aOriginAttributes) {
}
if (flags != 0) {
- aOriginAttributes.StripAttributes(flags);
+ // aOriginAttributes.StripAttributes(flags);
}
}
@@ -199,6 +199,8 @@ nsresult GetOriginFromPrincipal(nsIPrincipal* aPrincipal, nsACString& aOrigin) {
OriginAppendOASuffix(attrs, aOrigin);
+ // Disable userContext for permissions.
+ // attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID);
return NS_OK;
}
@@ -317,7 +319,7 @@ already_AddRefed<nsIPrincipal> GetNextSubDomainPrincipal(
if (!StaticPrefs::permissions_isolateBy_userContext()) {
// Disable userContext for permissions.
- attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID);
+ // attrs.StripAttributes(mozilla::OriginAttributes::STRIP_USER_CONTEXT_ID);
}
nsCOMPtr<nsIPrincipal> principal =
diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp
index d99e7f91503e84690d711bca2c2d916e34e56214..b63e9d96219420f5e4fb58797e4c3d901cba77cc 100644
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -1065,9 +1065,12 @@ void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) {
if (!StaticPrefs::security_csp_enable()) {
return;
}
-
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ if (mDocShell && static_cast<nsDocShell*>(mDocShell.get())->IsBypassCSPEnabled()) {
+ return;
+ }
+
nsresult rv = NS_OK;
nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = mDocument->GetPreloadCsp();
if (!preloadCsp) {
diff --git a/security/manager/ssl/nsCertOverrideService.cpp b/security/manager/ssl/nsCertOverrideService.cpp
index 6dca2b78830edc1ddbd66264bd332853729dac71..fbe89c9682834e11b9d9219d9eb056ede084435a 100644
--- a/security/manager/ssl/nsCertOverrideService.cpp
+++ b/security/manager/ssl/nsCertOverrideService.cpp
@@ -634,7 +634,7 @@ static bool IsDebugger() {
NS_IMETHODIMP
nsCertOverrideService::
SetDisableAllSecurityChecksAndLetAttackersInterceptMyData(bool aDisable) {
- if (!(PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR") || IsDebugger())) {
+ if (false /* juggler hacks */ && !(PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR") || IsDebugger())) {
return NS_ERROR_NOT_AVAILABLE;
}
diff --git a/testing/juggler/BrowserContextManager.js b/testing/juggler/BrowserContextManager.js
new file mode 100644
index 0000000000000000000000000000000000000000..483667dbec8e4c76533e4cf5e69ca9e322f2e708
--- /dev/null
+++ b/testing/juggler/BrowserContextManager.js
@@ -0,0 +1,194 @@
+"use strict";
+
+const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm");
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm');
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
+const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
+const helper = new Helper();
+
+const IDENTITY_NAME = 'JUGGLER ';
+const HUNDRED_YEARS = 60 * 60 * 24 * 365 * 100;
+
+const ALL_PERMISSIONS = [
+ 'geo',
+ 'microphone',
+ 'camera',
+ 'desktop-notifications',
+];
+
+class BrowserContextManager {
+ static instance() {
+ return BrowserContextManager._instance || null;
+ }
+
+ static initialize() {
+ if (BrowserContextManager._instance)
+ return;
+ BrowserContextManager._instance = new BrowserContextManager();
+ }
+
+ constructor() {
+ this._browserContextIdToBrowserContext = new Map();
+ this._userContextIdToBrowserContext = new Map();
+
+ // Cleanup containers from previous runs (if any)
+ for (const identity of ContextualIdentityService.getPublicIdentities()) {
+ if (identity.name && identity.name.startsWith(IDENTITY_NAME)) {
+ ContextualIdentityService.remove(identity.userContextId);
+ ContextualIdentityService.closeContainerTabs(identity.userContextId);
+ }
+ }
+
+ this._defaultContext = new BrowserContext(this, undefined, undefined);
+ }
+
+ createBrowserContext(options) {
+ return new BrowserContext(this, helper.generateId(), options);
+ }
+
+ browserContextForId(browserContextId) {
+ return this._browserContextIdToBrowserContext.get(browserContextId);
+ }
+
+ browserContextForUserContextId(userContextId) {
+ return this._userContextIdToBrowserContext.get(userContextId);
+ }
+
+ getBrowserContexts() {
+ return Array.from(this._browserContextIdToBrowserContext.values());
+ }
+}
+
+class BrowserContext {
+ constructor(manager, browserContextId, options) {
+ EventEmitter.decorate(this);
+
+ this._manager = manager;
+ this.browserContextId = browserContextId;
+ this.userContextId = undefined;
+ if (browserContextId !== undefined) {
+ const identity = ContextualIdentityService.create(IDENTITY_NAME + browserContextId);
+ this.userContextId = identity.userContextId;
+ }
+ this._principals = [];
+ this._manager._browserContextIdToBrowserContext.set(this.browserContextId, this);
+ this._manager._userContextIdToBrowserContext.set(this.userContextId, this);
+ this.options = options || {};
+ this.options.scriptsToEvaluateOnNewDocument = [];
+ }
+
+ destroy() {
+ if (this.userContextId !== undefined) {
+ ContextualIdentityService.remove(this.userContextId);
+ ContextualIdentityService.closeContainerTabs(this.userContextId);
+ }
+ this._manager._browserContextIdToBrowserContext.delete(this.browserContextId);
+ this._manager._userContextIdToBrowserContext.delete(this.userContextId);
+ }
+
+ addScriptToEvaluateOnNewDocument(script) {
+ this.options.scriptsToEvaluateOnNewDocument.push(script);
+ this.emit(BrowserContext.Events.ScriptToEvaluateOnNewDocumentAdded, script);
+ }
+
+ grantPermissions(origin, permissions) {
+ const attrs = {userContextId: this.userContextId};
+ const principal = Services.scriptSecurityManager.createContentPrincipal(NetUtil.newURI(origin), attrs);
+ this._principals.push(principal);
+ for (const permission of ALL_PERMISSIONS) {
+ const action = permissions.includes(permission) ? Ci.nsIPermissionManager.ALLOW_ACTION : Ci.nsIPermissionManager.DENY_ACTION;
+ Services.perms.addFromPrincipal(principal, permission, action);
+ }
+ }
+
+ resetPermissions() {
+ for (const principal of this._principals) {
+ for (const permission of ALL_PERMISSIONS)
+ Services.perms.removeFromPrincipal(principal, permission);
+ }
+ this._principals = [];
+ }
+
+ setCookies(cookies) {
+ const protocolToSameSite = {
+ [undefined]: Ci.nsICookie.SAMESITE_NONE,
+ 'Lax': Ci.nsICookie.SAMESITE_LAX,
+ 'Strict': Ci.nsICookie.SAMESITE_STRICT,
+ };
+ for (const cookie of cookies) {
+ const uri = cookie.url ? NetUtil.newURI(cookie.url) : null;
+ let domain = cookie.domain;
+ if (!domain) {
+ if (!uri)
+ throw new Error('At least one of the url and domain needs to be specified');
+ domain = uri.host;
+ }
+ let path = cookie.path;
+ if (!path)
+ path = uri ? dirPath(uri.filePath) : '/';
+ let secure = false;
+ if (cookie.secure !== undefined)
+ secure = cookie.secure;
+ else if (uri && uri.scheme === 'https')
+ secure = true;
+ Services.cookies.add(
+ domain,
+ path,
+ cookie.name,
+ cookie.value,
+ secure,
+ cookie.httpOnly || false,
+ cookie.expires === undefined || cookie.expires === -1 /* isSession */,
+ cookie.expires === undefined ? Date.now() + HUNDRED_YEARS : cookie.expires,
+ { userContextId: this.userContextId } /* originAttributes */,
+ protocolToSameSite[cookie.sameSite],
+ );
+ }
+ }
+
+ clearCookies() {
+ Services.cookies.removeCookiesWithOriginAttributes(JSON.stringify({ userContextId: this.userContextId }));
+ }
+
+ getCookies() {
+ const result = [];
+ const sameSiteToProtocol = {
+ [Ci.nsICookie.SAMESITE_NONE]: 'None',
+ [Ci.nsICookie.SAMESITE_LAX]: 'Lax',
+ [Ci.nsICookie.SAMESITE_STRICT]: 'Strict',
+ };
+ for (let cookie of Services.cookies.cookies) {
+ if (cookie.originAttributes.userContextId !== (this.userContextId || 0))
+ continue;
+ if (cookie.host === 'addons.mozilla.org')
+ continue;
+ result.push({
+ name: cookie.name,
+ value: cookie.value,
+ domain: cookie.host,
+ path: cookie.path,
+ expires: cookie.isSession ? -1 : cookie.expiry,
+ size: cookie.name.length + cookie.value.length,
+ httpOnly: cookie.isHttpOnly,
+ secure: cookie.isSecure,
+ session: cookie.isSession,
+ sameSite: sameSiteToProtocol[cookie.sameSite],
+ });
+ }
+ return result;
+ }
+}
+
+BrowserContext.Events = {
+ ScriptToEvaluateOnNewDocumentAdded: Symbol('BrowserContext.Events.ScriptToEvaluateOnNewDocumentAdded'),
+};
+
+function dirPath(path) {
+ return path.substring(0, path.lastIndexOf('/') + 1);
+}
+
+var EXPORTED_SYMBOLS = ['BrowserContextManager', 'BrowserContext'];
+this.BrowserContextManager = BrowserContextManager;
+this.BrowserContext = BrowserContext;
+
diff --git a/testing/juggler/Helper.js b/testing/juggler/Helper.js
new file mode 100644
index 0000000000000000000000000000000000000000..862c680198bbb503a5f04c19bdb8fdf2cd8c9cef
--- /dev/null
+++ b/testing/juggler/Helper.js
@@ -0,0 +1,102 @@
+const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+class Helper {
+ addObserver(handler, topic) {
+ Services.obs.addObserver(handler, topic);
+ return () => Services.obs.removeObserver(handler, topic);
+ }
+
+ addMessageListener(receiver, eventName, handler) {
+ receiver.addMessageListener(eventName, handler);
+ return () => receiver.removeMessageListener(eventName, handler);
+ }
+
+ addEventListener(receiver, eventName, handler) {
+ receiver.addEventListener(eventName, handler);
+ return () => receiver.removeEventListener(eventName, handler);
+ }
+
+ on(receiver, eventName, handler) {
+ // The toolkit/modules/EventEmitter.jsm dispatches event name as a first argument.
+ // Fire event listeners without it for convenience.
+ const handlerWrapper = (_, ...args) => handler(...args);
+ receiver.on(eventName, handlerWrapper);
+ return () => receiver.off(eventName, handlerWrapper);
+ }
+
+ addProgressListener(progress, listener, flags) {
+ progress.addProgressListener(listener, flags);
+ return () => progress.removeProgressListener(listener);
+ }
+
+ removeListeners(listeners) {
+ for (const tearDown of listeners)
+ tearDown.call(null);
+ listeners.splice(0, listeners.length);
+ }
+
+ generateId() {
+ const string = uuidGen.generateUUID().toString();
+ return string.substring(1, string.length - 1);
+ }
+
+ getNetworkErrorStatusText(status) {
+ if (!status)
+ return null;
+ for (const key of Object.keys(Cr)) {
+ if (Cr[key] === status)
+ return key;
+ }
+ // Security module. The following is taken from
+ // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/How_to_check_the_secruity_state_of_an_XMLHTTPRequest_over_SSL
+ if ((status & 0xff0000) === 0x5a0000) {
+ // NSS_SEC errors (happen below the base value because of negative vals)
+ if ((status & 0xffff) < Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE)) {
+ // The bases are actually negative, so in our positive numeric space, we
+ // need to subtract the base off our value.
+ const nssErr = Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE) - (status & 0xffff);
+ switch (nssErr) {
+ case 11:
+ return 'SEC_ERROR_EXPIRED_CERTIFICATE';
+ case 12:
+ return 'SEC_ERROR_REVOKED_CERTIFICATE';
+ case 13:
+ return 'SEC_ERROR_UNKNOWN_ISSUER';
+ case 20:
+ return 'SEC_ERROR_UNTRUSTED_ISSUER';
+ case 21:
+ return 'SEC_ERROR_UNTRUSTED_CERT';
+ case 36:
+ return 'SEC_ERROR_CA_CERT_INVALID';
+ case 90:
+ return 'SEC_ERROR_INADEQUATE_KEY_USAGE';
+ case 176:
+ return 'SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED';
+ default:
+ return 'SEC_ERROR_UNKNOWN';
+ }
+ }
+ const sslErr = Math.abs(Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE) - (status & 0xffff);
+ switch (sslErr) {
+ case 3:
+ return 'SSL_ERROR_NO_CERTIFICATE';
+ case 4:
+ return 'SSL_ERROR_BAD_CERTIFICATE';
+ case 8:
+ return 'SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE';
+ case 9:
+ return 'SSL_ERROR_UNSUPPORTED_VERSION';
+ case 12:
+ return 'SSL_ERROR_BAD_CERT_DOMAIN';
+ default:
+ return 'SSL_ERROR_UNKNOWN';
+ }
+ }
+ return '<unknown error>';
+ }
+}
+
+var EXPORTED_SYMBOLS = [ "Helper" ];
+this.Helper = Helper;
+
diff --git a/testing/juggler/NetworkObserver.js b/testing/juggler/NetworkObserver.js
new file mode 100644
index 0000000000000000000000000000000000000000..8fe6a596bda3f58e6f93ba943fbbc0819bf0fc01
--- /dev/null
+++ b/testing/juggler/NetworkObserver.js
@@ -0,0 +1,689 @@
+"use strict";
+
+const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
+const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm');
+const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js");
+const {TargetRegistry} = ChromeUtils.import('chrome://juggler/content/TargetRegistry.js');
+const {BrowserContextManager} = ChromeUtils.import('chrome://juggler/content/BrowserContextManager.js');
+
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+const Cm = Components.manager;
+const CC = Components.Constructor;
+const helper = new Helper();
+
+const BinaryInputStream = CC('@mozilla.org/binaryinputstream;1', 'nsIBinaryInputStream', 'setInputStream');
+const BinaryOutputStream = CC('@mozilla.org/binaryoutputstream;1', 'nsIBinaryOutputStream', 'setOutputStream');
+const StorageStream = CC('@mozilla.org/storagestream;1', 'nsIStorageStream', 'init');
+
+// Cap response storage with 100Mb per tracked tab.
+const MAX_RESPONSE_STORAGE_SIZE = 100 * 1024 * 1024;
+
+/**
+ * This is a nsIChannelEventSink implementation that monitors channel redirects.
+ */
+const SINK_CLASS_DESCRIPTION = "Juggler NetworkMonitor Channel Event Sink";
+const SINK_CLASS_ID = Components.ID("{c2b4c83e-607a-405a-beab-0ef5dbfb7617}");
+const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1";
+const SINK_CATEGORY_NAME = "net-channel-event-sinks";
+
+class NetworkObserver {
+ static instance() {
+ return NetworkObserver._instance || null;
+ }
+
+ static initialize() {
+ if (NetworkObserver._instance)
+ return;
+ NetworkObserver._instance = new NetworkObserver();
+ }
+
+ constructor() {
+ EventEmitter.decorate(this);
+ this._browserSessionCount = new Map();
+ this._activityDistributor = Cc["@mozilla.org/network/http-activity-distributor;1"].getService(Ci.nsIHttpActivityDistributor);
+ this._activityDistributor.addObserver(this);
+
+ this._redirectMap = new Map(); // oldId => newId
+ this._resumedRequestIdToHeaders = new Map(); // requestId => { headers }
+ this._postResumeChannelIdToRequestId = new Map(); // post-resume channel id => pre-resume request id
+
+ this._channelSink = {
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIChannelEventSink]),
+ asyncOnChannelRedirect: (oldChannel, newChannel, flags, callback) => {
+ this._onRedirect(oldChannel, newChannel, flags);
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ },
+ };
+ this._channelSinkFactory = {
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIFactory]),
+ createInstance: (aOuter, aIID) => this._channelSink.QueryInterface(aIID),
+ };
+ // Register self as ChannelEventSink to track redirects.
+ const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(SINK_CLASS_ID, SINK_CLASS_DESCRIPTION, SINK_CONTRACT_ID, this._channelSinkFactory);
+ Services.catMan.addCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID, SINK_CONTRACT_ID, false, true);
+
+ this._browserInterceptors = new Map(); // Browser => (requestId => interceptor).
+ this._extraHTTPHeaders = new Map();
+ this._browserResponseStorages = new Map();
+ this._browserAuthCredentials = new Map();
+
+ this._eventListeners = [
+ helper.addObserver(this._onRequest.bind(this), 'http-on-modify-request'),
+ helper.addObserver(this._onResponse.bind(this, false /* fromCache */), 'http-on-examine-response'),
+ helper.addObserver(this._onResponse.bind(this, true /* fromCache */), 'http-on-examine-cached-response'),
+ helper.addObserver(this._onResponse.bind(this, true /* fromCache */), 'http-on-examine-merged-response'),
+ ];
+ }
+
+ setExtraHTTPHeaders(browser, headers) {
+ if (!headers)
+ this._extraHTTPHeaders.delete(browser);
+ else
+ this._extraHTTPHeaders.set(browser, headers);
+ }
+
+ enableRequestInterception(browser) {
+ if (!this._browserInterceptors.has(browser))
+ this._browserInterceptors.set(browser, new Map());
+ }
+
+ disableRequestInterception(browser) {
+ const interceptors = this._browserInterceptors.get(browser);
+ if (!interceptors)
+ return;
+ this._browserInterceptors.delete(browser);
+ for (const interceptor of interceptors.values())
+ interceptor._resume();
+ }
+
+ _takeInterceptor(browser, requestId) {
+ const interceptors = this._browserInterceptors.get(browser);
+ if (!interceptors)
+ throw new Error(`Request interception is not enabled`);
+ const interceptor = interceptors.get(requestId);
+ if (!interceptor)
+ throw new Error(`Cannot find request "${requestId}"`);
+ interceptors.delete(requestId);
+ return interceptor;
+ }
+
+ resumeInterceptedRequest(browser, requestId, method, headers, postData) {
+ this._takeInterceptor(browser, requestId)._resume(method, headers, postData);
+ }
+
+ getResponseBody(browser, requestId) {
+ const responseStorage = this._browserResponseStorages.get(browser);
+ if (!responseStorage)
+ throw new Error('Responses are not tracked for the given browser');
+ return responseStorage.getBase64EncodedResponse(requestId);
+ }
+
+ fulfillInterceptedRequest(browser, requestId, status, statusText, headers, base64body) {
+ this._takeInterceptor(browser, requestId)._fulfill(status, statusText, headers, base64body);
+ }
+
+ abortInterceptedRequest(browser, requestId, errorCode) {
+ this._takeInterceptor(browser, requestId)._abort(errorCode);
+ }
+
+ setAuthCredentials(browser, username, password) {
+ this._browserAuthCredentials.set(browser, { username, password });
+ }
+
+ _requestId(httpChannel) {
+ const id = httpChannel.channelId + '';
+ return this._postResumeChannelIdToRequestId.get(id) || id;
+ }
+
+ _onRedirect(oldChannel, newChannel, flags) {
+ if (!(oldChannel instanceof Ci.nsIHttpChannel) || !(newChannel instanceof Ci.nsIHttpChannel))
+ return;
+ const oldHttpChannel = oldChannel.QueryInterface(Ci.nsIHttpChannel);
+ const newHttpChannel = newChannel.QueryInterface(Ci.nsIHttpChannel);
+ const browser = this._getBrowserForChannel(oldHttpChannel);
+ if (!browser)
+ return;
+ const oldRequestId = this._requestId(oldHttpChannel);
+ const newRequestId = this._requestId(newHttpChannel);
+ if (this._resumedRequestIdToHeaders.has(oldRequestId)) {
+ // When we call resetInterception on a request, we get a new "redirected" request for it.
+ const { method, headers, postData } = this._resumedRequestIdToHeaders.get(oldRequestId);
+ if (headers) {
+ // Apply new request headers from interception resume.
+ for (const header of requestHeaders(newChannel))
+ newChannel.setRequestHeader(header.name, '', false /* merge */);
+ for (const header of headers)
+ newChannel.setRequestHeader(header.name, header.value, false /* merge */);
+ }
+ if (method)
+ newChannel.requestMethod = method;
+ if (postData && newChannel instanceof Ci.nsIUploadChannel) {
+ const synthesized = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ synthesized.data = atob(postData);
+ newChannel.setUploadStream(synthesized, 'application/octet-stream', -1);
+ }
+ // Use the old request id for the new "redirected" request for protocol consistency.
+ this._resumedRequestIdToHeaders.delete(oldRequestId);
+ this._postResumeChannelIdToRequestId.set(newRequestId, oldRequestId);
+ } else if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL)) {
+ // Regular (non-internal) redirect.
+ this._redirectMap.set(newRequestId, oldRequestId);
+ }
+ }
+
+ observeActivity(channel, activityType, activitySubtype, timestamp, extraSizeData, extraStringData) {
+ if (activityType !== Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION)
+ return;
+ if (!(channel instanceof Ci.nsIHttpChannel))
+ return;
+ const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
+ const browser = this._getBrowserForChannel(httpChannel);
+ if (!browser)
+ return;
+ if (activitySubtype !== Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE)
+ return;
+ if (this._isResumedChannel(httpChannel))
+ return;
+ this._sendOnRequestFinished(httpChannel);
+ }
+
+ _getBrowserForChannel(httpChannel) {
+ let loadContext = null;
+ try {
+ if (httpChannel.notificationCallbacks)
+ loadContext = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext);
+ } catch (e) {}
+ try {
+ if (!loadContext && httpChannel.loadGroup)
+ loadContext = httpChannel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
+ } catch (e) { }
+ if (!loadContext || !this._browserSessionCount.has(loadContext.topFrameElement))
+ return;
+ return loadContext.topFrameElement;
+ }
+
+ _isResumedChannel(httpChannel) {
+ return this._postResumeChannelIdToRequestId.has(httpChannel.channelId + '');
+ }
+
+ _onRequest(channel, topic) {
+ if (!(channel instanceof Ci.nsIHttpChannel))