mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Merge mozilla-central to inbound. a=merge CLOSED TREE
This commit is contained in:
commit
c603510fa9
@ -94,7 +94,12 @@ const messageListeners = {
|
||||
if (this.content.document.fullscreenEnabled) {
|
||||
media.requestFullscreen();
|
||||
}
|
||||
|
||||
break;
|
||||
case "pictureinpicture":
|
||||
let event = new this.content.CustomEvent("MozTogglePictureInPicture", {
|
||||
bubbles: true,
|
||||
}, this.content);
|
||||
media.dispatchEvent(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1486,6 +1486,9 @@ pref("media.autoplay.block-webaudio", true);
|
||||
pref("media.autoplay.block-webaudio", false);
|
||||
#endif
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
pref("media.videocontrols.picture-in-picture.enabled", false);
|
||||
#endif
|
||||
|
||||
// Play with different values of the decay time and get telemetry,
|
||||
// 0 means to randomize (and persist) the experiment value in users' profiles,
|
||||
|
@ -175,6 +175,13 @@
|
||||
accesskey="&leaveDOMFullScreen.accesskey;"
|
||||
label="&leaveDOMFullScreen.label;"
|
||||
oncommand="gContextMenu.leaveDOMFullScreen();"/>
|
||||
#ifdef NIGHTLY_BUILD
|
||||
<!-- Don't forget to add a properly localized label and access key
|
||||
before letting this ride up to beta. -->
|
||||
<menuitem id="context-video-pictureinpicture"
|
||||
label="Picture in Picture"
|
||||
oncommand="gContextMenu.mediaCommand('pictureinpicture');"/>
|
||||
#endif
|
||||
<menuseparator id="context-media-sep-commands"/>
|
||||
<menuitem id="context-reloadimage"
|
||||
label="&reloadImageCmd.label;"
|
||||
|
@ -670,6 +670,12 @@ nsContextMenu.prototype = {
|
||||
this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
|
||||
this.showItem("context-media-hidecontrols", this.target.controls && (this.onVideo || (this.onAudio && !this.inSyntheticDoc)));
|
||||
this.showItem("context-video-fullscreen", this.onVideo && !this.target.ownerDocument.fullscreen);
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
let shouldDisplay = Services.prefs.getBoolPref("media.videocontrols.picture-in-picture.enabled") &&
|
||||
this.onVideo &&
|
||||
!this.target.ownerDocument.fullscreen;
|
||||
this.showItem("context-video-pictureinpicture", shouldDisplay);
|
||||
}
|
||||
this.showItem("context-media-eme-learnmore", this.onDRMMedia);
|
||||
this.showItem("context-media-eme-separator", this.onDRMMedia);
|
||||
|
||||
|
@ -415,6 +415,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
PageThumbs: "resource://gre/modules/PageThumbs.jsm",
|
||||
PdfJs: "resource://pdf.js/PdfJs.jsm",
|
||||
PermissionUI: "resource:///modules/PermissionUI.jsm",
|
||||
PictureInPicture: "resource://gre/modules/PictureInPicture.jsm",
|
||||
PingCentre: "resource:///modules/PingCentre.jsm",
|
||||
PlacesBackups: "resource://gre/modules/PlacesBackups.jsm",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
|
||||
@ -512,6 +513,8 @@ const listeners = {
|
||||
"ContentSearch": ["ContentSearch"],
|
||||
"FormValidation:ShowPopup": ["FormValidationHandler"],
|
||||
"FormValidation:HidePopup": ["FormValidationHandler"],
|
||||
"PictureInPicture:Request": ["PictureInPicture"],
|
||||
"PictureInPicture:Close": ["PictureInPicture"],
|
||||
"Prompt:Open": ["RemotePrompt"],
|
||||
"Reader:FaviconRequest": ["ReaderParent"],
|
||||
"Reader:UpdateReaderButton": ["ReaderParent"],
|
||||
|
@ -3189,17 +3189,18 @@ var SessionStoreInternal = {
|
||||
* @param tab to navigate and restore.
|
||||
*/
|
||||
async _asyncNavigateAndRestore(tab) {
|
||||
let initialBrowser = tab.linkedBrowser;
|
||||
let permanentKey = tab.linkedBrowser.permanentKey;
|
||||
|
||||
// NOTE: This is currently the only async operation used, but this is likely
|
||||
// to change in the future.
|
||||
await TabStateFlusher.flush(initialBrowser);
|
||||
await TabStateFlusher.flush(tab.linkedBrowser);
|
||||
|
||||
// Now that we have flushed state, our loadArguments, etc. may have been
|
||||
// overwritten by multiple calls to navigateAndRestore. Load the most
|
||||
// recently stored one.
|
||||
let {loadArguments, historyIndex} =
|
||||
this._remotenessChangingBrowsers.get(initialBrowser.permanentKey);
|
||||
this._remotenessChangingBrowsers.delete(initialBrowser.permanentKey);
|
||||
this._remotenessChangingBrowsers.get(permanentKey);
|
||||
this._remotenessChangingBrowsers.delete(permanentKey);
|
||||
|
||||
// The tab might have been closed/gone in the meantime.
|
||||
if (tab.closing || !tab.linkedBrowser) {
|
||||
|
@ -1581,9 +1581,9 @@ var Scratchpad = {
|
||||
this.editor.on("cursorActivity", this.updateStatusBar);
|
||||
const okstring = this.strings.GetStringFromName("selfxss.okstring");
|
||||
const msg = this.strings.formatStringFromName("selfxss.msg", [okstring], 1);
|
||||
this._onPaste = WebConsoleUtils.pasteHandlerGen(this.editor.container.contentDocument.body,
|
||||
this.notificationBox,
|
||||
msg, okstring);
|
||||
this._onPaste = pasteHandlerGen(this.editor.container.contentDocument.body,
|
||||
this.notificationBox, msg, okstring);
|
||||
|
||||
editorElement.addEventListener("paste", this._onPaste, true);
|
||||
editorElement.addEventListener("drop", this._onPaste);
|
||||
this.editor.on("saveRequested", () => this.saveFile());
|
||||
@ -2268,3 +2268,52 @@ XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function() {
|
||||
addEventListener("load", Scratchpad.onLoad.bind(Scratchpad), false);
|
||||
addEventListener("unload", Scratchpad.onUnload.bind(Scratchpad), false);
|
||||
addEventListener("close", Scratchpad.onClose.bind(Scratchpad), false);
|
||||
|
||||
/**
|
||||
* The inputNode "paste" event handler generator. Helps prevent
|
||||
* self-xss attacks
|
||||
*
|
||||
* @param Element inputField
|
||||
* @param Element notificationBox
|
||||
* @returns A function to be added as a handler to 'paste' and
|
||||
*'drop' events on the input field
|
||||
*/
|
||||
function pasteHandlerGen(inputField, notificationBox, msg, okstring) {
|
||||
const handler = function(event) {
|
||||
if (WebConsoleUtils.usageCount >= WebConsoleUtils.CONSOLE_ENTRY_THRESHOLD) {
|
||||
inputField.removeEventListener("paste", handler);
|
||||
inputField.removeEventListener("drop", handler);
|
||||
return true;
|
||||
}
|
||||
if (notificationBox.getNotificationWithValue("selfxss-notification")) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
const notification = notificationBox.appendNotification(msg,
|
||||
"selfxss-notification", null,
|
||||
notificationBox.PRIORITY_WARNING_HIGH, null,
|
||||
function(eventType) {
|
||||
// Cleanup function if notification is dismissed
|
||||
if (eventType == "removed") {
|
||||
inputField.removeEventListener("keyup", pasteKeyUpHandler);
|
||||
}
|
||||
});
|
||||
|
||||
function pasteKeyUpHandler() {
|
||||
const value = inputField.value || inputField.textContent;
|
||||
if (value.includes(okstring)) {
|
||||
notificationBox.removeNotification(notification);
|
||||
inputField.removeEventListener("keyup", pasteKeyUpHandler);
|
||||
WebConsoleUtils.usageCount = WebConsoleUtils.CONSOLE_ENTRY_THRESHOLD;
|
||||
}
|
||||
}
|
||||
inputField.addEventListener("keyup", pasteKeyUpHandler);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
};
|
||||
return handler;
|
||||
}
|
||||
|
@ -34,14 +34,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
|
||||
"@mozilla.org/widget/clipboardhelper;1",
|
||||
"nsIClipboardHelper");
|
||||
|
||||
Object.defineProperty(this, "WebConsoleUtils", {
|
||||
get: function() {
|
||||
return require("devtools/client/webconsole/utils").Utils;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["VariablesView", "escapeHTML"];
|
||||
|
||||
/**
|
||||
@ -3328,7 +3320,7 @@ VariablesView.getGrip = function(aValue) {
|
||||
// fall through
|
||||
case "function":
|
||||
return { type: "object",
|
||||
class: WebConsoleUtils.getObjectClassName(aValue) };
|
||||
class: getObjectClassName(aValue) };
|
||||
default:
|
||||
console.error("Failed to provide a grip for value of " + typeof value +
|
||||
": " + aValue);
|
||||
@ -3336,6 +3328,89 @@ VariablesView.getGrip = function(aValue) {
|
||||
}
|
||||
};
|
||||
|
||||
// Match the function name from the result of toString() or toSource().
|
||||
//
|
||||
// Examples:
|
||||
// (function foobar(a, b) { ...
|
||||
// function foobar2(a) { ...
|
||||
// function() { ...
|
||||
const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
|
||||
|
||||
/**
|
||||
* Helper function to deduce the name of the provided function.
|
||||
*
|
||||
* @param function function
|
||||
* The function whose name will be returned.
|
||||
* @return string
|
||||
* Function name.
|
||||
*/
|
||||
function getFunctionName(func) {
|
||||
let name = null;
|
||||
if (func.name) {
|
||||
name = func.name;
|
||||
} else {
|
||||
let desc;
|
||||
try {
|
||||
desc = func.getOwnPropertyDescriptor("displayName");
|
||||
} catch (ex) {
|
||||
// Ignore.
|
||||
}
|
||||
if (desc && typeof desc.value == "string") {
|
||||
name = desc.value;
|
||||
}
|
||||
}
|
||||
if (!name) {
|
||||
try {
|
||||
const str = (func.toString() || func.toSource()) + "";
|
||||
name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
|
||||
} catch (ex) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object class name. For example, the |window| object has the Window
|
||||
* class name (based on [object Window]).
|
||||
*
|
||||
* @param object object
|
||||
* The object you want to get the class name for.
|
||||
* @return string
|
||||
* The object class name.
|
||||
*/
|
||||
function getObjectClassName(object) {
|
||||
if (object === null) {
|
||||
return "null";
|
||||
}
|
||||
if (object === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
|
||||
const type = typeof object;
|
||||
if (type != "object") {
|
||||
// Grip class names should start with an uppercase letter.
|
||||
return type.charAt(0).toUpperCase() + type.substr(1);
|
||||
}
|
||||
|
||||
let className;
|
||||
|
||||
try {
|
||||
className = ((object + "").match(/^\[object (\S+)\]$/) || [])[1];
|
||||
if (!className) {
|
||||
className = ((object.constructor + "")
|
||||
.match(/^\[object (\S+)\]$/) || [])[1];
|
||||
}
|
||||
if (!className && typeof object.constructor == "function") {
|
||||
className = getFunctionName(object.constructor);
|
||||
}
|
||||
} catch (ex) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a custom formatted property string for a grip.
|
||||
*
|
||||
|
@ -13,14 +13,6 @@ var promise = require("promise");
|
||||
var defer = require("devtools/shared/defer");
|
||||
var {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
|
||||
|
||||
Object.defineProperty(this, "WebConsoleUtils", {
|
||||
get: function() {
|
||||
return require("devtools/client/webconsole/utils").Utils;
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "VARIABLES_SORTING_ENABLED", () =>
|
||||
Services.prefs.getBoolPref("devtools.debugger.ui.variables-sorting-enabled")
|
||||
);
|
||||
@ -562,7 +554,7 @@ VariablesViewController.prototype = {
|
||||
}
|
||||
|
||||
// If the source is a long string then show the arrow.
|
||||
if (WebConsoleUtils.isActorGrip(aSource) && aSource.type == "longString") {
|
||||
if (isActorGrip(aSource) && aSource.type == "longString") {
|
||||
aTarget.showArrow();
|
||||
}
|
||||
|
||||
@ -577,7 +569,7 @@ VariablesViewController.prototype = {
|
||||
|
||||
// Register all the actors that this controller now depends on.
|
||||
for (const grip of [aTarget.value, aTarget.getter, aTarget.setter]) {
|
||||
if (WebConsoleUtils.isActorGrip(grip)) {
|
||||
if (isActorGrip(grip)) {
|
||||
this._actors.add(grip.actor);
|
||||
}
|
||||
}
|
||||
@ -850,3 +842,15 @@ var StackFrameUtils = this.StackFrameUtils = {
|
||||
return label;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given value is a grip with an actor.
|
||||
*
|
||||
* @param mixed grip
|
||||
* Value you want to check if it is a grip with an actor.
|
||||
* @return boolean
|
||||
* True if the given value is a grip with an actor.
|
||||
*/
|
||||
function isActorGrip(grip) {
|
||||
return grip && typeof (grip) == "object" && grip.actor;
|
||||
}
|
||||
|
@ -9,23 +9,9 @@
|
||||
const {Cc, Ci} = require("chrome");
|
||||
const Services = require("Services");
|
||||
|
||||
// Match the function name from the result of toString() or toSource().
|
||||
//
|
||||
// Examples:
|
||||
// (function foobar(a, b) { ...
|
||||
// function foobar2(a) { ...
|
||||
// function() { ...
|
||||
const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
|
||||
|
||||
// Number of terminal entries for the self-xss prevention to go away
|
||||
const CONSOLE_ENTRY_THRESHOLD = 5;
|
||||
|
||||
exports.CONSOLE_WORKER_IDS = [
|
||||
"SharedWorker",
|
||||
"ServiceWorker",
|
||||
"Worker",
|
||||
];
|
||||
|
||||
var WebConsoleUtils = {
|
||||
|
||||
CONSOLE_ENTRY_THRESHOLD,
|
||||
@ -43,50 +29,6 @@ var WebConsoleUtils = {
|
||||
return str;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clone an object.
|
||||
*
|
||||
* @param object object
|
||||
* The object you want cloned.
|
||||
* @param boolean recursive
|
||||
* Tells if you want to dig deeper into the object, to clone
|
||||
* recursively.
|
||||
* @param function [filter]
|
||||
* Optional, filter function, called for every property. Three
|
||||
* arguments are passed: key, value and object. Return true if the
|
||||
* property should be added to the cloned object. Return false to skip
|
||||
* the property.
|
||||
* @return object
|
||||
* The cloned object.
|
||||
*/
|
||||
cloneObject: function(object, recursive, filter) {
|
||||
if (typeof object != "object") {
|
||||
return object;
|
||||
}
|
||||
|
||||
let temp;
|
||||
|
||||
if (Array.isArray(object)) {
|
||||
temp = [];
|
||||
Array.forEach(object, function(value, index) {
|
||||
if (!filter || filter(index, value, object)) {
|
||||
temp.push(recursive ? WebConsoleUtils.cloneObject(value) : value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
temp = {};
|
||||
for (const key in object) {
|
||||
const value = object[key];
|
||||
if (object.hasOwnProperty(key) &&
|
||||
(!filter || filter(key, value, object))) {
|
||||
temp[key] = recursive ? WebConsoleUtils.cloneObject(value) : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return temp;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copies certain style attributes from one element to another.
|
||||
*
|
||||
@ -104,113 +46,6 @@ var WebConsoleUtils = {
|
||||
to.style.fontStyle = style.fontStyle;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if the given request mixes HTTP with HTTPS content.
|
||||
*
|
||||
* @param string request
|
||||
* Location of the requested content.
|
||||
* @param string location
|
||||
* Location of the current page.
|
||||
* @return boolean
|
||||
* True if the content is mixed, false if not.
|
||||
*/
|
||||
isMixedHTTPSRequest: function(request, location) {
|
||||
try {
|
||||
const requestURI = Services.io.newURI(request);
|
||||
const contentURI = Services.io.newURI(location);
|
||||
return (contentURI.scheme == "https" && requestURI.scheme != "https");
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to deduce the name of the provided function.
|
||||
*
|
||||
* @param function function
|
||||
* The function whose name will be returned.
|
||||
* @return string
|
||||
* Function name.
|
||||
*/
|
||||
getFunctionName: function(func) {
|
||||
let name = null;
|
||||
if (func.name) {
|
||||
name = func.name;
|
||||
} else {
|
||||
let desc;
|
||||
try {
|
||||
desc = func.getOwnPropertyDescriptor("displayName");
|
||||
} catch (ex) {
|
||||
// Ignore.
|
||||
}
|
||||
if (desc && typeof desc.value == "string") {
|
||||
name = desc.value;
|
||||
}
|
||||
}
|
||||
if (!name) {
|
||||
try {
|
||||
const str = (func.toString() || func.toSource()) + "";
|
||||
name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
|
||||
} catch (ex) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
return name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the object class name. For example, the |window| object has the Window
|
||||
* class name (based on [object Window]).
|
||||
*
|
||||
* @param object object
|
||||
* The object you want to get the class name for.
|
||||
* @return string
|
||||
* The object class name.
|
||||
*/
|
||||
getObjectClassName: function(object) {
|
||||
if (object === null) {
|
||||
return "null";
|
||||
}
|
||||
if (object === undefined) {
|
||||
return "undefined";
|
||||
}
|
||||
|
||||
const type = typeof object;
|
||||
if (type != "object") {
|
||||
// Grip class names should start with an uppercase letter.
|
||||
return type.charAt(0).toUpperCase() + type.substr(1);
|
||||
}
|
||||
|
||||
let className;
|
||||
|
||||
try {
|
||||
className = ((object + "").match(/^\[object (\S+)\]$/) || [])[1];
|
||||
if (!className) {
|
||||
className = ((object.constructor + "")
|
||||
.match(/^\[object (\S+)\]$/) || [])[1];
|
||||
}
|
||||
if (!className && typeof object.constructor == "function") {
|
||||
className = this.getFunctionName(object.constructor);
|
||||
}
|
||||
} catch (ex) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
return className;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the given value is a grip with an actor.
|
||||
*
|
||||
* @param mixed grip
|
||||
* Value you want to check if it is a grip with an actor.
|
||||
* @return boolean
|
||||
* True if the given value is a grip with an actor.
|
||||
*/
|
||||
isActorGrip: function(grip) {
|
||||
return grip && typeof (grip) == "object" && grip.actor;
|
||||
},
|
||||
|
||||
/**
|
||||
* Value of devtools.selfxss.count preference
|
||||
*
|
||||
@ -234,54 +69,6 @@ var WebConsoleUtils = {
|
||||
Services.prefs.setIntPref("devtools.selfxss.count", newUC);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The inputNode "paste" event handler generator. Helps prevent
|
||||
* self-xss attacks
|
||||
*
|
||||
* @param Element inputField
|
||||
* @param Element notificationBox
|
||||
* @returns A function to be added as a handler to 'paste' and
|
||||
*'drop' events on the input field
|
||||
*/
|
||||
pasteHandlerGen: function(inputField, notificationBox, msg, okstring) {
|
||||
const handler = function(event) {
|
||||
if (WebConsoleUtils.usageCount >= CONSOLE_ENTRY_THRESHOLD) {
|
||||
inputField.removeEventListener("paste", handler);
|
||||
inputField.removeEventListener("drop", handler);
|
||||
return true;
|
||||
}
|
||||
if (notificationBox.getNotificationWithValue("selfxss-notification")) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
const notification = notificationBox.appendNotification(msg,
|
||||
"selfxss-notification", null,
|
||||
notificationBox.PRIORITY_WARNING_HIGH, null,
|
||||
function(eventType) {
|
||||
// Cleanup function if notification is dismissed
|
||||
if (eventType == "removed") {
|
||||
inputField.removeEventListener("keyup", pasteKeyUpHandler);
|
||||
}
|
||||
});
|
||||
|
||||
function pasteKeyUpHandler(event2) {
|
||||
const value = inputField.value || inputField.textContent;
|
||||
if (value.includes(okstring)) {
|
||||
notificationBox.removeNotification(notification);
|
||||
inputField.removeEventListener("keyup", pasteKeyUpHandler);
|
||||
WebConsoleUtils.usageCount = CONSOLE_ENTRY_THRESHOLD;
|
||||
}
|
||||
}
|
||||
inputField.addEventListener("keyup", pasteKeyUpHandler);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
};
|
||||
return handler;
|
||||
},
|
||||
};
|
||||
|
||||
exports.Utils = WebConsoleUtils;
|
||||
|
@ -2652,9 +2652,14 @@ RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia(
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
} else {
|
||||
rv = permManager->TestExactPermissionFromPrincipal(principal, "screen",
|
||||
&audioPerm);
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (!dom::FeaturePolicyUtils::IsFeatureAllowed(
|
||||
doc, NS_LITERAL_STRING("display-capture"))) {
|
||||
audioPerm = nsIPermissionManager::DENY_ACTION;
|
||||
} else {
|
||||
rv = permManager->TestExactPermissionFromPrincipal(
|
||||
principal, "screen", &audioPerm);
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2671,9 +2676,14 @@ RefPtr<MediaManager::StreamPromise> MediaManager::GetUserMedia(
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
} else {
|
||||
rv = permManager->TestExactPermissionFromPrincipal(principal, "screen",
|
||||
&videoPerm);
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (!dom::FeaturePolicyUtils::IsFeatureAllowed(
|
||||
doc, NS_LITERAL_STRING("display-capture"))) {
|
||||
videoPerm = nsIPermissionManager::DENY_ACTION;
|
||||
} else {
|
||||
rv = permManager->TestExactPermissionFromPrincipal(
|
||||
principal, "screen", &videoPerm);
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ static FeatureMap sSupportedFeatures[] = {
|
||||
{"midi", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
|
||||
{"payment", FeaturePolicyUtils::FeaturePolicyValue::eAll},
|
||||
{"document-domain", FeaturePolicyUtils::FeaturePolicyValue::eAll},
|
||||
{"display-capture", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
|
||||
// TODO: not supported yet!!!
|
||||
{"speaker", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
|
||||
{"vr", FeaturePolicyUtils::FeaturePolicyValue::eAll},
|
||||
|
@ -139,17 +139,6 @@ interface nsIEditorSpellCheck : nsISupports
|
||||
*/
|
||||
void setFilterType(in unsigned long filterType);
|
||||
|
||||
/**
|
||||
* Like CheckCurrentWord, checks the word you give it, returning true if it's
|
||||
* misspelled. This is faster than CheckCurrentWord because it does not
|
||||
* compute any suggestions.
|
||||
*
|
||||
* Watch out: this does not clear any suggestions left over from previous
|
||||
* calls to CheckCurrentWord, so there may be suggestions, but they will be
|
||||
* invalid.
|
||||
*/
|
||||
boolean CheckCurrentWordNoSuggest(in AString suggestedWord);
|
||||
|
||||
/**
|
||||
* Update the dictionary in use to be sure it corresponds to what the editor
|
||||
* needs. The update is asynchronous and is not complete until the given
|
||||
|
@ -434,14 +434,6 @@ EditorSpellCheck::CheckCurrentWord(const nsAString& aSuggestedWord,
|
||||
&mSuggestedWordList);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
EditorSpellCheck::CheckCurrentWordNoSuggest(const nsAString& aSuggestedWord,
|
||||
bool* aIsMisspelled) {
|
||||
NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
return mSpellChecker->CheckWord(aSuggestedWord, aIsMisspelled, nullptr);
|
||||
}
|
||||
|
||||
RefPtr<CheckWordPromise> EditorSpellCheck::CheckCurrentWordsNoSuggest(
|
||||
const nsTArray<nsString>& aSuggestedWords) {
|
||||
if (NS_WARN_IF(!mSpellChecker)) {
|
||||
|
@ -12,7 +12,6 @@ sync protocol PRemoteSpellcheckEngine {
|
||||
parent:
|
||||
async __delete__();
|
||||
|
||||
sync Check(nsString aWord) returns (bool aIsMisspelled);
|
||||
async CheckAsync(nsString[] aWord) returns (bool[] aIsMisspelled);
|
||||
|
||||
sync CheckAndSuggest(nsString aWord) returns (bool aIsMisspelled, nsString[] aSuggestions);
|
||||
|
@ -36,15 +36,6 @@ mozilla::ipc::IPCResult RemoteSpellcheckEngineParent::RecvSetDictionaryFromList(
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult RemoteSpellcheckEngineParent::RecvCheck(
|
||||
const nsString& aWord, bool* aIsMisspelled) {
|
||||
nsresult rv = mSpellChecker->CheckWord(aWord, aIsMisspelled, nullptr);
|
||||
|
||||
// If CheckWord failed, we can't tell whether the word is correctly spelled.
|
||||
if (NS_FAILED(rv)) *aIsMisspelled = false;
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult RemoteSpellcheckEngineParent::RecvCheckAsync(
|
||||
nsTArray<nsString>&& aWords, CheckAsyncResolver&& aResolve) {
|
||||
nsTArray<bool> misspells;
|
||||
|
@ -26,9 +26,6 @@ class RemoteSpellcheckEngineParent : public PRemoteSpellcheckEngineParent {
|
||||
virtual mozilla::ipc::IPCResult RecvSetDictionaryFromList(
|
||||
nsTArray<nsString>&& aList, SetDictionaryFromListResolver&& aResolve);
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvCheck(const nsString& aWord,
|
||||
bool* aIsMisspelled);
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvCheckAsync(nsTArray<nsString>&& aWord,
|
||||
CheckAsyncResolver&& aResolve);
|
||||
|
||||
|
@ -150,15 +150,12 @@ nsresult mozSpellChecker::CheckWord(const nsAString &aWord, bool *aIsMisspelled,
|
||||
bool correct;
|
||||
|
||||
if (XRE_IsContentProcess()) {
|
||||
nsString wordwrapped = nsString(aWord);
|
||||
bool rv;
|
||||
if (aSuggestions) {
|
||||
rv = mEngine->SendCheckAndSuggest(wordwrapped, aIsMisspelled,
|
||||
aSuggestions);
|
||||
} else {
|
||||
rv = mEngine->SendCheck(wordwrapped, aIsMisspelled);
|
||||
MOZ_ASSERT(aSuggestions, "Use CheckWords if content process");
|
||||
if (!mEngine->SendCheckAndSuggest(nsString(aWord), aIsMisspelled,
|
||||
aSuggestions)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
return rv ? NS_OK : NS_ERROR_NOT_AVAILABLE;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!mSpellCheckingEngine) {
|
||||
|
@ -60,7 +60,8 @@ class mozSpellChecker final {
|
||||
* @param aIsMisspelled will be set to true if the word is misspelled.
|
||||
* @param aSuggestions is an array of nsStrings which represent the
|
||||
* suggested replacements for the misspelled word. The array will be empty
|
||||
* if there aren't any suggestions.
|
||||
* in chrome process if there aren't any suggestions. If suggestions is
|
||||
* unnecessary, use CheckWords of async version.
|
||||
*/
|
||||
nsresult CheckWord(const nsAString& aWord, bool* aIsMisspelled,
|
||||
nsTArray<nsString>* aSuggestions);
|
||||
|
@ -433,7 +433,7 @@ static const FeatureInfo sFeatureInfoArr[] = {
|
||||
GLContext::Extensions_End}},
|
||||
{"texture_float_linear",
|
||||
GLVersion::GL3_1,
|
||||
GLESVersion::ES3,
|
||||
GLESVersion::NONE,
|
||||
GLContext::Extension_None,
|
||||
{GLContext::ARB_texture_float, GLContext::OES_texture_float_linear,
|
||||
GLContext::Extensions_End}},
|
||||
|
@ -962,7 +962,7 @@ impl AlphaBatchBuilder {
|
||||
PrimitiveInstanceKind::Picture { pic_index, .. } => {
|
||||
let picture = &ctx.prim_store.pictures[pic_index.0];
|
||||
let non_segmented_blend_mode = BlendMode::PremultipliedAlpha;
|
||||
let prim_cache_address = gpu_cache.get_address(&picture.gpu_location);
|
||||
let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_image_handle);
|
||||
|
||||
let prim_header = PrimitiveHeader {
|
||||
local_rect: picture.local_rect,
|
||||
@ -1906,7 +1906,7 @@ impl AlphaBatchBuilder {
|
||||
image_data.alpha_type,
|
||||
get_shader_opacity(opacity_binding),
|
||||
) {
|
||||
let prim_cache_address = gpu_cache.get_address(&tile.handle);
|
||||
let prim_cache_address = gpu_cache.get_address(&ctx.globals.default_image_handle);
|
||||
let prim_header = PrimitiveHeader {
|
||||
specific_prim_address: prim_cache_address,
|
||||
local_rect: tile.local_rect,
|
||||
|
@ -3,12 +3,12 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use api::{ColorF, DeviceIntPoint, DevicePixelScale, LayoutPixel, PicturePixel, RasterPixel};
|
||||
use api::{DeviceIntRect, DeviceIntSize, DocumentLayer, FontRenderMode, DebugFlags};
|
||||
use api::{DeviceIntRect, DeviceIntSize, DocumentLayer, FontRenderMode, DebugFlags, PremultipliedColorF};
|
||||
use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, RasterSpace, WorldPoint, WorldRect, WorldPixel};
|
||||
use clip::{ClipDataStore, ClipStore, ClipChainStack};
|
||||
use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
|
||||
use display_list_flattener::{DisplayListFlattener};
|
||||
use gpu_cache::GpuCache;
|
||||
use gpu_cache::{GpuCache, GpuCacheHandle};
|
||||
use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
|
||||
use hit_test::{HitTester, HitTestingRun};
|
||||
use internal_types::{FastHashMap, PlaneSplitter};
|
||||
@ -57,6 +57,40 @@ pub struct FrameBuilderConfig {
|
||||
pub testing: bool,
|
||||
}
|
||||
|
||||
/// A set of common / global resources that are retained between
|
||||
/// new display lists, such that any GPU cache handles can be
|
||||
/// persisted even when a new display list arrives.
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
pub struct FrameGlobalResources {
|
||||
/// The image shader block for the most common / default
|
||||
/// set of image parameters (color white, stretch == rect.size).
|
||||
pub default_image_handle: GpuCacheHandle,
|
||||
}
|
||||
|
||||
impl FrameGlobalResources {
|
||||
pub fn empty() -> Self {
|
||||
FrameGlobalResources {
|
||||
default_image_handle: GpuCacheHandle::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
gpu_cache: &mut GpuCache,
|
||||
) {
|
||||
if let Some(mut request) = gpu_cache.request(&mut self.default_image_handle) {
|
||||
request.push(PremultipliedColorF::WHITE);
|
||||
request.push(PremultipliedColorF::WHITE);
|
||||
request.push([
|
||||
-1.0, // -ve means use prim rect for stretch size
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder structure for `tiling::Frame`
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
pub struct FrameBuilder {
|
||||
@ -72,6 +106,7 @@ pub struct FrameBuilder {
|
||||
#[cfg_attr(feature = "capture", serde(skip))] //TODO
|
||||
pub hit_testing_runs: Vec<HitTestingRun>,
|
||||
pub config: FrameBuilderConfig,
|
||||
pub globals: FrameGlobalResources,
|
||||
}
|
||||
|
||||
pub struct FrameVisibilityContext<'a> {
|
||||
@ -193,6 +228,7 @@ impl FrameBuilder {
|
||||
background_color: None,
|
||||
root_pic_index: PictureIndex(0),
|
||||
pending_retained_tiles: RetainedTiles::new(),
|
||||
globals: FrameGlobalResources::empty(),
|
||||
config: FrameBuilderConfig {
|
||||
default_font_render_mode: FontRenderMode::Mono,
|
||||
dual_source_blending_is_enabled: true,
|
||||
@ -207,12 +243,14 @@ impl FrameBuilder {
|
||||
/// Provide any cached surface tiles from the previous frame builder
|
||||
/// to a new frame builder. These will be consumed or dropped the
|
||||
/// first time a new frame builder creates a frame.
|
||||
pub fn set_retained_tiles(
|
||||
pub fn set_retained_resources(
|
||||
&mut self,
|
||||
retained_tiles: RetainedTiles,
|
||||
globals: FrameGlobalResources,
|
||||
) {
|
||||
debug_assert!(self.pending_retained_tiles.tiles.is_empty());
|
||||
self.pending_retained_tiles = retained_tiles;
|
||||
self.globals = globals;
|
||||
}
|
||||
|
||||
pub fn with_display_list_flattener(
|
||||
@ -231,6 +269,7 @@ impl FrameBuilder {
|
||||
window_size,
|
||||
pending_retained_tiles: RetainedTiles::new(),
|
||||
config: flattener.config,
|
||||
globals: FrameGlobalResources::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,7 +279,7 @@ impl FrameBuilder {
|
||||
self,
|
||||
retained_tiles: &mut RetainedTiles,
|
||||
clip_scroll_tree: &ClipScrollTree,
|
||||
) {
|
||||
) -> FrameGlobalResources {
|
||||
self.prim_store.destroy(
|
||||
retained_tiles,
|
||||
clip_scroll_tree,
|
||||
@ -255,6 +294,8 @@ impl FrameBuilder {
|
||||
// avoid this, if there are still pending tiles, include them in
|
||||
// the retained tiles passed to the next frame builder.
|
||||
retained_tiles.merge(self.pending_retained_tiles);
|
||||
|
||||
self.globals
|
||||
}
|
||||
|
||||
/// Compute the contribution (bounding rectangles, and resources) of layers and their
|
||||
@ -474,6 +515,8 @@ impl FrameBuilder {
|
||||
resource_cache.begin_frame(stamp);
|
||||
gpu_cache.begin_frame(stamp);
|
||||
|
||||
self.globals.update(gpu_cache);
|
||||
|
||||
let mut transform_palette = TransformPalette::new();
|
||||
clip_scroll_tree.update_tree(
|
||||
pan,
|
||||
@ -557,6 +600,7 @@ impl FrameBuilder {
|
||||
surfaces: &surfaces,
|
||||
scratch,
|
||||
screen_world_rect,
|
||||
globals: &self.globals,
|
||||
};
|
||||
|
||||
pass.build(
|
||||
|
@ -1524,18 +1524,20 @@ impl TileCache {
|
||||
// every frame, which is wasteful.
|
||||
if tile.same_frames >= FRAMES_BEFORE_PICTURE_CACHING {
|
||||
// Ensure that this texture is allocated.
|
||||
resource_cache.texture_cache.update(
|
||||
&mut tile.handle,
|
||||
descriptor,
|
||||
TextureFilter::Linear,
|
||||
None,
|
||||
[0.0; 3],
|
||||
DirtyRect::All,
|
||||
gpu_cache,
|
||||
None,
|
||||
UvRectKind::Rect,
|
||||
Eviction::Eager,
|
||||
);
|
||||
if !resource_cache.texture_cache.is_allocated(&tile.handle) {
|
||||
resource_cache.texture_cache.update(
|
||||
&mut tile.handle,
|
||||
descriptor,
|
||||
TextureFilter::Linear,
|
||||
None,
|
||||
[0.0; 3],
|
||||
DirtyRect::All,
|
||||
gpu_cache,
|
||||
None,
|
||||
UvRectKind::Rect,
|
||||
Eviction::Eager,
|
||||
);
|
||||
}
|
||||
|
||||
let cache_item = resource_cache
|
||||
.get_texture_cache_item(&tile.handle);
|
||||
@ -2191,8 +2193,6 @@ pub struct PicturePrimitive {
|
||||
/// Local clip rect for this picture.
|
||||
pub local_clip_rect: LayoutRect,
|
||||
|
||||
pub gpu_location: GpuCacheHandle,
|
||||
|
||||
/// If Some(..) the tile cache that is associated with this picture.
|
||||
#[cfg_attr(feature = "capture", serde(skip))] //TODO
|
||||
pub tile_cache: Option<TileCache>,
|
||||
@ -2306,7 +2306,6 @@ impl PicturePrimitive {
|
||||
spatial_node_index,
|
||||
local_rect: LayoutRect::zero(),
|
||||
local_clip_rect,
|
||||
gpu_location: GpuCacheHandle::new(),
|
||||
tile_cache,
|
||||
options,
|
||||
}
|
||||
@ -2800,7 +2799,6 @@ impl PicturePrimitive {
|
||||
// stretch size from the segment rect in the shaders, we can
|
||||
// remove this invalidation here completely.
|
||||
if self.local_rect != surface_rect {
|
||||
gpu_cache.invalidate(&self.gpu_location);
|
||||
if let PictureCompositeMode::Filter(FilterOp::DropShadow(..)) = raster_config.composite_mode {
|
||||
gpu_cache.invalidate(&self.extra_gpu_data_handle);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use api::{
|
||||
use api::ImageKey as ApiImageKey;
|
||||
use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible};
|
||||
use frame_builder::FrameBuildingState;
|
||||
use gpu_cache::{GpuCacheHandle, GpuDataRequest};
|
||||
use gpu_cache::{GpuDataRequest};
|
||||
use intern::{Internable, InternDebug};
|
||||
use intern_types;
|
||||
use prim_store::{
|
||||
@ -31,7 +31,6 @@ use util::pack_as_float;
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct VisibleImageTile {
|
||||
pub tile_offset: TileOffset,
|
||||
pub handle: GpuCacheHandle,
|
||||
pub edge_flags: EdgeAaSegmentMask,
|
||||
pub local_rect: LayoutRect,
|
||||
pub local_clip_rect: LayoutRect,
|
||||
|
@ -2350,17 +2350,6 @@ impl PrimitiveStore {
|
||||
} else {
|
||||
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
|
||||
}
|
||||
|
||||
if let Some(mut request) = frame_state.gpu_cache.request(&mut pic.gpu_location) {
|
||||
request.push(PremultipliedColorF::WHITE);
|
||||
request.push(PremultipliedColorF::WHITE);
|
||||
request.push([
|
||||
-1.0, // -ve means use prim rect for stretch size
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
PrimitiveInstanceKind::TextRun { .. } |
|
||||
PrimitiveInstanceKind::Clear { .. } |
|
||||
@ -2769,16 +2758,8 @@ impl PrimitiveStore {
|
||||
frame_state.gpu_cache,
|
||||
);
|
||||
|
||||
let mut handle = GpuCacheHandle::new();
|
||||
if let Some(mut request) = frame_state.gpu_cache.request(&mut handle) {
|
||||
request.push(PremultipliedColorF::WHITE);
|
||||
request.push(PremultipliedColorF::WHITE);
|
||||
request.push([tile.rect.size.width, tile.rect.size.height, 0.0, 0.0]);
|
||||
}
|
||||
|
||||
image_instance.visible_tiles.push(VisibleImageTile {
|
||||
tile_offset: tile.offset,
|
||||
handle,
|
||||
edge_flags: tile.edge_flags & edge_flags,
|
||||
local_rect: tile.rect,
|
||||
local_clip_rect: tight_clip_rect,
|
||||
|
@ -611,15 +611,18 @@ impl Document {
|
||||
// surface tiles, that can be provided to the next frame builder.
|
||||
let mut retained_tiles = RetainedTiles::new();
|
||||
if let Some(frame_builder) = self.frame_builder.take() {
|
||||
frame_builder.destroy(
|
||||
let globals = frame_builder.destroy(
|
||||
&mut retained_tiles,
|
||||
&self.clip_scroll_tree,
|
||||
);
|
||||
}
|
||||
|
||||
// Provide any cached tiles from the previous frame builder to
|
||||
// the newly built one.
|
||||
built_scene.frame_builder.set_retained_tiles(retained_tiles);
|
||||
// Provide any cached tiles from the previous frame builder to
|
||||
// the newly built one.
|
||||
built_scene.frame_builder.set_retained_resources(
|
||||
retained_tiles,
|
||||
globals,
|
||||
);
|
||||
}
|
||||
|
||||
self.frame_builder = Some(built_scene.frame_builder);
|
||||
|
||||
|
@ -12,6 +12,7 @@ use debug_render::DebugItem;
|
||||
use device::{Texture};
|
||||
#[cfg(feature = "pathfinder")]
|
||||
use euclid::{TypedPoint2D, TypedVector2D};
|
||||
use frame_builder::FrameGlobalResources;
|
||||
use gpu_cache::{GpuCache};
|
||||
use gpu_types::{BorderInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
|
||||
use gpu_types::{TransformData, TransformPalette, ZBufferIdGenerator};
|
||||
@ -58,6 +59,7 @@ pub struct RenderTargetContext<'a, 'rc> {
|
||||
pub surfaces: &'a [SurfaceInfo],
|
||||
pub scratch: &'a PrimitiveScratchBuffer,
|
||||
pub screen_world_rect: WorldRect,
|
||||
pub globals: &'a FrameGlobalResources,
|
||||
}
|
||||
|
||||
/// Represents a number of rendering operations on a surface.
|
||||
|
@ -932,8 +932,6 @@ description = See corresponding comment in PBackgroundLSSnapshot.ipdl
|
||||
description = See corresponding comment in PBackgroundLSSnapshot.ipdl
|
||||
[PBackgroundLSSnapshot::Ping]
|
||||
description = See corresponding comment in PBackgroundLSSnapshot.ipdl
|
||||
[PRemoteSpellcheckEngine::Check]
|
||||
description =
|
||||
[PRemoteSpellcheckEngine::CheckAndSuggest]
|
||||
description =
|
||||
[PRemoteSpellcheckEngine::SetDictionary]
|
||||
|
@ -7408,9 +7408,7 @@ bool GCRuntime::shouldCollectNurseryForSlice(bool nonincrementalByAPI,
|
||||
return true;
|
||||
case State::Mark:
|
||||
return (nonincrementalByAPI || budget.isUnlimited() || lastMarkSlice ||
|
||||
nursery().minorGCRequested() ||
|
||||
nursery().freeSpace() <
|
||||
tunables.nurseryFreeThresholdForIdleCollection() ||
|
||||
nursery().shouldCollect() ||
|
||||
hasIncrementalTwoSliceZealMode());
|
||||
case State::Finish:
|
||||
return false;
|
||||
|
@ -697,7 +697,7 @@ inline void js::Nursery::endProfile(ProfileKey key) {
|
||||
totalDurations_[key] += profileDurations_[key];
|
||||
}
|
||||
|
||||
bool js::Nursery::needIdleTimeCollection() const {
|
||||
bool js::Nursery::shouldCollect() const {
|
||||
uint32_t threshold = tunables().nurseryFreeThresholdForIdleCollection();
|
||||
return minorGCRequested() || freeSpace() < threshold;
|
||||
}
|
||||
|
@ -362,7 +362,7 @@ class Nursery {
|
||||
minorGCTriggerReason_ = JS::GCReason::NO_REASON;
|
||||
}
|
||||
|
||||
bool needIdleTimeCollection() const;
|
||||
bool shouldCollect() const;
|
||||
|
||||
bool enableProfiling() const { return enableProfiling_; }
|
||||
|
||||
|
@ -1159,12 +1159,12 @@ JS_PUBLIC_API void JS_RemoveExtraGCRootsTracer(JSContext* cx,
|
||||
|
||||
JS_PUBLIC_API bool JS::IsIdleGCTaskNeeded(JSRuntime* rt) {
|
||||
// Currently, we only collect nursery during idle time.
|
||||
return rt->gc.nursery().needIdleTimeCollection();
|
||||
return rt->gc.nursery().shouldCollect();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API void JS::RunIdleTimeGCTask(JSRuntime* rt) {
|
||||
gc::GCRuntime& gc = rt->gc;
|
||||
if (gc.nursery().needIdleTimeCollection()) {
|
||||
if (gc.nursery().shouldCollect()) {
|
||||
gc.minorGC(JS::GCReason::IDLE_TIME_COLLECTION);
|
||||
}
|
||||
}
|
||||
|
10
js/src/tests/non262/TypedArray/bug1526838.js
Normal file
10
js/src/tests/non262/TypedArray/bug1526838.js
Normal file
@ -0,0 +1,10 @@
|
||||
// |reftest| skip-if(!this.hasOwnProperty("BigInt"))
|
||||
|
||||
const testArray = [1n];
|
||||
for (const constructor of anyTypedArrayConstructors) {
|
||||
assertThrows(() => new constructor(testArray), TypeError);
|
||||
assertThrows(() => new constructor(testArray.values()), TypeError);
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
@ -614,7 +614,7 @@ class ElementSpecific {
|
||||
}
|
||||
|
||||
double d;
|
||||
MOZ_ASSERT(v.isString() || v.isObject() || v.isSymbol());
|
||||
MOZ_ASSERT(v.isString() || v.isObject() || v.isSymbol() || IF_BIGINT(v.isBigInt(), false));
|
||||
if (!(v.isString() ? StringToNumber(cx, v.toString(), &d)
|
||||
: ToNumber(cx, v, &d))) {
|
||||
return false;
|
||||
|
@ -6567,30 +6567,9 @@ nsresult PresShell::EventHandler::HandleEvent(nsIFrame* aFrame,
|
||||
|
||||
if (pointerCapturingContent) {
|
||||
rootFrameToHandleEvent = pointerCapturingContent->GetPrimaryFrame();
|
||||
|
||||
if (!rootFrameToHandleEvent) {
|
||||
RefPtr<PresShell> shell =
|
||||
PresShell::GetShellForEventTarget(nullptr, pointerCapturingContent);
|
||||
if (!shell) {
|
||||
// If we can't process event for the capturing content, release
|
||||
// the capture.
|
||||
PointerEventHandler::ReleaseIfCaptureByDescendant(
|
||||
pointerCapturingContent);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIContent> overrideClickTarget =
|
||||
GetOverrideClickTarget(aGUIEvent, aFrame);
|
||||
|
||||
// Dispatch events to the capturing content even it's frame is
|
||||
// destroyed.
|
||||
PointerEventHandler::DispatchPointerFromMouseOrTouch(
|
||||
shell, nullptr, pointerCapturingContent, aGUIEvent, false,
|
||||
aEventStatus, nullptr);
|
||||
|
||||
return shell->HandleEventWithTarget(
|
||||
aGUIEvent, nullptr, pointerCapturingContent, aEventStatus, true,
|
||||
nullptr, overrideClickTarget);
|
||||
return HandleEventWithPointerCapturingContentWithoutItsFrame(
|
||||
aFrame, aGUIEvent, pointerCapturingContent, aEventStatus);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7448,6 +7427,47 @@ PresShell::EventHandler::ComputeRootFrameToHandleEventWithCapturingContent(
|
||||
: aRootFrameToHandleEvent;
|
||||
}
|
||||
|
||||
nsresult
|
||||
PresShell::EventHandler::HandleEventWithPointerCapturingContentWithoutItsFrame(
|
||||
nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
|
||||
nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus) {
|
||||
MOZ_ASSERT(aGUIEvent);
|
||||
MOZ_ASSERT(aPointerCapturingContent);
|
||||
MOZ_ASSERT(!aPointerCapturingContent->GetPrimaryFrame(),
|
||||
"Handle the event with frame rather than only with the content");
|
||||
MOZ_ASSERT(aEventStatus);
|
||||
|
||||
RefPtr<PresShell> presShellForCapturingContent =
|
||||
PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent);
|
||||
if (!presShellForCapturingContent) {
|
||||
// If we can't process event for the capturing content, release
|
||||
// the capture.
|
||||
PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingContent);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIContent> overrideClickTarget =
|
||||
GetOverrideClickTarget(aGUIEvent, aFrameForPresShell);
|
||||
|
||||
// Dispatch events to the capturing content even it's frame is
|
||||
// destroyed.
|
||||
PointerEventHandler::DispatchPointerFromMouseOrTouch(
|
||||
presShellForCapturingContent, nullptr, aPointerCapturingContent,
|
||||
aGUIEvent, false, aEventStatus, nullptr);
|
||||
|
||||
if (presShellForCapturingContent == mPresShell) {
|
||||
return HandleEventWithTarget(aGUIEvent, nullptr, aPointerCapturingContent,
|
||||
aEventStatus, true, nullptr,
|
||||
overrideClickTarget);
|
||||
}
|
||||
|
||||
EventHandler eventHandlerForCapturingContent(
|
||||
std::move(presShellForCapturingContent));
|
||||
return eventHandlerForCapturingContent.HandleEventWithTarget(
|
||||
aGUIEvent, nullptr, aPointerCapturingContent, aEventStatus, true, nullptr,
|
||||
overrideClickTarget);
|
||||
}
|
||||
|
||||
Document* PresShell::GetPrimaryContentDocument() {
|
||||
nsPresContext* context = GetPresContext();
|
||||
if (!context || !context->IsRoot()) {
|
||||
|
@ -500,6 +500,8 @@ class PresShell final : public nsIPresShell,
|
||||
EventHandler() = delete;
|
||||
EventHandler(const EventHandler& aOther) = delete;
|
||||
explicit EventHandler(PresShell& aPresShell) : mPresShell(aPresShell) {}
|
||||
explicit EventHandler(RefPtr<PresShell>&& aPresShell)
|
||||
: mPresShell(aPresShell.forget()) {}
|
||||
|
||||
/**
|
||||
* HandleEvent() may dispatch aGUIEvent. This may redirect the event to
|
||||
@ -796,6 +798,25 @@ class PresShell final : public nsIPresShell,
|
||||
nsIFrame* aRootFrameToHandleEvent, nsIContent* aCapturingContent,
|
||||
bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted);
|
||||
|
||||
/**
|
||||
* HandleEventWithPointerCapturingContentWithoutItsFrame() handles
|
||||
* aGUIEvent with aPointerCapturingContent when it does not have primary
|
||||
* frame.
|
||||
*
|
||||
* @param aFrameForPresShell The frame for mPresShell. Typically,
|
||||
* aFrame of HandleEvent().
|
||||
* @param aGUIEvent The handling event.
|
||||
* @param aPointerCapturingContent Current pointer capturing content.
|
||||
* Must not be nullptr.
|
||||
* @param aEventStatus [in/out] The event status of aGUIEvent.
|
||||
* @return Basically, result of
|
||||
* HandeEventWithTraget().
|
||||
*/
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
nsresult HandleEventWithPointerCapturingContentWithoutItsFrame(
|
||||
nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
|
||||
nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus);
|
||||
|
||||
/**
|
||||
* XXX Needs better name.
|
||||
* HandleEventInternal() dispatches aEvent into the DOM tree and
|
||||
|
@ -348,8 +348,32 @@ public final class GeckoProfile {
|
||||
mMozillaDir = GeckoProfileDirectories.getMozillaDirectory(context);
|
||||
|
||||
mProfileDir = profileDir;
|
||||
if (profileDir != null && !profileDir.isDirectory()) {
|
||||
throw new IllegalArgumentException("Profile directory must exist if specified.");
|
||||
if (profileDir != null) {
|
||||
if (!profileDir.isDirectory()) {
|
||||
throw new IllegalArgumentException("Profile directory must exist if specified: " +
|
||||
profileDir.getPath());
|
||||
}
|
||||
|
||||
// Ensure that we can write to the profile directory.
|
||||
//
|
||||
// We would use `writeFile`, but that function just logs exceptions; we need them to
|
||||
// provide useful feedback.
|
||||
FileWriter fileWriter = null;
|
||||
try {
|
||||
fileWriter = new FileWriter(new File(profileDir, ".can-write-sentinel"), false);
|
||||
fileWriter.write(0);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Profile directory must be writable if specified: " +
|
||||
profileDir.getPath(), e);
|
||||
} finally {
|
||||
try {
|
||||
if (fileWriter != null) {
|
||||
fileWriter.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(LOGTAG, "Error closing .can-write-sentinel; ignoring", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,6 @@ class ArchlinuxBootstrapper(NodeInstall, StyloInstall,
|
||||
# For downloading the Android SDK and NDK.
|
||||
'wget',
|
||||
# See comment about 32 bit binaries and multilib below.
|
||||
'multilib/lib32-libstdc++5',
|
||||
'multilib/lib32-ncurses',
|
||||
'multilib/lib32-readline',
|
||||
'multilib/lib32-zlib',
|
||||
|
@ -0,0 +1,7 @@
|
||||
[feature-policy.https.sub.html]
|
||||
[Default "display-capture" feature policy ["self"\] disallows cross-origin iframes.]
|
||||
expected: FAIL
|
||||
|
||||
[feature-policy.https.html]
|
||||
[Default "display-capture" feature policy ["self"\] disallows cross-origin iframes.]
|
||||
expected: FAIL
|
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<body>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=/common/get-host-info.sub.js></script>
|
||||
<script src=/feature-policy/resources/featurepolicy.js></script>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
async function gDM({audio, video}) {
|
||||
let stream;
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getDisplayMedia({audio, video});
|
||||
if (stream.getVideoTracks().length == 0) {
|
||||
throw {name: `requested video track must be present with ` +
|
||||
`audio ${audio} and video ${video}, or fail`};
|
||||
}
|
||||
} finally {
|
||||
if (stream) {
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cross_domain = get_host_info().HTTPS_REMOTE_ORIGIN;
|
||||
run_all_fp_tests_allow_self(
|
||||
cross_domain,
|
||||
'display-capture',
|
||||
'NotAllowedError',
|
||||
async () => {
|
||||
await gDM({video: true});
|
||||
await gDM({audio: true, video: true});
|
||||
await gDM({audio: true});
|
||||
}
|
||||
);
|
||||
</script>
|
||||
</body>
|
122
toolkit/actors/PictureInPictureChild.jsm
Normal file
122
toolkit/actors/PictureInPictureChild.jsm
Normal file
@ -0,0 +1,122 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* 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";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["PictureInPictureChild"];
|
||||
|
||||
const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
|
||||
|
||||
var gWeakVideo = null;
|
||||
|
||||
class PictureInPictureChild extends ActorChild {
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "MozTogglePictureInPicture": {
|
||||
this.togglePictureInPicture(event.target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
togglePictureInPicture(video) {
|
||||
if (this.inPictureInPicture(video)) {
|
||||
this.closePictureInPicture(video);
|
||||
} else {
|
||||
this.requestPictureInPicture(video);
|
||||
}
|
||||
}
|
||||
|
||||
inPictureInPicture(video) {
|
||||
return gWeakVideo && gWeakVideo.get() === video;
|
||||
}
|
||||
|
||||
closePictureInPicture() {
|
||||
this.mm.sendAsyncMessage("PictureInPicture:Close", {
|
||||
browingContextId: this.docShell.browsingContext.id,
|
||||
});
|
||||
}
|
||||
|
||||
requestPictureInPicture(video) {
|
||||
gWeakVideo = Cu.getWeakReference(video);
|
||||
this.mm.sendAsyncMessage("PictureInPicture:Request", {
|
||||
videoHeight: video.videoHeight,
|
||||
videoWidth: video.videoWidth,
|
||||
});
|
||||
}
|
||||
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "PictureInPicture:SetupPlayer": {
|
||||
this.setupPlayer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async setupPlayer() {
|
||||
if (!gWeakVideo) {
|
||||
this.closePictureInPicture();
|
||||
}
|
||||
|
||||
let originatingVideo = gWeakVideo.get();
|
||||
if (!originatingVideo) {
|
||||
this.closePictureInPicture();
|
||||
}
|
||||
|
||||
let webProgress = this.mm
|
||||
.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
if (webProgress.isLoadingDocument) {
|
||||
await new Promise(resolve => {
|
||||
this.mm.addEventListener("load", resolve, {
|
||||
once: true,
|
||||
mozSystemGroup: true,
|
||||
capture: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let stream = originatingVideo.mozCaptureStream();
|
||||
|
||||
let doc = this.content.document;
|
||||
let playerVideo = doc.createElement("video");
|
||||
playerVideo.srcObject = stream;
|
||||
playerVideo.removeAttribute("controls");
|
||||
playerVideo.setAttribute("autoplay", "true");
|
||||
|
||||
// Mute the video and rely on the originating video's audio playback.
|
||||
// This way, we sidestep the AutoplayPolicy blocking stuff.
|
||||
playerVideo.muted = true;
|
||||
|
||||
// Force the player video to assume maximum height and width of the
|
||||
// containing window
|
||||
playerVideo.style.height = "100vh";
|
||||
playerVideo.style.width = "100vw";
|
||||
|
||||
// And now try to get rid of as much surrounding whitespace as possible.
|
||||
playerVideo.style.margin = "0";
|
||||
doc.body.style.overflow = "hidden";
|
||||
doc.body.style.margin = "0";
|
||||
|
||||
playerVideo.play();
|
||||
|
||||
// A little hack to make the current frame show up in the player
|
||||
if (originatingVideo.paused) {
|
||||
await originatingVideo.play();
|
||||
await originatingVideo.pause();
|
||||
}
|
||||
|
||||
doc.body.appendChild(playerVideo);
|
||||
|
||||
let originatingWindow = originatingVideo.ownerGlobal;
|
||||
originatingWindow.addEventListener("unload", (e) => {
|
||||
this.closePictureInPicture(originatingVideo);
|
||||
}, { once: true });
|
||||
|
||||
this.content.addEventListener("unload", () => {
|
||||
gWeakVideo = null;
|
||||
}, { once: true });
|
||||
}
|
||||
}
|
@ -40,3 +40,8 @@ FINAL_TARGET_FILES.actors += [
|
||||
'WebNavigationChild.jsm',
|
||||
'ZoomChild.jsm',
|
||||
]
|
||||
|
||||
if CONFIG['NIGHTLY_BUILD']:
|
||||
FINAL_TARGET_FILES.actors += [
|
||||
'PictureInPictureChild.jsm',
|
||||
]
|
||||
|
@ -87,6 +87,9 @@ if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
|
||||
if CONFIG['NS_PRINTING']:
|
||||
DIRS += ['printing']
|
||||
|
||||
if CONFIG['NIGHTLY_BUILD']:
|
||||
DIRS += ['pictureinpicture']
|
||||
|
||||
if CONFIG['BUILD_CTYPES']:
|
||||
DIRS += ['ctypes']
|
||||
|
||||
|
167
toolkit/components/pictureinpicture/PictureInPicture.jsm
Normal file
167
toolkit/components/pictureinpicture/PictureInPicture.jsm
Normal file
@ -0,0 +1,167 @@
|
||||
/* 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";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["PictureInPicture"];
|
||||
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const PLAYER_URI = "chrome://global/content/pictureinpicture/player.xhtml";
|
||||
const PLAYER_FEATURES = `chrome,titlebar=no,alwaysontop,resizable`;
|
||||
const WINDOW_TYPE = "Toolkit:PictureInPicture";
|
||||
|
||||
/**
|
||||
* This module is responsible for creating a Picture in Picture window to host
|
||||
* a clone of a video element running in web content.
|
||||
*/
|
||||
|
||||
var PictureInPicture = {
|
||||
// Listeners are added in nsBrowserGlue.js lazily
|
||||
receiveMessage(aMessage) {
|
||||
let browser = aMessage.target;
|
||||
|
||||
switch (aMessage.name) {
|
||||
case "PictureInPicture:Request": {
|
||||
let videoData = aMessage.data;
|
||||
this.handlePictureInPictureRequest(browser, videoData);
|
||||
break;
|
||||
}
|
||||
case "PictureInPicture:Close": {
|
||||
/**
|
||||
* Content has requested that its Picture in Picture window go away.
|
||||
*/
|
||||
this.closePipWindow();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Find and close any pre-existing Picture in Picture windows.
|
||||
*/
|
||||
closePipWindow() {
|
||||
// This uses an enumerator, but there really should only be one of
|
||||
// these things.
|
||||
for (let win of Services.wm.getEnumerator(WINDOW_TYPE)) {
|
||||
if (win.closed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
win.close();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A request has come up from content to open a Picture in Picture
|
||||
* window.
|
||||
*
|
||||
* @param browser (xul:browser)
|
||||
* The browser that is requesting the Picture in Picture window.
|
||||
*
|
||||
* @param videoData (object)
|
||||
* An object containing the following properties:
|
||||
*
|
||||
* videoHeight (int):
|
||||
* The preferred height of the video.
|
||||
*
|
||||
* videoWidth (int):
|
||||
* The preferred width of the video.
|
||||
*
|
||||
* @returns Promise
|
||||
* Resolves once the Picture in Picture window has been created, and
|
||||
* the player component inside it has finished loading.
|
||||
*/
|
||||
async handlePictureInPictureRequest(browser, videoData) {
|
||||
let parentWin = browser.ownerGlobal;
|
||||
this.closePipWindow();
|
||||
let win = await this.openPipWindow(parentWin, videoData);
|
||||
win.setupPlayer(browser, videoData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Open a Picture in Picture window on the same screen as parentWin,
|
||||
* sized based on the information in videoData.
|
||||
*
|
||||
* @param parentWin (chrome window)
|
||||
* The window hosting the browser that requested the Picture in
|
||||
* Picture window.
|
||||
*
|
||||
* @param videoData (object)
|
||||
* An object containing the following properties:
|
||||
*
|
||||
* videoHeight (int):
|
||||
* The preferred height of the video.
|
||||
*
|
||||
* videoWidth (int):
|
||||
* The preferred width of the video.
|
||||
*
|
||||
* @returns Promise
|
||||
* Resolves once the window has opened and loaded the player component.
|
||||
*/
|
||||
async openPipWindow(parentWin, videoData) {
|
||||
let { videoHeight, videoWidth } = videoData;
|
||||
|
||||
// The Picture in Picture window will open on the same display as the
|
||||
// originating window, and anchor to the bottom right.
|
||||
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
|
||||
.getService(Ci.nsIScreenManager);
|
||||
let screen = screenManager.screenForRect(parentWin.screenX,
|
||||
parentWin.screenY, 1, 1);
|
||||
|
||||
// Now that we have the right screen, let's see how much available
|
||||
// real-estate there is for us to work with.
|
||||
let screenLeft = {}, screenTop = {}, screenWidth = {}, screenHeight = {};
|
||||
screen.GetAvailRectDisplayPix(screenLeft, screenTop, screenWidth,
|
||||
screenHeight);
|
||||
|
||||
// For now, the Picture in Picture window will be a maximum of a quarter
|
||||
// of the screen height, and a third of the screen width.
|
||||
const MAX_HEIGHT = screenHeight.value / 4;
|
||||
const MAX_WIDTH = screenWidth.value / 3;
|
||||
|
||||
let resultWidth = videoWidth;
|
||||
let resultHeight = videoHeight;
|
||||
|
||||
if (videoHeight > MAX_HEIGHT || videoWidth > MAX_WIDTH) {
|
||||
let aspectRatio = videoWidth / videoHeight;
|
||||
// We're bigger than the max - take the largest dimension and clamp
|
||||
// it to the associated max. Recalculate the other dimension to maintain
|
||||
// aspect ratio.
|
||||
if (videoWidth >= videoHeight) {
|
||||
// We're clamping the width, so the height must be adjusted to match
|
||||
// the original aspect ratio. Since aspect ratio is width over height,
|
||||
// that means we need to _divide_ the MAX_WIDTH by the aspect ratio to
|
||||
// calculate the appropriate height.
|
||||
resultWidth = MAX_WIDTH;
|
||||
resultHeight = Math.floor(MAX_WIDTH / aspectRatio);
|
||||
} else {
|
||||
// We're clamping the height, so the width must be adjusted to match
|
||||
// the original aspect ratio. Since aspect ratio is width over height,
|
||||
// this means we need to _multiply_ the MAX_HEIGHT by the aspect ratio
|
||||
// to calculate the appropriate width.
|
||||
resultHeight = MAX_HEIGHT;
|
||||
resultWidth = Math.floor(MAX_HEIGHT * aspectRatio);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have the dimensions of the video, we need to figure out how
|
||||
// to position it in the bottom right corner. Since we know the width of the
|
||||
// available rect, we need to subtract the dimensions of the window we're
|
||||
// opening to get the top left coordinates that openWindow expects.
|
||||
let pipLeft = screenWidth.value - resultWidth;
|
||||
let pipTop = screenHeight.value - resultHeight;
|
||||
let features = `${PLAYER_FEATURES},top=${pipTop},left=${pipLeft},` +
|
||||
`width=${resultWidth},height=${resultHeight}`;
|
||||
|
||||
let pipWindow =
|
||||
Services.ww.openWindow(parentWin, PLAYER_URI, null, features, null);
|
||||
|
||||
return new Promise(resolve => {
|
||||
pipWindow.addEventListener("load", () => {
|
||||
resolve(pipWindow);
|
||||
}, { once: true });
|
||||
});
|
||||
},
|
||||
};
|
30
toolkit/components/pictureinpicture/content/player.js
Normal file
30
toolkit/components/pictureinpicture/content/player.js
Normal file
@ -0,0 +1,30 @@
|
||||
/* 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/. */
|
||||
|
||||
async function setupPlayer(originatingBrowser, videoData) {
|
||||
window.windowUtils.setChromeMargin(0, 0, 0, 0);
|
||||
let holder = document.querySelector(".player-holder");
|
||||
let browser = document.getElementById("browser");
|
||||
browser.remove();
|
||||
|
||||
browser.setAttribute("nodefaultsrc", "true");
|
||||
browser.sameProcessAsFrameLoader = originatingBrowser.frameLoader;
|
||||
holder.appendChild(browser);
|
||||
|
||||
browser.loadURI("about:blank", {
|
||||
triggeringPrincipal: originatingBrowser.contentPrincipal,
|
||||
});
|
||||
|
||||
let mm = browser.frameLoader.messageManager;
|
||||
mm.sendAsyncMessage("PictureInPicture:SetupPlayer");
|
||||
|
||||
// If the content process hosting the video crashes, let's
|
||||
// just close the window for now.
|
||||
browser.addEventListener("oop-browser-crashed", () => {
|
||||
window.close();
|
||||
});
|
||||
|
||||
await window.promiseDocumentFlushed(() => {});
|
||||
browser.style.MozWindowDragging = "drag";
|
||||
}
|
25
toolkit/components/pictureinpicture/content/player.xhtml
Normal file
25
toolkit/components/pictureinpicture/content/player.xhtml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
]>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
windowtype="Toolkit:PictureInPicture">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="chrome://global/skin/pictureinpicture/player.css"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://global/content/pictureinpicture/player.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="player-holder">
|
||||
<xul:browser type="content" primary="true" remote="true" remoteType="web" id="browser"></xul:browser>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
8
toolkit/components/pictureinpicture/jar.mn
Normal file
8
toolkit/components/pictureinpicture/jar.mn
Normal file
@ -0,0 +1,8 @@
|
||||
#filter substitution
|
||||
# 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/.
|
||||
|
||||
toolkit.jar:
|
||||
content/global/pictureinpicture/player.xhtml (content/player.xhtml)
|
||||
content/global/pictureinpicture/player.js (content/player.js)
|
11
toolkit/components/pictureinpicture/moz.build
Normal file
11
toolkit/components/pictureinpicture/moz.build
Normal file
@ -0,0 +1,11 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'PictureInPicture.jsm',
|
||||
]
|
@ -97,6 +97,7 @@ var EXPORTED_SYMBOLS = ["ActorManagerParent"];
|
||||
|
||||
const {ExtensionUtils} = ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
const {DefaultMap} = ExtensionUtils;
|
||||
|
||||
@ -337,6 +338,21 @@ let ACTORS = {
|
||||
},
|
||||
};
|
||||
|
||||
if (AppConstants.NIGHTLY_BUILD) {
|
||||
ACTORS.PictureInPicture = {
|
||||
child: {
|
||||
module: "resource://gre/actors/PictureInPictureChild.jsm",
|
||||
events: {
|
||||
"MozTogglePictureInPicture": {capture: true, wantUntrusted: true},
|
||||
},
|
||||
|
||||
messages: [
|
||||
"PictureInPicture:SetupPlayer",
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
class ActorSet {
|
||||
constructor(group, actorSide) {
|
||||
this.group = group;
|
||||
|
@ -105,3 +105,4 @@ toolkit.jar:
|
||||
skin/classic/global/plugins/contentPluginBlocked.png (../../shared/plugins/contentPluginBlocked.png)
|
||||
skin/classic/global/plugins/contentPluginCrashed.png (../../shared/plugins/contentPluginCrashed.png)
|
||||
skin/classic/global/plugins/contentPluginStripe.png (../../shared/plugins/contentPluginStripe.png)
|
||||
skin/classic/global/pictureinpicture/player.css (../../shared/pictureinpicture/player.css)
|
||||
|
18
toolkit/themes/shared/pictureinpicture/player.css
Normal file
18
toolkit/themes/shared/pictureinpicture/player.css
Normal file
@ -0,0 +1,18 @@
|
||||
/* 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/. */
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.player-holder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
browser {
|
||||
flex: 1;
|
||||
}
|
Loading…
Reference in New Issue
Block a user