Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
"use strict";
6
7
// This is loaded into all XUL windows. Wrap in a block to prevent
8
// leaking to window scope.
9
{
10
const { Services } = ChromeUtils.import(
12
);
13
const { AppConstants } = ChromeUtils.import(
15
);
16
17
let LazyModules = {};
18
19
ChromeUtils.defineModuleGetter(
20
LazyModules,
21
"PermitUnloader",
23
);
24
25
const elementsToDestroyOnUnload = new Set();
26
27
window.addEventListener(
28
"unload",
29
() => {
30
for (let element of elementsToDestroyOnUnload.values()) {
31
element.destroy();
32
}
33
elementsToDestroyOnUnload.clear();
34
},
35
{ mozSystemGroup: true, once: true }
36
);
37
38
class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
39
static get observedAttributes() {
40
return ["remote"];
41
}
42
43
attributeChangedCallback(name, oldValue, newValue) {
44
// When we have already been set up via connectedCallback and the
45
// and the [remote] value changes, we need to start over. This used
46
// to happen due to a XBL binding change.
47
//
48
// Only do this when the rebuild frameloaders pref is off. This update isn't
49
// required when we rebuild the frameloaders in the backend.
50
if (
51
!Services.prefs.getBoolPref(
52
"fission.rebuild_frameloaders_on_remoteness_change",
53
false
54
) &&
55
name === "remote" &&
56
oldValue != newValue &&
57
this.isConnectedAndReady
58
) {
59
this.destroy();
60
this.construct();
61
}
62
}
63
64
constructor() {
65
super();
66
67
this.onPageHide = this.onPageHide.bind(this);
68
69
this.isNavigating = false;
70
71
this._documentURI = null;
72
this._characterSet = null;
73
this._documentContentType = null;
74
75
/**
76
* These are managed by the tabbrowser:
77
*/
78
this.droppedLinkHandler = null;
79
this.mIconURL = null;
80
this.lastURI = null;
81
82
this.addEventListener(
83
"keypress",
84
event => {
85
if (event.keyCode != KeyEvent.DOM_VK_F7) {
86
return;
87
}
88
89
if (event.defaultPrevented || !event.isTrusted) {
90
return;
91
}
92
93
const kPrefShortcutEnabled =
94
"accessibility.browsewithcaret_shortcut.enabled";
95
const kPrefWarnOnEnable = "accessibility.warn_on_browsewithcaret";
96
const kPrefCaretBrowsingOn = "accessibility.browsewithcaret";
97
98
var isEnabled = this.mPrefs.getBoolPref(kPrefShortcutEnabled);
99
if (!isEnabled) {
100
return;
101
}
102
103
// Toggle browse with caret mode
104
var browseWithCaretOn = this.mPrefs.getBoolPref(
105
kPrefCaretBrowsingOn,
106
false
107
);
108
var warn = this.mPrefs.getBoolPref(kPrefWarnOnEnable, true);
109
if (warn && !browseWithCaretOn) {
110
var checkValue = { value: false };
111
var promptService = Services.prompt;
112
113
var buttonPressed = promptService.confirmEx(
114
window,
115
this.mStrBundle.GetStringFromName(
116
"browsewithcaret.checkWindowTitle"
117
),
118
this.mStrBundle.GetStringFromName("browsewithcaret.checkLabel"),
119
// Make "No" the default:
120
promptService.STD_YES_NO_BUTTONS |
121
promptService.BUTTON_POS_1_DEFAULT,
122
null,
123
null,
124
null,
125
this.mStrBundle.GetStringFromName("browsewithcaret.checkMsg"),
126
checkValue
127
);
128
if (buttonPressed != 0) {
129
if (checkValue.value) {
130
try {
131
this.mPrefs.setBoolPref(kPrefShortcutEnabled, false);
132
} catch (ex) {}
133
}
134
return;
135
}
136
if (checkValue.value) {
137
try {
138
this.mPrefs.setBoolPref(kPrefWarnOnEnable, false);
139
} catch (ex) {}
140
}
141
}
142
143
// Toggle the pref
144
try {
145
this.mPrefs.setBoolPref(kPrefCaretBrowsingOn, !browseWithCaretOn);
146
} catch (ex) {}
147
},
148
{ mozSystemGroup: true }
149
);
150
151
this.addEventListener(
152
"dragover",
153
event => {
154
if (!this.droppedLinkHandler || event.defaultPrevented) {
155
return;
156
}
157
158
// For drags that appear to be internal text (for example, tab drags),
159
// set the dropEffect to 'none'. This prevents the drop even if some
160
// other listener cancelled the event.
161
var types = event.dataTransfer.types;
162
if (
163
types.includes("text/x-moz-text-internal") &&
164
!types.includes("text/plain")
165
) {
166
event.dataTransfer.dropEffect = "none";
167
event.stopPropagation();
168
event.preventDefault();
169
}
170
171
// No need to handle "dragover" in e10s, since nsDocShellTreeOwner.cpp in the child process
172
// handles that case using "@mozilla.org/content/dropped-link-handler;1" service.
173
if (this.isRemoteBrowser) {
174
return;
175
}
176
177
let linkHandler = Services.droppedLinkHandler;
178
if (linkHandler.canDropLink(event, false)) {
179
event.preventDefault();
180
}
181
},
182
{ mozSystemGroup: true }
183
);
184
185
this.addEventListener(
186
"drop",
187
event => {
188
// No need to handle "drop" in e10s, since nsDocShellTreeOwner.cpp in the child process
189
// handles that case using "@mozilla.org/content/dropped-link-handler;1" service.
190
if (
191
!this.droppedLinkHandler ||
192
event.defaultPrevented ||
193
this.isRemoteBrowser
194
) {
195
return;
196
}
197
198
let linkHandler = Services.droppedLinkHandler;
199
try {
200
// Pass true to prevent the dropping of javascript:/data: URIs
201
var links = linkHandler.dropLinks(event, true);
202
} catch (ex) {
203
return;
204
}
205
206
if (links.length) {
207
let triggeringPrincipal = linkHandler.getTriggeringPrincipal(event);
208
this.droppedLinkHandler(event, links, triggeringPrincipal);
209
}
210
},
211
{ mozSystemGroup: true }
212
);
213
214
this.addEventListener("dragstart", event => {
215
// If we're a remote browser dealing with a dragstart, stop it
216
// from propagating up, since our content process should be dealing
217
// with the mouse movement.
218
if (this.isRemoteBrowser) {
219
event.stopPropagation();
220
}
221
});
222
}
223
224
resetFields() {
225
if (this.observer) {
226
try {
227
Services.obs.removeObserver(
228
this.observer,
229
"browser:purge-session-history"
230
);
231
} catch (ex) {
232
// It's not clear why this sometimes throws an exception.
233
}
234
this.observer = null;
235
}
236
237
let browser = this;
238
this.observer = {
239
observe(aSubject, aTopic, aState) {
240
if (aTopic == "browser:purge-session-history") {
241
browser.purgeSessionHistory();
242
} else if (aTopic == "apz:cancel-autoscroll") {
243
if (aState == browser._autoScrollScrollId) {
244
// Set this._autoScrollScrollId to null, so in stopScroll() we
245
// don't call stopApzAutoscroll() (since it's APZ that
246
// initiated the stopping).
247
browser._autoScrollScrollId = null;
248
browser._autoScrollPresShellId = null;
249
250
browser._autoScrollPopup.hidePopup();
251
}
252
}
253
},
254
QueryInterface: ChromeUtils.generateQI([
255
Ci.nsIObserver,
256
Ci.nsISupportsWeakReference,
257
]),
258
};
259
260
this._documentURI = null;
261
262
this._documentContentType = null;
263
264
/**
265
* Weak reference to an optional frame loader that can be used to influence
266
* process selection for this browser.
267
* See nsIBrowser.sameProcessAsFrameLoader.
268
*/
269
this._sameProcessAsFrameLoader = null;
270
271
this._loadContext = null;
272
273
this._imageDocument = null;
274
275
this._webBrowserFind = null;
276
277
this._finder = null;
278
279
this._remoteFinder = null;
280
281
this._fastFind = null;
282
283
this._outerWindowID = null;
284
285
this._innerWindowID = null;
286
287
this._lastSearchString = null;
288
289
this._controller = null;
290
291
this._remoteWebNavigation = null;
292
293
this._remoteWebProgress = null;
294
295
this._contentTitle = "";
296
297
this._characterSet = "";
298
299
this._mayEnableCharacterEncodingMenu = null;
300
301
this._charsetAutodetected = false;
302
303
this._contentPrincipal = null;
304
305
this._contentStoragePrincipal = null;
306
307
this._csp = null;
308
309
this._referrerInfo = null;
310
311
this._contentRequestContextID = null;
312
313
this._fullZoom = 1;
314
315
this._textZoom = 1;
316
317
this._isSyntheticDocument = false;
318
319
this.mPrefs = Services.prefs;
320
321
this._mStrBundle = null;
322
323
this.blockedPopups = null;
324
325
this._audioMuted = false;
326
327
this._hasAnyPlayingMediaBeenBlocked = false;
328
329
/**
330
* Only send the message "Browser:UnselectedTabHover" when someone requests
331
* for the message, which can reduce non-necessary communication.
332
*/
333
this._shouldSendUnselectedTabHover = false;
334
335
this._unselectedTabHoverMessageListenerCount = 0;
336
337
this._securityUI = null;
338
339
this.urlbarChangeTracker = {
340
_startedLoadSinceLastUserTyping: false,
341
342
startedLoad() {
343
this._startedLoadSinceLastUserTyping = true;
344
},
345
finishedLoad() {
346
this._startedLoadSinceLastUserTyping = false;
347
},
348
userTyped() {
349
this._startedLoadSinceLastUserTyping = false;
350
},
351
};
352
353
this._userTypedValue = null;
354
355
this._AUTOSCROLL_SNAP = 10;
356
357
this._scrolling = false;
358
359
this._startX = null;
360
361
this._startY = null;
362
363
this._autoScrollPopup = null;
364
365
this._autoScrollNeedsCleanup = false;
366
367
/**
368
* These IDs identify the scroll frame being autoscrolled.
369
*/
370
this._autoScrollScrollId = null;
371
372
this._autoScrollPresShellId = null;
373
}
374
375
connectedCallback() {
376
// We typically use this to avoid running JS that triggers a layout during parse
377
// (see comment on the delayConnectedCallback implementation). In this case, we
378
// are using it to avoid a leak - see https://bugzilla.mozilla.org/show_bug.cgi?id=1441935#c20.
379
if (this.delayConnectedCallback()) {
380
return;
381
}
382
383
this.construct();
384
}
385
386
disconnectedCallback() {
387
this.destroy();
388
}
389
390
get autoscrollEnabled() {
391
if (this.getAttribute("autoscroll") == "false") {
392
return false;
393
}
394
395
return this.mPrefs.getBoolPref("general.autoScroll", true);
396
}
397
398
get canGoBack() {
399
return this.webNavigation.canGoBack;
400
}
401
402
get canGoForward() {
403
return this.webNavigation.canGoForward;
404
}
405
406
get currentURI() {
407
if (this.webNavigation) {
408
return this.webNavigation.currentURI;
409
}
410
return null;
411
}
412
413
get documentURI() {
414
return this.isRemoteBrowser
415
? this._documentURI
416
: this.contentDocument.documentURIObject;
417
}
418
419
get documentContentType() {
420
if (this.isRemoteBrowser) {
421
return this._documentContentType;
422
}
423
return this.contentDocument ? this.contentDocument.contentType : null;
424
}
425
426
set documentContentType(aContentType) {
427
if (aContentType != null) {
428
if (this.isRemoteBrowser) {
429
this._documentContentType = aContentType;
430
} else {
431
this.contentDocument.documentContentType = aContentType;
432
}
433
}
434
}
435
436
set sameProcessAsFrameLoader(val) {
437
this._sameProcessAsFrameLoader = Cu.getWeakReference(val);
438
}
439
440
get sameProcessAsFrameLoader() {
441
return (
442
this._sameProcessAsFrameLoader && this._sameProcessAsFrameLoader.get()
443
);
444
}
445
446
get loadContext() {
447
if (this._loadContext) {
448
return this._loadContext;
449
}
450
451
let { frameLoader } = this;
452
if (!frameLoader) {
453
return null;
454
}
455
this._loadContext = frameLoader.loadContext;
456
return this._loadContext;
457
}
458
459
get autoCompletePopup() {
460
return document.getElementById(this.getAttribute("autocompletepopup"));
461
}
462
463
get dateTimePicker() {
464
return document.getElementById(this.getAttribute("datetimepicker"));
465
}
466
467
/**
468
* Provides a node to hang popups (such as the datetimepicker) from.
469
* If this <browser> isn't the descendant of a <stack>, null is returned
470
* instead and popup code must handle this case.
471
*/
472
get popupAnchor() {
473
let stack = this.closest("stack");
474
if (!stack) {
475
return null;
476
}
477
478
let popupAnchor = stack.querySelector(".popup-anchor");
479
if (popupAnchor) {
480
return popupAnchor;
481
}
482
483
// Create an anchor for the popup
484
popupAnchor = document.createXULElement("hbox");
485
popupAnchor.className = "popup-anchor";
486
popupAnchor.hidden = true;
487
stack.appendChild(popupAnchor);
488
return popupAnchor;
489
}
490
491
set docShellIsActive(val) {
492
if (this.isRemoteBrowser) {
493
let { frameLoader } = this;
494
if (frameLoader && frameLoader.remoteTab) {
495
frameLoader.remoteTab.docShellIsActive = val;
496
}
497
} else if (this.docShell) {
498
this.docShell.isActive = val;
499
}
500
}
501
502
get docShellIsActive() {
503
if (this.isRemoteBrowser) {
504
let { frameLoader } = this;
505
if (frameLoader && frameLoader.remoteTab) {
506
return frameLoader.remoteTab.docShellIsActive;
507
}
508
return false;
509
}
510
return this.docShell && this.docShell.isActive;
511
}
512
513
set renderLayers(val) {
514
if (this.isRemoteBrowser) {
515
let { frameLoader } = this;
516
if (frameLoader && frameLoader.remoteTab) {
517
frameLoader.remoteTab.renderLayers = val;
518
}
519
} else {
520
this.docShellIsActive = val;
521
}
522
}
523
524
get renderLayers() {
525
if (this.isRemoteBrowser) {
526
let { frameLoader } = this;
527
if (frameLoader && frameLoader.remoteTab) {
528
return frameLoader.remoteTab.renderLayers;
529
}
530
return false;
531
}
532
return this.docShellIsActive;
533
}
534
535
get hasLayers() {
536
if (this.isRemoteBrowser) {
537
let { frameLoader } = this;
538
if (frameLoader && frameLoader.remoteTab) {
539
return frameLoader.remoteTab.hasLayers;
540
}
541
return false;
542
}
543
544
return this.docShellIsActive;
545
}
546
547
get imageDocument() {
548
if (this.isRemoteBrowser) {
549
return this._imageDocument;
550
}
551
var document = this.contentDocument;
552
if (!document || !(document instanceof Ci.nsIImageDocument)) {
553
return null;
554
}
555
556
try {
557
return {
558
width: document.imageRequest.image.width,
559
height: document.imageRequest.image.height,
560
};
561
} catch (e) {}
562
return null;
563
}
564
565
get isRemoteBrowser() {
566
return this.getAttribute("remote") == "true";
567
}
568
569
get remoteType() {
570
if (!this.isRemoteBrowser || !this.messageManager) {
571
return null;
572
}
573
574
return this.messageManager.remoteType;
575
}
576
577
get isCrashed() {
578
if (!this.isRemoteBrowser || !this.frameLoader) {
579
return false;
580
}
581
582
return !this.frameLoader.remoteTab;
583
}
584
585
get messageManager() {
586
// Bug 1524084 - Trying to get at the message manager while in the crashed state will
587
// create a new message manager that won't shut down properly when the crashed browser
588
// is removed from the DOM. We work around that right now by returning null if we're
589
// in the crashed state.
590
if (this.frameLoader && !this.isCrashed) {
591
return this.frameLoader.messageManager;
592
}
593
return null;
594
}
595
596
get webBrowserFind() {
597
if (!this._webBrowserFind) {
598
this._webBrowserFind = this.docShell
599
.QueryInterface(Ci.nsIInterfaceRequestor)
600
.getInterface(Ci.nsIWebBrowserFind);
601
}
602
return this._webBrowserFind;
603
}
604
605
get finder() {
606
if (this.isRemoteBrowser) {
607
if (!this._remoteFinder) {
608
// Don't attempt to create the remote finder if the
609
// messageManager has already gone away
610
if (!this.messageManager) {
611
return null;
612
}
613
615
let { FinderParent } = ChromeUtils.import(jsm, {});
616
this._remoteFinder = new FinderParent(this);
617
}
618
return this._remoteFinder;
619
}
620
if (!this._finder) {
621
if (!this.docShell) {
622
return null;
623
}
624
625
let Finder = ChromeUtils.import("resource://gre/modules/Finder.jsm", {})
626
.Finder;
627
this._finder = new Finder(this.docShell);
628
}
629
return this._finder;
630
}
631
632
get fastFind() {
633
if (!this._fastFind) {
634
if (!("@mozilla.org/typeaheadfind;1" in Cc)) {
635
return null;
636
}
637
638
var tabBrowser = this.getTabBrowser();
639
if (tabBrowser && "fastFind" in tabBrowser) {
640
return (this._fastFind = tabBrowser.fastFind);
641
}
642
643
if (!this.docShell) {
644
return null;
645
}
646
647
this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(
648
Ci.nsITypeAheadFind
649
);
650
this._fastFind.init(this.docShell);
651
}
652
return this._fastFind;
653
}
654
655
get outerWindowID() {
656
if (this.isRemoteBrowser) {
657
return this._outerWindowID;
658
}
659
return this.docShell.outerWindowID;
660
}
661
662
get innerWindowID() {
663
if (this.isRemoteBrowser) {
664
return this._innerWindowID;
665
}
666
try {
667
return this.contentWindow.windowUtils.currentInnerWindowID;
668
} catch (e) {
669
if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
670
throw e;
671
}
672
return null;
673
}
674
}
675
676
get browsingContext() {
677
return this.frameLoader.browsingContext;
678
}
679
/**
680
* Note that this overrides webNavigation on XULFrameElement, and duplicates the return value for the non-remote case
681
*/
682
get webNavigation() {
683
return this.isRemoteBrowser
684
? this._remoteWebNavigation
685
: this.docShell && this.docShell.QueryInterface(Ci.nsIWebNavigation);
686
}
687
688
get webProgress() {
689
return this.isRemoteBrowser
690
? this._remoteWebProgress
691
: this.docShell &&
692
this.docShell
693
.QueryInterface(Ci.nsIInterfaceRequestor)
694
.getInterface(Ci.nsIWebProgress);
695
}
696
697
get sessionHistory() {
698
return this.webNavigation.sessionHistory;
699
}
700
701
get markupDocumentViewer() {
702
return this.docShell.contentViewer;
703
}
704
705
get contentTitle() {
706
return this.isRemoteBrowser
707
? this._contentTitle
708
: this.contentDocument.title;
709
}
710
711
set characterSet(val) {
712
if (this.isRemoteBrowser) {
713
this.sendMessageToActor(
714
"UpdateCharacterSet",
715
{ value: val },
716
"BrowserTab"
717
);
718
this._characterSet = val;
719
} else {
720
this.docShell.charset = val;
721
this.docShell.gatherCharsetMenuTelemetry();
722
}
723
}
724
725
get characterSet() {
726
return this.isRemoteBrowser ? this._characterSet : this.docShell.charset;
727
}
728
729
get mayEnableCharacterEncodingMenu() {
730
return this.isRemoteBrowser
731
? this._mayEnableCharacterEncodingMenu
732
: this.docShell.mayEnableCharacterEncodingMenu;
733
}
734
735
set mayEnableCharacterEncodingMenu(aMayEnable) {
736
if (this.isRemoteBrowser) {
737
this._mayEnableCharacterEncodingMenu = aMayEnable;
738
}
739
}
740
741
get charsetAutodetected() {
742
return this.isRemoteBrowser
743
? this._charsetAutodetected
744
: this.docShell.charsetAutodetected;
745
}
746
747
set charsetAutodetected(aAutodetected) {
748
if (this.isRemoteBrowser) {
749
this._charsetAutodetected = aAutodetected;
750
}
751
}
752
753
get contentPrincipal() {
754
return this.isRemoteBrowser
755
? this._contentPrincipal
756
: this.contentDocument.nodePrincipal;
757
}
758
759
get contentStoragePrincipal() {
760
return this.isRemoteBrowser
761
? this._contentStoragePrincipal
762
: this.contentDocument.effectiveStoragePrincipal;
763
}
764
765
get csp() {
766
return this.isRemoteBrowser ? this._csp : this.contentDocument.csp;
767
}
768
769
get contentRequestContextID() {
770
if (this.isRemoteBrowser) {
771
return this._contentRequestContextID;
772
}
773
try {
774
return this.contentDocument.documentLoadGroup.requestContextID;
775
} catch (e) {
776
return null;
777
}
778
}
779
780
set showWindowResizer(val) {
781
if (val) {
782
this.setAttribute("showresizer", "true");
783
} else {
784
this.removeAttribute("showresizer");
785
}
786
}
787
788
get showWindowResizer() {
789
return this.getAttribute("showresizer") == "true";
790
}
791
792
set fullZoom(val) {
793
if (this.isRemoteBrowser) {
794
let changed = val.toFixed(2) != this._fullZoom.toFixed(2);
795
796
if (changed) {
797
this._fullZoom = val;
798
this.sendMessageToActor("FullZoom", { value: val }, "Zoom", true);
799
800
let event = new Event("FullZoomChange", { bubbles: true });
801
this.dispatchEvent(event);
802
}
803
} else {
804
this.markupDocumentViewer.fullZoom = val;
805
}
806
}
807
808
get referrerInfo() {
809
return this.isRemoteBrowser
810
? this._referrerInfo
811
: this.contentDocument.referrerInfo;
812
}
813
814
get fullZoom() {
815
if (this.isRemoteBrowser) {
816
return this._fullZoom;
817
}
818
return this.markupDocumentViewer.fullZoom;
819
}
820
821
set textZoom(val) {
822
if (this.isRemoteBrowser) {
823
let changed = val.toFixed(2) != this._textZoom.toFixed(2);
824
825
if (changed) {
826
this._textZoom = val;
827
this.sendMessageToActor("TextZoom", { value: val }, "Zoom", true);
828
829
let event = new Event("TextZoomChange", { bubbles: true });
830
this.dispatchEvent(event);
831
}
832
} else {
833
this.markupDocumentViewer.textZoom = val;
834
}
835
}
836
837
get textZoom() {
838
if (this.isRemoteBrowser) {
839
return this._textZoom;
840
}
841
return this.markupDocumentViewer.textZoom;
842
}
843
844
get isSyntheticDocument() {
845
if (this.isRemoteBrowser) {
846
return this._isSyntheticDocument;
847
}
848
return this.contentDocument.mozSyntheticDocument;
849
}
850
851
get hasContentOpener() {
852
if (this.isRemoteBrowser) {
853
return this.frameLoader.remoteTab.hasContentOpener;
854
}
855
return !!this.contentWindow.opener;
856
}
857
858
get mStrBundle() {
859
if (!this._mStrBundle) {
860
// need to create string bundle manually instead of using <xul:stringbundle/>
861
// see bug 63370 for details
862
this._mStrBundle = Services.strings.createBundle(
864
);
865
}
866
return this._mStrBundle;
867
}
868
869
get audioMuted() {
870
return this._audioMuted;
871
}
872
873
get shouldHandleUnselectedTabHover() {
874
return this._shouldSendUnselectedTabHover;
875
}
876
877
get securityUI() {
878
if (this.isRemoteBrowser) {
879
if (!this._securityUI) {
880
// Don't attempt to create the remote web progress if the
881
// messageManager has already gone away
882
if (!this.messageManager) {
883
return null;
884
}
885
887
let RemoteSecurityUI = ChromeUtils.import(jsm, {}).RemoteSecurityUI;
888
this._securityUI = new RemoteSecurityUI();
889
}
890
891
// We want to double-wrap the JS implemented interface, so that QI and instanceof works.
892
var ptr = Cc[
893
"@mozilla.org/supports-interface-pointer;1"
894
].createInstance(Ci.nsISupportsInterfacePointer);
895
ptr.data = this._securityUI;
896
return ptr.data.QueryInterface(Ci.nsISecureBrowserUI);
897
}
898
899
if (!this.docShell.securityUI) {
900
const SECUREBROWSERUI_CONTRACTID = "@mozilla.org/secure_browser_ui;1";
901
var securityUI = Cc[SECUREBROWSERUI_CONTRACTID].createInstance(
902
Ci.nsISecureBrowserUI
903
);
904
securityUI.init(this.docShell);
905
}
906
907
return this.docShell.securityUI;
908
}
909
910
set userTypedValue(val) {
911
this.urlbarChangeTracker.userTyped();
912
this._userTypedValue = val;
913
}
914
915
get userTypedValue() {
916
return this._userTypedValue;
917
}
918
919
get dontPromptAndDontUnload() {
920
return 1;
921
}
922
923
get dontPromptAndUnload() {
924
return 2;
925
}
926
927
_wrapURIChangeCall(fn) {
928
if (!this.isRemoteBrowser) {
929
this.isNavigating = true;
930
try {
931
fn();
932
} finally {
933
this.isNavigating = false;
934
}
935
} else {
936
fn();
937
}
938
}
939
940
goBack() {
941
var webNavigation = this.webNavigation;
942
if (webNavigation.canGoBack) {
943
this._wrapURIChangeCall(() => webNavigation.goBack());
944
}
945
}
946
947
goForward() {
948
var webNavigation = this.webNavigation;
949
if (webNavigation.canGoForward) {
950
this._wrapURIChangeCall(() => webNavigation.goForward());
951
}
952
}
953
954
reload() {
955
const nsIWebNavigation = Ci.nsIWebNavigation;
956
const flags = nsIWebNavigation.LOAD_FLAGS_NONE;
957
this.reloadWithFlags(flags);
958
}
959
960
reloadWithFlags(aFlags) {
961
this.webNavigation.reload(aFlags);
962
}
963
964
stop() {
965
const nsIWebNavigation = Ci.nsIWebNavigation;
966
const flags = nsIWebNavigation.STOP_ALL;
967
this.webNavigation.stop(flags);
968
}
969
970
/**
971
* throws exception for unknown schemes
972
*/
973
loadURI(aURI, aParams = {}) {
974
if (!aURI) {
975
aURI = "about:blank";
976
}
977
let {
978
referrerInfo,
979
triggeringPrincipal,
980
postData,
981
headers,
982
csp,
983
} = aParams;
984
let loadFlags =
985
aParams.loadFlags ||
986
aParams.flags ||
987
Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
988
let loadURIOptions = {
989
triggeringPrincipal,
990
csp,
991
referrerInfo,
992
loadFlags,
993
postData,
994
headers,
995
};
996
this._wrapURIChangeCall(() =>
997
this.webNavigation.loadURI(aURI, loadURIOptions)
998
);
999
}
1000
1001
gotoIndex(aIndex) {
1002
this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(aIndex));
1003
}
1004
1005
/**
1006
* Used by session restore to ensure that currentURI is set so
1007
* that switch-to-tab works before the tab is fully
1008
* restored. This function also invokes onLocationChanged
1009
* listeners in tabbrowser.xml.
1010
*/
1011
_setCurrentURI(aURI) {
1012
if (this.isRemoteBrowser) {
1013
this._remoteWebProgressManager.setCurrentURI(aURI);
1014
} else {
1015
this.docShell.setCurrentURI(aURI);
1016
}
1017
}
1018
1019
preserveLayers(preserve) {
1020
if (!this.isRemoteBrowser) {
1021
return;
1022
}
1023
let { frameLoader } = this;
1024
if (frameLoader.remoteTab) {
1025
frameLoader.remoteTab.preserveLayers(preserve);
1026
}
1027
}
1028
1029
deprioritize() {
1030
if (!this.isRemoteBrowser) {
1031
return;
1032
}
1033
let { frameLoader } = this;
1034
if (frameLoader.remoteTab) {
1035
frameLoader.remoteTab.deprioritize();
1036
}
1037
}
1038
1039
forceRepaint() {
1040
if (!this.isRemoteBrowser) {
1041
return;
1042
}
1043
let { frameLoader } = this;
1044
if (frameLoader && frameLoader.remoteTab) {
1045
frameLoader.remoteTab.forceRepaint();
1046
}
1047
}
1048
1049
getTabBrowser() {
1050
if (
1051
this.ownerGlobal.gBrowser &&
1052
this.ownerGlobal.gBrowser.getTabForBrowser &&
1053
this.ownerGlobal.gBrowser.getTabForBrowser(this)
1054
) {
1055
return this.ownerGlobal.gBrowser;
1056
}
1057
return null;
1058
}
1059
1060
addProgressListener(aListener, aNotifyMask) {
1061
if (!aNotifyMask) {
1062
aNotifyMask = Ci.nsIWebProgress.NOTIFY_ALL;
1063
}
1064
this.webProgress.addProgressListener(aListener, aNotifyMask);
1065
}
1066
1067
removeProgressListener(aListener) {
1068
this.webProgress.removeProgressListener(aListener);
1069
}
1070
1071
onPageHide(aEvent) {
1072
if (!this.docShell || !this.fastFind) {
1073
return;
1074
}
1075
var tabBrowser = this.getTabBrowser();
1076
if (
1077
!tabBrowser ||
1078
!("fastFind" in tabBrowser) ||
1079
tabBrowser.selectedBrowser == this
1080
) {
1081
this.fastFind.setDocShell(this.docShell);
1082
}
1083
}
1084
1085
updateBlockedPopups() {
1086
let event = document.createEvent("Events");
1087
event.initEvent("DOMUpdateBlockedPopups", true, true);
1088
this.dispatchEvent(event);
1089
}
1090
1091
retrieveListOfBlockedPopups() {
1092
this.messageManager.sendAsyncMessage(
1093
"PopupBlocking:GetBlockedPopupList",
1094
null
1095
);
1096
return new Promise(resolve => {
1097
let self = this;
1098
this.messageManager.addMessageListener(
1099
"PopupBlocking:ReplyGetBlockedPopupList",
1100
function replyReceived(msg) {
1101
self.messageManager.removeMessageListener(
1102
"PopupBlocking:ReplyGetBlockedPopupList",
1103
replyReceived
1104
);
1105
resolve(msg.data.popupData);
1106
}
1107
);
1108
});
1109
}
1110
1111
unblockPopup(aPopupIndex) {
1112
this.messageManager.sendAsyncMessage("PopupBlocking:UnblockPopup", {
1113
index: aPopupIndex,
1114
});
1115
}
1116
1117
audioPlaybackStarted() {
1118
if (this._audioMuted) {
1119
return;
1120
}
1121
let event = document.createEvent("Events");
1122
event.initEvent("DOMAudioPlaybackStarted", true, false);
1123
this.dispatchEvent(event);
1124
}
1125
1126
audioPlaybackStopped() {
1127
let event = document.createEvent("Events");
1128
event.initEvent("DOMAudioPlaybackStopped", true, false);
1129
this.dispatchEvent(event);
1130
}
1131
1132
/**
1133
* When the pref "media.block-autoplay-until-in-foreground" is on,
1134
* Gecko delays starting playback of media resources in tabs until the
1135
* tab has been in the foreground or resumed by tab's play tab icon.
1136
* - When Gecko delays starting playback of a media resource in a window,
1137
* it sends a message to call activeMediaBlockStarted(). This causes the
1138
* tab audio indicator to show.
1139
* - When a tab is foregrounded, Gecko starts playing all delayed media
1140
* resources in that tab, and sends a message to call
1141
* activeMediaBlockStopped(). This causes the tab audio indicator to hide.
1142
*/
1143
activeMediaBlockStarted() {
1144
this._hasAnyPlayingMediaBeenBlocked = true;
1145
let event = document.createEvent("Events");
1146
event.initEvent("DOMAudioPlaybackBlockStarted", true, false);
1147
this.dispatchEvent(event);
1148
}
1149
1150
activeMediaBlockStopped() {
1151
if (!this._hasAnyPlayingMediaBeenBlocked) {
1152
return;
1153
}
1154
this._hasAnyPlayingMediaBeenBlocked = false;
1155
let event = document.createEvent("Events");
1156
event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
1157
this.dispatchEvent(event);
1158
}
1159
1160
mute(transientState) {
1161
if (!transientState) {
1162
this._audioMuted = true;
1163
}
1164
this.frameLoader.browsingContext.notifyMediaMutedChanged(true);
1165
}
1166
1167
unmute() {
1168
this._audioMuted = false;
1169
this.frameLoader.browsingContext.notifyMediaMutedChanged(false);
1170
}
1171
1172
pauseMedia(disposable) {
1173
let suspendedReason;
1174
if (disposable) {
1175
suspendedReason = "mediaControlPaused";
1176
} else {
1177
suspendedReason = "lostAudioFocusTransiently";
1178
}
1179
1180
this.messageManager.sendAsyncMessage("AudioPlayback", {
1181
type: suspendedReason,
1182
});
1183
}
1184
1185
stopMedia() {
1186
this.messageManager.sendAsyncMessage("AudioPlayback", {
1187
type: "mediaControlStopped",
1188
});
1189
}
1190
1191
resumeMedia() {
1192
this.frameLoader.browsingContext.notifyStartDelayedAutoplayMedia();
1193
if (this._hasAnyPlayingMediaBeenBlocked) {
1194
this._hasAnyPlayingMediaBeenBlocked = false;
1195
let event = document.createEvent("Events");
1196
event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
1197
this.dispatchEvent(event);
1198
}
1199
}
1200
1201
unselectedTabHover(hovered) {
1202
if (!this._shouldSendUnselectedTabHover) {
1203
return;
1204
}
1205
this.messageManager.sendAsyncMessage("Browser:UnselectedTabHover", {
1206
hovered,
1207
});
1208
}
1209
1210
didStartLoadSinceLastUserTyping() {
1211
return (
1212
!this.isNavigating &&
1213
this.urlbarChangeTracker._startedLoadSinceLastUserTyping
1214
);
1215
}
1216
1217
construct() {
1218
elementsToDestroyOnUnload.add(this);
1219
this.resetFields();
1220
this.mInitialized = true;
1221
if (this.isRemoteBrowser) {
1222
/*
1223
* Don't try to send messages from this function. The message manager for
1224
* the <browser> element may not be initialized yet.
1225
*/
1226
1227
this._remoteWebNavigation = Cc[
1228
"@mozilla.org/remote-web-navigation;1"
1229
].createInstance(Ci.nsIWebNavigation);
1230
this._remoteWebNavigationImpl = this._remoteWebNavigation.wrappedJSObject;
1231
this._remoteWebNavigationImpl.swapBrowser(this);
1232
1233
// Initialize contentPrincipal to the about:blank principal for this loadcontext
1234
let { Services } = ChromeUtils.import(
1236
);
1237
let aboutBlank = Services.io.newURI("about:blank");
1238
let ssm = Services.scriptSecurityManager;
1239
this._contentPrincipal = ssm.getLoadContextContentPrincipal(
1240
aboutBlank,
1241
this.loadContext
1242
);
1243
// CSP for about:blank is null; if we ever change _contentPrincipal above,
1244
// we should re-evaluate the CSP here.
1245
this._csp = null;
1246
1247
this.messageManager.addMessageListener("Browser:Init", this);
1248
this.messageManager.addMessageListener("DOMTitleChanged", this);
1249
this.messageManager.addMessageListener("ImageDocumentLoaded", this);
1250
1251
// browser-child messages, such as Content:LocationChange, are handled in
1252
// RemoteWebProgress, ensure it is loaded and ready.
1254
let { RemoteWebProgressManager } = ChromeUtils.import(jsm, {});
1255
1256
let oldManager = this._remoteWebProgressManager;
1257
this._remoteWebProgressManager = new RemoteWebProgressManager(this);
1258
if (oldManager) {
1259
// We're transitioning from one remote type to another. This means that
1260
// the RemoteWebProgress listener is listening to the old message manager,
1261
// and needs to be pointed at the new one.
1262
this._remoteWebProgressManager.swapListeners(oldManager);
1263
}
1264
1265
this._remoteWebProgress = this._remoteWebProgressManager.topLevelWebProgress;
1266
1267
this.messageManager.loadFrameScript(
1269
true
1270
);
1271
1272
if (!this.hasAttribute("disablehistory")) {
1273
Services.obs.addObserver(
1274
this.observer,
1275
"browser:purge-session-history",
1276
true
1277
);
1278
}
1279
1281
let scope = {};
1282
Services.scriptloader.loadSubScript(rc_js, scope);
1283
let RemoteController = scope.RemoteController;
1284
this._controller = new RemoteController(this);
1285
this.controllers.appendController(this._controller);
1286
}
1287
1288
try {
1289
// |webNavigation.sessionHistory| will have been set by the frame
1290
// loader when creating the docShell as long as this xul:browser
1291
// doesn't have the 'disablehistory' attribute set.
1292
if (this.docShell && this.webNavigation.sessionHistory) {
1293
Services.obs.addObserver(
1294
this.observer,
1295
"browser:purge-session-history",
1296
true
1297
);
1298
1299
// enable global history if we weren't told otherwise
1300
if (
1301
!this.hasAttribute("disableglobalhistory") &&
1302
!this.isRemoteBrowser
1303
) {
1304
try {
1305
this.docShell.useGlobalHistory = true;
1306
} catch (ex) {
1307
// This can occur if the Places database is locked
1308
Cu.reportError("Error enabling browser global history: " + ex);
1309
}
1310
}
1311
}
1312
} catch (e) {
1313
Cu.reportError(e);
1314
}
1315
try {
1316
// Ensures the securityUI is initialized.
1317
var securityUI = this.securityUI; // eslint-disable-line no-unused-vars
1318
} catch (e) {}
1319
1320
// tabbrowser.xml sets "sameProcessAsFrameLoader" as a direct property
1321
// on some browsers before they are put into a DOM (and get a
1322
// binding). This hack makes sure that we hold a weak reference to
1323
// the other browser (and go through the proper getter and setter).
1324
if (this.hasOwnProperty("sameProcessAsFrameLoader")) {
1325
var sameProcessAsFrameLoader = this.sameProcessAsFrameLoader;
1326
delete this.sameProcessAsFrameLoader;
1327
this.sameProcessAsFrameLoader = sameProcessAsFrameLoader;
1328
}
1329
1330
if (!this.isRemoteBrowser) {
1331
// If we've transitioned from remote to non-remote, we'll give up trying to
1332
// keep the web progress listeners persisted during the transition.
1333
delete this._remoteWebProgressManager;
1334
delete this._remoteWebProgress;
1335
1336
this.addEventListener("pagehide", this.onPageHide, true);
1337
}
1338
1339
if (this.messageManager) {
1340
this.messageManager.addMessageListener(
1341
"PopupBlocking:UpdateBlockedPopups",
1342
this
1343
);
1344
this.messageManager.addMessageListener("Autoscroll:Start", this);
1345
this.messageManager.addMessageListener("Autoscroll:Cancel", this);
1346
this.messageManager.addMessageListener("AudioPlayback:Start", this);
1347
this.messageManager.addMessageListener("AudioPlayback:Stop", this);
1348
this.messageManager.addMessageListener(
1349
"AudioPlayback:ActiveMediaBlockStart",
1350
this
1351
);
1352
this.messageManager.addMessageListener(
1353
"AudioPlayback:ActiveMediaBlockStop",
1354
this
1355
);
1356
this.messageManager.addMessageListener(
1357
"UnselectedTabHover:Toggle",
1358
this
1359
);
1360
}
1361
}
1362
1363
/**
1364
* This is necessary because the destructor doesn't always get called when
1365
* we are removed from a tabbrowser. This will be explicitly called by tabbrowser.
1366
*/
1367
destroy() {
1368
elementsToDestroyOnUnload.delete(this);
1369
1370
// Make sure that any open select is closed.
1371
if (this.hasAttribute("selectmenulist")) {
1372
let menulist = document.getElementById(
1373
this.getAttribute("selectmenulist")
1374
);
1375
if (menulist && menulist.open) {
1376
let resourcePath = "resource://gre/actors/SelectParent.jsm";
1377
let { SelectParentHelper } = ChromeUtils.import(resourcePath);
1378
SelectParentHelper.hide(menulist, this);
1379
}
1380
}
1381
1382
this.resetFields();
1383
1384
if (!this.mInitialized) {
1385
return;
1386
}
1387
1388
this.mInitialized = false;
1389
1390
if (this.isRemoteBrowser) {
1391
try {
1392
this.controllers.removeController(this._controller);
1393
} catch (ex) {
1394
// This can fail when this browser element is not attached to a
1395
// BrowserDOMWindow.
1396
}
1397
return;
1398
}
1399
1400
this.lastURI = null;
1401
1402
if (!this.isRemoteBrowser) {
1403
this.removeEventListener("pagehide", this.onPageHide, true);
1404
}
1405
1406
if (this._autoScrollNeedsCleanup) {
1407
// we polluted the global scope, so clean it up
1408
this._autoScrollPopup.remove();
1409
}
1410
}
1411
1412
/**
1413
* We call this _receiveMessage (and alias receiveMessage to it) so that
1414
* bindings that inherit from this one can delegate to it.
1415
*/
1416
_receiveMessage(aMessage) {
1417
let data = aMessage.data;
1418
switch (aMessage.name) {
1419
case "PopupBlocking:UpdateBlockedPopups": {
1420
this.blockedPopups = {
1421
length: data.count,
1422
reported: !data.freshPopup,
1423
};
1424
1425
this.updateBlockedPopups();
1426
break;
1427
}
1428
case "Autoscroll:Start": {
1429
if (!this.autoscrollEnabled) {
1430
return { autoscrollEnabled: false, usingApz: false };
1431
}
1432
this.startScroll(data.scrolldir, data.screenX, data.screenY);
1433
let usingApz = false;
1434
if (
1435
this.isRemoteBrowser &&
1436
data.scrollId != null &&
1437
this.mPrefs.getBoolPref("apz.autoscroll.enabled", false)
1438
) {
1439
let { remoteTab } = this.frameLoader;
1440
if (remoteTab) {
1441
// If APZ is handling the autoscroll, it may decide to cancel
1442
// it of its own accord, so register an observer to allow it
1443
// to notify us of that.
1444
var os = Services.obs;
1445
os.addObserver(this.observer, "apz:cancel-autoscroll", true);
1446
1447
usingApz = remoteTab.startApzAutoscroll(
1448
data.screenX,
1449
data.screenY,
1450
data.scrollId,
1451
data.presShellId
1452
);
1453
}
1454
// Save the IDs for later
1455
this._autoScrollScrollId = data.scrollId;
1456
this._autoScrollPresShellId = data.presShellId;
1457
}
1458
return { autoscrollEnabled: true, usingApz };
1459
}
1460
case "Autoscroll:Cancel":
1461
this._autoScrollPopup.hidePopup();
1462
break;
1463
case "AudioPlayback:Start":
1464
this.audioPlaybackStarted();
1465
break;
1466
case "AudioPlayback:Stop":
1467
this.audioPlaybackStopped();
1468
break;
1469
case "AudioPlayback:ActiveMediaBlockStart":
1470
this.activeMediaBlockStarted();
1471
break;
1472
case "AudioPlayback:ActiveMediaBlockStop":
1473
this.activeMediaBlockStopped();
1474
break;
1475
case "UnselectedTabHover:Toggle":
1476
this._shouldSendUnselectedTabHover = data.enable
1477
? ++this._unselectedTabHoverMessageListenerCount > 0
1478
: --this._unselectedTabHoverMessageListenerCount == 0;
1479
break;
1480
}
1481
return undefined;
1482
}
1483
1484
receiveMessage(aMessage) {
1485
if (!this.isRemoteBrowser) {
1486
return this._receiveMessage(aMessage);
1487
}
1488
1489
let data = aMessage.data;
1490
switch (aMessage.name) {
1491
case "Browser:Init":
1492
this._outerWindowID = data.outerWindowID;
1493
break;
1494
case "DOMTitleChanged":
1495
this._contentTitle = data.title;
1496
break;
1497
case "ImageDocumentLoaded":
1498
this._imageDocument = {
1499
width: data.width,
1500
height: data.height,
1501
};
1502
break;
1503
default:
1504
return this._receiveMessage(aMessage);
1505
}
1506
return undefined;
1507
}
1508
1509
enableDisableCommandsRemoteOnly(
1510
aAction,
1511
aEnabledCommands,
1512
aDisabledCommands
1513
) {
1514
if (this._controller) {
1515
this._controller.enableDisableCommands(
1516
aAction,
1517
aEnabledCommands,
1518
aDisabledCommands
1519
);
1520
}
1521
}
1522
1523
updateSecurityUIForContentBlockingEvent(aEvent) {
1524
if (this.isRemoteBrowser && this.messageManager) {
1525
// Invoking this getter triggers the generation of the underlying object,
1526
// which we need to access with ._securityUI, because .securityUI returns
1527
// a wrapper that makes _update inaccessible.
1528
void this.securityUI;
1529
this._securityUI._updateContentBlockingEvent(aEvent);
1530
}
1531
}
1532
1533
get remoteWebProgressManager() {
1534
return this._remoteWebProgressManager;
1535
}
1536
1537
updateForStateChange(aCharset, aDocumentURI, aContentType) {
1538
if (this.isRemoteBrowser && this.messageManager) {
1539
if (aCharset != null) {
1540
this._characterSet = aCharset;
1541
}
1542
1543
if (aDocumentURI != null) {
1544
this._documentURI = aDocumentURI;
1545
}
1546
1547
if (aContentType != null) {
1548
this._documentContentType = aContentType;
1549
}
1550
}
1551
}
1552
1553
updateWebNavigationForLocationChange(aCanGoBack, aCanGoForward) {
1554
if (this.isRemoteBrowser && this.messageManager) {
1555
let remoteWebNav = this._remoteWebNavigationImpl;
1556
remoteWebNav.canGoBack = aCanGoBack;
1557
remoteWebNav.canGoForward = aCanGoForward;
1558
}
1559
}
1560
1561
updateForLocationChange(
1562
aLocation,
1563
aCharset,
1564
aMayEnableCharacterEncodingMenu,
1565
aCharsetAutodetected,
1566
aDocumentURI,
1567
aTitle,
1568
aContentPrincipal,
1569
aContentStoragePrincipal,
1570
aCSP,
1571
aReferrerInfo,
1572
aIsSynthetic,
1573
aInnerWindowID,
1574
aHaveRequestContextID,
1575
aRequestContextID,
1576
aContentType
1577
) {
1578
if (this.isRemoteBrowser && this.messageManager) {
1579
if (aCharset != null) {
1580
this._characterSet = aCharset;
1581
this._mayEnableCharacterEncodingMenu = aMayEnableCharacterEncodingMenu;
1582
this._charsetAutodetected = aCharsetAutodetected;
1583
}
1584
1585
if (aContentType != null) {
1586
this._documentContentType = aContentType;
1587
}
1588
1589
this._remoteWebNavigationImpl._currentURI = aLocation;
1590
this._documentURI = aDocumentURI;
1591
this._contentTile = aTitle;
1592
this._imageDocument = null;
1593
this._contentPrincipal = aContentPrincipal;
1594
this._contentStoragePrincipal = aContentStoragePrincipal;
1595
this._csp = aCSP;
1596
this._referrerInfo = aReferrerInfo;
1597
this._isSyntheticDocument = aIsSynthetic;
1598
this._innerWindowID = aInnerWindowID;
1599
this._contentRequestContextID = aHaveRequestContextID
1600
? aRequestContextID
1601
: null;
1602
}
1603
}
1604
1605
purgeSessionHistory() {
1606
if (this.isRemoteBrowser) {
1607
try {
1608
this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
1609
} catch (ex) {
1610
// This can throw if the browser has started to go away.
1611
if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
1612
throw ex;
1613
}
1614
}
1615
this._remoteWebNavigationImpl.canGoBack = false;
1616
this._remoteWebNavigationImpl.canGoForward = false;
1617
return;
1618
}
1619
this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
1620
}
1621
1622
createAboutBlankContentViewer(aPrincipal, aStoragePrincipal) {
1623
if (this.isRemoteBrowser) {
1624
// Ensure that the content process has the permissions which are
1625
// needed to create a document with the given principal.
1626
let permissionPrincipal = BrowserUtils.principalWithMatchingOA(
1627
aPrincipal,
1628
this.contentPrincipal
1629
);
1630
this.frameLoader.remoteTab.transmitPermissionsForPrincipal(
1631
permissionPrincipal
1632
);
1633
1634
// This still uses the message manager, for the following reasons:
1635
//
1636
// 1. Due to bug 1523638, it's virtually certain that, if we've just created
1637
// this <xul:browser>, that the WindowGlobalParent for the top-level frame
1638
// of this browser doesn't exist yet, so it's not possible to get at a
1639
// JS Window Actor for it.
1640
//
1641
// 2. JS Window Actors are tied to the principals for the frames they're running
1642
// in - switching principals is therefore self-destructive and unexpected.
1643
//
1644
// So we'll continue to use the message manager until we come up with a better
1645
// solution.
1646
this.messageManager.sendAsyncMessage(
1647
"BrowserElement:CreateAboutBlank",
1648
{ principal: aPrincipal, storagePrincipal: aStoragePrincipal }
1649
);
1650
return;
1651
}
1652
let principal = BrowserUtils.principalWithMatchingOA(
1653
aPrincipal,
1654
this.contentPrincipal
1655
);
1656
let storagePrincipal = BrowserUtils.principalWithMatchingOA(
1657
aStoragePrincipal,
1658
this.contentStoragePrincipal
1659
);
1660
this.docShell.createAboutBlankContentViewer(principal, storagePrincipal);
1661
}
1662
1663
stopScroll() {
1664
if (this._scrolling) {
1665
this._scrolling = false;
1666
window.removeEventListener("mousemove", this, true);
1667
window.removeEventListener("mousedown", this, true);
1668
window.removeEventListener("mouseup", this, true);
1669
window.removeEventListener("DOMMouseScroll", this, true);
1670
window.removeEventListener("contextmenu", this, true);
1671
window.removeEventListener("keydown", this, true);
1672
window.removeEventListener("keypress", this, true);
1673
window.removeEventListener("keyup", this, true);
1674
this.messageManager.sendAsyncMessage("Autoscroll:Stop");
1675
1676
try {
1677
Services.obs.removeObserver(this.observer, "apz:cancel-autoscroll");
1678
} catch (ex) {
1679
// It's not clear why this sometimes throws an exception
1680
}
1681
1682
if (this.isRemoteBrowser && this._autoScrollScrollId != null) {
1683
let { remoteTab } = this.frameLoader;
1684
if (remoteTab) {
1685
remoteTab.stopApzAutoscroll(
1686
this._autoScrollScrollId,
1687
this._autoScrollPresShellId
1688
);
1689
}
1690
this._autoScrollScrollId = null;
1691
this._autoScrollPresShellId = null;
1692
}
1693
}
1694
}
1695
1696
_createAutoScrollPopup() {
1697
var popup = document.createXULElement("panel");
1698
popup.className = "autoscroller";
1699
// We set this attribute on the element so that mousemove
1700
// events can be handled by browser-content.js.
1701
popup.setAttribute("mousethrough", "always");
1702
popup.setAttribute("consumeoutsideclicks", "true");
1703
popup.setAttribute("rolluponmousewheel", "true");
1704
popup.setAttribute("hidden", "true");
1705
return popup;
1706
}
1707
1708
startScroll(scrolldir, screenX, screenY) {
1709
const POPUP_SIZE = 32;
1710
if (!this._autoScrollPopup) {
1711
if (this.hasAttribute("autoscrollpopup")) {
1712
// our creator provided a popup to share
1713
this._autoScrollPopup = document.getElementById(
1714
this.getAttribute("autoscrollpopup")
1715
);
1716
} else {
1717
// we weren't provided a popup; we have to use the global scope
1718
this._autoScrollPopup = this._createAutoScrollPopup();
1719
document.documentElement.appendChild(this._autoScrollPopup);
1720
this._autoScrollNeedsCleanup = true;
1721
}
1722
this._autoScrollPopup.removeAttribute("hidden");
1723
this._autoScrollPopup.setAttribute("noautofocus", "true");
1724
this._autoScrollPopup.style.height = POPUP_SIZE + "px";
1725
this._autoScrollPopup.style.width = POPUP_SIZE + "px";
1726
this._autoScrollPopup.style.margin = -POPUP_SIZE / 2 + "px";
1727
}
1728
1729
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"].getService(
1730
Ci.nsIScreenManager
1731
);
1732
let screen = screenManager.screenForRect(screenX, screenY, 1, 1);
1733
1734
// we need these attributes so themers don't need to create per-platform packages
1735
if (screen.colorDepth > 8) {
1736
// need high color for transparency
1737
// Exclude second-rate platforms
1738
this._autoScrollPopup.setAttribute(
1739
"transparent",
1740
!/BeOS|OS\/2/.test(navigator.appVersion)
1741
);
1742
// Enable translucency on Windows and Mac
1743
this._autoScrollPopup.setAttribute(
1744
"translucent",
1745
AppConstants.platform == "win" || AppConstants.platform == "macosx"
1746
);
1747
}
1748
1749
this._autoScrollPopup.setAttribute("scrolldir", scrolldir);
1750
this._autoScrollPopup.addEventListener("popuphidden", this, true);
1751
1752
// Sanitize screenX/screenY for available screen size with half the size
1753
// of the popup removed. The popup uses negative margins to center on the
1754
// coordinates we pass. Unfortunately `window.screen.availLeft` can be negative
1755
// on Windows in multi-monitor setups, so we use nsIScreenManager instead:
1756
let left = {},
1757
top = {},
1758
width = {},
1759
height = {};
1760
screen.GetAvailRect(left, top, width, height);
1761
1762
// We need to get screen CSS-pixel (rather than display-pixel) coordinates.
1763
// With 175% DPI, the actual ratio of display pixels to CSS pixels is
1764
// 1.7647 because of rounding inside gecko. Unfortunately defaultCSSScaleFactor
1765
// returns the original 1.75 dpi factor. While window.devicePixelRatio would
1766
// get us the correct ratio, if the window is split between 2 screens,
1767
// window.devicePixelRatio isn't guaranteed to match the screen we're
1768
// autoscrolling on. So instead we do the same math as Gecko.
1769
const scaleFactor = 60 / Math.round(60 / screen.defaultCSSScaleFactor);
1770
let minX = left.value / scaleFactor + 0.5 * POPUP_SIZE;
1771
let maxX = (left.value + width.value) / scaleFactor - 0.5 * POPUP_SIZE;
1772
let minY = top.value / scaleFactor + 0.5 * POPUP_SIZE;
1773
let maxY = (top.value + height.value) / scaleFactor - 0.5 * POPUP_SIZE;
1774
let popupX = Math.max(minX, Math.min(maxX, screenX));
1775
let popupY = Math.max(minY, Math.min(maxY, screenY));
1776
this._autoScrollPopup.openPopupAtScreen(popupX, popupY);
1777
this._ignoreMouseEvents = true;
1778
this._scrolling = true;
1779
this._startX = screenX;
1780
this._startY = screenY;
1781
1782
window.addEventListener("mousemove", this, true);
1783
window.addEventListener("mousedown", this, true);
1784
window.addEventListener("mouseup", this, true);
1785
window.addEventListener("DOMMouseScroll", this, true);
1786
window.addEventListener("contextmenu", this, true);
1787
window.addEventListener("keydown", this, true);
1788
window.addEventListener("keypress", this, true);
1789
window.addEventListener("keyup", this, true);
1790
}
1791
1792
handleEvent(aEvent) {
1793
if (this._scrolling) {
1794
switch (aEvent.type) {
1795
case "mousemove": {
1796
var x = aEvent.screenX - this._startX;
1797
var y = aEvent.screenY - this._startY;
1798
1799
if (
1800
x > this._AUTOSCROLL_SNAP ||
1801
x < -this._AUTOSCROLL_SNAP ||
1802
(y > this._AUTOSCROLL_SNAP || y < -this._AUTOSCROLL_SNAP)
1803
) {
1804
this._ignoreMouseEvents = false;
1805
}
1806
break;
1807
}
1808
case "mouseup":
1809
case "mousedown":
1810
case "contextmenu": {
1811
if (!this._ignoreMouseEvents) {
1812
// Use a timeout to prevent the mousedown from opening the popup again.
1813
// Ideally, we could use preventDefault here, but contenteditable
1814
// and middlemouse paste don't interact well. See bug 1188536.
1815
setTimeout(() => this._autoScrollPopup.hidePopup(), 0);
1816
}
1817
this._ignoreMouseEvents = false;
1818
break;
1819
}
1820
case "DOMMouseScroll": {
1821
this._autoScrollPopup.hidePopup();
1822
aEvent.preventDefault();
1823
break;
1824
}
1825
case "popuphidden": {
1826
this._autoScrollPopup.removeEventListener(
1827
"popuphidden",
1828
this,
1829
true
1830
);
1831
this.stopScroll();
1832
break;
1833
}
1834
case "keydown": {
1835
if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
1836
// the escape key will be processed by
1837
// nsXULPopupManager::KeyDown and the panel will be closed.
1838
// So, don't consume the key event here.
1839
break;
1840
}
1841
// don't break here. we need to eat keydown events.
1842
}
1843
// fall through
1844
case "keypress":
1845
case "keyup": {
1846
// All keyevents should be eaten here during autoscrolling.
1847
aEvent.stopPropagation();
1848
aEvent.preventDefault();
1849
break;
1850
}
1851
}
1852
}
1853
}
1854
1855
closeBrowser() {
1856
// The request comes from a XPCOM component, we'd want to redirect
1857
// the request to tabbrowser.
1858
let tabbrowser = this.getTabBrowser();
1859
if (tabbrowser) {
1860
let tab = tabbrowser.getTabForBrowser(this);
1861
if (tab) {
1862
tabbrowser.removeTab(tab);
1863
return;
1864
}
1865
}
1866
1867
throw new Error(
1868
"Closing a browser which was not attached to a tabbrowser is unsupported."
1869
);
1870
}
1871
1872
swapBrowsers(aOtherBrowser) {
1873
// The request comes from a XPCOM component, we'd want to redirect
1874
// the request to tabbrowser so tabbrowser will be setup correctly,
1875
// and it will eventually call swapDocShells.
1876
let ourTabBrowser = this.getTabBrowser();
1877
let otherTabBrowser = aOtherBrowser.getTabBrowser();
1878
if (ourTabBrowser && otherTabBrowser) {
1879
let ourTab = ourTabBrowser.getTabForBrowser(this);
1880
let otherTab = otherTabBrowser.getTabForBrowser(aOtherBrowser);
1881
ourTabBrowser.swapBrowsers(ourTab, otherTab);
1882
return;
1883
}
1884
1885
// One of us is not connected to a tabbrowser, so just swap.
1886
this.swapDocShells(aOtherBrowser);
1887
}
1888
1889
swapDocShells(aOtherBrowser) {
1890
if (this.isRemoteBrowser != aOtherBrowser.isRemoteBrowser) {
1891
throw new Error(
1892
"Can only swap docshells between browsers in the same process."
1893
);
1894
}
1895
1896
// Give others a chance to swap state.
1897
// IMPORTANT: Since a swapDocShells call does not swap the messageManager
1898
// instances attached to a browser to aOtherBrowser, others
1899
// will need to add the message listeners to the new
1900
// messageManager.
1901
// This is not a bug in swapDocShells or the FrameLoader,
1902
// merely a design decision: If message managers were swapped,
1903
// so that no new listeners were needed, the new
1904
// aOtherBrowser.messageManager would have listeners pointing
1905
// to the JS global of the current browser, which would rather
1906
// easily create leaks while swapping.
1907
// IMPORTANT2: When the current browser element is removed from DOM,
1908
// which is quite common after a swpDocShells call, its
1909
// frame loader is destroyed, and that destroys the relevant
1910
// message manager, which will remove the listeners.
1911
let event = new CustomEvent("SwapDocShells", { detail: aOtherBrowser });
1912
this.dispatchEvent(event);
1913
event = new CustomEvent("SwapDocShells", { detail: this });
1914
aOtherBrowser.dispatchEvent(event);
1915
1916
// We need to swap fields that are tied to our docshell or related to
1917
// the loaded page
1918
// Fields which are built as a result of notifactions (pageshow/hide,
1919
// DOMLinkAdded/Removed, onStateChange) should not be swapped here,
1920
// because these notifications are dispatched again once the docshells
1921
// are swapped.
1922
var fieldsToSwap = ["_webBrowserFind"];
1923
1924
if (this.isRemoteBrowser) {
1925
fieldsToSwap.push(
1926
...[
1927
"_remoteWebNavigation",
1928
"_remoteWebNavigationImpl",
1929
"_remoteWebProgressManager",
1930
"_remoteWebProgress",
1931
"_remoteFinder",
1932
"_securityUI",
1933
"_documentURI",
1934
"_documentContentType",
1935
"_contentTitle",
1936
"_characterSet",
1937
"_mayEnableCharacterEncodingMenu",
1938
"_charsetAutodetected",
1939
"_contentPrincipal",
1940
"_imageDocument",
1941
"_fullZoom",
1942
"_textZoom",
1943
"_isSyntheticDocument",
1944
"_innerWindowID",
1945
]
1946
);
1947
}
1948
1949
var ourFieldValues = {};
1950
var otherFieldValues = {};
1951
for (let field of fieldsToSwap) {
1952
ourFieldValues[field] = this[field];
1953
otherFieldValues[field] = aOtherBrowser[field];
1954
}
1955
1956
if (window.PopupNotifications) {
1957
PopupNotifications._swapBrowserNotifications(aOtherBrowser, this);
1958
}
1959
1960
try {
1961
this.swapFrameLoaders(aOtherBrowser);
1962
} catch (ex) {
1963
// This may not be implemented for browser elements that are not
1964
// attached to a BrowserDOMWindow.
1965
}
1966
1967
for (let field of fieldsToSwap) {
1968
this[field] = otherFieldValues[field];
1969
aOtherBrowser[field] = ourFieldValues[field];
1970
}
1971