diff --git a/browser/base/content/pictureInPicture.css b/browser/base/content/pictureInPicture.css
index 6403392694..ca99293f6c 100644
--- a/browser/base/content/pictureInPicture.css
+++ b/browser/base/content/pictureInPicture.css
@@ -11,14 +11,53 @@ browser[remote="true"] {
min-height: 170px;
background: black;
color: white;
+ overflow: hidden;
+}
+
+#player-stack {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ overflow: hidden;
+ -moz-user-select: none;
+}
+
+#player-container,
+#player-overlay {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ min-width: 0;
+ min-height: 0;
}
#player-container {
background: black;
}
+#player-container > browser {
+ display: -moz-box;
+ width: 100%;
+ height: 100%;
+ min-width: 0;
+ min-height: 0;
+ border: 0;
+}
+
#player-overlay {
- -moz-window-dragging: drag;
+ display: flex;
+ flex-direction: column;
+ -moz-window-dragging: no-drag;
+}
+
+#drag-area {
+ flex: 1;
+ min-height: 0;
+ cursor: move;
}
.pip-controls {
@@ -29,22 +68,33 @@ browser[remote="true"] {
#player-overlay:hover .pip-controls,
#player-overlay[keyboard] .pip-controls,
-#player-overlay[seeking] .pip-controls {
+#player-overlay[seeking] .pip-controls,
+#player-stack[dragging] .pip-controls {
opacity: 1;
pointer-events: auto;
}
#top-controls {
+ display: flex;
+ flex-shrink: 0;
+ justify-content: flex-end;
padding: 8px;
background: linear-gradient(rgba(0, 0, 0, .55), transparent);
}
#playback-controls {
+ flex-shrink: 0;
padding: 12px 10px 8px;
background: linear-gradient(transparent, rgba(0, 0, 0, .85));
-moz-window-dragging: no-drag;
}
+#button-row {
+ display: flex;
+ align-items: center;
+ min-width: 0;
+}
+
.pip-controls button {
-moz-appearance: none;
-moz-window-dragging: no-drag;
@@ -72,6 +122,7 @@ browser[remote="true"] {
}
#seek {
+ box-sizing: border-box;
display: block;
width: 100%;
margin: 0 0 6px;
@@ -85,20 +136,56 @@ browser[remote="true"] {
}
#time {
+ flex: 1;
+ min-width: 0;
margin: 0 6px;
font-variant-numeric: tabular-nums;
}
-#resize {
- justify-self: end;
- align-self: end;
- width: 14px;
- height: 14px;
- cursor: se-resize;
+.resize-edge,
+.resize-corner {
+ position: absolute;
+ z-index: 2;
-moz-window-dragging: no-drag;
- opacity: 0;
}
-#resize:hover {
- opacity: 1;
+.resize-edge[data-resize="n"],
+.resize-edge[data-resize="s"] {
+ left: 14px;
+ right: 14px;
+ height: 6px;
+ cursor: ns-resize;
+}
+
+.resize-edge[data-resize="e"],
+.resize-edge[data-resize="w"] {
+ top: 14px;
+ bottom: 14px;
+ width: 6px;
+ cursor: ew-resize;
+}
+
+.resize-corner {
+ width: 14px;
+ height: 14px;
+}
+
+[data-resize*="n"] { top: 0; }
+[data-resize*="s"] { bottom: 0; }
+[data-resize*="e"] { right: 0; }
+[data-resize*="w"] { left: 0; }
+[data-resize="nw"], [data-resize="se"] { cursor: nwse-resize; }
+[data-resize="ne"], [data-resize="sw"] { cursor: nesw-resize; }
+
+@media (min-width: 700px) {
+ #playback-controls {
+ padding: 20px 20px 14px;
+ }
+ .pip-controls button {
+ padding: 8px 12px;
+ font-size: 14px;
+ }
+ #time {
+ font-size: 14px;
+ }
}
diff --git a/browser/base/content/pictureInPicture.js b/browser/base/content/pictureInPicture.js
index 7f3df38927..aaaf3d0114 100644
--- a/browser/base/content/pictureInPicture.js
+++ b/browser/base/content/pictureInPicture.js
@@ -13,6 +13,71 @@ var Player = {
sourceTab: null,
seeking: false,
state: null,
+ drag: null,
+
+ startDrag(event) {
+ if (event.button != 0 ||
+ event.target.closest("button, input, #playback-controls")) {
+ return;
+ }
+ let stack = document.getElementById("player-stack");
+ if (!stack.contains(event.target)) {
+ return;
+ }
+ event.preventDefault();
+ this.drag = {
+ direction: event.target.getAttribute("data-resize") || "",
+ pointerX: event.screenX,
+ pointerY: event.screenY,
+ x: window.screenX,
+ y: window.screenY,
+ width: window.outerWidth,
+ height: window.outerHeight,
+ };
+ // Capture keeps the gesture working after the pointer leaves the window.
+ stack.setCapture(true);
+ stack.setAttribute("dragging", "true");
+ },
+
+ moveDrag(event) {
+ let drag = this.drag;
+ if (!drag) {
+ return;
+ }
+ if (!(event.buttons & 1)) {
+ this.endDrag();
+ return;
+ }
+ event.preventDefault();
+ let dx = event.screenX - drag.pointerX;
+ let dy = event.screenY - drag.pointerY;
+ if (!drag.direction) {
+ window.moveTo(drag.x + dx, drag.y + dy);
+ return;
+ }
+ let west = drag.direction.includes("w");
+ let east = drag.direction.includes("e");
+ let north = drag.direction.includes("n");
+ let south = drag.direction.includes("s");
+ let width = Math.max(300, drag.width + (west ? -dx : east ? dx : 0));
+ let height = Math.max(170, drag.height + (north ? -dy : south ? dy : 0));
+ window.resizeTo(width, height);
+ // Anchor the opposite edge, using the actual size in case the platform
+ // imposed a constraint (for example, at a monitor boundary).
+ if (west || north) {
+ window.moveTo(west ? drag.x + drag.width - window.outerWidth : drag.x,
+ north ? drag.y + drag.height - window.outerHeight : drag.y);
+ }
+ },
+
+ endDrag() {
+ if (this.drag) {
+ this.drag = null;
+ let stack = document.getElementById("player-stack");
+ stack.releaseCapture();
+ stack.removeAttribute("dragging");
+ }
+ },
init() {
if (this.browser) {
@@ -129,6 +194,7 @@ var Player = {
},
destroy() {
+ this.endDrag();
if (this.sourceTab) {
this.sourceTab.removeEventListener("TabClose", this.close);
this.source.removeEventListener("oop-browser-crashed", this.close);
@@ -171,6 +237,14 @@ window.addEventListener("keydown", event => {
}
}
});
-window.addEventListener("mousemove", () => {
+window.addEventListener("mousedown", event => Player.startDrag(event));
+window.addEventListener("mousemove", event => {
+ Player.moveDrag(event);
document.getElementById("player-overlay").removeAttribute("keyboard");
});
+window.addEventListener("mouseup", () => Player.endDrag());
+window.addEventListener("blur", event => {
+ if (event.target == window) {
+ Player.endDrag();
+ }
+});
diff --git a/browser/base/content/pictureInPicture.xul b/browser/base/content/pictureInPicture.xul
index 9c37362607..cca7a7050a 100644
--- a/browser/base/content/pictureInPicture.xul
+++ b/browser/base/content/pictureInPicture.xul
@@ -12,28 +12,35 @@
hidechrome="true"
persist="screenX screenY width height">
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/base/content/pictureInPictureContent.js b/browser/base/content/pictureInPictureContent.js
index 9e9af4ddf3..074415b598 100644
--- a/browser/base/content/pictureInPictureContent.js
+++ b/browser/base/content/pictureInPictureContent.js
@@ -107,12 +107,13 @@ addMessageListener("PictureInPicture:Init", message => {
source = PictureInPicture.takeSource(message.data.id);
sourceWindow = source.ownerGlobal;
let doc = content.document;
- doc.documentElement.style.cssText = "height:100%;background:black";
- doc.body.style.cssText = "margin:0;height:100%;overflow:hidden";
+ doc.documentElement.style.cssText = "width:100%;height:100%;background:black;overflow:hidden";
+ doc.body.style.cssText = "margin:0;width:100%;height:100%;overflow:hidden";
output = doc.createElement("video");
still = doc.createElement("canvas");
for (let element of [output, still]) {
- element.style.cssText = "position:absolute;width:100%;height:100%;object-fit:contain";
+ element.style.cssText = "position:fixed;left:0;top:0;width:100vw;height:100vh;" +
+ "max-width:none;max-height:none;object-fit:contain";
doc.body.appendChild(element);
}
// Decoder capture redirects audio to the captured stream. Its samples