Merge mozilla-central to inbound. a=merge CLOSED TREE

This commit is contained in:
Oana Pop Rus 2019-02-12 12:12:06 +02:00
commit c603510fa9
51 changed files with 837 additions and 372 deletions

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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;"

View File

@ -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);

View File

@ -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"],

View File

@ -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) {

View File

@ -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;
}

View File

@ -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.
*

View File

@ -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;
}

View File

@ -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;

View File

@ -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));
}
}
}

View File

@ -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},

View File

@ -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

View File

@ -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)) {

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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}},

View File

@ -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,

View File

@ -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(

View File

@ -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);
}

View File

@ -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,

View File

@ -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,

View File

@ -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);

View File

@ -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.

View File

@ -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]

View File

@ -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;

View File

@ -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;
}

View File

@ -362,7 +362,7 @@ class Nursery {
minorGCTriggerReason_ = JS::GCReason::NO_REASON;
}
bool needIdleTimeCollection() const;
bool shouldCollect() const;
bool enableProfiling() const { return enableProfiling_; }

View File

@ -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);
}
}

View 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);

View File

@ -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;

View File

@ -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()) {

View File

@ -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

View File

@ -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);
}
}
}
}

View File

@ -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',

View File

@ -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

View File

@ -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>

View 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 });
}
}

View File

@ -40,3 +40,8 @@ FINAL_TARGET_FILES.actors += [
'WebNavigationChild.jsm',
'ZoomChild.jsm',
]
if CONFIG['NIGHTLY_BUILD']:
FINAL_TARGET_FILES.actors += [
'PictureInPictureChild.jsm',
]

View File

@ -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']

View 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 });
});
},
};

View 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";
}

View 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>

View 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)

View 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',
]

View File

@ -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;

View File

@ -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)

View 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;
}