/* 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 a UA widget. It runs in per-origin UA widget scope, // to be loaded by UAWidgetsChild.sys.mjs. /* * This is the class of entry. It will construct the actual implementation * according to the value of the "controls" property. */ this.VideoControlsWidget = class { constructor(shadowRoot, prefs) { this.shadowRoot = shadowRoot; this.prefs = prefs; this.element = shadowRoot.host; this.document = this.element.ownerDocument; this.window = this.document.defaultView; this.isMobile = this.window.navigator.appVersion.includes("Android"); } /* * Callback called by UAWidgets right after constructor. */ onsetup() { this.switchImpl(); } /* * Callback called by UAWidgets when the "controls" property changes. */ onchange() { this.switchImpl(); } /* * Actually switch the implementation. * - With "controls" set, the VideoControlsImplWidget controls should load. * - Without it, on mobile, the NoControlsMobileImplWidget should load, so * the user could see the click-to-play button when the video/audio is blocked. * - Without it, on desktop, the NoControlsPictureInPictureImpleWidget should load * if the video is being viewed in Picture-in-Picture. */ switchImpl() { let newImpl; let pageURI = this.document.documentURI; if (this.element.controls) { newImpl = VideoControlsImplWidget; } else if (this.isMobile) { newImpl = NoControlsMobileImplWidget; } else if (VideoControlsWidget.isPictureInPictureVideo(this.element)) { newImpl = NoControlsPictureInPictureImplWidget; } else if ( pageURI.startsWith("http://") || pageURI.startsWith("https://") ) { newImpl = NoControlsDesktopImplWidget; } // Skip if we are asked to load the same implementation, and // the underlying element state hasn't changed in ways that we // care about. This can happen if the property is set again // without a value change. if (this.impl && this.impl.constructor == newImpl) { this.impl.onchange(); return; } if (this.impl) { this.impl.teardown(); this.shadowRoot.firstChild.remove(); } if (newImpl) { this.impl = new newImpl(this.shadowRoot, this.prefs); this.mDirection = "ltr"; let intlUtils = this.window.intlUtils; if (intlUtils) { this.mDirection = intlUtils.isAppLocaleRTL() ? "rtl" : "ltr"; } this.impl.onsetup(this.mDirection); } else { this.impl = undefined; } } teardown() { if (!this.impl) { return; } this.impl.teardown(); this.shadowRoot.firstChild.remove(); delete this.impl; } onPrefChange(prefName, prefValue) { this.prefs[prefName] = prefValue; if (!this.impl) { return; } this.impl.onPrefChange(prefName, prefValue); } // If you change this, also change SEEK_TIME_SECS in PictureInPictureChild.sys.mjs static SEEK_TIME_SECS = 5; static isPictureInPictureVideo(someVideo) { return someVideo.isCloningElementVisually; } /** * Returns true if a