mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-20 23:37:33 +09:00
Finish off PiP
This commit is contained in:
parent
aac2156499
commit
55a53a2576
14 changed files with 293 additions and 21 deletions
|
|
@ -5,3 +5,100 @@
|
||||||
browser[remote="true"] {
|
browser[remote="true"] {
|
||||||
-moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
|
-moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#picture-in-picture {
|
||||||
|
min-width: 300px;
|
||||||
|
min-height: 170px;
|
||||||
|
background: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player-container {
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player-overlay {
|
||||||
|
-moz-window-dragging: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pip-controls {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player-overlay:hover .pip-controls,
|
||||||
|
#player-overlay[keyboard] .pip-controls,
|
||||||
|
#player-overlay[seeking] .pip-controls {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-controls {
|
||||||
|
padding: 8px;
|
||||||
|
background: linear-gradient(rgba(0, 0, 0, .55), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
#playback-controls {
|
||||||
|
padding: 12px 10px 8px;
|
||||||
|
background: linear-gradient(transparent, rgba(0, 0, 0, .85));
|
||||||
|
-moz-window-dragging: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pip-controls button {
|
||||||
|
-moz-appearance: none;
|
||||||
|
-moz-window-dragging: no-drag;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 2px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(30, 30, 30, .8);
|
||||||
|
color: white;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pip-controls button:hover,
|
||||||
|
.pip-controls button:focus {
|
||||||
|
border-color: white;
|
||||||
|
background: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
#close {
|
||||||
|
font-size: 22px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#seek {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
-moz-window-dragging: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
#seek:disabled {
|
||||||
|
opacity: .45;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#time {
|
||||||
|
margin: 0 6px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
#resize {
|
||||||
|
justify-self: end;
|
||||||
|
align-self: end;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
cursor: se-resize;
|
||||||
|
-moz-window-dragging: no-drag;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#resize:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ var Player = {
|
||||||
browser: null,
|
browser: null,
|
||||||
source: null,
|
source: null,
|
||||||
sourceTab: null,
|
sourceTab: null,
|
||||||
|
seeking: false,
|
||||||
|
state: null,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
if (this.browser) {
|
if (this.browser) {
|
||||||
|
|
@ -53,21 +55,70 @@ var Player = {
|
||||||
mm.addMessageListener("PictureInPicture:Ready", ready);
|
mm.addMessageListener("PictureInPicture:Ready", ready);
|
||||||
mm.addMessageListener("PictureInPicture:Close", this.close);
|
mm.addMessageListener("PictureInPicture:Close", this.close);
|
||||||
mm.addMessageListener("PictureInPicture:State", message => {
|
mm.addMessageListener("PictureInPicture:State", message => {
|
||||||
document.getElementById("play").label = message.data.paused ?
|
this.updateState(message.data);
|
||||||
this.playLabel : document.getElementById("pause-label").value;
|
|
||||||
document.getElementById("mute").label = message.data.muted ?
|
|
||||||
document.getElementById("unmute-label").value : this.muteLabel;
|
|
||||||
});
|
});
|
||||||
browser.addEventListener("oop-browser-crashed", this.close);
|
browser.addEventListener("oop-browser-crashed", this.close);
|
||||||
mm.loadFrameScript("chrome://browser/content/pictureInPictureContent.js", false);
|
mm.loadFrameScript("chrome://browser/content/pictureInPictureContent.js", false);
|
||||||
},
|
},
|
||||||
|
|
||||||
command(command) {
|
command(command, data = {}) {
|
||||||
if (this.browser) {
|
if (this.browser) {
|
||||||
this.browser.messageManager.sendAsyncMessage("PictureInPicture:Command", {command});
|
this.browser.messageManager.sendAsyncMessage("PictureInPicture:Command",
|
||||||
|
Object.assign({command}, data));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
formatTime(seconds) {
|
||||||
|
if (!Number.isFinite(seconds) || seconds < 0) {
|
||||||
|
return "0:00";
|
||||||
|
}
|
||||||
|
seconds = Math.floor(seconds);
|
||||||
|
let hours = Math.floor(seconds / 3600);
|
||||||
|
let minutes = Math.floor(seconds / 60) % 60;
|
||||||
|
let pad = value => value < 10 ? "0" + value : String(value);
|
||||||
|
return (hours ? hours + ":" + pad(minutes) : String(minutes)) +
|
||||||
|
":" + pad(seconds % 60);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTime(position) {
|
||||||
|
let duration = this.state && this.state.duration;
|
||||||
|
let total = Number.isFinite(duration) ? this.formatTime(duration) :
|
||||||
|
document.getElementById("live-label").value;
|
||||||
|
document.getElementById("time").value = this.formatTime(position) + " / " + total;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateState(state) {
|
||||||
|
this.state = state;
|
||||||
|
document.getElementById("play").label = state.paused ?
|
||||||
|
this.playLabel : document.getElementById("pause-label").value;
|
||||||
|
document.getElementById("mute").label = state.muted ?
|
||||||
|
document.getElementById("unmute-label").value : this.muteLabel;
|
||||||
|
if (!this.seeking) {
|
||||||
|
let seek = document.getElementById("seek");
|
||||||
|
seek.disabled = !(state.seekableEnd > state.seekableStart);
|
||||||
|
seek.min = state.seekableStart;
|
||||||
|
seek.max = state.seekableEnd || 1;
|
||||||
|
seek.value = state.currentTime;
|
||||||
|
this.updateTime(state.currentTime);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
seek(value) {
|
||||||
|
let position = Number(value);
|
||||||
|
if (!Number.isFinite(position) || document.getElementById("seek").disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.seeking = true;
|
||||||
|
document.getElementById("player-overlay").setAttribute("seeking", "true");
|
||||||
|
this.updateTime(position);
|
||||||
|
this.command("seek", {time: position});
|
||||||
|
},
|
||||||
|
|
||||||
|
finishSeeking() {
|
||||||
|
this.seeking = false;
|
||||||
|
document.getElementById("player-overlay").removeAttribute("seeking");
|
||||||
|
},
|
||||||
|
|
||||||
returnToTab() {
|
returnToTab() {
|
||||||
if (this.source && this.source.isConnected) {
|
if (this.source && this.source.isConnected) {
|
||||||
let win = this.source.ownerGlobal;
|
let win = this.source.ownerGlobal;
|
||||||
|
|
@ -104,10 +155,22 @@ window.addEventListener("unload", function onUnload(event) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener("keydown", event => {
|
window.addEventListener("keydown", event => {
|
||||||
|
document.getElementById("player-overlay").setAttribute("keyboard", "true");
|
||||||
if (event.key == "Escape") {
|
if (event.key == "Escape") {
|
||||||
window.close();
|
window.close();
|
||||||
} else if (event.key == " " && event.target.localName != "button") {
|
} else if (event.target.localName == "input" || event.target.localName == "button") {
|
||||||
|
return;
|
||||||
|
} else if (event.key == " ") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
Player.command("playpause");
|
Player.command("playpause");
|
||||||
|
} else if (event.key == "ArrowLeft" || event.key == "ArrowRight") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (Player.state) {
|
||||||
|
Player.command("seek", {time: Player.state.currentTime +
|
||||||
|
(event.key == "ArrowLeft" ? -5 : 5)});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
window.addEventListener("mousemove", () => {
|
||||||
|
document.getElementById("player-overlay").removeAttribute("keyboard");
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,35 @@
|
||||||
<?xml-stylesheet href="chrome://browser/content/pictureInPicture.css" type="text/css"?>
|
<?xml-stylesheet href="chrome://browser/content/pictureInPicture.css" type="text/css"?>
|
||||||
<!DOCTYPE window SYSTEM "chrome://browser/locale/browser.dtd">
|
<!DOCTYPE window SYSTEM "chrome://browser/locale/browser.dtd">
|
||||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||||
|
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||||
id="picture-in-picture" windowtype="Browser:PictureInPicture"
|
id="picture-in-picture" windowtype="Browser:PictureInPicture"
|
||||||
title="&pictureInPicture.title;" width="480" height="318"
|
title="&pictureInPicture.title;" width="480" height="270"
|
||||||
persist="screenX screenY width height" style="min-width: 240px; min-height: 160px;">
|
hidechrome="true"
|
||||||
|
persist="screenX screenY width height">
|
||||||
<script type="application/javascript" src="chrome://browser/content/pictureInPicture.js"/>
|
<script type="application/javascript" src="chrome://browser/content/pictureInPicture.js"/>
|
||||||
<vbox id="player-container" flex="1" style="background: black;"/>
|
<stack flex="1">
|
||||||
<hbox align="center" pack="center">
|
<vbox id="player-container" flex="1"/>
|
||||||
<button id="play" label="&mediaPlay.label;" oncommand="Player.command('playpause');"/>
|
<vbox id="player-overlay" flex="1">
|
||||||
<button id="mute" label="&mediaMute.label;" oncommand="Player.command('mute');"/>
|
<hbox class="pip-controls" id="top-controls" pack="end">
|
||||||
<button id="return" label="&pictureInPicture.return;" oncommand="Player.returnToTab();"/>
|
<button id="close" label="×" tooltiptext="&pictureInPicture.close;"
|
||||||
<button id="close" label="&pictureInPicture.close;" oncommand="window.close();"/>
|
aria-label="&pictureInPicture.close;" oncommand="window.close();"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
|
<spacer flex="1"/>
|
||||||
|
<vbox class="pip-controls" id="playback-controls">
|
||||||
|
<html:input id="seek" type="range" min="0" max="1" step="0.1" value="0"
|
||||||
|
disabled="disabled" aria-label="&pictureInPicture.seek;"
|
||||||
|
oninput="Player.seek(this.value);" onchange="Player.finishSeeking();"/>
|
||||||
|
<hbox align="center">
|
||||||
|
<button id="play" label="&mediaPlay.label;" oncommand="Player.command('playpause');"/>
|
||||||
|
<button id="mute" label="&mediaMute.label;" oncommand="Player.command('mute');"/>
|
||||||
|
<label id="time" value="0:00 / 0:00" flex="1" crop="end"/>
|
||||||
|
<button id="return" label="&pictureInPicture.return;" oncommand="Player.returnToTab();"/>
|
||||||
|
</hbox>
|
||||||
|
</vbox>
|
||||||
|
</vbox>
|
||||||
|
<resizer id="resize" dir="bottomright" right="0" bottom="0"/>
|
||||||
|
</stack>
|
||||||
<label id="pause-label" value="&mediaPause.label;" hidden="true"/>
|
<label id="pause-label" value="&mediaPause.label;" hidden="true"/>
|
||||||
<label id="unmute-label" value="&mediaUnmute.label;" hidden="true"/>
|
<label id="unmute-label" value="&mediaUnmute.label;" hidden="true"/>
|
||||||
|
<label id="live-label" value="&pictureInPicture.live;" hidden="true"/>
|
||||||
</window>
|
</window>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ var still = null;
|
||||||
var observer = null;
|
var observer = null;
|
||||||
var sourceWindow = null;
|
var sourceWindow = null;
|
||||||
var initialized = false;
|
var initialized = false;
|
||||||
var events = ["play", "pause", "volumechange", "ended", "seeked", "resize"];
|
var events = ["play", "pause", "volumechange", "ended", "seeking", "seeked",
|
||||||
|
"resize", "timeupdate", "durationchange", "progress"];
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
if (!source) {
|
if (!source) {
|
||||||
|
|
@ -26,8 +27,40 @@ function update() {
|
||||||
still.height = source.videoHeight;
|
still.height = source.videoHeight;
|
||||||
still.getContext("2d").drawImage(source, 0, 0, still.width, still.height);
|
still.getContext("2d").drawImage(source, 0, 0, still.width, still.height);
|
||||||
}
|
}
|
||||||
sendAsyncMessage("PictureInPicture:State",
|
let ranges = source.seekable;
|
||||||
{paused: source.paused || source.ended, muted: source.muted});
|
sendAsyncMessage("PictureInPicture:State", {
|
||||||
|
paused: source.paused || source.ended,
|
||||||
|
muted: source.muted,
|
||||||
|
currentTime: source.currentTime,
|
||||||
|
duration: source.duration,
|
||||||
|
seekableStart: ranges.length ? ranges.start(0) : 0,
|
||||||
|
seekableEnd: ranges.length ? ranges.end(ranges.length - 1) : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function seekTo(time) {
|
||||||
|
if (typeof time != "number" || !Number.isFinite(time)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ranges = source.seekable;
|
||||||
|
// Live streams may have no seekable range, or a moving DVR window.
|
||||||
|
// Pick the nearest valid point, including when there are gaps.
|
||||||
|
let position = null;
|
||||||
|
let distance = Infinity;
|
||||||
|
for (let i = 0; i < ranges.length; i++) {
|
||||||
|
let candidate = Math.max(ranges.start(i), Math.min(time, ranges.end(i)));
|
||||||
|
if (Math.abs(candidate - time) < distance) {
|
||||||
|
distance = Math.abs(candidate - time);
|
||||||
|
position = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (position !== null) {
|
||||||
|
try {
|
||||||
|
source.currentTime = position;
|
||||||
|
} catch (error) {
|
||||||
|
Components.utils.reportError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
|
|
@ -82,7 +115,11 @@ addMessageListener("PictureInPicture:Init", message => {
|
||||||
element.style.cssText = "position:absolute;width:100%;height:100%;object-fit:contain";
|
element.style.cssText = "position:absolute;width:100%;height:100%;object-fit:contain";
|
||||||
doc.body.appendChild(element);
|
doc.body.appendChild(element);
|
||||||
}
|
}
|
||||||
output.muted = true; // Audio continues to come from the originating video.
|
// Decoder capture redirects audio to the captured stream. Its samples
|
||||||
|
// already include the source's volume/mute settings. MediaStream sources
|
||||||
|
// keep their own audio output, so mute only that case to avoid an echo.
|
||||||
|
output.muted = !!source.srcObject;
|
||||||
|
output.volume = 1;
|
||||||
source.mozPictureInPicture = true;
|
source.mozPictureInPicture = true;
|
||||||
stream = source.mozCaptureStream();
|
stream = source.mozCaptureStream();
|
||||||
output.srcObject = stream;
|
output.srcObject = stream;
|
||||||
|
|
@ -124,6 +161,9 @@ addMessageListener("PictureInPicture:Command", message => {
|
||||||
case "mute":
|
case "mute":
|
||||||
source.muted = !source.muted;
|
source.muted = !source.muted;
|
||||||
break;
|
break;
|
||||||
|
case "seek":
|
||||||
|
seekTo(message.data.time);
|
||||||
|
break;
|
||||||
case "close":
|
case "close":
|
||||||
cleanup();
|
cleanup();
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -781,3 +781,5 @@ you can use these alternative items. Otherwise, their values should be empty. -
|
||||||
<!ENTITY pictureInPicture.title "Picture-in-Picture">
|
<!ENTITY pictureInPicture.title "Picture-in-Picture">
|
||||||
<!ENTITY pictureInPicture.return "Return to tab">
|
<!ENTITY pictureInPicture.return "Return to tab">
|
||||||
<!ENTITY pictureInPicture.close "Close">
|
<!ENTITY pictureInPicture.close "Close">
|
||||||
|
<!ENTITY pictureInPicture.seek "Playback position">
|
||||||
|
<!ENTITY pictureInPicture.live "Live">
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ this.PictureInPicture = {
|
||||||
}
|
}
|
||||||
let screen = browser.ownerGlobal.screen;
|
let screen = browser.ownerGlobal.screen;
|
||||||
let width = Math.min(480, screen.availWidth);
|
let width = Math.min(480, screen.availWidth);
|
||||||
let height = Math.min(Math.round(width * data.height / data.width) + 48,
|
let height = Math.min(Math.round(width * data.height / data.width),
|
||||||
Math.round(screen.availHeight * 0.8));
|
Math.round(screen.availHeight * 0.8));
|
||||||
this.window = browser.ownerGlobal.openDialog(
|
this.window = browser.ownerGlobal.openDialog(
|
||||||
"chrome://browser/content/pictureInPicture.xul", "",
|
"chrome://browser/content/pictureInPicture.xul", "",
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,7 @@ public:
|
||||||
NS_IMETHOD Move(double aX, double aY) override;
|
NS_IMETHOD Move(double aX, double aY) override;
|
||||||
virtual void SetSizeMode(nsSizeMode aMode) override;
|
virtual void SetSizeMode(nsSizeMode aMode) override;
|
||||||
NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
|
NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
|
||||||
|
void SetAlwaysOnTop(bool aAlwaysOnTop) override;
|
||||||
|
|
||||||
void EnteredFullScreen(bool aFullScreen, bool aNativeMode = true);
|
void EnteredFullScreen(bool aFullScreen, bool aNativeMode = true);
|
||||||
virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
|
virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
|
||||||
|
|
|
||||||
|
|
@ -1319,6 +1319,18 @@ nsCocoaWindow::SetSizeMode(nsSizeMode aMode)
|
||||||
// This has to preserve the window's frame bounds.
|
// This has to preserve the window's frame bounds.
|
||||||
// This method requires (as does the Windows impl.) that you call Resize shortly
|
// This method requires (as does the Windows impl.) that you call Resize shortly
|
||||||
// after calling HideWindowChrome. See bug 498835 for fixing this.
|
// after calling HideWindowChrome. See bug 498835 for fixing this.
|
||||||
|
void nsCocoaWindow::SetAlwaysOnTop(bool aAlwaysOnTop)
|
||||||
|
{
|
||||||
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
|
||||||
|
nsBaseWidget::SetAlwaysOnTop(aAlwaysOnTop);
|
||||||
|
if (mWindow && (mWindowType == eWindowType_toplevel ||
|
||||||
|
mWindowType == eWindowType_dialog)) {
|
||||||
|
[mWindow setLevel:aAlwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel];
|
||||||
|
[mWindow setHidesOnDeactivate:NO];
|
||||||
|
}
|
||||||
|
NS_OBJC_END_TRY_ABORT_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(bool aShouldHide)
|
NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(bool aShouldHide)
|
||||||
{
|
{
|
||||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||||
|
|
@ -1353,6 +1365,9 @@ NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(bool aShouldHide)
|
||||||
|
|
||||||
// Re-import state.
|
// Re-import state.
|
||||||
[mWindow importState:state];
|
[mWindow importState:state];
|
||||||
|
if (mAlwaysOnTop) {
|
||||||
|
SetAlwaysOnTop(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Reparent the content view.
|
// Reparent the content view.
|
||||||
[mWindow setContentView:contentView];
|
[mWindow setContentView:contentView];
|
||||||
|
|
|
||||||
|
|
@ -1255,6 +1255,15 @@ nsWindow::NativeMove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsWindow::SetAlwaysOnTop(bool aAlwaysOnTop)
|
||||||
|
{
|
||||||
|
nsBaseWidget::SetAlwaysOnTop(aAlwaysOnTop);
|
||||||
|
if (mShell) {
|
||||||
|
gtk_window_set_keep_above(GTK_WINDOW(mShell), aAlwaysOnTop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
nsWindow::SetZIndex(int32_t aZIndex)
|
nsWindow::SetZIndex(int32_t aZIndex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@ public:
|
||||||
virtual bool IsEnabled() const override;
|
virtual bool IsEnabled() const override;
|
||||||
|
|
||||||
void SetZIndex(int32_t aZIndex) override;
|
void SetZIndex(int32_t aZIndex) override;
|
||||||
|
void SetAlwaysOnTop(bool aAlwaysOnTop) override;
|
||||||
virtual void SetSizeMode(nsSizeMode aMode) override;
|
virtual void SetSizeMode(nsSizeMode aMode) override;
|
||||||
NS_IMETHOD Enable(bool aState) override;
|
NS_IMETHOD Enable(bool aState) override;
|
||||||
NS_IMETHOD SetFocus(bool aRaise = false) override;
|
NS_IMETHOD SetFocus(bool aRaise = false) override;
|
||||||
|
|
|
||||||
|
|
@ -803,6 +803,12 @@ class nsIWidget : public nsISupports
|
||||||
*/
|
*/
|
||||||
virtual void SetZIndex(int32_t aZIndex) = 0;
|
virtual void SetZIndex(int32_t aZIndex) = 0;
|
||||||
|
|
||||||
|
// Keep a top-level window above ordinary windows, including other apps.
|
||||||
|
virtual void SetAlwaysOnTop(bool aAlwaysOnTop)
|
||||||
|
{
|
||||||
|
mAlwaysOnTop = aAlwaysOnTop;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the widget's z-index.
|
* Gets the widget's z-index.
|
||||||
*/
|
*/
|
||||||
|
|
@ -2032,6 +2038,7 @@ protected:
|
||||||
bool mOnDestroyCalled;
|
bool mOnDestroyCalled;
|
||||||
nsWindowType mWindowType;
|
nsWindowType mWindowType;
|
||||||
int32_t mZIndex;
|
int32_t mZIndex;
|
||||||
|
bool mAlwaysOnTop = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsIWidget, NS_IWIDGET_IID)
|
NS_DEFINE_STATIC_IID_ACCESSOR(nsIWidget, NS_IWIDGET_IID)
|
||||||
|
|
|
||||||
|
|
@ -2033,6 +2033,17 @@ nsWindow::BeginResizeDrag(WidgetGUIEvent* aEvent,
|
||||||
*
|
*
|
||||||
**************************************************************/
|
**************************************************************/
|
||||||
|
|
||||||
|
void
|
||||||
|
nsWindow::SetAlwaysOnTop(bool aAlwaysOnTop)
|
||||||
|
{
|
||||||
|
nsBaseWidget::SetAlwaysOnTop(aAlwaysOnTop);
|
||||||
|
if (mWnd && (mWindowType == eWindowType_toplevel ||
|
||||||
|
mWindowType == eWindowType_dialog)) {
|
||||||
|
::SetWindowPos(mWnd, aAlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,
|
||||||
|
0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Position the window behind the given window
|
// Position the window behind the given window
|
||||||
void
|
void
|
||||||
nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
|
nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
|
||||||
|
|
@ -2059,6 +2070,10 @@ nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
|
||||||
flags |= SWP_NOACTIVATE;
|
flags |= SWP_NOACTIVATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Application z-order changes must not demote an always-on-top window.
|
||||||
|
if (mAlwaysOnTop) {
|
||||||
|
behind = HWND_TOPMOST;
|
||||||
|
}
|
||||||
::SetWindowPos(mWnd, behind, 0, 0, 0, 0, flags);
|
::SetWindowPos(mWnd, behind, 0, 0, 0, 0, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,7 @@ public:
|
||||||
virtual nsresult MakeFullScreen(bool aFullScreen,
|
virtual nsresult MakeFullScreen(bool aFullScreen,
|
||||||
nsIScreen* aScreen = nullptr) override;
|
nsIScreen* aScreen = nullptr) override;
|
||||||
NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
|
NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
|
||||||
|
void SetAlwaysOnTop(bool aAlwaysOnTop) override;
|
||||||
NS_IMETHOD Invalidate(bool aEraseBackground = false,
|
NS_IMETHOD Invalidate(bool aEraseBackground = false,
|
||||||
bool aUpdateNCArea = false,
|
bool aUpdateNCArea = false,
|
||||||
bool aIncludeChildren = false);
|
bool aIncludeChildren = false);
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,9 @@ NS_IMETHODIMP nsXULWindow::SetZLevel(uint32_t aLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// do it
|
// do it
|
||||||
|
if (mWindow) {
|
||||||
|
mWindow->SetAlwaysOnTop(aLevel >= nsIXULWindow::raisedZ);
|
||||||
|
}
|
||||||
mediator->SetZLevel(this, aLevel);
|
mediator->SetZLevel(this, aLevel);
|
||||||
PersistentAttributesDirty(PAD_MISC);
|
PersistentAttributesDirty(PAD_MISC);
|
||||||
SavePersistentAttributes();
|
SavePersistentAttributes();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue