/* 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/. */ import { GeckoViewActorChild } from "resource://gre/modules/GeckoViewActorChild.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { ManifestObtainer: "resource://gre/modules/ManifestObtainer.sys.mjs", }); export class ContentDelegateChild extends GeckoViewActorChild { notifyParentOfViewportFit() { if (this.triggerViewportFitChange) { this.contentWindow.cancelIdleCallback(this.triggerViewportFitChange); } this.triggerViewportFitChange = this.contentWindow.requestIdleCallback( () => { this.triggerViewportFitChange = null; const viewportFit = this.contentWindow.windowUtils.getViewportFitInfo(); if (this.lastViewportFit === viewportFit) { return; } this.lastViewportFit = viewportFit; this.eventDispatcher.sendRequest({ type: "GeckoView:DOMMetaViewportFit", viewportfit: viewportFit, }); } ); } // eslint-disable-next-line complexity handleEvent(aEvent) { debug`handleEvent: ${aEvent.type}`; switch (aEvent.type) { case "contextmenu": { function nearestParentAttribute(aNode, aAttribute) { while ( aNode && aNode.hasAttribute && !aNode.hasAttribute(aAttribute) ) { aNode = aNode.parentNode; } return aNode && aNode.getAttribute && aNode.getAttribute(aAttribute); } function createAbsoluteUri(aBaseUri, aUri) { if (!aUri || !aBaseUri || !aBaseUri.displaySpec) { return null; } return Services.io.newURI(aUri, null, aBaseUri).displaySpec; } const node = aEvent.composedTarget; const baseUri = node.ownerDocument.baseURIObject; const uri = createAbsoluteUri( baseUri, nearestParentAttribute(node, "href") ); const title = nearestParentAttribute(node, "title"); const alt = nearestParentAttribute(node, "alt"); const elementType = ChromeUtils.getClassName(node); const isImage = elementType === "HTMLImageElement"; const isMedia = elementType === "HTMLVideoElement" || elementType === "HTMLAudioElement"; let elementSrc = (isImage || isMedia) && (node.currentSrc || node.src); if (elementSrc) { const isBlob = elementSrc.startsWith("blob:"); if (isBlob && !URL.isValidObjectURL(elementSrc)) { elementSrc = null; } } if (uri || isImage || isMedia) { const msg = { type: "GeckoView:ContextMenu", // We don't have full zoom on Android, so using CSS coordinates // here is fine, since the CSS coordinate spaces match between the // child and parent processes. screenX: aEvent.screenX, screenY: aEvent.screenY, baseUri: (baseUri && baseUri.displaySpec) || null, uri, title, alt, elementType, elementSrc: elementSrc || null, textContent: node.textContent || null, }; this.eventDispatcher.sendRequest(msg); aEvent.preventDefault(); } break; } case "MozDOMFullscreen:Request": { this.sendAsyncMessage("GeckoView:DOMFullscreenRequest", {}); break; } case "MozDOMFullscreen:Entered": case "MozDOMFullscreen:Exited": // Content may change fullscreen state by itself, and we should ensure // that the parent always exits fullscreen when content has left // full screen mode. if (this.contentWindow?.document.fullscreenElement) { break; } // fall-through case "MozDOMFullscreen:Exit": this.sendAsyncMessage("GeckoView:DOMFullscreenExit", {}); break; case "DOMMetaViewportFitChanged": if (aEvent.originalTarget.ownerGlobal == this.contentWindow) { this.notifyParentOfViewportFit(); } break; case "DOMContentLoaded": { if (aEvent.originalTarget.ownerGlobal == this.contentWindow) { // If loaded content doesn't have viewport-fit, parent still // uses old value of previous content. this.notifyParentOfViewportFit(); } if (this.contentWindow !== this.contentWindow?.top) { // Only check WebApp manifest on the top level window. return; } this.contentWindow.requestIdleCallback(async () => { const manifest = await lazy.ManifestObtainer.contentObtainManifest( this.contentWindow ); if (manifest) { this.eventDispatcher.sendRequest({ type: "GeckoView:WebAppManifest", manifest, }); } }); break; } case "MozFirstContentfulPaint": { this.eventDispatcher.sendRequest({ type: "GeckoView:FirstContentfulPaint", }); break; } case "MozPaintStatusReset": { this.eventDispatcher.sendRequest({ type: "GeckoView:PaintStatusReset", }); break; } } } } const { debug, warn } = ContentDelegateChild.initLogging( "ContentDelegateChild" );