/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; // This is loaded into all XUL windows. Wrap in a block to prevent // leaking to window scope. { const { Services } = ChromeUtils.import( "resource://gre/modules/Services.jsm" ); const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); let LazyModules = {}; ChromeUtils.defineModuleGetter( LazyModules, "PermitUnloader", "resource://gre/actors/BrowserElementParent.jsm" ); ChromeUtils.defineModuleGetter( LazyModules, "E10SUtils", "resource://gre/modules/E10SUtils.jsm" ); ChromeUtils.defineModuleGetter( LazyModules, "RemoteWebNavigation", "resource://gre/modules/RemoteWebNavigation.jsm" ); const elementsToDestroyOnUnload = new Set(); window.addEventListener( "unload", () => { for (let element of elementsToDestroyOnUnload.values()) { element.destroy(); } elementsToDestroyOnUnload.clear(); }, { mozSystemGroup: true, once: true } ); class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) { static get observedAttributes() { return ["remote"]; } attributeChangedCallback(name, oldValue, newValue) { // When we have already been set up via connectedCallback and the // and the [remote] value changes, we need to start over. This used // to happen due to a XBL binding change. // // Only do this when the rebuild frameloaders pref is off. This update isn't // required when we rebuild the frameloaders in the backend. let rebuildFrameLoaders = LazyModules.E10SUtils.rebuildFrameloadersOnRemotenessChange || this.ownerGlobal.docShell.nsILoadContext.useRemoteSubframes; if ( !rebuildFrameLoaders && name === "remote" && oldValue != newValue && this.isConnectedAndReady ) { this.destroy(); this.construct(); } } constructor() { super(); this.onPageHide = this.onPageHide.bind(this); this.isNavigating = false; this._documentURI = null; this._characterSet = null; this._documentContentType = null; /** * These are managed by the tabbrowser: */ this.droppedLinkHandler = null; this.mIconURL = null; this.lastURI = null; // Track progress listeners added to this . These need to persist // between calls to destroy(). this.progressListeners = []; this.addEventListener( "keypress", event => { if (event.keyCode != KeyEvent.DOM_VK_F7) { return; } // shift + F7 is the default DevTools shortcut for the Style Editor. if (event.shiftKey) { return; } if (event.defaultPrevented || !event.isTrusted) { return; } const kPrefShortcutEnabled = "accessibility.browsewithcaret_shortcut.enabled"; const kPrefWarnOnEnable = "accessibility.warn_on_browsewithcaret"; const kPrefCaretBrowsingOn = "accessibility.browsewithcaret"; var isEnabled = this.mPrefs.getBoolPref(kPrefShortcutEnabled); if (!isEnabled) { return; } // Toggle browse with caret mode var browseWithCaretOn = this.mPrefs.getBoolPref( kPrefCaretBrowsingOn, false ); var warn = this.mPrefs.getBoolPref(kPrefWarnOnEnable, true); if (warn && !browseWithCaretOn) { var checkValue = { value: false }; var promptService = Services.prompt; var buttonPressed = promptService.confirmEx( window, this.mStrBundle.GetStringFromName( "browsewithcaret.checkWindowTitle" ), this.mStrBundle.GetStringFromName("browsewithcaret.checkLabel"), // Make "No" the default: promptService.STD_YES_NO_BUTTONS | promptService.BUTTON_POS_1_DEFAULT, null, null, null, this.mStrBundle.GetStringFromName("browsewithcaret.checkMsg"), checkValue ); if (buttonPressed != 0) { if (checkValue.value) { try { this.mPrefs.setBoolPref(kPrefShortcutEnabled, false); } catch (ex) {} } return; } if (checkValue.value) { try { this.mPrefs.setBoolPref(kPrefWarnOnEnable, false); } catch (ex) {} } } // Toggle the pref try { this.mPrefs.setBoolPref(kPrefCaretBrowsingOn, !browseWithCaretOn); } catch (ex) {} }, { mozSystemGroup: true } ); this.addEventListener( "dragover", event => { if (!this.droppedLinkHandler || event.defaultPrevented) { return; } // For drags that appear to be internal text (for example, tab drags), // set the dropEffect to 'none'. This prevents the drop even if some // other listener cancelled the event. var types = event.dataTransfer.types; if ( types.includes("text/x-moz-text-internal") && !types.includes("text/plain") ) { event.dataTransfer.dropEffect = "none"; event.stopPropagation(); event.preventDefault(); } // No need to handle "dragover" in e10s, since nsDocShellTreeOwner.cpp in the child process // handles that case using "@mozilla.org/content/dropped-link-handler;1" service. if (this.isRemoteBrowser) { return; } let linkHandler = Services.droppedLinkHandler; if (linkHandler.canDropLink(event, false)) { event.preventDefault(); } }, { mozSystemGroup: true } ); this.addEventListener( "drop", event => { // No need to handle "drop" in e10s, since nsDocShellTreeOwner.cpp in the child process // handles that case using "@mozilla.org/content/dropped-link-handler;1" service. if ( !this.droppedLinkHandler || event.defaultPrevented || this.isRemoteBrowser ) { return; } let linkHandler = Services.droppedLinkHandler; try { // Pass true to prevent the dropping of javascript:/data: URIs var links = linkHandler.dropLinks(event, true); } catch (ex) { return; } if (links.length) { let triggeringPrincipal = linkHandler.getTriggeringPrincipal(event); this.droppedLinkHandler(event, links, triggeringPrincipal); } }, { mozSystemGroup: true } ); this.addEventListener("dragstart", event => { // If we're a remote browser dealing with a dragstart, stop it // from propagating up, since our content process should be dealing // with the mouse movement. if (this.isRemoteBrowser) { event.stopPropagation(); } }); } resetFields() { if (this.observer) { try { Services.obs.removeObserver( this.observer, "browser:purge-session-history" ); } catch (ex) { // It's not clear why this sometimes throws an exception. } this.observer = null; } let browser = this; this.observer = { observe(aSubject, aTopic, aState) { if (aTopic == "browser:purge-session-history") { browser.purgeSessionHistory(); } else if (aTopic == "apz:cancel-autoscroll") { if (aState == browser._autoScrollScrollId) { // Set this._autoScrollScrollId to null, so in stopScroll() we // don't call stopApzAutoscroll() (since it's APZ that // initiated the stopping). browser._autoScrollScrollId = null; browser._autoScrollPresShellId = null; browser._autoScrollPopup.hidePopup(); } } }, QueryInterface: ChromeUtils.generateQI([ Ci.nsIObserver, Ci.nsISupportsWeakReference, ]), }; this._documentURI = null; this._documentContentType = null; /** * Weak reference to an optional frame loader that can be used to influence * process selection for this browser. * See nsIBrowser.sameProcessAsFrameLoader. */ this._sameProcessAsFrameLoader = null; this._loadContext = null; this._webBrowserFind = null; this._finder = null; this._remoteFinder = null; this._fastFind = null; this._outerWindowID = null; this._innerWindowID = null; this._lastSearchString = null; this._controller = null; this._remoteWebNavigation = null; this._remoteWebProgress = null; this._contentTitle = ""; this._characterSet = ""; this._mayEnableCharacterEncodingMenu = null; this._charsetAutodetected = false; this._contentPrincipal = null; this._contentStoragePrincipal = null; this._contentBlockingAllowListPrincipal = null; this._csp = null; this._referrerInfo = null; this._contentRequestContextID = null; this._fullZoom = 1; this._textZoom = 1; this._isSyntheticDocument = false; this.mPrefs = Services.prefs; this._mStrBundle = null; this.blockedPopups = null; this._audioMuted = false; this._hasAnyPlayingMediaBeenBlocked = false; /** * Only send the message "Browser:UnselectedTabHover" when someone requests * for the message, which can reduce non-necessary communication. */ this._shouldSendUnselectedTabHover = false; this._unselectedTabHoverMessageListenerCount = 0; this._securityUI = null; this.urlbarChangeTracker = { _startedLoadSinceLastUserTyping: false, startedLoad() { this._startedLoadSinceLastUserTyping = true; }, finishedLoad() { this._startedLoadSinceLastUserTyping = false; }, userTyped() { this._startedLoadSinceLastUserTyping = false; }, }; this._userTypedValue = null; this._AUTOSCROLL_SNAP = 10; this._autoScrollBrowsingContext = null; this._startX = null; this._startY = null; this._autoScrollPopup = null; this._autoScrollNeedsCleanup = false; /** * These IDs identify the scroll frame being autoscrolled. */ this._autoScrollScrollId = null; this._autoScrollPresShellId = null; } connectedCallback() { // We typically use this to avoid running JS that triggers a layout during parse // (see comment on the delayConnectedCallback implementation). In this case, we // are using it to avoid a leak - see https://bugzilla.mozilla.org/show_bug.cgi?id=1441935#c20. if (this.delayConnectedCallback()) { return; } this.construct(); } disconnectedCallback() { this.destroy(); } get autoscrollEnabled() { if (this.getAttribute("autoscroll") == "false") { return false; } return this.mPrefs.getBoolPref("general.autoScroll", true); } get canGoBack() { return this.webNavigation.canGoBack; } get canGoForward() { return this.webNavigation.canGoForward; } get currentURI() { if (this.webNavigation) { return this.webNavigation.currentURI; } return null; } get documentURI() { return this.isRemoteBrowser ? this._documentURI : this.contentDocument.documentURIObject; } get documentContentType() { if (this.isRemoteBrowser) { return this._documentContentType; } return this.contentDocument ? this.contentDocument.contentType : null; } set documentContentType(aContentType) { if (aContentType != null) { if (this.isRemoteBrowser) { this._documentContentType = aContentType; } else { this.contentDocument.documentContentType = aContentType; } } } set sameProcessAsFrameLoader(val) { this._sameProcessAsFrameLoader = Cu.getWeakReference(val); } get sameProcessAsFrameLoader() { return ( this._sameProcessAsFrameLoader && this._sameProcessAsFrameLoader.get() ); } get loadContext() { if (this._loadContext) { return this._loadContext; } let { frameLoader } = this; if (!frameLoader) { return null; } this._loadContext = frameLoader.loadContext; return this._loadContext; } get autoCompletePopup() { return document.getElementById(this.getAttribute("autocompletepopup")); } get dateTimePicker() { return document.getElementById(this.getAttribute("datetimepicker")); } /** * Provides a node to hang popups (such as the datetimepicker) from. * If this isn't the descendant of a , null is returned * instead and popup code must handle this case. */ get popupAnchor() { let stack = this.closest("stack"); if (!stack) { return null; } let popupAnchor = stack.querySelector(".popup-anchor"); if (popupAnchor) { return popupAnchor; } // Create an anchor for the popup popupAnchor = document.createElement("div"); popupAnchor.className = "popup-anchor"; popupAnchor.hidden = true; stack.appendChild(popupAnchor); return popupAnchor; } set docShellIsActive(val) { if (this.isRemoteBrowser) { let { frameLoader } = this; if (frameLoader && frameLoader.remoteTab) { frameLoader.remoteTab.docShellIsActive = val; } } else if (this.docShell) { this.docShell.isActive = val; } } get docShellIsActive() { if (this.isRemoteBrowser) { let { frameLoader } = this; if (frameLoader && frameLoader.remoteTab) { return frameLoader.remoteTab.docShellIsActive; } return false; } return this.docShell && this.docShell.isActive; } set renderLayers(val) { if (this.isRemoteBrowser) { let { frameLoader } = this; if (frameLoader && frameLoader.remoteTab) { frameLoader.remoteTab.renderLayers = val; } } else { this.docShellIsActive = val; } } get renderLayers() { if (this.isRemoteBrowser) { let { frameLoader } = this; if (frameLoader && frameLoader.remoteTab) { return frameLoader.remoteTab.renderLayers; } return false; } return this.docShellIsActive; } get hasLayers() { if (this.isRemoteBrowser) { let { frameLoader } = this; if (frameLoader && frameLoader.remoteTab) { return frameLoader.remoteTab.hasLayers; } return false; } return this.docShellIsActive; } get isRemoteBrowser() { return this.getAttribute("remote") == "true"; } get remoteType() { if (!this.isRemoteBrowser || !this.messageManager) { return null; } return this.messageManager.remoteType; } get isCrashed() { if (!this.isRemoteBrowser || !this.frameLoader) { return false; } return !this.frameLoader.remoteTab; } get messageManager() { // Bug 1524084 - Trying to get at the message manager while in the crashed state will // create a new message manager that won't shut down properly when the crashed browser // is removed from the DOM. We work around that right now by returning null if we're // in the crashed state. if (this.frameLoader && !this.isCrashed) { return this.frameLoader.messageManager; } return null; } get webBrowserFind() { if (!this._webBrowserFind) { this._webBrowserFind = this.docShell .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebBrowserFind); } return this._webBrowserFind; } get finder() { if (this.isRemoteBrowser) { if (!this._remoteFinder) { let jsm = "resource://gre/modules/FinderParent.jsm"; let { FinderParent } = ChromeUtils.import(jsm, {}); this._remoteFinder = new FinderParent(this); } return this._remoteFinder; } if (!this._finder) { if (!this.docShell) { return null; } let Finder = ChromeUtils.import("resource://gre/modules/Finder.jsm", {}) .Finder; this._finder = new Finder(this.docShell); } return this._finder; } get fastFind() { if (!this._fastFind) { if (!("@mozilla.org/typeaheadfind;1" in Cc)) { return null; } var tabBrowser = this.getTabBrowser(); if (tabBrowser && "fastFind" in tabBrowser) { return (this._fastFind = tabBrowser.fastFind); } if (!this.docShell) { return null; } this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance( Ci.nsITypeAheadFind ); this._fastFind.init(this.docShell); } return this._fastFind; } get outerWindowID() { if (this.isRemoteBrowser) { return this._outerWindowID; } return this.docShell.outerWindowID; } get innerWindowID() { if (this.isRemoteBrowser) { return this._innerWindowID; } try { return this.contentWindow.windowUtils.currentInnerWindowID; } catch (e) { if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) { throw e; } return null; } } get browsingContext() { if (this.frameLoader) { return this.frameLoader.browsingContext; } return null; } /** * Note that this overrides webNavigation on XULFrameElement, and duplicates the return value for the non-remote case */ get webNavigation() { return this.isRemoteBrowser ? this._remoteWebNavigation : this.docShell && this.docShell.QueryInterface(Ci.nsIWebNavigation); } get webProgress() { return this.isRemoteBrowser ? this._remoteWebProgress : this.docShell && this.docShell .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebProgress); } get sessionHistory() { return this.webNavigation.sessionHistory; } get markupDocumentViewer() { return this.docShell.contentViewer; } get contentTitle() { return this.isRemoteBrowser ? this._contentTitle : this.contentDocument.title; } set characterSet(val) { if (this.isRemoteBrowser) { this.sendMessageToActor( "UpdateCharacterSet", { value: val }, "BrowserTab" ); this._characterSet = val; } else { this.docShell.charset = val; this.docShell.gatherCharsetMenuTelemetry(); } } get characterSet() { return this.isRemoteBrowser ? this._characterSet : this.docShell.charset; } get mayEnableCharacterEncodingMenu() { return this.isRemoteBrowser ? this._mayEnableCharacterEncodingMenu : this.docShell.mayEnableCharacterEncodingMenu; } set mayEnableCharacterEncodingMenu(aMayEnable) { if (this.isRemoteBrowser) { this._mayEnableCharacterEncodingMenu = aMayEnable; } } get charsetAutodetected() { return this.isRemoteBrowser ? this._charsetAutodetected : this.docShell.charsetAutodetected; } set charsetAutodetected(aAutodetected) { if (this.isRemoteBrowser) { this._charsetAutodetected = aAutodetected; } } get contentPrincipal() { return this.isRemoteBrowser ? this._contentPrincipal : this.contentDocument.nodePrincipal; } get contentStoragePrincipal() { return this.isRemoteBrowser ? this._contentStoragePrincipal : this.contentDocument.effectiveStoragePrincipal; } get contentBlockingAllowListPrincipal() { return this.isRemoteBrowser ? this._contentBlockingAllowListPrincipal : this.contentDocument.contentBlockingAllowListPrincipal; } get csp() { return this.isRemoteBrowser ? this._csp : this.contentDocument.csp; } get contentRequestContextID() { if (this.isRemoteBrowser) { return this._contentRequestContextID; } try { return this.contentDocument.documentLoadGroup.requestContextID; } catch (e) { return null; } } set fullZoom(val) { if (this.isRemoteBrowser) { let changed = val.toFixed(2) != this._fullZoom.toFixed(2); if (changed) { this._fullZoom = val; this.sendMessageToActor("FullZoom", { value: val }, "Zoom", "roots"); let event = new Event("FullZoomChange", { bubbles: true }); this.dispatchEvent(event); } } else { this.markupDocumentViewer.fullZoom = val; } } get referrerInfo() { return this.isRemoteBrowser ? this._referrerInfo : this.contentDocument.referrerInfo; } get fullZoom() { if (this.isRemoteBrowser) { return this._fullZoom; } return this.markupDocumentViewer.fullZoom; } set textZoom(val) { if (this.isRemoteBrowser) { let changed = val.toFixed(2) != this._textZoom.toFixed(2); if (changed) { this._textZoom = val; this.sendMessageToActor("TextZoom", { value: val }, "Zoom", "roots"); let event = new Event("TextZoomChange", { bubbles: true }); this.dispatchEvent(event); } } else { this.markupDocumentViewer.textZoom = val; } } get textZoom() { if (this.isRemoteBrowser) { return this._textZoom; } return this.markupDocumentViewer.textZoom; } get isSyntheticDocument() { if (this.isRemoteBrowser) { return this._isSyntheticDocument; } return this.contentDocument.mozSyntheticDocument; } get hasContentOpener() { return !!this.browsingContext.opener; } get mStrBundle() { if (!this._mStrBundle) { // need to create string bundle manually instead of using // see bug 63370 for details this._mStrBundle = Services.strings.createBundle( "chrome://global/locale/browser.properties" ); } return this._mStrBundle; } get audioMuted() { return this._audioMuted; } get shouldHandleUnselectedTabHover() { return this._shouldSendUnselectedTabHover; } get securityUI() { if (this.isRemoteBrowser) { if (!this._securityUI) { // Don't attempt to create the remote web progress if the // messageManager has already gone away if (!this.messageManager) { return null; } let jsm = "resource://gre/modules/RemoteSecurityUI.jsm"; let RemoteSecurityUI = ChromeUtils.import(jsm, {}).RemoteSecurityUI; this._securityUI = new RemoteSecurityUI(); } // We want to double-wrap the JS implemented interface, so that QI and instanceof works. var ptr = Cc[ "@mozilla.org/supports-interface-pointer;1" ].createInstance(Ci.nsISupportsInterfacePointer); ptr.data = this._securityUI; return ptr.data.QueryInterface(Ci.nsISecureBrowserUI); } if (!this.docShell.securityUI) { const SECUREBROWSERUI_CONTRACTID = "@mozilla.org/secure_browser_ui;1"; var securityUI = Cc[SECUREBROWSERUI_CONTRACTID].createInstance( Ci.nsISecureBrowserUI ); securityUI.init(this.docShell); } return this.docShell.securityUI; } set userTypedValue(val) { this.urlbarChangeTracker.userTyped(); this._userTypedValue = val; } get userTypedValue() { return this._userTypedValue; } get dontPromptAndDontUnload() { return 1; } get dontPromptAndUnload() { return 2; } _wrapURIChangeCall(fn) { if (!this.isRemoteBrowser) { this.isNavigating = true; try { fn(); } finally { this.isNavigating = false; } } else { fn(); } } goBack() { var webNavigation = this.webNavigation; if (webNavigation.canGoBack) { this._wrapURIChangeCall(() => webNavigation.goBack()); } } goForward() { var webNavigation = this.webNavigation; if (webNavigation.canGoForward) { this._wrapURIChangeCall(() => webNavigation.goForward()); } } reload() { const nsIWebNavigation = Ci.nsIWebNavigation; const flags = nsIWebNavigation.LOAD_FLAGS_NONE; this.reloadWithFlags(flags); } reloadWithFlags(aFlags) { this.webNavigation.reload(aFlags); } stop() { const nsIWebNavigation = Ci.nsIWebNavigation; const flags = nsIWebNavigation.STOP_ALL; this.webNavigation.stop(flags); } /** * throws exception for unknown schemes */ loadURI(aURI, aParams = {}) { if (!aURI) { aURI = "about:blank"; } let { referrerInfo, triggeringPrincipal, postData, headers, csp, } = aParams; let loadFlags = aParams.loadFlags || aParams.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE; let loadURIOptions = { triggeringPrincipal, csp, referrerInfo, loadFlags, postData, headers, }; this._wrapURIChangeCall(() => this.webNavigation.loadURI(aURI, loadURIOptions) ); } gotoIndex(aIndex) { this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(aIndex)); } preserveLayers(preserve) { if (!this.isRemoteBrowser) { return; } let { frameLoader } = this; if (frameLoader.remoteTab) { frameLoader.remoteTab.preserveLayers(preserve); } } deprioritize() { if (!this.isRemoteBrowser) { return; } let { frameLoader } = this; if (frameLoader.remoteTab) { frameLoader.remoteTab.deprioritize(); } } getTabBrowser() { if ( this.ownerGlobal.gBrowser && this.ownerGlobal.gBrowser.getTabForBrowser && this.ownerGlobal.gBrowser.getTabForBrowser(this) ) { return this.ownerGlobal.gBrowser; } return null; } addProgressListener(aListener, aNotifyMask) { if (!aNotifyMask) { aNotifyMask = Ci.nsIWebProgress.NOTIFY_ALL; } this.progressListeners.push({ weakListener: Cu.getWeakReference(aListener), mask: aNotifyMask, }); this.webProgress.addProgressListener(aListener, aNotifyMask); } removeProgressListener(aListener) { this.webProgress.removeProgressListener(aListener); // Remove aListener from our progress listener list, and clear out dead // weak references while we're at it. this.progressListeners = this.progressListeners.filter( ({ weakListener }) => weakListener.get() && weakListener.get() !== aListener ); } /** * Move the previously-tracked web progress listeners to this 's * current WebProgress. */ restoreProgressListeners() { let listeners = this.progressListeners; this.progressListeners = []; for (let { weakListener, mask } of listeners) { let listener = weakListener.get(); if (listener) { this.addProgressListener(listener, mask); } } } onPageHide(aEvent) { if (!this.docShell || !this.fastFind) { return; } var tabBrowser = this.getTabBrowser(); if ( !tabBrowser || !("fastFind" in tabBrowser) || tabBrowser.selectedBrowser == this ) { this.fastFind.setDocShell(this.docShell); } } updateBlockedPopups() { let event = document.createEvent("Events"); event.initEvent("DOMUpdateBlockedPopups", true, true); this.dispatchEvent(event); } retrieveListOfBlockedPopups() { this.messageManager.sendAsyncMessage( "PopupBlocking:GetBlockedPopupList", null ); return new Promise(resolve => { let self = this; this.messageManager.addMessageListener( "PopupBlocking:ReplyGetBlockedPopupList", function replyReceived(msg) { self.messageManager.removeMessageListener( "PopupBlocking:ReplyGetBlockedPopupList", replyReceived ); resolve(msg.data.popupData); } ); }); } unblockPopup(aPopupIndex) { this.messageManager.sendAsyncMessage("PopupBlocking:UnblockPopup", { index: aPopupIndex, }); } audioPlaybackStarted() { if (this._audioMuted) { return; } let event = document.createEvent("Events"); event.initEvent("DOMAudioPlaybackStarted", true, false); this.dispatchEvent(event); } audioPlaybackStopped() { let event = document.createEvent("Events"); event.initEvent("DOMAudioPlaybackStopped", true, false); this.dispatchEvent(event); } /** * When the pref "media.block-autoplay-until-in-foreground" is on, * Gecko delays starting playback of media resources in tabs until the * tab has been in the foreground or resumed by tab's play tab icon. * - When Gecko delays starting playback of a media resource in a window, * it sends a message to call activeMediaBlockStarted(). This causes the * tab audio indicator to show. * - When a tab is foregrounded, Gecko starts playing all delayed media * resources in that tab, and sends a message to call * activeMediaBlockStopped(). This causes the tab audio indicator to hide. */ activeMediaBlockStarted() { this._hasAnyPlayingMediaBeenBlocked = true; let event = document.createEvent("Events"); event.initEvent("DOMAudioPlaybackBlockStarted", true, false); this.dispatchEvent(event); } activeMediaBlockStopped() { if (!this._hasAnyPlayingMediaBeenBlocked) { return; } this._hasAnyPlayingMediaBeenBlocked = false; let event = document.createEvent("Events"); event.initEvent("DOMAudioPlaybackBlockStopped", true, false); this.dispatchEvent(event); } mute(transientState) { if (!transientState) { this._audioMuted = true; } let context = this.frameLoader.browsingContext; context.notifyMediaMutedChanged(true); } unmute() { this._audioMuted = false; let context = this.frameLoader.browsingContext; context.notifyMediaMutedChanged(false); } pauseMedia(disposable) { let suspendedReason; if (disposable) { suspendedReason = "mediaControlPaused"; } else { suspendedReason = "lostAudioFocusTransiently"; } this.sendMessageToActor( "AudioPlayback", { type: suspendedReason }, "AudioPlayback", "roots" ); } stopMedia() { this.sendMessageToActor( "AudioPlayback", { type: "mediaControlStopped" }, "AudioPlayback", "roots" ); } resumeMedia() { this.frameLoader.browsingContext.notifyStartDelayedAutoplayMedia(); if (this._hasAnyPlayingMediaBeenBlocked) { this._hasAnyPlayingMediaBeenBlocked = false; let event = document.createEvent("Events"); event.initEvent("DOMAudioPlaybackBlockStopped", true, false); this.dispatchEvent(event); } } unselectedTabHover(hovered) { if (!this._shouldSendUnselectedTabHover) { return; } this.messageManager.sendAsyncMessage("Browser:UnselectedTabHover", { hovered, }); } didStartLoadSinceLastUserTyping() { return ( !this.isNavigating && this.urlbarChangeTracker._startedLoadSinceLastUserTyping ); } construct() { elementsToDestroyOnUnload.add(this); this.resetFields(); this.mInitialized = true; if (this.isRemoteBrowser) { /* * Don't try to send messages from this function. The message manager for * the element may not be initialized yet. */ this._remoteWebNavigation = new LazyModules.RemoteWebNavigation(this); // Initialize contentPrincipal to the about:blank principal for this loadcontext let aboutBlank = Services.io.newURI("about:blank"); let ssm = Services.scriptSecurityManager; this._contentPrincipal = ssm.getLoadContextContentPrincipal( aboutBlank, this.loadContext ); // CSP for about:blank is null; if we ever change _contentPrincipal above, // we should re-evaluate the CSP here. this._csp = null; this.messageManager.addMessageListener("Browser:Init", this); this.messageManager.addMessageListener("DOMTitleChanged", this); let jsm = "resource://gre/modules/RemoteWebProgress.jsm"; let { RemoteWebProgressManager } = ChromeUtils.import(jsm, {}); let oldManager = this._remoteWebProgressManager; this._remoteWebProgressManager = new RemoteWebProgressManager(this); if (oldManager) { // We're transitioning from one remote type to another. This means that // the RemoteWebProgress listener is listening to the old message manager, // and needs to be pointed at the new one. this._remoteWebProgressManager.swapListeners(oldManager); } this._remoteWebProgress = this._remoteWebProgressManager.topLevelWebProgress; if (!oldManager) { // If we didn't have a manager, then we're transitioning from local to // remote. Add all listeners from the previous to the new // RemoteWebProgress. this.restoreProgressListeners(); } this.messageManager.loadFrameScript( "chrome://global/content/browser-child.js", true ); if (!this.hasAttribute("disablehistory")) { Services.obs.addObserver( this.observer, "browser:purge-session-history", true ); } const { RemoteController } = ChromeUtils.import( "resource://gre/modules/RemoteController.jsm" ); this._controller = new RemoteController(this); this.controllers.appendController(this._controller); } try { // |webNavigation.sessionHistory| will have been set by the frame // loader when creating the docShell as long as this xul:browser // doesn't have the 'disablehistory' attribute set. if (this.docShell && this.webNavigation.sessionHistory) { Services.obs.addObserver( this.observer, "browser:purge-session-history", true ); // enable global history if we weren't told otherwise if ( !this.hasAttribute("disableglobalhistory") && !this.isRemoteBrowser ) { try { this.docShell.useGlobalHistory = true; } catch (ex) { // This can occur if the Places database is locked Cu.reportError("Error enabling browser global history: " + ex); } } } } catch (e) { Cu.reportError(e); } try { // Ensures the securityUI is initialized. var securityUI = this.securityUI; // eslint-disable-line no-unused-vars } catch (e) {} // tabbrowser.xml sets "sameProcessAsFrameLoader" as a direct property // on some browsers before they are put into a DOM (and get a // binding). This hack makes sure that we hold a weak reference to // the other browser (and go through the proper getter and setter). if (this.hasOwnProperty("sameProcessAsFrameLoader")) { var sameProcessAsFrameLoader = this.sameProcessAsFrameLoader; delete this.sameProcessAsFrameLoader; this.sameProcessAsFrameLoader = sameProcessAsFrameLoader; } if (!this.isRemoteBrowser) { // If we've transitioned from remote to non-remote, we no longer need // our RemoteWebProgress or its associated manager, but we'll need to // add the progress listeners to the new non-remote WebProgress. this._remoteWebProgressManager = null; this._remoteWebProgress = null; this.restoreProgressListeners(); this.addEventListener("pagehide", this.onPageHide, true); } if (this.messageManager) { this.messageManager.addMessageListener( "PopupBlocking:UpdateBlockedPopups", this ); this.messageManager.addMessageListener( "UnselectedTabHover:Toggle", this ); } } /** * This is necessary because the destructor doesn't always get called when * we are removed from a tabbrowser. This will be explicitly called by tabbrowser. */ destroy() { elementsToDestroyOnUnload.delete(this); // Make sure that any open select is closed. if (this.hasAttribute("selectmenulist")) { let menulist = document.getElementById( this.getAttribute("selectmenulist") ); if (menulist && menulist.open) { let resourcePath = "resource://gre/actors/SelectParent.jsm"; let { SelectParentHelper } = ChromeUtils.import(resourcePath); SelectParentHelper.hide(menulist, this); } } if (this._controller) { try { this.controllers.removeController(this._controller); } catch (ex) { Cu.reportError(ex); } } this.resetFields(); if (!this.mInitialized) { return; } this.mInitialized = false; this.lastURI = null; if (!this.isRemoteBrowser) { this.removeEventListener("pagehide", this.onPageHide, true); } if (this._autoScrollNeedsCleanup) { // we polluted the global scope, so clean it up this._autoScrollPopup.remove(); } } /** * We call this _receiveMessage (and alias receiveMessage to it) so that * bindings that inherit from this one can delegate to it. */ _receiveMessage(aMessage) { let data = aMessage.data; switch (aMessage.name) { case "PopupBlocking:UpdateBlockedPopups": { this.blockedPopups = { length: data.count, reported: !data.freshPopup, }; this.updateBlockedPopups(); break; } case "UnselectedTabHover:Toggle": this._shouldSendUnselectedTabHover = data.enable ? ++this._unselectedTabHoverMessageListenerCount > 0 : --this._unselectedTabHoverMessageListenerCount == 0; break; } return undefined; } receiveMessage(aMessage) { if (!this.isRemoteBrowser) { return this._receiveMessage(aMessage); } let data = aMessage.data; switch (aMessage.name) { case "Browser:Init": this._outerWindowID = data.outerWindowID; break; case "DOMTitleChanged": this._contentTitle = data.title; break; default: return this._receiveMessage(aMessage); } return undefined; } enableDisableCommandsRemoteOnly( aAction, aEnabledCommands, aDisabledCommands ) { if (this._controller) { this._controller.enableDisableCommands( aAction, aEnabledCommands, aDisabledCommands ); } } updateSecurityUIForSecurityChange(aSecurityInfo, aState, aIsSecureContext) { if (this.isRemoteBrowser && this.messageManager) { // Invoking this getter triggers the generation of the underlying object, // which we need to access with ._securityUI, because .securityUI returns // a wrapper that makes _update inaccessible. void this.securityUI; this._securityUI._update(aSecurityInfo, aState, aIsSecureContext); } } get remoteWebProgressManager() { return this._remoteWebProgressManager; } updateForStateChange(aCharset, aDocumentURI, aContentType) { if (this.isRemoteBrowser && this.messageManager) { if (aCharset != null) { this._characterSet = aCharset; } if (aDocumentURI != null) { this._documentURI = aDocumentURI; } if (aContentType != null) { this._documentContentType = aContentType; } } } updateWebNavigationForLocationChange(aCanGoBack, aCanGoForward) { if (this.isRemoteBrowser && this.messageManager) { this._remoteWebNavigation.canGoBack = aCanGoBack; this._remoteWebNavigation.canGoForward = aCanGoForward; } } updateForLocationChange( aLocation, aCharset, aMayEnableCharacterEncodingMenu, aCharsetAutodetected, aDocumentURI, aTitle, aContentPrincipal, aContentStoragePrincipal, aContentBlockingAllowListPrincipal, aCSP, aReferrerInfo, aIsSynthetic, aInnerWindowID, aHaveRequestContextID, aRequestContextID, aContentType ) { if (this.isRemoteBrowser && this.messageManager) { if (aCharset != null) { this._characterSet = aCharset; this._mayEnableCharacterEncodingMenu = aMayEnableCharacterEncodingMenu; this._charsetAutodetected = aCharsetAutodetected; } if (aContentType != null) { this._documentContentType = aContentType; } this._remoteWebNavigation._currentURI = aLocation; this._documentURI = aDocumentURI; this._contentTitle = aTitle; this._contentPrincipal = aContentPrincipal; this._contentStoragePrincipal = aContentStoragePrincipal; this._contentBlockingAllowListPrincipal = aContentBlockingAllowListPrincipal; this._csp = aCSP; this._referrerInfo = aReferrerInfo; this._isSyntheticDocument = aIsSynthetic; this._innerWindowID = aInnerWindowID; this._contentRequestContextID = aHaveRequestContextID ? aRequestContextID : null; } } purgeSessionHistory() { if (this.isRemoteBrowser) { this._remoteWebNavigation.canGoBack = false; this._remoteWebNavigation.canGoForward = false; } try { this.sendMessageToActor( "Browser:PurgeSessionHistory", {}, "PurgeSessionHistory", "roots" ); } catch (ex) { // This can throw if the browser has started to go away. if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) { throw ex; } } } createAboutBlankContentViewer(aPrincipal, aStoragePrincipal) { if (this.isRemoteBrowser) { // Ensure that the content process has the permissions which are // needed to create a document with the given principal. let permissionPrincipal = BrowserUtils.principalWithMatchingOA( aPrincipal, this.contentPrincipal ); this.frameLoader.remoteTab.transmitPermissionsForPrincipal( permissionPrincipal ); // This still uses the message manager, for the following reasons: // // 1. Due to bug 1523638, it's virtually certain that, if we've just created // this , that the WindowGlobalParent for the top-level frame // of this browser doesn't exist yet, so it's not possible to get at a // JS Window Actor for it. // // 2. JS Window Actors are tied to the principals for the frames they're running // in - switching principals is therefore self-destructive and unexpected. // // So we'll continue to use the message manager until we come up with a better // solution. this.messageManager.sendAsyncMessage( "BrowserElement:CreateAboutBlank", { principal: aPrincipal, storagePrincipal: aStoragePrincipal } ); return; } let principal = BrowserUtils.principalWithMatchingOA( aPrincipal, this.contentPrincipal ); let storagePrincipal = BrowserUtils.principalWithMatchingOA( aStoragePrincipal, this.contentStoragePrincipal ); this.docShell.createAboutBlankContentViewer(principal, storagePrincipal); } stopScroll() { if (this._autoScrollBrowsingContext) { window.removeEventListener("mousemove", this, true); window.removeEventListener("mousedown", this, true); window.removeEventListener("mouseup", this, true); window.removeEventListener("DOMMouseScroll", this, true); window.removeEventListener("contextmenu", this, true); window.removeEventListener("keydown", this, true); window.removeEventListener("keypress", this, true); window.removeEventListener("keyup", this, true); let autoScrollWnd = this._autoScrollBrowsingContext.currentWindowGlobal; if (autoScrollWnd) { autoScrollWnd .getActor("AutoScroll") .sendAsyncMessage("Autoscroll:Stop", {}); } this._autoScrollBrowsingContext = null; try { Services.obs.removeObserver(this.observer, "apz:cancel-autoscroll"); } catch (ex) { // It's not clear why this sometimes throws an exception } if (this.isRemoteBrowser && this._autoScrollScrollId != null) { let { remoteTab } = this.frameLoader; if (remoteTab) { remoteTab.stopApzAutoscroll( this._autoScrollScrollId, this._autoScrollPresShellId ); } this._autoScrollScrollId = null; this._autoScrollPresShellId = null; } } } _createAutoScrollPopup() { var popup = document.createXULElement("panel"); popup.className = "autoscroller"; popup.setAttribute("consumeoutsideclicks", "true"); popup.setAttribute("rolluponmousewheel", "true"); popup.setAttribute("hidden", "true"); return popup; } startScroll({ scrolldir, screenX, screenY, scrollId, presShellId, browsingContext, }) { if (!this.autoscrollEnabled) { return { autoscrollEnabled: false, usingApz: false }; } const POPUP_SIZE = 32; if (!this._autoScrollPopup) { if (this.hasAttribute("autoscrollpopup")) { // our creator provided a popup to share this._autoScrollPopup = document.getElementById( this.getAttribute("autoscrollpopup") ); } else { // we weren't provided a popup; we have to use the global scope this._autoScrollPopup = this._createAutoScrollPopup(); document.documentElement.appendChild(this._autoScrollPopup); this._autoScrollNeedsCleanup = true; } this._autoScrollPopup.removeAttribute("hidden"); this._autoScrollPopup.setAttribute("noautofocus", "true"); this._autoScrollPopup.style.height = POPUP_SIZE + "px"; this._autoScrollPopup.style.width = POPUP_SIZE + "px"; this._autoScrollPopup.style.margin = -POPUP_SIZE / 2 + "px"; } let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"].getService( Ci.nsIScreenManager ); let screen = screenManager.screenForRect(screenX, screenY, 1, 1); // we need these attributes so themers don't need to create per-platform packages if (screen.colorDepth > 8) { // need high color for transparency // Exclude second-rate platforms this._autoScrollPopup.setAttribute( "transparent", !/BeOS|OS\/2/.test(navigator.appVersion) ); // Enable translucency on Windows and Mac this._autoScrollPopup.setAttribute( "translucent", AppConstants.platform == "win" || AppConstants.platform == "macosx" ); } this._autoScrollPopup.setAttribute("scrolldir", scrolldir); this._autoScrollPopup.addEventListener("popuphidden", this, true); // Sanitize screenX/screenY for available screen size with half the size // of the popup removed. The popup uses negative margins to center on the // coordinates we pass. Unfortunately `window.screen.availLeft` can be negative // on Windows in multi-monitor setups, so we use nsIScreenManager instead: let left = {}, top = {}, width = {}, height = {}; screen.GetAvailRect(left, top, width, height); // We need to get screen CSS-pixel (rather than display-pixel) coordinates. // With 175% DPI, the actual ratio of display pixels to CSS pixels is // 1.7647 because of rounding inside gecko. Unfortunately defaultCSSScaleFactor // returns the original 1.75 dpi factor. While window.devicePixelRatio would // get us the correct ratio, if the window is split between 2 screens, // window.devicePixelRatio isn't guaranteed to match the screen we're // autoscrolling on. So instead we do the same math as Gecko. const scaleFactor = 60 / Math.round(60 / screen.defaultCSSScaleFactor); let minX = left.value / scaleFactor + 0.5 * POPUP_SIZE; let maxX = (left.value + width.value) / scaleFactor - 0.5 * POPUP_SIZE; let minY = top.value / scaleFactor + 0.5 * POPUP_SIZE; let maxY = (top.value + height.value) / scaleFactor - 0.5 * POPUP_SIZE; let popupX = Math.max(minX, Math.min(maxX, screenX)); let popupY = Math.max(minY, Math.min(maxY, screenY)); this._autoScrollPopup.openPopupAtScreen(popupX, popupY); this._ignoreMouseEvents = true; this._startX = screenX; this._startY = screenY; this._autoScrollBrowsingContext = browsingContext; window.addEventListener("mousemove", this, true); window.addEventListener("mousedown", this, true); window.addEventListener("mouseup", this, true); window.addEventListener("DOMMouseScroll", this, true); window.addEventListener("contextmenu", this, true); window.addEventListener("keydown", this, true); window.addEventListener("keypress", this, true); window.addEventListener("keyup", this, true); let usingApz = false; if ( this.isRemoteBrowser && scrollId != null && this.mPrefs.getBoolPref("apz.autoscroll.enabled", false) ) { let { remoteTab } = this.frameLoader; if (remoteTab) { // If APZ is handling the autoscroll, it may decide to cancel // it of its own accord, so register an observer to allow it // to notify us of that. Services.obs.addObserver( this.observer, "apz:cancel-autoscroll", true ); usingApz = remoteTab.startApzAutoscroll( screenX, screenY, scrollId, presShellId ); } // Save the IDs for later this._autoScrollScrollId = scrollId; this._autoScrollPresShellId = presShellId; } return { autoscrollEnabled: true, usingApz }; } cancelScroll() { this._autoScrollPopup.hidePopup(); } handleEvent(aEvent) { if (this._autoScrollBrowsingContext) { switch (aEvent.type) { case "mousemove": { var x = aEvent.screenX - this._startX; var y = aEvent.screenY - this._startY; if ( x > this._AUTOSCROLL_SNAP || x < -this._AUTOSCROLL_SNAP || (y > this._AUTOSCROLL_SNAP || y < -this._AUTOSCROLL_SNAP) ) { this._ignoreMouseEvents = false; } break; } case "mouseup": case "mousedown": case "contextmenu": { if (!this._ignoreMouseEvents) { // Use a timeout to prevent the mousedown from opening the popup again. // Ideally, we could use preventDefault here, but contenteditable // and middlemouse paste don't interact well. See bug 1188536. setTimeout(() => this._autoScrollPopup.hidePopup(), 0); } this._ignoreMouseEvents = false; break; } case "DOMMouseScroll": { this._autoScrollPopup.hidePopup(); aEvent.preventDefault(); break; } case "popuphidden": { this._autoScrollPopup.removeEventListener( "popuphidden", this, true ); this.stopScroll(); break; } case "keydown": { if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) { // the escape key will be processed by // nsXULPopupManager::KeyDown and the panel will be closed. // So, don't consume the key event here. break; } // don't break here. we need to eat keydown events. } // fall through case "keypress": case "keyup": { // All keyevents should be eaten here during autoscrolling. aEvent.stopPropagation(); aEvent.preventDefault(); break; } } } } closeBrowser() { // The request comes from a XPCOM component, we'd want to redirect // the request to tabbrowser. let tabbrowser = this.getTabBrowser(); if (tabbrowser) { let tab = tabbrowser.getTabForBrowser(this); if (tab) { tabbrowser.removeTab(tab); return; } } throw new Error( "Closing a browser which was not attached to a tabbrowser is unsupported." ); } swapBrowsers(aOtherBrowser) { // The request comes from a XPCOM component, we'd want to redirect // the request to tabbrowser so tabbrowser will be setup correctly, // and it will eventually call swapDocShells. let ourTabBrowser = this.getTabBrowser(); let otherTabBrowser = aOtherBrowser.getTabBrowser(); if (ourTabBrowser && otherTabBrowser) { let ourTab = ourTabBrowser.getTabForBrowser(this); let otherTab = otherTabBrowser.getTabForBrowser(aOtherBrowser); ourTabBrowser.swapBrowsers(ourTab, otherTab); return; } // One of us is not connected to a tabbrowser, so just swap. this.swapDocShells(aOtherBrowser); } swapDocShells(aOtherBrowser) { if (this.isRemoteBrowser != aOtherBrowser.isRemoteBrowser) { throw new Error( "Can only swap docshells between browsers in the same process." ); } // Give others a chance to swap state. // IMPORTANT: Since a swapDocShells call does not swap the messageManager // instances attached to a browser to aOtherBrowser, others // will need to add the message listeners to the new // messageManager. // This is not a bug in swapDocShells or the FrameLoader, // merely a design decision: If message managers were swapped, // so that no new listeners were needed, the new // aOtherBrowser.messageManager would have listeners pointing // to the JS global of the current browser, which would rather // easily create leaks while swapping. // IMPORTANT2: When the current browser element is removed from DOM, // which is quite common after a swpDocShells call, its // frame loader is destroyed, and that destroys the relevant // message manager, which will remove the listeners. let event = new CustomEvent("SwapDocShells", { detail: aOtherBrowser }); this.dispatchEvent(event); event = new CustomEvent("SwapDocShells", { detail: this }); aOtherBrowser.dispatchEvent(event); // We need to swap fields that are tied to our docshell or related to // the loaded page // Fields which are built as a result of notifactions (pageshow/hide, // DOMLinkAdded/Removed, onStateChange) should not be swapped here, // because these notifications are dispatched again once the docshells // are swapped. var fieldsToSwap = ["_webBrowserFind"]; if (this.isRemoteBrowser) { fieldsToSwap.push( ...[ "_remoteWebNavigation", "_remoteWebProgressManager", "_remoteWebProgress", "_remoteFinder", "_securityUI", "_documentURI", "_documentContentType", "_contentTitle", "_characterSet", "_mayEnableCharacterEncodingMenu", "_charsetAutodetected", "_contentPrincipal", "_contentStoragePrincipal", "_contentBlockingAllowListPrincipal", "_fullZoom", "_textZoom", "_isSyntheticDocument", "_innerWindowID", ] ); } var ourFieldValues = {}; var otherFieldValues = {}; for (let field of fieldsToSwap) { ourFieldValues[field] = this[field]; otherFieldValues[field] = aOtherBrowser[field]; } if (window.PopupNotifications) { PopupNotifications._swapBrowserNotifications(aOtherBrowser, this); } try { this.swapFrameLoaders(aOtherBrowser); } catch (ex) { // This may not be implemented for browser elements that are not // attached to a BrowserDOMWindow. } for (let field of fieldsToSwap) { this[field] = otherFieldValues[field]; aOtherBrowser[field] = ourFieldValues[field]; } if (!this.isRemoteBrowser) { // Null the current nsITypeAheadFind instances so that they're // lazily re-created on access. We need to do this because they // might have attached the wrong docShell. this._fastFind = aOtherBrowser._fastFind = null; } else { // Rewire the remote listeners this._remoteWebNavigation.swapBrowser(this); aOtherBrowser._remoteWebNavigation.swapBrowser(aOtherBrowser); if ( this._remoteWebProgressManager && aOtherBrowser._remoteWebProgressManager ) { this._remoteWebProgressManager.swapBrowser(this); aOtherBrowser._remoteWebProgressManager.swapBrowser(aOtherBrowser); } if (this._remoteFinder) { this._remoteFinder.swapBrowser(this); } if (aOtherBrowser._remoteFinder) { aOtherBrowser._remoteFinder.swapBrowser(aOtherBrowser); } } event = new CustomEvent("EndSwapDocShells", { detail: aOtherBrowser }); this.dispatchEvent(event); event = new CustomEvent("EndSwapDocShells", { detail: this }); aOtherBrowser.dispatchEvent(event); } getInPermitUnload(aCallback) { if (this.isRemoteBrowser) { let { remoteTab } = this.frameLoader; if (!remoteTab) { // If we're crashed, we're definitely not in this state anymore. aCallback(false); return; } aCallback(LazyModules.PermitUnloader.inPermitUnload(this.frameLoader)); return; } if (!this.docShell || !this.docShell.contentViewer) { aCallback(false); return; } aCallback(this.docShell.contentViewer.inPermitUnload); } permitUnload(aPermitUnloadFlags) { if (this.isRemoteBrowser) { if (!LazyModules.PermitUnloader.hasBeforeUnload(this.frameLoader)) { return { permitUnload: true, timedOut: false }; } return LazyModules.PermitUnloader.permitUnload( this.frameLoader, aPermitUnloadFlags ); } if (!this.docShell || !this.docShell.contentViewer) { return { permitUnload: true, timedOut: false }; } return { permitUnload: this.docShell.contentViewer.permitUnload( aPermitUnloadFlags ), timedOut: false, }; } print(aOuterWindowID, aPrintSettings, aPrintProgressListener) { if (!this.frameLoader) { throw Components.Exception("No frame loader.", Cr.NS_ERROR_FAILURE); } this.frameLoader.print( aOuterWindowID, aPrintSettings, aPrintProgressListener ); } async drawSnapshot(x, y, w, h, scale, backgroundColor) { let rect = new DOMRect(x, y, w, h); try { return this.browsingContext.currentWindowGlobal.drawSnapshot( rect, scale, backgroundColor ); } catch (e) { return false; } } dropLinks(aLinks, aTriggeringPrincipal) { if (!this.droppedLinkHandler) { return false; } let links = []; for (let i = 0; i < aLinks.length; i += 3) { links.push({ url: aLinks[i], name: aLinks[i + 1], type: aLinks[i + 2], }); } this.droppedLinkHandler(null, links, aTriggeringPrincipal); return true; } getContentBlockingLog() { let windowGlobal = this.browsingContext.currentWindowGlobal; if (!windowGlobal) { return null; } return windowGlobal.contentBlockingLog; } getContentBlockingEvents() { let windowGlobal = this.browsingContext.currentWindowGlobal; if (!windowGlobal) { return 0; } return windowGlobal.contentBlockingEvents; } // Send an asynchronous message to the remote child via an actor. // Note: use this only for messages through an actor. For old-style // messages, use the message manager. // The value of the scope argument determines which browsing contexts // are sent to: // 'all' - send to actors associated with all descendant child frames. // 'roots' - send only to actors associated with process roots. // undefined/'' - send only to the top-level actor and not any descendants. sendMessageToActor(messageName, args, actorName, scope) { if (!this.frameLoader) { return; } function sendToChildren(browsingContext, childScope) { let windowGlobal = browsingContext.currentWindowGlobal; // If 'roots' is set, only send if windowGlobal.isProcessRoot is true. if ( windowGlobal && (childScope != "roots" || windowGlobal.isProcessRoot) ) { windowGlobal.getActor(actorName).sendAsyncMessage(messageName, args); } // Iterate as long as scope in assigned. Note that we use the original // passed in scope, not childScope here. if (scope) { let contexts = browsingContext.getChildren(); for (let context of contexts) { sendToChildren(context, scope); } } } // Pass no second argument to always send to the top-level browsing context. sendToChildren(this.browsingContext); } enterModalState() { this.sendMessageToActor("EnterModalState", {}, "BrowserElement", "roots"); } leaveModalState() { this.sendMessageToActor("LeaveModalState", {}, "BrowserElement", "roots"); } getDevicePermissionOrigins(key) { if (typeof key !== "string" || key.length === 0) { throw new Error("Key must be non empty string."); } if (!this._devicePermissionOrigins) { this._devicePermissionOrigins = new Map(); } let origins = this._devicePermissionOrigins.get(key); if (!origins) { origins = new Set(); this._devicePermissionOrigins.set(key, origins); } return origins; } } MozXULElement.implementCustomInterface(MozBrowser, [Ci.nsIBrowser]); customElements.define("browser", MozBrowser); }