зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central. a=merge
This commit is contained in:
Коммит
4365e6ab34
|
@ -5392,6 +5392,12 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "topological-sort"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa7c7f42dea4b1b99439786f5633aeb9c14c1b53f75e282803c2ec2ad545873c"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.1"
|
||||
|
@ -5813,6 +5819,7 @@ dependencies = [
|
|||
"svg_fmt",
|
||||
"swgl",
|
||||
"time",
|
||||
"topological-sort",
|
||||
"tracy-rs",
|
||||
"webrender_api",
|
||||
"webrender_build",
|
||||
|
|
|
@ -304,6 +304,10 @@ nsAccessibilityService::ListenersChanged(nsIArray* aEventChanges) {
|
|||
|
||||
if (document) {
|
||||
LocalAccessible* acc = document->GetAccessible(content);
|
||||
if (!acc && (content == document->GetContent() ||
|
||||
content == document->DocumentNode()->GetRootElement())) {
|
||||
acc = document;
|
||||
}
|
||||
if (!acc && nsCoreUtils::HasClickListener(content)) {
|
||||
// Create an accessible for a inaccessible element having click event
|
||||
// handler.
|
||||
|
|
|
@ -312,7 +312,9 @@ void Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut) {
|
|||
}
|
||||
|
||||
const Accessible* Accessible::ActionAncestor() const {
|
||||
for (Accessible* parent = Parent(); parent && !parent->IsDoc();
|
||||
// We do want to consider a click handler on the document. However, we don't
|
||||
// want to walk outside of this document, so we stop if we see an OuterDoc.
|
||||
for (Accessible* parent = Parent(); parent && !parent->IsOuterDoc();
|
||||
parent = parent->Parent()) {
|
||||
if (parent->HasPrimaryAction()) {
|
||||
return parent;
|
||||
|
|
|
@ -89,12 +89,6 @@ void LinkableAccessible::Value(nsString& aValue) const {
|
|||
}
|
||||
}
|
||||
|
||||
uint8_t LinkableAccessible::ActionCount() const {
|
||||
bool isLink, isOnclick;
|
||||
ActionWalk(&isLink, &isOnclick);
|
||||
return (isLink || isOnclick) ? 1 : 0;
|
||||
}
|
||||
|
||||
const LocalAccessible* LinkableAccessible::ActionWalk(bool* aIsLink,
|
||||
bool* aIsOnclick) const {
|
||||
if (aIsOnclick) {
|
||||
|
@ -132,33 +126,6 @@ const LocalAccessible* LinkableAccessible::ActionWalk(bool* aIsLink,
|
|||
return localAction;
|
||||
}
|
||||
|
||||
void LinkableAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
|
||||
aName.Truncate();
|
||||
|
||||
// Action 0 (default action): Jump to link
|
||||
if (aIndex == eAction_Jump) {
|
||||
bool isOnclick, isLink;
|
||||
ActionWalk(&isLink, &isOnclick);
|
||||
if (isLink) {
|
||||
aName.AssignLiteral("jump");
|
||||
} else if (isOnclick) {
|
||||
aName.AssignLiteral("click");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LinkableAccessible::DoAction(uint8_t aIndex) const {
|
||||
if (aIndex != eAction_Jump) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const LocalAccessible* actionAcc = ActionWalk()) {
|
||||
return actionAcc->DoAction(aIndex);
|
||||
}
|
||||
|
||||
return AccessibleWrap::DoAction(aIndex);
|
||||
}
|
||||
|
||||
KeyBinding LinkableAccessible::AccessKey() const {
|
||||
if (const LocalAccessible* actionAcc =
|
||||
const_cast<LinkableAccessible*>(this)->ActionWalk()) {
|
||||
|
|
|
@ -45,13 +45,11 @@ class LeafAccessible : public AccessibleWrap {
|
|||
/**
|
||||
* Used for text or image accessible nodes contained by link accessibles or
|
||||
* accessibles for nodes with registered click event handler. It knows how to
|
||||
* report the state of the host link (traveled or not) and can activate (click)
|
||||
* the host accessible programmatically.
|
||||
* report the state of the host link (traveled or not) and can focus the host
|
||||
* accessible programmatically.
|
||||
*/
|
||||
class LinkableAccessible : public AccessibleWrap {
|
||||
public:
|
||||
enum { eAction_Jump = 0 };
|
||||
|
||||
LinkableAccessible(nsIContent* aContent, DocAccessible* aDoc)
|
||||
: AccessibleWrap(aContent, aDoc) {}
|
||||
|
||||
|
@ -63,9 +61,6 @@ class LinkableAccessible : public AccessibleWrap {
|
|||
virtual void TakeFocus() const override;
|
||||
|
||||
// ActionAccessible
|
||||
virtual uint8_t ActionCount() const override;
|
||||
virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
|
||||
virtual bool DoAction(uint8_t index) const override;
|
||||
virtual KeyBinding AccessKey() const override;
|
||||
|
||||
// ActionAccessible helpers
|
||||
|
|
|
@ -2616,3 +2616,26 @@ LocalAccessible* DocAccessible::GetAccessible(nsINode* aNode) const {
|
|||
return aNode == mDocumentNode ? const_cast<DocAccessible*>(this)
|
||||
: mNodeToAccessibleMap.Get(aNode);
|
||||
}
|
||||
|
||||
bool DocAccessible::HasPrimaryAction() const {
|
||||
if (HyperTextAccessible::HasPrimaryAction()) {
|
||||
return true;
|
||||
}
|
||||
// mContent is normally the body, but there might be a click listener on the
|
||||
// root.
|
||||
dom::Element* root = mDocumentNode->GetRootElement();
|
||||
if (mContent != root) {
|
||||
return nsCoreUtils::HasClickListener(root);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DocAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
|
||||
aName.Truncate();
|
||||
if (aIndex != 0) {
|
||||
return;
|
||||
}
|
||||
if (HasPrimaryAction()) {
|
||||
aName.AssignLiteral("click");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,6 +84,10 @@ class DocAccessible : public HyperTextAccessibleWrap,
|
|||
|
||||
virtual nsRect RelativeBounds(nsIFrame** aRelativeFrame) const override;
|
||||
|
||||
// ActionAccessible
|
||||
virtual bool HasPrimaryAction() const override;
|
||||
virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
|
||||
|
||||
// HyperTextAccessible
|
||||
virtual already_AddRefed<EditorBase> GetEditor() const override;
|
||||
|
||||
|
|
|
@ -1821,7 +1821,7 @@ role LocalAccessible::ARIATransformRole(role aRole) const {
|
|||
role LocalAccessible::NativeRole() const { return roles::NOTHING; }
|
||||
|
||||
uint8_t LocalAccessible::ActionCount() const {
|
||||
return HasPrimaryAction() ? 1 : 0;
|
||||
return HasPrimaryAction() || ActionAncestor() ? 1 : 0;
|
||||
}
|
||||
|
||||
void LocalAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
|
||||
|
@ -1888,12 +1888,17 @@ void LocalAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
|
|||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ActionAncestor()) {
|
||||
aName.AssignLiteral("click ancestor");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool LocalAccessible::DoAction(uint8_t aIndex) const {
|
||||
if (aIndex != 0) return false;
|
||||
|
||||
if (HasPrimaryAction()) {
|
||||
if (HasPrimaryAction() || ActionAncestor()) {
|
||||
DoCommand();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -695,8 +695,7 @@ template <class Derived>
|
|||
uint8_t RemoteAccessibleBase<Derived>::ActionCount() const {
|
||||
uint8_t actionCount = 0;
|
||||
if (mCachedFields) {
|
||||
if (HasPrimaryAction() ||
|
||||
((IsTextLeaf() || IsImage()) && ActionAncestor())) {
|
||||
if (HasPrimaryAction() || ActionAncestor()) {
|
||||
actionCount++;
|
||||
}
|
||||
|
||||
|
@ -715,25 +714,21 @@ void RemoteAccessibleBase<Derived>::ActionNameAt(uint8_t aIndex,
|
|||
if (mCachedFields) {
|
||||
aName.Truncate();
|
||||
nsAtom* action = GetPrimaryAction();
|
||||
if (!action && (IsTextLeaf() || IsImage())) {
|
||||
const Accessible* actionAcc = ActionAncestor();
|
||||
Derived* acc =
|
||||
actionAcc ? const_cast<Accessible*>(actionAcc)->AsRemote() : nullptr;
|
||||
if (acc) {
|
||||
action = acc->GetPrimaryAction();
|
||||
}
|
||||
}
|
||||
bool hasActionAncestor = !action && ActionAncestor();
|
||||
|
||||
switch (aIndex) {
|
||||
case 0:
|
||||
if (action) {
|
||||
action->ToString(aName);
|
||||
} else if (hasActionAncestor) {
|
||||
aName.AssignLiteral("click ancestor");
|
||||
} else if (mCachedFields->HasAttribute(nsGkAtoms::longdesc)) {
|
||||
aName.AssignLiteral("showlongdesc");
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (action && mCachedFields->HasAttribute(nsGkAtoms::longdesc)) {
|
||||
if ((action || hasActionAncestor) &&
|
||||
mCachedFields->HasAttribute(nsGkAtoms::longdesc)) {
|
||||
aName.AssignLiteral("showlongdesc");
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -20,6 +20,7 @@ const gActionDescrMap = {
|
|||
expand: "Expand",
|
||||
activate: "Activate",
|
||||
cycle: "Cycle",
|
||||
"click ancestor": "Click ancestor",
|
||||
};
|
||||
|
||||
async function testActions(browser, docAcc, id, expectedActions, domEvents) {
|
||||
|
@ -86,14 +87,23 @@ addAccessibleTask(
|
|||
<a id="link1" href="#">linkable textleaf accessible</a>
|
||||
<div id="link2" onclick="">linkable textleaf accessible</div>
|
||||
|
||||
<a id="link3" href="#">
|
||||
<img id="link3img" alt="image in link"
|
||||
src="http://example.com/a11y/accessible/tests/mochitest/moz.png">
|
||||
</a>
|
||||
|
||||
<div>
|
||||
<label for="TextBox_t2" id="label1">
|
||||
<span>Explicit</span>
|
||||
</label>
|
||||
<input name="in2" id="TextBox_t2" type="text" maxlength="17">
|
||||
</div>
|
||||
|
||||
<div onclick=""><p id="p_in_clickable_div">p in clickable div</p></div>
|
||||
`,
|
||||
async function(browser, docAcc) {
|
||||
is(docAcc.actionCount, 0, "Doc should not have any actions");
|
||||
|
||||
const _testActions = async (id, expectedActions, domEvents) => {
|
||||
await testActions(browser, docAcc, id, expectedActions, domEvents);
|
||||
};
|
||||
|
@ -105,7 +115,10 @@ addAccessibleTask(
|
|||
await _testActions("onclick_img", ["click"], gClickEvents);
|
||||
await _testActions("link1", ["jump"], gClickEvents);
|
||||
await _testActions("link2", ["click"], gClickEvents);
|
||||
await _testActions("link3", ["jump"], gClickEvents);
|
||||
await _testActions("link3img", ["click ancestor"], gClickEvents);
|
||||
await _testActions("label1", ["click"], gClickEvents);
|
||||
await _testActions("p_in_clickable_div", ["click ancestor"], gClickEvents);
|
||||
|
||||
await invokeContentTask(browser, [], () => {
|
||||
content.document
|
||||
|
@ -157,18 +170,39 @@ addAccessibleTask(
|
|||
await _testActions("onclick_img", ["showlongdesc"]);
|
||||
|
||||
// Remove 'href' from link and test linkable child
|
||||
acc = findAccessibleChildByID(docAcc, "link1");
|
||||
const link1Acc = findAccessibleChildByID(docAcc, "link1");
|
||||
is(
|
||||
acc.firstChild.getActionName(0),
|
||||
"jump",
|
||||
"linkable child has jump action"
|
||||
link1Acc.firstChild.getActionName(0),
|
||||
"click ancestor",
|
||||
"linkable child has click ancestor action"
|
||||
);
|
||||
await invokeContentTask(browser, [], () => {
|
||||
let link1 = content.document.getElementById("link1");
|
||||
link1.removeAttribute("href");
|
||||
});
|
||||
await untilCacheIs(() => acc.actionCount, 0, "link has no actions");
|
||||
is(acc.firstChild.actionCount, 0, "linkable child's actions removed");
|
||||
await untilCacheIs(() => link1Acc.actionCount, 0, "link has no actions");
|
||||
is(link1Acc.firstChild.actionCount, 0, "linkable child's actions removed");
|
||||
|
||||
// Add a click handler to the body. Ensure it propagates to descendants.
|
||||
await invokeContentTask(browser, [], () => {
|
||||
content.document.body.onclick = () => {};
|
||||
});
|
||||
await untilCacheIs(() => docAcc.actionCount, 1, "Doc has 1 action");
|
||||
await _testActions("link1", ["click ancestor"]);
|
||||
|
||||
await invokeContentTask(browser, [], () => {
|
||||
content.document.body.onclick = null;
|
||||
});
|
||||
await untilCacheIs(() => docAcc.actionCount, 0, "Doc has no actions");
|
||||
is(link1Acc.actionCount, 0, "link has no actions");
|
||||
|
||||
// Add a click handler to the root element. Ensure it propagates to
|
||||
// descendants.
|
||||
await invokeContentTask(browser, [], () => {
|
||||
content.document.documentElement.onclick = () => {};
|
||||
});
|
||||
await untilCacheIs(() => docAcc.actionCount, 1, "Doc has 1 action");
|
||||
await _testActions("link1", ["click ancestor"]);
|
||||
},
|
||||
{
|
||||
chrome: true,
|
||||
|
|
|
@ -227,4 +227,5 @@ var gActionDescrMap = {
|
|||
expand: "Expand",
|
||||
activate: "Activate",
|
||||
cycle: "Cycle",
|
||||
"click ancestor": "Click ancestor",
|
||||
};
|
||||
|
|
|
@ -65,8 +65,9 @@
|
|||
{
|
||||
ID: "img1",
|
||||
targetID: "link1",
|
||||
actionName: "jump",
|
||||
actionName: "click ancestor",
|
||||
events: CLICK_EVENTS,
|
||||
allowBubbling: true,
|
||||
eventSeq: [
|
||||
new linkChecker("link1"),
|
||||
],
|
||||
|
@ -79,8 +80,9 @@
|
|||
{
|
||||
ID: "img2",
|
||||
targetID: "link2",
|
||||
actionName: "jump",
|
||||
actionName: "click ancestor",
|
||||
events: CLICK_EVENTS,
|
||||
allowBubbling: true,
|
||||
},
|
||||
{
|
||||
ID: "link3",
|
||||
|
@ -90,8 +92,9 @@
|
|||
{
|
||||
ID: "img3",
|
||||
targetID: "link3",
|
||||
actionName: "jump",
|
||||
actionName: "click ancestor",
|
||||
events: CLICK_EVENTS,
|
||||
allowBubbling: true,
|
||||
},
|
||||
{
|
||||
ID: "link4",
|
||||
|
@ -101,8 +104,9 @@
|
|||
{
|
||||
ID: "img4",
|
||||
targetID: "link4",
|
||||
actionName: "jump",
|
||||
actionName: "click ancestor",
|
||||
events: CLICK_EVENTS,
|
||||
allowBubbling: true,
|
||||
},
|
||||
];
|
||||
testActions(actionsArray);
|
||||
|
|
|
@ -32,11 +32,11 @@
|
|||
states: STATE_LINKED,
|
||||
actions: "jump",
|
||||
interfaces: [ nsIAccessibleText, nsIAccessibleHyperText, nsIAccessibleHyperLink ],
|
||||
children: [ // all kids inherits linked state and jump action
|
||||
children: [ // all kids inherits linked state and action
|
||||
{
|
||||
role: ROLE_TEXT_LEAF,
|
||||
states: STATE_LINKED,
|
||||
actions: "jump",
|
||||
actions: "click ancestor",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -109,7 +109,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=429659
|
|||
testThis("nonLinkedImage", "moz.png", 89, 38);
|
||||
|
||||
// Test linked image
|
||||
var actionNamesArray = ["jump"];
|
||||
var actionNamesArray = ["click ancestor"];
|
||||
testThis("linkedImage", "moz.png", 89, 38, 1,
|
||||
actionNamesArray);
|
||||
|
||||
|
|
|
@ -70,10 +70,13 @@ add_task(async function test_downloads_panel_new_download() {
|
|||
DownloadsCommon.openDownload = async () => {
|
||||
ok(false, "openDownload was called when it was not expected");
|
||||
};
|
||||
let newTabPromise = BrowserTestUtils.openNewForegroundTab(
|
||||
let newTabPromise = BrowserTestUtils.openNewForegroundTab({
|
||||
gBrowser,
|
||||
TEST_PATH + "foo.txt"
|
||||
);
|
||||
opening: TEST_PATH + "foo.txt",
|
||||
waitForLoad: false,
|
||||
waitForStateStop: true,
|
||||
});
|
||||
|
||||
await promisePanelOpened();
|
||||
let downloadsListBox = document.getElementById("downloadsListBox");
|
||||
|
||||
|
|
|
@ -355,7 +355,7 @@ __webpack_require__.r(__webpack_exports__);
|
|||
* 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/. */
|
||||
|
||||
const CONFIGURABLE_STYLES = ["color", "fontSize"];
|
||||
const CONFIGURABLE_STYLES = ["color", "fontSize", "fontWeight", "letterSpacing", "lineHeight", "marginBlock", "marginInline", "paddingBlock", "paddingInline"];
|
||||
const ZAP_SIZE_THRESHOLD = 160;
|
||||
/**
|
||||
* Based on the .text prop, localizes an inner element if a string_id
|
||||
|
@ -428,7 +428,7 @@ const Localized = ({
|
|||
|
||||
|
||||
CONFIGURABLE_STYLES.forEach(style => {
|
||||
if (text[style]) props.style[style] = text[style];
|
||||
if (text[style] !== undefined) props.style[style] = text[style];
|
||||
});
|
||||
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().cloneElement( // Provide a default container for the text if necessary.
|
||||
children ?? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", null), props, // Conditionally pass in as void elements can't accept empty array.
|
||||
|
|
|
@ -218,10 +218,10 @@ body[lwt-newtab-brighttext] {
|
|||
.onboardingContainer .welcome-text h1,
|
||||
.onboardingContainer .welcome-text h2 {
|
||||
color: var(--in-content-page-color);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.onboardingContainer .welcome-text h1 {
|
||||
font-size: 24px;
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
margin: 0 6px;
|
||||
letter-spacing: -0.02em;
|
||||
|
@ -231,25 +231,17 @@ body[lwt-newtab-brighttext] {
|
|||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
margin: 10px 6px 0;
|
||||
line-height: 24px;
|
||||
max-width: 750px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.onboardingContainer .welcome-text.larger h1 {
|
||||
font-size: 36px;
|
||||
}
|
||||
.onboardingContainer .welcome-text.slim h1 {
|
||||
font-weight: 276;
|
||||
}
|
||||
.onboardingContainer .welcome-text.fancy h1 {
|
||||
background-image: linear-gradient(90deg, #9059FF, #FF4AA2, #FF8C00, #FF4AA2, #9059FF);
|
||||
background-size: 400% auto;
|
||||
background-clip: text;
|
||||
animation: shine 50s linear infinite;
|
||||
background-size: 200%;
|
||||
}
|
||||
@media (prefers-contrast: no-preference) {
|
||||
.onboardingContainer .welcome-text.fancy h1 {
|
||||
-webkit-text-fill-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
@ -257,9 +249,13 @@ body[lwt-newtab-brighttext] {
|
|||
background-image: linear-gradient(90deg, #C688FF, #FF84C0, #FFBD4F, #FF84C0, #C688FF);
|
||||
}
|
||||
}
|
||||
.onboardingContainer .welcome-text.shine h1 {
|
||||
animation: shine 50s linear infinite;
|
||||
background-size: 400%;
|
||||
}
|
||||
@keyframes shine {
|
||||
to {
|
||||
background-position: 400% center;
|
||||
background-position: 400%;
|
||||
}
|
||||
}
|
||||
.onboardingContainer .screen.light-text .welcome-text.fancy h1 {
|
||||
|
|
|
@ -238,11 +238,11 @@ body {
|
|||
h1,
|
||||
h2 {
|
||||
color: var(--in-content-page-color);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
margin: 0 6px;
|
||||
letter-spacing: -0.02em;
|
||||
|
@ -253,40 +253,33 @@ body {
|
|||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
margin: 10px 6px 0;
|
||||
line-height: 24px;
|
||||
max-width: 750px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
&.larger {
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
&.slim {
|
||||
h1 {
|
||||
font-weight: 276;
|
||||
}
|
||||
}
|
||||
|
||||
&.fancy {
|
||||
h1 {
|
||||
background-image: linear-gradient(90deg, #9059FF, #FF4AA2, #FF8C00, #FF4AA2, #9059FF);
|
||||
background-size: 400% auto;
|
||||
background-clip: text;
|
||||
animation: shine 50s linear infinite;
|
||||
background-size: 200%;
|
||||
@media (prefers-contrast: no-preference) {
|
||||
-webkit-text-fill-color: transparent;
|
||||
color: transparent;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-image: linear-gradient(90deg, #C688FF, #FF84C0, #FFBD4F, #FF84C0, #C688FF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.shine {
|
||||
h1 {
|
||||
animation: shine 50s linear infinite;
|
||||
background-size: 400%;
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
to {
|
||||
background-position: 400% center;
|
||||
background-position: 400%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,17 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
const CONFIGURABLE_STYLES = ["color", "fontSize"];
|
||||
const CONFIGURABLE_STYLES = [
|
||||
"color",
|
||||
"fontSize",
|
||||
"fontWeight",
|
||||
"letterSpacing",
|
||||
"lineHeight",
|
||||
"marginBlock",
|
||||
"marginInline",
|
||||
"paddingBlock",
|
||||
"paddingInline",
|
||||
];
|
||||
const ZAP_SIZE_THRESHOLD = 160;
|
||||
|
||||
/**
|
||||
|
@ -77,7 +87,7 @@ export const Localized = ({ text, children }) => {
|
|||
|
||||
// Apply certain configurable styles.
|
||||
CONFIGURABLE_STYLES.forEach(style => {
|
||||
if (text[style]) props.style[style] = text[style];
|
||||
if (text[style] !== undefined) props.style[style] = text[style];
|
||||
});
|
||||
|
||||
return React.cloneElement(
|
||||
|
|
|
@ -47,7 +47,7 @@ const PREFS_BEFORE_SECTIONS = () => [
|
|||
: [];
|
||||
},
|
||||
},
|
||||
icon: "topsites",
|
||||
icon: "chrome://browser/skin/topsites.svg",
|
||||
maxRows: 4,
|
||||
rowsPref: "topSitesRows",
|
||||
eventSource: "TOP_SITES",
|
||||
|
|
|
@ -64,9 +64,10 @@ const ONBOARDING_MESSAGES = () => [
|
|||
},
|
||||
has_noodles: true,
|
||||
title: {
|
||||
fontSize: "36px",
|
||||
string_id: "fx100-upgrade-thanks-header",
|
||||
},
|
||||
title_style: "fancy larger",
|
||||
title_style: "fancy shine",
|
||||
background:
|
||||
"url('chrome://activity-stream/content/data/content/assets/confetti.svg') top / 100% no-repeat var(--in-content-page-background)",
|
||||
subtitle: {
|
||||
|
|
|
@ -466,9 +466,10 @@ const MESSAGES = () => [
|
|||
height: "100px",
|
||||
},
|
||||
title: {
|
||||
fontSize: "36px",
|
||||
fontWeight: 276,
|
||||
string_id: "mr1-onboarding-default-header",
|
||||
},
|
||||
title_style: "slim larger",
|
||||
subtitle: {
|
||||
string_id: "mr1-onboarding-default-subtitle",
|
||||
},
|
||||
|
@ -527,8 +528,12 @@ const MESSAGES = () => [
|
|||
height: "200px",
|
||||
imageURL: "",
|
||||
},
|
||||
title: "Peace of mind.",
|
||||
title_style: "fancy slim larger",
|
||||
title: {
|
||||
fontSize: "36px",
|
||||
fontWeight: 276,
|
||||
raw: "Peace of mind.",
|
||||
},
|
||||
title_style: "fancy shine",
|
||||
text_color: "dark",
|
||||
subtitle:
|
||||
"For the best privacy protection, keep Firefox in easy reach.",
|
||||
|
|
|
@ -151,12 +151,17 @@ add_task(async function test_aboutwelcome_with_color_backdrop() {
|
|||
});
|
||||
|
||||
/**
|
||||
* Test rendering a screen with a title that's fancy, slim, and larger
|
||||
* Test rendering a screen with a title with custom styles.
|
||||
*/
|
||||
add_task(async function test_aboutwelcome_with_title_styles() {
|
||||
const TEST_TITLE_STYLE = "fancy slim larger";
|
||||
const TEST_TITLE_STYLE_CONTENT = makeTestContent("TEST_TITLE_STYLE_STEP", {
|
||||
title_style: TEST_TITLE_STYLE,
|
||||
title: {
|
||||
fontSize: "36px",
|
||||
fontWeight: 276,
|
||||
letterSpacing: 0,
|
||||
raw: "test",
|
||||
},
|
||||
title_style: "fancy shine",
|
||||
});
|
||||
|
||||
const TEST_TITLE_STYLE_JSON = JSON.stringify([TEST_TITLE_STYLE_CONTENT]);
|
||||
|
@ -166,7 +171,7 @@ add_task(async function test_aboutwelcome_with_title_styles() {
|
|||
browser,
|
||||
"renders screen with customized title style",
|
||||
// Expected selectors:
|
||||
[`div.welcome-text.fancy.slim.larger`]
|
||||
[`div.welcome-text.fancy.shine`]
|
||||
);
|
||||
|
||||
await test_element_styles(
|
||||
|
@ -177,6 +182,7 @@ add_task(async function test_aboutwelcome_with_title_styles() {
|
|||
"font-weight": "276",
|
||||
"font-size": "36px",
|
||||
animation: "50s linear 0s infinite normal none running shine",
|
||||
"letter-spacing": "normal",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -2073,6 +2073,9 @@ body.theme_dark .stp_tag_picker .stp_tag_picker_tag {
|
|||
body.theme_dark .stp_tag_picker .stp_tag_picker_tag_remove {
|
||||
color: #8F8F9D;
|
||||
}
|
||||
body.theme_dark .stp_tag_picker .stp_tag_picker_tag_remove:hover {
|
||||
color: #fff;
|
||||
}
|
||||
body.theme_dark .stp_tag_picker .stp_tag_picker_tag_remove:focus {
|
||||
outline: 2px solid #00DDFF;
|
||||
}
|
||||
|
@ -2096,20 +2099,18 @@ body.theme_dark .stp_tag_picker .stp_tag_picker_tag_duplicate {
|
|||
border-end-start-radius: 4px;
|
||||
}
|
||||
.stp_tag_picker .stp_tag_picker_input:focus {
|
||||
outline: 2px solid #0060df;
|
||||
border: 1px solid #0060DF;
|
||||
outline: 1px solid #0060DF;
|
||||
}
|
||||
body.theme_dark .stp_tag_picker .stp_tag_picker_input {
|
||||
background: none;
|
||||
color: #FBFBFB;
|
||||
}
|
||||
body.theme_dark .stp_tag_picker .stp_tag_picker_input:focus {
|
||||
outline: 2px solid #00DDFF;
|
||||
border: 1px solid #00DDFF;
|
||||
outline: 1px solid #00DDFF;
|
||||
}
|
||||
|
||||
.stp_tag_picker .stp_tag_picker_input:focus {
|
||||
border: 1px solid #0060DF;
|
||||
outline: 1px solid #0060DF;
|
||||
}
|
||||
.stp_tag_picker .stp_tag_picker_button {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.1rem;
|
||||
|
@ -2130,6 +2131,20 @@ body.theme_dark .stp_tag_picker .stp_tag_picker_input:focus {
|
|||
border: 1px solid #0060DF;
|
||||
outline: 1px solid #0060DF;
|
||||
}
|
||||
body.theme_dark .stp_tag_picker .stp_tag_picker_button {
|
||||
background-color: #2B2A33;
|
||||
color: #FBFBFB;
|
||||
}
|
||||
body.theme_dark .stp_tag_picker .stp_tag_picker_button:disabled {
|
||||
color: #666;
|
||||
}
|
||||
body.theme_dark .stp_tag_picker .stp_tag_picker_button:hover:enabled {
|
||||
background-color: #53535d;
|
||||
}
|
||||
body.theme_dark .stp_tag_picker .stp_tag_picker_button:focus:enabled {
|
||||
border: 1px solid #00DDFF;
|
||||
outline: 1px solid #00DDFF;
|
||||
}
|
||||
|
||||
.stp_popular_topics {
|
||||
padding: 0;
|
||||
|
@ -2173,6 +2188,9 @@ body.theme_dark .stp_popular_topics .stp_popular_topic .stp_popular_topic_link {
|
|||
body.theme_dark .stp_popular_topics .stp_popular_topic .stp_popular_topic_link:focus {
|
||||
outline: 2px solid #00DDFF;
|
||||
}
|
||||
body.theme_dark .stp_popular_topics .stp_popular_topic .stp_popular_topic_link:hover {
|
||||
background: #53535d;
|
||||
}
|
||||
|
||||
.stp_article_list {
|
||||
padding: 0;
|
||||
|
@ -2311,6 +2329,9 @@ body.theme_dark .stp_button.stp_button_primary {
|
|||
background: #00DDFF;
|
||||
color: #15141A;
|
||||
}
|
||||
body.theme_dark .stp_button.stp_button_primary:hover {
|
||||
background: #80ebfe;
|
||||
}
|
||||
body.theme_dark .stp_button.stp_button_primary:focus {
|
||||
outline: 2px solid #00DDFF;
|
||||
}
|
||||
|
@ -2345,6 +2366,9 @@ body.theme_dark .stp_button.stp_button_secondary {
|
|||
body.theme_dark .stp_button.stp_button_secondary:focus {
|
||||
outline: 2px solid #00DDFF;
|
||||
}
|
||||
body.theme_dark .stp_button.stp_button_secondary:hover {
|
||||
background: #53535d;
|
||||
}
|
||||
|
||||
.stp_button_wide .stp_button {
|
||||
display: block;
|
||||
|
|
|
@ -63,6 +63,10 @@
|
|||
background: #00DDFF;
|
||||
color: #15141A;
|
||||
|
||||
&:hover {
|
||||
background: #80ebfe;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid #00DDFF;
|
||||
}
|
||||
|
@ -102,6 +106,10 @@
|
|||
&:focus {
|
||||
outline: 2px solid #00DDFF;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #53535d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,10 @@
|
|||
&:focus {
|
||||
outline: 2px solid #00DDFF;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #53535d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,10 @@
|
|||
@include theme_dark {
|
||||
color: #8F8F9D;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid #00DDFF;
|
||||
}
|
||||
|
@ -78,7 +82,8 @@
|
|||
border-end-start-radius: 4px;
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid #0060df;
|
||||
border: 1px solid #0060DF;
|
||||
outline: 1px solid #0060DF;
|
||||
}
|
||||
|
||||
@include theme_dark {
|
||||
|
@ -86,13 +91,10 @@
|
|||
color: #FBFBFB;
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid #00DDFF;
|
||||
border: 1px solid #00DDFF;
|
||||
outline: 1px solid #00DDFF;
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
border: 1px solid #0060DF;
|
||||
outline: 1px solid #0060DF;
|
||||
}
|
||||
}
|
||||
|
||||
.stp_tag_picker_button {
|
||||
|
@ -114,5 +116,20 @@
|
|||
border: 1px solid #0060DF;
|
||||
outline: 1px solid #0060DF;
|
||||
}
|
||||
|
||||
@include theme_dark {
|
||||
background-color: #2B2A33;
|
||||
color: #FBFBFB;
|
||||
&:disabled {
|
||||
color: #666;
|
||||
}
|
||||
&:hover:enabled {
|
||||
background-color: #53535d;
|
||||
}
|
||||
&:focus:enabled {
|
||||
border: 1px solid #00DDFF;
|
||||
outline: 1px solid #00DDFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,17 @@
|
|||
* 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 = ["BuiltInThemeConfig"];
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AppConstants: "resource://gre/modules/AppConstants.jsm",
|
||||
});
|
||||
|
||||
/**
|
||||
* A Map of themes built in to the browser, alongwith a Map of collections those themes belong to. Params for the objects contained
|
||||
* within the map:
|
||||
|
@ -239,13 +248,13 @@ const ColorwayCollections = new Map([
|
|||
[
|
||||
"life-in-color",
|
||||
{
|
||||
expiry: "2022-02-08",
|
||||
expiry: AppConstants.NIGHTLY_BUILD ? "2022-08-03" : "2022-02-08",
|
||||
},
|
||||
],
|
||||
[
|
||||
"true-colors",
|
||||
{
|
||||
expiry: "2022-05-03",
|
||||
expiry: AppConstants.NIGHTLY_BUILD ? "2022-4-20" : "2022-05-03",
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
|
|
@ -56,6 +56,7 @@ js_source_path = [
|
|||
"../browser/components/uitour",
|
||||
"../browser/components/urlbar",
|
||||
"../remote/marionette",
|
||||
"../toolkit/actors",
|
||||
"../toolkit/components/extensions",
|
||||
"../toolkit/components/extensions/parent",
|
||||
"../toolkit/components/featuregates",
|
||||
|
|
|
@ -4140,11 +4140,17 @@ already_AddRefed<nsIWidget> nsGlobalWindowOuter::GetMainWidget() {
|
|||
|
||||
nsIWidget* nsGlobalWindowOuter::GetNearestWidget() const {
|
||||
nsIDocShell* docShell = GetDocShell();
|
||||
NS_ENSURE_TRUE(docShell, nullptr);
|
||||
if (!docShell) {
|
||||
return nullptr;
|
||||
}
|
||||
PresShell* presShell = docShell->GetPresShell();
|
||||
NS_ENSURE_TRUE(presShell, nullptr);
|
||||
if (!presShell) {
|
||||
return nullptr;
|
||||
}
|
||||
nsIFrame* rootFrame = presShell->GetRootFrame();
|
||||
NS_ENSURE_TRUE(rootFrame, nullptr);
|
||||
if (!rootFrame) {
|
||||
return nullptr;
|
||||
}
|
||||
return rootFrame->GetView()->GetNearestWidget(nullptr);
|
||||
}
|
||||
|
||||
|
|
|
@ -252,12 +252,14 @@ OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
|
|||
Maybe<int32_t> childId;
|
||||
HTMLCanvasElement* canvasElement;
|
||||
RefPtr<gfx::SourceSurface> surface;
|
||||
layers::CompositableHandle handle;
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
hasAlpha = !mData.mIsOpaque;
|
||||
isAlphaPremult = mData.mIsAlphaPremult;
|
||||
originPos = mData.mOriginPos;
|
||||
handle = mData.mHandle;
|
||||
managerId = mContextManagerId;
|
||||
childId = mContextChildId;
|
||||
canvasElement = mCanvasElement;
|
||||
|
@ -311,7 +313,9 @@ OffscreenCanvasDisplayHelper::GetSurfaceSnapshot() {
|
|||
// We don't have a usable surface, and the context lives in the compositor
|
||||
// process.
|
||||
return gfx::CanvasManagerChild::Get()->GetSnapshot(
|
||||
managerId.value(), childId.value(), hasAlpha);
|
||||
managerId.value(), childId.value(), handle,
|
||||
hasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8,
|
||||
hasAlpha && !isAlphaPremult, originPos == gl::OriginPos::BottomLeft);
|
||||
}
|
||||
|
||||
// If we don't have any protocol IDs, or an existing surface, it is possible
|
||||
|
|
|
@ -664,6 +664,42 @@ struct QueueParamTraits<std::pair<TypeA, TypeB>> {
|
|||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
template <>
|
||||
struct QueueParamTraits<mozilla::ipc::Shmem> {
|
||||
using ParamType = mozilla::ipc::Shmem;
|
||||
|
||||
template <typename U>
|
||||
static bool Write(ProducerView<U>& aProducerView, ParamType&& aParam) {
|
||||
if (!aProducerView.WriteParam(
|
||||
aParam.Id(mozilla::ipc::Shmem::PrivateIPDLCaller()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aParam.RevokeRights(mozilla::ipc::Shmem::PrivateIPDLCaller());
|
||||
aParam.forget(mozilla::ipc::Shmem::PrivateIPDLCaller());
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
static bool Read(ConsumerView<U>& aConsumerView, ParamType* aResult) {
|
||||
ParamType::id_t id;
|
||||
if (!aConsumerView.ReadParam(&id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mozilla::ipc::Shmem::SharedMemory* rawmem =
|
||||
aConsumerView.LookupSharedMemory(id);
|
||||
if (!rawmem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*aResult = mozilla::ipc::Shmem(mozilla::ipc::Shmem::PrivateIPDLCaller(),
|
||||
rawmem, id);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace webgl
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
#include "ImageContainer.h"
|
||||
|
||||
nsICanvasRenderingContextInternal::nsICanvasRenderingContextInternal() =
|
||||
default;
|
||||
|
@ -72,3 +73,12 @@ void nsICanvasRenderingContextInternal::DoSecurityCheck(
|
|||
mOffscreenCanvas->SetWriteOnly();
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<mozilla::layers::Image>
|
||||
nsICanvasRenderingContextInternal::GetAsImage() {
|
||||
RefPtr<mozilla::gfx::SourceSurface> snapshot = GetFrontBufferSnapshot(true);
|
||||
if (!snapshot) {
|
||||
return nullptr;
|
||||
}
|
||||
return mozilla::MakeAndAddRef<mozilla::layers::SourceSurfaceImage>(snapshot);
|
||||
}
|
||||
|
|
|
@ -146,9 +146,7 @@ class nsICanvasRenderingContextInternal : public nsISupports,
|
|||
// for possibly reinitializing with SetDimensions/InitializeWithSurface.
|
||||
NS_IMETHOD Reset() = 0;
|
||||
|
||||
virtual already_AddRefed<mozilla::layers::Image> GetAsImage() {
|
||||
return nullptr;
|
||||
}
|
||||
virtual already_AddRefed<mozilla::layers::Image> GetAsImage();
|
||||
|
||||
virtual bool UpdateWebRenderCanvasData(
|
||||
mozilla::nsDisplayListBuilder* aBuilder,
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "mozilla/dom/UserActivation.h"
|
||||
#include "mozilla/dom/MutationEventBinding.h"
|
||||
#include "mozilla/dom/WheelEventBinding.h"
|
||||
#include "mozilla/dom/WindowGlobalChild.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
#include "mozilla/TextUtils.h"
|
||||
|
@ -5522,6 +5523,74 @@ void HTMLInputElement::SetSelectionDirection(const nsAString& aDirection,
|
|||
state->SetSelectionDirection(aDirection, aRv);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
|
||||
void HTMLInputElement::ShowPicker(ErrorResult& aRv) {
|
||||
// Step 1. If this is not mutable, then throw an "InvalidStateError"
|
||||
// DOMException.
|
||||
if (!IsMutable()) {
|
||||
return aRv.ThrowInvalidStateError(
|
||||
"This input is either disabled or readonly.");
|
||||
}
|
||||
|
||||
// Step 2. If this's relevant settings object's origin is not same origin with
|
||||
// this's relevant settings object's top-level origin, and this's type
|
||||
// attribute is not in the File Upload state or Color state, then throw a
|
||||
// "SecurityError" DOMException.
|
||||
if (mType != FormControlType::InputFile &&
|
||||
mType != FormControlType::InputColor) {
|
||||
nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
|
||||
WindowGlobalChild* windowGlobalChild =
|
||||
window ? window->GetWindowGlobalChild() : nullptr;
|
||||
if (!windowGlobalChild || !windowGlobalChild->SameOriginWithTop()) {
|
||||
return aRv.ThrowSecurityError(
|
||||
"Call was blocked because the current origin isn't same-origin with "
|
||||
"top.");
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3. If this's relevant global object does not have transient
|
||||
// activation, then throw a "NotAllowedError" DOMException.
|
||||
if (!OwnerDoc()->HasValidTransientUserGestureActivation()) {
|
||||
return aRv.ThrowNotAllowedError(
|
||||
"Call was blocked due to lack of user activation.");
|
||||
}
|
||||
|
||||
// Step 4. Show the picker, if applicable, for this.
|
||||
//
|
||||
// https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
|
||||
// To show the picker, if applicable for an input element element:
|
||||
|
||||
// Step 1. Assert: element's relevant global object has transient activation.
|
||||
// Step 2. If element is not mutable, then return.
|
||||
// (See above.)
|
||||
|
||||
// Step 3. If element's type attribute is in the File Upload state, then run
|
||||
// these steps in parallel:
|
||||
if (mType == FormControlType::InputFile) {
|
||||
FilePickerType type = FILE_PICKER_FILE;
|
||||
if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
|
||||
HasAttr(nsGkAtoms::webkitdirectory)) {
|
||||
type = FILE_PICKER_DIRECTORY;
|
||||
}
|
||||
InitFilePicker(type);
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 4. Otherwise, the user agent should show any relevant user interface
|
||||
// for selecting a value for element, in the way it normally would when the
|
||||
// user interacts with the control
|
||||
if (mType == FormControlType::InputColor) {
|
||||
InitColorPicker();
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsDateTimeInputType(mType) && IsInComposedDoc()) {
|
||||
DateTimeValue value;
|
||||
GetDateTimeInputBoxValue(value);
|
||||
OpenDateTimePicker(value);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ACCESSIBILITY
|
||||
/*static*/ nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
|
||||
EventMessage aEventMessage) {
|
||||
|
|
|
@ -699,6 +699,8 @@ class HTMLInputElement final : public TextControlElement,
|
|||
SelectionMode aSelectMode,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void ShowPicker(ErrorResult& aRv);
|
||||
|
||||
bool WebkitDirectoryAttr() const {
|
||||
return HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=885996
|
|||
{ id: 'normal', type: 'prevent-default-1', result: false },
|
||||
{ id: 'normal', type: 'prevent-default-2', result: false },
|
||||
{ id: 'normal', type: 'click-method', result: true },
|
||||
{ id: 'normal', type: 'show-picker', result: true },
|
||||
{ id: 'normal', type: 'right-click', result: false },
|
||||
{ id: 'normal', type: 'middle-click', result: false },
|
||||
{ id: 'label-1', result: true },
|
||||
|
@ -87,6 +88,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=885996
|
|||
case 'click-method':
|
||||
element.click();
|
||||
break;
|
||||
case 'show-picker':
|
||||
SpecialPowers.wrap(document).notifyUserGestureActivation();
|
||||
element.showPicker();
|
||||
break;
|
||||
case 'right-click':
|
||||
synthesizeMouseAtCenter(element, { button: 2 });
|
||||
break;
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
<div id='div-click-on-demand' onclick="var i=document.createElement('input'); i.type='file'; i.click();" tabindex='1'>foo</div>
|
||||
<div id='div-keydown' onkeydown="document.getElementById('by-button').click();" tabindex='1'>foo</div>
|
||||
<a id='link-click' href="javascript:document.getElementById('by-button').click();" tabindex='1'>foo</a>
|
||||
<input id='show-picker' type='file'>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
@ -135,6 +136,7 @@ var testData = [["a", 1, MockFilePicker.filterImages, 1],
|
|||
["div-click-on-demand", 0, undefined, 0],
|
||||
["div-keydown", 0, undefined, 0],
|
||||
["link-click", 0, undefined, 0],
|
||||
["show-picker", 0, undefined, 0],
|
||||
];
|
||||
|
||||
var currentTest = 0;
|
||||
|
@ -202,6 +204,9 @@ function launchNextTest() {
|
|||
synthesizeMouseAtCenter(document.getElementById(testData[currentTest][0]), {});
|
||||
} else if (testData[currentTest][0] == 'div-keydown') {
|
||||
sendString("a");
|
||||
} else if (testData[currentTest][0] == 'show-picker') {
|
||||
SpecialPowers.wrap(document).notifyUserGestureActivation();
|
||||
document.getElementById(testData[currentTest][0]).showPicker();
|
||||
} else {
|
||||
document.getElementById(testData[currentTest][0]).click();
|
||||
}
|
||||
|
|
|
@ -15,6 +15,10 @@ collapse= Collapse
|
|||
expand = Expand
|
||||
activate= Activate
|
||||
cycle = Cycle
|
||||
# An action provided to accessibility clients such as screen readers to allow
|
||||
# them to click an element when the click will be handled by a container
|
||||
# (ancestor) element. This is not normally reported to users.
|
||||
click ancestor = Click ancestor
|
||||
|
||||
# Universal Access API support
|
||||
# (Mac Only)
|
||||
|
|
|
@ -15,3 +15,7 @@ collapse= Collapse
|
|||
expand = Expand
|
||||
activate= Activate
|
||||
cycle = Cycle
|
||||
# An action provided to accessibility clients such as screen readers to allow
|
||||
# them to click an element when the click will be handled by a container
|
||||
# (ancestor) element. This is not normally reported to users.
|
||||
click ancestor = Click ancestor
|
||||
|
|
|
@ -15,3 +15,7 @@ collapse= Collapse
|
|||
expand = Expand
|
||||
activate= Activate
|
||||
cycle = Cycle
|
||||
# An action provided to accessibility clients such as screen readers to allow
|
||||
# them to click an element when the click will be handled by a container
|
||||
# (ancestor) element. This is not normally reported to users.
|
||||
click ancestor = Click ancestor
|
||||
|
|
|
@ -273,6 +273,9 @@ PlanarYCbCrData ConstructPlanarYCbCrData(const VideoInfo& aInfo,
|
|||
data.mStereoMode = aInfo.mStereoMode;
|
||||
data.mYUVColorSpace = aBuffer.mYUVColorSpace;
|
||||
data.mColorDepth = aBuffer.mColorDepth;
|
||||
if (aInfo.mTransferFunction) {
|
||||
data.mTransferFunction = *aInfo.mTransferFunction;
|
||||
}
|
||||
data.mColorRange = aBuffer.mColorRange;
|
||||
data.mChromaSubsampling = aBuffer.mChromaSubsampling;
|
||||
return data;
|
||||
|
|
|
@ -244,8 +244,16 @@ class VideoInfo : public TrackInfo {
|
|||
// Should be 8, 10 or 12. Default value is 8.
|
||||
gfx::ColorDepth mColorDepth = gfx::ColorDepth::COLOR_8;
|
||||
|
||||
// Matrix coefficients (if specified by the video) imply a colorspace.
|
||||
Maybe<gfx::YUVColorSpace> mColorSpace;
|
||||
|
||||
// Color primaries are assumed to match the colorspace.
|
||||
|
||||
// Transfer functions get their own member, since we support different
|
||||
// values for the BT2020 primaries. For other colorspaces, this member
|
||||
// is ignored.
|
||||
Maybe<gfx::TransferFunction> mTransferFunction;
|
||||
|
||||
// True indicates no restriction on Y, U, V values (otherwise 16-235 for 8
|
||||
// bits etc)
|
||||
gfx::ColorRange mColorRange = gfx::ColorRange::LIMITED;
|
||||
|
|
|
@ -33,6 +33,7 @@ struct ParamTraits<mozilla::VideoInfo> {
|
|||
WriteParam(aWriter, aParam.mRotation);
|
||||
WriteParam(aWriter, aParam.mColorDepth);
|
||||
WriteParam(aWriter, aParam.mColorSpace);
|
||||
WriteParam(aWriter, aParam.mTransferFunction);
|
||||
WriteParam(aWriter, aParam.mColorRange);
|
||||
WriteParam(aWriter, aParam.HasAlpha());
|
||||
}
|
||||
|
@ -50,6 +51,7 @@ struct ParamTraits<mozilla::VideoInfo> {
|
|||
ReadParam(aReader, &aResult->mRotation) &&
|
||||
ReadParam(aReader, &aResult->mColorDepth) &&
|
||||
ReadParam(aReader, &aResult->mColorSpace) &&
|
||||
ReadParam(aReader, &aResult->mTransferFunction) &&
|
||||
ReadParam(aReader, &aResult->mColorRange) &&
|
||||
ReadParam(aReader, &alphaPresent)) {
|
||||
aResult->SetAlpha(alphaPresent);
|
||||
|
|
|
@ -44,6 +44,8 @@ AppleVTDecoder::AppleVTDecoder(const VideoInfo& aConfig,
|
|||
mColorSpace(aConfig.mColorSpace
|
||||
? *aConfig.mColorSpace
|
||||
: DefaultColorSpace({mPictureWidth, mPictureHeight})),
|
||||
mTransferFunction(aConfig.mTransferFunction ? *aConfig.mTransferFunction
|
||||
: gfx::TransferFunction::PQ),
|
||||
mColorRange(aConfig.mColorRange),
|
||||
mColorDepth(aConfig.mColorDepth),
|
||||
mStreamType(MP4Decoder::IsH264(aConfig.mMimeType) ? StreamType::H264
|
||||
|
@ -430,6 +432,8 @@ void AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage,
|
|||
} else {
|
||||
#if !defined(MAC_OS_VERSION_10_13) || \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_10_13
|
||||
CFStringRef kCVImageBufferTransferFunction_ITU_R_2100_HLG =
|
||||
CFSTR("ITU_R_2100_HLG");
|
||||
CFStringRef kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ =
|
||||
CFSTR("SMPTE_ST_2084_PQ");
|
||||
#endif
|
||||
|
@ -459,9 +463,12 @@ void AppleVTDecoder::OutputFrame(CVPixelBufferRef aImage,
|
|||
CVBufferSetAttachment(aImage, kCVImageBufferColorPrimariesKey,
|
||||
kCVImageBufferColorPrimaries_ITU_R_2020,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
CVBufferSetAttachment(aImage, kCVImageBufferTransferFunctionKey,
|
||||
kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
CVBufferSetAttachment(
|
||||
aImage, kCVImageBufferTransferFunctionKey,
|
||||
(mTransferFunction == gfx::TransferFunction::HLG)
|
||||
? kCVImageBufferTransferFunction_ITU_R_2100_HLG
|
||||
: kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ,
|
||||
kCVAttachmentMode_ShouldPropagate);
|
||||
}
|
||||
|
||||
CFTypeRefPtr<IOSurfaceRef> surface =
|
||||
|
|
|
@ -91,6 +91,7 @@ class AppleVTDecoder : public MediaDataDecoder,
|
|||
const uint32_t mDisplayWidth;
|
||||
const uint32_t mDisplayHeight;
|
||||
const gfx::YUVColorSpace mColorSpace;
|
||||
const gfx::TransferFunction mTransferFunction;
|
||||
const gfx::ColorRange mColorRange;
|
||||
const gfx::ColorDepth mColorDepth;
|
||||
|
||||
|
|
|
@ -142,6 +142,11 @@ class H264ChangeMonitor : public MediaChangeMonitor::CodecChangeMonitor {
|
|||
mCurrentConfig.mDisplay.height = spsdata.display_height;
|
||||
mCurrentConfig.mColorDepth = spsdata.ColorDepth();
|
||||
mCurrentConfig.mColorSpace = Some(spsdata.ColorSpace());
|
||||
// spsdata.transfer_characteristics has the same values as
|
||||
// gfx::CICP::TransferCharacteristics.
|
||||
mCurrentConfig.mTransferFunction = gfxUtils::CicpToTransferFunction(
|
||||
static_cast<gfx::CICP::TransferCharacteristics>(
|
||||
spsdata.transfer_characteristics));
|
||||
mCurrentConfig.mColorRange = spsdata.video_full_range_flag
|
||||
? gfx::ColorRange::FULL
|
||||
: gfx::ColorRange::LIMITED;
|
||||
|
@ -267,6 +272,14 @@ class VPXChangeMonitor : public MediaChangeMonitor::CodecChangeMonitor {
|
|||
|
||||
mCurrentConfig.mColorDepth = gfx::ColorDepthForBitDepth(info.mBitDepth);
|
||||
mCurrentConfig.mColorSpace = Some(info.ColorSpace());
|
||||
|
||||
// We don't update the transfer function here, because VPX bitstream
|
||||
// doesn't specify the transfer function. Instead, we keep the transfer
|
||||
// function (if any) that was set in mCurrentConfig when we were created.
|
||||
// If a video changes colorspaces away from BT2020, we won't clear
|
||||
// mTransferFunction, in case the video changes back to BT2020 and we
|
||||
// need the value again.
|
||||
|
||||
mCurrentConfig.mColorRange = info.ColorRange();
|
||||
if (mCodec == VPXDecoder::Codec::VP9) {
|
||||
mCurrentConfig.mExtraData->ClearAndRetainStorage();
|
||||
|
@ -333,6 +346,8 @@ class AV1ChangeMonitor : public MediaChangeMonitor::CodecChangeMonitor {
|
|||
mCurrentConfig.mColorSpace = gfxUtils::CicpToColorSpace(
|
||||
aInfo.mColorSpace.mMatrix, aInfo.mColorSpace.mPrimaries,
|
||||
gMediaDecoderLog);
|
||||
mCurrentConfig.mTransferFunction =
|
||||
gfxUtils::CicpToTransferFunction(aInfo.mColorSpace.mTransfer);
|
||||
mCurrentConfig.mColorRange = aInfo.mColorSpace.mRange;
|
||||
|
||||
if (mCurrentConfig.mImage != mInfo->mImage) {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "WebMDemuxer.h"
|
||||
#include "WebMBufferedParser.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "gfxUtils.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
#include "mozilla/SharedThreadPool.h"
|
||||
#include "MediaDataDemuxer.h"
|
||||
|
@ -305,6 +306,15 @@ nsresult WebMDemuxer::ReadMetadata() {
|
|||
NS_WARNING("Unknown WebM video codec");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// For VPX, this is our only chance to capture the transfer
|
||||
// characteristics, which we can't get from a VPX bitstream later.
|
||||
// We only need this value if the video is using the BT2020
|
||||
// colorspace, which will be determined on a per-frame basis later.
|
||||
mInfo.mVideo.mTransferFunction = gfxUtils::CicpToTransferFunction(
|
||||
static_cast<gfx::CICP::TransferCharacteristics>(
|
||||
params.transfer_characteristics));
|
||||
|
||||
// Picture region, taking into account cropping, before scaling
|
||||
// to the display size.
|
||||
unsigned int cropH = params.crop_right + params.crop_left;
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
|
||||
#include "mozilla/dom/WebGPUBinding.h"
|
||||
#include "CanvasContext.h"
|
||||
#include "nsDisplayList.h"
|
||||
#include "gfxUtils.h"
|
||||
#include "LayerUserData.h"
|
||||
#include "nsDisplayList.h"
|
||||
#include "mozilla/dom/HTMLCanvasElement.h"
|
||||
#include "mozilla/gfx/CanvasManagerChild.h"
|
||||
#include "mozilla/layers/CompositableInProcessManager.h"
|
||||
#include "mozilla/layers/ImageDataSerializer.h"
|
||||
#include "mozilla/layers/LayersSurfaces.h"
|
||||
|
@ -78,6 +80,7 @@ void CanvasContext::Configure(const dom::GPUCanvasConfiguration& aDesc) {
|
|||
} else if (mOffscreenCanvas) {
|
||||
dom::OffscreenCanvasDisplayData data;
|
||||
data.mSize = {mWidth, mHeight};
|
||||
data.mIsOpaque = false;
|
||||
data.mHandle = mHandle;
|
||||
mOffscreenCanvas->UpdateDisplayData(data);
|
||||
}
|
||||
|
@ -123,5 +126,51 @@ void CanvasContext::SwapChainPresent() {
|
|||
}
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<uint8_t[]> CanvasContext::GetImageBuffer(int32_t* aFormat) {
|
||||
gfxAlphaType any;
|
||||
RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
|
||||
if (!snapshot) {
|
||||
*aFormat = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
|
||||
return gfxUtils::GetImageBuffer(dataSurface, /* aIsAlphaPremultiplied */ true,
|
||||
aFormat);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP CanvasContext::GetInputStream(const char* aMimeType,
|
||||
const nsAString& aEncoderOptions,
|
||||
nsIInputStream** aStream) {
|
||||
gfxAlphaType any;
|
||||
RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
|
||||
if (!snapshot) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
|
||||
return gfxUtils::GetInputStream(dataSurface, /* aIsAlphaPremultiplied */ true,
|
||||
aMimeType, aEncoderOptions, aStream);
|
||||
}
|
||||
|
||||
already_AddRefed<mozilla::gfx::SourceSurface> CanvasContext::GetSurfaceSnapshot(
|
||||
gfxAlphaType* aOutAlphaType) {
|
||||
if (aOutAlphaType) {
|
||||
*aOutAlphaType = gfxAlphaType::Premult;
|
||||
}
|
||||
|
||||
auto* const cm = gfx::CanvasManagerChild::Get();
|
||||
if (!cm) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!mBridge || !mBridge->IsOpen() || !mHandle) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return cm->GetSnapshot(cm->Id(), mBridge->Id(), mHandle, mGfxFormat,
|
||||
/* aPremultiply */ false, /* aYFlip */ false);
|
||||
}
|
||||
|
||||
} // namespace webgpu
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -54,20 +54,12 @@ class CanvasContext final : public nsICanvasRenderingContextInternal,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override {
|
||||
MOZ_CRASH("todo");
|
||||
}
|
||||
mozilla::UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override;
|
||||
NS_IMETHOD GetInputStream(const char* aMimeType,
|
||||
const nsAString& aEncoderOptions,
|
||||
nsIInputStream** aStream) override {
|
||||
*aStream = nullptr;
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
nsIInputStream** aStream) override;
|
||||
already_AddRefed<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(
|
||||
gfxAlphaType* aOutAlphaType) override {
|
||||
return nullptr;
|
||||
}
|
||||
gfxAlphaType* aOutAlphaType) override;
|
||||
|
||||
void SetOpaqueValueFromOpaqueAttr(bool aOpaqueAttrValue) override {}
|
||||
bool GetIsOpaque() override { return true; }
|
||||
|
|
|
@ -640,6 +640,36 @@ static void PresentCallback(ffi::WGPUBufferMapAsyncStatus status,
|
|||
delete req;
|
||||
}
|
||||
|
||||
ipc::IPCResult WebGPUParent::GetFrontBufferSnapshot(
|
||||
IProtocol* aProtocol, const CompositableHandle& aHandle,
|
||||
Maybe<Shmem>& aShmem, gfx::IntSize& aSize) {
|
||||
const auto& lookup = mCanvasMap.find(aHandle.Value());
|
||||
if (lookup == mCanvasMap.end()) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
RefPtr<PresentationData> data = lookup->second.get();
|
||||
aSize = data->mTextureHost->GetSize();
|
||||
uint32_t stride =
|
||||
aSize.width * BytesPerPixel(data->mTextureHost->GetFormat());
|
||||
uint32_t len = data->mRowCount * stride;
|
||||
Shmem shmem;
|
||||
if (!AllocShmem(len, ipc::Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
uint8_t* dst = shmem.get<uint8_t>();
|
||||
uint8_t* src = data->mTextureHost->GetBuffer();
|
||||
for (uint32_t row = 0; row < data->mRowCount; ++row) {
|
||||
memcpy(dst, src, stride);
|
||||
src += data->mTargetPitch;
|
||||
dst += stride;
|
||||
}
|
||||
|
||||
aShmem.emplace(std::move(shmem));
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
ipc::IPCResult WebGPUParent::RecvSwapChainPresent(
|
||||
const CompositableHandle& aHandle, RawId aTextureId,
|
||||
RawId aCommandEncoderId) {
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
#include "WebGPUTypes.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace webgpu {
|
||||
namespace mozilla::webgpu {
|
||||
class ErrorBuffer;
|
||||
class PresentationData;
|
||||
|
||||
|
@ -90,6 +89,11 @@ class WebGPUParent final : public PWebGPUParent {
|
|||
ipc::IPCResult RecvDevicePopErrorScope(
|
||||
RawId aSelfId, DevicePopErrorScopeResolver&& aResolver);
|
||||
|
||||
ipc::IPCResult GetFrontBufferSnapshot(IProtocol* aProtocol,
|
||||
const CompositableHandle& aHandle,
|
||||
Maybe<Shmem>& aShmem,
|
||||
gfx::IntSize& aSize);
|
||||
|
||||
void ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
||||
private:
|
||||
|
@ -109,7 +113,6 @@ class WebGPUParent final : public PWebGPUParent {
|
|||
std::unordered_map<uint64_t, ErrorScopeStack> mErrorScopeMap;
|
||||
};
|
||||
|
||||
} // namespace webgpu
|
||||
} // namespace mozilla
|
||||
} // namespace mozilla::webgpu
|
||||
|
||||
#endif // WEBGPU_PARENT_H_
|
||||
|
|
|
@ -134,6 +134,9 @@ interface HTMLInputElement : HTMLElement {
|
|||
[Throws]
|
||||
void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
|
||||
|
||||
[Throws, Pref="dom.input.showPicker"]
|
||||
void showPicker();
|
||||
|
||||
// also has obsolete members
|
||||
};
|
||||
|
||||
|
|
|
@ -82,6 +82,6 @@ partial interface mixin WindowOrWorkerGlobalScope {
|
|||
|
||||
// https://wicg.github.io/scheduling-apis/#ref-for-windoworworkerglobalscope-scheduler
|
||||
partial interface mixin WindowOrWorkerGlobalScope {
|
||||
[Pref="dom.enable_web_task_scheduling", SameObject]
|
||||
[Replaceable, Pref="dom.enable_web_task_scheduling", SameObject]
|
||||
readonly attribute Scheduler scheduler;
|
||||
};
|
||||
|
|
|
@ -122,7 +122,8 @@ size_t CreatePlaneDictionary(CFTypeRefPtr<CFMutableDictionaryRef>& aDict,
|
|||
/* static */
|
||||
already_AddRefed<MacIOSurface> MacIOSurface::CreateNV12OrP010Surface(
|
||||
const IntSize& aYSize, const IntSize& aCbCrSize, YUVColorSpace aColorSpace,
|
||||
ColorRange aColorRange, ColorDepth aColorDepth) {
|
||||
TransferFunction aTransferFunction, ColorRange aColorRange,
|
||||
ColorDepth aColorDepth) {
|
||||
MOZ_ASSERT(aColorSpace == YUVColorSpace::BT601 ||
|
||||
aColorSpace == YUVColorSpace::BT709 ||
|
||||
aColorSpace == YUVColorSpace::BT2020);
|
||||
|
@ -209,12 +210,10 @@ already_AddRefed<MacIOSurface> MacIOSurface::CreateNV12OrP010Surface(
|
|||
// the same. Since we are creating the IOSurface directly, we use hard-coded
|
||||
// keys derived from inspecting the extracted IOSurfaces in the copying case,
|
||||
// but we use the API-defined values from CVImageBuffer.
|
||||
|
||||
// TODO: Check the actual color primaries and transfer functions of the source
|
||||
// data and use the corresponding values, instead of just guessing good-enough
|
||||
// values based on the color space.
|
||||
#if !defined(MAC_OS_VERSION_10_13) || \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_10_13
|
||||
CFStringRef kCVImageBufferTransferFunction_ITU_R_2100_HLG =
|
||||
CFSTR("ITU_R_2100_HLG");
|
||||
CFStringRef kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ =
|
||||
CFSTR("SMPTE_ST_2084_PQ");
|
||||
#endif
|
||||
|
@ -230,16 +229,14 @@ already_AddRefed<MacIOSurface> MacIOSurface::CreateNV12OrP010Surface(
|
|||
IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceTransferFunction"),
|
||||
kCVImageBufferTransferFunction_ITU_R_709_2);
|
||||
} else {
|
||||
// TODO Bug 1764618: we shouldn't guess these color primaries and transfer
|
||||
// functions -- we should respect what the video specifies.
|
||||
IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceYCbCrMatrix"),
|
||||
kCVImageBufferYCbCrMatrix_ITU_R_2020);
|
||||
IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceColorPrimaries"),
|
||||
kCVImageBufferColorPrimaries_ITU_R_2020);
|
||||
IOSurfaceSetValue(surfaceRef.get(), CFSTR("IOSurfaceTransferFunction"),
|
||||
kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ);
|
||||
NS_WARNING(
|
||||
"Rec2020 transfer function is ambiguous, guessing PQ instead of HLG.");
|
||||
(aTransferFunction == TransferFunction::HLG)
|
||||
? kCVImageBufferTransferFunction_ITU_R_2100_HLG
|
||||
: kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ);
|
||||
}
|
||||
// Override the color space to be the same as the main display, so that
|
||||
// CoreAnimation won't try to do any color correction (from the IOSurface
|
||||
|
|
|
@ -50,6 +50,7 @@ class MacIOSurface final
|
|||
typedef mozilla::gfx::BackendType BackendType;
|
||||
typedef mozilla::gfx::IntSize IntSize;
|
||||
typedef mozilla::gfx::YUVColorSpace YUVColorSpace;
|
||||
typedef mozilla::gfx::TransferFunction TransferFunction;
|
||||
typedef mozilla::gfx::ColorRange ColorRange;
|
||||
typedef mozilla::gfx::ColorDepth ColorDepth;
|
||||
|
||||
|
@ -61,8 +62,8 @@ class MacIOSurface final
|
|||
bool aHasAlpha = true);
|
||||
static already_AddRefed<MacIOSurface> CreateNV12OrP010Surface(
|
||||
const IntSize& aYSize, const IntSize& aCbCrSize,
|
||||
YUVColorSpace aColorSpace, ColorRange aColorRange,
|
||||
ColorDepth aColorDepth);
|
||||
YUVColorSpace aColorSpace, TransferFunction aTransferFunction,
|
||||
ColorRange aColorRange, ColorDepth aColorDepth);
|
||||
static already_AddRefed<MacIOSurface> CreateYUV422Surface(
|
||||
const IntSize& aSize, YUVColorSpace aColorSpace, ColorRange aColorRange);
|
||||
static void ReleaseIOSurface(MacIOSurface* aIOSurface);
|
||||
|
|
|
@ -349,6 +349,15 @@ enum class ColorDepth : uint8_t {
|
|||
_Last = COLOR_16,
|
||||
};
|
||||
|
||||
enum class TransferFunction : uint8_t {
|
||||
SRGB,
|
||||
PQ,
|
||||
HLG,
|
||||
_First = SRGB,
|
||||
_Last = HLG,
|
||||
Default = SRGB,
|
||||
};
|
||||
|
||||
enum class ColorRange : uint8_t {
|
||||
LIMITED,
|
||||
FULL,
|
||||
|
|
|
@ -144,13 +144,15 @@ RefPtr<webgpu::WebGPUChild> CanvasManagerChild::GetWebGPUChild() {
|
|||
}
|
||||
|
||||
already_AddRefed<DataSourceSurface> CanvasManagerChild::GetSnapshot(
|
||||
uint32_t aManagerId, int32_t aProtocolId, bool aHasAlpha) {
|
||||
uint32_t aManagerId, int32_t aProtocolId,
|
||||
const layers::CompositableHandle& aHandle, SurfaceFormat aFormat,
|
||||
bool aPremultiply, bool aYFlip) {
|
||||
if (!CanSend()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
webgl::FrontBufferSnapshotIpc res;
|
||||
if (!SendGetSnapshot(aManagerId, aProtocolId, &res)) {
|
||||
if (!SendGetSnapshot(aManagerId, aProtocolId, aHandle, &res)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -178,7 +180,7 @@ already_AddRefed<DataSourceSurface> CanvasManagerChild::GetSnapshot(
|
|||
}
|
||||
|
||||
SurfaceFormat format =
|
||||
aHasAlpha ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
|
||||
IsOpaque(aFormat) ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
|
||||
RefPtr<DataSourceSurface> surface =
|
||||
Factory::CreateDataSourceSurfaceWithStride(size, format, stride.value(),
|
||||
/* aZero */ false);
|
||||
|
@ -192,21 +194,32 @@ already_AddRefed<DataSourceSurface> CanvasManagerChild::GetSnapshot(
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// The buffer we read back from WebGL is R8G8B8A8, not premultiplied and has
|
||||
// its rows inverted. For the general case, we want surfaces represented as
|
||||
// premultiplied B8G8R8A8, with its rows ordered top to bottom. Given this
|
||||
// path is used for screenshots/SurfaceFromElement, that's the representation
|
||||
// we need.
|
||||
if (aHasAlpha) {
|
||||
if (!PremultiplyYFlipData(res.shmem->get<uint8_t>(), stride.value(),
|
||||
SurfaceFormat::R8G8B8A8, map.GetData(),
|
||||
map.GetStride(), format, size)) {
|
||||
// The buffer we may readback from the canvas could be R8G8B8A8, not
|
||||
// premultiplied, and/or has its rows iverted. For the general case, we want
|
||||
// surfaces represented as premultiplied B8G8R8A8, with its rows ordered top
|
||||
// to bottom. Given this path is used for screenshots/SurfaceFromElement,
|
||||
// that's the representation we need.
|
||||
if (aYFlip) {
|
||||
if (aPremultiply) {
|
||||
if (!PremultiplyYFlipData(res.shmem->get<uint8_t>(), stride.value(),
|
||||
aFormat, map.GetData(), map.GetStride(), format,
|
||||
size)) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
if (!SwizzleYFlipData(res.shmem->get<uint8_t>(), stride.value(), aFormat,
|
||||
map.GetData(), map.GetStride(), format, size)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} else if (aPremultiply) {
|
||||
if (!PremultiplyData(res.shmem->get<uint8_t>(), stride.value(), aFormat,
|
||||
map.GetData(), map.GetStride(), format, size)) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
if (!SwizzleYFlipData(res.shmem->get<uint8_t>(), stride.value(),
|
||||
SurfaceFormat::R8G8B8X8, map.GetData(),
|
||||
map.GetStride(), format, size)) {
|
||||
if (!SwizzleData(res.shmem->get<uint8_t>(), stride.value(), aFormat,
|
||||
map.GetData(), map.GetStride(), format, size)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/gfx/PCanvasManagerChild.h"
|
||||
#include "mozilla/gfx/Types.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -29,9 +30,10 @@ class CanvasManagerChild final : public PCanvasManagerChild {
|
|||
|
||||
explicit CanvasManagerChild(uint32_t aId);
|
||||
uint32_t Id() const { return mId; }
|
||||
already_AddRefed<DataSourceSurface> GetSnapshot(uint32_t aManagerId,
|
||||
int32_t aProtocolId,
|
||||
bool aHasAlpha);
|
||||
already_AddRefed<DataSourceSurface> GetSnapshot(
|
||||
uint32_t aManagerId, int32_t aProtocolId,
|
||||
const layers::CompositableHandle& aHandle, SurfaceFormat aFormat,
|
||||
bool aPremultiply, bool aYFlip);
|
||||
void ActorDestroy(ActorDestroyReason aReason) override;
|
||||
|
||||
static CanvasManagerChild* Get();
|
||||
|
|
|
@ -111,7 +111,7 @@ mozilla::ipc::IPCResult CanvasManagerParent::RecvInitialize(
|
|||
|
||||
mozilla::ipc::IPCResult CanvasManagerParent::RecvGetSnapshot(
|
||||
const uint32_t& aManagerId, const int32_t& aProtocolId,
|
||||
webgl::FrontBufferSnapshotIpc* aResult) {
|
||||
const CompositableHandle& aHandle, webgl::FrontBufferSnapshotIpc* aResult) {
|
||||
if (!aManagerId) {
|
||||
return IPC_FAIL(this, "invalid id");
|
||||
}
|
||||
|
@ -129,16 +129,33 @@ mozilla::ipc::IPCResult CanvasManagerParent::RecvGetSnapshot(
|
|||
return IPC_FAIL(this, "invalid actor");
|
||||
}
|
||||
|
||||
if (actor->GetProtocolId() != ProtocolId::PWebGLMsgStart ||
|
||||
actor->GetSide() != mozilla::ipc::Side::ParentSide) {
|
||||
if (actor->GetSide() != mozilla::ipc::Side::ParentSide) {
|
||||
return IPC_FAIL(this, "unsupported actor");
|
||||
}
|
||||
|
||||
RefPtr<dom::WebGLParent> webgl = static_cast<dom::WebGLParent*>(actor);
|
||||
webgl::FrontBufferSnapshotIpc buffer;
|
||||
mozilla::ipc::IPCResult rv = webgl->GetFrontBufferSnapshot(&buffer, this);
|
||||
if (!rv) {
|
||||
return rv;
|
||||
switch (actor->GetProtocolId()) {
|
||||
case ProtocolId::PWebGLMsgStart: {
|
||||
RefPtr<dom::WebGLParent> webgl = static_cast<dom::WebGLParent*>(actor);
|
||||
mozilla::ipc::IPCResult rv = webgl->GetFrontBufferSnapshot(&buffer, this);
|
||||
if (!rv) {
|
||||
return rv;
|
||||
}
|
||||
} break;
|
||||
case ProtocolId::PWebGPUMsgStart: {
|
||||
RefPtr<webgpu::WebGPUParent> webgpu =
|
||||
static_cast<webgpu::WebGPUParent*>(actor);
|
||||
IntSize size;
|
||||
mozilla::ipc::IPCResult rv =
|
||||
webgpu->GetFrontBufferSnapshot(this, aHandle, buffer.shmem, size);
|
||||
if (!rv) {
|
||||
return rv;
|
||||
}
|
||||
buffer.surfSize.x = static_cast<uint32_t>(size.width);
|
||||
buffer.surfSize.y = static_cast<uint32_t>(size.height);
|
||||
} break;
|
||||
default:
|
||||
return IPC_FAIL(this, "unsupported protocol");
|
||||
}
|
||||
|
||||
*aResult = std::move(buffer);
|
||||
|
|
|
@ -30,6 +30,7 @@ class CanvasManagerParent final : public PCanvasManagerParent {
|
|||
mozilla::ipc::IPCResult RecvInitialize(const uint32_t& aId);
|
||||
mozilla::ipc::IPCResult RecvGetSnapshot(
|
||||
const uint32_t& aManagerId, const int32_t& aProtocolId,
|
||||
const CompositableHandle& aHandle,
|
||||
webgl::FrontBufferSnapshotIpc* aResult);
|
||||
|
||||
private:
|
||||
|
|
|
@ -686,6 +686,13 @@ struct ParamTraits<mozilla::gfx::ColorDepth>
|
|||
mozilla::gfx::ColorDepth, mozilla::gfx::ColorDepth::_First,
|
||||
mozilla::gfx::ColorDepth::_Last> {};
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::gfx::TransferFunction>
|
||||
: public ContiguousEnumSerializerInclusive<
|
||||
mozilla::gfx::TransferFunction,
|
||||
mozilla::gfx::TransferFunction::_First,
|
||||
mozilla::gfx::TransferFunction::_Last> {};
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::gfx::ColorRange>
|
||||
: public ContiguousEnumSerializerInclusive<
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
* 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/. */
|
||||
|
||||
include "mozilla/layers/LayersMessageUtils.h";
|
||||
include protocol PWebGL;
|
||||
include protocol PWebGPU;
|
||||
|
||||
using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h";
|
||||
using mozilla::webgl::FrontBufferSnapshotIpc from "mozilla/dom/WebGLIpdl.h";
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -39,7 +41,7 @@ parent:
|
|||
// intended to be used by the main thread in the content process to block
|
||||
// reading without having to block on the worker thread that owns the context
|
||||
// instance.
|
||||
sync GetSnapshot(uint32_t aManagerId, int32_t aProtocolId) returns (FrontBufferSnapshotIpc ret);
|
||||
sync GetSnapshot(uint32_t aManagerId, int32_t aProtocolId, CompositableHandle aHandle) returns (FrontBufferSnapshotIpc ret);
|
||||
};
|
||||
|
||||
} // gfx
|
||||
|
|
|
@ -649,6 +649,7 @@ struct PlanarYCbCrData {
|
|||
StereoMode mStereoMode = StereoMode::MONO;
|
||||
gfx::ColorDepth mColorDepth = gfx::ColorDepth::COLOR_8;
|
||||
gfx::YUVColorSpace mYUVColorSpace = gfx::YUVColorSpace::Default;
|
||||
gfx::TransferFunction mTransferFunction = gfx::TransferFunction::SRGB;
|
||||
gfx::ColorRange mColorRange = gfx::ColorRange::LIMITED;
|
||||
gfx::ChromaSubsampling mChromaSubsampling = gfx::ChromaSubsampling::FULL;
|
||||
|
||||
|
|
|
@ -71,9 +71,9 @@ bool MacIOSurfaceImage::SetData(ImageContainer* aContainer,
|
|||
|
||||
auto ySize = aData.YDataSize();
|
||||
auto cbcrSize = aData.CbCrDataSize();
|
||||
RefPtr<MacIOSurface> surf =
|
||||
allocator->Allocate(ySize, cbcrSize, aData.mYUVColorSpace,
|
||||
aData.mColorRange, aData.mColorDepth);
|
||||
RefPtr<MacIOSurface> surf = allocator->Allocate(
|
||||
ySize, cbcrSize, aData.mYUVColorSpace, aData.mTransferFunction,
|
||||
aData.mColorRange, aData.mColorDepth);
|
||||
|
||||
surf->Lock(false);
|
||||
|
||||
|
@ -200,8 +200,8 @@ bool MacIOSurfaceImage::SetData(ImageContainer* aContainer,
|
|||
|
||||
already_AddRefed<MacIOSurface> MacIOSurfaceRecycleAllocator::Allocate(
|
||||
const gfx::IntSize aYSize, const gfx::IntSize& aCbCrSize,
|
||||
gfx::YUVColorSpace aYUVColorSpace, gfx::ColorRange aColorRange,
|
||||
gfx::ColorDepth aColorDepth) {
|
||||
gfx::YUVColorSpace aYUVColorSpace, gfx::TransferFunction aTransferFunction,
|
||||
gfx::ColorRange aColorRange, gfx::ColorDepth aColorDepth) {
|
||||
nsTArray<CFTypeRefPtr<IOSurfaceRef>> surfaces = std::move(mSurfaces);
|
||||
RefPtr<MacIOSurface> result;
|
||||
for (auto& surf : surfaces) {
|
||||
|
@ -224,7 +224,8 @@ already_AddRefed<MacIOSurface> MacIOSurfaceRecycleAllocator::Allocate(
|
|||
if (!result) {
|
||||
if (StaticPrefs::layers_iosurfaceimage_use_nv12_AtStartup()) {
|
||||
result = MacIOSurface::CreateNV12OrP010Surface(
|
||||
aYSize, aCbCrSize, aYUVColorSpace, aColorRange, aColorDepth);
|
||||
aYSize, aCbCrSize, aYUVColorSpace, aTransferFunction, aColorRange,
|
||||
aColorDepth);
|
||||
} else {
|
||||
result = MacIOSurface::CreateYUV422Surface(aYSize, aYUVColorSpace,
|
||||
aColorRange);
|
||||
|
|
|
@ -54,11 +54,11 @@ class MacIOSurfaceRecycleAllocator {
|
|||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MacIOSurfaceRecycleAllocator)
|
||||
|
||||
already_AddRefed<MacIOSurface> Allocate(const gfx::IntSize aYSize,
|
||||
const gfx::IntSize& aCbCrSize,
|
||||
gfx::YUVColorSpace aYUVColorSpace,
|
||||
gfx::ColorRange aColorRange,
|
||||
gfx::ColorDepth aColorDepth);
|
||||
already_AddRefed<MacIOSurface> Allocate(
|
||||
const gfx::IntSize aYSize, const gfx::IntSize& aCbCrSize,
|
||||
gfx::YUVColorSpace aYUVColorSpace,
|
||||
gfx::TransferFunction aTransferFunction, gfx::ColorRange aColorRange,
|
||||
gfx::ColorDepth aColorDepth);
|
||||
|
||||
private:
|
||||
~MacIOSurfaceRecycleAllocator() = default;
|
||||
|
|
|
@ -2148,8 +2148,8 @@ bool AsyncPanZoomController::CanScroll(const InputData& aEvent) const {
|
|||
// to checking if it is scrollable without adjusting its delta.
|
||||
// 2. For a non-auto-dir scroll, simply check if it is scrollable without
|
||||
// adjusting its delta.
|
||||
RecursiveMutexAutoLock lock(mRecursiveMutex);
|
||||
if (scrollWheelInput.IsAutoDir(mScrollMetadata.ForceMousewheelAutodir())) {
|
||||
RecursiveMutexAutoLock lock(mRecursiveMutex);
|
||||
auto deltaX = scrollWheelInput.mDeltaX;
|
||||
auto deltaY = scrollWheelInput.mDeltaY;
|
||||
bool isRTL =
|
||||
|
@ -2309,16 +2309,18 @@ nsEventStatus AsyncPanZoomController::OnScrollWheel(
|
|||
auto deltaX = aEvent.mDeltaX;
|
||||
auto deltaY = aEvent.mDeltaY;
|
||||
ParentLayerPoint delta;
|
||||
if (aEvent.IsAutoDir(mScrollMetadata.ForceMousewheelAutodir())) {
|
||||
// It's an auto-dir scroll, so check if its delta should be adjusted, if so,
|
||||
// adjust it.
|
||||
{
|
||||
RecursiveMutexAutoLock lock(mRecursiveMutex);
|
||||
bool isRTL = IsContentOfHonouredTargetRightToLeft(
|
||||
aEvent.HonoursRoot(mScrollMetadata.ForceMousewheelAutodirHonourRoot()));
|
||||
APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
|
||||
if (adjuster.ShouldBeAdjusted()) {
|
||||
adjuster.Adjust();
|
||||
adjustedByAutoDir = true;
|
||||
if (aEvent.IsAutoDir(mScrollMetadata.ForceMousewheelAutodir())) {
|
||||
// It's an auto-dir scroll, so check if its delta should be adjusted, if
|
||||
// so, adjust it.
|
||||
bool isRTL = IsContentOfHonouredTargetRightToLeft(aEvent.HonoursRoot(
|
||||
mScrollMetadata.ForceMousewheelAutodirHonourRoot()));
|
||||
APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL);
|
||||
if (adjuster.ShouldBeAdjusted()) {
|
||||
adjuster.Adjust();
|
||||
adjustedByAutoDir = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure the calls to GetScrollWheelDelta are outside the mRecursiveMutex
|
||||
|
|
|
@ -1381,6 +1381,26 @@ const float kIdentityNarrowYCbCrToRGB_RowMajor[16] = {
|
|||
}
|
||||
}
|
||||
|
||||
// Translate from CICP values to the transfer functions we support, or return
|
||||
// Nothing() if there is no appropriate match.
|
||||
//
|
||||
/* static */ Maybe<gfx::TransferFunction> gfxUtils::CicpToTransferFunction(
|
||||
const CICP::TransferCharacteristics aTransferCharacteristics) {
|
||||
switch (aTransferCharacteristics) {
|
||||
case CICP::TransferCharacteristics::TC_SRGB:
|
||||
return Some(gfx::TransferFunction::SRGB);
|
||||
|
||||
case CICP::TransferCharacteristics::TC_SMPTE2084:
|
||||
return Some(gfx::TransferFunction::PQ);
|
||||
|
||||
case CICP::TransferCharacteristics::TC_HLG:
|
||||
return Some(gfx::TransferFunction::HLG);
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void gfxUtils::WriteAsPNG(SourceSurface* aSurface, const nsAString& aFile) {
|
||||
WriteAsPNG(aSurface, NS_ConvertUTF16toUTF8(aFile).get());
|
||||
|
|
|
@ -232,6 +232,9 @@ class gfxUtils {
|
|||
const mozilla::gfx::CICP::ColourPrimaries,
|
||||
mozilla::LazyLogModule& aLogger);
|
||||
|
||||
static mozilla::Maybe<mozilla::gfx::TransferFunction> CicpToTransferFunction(
|
||||
const mozilla::gfx::CICP::TransferCharacteristics);
|
||||
|
||||
/**
|
||||
* Creates a copy of aSurface, but having the SurfaceFormat aFormat.
|
||||
*
|
||||
|
|
|
@ -1787,6 +1787,12 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "topological-sort"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa7c7f42dea4b1b99439786f5633aeb9c14c1b53f75e282803c2ec2ad545873c"
|
||||
|
||||
[[package]]
|
||||
name = "tracy-rs"
|
||||
version = "0.1.2"
|
||||
|
@ -1982,6 +1988,7 @@ dependencies = [
|
|||
"svg_fmt",
|
||||
"swgl",
|
||||
"time",
|
||||
"topological-sort",
|
||||
"tracy-rs",
|
||||
"webrender_api",
|
||||
"webrender_build",
|
||||
|
|
|
@ -54,6 +54,7 @@ etagere = "0.2.6"
|
|||
glean = "44.0.0"
|
||||
fog = { version = "0.1.0", optional = true }
|
||||
swgl = { path = "../swgl", optional = true }
|
||||
topological-sort = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
mozangle = "0.3.3"
|
||||
|
|
|
@ -21,6 +21,7 @@ use crate::image_source::{resolve_image, resolve_cached_render_task};
|
|||
use crate::util::VecHelper;
|
||||
use smallvec::SmallVec;
|
||||
use std::mem;
|
||||
use topological_sort::TopologicalSort;
|
||||
|
||||
use crate::render_target::{RenderTargetList, ColorRenderTarget};
|
||||
use crate::render_target::{PictureCacheTarget, TextureCacheRenderTarget, AlphaRenderTarget};
|
||||
|
@ -298,36 +299,71 @@ impl RenderTaskGraphBuilder {
|
|||
unique_surfaces: FastHashSet::default(),
|
||||
};
|
||||
|
||||
// Two traversals of the graph are required. The first pass determines how many passes
|
||||
// are required, and assigns render tasks a pass to be drawn on. The second pass determines
|
||||
// when the last time a render task is used as an input, and assigns what pass the surface
|
||||
// backing that render task can be freed (the surface is then returned to the render target
|
||||
// pool and may be aliased / reused during subsequent passes).
|
||||
// First, use a topological sort of the dependency graph to split the task set in to
|
||||
// a list of passes. This is necessary because when we have a complex graph (e.g. due
|
||||
// to a large number of sibling backdrop-filter primitives) traversing it via a simple
|
||||
// recursion can be too slow. The second pass determines when the last time a render task
|
||||
// is used as an input, and assigns what pass the surface backing that render task can
|
||||
// be freed (the surface is then returned to the render target pool and may be aliased
|
||||
// or reused during subsequent passes).
|
||||
|
||||
let mut pass_count = 0;
|
||||
let mut passes = Vec::new();
|
||||
let mut task_sorter = TopologicalSort::<RenderTaskId>::new();
|
||||
|
||||
// Traverse each root, and assign `render_on` for each task and count number of required passes
|
||||
for root_id in &self.roots {
|
||||
assign_render_pass(
|
||||
*root_id,
|
||||
PassId(0),
|
||||
&mut graph,
|
||||
&mut pass_count,
|
||||
);
|
||||
// Iterate the task list, and add all the dependencies to the topo sort
|
||||
for (parent_id, task) in graph.tasks.iter().enumerate() {
|
||||
let parent_id = RenderTaskId { index: parent_id as u32 };
|
||||
|
||||
for child_id in &task.children {
|
||||
task_sorter.add_dependency(
|
||||
parent_id,
|
||||
*child_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Pop the sorted passes off the topological sort
|
||||
loop {
|
||||
// Get the next set of tasks that can be drawn
|
||||
let tasks = task_sorter.pop_all();
|
||||
|
||||
// If there are no tasks left, we're done
|
||||
if tasks.is_empty() {
|
||||
// If the task sorter itself isn't empty but we couldn't pop off any
|
||||
// tasks, that implies a circular dependency in the task graph
|
||||
assert!(task_sorter.is_empty());
|
||||
break;
|
||||
} else {
|
||||
// Assign the `render_on` field to the task
|
||||
for task_id in &tasks {
|
||||
graph.tasks[task_id.index as usize].render_on = PassId(pass_count);
|
||||
}
|
||||
|
||||
// Store the task list for this pass, used later for `assign_free_pass`.
|
||||
passes.push(tasks);
|
||||
pass_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Always create at least one pass for root tasks
|
||||
pass_count = pass_count.max(1);
|
||||
|
||||
// Determine which pass each task can be freed on, which depends on which is
|
||||
// the last task that has this as an input.
|
||||
for root_id in &self.roots {
|
||||
assign_free_pass(
|
||||
*root_id,
|
||||
PassId(0),
|
||||
&mut graph,
|
||||
);
|
||||
// the last task that has this as an input. This must be done in top-down
|
||||
// pass order to ensure that RenderTaskLocation::Existing references are
|
||||
// visited in the correct order
|
||||
for pass in passes {
|
||||
for task_id in pass {
|
||||
assign_free_pass(
|
||||
task_id,
|
||||
&mut graph,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Construct passes array for tasks to be assigned to below
|
||||
for _ in 0 .. pass_count+1 {
|
||||
for _ in 0 .. pass_count {
|
||||
graph.passes.push(Pass {
|
||||
task_ids: Vec::new(),
|
||||
sub_passes: Vec::new(),
|
||||
|
@ -690,70 +726,13 @@ impl std::ops::Index<RenderTaskId> for RenderTaskGraph {
|
|||
}
|
||||
}
|
||||
|
||||
/// Recursive helper to assign pass that a task should render on
|
||||
fn assign_render_pass(
|
||||
id: RenderTaskId,
|
||||
pass: PassId,
|
||||
graph: &mut RenderTaskGraph,
|
||||
pass_count: &mut usize,
|
||||
) {
|
||||
let task = &mut graph.tasks[id.index as usize];
|
||||
|
||||
// No point in recursing into paths in the graph if this task already
|
||||
// has been set to draw after this pass.
|
||||
if task.render_on > pass {
|
||||
return;
|
||||
}
|
||||
|
||||
let next_pass = if task.kind.should_advance_pass() {
|
||||
// Keep count of number of passes needed
|
||||
*pass_count = pass.0.max(*pass_count);
|
||||
PassId(pass.0 + 1)
|
||||
} else {
|
||||
pass
|
||||
};
|
||||
|
||||
// A task should be rendered on the earliest pass in the dependency
|
||||
// graph that it's required. Using max here ensures the correct value
|
||||
// in the presence of multiple paths to this task from the root(s).
|
||||
task.render_on = task.render_on.max(pass);
|
||||
|
||||
// TODO(gw): Work around the borrowck - maybe we could structure the dependencies
|
||||
// storage better, to avoid this?
|
||||
let mut child_task_ids: SmallVec<[RenderTaskId; 8]> = SmallVec::new();
|
||||
child_task_ids.extend_from_slice(&task.children);
|
||||
|
||||
for child_id in child_task_ids {
|
||||
assign_render_pass(
|
||||
child_id,
|
||||
next_pass,
|
||||
graph,
|
||||
pass_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn assign_free_pass(
|
||||
id: RenderTaskId,
|
||||
pass: PassId,
|
||||
graph: &mut RenderTaskGraph,
|
||||
) {
|
||||
let task = &mut graph.tasks[id.index as usize];
|
||||
|
||||
// No point in recursing into paths in the graph if this task already
|
||||
// has been set to free before this pass.
|
||||
if task.free_after.0 + 1 < pass.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let render_on = task.render_on;
|
||||
|
||||
let next_pass = if task.kind.should_advance_pass() {
|
||||
PassId(pass.0 + 1)
|
||||
} else {
|
||||
pass
|
||||
};
|
||||
|
||||
let mut child_task_ids: SmallVec<[RenderTaskId; 8]> = SmallVec::new();
|
||||
child_task_ids.extend_from_slice(&task.children);
|
||||
|
||||
|
@ -791,12 +770,6 @@ fn assign_free_pass(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
assign_free_pass(
|
||||
child_id,
|
||||
next_pass,
|
||||
graph,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -322,6 +322,21 @@ void IProtocol::Unregister(int32_t aId) {
|
|||
return mToplevel->Unregister(aId);
|
||||
}
|
||||
|
||||
Shmem::SharedMemory* IProtocol::CreateSharedMemory(
|
||||
size_t aSize, SharedMemory::SharedMemoryType aType, bool aUnsafe,
|
||||
int32_t* aId) {
|
||||
return mToplevel->CreateSharedMemory(aSize, aType, aUnsafe, aId);
|
||||
}
|
||||
Shmem::SharedMemory* IProtocol::LookupSharedMemory(int32_t aId) {
|
||||
return mToplevel->LookupSharedMemory(aId);
|
||||
}
|
||||
bool IProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* aSegment) {
|
||||
return mToplevel->IsTrackingSharedMemory(aSegment);
|
||||
}
|
||||
bool IProtocol::DestroySharedMemory(Shmem& aShmem) {
|
||||
return mToplevel->DestroySharedMemory(aShmem);
|
||||
}
|
||||
|
||||
MessageChannel* IProtocol::GetIPCChannel() {
|
||||
return mToplevel->GetIPCChannel();
|
||||
}
|
||||
|
@ -393,8 +408,14 @@ bool IProtocol::AllocShmem(size_t aSize,
|
|||
return false;
|
||||
}
|
||||
|
||||
*aOutMem = Shmem(aSize, aType, false);
|
||||
return aOutMem->IsValid();
|
||||
Shmem::id_t id;
|
||||
Shmem::SharedMemory* rawmem(CreateSharedMemory(aSize, aType, false, &id));
|
||||
if (!rawmem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*aOutMem = Shmem(Shmem::PrivateIPDLCaller(), rawmem, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IProtocol::AllocUnsafeShmem(size_t aSize,
|
||||
|
@ -406,13 +427,30 @@ bool IProtocol::AllocUnsafeShmem(size_t aSize,
|
|||
return false;
|
||||
}
|
||||
|
||||
*aOutMem = Shmem(aSize, aType, true);
|
||||
return aOutMem->IsValid();
|
||||
Shmem::id_t id;
|
||||
Shmem::SharedMemory* rawmem(CreateSharedMemory(aSize, aType, true, &id));
|
||||
if (!rawmem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*aOutMem = Shmem(Shmem::PrivateIPDLCaller(), rawmem, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IProtocol::DeallocShmem(Shmem& aMem) {
|
||||
aMem.RevokeRights();
|
||||
return true;
|
||||
bool ok = DestroySharedMemory(aMem);
|
||||
#ifdef DEBUG
|
||||
if (!ok) {
|
||||
if (mSide == ChildSide) {
|
||||
FatalError("bad Shmem");
|
||||
} else {
|
||||
NS_WARNING("bad Shmem");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // DEBUG
|
||||
aMem.forget(Shmem::PrivateIPDLCaller());
|
||||
return ok;
|
||||
}
|
||||
|
||||
void IProtocol::SetManager(IProtocol* aManager) {
|
||||
|
@ -629,6 +667,104 @@ void IToplevelProtocol::Unregister(int32_t aId) {
|
|||
mActorMap.Remove(aId);
|
||||
}
|
||||
|
||||
Shmem::SharedMemory* IToplevelProtocol::CreateSharedMemory(
|
||||
size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, bool aUnsafe,
|
||||
Shmem::id_t* aId) {
|
||||
RefPtr<Shmem::SharedMemory> segment(
|
||||
Shmem::Alloc(Shmem::PrivateIPDLCaller(), aSize, aType, aUnsafe));
|
||||
if (!segment) {
|
||||
return nullptr;
|
||||
}
|
||||
int32_t id = NextId();
|
||||
Shmem shmem(Shmem::PrivateIPDLCaller(), segment.get(), id);
|
||||
|
||||
UniquePtr<Message> descriptor =
|
||||
shmem.MkCreatedMessage(Shmem::PrivateIPDLCaller(), MSG_ROUTING_CONTROL);
|
||||
if (!descriptor) {
|
||||
return nullptr;
|
||||
}
|
||||
Unused << GetIPCChannel()->Send(std::move(descriptor));
|
||||
|
||||
*aId = shmem.Id(Shmem::PrivateIPDLCaller());
|
||||
Shmem::SharedMemory* rawSegment = segment.get();
|
||||
MOZ_ASSERT(!mShmemMap.Contains(*aId), "Don't insert with an existing ID");
|
||||
mShmemMap.InsertOrUpdate(*aId, segment.forget().take());
|
||||
return rawSegment;
|
||||
}
|
||||
|
||||
Shmem::SharedMemory* IToplevelProtocol::LookupSharedMemory(Shmem::id_t aId) {
|
||||
return mShmemMap.Get(aId);
|
||||
}
|
||||
|
||||
bool IToplevelProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* segment) {
|
||||
for (const auto& shmem : mShmemMap.Values()) {
|
||||
if (segment == shmem) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IToplevelProtocol::DestroySharedMemory(Shmem& shmem) {
|
||||
Shmem::id_t aId = shmem.Id(Shmem::PrivateIPDLCaller());
|
||||
Shmem::SharedMemory* segment = LookupSharedMemory(aId);
|
||||
if (!segment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UniquePtr<Message> descriptor =
|
||||
shmem.MkDestroyedMessage(Shmem::PrivateIPDLCaller(), MSG_ROUTING_CONTROL);
|
||||
|
||||
MOZ_ASSERT(mShmemMap.Contains(aId),
|
||||
"Attempting to remove an ID not in the shmem map");
|
||||
mShmemMap.Remove(aId);
|
||||
Shmem::Dealloc(Shmem::PrivateIPDLCaller(), segment);
|
||||
|
||||
MessageChannel* channel = GetIPCChannel();
|
||||
if (!channel->CanSend()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return descriptor && channel->Send(std::move(descriptor));
|
||||
}
|
||||
|
||||
void IToplevelProtocol::DeallocShmems() {
|
||||
for (const auto& shmem : mShmemMap.Values()) {
|
||||
Shmem::Dealloc(Shmem::PrivateIPDLCaller(), shmem);
|
||||
}
|
||||
mShmemMap.Clear();
|
||||
}
|
||||
|
||||
bool IToplevelProtocol::ShmemCreated(const Message& aMsg) {
|
||||
Shmem::id_t id;
|
||||
RefPtr<Shmem::SharedMemory> rawmem(
|
||||
Shmem::OpenExisting(Shmem::PrivateIPDLCaller(), aMsg, &id, true));
|
||||
if (!rawmem) {
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(!mShmemMap.Contains(id), "Don't insert with an existing ID");
|
||||
mShmemMap.InsertOrUpdate(id, rawmem.forget().take());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IToplevelProtocol::ShmemDestroyed(const Message& aMsg) {
|
||||
Shmem::id_t id;
|
||||
MessageReader reader(aMsg);
|
||||
if (!IPC::ReadParam(&reader, &id)) {
|
||||
return false;
|
||||
}
|
||||
reader.EndRead();
|
||||
|
||||
Shmem::SharedMemory* rawmem = LookupSharedMemory(id);
|
||||
if (rawmem) {
|
||||
MOZ_ASSERT(mShmemMap.Contains(id),
|
||||
"Attempting to remove an ID not in the shmem map");
|
||||
mShmemMap.Remove(id);
|
||||
Shmem::Dealloc(Shmem::PrivateIPDLCaller(), rawmem);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
IPDLResolverInner::IPDLResolverInner(UniquePtr<IPC::Message> aReply,
|
||||
IProtocol* aActor)
|
||||
: mReply(std::move(aReply)),
|
||||
|
|
|
@ -73,8 +73,8 @@ enum {
|
|||
BUILD_IDS_MATCH_MESSAGE_TYPE = kuint16max - 8,
|
||||
BUILD_ID_MESSAGE_TYPE = kuint16max - 7, // unused
|
||||
CHANNEL_OPENED_MESSAGE_TYPE = kuint16max - 6,
|
||||
SHMEM_DESTROYED_MESSAGE_TYPE = kuint16max - 5, // unused
|
||||
SHMEM_CREATED_MESSAGE_TYPE = kuint16max - 4, // unused
|
||||
SHMEM_DESTROYED_MESSAGE_TYPE = kuint16max - 5,
|
||||
SHMEM_CREATED_MESSAGE_TYPE = kuint16max - 4,
|
||||
GOODBYE_MESSAGE_TYPE = kuint16max - 3,
|
||||
CANCEL_MESSAGE_TYPE = kuint16max - 2,
|
||||
|
||||
|
@ -198,6 +198,13 @@ class IProtocol : public HasResultCodes {
|
|||
IProtocol* Lookup(int32_t aId);
|
||||
void Unregister(int32_t aId);
|
||||
|
||||
Shmem::SharedMemory* CreateSharedMemory(size_t aSize,
|
||||
SharedMemory::SharedMemoryType aType,
|
||||
bool aUnsafe, int32_t* aId);
|
||||
Shmem::SharedMemory* LookupSharedMemory(int32_t aId);
|
||||
bool IsTrackingSharedMemory(Shmem::SharedMemory* aSegment);
|
||||
bool DestroySharedMemory(Shmem& aShmem);
|
||||
|
||||
MessageChannel* GetIPCChannel();
|
||||
const MessageChannel* GetIPCChannel() const;
|
||||
|
||||
|
@ -392,6 +399,13 @@ class IToplevelProtocol : public IProtocol {
|
|||
IProtocol* Lookup(int32_t aId);
|
||||
void Unregister(int32_t aId);
|
||||
|
||||
Shmem::SharedMemory* CreateSharedMemory(size_t aSize,
|
||||
SharedMemory::SharedMemoryType aType,
|
||||
bool aUnsafe, int32_t* aId);
|
||||
Shmem::SharedMemory* LookupSharedMemory(int32_t aId);
|
||||
bool IsTrackingSharedMemory(Shmem::SharedMemory* aSegment);
|
||||
bool DestroySharedMemory(Shmem& aShmem);
|
||||
|
||||
MessageChannel* GetIPCChannel() { return &mChannel; }
|
||||
const MessageChannel* GetIPCChannel() const { return &mChannel; }
|
||||
|
||||
|
@ -425,6 +439,10 @@ class IToplevelProtocol : public IProtocol {
|
|||
|
||||
void SetReplyTimeoutMs(int32_t aTimeoutMs);
|
||||
|
||||
void DeallocShmems();
|
||||
bool ShmemCreated(const Message& aMsg);
|
||||
bool ShmemDestroyed(const Message& aMsg);
|
||||
|
||||
virtual bool ShouldContinueFromReplyTimeout() { return false; }
|
||||
|
||||
// WARNING: This function is called with the MessageChannel monitor held.
|
||||
|
@ -481,6 +499,7 @@ class IToplevelProtocol : public IProtocol {
|
|||
// Used to be on mState
|
||||
int32_t mLastLocalId;
|
||||
IDMap<IProtocol*> mActorMap;
|
||||
IDMap<Shmem::SharedMemory*> mShmemMap;
|
||||
|
||||
MessageChannel mChannel;
|
||||
};
|
||||
|
|
|
@ -9,16 +9,176 @@
|
|||
#include "ProtocolUtils.h"
|
||||
#include "SharedMemoryBasic.h"
|
||||
#include "ShmemMessageUtils.h"
|
||||
#include "chrome/common/ipc_message_utils.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
namespace mozilla::ipc {
|
||||
namespace mozilla {
|
||||
namespace ipc {
|
||||
|
||||
class ShmemCreated : public IPC::Message {
|
||||
private:
|
||||
typedef Shmem::id_t id_t;
|
||||
|
||||
public:
|
||||
ShmemCreated(int32_t routingId, id_t aIPDLId, size_t aSize,
|
||||
SharedMemory::SharedMemoryType aType)
|
||||
: IPC::Message(routingId, SHMEM_CREATED_MESSAGE_TYPE, 0,
|
||||
HeaderFlags(NESTED_INSIDE_CPOW)) {
|
||||
MOZ_RELEASE_ASSERT(aSize < std::numeric_limits<uint32_t>::max(),
|
||||
"Tried to create Shmem with size larger than 4GB");
|
||||
IPC::MessageWriter writer(*this);
|
||||
IPC::WriteParam(&writer, aIPDLId);
|
||||
IPC::WriteParam(&writer, uint32_t(aSize));
|
||||
IPC::WriteParam(&writer, int32_t(aType));
|
||||
}
|
||||
|
||||
static bool ReadInfo(IPC::MessageReader* aReader, id_t* aIPDLId,
|
||||
size_t* aSize, SharedMemory::SharedMemoryType* aType) {
|
||||
uint32_t size = 0;
|
||||
if (!IPC::ReadParam(aReader, aIPDLId) || !IPC::ReadParam(aReader, &size) ||
|
||||
!IPC::ReadParam(aReader, reinterpret_cast<int32_t*>(aType))) {
|
||||
return false;
|
||||
}
|
||||
*aSize = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Log(const std::string& aPrefix, FILE* aOutf) const {
|
||||
fputs("(special ShmemCreated msg)", aOutf);
|
||||
}
|
||||
};
|
||||
|
||||
class ShmemDestroyed : public IPC::Message {
|
||||
private:
|
||||
typedef Shmem::id_t id_t;
|
||||
|
||||
public:
|
||||
ShmemDestroyed(int32_t routingId, id_t aIPDLId)
|
||||
: IPC::Message(routingId, SHMEM_DESTROYED_MESSAGE_TYPE) {
|
||||
IPC::MessageWriter writer(*this);
|
||||
IPC::WriteParam(&writer, aIPDLId);
|
||||
}
|
||||
};
|
||||
|
||||
static SharedMemory* NewSegment(SharedMemory::SharedMemoryType aType) {
|
||||
if (SharedMemory::TYPE_BASIC == aType) {
|
||||
return new SharedMemoryBasic;
|
||||
} else {
|
||||
NS_ERROR("unknown Shmem type");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static already_AddRefed<SharedMemory> CreateSegment(
|
||||
SharedMemory::SharedMemoryType aType, size_t aNBytes, size_t aExtraSize) {
|
||||
RefPtr<SharedMemory> segment = NewSegment(aType);
|
||||
if (!segment) {
|
||||
return nullptr;
|
||||
}
|
||||
size_t size = SharedMemory::PageAlignedSize(aNBytes + aExtraSize);
|
||||
if (!segment->Create(size) || !segment->Map(size)) {
|
||||
return nullptr;
|
||||
}
|
||||
return segment.forget();
|
||||
}
|
||||
|
||||
static already_AddRefed<SharedMemory> ReadSegment(
|
||||
const IPC::Message& aDescriptor, Shmem::id_t* aId, size_t* aNBytes,
|
||||
size_t aExtraSize) {
|
||||
if (SHMEM_CREATED_MESSAGE_TYPE != aDescriptor.type()) {
|
||||
NS_ERROR("expected 'shmem created' message");
|
||||
return nullptr;
|
||||
}
|
||||
SharedMemory::SharedMemoryType type;
|
||||
IPC::MessageReader reader(aDescriptor);
|
||||
if (!ShmemCreated::ReadInfo(&reader, aId, aNBytes, &type)) {
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<SharedMemory> segment = NewSegment(type);
|
||||
if (!segment) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!segment->ReadHandle(&reader)) {
|
||||
NS_ERROR("trying to open invalid handle");
|
||||
return nullptr;
|
||||
}
|
||||
reader.EndRead();
|
||||
size_t size = SharedMemory::PageAlignedSize(*aNBytes + aExtraSize);
|
||||
if (!segment->Map(size)) {
|
||||
return nullptr;
|
||||
}
|
||||
// close the handle to the segment after it is mapped
|
||||
segment->CloseHandle();
|
||||
return segment.forget();
|
||||
}
|
||||
|
||||
static void DestroySegment(SharedMemory* aSegment) {
|
||||
// the SharedMemory dtor closes and unmaps the actual OS shmem segment
|
||||
if (aSegment) {
|
||||
aSegment->Release();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(DEBUG)
|
||||
|
||||
static const char sMagic[] =
|
||||
"This little piggy went to market.\n"
|
||||
"This little piggy stayed at home.\n"
|
||||
"This little piggy has roast beef,\n"
|
||||
"This little piggy had none.\n"
|
||||
"And this little piggy cried \"Wee! Wee! Wee!\" all the way home";
|
||||
|
||||
struct Header {
|
||||
// Don't use size_t or bool here because their size depends on the
|
||||
// architecture.
|
||||
uint32_t mSize;
|
||||
uint32_t mUnsafe;
|
||||
char mMagic[sizeof(sMagic)];
|
||||
};
|
||||
|
||||
static void GetSections(Shmem::SharedMemory* aSegment, Header** aHeader,
|
||||
char** aFrontSentinel, char** aData,
|
||||
char** aBackSentinel) {
|
||||
MOZ_ASSERT(aSegment && aFrontSentinel && aData && aBackSentinel,
|
||||
"null param(s)");
|
||||
|
||||
*aFrontSentinel = reinterpret_cast<char*>(aSegment->memory());
|
||||
MOZ_ASSERT(*aFrontSentinel, "null memory()");
|
||||
|
||||
*aHeader = reinterpret_cast<Header*>(*aFrontSentinel);
|
||||
|
||||
size_t pageSize = Shmem::SharedMemory::SystemPageSize();
|
||||
*aData = *aFrontSentinel + pageSize;
|
||||
|
||||
*aBackSentinel = *aFrontSentinel + aSegment->Size() - pageSize;
|
||||
}
|
||||
|
||||
static Header* GetHeader(Shmem::SharedMemory* aSegment) {
|
||||
Header* header;
|
||||
char* dontcare;
|
||||
GetSections(aSegment, &header, &dontcare, &dontcare, &dontcare);
|
||||
return header;
|
||||
}
|
||||
|
||||
static void Protect(SharedMemory* aSegment) {
|
||||
MOZ_ASSERT(aSegment, "null segment");
|
||||
aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()),
|
||||
aSegment->Size(), RightsNone);
|
||||
}
|
||||
|
||||
static void Unprotect(SharedMemory* aSegment) {
|
||||
MOZ_ASSERT(aSegment, "null segment");
|
||||
aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()),
|
||||
aSegment->Size(), RightsRead | RightsWrite);
|
||||
}
|
||||
|
||||
//
|
||||
// In debug builds, we specially allocate shmem segments. The layout
|
||||
// is as follows
|
||||
//
|
||||
// Page 0: "front sentinel"
|
||||
// [nothing]
|
||||
// size of mapping
|
||||
// magic bytes
|
||||
// Page 1 through n-1:
|
||||
// user data
|
||||
// Page n: "back sentinel"
|
||||
|
@ -47,6 +207,11 @@ namespace mozilla::ipc {
|
|||
// The receiving process will then create a Shmem from the underlying
|
||||
// segment, and take the segment into the "mapped" state.
|
||||
//
|
||||
// In the "mapping" state, we use the front sentinel to verify the
|
||||
// integrity of the shmem segment. If valid, it has a size_t
|
||||
// containing the number of bytes the user allocated followed by the
|
||||
// magic bytes above.
|
||||
//
|
||||
// In the "mapped" state, the front and back sentinels have no access
|
||||
// rights. They act as guards against buffer overflows and underflows
|
||||
// in client code; if clients touch a sentinel, they die with SIGSEGV.
|
||||
|
@ -56,164 +221,256 @@ namespace mozilla::ipc {
|
|||
// to touch the segment, it dies with SIGSEGV.
|
||||
//
|
||||
|
||||
static size_t MappingSize(size_t aNBytes) {
|
||||
Shmem::Shmem(PrivateIPDLCaller, SharedMemory* aSegment, id_t aId)
|
||||
: mSegment(aSegment), mData(nullptr), mSize(0) {
|
||||
MOZ_ASSERT(mSegment, "null segment");
|
||||
MOZ_ASSERT(aId != 0, "invalid ID");
|
||||
|
||||
Unprotect(mSegment);
|
||||
|
||||
Header* header;
|
||||
char* frontSentinel;
|
||||
char* data;
|
||||
char* backSentinel;
|
||||
GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
|
||||
|
||||
// do a quick validity check to avoid weird-looking crashes in libc
|
||||
char check = *frontSentinel;
|
||||
(void)check;
|
||||
|
||||
MOZ_ASSERT(!strncmp(header->mMagic, sMagic, sizeof(sMagic)),
|
||||
"invalid segment");
|
||||
mSize = static_cast<size_t>(header->mSize);
|
||||
|
||||
size_t pageSize = SharedMemory::SystemPageSize();
|
||||
MOZ_ASSERT(IsPowerOfTwo(pageSize));
|
||||
MOZ_ASSERT(mSegment->Size() - (2 * pageSize) >= mSize,
|
||||
"illegal size in shared memory segment");
|
||||
|
||||
// Extra padding required to align to pagesize.
|
||||
size_t diff = aNBytes & (pageSize - 1);
|
||||
diff = (pageSize - diff) & (pageSize - 1);
|
||||
// transition into the "mapped" state by protecting the front and
|
||||
// back sentinels (which guard against buffer under/overflows)
|
||||
mSegment->Protect(frontSentinel, pageSize, RightsNone);
|
||||
mSegment->Protect(backSentinel, pageSize, RightsNone);
|
||||
|
||||
CheckedInt<size_t> totalSize = aNBytes;
|
||||
totalSize += diff;
|
||||
|
||||
#ifdef DEBUG
|
||||
// Allocate 2 extra pages to act as guard pages for the shared memory region
|
||||
// in debug-mode. No extra space is allocated in release mode.
|
||||
totalSize += 2 * pageSize;
|
||||
#endif
|
||||
|
||||
MOZ_RELEASE_ASSERT(totalSize.isValid());
|
||||
return totalSize.value();
|
||||
// don't set these until we know they're valid
|
||||
mData = data;
|
||||
mId = aId;
|
||||
}
|
||||
|
||||
static Span<char> ConfigureAndGetData(SharedMemory* aSegment) {
|
||||
Span<char> memory{reinterpret_cast<char*>(aSegment->memory()),
|
||||
aSegment->Size()};
|
||||
#ifdef DEBUG
|
||||
size_t pageSize = SharedMemory::SystemPageSize();
|
||||
auto [frontSentinel, suffix] = memory.SplitAt(pageSize);
|
||||
auto [data, backSentinel] = memory.SplitAt(suffix.Length() - pageSize);
|
||||
|
||||
// The sentinel memory regions should be non-readable and non-writable,
|
||||
// whereas the data region needs to be readable & writable.
|
||||
aSegment->Protect(frontSentinel.data(), frontSentinel.size(), RightsNone);
|
||||
aSegment->Protect(data.data(), data.size(), RightsRead | RightsWrite);
|
||||
aSegment->Protect(backSentinel.data(), backSentinel.size(), RightsNone);
|
||||
return data;
|
||||
#else
|
||||
return memory;
|
||||
#endif
|
||||
}
|
||||
|
||||
static RefPtr<SharedMemory> AllocSegment(size_t aNBytes,
|
||||
SharedMemory::SharedMemoryType aType) {
|
||||
size_t mappingSize = MappingSize(aNBytes);
|
||||
|
||||
MOZ_RELEASE_ASSERT(aType == SharedMemory::TYPE_BASIC,
|
||||
"Unknown SharedMemoryType!");
|
||||
RefPtr<SharedMemory> segment = new SharedMemoryBasic;
|
||||
if (!segment->Create(mappingSize) || !segment->Map(mappingSize)) {
|
||||
NS_WARNING("Failed to create or map segment");
|
||||
return nullptr;
|
||||
}
|
||||
return segment;
|
||||
}
|
||||
|
||||
Shmem::Shmem(size_t aNBytes, SharedMemoryType aType, bool aUnsafe)
|
||||
: Shmem(AllocSegment(aNBytes, aType), aNBytes, aUnsafe) {}
|
||||
|
||||
Shmem::Shmem(RefPtr<SharedMemory> aSegment, size_t aNBytes, bool aUnsafe) {
|
||||
if (!aSegment) {
|
||||
return;
|
||||
}
|
||||
size_t mappingSize = MappingSize(aNBytes);
|
||||
if (mappingSize != aSegment->Size()) {
|
||||
NS_WARNING("Segment has an incorrect size");
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = ConfigureAndGetData(aSegment);
|
||||
MOZ_RELEASE_ASSERT(data.size() >= aNBytes);
|
||||
|
||||
mSegment = aSegment;
|
||||
mData = data.data();
|
||||
mSize = aNBytes;
|
||||
#ifdef DEBUG
|
||||
mUnsafe = aUnsafe;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
void Shmem::AssertInvariants() const {
|
||||
MOZ_ASSERT(mSegment, "null segment");
|
||||
MOZ_ASSERT(mData, "null data pointer");
|
||||
MOZ_ASSERT(mSize > 0, "invalid size");
|
||||
MOZ_ASSERT(MappingSize(mSize) == mSegment->Size(),
|
||||
"size doesn't match segment");
|
||||
|
||||
// if the segment isn't owned by the current process, these will
|
||||
// trigger SIGSEGV
|
||||
*reinterpret_cast<volatile char*>(mData);
|
||||
*(reinterpret_cast<volatile char*>(mData) + mSize - 1);
|
||||
char checkMappingFront = *reinterpret_cast<char*>(mData);
|
||||
char checkMappingBack = *(reinterpret_cast<char*>(mData) + mSize - 1);
|
||||
|
||||
// avoid "unused" warnings for these variables:
|
||||
Unused << checkMappingFront;
|
||||
Unused << checkMappingBack;
|
||||
}
|
||||
|
||||
void Shmem::RevokeRights() {
|
||||
void Shmem::RevokeRights(PrivateIPDLCaller) {
|
||||
AssertInvariants();
|
||||
|
||||
// If we're not working with an "unsafe" shared memory, revoke access rights
|
||||
// to the entire shared memory region.
|
||||
if (!mUnsafe) {
|
||||
mSegment->Protect(reinterpret_cast<char*>(mSegment->memory()),
|
||||
mSegment->Size(), RightsNone);
|
||||
size_t pageSize = SharedMemory::SystemPageSize();
|
||||
Header* header = GetHeader(mSegment);
|
||||
|
||||
// Open this up for reading temporarily
|
||||
mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsRead);
|
||||
|
||||
if (!header->mUnsafe) {
|
||||
Protect(mSegment);
|
||||
} else {
|
||||
mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsNone);
|
||||
}
|
||||
|
||||
*this = Shmem();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mozilla::ipc
|
||||
|
||||
namespace IPC {
|
||||
|
||||
void ParamTraits<mozilla::ipc::Shmem>::Write(MessageWriter* aWriter,
|
||||
paramType&& aParam) {
|
||||
aParam.AssertInvariants();
|
||||
|
||||
MOZ_ASSERT(aParam.mSegment->Type() ==
|
||||
mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC,
|
||||
"Only supported type is TYPE_BASIC");
|
||||
|
||||
WriteParam(aWriter, uint64_t(aParam.mSize));
|
||||
aParam.mSegment->WriteHandle(aWriter);
|
||||
#ifdef DEBUG
|
||||
WriteParam(aWriter, aParam.mUnsafe);
|
||||
#endif
|
||||
|
||||
aParam.RevokeRights();
|
||||
}
|
||||
|
||||
bool ParamTraits<mozilla::ipc::Shmem>::Read(MessageReader* aReader,
|
||||
paramType* aResult) {
|
||||
*aResult = mozilla::ipc::Shmem();
|
||||
// static
|
||||
already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(PrivateIPDLCaller,
|
||||
size_t aNBytes,
|
||||
SharedMemoryType aType,
|
||||
bool aUnsafe,
|
||||
bool aProtect) {
|
||||
NS_ASSERTION(aNBytes <= UINT32_MAX, "Will truncate shmem segment size!");
|
||||
MOZ_ASSERT(!aProtect || !aUnsafe, "protect => !unsafe");
|
||||
|
||||
// Size is sent as uint64_t to deal with IPC between processes with different
|
||||
// `size_t` sizes.
|
||||
uint64_t rawSize = 0;
|
||||
if (!ReadParam(aReader, &rawSize)) {
|
||||
return false;
|
||||
}
|
||||
mozilla::CheckedInt<size_t> size{rawSize};
|
||||
if (!size.isValid() || size == 0) {
|
||||
return false;
|
||||
size_t pageSize = SharedMemory::SystemPageSize();
|
||||
// |2*pageSize| is for the front and back sentinel
|
||||
RefPtr<SharedMemory> segment = CreateSegment(aType, aNBytes, 2 * pageSize);
|
||||
if (!segment) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<mozilla::ipc::SharedMemory> segment =
|
||||
new mozilla::ipc::SharedMemoryBasic;
|
||||
if (!segment->ReadHandle(aReader) ||
|
||||
!segment->Map(mozilla::ipc::MappingSize(size.value()))) {
|
||||
return false;
|
||||
}
|
||||
Header* header;
|
||||
char* frontSentinel;
|
||||
char* data;
|
||||
char* backSentinel;
|
||||
GetSections(segment, &header, &frontSentinel, &data, &backSentinel);
|
||||
|
||||
bool unsafe = false;
|
||||
#ifdef DEBUG
|
||||
if (!ReadParam(aReader, &unsafe)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// initialize the segment with Shmem-internal information
|
||||
|
||||
*aResult = mozilla::ipc::Shmem(segment, size.value(), unsafe);
|
||||
return aResult->IsValid();
|
||||
// NB: this can't be a static assert because technically pageSize
|
||||
// isn't known at compile time, event though in practice it's always
|
||||
// going to be 4KiB
|
||||
MOZ_ASSERT(sizeof(Header) <= pageSize, "Shmem::Header has gotten too big");
|
||||
memcpy(header->mMagic, sMagic, sizeof(sMagic));
|
||||
header->mSize = static_cast<uint32_t>(aNBytes);
|
||||
header->mUnsafe = aUnsafe;
|
||||
|
||||
if (aProtect) Protect(segment);
|
||||
|
||||
return segment.forget();
|
||||
}
|
||||
|
||||
} // namespace IPC
|
||||
// static
|
||||
already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting(
|
||||
PrivateIPDLCaller, const IPC::Message& aDescriptor, id_t* aId,
|
||||
bool aProtect) {
|
||||
size_t size;
|
||||
size_t pageSize = SharedMemory::SystemPageSize();
|
||||
// |2*pageSize| is for the front and back sentinels
|
||||
RefPtr<SharedMemory> segment =
|
||||
ReadSegment(aDescriptor, aId, &size, 2 * pageSize);
|
||||
if (!segment) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Header* header = GetHeader(segment);
|
||||
|
||||
if (size != header->mSize) {
|
||||
// Deallocation should zero out the header, so check for that.
|
||||
if (header->mSize || header->mUnsafe || header->mMagic[0] ||
|
||||
memcmp(header->mMagic, &header->mMagic[1],
|
||||
sizeof(header->mMagic) - 1)) {
|
||||
NS_ERROR("Wrong size for this Shmem!");
|
||||
} else {
|
||||
NS_WARNING("Shmem was deallocated");
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The caller of this function may not know whether the segment is
|
||||
// unsafe or not
|
||||
if (!header->mUnsafe && aProtect) Protect(segment);
|
||||
|
||||
return segment.forget();
|
||||
}
|
||||
|
||||
// static
|
||||
void Shmem::Dealloc(PrivateIPDLCaller, SharedMemory* aSegment) {
|
||||
if (!aSegment) return;
|
||||
|
||||
size_t pageSize = SharedMemory::SystemPageSize();
|
||||
Header* header;
|
||||
char* frontSentinel;
|
||||
char* data;
|
||||
char* backSentinel;
|
||||
GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel);
|
||||
|
||||
aSegment->Protect(frontSentinel, pageSize, RightsWrite | RightsRead);
|
||||
memset(header->mMagic, 0, sizeof(sMagic));
|
||||
header->mSize = 0;
|
||||
header->mUnsafe = false; // make it "safe" so as to catch errors
|
||||
|
||||
DestroySegment(aSegment);
|
||||
}
|
||||
|
||||
#else // !defined(DEBUG)
|
||||
|
||||
Shmem::Shmem(PrivateIPDLCaller, SharedMemory* aSegment, id_t aId)
|
||||
: mSegment(aSegment), mData(aSegment->memory()), mSize(0), mId(aId) {
|
||||
mSize = static_cast<size_t>(*PtrToSize(mSegment));
|
||||
MOZ_RELEASE_ASSERT(mSegment->Size() - sizeof(uint32_t) >= mSize,
|
||||
"illegal size in shared memory segment");
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(PrivateIPDLCaller,
|
||||
size_t aNBytes,
|
||||
SharedMemoryType aType,
|
||||
bool /*unused*/,
|
||||
bool /*unused*/) {
|
||||
RefPtr<SharedMemory> segment =
|
||||
CreateSegment(aType, aNBytes, sizeof(uint32_t));
|
||||
if (!segment) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*PtrToSize(segment) = static_cast<uint32_t>(aNBytes);
|
||||
|
||||
return segment.forget();
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting(
|
||||
PrivateIPDLCaller, const IPC::Message& aDescriptor, id_t* aId,
|
||||
bool /*unused*/) {
|
||||
size_t size;
|
||||
RefPtr<SharedMemory> segment =
|
||||
ReadSegment(aDescriptor, aId, &size, sizeof(uint32_t));
|
||||
if (!segment) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// this is the only validity check done in non-DEBUG builds
|
||||
if (size != static_cast<size_t>(*PtrToSize(segment))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return segment.forget();
|
||||
}
|
||||
|
||||
// static
|
||||
void Shmem::Dealloc(PrivateIPDLCaller, SharedMemory* aSegment) {
|
||||
DestroySegment(aSegment);
|
||||
}
|
||||
|
||||
#endif // if defined(DEBUG)
|
||||
|
||||
UniquePtr<IPC::Message> Shmem::MkCreatedMessage(PrivateIPDLCaller,
|
||||
int32_t routingId) {
|
||||
AssertInvariants();
|
||||
|
||||
auto msg = MakeUnique<ShmemCreated>(routingId, mId, mSize, mSegment->Type());
|
||||
IPC::MessageWriter writer(*msg);
|
||||
if (!mSegment->WriteHandle(&writer)) {
|
||||
return nullptr;
|
||||
}
|
||||
// close the handle to the segment after it is shared
|
||||
mSegment->CloseHandle();
|
||||
return msg;
|
||||
}
|
||||
|
||||
UniquePtr<IPC::Message> Shmem::MkDestroyedMessage(PrivateIPDLCaller,
|
||||
int32_t routingId) {
|
||||
AssertInvariants();
|
||||
return MakeUnique<ShmemDestroyed>(routingId, mId);
|
||||
}
|
||||
|
||||
void IPDLParamTraits<Shmem>::Write(IPC::MessageWriter* aWriter,
|
||||
IProtocol* aActor, Shmem&& aParam) {
|
||||
WriteIPDLParam(aWriter, aActor, aParam.mId);
|
||||
|
||||
aParam.RevokeRights(Shmem::PrivateIPDLCaller());
|
||||
aParam.forget(Shmem::PrivateIPDLCaller());
|
||||
}
|
||||
|
||||
bool IPDLParamTraits<Shmem>::Read(IPC::MessageReader* aReader,
|
||||
IProtocol* aActor, paramType* aResult) {
|
||||
paramType::id_t id;
|
||||
if (!ReadIPDLParam(aReader, aActor, &id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Shmem::SharedMemory* rawmem = aActor->LookupSharedMemory(id);
|
||||
if (rawmem) {
|
||||
*aResult = Shmem(Shmem::PrivateIPDLCaller(), rawmem, id);
|
||||
return true;
|
||||
}
|
||||
*aResult = Shmem();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
} // namespace mozilla
|
||||
|
|
181
ipc/glue/Shmem.h
181
ipc/glue/Shmem.h
|
@ -19,62 +19,91 @@
|
|||
#include "mozilla/Range.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
namespace IPC {
|
||||
template <typename T>
|
||||
struct ParamTraits;
|
||||
}
|
||||
/**
|
||||
* |Shmem| is one agent in the IPDL shared memory scheme. The way it
|
||||
works is essentially
|
||||
*
|
||||
* (1) C++ code calls, say, |parentActor->AllocShmem(size)|
|
||||
|
||||
namespace mozilla::ipc {
|
||||
* (2) IPDL-generated code creates a |mozilla::ipc::SharedMemory|
|
||||
* wrapping the bare OS shmem primitives. The code then adds the new
|
||||
* SharedMemory to the set of shmem segments being managed by IPDL.
|
||||
*
|
||||
* (3) IPDL-generated code "shares" the new SharedMemory to the child
|
||||
* process, and then sends a special asynchronous IPC message to the
|
||||
* child notifying it of the creation of the segment. (What this
|
||||
* means is OS specific.)
|
||||
*
|
||||
* (4a) The child receives the special IPC message, and using the
|
||||
* |SharedMemory{Basic}::Handle| it was passed, creates a
|
||||
* |mozilla::ipc::SharedMemory| in the child
|
||||
* process.
|
||||
*
|
||||
* (4b) After sending the "shmem-created" IPC message, IPDL-generated
|
||||
* code in the parent returns a |mozilla::ipc::Shmem| back to the C++
|
||||
* caller of |parentActor->AllocShmem()|. The |Shmem| is a "weak
|
||||
* reference" to the underlying |SharedMemory|, which is managed by
|
||||
* IPDL-generated code. C++ consumers of |Shmem| can't get at the
|
||||
* underlying |SharedMemory|.
|
||||
*
|
||||
* If parent code wants to give access rights to the Shmem to the
|
||||
* child, it does so by sending its |Shmem| to the child, in an IPDL
|
||||
* message. The parent's |Shmem| then "dies", i.e. becomes
|
||||
* inaccessible. This process could be compared to passing a
|
||||
* "shmem-access baton" between parent and child.
|
||||
*/
|
||||
|
||||
namespace mozilla {
|
||||
namespace layers {
|
||||
class ShadowLayerForwarder;
|
||||
} // namespace layers
|
||||
|
||||
namespace ipc {
|
||||
|
||||
template <typename P>
|
||||
struct IPDLParamTraits;
|
||||
|
||||
// A `Shmem` is a wrapper around OS-level shared memory. It comes in two major
|
||||
// modes: safe and unsafe.
|
||||
//
|
||||
// Access to the memory within a "safe" shmem is conceptually transferred to the
|
||||
// remote process when the shmem is transferred by marking the mapped memory in
|
||||
// the sending process as inaccessable (debug-mode only). Code should not
|
||||
// attempt to access the shmem after it has been transferred, though it will
|
||||
// remain mapped until all other Shmem references have been dropped.
|
||||
//
|
||||
// Access to the memory within an "unsafe" shmem is not protected in the same
|
||||
// way, and can be shared between multiple processes.
|
||||
//
|
||||
// For now, the primary way to create a `Shmem` is to call the
|
||||
// `IProtocol::AllocShmem` or `IProtocol::AllocUnsafeShmem` methods on an IPDL
|
||||
// actor, however this requirement may be relaxed in the future.
|
||||
class Shmem final {
|
||||
friend struct IPC::ParamTraits<mozilla::ipc::Shmem>;
|
||||
friend struct IPDLParamTraits<mozilla::ipc::Shmem>;
|
||||
#ifdef DEBUG
|
||||
// For ShadowLayerForwarder::CheckSurfaceDescriptor
|
||||
friend class mozilla::layers::ShadowLayerForwarder;
|
||||
#endif
|
||||
|
||||
public:
|
||||
typedef int32_t id_t;
|
||||
// Low-level wrapper around platform shmem primitives.
|
||||
typedef mozilla::ipc::SharedMemory SharedMemory;
|
||||
typedef SharedMemory::SharedMemoryType SharedMemoryType;
|
||||
// Shmem objects should only be constructed directly from SharedMemory
|
||||
// objects by the Shmem implementation itself, or by a select few functions
|
||||
// in ProtocolUtils.{h,cpp}. You should not need to add new instances of
|
||||
// this token.
|
||||
struct PrivateIPDLCaller {};
|
||||
|
||||
Shmem() = default;
|
||||
Shmem() : mSegment(nullptr), mData(nullptr), mSize(0), mId(0) {}
|
||||
|
||||
// Allocates a brand new shared memory region with sufficient size for
|
||||
// `aNBytes` bytes. This region may be transferred to other processes by being
|
||||
// sent over an IPDL actor.
|
||||
Shmem(size_t aNBytes, SharedMemoryType aType, bool aUnsafe);
|
||||
|
||||
// Create a reference to an existing shared memory region. The segment must be
|
||||
// large enough for `aNBytes`.
|
||||
Shmem(RefPtr<SharedMemory> aSegment, size_t aNBytes, bool aUnsafe);
|
||||
|
||||
// NOTE: Some callers are broken if a move constructor is provided here.
|
||||
Shmem(const Shmem& aOther) = default;
|
||||
|
||||
Shmem(PrivateIPDLCaller, SharedMemory* aSegment, id_t aId);
|
||||
|
||||
~Shmem() {
|
||||
// Shmem only holds a "weak ref" to the actual segment, which is
|
||||
// owned by IPDL. So there's nothing interesting to be done here
|
||||
forget(PrivateIPDLCaller());
|
||||
}
|
||||
|
||||
Shmem& operator=(const Shmem& aRhs) = default;
|
||||
|
||||
bool operator==(const Shmem& aRhs) const { return mSegment == aRhs.mSegment; }
|
||||
bool operator!=(const Shmem& aRhs) const { return mSegment != aRhs.mSegment; }
|
||||
|
||||
// Returns whether this Shmem is valid, and contains a reference to internal
|
||||
// state.
|
||||
bool IsValid() const { return mSegment != nullptr; }
|
||||
// Returns whether this Shmem is writable by you, and thus whether you can
|
||||
// transfer writability to another actor.
|
||||
bool IsWritable() const { return mSegment != nullptr; }
|
||||
|
||||
// Legacy names for `IsValid()` - do _NOT_ actually confirm whether or not you
|
||||
// should be able to write to or read from this type.
|
||||
bool IsWritable() const { return IsValid(); }
|
||||
bool IsReadable() const { return IsValid(); }
|
||||
// Returns whether this Shmem is readable by you, and thus whether you can
|
||||
// transfer readability to another actor.
|
||||
bool IsReadable() const { return mSegment != nullptr; }
|
||||
|
||||
// Return a pointer to the user-visible data segment.
|
||||
template <typename T>
|
||||
|
@ -102,36 +131,80 @@ class Shmem final {
|
|||
return {get<T>(), Size<T>()};
|
||||
}
|
||||
|
||||
// For safe shmems in debug mode, immediately revoke all access rights to the
|
||||
// memory when deallocating it.
|
||||
// Also resets this particular `Shmem` instance to an invalid state.
|
||||
// These shouldn't be used directly, use the IPDL interface instead.
|
||||
id_t Id(PrivateIPDLCaller) const { return mId; }
|
||||
|
||||
SharedMemory* Segment(PrivateIPDLCaller) const { return mSegment; }
|
||||
|
||||
#ifndef DEBUG
|
||||
void RevokeRights() { *this = Shmem(); }
|
||||
void RevokeRights(PrivateIPDLCaller) {}
|
||||
#else
|
||||
void RevokeRights();
|
||||
void RevokeRights(PrivateIPDLCaller);
|
||||
#endif
|
||||
|
||||
void forget(PrivateIPDLCaller) {
|
||||
mSegment = nullptr;
|
||||
mData = nullptr;
|
||||
mSize = 0;
|
||||
mId = 0;
|
||||
}
|
||||
|
||||
static already_AddRefed<Shmem::SharedMemory> Alloc(PrivateIPDLCaller,
|
||||
size_t aNBytes,
|
||||
SharedMemoryType aType,
|
||||
bool aUnsafe,
|
||||
bool aProtect = false);
|
||||
|
||||
// Prepare this to be shared with another process. Return an IPC message that
|
||||
// contains enough information for the other process to map this segment in
|
||||
// OpenExisting() below. Return a new message if successful (owned by the
|
||||
// caller), nullptr if not.
|
||||
UniquePtr<IPC::Message> MkCreatedMessage(PrivateIPDLCaller,
|
||||
int32_t routingId);
|
||||
|
||||
// Stop sharing this with another process. Return an IPC message that
|
||||
// contains enough information for the other process to unmap this
|
||||
// segment. Return a new message if successful (owned by the
|
||||
// caller), nullptr if not.
|
||||
UniquePtr<IPC::Message> MkDestroyedMessage(PrivateIPDLCaller,
|
||||
int32_t routingId);
|
||||
|
||||
// Return a SharedMemory instance in this process using the descriptor shared
|
||||
// to us by the process that created the underlying OS shmem resource. The
|
||||
// contents of the descriptor depend on the type of SharedMemory that was
|
||||
// passed to us.
|
||||
static already_AddRefed<SharedMemory> OpenExisting(
|
||||
PrivateIPDLCaller, const IPC::Message& aDescriptor, id_t* aId,
|
||||
bool aProtect = false);
|
||||
|
||||
static void Dealloc(PrivateIPDLCaller, SharedMemory* aSegment);
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void AssertAligned() const {
|
||||
MOZ_RELEASE_ASSERT(0 == (mSize % sizeof(T)),
|
||||
"shmem size is not a multiple of sizeof(T)");
|
||||
if (0 != (mSize % sizeof(T))) MOZ_CRASH("shmem is not T-aligned");
|
||||
}
|
||||
|
||||
#ifndef DEBUG
|
||||
#if !defined(DEBUG)
|
||||
void AssertInvariants() const {}
|
||||
|
||||
static uint32_t* PtrToSize(SharedMemory* aSegment) {
|
||||
char* endOfSegment =
|
||||
reinterpret_cast<char*>(aSegment->memory()) + aSegment->Size();
|
||||
return reinterpret_cast<uint32_t*>(endOfSegment - sizeof(uint32_t));
|
||||
}
|
||||
|
||||
#else
|
||||
void AssertInvariants() const;
|
||||
#endif
|
||||
|
||||
RefPtr<SharedMemory> mSegment;
|
||||
void* mData = nullptr;
|
||||
size_t mSize = 0;
|
||||
#ifdef DEBUG
|
||||
bool mUnsafe = false;
|
||||
#endif
|
||||
void* mData;
|
||||
size_t mSize;
|
||||
id_t mId;
|
||||
};
|
||||
|
||||
} // namespace mozilla::ipc
|
||||
} // namespace ipc
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // ifndef mozilla_ipc_Shmem_h
|
||||
|
|
|
@ -11,20 +11,24 @@
|
|||
#include "mozilla/ipc/IPDLParamTraits.h"
|
||||
#include "mozilla/ipc/Shmem.h"
|
||||
|
||||
namespace IPC {
|
||||
namespace mozilla {
|
||||
namespace ipc {
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::ipc::Shmem> {
|
||||
typedef mozilla::ipc::Shmem paramType;
|
||||
struct IPDLParamTraits<Shmem> {
|
||||
typedef Shmem paramType;
|
||||
|
||||
static void Write(IPC::MessageWriter* aWriter, paramType&& aParam);
|
||||
static bool Read(IPC::MessageReader* aReader, paramType* aResult);
|
||||
static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
|
||||
paramType&& aParam);
|
||||
static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
|
||||
paramType* aResult);
|
||||
|
||||
static void Log(const paramType& aParam, std::wstring* aLog) {
|
||||
aLog->append(L"(shmem segment)");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace IPC
|
||||
} // namespace ipc
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // ifndef mozilla_ipc_ShmemMessageUtils_h
|
||||
|
|
|
@ -4227,6 +4227,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
"""
|
||||
DestroySubtree(NormalShutdown);
|
||||
ClearSubtree();
|
||||
DeallocShmems();
|
||||
if (GetLifecycleProxy()) {
|
||||
GetLifecycleProxy()->Release();
|
||||
}
|
||||
|
@ -4242,6 +4243,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
"""
|
||||
DestroySubtree(AbnormalShutdown);
|
||||
ClearSubtree();
|
||||
DeallocShmems();
|
||||
if (GetLifecycleProxy()) {
|
||||
GetLifecycleProxy()->Release();
|
||||
}
|
||||
|
@ -4371,6 +4373,32 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
|
||||
methods = []
|
||||
|
||||
if p.decl.type.isToplevel():
|
||||
|
||||
# "private" message that passes shmem mappings from one process
|
||||
# to the other
|
||||
if p.subtreeUsesShmem():
|
||||
self.asyncSwitch.addcase(
|
||||
CaseLabel("SHMEM_CREATED_MESSAGE_TYPE"),
|
||||
self.genShmemCreatedHandler(),
|
||||
)
|
||||
self.asyncSwitch.addcase(
|
||||
CaseLabel("SHMEM_DESTROYED_MESSAGE_TYPE"),
|
||||
self.genShmemDestroyedHandler(),
|
||||
)
|
||||
else:
|
||||
abort = StmtBlock()
|
||||
abort.addstmts(
|
||||
[
|
||||
_fatalError("this protocol tree does not use shmem"),
|
||||
StmtReturn(_Result.NotKnown),
|
||||
]
|
||||
)
|
||||
self.asyncSwitch.addcase(CaseLabel("SHMEM_CREATED_MESSAGE_TYPE"), abort)
|
||||
self.asyncSwitch.addcase(
|
||||
CaseLabel("SHMEM_DESTROYED_MESSAGE_TYPE"), abort
|
||||
)
|
||||
|
||||
# Keep track of types created with an INOUT ctor. We need to call
|
||||
# Register() or RegisterID() for them depending on the side the managee
|
||||
# is created.
|
||||
|
@ -4489,6 +4517,36 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
|
||||
return methods + [removemanagee, deallocmanagee, Whitespace.NL]
|
||||
|
||||
def genShmemCreatedHandler(self):
|
||||
assert self.protocol.decl.type.isToplevel()
|
||||
|
||||
return StmtCode(
|
||||
"""
|
||||
{
|
||||
if (!ShmemCreated(${msgvar})) {
|
||||
return MsgPayloadError;
|
||||
}
|
||||
return MsgProcessed;
|
||||
}
|
||||
""",
|
||||
msgvar=self.msgvar,
|
||||
)
|
||||
|
||||
def genShmemDestroyedHandler(self):
|
||||
assert self.protocol.decl.type.isToplevel()
|
||||
|
||||
return StmtCode(
|
||||
"""
|
||||
{
|
||||
if (!ShmemDestroyed(${msgvar})) {
|
||||
return MsgPayloadError;
|
||||
}
|
||||
return MsgProcessed;
|
||||
}
|
||||
""",
|
||||
msgvar=self.msgvar,
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# The next few functions are the crux of the IPDL code generator.
|
||||
# They generate code for all the nasty work of message
|
||||
|
|
|
@ -8107,23 +8107,28 @@ bool nsLayoutUtils::GetContentViewerSize(
|
|||
bool nsLayoutUtils::UpdateCompositionBoundsForRCDRSF(
|
||||
ParentLayerRect& aCompBounds, const nsPresContext* aPresContext) {
|
||||
SubtractDynamicToolbar shouldSubtractDynamicToolbar =
|
||||
SubtractDynamicToolbar::Yes;
|
||||
aPresContext->IsRootContentDocumentCrossProcess() &&
|
||||
aPresContext->HasDynamicToolbar()
|
||||
? SubtractDynamicToolbar::Yes
|
||||
: SubtractDynamicToolbar::No;
|
||||
|
||||
if (RefPtr<MobileViewportManager> MVM =
|
||||
aPresContext->PresShell()->GetMobileViewportManager()) {
|
||||
CSSSize intrinsicCompositionSize = MVM->GetIntrinsicCompositionSize();
|
||||
if (shouldSubtractDynamicToolbar == SubtractDynamicToolbar::Yes) {
|
||||
if (RefPtr<MobileViewportManager> MVM =
|
||||
aPresContext->PresShell()->GetMobileViewportManager()) {
|
||||
CSSSize intrinsicCompositionSize = MVM->GetIntrinsicCompositionSize();
|
||||
|
||||
if (nsIScrollableFrame* rootScrollableFrame =
|
||||
aPresContext->PresShell()->GetRootScrollFrameAsScrollable()) {
|
||||
// Expand the composition size to include the area initially covered by
|
||||
// the dynamic toolbar only if the content is taller than the intrinsic
|
||||
// composition size (i.e. the dynamic toolbar should be able to move only
|
||||
// if the content is vertically scrollable).
|
||||
if (intrinsicCompositionSize.height <
|
||||
CSSPixel::FromAppUnits(
|
||||
CalculateScrollableRectForFrame(rootScrollableFrame, nullptr)
|
||||
.Height())) {
|
||||
shouldSubtractDynamicToolbar = SubtractDynamicToolbar::No;
|
||||
if (nsIScrollableFrame* rootScrollableFrame =
|
||||
aPresContext->PresShell()->GetRootScrollFrameAsScrollable()) {
|
||||
// Expand the composition size to include the area initially covered by
|
||||
// the dynamic toolbar only if the content is taller than the intrinsic
|
||||
// composition size (i.e. the dynamic toolbar should be able to move
|
||||
// only if the content is vertically scrollable).
|
||||
if (intrinsicCompositionSize.height <
|
||||
CSSPixel::FromAppUnits(
|
||||
CalculateScrollableRectForFrame(rootScrollableFrame, nullptr)
|
||||
.Height())) {
|
||||
shouldSubtractDynamicToolbar = SubtractDynamicToolbar::No;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -761,8 +761,10 @@ bool SVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams,
|
|||
ctx.SetDeviceColor(DeviceColor::MaskOpaqueWhite());
|
||||
RefPtr<Path> path = CSSClipPathInstance::CreateClipPathForFrame(
|
||||
ctx.GetDrawTarget(), frame, mat);
|
||||
ctx.SetPath(path);
|
||||
ctx.Fill();
|
||||
if (path) {
|
||||
ctx.SetPath(path);
|
||||
ctx.Fill();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -20,10 +20,11 @@ class GeckoViewPromptChild extends GeckoViewActorChild {
|
|||
debug`handleEvent: ${type}`;
|
||||
|
||||
switch (type) {
|
||||
case "mozshowdropdown": // fall-through
|
||||
case "mozshowdropdown-sourcetouch": // fall-through
|
||||
case "click": // fall-through
|
||||
case "contextmenu": // fall-through
|
||||
case "MozOpenDateTimePicker":
|
||||
case "mozshowdropdown":
|
||||
case "mozshowdropdown-sourcetouch":
|
||||
case "click":
|
||||
case "contextmenu":
|
||||
case "DOMPopupBlocked":
|
||||
Services.prompt.wrappedJSObject.handleEvent(event);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ class PromptFactory {
|
|||
case "mozshowdropdown-sourcetouch":
|
||||
this._handleSelect(aEvent.composedTarget, /* aIsDropDown = */ true);
|
||||
break;
|
||||
case "MozOpenDateTimePicker":
|
||||
this._handleDateTime(aEvent.composedTarget);
|
||||
break;
|
||||
case "click":
|
||||
this._handleClick(aEvent);
|
||||
break;
|
||||
|
@ -80,7 +83,7 @@ class PromptFactory {
|
|||
type === "time" ||
|
||||
type === "datetime-local"
|
||||
) {
|
||||
this._handleDateTime(target, type);
|
||||
this._handleDateTime(target);
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
}
|
||||
|
@ -179,12 +182,12 @@ class PromptFactory {
|
|||
);
|
||||
}
|
||||
|
||||
_handleDateTime(aElement, aType) {
|
||||
_handleDateTime(aElement) {
|
||||
const prompt = new GeckoViewPrompter(aElement.ownerGlobal);
|
||||
prompt.asyncShowPrompt(
|
||||
{
|
||||
type: "datetime",
|
||||
mode: aType,
|
||||
mode: aElement.type,
|
||||
value: aElement.value,
|
||||
min: aElement.min,
|
||||
max: aElement.max,
|
||||
|
|
|
@ -44,6 +44,7 @@ const JSWINDOWACTORS = {
|
|||
contextmenu: { capture: false, mozSystemGroup: true },
|
||||
mozshowdropdown: {},
|
||||
"mozshowdropdown-sourcetouch": {},
|
||||
MozOpenDateTimePicker: {},
|
||||
DOMPopupBlocked: { capture: false, mozSystemGroup: true },
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1337,12 +1337,12 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
// TODO: Bug 1758540
|
||||
assumeThat(sessionRule.env.isFission, equalTo(false))
|
||||
|
||||
testAccessibilityFocusIframe(REMOTE_IFRAME);
|
||||
testIframeTree(REMOTE_IFRAME);
|
||||
}
|
||||
|
||||
@Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
|
||||
@Test fun testLocalIframeTree() {
|
||||
testAccessibilityFocusIframe(LOCAL_IFRAME);
|
||||
testIframeTree(LOCAL_IFRAME);
|
||||
}
|
||||
|
||||
@Setting(key = Setting.Key.FULL_ACCESSIBILITY_TREE, value = "true")
|
||||
|
|
|
@ -489,21 +489,21 @@ class PromptDelegateTest : BaseSessionTest() {
|
|||
equalTo("#123456"))
|
||||
}
|
||||
|
||||
@Ignore // TODO: Figure out weird test env behavior here.
|
||||
@WithDisplay(width = 100, height = 100)
|
||||
@Test fun dateTest() {
|
||||
sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
|
||||
|
||||
mainSession.loadTestPath(PROMPT_HTML_PATH)
|
||||
mainSession.waitForPageStop()
|
||||
|
||||
mainSession.evaluateJS("document.getElementById('dateexample').click();")
|
||||
mainSession.synthesizeTap(1, 1) // Provides user activation.
|
||||
|
||||
sessionRule.waitUntilCalled(object : PromptDelegate {
|
||||
sessionRule.delegateDuringNextWait(object : PromptDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onDateTimePrompt(session: GeckoSession, prompt: PromptDelegate.DateTimePrompt): GeckoResult<PromptDelegate.PromptResponse> {
|
||||
return GeckoResult.fromValue(prompt.dismiss())
|
||||
}
|
||||
})
|
||||
|
||||
mainSession.waitForJS("document.getElementById('dateexample').showPicker();")
|
||||
}
|
||||
|
||||
@Test fun fileTest() {
|
||||
|
|
|
@ -2670,6 +2670,12 @@
|
|||
value: true
|
||||
mirror: always
|
||||
|
||||
# Is support for HTMLInputElement.showPicker enabled?
|
||||
- name: dom.input.showPicker
|
||||
type: bool
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# Whether to allow or disallow web apps to cancel `beforeinput` events caused
|
||||
# by MozEditableElement#setUserInput() which is used by autocomplete, autofill
|
||||
# and password manager.
|
||||
|
|
|
@ -740,78 +740,6 @@ prefs: [dom.security.featurePolicy.experimental.enabled:true, dom.security.featu
|
|||
[HTMLMetaElement interface: document.createElement("meta") must inherit property "media" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: operation showPicker()]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: document.createElement("input") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("text") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("hidden") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("search") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("tel") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("url") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("email") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("password") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("date") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("month") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("week") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("time") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("datetime-local") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("number") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("range") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("color") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("checkbox") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("radio") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("file") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("submit") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("image") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("reset") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLInputElement interface: createInput("button") must inherit property "showPicker()" with the proper type]
|
||||
expected: FAIL
|
||||
|
||||
[HTMLLinkElement interface: attribute blocking]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
[show-picker-cross-origin-iframe.html]
|
||||
[Test showPicker() called from cross-origin iframe 1]
|
||||
expected: FAIL
|
||||
|
||||
[Test showPicker() called from cross-origin iframe 3]
|
||||
expected: FAIL
|
||||
disabled:
|
||||
if tsan: https://bugzilla.mozilla.org/show_bug.cgi?id=1745005
|
||||
if asan: https://bugzilla.mozilla.org/show_bug.cgi?id=1745005
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
[show-picker-disabled-readonly.html]
|
||||
[input[type=button\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=checkbox\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=color\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=date\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=datetime-local\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=email\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=file\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=hidden\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=image\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=month\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=number\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=password\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=radio\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=range\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=reset\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=search\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=submit\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=tel\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=text\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=time\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=url\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=week\] showPicker() throws when disabled]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=button\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=checkbox\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=color\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=date\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=datetime-local\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=email\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=file\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=hidden\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=image\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=month\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=number\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=password\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=radio\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=range\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=reset\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=search\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=submit\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=tel\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=text\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=time\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=url\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=week\] showPicker() throws when readonly]
|
||||
expected: FAIL
|
|
@ -1,132 +1,4 @@
|
|||
[show-picker-user-gesture.html]
|
||||
[input[type=button\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=checkbox\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=color\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=date\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=datetime-local\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=email\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=file\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=hidden\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=image\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=month\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=number\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=password\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=radio\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=range\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=reset\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=search\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=submit\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=tel\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=text\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=time\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=url\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=week\] showPicker() requires a user gesture]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=button\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=checkbox\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=color\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=date\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=datetime-local\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=email\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=file\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=hidden\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=image\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=month\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=number\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=password\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=radio\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=range\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=reset\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=search\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=submit\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=tel\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=text\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=time\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=url\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
|
||||
[input[type=week\] showPicker() does not throw when user activation is active]
|
||||
expected: FAIL
|
||||
disabled:
|
||||
if tsan: https://bugzilla.mozilla.org/show_bug.cgi?id=1745005
|
||||
if asan: https://bugzilla.mozilla.org/show_bug.cgi?id=1745005
|
||||
|
|
|
@ -18,13 +18,26 @@ for (const inputType of inputTypes) {
|
|||
}, `input[type=${inputType}] showPicker() throws when disabled`);
|
||||
}
|
||||
|
||||
const noReadonlySupport = ['button', 'checkbox', 'color', 'file',
|
||||
'hidden', 'image', 'radio', 'range', 'reset', 'submit'];
|
||||
for (const inputType of inputTypes) {
|
||||
test(() => {
|
||||
const input = document.createElement("input");
|
||||
input.setAttribute("type", inputType);
|
||||
input.setAttribute("readonly", "");
|
||||
if (!noReadonlySupport.includes(inputType)) {
|
||||
test(() => {
|
||||
const input = document.createElement("input");
|
||||
input.setAttribute("type", inputType);
|
||||
input.setAttribute("readonly", "");
|
||||
|
||||
assert_throws_dom('InvalidStateError', () => { input.showPicker(); });
|
||||
}, `input[type=${inputType}] showPicker() throws when readonly`);
|
||||
assert_throws_dom('InvalidStateError', () => { input.showPicker(); });
|
||||
}, `input[type=${inputType}] showPicker() throws when readonly`);
|
||||
} else {
|
||||
test(() => {
|
||||
const input = document.createElement("input");
|
||||
input.setAttribute("type", inputType);
|
||||
input.setAttribute("readonly", "");
|
||||
|
||||
// Missing user gesture activation throws.
|
||||
assert_throws_dom('NotAllowedError', () => { input.showPicker(); });
|
||||
}, `input[type=${inputType}] showPicker() doesn't throw when readonly`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
// META: title=Scheduler: scheduler should be replaceable
|
||||
// META: global=window,worker
|
||||
'use strict';
|
||||
|
||||
test(() => {
|
||||
class Scheduler {
|
||||
constructor() {
|
||||
scheduler = this;
|
||||
}
|
||||
}
|
||||
new Scheduler();
|
||||
}, 'Tests replacing window.scheduler with a different object');
|
|
@ -0,0 +1 @@
|
|||
{"files":{"Cargo.toml":"b4bfbe31191f3e86445d277cf036bc7b2318baf853e8b2b0c7fcca5d61f7a089","LICENSE-APACHE":"c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08","LICENSE-MIT":"bf04ecfb8f9aec247301556319593dd528886f67bb9ad81654025d12b20d9e01","README.md":"fb45c72e0317c713589e90b028324c0b7320b6eb7aebbbefd31ade38aaf7e1e2","rustfmt.toml":"785022765c76126d4a9954f880c30cc651b3895857ffcfed55ce8a4f8cf3f61a","src/lib.rs":"aaa8b5ff915f1c6e3132bef12afacede4fc91216fe0711efbd1ead7ab106e82e"},"package":"aa7c7f42dea4b1b99439786f5633aeb9c14c1b53f75e282803c2ec2ad545873c"}
|
|
@ -0,0 +1,21 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g. crates.io) dependencies
|
||||
#
|
||||
# If you believe there's an error in this file please file an
|
||||
# issue against the rust-lang/cargo repository. If you're
|
||||
# editing this file be aware that the upstream Cargo.toml
|
||||
# will likely look very different (and much more reasonable)
|
||||
|
||||
[package]
|
||||
name = "topological-sort"
|
||||
version = "0.1.0"
|
||||
authors = ["gifnksm <makoto.nksm+github@gmail.com>"]
|
||||
description = "Performs topological sorting."
|
||||
documentation = "https://docs.rs/topological-sort/~0.0"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/gifnksm/topological-sort-rs"
|
|
@ -0,0 +1,202 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2015 The topological-sort-rs Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,40 @@
|
|||
# topological-sort-rs
|
||||
|
||||
[![Build Status](https://travis-ci.org/gifnksm/topological-sort-rs.svg)](https://travis-ci.org/gifnksm/topological-sort-rs)
|
||||
[![Coverage Status](https://coveralls.io/repos/gifnksm/topological-sort-rs/badge.svg?branch=master&service=github)](https://coveralls.io/github/gifnksm/topological-sort-rs?branch=master)
|
||||
[![crates.io](http://meritbadge.herokuapp.com/topological-sort)](https://crates.io/crates/topological-sort)
|
||||
|
||||
Performs topological sorting.
|
||||
|
||||
[Documentation](https://docs.rs/topological-sort/~0.0)
|
||||
|
||||
## How to use?
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
topological-sort = "0.0"
|
||||
```
|
||||
|
||||
and this to your crate root:
|
||||
|
||||
```rust
|
||||
extern crate topological_sort;
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally
|
||||
submitted for inclusion in the work by you, as defined in the Apache-2.0
|
||||
license, shall be dual licensed as above, without any additional terms or
|
||||
conditions.
|
|
@ -0,0 +1,2 @@
|
|||
reorder_imports = true
|
||||
reorder_imported_names = true
|
|
@ -0,0 +1,384 @@
|
|||
// Copyright 2016 oauth-client-rs Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
|
||||
//! Performs topological sorting.
|
||||
|
||||
#![warn(bad_style)]
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(trivial_casts)]
|
||||
#![warn(trivial_numeric_casts)]
|
||||
#![warn(unused)]
|
||||
#![warn(unused_extern_crates)]
|
||||
#![warn(unused_import_braces)]
|
||||
#![warn(unused_qualifications)]
|
||||
#![warn(unused_results)]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(if_not_else))]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(invalid_upcast_comparisons))]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(items_after_statements))]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(mut_mut))]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(never_loop))]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(nonminimal_bool))]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(option_map_unwrap_or))]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(option_map_unwrap_or_else))]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(option_unwrap_used))]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(result_unwrap_used))]
|
||||
#![cfg_attr(feature = "cargo-clippy", warn(used_underscore_binding))]
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Dependency<T> {
|
||||
num_prec: usize,
|
||||
succ: HashSet<T>,
|
||||
}
|
||||
|
||||
impl<T: Hash + Eq> Dependency<T> {
|
||||
fn new() -> Dependency<T> {
|
||||
Dependency {
|
||||
num_prec: 0,
|
||||
succ: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Performs topological sorting.
|
||||
#[derive(Clone)]
|
||||
pub struct TopologicalSort<T> {
|
||||
top: HashMap<T, Dependency<T>>,
|
||||
}
|
||||
|
||||
impl<T: Hash + Eq + Clone> TopologicalSort<T> {
|
||||
/// Creates new empty `TopologicalSort`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate topological_sort;
|
||||
/// # fn main() {
|
||||
/// use topological_sort::TopologicalSort;
|
||||
/// let mut ts = TopologicalSort::<&str>::new();
|
||||
/// ts.add_dependency("hello_world.o", "hello_world");
|
||||
/// ts.add_dependency("hello_world.c", "hello_world");
|
||||
/// ts.add_dependency("stdio.h", "hello_world.o");
|
||||
/// ts.add_dependency("glibc.so", "hello_world");
|
||||
/// assert_eq!(vec!["glibc.so", "hello_world.c", "stdio.h"],
|
||||
/// { let mut v = ts.pop_all(); v.sort(); v });
|
||||
/// assert_eq!(vec!["hello_world.o"],
|
||||
/// { let mut v = ts.pop_all(); v.sort(); v });
|
||||
/// assert_eq!(vec!["hello_world"],
|
||||
/// { let mut v = ts.pop_all(); v.sort(); v });
|
||||
/// assert!(ts.pop_all().is_empty());
|
||||
/// # }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn new() -> TopologicalSort<T> {
|
||||
TopologicalSort {
|
||||
top: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of elements in the `TopologicalSort`.
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.top.len()
|
||||
}
|
||||
|
||||
/// Returns true if the `TopologicalSort` contains no elements.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.top.is_empty()
|
||||
}
|
||||
|
||||
/// Registers the two elements' dependency.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `prec` - The element appears before `succ`. `prec` is depended on by `succ`.
|
||||
/// * `succ` - The element appears after `prec`. `succ` depends on `prec`.
|
||||
pub fn add_dependency<P, S>(&mut self, prec: P, succ: S)
|
||||
where
|
||||
P: Into<T>,
|
||||
S: Into<T>,
|
||||
{
|
||||
self.add_dependency_impl(prec.into(), succ.into())
|
||||
}
|
||||
|
||||
fn add_dependency_impl(&mut self, prec: T, succ: T) {
|
||||
match self.top.entry(prec) {
|
||||
Entry::Vacant(e) => {
|
||||
let mut dep = Dependency::new();
|
||||
let _ = dep.succ.insert(succ.clone());
|
||||
let _ = e.insert(dep);
|
||||
}
|
||||
Entry::Occupied(e) => {
|
||||
if !e.into_mut().succ.insert(succ.clone()) {
|
||||
// Already registered
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.top.entry(succ) {
|
||||
Entry::Vacant(e) => {
|
||||
let mut dep = Dependency::new();
|
||||
dep.num_prec += 1;
|
||||
let _ = e.insert(dep);
|
||||
}
|
||||
Entry::Occupied(e) => {
|
||||
e.into_mut().num_prec += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a dependency link.
|
||||
pub fn add_link(&mut self, link: DependencyLink<T>) {
|
||||
self.add_dependency(link.prec, link.succ)
|
||||
}
|
||||
|
||||
/// Inserts an element, without adding any dependencies from or to it.
|
||||
///
|
||||
/// If the `TopologicalSort` did not have this element present, `true` is returned.
|
||||
///
|
||||
/// If the `TopologicalSort` already had this element present, `false` is returned.
|
||||
pub fn insert<U>(&mut self, elt: U) -> bool
|
||||
where
|
||||
U: Into<T>,
|
||||
{
|
||||
match self.top.entry(elt.into()) {
|
||||
Entry::Vacant(e) => {
|
||||
let dep = Dependency::new();
|
||||
let _ = e.insert(dep);
|
||||
true
|
||||
}
|
||||
Entry::Occupied(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the item that is not depended on by any other items and returns it, or `None` if
|
||||
/// there is no such item.
|
||||
///
|
||||
/// If `pop` returns `None` and `len` is not 0, there is cyclic dependencies.
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
self.peek().map(T::clone).map(|key| {
|
||||
let _ = self.remove(&key);
|
||||
key
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/// Removes all items that are not depended on by any other items and returns it, or empty
|
||||
/// vector if there are no such items.
|
||||
///
|
||||
/// If `pop_all` returns an empty vector and `len` is not 0, there is cyclic dependencies.
|
||||
pub fn pop_all(&mut self) -> Vec<T> {
|
||||
let keys = self.top
|
||||
.iter()
|
||||
.filter(|&(_, v)| v.num_prec == 0)
|
||||
.map(|(k, _)| k.clone())
|
||||
.collect::<Vec<_>>();
|
||||
for k in &keys {
|
||||
let _ = self.remove(k);
|
||||
}
|
||||
keys
|
||||
}
|
||||
|
||||
/// Return a reference to the first item that does not depend on any other items, or `None` if
|
||||
/// there is no such item.
|
||||
pub fn peek(&self) -> Option<&T> {
|
||||
self.top
|
||||
.iter()
|
||||
.filter(|&(_, v)| v.num_prec == 0)
|
||||
.map(|(k, _)| k)
|
||||
.next()
|
||||
}
|
||||
|
||||
/// Return a vector of references to all items that do not depend on any other items, or an
|
||||
/// empty vector if there are no such items.
|
||||
pub fn peek_all(&self) -> Vec<&T> {
|
||||
self.top
|
||||
.iter()
|
||||
.filter(|&(_, v)| v.num_prec == 0)
|
||||
.map(|(k, _)| k)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
|
||||
fn remove(&mut self, prec: &T) -> Option<Dependency<T>> {
|
||||
let result = self.top.remove(prec);
|
||||
if let Some(ref p) = result {
|
||||
for s in &p.succ {
|
||||
if let Some(y) = self.top.get_mut(s) {
|
||||
y.num_prec -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialOrd + Eq + Hash + Clone> FromIterator<T> for TopologicalSort<T> {
|
||||
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> TopologicalSort<T> {
|
||||
let mut top = TopologicalSort::new();
|
||||
let mut seen = Vec::<T>::default();
|
||||
for item in iter {
|
||||
let _ = top.insert(item.clone());
|
||||
for seen_item in seen.iter().cloned() {
|
||||
match seen_item.partial_cmp(&item) {
|
||||
Some(Ordering::Less) => {
|
||||
top.add_dependency(seen_item, item.clone());
|
||||
}
|
||||
Some(Ordering::Greater) => {
|
||||
top.add_dependency(item.clone(), seen_item);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
seen.push(item);
|
||||
}
|
||||
top
|
||||
}
|
||||
}
|
||||
|
||||
/// A link between two items in a sort.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct DependencyLink<T> {
|
||||
/// The element which is depened upon by `succ`.
|
||||
pub prec: T,
|
||||
/// The element which depends on `prec`.
|
||||
pub succ: T,
|
||||
}
|
||||
|
||||
impl<T> From<(T, T)> for DependencyLink<T> {
|
||||
fn from(tuple: (T, T)) -> Self {
|
||||
DependencyLink {
|
||||
succ: tuple.0,
|
||||
prec: tuple.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash + Clone> FromIterator<DependencyLink<T>> for TopologicalSort<T> {
|
||||
fn from_iter<I: IntoIterator<Item = DependencyLink<T>>>(iter: I) -> TopologicalSort<T> {
|
||||
let mut top = TopologicalSort::new();
|
||||
for link in iter {
|
||||
top.add_link(link);
|
||||
}
|
||||
top
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash + Eq + Clone> Iterator for TopologicalSort<T> {
|
||||
type Item = T;
|
||||
|
||||
fn next(&mut self) -> Option<T> {
|
||||
self.pop()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug + Hash + Eq> fmt::Debug for Dependency<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "prec={}, succ={:?}", self.num_prec, self.succ)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug + Hash + Eq + Clone> fmt::Debug for TopologicalSort<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.top)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::TopologicalSort;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
#[test]
|
||||
fn from_iter() {
|
||||
let t = vec![4, 3, 3, 5, 7, 6, 8];
|
||||
let mut ts = TopologicalSort::<i32>::from_iter(t);
|
||||
assert_eq!(Some(3), ts.next());
|
||||
assert_eq!(Some(4), ts.next());
|
||||
assert_eq!(Some(5), ts.next());
|
||||
assert_eq!(Some(6), ts.next());
|
||||
assert_eq!(Some(7), ts.next());
|
||||
assert_eq!(Some(8), ts.next());
|
||||
assert_eq!(None, ts.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
let mut ts = TopologicalSort::<i32>::new();
|
||||
ts.add_dependency(1, 2);
|
||||
ts.add_dependency(2, 3);
|
||||
ts.add_dependency(3, 4);
|
||||
assert_eq!(Some(1), ts.next());
|
||||
assert_eq!(Some(2), ts.next());
|
||||
assert_eq!(Some(3), ts.next());
|
||||
assert_eq!(Some(4), ts.next());
|
||||
assert_eq!(None, ts.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pop_all() {
|
||||
fn check(result: &[i32], ts: &mut TopologicalSort<i32>) {
|
||||
let l = ts.len();
|
||||
let mut v = ts.pop_all();
|
||||
v.sort();
|
||||
assert_eq!(result, &v[..]);
|
||||
assert_eq!(l - result.len(), ts.len());
|
||||
}
|
||||
|
||||
let mut ts = TopologicalSort::new();
|
||||
ts.add_dependency(7, 11);
|
||||
assert_eq!(2, ts.len());
|
||||
ts.add_dependency(7, 8);
|
||||
assert_eq!(3, ts.len());
|
||||
ts.add_dependency(5, 11);
|
||||
assert_eq!(4, ts.len());
|
||||
ts.add_dependency(3, 8);
|
||||
assert_eq!(5, ts.len());
|
||||
ts.add_dependency(3, 10);
|
||||
assert_eq!(6, ts.len());
|
||||
ts.add_dependency(11, 2);
|
||||
assert_eq!(7, ts.len());
|
||||
ts.add_dependency(11, 9);
|
||||
assert_eq!(8, ts.len());
|
||||
ts.add_dependency(11, 10);
|
||||
assert_eq!(8, ts.len());
|
||||
ts.add_dependency(8, 9);
|
||||
assert_eq!(8, ts.len());
|
||||
|
||||
check(&[3, 5, 7], &mut ts);
|
||||
check(&[8, 11], &mut ts);
|
||||
check(&[2, 9, 10], &mut ts);
|
||||
check(&[], &mut ts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cyclic_deadlock() {
|
||||
let mut ts = TopologicalSort::new();
|
||||
ts.add_dependency("stone", "sharp");
|
||||
|
||||
ts.add_dependency("bucket", "hole");
|
||||
ts.add_dependency("hole", "straw");
|
||||
ts.add_dependency("straw", "axe");
|
||||
ts.add_dependency("axe", "sharp");
|
||||
ts.add_dependency("sharp", "water");
|
||||
ts.add_dependency("water", "bucket");
|
||||
assert_eq!(ts.pop(), Some("stone"));
|
||||
assert!(ts.pop().is_none());
|
||||
println!("{:?}", ts);
|
||||
}
|
||||
}
|
|
@ -2235,8 +2235,7 @@ class PictureInPictureChild extends JSWindowActorChild {
|
|||
*
|
||||
* - The "site wrapper" script must export a class called "PictureInPictureVideoWrapper"
|
||||
* - Method names on a site wrapper class should match its caller's name
|
||||
* (i.e: PictureInPictureChildVideoWrapper.play will only call `play` on a site-wrapper,
|
||||
* if available)
|
||||
* (i.e: PictureInPictureChildVideoWrapper.play will only call `play` on a site-wrapper, if available)
|
||||
*/
|
||||
class PictureInPictureChildVideoWrapper {
|
||||
#sandbox;
|
||||
|
@ -2252,12 +2251,14 @@ class PictureInPictureChildVideoWrapper {
|
|||
* commanding the original <video>.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The original <video> we want to create a wrapper class for.
|
||||
* @param {Object} pipChild
|
||||
* Reference to PictureInPictureChild class calling this function.
|
||||
*/
|
||||
constructor(videoWrapperScriptPath, video, piPChild) {
|
||||
constructor(videoWrapperScriptPath, video, pipChild) {
|
||||
this.#sandbox = videoWrapperScriptPath
|
||||
? this.#createSandbox(videoWrapperScriptPath, video)
|
||||
: null;
|
||||
this.#PictureInPictureChild = piPChild;
|
||||
this.#PictureInPictureChild = pipChild;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2278,7 +2279,6 @@ class PictureInPictureChildVideoWrapper {
|
|||
* return null.
|
||||
*
|
||||
* @returns The expected output of the wrapper function.
|
||||
*
|
||||
*/
|
||||
#callWrapperMethod({ name, args = [], fallback = () => {}, validateRetVal }) {
|
||||
try {
|
||||
|
@ -2362,6 +2362,9 @@ class PictureInPictureChildVideoWrapper {
|
|||
return typeof val === "number";
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the sandbox for the site wrapper class
|
||||
*/
|
||||
destroy() {
|
||||
if (this.#sandbox) {
|
||||
Cu.nukeSandbox(this.#sandbox);
|
||||
|
@ -2381,6 +2384,13 @@ class PictureInPictureChildVideoWrapper {
|
|||
|
||||
/* Video methods to be used for video controls from the PiP window. */
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the play() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to handle video
|
||||
* behaviour when a video is played.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
*/
|
||||
play(video) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "play",
|
||||
|
@ -2390,6 +2400,13 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the pause() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to handle video
|
||||
* behaviour when a video is paused.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
*/
|
||||
pause(video) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "pause",
|
||||
|
@ -2399,6 +2416,14 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the getPaused() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to determine if
|
||||
* a video is paused or not.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
* @returns {Boolean} Boolean value true if paused, or false if video is still playing
|
||||
*/
|
||||
getPaused(video) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "getPaused",
|
||||
|
@ -2408,6 +2433,14 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the getEnded() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to determine if
|
||||
* video playback or streaming has stopped.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
* @returns {Boolean} Boolean value true if the video has ended, or false if still playing
|
||||
*/
|
||||
getEnded(video) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "getEnded",
|
||||
|
@ -2417,6 +2450,14 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the getDuration() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to get the current
|
||||
* duration of a video in seconds.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
* @returns {Number} Duration of the video in seconds
|
||||
*/
|
||||
getDuration(video) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "getDuration",
|
||||
|
@ -2426,6 +2467,14 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the getCurrentTime() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to get the current
|
||||
* time of a video in seconds.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
* @returns {Number} Current time of the video in seconds
|
||||
*/
|
||||
getCurrentTime(video) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "getCurrentTime",
|
||||
|
@ -2435,6 +2484,15 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the setCurrentTime() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to set the current
|
||||
* time of a video.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
* @param {Number} position
|
||||
* The current playback time of the video
|
||||
*/
|
||||
setCurrentTime(video, position) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "setCurrentTime",
|
||||
|
@ -2446,6 +2504,14 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the getVolume() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to get the volume
|
||||
* value of a video.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
* @returns {Number} Volume of the video between 0 (muted) and 1 (loudest)
|
||||
*/
|
||||
getVolume(video) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "getVolume",
|
||||
|
@ -2455,6 +2521,15 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the setVolume() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to set the volume
|
||||
* value of a video.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
* @param {Number} volume
|
||||
* Value between 0 (muted) and 1 (loudest)
|
||||
*/
|
||||
setVolume(video, volume) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "setVolume",
|
||||
|
@ -2466,6 +2541,15 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the setMuted() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to mute or unmute
|
||||
* a video.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
* @param {Boolean} shouldMute
|
||||
* Boolean value true to mute the video, or false to unmute the video
|
||||
*/
|
||||
setMuted(video, shouldMute) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "setMuted",
|
||||
|
@ -2477,7 +2561,17 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
setCaptionContainerObserver(video) {
|
||||
/**
|
||||
* OVERRIDABLE - calls the setCaptionContainerObserver() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to listen for any cue changes in a
|
||||
* video's caption container and execute a callback function responsible for updating the pip window's text tracks container whenever
|
||||
* a cue change is triggered {@see updatePiPTextTracks()}.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
* @param {Function} callback
|
||||
* The callback function to be executed when cue changes are detected
|
||||
*/
|
||||
setCaptionContainerObserver(video, callback) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "setCaptionContainerObserver",
|
||||
args: [
|
||||
|
@ -2491,6 +2585,14 @@ class PictureInPictureChildVideoWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDABLE - calls the shouldHideToggle() method defined in the site wrapper script. Runs a fallback implementation
|
||||
* if the method does not exist or if an error is thrown while calling it. This method is meant to determine if the pip toggle
|
||||
* for a video should be hidden by the site wrapper.
|
||||
* @param {HTMLVideoElement} video
|
||||
* The originating video source element
|
||||
* @returns {Boolean} Boolean value true if the pip toggle should be hidden by the site wrapper, or false if it should not
|
||||
*/
|
||||
shouldHideToggle(video) {
|
||||
return this.#callWrapperMethod({
|
||||
name: "shouldHideToggle",
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче