ContextMenuParent.sys.mjs (8147B)
1 /* vim: set ts=2 sw=2 sts=2 et tw=80: */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 7 8 const lazy = {}; 9 10 ChromeUtils.defineESModuleGetters(lazy, { 11 E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs", 12 FirefoxRelay: "resource://gre/modules/FirefoxRelay.sys.mjs", 13 WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs", 14 }); 15 16 XPCOMUtils.defineLazyServiceGetters(lazy, { 17 BrowserHandler: ["@mozilla.org/browser/clh;1", Ci.nsIBrowserHandler], 18 }); 19 20 XPCOMUtils.defineLazyPreferenceGetter( 21 lazy, 22 "TEXT_FRAGMENTS_ENABLED", 23 "dom.text_fragments.enabled", 24 false 25 ); 26 27 export class ContextMenuParent extends JSWindowActorParent { 28 receiveMessage(message) { 29 let browser = this.manager.rootFrameLoader.ownerElement; 30 if (browser.hasAttribute("disablecontextmenu")) { 31 return; 32 } 33 34 let win = browser.ownerGlobal; 35 // It's possible that the <xul:browser> associated with this 36 // ContextMenu message doesn't belong to a window that actually 37 // loads nsContextMenu.js. In that case, try to find the chromeEventHandler, 38 // since that'll likely be the "top" <xul:browser>, and then use its window's 39 // nsContextMenu instance instead. 40 if (!win.nsContextMenu) { 41 let topBrowser = browser.ownerGlobal.docShell.chromeEventHandler; 42 win = topBrowser.ownerGlobal; 43 } 44 45 message.data.context.showRelay &&= lazy.FirefoxRelay.isEnabled; 46 47 this.#openContextMenu(message.data, win, browser); 48 } 49 50 hiding() { 51 try { 52 this.sendAsyncMessage("ContextMenu:Hiding", {}); 53 } catch (e) { 54 // This will throw if the content goes away while the 55 // context menu is still open. 56 } 57 } 58 59 reloadFrame(targetIdentifier, forceReload) { 60 this.sendAsyncMessage("ContextMenu:ReloadFrame", { 61 targetIdentifier, 62 forceReload, 63 }); 64 } 65 66 getImageText(targetIdentifier) { 67 return this.sendQuery("ContextMenu:GetImageText", { 68 targetIdentifier, 69 }); 70 } 71 72 toggleRevealPassword(targetIdentifier) { 73 this.sendAsyncMessage("ContextMenu:ToggleRevealPassword", { 74 targetIdentifier, 75 }); 76 } 77 78 async useRelayMask(targetIdentifier, origin) { 79 if (!origin) { 80 return; 81 } 82 83 const windowGlobal = this.manager.browsingContext.currentWindowGlobal; 84 const browser = windowGlobal.rootFrameLoader.ownerElement; 85 const emailMask = await lazy.FirefoxRelay.generateUsername(browser, origin); 86 if (emailMask) { 87 this.sendAsyncMessage("ContextMenu:UseRelayMask", { 88 targetIdentifier, 89 emailMask, 90 }); 91 } 92 } 93 94 reloadImage(targetIdentifier) { 95 this.sendAsyncMessage("ContextMenu:ReloadImage", { targetIdentifier }); 96 } 97 98 getFrameTitle(targetIdentifier) { 99 return this.sendQuery("ContextMenu:GetFrameTitle", { targetIdentifier }); 100 } 101 102 mediaCommand(targetIdentifier, command, data) { 103 let windowGlobal = this.manager.browsingContext.currentWindowGlobal; 104 let browser = windowGlobal.rootFrameLoader.ownerElement; 105 let win = browser.ownerGlobal; 106 let windowUtils = win.windowUtils; 107 this.sendAsyncMessage("ContextMenu:MediaCommand", { 108 targetIdentifier, 109 command, 110 data, 111 handlingUserInput: windowUtils.isHandlingUserInput, 112 }); 113 } 114 115 canvasToBlobURL(targetIdentifier) { 116 return this.sendQuery("ContextMenu:Canvas:ToBlobURL", { targetIdentifier }); 117 } 118 119 saveVideoFrameAsImage(targetIdentifier) { 120 return this.sendQuery("ContextMenu:SaveVideoFrameAsImage", { 121 targetIdentifier, 122 }); 123 } 124 125 setAsDesktopBackground(targetIdentifier) { 126 return this.sendQuery("ContextMenu:SetAsDesktopBackground", { 127 targetIdentifier, 128 }); 129 } 130 131 getSearchFieldEngineData(targetIdentifier) { 132 return this.sendQuery("ContextMenu:SearchFieldEngineData", { 133 targetIdentifier, 134 }); 135 } 136 137 getTextDirective() { 138 return lazy.TEXT_FRAGMENTS_ENABLED 139 ? this.sendQuery("ContextMenu:GetTextDirective") 140 : null; 141 } 142 143 removeAllTextFragments() { 144 return this.sendQuery("ContextMenu:RemoveAllTextFragments"); 145 } 146 147 /** 148 * Handles opening of the context menu for the appropraite browser. 149 * 150 * @param {object} data 151 * The data for the context menu, received from the child. 152 * @param {DOMWindow} win 153 * The window in which the context menu is to be opened. 154 * @param {Browser} browser 155 * The browser the context menu is being opened for. 156 */ 157 #openContextMenu(data, win, browser) { 158 if (lazy.BrowserHandler.kiosk) { 159 // Don't display context menus in kiosk mode 160 return; 161 } 162 let wgp = this.manager; 163 164 if (!wgp.isCurrentGlobal) { 165 // Don't display context menus for unloaded documents 166 return; 167 } 168 169 // NOTE: We don't use `wgp.documentURI` here as we want to use the failed 170 // channel URI in the case we have loaded an error page. 171 let documentURIObject = wgp.browsingContext.currentURI; 172 173 let frameReferrerInfo = data.frameReferrerInfo; 174 if (frameReferrerInfo) { 175 frameReferrerInfo = 176 lazy.E10SUtils.deserializeReferrerInfo(frameReferrerInfo); 177 } 178 179 let linkReferrerInfo = data.linkReferrerInfo; 180 if (linkReferrerInfo) { 181 linkReferrerInfo = 182 lazy.E10SUtils.deserializeReferrerInfo(linkReferrerInfo); 183 } 184 185 let frameID = lazy.WebNavigationFrames.getFrameId(wgp.browsingContext); 186 187 win.nsContextMenu.contentData = { 188 context: data.context, 189 browser, 190 actor: this, 191 editFlags: data.editFlags, 192 spellInfo: data.spellInfo, 193 principal: wgp.documentPrincipal, 194 storagePrincipal: wgp.documentStoragePrincipal, 195 documentURIObject, 196 docLocation: documentURIObject.spec, 197 charSet: data.charSet, 198 referrerInfo: lazy.E10SUtils.deserializeReferrerInfo(data.referrerInfo), 199 frameReferrerInfo, 200 linkReferrerInfo, 201 contentType: data.contentType, 202 contentDisposition: data.contentDisposition, 203 frameID, 204 frameOuterWindowID: frameID, 205 frameBrowsingContext: wgp.browsingContext, 206 selectionInfo: data.selectionInfo, 207 disableSetDesktopBackground: data.disableSetDesktopBackground, 208 showRelay: data.showRelay, 209 loginFillInfo: data.loginFillInfo, 210 userContextId: wgp.browsingContext.originAttributes.userContextId, 211 webExtContextData: data.webExtContextData, 212 cookieJarSettings: wgp.cookieJarSettings, 213 }; 214 215 let popup = win.document.getElementById("contentAreaContextMenu"); 216 let context = win.nsContextMenu.contentData.context; 217 218 // Fill in some values in the context from the WindowGlobalParent actor. 219 context.principal = wgp.documentPrincipal; 220 context.storagePrincipal = wgp.documentStoragePrincipal; 221 context.frameID = frameID; 222 context.frameOuterWindowID = wgp.outerWindowId; 223 context.frameBrowsingContextID = wgp.browsingContext.id; 224 225 // We don't have access to the original event here, as that happened in 226 // another process. Therefore we synthesize a new MouseEvent to propagate the 227 // inputSource to the subsequently triggered popupshowing event. 228 let newEvent = new PointerEvent("contextmenu", { 229 bubbles: true, 230 cancelable: true, 231 screenX: context.screenXDevPx / win.devicePixelRatio, 232 screenY: context.screenYDevPx / win.devicePixelRatio, 233 button: 2, 234 pointerType: (() => { 235 switch (context.inputSource) { 236 case MouseEvent.MOZ_SOURCE_MOUSE: 237 return "mouse"; 238 case MouseEvent.MOZ_SOURCE_PEN: 239 return "pen"; 240 case MouseEvent.MOZ_SOURCE_ERASER: 241 return "eraser"; 242 case MouseEvent.MOZ_SOURCE_CURSOR: 243 return "cursor"; 244 case MouseEvent.MOZ_SOURCE_TOUCH: 245 return "touch"; 246 case MouseEvent.MOZ_SOURCE_KEYBOARD: 247 return "keyboard"; 248 default: 249 return ""; 250 } 251 })(), 252 }); 253 popup.openPopupAtScreen(newEvent.screenX, newEvent.screenY, true, newEvent); 254 } 255 }